stm32基于寄存器和标准外设库的流水灯设计
实验任务1:以 STM32最小系统核心板(STM32F103C8T6)+面板板+3只_(或更多)红绿蓝LED 搭建电路,使用GPIOA/GPIOB/GPIOC端口控制LED灯,轮流闪烁,间隔时长1秒。
1)写出程序设计思路,包括GPIOx端口的各寄存器地址和详细参数;
2)用C语言寄存器方式编程实现,代码须有详细注解。
3)STM32最小系统核心板子出厂时已经焊接好了1个led灯(标注了PC13处),一般可通过此灯的点亮让编程者验证自己烧录的代码是否正常运行了。请查阅最小版电路原理图和相关资料,将这个灯也用在流水灯中,重编新程序。
`代码如下:
#include "stm32f10x.h" // Device header
#include "Delay.h"
void blow_up()
{
RCC->APB2ENR = 0x0000001c;
GPIOC->CRH = 0x00000000;
GPIOB->CRL = 0x00000000; //第1位为3
GPIOA->CRL = 0x00000000; //第5位为3
GPIOC->ODR = 0x0002000;
GPIOB->ODR = 0x0000020;
GPIOA->ODR = 0x0000002;
}
void lighten_C()
{
RCC->APB2ENR = 0x0000001c; //使所有时钟使能
GPIOC->CRH = 0x00300000; //配置高寄存器GPIOx_CRH上的PC13,模式:“输出模式,最大速度50MHz”
GPIOB->CRL = 0x00000000; //配置低寄存器GPIOx_CRH上的PB1,模式:“输入模式”
GPIOA->CRL = 0x00000000; //配置低寄存器GPIOx_CRH上的PA5,模式:“输入模式”
GPIOC->ODR = 0x0000000; //低位电平有效,点亮PC13
GPIOB->ODR = 0x0000020;
GPIOA->ODR = 0x0000002;
}
void lighten_B()
{
RCC->APB2ENR = 0x0000001c; //使所有时钟使能
GPIOC->CRH = 0x00000000; //配置高寄存器GPIOx_CRH上的PC13,模式:“输入模式”
GPIOB->CRL = 0x00000030; //配置低寄存器GPIOx_CRH上的PB1,模式:“输出模式,最大速度50MHz”
GPIOA->CRL = 0x00000000; //配置低寄存器GPIOx_CRH上的PA5,模式:“输入模式”
GPIOC->ODR = 0x0002000; //低位电平有效,点亮PB1
GPIOB->ODR = 0x0000000;
GPIOA->ODR = 0x0000002;
}
void lighten_A()
{
RCC->APB2ENR = 0x0000001c; //使所有时钟使能
GPIOC->CRH = 0x00000000; //配置高寄存器GPIOx_CRH上的PC13,模式:“输入模式”
GPIOB->CRL = 0x00000000; //配置低寄存器GPIOx_CRH上的PB1,模式:“输入模式”
GPIOA->CRL = 0x00300000; //配置低寄存器GPIOx_CRH上的PA5,模式:“输出模式,最大速度50MHz”
GPIOC->ODR = 0x0002000; //低位电平有效,点亮PA5
GPIOB->ODR = 0x0000020;
GPIOA->ODR = 0x0000000;
}
int main(void)
{
while(1)
{
lighten_C();
Delay_ms(500);
lighten_B();
Delay_ms(500);
lighten_A();
Delay_ms(500);
//blow_up();
}
}
解释如下:
1.配置RCC寄存器,来使能GPIOC、GPIOB、GPIOA的时钟 GPIO都是APB2的外设, 因此在APB2外设时钟使能寄存器RCC_APB2ENR中配置。 查阅参考手册第96页可看到在APB2外设时钟使能寄存器RCC_APB2ENR中, 第4位名称为IOPC EN,即这一位就是用来使能GPIOC时钟的。 第3位名称为IOPB EN,即这一位就是用来使能GPIOB时钟的。 第2位名称为IOPA EN,即这一位就是用来使能GPIOA时钟的。 查看对应位(第4位)的描述可知: 当第4位的值为1时,就打开GPIOC时钟, 当第4位的值为0时,就关闭GPIOC时钟。 当第3位的值为1时,就打开GPIOB时钟, 当第3位的值为0时,就关闭GPIOB时钟。 当第2位的值为1时,就打开GPIOA时钟, 当第2位的值为0时,就关闭GPIOA时钟。
因此,我们当前对于APB2外设时钟使能寄存器RCC_APB2ENR的配置为: 第4、3、2位置为1,其余位先不管置为0。 其2进制表示为:0000 0000 0000 0000 0000 0000 0001 1100 转换为16进制表示为:0 0 0 0 0 0 1 C
注:2进制转为16进制方法:把2进制数进行4位4位的分割,把每4位 代表的16进制数写出来即可
2.在配置完GPIOA、GPIOB、GPIOC的时钟后,我们要配置GPIO寄存器 查阅参考手册第28页可看到: GPIOC端口包含的地址范围:0x4001 1000 – 0x4001 13FF GPIOB端口包含的地址范围:0X4001 0C00 – 0x4001 0FFF GPIOA端口包含的地址范围:0x4001 0800 – 0x4001 0BFF 本次实验我们选择配置的寄存器:PC13(板子自带的)、PB1、PA5 显然,PC13位于高寄存器GPIOx_CRH上; PB1、PA5位于低寄存器GPIOx_CRL上
2.1 配置高寄存器GPIOx_CRH(这个x可以是A到E的任意一个字母)。 查阅参考手册第114页可看到在端口配置高寄存器GPIOx_CRH中, 第23、22位所代表的CNF13 与 第21、20位所代表的MODE13就是用来配置PC13口的。
查阅参考手册第114页表17、表18 查看对应位(第23、22位 与 第21、20位)的描述可知: 要想把CNF13配置为“通用推挽输出模式”,其值要设为00。 要想把MODE13配置为“输出模式,最大速度50MHz”,其值要设为11.因此,我们当前对于端口配置高寄存器GPIOx_CRH的配置为: 第23、22位置为00,第21、20位置为11,其余位先不管置为0。 其2进制表示为:0000 0000 0011 0000 0000 0000 0000 0000 转换为16进制表示为:0 0 3 0 0 0 0 0
2.2 配置低寄存器GPIOx_CRL(这个x可以是A到E的任意一个字母)。 查阅参考手册第114页可看到在端口配置低寄存器GPIOx_CRL中, 第7、6位所代表的CNF1 与 第5、4位所代表的MODE1就是用来配置PB1口的。 查阅参考手册第114页表17、表18 查看对应位(第7、6位 与 第5、4位)的描述可知: 要想把CNF1配置为“通用推挽输出模式”,其值要设为00。 要想把MODE1配置为“输出模式,最大速度50MHz”,其值要设为11。 因此,我们当前对于端口配置高寄存器GPIOx_CRH的配置为: 第7、6位置为00,第5、4位置为11,其余位先不管置为0。 其2进制表示为:0000 0000 0000 0000 0000 0000 0011 0000 转换为16进制表示为:0 0 0 0 0 0 3 0
2.3 配置低寄存器GPIOx_CRL(这个x可以是A到E的任意一个字母)。 查阅参考手册第114页可看到在端口配置低寄存器GPIOx_CRL中,。 第23、22位所代表的CNF5 与 第21、20位所代表的MODE5就是用来配置PA5口的。 查阅参考手册第114页表17、表18 查看对应位(第23、22位 与 第21、20位)的描述可知: 要想把CNF5配置为“通用推挽输出模式”,其值要设为00。 要想把MODE5配置为“输出模式,最大速度50MHz”,其值要设为11. 因此,我们当前对于端口配置高寄存器GPIOx_CRH的配置为: 第23、22位置为00,第21、20位置为11,其余位先不管置为0。 其2进制表示为:0000 0000 0011 0000 0000 0000 0000 0000 转换为16进制表示为:0 0 3 0 0 0 0 0
3. 在配置完端口配置寄存器后,我们再来配置端口输出数据寄存器 第3个寄存器,从而给PC13口输出数据。 在端口输出数据寄存器GPIOx_ODR中配置,这个x可以是A到E的任意一个字母。 查阅参考手册第115页可看到在端口输出数据寄存器GPIOx_ODR中, 第13位所代表的ODR13就是用来配置PC13口输出数据的。 查看对应位(第13)的描述可知, 要想PC13、PB1、PA5口输出数据,其值要设为1。 因此,我们当前对于输出数据寄存器GPIOx_ODR的配置为: 第13、1、5位置为1,其余位先不管置为0。 其2进制表示为:0000 0000 0000 0000 0010 0000 0010 0010 转换为16进制表示为:0 0 0 0 2 0 2 2
注:这个灯是低电平点亮的, 所以00002022表示三个灯全灭,00000000表示三个灯全亮。
实验任务2:在实验1的基础上,改用标准外设库方式使用某个端口GPIOx端口管脚控制几个LED灯,轮流闪烁,间隔时长1秒。
1)写出工程项目创建文件夹、添加STM32标准外设库文件(*.c,*.h)的详细过程;
2)LED灯的亮/灭周期是通过软件循环延时完成的,其准确周期大致是多少呢?
在没有示波器条件下,可以使用Keil的软件仿真逻辑分析仪功能观察管脚的时序波形,更方便动态跟踪调试和定位代码故障点。 请用此功能观察GPIO端口的输出波形,并分析时序状态正确与否、高低电平转换周期(LED闪烁周期)实际为多少。
工程创建过程:
1、在桌面或其他盘新建一个文件夹并命名。
2、在该文件夹内再新建6个文件,分别命名为project、CMSIS、startup、User、Hardware、Driver。
project用于存放工程相关文件
CMSIS用于存放核心文件
startup用于存放启动文件
User用于存放main.c
Hardware用于存放自己编写的与外设相关的C文件和头文件
Driver 存放库函数C文件和头文件
3、复制文件
(1)复制核心文件core_cm3.c、core_cm3.h到CMSIS文件夹
(2)复制启动文件startup_stm32f10x_md.s到startup文件夹
(3)复制STM32F10x_StdPeriph_Driver文件里的inc和src两个文件到Driver。
(4)在User新建一个文本文档,重命名为main.c。并把stm32f10x.h、system_stm32f10x.c和system_stm32f10x.h复制到该文件。再把STM32F10x_StdPeriph_Lib_V3.5.0->Project->STM32F10x_StdPeriph_Template->文件下的stm32f10x_conf.h文件复制到User中。
4、打开Keil新建工程
(1)Project->New uvision Project
(2)选择芯片型号,更改工程名,将之前的文件包含进工程中。
(3)设置输出选项,生成HEX文件,烧录到到的单片机Flash中运行。
代码与解释如下:
```
#include "stm32f10x.h" // Device header
#include "Delay.h"
void lighten_C()
{
GPIO_InitTypeDef GPIO_Initstructure_C;//定义端口配置结构体变量
GPIO_InitTypeDef GPIO_Initstructure_B;
GPIO_InitTypeDef GPIO_Initstructure_A;
GPIO_Initstructure_C.GPIO_Mode = GPIO_Mode_Out_PP; //设置GPIOC端口为推挽输出,点亮引脚PC13的灯
GPIO_Initstructure_C.GPIO_Pin = GPIO_Pin_13; //点亮引脚PC13的灯
GPIO_Initstructure_C.GPIO_Speed = GPIO_Speed_50MHz;//速度为50MHz
GPIO_Initstructure_B.GPIO_Mode = GPIO_Mode_AIN; //设置GPIOB端口为全部输入,不点亮点亮引脚PB1的灯
GPIO_Initstructure_B.GPIO_Pin = GPIO_Pin_1; //不点亮引脚PB1的灯
GPIO_Initstructure_B.GPIO_Speed = GPIO_Speed_50MHz;//速度为50MHz
GPIO_Initstructure_A.GPIO_Mode = GPIO_Mode_AIN; //设置GPIOA端口为全部输入,不点亮点亮引脚PA5的灯
GPIO_Initstructure_A.GPIO_Pin = GPIO_Pin_5; //不点亮引脚PA5的灯
GPIO_Initstructure_A.GPIO_Speed = GPIO_Speed_50MHz;//速度为50MHz
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); //使能GPI0C端口的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能GPIOB端口的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能GPIOA端口的时钟
//配置初始化函数GPIO_Init(),这个函数包含两个参数,第一个参数指明对哪个端口进行初始化,第二个参数是个结构体,用于指明如何初始化
GPIO_Init(GPIOC,&GPIO_Initstructure_C);
GPIO_Init(GPIOB,&GPIO_Initstructure_B);
GPIO_Init(GPIOA,&GPIO_Initstructure_A);
}
void lighten_C()
{
GPIO_InitTypeDef GPIO_Initstructure_C;//定义端口配置结构体变量
GPIO_InitTypeDef GPIO_Initstructure_B;
GPIO_InitTypeDef GPIO_Initstructure_A;
GPIO_Initstructure_C.GPIO_Mode = GPIO_Mode_Out_PP; //设置GPIOC端口为推挽输出,点亮引脚PC13的灯
GPIO_Initstructure_C.GPIO_Pin = GPIO_Pin_13; //点亮引脚PC13的灯
GPIO_Initstructure_C.GPIO_Speed = GPIO_Speed_50MHz;//速度为50MHz
GPIO_Initstructure_B.GPIO_Mode = GPIO_Mode_AIN; //设置GPIOB端口为全部输入,不点亮点亮引脚PB1的灯
GPIO_Initstructure_B.GPIO_Pin = GPIO_Pin_1; //不点亮引脚PB1的灯
GPIO_Initstructure_B.GPIO_Speed = GPIO_Speed_50MHz;//速度为50MHz
GPIO_Initstructure_A.GPIO_Mode = GPIO_Mode_AIN; //设置GPIOA端口为全部输入,不点亮点亮引脚PA5的灯
GPIO_Initstructure_A.GPIO_Pin = GPIO_Pin_5; //不点亮引脚PA5的灯
GPIO_Initstructure_A.GPIO_Speed = GPIO_Speed_50MHz;//速度为50MHz
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); //使能GPI0C端口的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能GPIOB端口的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能GPIOA端口的时钟
//配置初始化函数GPIO_Init(),这个函数包含两个参数,第一个参数指明对哪个端口进行初始化,第二个参数是个结构体,用于指明如何初始化
GPIO_Init(GPIOC,&GPIO_Initstructure_C);
GPIO_Init(GPIOB,&GPIO_Initstructure_B);
GPIO_Init(GPIOA,&GPIO_Initstructure_A);
}
void lighten_B()
{
GPIO_InitTypeDef GPIO_Initstructure_C;//定义端口配置结构体变量
GPIO_InitTypeDef GPIO_Initstructure_B;
GPIO_InitTypeDef GPIO_Initstructure_A;
GPIO_Initstructure_C.GPIO_Mode = GPIO_Mode_AIN; //设置GPIOC端口为全部输入,不点亮引脚PC13的灯
GPIO_Initstructure_C.GPIO_Pin = GPIO_Pin_13; //不点亮引脚PC13的灯
GPIO_Initstructure_C.GPIO_Speed = GPIO_Speed_50MHz;//速度为50MHz
GPIO_Initstructure_B.GPIO_Mode = GPIO_Mode_Out_PP; //设置GPIOB端口为推挽输出,点亮引脚PB1的灯
GPIO_Initstructure_B.GPIO_Pin = GPIO_Pin_1; //点亮引脚PB1的灯
GPIO_Initstructure_B.GPIO_Speed = GPIO_Speed_50MHz;//速度为50MHz
GPIO_Initstructure_A.GPIO_Mode = GPIO_Mode_AIN; //设置GPIOA端口为全部输入,不点亮引脚PA5的灯
GPIO_Initstructure_A.GPIO_Pin = GPIO_Pin_5; //不点亮引脚PA5的灯
GPIO_Initstructure_A.GPIO_Speed = GPIO_Speed_50MHz;//速度为50MHz
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); //使能GPI0C端口的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能GPIOB端口的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能GPIOA端口的时钟
//配置初始化函数GPIO_Init(),这个函数包含两个参数,第一个参数指明对哪个端口进行初始化,第二个参数是个结构体,用于指明如何初始化
GPIO_Init(GPIOC,&GPIO_Initstructure_C);
GPIO_Init(GPIOB,&GPIO_Initstructure_B);
GPIO_Init(GPIOA,&GPIO_Initstructure_A);
}
void lighten_A()
{
GPIO_InitTypeDef GPIO_Initstructure_C;//定义端口配置结构体变量
GPIO_InitTypeDef GPIO_Initstructure_B;
GPIO_InitTypeDef GPIO_Initstructure_A;
GPIO_Initstructure_C.GPIO_Mode = GPIO_Mode_AIN; //设置GPIOC端口为全部输入,不点亮引脚PC13的灯
GPIO_Initstructure_C.GPIO_Pin = GPIO_Pin_13; //不点亮引脚PC13的灯
GPIO_Initstructure_C.GPIO_Speed = GPIO_Speed_50MHz;//速度为50MHz
GPIO_Initstructure_B.GPIO_Mode = GPIO_Mode_AIN; //设置GPIOB端口为全部输入,不点亮引脚PB1的灯
GPIO_Initstructure_B.GPIO_Pin = GPIO_Pin_1; //不点亮引脚PB1的灯
GPIO_Initstructure_B.GPIO_Speed = GPIO_Speed_50MHz;//速度为50MHz
GPIO_Initstructure_A.GPIO_Mode = GPIO_Mode_Out_PP; //设置GPIOA端口为推挽输出,点亮引脚PA5的灯
GPIO_Initstructure_A.GPIO_Pin = GPIO_Pin_5; //点亮引脚PA5的灯
GPIO_Initstructure_A.GPIO_Speed = GPIO_Speed_50MHz;//速度为50MHz
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); //使能GPI0C端口的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能GPIOB端口的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能GPIOA端口的时钟
//配置初始化函数GPIO_Init(),这个函数包含两个参数,第一个参数指明对哪个端口进行初始化,第二个参数是个结构体,用于指明如何初始化
GPIO_Init(GPIOC,&GPIO_Initstructure_C);
GPIO_Init(GPIOB,&GPIO_Initstructure_B);
GPIO_Init(GPIOA,&GPIO_Initstructure_A);
}
int main(void)
{
while(1)
{
lighten_C();
Delay_ms(500);
lighten_B();
Delay_ms(500);
lighten_A();
Delay_ms(500);
}
}
```
**仿真分析:
运行结果:
VID
仿真分析:
`本次流水灯共用到三个端口:PC13、PB1、PA5
仿真结果:
心得体会:
在用配置寄存器的方式来进行点灯的操作时, 需要不断地查手册来了解每个寄存器的每一位究竟是来干什么的, 而且,在配置端口时,我们把没有用到的端口均一股脑地配置为了0;而没有关心这个端口是否原来就配置的有值!我们这种操作是可能会影响到寄存器其余端口的原有配置的。我们希望在对我们要改动的端口进行改动的同时,不去改变其余端口,这就需要&= 和 |=的操作,这就更加麻烦了。
参考文章:手把手教学一文在Keil5创建工程模版(以STM32F103C8T6点灯为例)_keil怎么新建工程并生成hex-CSDN博客
基于寄存器与基于固件库的stm32 LED流水灯_keil基于寄存器的开发-CSDN博客
作者:一脸dio样754