GD32F103RB串口DMA IDLE空闲中断配置指南
串口通信有几种方式:
1、Rx来一个字节触发一次串口中断,读取字节,同时根据协议判断,属于什么帧,对应要执行什么操作;
void USART1_IRQHandler(void)
{
unsigned char Rec_Data;
if(USART_GetITStatus(USART1, USART_IT_RXNE)!=RESET)
{
if(USART_GetFlagStatus(USART1, USART_FLAG_PE)==RESET)//奇偶校验
{
Rec_Data= USART_ReceiveData(USART1);
UART1_receive(Rec_Data);
//UART_receive(com1, Rec_Data);
}
else
{
Rec_Data= USART_ReceiveData(USART1);//如果出现校验错误,只读UART1_DR,清除PE标志,不保存数据
}
}
}
void UART1_receive(unsigned char char_buf)
{
switch(com1.ptr)
{
//-------------------
case '0':
break;
//-------------------
case '1':
if(char_buf==DLE)
{
com1.ptr ='2';
break;
}
else
{
break;//
}
//-------------------
case '2':
switch(char_buf)
{
case STX: //STX?
com1.ptr ='3';
com1.F_STX=nCLEAR;
com1.F_resend=CLEAR;
com1.F_EOT=CLEAR;
com1.length=0;
break;
case EOT: //EOT?
com1.ptr ='0';
com1.F_resend=CLEAR;
com1.F_EOT=nCLEAR;
com1.valid = nCLEAR;
break;
case ENQ: //ENQ?
com1.ptr ='0';
com1.F_ENQ=nCLEAR;
com1.valid = nCLEAR;
break;
case DLE:
com1.ptr ='2';
break;
default:
com1.ptr='1';
break;
}
break;
//-------------------
case '3':
if(char_buf==DLE)
{
com1.ptr='4';
}
else //received one data
{
com1.buf[com1.length]=char_buf;//C开始
com1.bcc^=char_buf;
if(com1.length<COMMAX)
{
com1.length++;
}
}
break;
//-------------------
case '4':
switch(char_buf)
{
case ETX: //ETX?
com1.ptr ='5';
com1.bcc^=char_buf;
com1.F_ETX=nCLEAR;
break;
case DLE: //data=10h?
com1.ptr ='3';
com1.buf[com1.length]=char_buf;
com1.bcc^=char_buf;
if(com1.length<COMMAX)
{
com1.length++;
}
break;
default: //found 10 xx(NAK structure error)
com1.ptr='0';
com1.F_NAK=nCLEAR;
break;
}
break;
//-------------------
case '5': //com1.com1.bcc check
com1.F_BCC=nCLEAR;
if(com1.bcc ==char_buf)
{
com1.ptr='0';
com1.F_CMD=nCLEAR;
com1.valid = nCLEAR;
break;
}
else
{
com1.ptr='0';
com1.F_NAK=nCLEAR;
com1.valid = nCLEAR;
break;
}
//-------------------
default: //error routine
break;
}
}
2、串口FIFO,较少中断触发次数;(有空再展开讲)
3、本文章的主角:采用DMA省去cpu处理,IDLE接收不定长的数据包;以下代码并非直接copy就可用,根据自己的理解实现自己代码。
(1)串口初始化主要完成以下几个事情:
使能了串口IDLE中断,使能串口收发,使能串口收发DMA;
DMA配置:收发都使能,禁止收发循环,发送端的DMA中断;
static uint32_t USART_Num[COM_n] = {USART2};
static uint32_t DMA_Num[COM_n] = {DMA0}; //uasrt2 属于DMA0
static rcu_periph_enum DMA_RCU[COM_n] = {RCU_DMA0}; //uasrt2的DMA0时钟
static dma_channel_enum DMA_Chl_Tx[COM_n] = {DMA_CH1};//uasrt2 的发送口 属于DMA0的通道1
static dma_channel_enum DMA_Chl_Rx[COM_n] = {DMA_CH2};//uasrt2 的接收口 属于DMA0的通道2
static IRQn_Type DMA_IRQn_Tx[COM_n] = {DMA0_Channel1_IRQn};//DMA发送中断入口函数
static uint32_t COM_GPIO_PORT[COM_n] = {GPIOB};
static rcu_periph_enum COM_GPIO_CLK[COM_n] = {RCU_GPIOB};
static rcu_periph_enum USART_PERIPH_CLK[COM_n] = {RCU_USART2}; //USART模块时钟
static rcu_periph_enum USART_TX_GPIO_CLK[COM_n] = {RCU_GPIOB}; //USART_TX引脚所在的GPIO端口时钟
static rcu_periph_enum USART_RX_GPIO_CLK[COM_n] = {RCU_GPIOB }; //USART_RX引脚所在的GPIO端口时钟
//static rcu_periph_enum USART_RS485_GPIO_CLK[COM_n] = {RCU_GPIOB};
static uint32_t USART_TX_PIN[COM_n] = {GPIO_PIN_10}; //USART_TX 管脚
static uint32_t USART_RX_PIN[COM_n] = {GPIO_PIN_11}; //USART_RX 管脚
static IRQn_Type USART_IRQn[COM_n] = {USART2_IRQn}; //USART中断号
void init_Uart(void)
{
USART_Init(COM0, 115200); //COM0 指的是数组第一个 配置初始化
MYDMA_Config(COM0, 168); //DMA初始化
}
void USART_Init(uint8_t uComID, uint32_t uBound)
{
//使能时钟
rcu_periph_clock_enable(RCU_AF);
rcu_periph_clock_enable( USART_PERIPH_CLK[ uComID ] );
rcu_periph_clock_enable( USART_TX_GPIO_CLK[ uComID ] );
rcu_periph_clock_enable( USART_RX_GPIO_CLK[ uComID ] );
//配置USART 的TXD、RXD 复用管脚
gpio_init(COM_GPIO_PORT[uComID], GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, USART_TX_PIN[ uComID ]);/* connect port to USARTx_Tx */
gpio_init(COM_GPIO_PORT[uComID], GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, USART_RX_PIN[uComID]);/* connect port to USARTx_Rx */
//设置串口工作模式和波特率
usart_deinit(USART_Num[uComID]);
SetUsartPara( USART_Num[uComID], uBound );
/* USART DMA enable for transmission and reception */
usart_receive_config(USART_Num[uComID], USART_RECEIVE_ENABLE);
usart_transmit_config(USART_Num[uComID], USART_TRANSMIT_ENABLE);
usart_dma_transmit_config(USART_Num[uComID], USART_DENT_ENABLE);
usart_dma_receive_config(USART_Num[uComID], USART_DENR_ENABLE);
usart_enable(USART_Num[uComID]);
//设置USART中断
usart_interrupt_enable( USART_Num[uComID], USART_INT_IDLE ); //使能了串口空闲接收中断
usart_interrupt_flag_clear( USART_Num[uComID], USART_INT_FLAG_TBE );
usart_interrupt_flag_clear( USART_Num[uComID], USART_INT_FLAG_TC );
nvic_irq_enable( USART_IRQn[ uComID ], 0U, 3U );//串口中断优先级设置
}
void MYDMA_Config(uint8_t uComID,uint16_t BuffSize)
{
dma_parameter_struct dma_init_struct;
dma_channel_enum uDMA_RXCHx,uDMA_TXCHx;
IRQn_Type nvic_irq;
uint32_t uDMAx;
uint32_t uRecvBuff,uSendBuff;
uint32_t usart_periph;
{
usart_periph = USART_Num[uComID];
uDMAx = DMA_Num[uComID];
uDMA_TXCHx = DMA_Chl_Tx[uComID]; //DMA发送模式:CH1
uDMA_RXCHx = DMA_Chl_Rx[uComID]; //DMA接收模式:CH2
nvic_irq = DMA_IRQn_Tx[uComID]; //DMA发送中断请求
uRecvBuff = (uint32_t)rx_buffer; //USART0接收数据缓冲区首地址
uSendBuff = (uint32_t)tx_buffer; //USART0发送数据缓冲区首地址
}
rcu_periph_clock_enable(DMA_RCU[uComID]); //使能DMA0时钟
//配置使用DMA接收数据
dma_deinit(uDMAx, uDMA_RXCHx);
dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY;
dma_init_struct.memory_addr = (uint32_t)uRecvBuff;
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
dma_init_struct.number = BuffSize;
dma_init_struct.periph_addr = (uint32_t)&USART_DATA(usart_periph);
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_init(uDMAx, uDMA_RXCHx, &dma_init_struct);
/* configure DMA mode */
dma_circulation_disable(uDMAx, uDMA_RXCHx);
dma_memory_to_memory_disable(uDMAx, uDMA_RXCHx);
usart_dma_receive_config(usart_periph, USART_DENR_ENABLE);
dma_channel_enable(uDMAx, uDMA_RXCHx);
//=======================================================
//配置使用DMA发送数据
dma_deinit(uDMAx, uDMA_TXCHx);
dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;
dma_init_struct.memory_addr = (uint32_t)uSendBuff;
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
dma_init_struct.number = BuffSize;
dma_init_struct.periph_addr = (uint32_t)&USART_DATA(usart_periph);
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_init(uDMAx, uDMA_TXCHx, &dma_init_struct);
/* configure DMA mode */
dma_circulation_disable(uDMAx, uDMA_TXCHx);//禁止DMA循环
dma_memory_to_memory_disable(uDMAx, uDMA_TXCHx);
usart_dma_transmit_config(usart_periph, USART_DENT_ENABLE);
dma_interrupt_enable(uDMAx, uDMA_TXCHx, DMA_CHXCTL_FTFIE); //DMA发送中断使能
nvic_irq_enable(nvic_irq, 0U, 3U); //DMA发送中断DMA0_Channel1_IRQn优先级设置
}
(2)中断处理
DMA0_Channel1_IRQHandler,串口发送完数据,后触发的中断;在中断做了两件事:1、清除相应标志位;2、做了一个特殊处理(这个特殊处理完全没有必要):使能了串口的TC中断,目的是需要确保数据已经完全发出,发送完,后拉高485电平(有的方案不需要),进行等待接收;DMA发送中断触发并不代表串口完全已经把数据发出。TC置1,才是已经发出。这里多此一举:讲讲TC和TBE的关系:TBE=1,表示USART_TDATA为空,即可以把你要发的数据给USART_TDATA赋值了,可以理解buf缓存寄存器,但是并没有发出(可能还处在移位寄存器);TC=1,表示TBE为空,移位寄存器也是空,即完全已经发出了。
/*
USART2的发送中断
*/
void DMA0_Channel1_IRQHandler(void)
{
if(dma_interrupt_flag_get(DMA_Num[COM0], DMA_CH1, DMA_INT_FLAG_FTF))
{
dma_interrupt_flag_clear(DMA_Num[COM0], DMA_CH1, DMA_INT_FLAG_FTF);
dma_channel_disable(DMA_Num[COM0], DMA_CH1);//禁止DMA发送中断 已经发完 需要发送的函数会重新开启DMA中断
usart_interrupt_enable(USART_Num[COM0], USART_INT_TC); //启动串口TC中断 为什么要与串口发送完中断互动?其实也不用这样操作 //想确保数据已经移位出去后,开启485引脚,进行等待接收的操作 这样更准确
}
}
void USART2_IRQHandler(void)
{
uint8_t uDataBuf;
uint16_t uLen;
uDataBuf = uDataBuf;
if(RESET != usart_interrupt_flag_get(USART_Num[COM0], USART_INT_FLAG_IDLE))
{
uDataBuf = usart_data_receive(USART_Num[COM0]); //IDLE的清除:1读SR;2读DR
dma_channel_disable(DMA_Num[COM0], DMA_Chl_Rx[COM0]); //禁止DMA接收中断
dma_interrupt_flag_clear(DMA_Num[COM0], DMA_Chl_Rx[COM0], DMA_INT_FLAG_FTF); //清楚标记
uLen = UART_DMA_SIZE - dma_transfer_number_get(DMA_Num[COM0], DMA_Chl_Rx[COM0]);
if( uLen < UART_DMA_SIZE )
{
g_uRecvLen[COM0] = uLen; //记录获取多少长度字节
g_uRecvDataOK[COM0] = 0x01; //标记接收成功
}
MYDMA_Enable(COM0, 0, UART_DMA_SIZE); //使能DMA 接收中断
}
if(usart_interrupt_flag_get(USART_Num[COM0], USART_INT_FLAG_TBE) == RESET)//始终是RESET因为没有使能TBE中断 虽然TBE置位了
{
usart_flag_clear(USART_Num[COM0],USART_FLAG_TC);//清除TC
usart_interrupt_disable(USART_Num[COM0], USART_INT_TC);
g_uSendFlag[COM0] = 0x00; //设置发送完成标志
//gpio_bit_reset( USART_RS485_GPIO_PORT[COM0], USART_RS485_PIN[COM0] ); //电平拉低 485等待接收
}
}
/*
这段参考:https://blog.csdn.net/luliplus/article/details/123190671
void USART0_sendStr(const char*msg)
{
uint32_t i;
usart_flag_clear(USART0,USART_FLAG_TC); //为了使用TC,在发送前要先清除TC
for(i=0;msg[i];i++)
{
while(!usart_flag_get(USART0,USART_FLAG_TBE)){} //等待TBE硬件置1
usart_data_transmit(USART0,msg[i]); //向TDATA寄存器写入字节数据
}
while(!usart_flag_get(USART0,USART_FLAG_TC)){} //等待TC硬件置1,代表发送完成
}
*/
(3)使用
//--------------------------------轮询接收
void runUart(void)
{
unsigned short uiLen;
if(IS_TIM10MS)
{
if( g_uRecvDataOK[COM0] == 0x01)
{
uiLen = g_uRecvLen[COM0];
g_uRecvDataOK[COM0] = 0x00;
uartHandleFunc(rx_buffer,uiLen);
}
}
}
//----------------------------------发送 每次发送的时候会使能DMA因为 发送完中断里面关闭了DMA
SendData(COM0,tx_buffer,Len);
void SendData(unsigned char ucCOM,unsigned char ucData[],unsigned int usDataLen)
{
unsigned char *pucData;
unsigned int iTmp2;
switch(ucCOM)
{
case COM0:
{
pucData = ( unsigned char * )tx_buffer;
break;
}
default:
{
return;
}
}
for( iTmp2 = 0; iTmp2 < usDataLen; iTmp2++ )
{
*( pucData + iTmp2 ) = ucData[ iTmp2 ];
}
//发送数据
UARTDMADataSend( ucCOM, usDataLen );
}
void UARTDMADataSend(uint8_t ucCOM, uint16_t uLen)
{
uint16_t uTemp;
uint32_t usart_periph;
switch(ucCOM)
{
case COM0:
usart_periph = USART_Num[COM0];
//uSendBuf = ( uint8_t * )UART0_DMA_TX_SRC; //USART0发送数据缓冲区首地址
break;
case COM1:
usart_periph = USART1;
//uSendBuf = ( uint8_t * )UART1_DMA_TX_SRC; //USART1发送数据缓冲区首地址
break;
default:
return;
}
if(uLen > 0)
{
//while(g_uSendFlag[ucCOM]) ; //等待上次发送完成
g_uSendFlag[ucCOM] = 0x01; //设置准备发送标志
//gpio_bit_set( USART_RS485_GPIO_PORT[ucCOM], USART_RS485_PIN[ucCOM] ); //设置RS485为发送模式
for(uTemp = 0x00; uTemp < 100; uTemp++) ;
DMA_Enable(ucCOM, 1, uLen); //开启一次DMA传输
usart_dma_transmit_config(usart_periph, USART_DENT_ENABLE); //使能串口DMA发送 USART0
uLen = 0x00;
}
}
void DMA_Enable(uint8_t uComID, uint8_t uMode, uint16_t BufSize)
{
uint32_t uDMAx;
dma_channel_enum uDMA_CHx;
switch(uComID)
{
case COM0:
uDMAx = DMA0;
if(uMode == 0) uDMA_CHx = DMA_CH2; //DMA接收模式:CH4
else uDMA_CHx = DMA_CH1; //DMA发送模式:CH3
break;
default:
return;
}
dma_channel_disable(uDMAx, uDMA_CHx); //关闭DMA的通道X DMA_CH0
dma_transfer_number_config(uDMAx, uDMA_CHx, BufSize); //DMA通道的DMA缓存的大小
dma_channel_enable(uDMAx, uDMA_CHx); //使能DMA通道X
}
作者:外道幻想