目录
一、寄存器位移点灯
根据之前博客中介绍的寄存器/地址映射,在这里采用寄存器位移方法点灯就比较简单了。我们采用的工具仍然是Keil,STM32最小核心板,面包板,若干杜邦线以及LED。
【问题描述】
通过配置STM32的PA5、PB9、PC14端口,使其周期性地输出高低电平,采用通用推挽输出模式,最高输出时钟频率为2Mhz。进而驱动LED灯的亮灭,实现流水灯的功能。
【问题分析】
1. 首先,要使对应IO口的时钟使能;
2. 其次,要操作寄存器,一定要知道寄存器是谁,在哪里。因此第二步骤,配置寄存器地址;
3. 之后,配置端口的输入输出模式;
4.如果要实现周期性的亮灭,需要加入延迟函数delay();
5. 最后,编译成功后,将程序烧录到单片机中。
【问题解答】
1.使对应IO口的时钟使能
经过查阅STM32F103x的芯片手册的发现:复位和时钟控制的起始地址为0x4002 1000
,控制GPIO等外设的寄存器为APB2外设时钟,因此加上偏移量0x18
,得到使能时钟地址为0x4002 1018
。
查询APB外设时钟位定义,发现位2、位3、位4分别使能GPIOA、GPIOB、GPIOC。
-------------------------------代码如下------------------------------
//----------------APB2使能时钟寄存器 ---------------------
#define CLKENR *((volatile unsigned int *)0x4002 1018)
CLKENR|=1<<2|1<<3|1<<4; //APB2-GPIOA、GPIOB、GPIOC外设时钟使能
【语句解释】
关键词:volatile
当地址是IO端口的时候,读写这个地址是不能对它进行缓存的,这是相对于某些嵌入式中有cache才有这个。比如写这个IO端口的时候,如果没有这个volatile,很可能由于编译器的优化,会先把值先写到一个缓冲区,到一定时候再写到io端口,这样就不能使数据及时的写到IO端口,有了volatile说明以后,就不会再经过cache,write buffer这种,而是直接写到IO端口,从而避免了读写IO端口的延时。
简单来说:让对volatile 变量的存取不能缓存到寄存器,每次使用时需要重新存取。
2.寄存器地址配置
查询数据手册,得到GPIOA、GPIOB、GPIOC的起始地址为0x40010800、0x40010C00、0x40011000
;
-------------------------------代码如下------------------------------
//----------------GPIOA寄存器地址 -----------------------
#define GPIOA *((unsigned volatile int*)0x40010800)
//----------------GPIOB寄存器地址 -----------------------
#define GPIOB *((unsigned volatile int*)0x40010C00)
//----------------GPIOC寄存器地址 -----------------------
#define GPIOC *((unsigned volatile int*)0x40011004)
3.配置输入输出模式以及最高输出速率
STM32中,用低寄存器(GPIOx_CRL)来配置引脚Px0-Px7, 用高寄存器(GPIOx_CRH)来配置引脚Px8-Px15。因此在本次实验中用到的三个引脚A5、B9、C14。端口A5需要用低寄存器(GPIOx_CRL)来配置,B9、C14需要高寄存器(GPIOx_CRH)来配置。
根据数据手册,可得低寄存器(GPIOx_CRL)基于寄存器地址偏移量为0x00
,高寄存器(GPIOx_CRH)在寄存器地址偏移量为0x04
;输出寄存器基于GPIO寄存器地址偏移量为0x0C
---------------------配置寄存器地址代码如下--------------------
//----------------GPIOA配置寄存器 -----------------------
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
#define GPIOC_ODR *((unsigned volatile int*)0x4001080C)
//----------------GPIOB配置寄存器 -----------------------
#define GPIOB_CRL *((unsigned volatile int*)0x40010C00)
#define GPIOC_ODR *((unsigned volatile int*)0x40010C0C)
//----------------GPIOC配置寄存器 -----------------------
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
#define GPIOC_ODR *((unsigned volatile int*)0x4001100C)
根据下面的规则进行输入输出模式配置。
--------------------配置输入输出模式代码如下------------------
GPIOA_CRL&=0xFF0FFFFF; //设置位 清零
GPIOA_CRL|=0x00200000; //PA5推挽输出,把第23、22、21、20变为0010
GPIOA_ODR&=(0<<4); //设置初始灯为灭
GPIOB_CRH&=0xFFFFFF0F; //设置位 清零
GPIOB_CRH|=0x00000020; //PB9推挽输出,把第7、6、5、4变为0010
GPIOB_ODR&=(0<<5); //设置初始灯为灭
GPIOC_CRH&=0xF0FFFFFF; //设置位 清零
GPIOC_CRH|=0x02000000; //PC14推挽输出,把第27、26、25、24变为0010
GPIOC_ODR&=(0<<14); //设置初始灯为灭
4.编写Delay()函数
编写Delay()函数如下:
void Delay()
{
u32 i=0;
for(;i<10000;i++);
}
5.创建keil工程,并编译
---------------------------完整代码如下---------------------------
#include "stm32f10x.h""
#define CLKENR *((volatile unsigned int *)0x40021018)
//----------------GPIOA寄存器地址 -----------------------
#define GPIO_A *((unsigned volatile int*)0x40010800)
//----------------GPIOB寄存器地址 -----------------------
#define GPIO_B *((unsigned volatile int*)0x40010C00)
//----------------GPIOC寄存器地址 -----------------------
#define GPIO_C *((unsigned volatile int*)0x40011004)
//----------------GPIOA配置寄存器 -----------------------
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
#define LED1 *((unsigned volatile int*)0x4001080C)
//----------------GPIOB配置寄存器 -----------------------
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04)
#define LED2 *((unsigned volatile int*)0x40010C0C)
//----------------GPIOC配置寄存器 -----------------------
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
#define LED3 *((unsigned volatile int*)0x4001100C)
void Delay()
{
u32 i=0;
for(;i<10000;i++);
}
int main()
{
CLKENR|=1<<2|1<<3|1<<4; //APB2-GPIOA、GPIOB、GPIOC外设时钟使能
GPIOA_CRL&=0xFF0FFFFF; //设置位 清零
GPIOA_CRL|=0x00200000; //PA5推挽输出,把第23、22、21、20变为0010
LED1&=(0<<4); //设置初始灯为灭
GPIOB_CRH&=0xFFFFFF0F; //设置位 清零
GPIOB_CRH|=0x00000020; //PB9推挽输出,把第7、6、5、4变为0010
LED2&=(0<<5); //设置初始灯为灭
GPIOC_CRH&=0xF0FFFFFF; //设置位 清零
GPIOC_CRH|=0x02000000; //PC14推挽输出,把第27、26、25、24变为0010
LED3&=(0<<14); //设置初始灯为灭
while(1)
{
//LED1
LED1|=1<<5; //PB5高电平
Delay();
LED1&=(0<<5); //PB5低电平,因为是置0,所以用按位与
//LED2
LED2|=1<<9; //PB9高电平
Delay();
LED2&=(0<<9); //PB5低电平,因为是置0,所以用按位与
//LED3
LED3|=1<<14; //PB14高电平
Delay();
LED3&=(0<<14); //PB5低电平,因为是置0,所以用按位与
}
}
进行编译!
6.软件仿真
关于软件仿真的设置可以去看博主之前写的文章
观看GPIOA_5波形:
观看GPIOB_9波形:
观看GPIOC_14波形:
每个灯亮的间隔为1.6ms,如果想要闪烁更为明显可以调整delay()函数中的数值。
7.硬件实现
接线方法原理可以在之前的博客中查看,演示视频如下:
会发现灯亮灭周期太短,经过修改延迟函数,变为如下:
二、基于HAL库点灯
STM32cubeMX的下载与安装过程,可以参考之前的博客
1.新建工程
(1)“File
”—“New Project”
,完成工程创建。
(2)找到自己需要的芯片型号,“Start Project ”
2.环境配置
(1)选择System Core
下的SYS
,将Debug
设置为Serial Wire
:
这个根据自己的情况进行选择 :JTAG/SWD接口这是ALIENTEK精英STM32F103板载的20针标准JTAG调试口(JTAG),该JTAG口直接可以和ULINK、JLINK或者STLINK等调试器(仿真器)连接。
SW,Serial Wire 是串行调试接口,在本次实验中用这个即可。
(2)时钟配置
在Cubemx晶振选择有3种:
Disable 这个是不用外部晶振;
Crystal/Ceramic Resontor 这个是用无源外部晶振;
BYPASS Clock Source 这个是有源外部晶振;
选择BYPASS Clock Source
:系统时钟由外部提供
选择Crystal/Ceramic Resontor:系统时钟由外部晶振和芯片内部电路协作提供。
选择“RCC
”-“HSE
”-“Crystal/Ceramic Resontor
”
配置完成后,芯片上的脚出现高亮。
(3)继续时钟配置,限定最大时钟频率。
在“时钟树”下进行配置,将HCLK数值改为“2”,这样就设定了最大2MHz的HCKL
的数值。
(4)GPIO配置
首先,找到PA5,PB9,PC14,右键,选择“GPIO_Output
”
之后,点击右侧菜单栏的“GPIO”,将三个引脚的输出默认低电平,输出模式默认“no pull up and no pull down
”:如果引脚配置为output,一般选择no pull,这样,引脚才能根据你的output数据,进行正确输出。
(5)导出源代码
首先,在Project Manager
下的Project
中设置工程名称和工程路径,并选择编译软件:
其次,取消勾选Use lastest available version
,选择其他版本:
之后,在Code Generate
中选择第一个,然后Generate Code
,即生成代码:
最后,点击“Create Code
”.
3.编写流水灯代码
(1)打开由CubeMx软件生成的代码。
找到之前存放文件的文件路径,打开找到“MDK-ARM
”,打开带有MDK
标识的工程
(2)编写main()函数代码
①找到main.c函数,在main
函数体中的while()
循环中写入使LED亮的代码;
②在while()循环中写入如下代码:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); // 灯1灭
HAL_Delay(1000); // 延时1s
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // 灯1亮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET); // 灯2灭
HAL_Delay(1000); // 延时1s
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET); // 灯2亮
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_SET); // 灯3灭
HAL_Delay(1000); // 延时1s
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET); // 灯3亮
③编译通过
4.软件仿真
和上述寄存器实现流水灯方法一致,观看GPIOA_5波形:
GIOPB_9波形:
GIOPC_12波形:
通过软件仿真,发现周期并不是1s.而是0.7s左右。
5.硬件实现。
所有电路连接和上述一样,只需将对应程序的hex文件烧录到单片机中即可,实物演示如图所示:
三、总结
对寄存器的操作,实质上时对其地址进行操作,要注意位运算,在过程中由于没有取“|”运算,导致时钟配置出现问题,后续检查过程中发现如果不进行位或运算,该时钟使能就会被其他GPIO时钟覆盖。