单片机:第二讲 用STM32点亮一个LED灯

本文最后更新于:3 个月前

单片机的第一步都是从点亮一个LED灯开始的,就跟学习一门新的语言从输出Hello World!开始一样。

硬件以及软件要求

硬件

推荐使用的芯片是STM32C8T6,因为这一款芯片的教程比较多,在遇到不懂的时候,非常容易就可以在网上查阅到它的资料。直接淘宝购买STM32最小系统板就可以了。这里就不贴链接了,自己淘宝看着办吧。
其他外设暂时不需要,以后再说。

软件

现在使用的代码都是高级语言,机器是无法识别的,一般STM32使用的高级语言是C语言。C语言相对于其他语言来说,更接近于机器语言,对于面向硬件编程来说有着不可比拟的优势,当然其他语言不是说不可以,而是没有这么方便。

C语言一般使用GCC作为编译器,C++一般使用G++编译器。
编译过程一般是4个步骤:1.预处理(Preprocessing), 2.编译(Compilation), 3.汇编(Assemble), 4.链接(Linking)。
GCC编译过程

由于单片机是无法直接识别高级语言的,需要先将高级语言编译为机器语言,如汇编等,才能运行。
但是一般来说,STM32的性能并不支持直接编译高级语言,所以我们需要先将代码在我们的电脑上编译之后再上传到STM32上。一些比如树莓派等高性能的单片机就可以直接运行代码,而不需要单独编译。

在STM32上编译一般会使用arm-none-eabi-gcc对代码进行编译。

Keil_V5 MDK-Arm

对于新手来说,这里推荐使用专业的软件如Keil_V5 MDK-Arm
直接点击下载,然后再下载一个破解器。破解一下就可以了。

然后我们再安装一下keil的stm32芯片包:链接:https://pan.baidu.com/s/1I7HkfhaPlB-8oJ8wkkhncw
密码:wyzi

送一个汉化补丁:https://www.lanzous.com/i8pb30h

烧写软件

烧写软件一般就是烧录编译好的文件(.hex)的软件,其实在Keil里面是支持使用各种烧写器来烧写代码的例如Jlink或者STlink来烧录,但是我都没有,所以就用USBtoTTL来烧录,这种烧录方式用的通信协议叫做UART,使用的电平标准是TTL标准,TTL标准是低电平为0,高电平为1(+5V电平)。RS-232标准是正电平为0,负电平为1(±15V电平)。我们电脑上是有直连的RS232电平接口,就是那个蓝色D型9针的接口
9b414ab3ec773de64c7f4e0920f4fea6_Center-2020-06-21
然后也可以使用USBtoTTL通过USB口来连接我们的单片机。
ea58e86265c235f2a9e736b76850b9d9_Center-2020-06-21
TTL转RS232一般会使用MAX232芯片,这个芯片可以将TTL和RS232电平信号互相转换。

烧录hex文件的话推荐使用FlyMCU来烧录:http://www.downcc.com/soft/365277.html

新建工程

新建项目

首先,需要创建一个用来存放代码的文件夹,然后按照如下结构新建文件夹,方便我们管理代码
STM32

在Doc中存放一般文档,例如说明文件等
在Libraies里面存放你的库文件,这个一般是由芯片制造商提供
在Project里面存放工程相关文件
在User里面存放我们自己写的代码

这里我放一个我已经建好的STM32F103C8T6工程模板,大家以后可以直接使用。
STM32F103工程模板

创建用户文件

如果你是使用模板的,当然也建议你使用模板。你可以不用做下面的内容了,但是需要了解原理,并且懂的创建文件。
首先直接点击左上角创建一个空白文件,然后再点击保存在User文件夹,将文件进行保存为main.c
然后右键左边的user文件夹,选择Manage Project Items…
text
再接着选择User再选择添加文件,然后将main.c文件添加进来
这就是添加源文件的基本步骤,可以不需要添加.h文件,只需要添加.c文件即可。添加文件步骤不再重复,每个.c文件都必须要添加。

tips:

  • 我们一般写嵌入式代码的步骤都是先定义需要用的函数,在对应的.h文件中写下,然后再去写.c文件去实现我们需要的函数。
  • 记得在每个文件的最后都需要加一行空行,虽然不知道为啥,但是加了绝对没有问题。

delay.h

1
2
3
4
5
6
7
8
9
10
#ifndef __DELAY_H
#define __DELAY_H
#include <stm32f10x.h>

void delay_init(u8 SYSCLK);
void delay_ms(u16 nms); //延迟毫秒函数
void delay_us(u32 nus); //延迟微秒函数

#endif

这个是延时函数的头文件,定义延时函数所要用到的三个函数。

delay.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <stm32f10x.h>
#include "delay.h"

static u8 fac_us=0;//us延时倍乘数
static u16 fac_ms=0;//ms延时倍乘数
//初始化延迟函数
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void delay_init(u8 SYSCLK)
{
SysTick->CTRL&=0xfffffffb;//bit2清空,选择外部时钟 HCLK/8
fac_us=SYSCLK/8;
fac_ms=(u16)fac_us*1000;
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对72M条件下,nms<=1864
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL=0x01 ; //开始倒数
do
{
temp=SysTick->CTRL;
}
while(temp&0x01&&!(temp&(1<<16)));//等待时间到达
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
//延时nus
//nus为要延时的us数.
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL=0x01 ; //开始倒数
do
{
temp=SysTick->CTRL;
}
while(temp&0x01&&!(temp&(1<<16)));//等待时间到达
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}

延迟函数,调用外部时钟来实现计时。
仔细研究就会发现其实这个函数非常简单,就是计算外部时钟的震荡次数来实现延时的,所以这个函数的延时与我们设置的晶振的评率有关,如果发现延时不对,那就是晶振频率不对,重新设置一下就好了,如果你嫌麻烦,使用软件延时也是可以的,STM32是属于ARM CortexM3指令,所以他的指令周期是1.25MIPS/MHz,所以一个指令周期大约是1.25/72MHz=0.0173us。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//微秒级的延时
void delay_us(u16 time)
{
u16 i=0;
while(time--)
{
i=58;
while(i--) ;
}
}
//毫秒级的延时
void delay_ms(u16 time)

u16 i=0;
while(time--)
{
i=57800;
while(i--) ;
}
}

led.h

1
2
3
4
5
6
7
8
9
10
#ifndef __LED_H
#define __LED_H

//LED端口定义
#define LED0 BIT_ADDR(GPIOA_ODR_Addr,8)// PA8
#define LED1 BIT_ADDR(GPIOD_ODR_Addr,2)// PD2

void LED_Init(void);//初始化
#endif

led.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stm32f10x.h>
#include "led.h"

//初始化PA8和PD2为输出口.并使能这两个口的时钟
//LED IO初始化
void LED_Init(void)
{
RCC->APB2ENR|=1<<2; //使能PORTA时钟
RCC->APB2ENR|=1<<5; //使能PORTD时钟

GPIOA->CRH&=0XFFFFFFF0;
GPIOA->CRH|=0X00000003;//PA8 推挽输出
GPIOA->ODR|=1<<8; //PA8 输出高

GPIOD->CRL&=0XFFFFF0FF;
GPIOD->CRL|=0X00000300;//PD.2推挽输出
GPIOD->ODR|=1<<2; //PD.2输出高
}

这个就是初始化LED灯,至于哪个引脚是LED灯的引脚,可以自己参考单片机的原理图,而且如何使能引脚寄存器,也可以在手册上找到。

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stm32f10x.h>

#include "delay.h"
#include "led.h"

int main(void)
{
Stm32_Clock_Init(9); //系统时钟设置
delay_init(72); //延时初始化
LED_Init(); //初始化与LED连接的硬件接口
while(1)
{
LED0=0;
LED1=0;
delay_ms(3000);
LED0=1;
LED1=0;
delay_ms(3000);
}
}

最后就是执行函数了,先初始化,再使用while(1)循环,我们每个单片机基本上都有一个while(1)循环,用来保证程序的正常运行。

编译+烧录文件

编译

在编译之前,我们需要进行一下简单的设置,用来告诉编译器,我们正在做什么,需要怎样的输出。首先点击Keil上的魔术棒,
text
然后点击输出,勾选创建hex文件,再点击C/C++,定义中输入STM32F10X_MD,USE_STDPERIPH_DRIVER

然后再点击编译运行就,看到creating hex file…就可以了,有Error就解决一下,Warning可以不用管。
text

烧录

在Project/Object中可以找到hex文件。
然后打开FlyMcu,插上你的STM32的USB转TTL线到电脑上,连接如图,记得TXD和RXD反接。
text
然后我们打开FlyMcu,然后点击搜索串口,再点击擦除芯片。
这个时候我们会发现,窗口显示芯片超时无应答,无法连接
text

一般新手遇到这个问题就会百思不得其解,就是没弄懂芯片为啥不响应,甚至会怀疑是芯片出问题了。
去年就是这个问题,杨俊彩来找我,我也是弄了一晚上没有弄明白为啥,然后我昨天查阅了资料才明白,在STM32运行代码之后会对芯片进行一个锁死,不再允许新的代码写入,这个时候,我们只需要将BT0引脚置为高电平,就可以强制写入芯片了。一般会送一个键帽。键帽的作用就是这样。或者你用一根线来连接BT0和3.3V引脚也是可以的。
如果这个时候还是无法写入,再按一下RST键,让芯片重置就可以写入了。

text

这个时候就可以看到STM32上的两个引脚正在闪烁了,如果有其他问题,多试试拔线和按RST键,毕竟编程嘛,主要靠玄学。

如果还有啥不懂得问题的话,可以在评论区提出,我尽量一一解答。