25 KiB
我们用的开发板和书上的不一样,另外开发环境不太一样,因此这一章内容我们主要介绍开发板和开发环境。
[toc]
1. STM32微处理器概述
1.1. Cortex-M 家族
使用ARM Cortex-M 内核,由意法半导体生产的STM32F1系列。STM32被广泛应用于各种消费电子产品、工业控制、汽车领域,有完善的产品线和开发工具。
1.2. STM32F103C8T6
- Arm® 32-bit Cortex ®-M3 CPU core
- Memories
- 64 or 128 Kbytes of Flash memory
- 20 Kbytes of SRAM
- 2x 12-bit, 1 μs A/D converters (up to 16 channels)
- DMA
- 7-channel DMA controller
- Peripherals supported: timers, ADC, SPIs,
- I2Cs and USARTs
- Up to 80 fast I/O ports
- Debug mode – Serial wire debug (SWD) and JTAG
- Seven timers
- Three 16-bit timers, each with up to 4 IC/OC/PWM or pulse counter and quadrature (incremental) encoder input
- 16-bit, motor control PWM timer with dead-time generation and emergency stop
- Two watchdog timers (independent and window)
- SysTick timer 24-bit downcounter
- Up to nine communication interfaces
- Up to two I2C interfaces (SMBus/PMBus®)
- Up to three USARTs (ISO 7816 interface,
- LIN, IrDA capability, modem control)
- Up to two SPIs (18 Mbit/s)
- CAN interface (2.0B Active)
- USB 2.0 full-speed interface
1.2.1. 封装
1.2.2. 芯片引脚(p17)
- 电源:VDD、VSS;VDDA、VSSA
- 复位:NRST
- 时钟:OSC,高速和低速,高速8M;低速32.768K,主要用于时钟
- 调试:JATG(下面分析开发板时会标出)
- 通用:GPIO
参考资料:
- [如何在STM32中获得最佳ADC精度](../../STM32官方手册/AN2834 - 如何在STM32中获得最佳ADC精度.pdf)
- STM32中文参考手册
- STM32CubeIDE入门指南
- STM32CubeMX用户手册
- STM32F1xxHAL库与LL库手册(英文)
- STM32F10xx硬件开发应用笔记
- STM32F103中文参考手册
- STM32F103C8规格书
- STM32国产替代者
2. KeysKing开发板
两个刚毕业的大学生做的项目,非常适合教学使用,有丰富的扩展。有视频教材。
- 自己负责自己的硬件,损坏需要赔偿
- 每个盒子中有一张表格,请核对表格仔细检查附件
- 领用与归还都需要在表格签字
- 大多情况下不需要使用附件,如果要使用附件,请确保电源不要接错烧板子
- B站有视频,有兴趣的朋友可以看视频做更多的实验
2.1. 主要外部附件
2.2. 开发板硬件简单解析
如何看懂电路图?简单,标号一样的就是一样的,或者是相连接的。
2.2.1. 供电
AP2112是一个线性稳压器件,把5V降压成3.3V为MCU供电。因此开发板有两套电压,5V和3.3V,请注意区分。5V电压哪里来?
通过TYPE-C接口,接计算机的USB接口,其中的VBUS就是5V;
TYPE-C连接到 CH343P (USB转串口),为计算机提供了一个串口,该串口与MCU的PA2、PA3连接,为计算机和开发板的串口建立了通信。
另外,调试接口的SWD中的VSWD也是计算机USB的5V。
最后看一下电流防倒灌电路:
VBUS是TYPE-C的5V,VSWD是调试接口SWD的5V,通过防倒灌电路使得这两路电源都进入AP2112线性稳压的5V端口,为开发板提供3.3V电压。防倒灌电路等效为一个二极管,使得两个5V电源可以不干扰(同时供电)。
2.2.2. 时钟和复位
时钟电路(P20):
时钟电路与书上的开发板不一致,但是也分为4个时钟。请注意,频率和书上的不同!
- LSI:内部低速时钟,40k;
- HSI:内部高速时钟 8M;
- LSE:外部低速时钟 32.768k;
- HSE:外部高速时钟 8M。
1的位置是两个时钟,低速(32.768k)和高速(8M)。其中32.768k 是为计时提供精确的脉冲;8M高速通过MCU内部倍频后,可以为MCU内部提供高达72M的核心时钟。
为什么需要32.768k的时钟?
复位电路:
2位置是复位电路(低有效),很简单,该端口有个电容,为MCU上电的瞬间提供一个低电位,使得MCU复位;如果按下按钮,该端口被拉低,MCU强制复位。
复位电容的工作原理是什么?
为什么复位会用低电位,而不用高电位复位?
上述是开发板的主要核心电路分析,后面的课程当中对用到的电路进行分析。要学会看电路的原理图。
3. 开发环境
3.1. 开发软件
3.1.1. 开软件开发工具(p45)
- STM32CubeMX:图形化的芯片配置工具,可以完成目标选择、引脚分配、外设配置和始终配置等工作,并自动生成初始化代码和应用程序的工程框架;
- STM32CubeIDE:基于eclipse 的 c/c++ 工具链的集成开发环境,提供代码编辑、编译、下载和调试等多种功能,同时将STM32CubeMX以插件的方式继承到IDE中。
3.1.2. 嵌入式软件
- STM32CubeMCU Packages (MCU固件包)
- STM32Cube HAL APIs
- STM32Cube LL APIs
- 中间件
3.2. 开发环境安装
书上介绍的开发环境是Keil开发环境,这个在51中也使用过,但是该软件是属于商业软件,需要购买。因此我们使用意法半导体提供的免费开发环境stm32cubeide,其开发过程与书上讲的几乎完全一致,且集成度更好,更便于使用。
下载 stm32cubeide 。
另外,后续的开发中会使用到串口,推荐开源的 xtools(在我的Linux系统中运行有问题),或者是 ScriptCommunicator。
注意,虽然下载不需要用户,但是使用需要登录!否则无法下载相应的芯片资源,无法进行编译!在下图中 MyST 菜单中登录后才能进行相应的开发。
STM32CubeIDE集成了:STM32CubeMX:芯片选型、配置、代码生产等,生成的框架可以供多种IDE使用基于Eclipse的集成开发环境。安装完成后,界面如下:
一般的开发流程如下:
- 目标选择
- 引脚分配
- 外设配置
- 时钟配置
- 工程配置
- 程序编写
3.3. 升级STLink固件
STLink 调试器版本需更新,否则可能无法下载程序和进行调试。
- 把调试器插入计算机任何一个USB口;
- 在Help菜单中找到 ST-Link 更新,出现以下界面:
- Refresh Device List 的左边应该出现 ST-LINK/V2 的选项,然后点击1所示的位置,出现2位置的信息,最后点击3位置更新固件。
3.4. 示例:LED闪烁
3.4.1. 新建项目
File->New->STM32 Project 出现以下界面:
在1位置输入 103C8T6 后,会在2位置出现芯片选择,选择第一个就好,然后点击3位置的Next。
在1位置输入项目名称,然后在2位置点击 Next。
选择开发固件(其实就是函数编程的接口版本,类似C开发中使用的三方库),一般不用更改,直接点击2位置的Finish完成。如果遇到下图对话框,选择Yes。
3.4.2. 硬件配置
上图是说IDE试图打开设备配置的配置方案,询问你是否需要打开。然后下面的界面对芯片进行配置。
查看RGB硬件的端口,暂时使用PA6作为需要闪烁的LED。鼠标点击PA6 选择“GPIO_Output”,让PA6作为输出端口;接下来,鼠标右键点击PA6,选择Enter User Label。注意,User Label 是给端口起一个更清晰的别名,这个别名在编程的时候被当成是常量,因此,名字需要符合C语言常量的规则。
好了,目前我们的芯片看起来是这样:
PA6变绿,并且有个图钉,表示这个端口已经被配置和占用。使用ctl+s 保存更改,IDE会询问是否生成代码框架,当然选择Yes。
3.4.3. 加入代码
打开 main.c 大约在101 行左右,输入以下代码。注意,注释代码是IDE自动生成的,不要删除!
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(1000);
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
/* USER CODE END 3 */
}
代码一定需要在 /* USER CODE BEGIN 3 / 和 / USER CODE END 3 */ 之间,因为IDE自动维护代码,否则下一次你重新配置硬件后重新生成代码,你自己的代码有可能丢失,因此请仔细阅读注释文件。
接下来点击运行按钮:
可能会出现运行配置的界面:
注意接口应该是SWD,一般不用修改,直接OK后观察开发板的效果。
3.4.4. 调试
如下图,选择编译模式为Debug
在main.c 中,加入的代码前面双击,会出现一个蓝点,这就是程序运行后中断等待的地方。
下图中,点击一个小虫子的图标进行调试:
程序开始会在 HAL_Init(); 处中断,下图是调试工具:
依次是:
- 重新开始调试;
- 继续(F8);
- 暂停
- 停止(ctl+F2)
- 端口连接
- 进入到函数(F5)
- 跳过函数(F6)
- 跳出函数(F7)
使用 F8 继续,在刚刚的断点处中断:
继续按F8,间隔1秒以上,观察蓝色LED的变化。
4. 开发环境详解
Stm32CubeIDE 其实是由多个工具所构成:
- Stm32CubeMX:该工具是一个独立的工具,可以嵌入到 Stm32CubeIDE 中进行工作。就是在本章 3.2.1 中新建项目用到的这个工具。这个工具包含了芯片选择、芯片配置、芯片相关开发资源下在与更新、代码框架生成和更新等功能。可以这么说,这是STM32开发工具的核心,IDE只是更容易编程和调试而已。
- Eclipse CDT IDE:这是基于Eclipse的C/C++开发环境,用于便捷的代码开发和调试。
- GCC for ARM:C/C++交叉编译环境,用于生成目标代码。
- ST-Link 工具:下载和调试工具,用于把目标代码下载到开发板,或者与Eclipse协同对代码进行调试。
4.1. Stm32CubeMX
该工具是整个STM32开发的核心,在IDE中,有个后缀为ioc的文件:
这就是Stm32CubeMX所维护的文件,该文件中有当前MCU的所有配置信息、项目所依赖的软件功能配置、代码生成控制等信息。通过该工具,最终目的是生成一个代码框架,我们只需要在代码框架下加入我们的业务逻辑就好了。这样就避免了繁琐的芯片初始化、配置等过程,这些工具都给你做好了。
在IDE中,双击后缀为ioc的文件就会打开 Stm32CubeMX 的界面,如果进行了修改,保存的时候会询问是否重新生成代码框架。
这个工具还有一个功能是维护 MCU Packages 就是不同系列MCU的底层框架代码和一些附加功能代码(网络、USB、操作系统等)。通过 Help 菜单中的 Manage Embedded Software Packages 功能可以打开。可以看到,支持的MCU系列的相关软件包。(P48)
4.2. 编译下载过程
上面就是整个的开发的流程,STM32CubeMX在配置方面减少了开发者大量复杂且容易出错的芯片配置和初始化工作,使得开发者可以专注自己的业务逻辑实现。
接下来我们来看看STM32CubeMX帮助我们做了些什么,我们的代码结构如下:
- Binaries 是编译后的目标文件;
- Includes 是引用的 Embedded Software Packages 中的头文件,主要实现芯片初始化、外设配置、操作系统、网络协议栈等功能。这些功能都准备好了,只需要配置和使用就可以了;
- Core 用户的核心代码在这里;
- Drivers 硬件抽象层的代码在这里,包括各种外围硬件驱动,操作系统配置等;
- Debug/Release 和传统C/C++一样,编译的中间代码;
- Blink.ioc 工程文件,被 Stm32CubeMX 所维护;
- 其他文件。。。
其实,大部分时间,开发者只需要维护 Core 中的部分代码即可。
4.3. 代码分析
这里重点看 main.h 和 main.c 这两个文件。
main.h 中重点看看 60 行左右:
/* Private defines -----------------------------------------------------------*/
#define LED_Pin GPIO_PIN_6
#define LED_GPIO_Port GPIOA
这两行宏定义是因为刚刚把 PA6 定义了一个Label叫做LED。框架在生成代码的时候会自动生成相应的常量(注意常量的生成规则),以后在使用的时候,就可以使用这些常量了。
在main.c的 static void MX_GPIO_Init(void) 函数中,大约 150 行
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : LED_Pin */
GPIO_InitStruct.Pin = LED_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LED_GPIO_Port, &GPIO_InitStruct);
生成的代码对PA5端口进行了初始化。以上的代码都是自动生成的,一般不用去关心,框架简化了我们的开发工作。
用户逻辑代码,在main.c 大约100行左右:
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(1000);
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
/* USER CODE END 3 */
}
注意用户代码必须在 /* USER CODE BEGIN 3 / 和 / USER CODE END 3 */ 之间!否则重新生成代码的时候会被删除。
HAL_Delay(1000); 表示使用延时1000毫秒;
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); 两个参数,第一个代表端口,就是上面的常量定义的LED端口;第二个代表具体的引脚,就是LED的端口。
其实框架还生成了很多工作,包括MCU的时钟初始化、总线初始化、内存初始化等工作。这些工作都使用 Stm32CubeMX 的配置进行,代码是自动生成的,一般不用关心。在后续的工作当中,我们还会用到很多Stm32CubeMX的配置功能。
4.4. Stm32CubeMX 的使用(P59)
该工具四大功能:
- Pinout & Configuartion:端口,外设和中间件(操作系统、网络协议栈、USB驱动等)配置;
- Clock Configuration:时钟配置;
- Project Manager:项目配置,主要用于代码生成的一些偏好设置,一般不用关心;
- Tools:工具,主要是能耗分析等。
一般最常用的就是 Pinout Configuartion 和 Clock Configuration
5. 串口通信演示(p83)
注意:书上使用的开发环境是Keil,而我们使用的是 STM32CubeIDE,另外,开发板也不一样,下面会说明。
85页设置了一个 fputc 函数来使得 printf 可以输出到串口,这种方式在 Stm32CubeIDE 中无法使用。下面会说道。
上一个LED的演示很简单,很多地方其实没有进行配置,在串口通信中,我们会更详细的学习如何进行开发。
5.1. 硬件连接
这次使用1和2两个接口:
- 通过一条type-c连接线接入开发机的USB,用于在开发机上虚拟一个串口。可能需要安装驱动。
- 和ST-Link连接后,接入开发机的另外一个USB,用于程序下载和调试。
查看1对应的端口说明:
知道串口的两个引脚是 PA2和PA3,这个等一下使用。
5.2. 项目设置
请参考[示例:LED闪烁](#3.4. 示例:LED闪烁)建立一个项目,命名为uart。
5.2.1. 时钟设置(p66)
如果我们打开时钟配置界面会发现:
LSE和HSE全部都是灰色,而LSI和HSI都是蓝色,这表示目前芯片的配置使用芯片内部的时钟;如果我们要使用外部时钟,需要在 Pinout & Congiguration 中使能相应的配置。
首选选择 Pinout & Congiguration 功能,在左边的列表(2位置)中选择 RCC,然后右边出现的选单(3位置)下拉列表中把HSE和LSE全部选择成外部晶体,如上图所示。这时右边的芯片图相应的引脚成绿色,表示被占用。
回到 Clock Configuration ,进行一下设置:
这样就可以使用外部时钟了,外部时钟的好处是更精确。这里的图叫做时钟树,我们先不关心时钟树的具体内容,先就这么设置,后面用到的时候会详细讲解。
5.2.2. 设置调试模式
在 Pinout & Congiguration 的左边,选择 System Core 中的 SYS,修改以下配置:
调试方式选择 Serial Wire。其实不用设置也可以,缺省的调试方式就是 Serial Wire,这个就是我们 ST-Link 使用的调试方式。
5.2.3. USART2 配置
通过开发板提供的资料,我们知道串口使用PA2和PA3,但是这两个引脚对应哪个串口?
在芯片视图中点击PA2:
看到了吗?使用的是USART2这个串口,接下来对串口进行配置。
在 Pinout & Congiguration 的左边,选择Connectivity 的 USART2,并在右边的 Mode 选项中选择 Asynchronous (异步),看看下面的串口参数(4的位置),我们知道:
- 波特率:115200
- 数据位长度:8位
- 校验:无
- 停止位:1
记住这些参数,在开发机中的串口监视也需要采用同样的参数才能收到数据。当设置完成后,使用 ctl+s 保存,会提示是否需要生成代码,当然要生成。
5.3. 软件编写
在 main.c 函数的大约 100 行左右修改代码如下:
/* Infinite loop */
/* USER CODE BEGIN WHILE */
char *msg = "hello world!\n";
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(1000);
HAL_UART_Transmit(&huart2, msg, strlen(msg), 100);
}
/* USER CODE END 3 */
}
编译后发现有三个警告,
09:10:24 Build Finished. 0 errors, 3 warnings. (took 757ms)
虽然警告一般不影响我们的程序,但我们还是试图解决。一直向上面,看看警告的具体内容:
../Core/Src/main.c:103:49: warning: implicit declaration of function 'strlen' [-Wimplicit-function-declaration]
103 | HAL_UART_Transmit(&huart2, msg, strlen(msg), 100);
| ^~~~~~
../Core/Src/main.c:21:1: note: include '<string.h>' or provide a declaration of 'strlen'
20 | #include "main.h"
+++ |+#include <string.h>
这里其实已经给了我们解决方案,因为strlen函数是在 string.h 中,而没有引入这个头文件(string.h应该是缺省引入的),需要添加头文件的引用。注意,这个引用必须在特定的位置,否则STM32CubeMX重新生成代码的时候会删除。
来到main.c的第22行左右:加入引用:
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
/* USER CODE END Includes */
下一个警告是:
../Core/Src/main.c:103:44: warning: pointer targets in passing argument 2 of 'HAL_UART_Transmit' differ in signedness [-Wpointer-sign]
103 | HAL_UART_Transmit(&huart2, msg, strlen(msg), 100);
| ^~~
| |
| char *
In file included from ../Core/Inc/stm32f1xx_hal_conf.h:338,
from ../Drivers/STM32F1xx_HAL_Driver/Inc/stm32f1xx_hal.h:29,
from ../Core/Inc/main.h:30,
from ../Core/Src/main.c:20:
../Drivers/STM32F1xx_HAL_Driver/Inc/stm32f1xx_hal_uart.h:749:79: note: expected 'const uint8_t *' {aka 'const unsigned char *'} but argument is of type 'char *'
749 | HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_UART_Transmit 这个函数负责向串口发送数据,其第二个参数的类型是 uint8_t * 类型,但是我们传入的是 char * 类型。虽然这两个类型是兼容的,我们还是最好改一下。有多种方案,现在我们直接用强制类型转换。
/* Infinite loop */
/* USER CODE BEGIN WHILE */
char *msg = "hello world!\n";
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(1000);
HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), 100);
}
/* USER CODE END 3 */
}
如果把 msg 的类型改成 uint8_t* 任然会出现警告,自己试试为什么?
好了,完美了,没有任何警告。
5.3.1. 与书上的不同
85页设置了一个 fputc 函数来使得 printf 可以输出到串口,这种方式在 Stm32CubeIDE 中无法使用。因此还是按照该文档的方法来进行!以后凡是书上遇到使用printf重定向到串口的方式都不能使用。
5.4. 运行效果
把程序下载到开发板,打开串口工具,注意波特率等参数一定和开发板的串口参数一致:
最后我们成功的收到了串口发送的数据:
6. 实践任务
- 按照本章的介绍,独自完成 Blink 点亮小灯的程序;
- Eclipse 对 C语言的代码解析和语法分析没有 clion 好,有能力的同学可以尝试使用 clion 和 stm32cube 来进行开发和调试。