【RT-Thread学习笔记 6】RT-Thread下的串口驱动程序分析

来源:互联网 发布:邀请函app制作软件 编辑:程序博客网 时间:2024/06/07 02:16

编写本文稿的目的,在于通过分析stm32平台上的串口中断源码,学习

  • RTT中如何编写中断处理程序

  • 如何编写RTT设备驱动接口代码

  • 了解串行设备的常见处理机制

先以RTT官方源码中的STM32 BSP包来分析。rt-thread\bsp\stm32f10x 下,涉及的文件为:

  1. usart.c

  2. usart.h

  3. serail.c

  4. serail.h

RTT的设备驱动程序概述

编写uart的驱动程序,首先需要了解RTT的设备框架,RTT的设备框架我们已经大致的介绍了一下,这里以usart的驱动来具体分析RTT的IO设备管理。注:参考《RTT实时操作系统编程指南》 I/O设备管理一章。

我们可以将USART的硬件驱动分成两个部分,如下图所示

  +----------------------+
  | rtt下的usart设备驱动     |
  |---------------------- |
  | usart硬件初始化代码      |
  |---------------------- |
  | usart 硬件                  |
  +----------------------+

实际上,在缺乏操作系统的平台,即裸机平台上,我们通常只需要编写USART硬件初始化代码即可。而引入了RTOS,如RTT后,RTT中自带IO设备管理层,它是为了将各种各样的硬件设备封装成具有统一的接口的逻辑设备,以方便管理及使用。

让我们从下向上看,先来看看USART硬件初始化程序,这部分代码位于usart.c和usart.h中。

USART硬件初始化

假如在接触RTT之前,你已经对stm32很熟悉了,那么此文件中定义的函数名一定让你倍感亲切。这里实现的函数有:

  • static void RCC_Configuration(void);

  • static void GPIO_Configuration(void);

  • static void NVIC_Configuration(void);

  • static void DMA_Configuration(void);

  • void rt_hw_usart_init();

前四个函数,是跟ST官方固件库提供的示例代码的名字保持一致。这些函数内部也是直接调用官方库代码实现的。具体不再赘述。

对STM32裸机开发尚不太熟悉的朋友,建议先去官方网站下载官方固件源码包,以及应用手册和示例程序,ST提供了大量的文档和示例代码,利用好这些资源可以极大地加快开发。

现在来重点关注一下最后一个函数,即 rt_hw_usart_init函数的实现。

/* * Init all related hardware in here * rt_hw_serial_init() will register all supported USART device */void rt_hw_usart_init(){USART_InitTypeDef USART_InitStructure;USART_ClockInitTypeDef USART_ClockInitStructure;   RCC_Configuration();   GPIO_Configuration();   NVIC_Configuration();   DMA_Configuration();   /* uart init */#ifdef RT_USING_UART1USART_InitStructure.USART_BaudRate = 115200;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_ClockInitStructure.USART_Clock = USART_Clock_Disable;USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low;USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge;USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable;USART_Init(USART1, &USART_InitStructure);USART_ClockInit(USART1, &USART_ClockInitStructure);   /* register uart1 */rt_hw_serial_register(&uart1_device, "uart1",RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_STREAM,&uart1);   /* enable interrupt */USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);#endif   #ifdef RT_USING_UART2....#endif   #ifdef RT_USING_UART3....#endif}

上述代码中,大部分代码都是调用ST库函数,请注意下列语句。

/* register uart1 */rt_hw_serial_register(&uart1_device, "uart1",RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_STREAM,&uart1);

这个函数的实现位于serial.c中,我们将在下一小节分析,暂且不表。

显然,函数rt_hw_usart_init,顾名思义,是用于初始化USART硬件的函数,因此这个函数一定会在USART使用之前被调用。搜索工程发现,这个函数是在board.c中rt_hw_board_init函数中被调用,而rt_hw_board_init函数又是在startup.c里的 rtthread_startup函数中调用的。进一步在startup.c的main函数中调用的,我们将实际的路径调用过程绘制如下。

  startup.c main()  ---> startup.c rtthread_startup()  ---> board.c   rt_hw_board_init()   ---> usart.c   rt_hw_usart_init()

到这里,USART硬件的初始化工作已经完成完成了99%,下一步,我们需要为USART编写代码,将其纳入到RTT的设备管理层之中,正如前面所说,这部分代码在serial.c中实现。我们来重点分析这一文件。

在RTT下使用USART,将USART纳入RTT的IO设备层中

RTT IO设备驱动简介

要想将某个设备纳入到RTT的IO设备层中,需要为这个设备创建一个名为rt_device的数据结构。该数据结构在rtdef.h中定义。

/** * Device structure */struct rt_device{    struct rt_object parent;                        /**< inherit from rt_object                     */   enum rt_device_class_type type;                 /**< device type                                */    rt_uint16_t flag, open_flag;                    /**< device flag and device open flag           */   rt_uint8_t device_id;                           /* 0 - 255 */   /* device call back */    rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);    rt_err_t (*tx_complete)(rt_device_t dev, void* buffer);   /* common device interface */    rt_err_t  (*init)   (rt_device_t dev);    rt_err_t  (*open)   (rt_device_t dev, rt_uint16_t oflag);    rt_err_t  (*close)  (rt_device_t dev);    rt_size_t (*read)   (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);    rt_size_t (*write)  (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);    rt_err_t  (*control)(rt_device_t dev, rt_uint8_t cmd, void *args);   #ifdef RT_USING_DEVICE_SUSPEND   rt_err_t (*suspend) (rt_device_t dev);    rt_err_t (*resumed) (rt_device_t dev);#endif   void *user_data;                                /**< device private data                        */};

对这个数据结构做一些详细的说明。

  • struct rt_object parent;这个域是RTT的所谓的面向对象设计,跟我们关系不大。

  • type域配合前面的parent域,来制定设备的类型,也与我们关系不大。

  • flag和openflag用来存储设备的权限,比如是只读,还是读写等等。

  • device_id即设备号,每一个设备都拥有唯一的编号,内核可以根据这个编号查找到设备。

接下来就是定义了一组函数指针,用于操作这个设备的一些回调(callback)函数。他们分别是:

  rx_indicate  tx_complete  init  open  close  read  write  control

以及一个指针变量,由用户根据实际需要填充

  void *user_data;  

如果在rtconfig.h中使能了RT_USING_DEVICE_SUSPEND宏,还会增加两个函数

  rt_err_t (*suspend) (rt_device_t dev);  rt_err_t (*resumed) (rt_device_t dev);

这些域并不一定全部填充,后面我们会看到对于有些函数,可以为其填充一个空函数。

RTT的设备管理,可以简单的概括为:每一个设备都会用于一个rt_device数据结构,这些数据结构通过某种方式组织起来,每个数据结构都会有一个唯一的device_id,以及一组硬件操作函数等等。这样硬件就被抽象成统一的逻辑设备了,即rt_device。

还有一个小问题,device_id是纯粹的数字,所以难以记忆,因此RTT中为其分配一个ascii码字符串来以方便是使用,比如将字符串”uart”和usart的rt_device数据结构关联起来,这和网络里,ip地址不好记忆,因此使用域名系统是一个道理。

那么自然而然,我们需要一些函数来操作逻辑设备,这些函数在rt-thread/src/device.c文件中提供,它们是:

  • rt_err_t rt_device_register(rt_device_t dev, const char *name, rt_uint16_t flags)

  • 将rt_device数据结构加入到RTT的设备层中,这个过程称为“注册”。RTT的设备管理层会为这个数据结构创建唯一的device_id。

  • rt_err_t rt_device_unregister(rt_device_t dev)

    • 与注册相反,自然是注销了,将某个设备从RTT的设备驱动层中移除。

  • rt_device_t rt_device_find(const char *name)

    • 根据设备的字符串名查找某个设备。

  • rt_err_t rt_device_init(rt_device_t dev)

    • 通过调用rt_device数据结构中的init函数来初始设备。

  • rt_err_t rt_device_init_all(void)

    • 初始化RTT设备管理层中的所有已注册的设备

  • rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag)

    • 通过调用rt_device数据结构中的open函数来打开设备。

  • rt_err_t rt_device_close(rt_device_t dev)

    • 通过调用rt_device数据结构中的close函数来关闭设备。

  • rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)

    • 通过调用rt_device数据结构中的read函数来从设备上读取数据。

  • rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)

    • 通过调用rt_device数据结构中的write函数来向设备写入数据(比如设备是flash,SD卡等,nand or nor flash等等)。

说明:关于这些函数各个参数的作用,建议参考官方提供的API文档。http://www.rt-thread.org/rt-thread/rttdoc_1_0_0/group___device.html

分析USART下的RTT设备驱动源码

相对于stm32的内核来说,USART是一种低速的串行设备,并且为了最大的发挥的MCU的性能,因此使用中断方式实现接收(发送也可以使用DMA方式)。这些已经在usart.c中使能了。

串口接收情况

先来考虑串口接受数据的情况,串口收到一个字节的数据,就会触发串口中断USART1_IRQHandler,数据字节会存放于串口的硬件寄存器中。但是在RTOS中,通常存在多个线程,如果某个处理串口数据的线程在没有串口数据时阻塞,当下一串口数据到来时,如果该数据线程依然没有唤醒并启动,并读取串口字节,则上一个串口字节丢失了,因此这不是一个优良的设计,我们需要设计一种机制来解决这种潜在的问题。实际上,缓冲机制可以大大缓解这个问题。

所谓缓冲机制,简略的来说,即开辟一个缓冲区,可以是静态数组,也可以是malloc(或mempool)申请的动态缓冲区。在串口中断中,先从串口的硬件寄存器中读取数据,并保存到缓冲区中。这种情况下,我们需要两个变量,一个用于标记当前写入的位置,另外一个用来表示已经被处理的数据的位置。这样当数据处理线程阻塞时,连续收到的数据会保存到缓冲区中而避免了丢失。当中断中已经接收到了一些串口数据后,数据处理线程终于就绪,并开始处理数据,通常来说处理数据的速度必然比接受到的数据要快,因此这样就能解决前面所说的问题。

【图】

聪明的读者发现了,还有一个小问题,缓冲区的长度必然是有限的,终归会有用到头的时候,那该怎么办呢?别担心,缓冲区前面已经被处理过的数据所占用的空间按自然可以重复使用,即,当接收指针指向了缓冲区末尾时,只要缓冲区头的数据已经被处理过了,自然可以直接将缓冲区指针从新设置为头,对于表示已处理的指针变量同理。这样这个缓冲区也就成为了一个环形缓冲区。

关于环形缓冲区,可以参考:http://www.rt-thread.org/dokuwiki/doku.php?id=rt-thread%E4%B8%80%E8%88%AC%E6%80%A7%E9%97%AE%E9%A2%98

串口发送情况

RTT在stm32的串口发送上,为了最大限度的发挥硬件的效能,使用了DMA来实现自动发送。同接收类似,也使用了缓冲机制。不过因为涉及的DMA,这个机制实现稍微复杂,我们将在稍后做分析。

源码分析

先来看看一些重要数据结构,它们在serial.h中定义:

/* STM32F10x library definitions */#include <stm32f10x.h>   #define UART_RX_BUFFER_SIZE64#define UART_TX_DMA_NODE_SIZE4   /* data node for Tx Mode */struct stm32_serial_data_node{rt_uint8_t *data_ptr;rt_size_t  data_size;struct stm32_serial_data_node *next, *prev;};struct stm32_serial_dma_tx{/* DMA Channel */DMA_Channel_TypeDef* dma_channel;   /* data list head and tail */struct stm32_serial_data_node *list_head, *list_tail;   /* data node memory pool */struct rt_mempool data_node_mp;rt_uint8_t data_node_mem_pool[UART_TX_DMA_NODE_SIZE *(sizeof(struct stm32_serial_data_node) + sizeof(void*))];};   struct stm32_serial_int_rx{rt_uint8_t  rx_buffer[UART_RX_BUFFER_SIZE];rt_uint32_t read_index, save_index;};   struct stm32_serial_device{USART_TypeDef* uart_device;   /* rx structure */struct stm32_serial_int_rx* int_rx;   /* tx structure */struct stm32_serial_dma_tx* dma_tx;};

可以看到,对于stm32的串行设备,抽象为一个专门的数据结构 struct stm32_serial_device uart_device域将用来填充具体的硬件USART指针,在stm32系列芯片上,可能存在USART1到USART3多个硬件USART。

int_rx是一个专门的用于处理接受数据的数据结构指针。dma_tx同理,关于它们的具体定义都在前面的代码中。

struct stm32_serial_int_rx {

rt_uint8_t  rx_buffer[UART_RX_BUFFER_SIZE];rt_uint32_t read_index, save_index;

}; 可以看到,跟上一小节说明类似,这里定义了一个名为rx_buffer的缓冲区,并且定义了两个变量read_index表示已经读取(即已被处理)的索引,而save_index,则表示下一个可以用于存储接受数据的索引。

接下来,让我们深入代码,来看看究竟是如何处理的:首先来看看USART1_IRQHandler(void)的源码,位于stm32f10x_it.c中

void USART1_IRQHandler(void){#ifdef RT_USING_UART1    extern struct rt_device uart1_device;extern void rt_hw_serial_isr(struct rt_device *device);   /* enter interrupt */    rt_interrupt_enter();   rt_hw_serial_isr(&uart1_device);   /* leave interrupt */    rt_interrupt_leave();#endif}

在RTT下的每一个中断服务子程序的入口都调用了

rt_interrupt_enter();

在中断函数的子程序的出口则调用了

rt_interrupt_leave();

中间的函数 rt_hw_serial_isr,来重点关注一下:

/* ISR for serial interrupt */void rt_hw_serial_isr(rt_device_t device){struct stm32_serial_device* uart = (struct stm32_serial_device*) device->user_data;   if(USART_GetITStatus(uart->uart_device, USART_IT_RXNE) != RESET)        //判断标志位,判断是否是能了接受中断{/* interrupt mode receive */RT_ASSERT(device->flag & RT_DEVICE_FLAG_INT_RX);   /* save on rx buffer */while (uart->uart_device->SR & USART_FLAG_RXNE)            //从datasheet上查到,SR的RXNE标志位表示确实接收到了字节{rt_base_t level;   /* disable interrupt */            //暂时关闭中断,因为要操作uart数据结构level = rt_hw_interrupt_disable();   /* save character */uart->int_rx->rx_buffer[uart->int_rx->save_index] = uart->uart_device->DR & 0xff;uart->int_rx->save_index ++;    //下面的代码检查save_index是否已经到到缓冲区尾部,如果是则回转到头部,称为一个环形缓冲区if (uart->int_rx->save_index >= UART_RX_BUFFER_SIZE)uart->int_rx->save_index = 0;   //这种情况表示反转后的save_index追上了read_index,则增大read_index,丢弃一个旧的数据/* if the next position is read index, discard this 'read char' */if (uart->int_rx->save_index == uart->int_rx->read_index){uart->int_rx->read_index ++;if (uart->int_rx->read_index >= UART_RX_BUFFER_SIZE)uart->int_rx->read_index = 0;}   /* enable interrupt *///uart数据结构已经操作完成,重新使能中断rt_hw_interrupt_enable(level);}   /* clear interrupt */USART_ClearITPendingBit(uart->uart_device, USART_IT_RXNE);   /* invoke callback */if (device->rx_indicate != RT_NULL){rt_size_t rx_length;   /* get rx length */rx_length = uart->int_rx->read_index > uart->int_rx->save_index ?UART_RX_BUFFER_SIZE - uart->int_rx->read_index + uart->int_rx->save_index :uart->int_rx->save_index - uart->int_rx->read_index;   device->rx_indicate(device, rx_length);}}   if (USART_GetITStatus(uart->uart_device, USART_IT_TC) != RESET){/* clear interrupt */USART_ClearITPendingBit(uart->uart_device, USART_IT_TC);}}

这里来重点说明一下下面代码的作用。【绘制图形,待添加】

        //这种情况表示反转后的save_index追上了read_index,则增大read_index,丢弃一个旧的数据        /* if the next position is read index, discard this 'read char' */        if (uart->int_rx->save_index == uart->int_rx->read_index)        {            uart->int_rx->read_index ++;            if (uart->int_rx->read_index >= UART_RX_BUFFER_SIZE)                uart->int_rx->read_index = 0;        }

这段代码又是做什么用的呢?

    /* invoke callback */    if (device->rx_indicate != RT_NULL)    {        rt_size_t rx_length;   /* get rx length */        rx_length = uart->int_rx->read_index > uart->int_rx->save_index ?            UART_RX_BUFFER_SIZE - uart->int_rx->read_index + uart->int_rx->save_index :            uart->int_rx->save_index - uart->int_rx->read_index;   device->rx_indicate(device, rx_length);    }

默认情况下usart的rt_device结构体中rx_indicate域被置空,因此不会运行这一段代码。如果使用rt_device_set_rx_indicate(rt_device_t dev, rt_err_t(* rx_ind)(rt_device_t dev, rt_size_t size))函数为一个串口设备注册了接收事件回调函数,在该串口接收到数据后,就会调用之前注册的rx_ind函数,将当前设备指针以及待读取的数据长度作为调用参数传递给用户

编写设备函数,open,close等等

分析完毕中断处理程序,接下来我们要分析rt_devcie数据结构中,open,read等函数的编写。

init

init函数完成对设备数据结构的初始化工作。 RTT的设备驱动存在大量的预定义宏,它们在rtdef.h中定义。

(1)接收/发送模式,似乎共有三种,分别是中断模式,DMA模式和轮询模式。在serial.c中,对于接收,只支持中断模式,和轮询模式。对于发送,只支持轮询发送模式和DMA发送模式。

|------+----------------+----------------+---------||      | FLAG_INT_RX/TX | FLAG_DMA_RX/TX | polling ||------+----------------+----------------+---------|| 发送 | Yes            | no             | yes     ||------+----------------+----------------+---------|| 接受 | no             | yes            | yes     ||------+----------------+----------------+---------|

(2)设备权限分为只读,只写和读写三种,分别由 RT_DEVICE_FLAG_RDONLY 只读 RT_DEVICE_FLAG_WRONLY 只写 RT_DEVICE_FLAG_RDWR 读写

(3)设备当前状态 RT_DEVICE_FLAG_REMOVABLE 可移除设备 RT_DEVICE_FLAG_STANDALONE 啥意思??? RT_DEVICE_FLAG_ACTIVATED 设备处于活动状态,表示设备已经被init过了 RT_DEVICE_FLAG_SUSPENDED 设备当前被挂起 RT_DEVICE_FLAG_STREAM 设备处于流模式(到底啥意思?–字符串模式,发送数据时会在'\n'前自动添加一个'\r')

注意,上面描述的这么多状态,在一个设备驱动中并非全部都需要予以支持。这需要根据自驱动的实际情况实现。

先来看init函数,如下所示:

static rt_err_t rt_serial_init (rt_device_t dev){struct stm32_serial_device* uart = (struct stm32_serial_device*) dev->user_data;   if (!(dev->flag & RT_DEVICE_FLAG_ACTIVATED)){if (dev->flag & RT_DEVICE_FLAG_INT_RX){rt_memset(uart->int_rx->rx_buffer, 0,sizeof(uart->int_rx->rx_buffer));uart->int_rx->read_index = 0;uart->int_rx->save_index = 0;}        ......   /* Enable USART */USART_Cmd(uart->uart_device, ENABLE);   dev->flag |= RT_DEVICE_FLAG_ACTIVATED;}   return RT_EOK;}开始时,设备的dev->flag域全是0,即为非激活模式,如果RX为INT_RX,模式,可以看到即简单的将uart->int_rx全部清0。
open

因为在usart.c中已经初始usart设备,然后init中通过USART_Cmd语句后,串口就会开始工作。因此open函数设置为空即可

close

同colse,之间置空即可

read

static rt_size_t rt_serial_read (rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size)

pos表示读写的位置,buffer是用于存储读取到数据的缓冲区。size为字节数目。对于USART这种串行的流设备来说,pos没有意义,因此这里的pos没有意义。 rt_device数据结构dev的的 user_data域存放了(struct stm32_serial_device*)型指针。【待修改】如果采用INT_RX模式,即中断接受模式,则主体代码为

while (size){rt_base_t level;   /* disable interrupt */level = rt_hw_interrupt_disable();   if (uart->int_rx->read_index != uart->int_rx->save_index){/* read a character */*ptr++ = uart->int_rx->rx_buffer[uart->int_rx->read_index];size--;   /* move to next position */uart->int_rx->read_index ++;if (uart->int_rx->read_index >= UART_RX_BUFFER_SIZE)uart->int_rx->read_index = 0;}else{/* set error code */err_code = -RT_EEMPTY;   /* enable interrupt */rt_hw_interrupt_enable(level);break;}   /* enable interrupt */rt_hw_interrupt_enable(level);}   </code c>上述代码很容易理解,不再赘述。   如果采用查询模式,则主体代码为:<code c>/* polling mode */while ((rt_uint32_t)ptr - (rt_uint32_t)buffer < size){while (uart->uart_device->SR & USART_FLAG_RXNE){*ptr = uart->uart_device->DR & 0xff;ptr ++;}}

这个函数返回实际读到的数据数目。

write

向串口写入数据,即发送数据。

(1) INT_TX模式,则报错

/* interrupt mode Tx, does not support */RT_ASSERT(0);

(2) DMA模式

写操作处理部分:

if (dev->flag & RT_DEVICE_FLAG_DMA_TX){/* DMA mode Tx */   /* allocate a data node */struct stm32_serial_data_node* data_node = (struct stm32_serial_data_node*)rt_mp_alloc (&(uart->dma_tx->data_node_mp), RT_WAITING_FOREVER);if (data_node == RT_NULL){/* set error code */err_code = -RT_ENOMEM;}else{rt_uint32_t level;   /* fill data node */data_node->data_ptr = ptr;data_node->data_size = size;   /* insert to data link */data_node->next = RT_NULL;   /* disable interrupt */level = rt_hw_interrupt_disable();   data_node->prev = uart->dma_tx->list_tail;if (uart->dma_tx->list_tail != RT_NULL)uart->dma_tx->list_tail->next = data_node;uart->dma_tx->list_tail = data_node;   if (uart->dma_tx->list_head == RT_NULL){/* start DMA to transmit data */uart->dma_tx->list_head = data_node;   /* Enable DMA Channel */rt_serial_enable_dma(uart->dma_tx->dma_channel,(rt_uint32_t)uart->dma_tx->list_head->data_ptr,uart->dma_tx->list_head->data_size);}   /* enable interrupt */rt_hw_interrupt_enable(level);}}

在DMA发送模式下,uart驱动将为每次写操作分配一个data_node数据节点,将本次写入的数据指针地址、长度写入此节点,并其插入到uart→dma_tx链表尾部,等待DMA中断处理此节点。

若判断到当前发送链表头为空时

uart->dma_tx->list_head == RT_NULL

说明没有正在进行的DMA活动,则将新加入的节点设置为链表头,启动DMA,开始发送数据。

(3)轮询模式

/* polling mode */if (dev->flag & RT_DEVICE_FLAG_STREAM){/* stream mode */while (size){if (*ptr == '\n'){while (!(uart->uart_device->SR & USART_FLAG_TXE));uart->uart_device->DR = '\r';/* interrupt mode Tx, does not support */RT_ASSERT(0);}   while (!(uart->uart_device->SR & USART_FLAG_TXE));uart->uart_device->DR = (*ptr & 0x1FF);   ++ptr; --size;}}else{/* write data directly */while (size){while (!(uart->uart_device->SR & USART_FLAG_TXE));uart->uart_device->DR = (*ptr & 0x1FF);   ++ptr; --size;}}

从上面的代码可以看到,所谓的STREAM模式,即在字符串中遇到\n换行,则自动插入\r回车符。

control
static rt_err_t rt_serial_control (rt_device_t dev, rt_uint8_t cmd, void *args){struct stm32_serial_device* uart;   RT_ASSERT(dev != RT_NULL);   uart = (struct stm32_serial_device*)dev->user_data;switch (cmd){case RT_DEVICE_CTRL_SUSPEND:/* suspend device */dev->flag |= RT_DEVICE_FLAG_SUSPENDED;USART_Cmd(uart->uart_device, DISABLE);break;   case RT_DEVICE_CTRL_RESUME:/* resume device */dev->flag &= ~RT_DEVICE_FLAG_SUSPENDED;USART_Cmd(uart->uart_device, ENABLE);break;}   return RT_EOK;}

这个函数非常容易懂,不再赘述。

注册USART的rt_device结构
rt_err_t rt_hw_serial_register(rt_device_t device, const char* name, rt_uint32_t flag, struct stm32_serial_device *serial){RT_ASSERT(device != RT_NULL);   if ((flag & RT_DEVICE_FLAG_DMA_RX) ||(flag & RT_DEVICE_FLAG_INT_TX)){RT_ASSERT(0);}   device->type = RT_Device_Class_Char;device->rx_indicate = RT_NULL;device->tx_complete = RT_NULL;device->init = rt_serial_init;device->open= rt_serial_open;device->close= rt_serial_close;device->read = rt_serial_read;device->write = rt_serial_write;device->control = rt_serial_control;device->user_data= serial;   /* register a character device */return rt_device_register(device, name, RT_DEVICE_FLAG_RDWR | flag);}

上面的函数也同样利于理解,只是简单的填充device数据结构。需要注意两个地方。

device->user_data= serial; 

user_data域用于存储struct stm32_serial_device *serial

最后调用rt_device_register函数将rt_device注册到RTT的设备层中,所有的设备将形成一个链表。

1 0
原创粉丝点击