博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
RT-Thread uart串口设备驱动代码结构剖析
阅读量:524 次
发布时间:2019-03-08

本文共 9927 字,大约阅读时间需要 33 分钟。

硬件测试平台:正点原子潘多拉STM32L4开发板

OS内核版本:4.0.0

注意:下面的示例代码是从原子提供的例程中摘录,因此可能与最新的RT-Thread源码有出入(因为RT-Thread源码在不断的开发维护中)

下面摘录的例程中,关键位置我给出了注释

下面开始正文:

RT-Thread的Finsh串口控制台有个标志性的开头打印信息如下:

\ | /- RT -     Thread Operating System / | \     4.0.0 build Dec 18 2018 2006 - 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_SERIAL stm32_hw_usart_init();#endif#ifdef RT_USING_CONSOLE rt_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_RX RT_DEVICE_FLAG_DMA_RX |#endif RT_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_RX USART1_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_RX IRQn_Type dma_irq; rt_size_t last_index; DMA_HandleTypeDef hdma_rx;#endif char * 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 这个是串口默认配置。

/* Default config for serial_configure structure */#define RT_SERIAL_CONFIG_DEFAULT           \{                                          \    BAUD_RATE_115200, /* 115200 bits/s */  \    DATA_BITS_8,      /* 8 databits */     \    STOP_BITS_1,      /* 1 stopbit */      \    PARITY_NONE,      /* No parity  */     \    BIT_ORDER_LSB,    /* LSB first sent */ \    NRZ_NORMAL,       /* Normal mode */    \    RT_SERIAL_RB_BUFSZ, /* Buffer size */  \    0                                      \}//一一对应关系如下struct serial_configure{
rt_uint32_t baud_rate; /* 波特率 */ rt_uint32_t data_bits :4; /* 数据位 */ rt_uint32_t stop_bits :2; /* 停止位 */ rt_uint32_t parity :2; /* 奇偶校验位 */ rt_uint32_t bit_order :1; /* 高位在前或者低位在前 */ rt_uint32_t invert :1; /* 模式 */ rt_uint32_t bufsz :16; /* 接收数据缓冲区大小 */ rt_uint32_t reserved :6; /* 保留位 */};

再看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配置。
限于篇幅这几个函数不放在这里了。

void USART1_IRQHandler(void){
/* enter interrupt */ rt_interrupt_enter(); uart_isr(&uart1.serial); /* leave interrupt */ rt_interrupt_leave();}void USART1_RX_DMA_IRQHandler(void){
/* enter interrupt */ rt_interrupt_enter(); HAL_DMA_IRQHandler(uart1.UartHandle.hdmarx); /* leave interrupt */ rt_interrupt_leave();}

串口中断和串口接收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_CONSOLE    rt_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函数完成串口初始化配置。

rt_err_t rt_hw_serial_register(struct rt_serial_device *serial,                               const char              *name,                               rt_uint32_t              flag,                               void                    *data){
rt_err_t ret; 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;#ifdef RT_USING_DEVICE_OPS device->ops = &serial_ops;#else 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;#endif device->user_data = data; /* register a character device */ ret = rt_device_register(device, name, flag);#if defined(RT_USING_POSIX) /* set fops */ device->fops = &_serial_fops;#endif return ret;}
static rt_err_t rt_serial_init(struct rt_device *dev){
rt_err_t result = RT_EOK; struct rt_serial_device *serial; RT_ASSERT(dev != RT_NULL); serial = (struct rt_serial_device *)dev; /* initialize rx/tx */ serial->serial_rx = RT_NULL; serial->serial_tx = RT_NULL; /* apply configuration */ if (serial->ops->configure) result = serial->ops->configure(serial, &serial->config); return result;}

本文通篇讲了用于控制台的串口1如何注册到系统中并完成了初始化,其余串口也是同理。

主要关注两个问题,1是如何修改某个串口的波特率,2是如果增加串口。
如果底层驱动做好的情况下,实际上直接改宏控就能完成增加串口,env工具可以快速完成配置,也可以手动改rtconfig.h文件。

也可以先全部默认的参数,然后在任务中重新配置,比如:

如下步骤可以实现对应的配置,比如波特率115200改成9600

#define SAMPLE_UART_NAME       "uart2"    /* 串口设备名称 */static rt_device_t serial;                /* 串口设备句柄 */struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;  /* 初始化配置参数 *//* step1:查找串口设备 */serial = rt_device_find(SAMPLE_UART_NAME);/* step2:修改串口配置参数 */config.baud_rate = BAUD_RATE_9600;        //修改波特率为 9600config.data_bits = DATA_BITS_8;           //数据位 8config.stop_bits = STOP_BITS_1;           //停止位 1config.bufsz     = 128;                   //修改缓冲区 buff size 为 128config.parity    = PARITY_NONE;           //无奇偶校验位/* step3:控制串口设备。通过控制接口传入命令控制字,与控制参数 */rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);/* step4:打开串口设备。以中断接收及轮询发送模式打开串口设备 */rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);

转载地址:http://yvxnz.baihongyu.com/

你可能感兴趣的文章