小小毛

  • 51 主题
  • 156 帖子
  • 849 积分
  • 身份:版主
  • E币:984

【原创】对于没有串口Idle中断的MCU,如何接收数据

2021-1-24 17:37:09 显示全部楼层
      如何进行串口收发,见我上一篇帖子:
https://mbb.eet-china.com/forum/topic/86027_1_1.html
这里,有3个重要的处理思想:
1. 利用中断接收;2. 利用队列接收;
3. 如何处理不定长数据,而且要高效;

    这里,我重点讲解如何结合RTOS进行处理数据。我们巧妙的利用了RTSO自带的消息队列,我们可以把每一个接收的数据看做一个消息元素。
先回顾一下知识点:

FreeRTOS消息队列

        基于 FreeRTOS 的应用程序由一组独立的任务构成——每个任务都是具有独立权限的程序。这些独立的任务之间的通讯与同步一般都是基于操作系统提供的IPC通讯机制,而FreeRTOS 中所有的通信与同步机制都是基于队列实现的。
       消息队列是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传送信息,实现了任务接收来自其他任务或中断的不固定长度的消息。任务能够从队列里面读取消息,当队列中的消息是空时,挂起读取任务,用户还可以指定挂起的任务时间;当队列中有新消息时,挂起的读取任务被唤醒并处理新消息,消息队列是一种异步的通信方式。

队列特性1.数据存储

        队列可以保存有限个具有确定长度的数据单元。队列可以保存的最大单元数目被称为队列的“深度”。在队列创建时需要设定其深度和每个单元的大小。
通常情况下,队列被作为 FIFO(先进先出)缓冲区使用,即数据由队列尾写入,从队列首读出。当然,由队列首写入也是可能的。
往队列写入数据是通过字节拷贝把数据复制存储到队列中;从队列读出数据使得把队列中的数据拷贝删除。

2.读阻塞

        当某个任务试图读一个队列时,其可以指定一个阻塞超时时间。在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务例程往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。
由于队列可以被多个任务读取,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列数据有效。这种情况下,一旦队列数据有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。

说些题外话,ucos中是具有广播消息的,当有多个任务阻塞在队列上,当发送消息的时候可以选择广播消息,那么这些阻塞的任务都能被解除阻塞。

3.写阻塞

        与读阻塞想反,任务也可以在写队列时指定一个阻塞超时时间。这个时间是当被写队列已满时,任务进入阻塞态以等待队列空间有效的最长时间。
由于队列可以被多个任务写入,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列空间有效。这种情况下,一旦队列空间有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。

消息队列的工作流程1.发送消息

       任务或者中断服务程序都可以给消息队列发送消息,当发送消息时,如果队列未满或者允许覆盖入队, FreeRTOS 会将消息拷贝到消息队列队尾,否则,会根据用户指定的阻塞超时时间进行阻塞,在这段时间中,如果队列一直不允许入队,该任务将保持阻塞状态以等待队列允许入队。当其它任务从其等待的队列中读取入了数据(队列未满),该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间,即使队列中还不允许入队,任务也会自动从阻塞态转移为就绪态,此时发送消息的任务或者中断程序会收到一个错误码 errQUEUE_FULL。
发送紧急消息的过程与发送消息几乎一样,唯一的不同是,当发送紧急消息时,发送的位置是消息队列队头而非队尾,这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理。
下面是消息队列的发送API接口,函数中有FromISR则表明在中断中使用的。

消息队列读取


       任务调用接收函数收取队列消息, 函数首先判断当前队列是否有未读消息, 如果没有, 则会判断参数 xTicksToWait, 决定直接返回函数还是阻塞等待。
如果队列中有消息未读, 首先会把待读的消息复制到传进来的指针所指内, 然后判断函数参数 xJustPeeking == pdFALSE的时候, 符合的话, 说明这个函数读取了数据, 需要把被读取的数据做出队处理, 如果不是, 则只是查看一下(peek),只是返回数据,但是不会把数据清除。
对于正常读取数据的操作, 清除数据后队列会空出空位, 所以查看队列中的等待列表中是否有任务等发送数据而被挂起, 有的话恢复一个任务就绪, 并根据优先级判断是否需要出进行任务切换。
对于只是查看数据的, 由于没有清除数据, 所以没有空间新空出,不需要检查发送等待链表, 但是会检查接收等待链表, 如果有任务挂起会切换其到就绪并判断是否需要切换。

接下来,我们可以从中断再到任务这样一个流程去编写代码:

如下的框图来说明一下 FreeRTOS 消息队列的实现,让大家有一个形象的认识。
1. 中断如何处理:

  1. ///<LPUART0 中断服务函数
  2. void LpUart0_IRQHandler(void)
  3. {
  4.         
  5.          BaseType_t xHigherPriorityTaskWoken = pdFALSE;

  6.         
  7.          uint8_t res=0;
  8. //        if(LPUart_GetStatus(M0P_LPUART0, LPUartPE))  /*奇偶检验错误*/   
  9. //    {
  10. //        LPUart_ClrStatus(M0P_LPUART0, LPUartPE);     
  11. //    }
  12.         
  13.     if(LPUart_GetStatus(M0P_LPUART0, LPUartRC))       ///接收数据中断
  14.     {
  15.         LPUart_ClrStatus(M0P_LPUART0, LPUartRC);      ///<清接收中断请求        
  16.                
  17.                 res =  LPUart_ReceiveData(M0P_LPUART0);
  18.         xQueueSendFromISR(usart_Queue,(void *) &res,&xHigherPriorityTaskWoken);
  19.         portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
  20.         
  21.     }
  22. }

任务中接收信号,这里并不是每一条消息都接收吗,因为没有空闲中断,而是做了100ms绝对延时,确保一帧数据接收完成。
  1. /**
  2. ***********************************************************************
  3. ** \brief   2400波特率:'100ms = 24bytes'
  4. **
  5. **
  6. ** \param 1 :void
  7. ** \retval   void                                 
  8. ***********************************************************************/        
  9. void APP_LocalCOM_ReadData(void)
  10. {
  11.         uint8_t  temp_bytes = 0; /*队列中字节长度new*/
  12.         uint8_t cnt;
  13.         static uint8_t  buff[QueueSIZE] = {0}; /*暂存接收协议,从0x68开始,用于crc计算*/
  14.         static TickType_t StartTick = 0;
  15.         static uint8_t ShadowBytes = 0;  /*old*/
  16.     temp_bytes = uxQueueMessagesWaiting(usart_Queue);//检查消息数
  17.         if(temp_bytes == 0)//检查队列的长度
  18.         {
  19.            ShadowBytes = 0;
  20.         }
  21.         else
  22.         {
  23.                 if(ShadowBytes != temp_bytes)//有新的数据
  24.                 {
  25.                         ShadowBytes = temp_bytes;
  26.                         StartTick = xTaskGetTickCount();
  27.                 }
  28.                 else
  29.                 {
  30.                         if(xTaskGetTickCount() - StartTick > 100)
  31.                         {
  32.                                 for(cnt = 0; cnt<temp_bytes; cnt++)
  33.                                 {
  34.                                         xQueueReceive(usart_Queue,(void*)&buff[cnt%QueueSIZE],(TickType_t)100);//接收数据
  35.                                 }                        
  36.                                 protocol_parse(buff,temp_bytes);               
  37.                                 //BSP_UARTx_SendBytes(M0P_UART0,temp_bytes, buff); //test
  38.                         }
  39.                 }
  40.    }
  41. }        
  42.         










您需要登录后才可以评论 登录 | 立即注册

最新评论

楼层直达:

小小毛

  • 51 主题
  • 156 帖子
  • 849 积分
  • 身份:版主
  • E币:984

SnailWillow

  • 19 主题
  • 92 帖子
  • 836 积分
  • 身份:LV3 中级技术员
  • 论坛新秀
  • E币:928
SnailWillow 2021-1-25 09:58:25 显示全部楼层
哈哈哈, 楼主可爱,自己赞自己!

点评

已置顶  详情 回复 发表于 2021-1-26 15:26
这几天一直在研究这个东西,UART+ FreeRTOS  详情 回复 发表于 2021-1-25 12:04

小小毛

  • 51 主题
  • 156 帖子
  • 849 积分
  • 身份:版主
  • E币:984
小小毛 2021-1-25 12:04:12 显示全部楼层
SnailWillow 发表于 2021-1-25 09:58
哈哈哈, 楼主可爱,自己赞自己!

这几天一直在研究这个东西,UART+ FreeRTOS
广告

moyanming2013

  • 118 主题
  • 375 帖子
  • 1254 积分
  • 身份:LV4 高级技术员
  • E币:776

Lgnited

  • 46 主题
  • 157 帖子
  • 812 积分
  • 身份:版主
  • 论坛新秀
  • E币:883

老树根家的胖豆豆

  • 9 主题
  • 30 帖子
  • 436 积分
  • 身份:LV2 初级技术员
  • E币:1375

小小毛

  • 51 主题
  • 156 帖子
  • 849 积分
  • 身份:版主
  • E币:984
广告

curton

  • 572 主题
  • 2709 帖子
  • 14915 积分
  • 身份:版主
  • 论坛新秀 灌水之王 优秀版主
  • E币:4119

二哲科技

  • 118 主题
  • 344 帖子
  • 1314 积分
  • 身份:版主
  • 论坛新秀
  • E币:1590
二哲科技 2021-2-17 17:43:41 显示全部楼层
厉害了,这总结的挺好的啊,以后可能有机会用上!good!

星海扬帆

  • 0 主题
  • 16 帖子
  • 349 积分
  • 身份:LV2 初级技术员
  • E币:249
星海扬帆 2021-2-24 14:27:34 显示全部楼层
没有IDLE中断的MCU是常态,LINUX里面有标准的不依赖于OS的模板,照抄就行了
快速回复
10
23
广告
关闭 热点推荐上一条 /8 下一条
快速回复 返回列表