OpenEdv-开源电子网

 找回密码
 立即注册

扫一扫,访问微社区

正点原子全套STM32开发资料,上千讲STM32视频教程,RT1052教程免费下载啦...

12
返回列表 发新帖
楼主: warship

我对STM32所用位带操作宏的超详细剖析、优势分析及应用推广探索研究(持续更新,欢迎讨论交流)

[复制链接]

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 2018-7-11 18:05:40 | 显示全部楼层
正点原子公众号
先看电源控制寄存器,位操作宏定义如下:
//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引脚上的上升沿将系统从待机模式唤醒)

回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 2018-7-11 18:13:31 | 显示全部楼层
这样就可以把所有有关电源的部分都搞定了,
从这里我们也可以理解STM官方设计库函数的工程师们的苦衷,
寄存器中有不少是需要多位一起操作的,
比如上面用连续3BIT位来定义PVD电压阀值,
这时候位操作就有些哆嗦了,用位操作需要操作3次,
这样就不如用传统的读--修改--写的操作了。
为了统一风格,索性就完全舍弃位操作了。
回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 2018-7-11 18:16:46 | 显示全部楼层
本帖最后由 warship 于 2018-7-11 18:25 编辑

用位操作风格改写原子的寄存器版本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;没有改写成位操作,因为有几个系统寄存器并不在位绑定区。               


回复 支持 反对

使用道具 举报

  离线 

29

主题

259

帖子

0

精华

高级会员

Rank: 4

积分
808
金钱
808
注册时间
2012-3-30
在线时间
248 小时
发表于 2018-7-11 19:33:47 | 显示全部楼层
一看就是好东西
回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 2018-7-12 20:00:58 | 显示全部楼层
正点原子公众号
czhaii 发表于 2018-7-11 19:33
一看就是好东西

感谢支持
回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 2018-7-12 20:01:41 | 显示全部楼层
/******************************************************************************/
/*                                                                            */
/*                    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
回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 2018-7-12 20:05:22 | 显示全部楼层
看起来EXIT寄存器是最适合使用位操作的,
因为它的全部6个寄存器都是按位来表达的。
又由于排列工整而有规律,可以用带参数的宏定义来表达。
注意,这里我对命名进行了些许调整,
使得其更便于记忆和理解。
回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 2018-7-13 09:51:35 | 显示全部楼层
本帖最后由 warship 于 2018-7-13 09:53 编辑

补记一下,其实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互换一下位置行不行?


回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 2018-7-13 16:03:37 | 显示全部楼层
本帖最后由 warship 于 2018-7-13 16:15 编辑

反正都在一个结构体内,按名称访问不就行了吗?
正常的C程序是可以的,因为我都是按成员名称赋值,再按成员名称读取,不会有任何问题。
但这里是和硬件息息相关的,有时候某些成员并不是你去赋值你去读,而是由硬件赋值的。
而硬件是按物理地址赋值的(对于硬件来说,这些所谓的结构体呀、成员什么的都一概不知!!),
互换位置之后,硬件给甲寄存器赋的值被你当成乙寄存器的值来读,
那不是彻底乱套了吗?
所以,这里的顺序是相当重要的。中间如果有保留的地址空间都需要如实地填充,必须确保各个
寄存器在外设基址之上的偏移量是正确的。



回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 2018-7-13 16:07:19 | 显示全部楼层
看了下面的情况,你就对此更加深理解了,从1380行开始,有类似如下的宏定义:
#define TIM2                ((TIM_TypeDef *) TIM2_BASE)
#define TIM3                ((TIM_TypeDef *) TIM3_BASE)
#define TIM4                ((TIM_TypeDef *) TIM4_BASE)
......
#define EXTI                ((EXTI_TypeDef *) EXTI_BASE)
这个是什么意思?  
前面所定义的外设基址,如TIM2_BASE并不是地址,只是一个具体的32位常数。
这里的定义把这个常数用强制类型转换的方式使其变成了真正的地址---指向结构体变量的指针
这样,外设TIMER2的寄存器族就与具体的物理地址联系起来,
它在结构体中按顺序排列使寄存器与其真正的物理地址完全对应起来了。
这样,EXTI->IMR就能够实打实地访问到真正的中断屏蔽寄存器了。
(甚至我们都不需要申明一个结构体的实体变量,这是因为只要有具体的地址,就相当有了实体
从指定地址按结构体的成员组成及类型去读取就可以,这就是顺序必须严格的原因)
以上这些显然就是寄存器版本编程的基础,所以寄存器版本编程必须#include "stm32f10x.h"

总之,要牢记的是,有了stm32f10x.h这个头文件,我们就可以通过“外设名->寄存器名”的方式对所有寄存器进行访问了。
回复 支持 反对

使用道具 举报

  离线 

1

主题

7

帖子

0

精华

新手上路

积分
35
金钱
35
注册时间
2016-2-19
在线时间
3 小时
发表于 7 天前 | 显示全部楼层
你好,感谢分享
有一个 u16 d[500]的数据,按字读写是这样 d[x]=0;  这样
但时,有时要用读写中间的一位,我就不知道要怎么搞了

回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 7 天前 | 显示全部楼层
本帖最后由 warship 于 2018-7-16 09:52 编辑
qq280502071 发表于 2018-7-16 01:17
你好,感谢分享
有一个 u16 d[500]的数据,按字读写是这样 d[x]=0;  这样
但时,有时要用读写中间的一位 ...

#define MyVAR_Addr    0x2000A100   //选择SRAM区的一个固定地址
//定义一个全局变量数组, 分配于上述固定地址
volatile u32 d[500] __attribute__((at(MyVAR_Addr)));

#define MyVAR_BIT(m,n) MEM_ADDR(BITBAND(MyVAR_Addr+m, n))  

这样,代码中就可以方便地进行位操作了,
比如:MyVAR_BIT(70,3)=1;就可以对d[70]的第3位置1

回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 7 天前 | 显示全部楼层
楼上给出的是32位数组,对于16位,可仿此改造一下。
这么大的数组不建议频繁地去操作位。
如果真有需要频繁地去对不同的位进行操作才这么做。
如果每一个数据都会频繁地对不同的位进行操作,
还可以有一种折中的做法就是按上文方法只定义一个位段操作字,
先将d[x]存入该字,进行完位操作后再写回去。
回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 7 天前 | 显示全部楼层
本帖最后由 warship 于 2018-7-16 19:46 编辑

RCC有关寄存器的位操作:
RCC是复位与时钟控制,是系统比较重要的寄存器,
它基本所有的寄存器都比较适合采用位操作。
这里的宏定义我采取了忽略寄存器名称,而使用归纳用途来定义的方法,
其实各个外设之所以有不同的寄存器名称,一是为了区分不同的寄存器,二是为了编程寻址的方便。
但是有一个缺点就是,如果类似的BIT位比较多,就得分成多个不同的寄存器,
于是我们就见到了CR1、CR2、CR3之类的,这些其实是比较麻烦的,
为了查某个控制位,还得去查这个位在哪个控制器。
还有我们大家容易犯的错误就是:因为STM32对总线按速度进行了分区,有AHB、APBH1、APBH2之类的,
由于SPI1和SPI2在不同的分区,我们经常因为一时忽略了总线区域的变化而配错,
这种错误往往比较隐蔽,以为自己开启了SPI1的时钟而其实没有,造成外设不工作。
采用位操作,我们在定义宏的时候,就可以小封装一下,将这些细节封装成统一的形式,
开启时钟的时候,只须使用bRCC_ENABLE_SPI1或者bRCC_ENABLE_SPI2,而不能去查它们究竟在哪个总线区域,
由哪个寄存器的比特位来设置。
其它类似的按功能区分的有:RESET(复位)、CONFIG(配置)、CLK(系统时钟)、FLG(标志位)、INT(中断位)等,
具体可看楼下的定义:


回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 7 天前 | 显示全部楼层
/******************************************************************************/
/*                                                                            */
/*                         RCC寄存器--复位和时钟控制                           */
/*                                                                            */
/******************************************************************************/
//typedef struct
//{
//  __IO uint32_t CR;
//  __IO uint32_t CFGR;
//  __IO uint32_t CIR;
//  __IO uint32_t APB2RSTR;
//  __IO uint32_t APB1RSTR;
//  __IO uint32_t AHBENR;
//  __IO uint32_t APB2ENR;
//  __IO uint32_t APB1ENR;
//  __IO uint32_t BDCR;
//  __IO uint32_t CSR;

//#ifdef STM32F10X_CL  
//  __IO uint32_t AHBRSTR;
//  __IO uint32_t CFGR2;
//#endif /* STM32F10X_CL */

//#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)   
//  uint32_t RESERVED0;
//  __IO uint32_t CFGR2;
//#endif /* STM32F10X_LD_VL || STM32F10X_MD_VL || STM32F10X_HD_VL */
//} RCC_TypeDef;

//RCC_CR--时钟控制寄存器
#define  bRCC_CLK_HSION        BIT_ADDR(RCC_BASE, 0) //LSI时钟: 0禁用,1开启
#define  bRCC_CLK_HSIRDY        BIT_ADDR(RCC_BASE, 1) //LSI时钟状态由硬件控制(只读):0不可用,1就绪
#define  bRCC_CLK_HSEON        BIT_ADDR(RCC_BASE, 16) //HSE时钟: 0禁用,1开启
#define  bRCC_CLK_HSERDY        BIT_ADDR(RCC_BASE, 17) //HSE时钟状态由硬件控制(只读):0不可用,1就绪
#define  bRCC_CLK_HSEBYP       BIT_ADDR(RCC_BASE, 18) //外部时钟旁路(调试用)-- 0不旁路  1旁路
#define  bRCC_CLK_CSSON     BIT_ADDR(RCC_BASE, 19) //系统时钟安全系统使能  0时钟检测禁用  1外部时钟就绪后启动检测
#define  bRCC_CLK_PLLON       BIT_ADDR(RCC_BASE, 24) //PLL倍频: 0禁用,1开启
#define  bRCC_CLK_PLLRDY      BIT_ADDR(RCC_BASE, 25) //PLL倍频状态由硬件控制(只读):0不可用,1就绪
//本寄存器还有5BIT的HSITRIM  内部高速时钟调整
//           8BIT的HSICAL  内部高速时钟校准  用于补偿因温度等变化对内部RC振荡器时钟频率的影响.

//RCC_CFGR--时钟配置寄存器
#define  bRCC_CONFIG_SW0        BIT_ADDR(RCC_BASE+0x04, 0) //系统时钟选择2BIT-- 00:HSI  01:HSE
#define  bRCC_CONFIG_SW1        BIT_ADDR(RCC_BASE+0x04, 1) //                   10LL  11: 无效
#define  bRCC_CONFIG_SW_PLL           BIT_ADDR(RCC_BASE+0x04, 1)  //SYSCLK时钟选择位1,置位时为选择PLL作为系统时钟源
#define  bRCC_CONFIG_SWS0        BIT_ADDR(RCC_BASE+0x04, 2) //系统时钟源指示,只读: 定义同上
#define  bRCC_CONFIG_SWS1        BIT_ADDR(RCC_BASE+0x04, 3) //
#define  bRCC_CONFIG_SWS_PLL                   BIT_ADDR(RCC_BASE+0x04, 3)  //SYSCLK时钟指示位1,为1时指示PLL已经为系统时钟源
#define  bRCC_CONFIG_PLLSRC       BIT_ADDR(RCC_BASE+0x04, 16) //PLL钟源选择, 0: HSI/2  1:HSE(PREDIV1的输出)
#define  bRCC_CONFIG_PLLXTPRE     BIT_ADDR(RCC_BASE+0x04, 17) //输出至PLL的HSE是否分频 0:不分频  1:二分频
#define  bRCC_CONFIG_USBPRE       BIT_ADDR(RCC_BASE+0x04, 22) //USB预分频控制 0: PLL/1.5   1: PLL
#define  bRCC_CONFIG_ADCPRE0      BIT_ADDR(RCC_BASE+0x04, 14) //ADC(对PLCK2)预分频2BIT控制: 00: 2分频  01: 4分频
#define  bRCC_CONFIG_ADCPRE1      BIT_ADDR(RCC_BASE+0x04, 15) //                           10: 6分频  11: 8分频
//本寄存器还有4BIT的HPRE控制位: 控制系统时钟SYSCLK分频至AHB
//           3BIT的PPRE1控制位: 控制至APB1的预分频
//           3BIT的PPRE2控制位: 控制至APB2的预分频
//           4BIT的PLLMUL:选择PLL的倍频数,2-16
//           3BIT的MCO控制位: 对MCU输出时钟的选择进行控制

//RCC_CIR--时钟中断寄存器
#define  bRCC_INT_LSIRDYFLG      BIT_ADDR(RCC_BASE+0x08, 0) //LSI稳定且对应IE置位时由硬件置位,只读0无效,1可用
#define  bRCC_INT_LSERDYFLG      BIT_ADDR(RCC_BASE+0x08, 1) //LSE稳定且对应IE置位时由硬件置位,只读0无效,1可用
#define  bRCC_INT_HSIRDYFLG      BIT_ADDR(RCC_BASE+0x08, 2) //HSI稳定且对应IE置位时由硬件置位,只读0无效,1可用
#define  bRCC_INT_HSERDYFLG      BIT_ADDR(RCC_BASE+0x08, 3) //HSE稳定且对应IE置位时由硬件置位,只读0无效,1可用
#define  bRCC_INT_PLLRDYFLG      BIT_ADDR(RCC_BASE+0x08, 4) //PLL锁定且对应IE置位时由硬件置位,只读0无效,1可用
#define  bRCC_INT_CSSF           BIT_ADDR(RCC_BASE+0x08, 7) //外部振荡器失效时由硬件置位,只读0:无效,1中断可用
#define  bRCC_INT_LSIRDYIE           BIT_ADDR(RCC_BASE+0x08, 8) //LSI可用   中断使能, 0禁用  1使能
#define  bRCC_INT_LSERDYIE           BIT_ADDR(RCC_BASE+0x08, 9) //下同
#define  bRCC_INT_HSIRDYIE           BIT_ADDR(RCC_BASE+0x08, 10)
#define  bRCC_INT_HSERDYIE           BIT_ADDR(RCC_BASE+0x08, 11)
#define  bRCC_INT_PLLRDYIE           BIT_ADDR(RCC_BASE+0x08, 12)
#define  bRCC_INT_LSIRDYCLR      BIT_ADDR(RCC_BASE+0x08, 16) //写1以清零相应的LSIRDYF,下同
#define  bRCC_INT_LSERDYCLR      BIT_ADDR(RCC_BASE+0x08, 17)
#define  bRCC_INT_HSIRDYCLR      BIT_ADDR(RCC_BASE+0x08, 18)
#define  bRCC_INT_HSERDYCLR      BIT_ADDR(RCC_BASE+0x08, 19)
#define  bRCC_INT_PLLRDYCLR      BIT_ADDR(RCC_BASE+0x08, 20)
#define  bRCC_INT_CSSCLR         BIT_ADDR(RCC_BASE+0x08, 23)

//RCC_APB2RSTR寄存器
#define  bRCC_RESET_AFIO                         BIT_ADDR(RCC_BASE+0x0C, 0)   //0无效,1复位,下同
#define  bRCC_RESET_GPIOA                 BIT_ADDR(RCC_BASE+0x0C, 2)
#define  bRCC_RESET_GPIOB                 BIT_ADDR(RCC_BASE+0x0C, 3)
#define  bRCC_RESET_GPIOC                 BIT_ADDR(RCC_BASE+0x0C, 4)
#define  bRCC_RESET_GPIOD                 BIT_ADDR(RCC_BASE+0x0C, 5)
#define  bRCC_RESET_GPIOE                 BIT_ADDR(RCC_BASE+0x0C, 6)
#define  bRCC_RESET_ADC1                         BIT_ADDR(RCC_BASE+0x0C, 9)
#define  bRCC_RESET_ADC2                         BIT_ADDR(RCC_BASE+0x0C, 10)
#define  bRCC_RESET_TIM1                         BIT_ADDR(RCC_BASE+0x0C, 11)
#define  bRCC_RESET_SPI1                         BIT_ADDR(RCC_BASE+0x0C, 12)
#define  bRCC_RESET_USART1                       BIT_ADDR(RCC_BASE+0x0C, 14)
//RCC_APB1RSTR寄存器
#define  bRCC_RESET_TIM2                 BIT_ADDR(RCC_BASE+0x10, 0)   //0无效,1复位,下同
#define  bRCC_RESET_TIM3                 BIT_ADDR(RCC_BASE+0x10, 1)
#define  bRCC_RESET_TIM4                 BIT_ADDR(RCC_BASE+0x10, 2)
#define  bRCC_RESET_WWDG                 BIT_ADDR(RCC_BASE+0x10, 11)
#define  bRCC_RESET_SPI2                 BIT_ADDR(RCC_BASE+0x10, 14)
#define  bRCC_RESET_USART2               BIT_ADDR(RCC_BASE+0x10, 17)
#define  bRCC_RESET_USART3               BIT_ADDR(RCC_BASE+0x10, 18)
#define  bRCC_RESET_I2C1                 BIT_ADDR(RCC_BASE+0x10, 21)
#define  bRCC_RESET_I2C2                 BIT_ADDR(RCC_BASE+0x10, 22)
#define  bRCC_RESET_USB                  BIT_ADDR(RCC_BASE+0x10, 23)
#define  bRCC_RESET_CAN                        BIT_ADDR(RCC_BASE+0x10, 25)
#define  bRCC_RESET_BKP                  BIT_ADDR(RCC_BASE+0x10, 27)
#define  bRCC_RESET_PWR                  BIT_ADDR(RCC_BASE+0x10, 28)
//RCC_AHBEN寄存器
#define  bRCC_ENABLE_DMA                   BIT_ADDR(RCC_BASE+0x14, 0)  //0关闭时钟,1开启时钟,下同
#define  bRCC_ENABLE_SRAM                  BIT_ADDR(RCC_BASE+0x14, 2)
#define  bRCC_ENABLE_FLITF                 BIT_ADDR(RCC_BASE+0x14, 4)
#define  bRCC_ENABLE_CRC                          BIT_ADDR(RCC_BASE+0x14, 6)
//RCC_APB2ENR寄存器
#define  bRCC_ENABLE_AFIO                 BIT_ADDR(RCC_BASE+0x18, 0)   //0关闭时钟,1开启时钟,下同
#define  bRCC_ENABLE_GPIOA                 BIT_ADDR(RCC_BASE+0x18, 2)
#define  bRCC_ENABLE_GPIOB                BIT_ADDR(RCC_BASE+0x18, 3)
#define  bRCC_ENABLE_GPIOC                BIT_ADDR(RCC_BASE+0x18, 4)
#define  bRCC_ENABLE_GPIOD                 BIT_ADDR(RCC_BASE+0x18, 5)
#define  bRCC_ENABLE_GPIOE                 BIT_ADDR(RCC_BASE+0x18, 6)
#define  bRCC_ENABLE_ADC1                 BIT_ADDR(RCC_BASE+0x18, 9)
#define  bRCC_ENABLE_ADC2                 BIT_ADDR(RCC_BASE+0x18, 10)
#define  bRCC_ENABLE_TIM1                 BIT_ADDR(RCC_BASE+0x18, 11)
#define  bRCC_ENABLE_SPI1                 BIT_ADDR(RCC_BASE+0x18, 12)
#define  bRCC_ENABLE_USART1               BIT_ADDR(RCC_BASE+0x18, 14)
//RCC_APB1ENR寄存器
#define  bRCC_ENABLE_TIM2                 BIT_ADDR(RCC_BASE+0x1C, 0)   //0关闭时钟,1开启时钟,下同
#define  bRCC_ENABLE_TIM3                 BIT_ADDR(RCC_BASE+0x1C, 1)
#define  bRCC_ENABLE_TIM4                 BIT_ADDR(RCC_BASE+0x1C, 2)
#define  bRCC_ENABLE_WWDG                 BIT_ADDR(RCC_BASE+0x1C, 11)
#define  bRCC_ENABLE_SPI2                 BIT_ADDR(RCC_BASE+0x1C, 14)
#define  bRCC_ENABLE_USART2               BIT_ADDR(RCC_BASE+0x1C, 17)
#define  bRCC_ENABLE_USART3               BIT_ADDR(RCC_BASE+0x1C, 18)
#define  bRCC_ENABLE_I2C1                 BIT_ADDR(RCC_BASE+0x1C, 21)
#define  bRCC_ENABLE_I2C2                 BIT_ADDR(RCC_BASE+0x1C, 22)
#define  bRCC_ENABLE_USB                  BIT_ADDR(RCC_BASE+0x1C, 23)
#define  bRCC_ENABLE_CAN                         BIT_ADDR(RCC_BASE+0x1C, 25)
#define  bRCC_ENABLE_BKP                  BIT_ADDR(RCC_BASE+0x1C, 27)
#define  bRCC_ENABLE_PWR                  BIT_ADDR(RCC_BASE+0x1C, 28)
//RCC_BDCR寄存器--备份区域控制
#define  bRCC_CLK_LSEON                  BIT_ADDR(RCC_BASE+0x20, 0)  //LSE时钟: 0禁用,1开启
#define  bRCC_CLK_LSERDY                 BIT_ADDR(RCC_BASE+0x20, 1) //LSE时钟状态由硬件控制(只读):0不可用,1就绪
#define  bRCC_CLK_RTCSEL0                BIT_ADDR(RCC_BASE+0x20, 24) //RTC时钟源选择两位共同控制: 00:无时钟 01SE
#define  bRCC_CLK_RTCSEL1                BIT_ADDR(RCC_BASE+0x20, 26) //                      10SI   11: HSE/128
#define  bRCC_ENABLE_RTC                 BIT_ADDR(RCC_BASE+0x20, 27)  //0禁用RTC; 1:使能RTC
#define  bRCC_RESET_BKUPDOMAIN           BIT_ADDR(RCC_BASE+0x20, 28)   //备份区域软复位  写1复位 0复位未被激活

//RCC_CSR寄存器--状态与控制
#define  bRCC_CLK_LSION                   BIT_ADDR(RCC_BASE+0x24, 0)   //LSI时钟: 0禁用,1开启
#define  bRCC_CLK_LSIRDY                  BIT_ADDR(RCC_BASE+0x24, 1)   //LSI时钟状态由硬件控制(只读):0不可用,1就绪
#define  bRCC_FLG_RMVF                    BIT_ADDR(RCC_BASE+0x24, 24) //清除复位标志 写0复位未激活的复位标志,写1则清复位标志
#define  bRCC_FLG_PINRSTF                 BIT_ADDR(RCC_BASE+0x24, 26)  //引脚复位标志  硬件置1软件写RMVF位清0
#define  bRCC_FLG_PORRSTF                 BIT_ADDR(RCC_BASE+0x24, 27)  //端口复位标志  硬件置1软件写RMVF位清0
#define  bRCC_FLG_SFTRSTF                 BIT_ADDR(RCC_BASE+0x24, 28)   //软件复位标志  硬件置1软件写RMVF位清0
#define  bRCC_FLG_IWDGRSTF                BIT_ADDR(RCC_BASE+0x24, 29)  //独立看门狗复位标志  硬件置1软件写RMVF位清0
#define  bRCC_FLG_WWDGRSTF                BIT_ADDR(RCC_BASE+0x24, 30)//窗口看门狗复位标志  硬件置1软件写RMVF位清0
#define  bRCC_FLG_LPWRRSTF                BIT_ADDR(RCC_BASE+0x24, 31)//低功耗管理复位标志  硬件置1软件写RMVF位清0
回复 支持 反对

使用道具 举报

  离线 

1

主题

303

帖子

0

精华

高级会员

Rank: 4

积分
733
金钱
733
注册时间
2015-6-11
在线时间
130 小时
发表于 7 天前 | 显示全部楼层
本帖最后由 zc123 于 2018-7-16 21:10 编辑

其实c语言的位域比位带操作更直观些,也更符合寄存器的位操作理解,不过这代表更大的工作量,所以现在的库设计不如说是比较中庸但通用的设计                        
typedef union
{
    struct
    {
        uint32_t HSION:1;
        uint32_t HSIRDY:1;
        //省略 中间27
        uint32_t reserved:3;
    }bit;
    uint32_t all;
}UNION_RCC_CR;
typedef struct
{
  __IO UNION_RCC_CR CR;
}RCC_TypeDef;

RCC_TypeDef  *RCC;
访问全部用
RCC->CR.all
访问单个位用
RCC->CR.bit.HSION
无论1bit, 2bit还是更多都完全兼容,不过整个下来工作量会大一堆
另外位带是由内核也就ARM定义的M3自带的功能,但实际应用起来其实很鸡肋,对于SRAM区域,位操作用仅适用于单bit操作,可是比起&,|也并没有太大的性能优势,多bit操作就完全不用考虑,对于寄存器来说也差不多,一方面大幅度增加了代码量(宏的代码量远高于结构体),一方面降低了可读性,所以库中基本不会考虑用位带的,一般要不然为了代码精简(提供完善的库), 用户修改寄存器的需求就自己去查数据手册,要不然就用上面的位域,可以大幅度提高代码的可读性,但带来的代码量膨胀也不可避免的

回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 7 天前 | 显示全部楼层
本帖最后由 warship 于 2018-7-17 11:01 编辑
zc123 发表于 2018-7-16 20:55
其实c语言的位域比位带操作更直观些,也更符合寄存器的位操作理解,不过这代表更大的工作量,所以现在的库 ...

多谢指教,
这种联合及结构体定义也可操作位域,但工作量确实不小,比我上面的要大得多。
你说得很对,在实际编制位带宏定义的时候我也感觉到库函数舍弃位带操作的苦衷,
位带操作对同时操作多位多有不便,也难以与现有库函数形成统一的风格。
其实我也是好玩,对一些重要的适合位带操作的位定义一下,以用起来方便为原则,
这个完全是附加的,只须包含一个宏定义头,而这个宏定义头中,没有申明任何新的东西,就可以与库函数及寄存器混合编程。
你说的大幅度增加代码量,其实只是一个宏定义头,库函数光是在stm32f10x.h中定义位操作的位域值就比我这个宏定义头要多。
且不用说,为了封装成初始化赋值结构在各个分外设的头文件中为位段值所定义的枚举宏,
至于为了实现库函数的实体代码就更是汗牛充栋了,当然,最终也毫无疑问地提高了用库函数编程的易用性和代码的可读性。
其实我感觉,大多数的位操作如果设计一套助记符作为宏,程序的可读性也会比较强,
只不过,这种东西的封装程度及规范性比库函数要差一些,并且限于多位操作的情况不好处理,也就只能这样了。

这样,在不增加任何代价(只须include我定义的宏)的情况下就可以既提高代码效率,又增强可读性,

比如:一句bRCC_ENABLE_SPI2=1;就可以打开SPI2外设的时钟,而不用管它是挂在哪个总线上的。
库函数版本要用 RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
寄存器版本要用 RCC->APB1ENR|=1<<14;
显然是有兼具提高代码效率和增强可读性的效果,
这,就是我想做这项工作的初心。


回复 支持 反对

使用道具 举报

  离线 

53

主题

434

帖子

0

精华

高级会员

Rank: 4

积分
868
金钱
868
注册时间
2014-3-7
在线时间
165 小时
发表于 6 天前 | 显示全部楼层
本帖最后由 ssssssssssss 于 2018-7-17 09:32 编辑
warship 发表于 2018-7-7 08:28
STM32内部的位段操作机理的实质是:把对别名区的地址的读写--〉映射成对相应的位的读写
而不是反过来( ...

1.其实我现在不理解的是stm32内部flash到底是如何安排别名区的?在什么位置?((addr & 0xF000 0000)+0x200 0000+((addr &0xF FFFF)<<5)+(bitnum<<2))  按照上面这句的话,外设区的话位带区的话从0x4200 0000开始,??2.为什么操作别名区地址可以操作实际的寄存器?3.stm32所有系列都支持位带操作吗?这也是一个疑问?

uuQQ截图20180717090029.png
嗯呢
回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 6 天前 | 显示全部楼层
本帖最后由 warship 于 2018-7-17 11:08 编辑
ssssssssssss 发表于 2018-7-17 09:02
1.其实我现在不理解的是stm32内部flash到底是如何安排别名区的?在什么位置?((addr & 0xF000 0000)+0x20 ...

1,别名区都安排在位带的上方200 0000处即地址加32M处,你的理解是对的,对于外设寄存器的别名区从4200 0000开始,别名区不在FLASH,是一个虚的,永远也不可能有这个实体的物理存储空间,这个是位带操作的前提条件(这个是由CM3的存储器规划所保证的)。
2, 对于C编译器来说,是不知道上述的地址是不存在的。我们编程者所做的工作,就是想办法(也就是按ARM设计好的映射方法)去访问这个不存在的地址,这样编译出来的目标代码,本质上就是对一个不存在的地址进行读写的实际无意义甚至会产生不可预知错误的语句,但STM32在具体执行这条指令的时候,利用内核的硬件机制改变方向,去执行对应寄存器BIT位的读写。
3,所有系列都支持。
回复 支持 反对

使用道具 举报

  离线 

53

主题

434

帖子

0

精华

高级会员

Rank: 4

积分
868
金钱
868
注册时间
2014-3-7
在线时间
165 小时
发表于 6 天前 | 显示全部楼层
本帖最后由 ssssssssssss 于 2018-7-17 09:56 编辑
warship 发表于 2018-7-17 09:43
1,别名区都安排在位带的上方200 0000处即地址加32M处,你的理解是对的,对于外设寄存器的别名区从4200 0 ...

st既然有这个功能的话,那应该有一个支持他的硬件存在吧?或者说他内部有个小程序,可以换算出真的地址?按照您说的那应该是硬件实现计算
嗯呢
回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 6 天前 | 显示全部楼层
本帖最后由 warship 于 2018-7-17 10:39 编辑
ssssssssssss 发表于 2018-7-17 09:54
st既然有这个功能的话,那应该有一个支持他的硬件存在吧?或者说他内部有个小程序,可以换算出真的地址? ...

这种东西不需要小程序,移位寄存器、简单逻辑电路、一个指令周期就可以解决的。只须将地址左移5位(除高4位不变外)就对应到了寄存器,移出的那一部分就去访问具体的BIT位去了。
为什么要设计成统一的膨胀32倍(不惜牺牲巨大的存储空间), 就是为了硬件逻辑实现方便!!!
回复 支持 反对

使用道具 举报

  离线 

53

主题

434

帖子

0

精华

高级会员

Rank: 4

积分
868
金钱
868
注册时间
2014-3-7
在线时间
165 小时
发表于 6 天前 | 显示全部楼层
warship 发表于 2018-7-17 10:19
这种东西不需要小程序,移位寄存器、简单逻辑电路、一个指令周期就可以解决的。只须将地址左移5位(除高4 ...

很牛学习了
嗯呢
回复 支持 反对

使用道具 举报

  离线 

2

主题

22

帖子

0

精华

新手上路

积分
42
金钱
42
注册时间
2018-6-27
在线时间
6 小时
发表于 6 天前 | 显示全部楼层
很受启发,感谢感谢
回复 支持 反对

使用道具 举报

  离线 

0

主题

9

帖子

0

精华

初级会员

Rank: 2

积分
90
金钱
90
注册时间
2018-6-27
在线时间
9 小时
发表于 6 天前 | 显示全部楼层
分析精辟,讲解到位,谢谢分享!
回复 支持 反对

使用道具 举报

  离线 

0

主题

27

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
228
金钱
228
注册时间
2014-11-23
在线时间
92 小时
发表于 6 天前 | 显示全部楼层
不错,分析细致,学习了!
回复 支持 反对

使用道具 举报

  离线 

3

主题

11

帖子

0

精华

新手上路

积分
36
金钱
36
注册时间
2018-7-16
在线时间
6 小时
发表于 5 天前 | 显示全部楼层
warship 发表于 2018-7-3 21:00
这三句是一环套一环的,
首先第一句:
#define    BITBAND(addr, bitnum)           ((addr & 0xF000 000 ...

BITBAND(addr, bitnum)这个算符号常量吗?不懂这个,希望解答。
回复 支持 反对

使用道具 举报

  离线 

3

主题

11

帖子

0

精华

新手上路

积分
36
金钱
36
注册时间
2018-7-16
在线时间
6 小时
发表于 5 天前 | 显示全部楼层
warship 发表于 2018-7-3 21:01
简单吧,这就是所有的位操作的奥秘了!!
有了这三句,你就可以完成所有的位操作,让我们举一个实例,比方 ...

哈哈,我这个菜鸟还真不会,不过根据您给的地址在手册找到了。
回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 5 天前 | 显示全部楼层
wgb123 发表于 2018-7-18 22:31
BITBAND(addr, bitnum)这个算符号常量吗?不懂这个,希望解答。

是带参数的宏定义,
其作用过程有点类似函数,
但与函数在本质上完全不同,真正的函数是在代码运行过程中被执行的,它的参数在运行前是未知的。
而这是由编译器负责解释执行的,
在这里,参数addr和 bitnum给编译器时都会是一个确定的数字,
所以编译器,严格地来说是预编译器就已经能够把这个BITBAND(addr, bitnum) 算出一个确定的常数来。
您理解的也对,预编译器(负责处理宏定义替换的)真正交给编译器的完全就是一个常数了。
回复 支持 反对

使用道具 举报

  离线 

3

主题

11

帖子

0

精华

新手上路

积分
36
金钱
36
注册时间
2018-7-16
在线时间
6 小时
发表于 4 天前 | 显示全部楼层
warship 发表于 2018-7-18 22:54
是带参数的宏定义,
其作用过程有点类似函数,
但与函数在本质上完全不同,真正的函数是在代码运行过程 ...

谢谢您的解答。
回复 支持 反对

使用道具 举报

  离线 

17

主题

425

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1408
金钱
1408
注册时间
2016-4-22
在线时间
182 小时
发表于 4 天前 | 显示全部楼层
warship 发表于 2018-7-4 20:04
这里我建议大家使用下面的方法:
这个方法沿袭了前面寄存器位带操作的成果
便于加深理解,并且感觉比上面 ...

不知道这种特意操作内存某个bit的需求在哪里,如果不是从上一楼的结构体引申而来,我真没遇到过。但是如果是结构体,那么肯定是跟业务相关的,我是不会定义这些宏的。

带位操作结构体是常态,就拿协议栈中ip头来说,第一个字节版本号和长度各4bit,字节操作多简单,谁会去定义宏这样写?而且你这个好像只能操作1bit吧?

至于说比结构体少占内存这说法就更怪异了,少多少,少的谁能访问?怎么访问?你确定理解?
伤情最是晚凉天,憔悴斯人不堪怜。
邀酒摧肠三杯醉,寻香惊梦五更寒。
钗头凤斜卿有泪,荼蘼花了我无缘。
小楼寂寞新雨月,也难如钩也难圆。
回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 4 天前 | 显示全部楼层
本帖最后由 warship 于 2018-7-19 18:53 编辑
ysq7120 发表于 2018-7-19 09:14
不知道这种特意操作内存某个bit的需求在哪里,如果不是从上一楼的结构体引申而来,我真没遇到过。但是如 ...

这个就见仁见智了,
你没有遇到,不见得别人就没有用。
这种本身就是针对寄存器逐比特操作的(而这种操作在库函数中至少占60%以上)。
我说的比结构体少占内存说的也是相对这种情况。
不信的话,你就把33楼的代码改写成结构体,然后编译出目标代码比一比试试看。

回复 支持 反对

使用道具 举报

  离线 

29

主题

259

帖子

0

精华

高级会员

Rank: 4

积分
808
金钱
808
注册时间
2012-3-30
在线时间
248 小时
发表于 4 天前 | 显示全部楼层
辛苦辛苦。
回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 4 天前 | 显示全部楼层

多谢鼓励支持。
回复 支持 反对

使用道具 举报

  离线 

17

主题

425

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1408
金钱
1408
注册时间
2016-4-22
在线时间
182 小时
发表于 3 天前 | 显示全部楼层
warship 发表于 2018-7-19 18:39
这个就见仁见智了,
你没有遇到,不见得别人就没有用。
这种本身就是针对寄存器逐比特操作的(而这种操 ...

我并没有说寄存器,我针对你说内存中的结构体这部分。我更没说别人没有用,我只想表达这种用法对一个内存中的结构体操作谋一位没有什么特别优势。节省内存也反对而已,不能因为那个例子。

其他的我没意见。
伤情最是晚凉天,憔悴斯人不堪怜。
邀酒摧肠三杯醉,寻香惊梦五更寒。
钗头凤斜卿有泪,荼蘼花了我无缘。
小楼寂寞新雨月,也难如钩也难圆。
回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
 楼主| 发表于 3 天前 | 显示全部楼层
本帖最后由 warship 于 2018-7-20 20:39 编辑
ysq7120 发表于 2018-7-20 15:28
我并没有说寄存器,我针对你说内存中的结构体这部分。我更没说别人没有用,我只想表达这种用法对一个内存 ...

针对内存,您说的我完全同意。
对于内存来说,比较接近用户的应用,一般不建议采用位操作模式。
所以在61楼,有网友问我对大数组的位访问怎么搞,我也主张除非有大范围且频繁的位操作才有意义。
但既然STM32对内存有位段操作的模式,对于用户自定义频繁使用的标志位可以集中定义内存变量,然后使用位操作机制来访问。这种标志当然都是单BIT的,一般整个程序定义32个以内(即一个u32变量)足矣,这样的一个字用位操作来访问比定义整字节或结构体(联合)无疑要省内存并且执行起来要高效得多。至于您说的定义通信协议或其它的链路层应用层协议,一般字段都是多位且宽度不一,位段操作的便利性和可读性就无法和结构体相比了。
所以,我后面推荐的都是对寄存器的位操作,这种位操作只须定义一个宏就可以高效地对寄存器进行操作,不增加任何附加代码且抛开了库函数(使用库函数编程的人也不妨碍直接对位的访问),我也说了对寄存器中多位的同时操作是位段操作的局限性,这一部分对于使用寄存器版本编程的人还须借助结构体的访问方式,但二者并无冲突,可以混合使用,鉴于STM32的外设寄存器中单BIT的定义覆盖了至少60%以上,这种位段操作我认为还是有意义的,并且如果在宏定义的名称上做点文章,将大大提高程序的可读性和编程的便利性。
比如我前面讲过的,
一句bRCC_ENABLE_SPI2=1;        就可以打开SPI2外设的时钟,而不用管它是挂在哪个总线上的。
库函数版本要用 RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);  (这里经常搞错APB1还是APB2,且错误极难排查
寄存器版本要用 RCC->APB1ENR|=1<<14;
显然本方法是有兼具提高代码效率和增强可读性的效果,
这,就是我想做这项工作的初心。
希望得到您的支持,请多指教并提宝贵意见。


回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则




关闭

"原子哥”推荐上一条 /1 下一条

QQ|联系我们|手机版|官方淘宝店|新浪微博|微信公众平台|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2018-7-23 21:34

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表
/* */