RT-Thread uart串口设备驱动代码结构剖析
硬件測試平臺:正點原子潘多拉STM32L4開發板
OS內核版本:4.0.0
注意:下面的示例代碼是從原子提供的例程中摘錄,因此可能與最新的RT-Thread源碼有出入(因為RT-Thread源碼在不斷的開發維護中)
下面摘錄的例程中,關鍵位置我給出了注釋
下面開始正文:
RT-Thread的Finsh串口控制臺有個標志性的開頭打印信息如下:
\ | / - RT - Thread Operating System/ | \ 4.0.0 build Dec 18 20182006 - 2018 Copyright by rt-thread team在components.c中找到rtthread_startup()函數
int rtthread_startup(void) {rt_hw_interrupt_disable();/* board level initialization* NOTE: please initialize heap inside board initialization.*/rt_hw_board_init();/* show RT-Thread version */rt_show_version();rt_show_version();就是打印這段開頭信息的。
/*** This function will show the version of rt-thread rtos*/ void rt_show_version(void) {rt_kprintf("\n \\ | /\n");rt_kprintf("- RT - Thread Operating System\n");rt_kprintf(" / | \\ %d.%d.%d build %s\n",RT_VERSION, RT_SUBVERSION, RT_REVISION, __DATE__);rt_kprintf(" 2006 - 2018 Copyright by rt-thread team\n"); } RTM_EXPORT(rt_show_version);顯然,既然能串口打印了,那么必然在這之前已經初始化了該串口,那么rt_show_version()之前的rt_hw_board_init()函數里一定初始化了串口。
void rt_hw_board_init() { //...略/* Second initialize the serial port, so we can use rt_kprintf right away */ #ifdef RT_USING_SERIALstm32_hw_usart_init(); #endif#ifdef RT_USING_CONSOLErt_console_set_device(RT_CONSOLE_DEVICE_NAME); #endif //...略 } int stm32_hw_usart_init(void) {struct stm32_uart* uarts[] ={ #ifdef BSP_USING_UART1&uart1, #endif#ifdef BSP_USING_UART2&uart2, #endif#ifdef BSP_USING_UART3&uart3, #endif#ifdef BSP_USING_LPUART1&lpuart1, #endif};int i;for (i = 0; i < sizeof(uarts) / sizeof(uarts[0]); i++){struct stm32_uart *uart = uarts[i];rt_err_t result;/* register UART device */result = rt_hw_serial_register(&uart->serial,uart->uart_name, #ifdef BSP_UART_USING_DMA_RXRT_DEVICE_FLAG_DMA_RX | #endifRT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,uart);RT_ASSERT(result == RT_EOK);(void)result;}return 0; }stm32_hw_usart_init()這個函數注冊了使用到的串口設備到設備框架層,注冊完就可以直接使用內核通用接口函數操作設備了,比如開啟設備,讀設備,寫設備,通過BSP_USING_UART1這類宏控來控制將要使用哪幾個串口。注意,這里只是注冊,并沒完成真正的底層硬件初始化。真正的完成底層硬件初始化在rt_console_set_device(RT_CONSOLE_DEVICE_NAME);這里面。即調用了打開設備才是完成了串口硬件初始化串口才能正常使用。
/* UART1 device driver structure */ static struct stm32_uart uart1 = {{USART1}, // UART_HandleTypeDef UartHandle;USART1_IRQn, // IRQn_Type irq;#ifdef BSP_UART_USING_DMA_RXUSART1_RX_DMA_IRQN, // IRQn_Type dma_irq;0, // rt_size_t last_index;// DMA_HandleTypeDef hdma_rx;{USART1_RX_DMA_CHANNEL, {USART1_RX_DMA_REUQEST}}, #endif"uart1", // char * uart_name;// struct rt_serial_device serial;{{0}, &stm32_uart_ops, RT_SERIAL_CONFIG_DEFAULT} };stm32_uart類型如下:
/* STM32 uart driver */ struct stm32_uart {UART_HandleTypeDef UartHandle;IRQn_Type irq;#ifdef BSP_UART_USING_DMA_RXIRQn_Type dma_irq;rt_size_t last_index;DMA_HandleTypeDef hdma_rx; #endifchar * uart_name;struct rt_serial_device serial; };struct rt_serial_device 類型如下:
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;&stm32_uart_ops 對應 const struct rt_uart_ops *ops;
RT_SERIAL_CONFIG_DEFAULT 對應 struct serial_configure config;
stm32_uart_ops這個結構體實現接口層和底層的連接。
RT_SERIAL_CONFIG_DEFAULT 這個是串口默認配置。
再看stm32_uart_ops
static const struct rt_uart_ops stm32_uart_ops = {stm32_configure,stm32_control,stm32_putc,stm32_getc, };這里的stm32_configure函數就實現了STM32串口的底層初始化流程,包含HAL_UART_Init函數調用。
stm32_control函數是開啟和清楚接收中斷的,以及串口DMA配置。
限于篇幅這幾個函數不放在這里了。
串口中斷和串口接收DMA中斷也在drv_usart.c文件里
uart_isr(&uart1.serial);主要是清除中斷標志位
用過HAL庫的都知道,外設的GPIO初始化,外設時鐘使能,NVIC配置 都在HAL_UART_MspInit函數中完成,HAL_UART_MspInit函數也在drv_usart.c文件里
void HAL_UART_MspInit(UART_HandleTypeDef *huart)同時將由于不同的GPIO可能導致的不同的端口配置和復用通道,DMA通道都用宏封裝
#ifdef BSP_USING_UART1#define USART1_RX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()#define USART1_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()/* Definition for USART1 Pins */#define USART1_TX_PIN GPIO_PIN_9#define USART1_TX_GPIO_PORT GPIOA#define USART1_TX_AF GPIO_AF7_USART1#define USART1_RX_PIN GPIO_PIN_10#define USART1_RX_GPIO_PORT GPIOA#define USART1_RX_AF GPIO_AF7_USART1#define USART1_RX_DMA_CHANNEL DMA1_Channel5#define USART1_RX_DMA_REUQEST DMA_REQUEST_2#define USART1_RX_DMA_IRQN DMA1_Channel5_IRQn#define USART1_RX_DMA_IRQHandler DMA1_Channel5_IRQHandler #endif上面的串口1使用的是默認配置RT_SERIAL_CONFIG_DEFAULT,
如果要開其他串口或是要改配置比如改波特率,可以仿照RT_SERIAL_CONFIG_DEFAULT這個宏再封裝一個出來改對應的項,賦值到對應串口的設備結構體中去,如static struct stm32_uart uart1
上面提到,實際打開串口完成硬件初始化配置是在rt_console_set_device函數中
#ifdef RT_USING_CONSOLErt_console_set_device(RT_CONSOLE_DEVICE_NAME); #endifrt_device_t rt_console_set_device(const char *name) {rt_device_t new, old;/* save old device */old = _console_device;/* find new console device */new = rt_device_find(name);if (new != RT_NULL){if (_console_device != RT_NULL){/* close old console device */rt_device_close(_console_device);}/* set new console device */rt_device_open(new, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM);_console_device = new;}return old; }#define RT_CONSOLE_DEVICE_NAME “uart1”
這個宏定義在rtconfig.h中,定義了使用哪個串口作為控制臺的端口。這里使用的是串口1,可以根據需要進行修改
在rt_console_set_device函數中,完成了打開串口設備的動作 rt_device_open(new, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM);
rt_device_open函數中調用了device_init和device_open,
#define device_init (dev->init)
#define device_open (dev->open)
實際上是 (dev->init)和(dev->open)
在rt_hw_serial_register函數中device->init = rt_serial_init;
在rt_serial_init函數中看到了serial->ops->configure(serial, &serial->config); 前面說了,configure這個函數指針指向了stm32_configure,這個里面調用了HAL_UART_Init函數完成串口初始化配置。
本文通篇講了用于控制臺的串口1如何注冊到系統中并完成了初始化,其余串口也是同理。
主要關注兩個問題,1是如何修改某個串口的波特率,2是如果增加串口。
如果底層驅動做好的情況下,實際上直接改宏控就能完成增加串口,env工具可以快速完成配置,也可以手動改rtconfig.h文件。
也可以先全部默認的參數,然后在任務中重新配置,比如:
如下步驟可以實現對應的配置,比如波特率115200改成9600
RT-Thread 官方串口配置文檔
總結
以上是生活随笔為你收集整理的RT-Thread uart串口设备驱动代码结构剖析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言string.h文件函数汇总详解
- 下一篇: C语言面试题分享(6)