STM32-空闲中断+DMA实现串口数据接收
接上一个随笔的空闲中断..........
通过江科大老师的图来复习一下关于DMA的知识
DMA的作用
DMA作为一个外设,它的作用就是帮CPU处理数据搬运的事情,减少CPU的消耗
如何佩戴STM32的DMA?
1.使能时钟
2.DMA参数配置
3.DMA使能
关于DMA的各个参数
DMA_PeripheralBaseAddr: 外设寄存器的地址。(stm32官方将外设地址和内存地址相互区分开了)
DMA_PeripheralDataSize:每次操作外设寄存器的数据大小
DMA_PeripheralInc :外设寄存器地址是否自增
DMA_MemoryBaseAddr,DMA_MemoryDataSize,DMA_MemoryInc同理,只不过是从外设地址变成了内存地址
DMA_BufferSize :DMA的转运次数,对应它的相关寄存器 CNDTR寄存器(传输计数器) ,该寄存器记录转运次数并不断递减
DMA_Mode :分为正常模式和循环模式,循环模式CNDTR寄存器会自动重载
DMA_M2M :选择触发方式,本例中使用的是硬件触发,即外设触发
DMA_Priority :DMA转运的优先级,在多个DMA通道转运的时候会发挥作用吧。
DMA_DIR :DMA转运的方向,由外设充当的角色决定,如这里外设地址作为SRC(source:来源),说明数据是从外设到内存中
关于DMA的启动
三个条件: 1.DMA使能 2.传输计数器 != 0 3.有触发源提供触发信号(无论是软件触发还是硬件(外设)触发)。
更详细的学习DMA建议去B站学习江科大老师的视频
以下为代码部分
serial部分和空闲中断部分差不多,唯一区别就是中断处理函数部分和初始化部分多了串口触发DMA使能USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
#include "stm32f10x.h" // Device header #include <stdio.h> #include <stdarg.h> uint8_t Serial_RxData[255]; uint8_t Serial_RxFlag; //数据接收完成标志位 uint8_t Serial_count; //串口接收到数据的总数 void Serial_Init(void) { //配置串口,端口时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //配置端口GPIO GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //配置串口配置 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_Init(USART1, &USART_InitStructure); //配置串口接收完成中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //串口空闲中断 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_Init(&NVIC_InitStructure); //开启DMA接收请求,dma硬件触发开启的最后一块拼图 USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); USART_Cmd(USART1, ENABLE); Serial_count = 0; } void Serial_SendByte(uint8_t Byte) { USART_SendData(USART1, Byte); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); } void Serial_SendArray(uint8_t *Array, uint16_t Length) { uint16_t i; for (i = 0; i < Length; i ++) { Serial_SendByte(Array[i]); } } void Serial_SendString(char *String) { uint8_t i; for (i = 0; String[i] != '\0'; i ++) { Serial_SendByte(String[i]); } } uint32_t Serial_Pow(uint32_t X, uint32_t Y) { uint32_t Result = 1; while (Y --) { Result *= X; } return Result; } void Serial_SendNumber(uint32_t Number, uint8_t Length) { uint8_t i; for (i = 0; i < Length; i ++) { Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0'); } } int fputc(int ch, FILE *f) { Serial_SendByte(ch); return ch; } void Serial_Printf(char *format, ...) { char String[100]; va_list arg; va_start(arg, format); vsprintf(String, format, arg); va_end(arg); Serial_SendString(String); } uint8_t Serial_GetRxFlag(void) { if (Serial_RxFlag == 1) { Serial_RxFlag = 0; return 1; } return 0; } void USART1_IRQHandler(void) { uint8_t clear; // if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) // { // //USART_ReceiveData()函数会返回DR寄存器的1个字节数据回来 // Serial_RxData[Serial_count++] = USART_ReceiveData(USART1); // USART_ClearITPendingBit(USART1, USART_IT_RXNE); // } if (USART_GetITStatus(USART1, USART_IT_IDLE) == SET) { //只是单纯为了清除空闲中断位 clear= USART1->SR; //读取状态寄存器 clear= USART1->DR; //读取数据寄存器 DMA_Cmd(DMA1_Channel5,DISABLE); Serial_count = 255 - DMA_GetCurrDataCounter(DMA1_Channel5); //得到DMA接收到了多少个数据值,因为配置之后计数寄存器的值会不断自减,减法结果就是接收到的数据 DMA1_Channel5->CNDTR = 255; //重新赋计数寄存器的值 DMA_Cmd(DMA1_Channel5,ENABLE); //此标志位确定所有数据都接收完成 Serial_RxFlag = 1; } }
serial.h代码
#ifndef __SERIAL_H #define __SERIAL_H #include <stdio.h> extern uint8_t Serial_count; extern uint8_t Serial_RxFlag; extern uint8_t Serial_RxData[255]; void Serial_Init(void); void Serial_SendByte(uint8_t Byte); void Serial_SendArray(uint8_t *Array, uint16_t Length); void Serial_SendString(char *String); void Serial_SendNumber(uint32_t Number, uint8_t Length); void Serial_Printf(char *format, ...); uint8_t Serial_GetRxFlag(void); uint8_t Serial_GetRxData(void); #endif
DMA部分代码,要确认好要配置的通道,如这里我使用了串口1的接收PA10引脚,对应DMA通道就是DMA1的通道5,具体只需要去手册DMA章节上查找即可
void My_usart_DMA_Init() { /* STM32把外设和存储器区分开了 DMA转运的条件:1.DMA使能 2:触发源有信号 3:转运数 > 0 */ /*开启时钟*/ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA的时钟 /*接收DMA初始化*/ DMA_InitTypeDef DMA_InitStructure; //定义结构体变量 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; //串口的DR寄存器作为外设地址 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据宽度,选择字节 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址自增,选择不使能(因为要使用串口DR寄存器) DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Serial_RxData; //存储器基地址,给定Serial_RxData,一个自己定义的缓冲区数组 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据宽度,选择字节 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址自增,选择使能 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,选择由外设到存储器,外设站点作为源头 DMA_InitStructure.DMA_BufferSize = 255; //转运的数据大小(转运次数) DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //模式,选择正常模式 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA触发方式,ENABLE:软件触发 DISABLE:硬件触发 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级,选择中等 DMA_Init(DMA1_Channel5, &DMA_InitStructure); //将结构体变量交给DMA_Init,配置DMA1的通道1 //上面配置了外设地址是DR寄存器地址,内存地址是缓冲区数组地址,转运最大数量255,外设站点作为源头(数据: 外设 -> 数组 ) /*DMA使能*/ /* 我这里要使用串口1的DMA接收,所以开启了DMA1的通道5 */ DMA_Cmd(DMA1_Channel5, ENABLE); //这里先不给使能,初始化后不会立刻工作,等后续调用Transfer后,再开始 }
main函数部分
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "MyDMA.h" #include "Serial.h" int main(void) { /*模块初始化*/ OLED_Init(); //OLED初始化 Serial_Init(); My_usart_DMA_Init(); Serial_Printf("START"); while (1) { if(Serial_RxFlag == 1) { Serial_RxFlag = 0; for(uint8_t i=0;i<Serial_count;i++) { Serial_SendByte(Serial_RxData[i]); Serial_Printf("\r\n"); } } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了