串口一次只能传1byte数据,在实际应用中,我们会发送和接收一串数据,如果没发送和接收一个数据就会进去中断会严重影响程序的正常执行,占用过多的cpu资源。如果串口模块能够自动判别一串数据的结束,并且把接收数据放在我们指定的ram,发送数据直接扔到ram不用cpu操作,那该多省心!stm32的串口DMA+空闲中断接收和发送方案就能实现。
我使用的是stm32f103芯片和freertos系统,串口程序已经长时间高效稳定运行在几万台设备上。我使用的是uart4。
代码如下:
初始化
void UART4_Init()
{
GPIO_Configuration();
USART_Configuration();
DMA_Configuration();
NVIC_Configuration();
}
static void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 第1步:打开GPIO和USART部件的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE);
/* 第2步:将USART1 Tx的GPIO配置为推挽复用模式 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PC10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* 第3步:将USART Rx的GPIO配置为浮空输入模式
由于CPU复位后,GPIO缺省都是浮空输入模式,因此下面这个步骤不是必须的
但是,我还是建议加上便于阅读,并且防止其它地方修改了这个口线的设置参数
*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//PC11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOC, &GPIO_InitStructure);
//方向
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;//PC10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
//串口初始化:
static void USART_Configuration(void)
{
USART_InitTypeDef USART_InitStructure;
/* 第4步:配置USART参数
- BaudRate = 115200 baud
- Word Length = 8 Bits
- One Stop Bit
- No parity
- Hardware flow control disabled (RTS and CTS signals)
- Receive and transmit enabled
*/
USART_InitStructure.USART_BaudRate = 460800;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
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(UART4, &USART_InitStructure);
//中断配置
USART_ITConfig(UART4,USART_IT_TC,DISABLE);
USART_ITConfig(UART4,USART_IT_RXNE,DISABLE);
USART_ITConfig(UART4,USART_IT_IDLE,ENABLE);
USART_ITConfig(UART4,USART_IT_ERR,ENABLE);
USART_ITConfig(UART4,USART_IT_PE,ENABLE);
/* 第5步:使能 USART, 配置完毕 */
USART_Cmd(UART4, ENABLE);
/* CPU的小缺陷:串口配置好,如果直接Send,则第1个字节发送不出去
如下语句解决第1个字节无法正确发送出去的问题 */
USART_ClearFlag(UART4, USART_FLAG_TC); /* 清发送外城标志,Transmission Complete flag */
}
static void DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
/* DMA clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);//DMA2
/* DMA2 Channel5 (triggered by USART4 Tx event) Config */
DMA_DeInit(DMA2_Channel5);
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&(UART4->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)UART4_DMA_SEND_DATA;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//内存到外设
DMA_InitStructure.DMA_BufferSize = 256;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不增加
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存地址增加
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//位宽8bit
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//位宽8bit
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA2_Channel5, &DMA_InitStructure);
DMA_ITConfig(DMA2_Channel5, DMA_IT_TC, ENABLE);
DMA_ITConfig(DMA2_Channel5, DMA_IT_TE, ENABLE);
/* Enable USART4 DMA TX request */
USART_DMACmd(UART4, USART_DMAReq_Tx, ENABLE);
DMA_Cmd(DMA2_Channel5, DISABLE);
/* DMA1 Channel5 (triggered by USART1 Rx event) Config */
DMA_DeInit(DMA2_Channel3);
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&(UART4->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)UART4_DMA_RECEIVE_DATA;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设到内存
DMA_InitStructure.DMA_BufferSize = 256;
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_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA2_Channel3, &DMA_InitStructure);
DMA_ITConfig(DMA2_Channel3, DMA_IT_TC, ENABLE);
DMA_ITConfig(DMA2_Channel3, DMA_IT_TE, ENABLE);
/* Enable USART1 DMA RX request */
//USART_DMACmd(UART4, USART_DMAReq_Rx, ENABLE);
//DMA_Cmd(DMA2_Channel3, ENABLE);
}
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Configure one bit for preemption priority */
//NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
#if 1
/* Enable the USART4 Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = UART4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 14;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/*Enable DMA2 Channel3 Interrupt 接收*/
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Channel3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 14;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/*Enable DMA2 Channel5 Interrupt 发送*/
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Channel4_5_IRQn;//
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 14;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
#endif
#if 0
/* Enable the USART1 Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = UART4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
#endif
USART_DMACmd(UART4, USART_DMAReq_Rx, ENABLE);
DMA_Cmd(DMA2_Channel3, ENABLE);
}
中断服务程序
//USART4中断服务函数
void UART4_IRQHandler(void)
{
INT16 DATA_LEN;
UINT16 i;
//UINT16 data;
DMA_Channel_TypeDef *DMA2_CH3 = DMA2_Channel3;
USART_TypeDef *uart1 = UART4;
BaseType_t xHigherPriorityTaskWoken;
DMA_Cmd(DMA2_Channel3, DISABLE);//关闭DMA,防止处理其间有数据
if(USART_GetITStatus(UART4, USART_IT_IDLE) != RESET)//如果为空闲总线中断
{
//DMA_Cmd(DMA2_Channel3, DISABLE);//关闭DMA,防止处理其间有数据
DMA_ClearFlag(DMA2_FLAG_GL3 | DMA2_FLAG_TC3 | DMA2_FLAG_TE3 | DMA2_FLAG_HT3);//清标志
//USART_RX_STA = USART1->SR;//先读SR,然后读DR才能清除
//USART_RX_STA = USART1->DR;
DATA_LEN = 256 - DMA_GetCurrDataCounter(DMA2_Channel3);
if(DATA_LEN > 0 && DATA_LEN <= 256)
{
//USART用DMA传输替代查询方式发送,克服被高优先级中断而产生丢帧现象。
//DMA_Cmd(DMA2_Channel3, DISABLE); //改变datasize前先要禁止通道工作
DMA2_CH3->CNDTR = 256; //DMA2,传输数据量
//post mailbox
uart_to_keyboard_msg.address = UART4_DMA_RECEIVE_DATA;
uart_to_keyboard_msg.length = DATA_LEN;
total_rx++;
xQueueSendToBackFromISR(Keyboard_Queue,(void*)&uart_to_keyboard_msg,&xHigherPriorityTaskWoken);
//xSemaphoreGiveFromISR(BinarySemaphore,&xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
//读SR后读DR清除Idle
i = uart1->SR;
i = uart1->DR;
i = i;
}
else if(USART_GetITStatus(UART4, USART_IT_PE | USART_IT_FE | USART_IT_NE|USART_IT_ORE) != RESET)//出错
{
USART_ClearITPendingBit(UART4, USART_IT_PE | USART_IT_FE | USART_IT_NE|USART_IT_ORE);
total_err1++;
//读SR后读DR清除ORE (Overwrite )错误中断,以前以为清除中断就可以了,芯片资料要求需要读数据寄存器的值
i = uart1->SR;
i = uart1->DR;
}
else
{
total_err2++;
//funcCode.all[FUNCCODE_P11_15] = total_err2;
i = uart1->SR;
i = uart1->DR;
}
DMA_Cmd(DMA2_Channel3, DISABLE);//关闭DMA,防止处理其间有数据
DMA2_CH3->CNDTR = 256;//重装填
DMA_Cmd(DMA2_Channel3, ENABLE);//处理完,重开DMA
USART_ClearITPendingBit(UART4, USART_IT_TC);
USART_ClearITPendingBit(UART4, USART_IT_IDLE);
}
//DMA2_Channel5中断服务函数,DMA发送完成中断
void DMA2_Channel4_5_IRQHandler(void)
{
DMA_ClearITPendingBit(DMA2_IT_TC5);
DMA_ClearITPendingBit(DMA2_IT_TE5);
DMA_Cmd(DMA2_Channel5, DISABLE);//关闭DMA
//KeyboardRs485RxMode();
}
//DMA2_Channel3中断服务函数 接收
void DMA2_Channel3_IRQHandler(void)
{
DMA_Channel_TypeDef *DMA2_CH13 = DMA2_Channel3;
DMA_ClearITPendingBit(DMA2_IT_TC3);
DMA_ClearITPendingBit(DMA2_IT_TE3);
DMA_Cmd(DMA2_Channel3, DISABLE);//关闭DMA,防止处理其间有数据
DMA2_CH13->CNDTR = 256;//重装填
DMA_Cmd(DMA2_Channel3, ENABLE);//处理完,重开DMA
}
串口接收和发送应用接口函数
INT32 KeyboardRs485TxFrame(uint8_t * buffer,uint32_t len)
{
//unsigned int baseAddr = 0;
//unsigned int i;
unsigned int sleep_time = (len/40)+2;//20*5ms
Bool ret_val = 1;
DMA_Channel_TypeDef *DMA_CHx = DMA2_Channel5;
KeyboardRs485TxMode();
memcpy(UART4_DMA_SEND_DATA,buffer,len);
DMA_Cmd(DMA2_Channel5, DISABLE);//关闭DMA
DMA_CHx->CNDTR=len; //DMA1,传输数据量
DMA_Cmd(DMA2_Channel5, ENABLE); //开启DMA传输
vTaskDelay(sleep_time);//估算发送时间
total_tx++;
DMA_Cmd(DMA2_Channel5, DISABLE);//关闭DMA
return ret_val;
}
/**
* \brief recieve a frame .
*
* \param buffer [IN] save a frame from rs485
* \return
* RX length from rs485
*
**/
unsigned int KeyboardRs485RxFrame(unsigned char *buffer,unsigned int timeout)
{
unsigned int len = 0;
unsigned int i = 0;
msg_object_t msg;
KeyboardRs485RxMode();
if(xQueueReceive(Keyboard_Queue,&msg,timeout) == pdFALSE)
//if(xSemaphoreTake(BinarySemaphore, timeout) == pdFALSE)
{
return len;
}
len = msg.length;
//len = uart_to_keyboard_msg.length;
//copy to buffer
for(i = 0; i < len; i++)
{
buffer[i] = msg.address[i];
//buffer[i] = uart_to_keyboard_msg.address[i];
}
return len;
}