重要的内容写在前面:
- 该系列是以up主江协科技的STM32视频教程为基础写下去的,大部分内容都参考了老师的课件,对于一些个人认为比较重要但是老师仅口述的部分,笔者都有用文字的方式记录并标出了重点。
- 文中的图片基本都来源于老师的课件以及开发板和芯片的手册,粘贴过来是为了方便阅读。
- 如果有条件的可以先学习一些相关课程再去看STM32的教程,学起来会更加轻松(不太建议零基础开始直接STM32,听起来可能会有点困难,可以先学51单片机),相关课程有数字电路(强烈推荐先学数电,不然可能会有很多地方理解起来很困难)、模拟电路、计算机组成原理(像寄存器、存储器、中断等在这门课里有很详细的介绍)、计算机网络等。
- 如有错漏欢迎指出。
视频链接:
[1-1] 课程简介
一、STM32基础知识
1、STM32是ST公司基于ARM Cortex-M内核开发的32位微控制器,STM32常应用在嵌入式领域,如智能车、无人机、机器人、无线通信、物联网、工业控制、娱乐电子产品等。STM32功能强大、性能优异、片上资源丰富、功耗低,是一款经典的嵌入式微控制器。
2、ARM既指ARM公司,也指ARM处理器内核。ARM公司是全球领先的半导体知识产权(IP)提供商,全世界超过95%的智能手机和平板电脑都采用ARM架构,ARM公司设计ARM内核,半导体厂商完善内核周边电路并生产芯片。
3、STM32F103C8T6:
系列:主流系列STM32F1
内核:ARM Cortex-M3
主频:72MHz
RAM:20K(SRAM)
ROM:64K(Flash)
供电:2.0~3.6V(标准3.3V)
封装:LQFP48
4、片上资源/外设:(注意,不是每个系列都有下列全部的资源/外设;开发板上的外设会在后面逐一详细介绍,这里有个印象即可)
(1)表中前两个外设是ARM Cortex-M3内核中的外设,剩下的都是内核外的外设。
(2)NVIC是内核里面用于管理中断的设备,比如配置中断的优先级。
(3)SysTick是内核中的一个定时器,主要用来给操作系统提供定时服务(可以用这个定时器完成Delay函数的功能,Delay函数就是延时函数)。
(4)RCC可以对系统的时钟进行配置,以及使能各个模块的时钟(在STM32中,其它外设在刚上电的情况下默认是没有时钟的,如果不使能各模块相应的时钟,那么将无法对外设进行操作)。
(5)GPIO就是通用的I/O口,可以通过GPIO进行点灯、读取按键状态等操作。
(6)AFIO是复用I/O口,它可以完成复用功能端口的重定义以及中断端口的配置。
(7)EXTI是外部中断,配置好外部中断后,当引脚有电平变化时就可以触发中断,让CPU来处理任务。
(8)TIM是定时器,它可以说是整个STM32中最常用、功能最多的外设,TIM分为高级定时器(最复杂)、通用定时器(最常用)和基本定时器三种类型,这个定时器不仅可以完成定时中断的任务,还可以完成测频率、生成PWM波形(看过51单片机教程的应该对这个很熟悉)、配置成专用的编码器接口等功能。
(9)ADC是模数转换器,STM32内置了12位的AD转换器,可以直接读取I/O口的模拟电压值,无需外部连接AD芯片,使用起来更加方便。
(10)DMA是直接内存访问,它可以帮助CPU完成搬运大量数据的繁杂任务。(学过《计算机组成原理》的应该对这个非常熟悉)
(11)USART是同步或者异步串口,我们平常用的UART是异步串口。
(12)I2C和SPI是非常常用的两种通信协议(所谓协议就是约定,主机和从机之间的数据传输不是一气呵成的,而是一位一位进行传输的,双方需要约定好什么信号代表什么信息),STM32页内置了它们的控制器,可以用硬件来输出时序波形(用通用I/O口来模拟时序波形也是没有问题的,这个和在学习51单片机时敲的时序相关代码差不多)。
(13)CAN和USB也是通信协议,CAN一般用于汽车领域,USB在生活中则更常见。
(14)RTC是实时时钟,在STM32内部完成年月日、时分秒的计时功能,而且可以接外部供电电池,即使单片机掉电也能继续运行一段不短的时间。
(15)CRC是一种数据的校验方式,用于判断数据的正确性(数据在传输过程中可能会出现错误)。
(16)PWR电源控制可以让芯片进入睡眠模式等状态以达到省电的目的。
(17)BKP备份寄存器是一段存储器,当系统掉电时仍可在备用电池的支持下保持数据,这个根据需要可以完成一些特殊的功能。
(18)IWDG和WWDG是看门狗,当单片机因为电磁干扰而死机或者程序设计不合理而出现死循环时,看门狗可以及时复位芯片,保证系统的稳定运行。
(19)DAC是数模转换器,它可以在I/O口直接输出模拟电压(ADC模数转换的逆过程)。
(20)SDIO是SD卡接口,可以用来读取SD卡;FSMC是可变静态存储控制器,可以用于扩展内存、用于某些硬件的操作或者配置成其它总线协议;USB OTG是USB主机接口,用OTG功能可以让STM32作为USB主机去读取其它USB设备。
5、系统结构:
(1)左上角的Cortex-M3内核引出了三条总线,分别是ICode指令总线、DCode数据总线和System系统总线,ICode指令总线和DCode数据总线主要用于连接Flash闪存,Flash中存储的就是我们编写的程序,ICode指令总线负责加载程序指令,DCode数据总线负责加载数据(比如常量和调试数据)。
(2)AHB(先进高性能)系统总线用于挂载主要外设;APB(先进外设总线)用于连接一般的外设。因为AHB和APB的总线协议、总线速度还有数据传送格式存在差异,所以二者之间需要加两个桥接来完成数据的转换和缓存。(性能比较:AHB>APB2>APB1;APB2和AHB一般同频率,都为72MHz,而APB1一般是36MHz)
6、引脚定义:
(1)表中表红色的是电源相关的引脚,标蓝色的是最小系统相关的引脚,标绿色的是I/O口、功能口引脚;S代表电源,I代表输入,O代表输出,I/O代表输入输出。
(2)I/O电平一列,标有“FT”的引脚代表能“容忍”5V的电压,未标有“FT”的引脚只能容忍3.3V的电压(除非加装电平转换电路)。
(3)主功能就是上电后默认的功能(一般和引脚名称相同);有些引脚除了主功能外还有默认复用功能,默认复用功能是I/O口上同时连接的外设功能引脚,软件在配置I/O口时需要选择使用通用功能还是复用功能;有的引脚甚至还有重定义功能,如果有两个功能同时复用在同一个I/O口上,而这两个功能又同时都需要实现,那么就可以把其中一个复用功能重映射到其它端口上(前提是被映射的端口其重定义列表中有对应的功能)。
(4)建议优先使用表中加粗的引脚。
7、启动配置:
(1)主闪存存储器模式是最常用的模式,BOOT0置为0(BOOT1置为1或0都可以),该模式下会正常执行Flash闪存里的程序。
(2)需要使用串口下载程序时就配置为系统存储器模式。
(3)内置SRAM用于进行程序调试。
8、最小系统电路:
9、C语言数据类型:
10、C语言宏定义:
(1)关键字:#define
(2)用途:用一个字符串代替一个数字,便于理解,防止出错;提取程序中经常出现的参数,便于快速修改。
(3)定义宏定义:
#define ABC 12345
(4)引用宏定义:
int a = ABC; //等效于int a = 12345;
11、C语言typedef:
(1)关键字:typedef
(2)用途:将一个比较长的变量类型名换个名字,便于使用(原来的名字仍可正常使用)。
(3)定义typedef:
typedef unsigned char uint8_t;
(4)引用typedef:
uint8_t a; //等效于unsigned char a;
12、C语言结构体:
(1)关键字:struct
(2)用途:数据打包,不同类型变量的集合。
(3)定义结构体变量:
struct{char x; int y; float z;} StructName;
因为结构体变量类型较长,所以通常用typedef更改变量类型名。
(4)引用结构体成员:
StructName.x = 'A';
StructName.y = 66;
StructName.z = 1.23;
或 pStructName->x = 'A'; //pStructName为结构体的地址 pStructName->y = 66;
pStructName->z = 1.23;
13、C语言枚举:
(1)关键字:enum
(2)用途:定义一个取值受限制的整型变量,用于限制变量取值范围;宏定义的集合。
(3)定义枚举变量:
enum{FALSE = 0, TRUE = 1} EnumName;
因为枚举变量类型较长,所以通常用typedef更改变量类型名。
(4)引用枚举成员:
EnumName = FALSE;
EnumName = TRUE;
二、新建一个工程
1、打开Keil5软件,在“Project”一栏找到“New μVision Project”,点击它。
2、建议在桌面单独为STM32建一个新文件夹,在该文件夹中存放不同的项目,每个项目再独自命名一个文件夹,生成的项目文件放在这个文件夹中(项目文件命名建议命为“project”)。
3、点击“保存”后,开始选择芯片型号,本教程采用的是STM32F103C8T6。
4、点击“OK”后,会弹出一个新建工程小助手,目前暂时可以不予理会,点击关闭即可。
5、在工程项目文件夹中新建一个“Start”文件夹,然后在资料包的固件库中找到如下文件,将它们全部复制到“Start”文件夹中。
6、继续在固件库里面找到下图所示的三个文件,也将它们复制到“Start”文件夹中。
7、继续在固件库里面找到下图所示的两个文件,也将它们复制到“Start”文件夹中。
8、按照下图所示将其名称修改为“Start”,然后根据图片提示往里面添加文件。
启动文件有很多个,选择哪一个由芯片型号决定,本教程按下图选择即可。
9、按照下图所示添加头文件路径,否则软件会找不到.h文件。
10、在项目文件夹中新建文件夹“User”(用于存放我们编写的代码文件),相应地,在Keil的项目中也添加一个同名的组,然后在组中添加代码文件(添加代码文件时路径要选择“User”)。
11、在main.c文件中添加如下代码,然后进行编译(一般点击蓝框所示的按钮即可),正常情况下是0错误0警告。
12、点灯:(重点认识库函数如何使用)
(1)按照下图所示将STM32最小系统版、STLINK用四根杜邦线按照插针附近的标识符接好,注意千万不能接错,特别是3.3V和GND两个脚,否则可能会把芯片烧坏。
(2)配置调试器。(图二勾上“Reset and Run”是为了方便调试,每下载一个程序到开发板上开发板都会自动复位,不需要我们去按复位按键;注意,图二下面还有一个“确定”键没有截进来,勾选“Reset and Run”要点击确定,否则设置不会被保存)
(3)单击下图所示按钮可以将程序下载到开发板中,目前开发板不会有任何现象。
(4)第一个点灯程序:在main.c文件中写入如下代码,然后进行编译,将程序下载到开发板中,可以看到开发板的一盏LED灯被点亮(与上一步的现象做对比)。(看不懂不用着急,后面都会有详细的解释)
#include "stm32f10x.h" // Device header
int main()
{
//通过直接配置寄存器点灯(需要查手册,而且寄存器位数多,很麻烦)
RCC->APB2ENR = 0x00000010; //打开GPIOC的时钟
GPIOC->CRH = 0x00300000;
GPIOC->ODR = 0x00000000;
while(1)
{
}
}
附:需要查阅手册的地方
(5)可以看到,仅仅只是一个点灯操作,如果通过直接配置寄存器的方法进行点灯,单单是查手册就已经很麻烦了,更何况一个寄存器还有32位,配置起来也很麻烦,这时可以通过库函数的方式进行配置。
在项目文件夹中新建“Library”文件夹,然后在固件库资料包中找到“inc”和“src”两个文件夹,将里面的.h文件和.c文件全部复制到“Library”中(同时在Keil的项目中也添加一个同名的组,然后在组中添加刚刚复制过来的全部文件)。
接着在资料包中找到下图所示的三个文件,将它们复制到“User”文件夹中,同时把他们添加到Keil的项目的User组中。
接着按照下图所示往Define上写入“USE_STDPERIPH_DRIVER”,然后在头文件路径中添加“User”和“Library”,点击“OK”,至此基于库函数的工程就创建完毕了。
(6)在使用库函数时,首先确认函数的名称(一般函数的作用和其名称有很大关联),然后按照下图所示可以直接到达函数体,函数体上方有供用户选择的函数参数(param),它们基本都经过宏定义或者枚举“包装”,语意非常直观,用户根据名称直接复制使用即可,这样既能避免查手册的麻烦,还能使代码具有更强的可读性。(这个例子看不懂没关系,后面会有更详细的解释,目前知道如何使用库函数即可)
①使能GPIOC的时钟:使用的函数为RCC_APB2PeriphClockCmd。
最终根据步骤写出的代码:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
②配置端口模式:使用的函数为GPIO_Init,其函数参数中有结构体,使用该函数时需要先定义一个结构体变量,然后再给结构体赋值,最后才能调用这个函数。寻找结构体定义的方法和寻找函数定义一致,选中结构体类型名后右键跳转即可。(GPIO_Init的函数体不短,下图没有截全)
结构体中也有自己的参数,对于GPIOSpeed_TypeDef以及GPIOMode_TypeDef这种明显是枚举类型的参数,可以按照同样的方法进行跳转,很快就能找到可供用户选择的参数。
对于GPIO_Pin这种非枚举类型的参数,可以看到右侧的注释中有解释,官方已经给各个引脚进行了宏定义,要找到宏定义列表,可以选中“GPIO_Pins_define”后按下Ctrl+F,点击“Find Next”,很快就能找到引脚相关的宏定义,这些就是可供选择的引脚参数。(一般“@ref”后跟着的就是用于搜索的索引)
最终根据步骤写出的代码:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //结构体参数之一(模式)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //结构体参数之一(引脚)
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //结构体参数之一(速度)
GPIO_Init(GPIOC,&GPIO_InitStructure);
③点灯关键:
根据步骤写出的代码:
GPIO_ResetBits(GPIOC,GPIO_Pin_13); //将PC13置为低电平
13、工程架构: