当前位置:   article > 正文

关于stm32固件库是如何通过指针操作来控制寄存器的探讨!_用指针操作寄存器

用指针操作寄存器

ps:虽然之前在学校学了stm32,但是学的模模糊糊的,后来自己在写代码的时候发现很多官方例程里面使用了很多结构体指针和各种指针类型转换,自己就琢磨了指针方面的知识。之后便突然联想到刚开始学32时候的疑惑,就是固件库是如何给寄存器赋值操作寄存器?突然之间有了领悟。此文章也算是探讨了一下指针在单片机编程里面的一些用法。个人理解,如有错误,望指正!

先来做个小实验,怎么在知道地址的情况下去操作地址里面的值。

 我定义了一个变量a,然后我打印出了变量a的十六进制地址,我们假设a是寄存器,0x407038是寄存器的地址。指针*p我们待会用到。

然后我们来把地址赋给p。但是0x407038只是一个常量,我们用(int*)强制类型转换,这样我们就把这个常量转化成了一个地址。

然后我们直接给指针*p赋值,在打印出寄存器a的值。好!我们成功通过赋值地址给指针,然后给指针赋值,达到了改变寄存器a值的目的。接下来我们进入正文!

我们拿单片机里面最简单点亮一个led来说,我的单片机的led是GPIOB,pin5低电平点亮,然后我们知道单片机外设工作首先要打开相应的外设时钟,这里就不多赘述了,我们直接上图。GPIOB挂在APB2总线上面,所以我们需要打开APB2的时钟,APB2又属于RCC,我们在手册里面找一下。

找到了,RCC的首地址是0x4021000,我们跳过去看一下。

出现!APB2的外设使能寄存器,它的偏移地址是0x18,也就是说,这个寄存器的地址是0x18+0x40021000=0x40021018。接下来我们来找一下相应的GPIOB寄存器。我们使用和刚刚一样的方法。

 

ok,我们在手册里面找到了他们的地址,我们使用和上面一样的方法,基地址加偏移地址就可以得到寄存器的地址了,不过配置GPIOBpin5需要配置两个寄存器CRL与ODR,他们分别配置输出模式,输出速率以及输出的引脚,我们需要的配置是推挽输出,10M的速度,pin5脚。

 

 通过查询寄存器我们需要配置CRL的第21位为1,ODR的第5位为0,接下俩我们上代码,利用刚刚小实验的方法。

 

代码如下,我们用来了两种不同的表现方式,第一种我们直接常量强制转换成地址然后用*号打开地址,就可以往里面写值了。第二种方式我们定义了一个结构体,它的成员大小全是32位(因为每个寄存器都是32位,地址偏移都是四个字节),然后强制转化地址常量为结构体类型的指针,这个指针指向的结构体的首地址,然后我们通过结构体来访问其中的寄存器。我们每访问下一个成员,地址都自动加了4字节,正好指在了对应的寄存器首地址。

接下来在主函数里面我们就可以直接给值了,欧克,我们编译下载,led成功点亮。其实我们这样的方法就是直接操作寄存器,在32编程里面效率大部分时候是比较低下的,因此就有了固件库编程(现在比较流行hal库了),刚开始接触固件库的时候,几乎没有直接操作寄存器的感觉,只知道按流程来,先开时钟,然后配置函数,然后就好了。那它的原理又是什么呢,会不会就和这个样子的呢,我们来写一段固件库函数点灯。

maaain.c

  1. typedef unsigned int uint32_t;
  2. typedef struct
  3. {
  4. uint32_t CRL;
  5. uint32_t CRH;
  6. uint32_t IDR;
  7. uint32_t ODR;
  8. uint32_t BSRR;
  9. uint32_t BRR;
  10. uint32_t LCKR;
  11. }GPIO_Typedef;
  12. #define GBIOB ((GPIO_Typedef*)0X40010C00)
  13. #define RCC_APB2 *(uint32_t*)0x40021018
  14. int main()
  15. {
  16. RCC_APB2 |= 1<<3;
  17. GBIOB->CRL |=1<<21;
  18. GBIOB->ODR |=0<<5;
  19. }

如图所示,这里是怎么做到操作了寄存器的呢,我们深挖一下。我们先看时钟部分,我们重定义到RCC_APB2Periph_GPIOB里面看看。 

  1. #include "stm32f10x.h" // Device header
  2. int main()
  3. {
  4. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  5. GPIO_InitTypeDef GPIO_struct;
  6. GPIO_struct.GPIO_Mode=GPIO_Mode_Out_PP;
  7. GPIO_struct.GPIO_Pin=GPIO_Pin_5;
  8. GPIO_struct.GPIO_Speed=GPIO_Speed_10MHz;
  9. GPIO_Init(GPIOB,&GPIO_struct);
  10. GPIO_ResetBits(GPIOB,GPIO_Pin_5);
  11. while(1);
  12. }

 是不是非常的熟悉,就和我们刚刚做的差不多,这里的地址加起来其实就是我们刚刚的地址,寄存器寻址也是用的结构体类型指针强转的方法。RCC_APB2Periph_GPIOB是0x00...8转化成二进制,不就是1000吗,也就是1<<3。只不过它封装成了一个函数,囊括了所有的寄存器。

 

 

 

 

 GPIO_init的函数内容太多,我们就不往里面细看了,我们大致的看一下,好像也就是上面那么一回事,不过GPIO寄存器操作复杂了一点,但是我们对一下地址各种数据,在结合一下我们刚刚上面的寄存器操作,好像豁然开朗了,都是一样的操作。这里面的操作离不开指针操作,就是把常量地址强制转化成指针类型的真实地址,编写了一个可以应对各种情况的函数来操作地址,最后封装起来给我们调用。

 

 

 

 好啦,文章到这里差不多结束了,个人觉得这里面最有趣的就是指针操作,指针不愧是c语言的灵魂!借此我们也可以发散思维一下,我们是不是也可以用结构体,甚至是数组,借助指针来存贮以及操作数据了,甚至还能少定义一些变量直接改变数据,如果加上位操作就更方便。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/778203
推荐阅读
相关标签
  

闽ICP备14008679号