STM32从地址到寄存器

本文希望在上一节的基础上,把指针操作过渡到寄存器的使用,来帮助读者深入理解寄存器。

引入头文件

主函数里出现了强制转换与指针的操作,程序不那么容易理解。我们把寄存器的地址进行宏定义,可以增强可读性。

#define RCC_APB2ENR (*(unsigned int *)0x40021018)
#define GPIOB_CRH (*(unsigned int *)0x40010c04)
#define GPIOB_ODR (*(unsigned int *)0x40010c0c)

int main(void)
{
    RCC_APB2ENR = 0x00000008;
    GPIOB_CRH = 0x44444443;
    GPIOB_ODR = 0x00000000;     
    return 0;             
}

一个工程里可能有多个.c文件,假如另外一个.c文件也想使用这些寄存器,需要重复定义,很麻烦。所以,程序再次修改,增加一个头文件main.h,把宏定义放入头文件内。如果别的.c文件也想使用这些寄存器,只需要包含main.h头文件即可。
  在main.c文件的同一级目录下,新建一个main.h并在main.h内输入以下代码:

#define RCC_APB2ENR (*(unsigned int *)0x40021018)
#define GPIOB_CRH (*(unsigned int *)0x40010c04)
#define GPIOB_ODR (*(unsigned int *)0x40010c0c)

在main.c内把代码改为:

#include "main.h"

int main(void)
{
    RCC_APB2ENR = 0x00000008;
    GPIOB_CRH = 0x44444443;
    GPIOB_ODR = 0x00000000;     
    return 0;             
}

在.c文件中,用“#include “main.h””这行代码代替了原先的3行宏定义,宏定义都放到了main.h文件里。编译工程,并下载程序,可以看出现象仍是LED1点亮。
  可以想象出,GPIO与时钟相关的寄存器都是很常用的寄存器,如果每一次操作这些IO口都需要看数据手册的话,太累,所以人家做芯片的把常用的寄存器对应的地址都设置好了,并放到一个头文件内,就是<stm32f10x.h>,修改main.c的代码,包含stm32f10x.h头文件。

使用官方的头文件

上一篇文章已经计算过结构体中元素的地址偏移,这里直接给出结论:
  代码GPIOB->CRH就是操作地址0x4010 0C04。同理,时钟使能的操作也要使用结构体。
  代码修改为:

//#include "main.h"
#include <stm32f10x.h>  
int main(void)
{
    RCC->APB2ENR = 0x00000008;
    GPIOB->CRH = 0x44444443;
    GPIOB->ODR = 0x00000000;
    return 0;             
}

按位操作

寄存器是可以按位操作的,为了使第一个代码足够简单,所以用了一些错误的写法,操作了无关的引脚,如果被“连带”的引脚恰好连接了别的外设,这外设多无辜。现在拨乱反正。
  先复习按位与或非的知识

A 1 1 0 0
B 1 0 1 0
&与 1 0 0 0
"|"或 1 1 1 0
^异或 0 1 1 0
~A 非A 0 0 1 1

注意,跟逻辑非的操作不同,按位非的操作符是~,而不是!感叹号。
  按位操作主要的作用就是清0或者置1,会用到以下的表达式:
0&n = 0 1&n = n
1|n = 1 0|n = n
  如果想保持数据a其它位不变,把第二位写成零,a&=0b 1101。
  把第二位写成1,a|=0b 0010。
  修改代码

#include <stm32f10x.h>  

int main(void)
{
    RCC->APB2ENR |= 0x00000008;
    GPIOB->CRH &= 0xfffffff0;//先清零
    GPIOB->CRH |= 0x00000003;
    GPIOB->ODR &= 0xfffffeff;
    return 0;             
}

还可以优化,代码不好看懂。代码是写给机器运行的,却是写给人看的。看到这些代码的人,是不是还需要去翻看数据手册和原理图,才知道去操作哪个引脚呢?接下来修改为按位操作:

#include <stm32f10x.h>  

int main(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    GPIOB->CRH &= ~(0xf<<(0*4));
    GPIOB->CRH |= 0x3<<(0*4);     
    GPIOB->ODR &= ~(1<<8);
    return 0;             
}

为什么费尽心机要把这些寄存器的配置改为按位操作?因为这样的操作有比较好的可读性和扩展性。有的人认为这些代码不好读,这是因为读的太少,例如 对于ODR寄存器,牢牢记住“或操作”置1,输出高电平,“与操作”置0,输出低电平。
  为什么左移0*4位,这个操作就是不左移呀?因为这样的写法很容易看出是操作引脚0或8,并且便于修改。例如,我现在想改为操作PB1,代码只需要稍微修改下:

#include <stm32f10x.h>  
int main(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    GPIOB->CRL &= ~(0xf<<(1*4));
    GPIOB->CRL |= 0x3<<(1*4);     
    GPIOB->ODR &= ~(1<<1);
    return 0;             
}

其中14,因为这个1代表的就是PB1(注意,同时CRH也换成了CRL),4代表的是每4位设置一个IO。如果是PB2,那么代码就改为GPIOB->CRL |= 0x2<<(24);,是PBn就改为GPIOB->CRL |= 0x2<<(n*4);第n个引脚输出0,也可以改为GPIOB->ODR &= ~(1<<n);扩展性也得到了增强。

发布了127 篇原创文章 · 获赞 312 · 访问量 56万+

猜你喜欢

转载自blog.csdn.net/geek_monkey/article/details/86293880