并非所有处理器都有库函数,而只有在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个端口;可对位设置及清零
端口:每个端口配置两个配置寄存器、两个数据寄存器、一个置位/复位寄存器、一个复位寄存器、一个锁定寄存器
端口位配置
4、GPIO库函数
5、GPIO寄存器
有了以上的基础,那么先简单的编写一个点亮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
需要解释的是,每个端口都有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是端口输出数据寄存器,其参数如下
此寄存器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寄存器的配置
由系统的构架图可以看出
其中GPIOA是挂靠在APB2总线上的,而此时便需要另外一个寄存器——RCC寄存器,可以在数据手册文档中找到RCC在存储器中的地址映射在0x40021000-0x400233FFH。关于RCC在线路中的时钟控制,此节暂不详谈,可知其选择为72MHz即可。
具体RCC中包含了如下寄存器
该寄存器的端口位设置如下
若要开启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)
{ }
}