编写本文稿的目的,在于通过分析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的设备层中,所有的设备将形成一个链表。