硬件测试平台:正点原子潘多拉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);
#endif
rt_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文件。