完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
2个回答
|
|
DMA—直接存储区访问实验例程
本章节为DMA直接存储区访问的实验例程讲解,以“正点原子”的例程为基础进行讲解,如有不足之处还恳请各位大佬不吝赐教。 1. DMA简介 DMA的详细介绍已经在上一讲中进行过详细的介绍:《STM32从零开始学习历程》——DMA直接存储区访问理论知识 2. 本实验历程实现功能介绍 根据《STM32从零开始学习历程》——DMA直接存储区访问理论知识的详细介绍,我们可以知道DMA是一种可以不通过CPU的直接进行数据传输的控制器。本例程主要功能为使用DMA串口通讯将一定量的数据发送出去,使用串口助手接收发送到的数据。程序功能要点如下: (1). 通过DMA将数据发送到USART1,使用串口助手接收数据。 (2). 使用一个按键控制DMA发送,按下按钮就进行一次DMA数据发送操作。 (3). LCD屏幕显示发送状态与发送进度。(LCD的讲解将在后续blog中讲解,本文只要会用就行) 3. 实验准备 软件:Keil μVision5 v5.33(MDK5),串口助手XCOM V2.6 环境:Windows10 Enterprise x64 芯片:STM32F406ZGT6 设备:正点原子STM32F4探索者开发板,正点原子4.3寸 TFTLCD屏 仿真器:ST-Link 4. 硬件设计 本实验中需要用到USART1,所以我们需要将USART1的TX与RX引脚与相应的GPIO引脚相连接,此处我们使用PB6/PB7引脚进行通讯,关于USART通讯串口的配置与选择问题可以看:《STM32从零开始学习历程》——USART串口通讯实验篇1——中断接收与发送,此处就不多赘述。 硬件连接呢我们还是通过使用杜邦线将USART TX/RX与PB6/PB7向连接,同时将USART1串口连接至电脑。 5. 程序设计流程 1.DMA配置程序过程 ①使能DMA时钟 RCC_AHB1PeriphClockCmd(); ② 初始化DMA通道参数 DMA_Init(); ③使能串口DMA发送,串口DMA使能函数: USART_DMACmd(); ④查询DMA的EN位,确保数据流就绪,可以配置 DMA_GetCmdStatus(); ⑤设置通道当前剩余数据量 DMA_SetCurrDataCounter(); ⑥使能DMA1通道,启动传输。 DMA_Cmd(); ⑤查询DMA传输状态 DMA_GetFlagStatus(); ⑥获取/设置通道当前剩余数据量: DMA_GetCurrDataCounter(); 2. 相关函数介绍 1)使能 DMA2 时钟,并等待数据流可配置 。 DMA的时钟使能是通过 AHB1ENR 寄存器来控制的,这里我们要先使能时钟,才可以配置 DMA相关寄存器。所以先要使能 DMA2 的时钟。另外,要对配置寄存器( DMA_SxCR )进行设置,必须先等待其最低位为 0 (也就是 DMA 传输禁止了),才可以进行配置。 库函数使能DMA2 时钟的方法为: RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE); //DMA2 时钟使能等待 DMA 可配置,也就是等待 DMA_SxCR 寄存器最低位为 0 的方法为: while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE) { } // 等待 DMA 可配置 2)初始化 DMA2 数据流 7 ,包括配置通道,外设地址,存储器地址,传输数据量等 。 DMA的某个数据流各种配置参数初始化是通过 DMA_Init 函数实现的: void DMA_Init(DMA_Stream_TypeDef* DMAy_Streamx, DMA_InitTypeDef* DMA_InitStruct) 函数的第一个参数是指定初始化的 DMA 的数据流编号,这个很容易理解。入口参数范围为: DMA x _Stream0 DMA x _Stream 7(x=1,2) 。 下面我们主要看看第二个参数。跟其他外设一样,同样是通过初始化结构体成员变量值来达到初始化的目的,下面我们来看看 DMA_InitTypeDef结构体的定义: typedef struct { uint32_t DMA_Channel; uint32_t DMA_PeripheralBaseAddr; uint32_t DMA_Memory0BaseAddr; uint32_t DMA_DIR; uint32_t DMA_BufferSize; uint32_t DMA_PeripheralInc; uint32_t DMA_MemoryInc; uint32_t DMA_Pe ripheralDataSize; uint32_t DMA_MemoryDataSize; uint32_t DMA_Mode; uint32_t DMA_Priority; uint32_t DMA_FIFOMode; uint32_t DMA_FIFOThreshold; uint32_t DMA_MemoryBurst; uint32_t DMA_PeripheralBurst; } DMA_InitTypeDef; 1) DMA_Channel: DMA 请求通道选择,可选通道0 至通道7,每个外设对应固定的通道,具体设置值需要查表DMA1 各个通道的请求映像和表DMA2 各个通道的请求映像。 2) DMA_PeripheralBaseAddr: 外设地址,设定DMA_SxPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储区地址。 ADC3 的数据寄存器ADC_DR 地址为((uint32_t)ADC3+0x4C)。 3) DMA_Memory0BaseAddr: 存储器0 地址,设定DMA_SxM0AR 寄存器值;一般设置为我们自义存储区的首地址。我们程序先自定义一个16 位无符号整形数组ADC_ConvertedValue[4]用来存放每个通道的ADC 值, 所以把数组首地址(直接使用数组名即可) 赋值给DMA_Memory0BaseAddr。 4) DMA_DIR: 传输方向选择,可选外设到存储器、存储器到外设以及存储器到存储器。它设定DMA_SxCR 寄存器的DIR[1:0] 位的值。ADC 采集显然使用外设到存储器模式。 5) DMA_BufferSize: 设定待传输数据数目,初始化设定DMA_SxNDTR 寄存器的值。这里ADC是采集4 个通道数据,所以待传输数目也就是4。 6) DMA_PeripheralInc: 如果配置为DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定DMA_SxCR 寄存器的PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。ADC3 的数据寄存器地址是固定并且只有一个所以不使能外设地址递增。 7) DMA_MemoryInc: 如果配置为DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定DMA_SxCR 寄存器的MINC 位的值;我们自定义的存储区一般都是存放多个数据的,所以使能存储器地址自动递增功能。我们之前已经定义了一个包含4 个元素的数字用来存放数据,使能存储区地址递增功能,自动把每个通道数据存放到对应数组元素内。 8) DMA_PeripheralDataSize: 外设数据宽度,可选字节(8 位)、半字(16 位) 和字(32 位),它设定DMA_SxCR 寄存器的PSIZE[1:0] 位的值。ADC 数据寄存器只有低16 位数据有效,使用半字数据宽度。 9) DMA_MemoryDataSize: 存储器数据宽度,可选字节(8 位)、半字(16 位) 和字(32 位),它设定DMA_SxCR 寄存器的MSIZE[1:0] 位的值。保存ADC 转换数据也要使用半字数据宽度,这跟我们定义的数组是相对应的。 10) DMA_Mode: DMA 传输模式选择,可选一次传输或者循环传输,它设定DMA_SxCR 寄存器的CIRC 位的值。我们希望ADC 采集是持续循环进行的,所以使用循环传输模式。 11) DMA_Priority: 软件设置数据流的优先级,有4 个可选优先级分别为非常高、高、中和低,它设定DMA_SxCR 寄存器的PL[1:0] 位的值。DMA 优先级只有在多个DMA 数据流同时使用时才有意义,这里我们设置为非常高优先级就可以了。 12) DMA_FIFOMode: FIFO 模式使能,如果设置为DMA_FIFOMode_Enable 表示使能FIFO 模式功能;它设定DMA_SxFCR 寄存器的DMDIS 位。ADC 采集传输使用直接传输模式即可,不需要使用FIFO 模式。 13) DMA_FIFOThreshold: FIFO 阈值选择,可选4 种状态分别为FIFO 容量的1/4、1/2、3/4 和满;它设定DMA_SxFCR 寄存器的FTH[1:0] 位;DMA_FIFOMode 设置为DMA_FIFOMode_Disable,那DMA_FIFOThreshold 值无效。ADC 采集传输不使用FIFO 模式,设置改值无效。 14) DMA_MemoryBurst: 存储器突发模式选择,可选单次模式、4 节拍的增量突发模式、8 节拍的增量突发模式或16 节拍的增量突发模式,它设定DMA_SxCR 寄存器的MBURST[1:0] 位的值。ADC 采集传输是直接模式,要求使用单次模式。 15) DMA_PeripheralBurst: 外设突发模式选择,可选单次模式、4 节拍的增量突发模式、8 节拍的增量突发模式或16 节拍的增量突发模式,它设定DMA_SxCR 寄存器的PBURST[1:0] 位的值。 ADC 采集传输是直接模式,要求使用单次模式。 3)使能串口 1 的 DMA 发送 进行DMA 配置之后,我们就要开启串口的 DMA 发送功能,使用的函数是: USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口 1的 DMA发送 如果是要使能串口DMA 接受,那么第二个参数修改为 USART_DMAReq_Rx 即可。 4)使能 DMA 2 数据流 7 ,启动传输。 使能 DMA 数据流的函数为: void DMA_Cmd(DMA_Stream_TypeDef* DMAy_Streamx, FunctionalState NewState) 使能 DMA2_Stream7 ,启动传输的方法为: DMA_Cmd DMA2_Stream7 ENABLE 通过以上4 步设置,我们就可以启动一次 USART1 的 DMA 传输了。 5)查询 DMA 传输状态 在DMA 传输过程中,我们要查询 DMA 传输通道的状态,使用的函数是: FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG) 比如我们要查询DMA数据流 7传输是否完成,方法是: DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7); 这里还有一个比较重要的函数就是获取当前剩余数据量大小的函数: int16_t DMA_GetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx); 比如我们要获取DMA数据流 7还有多少个数据没有传输,方法是: DMA_GetCurrDataCounter(DMA1_Channel4); 同样,我们也可以设置对应的DMA数据流传输的数据量大小 ,函数为: void DMA_SetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx, uint16_t Counter); 6. 程序详解 USART.C 对于USART串口初始化的子程序我们仍然使用串口通讯中的程序代码。 //标准库需要的支持函数 struct __FILE { int handle; }; FILE __stdout; //定义_sys_exit()以避免使用半主机模式 void _sys_exit(int x) { x = x; } void uart_init(u32 bound){ //GPIO端口设置 GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); //使能GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //使能USART1时钟 //串口1对应引脚复用映射 GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_USART1); //GPIOB6复用为USART1 GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_USART1); //GPIOB7复用为USART1 //USART1端口配置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //GPIOB6与GPIOB7 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化PB6,PB7 //USART1 初始化设置 USART_InitStructure.USART_BaudRate = bound; //波特率设置 USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式 USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位 USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式 USART_Init(USART1, &USART_InitStructure); //初始化串口1 USART_Cmd(USART1, ENABLE); //使能串口 } //=============================================================================== ///重定向c库函数printf到串口,重定向后可使用printf函数 int fputc(int ch , FILE *f) { USART_SendData(USART1,(uint8_t) ch ); while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); return (ch); } //=============================================================================== ///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数 int fgetc(FILE *f) { while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); return (int)USART_ReceiveData(USART1); } //=============================================================================== 中断服务函数。 void USART1_IRQHandler(void) //串口1中断服务程序 { uint8_t temp; if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET) { temp = USART_ReceiveData(USART1); USART_SendData(USART1,temp); } |
|
|
|
DMA.C
DMA初始化函数。此函数有5个形参。 DMA_Stream_TypeDef *DMA_Streamx: 为选择数据流。 u32 chx: 为通道选择。 u32 par: 为外设地址。 u32 mar: 存储器地址。 u16 ndtr: 为数据传输量。 void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr) { DMA_InitTypeDef DMA_InitStructure; if((u32)DMA_Streamx>(u32)DMA2) //得到当前stream是属于DMA2还是DMA1 { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE); //DMA2时钟使能 }else { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE); //DMA1时钟使能 } DMA_DeInit(DMA_Streamx); while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE) { } //等待DMA可配置 // 配置 DMA 初始化函数 // DMA_InitStructure.DMA_Channel = chx; //通道选择 DMA_InitStructure.DMA_PeripheralBaseAddr = par; //DMA外设地址 DMA_InitStructure.DMA_Memory0BaseAddr = mar; //DMA 存储器0地址 DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //存储器到外设模式 DMA_InitStructure.DMA_BufferSize = ndtr; //数据传输量 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设非增量模式 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器增量模式 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据长度:8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据长度:8位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 使用普通模式 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //中等优先级 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //不使能FIFO模式 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; //FIFO 阈值选择,此时无效 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //存储器突发单次传输 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //外设突发单次传输 DMA_Init(DMA_Streamx, &DMA_InitStructure); //初始化DMA Stream } 开启一次DMA数据传输。 其中:DMA_Stream_TypeDef *DMA_Streamx 为DMA数据流; u16 ndtr为为数据传输量。 void MYDMA_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr) { DMA_Cmd(DMA_Streamx, DISABLE); //关闭DMA传输 while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){} //确保DMA可以被设置 DMA_SetCurrDataCounter(DMA_Streamx,ndtr); //数据传输量 DMA_Cmd(DMA_Streamx, ENABLE); //开启DMA传输 } MAIN.C ①进行宏定义 #define SEND_BUF_SIZE 9600 //发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍. u8 SendBuff[SEND_BUF_SIZE]; //发送数据缓冲区 const u8 TEXT_TO_SEND[]={"DMA串口实验 :Hello World!"}; int main(void) { u16 i; u8 t=0; u8 j,mask=0; float pro=0; //进度 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置系统中断优先级分组2 delay_init(168); //初始化延时函数 uart_init(115200); //初始化串口波特率为115200 LED_Init(); //初始化LED LCD_Init(); //LCD初始化 KEY_Init(); //按键初始化 MYDMA_Config(DMA2_Stream7,DMA_Channel_4,(u32)&USART1->DR,(u32)SendBuff,SEND_BUF_SIZE); //DMA2,STEAM7数据流7,CH4通道4,外设为串口1,存储器为SendBuff,长度为:SEND_BUF_SIZE. POINT_COLOR=RED; LCD_ShowString(30,50,200,16,16,"STM32F407"); LCD_ShowString(30,70,200,16,16,"DMA"); LCD_ShowString(30,90,200,16,16,"=========="); LCD_ShowString(30,110,200,16,16,"2021/4/1"); LCD_ShowString(30,130,200,16,16,"KEY0:Start"); POINT_COLOR=BLUE; //设置字体为蓝色 //显示提示信息 j=sizeof(TEXT_TO_SEND); for(i=0;i if(t>=j) //加入换行符 { if(mask) { SendBuff=0x0a; t=0; }else { SendBuff=0x0d; mask++; } }else //复制TEXT_TO_SEND语句 { mask=0; SendBuff=TEXT_TO_SEND[t]; t++; } } POINT_COLOR=BLUE; //设置字体为蓝色 i=0; while(1) { t=KEY_Scan(0); if(t==KEY0_PRES) //KEY0按下 { printf("rnDMA DATA:rn"); LCD_ShowString(30,150,200,16,16,"Start Transimit...."); LCD_ShowString(30,170,200,16,16," %") ; //显示百分号 USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送 MYDMA_Enable(DMA2_Stream7,SEND_BUF_SIZE); //开始一次DMA传输! //等待DMA传输完成,此时我们来做另外一些事,点灯 //实际应用中,传输数据期间,可以执行另外的任务 while(1) { if(DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7)!=RESET) //等待DMA2_Steam7传输完成 { DMA_ClearFlag(DMA2_Stream7,DMA_FLAG_TCIF7); //清除DMA2_Steam7传输完成标志 break; } pro=DMA_GetCurrDataCounter(DMA2_Stream7); //得到当前还剩余多少个数据 pro=1-pro/SEND_BUF_SIZE; //得到百分比 pro*=100; //扩大100倍 LCD_ShowNum(30,170,pro,3,16); } LCD_ShowNum(30,170,100,3,16); //显示100% LCD_ShowString(30,150,200,16,16,"Transimit Finished!"); //提示传送完成 } i++; delay_ms(10); if(i==20) { LED0=!LED0; //提示系统正在运行 i=0; } } } //开启一次DMA传输 //DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7 //ndtr:数据传输量 void MYDMA_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr) { DMA_Cmd(DMA_Streamx, DISABLE); //关闭DMA传输 while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){} //确保DMA可以被设置 DMA_SetCurrDataCounter(DMA_Streamx,ndtr); //数据传输量 DMA_Cmd(DMA_Streamx, ENABLE); //开启DMA传输 } 7. 效果展示 将程序下载进芯片后,屏幕将出现提示信息,按下按键0将执行一次DMA数据传输操作。 打开串口调试助手,设置好波特率,打开串口,将看到所有传输的数据。 注意事项: 在传输数据的过程中如果SEND_BUF_SIZE发送数据的长度没有定义准确将导致发送数据的不完整,如下图所示: 显然最后一行没有将“STM32F4 DMA”完整发送,如果需要完整发送需要对SEND_BUF_SIZE进行合理取值,最好等于sizeof(TEXT_TO_SEND)+2的整数倍。 8. 小结 本实验到此结束,其实实验不难,主要是一个实验的过程,需要对这个过程进行了解,还有就是相关函数的使用标志位的判定等。此外,多查手册仍然很重要。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1752 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1611 浏览 1 评论
1052 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
721 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1666 浏览 2 评论
1924浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
711浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
560浏览 3评论
583浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
544浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-18 12:24 , Processed in 0.770296 second(s), Total 48, Slave 41 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号