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

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

时间: 2024-10-05 22:09:11

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

STM32 + RT Thread OS 串口通讯

1.   创建项目 a)   禁用Finsh和console b)   默认情况下,项目文件包含了finsh,它使用COM1来通讯,另外,console输出(rt_kprintf)也使用了COM1.因此,在运行scons命令生成项目文件之前,修改rtconfig.h,禁用这两项.(下图L65, L70) c)   生成项目文件 运行scons --target=mdk4 –s 打开生成的项目文件,可以看到,文件组finsh已经不再被包含进来了. d)   创建echo.c 新建一个C文件echo

RT Thread学习历程(1):串口乱码问题

因为学习实时系统,最近接触到RT Thread. 把RT Thread官网上的示例代码烧录到STM32的板子上之后,在串口软件上接收到的全是乱码,一开始以为是串口软件的问题,换了2个软件之后情况都一样,最后发现是晶振的问题,我用的是STM32F407VGT6,晶振要设为8MHz,代码相应的设置晶振的部分也要修改.

【转载】使用Win32API实现Windows下异步串口通讯

一,异步非阻塞串口通讯的优点 读写串行口时,既可以同步执行,也可以重叠(异步)执行.在同步执行时,函数直到操作完成后才返回.这意味着在同步执行时线程会被阻塞,从而导致效率下降.在重叠执行时,即使操作还未完成,调用的函数也会立即返回.费时的I/O操作在后台进行,这样线程就可以干别的事情.例如,线程可以在不同的句柄上同时执行I/O操作,甚至可以在同一句柄上同时进行读写操作."重叠"一词的含义就在于此. 二,异步非阻塞串口通讯的基本原理首先,确定要打开的串口名.波特率.奇偶校验方式.数据位.

android 电容屏(三):驱动调试之驱动程序分析篇

平台信息: 内核:linux3.4.39系统:android4.4 平台:S5P4418(cortex a9) 作者:瘋耔(欢迎转载,请注明作者) 欢迎指正错误,共同学习.共同进步!! 关注博主新浪博客:http://weibo.com/cpjphone   以goodix的gt8105为例 一.总体架构 硬件部分:先看一个总体的图吧,其实触摸屏原理也比较简单,触摸屏和主控芯片间的联系,如下主要有三部分: 1.IIC部分,初始化gt8105的数据和传回主控制的坐标位置信息就是通过IIC这条线传输

linux串口驱动分析

linux串口驱动分析 硬件资源及描写叙述 s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)port,每一个port都能够在中断模式或 DMA 模式下操作.UART 使用系统时钟能够支持最高 115.2Kbps 的波特率.每一个 UART 通道对于接收器和发送器包含了 2 个 64 位的 FIFO. 寄存器 名称 地址 在linux中的描写叙述 (2410 和 2440 处理器对内存地址映射关系同样) UART 线性控制寄存器(ULCONn) ULC

Linux中块设备驱动程序分析

基于<Linux设备驱动程序>书中的sbull程序以对Linux块设备驱动总结分析. 开始之前先来了解这个块设备中的核心数据结构: struct sbull_dev { int size;                       /* Device size in sectors */ u8 *data;                       /* The data array */ short users;                    /* How many users

tiny4412 串口驱动分析四 --- 修改默认的串口输出

作者:彭东林 邮箱:[email protected] 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 u-boot:U-Boot 2010.12 Linux内核版本:linux-3.0.31 Android版本:android-4.1.2 tiny4412默认是从uart0来输出和读取信息的,而tiny4412上留了两个串口,分别对应的是uart0和uart3,下面我们修改配置,使控制终端从uart0变成ua

debian下使用dynamic printk分析usb转串口驱动执行流程

看了一篇文章<debug by printing>,文中提到了多种通过printk来调试驱动的方法,其中最有用的就是"Dynamic debugging". “Dynamic debugging"的官方文档:http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/dynamic-debug-howto.txt?id=HEAD "Dyanmic de

Handler Thread 内部类引起内存泄露分析

非静态内部类引起内存泄漏的原因 内部类的实现其实是通过编译器的语法糖(Syntactic sugar)实现的,通过生成相应的子类即以OutClassName$InteriorClassName命名的Class文件.并添加构造函数,在构造函数中[传入]外部类,这也是为什么内部类能使用外部类的方法与字段的原因.所以,当外部类与内部类生命周期不一致的时候很有可能发生内存泄漏. Handler引起内存泄漏案例分析 例如,当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有