天天看点

【STM32F10x系列】一:寄存器相关操作

并非所有处理器都有库函数,而只有在STM32才拥有库函数以方便的调用库函数去书写代码。真正操作处理器的本质还是寄存器,只有理解如何利用寄存器去运行程序,去学习其他处理器就会比较容易上手了

程序在地址中跳转,而地址以寄存器的方式进行分布。所以今天先尝试使用地址来操作寄存器写入程序。具体的地址分布可根据文档《STM32F103xE数据手册》及《STM32F10x参考手册》进行查询

预备知识

1、main()函数及编译的预处理工作

  编译器在编译时总是从main.c入口函数开始运行(初学暂未能遇到从其他.c开始编译的类型),然后去读取在预处理阶段(include)的其他.h文件,将其他.h文件中的宏、变量、函数等进行检查观测是否出现重复定义或者错误,最后将其内容扫描进.c文件中运行。其他.h文件中包含的函数一般写入其对应的.c文件中加以模块化调用

2、volatile关键字

  volatile关键字告诉编译器从寄存器地址取值而不是在缓存器里取值,使用volatile关键字声明变量的值,可以保证对特殊地址的稳定访问而不至于被改变

3、GPIO

GPIO标准:标准I/O口承受5V电压;18MHz翻转速度;每个GPIO口分为7个端口;可对位设置及清零

端口:每个端口配置两个配置寄存器、两个数据寄存器、一个置位/复位寄存器、一个复位寄存器、一个锁定寄存器

端口位配置

【STM32F10x系列】一:寄存器相关操作
【STM32F10x系列】一:寄存器相关操作

4、GPIO库函数

【STM32F10x系列】一:寄存器相关操作

5、GPIO寄存器

【STM32F10x系列】一:寄存器相关操作
有了以上的基础,那么先简单的编写一个点亮GPIOA端口1,2,3,4管脚的led灯程序吧

     ——首先在main.c文件中包含stm32f10x.h头文件,这个文件中包含了各种外设库函数的定义,并且在.c文件中写下入口函数,具体如下

#include "stm32f10x.h"
int main(void)
{

}
           

注意一件有趣的事情是,在keil软件中若最后一行代码 } 后面没有多加一行,就会出现如下错误:

user\led_exti.c(46): warning:  #1-D: last line of file ends without a newline

这只是工程师设计软件时候的设置,以后只要记得写完代码后多回车一行即可

    ——关于GPIOx_CRL的配置

  通过上面的图示可以看出,若需要配置GPIOA的1234管脚,那么需要使用到端口配置低寄存器GPIOx_CRL

【STM32F10x系列】一:寄存器相关操作
【STM32F10x系列】一:寄存器相关操作

  需要解释的是,每个端口都有16个管脚,其中0-7为低位,8-15为高位。

  可以看出编号为0123的位属于PA0,4567的位属于PA1,依次类推可找出需要设置的PA1/2/3/4

  因为按要求需要点亮GPIOA,则寄存器名:GPIOA_CRL。表中MODE和CNFx依次排列,MODE用于选择输入/输出速率,CNFx用于选择输入/输出工作模式。

绝大多数情况下使用推挽式输出,遇到I2C总线使用开漏输出

  所以在CNF中选择00即可,而在速率选择中暂选择50MHz,也即是11。故在PA1管脚,也即是4567位上写入0011,依次可推断出其他管脚的值都是换算成十六进制的0x3。

  在寄存器中,一个地址的是由基地址+偏移地址 组成,从《STM32F103xE数据手册》(以下简称数据手册)中可得知,PortA的地址映射在0x40010800-0x40010BFF中,这是基地址,另可知偏移地址为0x00。所以当前GPIOx_CRL的地址等于0x40010800H+0x00H,之后在此地址中写入0011、0011的数据就可以实现管脚的初始化配置。

#define GPIO_CRL   (*(volatile unsigned long *)0x40010800)
           
注解:其中使用unsigned long类型取出寄存器地址0x40010800,然后外围再套一个取地址符以将该地址取入GPIO_CRL这个变量名中

  ——关于GPIOx_ODR的配置

  GPIOx_ODR是端口输出数据寄存器,其参数如下

【STM32F10x系列】一:寄存器相关操作

  此寄存器16-31位是保留位。在0-15管脚中写入1,则输入高电平;写入0,则输入低电平。若设置的LED是共阴极触发,则写入1输出高电平点亮,写入0输出低电平熄灭。可知ODR的偏移地址为0Ch,则寄存器GPIOx_ODR的地址为0x40010800H+0x0CH。

  其中GPIOx_BSRR也可实现端口位设置,不过暂时不管,其BRR为端口位清除。

  GPIOx_LCKR为端口配置锁定寄存器,可以让端口为写入操作期间不可改变端口值,也暂时不需要考虑。

#define	GPIOA_ODR   (*(unsigned long *)0x4001080c)
           

—— 关于RCC寄存器的配置

  由系统的构架图可以看出

【STM32F10x系列】一:寄存器相关操作

  其中GPIOA是挂靠在APB2总线上的,而此时便需要另外一个寄存器——RCC寄存器,可以在数据手册文档中找到RCC在存储器中的地址映射在0x40021000-0x400233FFH。关于RCC在线路中的时钟控制,此节暂不详谈,可知其选择为72MHz即可。

  具体RCC中包含了如下寄存器

【STM32F10x系列】一:寄存器相关操作

该寄存器的端口位设置如下

【STM32F10x系列】一:寄存器相关操作

若要开启GPIOA端口使能,则需要将位2写入1

  其中RCC_APB2ENR的寄存器偏移地址是0x18。所以可知寄存器RCC_APB2ENR的地址为0x40021000H+0x18H

#define RCCAPB2ENR (*(volatile unsigned long *)0x40021018)
           

——当我们整理好资料后,最终编写出的程序即为

#include "stm32f10x.h"

#define RCCAPB2ENR (*(volatile unsigned long *)0x40021018)  
#define GPIO_CRL   (*(volatile unsigned long *)0x40010800)  
#define	GPIOA_ODR   (*(unsigned long *)0x4001080c)

void Delay(unsigned long N);

int main(void)
{
    RCCAPB2ENR |= (1<<2); //使能
    GPIOA_CRL &= 0xfff0000f; //清除端口位
    GPIOA_CRL |= 0x00033330; //将端口设置为推挽式输出和50MHz速率
    GPIOA_ODR  &= ~((1<<1)|(1<<2)|(1<<3)|(1<<4)); //输入低电平,灯熄灭
    
    
    while(1)
    {
    GPIOA_ODR &= ((1<<1)|(1<<2)|(1<<3)|(1<<4)); //点亮LED灯
    Delay(10000);
    }
}
void Delay(unsigned long N)
{
    while(N)
    { }
}