SYSTEM文件部分
Delay.c(寄存器版本和库函数版本差别不大)
SysTick定时器
SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号: 15)。在以前,大多操作系统需要一个硬件定时器来产生操作系统需要的滴答中断,作为整个系统的时基。例如,为多个任务许以不同数目的时间片,确保没有一个任务能霸占系统;或者把每个定时器周期的某个时间范围赐予特定的任务等,还有操作系统提供的各种定时功能,都与这个滴答定时器有关。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。Cortex‐M3处理器内部包含了一个简单的定时器。因为所有的CM3芯片都带有这个定时器,软件在不同 CM3器件间的移植工作得以化简。该定时器的时钟源可以是内部时钟(FCLK,CM3上的自由运行时钟),或者是外部时钟(CM3处理器上的STCLK信号)。不过,STCLK的具体来源则由芯片设计者决定,因此不同产品之间的时钟频率可能会大不相同,你需要检视芯片的器件手册来决定选择什么作为时钟源。
SysTick定时器能产生中断,CM3为它专门开出一个异常类型,并且在向量表中有它的一席之地。它使操作系统和其它系统软件在CM3器件间的移植变得简单多了,因为在所有CM3产品间对其处理都是相同的。
![](https://i-blog.csdnimg.cn/blog_migrate/4b182b81f6864512b847b9d23b462ef1.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4090fd0d8d406e5339ef091eda703aa9.png)
延时的 ms 数不能太长。否则超出了 LOAD 的范围,高位会被舍去,导致延时不准。最大延迟 ms 数可以通过公式: nms<=0xffffff*8*1000/SYSCLK 计算。 SYSCLK单位为 Hz, nms 的单位为 ms。如果时钟为 72M,那么 nms 的最大值为 1864ms。超过这个值,建议通过多次调用 delay_ms 实现,否则就会导致延时不准确。
后面delay_ms()和delay_us()两个函数就根据上面的fac_us和fac_ms进行乘法运算
OS自己还没接触过,不过看了原子哥的时钟摘取的方法也是脑洞大开
Sys.c(寄存器版本和库函数版本的差别有点大,库函数只介绍了位带操作,而寄存器版本的教程信息量好大…..)
位带操作
![](https://i-blog.csdnimg.cn/blog_migrate/167b0a9f1198bce4f939f070c60a9f61.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4054dc9dfee6277f0339b5aa1329ae29.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f4077d1781f23a0e9ab7fd2869fd744d.png)
<<CM3 权威指南>>第五章(87 页~92 页),对比了使用位带操作和不使用位带操作的区别,包括读、写、以及汇编代码的区别。
*((volatile unsigned long *)(addr)):这个解释下面该论坛的大神已经解释的很清楚了,简单明了
http://www.openedv.com/posts/list/918.htm
“<<和>>实现乘除法的优势”:位操作只需一个指令周期即可完成,而大部分的C 编译器的“%”运算均是调用子程序来完成,代码长、执行速度慢。通常,只要求是求2n 方的余数,均可使用位操作的方法来代替。通常如果需要乘以或除以2n,都可以用移位的方法代替。如果乘以2n,都可以生成左移的代码,而乘以其它的整数或除以任何数,均调用乘除法子程序。用移位的方法得到代码比调用乘除法子程序生成的代码效率高。实际上,只要是乘以或除以一个整数,均可以用移位的方法得到结果,提醒自己以后尽量多用移位来代替乘除。
Stm32_Clock_Init(9):库函数版本加了SystemInit,寄存器用的是原子哥写的一个Stm32_Clock_Init(9);函数![](https://i-blog.csdnimg.cn/blog_migrate/b061f8cf1e16e89aa80ebef253779a74.png)
寄存器使用的是Stm32_Clock_Init(u8 PLL),库函数使用的SystemInit(),这两个里面的代码几乎都是一样的
1、先是复位配置向量表
2、配置中断向量表基址和偏移量
3、使能外部时钟HSE
4、倍频PLL选择为9倍频,即72Mhz
5、PLL作为系统时钟
Sys.c其中有这个函数还挺好用的,直接调用就能实现软复位
void Sys_Soft_Reset(void)
{
SCB->AIRCR =0X05FA0000|(u32)0x04;
}
低功耗一览表![](https://i-blog.csdnimg.cn/blog_migrate/b36c58f0521eece094c243d1abfd4b3e.png)
进入待机模式
库函数:PWR_EnterSTANDBYMode()
寄存器:Sys_Standby() -> 这里面有一个函数是WFI_SET()用于进入模式的操作
__asm void WFI_SET(void)
{
WFI;
}
该函数其实是在 C 语言里面嵌入一条汇编指令,因为CM3 内核的 STM32 支持的 THUMB 指令,并不能内嵌汇编,所以需要通过这个方法来实现汇编代码的嵌入。
__asm关键字:启动内联汇编并且能写在任何c/c++合法语句之处.它不能单独出现.它必须接汇编指令、一组被大括号包含的指令或一对空括号.术语“__asm 块”在这里是任意一个指令或一组指令无论是否在括号内。括号可以清晰的将C或C++代码和汇编代码分开,并且避免了不必要的重复__asm关键字。括号也能避免模糊性。
下面原子哥还有几个类似的函数,记录下方便以后调用
//关闭所有中断
__asm void INTX_DISABLE(void)
{
CPSID I;
}
//开启所有中断
__asm void INTX_ENABLE(void)
{
CPSIE I;
}
//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(u32 addr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}
JTAG和SWD模式设置
// mode: JTAG,SWD 模式设置;00,全使能;01,使能 SWD;10,全关闭;
寄存器:void JTAG_Set(u8 mode) //用于设置 JTAG 的模式
库函数:GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
中断管理函数
以下是几个概念
可屏蔽中断:
可屏蔽中断由有中断能力的外围设备所产生,包括处在定时模式的定时器溢出。每个可屏蔽中断源可以由中断使能位所单独关闭,或者由寄存器中的通用中断使能位GIE一齐关闭。
非屏蔽中断:
非屏蔽中断是出现在NMI线上的请求 ,不受中断标志位IF的影响,在当前指令执行完以后,CPU就响应。这种中断通常用于电源故障。非屏蔽中断的优先权高于屏蔽中断。
区别:
可屏蔽中断和不可屏蔽中断都属于外部中断,是由外部中断源引起的;但它们也有区别:可屏蔽中断是通过CPU的INTR引脚引入,当中断标志
IF=1时允许中断,当IF=0时禁止中断,不可屏蔽中断是由NMI引脚引入,不受IF标志的影响
过程:
简述:保存断点入栈,响应经中断控制器8259优先级判断后的中断请求,恢复断点。
I/O设备发出的所有中断(IRQ)都可以产生可屏蔽中断,受标志位IF的影响,根据中断循序标志的设置来判断CPU是否响应中断请求。
1.接口发出中断请求
2.当前指令执行完后,CPU响应中断
3.终端类型号N送CPU
4.当前的EFR CS HE EIP 推入栈
5.清除IF(中断允许标志位,IF=0,禁止CPU响应外部中断请求,IF=1,允许响应)和TF(跟踪标志,也称单步执行,CPU按跟踪方式执行)
6.由中断类型号,查中断向量表获得中断服务子程序的入口地址
7.CPU执行中断子程序
8.开中断
9.IRET指令使EIP CS HE EFR弹出栈,返回被中断的源程序
![](https://i-blog.csdnimg.cn/blog_migrate/0c170cca6600f5db313586d47c239c00.jpeg)
中断挂起:
可以理解为“等待响应”
其中教程中有个ICER寄存器用于中断除能,这里要专门设置一个寄存器 ICER 来清除中断位,而不是向 ISER 写 0 来清除,是因为 NVIC 的这些寄存器都是写 1 有效的,写 0 是无效的。具体为什么这么设计,看下面这幅图
![](https://i-blog.csdnimg.cn/blog_migrate/00360d59ef47892b7ac4903f9c67d896.png)
下图也是经常用到,放这里,免得总是去翻
![](https://i-blog.csdnimg.cn/blog_migrate/6409dd88ebb08a94bcc7b3e9be5b3bc0.png)
原子哥代码中: void MY_NVIC_PriorityGroupConfig(u8 NVIC_Group)这个函数用来设置中断分组,
主要用于是对SCB->AIRCR=temp; 这条代码的操作,SCB->AIRCR是应用程序中断及复位控制寄存器,第8到第10位用于分组设置
NVIC_Init()进行了5步操作
1、设置分组
2、设置抢占优先级
3、设置响应优先级
4、设置通道
5、使能中断位
Ex_NVIC_Config()外部中断的配置
1、使能IO口复用时钟
2、设置中断寄存器组(总共4个组,每个组可以对应4个IO,总共可以对应16个IO(0~15)),以及使能的位
3、选择触发方式
4、开启中断
顺便看了下一些寄存器对应地址的方法,主要是分几步#define将各个基地址进行相加,在得到最后的地址后将地址强制转换为结构体指针类型,由于结构体的顺序结构可以将一段地址分配到对应的字节数里面,使用的时候直接用结构体指针指向就行了,非常方便,希望以后能学会使用这种方法写代码。
此论坛大神解释的事件与中断的区别:
事件:是表示检测到某一动作(电平边沿)触发事件发生了。
中断:有某个事件发生并产生中断,并跳转到对应的中断处理程序中。
事件可以触发中断,也可以不触发
中断有可能被更优先的中断屏蔽,事件不会
事件本质上就是一个触发信号,是用来触发特定的外设模块或核心本身(唤醒).
事件只是一个触发信号(脉冲),而中断则是一个固定的电平信号
Usart.c
UART:通用异步收发器;
USART:通用同步异步收发器。一般而言,单片机中,名称为UART的接口一般只能用于异步串行通讯,而名称为USART的接口既可以用于同步串行通讯,也能用于异步串行通讯。
同步通信与异步通信区别:
1.同步通信要求接收端时钟频率和发送端时钟频率一致,发送端发送连续的比特流;异步通信时不要求接收端时钟和发送端时钟同步,发送端发送完一个字节后,可经过任意长的时间间隔再发送下一个字节。
2.同步通信效率高;异步通信效率较低。
3.同步通信较复杂,双方时钟的允许误差较小;异步通信简单,双方时钟可允许一定误差。
4.同步通信可用于点对多点;异步通信只适用于点对点。
![](https://i-blog.csdnimg.cn/blog_migrate/24d6045917d8be25542eeaa608f1c104.png)
串口部分寄存器跟库函数版本还有差别,用库函数完全不用关心,只管直接设波特率就好了,我也不知道还有USART_BRR这个寄存器
以下是计算BRR寄存器值的公式,给了这条公式直接套就可以得出数值了
原子哥教程中也有提醒到在某些波特率和PCLK2频率下,还是会存在误差的,如下图
串口初始化过程:void uart_init(u32 pclk2, u32 bound)
1、先根据公式计算得出USARTDIV的小数和整数部分
2、使能PORTA口时钟
3、使能串口时钟
4、IO口状态设置
5、复位串口1
6、停止复位
7、波特率设置(有上面得出的值直接赋给BRR寄存器)
8、设置为1位停止,无校验位
9、如果使能了接收还需要使能串口中断,设置中断配置
STM32时钟系统
采用一个系统时钟不是很简单吗?为什么 STM32 要有多个时钟源呢? 因为首先
STM32 本身非常复杂,外设非常的多,但是并不是所有外设都需要系统时钟这么高的频率,比
如看门狗以及 RTC 只需要几十 k 的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干
扰能力也会越弱,所以对于较为复杂的 MCU 一般都是采取多时钟源的方法来解决这些问题。
顺便记多一个图
![](https://i-blog.csdnimg.cn/blog_migrate/194ea6b385dba075d5968cf61e0bb743.png)