目录
一、寄存器方式点亮LED流水灯
STM32点亮LED的操作过程是:CPU给指令->GPIO收到指令->配置内部寄存器->配置IO口模式->控制LED亮灭
STM32开发板中包含较多寄存器,实现流水灯操作需要对相应的引脚进行如下操作:
- 时钟配置
- 输入输出模式设置
- 最大速率设置
- 编写程序
- 烧录运行
在STM32F103C8T6上通过初始化GPIO点亮LED,通过将其初始化控制片外引脚即I/O口拉高降低输出高低电平,以控制LED灯的亮灭即可。
1.时钟配置
查阅手册:
- 时钟使能寄存器映射基地址:
- 端口偏移地址以及对应端口所在位置
可以看到外设时钟使能寄存器偏移量为0x18,起始地址为0x4002 1000,因此该寄存器的地址为0x4002 1018
//APB2时钟使能寄存器
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018);
手册中提到,RCC_APB2ENR(APB2外设时钟使能寄存器)的位3是IOPBEN,即IO端口B时钟使能,将该端口置1就是开启GPIO时钟.
RCC->APB2ENR|=1<<2; //APB2-GPIOA外设时钟使能
RCC->APB2ENR|=1<<3; //APB2-GPIOB外设时钟使能
RCC->APB2ENR|=1<<4; //APB2-GPIOC外设时钟使能
2.输入输出模式设置、最大速率设置
本次实验采用通用推挽输出模式,最高输出时钟频率2Mhz。分别用到GPIOA-5、GPIOB-9、GPIOC-14 三个引脚。其中A4属于端口配置低寄存器偏移地址为0x00,B9、C14属于端口配置高寄存器偏移地址为0x04。
- GPIO端口基地址
- 配置对应引脚寄存器,寄存器地址=基地址+偏移量
//GPIOA配置寄存器
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
//GPIOB配置寄存器
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04)
//GPIOC配置寄存器
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
- 设置推挽模式输出,输出速度位2MHz
-
将GPIOA-5配置成推挽模式输出,且最大速度为2MHz,根据数据手册可以知道它属于端口配置低寄存器模块,CNF5和MODE5位为0,其余位为F,即GPIOB_CRH&=0xFF0FFFfF;又因为其为推挽模式输出,其最大速度为2MHz,所以4位寄存器的配置就是CNF5【00】MODE5【10】,【0010】换算为十进制为【2】,因此GPIOB_CRH|=0x00200000。
-
将GPIOB-9配置成推挽模式输出,且最大速度为2MHz,根据数据手册可以知道它属于端口配置高寄存器模块,CNF9和MODE9位为0,其余位为F,即GPIOB_CRH&=0xFFFFFF0F;又因为其为推挽模式输出,其最大速度为2MHz,所以4位寄存器的配置就是CNF9【00】MODE9【10】,【0010】换算为十进制为【2】,因此GPIOB_CRH|=0x00000020。
-
将GPIOC-14配置成推挽模式输出,且最大速度为2MHz,根据数据手册可以知道它属于端口配置高寄存器模块,CNF14和MODE14位为0,其余位为F,即GPIOB_CRH&=0xF0FFFFFF;又因为其为推挽模式输出,其最大速度为2MHz,所以4位寄存器的配置就是CNF14【00】MODE14【10】,【0010】换算为十进制为【2】,因此GPIOB_CRH|=0x02000000。
GPIOA_CRL&=0xFFF0FFFF; //设置位清零
GPIOA_CRL|=0X00200000; //PA5推挽输出,把第23、22、21、20位变为0010
GPIOA->ODR|=1<<5; //设置PA5初始灯为灭
GPIOB_CRH&=0xFF0FFFFF; //设置位清零
GPIOB_CRH|=0x00000020; //PB9推挽输出,把第7、6、5、4变为0010
GPIOB->ODR|=0x1<<9; //设置初始灯为灭
GPIOC_CRH&=0xFF0FFFFF; //设置位清零
GPIOC_CRH|=0x02000000; //PC14推挽输出,把第27、26、25、24变为0010
GPIOC->ODR|=0x1<<14; //设置初始灯为灭
3.编程并烧录运行
#include "stm32f10x.h" // Device header
#include "Delay.h"
//APB2时钟使能寄存器
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
//GPIOA配置寄存器
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
#define GPIOA_ODR *((unsigned volatile int*)0x4001080C)
//GPIOB配置寄存器
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
//GPIOC配置寄存器
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
#define GPIOC_ODR *((unsigned volatile int*)0x4001100C)
//函数声明
void A_LED_LIGHT(void);
void B_LED_LIGHT(void);
void C_LED_LIGHT(void);
//控制灯亮灭
void A_LED_LIGHT()
{
GPIOA_ODR=0x0<<5; //PA5低电平
GPIOB_ODR=0x1<<9; //PB9高电平
GPIOC_ODR=0x1<<14; //PC14高电平
}
void B_LED_LIGHT()
{
GPIOA_ODR=0x1<<5; //PA5高电平
GPIOB_ODR=0x0<<9; //PB9低电平
GPIOC_ODR=0x1<<14; //PC14高电平
}
void C_LED_LIGHT()
{
GPIOA_ODR=0x1<<5; //PA5高电平
GPIOB_ODR=0x1<<9; //PB9高电平
GPIOC_ODR=0x0<<14; //PC14低电平
}
//------------------------主函数--------------------------
int main(){
RCC_APB2ENR|=1<<2; //APB2-GPIOA外设时钟使能
RCC_APB2ENR|=1<<3; //APB2-GPIOB外设时钟使能
RCC_APB2ENR|=1<<4; //APB2-GPIOC外设时钟使能
GPIOA_CRL&=0xFFF0FFFF; //设置位清零
GPIOA_CRL|=0X00200000; //PA5推挽输出,把第23、22、21、20位变为0010
GPIOB_CRH&=0xFF0FFFFF; //设置位清零
GPIOB_CRH|=0x00000020; //PB9推挽输出,把第7、6、5、4变为0010
GPIOC_CRH&=0xFF0FFFFF; //设置位清零
GPIOC_CRH|=0x02000000; //PC14推挽输出,把第27、26、25、24变为0010
GPIOA_ODR |= (1<<5);
GPIOB_ODR |= (1<<9); //设置灯的初始状态为灭
GPIOC_ODR |= (1<<14);
while(1)
{
A_LED_LIGHT();
Delay_ms(500);
B_LED_LIGHT();
Delay_ms(500);
C_LED_LIGHT();
Delay_ms(500);
}
}
4.实现成果展示:
二、标准外设库方式
引入标准外设库之后,先了解一下需要用到的函数。
1. RCC 外设时钟控制函数:第一个参数选择外设,第二个参数使能或失能。
2. GPIO库函数
第一个函数可以复位指定的GPIO外设
第二个函数复位AFIO外设
第三个函数,用结构体参数初始化GPIO口
第四个函数给结构体变量赋一个默认值
后面四个都是GPIO口的读取函数
再后面四个都是GPIO口的实现函数
3.实现过程:
- 打开GPIOA时钟
- 选择输出方式
- 选择引脚并设置输出速度
- 编写程序并烧录运行
#include "stm32f10x.h" // Device header
#include "Delay.h" //延时器
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//打开GPIOA的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_Out_PP; //选择输出方式 这里是推挽输出
//推挽输出就是可以输出高低电频,直接驱动小功耗器件
GPIO_InitStructure.GPIO_Pin= GPIO_Pin_0|GPIO_Pin_3|GPIO_Pin_5; //选择引脚 初始化
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz; //选的50MHz的输出速度
GPIO_Init(GPIOA,&GPIO_InitStructure); //把GPIO_InitStructure放在GPIO_Init函数的第二个参数上,初始化就完成了
while(1){
GPIO_Write(GPIOA,GPIO_Pin_0);
Delay_ms(500);
GPIO_Write(GPIOA,GPIO_Pin_3);
Delay_ms(500);
GPIO_Write(GPIOA,GPIO_Pin_5);
Delay_ms(500);
}
}
4.遇到的问题
流水灯的循环点亮部分的代码原本的写法是这样的:
GPIO_Write(GPIOA,~0x0001);
Delay_ms(500);
老师指出标准的写法是直接写引脚名:
GPIO_Write(GPIOA,GPIO_Pin_0);
Delay_ms(500);