移植系统最重要的细节之一就是配置系统时钟

第一次玩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手册说法不一,那么应该相信谁呢?

  • M3权威指南(SysTick->CTRL寄存器)
  • CLKSOURCE位用于选择外部/内核时钟源
  • 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


  • 原子哥的解答
  • 以及我通过实际实验得到的结果 和理解
    对于CTRL和LOAD的操作:注释部分和未注释部分,两者实验结果及现象一模一样
  • 测试程序
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可在任何运行模式下被访问,它包括条件标志位、中断禁止位、当前处理器模式标志位,以及其他一些相关的控制和状态位。

寄存器(R0~R16)以及从SysTick系统时钟理解RTOS移植初始化相关推荐

  1. STM32-STM32中SysTick系统时钟运用

    SysTick一共有4个寄存器,名称和地址分别为 STK_CSR        0XE000E010        控制寄存器 STK_LOAD        0XE000E014        重载 ...

  2. STM32之Systick(系统时钟滴答定时器)

    systick定时器有两个可选的时钟源,一个是外部时钟源(STCLK,等于HCLK/8),另一个是内核时钟(FCLK,等于HCLK).假若你选择内核时钟,并将HCLK频率设置为72MHz的话,系统时钟 ...

  3. 基于STM32F103--时钟树详解和系统时钟内部流程解析

    前言        本次我们认识一下STM32F103的时钟树架构,以及系统时钟在内部的初始化是怎么处理的,大部分是自己收集和整理,如有侵权请联系我删除. 本博客内容原创,创作不易,转载请注明 1.初 ...

  4. STM32使用内部时钟HSI作为系统时钟及配置Systick定时器

    参考:https://blog.csdn.net/huangyangquan/article/details/78790443 https://www.cnblogs.com/dustinzhu/p/ ...

  5. 系统时钟 SysTick

    什么是 SysTick? CM3的内核中包含一个SysTick时钟.SysTick为一个24位的递减计数器,SysTick设定初值并使能后,每经过1个系统时钟周期,计数值就减1.当计数到0后,SysT ...

  6. STM32直接用寄存器设置系统时钟

    时钟对单片机的重要性,就如同心脏对人的重要性,它能驱动处理器内核完成指令,外设也在它的驱动下完成动作.总而言之,时钟是单片机里很重要的一项.那么,我们为什么要设置系统时钟呢?有的外设需要的工作频率不一 ...

  7. STM32F103单片机系统时钟部分归纳

    STM32F103系列增强型微控制器 --时钟控制(RCC) 三种不同的时钟源可用作系统时钟(SYSCLOCK): HIS振荡器时钟(由芯片内部RC振荡器提供) HSE振荡器时钟(由芯片外部晶体振荡器 ...

  8. STM32系统时钟默认设置

    "我们一直都说STM32有一个非常复杂的时钟系统,然而在原子或者野火的例程中,只要涉及到时钟,我们却只能看到类似的库函数调用,如RCC_APB2PeriphClockCmd(RCC_APB2 ...

  9. STM32F2系列系统时钟默认配置

    新到一家公司后,有个项目要用到STM32F207Vx单片机,找到网上的例子照猫画虎的写了几个例子,比如ADC,可是到了ADC多通道转换的时候就有点傻眼了,这里面的时钟跑的到底是多少M呢?单片机外挂的时 ...

最新文章

  1. ISME:污水厂抗性组受细菌组成和基因交换驱动且出水中抗性表达活跃(一作解读)
  2. Namenode主备切换或报 IPC Server handler 23 on 8020
  3. 从ReLU到Sinc,26种神经网络激活函数可视化
  4. 设计模式(二)__装饰设计模式
  5. kafka0.9 java commit_kafka0.9.0及0.10.0配置属性
  6. composer 依赖包版本冲突_composer快速入门教程
  7. java sleep唤醒_JAVA wait(), notify(),sleep详解(转)
  8. ts怎么转换成m3u8直播源_HLS及M3U8介绍
  9. 值传递,指针传递,引用传递
  10. 《JavaScript 模式》读书笔记
  11. maven项目中操作mysql数据库案例
  12. python官网下载非常慢解决方法
  13. 畅购商城:Spring Security Oauth2 JWT(下)
  14. VMware Workstation下载及安装
  15. laravel 中 使用 composer 的中国镜像安装时报错(找不到包)
  16. prometheus+alertmanager+webhook实现自定义监控报警系统
  17. 通过windows官网工具制作win10启动盘并安装win10系统
  18. php 使用内置web服务器
  19. C++ 函数的声明和定义
  20. 百度CEO李彦宏:我是互联网的信徒

热门文章

  1. Android编解码流程
  2. 获得汉字字符串拼音首字母 .
  3. 百度云无法在网页上调用客户端进行下载文件(已安装最新版)
  4. 如何用 Python 做自动化测试
  5. matlab使用yalmip工具箱
  6. GEO数据库数据下载方法总结
  7. 转码机器人(微信转码,小程序转码)
  8. 【大学物理·光学】单缝的夫琅禾费衍射
  9. oracle中loder,Oracle Sql Loader的学习使用
  10. 查询SCI期刊的英文缩写