RS485通讯中使用STM32串口以DMA方式发送数据丢失字节的问题
2018-06-08 来源:eefocus
1、开发平台
计算机操作系统:WIN7 64位;
开发环境:Keil MDK 5.14;
MCU:STM32F407ZET6;
STM32F4xx固件库:STM32F4xx_DSP_StdPeriph_Lib_V1.4.0;
串口调试助手;
2、问题描述
在测试用STM32F4xx芯片的串口USART1以DMA方式进行RS485收发通讯时,出现数据字节丢失的现象,一般丢失1~2个字节。
出现问题时测试的简单收发机制:使能串口USART1的DMA收发功能,开启了DMA发送完成中断和USART1空闲中断。通过串口调试助手发送N个字节给MCU,当MCU产生USART1空闲中断时,在USART1空闲中断服务程序中将DMA接收到的N个字节数据从接收缓存拷贝到发送缓存,准备好数据后,RS485切换为发送模式,通过启动一次DMA发送,将N个字节数据原样回送到串口调试助手。最后,在DMA发送完成中断服务程序中判断到有DMA发送完成标志TCIF7置位时,立即将RS485再次切换为接收模式。
3、原因分析
在STM32F4xx英文参考手册(RM0090)中,USART章节的使用DMA发送小节给出了如下时序图:
由图可见,当DMA将第3个字节Frame 3写到USART数据寄存器USART_DR时,TX线上才刚准备出现第2个字节Frame 2的时序,并且DMA发送完成中断标志在TX线还未出现第2个字节Frame 2时序时就由硬件置1了,所以,如果软件中在DMA发送完成中断服务程序中检测到DMA TCIF标志置1后马上将RS485切换为接收模式,则后面的字节数据将会丢失。
所以,需要让数据字节不丢失的话,必须让所有字节(包括字节的停止位)在TX线上稳定发送完成后,才能将RS485切换为接收模式。
4、解决方法
如上图所示,有一个关键点是:当所有字节(包括字节的停止位)在TX线上稳定发送完成后,串口发送完成标志(TC flag)置1。所以,有两个解决方法:
方法一:用DMA发送完成中断,不用USART1发送完成中断。在DMA发送完成中断服务程序中检测到有TCIF7置1时,再等待USART1发送完成标志TC置1,直到USART1发送完成标志TC置1后,清零USART1发送完成标志TC,然后再将RS485切换为接收模式。
方法二:用USART1发送完成中断,不用DMA发送完成中断。在USART1中断服务程序USART1_IRQHandler()中,检测到有USART1发送完成标志TC置1时,清零USART1发送完成标志TC,并且要清零DMA发送完成标志DMA_FLAG_TCIF7,最好同时清零DMA_FLAG_FEIF7、DMA_FLAG_DMEIF7、DMA_FLAG_TEIF7 、DMA_FLAG_HTIF7,然后再将RS485切换为接收模式。
5、参考源代码
Usart.h头文件
/*----------------------------------------------------------------------------------------------------
*Copyright: SXD Tech. Co., Ltd.
*开发 环境: Keil MDK 5.14 && STM32F407ZET6
*文件 名称: USART串行通信驱动头文件
*作 者: 顺信德
*版 本: V1.0
*日 期: 2018-2-6
*说 明:
*修改 日志: (1)
----------------------------------------------------------------------------------------------------*/
#ifndef _USART_H_
#define _USART_H_
#include 'Global.h'
/*---------------------------------------------宏定义(S)---------------------------------------------*/
#define RS485_Recv(); {PFout(11)=0;} //SP485接收模式,低电平有效
#define RS485_Send(); {PFout(11)=1;} //SP485发送模式,高电平有效
#define USART1_SEND_MAXLEN 512 /*串口1最大发送字节长度*/
#define USART1_RECV_MAXLEN 512 /*串口1最大接收字节长度*/
/*---------------------------------------------宏定义(E)---------------------------------------------*/
/*--------------------------------------------端口定义(S)--------------------------------------------*/
/*--------------------------------------------端口定义(E)--------------------------------------------*/
/*--------------------------------------------变量声明(S)--------------------------------------------*/
extern u32 G_u32RS485BaudRate; //RS485通讯波特率
extern u8 G_u8Usart1SendBuf[USART1_SEND_MAXLEN]; //发送数据缓冲区
extern u8 G_u8Usart1RecvBuf[USART1_RECV_MAXLEN]; //接收数据缓冲区
extern u16 G_u16CommRecvLen; //通讯接收的一帧数据长度
/*--------------------------------------------变量声明(E)--------------------------------------------*/
/*--------------------------------------------函数声明(S)--------------------------------------------*/
extern void USART1_Init(u32 baud); //USART1串口初始化
extern void USART_DMA_SendStart(DMA_Stream_TypeDef *DMA_Streamx, u16 m_u16SendCnt); //串口USART1启动一次DMA传输
extern void USART1_DMA_SendNByte(u8 *m_pSendBuf, u16 m_u16SendCnt); //串口USART1以DMA方式发送多字节
/*--------------------------------------------函数声明(E)--------------------------------------------*/
#endif
Usart.c源文件
方法一:用DMA发送完成中断
/*----------------------------------------------------------------------------------------------------
*Copyright: SXD Tech. Co., Ltd.
*开发 环境: Keil MDK 5.14 && STM32F407ZET6
*文件 名称: USART串行通信驱动源文件
*作 者: 顺信德
*版 本: V1.0
*日 期: 2018-2-6
*说 明:
*修改 日志: (1)
----------------------------------------------------------------------------------------------------*/
#include 'Usart.h'
/*--------------------------------------------变量定义(S)--------------------------------------------*/
u32 G_u32RS485BaudRate = 9600; //RS485通讯波特率
u8 G_u8Usart1SendBuf[USART1_SEND_MAXLEN]={0,}; //发送数据缓冲区
u8 G_u8Usart1RecvBuf[USART1_RECV_MAXLEN]={0,}; //接收数据缓冲区
u16 G_u16CommRecvLen=0; //通讯接收的一帧数据长度
/*--------------------------------------------变量定义(E)--------------------------------------------*/
/*--------------------------------------------函数声明(S)--------------------------------------------*/
void USART1_Init(u32 baud); //USART1串口初始化
void USART_DMA_SendStart(DMA_Stream_TypeDef *DMA_Streamx, u16 m_u16SendCnt); //串口USART1启动一次DMA传输
void USART1_DMA_SendNByte(u8 *m_pSendBuf, u16 m_u16SendCnt); //串口USART1以DMA方式发送多字节
/*--------------------------------------------函数声明(E)--------------------------------------------*/
/*----------------------------------------------------------------------------------------------------
*函数名称:void USART1_Init(u32 baud)
*函数功能:USART1串口初始化函数
*入口参数:u32 baud - 波特率(单位bps)
*出口参数:无
*说 明:用于RS485通信;
----------------------------------------------------------------------------------------------------*/
void USART1_Init(u32 baud)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
u16 mid_u16RetryCnt = 0;
USART_DeInit(USART1);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //使能USART1时钟
//USART1对应引脚复用映射
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); //GPIOA9复用为USART1_TX
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); //GPIOA10复用为USART1_RX
//USART1端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz; //速度25MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9,PA10
//USART1初始化设置
USART_InitStructure.USART_BaudRate = baud; //波特率设置
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); //初始化USART1
USART_Cmd(USART1, ENABLE); //使能USART1
USART_ClearFlag(USART1, USART_FLAG_TC); //清除发送完成标志
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); //等待空闲帧发送完成后再清零发送完成标志
USART_ClearFlag(USART1, USART_FLAG_TC); //清除发送完成标志
USART_ITConfig(USART1, USART_IT_TC, DISABLE); //禁止USART1传输完成中断
USART_ITConfig(USART1, USART_IT_RXNE, DISABLE); //禁止USART1接收不为空中断
USART_ITConfig(USART1, USART_IT_TXE, DISABLE); //禁止USART1发送空中断
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //开启USART1空闲中断
//USART1 NVIC配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3; //抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure);
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); //使能串口1的DMA发送
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); //使能串口1的DMA接收
// - USART1发送DMA配置
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE); //DMA2时钟使能
DMA_DeInit(DMA2_Stream7);
while ((DMA_GetCmdStatus(DMA2_Stream7) != DISABLE) && (mid_u16RetryCnt++ < 500)); //等待DMA可配置
//配置DMA2_Stream7
DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR; //DMA外设地址
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)G_u8Usart1SendBuf; //DMA 存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //存储器到外设模式
DMA_InitStructure.DMA_BufferSize = USART1_SEND_MAXLEN; //数据传输量
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;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //存储器突发单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //外设突发单次传输
DMA_Init(DMA2_Stream7, &DMA_InitStructure); //初始化DMA Stream
//DMA2_Stream7的NVIC配置
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
DMA_ClearITPendingBit(DMA2_Stream7, DMA_IT_TCIF7); //清除DMA发送完成中断标志
DMA_ITConfig(DMA2_Stream7, DMA_IT_TC, ENABLE); //使能DMA发送完成中断
DMA_Cmd(DMA2_Stream7, ENABLE); //使能DMA2_Stream7
// - USART1接收DMA配置
mid_u16RetryCnt = 0;
DMA_DeInit(DMA2_Stream5);
while ((DMA_GetCmdStatus(DMA2_Stream5) != DISABLE) && (mid_u16RetryCnt++ < 500)); //等待DMA可配置
//配置DMA2_Stream5
DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR; //DMA外设地址
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)G_u8Usart1RecvBuf; //DMA 存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; //外设到存储器模式
DMA_InitStructure.DMA_BufferSize = USART1_RECV_MAXLEN; //数据传输量
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;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //存储器突发单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //外设突发单次传输
DMA_Init(DMA2_Stream5, &DMA_InitStructure); //初始化DMA Stream
DMA_Cmd(DMA2_Stream5, ENABLE); //使能DMA2_Stream5
}
/*--------------------------------------------------------------------------------------
函数名称:void USART_DMA_SendStart(DMA_Stream_TypeDef *DMA_Streamx, u16 m_u16SendCnt)
函数功能:串口USART1启动一次DMA传输函数
入口参数:DMA_Stream_TypeDef DMA_Streamx - DMA数据流(DMA1_Stream0~7/DMA2_Stream0~7);
u16 m_u16SendCnt - 待传输数据字节数
出口参数:无
说 明:无
---------------------------------------------------------------------------------------*/
void USART_DMA_SendStart(DMA_Stream_TypeDef *DMA_Streamx, u16 m_u16SendCnt)
{
u16 l_u16RetryCnt = 0;
DMA_Cmd(DMA_Streamx, DISABLE); //关闭DMA传输
while ((DMA_GetCmdStatus(DMA_Streamx) != DISABLE) && (l_u16RetryCnt++ < 500)); //等待DMA可配置
DMA_SetCurrDataCounter(DMA_Streamx, m_u16SendCnt); //数据传输量
DMA_Cmd(DMA_Streamx, ENABLE); //开启DMA传输
}
/*--------------------------------------------------------------------------------------
函数名称:void USART1_DMA_SendNByte(u8 *m_pSendBuf, u16 m_u16SendCnt)
函数功能:串口USART1以DMA方式发送多字节函数
入口参数:u8 *m_pSendBuf - 待发送数据缓存, u16 m_u16SendCnt - 待发送数据个数
出口参数:无
说 明:无
---------------------------------------------------------------------------------------*/
void USART1_DMA_SendNByte(u8 *m_pSendBuf, u16 m_u16SendCnt)
{
memcpy(G_u8Usart1SendBuf, m_pSendBuf, m_u16SendCnt);
USART_DMA_SendStart(DMA2_Stream7, m_u16SendCnt); //启动一次DMA传输
}
/*--------------------------------------------------------------------------------------
函数名称:void DMA2_Stream7_IRQHandler(void)
函数功能:串口USART1以DMA方式发送完成中断服务程序
入口参数:无
出口参数:无
说 明:无
---------------------------------------------------------------------------------------*/
void DMA2_Stream7_IRQHandler(void)
{
if(DMA_GetFlagStatus(DMA2_Stream7, DMA_FLAG_TCIF7) != RESET) //DMA发送完成?
{
DMA_ClearFlag(DMA2_Stream7, DMA_FLAG_TCIF7 | DMA_FLAG_FEIF7 |
DMA_FLAG_DMEIF7 | DMA_FLAG_TEIF7 | DMA_FLAG_HTIF7); //清除标志位
while(!USART_GetFlagStatus(USART1, USART_FLAG_TC)); //等待USART1发送完成标志TC置1
USART_ClearFlag(USART1, USART_FLAG_TC); //清除发送完成标志
RS485_Recv(); //切换为RS485接收模式
}
}
/*--------------------------------------------------------------------------------------
函数名称:void USART1_IRQHandler(void)
函数功能:USART串口1中断服务程序
入口参数:无
出口参数:无
说 明:无
---------------------------------------------------------------------------------------*/
void USART1_IRQHandler(void)
{
u16 l_u16Temp = 0;
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) //若有空闲中断
{
DMA_Cmd(DMA2_Stream5, DISABLE); //关闭DMA2_Stream5,防止处理期间有数据
DMA_ClearFlag(DMA2_Stream5, DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 |
DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5); //清除标志位
//清除USART总线空闲中断标志(只要读USART1->SR和USART1->DR即可)
l_u16Temp = USART1->SR;
l_u16Temp = USART1->DR;
G_u16CommRecvLen = USART1_RECV_MAXLEN - DMA_GetCurrDataCounter(DMA2_Stream5); //求出接收到数据的字节数
if(G_u16CommRecvLen <= USART1_RECV_MAXLEN)
{
RS485_Send(); //RS485发送模式
USART1_DMA_SendNByte(G_u8Usart1RecvBuf, G_u16CommRecvLen); //回送接收到的数据
}
DMA_SetCurrDataCounter(DMA2_Stream5, USART1_RECV_MAXLEN); //设置传输数据长度
DMA_Cmd(DMA2_Stream5, ENABLE); //使能DMA2_Stream5
}
}
方法二:用USART1发送完成中断
/*----------------------------------------------------------------------------------------------------
*Copyright: SXD Tech. Co., Ltd.
*开发 环境: Keil MDK 5.14 && STM32F407ZET6
*文件 名称: USART串行通信驱动源文件
*作 者: 顺信德
*版 本: V1.0
*日 期: 2018-2-6
*说 明:
*修改 日志: (1)
----------------------------------------------------------------------------------------------------*/
#include 'Usart.h'
/*--------------------------------------------变量定义(S)--------------------------------------------*/
u32 G_u32RS485BaudRate = 9600; //RS485通讯波特率
u8 G_u8Usart1SendBuf[USART1_SEND_MAXLEN]={0,}; //发送数据缓冲区
u8 G_u8Usart1RecvBuf[USART1_RECV_MAXLEN]={0,}; //接收数据缓冲区
u16 G_u16CommRecvLen=0; //通讯接收的一帧数据长度
/*--------------------------------------------变量定义(E)--------------------------------------------*/
/*--------------------------------------------函数声明(S)--------------------------------------------*/
void USART1_Init(u32 baud); //USART1串口初始化
void USART_DMA_SendStart(DMA_Stream_TypeDef *DMA_Streamx, u16 m_u16SendCnt); //串口USART1启动一次DMA传输
void USART1_DMA_SendNByte(u8 *m_pSendBuf, u16 m_u16SendCnt); //串口USART1以DMA方式发送多字节
/*--------------------------------------------函数声明(E)--------------------------------------------*/
/*----------------------------------------------------------------------------------------------------
*函数名称:void USART1_Init(u32 baud)
*函数功能:USART1串口初始化函数
*入口参数:u32 baud - 波特率(单位bps)
*出口参数:无
*说 明:用于RS485通信;
----------------------------------------------------------------------------------------------------*/
void USART1_Init(u32 baud)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
u16 mid_u16RetryCnt = 0;
USART_DeInit(USART1);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //使能USART1时钟
//USART1对应引脚复用映射
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); //GPIOA9复用为USART1_TX
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); //GPIOA10复用为USART1_RX
//USART1端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz; //速度25MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9,PA10
//USART1初始化设置
USART_InitStructure.USART_BaudRate = baud; //波特率设置
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); //初始化USART1
USART_Cmd(USART1, ENABLE); //使能USART1
USART_ClearFlag(USART1, USART_FLAG_TC); //清除发送完成标志
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); //等待空闲帧发送完成后再清零发送完成标志
USART_ClearFlag(USART1, USART_FLAG_TC); //清除发送完成标志
USART_ITConfig(USART1, USART_IT_TC, ENABLE); //使能USART1传输完成中断
USART_ITConfig(USART1, USART_IT_RXNE, DISABLE); //禁止USART1接收不为空中断
USART_ITConfig(USART1, USART_IT_TXE, DISABLE); //禁止USART1发送空中断
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //开启USART1空闲中断
//USART1 NVIC配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3; //抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure);
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); //使能串口1的DMA发送
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); //使能串口1的DMA接收
// - USART1发送DMA配置
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE); //DMA2时钟使能
DMA_DeInit(DMA2_Stream7);
while ((DMA_GetCmdStatus(DMA2_Stream7) != DISABLE) && (mid_u16RetryCnt++ < 500)); //等待DMA可配置
//配置DMA2_Stream7
DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR; //DMA外设地址
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)G_u8Usart1SendBuf; //DMA 存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //存储器到外设模式
DMA_InitStructure.DMA_BufferSize = USART1_SEND_MAXLEN; //数据传输量
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;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //存储器突发单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //外设突发单次传输
DMA_Init(DMA2_Stream7, &DMA_InitStructure); //初始化DMA Stream
DMA_Cmd(DMA2_Stream7, ENABLE); //使能DMA2_Stream7
// - USART1接收DMA配置
mid_u16RetryCnt = 0;
DMA_DeInit(DMA2_Stream5);
while ((DMA_GetCmdStatus(DMA2_Stream5) != DISABLE) && (mid_u16RetryCnt++ < 500)); //等待DMA可配置
//配置DMA2_Stream5
DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR; //DMA外设地址
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)G_u8Usart1RecvBuf; //DMA 存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; //外设到存储器模式
DMA_InitStructure.DMA_BufferSize = USART1_RECV_MAXLEN; //数据传输量
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;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //存储器突发单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //外设突发单次传输
DMA_Init(DMA2_Stream5, &DMA_InitStructure); //初始化DMA Stream
DMA_Cmd(DMA2_Stream5, ENABLE); //使能DMA2_Stream5
}
/*--------------------------------------------------------------------------------------
函数名称:void USART_DMA_SendStart(DMA_Stream_TypeDef *DMA_Streamx, u16 m_u16SendCnt)
函数功能:串口USART1启动一次DMA传输函数
入口参数:DMA_Stream_TypeDef DMA_Streamx - DMA数据流(DMA1_Stream0~7/DMA2_Stream0~7);
u16 m_u16SendCnt - 待传输数据字节数
出口参数:无
说 明:无
---------------------------------------------------------------------------------------*/
void USART_DMA_SendStart(DMA_Stream_TypeDef *DMA_Streamx, u16 m_u16SendCnt)
{
u16 l_u16RetryCnt = 0;
DMA_Cmd(DMA_Streamx, DISABLE); //关闭DMA传输
while ((DMA_GetCmdStatus(DMA_Streamx) != DISABLE) && (l_u16RetryCnt++ < 500)); //等待DMA可配置
DMA_SetCurrDataCounter(DMA_Streamx, m_u16SendCnt); //数据传输量
DMA_Cmd(DMA_Streamx, ENABLE); //开启DMA传输
}
/*--------------------------------------------------------------------------------------
函数名称:void USART1_DMA_SendNByte(u8 *m_pSendBuf, u16 m_u16SendCnt)
函数功能:串口USART1以DMA方式发送多字节函数
入口参数:u8 *m_pSendBuf - 待发送数据缓存, u16 m_u16SendCnt - 待发送数据个数
出口参数:无
说 明:无
---------------------------------------------------------------------------------------*/
void USART1_DMA_SendNByte(u8 *m_pSendBuf, u16 m_u16SendCnt)
{
memcpy(G_u8Usart1SendBuf, m_pSendBuf, m_u16SendCnt);
USART_DMA_SendStart(DMA2_Stream7, m_u16SendCnt); //启动一次DMA传输
}
/*--------------------------------------------------------------------------------------
函数名称:void USART1_IRQHandler(void)
函数功能:USART串口1中断服务程序
入口参数:无
出口参数:无
说 明:无
---------------------------------------------------------------------------------------*/
void USART1_IRQHandler(void)
{
u16 l_u16Temp = 0;
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) //若有空闲中断
{
DMA_Cmd(DMA2_Stream5, DISABLE); //关闭DMA2_Stream5,防止处理期间有数据
DMA_ClearFlag(DMA2_Stream5, DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 |
DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5);//清零标志位
//清除USART总线空闲中断标志(只要读USART1->SR和USART1->DR即可)
l_u16Temp = USART1->SR;
l_u16Temp = USART1->DR;
G_u16CommRecvLen = USART1_RECV_MAXLEN - DMA_GetCurrDataCounter(DMA2_Stream5); //求出接收到数据的字节数
if(G_u16CommRecvLen <= USART1_RECV_MAXLEN)
{
RS485_Send(); //RS485发送模式
USART1_DMA_SendNByte(G_u8Usart1RecvBuf, G_u16CommRecvLen);
}
DMA_SetCurrDataCounter(DMA2_Stream5, USART1_RECV_MAXLEN); //设置传输数据长度
DMA_Cmd(DMA2_Stream5, ENABLE); //使能DMA2_Stream5
}
if(USART_GetITStatus(USART1, USART_IT_TC) != RESET) //若有发送完成中断
{
USART_ClearITPendingBit(USART1, USART_IT_TC); //清除USART1发送完成中断标志
DMA_ClearFlag(DMA2_Stream7, DMA_FLAG_TCIF7 | DMA_FLAG_FEIF7 |
DMA_FLAG_DMEIF7 | DMA_FLAG_TEIF7 | DMA_FLAG_HTIF7);//清零标志位
RS485_Recv(); //切换为RS485接收模式
}
}
6、声明
本程序的收发机制只是简单的处理机制,只是为了说明解决数据丢失字节问题的方法,对进行快速大数据通讯时会出现乱码。所以,用于实际项目中,需对此程序的收发处理机制进行重新设计。两种方法中,个人认为方法二更好,因为方法一在中断里面等待白白耗费了时间。
- Modbus通讯协议4-20ma/0-10v转RS485数字模拟信号隔离AD转换模块
- 永宏FBs系列PLC的RS485通讯方式介绍
- STM32之RS485通讯方式实现
- PLC、RS485、变频器通讯接线图详解
- 三菱FX5U系列PLC采用RS485总线进行modbus通讯
- N76E003 之 RS485 通讯
- 8051/2单片机常用的本地通讯方式 UART,RS485,I2C,SPI 之 Modbus 485 2
- 8051/2单片机常用的本地通讯方式 UART,RS485,I2C,SPI 之 I2C E2PROM 3
- 8051/2单片机常用的本地通讯方式 UART,RS485,I2C,SPI 之 SPI 25AA040A 读写 E2PROM 4
- 8051/2单片机常用的本地通讯方式 UART,RS485,I2C,SPI 之 UART串口通讯 1