首先单步调试进入的是rtthread_startup(void)函数,使用函数rt_hw_interrupt_disable(void)失能全局中断,然后进入rt_hw_board_init(void)函数。在该函数中可以看到:
#ifdef RT_USING_SERIAL
stm32_hw_usart_init();
#endif
如果在rtconfig.h文件中定义了RT_USING_SERIAL,则执行硬件串口初始化。下面第一张图是整理了串口设备结构体变量的配置(不是直接将参数赋值给串口相关寄存器)
看图中间,函数stm32_hw_usart_init():先定义一个stm32_uart型的结构体数组指针,里面放的是串口结构体的地址(串口1,串口2,串口3,…),这些串口结构体变量必需是已经定义好的。比如图右边贴出uart1结构体的成员变量赋值信息,第一个是硬件串口句柄,第二个是中断函数句柄,如果使用DMA接收模式,就多了个DMA的配置,比较关键的是下面的设备名称“uart1”和rt_serial_device类型的成员赋值信息(这些赋值信息是最后会传递给stm32_configure函数完成串口驱动配置的)。
接着继续看函数stm32_hw_usart_init()剩下的内容:
int stm32_hw_usart_init(void)
{
struct stm32_uart* uarts[] =
{
/*.....*/
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;
}
上面的代码for循环中用到rt_hw_serial_register函数来注册串口设备,怎么个注册法?
跳进该函数可以看到:
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);
/*这里要提取rt_serial_device的父类rt_device,一层一层的感觉有没有哈*/
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;
}
函数内部只是定义了一个struct rt_device *类型的变量。该变量指向serial的parent,给该变量赋值等同于给serial的parent(struct rt_device类型)赋值,最后进入rt_device_register看看:
rt_err_t rt_device_register(rt_device_t dev,
const char *name,
rt_uint16_t flags)
{
if (dev == RT_NULL)//如果设备句柄为RT_NULL,直接返回,注册失败
return -RT_ERROR;
/*如果能通过设备名找到设备,说明已经注册过了,直接返回,无需再注册*/
if (rt_device_find(name) != RT_NULL)
return -RT_ERROR;
/*否则注册*/
/*函数的作用是初始化一个对象然后将它添加到对象系统管理器中*/
rt_object_init(&(dev->parent), RT_Object_Class_Device, name);
dev->flag = flags;
dev->ref_count = 0;
dev->open_flag = 0;
#if defined(RT_USING_POSIX)
dev->fops = RT_NULL;
rt_wqueue_init(&(dev->wait_queue));
#endif
return RT_EOK;
}
那么整个函数stm32_hw_usart_init已经执行完了,可以知道走到这一步仅仅是配置好设备的信息还有注册设备到管理器中,并没有执行设备(串口)的底层驱动配置相关程序。那么接着往下走来到:
#ifdef RT_USING_CONSOLE
rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif
如果在rt_config.h文件中声明了RT_USING_CONSOLE,执行rt_console_set_device()函数,右击选择go to definition可以知道RT_CONSOLE_DEVICE_NAME就是"uart1",当然这个可以改成别的设备名,这时控制台会使用串口1向终端传递接收数据。
rt_device_t rt_console_set_device(const char *name)
{
rt_device_t new, old;
/* save old device */
old = _console_device;//RT_NULL
/* 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;
}
上面函数中最重要的是函数rt_device_open,注意传参是设备句柄和表示该设备以什么形式打开的变量。下面贴出自己加了注释的rt_device_open函数:
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag)
{
rt_err_t result = RT_EOK;
RT_ASSERT(dev != RT_NULL);
RT_ASSERT(rt_object_get_type(&dev->parent) == RT_Object_Class_Device);
/* if device is not initialized, initialize it. */
if (!(dev->flag & RT_DEVICE_FLAG_ACTIVATED))
{
if (dev->init != RT_NULL)
{
/*如果设备init函数存在那么就执行dev->init(dev)*/
/*回到rt_hw_serial_register查看知道device->init = rt_serial_init*/
result = device_init(dev);
if (result != RT_EOK)
{
rt_kprintf("To initialize device:%s failed. The error code is %d\n",
dev->parent.name, result);
return result;
}
}
dev->flag |= RT_DEVICE_FLAG_ACTIVATED;
}
/*...*/
}
在注释中提到了函数rt_serial_init,那么看下该函数:
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;
}
上面的函数中值得一提的是struct rt_device *类型的传参强转成struct rt_serial_device *类型的。那么这两个结构体有什么联系呢?
我们看看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;
};
从上面的定义中可以知道struct rt_device类型的变量parent是结构体rt_serial_device的第一个成员变量。我们知道,结构体的第一个成员变量的地址等于该结构体的地址,所以这样强转非常合适,这样一来程序中的【result = serial->ops->configure(serial, &serial->config);】就等同于调用【uartx.serial.ops->configure(&uartx.serial,&uartx.serial.config);】。这里的uartx可以是uart1,2,3,…。这里不懂的可以看最上面的图片。
至此,串口的初始化流程分析完毕。
如果想开启另一个串口,那么除了在rt_config.h中定义#define BSP_USING_UARTx和将串口对象注册到对象系统管理器中之外,还需要手动写串口的底层驱动配置程序,这个完全可以模仿rt_console_set_device函数,只需两步就可以完成:第一步调用rt_device_find(name)得到串口句柄,第二步根据句柄使用函数rt_device_open()完成底层驱动配置。