前段时间看帖子发现CMSIS-Driver中的串口驱动可以使用空闲中断,好吧,或许以前有,是我没研究深入。今天,我把使用方法分享出来,供大家学习。
新建工程可以参考我的另一篇文章,那个是比较早的,现在版本更新很快,不过基本创建也是差不多的。这里我只是说明如何使用它的空闲中断方式去接收数据。
官方的CMSIS-Driver串口驱动中,有几个事件标志(如图)。
对于接收,我们常用的只有这两个ARM_USART_EVENT_RECEIVE_COMPLETE|ARM_USART_EVENT_SEND_COMPLETE接收完成和发送完成事件。
这里的接收事件,是在调用int32_t(* Receive)(void *data, uint32_t num)函数时,串口接收到num个数据后,产生的接收完成事件。这个功能,在我们接收不定长的数据时,是很不好的用的。我上一篇文章对于接收不定长的数据,是每次只接收一个字节,然后用队列方式或者自己判断帧空闲方式去实现不定长数据接收。这样做就很麻烦。现在它的驱动里有另一个事件ARM_USART_EVENT_RX_TIMEOUT,这个事件是在开启DMA后,对于调用Receive函数,产生的接收超时事件。也就是接收到了一帧数据,只是这一帧数据没有达到我要接收的字节个数。我们把Receive函数中的num值设大一点,相当于给DMA接收设一个大一点的缓存,然后利用它的接收超时事件,来接收一帧数据。看它的源代码就是开启了IDLE中断,中断发生时,产生该接收超时事件。看下使用例子代码
void myUSART2_callback(uint32_t event)
{
if(event & ARM_USART_EVENT_RECEIVE_COMPLETE)//表示buf接收满
{
osThreadFlagsSet(tid_usart2_Thread, EVENT_FLAGS_USART2_RX_COMPLETE);
}
else if(event & ARM_USART_EVENT_RX_TIMEOUT) //接收超时事件,表示接收到一帧数据
{
osThreadFlagsSet(tid_usart2_Thread, EVENT_FLAGS_USART2_RX_TIMEOUT);
}
else if(event & ARM_USART_EVENT_SEND_COMPLETE)
{
osThreadFlagsSet(tid_usart2_Thread, EVENT_FLAGS_USART2_TX_COMPLETE);
}
}
上片代码是串口的回调函数,其中我在项目使用的CMSIS-RTOS2,在相应的事件中设置了相关的事件标志,以供线程使用。
我这里只关注了接收完成、接收超时和发送完成事件,还可以关注接收溢出,发送错误等其它事件。
void myThread_USART2(void *argument) //RS485
{
uint32_t flags;
osStatus_t status;
static ARM_DRIVER_USART * USARTdrv = &Driver_USART2;
uint8_t rx_buf[50];
MSG_QUEUE_t rs485_msg;
MSG_QUEUE_t lora_msg;
RS485_CtrlInit();
RS485_SetRx();
USARTdrv->Initialize(myUSART2_callback);
/*Power up the USART peripheral */
USARTdrv->PowerControl(ARM_POWER_FULL);
/*Configure the USART to 9600 Bits/sec */
USARTdrv->Control(ARM_USART_MODE_ASYNCHRONOUS |
ARM_USART_DATA_BITS_8 |
ARM_USART_PARITY_NONE |
ARM_USART_STOP_BITS_1 |
ARM_USART_FLOW_CONTROL_NONE, 9600);
/* Enable Receiver and Transmitter lines */
USARTdrv->Control (ARM_USART_CONTROL_TX, 1);
USARTdrv->Control (ARM_USART_CONTROL_RX, 1);
while(1)
{
USARTdrv->Receive(rx_buf,sizeof(rx_buf));
flags = osThreadFlagsWait(EVENT_FLAGS_USART2_RX_COMPLETE|
EVENT_FLAGS_USART2_RX_TIMEOUT|
EVENT_FLAGS_RS485_TX,
osFlagsWaitAny,
osWaitForever);
switch(flags)
{
case EVENT_FLAGS_USART2_RX_COMPLETE: //buf存满
break;
case EVENT_FLAGS_USART2_RX_TIMEOUT: //接收到一帧数据
USARTdrv->Control (ARM_USART_ABORT_RECEIVE, 1); //在这里要终止接收,防止下一帧数据过来,影响数据接收
osThreadFlagsSet(tid_usart3_Thread,EVENT_FLAGS_LORA_TX); //设置LORA发送事件
lora_msg.Len = USARTdrv->GetRxCount();
memcpy(lora_msg.Buf,rx_buf,lora_msg.Len);
status = osMessageQueuePut(mid_lora_msg,&lora_msg,0,0);
if(status == osOK)
{
memset(&lora_msg,0,sizeof(lora_msg));
}
printf("%s",rx_buf);
break;
case EVENT_FLAGS_RS485_TX:
status = osMessageQueueGet(mid_rs485_msg,&rs485_msg,0,osWaitForever);
if(status == osOK)
{
RS485_SetTx();
USARTdrv->Send(rs485_msg.Buf,rs485_msg.Len);
osThreadFlagsWait(EVENT_FLAGS_USART2_TX_COMPLETE,osFlagsWaitAny,osWaitForever);
RS485_SetRx();
}
break;
}
}
}
这个是该串口的任务线程,该线程通过从串口2接收到一帧数据后,通过RS485串口发送出去。可以看到使用接收超时事件后,就可以接收不定长的数据帧,用起来贼爽。对于不带RTOS的使用方法,其实没什么区别。重点是不要漏掉2个操作,开始接收(USARTdrv->Receive(rx_buf,sizeof(rx_buf));)和终止接收(USARTdrv->Control (ARM_USART_ABORT_RECEIVE, 1);)否则你会发现,你的串口要么接收不了,要么接收的数据是乱的。