eric2013 发表于 2019-7-24 00:18:29

串口FIFO的comGetChar函数正确使用姿势

多位网友咨询这个函数。

觉得使用不方便,希望提供一个数组方式,其实这个函数很好用。

像我们的ESP8266,GPS,GPRS等例子都是基于这个函数实现。

可以论坛置顶帖V6网盘下载相应例子。
static/image/hrline/4.gif
简单使用方式一:

这里用户可以将读取的read值存到一个数组里面,每读取到一次往数组里面存一下。



略微复杂的使用方式二:
比如远程端发送的数据格式如下:



程序代码就可以这样实现:

/*
*********************************************************************************************************
*      函 数 名: main
*      功能说明: c程序入口
*      形    参: 无
*      返 回 值: 错误代码(无需处理)
*********************************************************************************************************
*/
int main(void)
{
      uint8_t ucKeyCode;      
      uint8_t read;
      uint8_t ucStatus = 0;/* 状态机标志 */
      uint8_t ucCount=0, i;
      uint8_t buf;
      
      
      bsp_Init();                /* 硬件初始化 */

      bsp_StartAutoTimer(0, 100);      /* 启动1个100ms的自动重装的定时器 */
      
      /* 主程序大循环 */
      while (1)
      {
                /* CPU空闲时执行的函数,在 bsp.c */
                bsp_Idle();               
               
                /* 判断定时器超时时间 */
                if (bsp_CheckTimer(0))      
                {
                        /* 每隔100ms 进来一次 */
                        /* 翻转LED2的状态 */
                        bsp_LedToggle(2);      
                }
               
                /* 接收到的串口命令处理 */
                if (comGetChar(COM1, &read))
                {
                        switch (ucStatus)
                        {
                              /* 状态0保证接收到0x01 */
                              case 0:
                                        if(read == 0x01)
                                        {
                                                ucStatus = 1;      
                                        }
                                        break;
                                       
                              /* 状态1保证接收到0x03 */
                              case 1:
                                        if(read == 0x03)
                                        {
                                                ucStatus = 2;
                                        }
                                        else
                                        {
                                                ucStatus = 0;
                                        }
                                        break;
                                       
                              /* 状态2保证接收到0x0E */
                              case 2:
                                        if(read == 0x0E)
                                        {
                                                ucStatus = 3;
                                        }
                                        else
                                        {
                                                ucStatus = 0;
                                        }
                                        break;

                              case 3:
                                        buf = read;
                                       
                                        /* 接收够15个数据 */
                                        if(ucCount == 15)
                                        {
                                                /* 打印接收到的数据值 */
                                                printf("接收到的数据:");
                                                for(i = 0; i < ucCount + 1; i++)
                                                {
                                                      printf("%x ", buf);
                                                }
                                                printf("\r\n");
                                                ucStatus = 0;
                                                ucCount=0;
                                        }
                                        else
                                        {
                                                ucCount++;
                                        }
                                        break;
                              
                              default:
                                        break;
                        }
                }
               
      }
}






leiyitan 发表于 2019-7-24 00:22:38

我在好多项目上都是用的Uart+DMA+idle接收,DMA发送。用idle的方式不太适合数据帧断断续续

eric2013 发表于 2019-7-24 00:26:01

leiyitan 发表于 2019-7-24 00:22
我在好多项目上都是用的Uart+DMA+idle接收,DMA发送。用idle的方式不太适合数据帧断断续续

DMA方式的就是太灵活,这里有:

基于V6的CMSIS-Driver串口应用,支持8串口DMA不定长收发,比CubeMX还要省事
http://www.armbbs.cn/forum.php?mod=viewthread&tid=93714&fromuid=58
(出处: 安富莱电子论坛)

风轻云淡 发表于 2019-7-24 11:15:55

mark。。
串口没人用法不一样。。。。。。。。
学习一下。

noviceyao 发表于 2019-8-13 11:55:27

谢谢Eric老师的例程:loveliness:,之前没有注册成功。在此分享一下我应用此例程的使用结果,我的目标是接收7个两位的16进制数,并提取三四位进行显示通过更改此例程实现。
                while (status == 1)                                                       /* 读取变送器中数据主循环 */
                {      
                                comSendBuf(COM3,_ucaBuf,sizeof(_ucaBuf));
                                bsp_DelayMS(500);
                                for(i=0;i<7;i++)                       /* 等于7时,每个都读取14位返回值 */
                                {
                                if (comGetChar(COM3, &read)) /* 接收到的串口命令处理 */
                {                                                               
                        switch (ucStatus)
                        {      
                                                                case 0:
                                        if(read == 0x01)/* 状态0保证接收到0x01 */
                                        {ucStatus = 1;}
                                        break;            
                              case 1:
                                        if(read == 0x03) /* 状态1保证接收到0x03 */
                                        {ucStatus = 2;}
                                        else
                                        {ucStatus = 0;}
                                        break;
                              case 2:
                                        if(read == 0x02)/* 状态2保证接收到0x02 */
                                        {ucStatus = 3;}
                                        else
                                        {ucStatus = 0;}
                                        break;
                              case 3:
                                                                                sensor_buf = read;
                                        if(ucCount == 3)                                /* 接收够4个数据 */
                                        {
                                                for(i = 0; i < ucCount+1; i++)
                                                {
//                                                                                                        printf("%c ", buf);
                                                }
                                                                                                FormRSV->ReturnValue=sensor_buf*16*16+sensor_buf;                        /* 16进制返回值 */
                                                                                                DispReturnValue();
                                                                                                FormRSV->TempValue=(sensor_buf*16*16+sensor_buf)/10-273;        /* 转换成温度值*/                                       
                                                                                                DispTempValue();
                                                ucStatus = 0;
                                                                                                ucCount=0;
                                        }
                                        else
                                        {ucCount++;}
                                        break;
                              default:
                                                                                break;
                        }
                }
                                }
                                ucTouch = TOUCH_GetKey(&tpX, &tpY);        /* 读取触摸事件 */
                                if (ucTouch != TOUCH_NONE)
                                {
                                        switch (ucTouch)
                                        {
                                                case TOUCH_DOWN:                /* 触笔按下事件 */
                                                        if (TOUCH_InRect(tpX, tpY, BTN_RET_X, BTN_RET_Y, BTN_RET_H, BTN_RET_W))
                                                        {
                                                                FormRSV->BtnRet.Focus = 1;
                                                                LCD_DrawButton(&FormRSV->BtnRet);
                                                                status = 0;
                                                        }
                                                        break;

                                                case TOUCH_RELEASE:                /* 触笔释放事件 */
                                                        if (TOUCH_InRect(tpX, tpY, BTN_RET_X, BTN_RET_Y, BTN_RET_H, BTN_RET_W))
                                                        {
                                                                FormRSV->BtnRet.Focus = 0;
                                                                LCD_DrawButton(&FormRSV->BtnRet);
                                                                fQuit = 1;        /* 返回 */
                                                                status = 0;
                                                        }
                                                        else        /* 按钮失去焦点 */
                                                        {
                                                                FormRSV->BtnRet.Focus = 0;
                                                                LCD_DrawButton(&FormRSV->BtnRet);
                                                                status = 0;
                                                        }
                                                        break;
                        }
                }
                }

noviceyao 发表于 2019-8-13 11:57:06

谢谢Eric老师的例程。我是通过此例程修改后完成的实时读取7个两位16进制数,并实现实时显示其中第3个和第4个的值
                while (status == 1)                                                       /* 读取变送器中数据主循环 */
                {      
                                comSendBuf(COM3,_ucaBuf,sizeof(_ucaBuf));
                                bsp_DelayMS(500);
                                for(i=0;i<7;i++)                       /* 等于7时,每个都读取14位返回值 */
                                {
                                if (comGetChar(COM3, &read)) /* 接收到的串口命令处理 */
                {                                                               
                        switch (ucStatus)
                        {      
                                                                case 0:
                                        if(read == 0x01)/* 状态0保证接收到0x01 */
                                        {ucStatus = 1;}
                                        break;            
                              case 1:
                                        if(read == 0x03) /* 状态1保证接收到0x03 */
                                        {ucStatus = 2;}
                                        else
                                        {ucStatus = 0;}
                                        break;
                              case 2:
                                        if(read == 0x02)/* 状态2保证接收到0x02 */
                                        {ucStatus = 3;}
                                        else
                                        {ucStatus = 0;}
                                        break;
                              case 3:
                                                                                sensor_buf = read;
                                        if(ucCount == 3)                                /* 接收够4个数据 */
                                        {
                                                for(i = 0; i < ucCount+1; i++)
                                                {
//                                                                                                        printf("%c ", buf);
                                                }
                                                                                                FormRSV->ReturnValue=sensor_buf*16*16+sensor_buf;                        /* 16进制返回值 */
                                                                                                DispReturnValue();
                                                                                                FormRSV->TempValue=(sensor_buf*16*16+sensor_buf)/10-273;        /* 转换成温度值*/                                       
                                                                                                DispTempValue();
                                                ucStatus = 0;
                                                                                                ucCount=0;
                                        }
                                        else
                                        {ucCount++;}
                                        break;
                              default:
                                                                                break;
                        }
                }
                                }
                                ucTouch = TOUCH_GetKey(&tpX, &tpY);        /* 读取触摸事件 */
                                if (ucTouch != TOUCH_NONE)
                                {
                                        switch (ucTouch)
                                        {
                                                case TOUCH_DOWN:                /* 触笔按下事件 */
                                                        if (TOUCH_InRect(tpX, tpY, BTN_RET_X, BTN_RET_Y, BTN_RET_H, BTN_RET_W))
                                                        {
                                                                FormRSV->BtnRet.Focus = 1;
                                                                LCD_DrawButton(&FormRSV->BtnRet);
                                                                status = 0;
                                                        }
                                                        break;

                                                case TOUCH_RELEASE:                /* 触笔释放事件 */
                                                        if (TOUCH_InRect(tpX, tpY, BTN_RET_X, BTN_RET_Y, BTN_RET_H, BTN_RET_W))
                                                        {
                                                                FormRSV->BtnRet.Focus = 0;
                                                                LCD_DrawButton(&FormRSV->BtnRet);
                                                                fQuit = 1;        /* 返回 */
                                                                status = 0;
                                                        }
                                                        else        /* 按钮失去焦点 */
                                                        {
                                                                FormRSV->BtnRet.Focus = 0;
                                                                LCD_DrawButton(&FormRSV->BtnRet);
                                                                status = 0;
                                                        }
                                                        break;
                        }
                }
                }

eric2013 发表于 2019-8-13 12:08:36

noviceyao 发表于 2019-8-13 11:55
谢谢Eric老师的例程,之前没有注册成功。在此分享一下我应用此例程的使用结果,我的目标是接收 ...

非常感谢分享

EastWind 发表于 2020-2-25 10:16:16

请教一个问题:我也是参考这个例子, /* 接收到的串口命令处理 */
                if (comGetChar(COM1, &read))
                {
                        switch (ucStatus)
                        



程序进行到这   if (comGetChar(COM1, &read)),总判断为假跳过这段程序。接收不到数据,不知道哪里配置错了。

eric2013 发表于 2020-2-25 10:36:29

EastWind 发表于 2020-2-25 10:16
请教一个问题:我也是参考这个例子, /* 接收到的串口命令处理 */
                if (comGetChar(COM1,...

中断里面打个断点,先看看接收到数据了没

EastWind 发表于 2020-2-25 11:00:32

中断能进,发送没问,用AC6编译的不影响吧。
static void UartIRQ(UART_T *_pUart)

eric2013 发表于 2020-2-25 11:04:45

EastWind 发表于 2020-2-25 11:00
中断能进,发送没问,用AC6编译的不影响吧。
static void UartIRQ(UART_T *_pUart)

AC6没事,别的没什么要注意的了。

能进中断,数据也没错的话,那就没问题。

其它问题你再找找。

EastWind 发表于 2020-2-25 15:24:38

谢谢 硬汉。
找到毛病了,我把
uint8_t read;
      uint8_t ucStatus = 0;/* 状态机标志 */
      uint8_t ucCount=0, i;
      uint8_t buf;
这几个变量定义到while循环中了。造成每回读read时,都初始化一会。
以后要注意这个简单的问题。也给大家提个醒。

weiyuliang 发表于 2020-2-28 16:06:36

非常感谢分享,这个结构确实很好用,比如之前做的项目TTL接一个2.4G射频接收发模块做为主机;下面会有1000个2.4G射频模块作为从机,都会不定时的往上发送数据(温度、湿度、天然气信息); 串口接收到数据之后放入串口FIFO;每次接收到数据会使用回调函数开启一个软件定时器,软件定时超时时间是5~10ms,软件定时器超时时间到之后,会释放一个信号量,告诉线程现在有数据了,需要去处理;
不知道大家还有没有其它方式,能够更好、更合理的处理接收数据;

jaywen 发表于 2020-8-27 14:33:03

硬汉哥你好!看了你这个串口fifo   有一个实际需求是 当作为主机的时候也就是发送 等待从机回 这种利用你这个怎么实现了?一直没想到好的方法。因为涉及超时,重发等等

jaywen 发表于 2020-8-27 15:39:33

weiyuliang 发表于 2020-2-28 16:06
非常感谢分享,这个结构确实很好用,比如之前做的项目TTL接一个2.4G射频接收发模块做为主机;下面会有1000 ...

假如   有一个实际需求是 当作为主机的时候也就是发送 等待从机回 这种利用你这个怎么实现了?一直没想到好的方法。因为涉及超时,重发等等这个怎么实现?

eric2013 发表于 2020-8-27 16:57:46

jaywen 发表于 2020-8-27 14:33
硬汉哥你好!看了你这个串口fifo   有一个实际需求是 当作为主机的时候也就是发送 等待从机回 这种利用你 ...

论坛置顶帖下载V6标准库里面的GPS,ESP8266之类的串口例子看下,就更熟练了。

jaywen 发表于 2020-8-27 18:48:45

eric2013 发表于 2020-8-27 16:57
论坛置顶帖下载V6标准库里面的GPS,ESP8266之类的串口例子看下,就更熟练了。

我看了哈 !不是很适合我。因为你那个是阻塞的 while 一直在那等待这样感觉fifo的意义不大了

eric2013 发表于 2020-8-27 20:08:04

jaywen 发表于 2020-8-27 18:48
我看了哈 !不是很适合我。因为你那个是阻塞的 while 一直在那等待这样感觉fifo的意义不大了

这个不是简单的while死循环

RTOS下:
这种方式简单易移植,单独安排在一个任务里面即可。对其它任务毫不影响。

裸机下:
这种方式也是简单易移植,仅需一个.C和一个h文件,用户就可以使用,无需状态机之类的应用层移植。
更关键的点在每个while里面有个bsp_Idle函数,这个函数可以实现触摸检测,网络协议轮询等操作。从而也不会影响其它需要周期执行的功能,裸机的性能全部发挥了出来。

清风徐来 发表于 2020-11-18 15:48:00

有个疑问如何实现串口接收一组数据, 比如认为 接收到 ‘\N’ 字符,是一个完整的一组数据, 以便对一组数据解析。    直接通过 comgetchar 加 while() 循环实现?

清风徐来 发表于 2020-11-18 22:30:24

因为单片机要实现 scpi协议。。才有上面的要求。。。

eric2013 发表于 2020-11-19 09:14:50

清风徐来 发表于 2020-11-18 15:48
有个疑问如何实现串口接收一组数据, 比如认为 接收到 ‘\N’ 字符,是一个完整的一组数据, 以便对一组数 ...

可以的,看GPS的例子。

清风徐来 发表于 2020-11-19 16:28:38

eric2013 发表于 2020-11-19 09:14
可以的,看GPS的例子。

看了,受益良多。 如果没有结束符 或者 有结束符 ((如结尾有0x0D 0x0A)) 这两种情况,处理方法类似,都有状态机的思想。需要的数据先放入数组中,方便后面处理

eric2013 发表于 2020-11-20 09:42:02

清风徐来 发表于 2020-11-19 16:28
看了,受益良多。 如果没有结束符 或者 有结束符 ((如结尾有0x0D 0x0A)) 这两种情况,处理方法类似,都有 ...

对,这个试试,FIFO机制就显现出他的优势了。

282209507 发表于 2021-5-25 23:41:31

mark好东西啊!!!!!!!!!!

阴世幽泉 发表于 2022-8-23 23:23:54

我用这种方式,发现上位机如果每次发送1K数据包,串口这边会丢包,是不是因为在while大循环中太过频率调用comGetChar导致串口接收中断时不时被关闭,如果关闭时候刚好有数据进来这个时候不能触发中断,下次再有数据进来就直接覆盖上一次收到的rdr寄存器数据?

eric2013 发表于 2022-8-24 01:52:53

阴世幽泉 发表于 2022-8-23 23:23
我用这种方式,发现上位机如果每次发送1K数据包,串口这边会丢包,是不是因为在while大循环中太过频率调用c ...

是不是bsp_uart_fifo.h的缓冲开小了,没有及时读取的话,数据被会被后面的新数据覆盖了。

这个ymodem的例子就是上位机发送1024字节的。

BSP视频教程第22期:基于串口的XYZmodem文件传输协议实现,含上位机和下位机全开源,制作了一个Ymodem方式固件更新(2022-08-08)
https://www.armbbs.cn/forum.php?mod=viewthread&tid=114625&fromuid=58
(出处: 硬汉嵌入式论坛)

阴世幽泉 发表于 2022-8-27 12:00:37

eric2013 发表于 2022-8-24 01:52
是不是bsp_uart_fifo.h的缓冲开小了,没有及时读取的话,数据被会被后面的新数据覆盖了。

这个ymodem ...

感谢硬汉大神分享,应该是我这边程序没处理好

winddevil 发表于 2022-10-26 11:58:42

EastWind 发表于 2020-2-25 15:24
谢谢 硬汉。
找到毛病了,我把
uint8_t read;


我好像也是这个毛病

winddevil 发表于 2022-10-26 12:45:45

额,测试了之后还是发现这种方式comGetChar只会返回0,就是进不了if

winddevil 发表于 2022-10-26 15:09:52

winddevil 发表于 2022-10-26 12:45
额,测试了之后还是发现这种方式comGetChar只会返回0,就是进不了if

已经查证能进中断能存到BUFFER,只是把寄存器换成了F0系列的TDR\RDR双寄存器替代DR寄存器,数据的定义定义成全局的static了,但是返回值仍然是0,我再打几个断点查证一下

winddevil 发表于 2022-10-26 15:50:47

winddevil 发表于 2022-10-26 15:09
已经查证能进中断能存到BUFFER,只是把寄存器换成了F0系列的TDR\RDR双寄存器替代DR寄存器,数据的定义定 ...

查证是进入了ORE中断了,具体的晚上发帖分享一下,找到了问题,但是不一定解决问题

eric2013 发表于 2022-10-27 01:00:12

winddevil 发表于 2022-10-26 15:50
查证是进入了ORE中断了,具体的晚上发帖分享一下,找到了问题,但是不一定解决问题

STM32确实容易出这个ORE问题,中断里面加上对应的标志判断靠谱些。

yc5682668 发表于 2022-11-15 09:51:48

eric2013 发表于 2020-8-27 20:08
这个不是简单的while死循环

RTOS下:


RTOS下放在任务里,那就是osDelay(1)这种方式去查询是吧。还可以改良一下,在中断里释放信号量,任务里等待接收信号量

eric2013 发表于 2022-11-15 10:00:48

yc5682668 发表于 2022-11-15 09:51
RTOS下放在任务里,那就是osDelay(1)这种方式去查询是吧。还可以改良一下,在中断里释放信号量,任务里等 ...

不改量更好。

加这个你还得处理不定长接收问题,如果任务不能及时处理还会有丢失本次消息通知问题,如果1次1个字节,频繁率超级高等等一些列问题。

使用这个,由于它是串口FIFO,没有及时处理时,还在FIFO里面。容错率要高很多。而且查询CPU率不到千分之一。

yc5682668 发表于 2022-11-15 11:21:26

eric2013 发表于 2022-11-15 10:00
不改量更好。

加这个你还得处理不定长接收问题,如果任务不能及时处理还会有丢失本次消息通知问题,如 ...

osDelay(1)这样查询对于数据量较大时解析起来会不会闲的比较慢?如果数据量较大时,一般是怎样的处理思路?

eric2013 发表于 2022-11-16 14:36:33

yc5682668 发表于 2022-11-15 11:21
osDelay(1)这样查询对于数据量较大时解析起来会不会闲的比较慢?如果数据量较大时,一般是怎样的处理思路 ...

数据量大也处理的过来。

yidaoke 发表于 2023-5-5 18:40:41

如果本次收到是数据不足15个,下次再发过来的数据就对应不上了,因为 不足15个数据 ,ucCount没有清零,下下次再发15个数据有可能又正确接收

yidaoke 发表于 2023-5-5 18:47:27

如果接收的数据不足15个,ucCount不能清零,下次收到的数据就对应不上,下下次再发15个数据可能就的正确的

eric2013 发表于 2023-5-6 09:40:50

yidaoke 发表于 2023-5-5 18:40
如果本次收到是数据不足15个,下次再发过来的数据就对应不上了,因为 不足15个数据 ,ucCount没有清零, ...

楼主位只是个简单分享,实际应用,自己定制好协议,做好容错。

Aaron_EE 发表于 2023-9-8 15:33:45

请教一个问题,是否设计一个函数,直接把RxBuffer的数据直接copy到一个数组中,直接输出
页: [1] 2
查看完整版本: 串口FIFO的comGetChar函数正确使用姿势