日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

RT thread 设备驱动组件之USART设备

發(fā)布時間:2023/12/3 综合教程 34 生活家
生活随笔 收集整理的這篇文章主要介紹了 RT thread 设备驱动组件之USART设备 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

? ? ? 本文以stm32f4xx平臺介紹串口驅(qū)動,主要目的是:1、RTT中如何編寫中斷處理程序;2、如何編寫RTT設(shè)備驅(qū)動接口代碼;3、了解串行設(shè)備的常見處理機制。所涉及的主要源碼文件有:驅(qū)動框架文件(usart.c,usart.h),底層硬件驅(qū)動文件(serial.c,serial.h)。應(yīng)用串口設(shè)備驅(qū)動時,需要在rtconfig.h中宏定義#define RT_USING_SERIAL。

一、RTT的設(shè)備驅(qū)動程序概述

? ? ? 編寫uart的驅(qū)動程序,首先需要了解RTT的設(shè)備框架,這里以usart的驅(qū)動來具體分析RTT的IO設(shè)備管理。注:參考《RTT實時操作系統(tǒng)編程指南》 I/O設(shè)備管理一章。

我們可以將USART的硬件驅(qū)動分成兩個部分,如下圖所示

? +----------------------+
? | rtt下的usart設(shè)備驅(qū)動???? |
? |---------------------- |
? | usart硬件初始化代碼????? |
? |---------------------- |
? | usart 硬件????????????????? |
? +----------------------+

? ? ? 實際上,在缺乏操作系統(tǒng)的平臺,即裸機平臺上,我們通常只需要編寫USART硬件初始化代碼即可。而引入了RTOS,如RTT后,RTT中自帶IO設(shè)備管理層,它是為了將各種各樣的硬件設(shè)備封裝成具有統(tǒng)一的接口的邏輯設(shè)備,以方便管理及使用。讓我們從下向上看,先來看看USART硬件初始化程序,這部分代碼位于usart.c和usart.h中。

二、USART硬件初始化

假如在接觸RTT之前,你已經(jīng)對stm32很熟悉了,那么此文件中定義的函數(shù)名一定讓你倍感親切。這里實現(xiàn)的函數(shù)有:

  • static void RCC_Configuration(void);

  • static void GPIO_Configuration(void);

  • static void NVIC_Configuration(void);

  • static void DMA_Configuration(void);

  • void stm32_hw_usart_init();

前四個函數(shù),是跟ST官方固件庫提供的示例代碼的名字保持一致。這些函數(shù)內(nèi)部也是直接調(diào)用官方庫代碼實現(xiàn)的。具體不再贅述。對STM32裸機開發(fā)不太熟悉的朋友,建議先去官方網(wǎng)站下載官方固件源碼包,以及應(yīng)用手冊和示例程序,ST提供了大量的文檔和示例代碼,利用好這些資源可以極大地加快開發(fā)。

下面重點看一下 usart.c中stm32_hw_usart_init():

int stm32_hw_usart_init(void)
{struct stm32_uart *uart;struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;RCC_Configuration();GPIO_Configuration();#ifdef RT_USING_UART2uart = &uart2;serial2.ops    = &stm32_uart_ops;serial2.config = config;NVIC_Configuration(&uart2);/* register UART2 device */rt_hw_serial_register(&serial2,"uart2",RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, //dev->flag (not dev->open_flag)
                          uart);
#endif /* RT_USING_UART2 */#ifdef RT_USING_UART3
...
#endif /* RT_USING_UART3 */return 0;
}
//INIT_BOARD_EXPORT(stm32_hw_usart_init); //it must be invoked in board.c(rt_hw_board_init for setting CONSOLE_DEVICE)

? ? ? 首先該函數(shù)調(diào)用RCC_Configuration()和GPIO_Configuration()打開串口外設(shè)時鐘和IO口配置;調(diào)用NVIC_Configuration(&uart2)設(shè)置串口中斷,其中參數(shù)&uart2為一個自定義結(jié)構(gòu)體類型:

/* STM32 uart driver */
struct stm32_uart
{USART_TypeDef *uart_device;IRQn_Type irq;
};

? ? ? 接著初始化設(shè)備類對象serial2中的ops和config兩個參數(shù),并在usart.c中實現(xiàn)了stm32_uart_ops中的四個函數(shù)。

static const struct rt_uart_ops stm32_uart_ops =
{stm32_configure,stm32_control,stm32_putc,stm32_getc,
};
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;

? ? ? 最后注冊串口設(shè)備serial2,注冊函數(shù)位于serial.c中:

/* register UART2 device */
rt_hw_serial_register(&serial2,"uart2",RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, //dev->flag (not dev->open_flag)uart);

? ? ? 顯然,函數(shù)?stm32_hw_usart_init(),顧名思義,是用于初始化USART硬件的函數(shù),因此這個函數(shù)一定會在USART使用之前被調(diào)用。搜索工程發(fā)現(xiàn),這個函數(shù)是在board.c中rt_hw_board_init函數(shù)中被調(diào)用,而rt_hw_board_init函數(shù)又是在startup.c里的 rtthread_startup函數(shù)中調(diào)用的。進一步在startup.c的main函數(shù)中調(diào)用的,我們將實際的路徑調(diào)用過程繪制如下。

  startup.c main()---> startup.c rtthread_startup()---> board.c   rt_hw_board_init() ---> usart.c   rt_hw_usart_init()

? ? ? 到這里,USART硬件的初始化工作已經(jīng)完成完成了99%,下一步,我們需要為USART編寫代碼,將其納入到RTT的設(shè)備管理層之中,正如前面所說,這部分代碼在serial.c中實現(xiàn)。我們來重點分析這一文件。

三、在RTT下使用USART,將USART納入RTT的IO設(shè)備層中

? ? ? 相對于stm32的內(nèi)核來說,USART是一種低速的串行設(shè)備,并且為了最大的發(fā)揮的MCU的性能,因此使用查詢方式發(fā)送、中斷方式接收(發(fā)送也可以使用DMA方式)。這些已經(jīng)在usart.c中使能了。首先看一些serial.h中的重要數(shù)據(jù)結(jié)構(gòu):

/** Serial FIFO mode */
struct rt_serial_rx_fifo
{/* software fifo */rt_uint8_t *buffer;rt_uint16_t put_index, get_index;
};struct rt_serial_tx_fifo
{struct rt_completion completion;
};/* * Serial DMA mode*/
struct rt_serial_rx_dma
{rt_bool_t activated;
};struct rt_serial_tx_dma
{rt_bool_t activated;struct rt_data_queue data_queue;
};struct rt_serial_device
{struct rt_device          parent;const struct rt_uart_ops *ops;struct serial_configure   config;void *serial_rx;void *serial_tx;
};
typedef struct rt_serial_device rt_serial_t;
/*** uart operators*/
struct rt_uart_ops
{rt_err_t (*configure)(struct rt_serial_device *serial, struct serial_configure *cfg);rt_err_t (*control)(struct rt_serial_device *serial, int cmd, void *arg);int (*putc)(struct rt_serial_device *serial, char c);int (*getc)(struct rt_serial_device *serial);rt_size_t (*dma_transmit)(struct rt_serial_device *serial, const rt_uint8_t *buf, rt_size_t size, int direction);
};

在serial.c中主要實現(xiàn)rtthread系統(tǒng)的IO設(shè)備統(tǒng)一接口函數(shù),并注冊串口設(shè)備:

/** serial register*/
rt_err_t rt_hw_serial_register(struct rt_serial_device *serial,const char              *name,rt_uint32_t              flag,void                    *data)
{struct rt_device *device;RT_ASSERT(serial != RT_NULL);device = &(serial->parent);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   = data;/* register a character device */return rt_device_register(device, name, flag);
}

? ? ? ?對于串口發(fā)送數(shù)據(jù),默認采用查詢方式。因為串口設(shè)備注冊的時候,其設(shè)備標志為RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,沒有RT_DEVICE_FLAG_DMA_TX或RT_DEVICE_FLAG_INT_TX標志。 數(shù)據(jù)發(fā)送流程為:rt_device_write()-->rt_serial_write()-->_serial_poll_tx()-->stm32_putc()。

? ? ? 考慮串口接受數(shù)據(jù)的情況,串口收到一個字節(jié)的數(shù)據(jù),就會觸發(fā)串口中斷USARTx_IRQHandler,數(shù)據(jù)字節(jié)會存放于串口的硬件寄存器中。但是在RTOS中,通常存在多個線程,如果某個處理串口數(shù)據(jù)的線程在沒有串口數(shù)據(jù)時阻塞,當下一串口數(shù)據(jù)到來時,如果該數(shù)據(jù)線程依然沒有喚醒并啟動,并讀取串口字節(jié),則上一個串口字節(jié)丟失了,因此這不是一個優(yōu)良的設(shè)計,我們需要設(shè)計一種機制來解決這種潛在的問題。實際上,緩沖機制可以大大緩解這個問題。數(shù)據(jù)讀取流程為:rt_device_read()-->rt_serial_read()-->rt_hw_serial_isr()-->stm32_getc()。

? ? ? 所謂緩沖機制,簡略的來說,即開辟一個緩沖區(qū),可以是靜態(tài)數(shù)組,也可以是malloc(或mempool)申請的動態(tài)緩沖區(qū)。在串口中斷中,先從串口的硬件寄存器中讀取數(shù)據(jù),并保存到緩沖區(qū)中。這種情況下,我們需要兩個變量,一個用于標記當前寫入的位置,另外一個用來表示已經(jīng)被處理的數(shù)據(jù)的位置。這樣當數(shù)據(jù)處理線程阻塞時,連續(xù)收到的數(shù)據(jù)會保存到緩沖區(qū)中而避免了丟失。當中斷中已經(jīng)接收到了一些串口數(shù)據(jù)后,數(shù)據(jù)處理線程終于就緒,并開始處理數(shù)據(jù),通常來說處理數(shù)據(jù)的速度必然比接受數(shù)據(jù)要快,因此這樣就能解決前面所說的問題。聰明的讀者發(fā)現(xiàn)了,還有一個小問題,緩沖區(qū)的長度必然是有限的,終歸會有用到頭的時候,那該怎么辦呢?別擔心,緩沖區(qū)前面已經(jīng)被處理過的數(shù)據(jù)所占用的空間按自然可以重復(fù)使用,即,當接收指針指向了緩沖區(qū)末尾時,只要緩沖區(qū)頭的數(shù)據(jù)已經(jīng)被處理過了,自然可以直接將緩沖區(qū)指針從新設(shè)置為頭,對于表示已處理的指針變量同理。這樣這個緩沖區(qū)也就成為了一個環(huán)形緩沖區(qū)。

下面重點看一下中斷函數(shù):

#if defined(RT_USING_UART2)
/* UART2 device driver structure */
struct stm32_uart uart2 =
{USART2,USART2_IRQn,
};
struct rt_serial_device serial2;void USART2_IRQHandler(void)
{struct stm32_uart *uart;uart = &uart2;/* enter interrupt */rt_interrupt_enter();if (USART_GetITStatus(uart->uart_device, USART_IT_RXNE) != RESET){rt_hw_serial_isr(&serial2, RT_SERIAL_EVENT_RX_IND);//USART_IT_RXNE is cleared automatically by reading USART_DR in stm32_getc()
    }if (USART_GetITStatus(uart->uart_device, USART_IT_TC) != RESET){/* clear interrupt */USART_ClearITPendingBit(uart->uart_device, USART_IT_TC);}/* leave interrupt */rt_interrupt_leave();
}
#endif /* RT_USING_UART2 */

該中斷函數(shù)在usart.c中,在RTT下的每一個中斷服務(wù)子程序的入口都調(diào)用了rt_interrupt_enter(),在中斷函數(shù)的子程序的出口則調(diào)用了rt_interrupt_leave()。
/* ISR for serial interrupt */
void rt_hw_serial_isr(struct rt_serial_device *serial, int event)
{switch (event & 0xff){case RT_SERIAL_EVENT_RX_IND:{int ch = -1;rt_base_t level;struct rt_serial_rx_fifo* rx_fifo;rx_fifo = (struct rt_serial_rx_fifo*)serial->serial_rx;RT_ASSERT(rx_fifo != RT_NULL);/* interrupt mode receive */RT_ASSERT(serial->parent.open_flag & RT_DEVICE_FLAG_INT_RX);while (1){ch = serial->ops->getc(serial);if (ch == -1) break;/* disable interrupt */level = rt_hw_interrupt_disable();rx_fifo->buffer[rx_fifo->put_index] = ch;rx_fifo->put_index += 1;if (rx_fifo->put_index >= serial->config.bufsz) rx_fifo->put_index = 0;/* if the next position is read index, discard this 'read char' */if (rx_fifo->put_index == rx_fifo->get_index){rx_fifo->get_index += 1;if (rx_fifo->get_index >= serial->config.bufsz) rx_fifo->get_index = 0;}/* enable interrupt */rt_hw_interrupt_enable(level);}/* invoke callback */if (serial->parent.rx_indicate != RT_NULL){rt_size_t rx_length;/* get rx length */level = rt_hw_interrupt_disable();rx_length = (rx_fifo->put_index >= rx_fifo->get_index)? (rx_fifo->put_index - rx_fifo->get_index):(serial->config.bufsz - (rx_fifo->get_index - rx_fifo->put_index));rt_hw_interrupt_enable(level);serial->parent.rx_indicate(&serial->parent, rx_length);}break;}case RT_SERIAL_EVENT_TX_DONE:{struct rt_serial_tx_fifo* tx_fifo;tx_fifo = (struct rt_serial_tx_fifo*)serial->serial_tx;rt_completion_done(&(tx_fifo->completion));break;}case RT_SERIAL_EVENT_TX_DMADONE:{const void *data_ptr;rt_size_t data_size;const void *last_data_ptr;struct rt_serial_tx_dma* tx_dma;tx_dma = (struct rt_serial_tx_dma*) serial->serial_tx;rt_data_queue_pop(&(tx_dma->data_queue), &last_data_ptr, &data_size, 0);if (rt_data_queue_peak(&(tx_dma->data_queue), &data_ptr, &data_size) == RT_EOK){/* transmit next data node */tx_dma->activated = RT_TRUE;serial->ops->dma_transmit(serial, data_ptr, data_size, RT_SERIAL_DMA_TX);}else{tx_dma->activated = RT_FALSE;}/* invoke callback */if (serial->parent.tx_complete != RT_NULL){serial->parent.tx_complete(&serial->parent, (void*)last_data_ptr);}break;}case RT_SERIAL_EVENT_RX_DMADONE:{int length;struct rt_serial_rx_dma* rx_dma;rx_dma = (struct rt_serial_rx_dma*)serial->serial_rx;/* get DMA rx length */length = (event & (~0xff)) >> 8;serial->parent.rx_indicate(&(serial->parent), length);rx_dma->activated = RT_FALSE;break;}}
}

該函數(shù)位于serial.c中,默認情況下usart的rt_device結(jié)構(gòu)體中rx_indicate域被置空,因此不會運行這一段代碼。如果使用rt_device_set_rx_indicate(rt_device_t dev, rt_err_t(* rx_ind)(rt_device_t dev, rt_size_t size))函數(shù)為一個串口設(shè)備注冊了接收事件回調(diào)函數(shù),在該串口接收到數(shù)據(jù)后,就會調(diào)用之前注冊的rx_ind函數(shù),將當前設(shè)備指針以及待讀取的數(shù)據(jù)長度作為調(diào)用參數(shù)傳遞給用戶。

轉(zhuǎn)載于:https://www.cnblogs.com/King-Gentleman/p/4653011.html

總結(jié)

以上是生活随笔為你收集整理的RT thread 设备驱动组件之USART设备的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。