STM32学习笔记SYS程序解释原子.docx
《STM32学习笔记SYS程序解释原子.docx》由会员分享,可在线阅读,更多相关《STM32学习笔记SYS程序解释原子.docx(24页珍藏版)》请在冰豆网上搜索。
STM32学习笔记SYS程序解释原子
SYS.C程序解释
#include
#include"sys.h"
//设置向量表偏移地址
//NVIC_VectTab:
基址
//Offset:
偏移量
//CHECKOK
//091207
voidMY_NVIC_SetVectorTable(u32NVIC_VectTab,u32Offset)
{
//检查参数合法性
assert_param(IS_NVIC_VECTTAB(NVIC_VectTab));
assert_param(IS_NVIC_OFFSET(Offset));
SCB->VTOR=NVIC_VectTab|(Offset&(u32)0x1FFFFF80);//设置NVIC的向量表偏移寄存器
//用于标识向量表是在CODE区还是在RAM区
}
解释:
前面两行是用来检查参数合法性,这里不作分析。
重点看第三行。
#defineNVIC_VectTab_RAM ((u32)0x)
#defineNVIC_VectTab_FLASH ((u32)0x08000000)
typedefstruct
{
vuc32CPUID;
vu32ICSR;
vu32VTOR;
vu32AIRCR;
vu32SCR;
vu32CCR;
vu32SHPR[3];
vu32SHCSR;
vu32CFSR;
vu32HFSR;
vu32DFSR;
vu32MMFAR;
vu32BFAR;
vu32AFSR;
}SCB_TypeDef;
在<<权威指南>>第一百零四页,有这么一段话:
NVIC中有一个寄存器,称为“向量表偏移量寄存器”(在地址0xE000_ED08处),通过修改它的值就能定位向量表。
但必须注意的是:
向量表的起始地址是有要求的:
必须先求出系统中共有多少个向量,再把这个数字向上增大到是2的整次幂,而起始地址必须对齐到后者的边界上。
例如,如果一共有32个中断,则共有32+16(系统异常)=48个向量,向上增大到2的整次幂后值为64,因此地址
地址必须能被64*4=256整除,从而合法的起始地址可以是:
0x0,0x100,0x200等。
Offset:
是偏移量的计算
也就是说STM32自己有60个中断,加上CM3的16个,总共有76个中断,扩大到2的整次幂,那就是128,然后再乘以4,得到512,也就是0X200.根据这样计算,合法的偏移地址应该是0X0,0X200,0X400,0X600,0x800......
29 TBLBASE R/W 0 Table base in Code (0) or RAM
(1)
28:
7 TBLOFF R/W 0 Table offset value from Code region or RAM region
屏蔽前七位0x1FFFFF80和后三位+Offset + VECTTOR就是开始地址,低7位没有用到,所以&0X80。
VTOR设置只有BIT【28:
7】,你把(u32)0x1FFFFF80二进制看看是不是【28:
7】。
//设置NVIC分组
//NVIC_Group:
NVIC分组0~4总共5组
//CHECKOK
//091209
voidMY_NVIC_PriorityGroupConfig(u8NVIC_Group)
{
u32temp,temp1;
temp1=(~NVIC_Group)&0x07;//取后三位
temp1<<=8;
temp=SCB->AIRCR;//读取先前的设置
temp&=0X0000F8FF;//清空先前分组
temp|=0X05FA0000;//写入钥匙
temp|=temp1;
SCB->AIRCR=temp;//设置分组
}
解释:
CM3内核支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有256级的可编程中断设置。
但STM32并没有使用CM3内核的全部东西,而是只用了它的一部分。
STM32有76个中断,包括16个内核中断和60个可屏蔽中断,具有16级可编程的中断优先级。
而我们常用的就是这60个可屏蔽中断,所以我们就只针对这60个可屏蔽中断进行介绍。
在MDK内,与NVIC相关的寄存器,MDK为其定义了如下的结构体:
typedefstruct
{
vu32ISER[2];
u32RESERVED0[30];
vu32ICER[2];
u32RSERVED1[30];
vu32ISPR[2];
u32RESERVED2[30];
vu32ICPR[2];
u32RESERVED3[30];
vu32IABR[2];
u32RESERVED4[62];
vu32IPR[15];
}NVIC_TypeDef;
STM32的中断在这些寄存器的控制下有序的执行的。
了解这些中断寄存器,你才能方便的
使用STM32的中断。
下面重点介绍这几个寄存器:
ISER[2]:
ISER全称是:
InterruptSet-EnableRegisters,这是一个中断使能寄存器组。
上面说了STM32的可屏蔽中断只有60个,这里用了2个32位的寄存器,总共可以表示64个中断。
而STM32只用了其中的前60位。
ISER[0]的bit0~bit31分别对应中断0~31。
ISER[1]的bit0~27对应中断32~59;这样总共60个中断就分别对应上了。
你要使能某个中断,必须设置相应的ISER位为1,使该中断被使能(这里仅仅是使能,还要配合中断分组、屏蔽、IO口映射等设置才算是一个完整的中断设置)。
具体每一位对应哪个中断,请参考stm32f10x_nvic..h里面的第36行处。
ICER[2]:
全称是:
InterruptClear-EnableRegisters,是一个中断除能寄存器组。
该寄存器组与ISER的作用恰好相反,是用来清除某个中断的使能的。
其对应位的功能,也和ICER一样。
这里要专门设置一个ICER来清除中断位,而不是向ISER写0来清除,是因为NVIC的这些寄存器都是写1有效的,写0是无效的。
具体为什么这么设计,请看《CM3权威指南》第125页,NVIC概览一章。
ISPR[2]:
全称是:
InterruptSet-PendingRegisters,是一个中断挂起控制寄存器组。
每个位对应的中断和ISER是一样的。
通过置1,可以将正在进行的中断挂起,而执行同级或更高级别的中断。
写0是无效的。
ICPR[2]:
全称是:
InterruptClear-PendingRegisters,是一个中断解挂控制寄存器组。
其作用与ISPR相反,对应位也和ISER是一样的。
通过设置1,可以将挂起的中断接挂。
写0无效。
IABR[2]:
全称是:
ActiveBitRegisters,是一个中断激活标志位寄存器组。
对应位所代表的中断和ISER一样,如果为1,则表示该位所对应的中断正在被执行。
这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个。
在中断执行完了由硬件自动清零。
IPR[15]:
全称是:
InterruptPriorityRegisters,是一个中断优先级控制的寄存器组。
这个寄存器组相当重要!
STM32的中断分组与这个寄存器组密切相关。
IPR寄存器组由15个32bit的寄存器组成,每个可屏蔽中断占用8bit,这样总共可以表示15*4=60个可屏蔽中断。
刚好和STM32的可屏蔽中断数相等。
IPR[0]的[31~24],[23~16],[15~8],[7~0]分别对应中中断3~0,依次类推,总共对应60个外部中断。
而每个可屏蔽中断占用的8bit并没有全部使用,而是只用了高4位。
这4位,又分为抢占优先级和子优先级。
抢占优先级在前,子优先级在后。
而这两个优先级各占几个位又要根据SCB->AIRCR中中断分组的设置来决定。
这里简单介绍一下STM32的中断分组:
STM32将中断分为5个组,组0~4。
该分组的设置是由SCB->AIRCR寄存器的bit10~8来定义的。
具体的分配关系如下表所示:
优先级具体详解请详见CM3权威指南第108页。
通过这个表,我们就可以清楚的看到组0~4对应的配置关系,例如组设置为3,那么此时所有的60个中断,每个中断的中断优先寄存器的高四位中的最高3位是抢占优先级,低1位是响应优先级。
每个中断,你可以设置抢占优先级为0~7,响应优先级为1或0。
抢占优先级的级别高于响应优先级。
而数值越小所代表的优先级就越高。
MY_NVIC_PriorityGroupConfig(u8NVIC_Group):
该函数的参数NVIC_Group为要设置的分组号,可选范围为0~4,总共5组。
如果参数非法,将可能导致不可预料的结果。
temp1=(~NVIC_Group)&0x07;//取后三位
假设分组为第3组,则NVIC_Group=3=00000011,~NVIC_Group=11111100.
~NVIC_Group&0x07=00000100.
赋给temp1=00000000000000000000000000000100(u32)
temp1<<=8;
此时左移8位后temp1=00000000000000000000010000000000
temp=SCB->AIRCR;//读取先前的设置
temp&=0X0000F8FF;//清空先前分组
设先前设置为:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
与:
00000000000000001111100011111111相与。
=0x0000F8FF=00000000000000001111100011111111
使得SCB->AIRCR[10:
8]为零,从而就达到分组清零的目的。
temp|=0X05FA0000;//写入钥匙
通过上面的介绍我们知道SCB->AIRCR的修改需要通过在高16位写入0X05FA这
个密钥才能修改的,故在设置AIRCR之前,应该把密钥加入到要写入的内容的高16位,以保证能正常的写入AIRCR。
在修改AIRCR的时候,我们一般采用读->改->写的步骤,来实现不改变AIRCR原来的其他设置。
以上就是MY_NVIC_PriorityGroupConfig函数设置中断优先级分组的思路。
temp|=temp1;
把上面左移8位得到的分组赋给temp,即SCB->AIRCR[10:
8]为100,即得到上面的假设第3组。
SCB->AIRCR=temp;//设置分组
最后把这个分组写入AIRCR中,完成分组的设置。
//设置NVIC
//NVIC_PreemptionPriority:
抢占优先级
//NVIC_SubPriority:
响应优先级
//NVIC_Channel:
中断编号
//NVIC_Group:
中断分组0~4
//注意优先级不能超过设定的组的范围!
否则会有意想不到的错误
//组划分:
//组0:
0位抢占优先级,4位响应优先级
//组1:
1位抢占优先级,3位响应优先级
//组2:
2位抢占优先级,2位响应优先级
//组3:
3位抢占优先级,1位响应优先级
//组4:
4位抢占优先级,0位响应优先级
//NVIC_SubPriority和NVIC_PreemptionPriority的原则是,数值越小,越优先
//CHECKOK
//100329
voidMY_NVIC_Init(u8NVIC_PreemptionPriority,u8NVIC_SubPriority,u8NVIC_Channel,u8NVIC_Group)
{
u32temp;
u8IPRADDR=NVIC_Channel/4;//每组只能存4个,得到组地址
u8IPROFFSET=NVIC_Channel%4;//在组内的偏移
IPROFFSET=IPROFFSET*8+4;//得到偏移的确切位置
MY_NVIC_PriorityGroupConfig(NVIC_Group);//设置分组
temp=NVIC_PreemptionPriority<<(4-NVIC_Group);
temp|=NVIC_SubPriority&(0x0f>>NVIC_Group);
temp&=0xf;//取低四位
if(NVIC_Channel<32)NVIC->ISER[0]|=1<elseNVIC->ISER[1]|=1<<(NVIC_Channel-32);
NVIC->IPR[IPRADDR]|=temp<解释:
这个函数是NVIC设置函数MY_NVIC_Init,该函数有4个参数,分别为:
NVIC_PreemptionPriority、NVIC_SubPriority、NVIC_Channel、NVIC_Group。
第一个参数NVIC_PreemptionPriority为中断抢占优先级数值,第二个参数NVIC_SubPriority为中断子优先级数值,前两个参数的值必须在规定范围内,否则也可能产生意想不到的错误。
第三个参数NVIC_Channel为中断的编号(范围为0~59),最后一个参数NVIC_Group为中断分组设置(范围为0~4)。
IPR[0]的[31~24],[23~16],[15~8],[7~0]分别对应中中断3~0
IPR[1]的[31~24],[23~16],[15~8],[7~0]分别对应中中断7~4
.
.
.
IPR[14]的[31~24],[23~16],[15~8],[7~0]分别对应中中断59~56
u8IPRADDR=NVIC_Channel/4;//每组只能存4个,得到组地址
假如是中断7,则IPRADDR=1,即我们用的是IPR[1]。
u8IPROFFSET=NVIC_Channel%4;//在组内的偏移
中断7对应的偏移是3。
IPROFFSET=IPROFFSET*8+4;//得到偏移的确切位置
那就是到[31~24]。
注意,每个中断的8位只用了它的高4位,所以是+4.其实是[31~28].
MY_NVIC_PriorityGroupConfig(NVIC_Group);//设置分组
参照前面的分析。
temp=NVIC_PreemptionPriority<<(4-NVIC_Group);
假设设置分组3,假如NVIC_PreemptionPriority=00000011,左移1位,temp=00000000000000000000000000000110.
temp|=NVIC_SubPriority&(0x0f>>NVIC_Group);
00001111右移3位是:
00000001,然后与00000001(1位相应优先级数值设为1),得00000001,然后与temp相或为:
00000111.temp=00000000000000000000000000000111.
temp&=0xf;//取低四位
此时temp=00000000000000000000000000000111.
if(NVIC_Channel<32)NVIC->ISER[0]|=1<elseNVIC->ISER[1]|=1<<(NVIC_Channel-32);
使能中断位,这个很好理解。
NVIC->IPR[IPRADDR]|=temp<假如还是中断7,则IPROFFSET=28,则temp左移28位为
01110000000000000000000000000000,此时IPR[1]的[31~28]位是0111.
//外部中断配置函数
//只针对GPIOA~G;不包括PVD,RTC和USB唤醒这三个
//参数:
GPIOx:
0~6,代表GPIOA~G;BITx:
需要使能的位;TRIM:
触发模式,1,下降沿;2,上升沿;3,任意电平触发
//该函数一次只能配置1个IO口,多个IO口,需多次调用
//该函数会自动开启对应中断,以及屏蔽线
//待测试...
voidEx_NVIC_Config(u8GPIOx,u8BITx,u8TRIM)
{
u8EXTADDR;
u8EXTOFFSET;
EXTADDR=BITx/4;//得到中断寄存器组的编号
EXTOFFSET=(BITx%4)*4;
RCC->APB2ENR|=0x01;//使能io复用时钟
AFIO->EXTICR[EXTADDR]&=~(0x000F<!
!
AFIO->EXTICR[EXTADDR]|=GPIOx<//自动设置
EXTI->IMR|=1<//EXTI->EMR|=1<)
if(TRIM&0x01)EXTI->FTSR|=1<if(TRIM&0x02)EXTI->RTSR|=1<}
解释:
该函数为Ex_NVIC_Config,该函数有3个参数:
GPIOx为GPIOA~G(0~6),在sys.h里面有定义。
代表要配置的IO口。
BITx则代表这个IO口的第几位。
TRIM为触发方式,低2位有效(0x01代表下降触发;0x02代表上升沿触发;0x03代表任意电平触发)。
STM32的19个外部中断为:
线0~15:
对应外部IO口的输入中断。
因为STM32任何一个IO口都可以配置成中断输入口,但是IO口的数目远大于中断线数(16个)。
于是STM32就这样设计,GPIOA~GPIOG的[15:
0]分别对应中断线15~0。
这样每个中断线对应了最多7个IO口,以线0为例:
它对应了GPIOA.0、PIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0。
而中断线每次只能连接到1个IO口上,这样就需要EXTICR来决定对应的中断线配置到哪个GPIO上了。
EXTICR[0]的[3:
0]只能存放GPIOA.0~G.0--------------线0;
EXTICR[0]的[7:
4]只能存放GPIOA.1~G.1--------------线1;
.
.
.
EXTICR[3]的[15:
12]只能存放GPIOA.15~G.15-----------线15;
EXTADDR=BITx/4;//得到中断寄存器组的编号
假设是GPIOA的第5口,则EXTADDR=1,即选择EXTICR[1]。
EXTOFFSET=(BITx%4)*4;
假设是GPIOA的第5口,则EXTOFFSET=4,即选择EXTICR[1]的[7:
4].
RCC->APB2ENR|=0x01;//使能io复用时钟
这个很容易理解。
AFIO->EXTICR[EXTADDR]&=~(0x000F<!
!
0x000F左移4位是0x00F0,取个反即0XFF0F,正好是[7:
4]清零。
AFIO->EXTICR[EXTADDR]|=GPIOx<GPIOA=0,则左移4位,还是0,对应[7:
4]=0000,正好选择PA口;
GPIOB=1,左移4位,就是00010000,对应[7:
4]=0001,正好选择PB口。
IMR:
中断屏蔽寄存器。
这是一个32寄存器。
但是只有前19位有效。
当位x设置为1时,则开启这个线上的中断,否则关闭该线上的中断。
EMR:
事件屏蔽寄存器,同IMR,只是该寄存器是针对事件的屏蔽和开启。
EXTI->IMR|=1<把位和中断线对应起来,这个容易理解。
if(TRIM&0x01)EXTI->FTSR|=1<如果是下降沿,则把FTSR对应位的值赋1.
if(TRIM&0x02)EXTI->RTSR|=1<如果是上升沿,则把RTSR对应位的值赋1.
//不能在这里执行所有外设复位!
否则至少引起串口不工作.
//把所有时钟寄存器复位
voidMYRCC_DeInit(void)
{
RCC->APB1RSTR=0x00000000;//复位结束
RCC->APB2RSTR=0x00000000;
RCC->AHBENR=0x00000014;//睡眠模式闪存和SRAM时钟使能.其他关闭.
RCC->APB2ENR=0x00000000;//外设时钟关闭.
RCC->APB1ENR=0x00000000;
RCC->CR|=0x00000001;//使能内部高速时钟HSION
RCC->CFGR&=0xF8FF0000;//复位SW[1:
0],HPRE[3:
0],PPRE1[2:
0],PPRE2[2:
0],ADCPRE[1:
0],MCO[2:
0]
RCC->CR&=0xFEF6FFFF;//复位HSEON,CSSON,PLLON
RCC->CR&=0xFFFBFFFF;//复位HSEBYP
RCC->CFGR&=0xFF80FFFF;//复位PLLSRC,PLLXTPRE,PLLMUL[3:
0]andUSBPRE
RCC->CIR=0x00000000;//关闭所有中断
//配置向量表
#ifdefVECT_TAB_RAM
MY_NVIC_SetVectorTable(NVIC_VectTab_RAM,0x0);
#else
MY_NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x0);
#endif
}
解释:
关于复位用到得一系列寄存器参考“免积分-STM32-中文资料参考”的第51页后。
/