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


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

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

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

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

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

    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 设备处于流模式(到底啥意思?–字符串模式,发送数据时会在' '前自动添加一个' ')

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

    先来看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 == '
    ')
    				{
    					while (!(uart->uart_device->SR & USART_FLAG_TXE));
    					uart->uart_device->DR = '
    ';
    		/* 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模式,即在字符串中遇到 换行,则自动插入 回车符。

    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的设备层中,所有的设备将形成一个链表。

  • 相关阅读:
    爬取阳光问政平台
    CrawlSpider爬取腾讯招聘信息
    LinkExtractor
    镜像源列表
    screen命令总结
    PHP解决中文乱码问题
    Linux查找大文件或目录
    CentOS6/7系列防火墙管理
    删除win10冗余的服务
    解决win10安装wireshark新版本,欢迎界面卡顿情况
  • 原文地址:https://www.cnblogs.com/zyqgold/p/3945604.html
Copyright © 2020-2023  润新知