裸机思维:
51单片机老师说:系统上电后,有外部RC保证NRST引脚处于一定时长的低电平时间,使单片机完成复位操作……
那么在stm32的数据手册中一定也会有同样的介绍:(这里选了F103的手册,因为博主觉得这些部分都是一样的,找个F103的中文手册也方便大家阅读)
emm……啥?供电电压不够自己就能把复位脚拉低了?
于是满文档找复位,原来内部电路中有一个脉冲发生器,内部把NRST接地20us执行了复位操作。
好吧,看来以后设计这里的外部上拉和对地电容可以只考虑消除干扰,不用考虑为复位时间去计算了。
先看看手册中关于boot的介绍:
系统从存储器的0x00000004地址开始执行代码,查询中断向量表,即执行Reset所指向的函数:
这里我们的boot跳线就是00了,直接从Flash启动,所以我们的Code虽然存储在0x80000000开始的地方,但是会被映射到0x00000000处,即执行0x80000004处的代码。在线debug走一波:
此时已经复位过了,可以看到除了SP和PC已经加载了,其他都已经被清0或者值F了。
仔细可以发现,PC指针和实际的值并不相同,差了1。
这是因为,此时的汇编是Thumb指令,可以在编译产生的内存映像文件(.map)中看到:
因为ARM中的指令至少是半字(16bit)对齐的,比如0x00和0x01两个地址共同表示一个16bit数,我们只需要访问0x00就能知道这个数了,0x01就失去了意义。
所以这里的最低位就被用来区分是Thumb指令集还是ARM指令集,0表示是ARM指令集,1表示为Thumb指令集,这里0x080000004地址中存储的0x0800039D表示的则是:
我指向了一个Thumb指令集,地址是0x0800039C,请寻址吧!
于是单片机就开始执行Reset_Handler中的指令:
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
即执行SystemInit
和__main
函数。
SystemInit
中,和RTT没有什么关系,主要就是进行RCC配置、设置时钟源等操作。
void SystemInit(void)
{
/* FPU settings ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
/* Reset the RCC clock configuration to the default reset state ------------*/
/* Set MSION bit */
RCC->CR |= RCC_CR_MSION;
/* Reset CFGR register */
RCC->CFGR = 0x00000000U;
/* Reset HSEON, CSSON , HSION, and PLLON bits */
RCC->CR &= 0xEAF6FFFFU;
/* Reset PLLCFGR register */
RCC->PLLCFGR = 0x00001000U;
/* Reset HSEBYP bit */
RCC->CR &= 0xFFFBFFFFU;
/* Disable all interrupts */
RCC->CIER = 0x00000000U;
/* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
}
注意这里执行的RCC->CFGR = 0x00000000U;
,默认使用了HSI时钟,后面肯定还会有配置HSE使用外部时钟保证性能的代码。
$Sub$$main
主函数执行完SystemInit
就是__main
了。
MDK的扩展功能中,有sub和super两个关键字:
为了在进入 main() 之前完成 RT-Thread 系统功能初始化,我们使用了 MDK 的扩展功能 $Sub$$ 和 $Super$$。
可以给 main 添加 $Sub$$ 的前缀符号作为一个新功能函数 $Sub$$main,
这个 $Sub$$main 可以先调用一些要补充在 main 之前的功能函数(这里添加 RT-Thread 系统初始化功能),
再调用 $Super$$main 转到 main() 函数执行,这样可以让用户不用去管 main() 之前的系统初始化操作。
即:
__main会跳转到 $Sub$$main, $Sub$$main 执行完就完了。并不会再去执行我们写的main函数。
所以正常情况下我们需要在 $Sub$$main 的最后一行,调用一次 $Super$$main,转而运行我们写的main函数。
看不太懂?没关系,我们把工程中的所有submain和supermain都找出来~(删掉了各种和编译器相关的define方便阅读):
int $Sub$$main(void)
{
rt_hw_interrupt_disable();
rtthread_startup();
return 0;
}
/* 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();
$Super$$main(); /* for ARMCC. */
}
通过断点监视,我们发现__main
指向了0x08000188的位置,没事,那就跟踪嘛~
实在不行在汇编里打断点嘛~
这里先跳转到0x08000190,继续往下1A0处会跳转到1A6,1BA跳转1C4,1E2跳1F0,208跳200,这里一直重复,取消了这几个重复的断点,就GG了……
没事,再来嘛~
重新设置断点,21A跳1CA了,然后一直在这之间跳,这谁TM写的循环吧,到底要循环多少次啊!!!
断点全删了,直接打断点到21C,跳过这堆循环了……
此时的lr寄存器为19F,又跳转到19E去了,往下继续,然后1A0跳1A6,1BA跳220,又循环22E跳228了……
循环完了到230开始,一直到23A,此时LR寄存器为19F,跳到了19E,往下运行到1A2时,跳转到248:
再跳转到832:
再跳转到888:
88A跳838,84A跳3B8:
3C0跳84F:
87A跳24D:
24E跳23C:
23E跳7B56:
7B5E跳243:
242跳252:
252跳89E:
于是进入了sub_main:
虽然不知道前面一通汇编操作猛如虎是在干什么,不过对于C语言层面来说,执行完SystemInit
后面就是执行的$Sub$$main
了。
RTT的初始化过程写在$Sub$$main
中的,即先禁用全局中断,再执行初始化内容。
int $Sub$$main(void)
{
rt_hw_interrupt_disable();
rtthread_startup();
return 0;
}
禁用全局中断是用汇编写的:
;/*
; * rt_base_t rt_hw_interrupt_disable();
; */
rt_hw_interrupt_disable PROC
EXPORT rt_hw_interrupt_disable
MRS r0, PRIMASK
CPSID I
BX LR
ENDP
该函数放在\rt-thread\libcpu\arm\cortex-m4\context_rvds.S
中。
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();
/* 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();
#ifdef RT_USING_SMP
rt_hw_spin_lock(&_cpus_lock);
#endif /*RT_USING_SMP*/
/* start scheduler */
rt_system_scheduler_start();
/* never reach here */
return 0;
}
/* 板级初始化:需在该函数内部进行系统堆的初始化 */
先设置中断向量表偏移,这里的NVIC_VTOR_MASK
应该是和OTA相关的,这里先埋个坑,后面学了OTA再回来补。
void rt_hw_board_init()
{
/* NVIC Configuration */
#define NVIC_VTOR_MASK 0x3FFFFF80
#ifdef VECT_TAB_RAM
/* Set the Vector Table base location at 0x10000000 */
SCB->VTOR = (0x10000000 & NVIC_VTOR_MASK);
#else /* VECT_TAB_FLASH */
/* Set the Vector Table base location at 0x08000000 */
SCB->VTOR = (0x08000000 & NVIC_VTOR_MASK);
#endif
/* HAL_Init() function is called at the beginning of program after reset and before
* the clock configuration.
*/
HAL_Init();
SystemClock_Config();
/* First initialize pin port,so we can use pin driver in other bsp driver */
rt_hw_pin_init();
/* Second initialize the serial port, so we can use rt_kprintf right away */
stm32_hw_usart_init();
rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);
rt_components_board_init();
}
然后是HAL_Init
,根据注释可以知道,这个是配置Flash缓存、NVIC中断分组、时钟源等的操作。
不过RTT工程的HAL_InitTick
函数中直接return了OK,没有内容,打开了L475的裸机例程,发现里面是有内容的,这里暂且再挖一个坑,后面对比了不同再来研究。
HAL_MspInit();
函数没有内容。
/**
* @brief Configure the Flash prefetch, the Instruction and Data caches,
* the time base source, NVIC and any required global low level hardware
* by calling the HAL_MspInit() callback function to be optionally defined in user file
* stm32l4xx_hal_msp.c.
*
* @note HAL_Init() function is called at the beginning of program after reset and before
* the clock configuration.
*
* @note In the default implementation the System Timer (Systick) is used as source of time base.
* The Systick configuration is based on MSI clock, as MSI is the clock
* used after a system Reset and the NVIC configuration is set to Priority group 4.
* Once done, time base tick starts incrementing: the tick variable counter is incremented
* each 1ms in the SysTick_Handler() interrupt handler.
*
* @retval HAL status
*/
HAL_StatusTypeDef HAL_Init(void)
{
HAL_StatusTypeDef status = HAL_OK;
/* Configure Flash prefetch, Instruction cache, Data cache */
/* Default configuration at reset is: */
/* - Prefetch disabled */
/* - Instruction cache enabled */
/* - Data cache enabled */
#if (INSTRUCTION_CACHE_ENABLE == 0)
__HAL_FLASH_INSTRUCTION_CACHE_DISABLE();
#endif /* INSTRUCTION_CACHE_ENABLE */
#if (DATA_CACHE_ENABLE == 0)
__HAL_FLASH_DATA_CACHE_DISABLE();
#endif /* DATA_CACHE_ENABLE */
#if (PREFETCH_ENABLE != 0)
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE */
/* Set Interrupt Group Priority */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
/* Use SysTick as time base source and configure 1ms tick (default clock after Reset is MSI) */
if (HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)
{
status = HAL_ERROR;
}
else
{
/* Init the low level hardware */
HAL_MspInit();
}
/* Return function status */
return status;
}
作为一个经常使用F103和STD标准库的弱鸡,其实在F103的SystemInit中也有如下函数实现和上述相同的功能,这里不再展开。
/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();
然后是SystemClock_Config();
,代码放在了Drivers文件夹下的drv_clock.c文件中,这里应该是设置时钟树,即各个总线的速度,设置Sys_Tick的中断优先级为最高;好像还顺便设置了串口的时钟源。
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
HAL_StatusTypeDef status;
/* Initializes the CPU, AHB and APB busses clocks */
SystemCoreClockUpdate();
/* Initializes the CPU, AHB and APB busses clocks */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE | RCC_OSCILLATORTYPE_LSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.LSEState = RCC_LSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 1;
RCC_OscInitStruct.PLL.PLLN = 20;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
status = HAL_RCC_OscConfig(&RCC_OscInitStruct);
if (status != HAL_OK)
{
RT_ASSERT(0);
}
/**Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{
}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_SDMMC1
| RCC_PERIPHCLK_LPTIM1
| RCC_PERIPHCLK_USART1
| RCC_PERIPHCLK_USART2
| RCC_PERIPHCLK_USART3
| RCC_PERIPHCLK_LPUART1
| RCC_PERIPHCLK_USB;
PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1;
PeriphClkInit.Usart3ClockSelection = RCC_USART3CLKSOURCE_PCLK1;
PeriphClkInit.Uart4ClockSelection = RCC_UART4CLKSOURCE_PCLK1;
PeriphClkInit.Lpuart1ClockSelection = RCC_LPUART1CLKSOURCE_LSE;
PeriphClkInit.Lptim1ClockSelection = RCC_LPTIM1CLKSOURCE_LSE;
PeriphClkInit.UsbClockSelection = RCC_USBCLKSOURCE_PLLSAI1;
PeriphClkInit.Sdmmc1ClockSelection = RCC_SDMMC1CLKSOURCE_PLLSAI1;
PeriphClkInit.PLLSAI1.PLLSAI1Source = RCC_PLLSOURCE_HSE;
PeriphClkInit.PLLSAI1.PLLSAI1M = 1;
PeriphClkInit.PLLSAI1.PLLSAI1N = 12;
PeriphClkInit.PLLSAI1.PLLSAI1P = RCC_PLLP_DIV7;
PeriphClkInit.PLLSAI1.PLLSAI1Q = RCC_PLLQ_DIV2;
PeriphClkInit.PLLSAI1.PLLSAI1R = RCC_PLLR_DIV2;
PeriphClkInit.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_48M2CLK;
PeriphClkInit.PLLSAI2.PLLSAI2Source = RCC_PLLSOURCE_HSE;
PeriphClkInit.PLLSAI2.PLLSAI2M = 1;
PeriphClkInit.PLLSAI2.PLLSAI2N = 40;
PeriphClkInit.PLLSAI2.PLLSAI2P = RCC_PLLP_DIV7;
PeriphClkInit.PLLSAI2.PLLSAI2R = RCC_PLLR_DIV2;
PeriphClkInit.PLLSAI2.PLLSAI2ClockOut = RCC_PLLSAI2_SAI2CLK;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
RT_ASSERT(0);
}
/* Configure the main internal regulator output voltage */
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
{
RT_ASSERT(0);
}
/* Configure the Systick interrupt time */
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / RT_TICK_PER_SECOND);
/* Configure the Systick */
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
/* SysTick_IRQn interrupt configuration */
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}
调用rtt的设备注册函数,具体实现先埋坑。
int rt_hw_pin_init(void)
{
return rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL);
}
调用rtt的串口注册函数。
int stm32_hw_usart_init(void)
{
struct stm32_uart* uarts[] = { &uart1, };
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,
RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
uart);
RT_ASSERT(result == RT_EOK);
(void)result;
}
return 0;
}
看函数名称,应该是设置console的的设备。
#define RT_CONSOLE_DEVICE_NAME "uart1"
根据传入参数我们知道,把串口1用于调试窗口;而前面已经进行了串口1的设备注册,所以这里设置后,就可以正常使用rt_kprintf
进行打印了。
/**
* This function will set a device as console device.
* After set a device to console, all output of rt_kprintf will be
* redirected to this new device.
*
* @param name the name of new console device
*
* @return the old console device handler
*/
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;
}
RTM_EXPORT(rt_console_set_device);
看函数名是内存堆的初始化。
看传入的参数begin应该是指IRAM1中的剩余空间的起始点。
#define HEAP_BEGIN (&Image$$RW_IRAM1$$ZI$$Limit)
#define HEAP_END STM32_SRAM_END
#define STM32_SRAM_END (0x20000000 + STM32_SRAM_SIZE * 1024)
/**
* @ingroup SystemInit
*
* This function will initialize system heap memory.
*
* @param begin_addr the beginning address of system heap memory.
* @param end_addr the end address of system heap memory.
*/
void rt_system_heap_init(void *begin_addr, void *end_addr)
{
struct heap_mem *mem;
rt_ubase_t begin_align = RT_ALIGN((rt_ubase_t)begin_addr, RT_ALIGN_SIZE);
rt_ubase_t end_align = RT_ALIGN_DOWN((rt_ubase_t)end_addr, RT_ALIGN_SIZE);
RT_DEBUG_NOT_IN_INTERRUPT;
/* alignment addr */
if ((end_align > (2 * SIZEOF_STRUCT_MEM)) &&
((end_align - 2 * SIZEOF_STRUCT_MEM) >= begin_align))
{
/* calculate the aligned memory size */
mem_size_aligned = end_align - begin_align - 2 * SIZEOF_STRUCT_MEM;
}
else
{
rt_kprintf("mem init, error begin address 0x%x, and end address 0x%x\n",
(rt_ubase_t)begin_addr, (rt_ubase_t)end_addr);
return;
}
/* point to begin address of heap */
heap_ptr = (rt_uint8_t *)begin_align;
RT_DEBUG_LOG(RT_DEBUG_MEM, ("mem init, heap begin address 0x%x, size %d\n",
(rt_ubase_t)heap_ptr, mem_size_aligned));
/* initialize the start of the heap */
mem = (struct heap_mem *)heap_ptr;
mem->magic = HEAP_MAGIC;
mem->next = mem_size_aligned + SIZEOF_STRUCT_MEM;
mem->prev = 0;
mem->used = 0;
/* initialize the end of the heap */
heap_end = (struct heap_mem *)&heap_ptr[mem->next];
heap_end->magic = HEAP_MAGIC;
heap_end->used = 1;
heap_end->next = mem_size_aligned + SIZEOF_STRUCT_MEM;
heap_end->prev = mem_size_aligned + SIZEOF_STRUCT_MEM;
rt_sem_init(&heap_sem, "heap", 1, RT_IPC_FLAG_FIFO);
/* initialize the lowest-free pointer to the start of the heap */
lfree = (struct heap_mem *)heap_ptr;
}
rt_components_board_init() 函数执行的比较早,主要初始化相关硬件环境,执行这个函数时将会遍历通过
INIT_BOARD_EXPORT(fn) 申明的初始化函数表,并调用各个函数。
/**
* RT-Thread Components Initialization for board
*/
void rt_components_board_init(void)
{
const init_fn_t *fn_ptr;
for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
{
(*fn_ptr)();
}
}
版本显示函数就比较简单了,前面已经设置好了rt_kprintf的使用,这里打印就行了。
/**
* 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);
系统定时器初始化,参考rtt官方的文档手册,因为定时器是一个双向链表(有next和last指针),所以这里要对头元素进行init,即last=自己,next也等于自己,不init的话,last和next都指向0,显然是要GG的~
/**
* @ingroup SystemInit
*
* This function will initialize system timer
*/
void rt_system_timer_init(void)
{
int i;
for (i = 0; i < sizeof(rt_timer_list) / sizeof(rt_timer_list[0]); i++)
{
rt_list_init(rt_timer_list + i);
}
}
RT-Thread 的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到CPU 的使用权。
上面是官方文档说的,之前好像也在官方文档看到过说(但是现在没找到了,不知道是不是自己记错了,反正我暂时是这样理解的):由于RTT的线程调度是抢占式的,所以会有一个优先级链表,把优先级高的放在前面,然后从前往后遍历,找到就绪的就执行。
所以针对这个双向链表,未初始化的时候指针都是指向0的,这里执行的操作依然让指针指向自己,以保证不会GG。
/**
* @ingroup SystemInit
* This function will initialize the system scheduler
*/
void rt_system_scheduler_init(void)
{
rt_scheduler_lock_nest = 0;
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("start scheduler: max priority 0x%02x\n",
RT_THREAD_PRIORITY_MAX));
for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++)
{
rt_list_init(&rt_thread_priority_table[offset]);
}
/* initialize ready priority group */
rt_thread_ready_priority_group = 0;
/* initialize thread defunct */
rt_list_init(&rt_thread_defunct);
}
RTT的应用初始化,即在应用层创建线程了。创建一个名为“main”的线程,对应函数为main_thread_entry
,设置相应的栈空间、优先级并启动。
#define RT_MAIN_THREAD_STACK_SIZE 2048
#define RT_MAIN_THREAD_PRIORITY 10
void rt_application_init(void)
{
rt_thread_t tid;
tid = rt_thread_create("main", main_thread_entry, RT_NULL,
RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
RT_ASSERT(tid != RT_NULL);
rt_thread_startup(tid);
}
main_thread_entry内容如下,其实就是直接调用main函数:
/* 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();
$Super$$main(); /* for ARMCC. */
}
rt_components_init() 函数会在操作系统运行起来之后创建的 main 线程里被调用执行,这个时候硬件环境和操作系统已经初始化完成,可以执行应用相关代码。rt_components_init() 函数会遍历通过剩下的其他几个宏申明的初始化函数表。
/**
* RT-Thread Components Initialization
*/
void rt_components_init(void)
{
const init_fn_t *fn_ptr;
for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
{
(*fn_ptr)();
}
}
SOFT_TIMER 模式可配置,通过宏定义 RT_USING_TIMER_SOFT 来决定是否启用该模式。该模式被启用后,系统会在初始化时创建一个 timer 线程,然后 SOFT_TIMER 模式的定时器超时函数在都会在 timer 线程的上下文环境中执行。可以在初始化 / 创建定时器时使用参数 RT_TIMER_FLAG_SOFT_TIMER 来指定设置 SOFT_TIMER 模式。
因为我们没有使用软定时器功能,所以这个的#ifdef RT_USING_TIMER_SOFT
并不会成立,相当于一个空函数。
/**
* @ingroup SystemInit
*
* This function will initialize system timer thread
*/
void rt_system_timer_thread_init(void)
{
#ifdef RT_USING_TIMER_SOFT
int i;
for (i = 0;
i < sizeof(rt_soft_timer_list) / sizeof(rt_soft_timer_list[0]);
i++)
{
rt_list_init(rt_soft_timer_list + i);
}
/* start software timer thread */
rt_thread_init(&timer_thread,
"timer",
rt_thread_timer_entry,
RT_NULL,
&timer_thread_stack[0],
sizeof(timer_thread_stack),
RT_TIMER_THREAD_PRIO,
10);
/* startup */
rt_thread_startup(&timer_thread);
#endif
}
创建一个空闲线程,作为一个优先级最低的线程,用于CPU空闲时运行及进行RTT的其他处理。
/**
* @ingroup SystemInit
*
* This function will initialize idle thread, then start it.
*
* @note this function must be invoked when system init.
*/
void rt_thread_idle_init(void)
{
int i;
char tidle_name[RT_NAME_MAX];
for (i = 0; i < _CPUS_NR; i++)
{
rt_sprintf(tidle_name, "tidle%d", i);
rt_thread_init(&idle[i],
tidle_name,
rt_thread_idle_entry,
RT_NULL,
&rt_thread_stack[i][0],
sizeof(rt_thread_stack[i]),
RT_THREAD_PRIORITY_MAX - 1,
32);
/* startup */
rt_thread_startup(&idle[i]);
}
}
idle线程主要是负责执行idle_hook程序,如看门狗喂狗,和RTT的资源释放等操作。
static void rt_thread_idle_entry(void *parameter)
{
while (1)
{
#ifdef RT_USING_IDLE_HOOK
rt_size_t i;
for (i = 0; i < RT_IDEL_HOOK_LIST_SIZE; i++)
{
if (idle_hook_list[i] != RT_NULL)
{
idle_hook_list[i]();
}
}
#endif
rt_thread_idle_excute();
}
}
rt_thread_idle_excute中执行线程的删除、对象内存释放等操作。
void rt_thread_idle_excute(void)
{
/* Loop until there is no dead thread. So one call to rt_thread_idle_excute
* will do all the cleanups. */
while (_has_defunct_thread())
{
rt_base_t lock;
rt_thread_t thread;
RT_DEBUG_NOT_IN_INTERRUPT;
/* disable interrupt */
lock = rt_hw_interrupt_disable();
/* re-check whether list is empty */
if (_has_defunct_thread())
{
/* get defunct thread */
thread = rt_list_entry(rt_thread_defunct.next,
struct rt_thread,
tlist);
/* remove defunct thread */
rt_list_remove(&(thread->tlist));
/* lock scheduler to prevent scheduling in cleanup function. */
rt_enter_critical();
/* invoke thread cleanup */
if (thread->cleanup != RT_NULL)
thread->cleanup(thread);
/* if it's a system object, not delete it */
if (rt_object_is_systemobject((rt_object_t)thread) == RT_TRUE)
{
/* unlock scheduler */
rt_exit_critical();
/* enable interrupt */
rt_hw_interrupt_enable(lock);
return;
}
/* unlock scheduler */
rt_exit_critical();
}
else
{
/* enable interrupt */
rt_hw_interrupt_enable(lock);
/* may the defunct thread list is removed by others, just return */
return;
}
/* enable interrupt */
rt_hw_interrupt_enable(lock);
#ifdef RT_USING_HEAP
/* release thread's stack */
RT_KERNEL_FREE(thread->stack_addr);
/* delete thread object */
rt_object_delete((rt_object_t)thread);
#endif
}
}
线程调度开始则是获取最高优先级的线程,然后启动线程调度器,并开始进行线程切换。
/**
* @ingroup SystemInit
* This function will startup scheduler. It will select one thread
* with the highest priority level, then switch to it.
*/
void rt_system_scheduler_start(void)
{
register struct rt_thread *to_thread;
rt_ubase_t highest_ready_priority;
to_thread = _get_highest_priority_thread(&highest_ready_priority);
rt_current_thread = to_thread;
rt_schedule_remove_thread(to_thread);
/* switch to new thread */
rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp);
/* never come back */
}
至此,RTT就启动完成,后面就是各种任务调度了。