STM32F103 LED亮灭教程(版本1:寄存器地址控制)
实验目的:
1、了解stm32f103c8t6最小核心板的主要引脚接口;
2、掌握Keil 开发stm32程序的环境搭建和设置;
3、使用GPIO引脚,外接LED灯,编写程序让LED灯周期性亮灭。
STM32F10xxx参考手册链接:https://pan.baidu.com/s/10gACCMp8zm7OMX7J_mC4SQ
提取码:9962
1.了解STM32最小系统核心板的电路原理图,用Proteus 设计一个STM32最小系统板+LED流水灯实验原理图,仿真运行。
最小系统板的引脚原理图如下:
stm32最小系统板的电路原理图:
Proteus仿真(利用PA4,PB10,PC15分别点亮绿灯、蓝灯、红灯)
keil新建工程:
选择stm32f103c8系列芯片
勾选CORE和Startup
选择ST-Link Debugger并点击setting进入设置
勾选Reset and Run
Proteus仿真如下:
2.STM32最小系统核心板(STM32F103C8T6)+面板板+3只_(或更多)红绿蓝LED 搭建电路,使用GPIOA、GPIOB、GPIOC这3个端口控制LED灯,轮流闪烁,间隔时长1秒。
2.1程序设计思路以及GPIOx端口的各寄存器地址和详细参数
程序设计思路:
1.开启GPIO的时钟
2.初始化GPIO口
3.利用高低电平和延时函数实现流水灯
2.1.1开启GPIO时钟
GPIO端口地址
RCC寄存器地址
因为RCC起始地址为:0x40021000
APB2外设时钟使能寄存器的偏移地址是0x18
所以实际地址为:0x40021000+0x18= 0x40021018
使能GPIOA、GPIOB、GPIOC时钟代码如下:
#define RCC_APB2ENR (*(unsigned int *)0x40021018)
// 打开时钟
RCC_APB2ENR |= (1<<3); // 打开 GPIOB 时钟
RCC_APB2ENR |= (1<<4); // 打开 GPIOC 时钟
RCC_APB2ENR |= (1<<2); // 打开 GPIOA 时钟
2.1.2GPIO端口配置
GPIO的配置寄存器CRL和CRH
STM32的一组GPIO有16个IO口,比如GPIOA这一组,有GPIOA0~GPIOA15一共16个IO口。每一个IO口需要寄存器的4位用来配置工作模式。
那么一组GPIO就需要16×4=64位的寄存器来存放这一组GPIO的工作模式的配置,但STM32的寄存器都是32位的,所以只能使用2个32位的寄存器来存放了。CRL用来存放低八位的IO口(GPIOx0—GPIOx7)的配置,CRH用来存放高八位的IO口(GPIOx8—GPIOx15)的配置。
这两个寄存器的全称是:端口配置低寄存器(GPIOx_CRL) (x=A…E) 和 端口配置高寄存器(GPIOx_CRH) (x=A…E)
也就是每一组GPIO都有两个32位的寄存器是用来配置IO口的工作模式的。
我们都清楚STM32的GPIO有八种工作模式,4个二进制数可以组合出16种情况,而我们只需要8种就行了。至于4位数怎么组合是什么工作模式,我们看STM32的手册。
工作模式的配置
可以看出,4位中又分为了CNFy和MODEy(y表示这组GPIO的第几个IO口),现在我们分析这两个的作用:
MODEy:
00:输入模式(复位后的状态)
01:输出模式,最大速度10MHz
10:输出模式,最大速度2MHz
11:输出模式,最大速度50MHz
可以看出MODEy是用来配置是输出还是输入模式的一般是使用00和11这两种情况。00是输入模式,11是输出模式。
CNFy:
在输入模式(MODE[1:0]=00):
00:模拟输入模式
01:浮空输入模式(复位后的状态)
10:上拉/下拉输入模式
11:保留
在输出模式(MODE[1:0]>00):
00:通用推挽输出模式
01:通用开漏输出模式
10:复用功能推挽输出模式
11:复用功能开漏输出模式
这些就是CNFy的配置,配置具体的工作模式。配合MODEy就可以配置出所有的工作模式了。
比如我需要配置上拉输入模式,那么4位寄存器的配置就是CNFy【10】MODEy【00】
具体的假如我要将PB1端口设置为推挽输出模式,就应该用到GPIOx_CRL,将除了PB1端口的其它位的CNFy[1:0]填入11(保留)MODEy[1:0]位也填入11,两个连起来就是1111(对应十六进制的F),而CNF1[1:0]填入00(通用推挽) MODE1[1:0]填入11(输出模式50Mhz)即是0011,对应的十六进制为3
GPIOB_CRL&=0xffffff0f;//设置位清零
GPIOB_CRL|=0x00000030;//通用推挽输出,频率50Mhz
所以设置PA4、PB1、PC15的代码为:
GPIOA_CRL &= 0xfff0ffff; //设置位 清零
GPIOA_CRL|=0x00030000; //PA4推挽输出 频率50Mhz
GPIOB_CRL&=0xffffff0f;//设置位清零
GPIOB_CRL|=0x00000030;//PB1通用推挽输出,频率50Mhz
GPIOC_CRH &= 0x0fffffff; //设置位 清零
GPIOC_CRH|=0x30000000; //PC15推挽输出 频率50Mhz
2.1.3流水灯实现(C语言寄存器)
#define GPIOB_BASE 0x40010C00
#define GPIOC_BASE 0x40011000
#define GPIOA_BASE 0x40010800
#define RCC_APB2ENR (*(unsigned int *)0x40021018)
#define GPIOB_CRL (*(unsigned int *)0x40010C00)
#define GPIOC_CRH (*(unsigned int *)0x40011004)
#define GPIOA_CRL (*(unsigned int *)0x40010800)
#define GPIOB_ODR (*(unsigned int *)0x40010C0C)
#define GPIOC_ODR (*(unsigned int *)0x4001100C)
#define GPIOA_ODR (*(unsigned int *)0x4001080C)
void Delay_ms(volatile unsigned int);
void A_LED_LIGHT(void);
void B_LED_LIGHT(void);
void C_LED_LIGHT(void);
void Delay_ms( volatile unsigned int t)//毫秒级延时
{
unsigned int i;
while(t--)
for (i=0;i<800;i++);
}
void A_LED_LIGHT(){
GPIOA_ODR=0x1<<4; //PA4低电平
GPIOB_ODR=0x0<<1; //PB1高电平
GPIOC_ODR=0x0<<15; //PC15高电平
}
void B_LED_LIGHT(){
GPIOA_ODR=0x0<<4; //PA4高电平
GPIOB_ODR=0x1<<1; //PB1低电平
GPIOC_ODR=0x0<<15; //PC15高电平
}
void C_LED_LIGHT(){
GPIOA_ODR=0x0<<4; //PA4高电平
GPIOB_ODR=0x0<<1; //PB1高电平
GPIOC_ODR=0x1<<15; //PC15低电平
}
int main(){
// 开启时钟
RCC_APB2ENR |= (1<<3); // 开启 GPIOB 时钟
RCC_APB2ENR |= (1<<4); // 开启 GPIOC 时钟
RCC_APB2ENR |= (1<<2); // 开启 GPIOA 时钟
//初始化GPIO
GPIOA_CRL &= 0xfff0ffff; //设置位 清零
GPIOA_CRL|=0x00030000; //PA4推挽输出 频率50Mhz
GPIOB_CRL&=0xffffff0f;//设置位清零
GPIOB_CRL|=0x00000030;//PB1通用推挽输出,频率50Mhz
GPIOC_CRH &= 0x0fffffff; //设置位 清零
GPIOC_CRH|=0x30000000; //PC15推挽输出 频率50Mhz
// 3个LED初始化为不亮(即高点位)
GPIOA_ODR |= (1<<4);
GPIOB_ODR |= (1<<1);
GPIOC_ODR |= (1<<15);
while(1){
B_LED_LIGHT();
Delay_ms(10000);//延时1s
C_LED_LIGHT();
Delay_ms(10000);
A_LED_LIGHT();
Delay_ms(10000);
}
}
现象如下:
2.3修改码让PC13控制的LED也加入流水灯
接线图不变,配置GPIOC的代码如下:
#define GPIOB_BASE 0x40010C00
#define GPIOC_BASE 0x40011000
#define GPIOA_BASE 0x40010800
#define RCC_APB2ENR (*(unsigned int *)0x40021018)
#define GPIOB_CRL (*(unsigned int *)0x40010C00)
#define GPIOC_CRH (*(unsigned int *)0x40011004)
#define GPIOA_CRL (*(unsigned int *)0x40010800)
#define GPIOB_ODR (*(unsigned int *)0x40010C0C)
#define GPIOC_ODR (*(unsigned int *)0x4001100C)
#define GPIOA_ODR (*(unsigned int *)0x4001080C)
void Delay_ms(volatile unsigned int);
void A_LED_LIGHT(void);
void B_LED_LIGHT(void);
void C_LED_LIGHT(void);
void Delay_ms( volatile unsigned int t)
{
unsigned int i;
while(t--)
for (i=0;i<800;i++);
}
void A_LED_LIGHT(){
GPIOA_ODR=0x1<<4;
GPIOB_ODR=0x0<<1;
GPIOC_ODR=0x1<<13;
}
void B_LED_LIGHT(){
GPIOA_ODR=0x0<<4;
GPIOB_ODR=0x1<<1;
GPIOC_ODR=0x1<<13;
}
void C_LED_LIGHT(){
GPIOA_ODR=0x0<<4;
GPIOB_ODR=0x0<<1;
GPIOC_ODR=0x0<<13;
}
int main(){
RCC_APB2ENR |= (1<<3);
RCC_APB2ENR |= (1<<4);
RCC_APB2ENR |= (1<<2);
GPIOA_CRL &= 0xfff0ffff;
GPIOA_CRL|=0x00030000;
GPIOB_CRL&=0xffffff0f;
GPIOB_CRL|=0x00000030;
GPIOC_CRH &= 0xff0fffff;
GPIOC_CRH|=0x00300000;
GPIOA_ODR |= (1<<4);
GPIOB_ODR |= (1<<1);
GPIOC_ODR |= (1<<13);
while(1){
B_LED_LIGHT();
Delay_ms(10000);
C_LED_LIGHT();
Delay_ms(10000);
A_LED_LIGHT();
Delay_ms(10000);
}
}
现象如下:
总结
用寄存器进行编程需要依靠STM32F103参考手册去查找地址,编程方式比较原始,麻烦,但是能让我们懂得一些底层原理,帮助我们更好地理解和学习stm32
参考资料:
1.STM32 GPIO的配置寄存器(CRL、CRH)快速学习_gpioa->crh-CSDN博客
2.STM32F103的流水灯点亮版本1(寄存器地址操作)_pc13推挽输出-CSDN博客
3.【寻址方式】基地址与偏移地址的详细解释-CSDN博客
作者:落笔太慌张~