DMA(Direct Memory Access,直接存储器访问) ,DMA 传输是将数据从一个地址空间复制到另外一个地址空间。CPU 只负责初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。 一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。 根据以上步骤书写代码,因为分文件书写这样看着会有点麻烦,所以我就将示例写在一个文件中,望各位大佬见谅 说明: 发送了36个字符 36个字符加rn刚好38个字符,成功 使用DMA的好处就是可以不用占用CPU的资源,另外CPU进入串口的空闲中断(USART_IT_IDLE)会比串口的接收中断(USART_IT_RXNE)的次数要少。而且使用空闲中断完美的解决了接收任意长度的数据,如若大佬们认为以上代码有任何Bug或错请在评论指出,我也会不断的完善。
DMA
简介
原理
CPU对DMA控制器初始化,并向I/O接口发出操作命令,I/O接口提出DMA请求。
DMA控制器对DMA请求判别优先级及屏蔽,向总线裁决逻辑提出总线请求。当CPU执行完当前总线周期即可释放总线控制权。此时,总线裁决逻辑输出总线应答,表示DMA已经响应,通过DMA控制器通知I/O接口开始DMA传输。
DMA控制器获得总线控制权后,CPU即刻挂起或只执行内部操作,由DMA控制器输出读写命令,直接控制RAM与I/O接口进行DMA传输。
在DMA控制器的控制下,在存储器和外部设备之间直接进行数据传送,在传送过程中不需要中央处理器的参与。开始时需提供要传送的数据的起始位置和数据长度。
当完成规定的成批数据传送后,DMA控制器即释放总线控制权,并向I/O接口发出结束信号。当I/O接口收到结束信号后,一方面停 止I/O设备的工作,另一方面向CPU提出中断请求,使CPU从不介入的状态解脱,并执行一段检查本次DMA传输操作正确性的代码。最后,带着本次操作结果及状态继续执行原来的程序。STM32标准库编程
DMA初始化结构体
结构体定义
typedef struct { //指定DMAy信道的外设基址 uint32_t DMA_PeripheralBaseAddr; //指定DMAy信道的内存基地址。 uint32_t DMA_MemoryBaseAddr; //指定这个外设是作为数据传输的目的地还是数据传输的来源 uint32_t DMA_DIR; //指定信道缓存的大小 uint32_t DMA_BufferSize; //指定外设地址寄存器是否递增 uint32_t DMA_PeripheralInc; //指定内存地址寄存器是否递增 uint32_t DMA_MemoryInc; //指定外设数据宽度。 uint32_t DMA_PeripheralDataSize; //指定内存数据宽度。 uint32_t DMA_MemoryDataSize; //指定DMAy信道x的操作模式。 uint32_t DMA_Mode; //指定DMAy信道x的软件优先级 uint32_t DMA_Priority; //指定DMAy通道x是否将在内存到内存传输中使用 uint32_t DMA_M2M; } DMA_InitTypeDef;
结构体参数取值
指定这个外设是作为数据传输的目的地还是数据传输的来源//外设作为数据传输的目的地 #define DMA_DIR_PeripheralDST ((uint32_t)0x00000010) //外设作为数据传输的来源 #define DMA_DIR_PeripheralSRC ((uint32_t)0x00000000)
指定外设地址寄存器是否递增//递增 #define DMA_PeripheralInc_Enable ((uint32_t)0x00000040) //不递增 #define DMA_PeripheralInc_Disable ((uint32_t)0x00000000)
指定内存地址寄存器是否递增//递增 #define DMA_MemoryInc_Enable ((uint32_t)0x00000080) //不递增 #define DMA_MemoryInc_Disable ((uint32_t)0x00000000)
指定外设数据宽度。//一个字节(8位) #define DMA_PeripheralDataSize_Byte ((uint32_t)0x00000000) //半个字(16位) #define DMA_PeripheralDataSize_HalfWord ((uint32_t)0x00000100) //一个字(32位) #define DMA_PeripheralDataSize_Word ((uint32_t)0x00000200)
指定内存数据宽度。//一个字节(8位) #define DMA_MemoryDataSize_Byte ((uint32_t)0x00000000) //半个字(16位) #define DMA_MemoryDataSize_HalfWord ((uint32_t)0x00000400) //一个字(32位) #define DMA_MemoryDataSize_Word ((uint32_t)0x00000800)
指定DMAy信道x的操作模式。//循环模式 #define DMA_Mode_Circular ((uint32_t)0x00000020) //普通模式 #define DMA_Mode_Normal ((uint32_t)0x00000000)
指定DMAy信道x的软件优先级//很高 #define DMA_Priority_VeryHigh ((uint32_t)0x00003000) //高 #define DMA_Priority_High ((uint32_t)0x00002000) //中等 #define DMA_Priority_Medium ((uint32_t)0x00001000) //低 #define DMA_Priority_Low ((uint32_t)0x00000000)
指定DMAy通道x是否将在内存到内存传输中使用//是 #define DMA_M2M_Enable ((uint32_t)0x00004000) //不是 #define DMA_M2M_Disable ((uint32_t)0x00000000)
DMA串口编程的一般步骤
示例代码
USART1与蓝牙进行连接,用于输入数据
USART2与PC进行连接,用于显示输入的数据#include <stm32f10x.h> #include <string.h> #include <stdio.h> //缓冲区的大小 #define Buff_Size 1024 //此次接收结束的标志 volatile char rec_end_flag; //接收二级缓存中的数量 volatile unsigned short count; //一级缓存,即DMA直接搬运数据的目的地 volatile char usart1_recv_buff[Buff_Size]; /*二级缓存,即某次传输结束后将一级缓存的数据拷贝到此, 方便继续开启DMA使用一级缓存接收数据而数据不会被覆盖*/ volatile char recv_buff[Buff_Size]; //设置NVIC的优先级分组 void NVIC_config(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); } //初始化串口以及GPIO void usart1_init(void) { //用于GPIO初始化的结构体 GPIO_InitTypeDef GPIO_InitStructure; //用于串口初始化的结构体 USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStruct; //1. 初始化结构体 NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2; //应用NVIC结构体 NVIC_Init(&NVIC_InitStruct); //2. 使能相应的串口以及对应的GPIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); //3. 配置GPIO结构体并应用 //初始化Usart1的Txd脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化Usart1的Rxd脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //应用GPIO结构体 GPIO_Init(GPIOA, &GPIO_InitStructure); //4. 配置Usart结构体并应用 //初始化Usart1结构体 USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; 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_Tx | USART_Mode_Rx; USART_Init(USART1, &USART_InitStructure); //5. 使能串口中断(看你需要什么中断,eg:使用USART_IT_IDLE) USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //6. 开启DMA接收 USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); //7. 使能串口 USART_Cmd(USART1, ENABLE); } //使用Debug的显示 void usart2_init(void) { //用于GPIO初始化的结构体 GPIO_InitTypeDef GPIO_InitStructure; //用于串口初始化的结构体 USART_InitTypeDef USART_InitStructure; //开启该串口以及其对应GPIO的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //初始化Usart2的Txd脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化Usart2的Rxd脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化Usart2结构体 USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; 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_Tx | USART_Mode_Rx; USART_Init(USART2, &USART_InitStructure); //使能Usart1 USART_Cmd(USART2, ENABLE); } //初始化DMA void DMA1_init(void) { DMA_InitTypeDef DMA_InitStructure; //1.使能DMA1时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //将DMA的通道5的寄存器重设为缺省值 DMA_DeInit(DMA1_Channel5); //2. 配置DMA结构体并应用 //设置DMA源地址 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) & (USART1->DR); //内存地址基地址 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)usart1_recv_buff; //数据传输方向,从外设读取发送到内存 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 1024; //DMA通道的DMA缓存的大小 //外设地址寄存器不递增 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //内存地址寄存器递增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //外设数据宽度为8位 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //内存数据宽度为8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //工作模式为正常模式 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //DMA通道 x拥有中等优先级 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //此传输不是内存到内存传输 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //根据DMA_InitStruct中指定的参数初始化DMAy通道x。 DMA_Init(DMA1_Channel5, &DMA_InitStructure); //3. 使能DMA DMA_Cmd(DMA1_Channel5, ENABLE); } //串口1中断函数 void USART1_IRQHandler(void) { //判断是否为空闲中断 if (USART_GetITStatus(USART1, USART_IT_IDLE) == SET) { //数据接收完毕标志置1 rec_end_flag = 1; //关闭DMA,准备重新配置 DMA_Cmd(DMA1_Channel5, DISABLE); //clear DMA1 Channel5 global interrupt. DMA_ClearITPendingBit(DMA1_IT_GL5); //计算接收数据长度 count = 1024 - DMA_GetCurrDataCounter(DMA1_Channel5); memcpy((void *)recv_buff, (void *)usart1_recv_buff, count); //重新配置 DMA_SetCurrDataCounter(DMA1_Channel5, 1024); DMA_Cmd(DMA1_Channel5, ENABLE); //清除IDLE标志位 USART1->SR; USART1->DR; } } //串口发送一个字符(调试使用) void usart_send_ch(USART_TypeDef *USARTx, char ch) { while (RESET == USART_GetFlagStatus(USARTx, USART_FLAG_TC)) ; USART_SendData(USARTx, ch); } //串口发送字符串(调试使用) void usart_send_str(USART_TypeDef *USARTx, char *str, int length) { int i; for (i = 0; i < length; i++) { usart_send_ch(USARTx, *(str + i)); } //字符串末尾加换行rn char enter_str[] = {'r', 'n'}; for (i = 0; i < 2; i++) { usart_send_ch(USARTx, enter_str[i]); } } int main() { // 1. 配置NVIC分组 NVIC_config(); // 2. 初始化串口以及GPIO usart1_init(); //使用Debug的显示 usart2_init(); // 3. 初始化DMA DMA1_init(); // 4. 编写串口中断函数 //见USART1_IRQHandler while (1) { //如此次传输完成 if (rec_end_flag == 1) { //将二级缓存的数据发送到串口2进行显示输出 usart_send_str(USART2, (char *)recv_buff, count); //处理完数据后将标志置0等待下次传输结束 rec_end_flag = 0; } } }
结果展示
手机端发送
PC端接收
接着又发送了长一点的数据,也没有任何问题
总结
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算