打印
[应用相关]

我对STM32所用位带操作宏的超详细剖析、优势分析

[复制链接]
楼主: characteristic
手机看帖
扫描二维码
随时随地手机跟帖
21
characteristic|  楼主 | 2019-6-17 15:09 | 只看该作者 回帖奖励 |倒序浏览
深入剖析了位带操作的机理和优势之后,
有一个疑问,其实外设寄存器都可以通过位带操作,
尤其是寄存器比特位的操作和判别,用位带操作的高效尤其明显。
但是看STM官方的库,却基本不用位带操作:
举个例子,SystemInit()中,把PLL作为系统时钟源,代码如下:
   RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    //系统时钟选择PLL(BIT1位)置1
这种对整个32位进行读出--逻辑运算--再写回的效率是很低的。
假如用位带操作,则只须 bRCC_CFGR_SW_PLL=1; 就行了
前面要做的准备工作只是定义一个位别名地址宏: #define  bRCC_CFGR_SW_PLL  0x42420040就可以了。
有人可能会想,这是否增加了宏定义的工作量?    我反问:RCC_CFGR_SW_PLL难道就不需要定义吗?

使用特权

评论回复
22
characteristic|  楼主 | 2019-6-17 15:09 | 只看该作者
拿前不久我使用的一个从STOP模式中快速恢复时钟的函数为例,
库函数版本如下:
/**
  * 本函数用于从STOP模式唤醒后重新配置系统时钟:使能HSE,PLL并选择PLL作为系统时钟源
  *         
  */
static void SYSCLKConfig_STOP(void)
{  
  /* 从STOP模式唤醒后重新配置系统时钟 */
  /* 使能 HSE */
  RCC_HSEConfig(RCC_HSE_ON);
  
  /* 等待HSE时钟就绪 */
  while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET)
  {}  
  
  /* 使能PLL */
  RCC_PLLCmd(ENABLE);
  
  /* 等待PLL就绪 */
  while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
  {}
  
  /* 选择PLL作为系统时钟源 */
  RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
  
  /* 等待时钟源配置就绪 */
  while (RCC_GetSYSCLKSource() != 0x08)
  {}
}

使用特权

评论回复
23
characteristic|  楼主 | 2019-6-17 15:09 | 只看该作者
如果改写成STM官方惯用的寄存器版本,则如下:
static void SYSCLKConfig_STOP(void)
{  
  /* 从STOP模式唤醒后重新配置系统时钟 */
  /* 使能 HSE */   
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
  
  /* 等待HSE时钟就绪 */
  while ((RCC->CR & RCC_CR_HSERDY) == 0)
  {}
  
  /* 使能 PLL */
  RCC->CR |= RCC_CR_PLLON;
  
  /* 等待直到 PLL 就绪 */
  while((RCC->CR & RCC_CR_PLLRDY) == 0)
   {
    }
  
  /* 把 PLL 作为系统时钟源 */     
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); //系统时钟选择位(BIT1:0)复位(00为HSI 01为HSE 10为PLL)
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    //系统时钟选择PLL(BIT1位)置1

  /* 等待,直到 PLL 被用作系统时钟源 */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) //BIT3为1指示PLL已为钟源
    {
    }
}

使用特权

评论回复
24
characteristic|  楼主 | 2019-6-17 15:10 | 只看该作者
用到的寄存器位:
RCC寄存器基址  4002 1000
RCC->CR   +0
RCC_CR_HSEON    位16
RCC_CR_HSERDY   位17
RCC_CR_PLLON     位24
RCC_CR_PLLRDY   位25

RCC->CFGR +4
RCC_CFGR_SW0      位0
RCC_CFGR_SW_PLL  位1
RCC_CFGR_SWS_PLL  位3
现在定义一下位别名地址:
// #define RCC_BASE   0x40021000    //这个官方已有定义
#define bRCC_CR_HSEON   BIT_ADDR(RCC_BASE, 16)  
#define bRCC_CR_HSERDY   BIT_ADDR(RCC_BASE, 17)
#define bRCC_CR_PLLON   BIT_ADDR(RCC_BASE, 24)
#define bRCC_CR_PLLRDY    BIT_ADDR(RCC_BASE, 25)

#define bRCC_CFGR_SW0       BIT_ADDR(RCC_BASE+4, 0)
#define bRCC_CFGR_SW_PLL   BIT_ADDR(RCC_BASE+4, 1)
#define bRCC_CFGR_SWS_PLL   BIT_ADDR(RCC_BASE+4, 3)

为了与官方定义区别,位地址宏均加上前缀b
话说这种基础性的工作,一个上午就可以全部搞定,做成一个位别名地址宏的头文件。

使用特权

评论回复
25
characteristic|  楼主 | 2019-6-17 15:10 | 只看该作者
//改写成位操作版本,如下:
static void SYSCLKConfig_STOP(void)
{  
  /* 从STOP模式唤醒后重新配置系统时钟 */
  /* 使能 HSE */   
  bRCC_CR_HSEON=1;
  
  /* 等待HSE时钟就绪 */
  while (!bRCC_CR_HSERDY){}
  
  /* 使能 PLL */
  bRCC_CR_PLLON=1;
  
  /* 等待直到 PLL 就绪 */
  while(!bRCC_CR_PLLRDY){}
  
  /* 把 PLL 作为系统时钟源 */     
  bRCC_CFGR_SW0=0;       //系统时钟选择SW0位复位   
  bRCC_CFGR_SW_PLL=1;    //系统时钟选择PLL(BIT1位)置1

  /* 等待,直到 PLL 被用作系统时钟源 */
  while (!bRCC_CFGR_SWS_PLL) //等待BIT3为1指示PLL已为钟源
   {}
}




//怎么样? 非常简洁!!!
//不仅程序简洁,方便书写,而且可读性也非常强。 如果再定义#define YES 1 或(和) #define ENABLE  1  的话,全函数如下:
static void SYSCLKConfig_STOP(void)
{  
  bRCC_CR_HSEON=YES;         
  while (! bRCC_CR_HSERDY);    bRCC_CR_PLLON=YES;         
  while(! bRCC_CR_PLLRDY);     
  bRCC_CFGR_SW_PLL=YES;         
  while (! bRCC_CFGR_SWS_PLL);     
}
//看起来就像自然描述语言一样,但却非常底层,远远超越了寄存器版本的高效。

使用特权

评论回复
26
characteristic|  楼主 | 2019-6-17 15:10 | 只看该作者
再比较一下,编译出来的目标代码,先看位操作版本:
   506:   bRCC_CR_HSEON=1;
   507:   
   508:   /* 等待HSE时钟就绪 */
0x08000D58 2001      MOVS     r0,#0x01
0x08000D5A 490F      LDR      r1,[pc,#60]  ; @0x08000D98
0x08000D5C 6408      STR      r0,[r1,#0x40]
   509:   while (!bRCC_CR_HSERDY){}
   510:   
   511:   /* 使能 PLL */
0x08000D5E BF00      NOP      
0x08000D60 480D      LDR      r0,[pc,#52]  ; @0x08000D98
0x08000D62 6C40      LDR      r0,[r0,#0x44]
0x08000D64 2800      CMP      r0,#0x00
0x08000D66 D0FB      BEQ      0x08000D60
   512:   bRCC_CR_PLLON=1;
   513:   
   514:   /* 等待直到 PLL 就绪 */
0x08000D68 2001      MOVS     r0,#0x01
0x08000D6A 490B      LDR      r1,[pc,#44]  ; @0x08000D98
0x08000D6C 6608      STR      r0,[r1,#0x60]
   515:   while(!bRCC_CR_PLLRDY){}
   516:   
   517:   /* 把 PLL 作为系统时钟源 */      
0x08000D6E BF00      NOP      
0x08000D70 4809      LDR      r0,[pc,#36]  ; @0x08000D98
0x08000D72 6E40      LDR      r0,[r0,#0x64]
0x08000D74 2800      CMP      r0,#0x00
0x08000D76 D0FB      BEQ      0x08000D70
   518:   bRCC_CFGR_SW0=0;       //系统时钟选择SW0位复位   
0x08000D78 2000      MOVS     r0,#0x00
0x08000D7A 4907      LDR      r1,[pc,#28]  ; @0x08000D98
0x08000D7C 3180      ADDS     r1,r1,#0x80
0x08000D7E 6008      STR      r0,[r1,#0x00]
   519:   bRCC_CFGR_SW_PLL=1;    //系统时钟选择PLL(BIT1位)置1
   520:  
   521:   /* 等待,直到 PLL 被用作系统时钟源 */
0x08000D80 2001      MOVS     r0,#0x01
0x08000D82 4905      LDR      r1,[pc,#20]  ; @0x08000D98
0x08000D84 F8C10084  STR      r0,[r1,#0x84]
   522:   while (!bRCC_CFGR_SWS_PLL) //BIT3为1指示PLL已为钟源
   523:    {} 0x08000D88 BF00      NOP      
0x08000D8A 4803      LDR      r0,[pc,#12]  ; @0x08000D98
0x08000D8C 308C      ADDS     r0,r0,#0x8C
0x08000D8E 6800      LDR      r0,[r0,#0x00]
0x08000D90 2800      CMP      r0,#0x00
0x08000D92 D0FA      BEQ      0x08000D8A
   524: }

使用特权

评论回复
27
characteristic|  楼主 | 2019-6-17 15:14 | 只看该作者
库函数版本的编译结果我就不贴了,
调用库函数,不停地压栈出栈,那效率就可想而知了。
只比较上述两种版本:
位操作版本:0x08000D58--0x08000D92
寄存器版本:0x08000D58--0x08000DAE
不到10行的源代码,编译出来的目标代码差了0xAE-0x92=0x1C
即寄存器版本多出来28个字节,
而且从目标代码来看,位操作直接精准地操作目标位,
而寄存器版本则,取出整个32位,进行AND/ORR逻辑运算,然后再写回结果,明显效率差了不少。

使用特权

评论回复
28
characteristic|  楼主 | 2019-6-17 15:15 | 只看该作者
意外的收获是发现使用位带操作寄存器可以使代码可阅读性更强,
有时候可以省略掉注释。
比如:原子的时钟初始化代码中有
while(!(RCC->CR>>17));                   //等待外部时钟就绪         //这种必须加注释,否则很难懂,时间长了自己也看不懂
用我上面介绍的位带操作则书写为: while(!bRCC_CR_HSERDY); 不加注释也可以明白

使用特权

评论回复
29
characteristic|  楼主 | 2019-6-17 15:15 | 只看该作者
不过,位带操作的局限性是如果同时给多个位赋值,就得分开操作各个位,这时候就不如直接操作寄存器本身了。
但是没有关系,可以采取操作单一比特时用位带,两个以上时仍读写整个寄存器的混合编程方式。
我试着对原子寄存器版本中的系统时钟初始化函数局部使用位带操作进行改写。
原版是:
void Stm32_Clock_Init(u8 PLL)
{
        unsigned char temp=0;   
        MYRCC_DeInit();                  //复位并配置向量表
        RCC->CR|=0x00010000;  //外部高速时钟使能HSEON
        while(!(RCC->CR>>17));//等待外部时钟就绪
        RCC->CFGR=0X00000400; //APB1=DIV2;APB2=DIV1;AHB=DIV1;
        PLL-=2;                                  //抵消2个单位(因为是从2开始的,设置0就是2)
        RCC->CFGR|=PLL<<18;   //设置PLL值 2~16
        RCC->CFGR|=1<<16;          //PLLSRC ON
        FLASH->ACR|=0x32;          //FLASH 2个延时周期
        RCC->CR|=0x01000000;  //PLLON
        while(!(RCC->CR>>25));//等待PLL锁定
        RCC->CFGR|=0x00000002;//PLL作为系统时钟         
        while(temp!=0x02)     //等待PLL作为系统时钟设置成功
        {   
                temp=RCC->CFGR>>2;
                temp&=0x03;
        }   
}

使用特权

评论回复
30
characteristic|  楼主 | 2019-6-17 15:15 | 只看该作者
改写后变成(已经测试通过):
void Stm32_Clock_Init(u8 PLL)
{
        MYRCC_DeInit();          //复位并配置向量表
        bRCC_CR_HSEON=1;   
        while(!bRCC_CR_HSERDY);
        RCC->CFGR=0X00000400; //APB1=DIV2;APB2=DIV1;AHB=DIV1;
        PLL-=2;                          //抵消2个单位(因为是从2开始的,设置0就是2)
        RCC->CFGR|=PLL<<18;   //设置PLL的倍频数 2~16
        bRCC_CFGR_PLLSRC=1;      
        FLASH->ACR|=0x32;           //FLASH 2个延时周期
        bRCC_CR_PLLON=1;           
        while(!bRCC_CR_PLLRDY);  
        bRCC_CFGR_SW_PLL=1;    //PLL作为系统时钟
        while(!bRCC_CFGR_SWS_PLL); //等待PLL作为系统时钟设置成功
}        

使用特权

评论回复
31
equivalent| | 2019-6-17 15:17 | 只看该作者
***************************************************************
在stm32f10x.h中,从第1267行开始,是外设存储器映射的宏定义
#define SRAM_BASE             ((uint32_t)0x20000000) /*!< SRAM base address in the alias region */
#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */

#define SRAM_BB_BASE          ((uint32_t)0x22000000) /*!< SRAM base address in the bit-band region */
#define PERIPH_BB_BASE        ((uint32_t)0x42000000) /*!< Peripheral base address in the bit-band region */
分别是SRAM区和片上外设起始地址和它们对应的别名区起始地址,不过看英文注释好像正好搞反了。

接下来从第1281行开始,是专门的片上外设基址宏定义,
它根据总线区的不同,分成了三段,分别是APB1段、APB2段、AHB段
#define APB1PERIPH_BASE       PERIPH_BASE
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)
然后是各个外设的具体基址,具体定义值是上述各段基址加段内偏移
#define TIM2_BASE             (APB1PERIPH_BASE + 0x0000)
#define TIM3_BASE             (APB1PERIPH_BASE + 0x0400)
#define TIM4_BASE             (APB1PERIPH_BASE + 0x0800)
......
#define AFIO_BASE             (APB2PERIPH_BASE + 0x0000)
#define EXTI_BASE             (APB2PERIPH_BASE + 0x0400)
#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)
......
#define DMA1_BASE             (AHBPERIPH_BASE + 0x0000)
#define DMA1_Channel1_BASE    (AHBPERIPH_BASE + 0x0008)
#define DMA1_Channel2_BASE    (AHBPERIPH_BASE + 0x001C)
#define DMA1_Channel3_BASE    (AHBPERIPH_BASE + 0x0030)
......
不管怎么定义,
反正可以通过XXXX_BASE最后得到各外设的基址

使用特权

评论回复
32
equivalent| | 2019-6-17 15:17 | 只看该作者
从第1463行开始就是各个外设寄存器的比特定义了:
对于具独立意义的比特定义采取了32位字中其它无关比特为0,自身所在比特为1的方式,如:
#define  RCC_CSR_LSIRDY                      ((uint32_t)0x00000002)
说明RCC外设的CSR寄存器中的比特位是第1位,而这样的定义正说明了STM官方的工程师们在编制库函数时,
完全放弃了位段操作的思想精髓,而捡起了用“与”、“或”操作来清位/置位的传统的、效率较低的方法。
唯一我可以为他们找到的理由是:因为有许多同时多比特操作的场合,所以二者同时兼顾起来有困难。
不管他了,我们要想定义位别名,就可以借用这个名称定义,并加一个前缀b,用来表示位别名。
即#define  bRCC_CSR_LSIRDY     BIT_ADDR(RCC_BASE+n, 1)  
括号中的1为比特位号,表示该比特在寄存器CSR中的第1比特,
RCC_BASE就是RCC的基址,直接借用就可以了。
还缺少一个数字,就是n, 这个n是某个特定寄存器在其外设基址基础之上的偏移量,
我没有在文件中找到,但偏移量以及比特位都可以在参考手册上查到。

使用特权

评论回复
33
equivalent| | 2019-6-17 15:18 | 只看该作者
还有一个需要统一约定的问题,那就是有些同类的外设可能有多个,但它们的寄存器名称及位定义是一致的。
如多个GPIO、多个TIM、多个DMA通道、多个SPI等等,则需要在宏定义名称中增加我们习惯的数字或字母以示区分,
如bGPIOE_ODR_ODR0、bSPI1_SR_BSY、bTIM2_SMCR_ECE等,它们各自的区别是基址不同,如TIM2_BASE、TIM3_BASE等等。

使用特权

评论回复
34
equivalent| | 2019-6-17 15:18 | 只看该作者
实际应用时,可以选择那些比较重要,操作比较频繁的位来优先进行定义和使用,
为了便于理解和**,也可以完全自己编制宏名称,如:bRCC_CR_HSIRDY,直接定义成bHSI_Ready
不过,为了查找来龙去脉,可以通过宏定义加壳的方式二次定义,#define  bHSI_Ready  bRCC_CSR_LSIRDY

使用特权

评论回复
35
equivalent| | 2019-6-17 15:18 | 只看该作者
先看电源控制寄存器,位操作宏定义如下:
//PWR_CR--电源控制寄存器
#define  bPWR_CR_LPDS          BIT_ADDR(PWR_BASE, 0)     //深睡眠下的低功耗(PDDS=0时,与PDDS位协同操作)定义:0(在待机模式下电压调压器开启),1(在待机模式下电压调压器处于低功耗模式)
#define  bPWR_CR_PDDS          BIT_ADDR(PWR_BASE, 1)     //掉电深睡眠(与LPDS位协同操作)定义:0(当CPU进入深睡眠时进入停机模式,调压器状态由LPDS位控制),1(CPU进入深睡眠时进入待机模式)
#define  bPWR_CR_CWUF          BIT_ADDR(PWR_BASE, 2)     //清除唤醒位(始终输出为0)定义:0(无效),1(2个系统时钟周期后清除WUF唤醒位(写)
#define  bPWR_CR_CSBF          BIT_ADDR(PWR_BASE, 3)     //清除待机位(始终输出为0)定义:0(无效),1(清除SBF待机位(写)
#define  bPWR_CR_PVDE          BIT_ADDR(PWR_BASE, 4)     //电源电压检测器(PVD)使能。定义:0(禁止PVD),1(开启PVD)
#define  bPWR_CR_DBP           BIT_ADDR(PWR_BASE, 8)     //取消后备区域写保护。复位值为0。定义:0为禁止写入,1为允许写入。注:如果rtc时钟是HSE/128,必须保持为1

//#define  PWR_CR_PLS  以下3BIT定义PVD电压阀值
#define  bPWR_CR_PLS_0         BIT_ADDR(PWR_BASE, 5)     //定义:  000(2.2v),001(2.3v),010(2.4v)
#define  bPWR_CR_PLS_1         BIT_ADDR(PWR_BASE, 6)     //011(2.5v),100(2.6v),101(2.7v)
#define  bPWR_CR_PLS_2         BIT_ADDR(PWR_BASE, 7)     //110(2.8v),111(2.9v)



//PWR_CSR--电源控制状态寄存器
#define  bPWR_CSR_WUF           BIT_ADDR(PWR_BASE+4, 0)     //唤醒标志(该位由硬件设置,并只能由POR/PDR(上电/掉电复位)或设置电源控制寄存器(PWR_CR)的CWUF位清除)
                                                       //定义:0(没有唤醒事件),1(在WKUP引脚上发生唤醒事件或出现RTC闹钟事件)
                                                       //注:当WKUP引脚已经是高电平时,在(通过设置EWUP位)使能WKUP引脚时,会检测到一个额外事件
#define  bPWR_CSR_SBF           BIT_ADDR(PWR_BASE+4, 1)     //待机标志位(该位由硬件设置,并只能由POR/PDR(上电/掉电复位)或设置电源控制寄存器(PWR_CR)的CSBUF位清除)定义:0(不在待机)1(已待机)
#define  bPWR_CSR_PVDO          BIT_ADDR(PWR_BASE+4, 2)     //PVDO-PVD输出(当PVD被PVDE位使能后该位才有效)定义:0(VDD/VDDA高于PLS[2-0]选定的PVD阀值),1(VDD/VDDA低于PLS[2-0]选定的PVD阀值)
                                                       //注:在待机模式下PVD被停止,因此,待机模式后或复位后,直到设置PVDE位之前,该位为0
#define  bPWR_CSR_EWUP          BIT_ADDR(PWR_BASE+4, 8)     //EWUP使能WKUP引脚。定义:0(WKUP为通用IO),1(用于待机唤醒模式,WKUP引脚被强置为输入下拉的配置(WKUP引脚上的上升沿将系统从待机模式唤醒)

使用特权

评论回复
36
equivalent| | 2019-6-17 15:19 | 只看该作者
这样就可以把所有有关电源的部分都搞定了,
从这里我们也可以理解STM官方设计库函数的工程师们的苦衷,
寄存器中有不少是需要多位一起操作的,
比如上面用连续3BIT位来定义PVD电压阀值,
这时候位操作就有些哆嗦了,用位操作需要操作3次,
这样就不如用传统的读--修改--写的操作了。
为了统一风格,索性就完全舍弃位操作了。

使用特权

评论回复
37
equivalent| | 2019-6-17 15:19 | 只看该作者
用位操作风格改写原子的寄存器版本SYS.C中的进入待机模式函数:
原子代码为:
//进入待机模式         
void Sys_Standby(void)
{
        SCB->SCR|=1<<2;//使能SLEEPDEEP位 (SYS->CTRL)           
  RCC->APB1ENR|=1<<28;     //使能电源时钟            
         PWR->CSR|=1<<8;          //设置WKUP用于唤醒
        PWR->CR|=1<<2;           //清除Wake-up 标志
        PWR->CR|=1<<1;           //PDDS置位                  
        WFI_SET();                                 //执行WFI指令                 
}

可以改写成:
void Sys_Standby(void)
{
        SCB->SCR|=1<<2;//使能SLEEPDEEP位 (SYS->CTRL)           
  bRCC_ENABLE_PWR=1;        //使能电源时钟            
         bPWR_CSR_EWUP=1;          //设置WKUP用于唤醒
        bPWR_CR_CWUF=1;           //清除Wake-up 标志
        bPWR_CR_PDDS=1;           //PDDS置位                  
        WFI_SET();                                 //执行WFI指令                 
}
注意第一句SCB->SCR|=1<<2;没有改写成位操作,因为有几个系统寄存器并不在位绑定区。

使用特权

评论回复
38
equivalent| | 2019-6-17 15:19 | 只看该作者

/******************************************************************************/
/*                                                                            */
/*                    EXIT寄存器--外部中断/事件控制器(n=0-18)                  */
/*                                                                            */
/******************************************************************************/

#define  bEXTI_INT_MASK(n)                                                                        BIT_ADDR(EXTI_BASE, n)    //中断屏蔽:0屏蔽,1不屏蔽
#define  bEXTI_EVT_MASK(n)                                                                        BIT_ADDR(EXTI_BASE+4, n)  //事件屏蔽:0屏蔽,1不屏蔽
#define  bEXTI_TRIG_RISE(n)                                                                        BIT_ADDR(EXTI_BASE+8, n)  //上升沿触发:0禁用,1使能,可与下降沿触发共存
#define  bEXTI_TRIG_FALL(n)                                                                        BIT_ADDR(EXTI_BASE+12, n) //下降沿触发:0禁用,1使能,可与上升沿触发共存
#define  bEXTI_SFT_RQST(n)                                                                  BIT_ADDR(EXTI_BASE+16, n) //软中断请求,写1产生中断挂起,向下面的PR相关位写1则清本位
#define  bEXTI_INT_PENDING(n)                                                                BIT_ADDR(EXTI_BASE+20, n) //中断挂起,硬件置1,写1清0

使用特权

评论回复
39
equivalent| | 2019-6-17 15:19 | 只看该作者
看起来EXIT寄存器是最适合使用位操作的,
因为它的全部6个寄存器都是按位来表达的。
又由于排列工整而有规律,可以用带参数的宏定义来表达。
注意,这里我对命名进行了些许调整,
使得其更便于**和理解。

使用特权

评论回复
40
equivalent| | 2019-6-17 15:20 | 只看该作者
补记一下,其实stm32f10x.h是一个非常重要的头文件,即使是你使用寄存器来编程序,也是必须依赖它的,
因为它除了上面说的定义了外设的基址外,
它还把各个外设所包含的所有寄存器都封装成了一个个的结构体,
即:每个外设的各种寄存器封装成一个结构体而且这个结构体不是随便封装的,
它是按照寄存器的地址严格排列的,即它暗藏了各个寄存器的地址偏移量。

比如EXTI外部中断寄存器,封装如下:
typedef struct
{
  __IO uint32_t IMR;
  __IO uint32_t EMR;
  __IO uint32_t RTSR;
  __IO uint32_t FTSR;
  __IO uint32_t SWIER;
  __IO uint32_t PR;
} EXTI_TypeDef;
如果把IMR和EMR互换一下位置行不行?

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则