文章目录
前言
学习rt-thread主要是为了后期直接拿来玩几个项目,同时rt-thread有很多借鉴linux的地方,很适合用来学习分层架构,因此更多的是介绍实现思想,而不是实现细节。
本着不重复造轮子的原则,如果有看到其他写的很好的地方,会直接放链接,不再赘述。
测试代码
直接使用GitHub上面的示例工程拿来运行,运行环境stm32f103,main函数如下:
#include <stdlib.h>
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
/* defined the LED0 pin: PB5 */
#define LED0_PIN GET_PIN(B, 5)
int main(void)
{
/* set LED0 pin mode to output */
rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);
rt_kprintf("hello world\n");
while (1)
{
rt_kprintf("hello world\n");
rt_pin_write(LED0_PIN, PIN_HIGH);
rt_thread_mdelay(500);
rt_pin_write(LED0_PIN, PIN_LOW);
rt_thread_mdelay(500);
}
}
函数调用顺序
1. kservice.c文件下的rt_kprintf函数:
这里的核心就2点,数据buf + 隔离;
- 将数据存入rt_log_buf[RT_CONSOLEBUF_SIZE]里;
注意: RT_CONSOLEBUF_SIZE由rtconfig.h定义,stm32f103里是
#define RT_CONSOLEBUF_SIZE 128
;
- 通过rt_device_write函数将数据进行发送;这里是进行一次隔离平台的函数;
RT_WEAK int rt_kprintf(const char *fmt, ...)
{
va_list args;
rt_size_t length;
static char rt_log_buf[RT_CONSOLEBUF_SIZE];
va_start(args, fmt);
/* the return value of vsnprintf is the number of bytes that would be
* written to buffer had if the size of the buffer been sufficiently
* large excluding the terminating null byte. If the output string
* would be larger than the rt_log_buf, we have to adjust the output
* length. */
length = rt_vsnprintf(rt_log_buf, sizeof(rt_log_buf) - 1, fmt, args);
if (length > RT_CONSOLEBUF_SIZE - 1)
length = RT_CONSOLEBUF_SIZE - 1;
#ifdef RT_USING_DEVICE
if (_console_device == RT_NULL)
{
rt_hw_console_output(rt_log_buf);
}
else
{
rt_device_write(_console_device, 0, rt_log_buf, length); // 执行这句
}
#else
rt_hw_console_output(rt_log_buf);
#endif /* RT_USING_DEVICE */
va_end(args);
return length;
}
2. device.c文件下的rt_device_write函数
这里也是一层隔离层,从kserver -> device层:
#define device_write (dev->write)
rt_size_t rt_device_write(rt_device_t dev,
rt_off_t pos,
const void *buffer,
rt_size_t size)
{
/* parameter check */
RT_ASSERT(dev != RT_NULL);
RT_ASSERT(rt_object_get_type(&dev->parent) == RT_Object_Class_Device);
if (dev->ref_count == 0)
{
rt_set_errno(-RT_ERROR);
return 0;
}
/* call device_write interface */
if (device_write != RT_NULL)
{
return device_write(dev, pos, buffer, size); // 执行这句
}
/* set error code */
rt_set_errno(-RT_ENOSYS);
return 0;
}
RTM_EXPORT(rt_device_write);
值得注意的是,这里需要函数指针,需要先注册
:
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);
#ifdef RT_USING_POSIX_STDIO
/* set fops */
device->fops = &_serial_fops;
#endif
return ret;
}
通过注册方式,实现平台的分离,例如stm32f103和ESP32-C3板子下,只需要都实现这个注册函数(在rt_hw_uart_init函数里),然后在编译时选择对应的平台,即可实现对write函数指针的赋值,也就能运行不同的函数,而不用修改代码。
3. serial.c文件下的rt_serial_write函数
这里也是一层隔离层,从device->components层:
static rt_size_t rt_serial_write(struct rt_device *dev,
rt_off_t pos,
const void *buffer,
rt_size_t size)
{
struct rt_serial_device *serial;
RT_ASSERT(dev != RT_NULL);
if (size == 0) return 0;
serial = (struct rt_serial_device *)dev;
if (dev->open_flag & RT_DEVICE_FLAG_INT_TX)
{
return _serial_int_tx(serial, (const rt_uint8_t *)buffer, size);
}
#ifdef RT_SERIAL_USING_DMA
else if (dev->open_flag & RT_DEVICE_FLAG_DMA_TX)
{
return _serial_dma_tx(serial, (const rt_uint8_t *)buffer, size);
}
#endif /* RT_SERIAL_USING_DMA */
else
{
return _serial_poll_tx(serial, (const rt_uint8_t *)buffer, size); // 执行这句
}
}
rt_inline int _serial_poll_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length)
{
int size;
RT_ASSERT(serial != RT_NULL);
size = length;
while (length)
{
/*
* to be polite with serial console add a line feed
* to the carriage return character
*/
if (*data == '\n' && (serial->parent.open_flag & RT_DEVICE_FLAG_STREAM))
{
serial->ops->putc(serial, '\r');
}
serial->ops->putc(serial, *data); // 执行这句
++ data;
-- length;
}
return size - length;
}
// 最后执行这个函数
static int stm32_putc(struct rt_serial_device *serial, char c)
{
struct stm32_uart *uart;
RT_ASSERT(serial != RT_NULL);
uart = rt_container_of(serial, struct stm32_uart, serial);
UART_INSTANCE_CLEAR_FUNCTION(&(uart->handle), UART_FLAG_TC);
#if defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32WL) || defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32F0) \
|| defined(SOC_SERIES_STM32L0) || defined(SOC_SERIES_STM32G0) || defined(SOC_SERIES_STM32H7) || defined(SOC_SERIES_STM32L5) \
|| defined(SOC_SERIES_STM32G4) || defined(SOC_SERIES_STM32MP1) || defined(SOC_SERIES_STM32WB) || defined(SOC_SERIES_STM32F3) \
|| defined(SOC_SERIES_STM32U5)
uart->handle.Instance->TDR = c;
#else
uart->handle.Instance->DR = c;
#endif
while (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_TC) == RESET);
return 1;
}
通过上面的实现,可以了解到:
- 最后给uart的DR寄存器写值即可发送数据,并等待数据发送完成;因此示例的函数打印是死等发送,会比较占用CPU资源;
- 最后发送的是\r\n,做了一些处理,防止windows环境下换行显示失败;
- 和linux的链表操作类似,通过rt_container_of函数来查找父节点,这个点可以学习和了解,但是这里就不再赘述;
同理,这里的putc函数一看也是注册进来的,查找一下如何实现:
static const struct rt_uart_ops stm32_uart_ops =
{
.configure = stm32_configure,
.control = stm32_control,
.putc = stm32_putc,
.getc = stm32_getc,
.dma_transmit = stm32_dma_transmit
};
// 由rtconfig.h里宏来确定个数,这里就一个BSP_USING_UART1
static struct stm32_uart_config uart_config[] =
{
#ifdef BSP_USING_UART1
UART1_CONFIG,
#endif
#ifdef BSP_USING_UART2
UART2_CONFIG,
#endif
#ifdef BSP_USING_UART3
UART3_CONFIG,
#endif
#ifdef BSP_USING_UART4
UART4_CONFIG,
#endif
#ifdef BSP_USING_UART5
UART5_CONFIG,
#endif
#ifdef BSP_USING_UART6
UART6_CONFIG,
#endif
#ifdef BSP_USING_UART7
UART7_CONFIG,
#endif
#ifdef BSP_USING_UART8
UART8_CONFIG,
#endif
#ifdef BSP_USING_LPUART1
LPUART1_CONFIG,
#endif
};
static struct stm32_uart uart_obj[sizeof(uart_config) / sizeof(uart_config[0])] = {0};
int rt_hw_usart_init(void)
{
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
rt_err_t result = 0;
stm32_uart_get_dma_config();
for (rt_size_t i = 0; i < sizeof(uart_obj) / sizeof(struct stm32_uart); i++)
{
/* init UART object */
uart_obj[i].config = &uart_config[i];
uart_obj[i].serial.ops = &stm32_uart_ops;
uart_obj[i].serial.config = config; // 使用默认配置
/* register UART device */
result = rt_hw_serial_register(&uart_obj[i].serial, uart_obj[i].config->name,
RT_DEVICE_FLAG_RDWR
| RT_DEVICE_FLAG_INT_RX
| RT_DEVICE_FLAG_INT_TX
| uart_obj[i].uart_dma_flag
, NULL);
RT_ASSERT(result == RT_EOK);
}
return result;
}
// 板级初始化函数
RT_WEAK void rt_hw_board_init(void)
{
/* HAL_Init() function is called at the beginning of the program */
HAL_Init();
/* System clock initialization */
SystemClock_Config();
/* Heap initialization */
#if defined(RT_USING_HEAP)
rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);
#endif
/* Pin driver initialization is open by default */
#ifdef RT_USING_PIN
rt_hw_pin_init();
#endif
/* USART driver initialization is open by default */
#ifdef RT_USING_SERIAL
rt_hw_usart_init(); // 初始化了这句
#endif
/* Set the shell console output device */
#if defined(RT_USING_CONSOLE) && defined(RT_USING_DEVICE)
rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif
/* Board underlying hardware initialization */
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
}
RT_SERIAL_CONFIG_DEFAULT
这个是默认的串口配置;
总结
最后分析下来,rt_kprintf的本质其实很简单,主要复杂在rt-thread为了平台适配隔离,给做的一套框架,因此学习rt-thread对我来说主要就是了解这个框架。
总的来说,其实就是linux的框架的简单版本。