最近在写一个serial 的应用想起以前写过的一些单片机上的uart 程序,有着许许多多的圈圈点点的,也就来扒一扒串口机制的事情了。 学习单片机都会接触到串口这个东西,多数的教程都是讲讲如何把寄存器配置好,然后可以发出数据、接收数据,而对如何应用基本完全不谈。而其实不管是哪一类CPU,串口的模式基本相同,毕竟这东西从单片机诞生至今也算是个白发老头的年纪了。uart 寄存器配置不说,每个CPU有自己的一套,我主要说数据的收发。 一种是Polling机制,如下图,发送/接收约定数量个数的数据,不断的检测发送/接收标志位,然后把新数据填入buff 中直到达到约定的数量后结束跳出。很明显,这里有个很大的问题,如果发送/接收没有达到约定数量就会出现在等待发送/接收成功这个检测点不断的循环,没有数据的情况下那就完全跳不出这个环节了,程序也就死机了

以STM32为例的Polling机制程序

1

2

3

4

5

6

7

8

9

10

11

for( i=0;TxBuf1[i]!='\0';i++)

{

    USART_SendData(USART1,TxBuf1[i]);// 发送Data

    while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);//等待发送成功      

}

for( i=0;RxBuf1[i]!='\0';i++)

{

    RxBuf[i] = USART_ReceiveData(USART1);// 接收Data

    while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE)==RESET);//等待发送成功      

}

另一种常见的机制就是Interrupt。中断机制的好处在于程序一直处于大循环中,只有数据来到了标志位产生并中断了才进入中断函数接收一个byte,发送接收后返回Loop 的运行代码继续运行。这里就很好的避免了Polling 等待中却没有数据来临而导致的死循环情况。但这里又产生一个问题数据的发送/接收数量要到什么时候才能停止?当然我们可以定义一个约定长度,当填满这个buff的size时认为数据发送/接收结束

还是以STM32 为例,中断uart 代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

void USART1_IRQHandler()  

{  

    if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET) //读中断产生 

    {    

         USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清除中断标志 

         RxBuffer1[Rx_Num] = USART_ReceiveData(USART1); 

         Rx_Num++; 

         if(Rx_Num == MAX_BUF)//接收数据数量完成

         {   

              USART_ITConfig(USART1, USART_IT_RXE, DISABLE); //关闭接收中断

         }    

    }

    if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET)//发送中断产生

    {   

         USART_SendData(USART1, TxBuffer1[Tx_Num--]);

         if(Tx_Num == 0)//发送数据数量完成

         {   

              USART_ITConfig(USART1, USART_IT_TXE, DISABLE); //关闭发送中断

         }    

     }

}

到此,上面两种最最基本最常见的uart 机制。然而这种方式并不能很好的处理我们日常使用串口的数据,例如不定长度的,数据本身断流的问题,已经高级cpu 中内置buff 的自动发送,大数据块发送等等。
因此在基础的uart 数据收发上,引入了分层的数据收发管理 serial。serial 顾名思义就是串行数据,用于管理uart 等串行设备传送上来的数据并加以分析机制来管理数据状态。
通常 uart 的数据发送/接收分三种模式:轮询、中断、缓存buff(DMA 或 BUF), 前面两种模式上面都介绍过,而第三种通常在比较高级点的cpu 上才会支持,如SMT32 的USART DMA,数据一旦设置给cpu DMA 控制器,控制器自动把DMA 中的数据自动发送,发送/结束过程CPU 完全不参与,结束后DMA控制器会告诉CPU 任务已经完成了,适合数据量大的情况下使用。

分清三种模式后,便可针对这三种模式来做数据的收发管理,对于应用来说我们希望只用 Uart_send(*buf); 和 len=Uart_Read(*buf); 两个简单的函数就实现数据的发送和接收,并且不会对主程序造成高阻塞情况。

使用Uart API 接口函数 不需要知道uart 如何操作数据,在发送时候,只要串口打开,并且可运行下,把数据发送给Uart API 即可,在接收时候只要有数据接收到并且可用的情况下就能读取到数据。设计中为高效利用稀有的ram 使用ringbuf (环形缓存),当然如果选择Polling 模式就会绕开ringbuf了。  Send 部分设计相对简单,整个逻辑基本如下:

数据进入后判断当前配置的状态,选择响应的发送方式。polling 会打断整个process 直至发送完毕后才把CPU还给主进程,Interrupt 不断在中断函数与主process 之间来回切换,DMA 则配置完后完全不需要管理 process 继续自己跑。 产考代码:
需要用到的数据结构:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

struct serial_ringbuffer

{

    uint8_t  buffer[RB_BUFSZ];

    uint16_t put_index, get_index;

    uint16_t lst_time;

};

struct serial_configure

{

    uint16_t baud_rate;

    uint16_t data_bits;

    uint16_t stop_bits;

    uint16_t parity;

    uint16_t bit_order;

    uint16_t invert;   

    uint16_t reserved;

};

typedef struct _serial_device

{

    /* UART1 or UART2 ...*/

    uart_device dev; 

    struct serial_configure   config;

    /* rx structure */

    struct serial_ringbuffer *int_rx;

    /* tx structure */

    struct serial_ringbuffer *int_tx;

    /* tx dataqueue */

    struct queue      tx_dq;

    /* dma transfer flag */

    unsigned char dma_flag;

}serial_device;

发送部分代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

static size_t serial_write( serial_device *dev,const void *buffer, size_t size)

{

    uint8_t *ptr;

    size_t write_nbytes = 0;

    struct serial_device *serial;

    if (size == 0)

        return 0;

    serial = (struct serial_device *)dev;

    ptr = (uint8_t*)buffer;

    if (dev->flag & FLAG_INT_TX) /* int mode*/

    {

     /* cp buff to ringbuff tx */

         while(size)

         

             serial_ringbuffer_putchar(serial->int_tx, *ptr);

             ptr++;

             size--;

         }

    }

    else if (dev->flag & FLAG_DMA_TX)/* dma mode*/

    {

         dma_transmit(serial, buffer, size);

         size = 0;

    }

    else

    {

       /* polling mode */

         while (size)

         {

               putc(serial, *ptr);

               ++ ptr;

               -- size;

         }

     }

     return size;

}

Interrupt 模式和 DMA 模式还需要在中断中做判断处理,后面再帖中断处理的事件处理方式.
读取数据和写入数据比较接近,但也有很多区别,发送数据只要把发送buff 指定后便可以了,而接受数据需要判断数据长度是否到达 或者 是否达到预定的时间内都已经没有数据输入了,之后才能把接收到的数据送给 应用层。即使uart 设备已经接收到数据,但没有到达特定条件不能把数据送往应用层,应用层也获取不到数据,len=Uart_Read(*buf)获得的len 为 0 。

数据的读取基本是应用程序中的一个动作,只要数据符合要求,就返回长度表示有效,否则返回0个数据表示当前没有数据,直到下一次应用程序再次询问的时候再发送下一次的状态给应用程序。可以看到Interrupt 上接收多了一个timer ,这是用于当数据接收没有存满buffer 而又已经没有数据来临的时候让timer计时,超出规定时间后把数据传送给应用层处理。 接收部分强化的主要是中端后的判断处理,接收部分代码和发送基本相似:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

static size_t serial_read(struct device *dev,void *buffer,size_t size)

{

    uint8_t *ptr;

    uint8_t *dat;

    static uint8_t dma_st = 0;

    uint32_t read_nbytes = 0;

    struct serial_device *serial;

    if (size == 0)

        return 0;

    serial = (struct serial_device *)dev;

    ptr = (uint8_t *)buffer;

    if (dev->flag & FLAG_INT_RX)

    {

        /* interrupt mode Rx */

        if(dev->flag & FLAG_INT_OVERTIME)

        {

            read_nbytes = serial_ringbuffer_getc(serial->int_rx);

            dat = serial->int_rx;

            size = read_nbytes;

            while(size--)

            {

                *prt = dat & 0xFF;

                prt++;

                dat++;

            }

        }

    }

    else if (dev->flag & FLAG_DMA_RX)/* dma mode*/

    {

        if(dma_st == 0)

        {

            dma_receive(serial, buffer, size);

            dma_st = 1;

        }

        else if(dev->flag & FLAG_INT_DMARN)

        {

            read_nbytes = serial_ringbuffer_getc(serial->int_rx);

            dma_st = 0;

        }

    }

    else

    {

        /* polling mode */

        while ((uint32_t)ptr - (uint32_t)buffer < size)

        {

            *ptr = serial->getc(serial);

            ptr ++;

            read_nbytes++;

        }

    }

    return read_nbytes;

}

两个函数serial_write()和serial_read() 便可简单的封装给上层应用。而重点的地方还在于Interrupt 函数中对数据的判断处理,上面两个函数主要还是把数据分发给配置好适合的收发模式,而中断决定读取数据的情况。
STM32为例 中断函数:

1

2

3

4

5

6

7

8

9

10

11

void USART1_IRQHandler(void)

{

    if(UART1->SR & USART_FLAG_TXE)

    {

        UART_Tx_ISR();

    }

    if(UART1->SR & USART_FLAG_RXNE)

    {

        UART_Rx_ISR();

    }

}

分别给接收和发送创造一个中断处理函数:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

void UART_Tx_ISR(void)

{

    unsigned short datalen;

    unsigned short wp, rp;

    if(id >= MAX_UART_NUM)

        return;

    //snapshot

    wp = device->int_tx.put_index;

    rp = device->int_tx.get_index;

    

    //get data len

    datalen = UART_Get_Data_Len(wp, rp, UART_BUF_LEN);

    if(datalen == 0)

        {

            USART_ITConfig(USART1, USART_IT_TXE, DISABLE);   

            return;

        }

    USART1->DR   = g_uart_tx_buf[id].Buf[rp];

    

    device->int_tx.get_index ++;

    device->int_tx.get_index %= UART_BUF_LEN;

}

#define OVERTIME 50;

void UART_Rx_ISR(void)

{

    unsigned short dat;

    if(id >= MAX_UART_NUM)

        return;

        dat = USART1->DR;

    g_uart_rx_buf[id].int_rx.buffer[put_index]  = dat & 0xFF;

    g_uart_rx_buf[id].int_rx.put_index ++;

    g_uart_rx_buf[id].int_rx.put_index %= UART_BUF_LEN;

    g_uart_rx_buf[id].int_rx.lst_time = OVERTIME;

}

其中 lst_time 需要添加一个定时器来减,当时间到达0 即可关闭uart rx 中断,并置位标志位,让应用读取数据后再打开。 PS : 一位前辈和我说过这么一句话,其实破平台也能做出优质的产品,而如果代码机制是破的,再高频率的CPU 再好的平台做出来的产品都是很劣质的产品。好的代码机制能做出好的产品设计。

从uart到serial-ringbuff(环形缓存)相关推荐

  1. 2.12 FreeRTOS_RingBuff 环形缓存数组的使用

    前面我博客写了一篇<STM32 串口传输最佳处理方式 FreeRTOS+队列+DMA+IDLE(一)>就是利用RingBuff环形缓存数组来存数据,大家可以看着那边代码来看. 详细描述一下 ...

  2. 【Renesas RA6M4开发板之UART与Serial studio串口交互】

    [Renesas RA6M4开发板之UART与Serial studio串口交互] 1.0 UART简介 1.1 原理 1.2 访问 PWM 设备 2. RT-theard配置 2.1 硬件需求 2. ...

  3. C语言--环形缓存区

    环形缓存区工作原理 环形缓冲区是固定大小的缓冲区,工作原理就像内存是连续的且可循环.在生成和使用内存时,不需要将原来的数据全部清理掉,只要调整head/tail指针即可.当添加数据时,head指针前进 ...

  4. c语言环形存储,环形缓存区bufferC语言实现

    buffer[iput]=z; iput = addring(iput); n++; } else printf("Buffer is full\n"); } int main{v ...

  5. java 环形缓存_shuffle 中环形缓冲区

    shuffle中环形缓冲区使用于map shuffle阶段存放map的缓存数据,当缓冲区的数据达到一定比率(80%)就会将缓冲区的数据刷写到磁盘文件中,在刷盘之前,会对数据分区.排序.合并,对缓冲区的 ...

  6. qt 串口 环形缓存_qt linux串口 缓冲区多大

    满意答案 Zc的爱丶很美 2016.09.11 采纳率:51%    等级:9 已帮助:515人 一.程序设计的基础,例如:基本的编程语言基础,至少对数据类型.程序的结构及流程控制等最基本的内容要相当 ...

  7. linux 串口驱动 atmel_set_mctrl何时调用,linux uart serial使用驱动分析

    uart tty serial 驱动分析 内核版本3.14.23 以atmel为例: 起点: static int __init atmel_serial_init(void) { int ret; ...

  8. 环形缓冲区RingBuff的代码实现

    ~今天我们一起来聊一下环形缓冲区RingBuff又叫LoopBuff等等,都是相同的东西,只是一个名字不同罢了. ~我们在编写代码的时候缓冲区是几乎每个代码都必不可少的东西,比如存放串口接收的数据.做 ...

  9. 相信我,SDRAM真的不难(九)----基于SDRAM缓存的串口传图综合实战(UART + SDRAM + VGA)

    写在前面 本文是SDRAM系列文章的第九篇,前面八篇已经实现了一个简单的SDRAM控制器.正所谓光说不练云玩家,接下来我们搞搞实战,真正把SDRAM给用起来. 本文将结合UART模块.VGA模块.SD ...

最新文章

  1. View绘制流程的入口
  2. 浙江大学医学院附属儿童医院倪艳组招聘博士后和科研助理-肠道微生物和代谢方向...
  3. 三问TDD: 单元测试总是好的吗?
  4. Linux的基本使用
  5. 关于主窗体与子窗体之间的通信以及面向对象思想的一些应用
  6. [not] exists 和 in
  7. .NET方向高级开发人员面试时应该事先考虑的问题
  8. 【ENSP模拟器】ENSP问题:Cloud绑定信息只有UDP一个
  9. 混淆矩阵 confusion_matrix
  10. SELECT 1 FROM DUAL中的DUAL的作用
  11. 常见的http状态码以及https的通讯过程和DNS的解析过程
  12. php 与shell有什么关系,shell是什么意思
  13. Android平台下的图片/视频转Ascii码图片/视频 (一)
  14. 柬埔寨招聘中文计算机,柬埔寨ll中文老师1000美金+招聘机会来啦,快来围观!!!...
  15. Openstack采用ISO格式文件创建云主机
  16. Java实现 蓝桥杯VIP 算法训练 删除多余括号
  17. Photozoom图像放大的技术一二事
  18. JAVA实现PDF和EXCEL生成和数据动态插入以及导出
  19. 牙齿修复大致可分为哪几类?
  20. Python学习2,拆分plist图集,还原成小图

热门文章

  1. 练习print函数的使用(python)
  2. JavaScript工作原理
  3. FFmpeg系列-视频解码后保存帧图片为ppm
  4. Jmeter压力测试简单教程(包括服务器状态监控)-----转载自lsoqvle 的博客(https://blog.csdn.net/cbzcbzcbzcbz/article/details/780)
  5. TSCLIB.DLL函数库使用说明
  6. [附源码]SSM计算机毕业设计宠物寻回系统JAVA
  7. 过于执着其实没有什么好下场--《科学怪人之再生情缘》
  8. 暑期python培训价格
  9. 【高等数学基础进阶】定积分与反常积分-反常积分
  10. 6 实现多主机间 Docker 容器通信