RT-Thread下的串口驱动程序分析【转载】

2023-06-08,,

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

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

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

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

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

    usart.c

    usart.h

    serail.c

    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_UART1
USART_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_SIZE 64
#define UART_TX_DMA_NODE_SIZE 4   /* 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的设备层中,所有的设备将形成一个链表。

RT-Thread下的串口驱动程序分析【转载】的相关教程结束。

《RT-Thread下的串口驱动程序分析【转载】.doc》

下载本文的Word格式文档,以方便收藏与打印。