RT-Thread下的串口驅動程序分析【轉載】

編寫本文稿的目的,在於經過分析stm32平臺上的串口中斷源碼,學習 php

  • RTT中如何編寫中斷處理程序html

  • 如何編寫RTT設備驅動接口代碼node

  • 瞭解串行設備的常見處理機制編程

先以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的設備層中,全部的設備將造成一個鏈表。

相關文章
相關標籤/搜索