stm32F103DMA
STM32F103串口DMA收发标准库_stm32f103 dma表-CSDN博客代码文章引用至。这里只做了一些改进。void DMA_USART1_Send(uint8_t *data,u16 size)//串口1DMA发送函数 { DMA_Cmd(DMA1_Channel4, DISABLE);//这里关闭了通道4while (DMA_GetCurrDataCounter(...)) 可能会陷入死循环因为硬件发送还在进行CNDTR 不会自动减少 memcpy(DMA_USART1_TX_BUF, data, size); while (DMA_GetCurrDataCounter(DMA1_Channel4)); // 检查DMA发送通道内是否还有数据等待上次数据发送完成 DMA_SetCurrDataCounter(DMA1_Channel4, size); // 重新写入要传输的数据数量 DMA_Cmd(DMA1_Channel4, ENABLE); // 启动DMA发送 }测试代码如下#include stm32f10x.h // Device header #include Delay.h #include UARTDMA.h extern volatile uint16_t com1_rx_len; //接收帧数据的长度 extern volatile uint8_t com1_recv_end_flag; //帧数据接收完成标 extern uint8_t com1_rx_buffer[USART_MAX_LEN];//接收数据缓存 int main(void) { USART1_Config(115200); uint8_t welcome[] STM32F103 DMA UART Demo Ready!\r\n; DMA_USART1_Send(welcome, sizeof(welcome) - 1); while (1) { DMA_USART1_Send(welcome, sizeof(welcome) - 1);//测试 // 检查是否收到完整的一帧数据 if (com1_recv_end_flag 1) { com1_recv_end_flag 0; // 清除标志 // 这里处理收到的数据例如原样发回回声测试 if (com1_rx_len 0) { DMA_USART1_Send(com1_rx_buffer, com1_rx_len); } // 可选清空接收缓冲或打印长度 com1_rx_len 0; } } }错误原因提前关闭了通道4因为在 调用DMA_USART1_Send(welcome, sizeof(welcome) - 1);后又马上再调用一次第一次调用串口还没来得及发送到第二次调用的时候时间只能发送S这一个字母然后这个函数当中提前关闭了通道4然后接着检测CNDTR寄存器为0因为DMA已禁用CNDTR不再变化所以进入死循环。改正代码如下void DMA_USART1_Send(uint8_t *data,u16 size)//串口1DMA发送函数,改进死循环风险 { while (DMA_GetCurrDataCounter(DMA1_Channel4)); // 检查DMA发送通道内是否还有数据等待上次数据发送完成 DMA_Cmd(DMA1_Channel4, DISABLE); memcpy(DMA_USART1_TX_BUF, data, size); DMA_SetCurrDataCounter(DMA1_Channel4, size); // 重新写入要传输的数据数量 DMA_Cmd(DMA1_Channel4, ENABLE); // 启动DMA发送 }更进一步。可以自行增加超时检测。改进后main.c不用更改不用加延时函数仍然可以继续运行。回传正常改进二原文中在void USART1_IRQHandler(void)中断函数中使用了这行代码USART_ClearITPendingBit(USART1,USART_IT_IDLE);本意是想给IDLE寄存器置零如何定位到IDLE寄存器的可以参考https://blog.csdn.net/2301_78788860/article/details/161763692?fromshareblogdetailsharetypeblogdetailsharerId161763692sharereferPCsharesource2301_78788860sharefromfrom_linkhttps://blog.csdn.net/2301_78788860/article/details/161763692?fromshareblogdetailsharetypeblogdetailsharerId161763692sharereferPCsharesource2301_78788860sharefromfrom_link这篇文章即清除总线空闲标志位。但是我们查看数据手册可知。IDLE只有可读权限所以这行清除标志位的代码作用是无效的。正确做法是先读USART_SR然后读USART_DR寄存器但是不是否认原文作者的代码不可用给我提供了很好的借鉴意义。那么原作者的代码为什么可以用0解释如下作者在串口1中断函数中编写了if(USART_GetITStatus(USART1,USART_IT_IDLE)!RESET) 这段代码下面这张图片是ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)这个函数的部分代码也是原作者代码可以正常运行的主要原因即在函数内部读取了SR寄存器。然后作者在该函数的最后写USART_ReceiveData(USART1);这行代码在USART_ReceiveData函数中读取了DR寄存器所以正好清除了IDLE总线空闲标志位。但是中断服务函数中USART_ClearITPendingBit(USART1,USART_IT_IDLE);这行代码没有实际作用。测试这个是测试的代码。uint8_t LDLE[] LDLE1\r\n; uint8_t RLDLE[] RLDLE0\r\n; void USART1_IRQHandler(void) //串口1中断服务程序 { /* 使用串口DMA空闲接收 */ if(USART_GetITStatus(USART1,USART_IT_IDLE)!RESET) { com1_recv_end_flag1; //接收完成标志 USART_GetITStatus(USART1,USART_IT_IDLE)!RESET也会读取所以不需要在读 USART_ClearITPendingBit(USART1,USART_IT_IDLE);//不可以这样使用 DMA_Cmd(DMA1_Channel5, DISABLE); /* 暂时关闭dma数据尚未处理 */ com1_rx_len USART_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel5); DMA_SetCurrDataCounter(DMA1_Channel5,USART_MAX_LEN); DMA_Cmd(DMA1_Channel5, ENABLE); /*打开DMADMA检查RXNE位来控制搬运*/ if(USART_GetITStatus(USART1,USART_IT_IDLE)!RESET) { DMA_USART1_Send(LDLE, sizeof(LDLE) - 1);//测试 }else{ DMA_USART1_Send(RLDLE, sizeof(RLDLE) - 1);//测试 } } }#include stm32f10x.h // Device header #include Delay.h #include UARTDMA.h extern volatile uint16_t com1_rx_len; //接收帧数据的长度 extern volatile uint8_t com1_recv_end_flag; //帧数据接收完成标 extern uint8_t com1_rx_buffer[USART_MAX_LEN];//接收数据缓存 int main(void) { USART1_Config(9600); uint8_t welcome[] STM32F103 DMA UART Demo Ready!\r\n; DMA_USART1_Send(welcome, sizeof(welcome) - 1); while (1) { DMA_USART1_Send(welcome, sizeof(welcome) - 1);//测试 // 检查是否收到完整的一帧数据 if (com1_recv_end_flag 1) { com1_recv_end_flag 0; // 清除标志 // 这里处理收到的数据例如原样发回回声测试 if (com1_rx_len 0) { DMA_USART1_Send(com1_rx_buffer, com1_rx_len); } // 可选清空接收缓冲或打印长度 com1_rx_len 0; } Delay_ms(1000); } }使这个USART_ClearITPendingBit(USART1,USART_IT_IDLE);清除IDLE标志位。当我们使用上口助手发送信息的时候它会一直。输出这个标志位。主函数并不能正常打印内容。并在说明卡在中断服务函数中。我们改成代码发现可以正常输出了。uint32_t temp USART1-DR; 和 USART_ReceiveData(USART1);是同样的作用都是读Dr寄存器这里只是为了直观一点。void USART1_IRQHandler(void) //串口1中断服务程序 { /* 使用串口DMA空闲接收 */ if(USART_GetITStatus(USART1,USART_IT_IDLE)!RESET) //空闲中断触发 { com1_recv_end_flag1; //接收完成标志 //uint32_t temp USART1-SR; // 读SR清除IDLE所需 USART_GetITStatus(USART1,USART_IT_IDLE)!RESET也会读取所以不需要在读 uint32_t temp USART1-DR; // 读DR同时清除RXNE //USART_ReceiveData(USART1);//清除RXNE标志位接收函数有清标志位的作用 //USART_ClearITPendingBit(USART1,USART_IT_IDLE);//不可以这样 DMA_Cmd(DMA1_Channel5, DISABLE); /* 暂时关闭dma数据尚未处理 */ com1_rx_len USART_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel5); DMA_SetCurrDataCounter(DMA1_Channel5,USART_MAX_LEN); DMA_Cmd(DMA1_Channel5, ENABLE); /*打开DMADMA检查RXNE位来控制搬运*/ if(USART_GetITStatus(USART1,USART_IT_IDLE)!RESET) { DMA_USART1_Send(LDLE, sizeof(LDLE) - 1);//测试 }else{ DMA_USART1_Send(RLDLE, sizeof(RLDLE) - 1);//测试 } } }完整代码#include UARTDMA.h #include string.h volatile uint16_t com1_rx_len 0; //接收帧数据的长度 volatile uint8_t com1_recv_end_flag 0; //帧数据接收完成标 uint8_t com1_rx_buffer[USART_MAX_LEN]{0};//接收数据缓存 uint8_t DMA_USART1_TX_BUF[200]; //发送数据缓存 void USART1_Config(u32 bound)//同时配置接收和发送 { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; //1时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //2GPIO USART1_TX GPIOA.9 GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; //PA.9 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; //复用推挽输出 GPIO_Init(GPIOA, GPIO_InitStructure);//初始化GPIOA.9 //USART1_RX GPIOA.10初始化 GPIO_InitStructure.GPIO_Pin GPIO_Pin_10;//PA10 GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING;//浮空输入 GPIO_Init(GPIOA, GPIO_InitStructure);//初始化GPIOA.10 USART_DeInit(USART1); //3中断 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority2 ;//抢占优先级2 NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; //子优先级1 NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; //IRQ通道使能 NVIC_Init(NVIC_InitStructure); //根据指定的参数初始化NVIC寄存器 NVIC_InitStructure.NVIC_IRQChannel DMA1_Channel4_IRQn; //通道DMA1_Channel4_IRQn NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 2; //抢占优先级为 2 NVIC_InitStructure.NVIC_IRQChannelSubPriority 43; //响应优先级为 7 NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; //通道中断使能 NVIC_Init(NVIC_InitStructure);//通知cpu所有数据已经复制完成可以处理了。或者传输中发生错误 NVIC_InitStructure.NVIC_IRQChannel DMA1_Channel5_IRQn ;//串口1接收中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 2; //抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority 53; //子优先级 NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; //IRQ通道使能 NVIC_Init(NVIC_InitStructure);//防止溢出触发时机当DMA接收的数据量达到预设的缓冲大小代码中是 USART_MAX_LEN时。 //4配置 USART设置 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_ITConfig(USART1, USART_IT_IDLE, ENABLE); USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); USART_Cmd(USART1,ENABLE); DMA_InitTypeDef DMA_Initstructure; /*开启DMA时钟*/ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); DMA_DeInit(DMA1_Channel5); /*DMA配置*/ DMA_Initstructure.DMA_PeripheralBaseAddr (u32)(USART1-DR);; DMA_Initstructure.DMA_MemoryBaseAddr (u32)com1_rx_buffer; DMA_Initstructure.DMA_DIR DMA_DIR_PeripheralSRC;//外设 → 内存 DMA_Initstructure.DMA_BufferSize USART_MAX_LEN; DMA_Initstructure.DMA_PeripheralInc DMA_PeripheralInc_Disable;//外设地址不递增 DMA_Initstructure.DMA_MemoryInc DMA_MemoryInc_Enable;//内存地址递增 DMA_Initstructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte;//数据宽度外设和内存都是 1 字节 DMA_Initstructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; DMA_Initstructure.DMA_Mode DMA_Mode_Normal;//模式Normal单次模式。传输完 USART_MAX_LEN 个字节后DMA 停止工作不再响应新的串口数据。需要时再手动重新启动 DMA。 DMA_Initstructure.DMA_Priority DMA_Priority_High;//DMA 仲裁优先级 DMA_Initstructure.DMA_M2M DMA_M2M_Disable;//禁止内存到内存模式因为外设→内存不是内存→内存。 DMA_Init(DMA1_Channel5,DMA_Initstructure); DMA_InitTypeDef DMA_InitStructure; DMA_DeInit(DMA1_Channel4); //初始化DMA1 /* 配置DMA1 USART1发送 */ DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)(USART1-DR); DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)DMA_USART1_TX_BUF; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize 0; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; DMA_InitStructure.DMA_Priority DMA_Priority_VeryHigh; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel4, DMA_InitStructure);//初始化 //启动DMA DMA_Cmd(DMA1_Channel4,ENABLE); //开启DMA发送发成中断 DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE); USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); DMA_Cmd(DMA1_Channel5,ENABLE); } void USART1_IRQHandler(void) //串口1中断服务程序 { /* 使用串口DMA空闲接收 */ if(USART_GetITStatus(USART1,USART_IT_IDLE)!RESET) //空闲中断触发读取SR寄存器RX数据线空闲IDLE位不会再次被置高直到RXNE位被置起即又检测到一次空闲总线。防止TC中断进入接受处理的代码 { com1_recv_end_flag1; //接收完成标志 DMA_Cmd(DMA1_Channel5, DISABLE); /* 暂时关闭dma数据尚未处理 */ com1_rx_len USART_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel5);/* 获取接收到的数据长度 单位为字节CNDTR 寄存器Current Number of Data Transfer它表示还有多少个数据尚未传输。*/ DMA_SetCurrDataCounter(DMA1_Channel5,USART_MAX_LEN);/* 重新赋值计数值必须大于等于最大可能接收到的数据帧数目 */ //uint32_t temp USART1-SR; // 读SR清除IDLE所需USART_GetITStatus(USART1,USART_IT_IDLE)!RESET也会读取所以不需要在读 uint32_t temp USART1-DR; // 读DR同时清除RXNE //USART_ReceiveData(USART1);//清除RXNE标志位接收函数有清标志位的作用 //USART_ClearITPendingBit(USART1,USART_IT_IDLE);//清除空闲标准位IDLE ,不可以这样使用IDLE位由软件序列清除该位(先读USART_SR然后读USART_DR) DMA_Cmd(DMA1_Channel5, ENABLE); /*打开DMADMA检查RXNE位来控制搬运*/ } /* 检查DMA发送完成关闭TC标志位 */ //if(USART_GetFlagStatus(USART1,USART_IT_TXE)RESET) //串口发送完成 if(USART_GetFlagStatus(USART1,USART_IT_TC)RESET) //串口发送完成我的改正 { USART_ITConfig(USART1,USART_IT_TC,DISABLE); } } void DMA1_Channel4_IRQHandler(void) { if(DMA_GetITStatus(DMA1_IT_TC4))//DMA搬运完成传输完成中断 { DMA_ClearITPendingBit(DMA1_IT_TC4); // 清除传输完成中断标志位 DMA_Cmd(DMA1_Channel4,DISABLE); DMA1_Channel4-CNDTR0; // 清除数据长度 USART_ITConfig(USART1,USART_IT_TC,ENABLE); //打开串口发送完成中断 } } void DMA_USART1_Send(uint8_t *data,u16 size)//串口1DMA发送函数,改进死循环风险 { while (DMA_GetCurrDataCounter(DMA1_Channel4)); // 检查DMA发送通道内是否还有数据等待上次数据发送完成 DMA_Cmd(DMA1_Channel4, DISABLE); memcpy(DMA_USART1_TX_BUF, data, size); DMA_SetCurrDataCounter(DMA1_Channel4, size); // 重新写入要传输的数据数量 DMA_Cmd(DMA1_Channel4, ENABLE); // 启动DMA发送 } //void DMA_USART1_Send(uint8_t *data,u16 size)//串口1DMA发送函数 //{ // DMA_Cmd(DMA1_Channel4, DISABLE);//这里关闭了通道4while (DMA_GetCurrDataCounter(...)) 可能会陷入死循环因为硬件发送还在进行CNDTR 不会自动减少 // memcpy(DMA_USART1_TX_BUF, data, size); // while (DMA_GetCurrDataCounter(DMA1_Channel4)); // 检查DMA发送通道内是否还有数据等待上次数据发送完成 // DMA_SetCurrDataCounter(DMA1_Channel4, size); // 重新写入要传输的数据数量 // DMA_Cmd(DMA1_Channel4, ENABLE); // 启动DMA发送 //}#ifndef __UARTDMA_H #define __UARTDMA_H #include stm32f10x.h #include stdio.h #include string.h #define USART_MAX_LEN 200 void USART1_Config(u32 bound); void DMA_USART1_Send(uint8_t *data,u16 size); #endif#include stm32f10x.h // Device header #include Delay.h #include UARTDMA.h extern volatile uint16_t com1_rx_len; //接收帧数据的长度 extern volatile uint8_t com1_recv_end_flag; //帧数据接收完成标 extern uint8_t com1_rx_buffer[USART_MAX_LEN];//接收数据缓存 int main(void) { USART1_Config(115200); uint8_t welcome[] STM32F103 DMA UART Demo Ready!\r\n; DMA_USART1_Send(welcome, sizeof(welcome) - 1); while (1) { DMA_USART1_Send(welcome, sizeof(welcome) - 1);//测试 // 检查是否收到完整的一帧数据 if (com1_recv_end_flag 1) { com1_recv_end_flag 0; // 清除标志 // 这里处理收到的数据例如原样发回回声测试 if (com1_rx_len 0) { DMA_USART1_Send(com1_rx_buffer, com1_rx_len); } // 可选清空接收缓冲或打印长度 com1_rx_len 0; } } }