RT-Thread 将 PIN、I2C、SPI、USB、UART 等作为外设设备,统一通过设备注册完成。实现了按名称访问的设备管理子系统,可按照统一的 API 界面访问硬件设备。
对不同的设备可以挂接相应的事件。当设备事件触发时,由驱动程序通知给上层的应用程序。
MDK裸机启动流程:系统启动后,先从汇编代码startup_stm32f103xe.s 开始运行,然后跳转到 C 代码的main()函数进行运行
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
RT-Thread在进入main()之前会进行RT-Thread系统功能初始化,
我们使用了 MDK 的扩展功能 $Sub$$ 和$Super$$ 。可以给 main 添加 $Sub$$ 的前缀符号作为一个新功能函数 $Sub$$main ,这个 $Sub$$main 可以先调用一些要补充在 main 之前的功能函数(这里添加 RT-Thread 系统初始化功能),再调用 $Super$$main
转到 main() 函数执行,这样可以让用户不用去管 main() 之前的系统初始化操作。
T-Thread 支持多种平台和多种编译器,而 rtthread_startup() 函数是 RT-Thread 规定的统一入口点,所以 $Sub$$main 函数只需调用rtthread_startup() 函数即可;
在 components.c 的代码中找到rtthread_startup() 函数,我们看到 RT-Thread 的启动流程如下图所示:
/* components.c 中定义的这段代码*/
/* re-define main function */
int $Sub$$main(void)
{
rt_hw_interrupt_disable();
rtthread_startup();
return ;
}
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();
/\* timer system initialization \*/
rt\_system\_timer\_init();
/\* scheduler system initialization \*/
rt\_system\_scheduler\_init();
#ifdef RT_USING_SIGNALS
/* signal system initialization */
rt_system_signal_init();
#endif
/\* create init\_thread \*/
rt\_application\_init();
/\* timer thread initialization \*/
rt\_system\_timer\_thread\_init();
/\* idle thread initialization \*/
rt\_thread\_idle\_init();
/\* start scheduler \*/
rt\_system\_scheduler\_start();
/\* never reach here \*/
return ;
}
/* the system main thread */
void main_thread_entry(void *parameter)
{
extern int main(void);
extern int $Super$$main(void);
/\* RT-Thread components initialization \*/
rt\_components\_init();
/\* invoke system main function \*/
#if defined(__CC_ARM) || defined(__CLANG_ARM)
$Super$$main(); /* for ARMCC. */
#elif defined(__ICCARM__) || defined(__GNUC__)
main();
#endif
}
void rt_application_init(void)
{
rt_thread_t tid;
#ifdef RT_USING_HEAP
tid = rt_thread_create("main", main_thread_entry, RT_NULL,
*, , );
RT_ASSERT(tid != RT_NULL);
rt_hw_board_init() 中完成系统时钟设置,为系统提供心跳、串口初始化,将系统输入输出终端绑定到这个串口,后续系统运行信息就会从串口打印出来。
main() 函数是 RT-Thread 的用户代码入口,用户可以在 main() 函数里添加自己的应用。
int main(void)
{
/* user app entry */
return ;
}
一般 MCU 包含的存储空间有:片内 Flash 与片内 RAM,RAM 相当于内存,Flash 相当于硬盘。编译器会将一个程序分类为好几个部分,分别存储在 MCU 不同的存储区。
RO Size 包含了 Code 及 RO-data,表示程序占用 Flash 空间的大小;
RW Size 包含了 RW-data 及 ZI-data,表示运行时占用的 RAM 的大小;
ROM Size 包含了 Code、RO Data 以及 RW Data,表示烧写程序所占用的 Flash 空间的大小;
程序运行之前,需要有文件实体被烧录到 STM32 的 Flash 中,一般是 bin 或者 hex 文件,该被烧录文件称为可执行映像文件。如图下图中左图所示,是可执行映像文件烧录到 STM32 后的内存分布,它包含 RO 段和 RW 段两个部分:其中 RO 段中保存了 Code、RO-data 的数据,RW 段保存了 RW-data 的数据,由于 ZI-data 都是 0,所以未包含在映像文件中。
STM32 在上电启动之后默认从 Flash 启动,启动之后会将 RW 段中的 RW-data(初始化的全局变量)搬运到 RAM 中,但不会搬运 RO 段,即 CPU 的执行代码从 Flash 中读取,另外根据编译器给出的 ZI 地址和大小分配出 ZI 段,并将这块 RAM 区域清零。
自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。
int stm32_hw_usart_init(void)
{
struct stm32_uart *uart;
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
#ifdef RT_USING_UART1
uart = &uart1;
uart->UartHandle.Instance = USART1;
serial1.ops = &stm32\_uart\_ops;
serial1.config = config;
/\* register UART1 device \*/
rt\_hw\_serial\_register(&serial1,
"uart1",
RT\_DEVICE\_FLAG\_RDWR | RT\_DEVICE\_FLAG\_INT\_RX,
uart);
#endif /* RT_USING_UART1 */
……
……
}
INIT_BOARD_EXPORT(stm32_hw_usart_init);
代码最后的 INIT_BOARD_EXPORT(stm32_hw_usart_init) 表示使用自动初始化功能,按照这种方式,stm32_hw_usart_init() 函数就会被系统自动调用
系统启动流程图中,有两个函数:rt_components_board_init() 与 rt_components_init(),其后的带底色方框内部的函数表示被自动初始化的函数
“board init functions” 为所有通过 INIT_BOARD_EXPORT(fn) 申明的初始化函数。
“pre-initialization functions” 为所有通过 INIT_PREV_EXPORT(fn) 申明的初始化函数。
“device init functions” 为所有通过 INIT_DEVICE_EXPORT(fn) 申明的初始化函数。
“components init functions” 为所有通过 INIT_COMPONENT_EXPORT(fn) 申明的初始化函数。
“enviroment init functions” 为所有通过 INIT_ENV_EXPORT(fn) 申明的初始化函数。
“application init functions” 为所有通过 INIT_APP_EXPORT(fn) 申明的初始化函数。
rt_components_board_init() 函数执行的比较早,主要初始化相关硬件环境,执行这个函数时将会遍历通过 INIT_BOARD_EXPORT(fn) 申明的初始化函数表,并调用各个函数。
rt_components_init() 函数会在操作系统运行起来之后创建的 main 线程里被调用执行,这个时候硬件环境和操作系统已经初始化完成,可以执行应用相关代码。
RT-Thread 的自动初始化机制使用了自定义 RTI 符号段,将需要在启动时进行初始化的函数指针放到了该段中,形成一张初始化函数表,在系统启动过程中会遍历该表,并调用表中的函数,达到自动初始化的目的。
用来实现自动初始化功能的宏接口定义详细描述如下
静态内核对象和动态内核对象,静态内核对象通常放在RW 段和 ZI 段中,在系统启动后在程序中初始化;动态内核对象则是从内存堆中创建的,而后手工做初始化。
static rt_uint8_t main_stack[];
struct rt_thread main_thread;
void rt_application_init(void)
{
rt_thread_t tid;
#ifdef RT_USING_HEAP
/*
* 线程名:main
* 线程入口:main_thread_entry
* 参数:RT_NULL
* 站空间大小:1024*100
* 优先级:0
* 时间片:20个OS Tick
*/
tid = rt_thread_create("main", main_thread_entry, RT_NULL, //动态线程
*, , );
RT_ASSERT(tid != RT_NULL);
#else
rt_err_t result;
tid = &main\_thread;
/\*
\* 静态线程对象:main\_thread
\* 线程名:main
\* 线程入口:main\_thread\_entry
\* 参数:RT\_NULL
\* 线程起始地址:main\_stack
\* 线程堆栈大小:sizeof(main\_stack)
\* 优先级:RT\_MAIN\_THREAD\_PRIORITY
\* 时间片:20个OS Tick
\*/
result = rt\_thread\_init(tid, "main", main\_thread\_entry, RT\_NULL, //静态线程
main\_stack, sizeof(main\_stack), RT\_MAIN\_THREAD\_PRIORITY, );
RT\_ASSERT(result == RT\_EOK);
/\* if not define RT\_USING\_HEAP, using to eliminate the warning \*/
(void)result;
#endif
rt\_thread\_startup(tid); //启动线程
}
main_thread 是一个静态线程对象,线程控制块 main_thread 与栈空间 main_stack 都是编译时决定的;
mian是一个动态线程对象,用到的空间是动态分配的,包括线程控制块和栈空间;
静态对象会占用 RAM 空间,不依赖于内存堆管理器,内存分配时间确定。动态对象则依赖于内存堆管理器,运行时申请 RAM 空间,当对象被删除后,占用的 RAM 空间被释放;
内核对象控制块的数据结构
/**
* Base structure of Kernel object
*/
struct rt_object
{
char name[RT_NAME_MAX]; /**< name of kernel object 内核对象名称*/
rt_uint8_t type; /**< type of kernel object 内核对象类型*/
rt_uint8_t flag; /**< flag of kernel object 内核对象的参数*/
#ifdef RT_USING_MODULE
void *module_id; /**< id of application module 内核对象模型id*/
#endif
rt_list_t list; /**< list node of kernel object 内核对象管理链表*/
};
typedef struct rt_object *rt_object_t; /**< Type for kernel objects. */
内核对象支持的类型:
enum rt_object_class_type
{
RT_Object_Class_Thread = , /**< The object is a thread. 线程类型*/
RT_Object_Class_Semaphore, /**< The object is a semaphore. 信号量类型*/
RT_Object_Class_Mutex, /**< The object is a mutex. 互斥量类型*/
RT_Object_Class_Event, /**< The object is a event. 时间类型*/
RT_Object_Class_MailBox, /**< The object is a mail box. 邮箱类型*/
RT_Object_Class_MessageQueue, /**< The object is a message queue. 消息队列类型*/
RT_Object_Class_MemHeap, /**< The object is a memory heap 内存堆类型*/
RT_Object_Class_MemPool, /**< The object is a memory pool. 内存池类型*/
RT_Object_Class_Device, /**< The object is a device 设备类型*/
RT_Object_Class_Timer, /**< The object is a timer. 定时器类型*/
RT_Object_Class_Module, /**< The object is a module. 模块*/
RT_Object_Class_Unknown, /**< The object is unknown. 未知类型*/
RT_Object_Class_Static = 0x80 /**< The object is a static object. 静态类型*/
};
如果是静态对象,那么对象类型的最高位将是 1(是RT_Object_Class_Static 与其他对象类型的与操作),否则就是动态对象,系统最多能够容纳的对象类别数目是 127 个。
内核对象容器的数据结构
/**
* The information of the kernel object
*/
struct rt_object_information
{
enum rt_object_class_type type; /**< object class type */
rt_list_t object_list; /**< object list */
rt_size_t object_size; /**< object size */
};
一类对象由一个 rt_object_information 结构体来管理,每一个这类对象的具体实例都通过链表的形式挂接在 object_list 上。而这一类对象的内存块尺寸由 object_size 标识出来
对于一个动态对象,当不再使用时,可以调用如下接口删除对象,并释放相应的系统资源:
/**
* This function will delete an object and release object memory.
*
* @param object the specified object to be deleted.
*/
void rt_object_delete(rt_object_t object)
当调用以上接口时,首先从对象容器链表中脱离对象,然后释放对象所占用的内存。
判断指定对象是否是系统对象(静态内核对象)。辨别对象使用以下接口:
/**
* This function will judge the object is system object or not.
* Normally, the system object is a static object and the type
* of object set to RT_Object_Class_Static.
*
* @param object the specified object to be judged.
*
* @return RT_TRUE if a system object, RT_FALSE for others.
*/
rt_bool_t rt_object_is_systemobject(rt_object_t object)
在 RT-Thread 操作系统中,一个系统对象也就是一个静态对象,对象类型标识上 RT_Object_Class_Static 位置位。通常使用 rt_object_init() 方式初始化的对象都是系统对象。
RT-Thread 的一个重要特性是高度可裁剪性,支持对内核进行精细调整,对组件进行灵活拆卸。配置主要是通过修改工程目录下的 rtconfig.h 文件来进行,用户可以通过打开 / 关闭该文件中的宏定义来对代码进行条件编译,最终达到系统配置和裁剪的目的。
在实际应用中,系统配置文件 rtconfig.h 是由配置工具自动生成的,无需手动更改。
/* RT-Thread 内核部分 */
#define RT_NAME_MAX 8 // 表示内核对象的名称的最大长度,若代码中对象名称的最大长度大于宏定义的长度,*多余的部分将被截掉。
#define RT_ALIGN_SIZE 4 // 字节对齐时设定对齐的字节个数。常使用 ALIGN(RT_ALIGN_SIZE) 进行字节对齐。
#define RT_THREAD_PRIORITY_32
#define RT_THREAD_PRIORITY_MAX 32 // 定义系统线程优先级数;通常用 RT_THREAD_PRIORITY_MAX-1 定义空闲线程的优先级
#define RT_TICK_PER_SECOND 1000 // 定义时钟节拍,为 1000 时表示 1000 个 tick 每秒,一个 tick 为 10ms
#define RT_USING_OVERFLOW_CHECK // 检查栈是否溢出,未定义则关闭
#define RT_USING_HOOK // 定义该宏使用 HOOK
#define RT_IDEL_HOOK_LIST_SIZE 4 // 定义HOOK链表大小
#define IDLE_THREAD_STACK_SIZE 1024 // 定义线程栈大小
#define RT_DEBUG // 定义该宏开启 debug 模式,未定义则关闭
/* 线程间同步与通信部分,该部分会使用到的对象有信号量、互斥量、事件、邮箱、消息队列、信号等。 */
#define RT_USING_SEMAPHORE // 定义该宏可开启信号量的使用,未定义则关闭
#define RT_USING_MUTEX // 定义该宏可开启互斥量的使用,未定义则关闭
#define RT_USING_EVENT // 定义该宏可开启事件集的使用,未定义则关闭
#define RT_USING_MAILBOX // 定义该宏可开启邮箱的使用,未定义则关闭
#define RT_USING_MESSAGEQUEUE // 定义该宏可开启消息队列的使用,未定义则关闭
/* 内存管理部分 */
#define RT_USING_MEMPOOL // 开启静态内存池的使用
#define RT_USING_MEMHEAP // 定义该宏可开启两个或以上内存堆拼接的使用,未定义则关闭
#define RT_USING_SMALL_MEM // 开启小内存管理算法
#define RT_USING_HEAP // 开启堆的使用
/* 内核设备对象 */
#define RT_USING_DEVICE // 表示开启了系统设备的使用
#define RT_USING_CONSOLE // 定义该宏可开启系统控制台设备的使用,未定义则关闭
#define RT_CONSOLEBUF_SIZE 128 // 定义控制台设备的缓冲区大小
#define RT_CONSOLE_DEVICE_NAME "uart1" // 控制台设备的名称
/* 自动初始化方式 */
#define RT_USING_COMPONENTS_INIT // 定义该宏开启自动初始化机制,未定义则关闭
/* Command shell */
#define RT_USING_FINSH // 定义该宏可开启系统 FinSH 调试工具的使用,未定义则关闭
#define FINSH_THREAD_NAME "tshell" // 开启系统 FinSH 时:将该线程名称定义为 tshell
#define FINSH_USING_HISTORY // 开启系统 FinSH 时:使用历史命令
#define FINSH_HISTORY_LINES 5 // 开启系统 FinSH 时:对历史命令行数的定义
#define FINSH_USING_SYMTAB // 开启系统 FinSH 时:定义该宏开启使用 Tab 键,未定义则关闭
#define FINSH_USING_DESCRIPTION // 开启系统 FinSH 时:定义该宏使用描述,未定义则关闭
#define FINSH_THREAD_PRIORITY 20 // 开启系统 FinSH 时:定义该线程的优先级
#define FINSH_THREAD_STACK_SIZE 2048 // 开启系统 FinSH 时:定义该线程的栈大小
#define FINSH_CMD_SIZE 80 // 开启系统 FinSH 时:定义命令字符长度
/* Device virtual file system */
#define RT_USING_DFS // 使用DFS文件系统
#define DFS_USING_WORKDIR // 使用工作目录
#define DFS_FILESYSTEMS_MAX 4 // 文件系统
#define DFS_FILESYSTEM_TYPES_MAX 4 // 文件系统类型
#define DFS_FD_MAX 8 // DF容器大小
#define RT_USING_DFS_ELMFAT // 使用elmfat文件系统
RT-Thread 中经常使用一些宏定义,举例 Keil 编译环境下一些常见的宏定义:rtdef.h
rt_inline,定义如下,static 关键字的作用是令函数只能在当前的文件中使用;inline 表示内联,用static 修饰后在调用函数时会建议编译器进行内联展开。
#define rt_inline static __inline
RT_USED,定义如下,该宏的作用是向编译器说明这段代码有用,即使函数中没有调用也要保留编译。例如 RT-Thread 自动初始化功能使用了自定义的段,使用 RT_USED 会将自定义的代码段保留。
#define RT_USED __attribute__((used))
RT_UNUSED,定义如下,表示函数或变量可能不使用,这个属性可以避免编译器产生警告信息。
#define RT_UNUSED __attribute__((unused))
RT_WEAK,定义如下,常用于定义函数,编译器在链接函数时会优先链接没有该关键字前缀的函数,如果找不到则再链接由 weak 修饰的函数
#define RT_WEAK __weak
ALIGN(n),定义如下,作用是在给某对象分配地址空间时,将其存放的地址按照 n 字节对齐,这里 n 可取 2 的幂次方。字节对齐的作用不仅是便于 CPU 快速访问,同时合理的利用字节对齐可以有效地节省存储空间。
#define ALIGN(n) __attribute__((aligned(n)))
RT_ALIGN(size,align),定义如下,作用是将 size 提升为 align 定义的整数的倍数,例如,RT_ALIGN(13,4) 将返回 16。
#define RT_ALIGN(size, align) (((size) + (align) - 1) & ~((align) - 1))
《RT-Thread 编程指南》
手机扫一扫
移动阅读更方便
你可能感兴趣的文章