STM 32 —— Hello World(寄存器方式实现流水灯)

STM 32 —— Hello World (寄存器方式实现流水灯)

STM 32 上的 C 程序,与我们平常所写的 C 程序有所不同,我们平时缩写的 C 程序可以在命令行或编译器中看到输出结果,而 STM 32 中的 C 程序,我们在电脑上运行时只能看到程序的运行状态,所以 STM 32 ,中一般的最简单的 Hello World 程序就是点亮流水灯

注意:创建项目时与调试运行不同,这里只选择 CORE ,不选择 Startup :

image

原理

点亮流水灯的原理很简单,只需要确定所使用的引脚,并完成对应引脚上时钟、端口以及寄存器的配置,即可完成点灯的程序

程序设计

引脚控制

实验要求分别在 GPIOA-5、GPIOB-9、GPIOC-14 这三个引脚上控制 LED 灯(最高时钟 2Mhz)

首先要确定这三个引脚的地址

基地址如下:

image

偏移地址如下:

image

image

image

寄存器配置如下:

// 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 :

image

在 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 时钟

时钟位置如图:

image

初始状态下,小灯泡为灭,通过循环,使小灯泡可以按顺序延时点亮,实现一种流水灯的效果,具体代码如下:

// 这里以在 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;
	}
	
}

单片机接线图

单片机接线如下:

image

image

注意:rxd 是输入信号,txd 是输出信号,输入信号使用的引脚是 a09 ,输出信号使用的引脚是 a10,接反后在读取芯片的时候无法读取到全部芯片内容,错误图片如下:

image

烧录

首先需要生成相应的程序,在输出中选择生成 hex 文件:

image

生成的文件会在如下位置:

image

然后我们使用 FlyMu ,单片机在线编程网使用最多的 usb 转 ttl 烧录工具

官方网址:单片机在线编程网

下载链接:FlyMu 官网下载链接

界面及使用方法如下:

image

注意:我们再烧录的时候一定要在 usb 转 ttl 设备插入电脑之前。将 Boot0 设置为 1 :

image

然后在烧录完成之后,拔出 usb ,将 Boot0 切换回 0 之后再供电才能正常运行程序

image

运行结果

image

警告分析

出现如下警告:

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 的版本,如果报错,请自行修改,编译器版本修改位置如下:

image


参考文档

  1. STM32F103C8T6实现流水灯

  2. stm32寄存器实现流水灯

posted @ 2022-10-09 21:11  ppqppl  阅读(307)  评论(0编辑  收藏  举报