本文共 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/