STM32寄存器操作简单介绍

STM32单片机可以使用库函数进行操作,再加上现在越来越强大的MDK ARM编译环境,使用库函数开发简直是不二的选择,但是工具越来越强大的同时意味着对编程人员降低了要求,在很大程度上降低了准入门槛,我们因此高兴吗?我倒是觉得这很像温水煮青蛙,所以库函数固然好用却也不应该抛弃最初的做法——寄存器操作。
先从启动文件开始介绍:这里介绍比较常见的一种启动方式(从内部的的FLASH启动,至于另外两个是否常用本人不甚了解),在启动代码了首先对栈和堆的大小进行定义,并在代码的起始处建立中断向量表,其第一个表项是栈顶(__initial_sp )地址,第二个表项是复位中断服务( Reset_Handler )入口地址。然后在复位中断服务程序中跳转C/C++标准实时库的__main函数,完成用户堆栈等的初始化后,跳转.c文件中的main函数开始执行C程序。STM32从内部FLASH启动,中断向量表起始地位为0x8000000,则栈顶地址存放于0x8000000处,而复位中断服务入口地址存放于0x8000004处。当STM32遇到复位信号后,则从0x80000004处取出复位中断服务入口地址,继而执行复位中断服务程序,然后跳转__main函数,完成这些事情在才始进入最耗费我们精力的main函数。以上内容是否重要,因项目而异,不过栈大小和堆大小可能会需要改动的,如果有机会学习cortex-A系列的SoC,比如三星的S5PV210之类一定会接触到启动文件(bootloader)的。
一个插曲,就是systeminit ,这个函数在官方提供的3.5版本的固件库的启动文件里是在main函数之前运行的,也就是说在官方给的库函数里面是有这个函数的,并且里面的内容是官方给出的,目的是设置系统的频率,一般是72M,但是我希望直接操作寄存器一定要写这个函数吗,或者一定要在main函数之前执行吗,答案是不一定,一个简单粗暴的办法就是直接写一个空的systeminit函数,写一个空的并不建议因为毕竟要调整这个SoC各个总线的频率,不如添加上。直接在启动文件里删去关于systeminit的代码也是可以的,在之后的操作里面写main函数的时候先调用systeminit函数,自己设置频率。
进入主题,说说寄存器,从官方给出的存储器映像可以看出各个外设的的基地址,这些基地址在加上相应的偏移量就是我们希望的寄存器的地址,之后修改寄存器的内容就可以,原理就是这样,关键是如何构造好的数据结构对这些寄存器进行操作。下面通过实例介绍三种方法:
实例:让GPIOB0置位,方式推挽输出,思路是1、开GPIOB的时钟 2、设置输出方式 3、端口置位

第一种:直接宏定义精确到各个具体的寄存器。GPIOB开始的地址是0X40010C00,其中端口配置低寄存器(GPIOx_CRL)偏移地址是0x00,这样(0X40010C00+0x00)就是CRL寄存器的地址,使用(long *)(0X40010C00+0x00),将这个地址强制转换成指针类型,在利用*((long *)(0X40010C00+0x00))操作(0X40010C00+0x00)地址里的内容。

#define GPIOB_CRL    (*((long *)(0X40010C00+0x00)))
#define RCC_APB2ENR  (*((long *)(0x40021000+0x18)))
#define GPIOB_ODR    (*((long *)(0X40010C00+0x0C)))

这样就可以像上面的思路操作了。

RCC_APB2ENR|=(1<<3);//开GPIOB的时钟
GPIOB_CRL=3;//设置输出方式
GPIOB_ODR=0XFFFF;//端口置位

这样操作直观,但是需要的宏太多,容易出现问题,稍不留神偏移量写错就前功尽弃了。
第二种:构造结构体,将GPIO的寄存器全部写进结构体,相比上面的方法让编译器帮我们进行计算,减少出错的可能,本来像上面那样体现不出C语言这个工具的强大。首先构造

typedef struct
{
  int CRL;
  int CRH;
  int IDR;
  int ODR;
  int BSRR;
  int BRR;
  int LCKR;
} GPIO_TypeDef;

之后

#define GPIOB ((GPIO_TypeDef*)(0X40010C00))

这样就可以用指针的方式操作GPIOB的各个寄存器了,这里的GPIO_TypeDef类型的数据结构保证操作的寄存器全是GPIO的寄存器不会越界,具体的原因是C语言的问题,这里不做过多的解释,以免跑题。下面按照最开始的操作思路进行:

RCC->APB2ENR|=(1<<3);//开GPIOB的时钟
GPIOB->CRL=3;//设置输出方式
GPIOB->ODR=0XFFFF;//端口置位

显然这种方式借助C语言这个工具和编译器帮我们解决了很多的问题。
第三种方法:查看库文件stm32f10x.h有以下的宏定义

#define PERIPH_BASE           ((uint32_t)0x40000000)
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)

要知道define定义就是简单的字符串替换,替换之后的结果是

#define GPIOB   ((GPIO_TypeDef *) 0x40000000 + 0x10000 + 0x0C00)

可以看出就是第二种方法里面的

#define GPIOB ((GPIO_TypeDef*)(0X40010C00))
所以既然官方已经帮我们做好了我们为什么不用呢。所以直接包含头文件#include “stm32f10x.h” 就可以了。说到这里似乎又回到了起点,这不是还是使用了官方的库函数吗?但是这和#include 是一样的道理,只是用到了官方已经做好的寄存器名字的定义,以及一种简单的调用寄存器的方法。
使用库函数还是直接操作寄存器更好,以上的例子并没有说明问题,我们看到的只是库函数无外乎是操作寄存器,没有看到的是库函数的函数多级调用,这里不做说明。
最后一句,对于我们使用的人来说一定不要被层层的外包装所迷惑。






你可能感兴趣的:(C语言学习,STM32)