rt-thread源码解析
今天开发过程遇到一个很奇怪的问题,有一个对喇叭进行声音播放的进程里每次进入这个进程后其他任务都无法工作了,因为这个播放的任务优先级调到了比其他任务都低的级别,本质是为了能在播放音乐时不影响其他任务继续工作,所以在播放的任务是一个大约2分钟的传输数据,奇怪在于这两分钟内其他任务既然无法工作了,只有在这个任务结束后一切才正常,其实我很快就定位到问题,但在解决这个问题后还是将rt-thread的源码过了一遍。这个问题的主要原因是调用了sys_tick_delay()这个函数,因为这个函数的延时也是用sys_tick中断来写的,与rt-thread的中断发生了冲突才导致的,本文使用的事最新得2.1版本的rt-thread,硬件平台为nuc130这款mcu,接下来看下启动流程
启动过程
start up里面的就不看了,大同小异,前面我也写过这部分,有兴趣可以去看下,我们直接看main里面的内容
int main(void)
{
/* disable interrupt first */
rt_hw_interrupt_disable();
/* startup RT-Thread RTOS */
rtthread_startup();
return 0;
}
这里先关掉硬件中断,将总中断关掉,主要的初始化工作rtthread_startup()这里完成
void rtthread_startup(void)
{
/* init board */
rt_hw_board_init();
/* show version */
rt_show_version();
/* init timer system */
rt_system_timer_init();
#ifdef RT_USING_HEAP
rt_system_heap_init((void*)M05X_SRAM_BEGIN, (void*)M05X_SRAM_END);
#endif
/* init scheduler system */
rt_system_scheduler_init();
/* init application */
rt_application_init();
/* init timer thread */
rt_system_timer_thread_init();
/* init idle thread */
rt_thread_idle_init();
/* start scheduler */
rt_system_scheduler_start();
/* never reach here */
return ;
}
我们一个个函数都看下
void rt_hw_board_init()
{
/* NVIC Configuration */
NVIC_Configuration();
/* Configure the system clock */
rt_hw_system_init();
/* Configure the SysTick */
SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
/* Initial usart driver, and set console device */
rt_hw_usart_init();
#ifdef RT_USING_CONSOLE
rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif
/* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
}
这里先对NVIC控制器做操作,我这边没做操作使用默认的系统设置,之后进行系统时钟的一些初始化,设置时钟源和频率,在设置sys_tick也就是这个系统切换的间隔,一般10ms一次就够了,具体切换间隔看应用需求,之后就是串口的初始化,这个我们细看下,应用到linux里面很经典的device-driver的建构,实现设备和驱动的分离。
void rt_hw_usart_init(void)
{
#ifdef RT_USING_UART0
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
config.baud_rate = BAUD_RATE_115200;
serial0.ops = &nuc130_uart_ops;
serial0.config = config;
/* register UART0 device */
rt_hw_serial_register(&serial0, "uart0",
RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
UART0);
#endif /* RT_USING_UART0 */
}
这里定义了一个全局的serial0的数据结构,我们看下这个数据
struct rt_device
{
struct rt_object parent; /**< inherit from rt_object */
enum rt_device_class_type type; /**< device type */
rt_uint16_t flag; /**< device flag */
rt_uint16_t open_flag; /**< device open flag */
rt_uint8_t ref_count; /**< reference count */
rt_uint8_t device_id; /**< 0 - 255 */
/* device call back */
rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);
rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);
/* common device interface */
rt_err_t (*init) (rt_device_t dev);
rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag);
rt_err_t (*close) (rt_device_t dev);
rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
rt_size_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
rt_err_t (*control)(rt_device_t dev, rt_uint8_t cmd, void *args);
void *user_data; /**< device private data */
};
struct rt_uart_ops
{
rt_err_t (*configure)(struct rt_serial_device *serial, struct serial_configure *cfg);
rt_err_t (*control)(struct rt_serial_device *serial, int cmd, void *arg);
int (*putc)(struct rt_serial_device *serial, char c);
int (*getc)(struct rt_serial_device *serial);
rt_size_t (*dma_transmit)(struct rt_serial_device *serial, const rt_uint8_t *buf, rt_size_t size, int direction);
};
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 :4;
};
struct rt_serial_device
{
struct rt_device parent;
const struct rt_uart_ops *ops;
struct serial_configure config;
void *serial_rx;
void *serial_tx;
};
上面我截取了用到的所以数据结构,我们看下rt_serial_device的第一个数据结构是rt_device,这个是整个架构的核心,每个设备都包含这个device,在注册时将每个device都放入双向链表中实现快速遍历,rt_device的第一数据结构是rt_object可以理解为每个设备都包含的通用信息,会被每个每个设备所包含,其他的比如type表示设备类型,这里很linux几乎一样,rt-thread里面也是分为字符设备,网络设备,块设备,还有一些比如引用次数,设备号等等,里面还提供了一组标准的POSIX的结果便于以后直接移植linux程序,想法很超前,虽然是10级年轻就开发的软件,我们看下rt_object最后一个数据结构,user_data这个变量很有用,他可以将设备的信息穿个他,比如我们需要将串口的地址传入以备各个接口调用,如果需要传入的数据比较大,比如网络设备可以利用结构体封装之后传入结构体地址,这个一定程度上起到封装作用,C语言在语言上的缺陷被这样实现起来反而看的是如此精巧。
rt_uart_ops和serial_configure从上面可以看出来就是操作串口的实际函数和初始化需要用到的数据,如读写,配置、串口的波特率,控制位等等,为了封装本质上ops的函数是更底层的函数,因为串口的需要的接比较少,所以用户只需调用 void *serial_rx和void *serial_tx这两个接口函数即可。
我们在看下如何将这些信息初始化,接口函数,配置信息填充进去
rt_hw_serial_register(&serial0, "uart0",
RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
UART0);
这个注册函数就是做这些操作
rt_err_t rt_hw_serial_register(struct rt_serial_device *serial,
const char *name,
rt_uint32_t flag,
void *data)
{
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;
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;
device->user_data