完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
移植系统最重要的细节之一就是配置系统时钟
第一次玩RT-Thread,发现同样的程序逻辑,测试现象不一样,从现象很明显看出来是时钟频率配置不一样。 由于之前玩STM32几乎没有关注过系统时钟的初始化,并且M3手册和STM32手册对于CTRL寄存器的CLKSOURCE位的说法不一,那么应该相信谁呢? 于是乎花了一下午的时间,才从系统启动过程的角度,经过测试程序的实际验证逐渐找到了原因所在。 (把我直接传送到1.4小节吧~~) 一、ARM寄存器别名及APCS 二、R0~R16寄存器用途、介绍 R0-R3 用作传入函数参数,传出函数返回值。在子程序调用之间,可以将 r0-r3 用于任何用途。 被调用函数在返回之前不必恢复 r0-r3。如果调用函数需要再次使用 r0-r3 的内容,则它必须保留这些内容。 RT-Thread代码启动过程 1、从系统初始化开始执行,将函数地址赋给R0寄存器,跳转到R0地址执行并返回此处(BLX是带链接的跳转,即带返回的跳转)。 startup_stm32f10x_hd.s 文件: 1.1 配置系统时钟(相当于CUBEMX配置时钟树) 执行LDR R0, =SystemInit 跳转到 system_stm32f10x.c执行void SystemInit (void)函数配置时钟: RCC->CR时钟控制寄存器 RCC->CFGR 时钟配置寄存器 RCC_CIR 时钟中断寄存器 然后跳到 static void SetSysClockTo72(void)函数: 1.2将main函数地址给R0,将函数地址赋给R0,跳转到R0地址执行,不返回(BX是跳转,不返回)。 1.3跳转到了 $ Sub $ $main,主要是一些系统启动代码(系统初始化)。 在rtthread_startup中,主要实现了板级初始化(初始化外设和驱动);打印RT-Thread的logo和版本信息;初始化系统定时器;初始化调度器;创建application线程(这里将用户main函数作为一个线程,用户main里面是空的);初始化软件定时器;创建空闲线程;启动系统调度(启用调度后,main函数就会参与调度开始运行)。 1.4重点讲一下RTOS系统时钟(滴答定时器)的初始化 1、什么是SYSTICK: 这是一个24位的系统节拍定时器system tick timer,SysTick,具有自动重载和溢出中断功能,所有基于Cortex_M3处理器的微控制器都可以由这个定时器获得一定的时间间隔。 2、作用: 在单任务引用程序中,因为其架构就决定了它执行任务的串行性,这就引出一个问题:当某个任务出现问题时,就会牵连到后续的任务,进而导致整个系统崩溃。要解决这个问题,可以使用实时操作系统(RTOS). 因为RTOS以并行的架构处理任务,单一任务的崩溃并不会牵连到整个系统。这样用户出于可靠性的考虑可能就会基于RTOS来设计自己的应用程序。这样SYSTICK存在的意义就是提供必要的时钟节拍,为RTOS的任务调度提供一个有节奏的“心跳”。 3、微控制器的定时器资源一般比较丰富,比如STM32存在8个定时器,为啥还要再提供一个SYSTICK?原因就是所有基于ARM Cortex_M3内核的控制器都带有SysTick定时器,这样就方便了程序在不同的器件之间的移植。而使用RTOS的第一项工作往往就是将其移植到开发人员的硬件平台上,由于SYSTICK的存在无疑降低了移植的难度。 4、工作原理 SysTick定时器是一个24位的倒计数,在一定频率下,当倒计数为0时,将从RELOAD寄存器中取值作为定时器的初始值,同时可以选择在这个时候产生中断(异常号:15)。 5、实例 在RT-Thread系统初始化void rt_hw_board_init()函数下调用了static __INLINE uint32_t SysTick_Config(uint32_t ticks) 为了便于理解: M3权威指南和STM32手册说法不一,那么应该相信谁呢?
#define SysTick_CLKSource_HCLK_Div8 ((uint32_t)0xFFFFFFFB) #define SysTick_CLKSource_HCLK ((uint32_t)0x00000004) SysTick->CTRL |= SysTick_CLKSource_HCLK; // CTRL寄存器的CLKSOURCE位 置1 SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8; // CTRL寄存器的CLKSOURCE位 置0
static struct rt_thread led0_thread;//线程控制块 static struct rt_thread led1_thread;//线程控制块 ALIGN(RT_ALIGN_SIZE) static rt_uint8_t rt_led0_thread_stack[256];//线程栈 static rt_uint8_t rt_led1_thread_stack[256];//线程栈 //线程LED0 static void led0_thread_entry(void* parameter) { while(1) { LED0=~LED0; rt_thread_delay(300); } } //线程LED1 static void led1_thread_entry(void* parameter) { while(1) { LED1=~LED1; rt_thread_delay(1000); } } int main(void) { // 创建静态线程 rt_thread_init(&led0_thread, //线程控制块 "led0", //线程名字,在shell里面可以看到 led0_thread_entry, //线程入口函数 RT_NULL, //线程入口函数参数 &rt_led0_thread_stack[0], //线程栈起始地址 sizeof(rt_led0_thread_stack), //线程栈大小 3, //线程的优先级 20); //线程时间片 rt_thread_startup(&led0_thread); //启动线程led0_thread,开启调度 // 创建静态线程 rt_thread_init(&led1_thread, //线程控制块 "led1", //线程名字,在shell里面可以看到 led1_thread_entry, //线程入口函数 RT_NULL, //线程入口函数参数 &rt_led1_thread_stack[0], //线程栈起始地址 sizeof(rt_led1_thread_stack), //线程栈大小 3, //线程的优先级 20); //线程时间片 rt_thread_startup(&led1_thread); //启动线程led1_thread,开启调度 } 实验现象相同,所以说,对于stm32,将CTRL寄存器的CLKSOURCE位置0所选择的时钟源是:AHB时钟(HCLK)8分频,即 本来就是被8分频过的。 1.5 $ Sub $ $main在main之前干的活就是进行rt-thread系统初始化。 以下是在rt_application_init()函数中创建的main函数线程: $ Super $ $main可以直接跳到main()函数: 总结:可以这样使用给main函数打补丁: int $Sub$$main(void) { //添加补丁函数 $Super$$main(); //使用本句直接转到main()运行 } 当然,main()函数可以换做其它需要的函数 R4-R10 被用来存放函数的局部变量。如果被调用函数使用了这些寄存器,它在返回之前必须恢复这些寄存器的值。 R11- fp(frame pointer)寄存器 即可以用来记录回溯信息,也可以当做局部变量来使用 R12-内部调用暂时寄存器 ip 它在过程链接胶合代码(例如,交互操作胶合代码)中用于此角色。 在过程调用之间,可以将它用于任何用途。被调用函数在返回之前不必恢复 r12。 R13 -栈指针 sp 用户模式和系统模式共用一个SP,每种异常模式都有各自专用的R13寄存器(SP)。它们通常指向各模式所对应的专用堆栈,也就是ARM处理器允许用户程序有六个不同的堆栈空间,ARM处理器中的R13被用作SP。当不使用堆栈时,R13 也可以用做通用数据寄存器. 当程序的运行进入异常模式时,可以将需要保护的寄存器放入R13所指向的堆栈,而当程序从异常模式返回时,则从对应的堆栈中恢复,采用这种方式可以保证异常发生后程序的正常执行。 R14-链接寄存器 LR 在ARM体系结构中LR的特殊用途有两种: 一是执行子程序调用指令(BL )时,会自动完成将当前的PC的值减去4的结果数据保存到LR寄存器。即将调用指令的下紧邻指令的地址保存到LR。返回时将lr赋给pc即可。 二是当异常发生时,会自动完成将当前的PC保存到LR寄存器,返回时将lr-4赋给pc即可,因此在各种异常模式下可以根据LR的值返回到异常发生前的相应位置继续执行。 为什么异常发生时,需要 sub lr, lr, #4 ? 是因为arm流水线,也就是执行第1条指令,第2条指令进行译码,将第3条指令从存储器中取出,那么pc当前等于pc+8,所以在异常发生时,此时lr=pc+8,但是pc+4是没有被执行的,所以异常返回时需要返回到(lr-4)地址上,执行已经译码的地址上。 stmdb和ldmia汇编指令 stmdb用于将寄存器压栈,ldmia用于将寄存器弹出栈。 stmdb和ldmia指令一般配对使用,用于保存使用到的寄存器。 ARM指令的多数据传输(STM、LDM)中,提到:多寄存器的Load和Store指令分为2组:一组用于数据的存储与读取,对应于IA、IB、DA、DB,一组用于堆栈操作,对应于FD、ED、FA、EA,两组中对应的指令含义相同。 即: STMIB(地址先增而后完成操作)、STMFA(满递增堆栈); STMIA(完成操作而后地址递增)、STMEA(空递增堆栈); STMDB(地址先减而后完成操作)、STMFD(满递减堆栈); STMDA(完成操作而后地址递减)、STMED(空递减堆栈)。 上述各组2个指令含义相同只是适用场合不同,同理有: LDMIB、LDMED; LDMIA、LDMFD; LDMDB、LDMEA; LDMDA、LDMFA。 IA模式表示:每次传送后地址+4;(After Increase) DB模式表示:每次传送前地址-4;(Before Decrease) 多寄存器加载/存储指令共有8种模式(4个用与数据块的传输,4个用于栈操作) 例1:汇编指令 stmdb sp!,{r0-r12,lr} 含义:sp = sp - 4,先压lr,sp = lr(即将lr中的内容放入sp所指的内存地址)。sp = sp - 4,再压r12,sp = r12。sp = sp - 4,再压r11,sp = r11…sp = sp - 4,最后压r0,sp = r0。 如果想要将r0-r12和lr弹出,可以用ldmia指令: ldmia sp!,{r0-r12,lr} 例二: //将R1-R7的数据保存到寄存器中,存储器指针在保存第一个值之后增加,增长方向为向上增长 STMIA R0!,{R1-R7} //将R1-R7的数据保存到寄存器中,存储器指针在保存第一个值之后增加,增长方向为向下增长 STMDB R0!,{R1-R7} STMIA:比如当前r0指向的内存地址是 0x1000 STMIA R0!,{R1-R7} 就是首先把r1存入0x1000,然后r2存入0x1004,然后r3存入0x1008。如果是32位的处理器就是每次加4个字节,以此类推把 r1-r7按照递增的地址存入。R0!就是从R0的地址开始存的意思。 STMDB则是地址从R0开始减少,依次存储。 R15-程序计数器 PC PC总是指向当前指令的下两条指令的地址,即PC的值为当前指令的地址值加8个字节程序状态寄存器 R16-CPSR(CurrentProgram Status Register,当前程序状态寄存器) CPSR可在任何运行模式下被访问,它包括条件标志位、中断禁止位、当前处理器模式标志位,以及其他一些相关的控制和状态位。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1617 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1543 浏览 1 评论
977 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
683 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1595 浏览 2 评论
1863浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
644浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
515浏览 3评论
531浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
504浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-22 04:06 , Processed in 0.606393 second(s), Total 45, Slave 38 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号