最近重新开始学32,搞到串口 DMA 的时候, 数据读取卡了很长一段时间,最终,功夫不负有心人终于搞出来了。在此以记录一下,方便以后查询使用。
在调试的过程中也遇到了很多bug,有些简直就是低级问题,但是还是卡了很久,在此写出来给自己加深印象,同时已给后来者一个前车之鉴
1、在32的程序编写中,若使用到了中断部分,特别是中断函数一定要注意,查询清除中断标志位到底是什么。
获取中断标志位 是 USART_GetITStatus 函数,笔者就是没弄清 用成了 USART_ GetFlagStatus 函数,
USART_GetITStatus 检查指定的 USART 中断发生与否
USART_ GetFlagStatus 检查指定的 USART 标志位设置与否
就是这个问题卡了笔者很久,甚至笔者检查了很多次都没注意到
2、中断函数的使用, 这个在使用时若不注意就会用错,比如 中断串口2 ,却使用成了 中断串口1
关于中断函数的查询可以通过kile5 Startup文件夹 下的 startup_stm32f10x_md.s 进行查询
3、代码功能独立
这部分主要就是要将各函数功能进行,独立。方便理清逻辑,可能很多人觉得很麻烦,且很多人没有此习惯,但是在学习和工作中稍微花点时间,将其进行整理还是很有必要的。
笔者在调试这部分时,就出现了逻辑混乱的情况,将 GPIO, 串口,DMA 全部写在一起,导致最后混乱。最终每次只会搬运最后一个字符。具体原因笔者也没搞清楚。若有大佬能够了解,望指点一二。
————————————————————分割线————————————————————
以上废话,下为正文
DMA 串口数据读取主要就是 将串口的数据搬移到内存中
在实际代码中,笔者定义了一个char 数组进行存储。并通过重定义printf 的方式将 char 数组内的内容输出出来(后面看大家需不需要,也可以将DMA 数据发送的代码 分享出来 嘿嘿嘿)。
主要步骤如下
1、选择使用的串口(串口1)
2、查询该串口对应的引脚及DMA通道(A9,A10, DMA1chanel4, DMA1chanel5)Tip:可以通过STM32参考手册查询,后面会放的(感谢正点原子)
3、配置串口1 GPIO
4、配置串口1 USART 及 中断
5、配置串口1 对应 DMA
着重需要注意的是:这里使用的是 串口1 的 空闲中断。空闲中断在设备接收到数据后 一个字节内的时间还是为接收到数据就会产生,在清除之后,当下一次接收到数据之后的1个字节时间内才会再次产生。 说人话就是,正常情况下不会产生,只有在接收数据后,没有数据了,才会再次产生。
此处使用串口 的空闲中断可以实现 接收不定长数据,若使用的是 定长数据 就可以使用DMA 的接收完成中断(接收完成是当前数据接收完成,还需要判断接收次数是否为 0)
笔者设备 STM32F103RCT6 + window10,若其他设备可根据提供的资料进行查询对应的引脚和通道
————————————————————分割线————————————————————
开始放代码
配置引脚
void USART1_GPIO_Init(void){//初始化 串口 1 GPIO
//GPIO引脚功能初始化结构体
GPIO_InitTypeDef GPIO_InitStruct;
//1、使能串口引脚,复用功能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA, ENABLE);
//2、初始化引脚功能 输入 悬空(输入高为高, 输入低为低), 输出 复用推挽
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;//初始化引脚为 A9
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;//初始化引脚功能为 复用推挽模式
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//设置引脚传输速率为 10MHz
GPIO_Init(GPIOA, & GPIO_InitStruct);//初始化 A9引脚 (发送)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;//初始化引脚为 A10
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;//初始化引脚功能为 悬空输入
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//设置引脚传输速率为 10MHz
GPIO_Init(GPIOA, & GPIO_InitStruct);//初始化 A1引脚 (接收)
}
配置串口
void USART1_Init(uint32_t BaudRate){
//串口初始化结构体
USART_InitTypeDef USART_InitStruct;
//串口优先级结构体
NVIC_InitTypeDef NVIC_InitStruct;
//1、使能串口引脚,复用功能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
//2、串口初始化
//2.1 初始化波特率
USART_InitStruct.USART_BaudRate = BaudRate;//初始化串口波特率(使用形参确定)
//2.2 初始化数据位
USART_InitStruct.USART_WordLength = USART_WordLength_8b;//初始化串口数据位为 8 位
//2.3 初始化停止位
USART_InitStruct.USART_StopBits = USART_StopBits_1;//初始化串口数据位为 0 位
//2.4 初始化奇偶校验位
USART_InitStruct.USART_Parity = USART_Parity_No;//初始化串口奇偶校验位
//2.5 初始化流控
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//初始化串口控制为无流控
//2.6 初始化串口工作模式
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_Init(USART1, & USART_InitStruct);
//3、设置串口 接收中断 (空闲中断-搬运数据完毕 或 未搬运数据 当开始搬运数据后 一段时间未搬运下一个数据,发出此中断)
//3.1 配置串口中断优先级通道
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
//3.2 使能命令
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
//3.3 设置抢占优先级
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
//3.4 设置优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
NVIC_Init( & NVIC_InitStruct);
//5、配置串口接收中断
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//串口1 空闲中断
//5、中断处理
USART_DMACmd(USART1,USART_DMAReq_Rx, ENABLE);//开启串口1 DMA 接收请求
//6、使能串口
USART_Cmd(USART1,ENABLE);//若重载 printf ,就无需使用 DMA 进行数据搬移。(故此处应该无需使用重载)
}
配置 DMA
void USART1_DMA_Init(void){
//DMA初始化结构体
DMA_InitTypeDef DMA_InitStruct;
//1、使能DMA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
//2、接收数据 DMA通道5初始化
//2.1 初始化外设地址
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)& USART1->DR;//设置串口发送数据寄存器
//2.2 初始化内存地址
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)Get_Date;//接收内存地址
//2.3 初始化 DMA 方向
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;//外设为源地址
//2.4 初始化 发送数据大小 ,6.2内存大小
DMA_InitStruct.DMA_BufferSize = BUFFSIZE;//此处的接收数据大小设置一个固定的,较大的
//2.5 初始化外设地址增量(向发送端写入数据,无需使用增量)
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//无增量
//2.6 初始化内存地址增量(读取内存地址,写入外设,有增量)
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;//有增量
//2.7 初始化外设数据宽度
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//无增量的话应该不用设置
//2.8 初始化内存数据宽度
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//增量为全字
//2.9 初始化DMA工作模式
DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;//正常循环模式
//2.10 初始化DMA通道优先级
DMA_InitStruct.DMA_Priority = DMA_Priority_High;//通道优先级 高
//2.11 初始化DMA存储器to存储器(内存写入外设,无需使用)
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;//不使用 内存到内存
DMA_Init(DMA1_Channel5, & DMA_InitStruct);//通道5 串口1 读数据
DMA_Cmd(DMA1_Channel5, ENABLE);//开启通道5 使能位
}
中断和调用函数部分
void Mov_DmaDate_To_Buffer(void){//搬移函数,将DMA寄存器中的数据搬移到另一个寄存器
DMA_Cmd(DMA1_Channel5, DISABLE);//关闭 DMA 通道 5 (关闭串口1 读)
DMA1_Channel5->CNDTR = BUFFSIZE;//重新设置 通道 待传输数据大小
DMA_Cmd(DMA1_Channel5, ENABLE);//开启 DMA 通道 5 (开启串口1 读)
printf("%s\n",Get_Date);//输出接收的数据
}
void USART1_IRQHandler(void){//串口1 中断处理函数
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET){//若中断标志位为 空闲中断
//调用搬移函数
Mov_DmaDate_To_Buffer();//调用空闲中断处理函数
USART_ReceiveData(USART1);//通过读取数据实现关闭 空闲中断
//清除标志位
Recevie_State = true;//设置接收数据完毕标志位
}
}