STM 32 —— Hello World(寄存器方式实现流水灯)
STM 32 —— Hello World (寄存器方式实现流水灯)
STM 32 上的 C 程序,与我们平常所写的 C 程序有所不同,我们平时缩写的 C 程序可以在命令行或编译器中看到输出结果,而 STM 32 中的 C 程序,我们在电脑上运行时只能看到程序的运行状态,所以 STM 32 ,中一般的最简单的 Hello World 程序就是点亮流水灯
注意:创建项目时与调试运行不同,这里只选择 CORE ,不选择 Startup :
原理
点亮流水灯的原理很简单,只需要确定所使用的引脚,并完成对应引脚上时钟、端口以及寄存器的配置,即可完成点灯的程序
程序设计
引脚控制
实验要求分别在 GPIOA-5、GPIOB-9、GPIOC-14 这三个引脚上控制 LED 灯(最高时钟 2Mhz)
首先要确定这三个引脚的地址
基地址如下:
偏移地址如下:
寄存器配置如下:
// 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_CRL *((unsigned volatile int*)0x40010C00)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
// GPIOC配置寄存器
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
#define GPIOC_ODR *((unsigned volatile int*)0x4001100C)
设置延时
由于我们的要求是一个小灯泡亮起后 1s 熄灭,在熄灭 1s 后,下一个小灯泡亮起,所以这里就要用到延时函数,那么延时函数改怎么写?这里主要介绍两种延时方法
普通延时
普通延时法是 STM32 最简单的一种延时方法,就是让单片机做一些五负案金腰带事情来打发时间,一般是通过循环来消耗时间,达到一种延时的目的,但是这种延时一般与单片机芯片的性能有关,延时不是很准确
由芯片数据手册可以指导内部 APB2 时钟频率最大值是 72MHz :
在 STM32 中最常用的延时一共有两种,delay_ms (毫秒级延时) 和 delay_us (微秒级延时) ,代码分别如下:
// 粗延时函数,微秒级,1s = 1000000μs
void delay_us(u32 time){
u32 i;
while(time--){
i = 800; // 自行定义合理数字
while(i--){
// 由于循环中的变量的声明和运算,也会无形中影响循环的速度,所以,这里.什么都不用写
}
}
}
// 毫秒级延时
void delay_ms(u32 time){
u32 i;
while(time--){
i = 12000; // 自行定义合理数字
while(i--){
// 由于循环中的变量的声明和运算,也会无形中影响循环的速度,所以,这里.什么都不用写
}
}
}
SysTick 定时器延时
目前还没有深入了解 SysTick 定时器,后续学习会持续更新
设置小灯泡状态
在对小灯泡进行设置之前,需要给时钟使能(即开启时钟):
// 时钟使能
RCC_APB2ENR |= (1<<2);// 开启 GPIOA 时钟
RCC_APB2ENR |= (1<<3); // 开启 GPIOB 时钟
RCC_APB2ENR |= (1<<4); // 开启 GPIOC 时钟
时钟位置如图:
初始状态下,小灯泡为灭,通过循环,使小灯泡可以按顺序延时点亮,实现一种流水灯的效果,具体代码如下:
// 这里以在 GPIOA-5 点亮小灯泡为例
GPIOA_CRL &= 0xFF0FFFFF; // 清空控制 PA5 的端口位
// F 的位置表示的是对应引脚的位置上的电压,从右向左 CRL 分别为1、2、……、8,CRH 分别为9、10、……、16
GPIOA_CRL |= 0x00300000; // PB5 推挽输出(和电压设置有关), 绿灯
GPIOA_ODR |= 1<<5; // 设置灯初始状态为关闭
代码中使用了 “&=~”、“|=” 这种操作方法是为了避免影响到寄存器中的其它位,因为寄存器不能按位读写,假如我们直接给 CRL 寄存器赋值:GPIOB_CRL = 0x0000001; 这时 CRL 的的低 4 位被设置成 “0001” 输出模式,但其它 GPIO 引脚就有意见了,因为其它引脚的 MODER 位都已被设置成输入模式
设置好小灯泡的初始状态之后,就只需要通过循环点亮小灯泡即可:
while(1){
// A灯
GPIOA_ODR &= ~(1<<5); //PB5低电平,因为是置0,所以用按位与
delay(1000);
GPIOA_ODR |= 1<<5; // 设置高电位,点亮
// B灯
GPIOB_ODR &= ~(1<<9);
delay(1000);
GPIOB_ODR |= 1<<9;
// C灯
GPIOC_ODR &= ~(1<<14);
delay(1000);
GPIOC_ODR |= 1<<14;
}
完整程序
#include "stm32f10x.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 SystemInit(){}
void delay(u32 time){
u32 i = 0;
while(time--){
i = 2000;
while(i--){
}
}
}
int main(void){
RCC_APB2ENR |= (1<<2); // GPIOA 时钟使能
RCC_APB2ENR |= (1<<3); // GPIOB 时钟使能
RCC_APB2ENR |= (1<<4); // GPIOC 时钟使能
GPIOA_CRL &= 0xFF0FFFFF; // 清空控制 PA5 的端口位
// F 的位置表示的是对应引脚的位置上的电压,从右向左 CRL 分别为1、2、……、8,CRH 分别为9、10、……、16
GPIOA_CRL |= 0x00300000; // PB5 推挽输出(和电压设置有关), 绿灯
//GPIOA_ODR &= ~(1<<5);
GPIOA_ODR |= 1<<5; // 设置灯初始状态为关闭
GPIOB_CRH &= 0xFFFFFF0F;
GPIOB_CRH |= 0x00000020; // 红灯
//GPIOA_ODR &= ~(1<<9);
GPIOB_ODR |= 1<<9;
GPIOC_CRH &= 0xF0FFFFFF;
GPIOC_CRH |= 0x02000000; // 黄灯
//GPIOC_ODR &= ~(1<<14);
GPIOC_ODR |= 1<<14;
while(1){
GPIOA_ODR &= ~(1<<5); //PB5低电平,因为是置0,所以用按位与
delay(1000);
GPIOA_ODR |= 1<<5; // 设置高电位
// 后面的两个灯的设置方式与上面相同
GPIOB_ODR &= ~(1<<9);
delay(1000);
GPIOB_ODR |= 1<<9;
GPIOC_ODR &= ~(1<<14);
delay(1000);
GPIOC_ODR |= 1<<14;
}
}
单片机接线图
单片机接线如下:
注意:rxd 是输入信号,txd 是输出信号,输入信号使用的引脚是 a09 ,输出信号使用的引脚是 a10,接反后在读取芯片的时候无法读取到全部芯片内容,错误图片如下:
烧录
首先需要生成相应的程序,在输出中选择生成 hex 文件:
生成的文件会在如下位置:
然后我们使用 FlyMu ,单片机在线编程网使用最多的 usb 转 ttl 烧录工具
官方网址:单片机在线编程网
下载链接:FlyMu 官网下载链接
界面及使用方法如下:
注意:我们再烧录的时候一定要在 usb 转 ttl 设备插入电脑之前。将 Boot0 设置为 1 :
然后在烧录完成之后,拔出 usb ,将 Boot0 切换回 0 之后再供电才能正常运行程序
运行结果
警告分析
出现如下警告:
MAIN\LED.c(63): warning: #1-D: last line of file ends without a newline
不用担心,这只是 keil5 中的一个 bug ,只需要在全部程序中的最后一行加一个换行即可
报错分析
如果我们的程序中只有源代码文件,没有启动文件,就会有一下报错:
.\Objects\LEDs.sct(7): error: L6236E: No section matches selector - no section to be FIRST/LAST.
只需要在标准库库中找到对应的启动文件添加到程序中即可
如果出现如下报错:
.\Objects\LEDs.axf: Error: L6218E: Undefined symbol SystemInit (referred from startup_stm32f10x_md.o).
说明没有定义系统初始化函数,只需要添加如下代码即可:
void SystemInit(){}
// 目前对于点灯这种简单程序函数中什么都不用写
最后说明一下,如果代码没有问题,但是还是持续出现报错,就要考虑编译器版本是否符合标准,这里使用的是 version 5 的版本,如果报错,请自行修改,编译器版本修改位置如下: