STM32——基础篇
技术笔记!
一、初识STM32
1.1 ARM内核系列
A 系列:Application缩写。高性能应用,比如:手机、电脑、电视等。
R 系列:Real-time缩写。实时性强,汽车电子、军工、无线基带等。
M 系列:Microcontroller缩写。超低功耗,工控、消费电子、家电、医疗器械等。
性能由高到低,时钟频率由高到低。
1.2 STM32命名规则
ST -- 意法半导体
M -- Microelectronics 微电子
32 -- 总线宽度
1.3 数据手册的获取方式
1. ST官网:www.st.com
2. 中文社区网 https://www.stmcu.com.cn/Designresource/list/STM32F1/document/datasheet
1.4 如何阅读数据手册
1. 原理图
时钟电路
复位电路
电源电路
BOOT启动电路
程序下载电路
扩展接口
2. 芯片手册
二、嵌入式硬件基础
2.1 STM32引脚种类
1. 电源引脚
基本以字母 V 开头,比如 VDD/VSS、VDDA/VSSA、VREF+/VREF-、VBAT 等。
VDD/VSS:数字部分电源正/负引脚,为 STM32 供电。
VDDA/VSSA:模拟部分电源正/负引脚,为内部模拟部分供电。
VREF+/VREF-:为 ADC/DAC 提供参考电压,100 脚以上的型号才有这两个脚。
VBAT:RTC/后备区域供电引脚。
2. 晶振引脚
一共两组:OSC_IN/OSC_OUT、OSC_IN32/OSC_OUT32。
OSC_IN/OSC/OUT:外部 HSE 晶振引脚(高速),用于给 STM32 提供高精度系统时钟。
OSC_IN32/OSC_OUT32:外部 LSE 晶振引脚(低速),用于给 STM32 内部 RTC 提供晶振。
3. 复位引脚
只有一个:NRST
用于复位 STM32 ,低电平有效。
4. 下载引脚
第一种:JTAG,可仿真/调试,但占用引脚数量较多。
第二种:SWD,可仿真/调试,但占用引脚数量较少,强烈建议!
第三种:串口,只能串口1,其他串口不行!只能下载程序,不可调试。
5. BOOT 引脚
启动选择引脚。
6. GPIO引脚
以字母P开头的都是GPIO引脚。
2.2 STM32 最小系统
单片机最小系统是指能够将单片机芯片运行所需要的最少的硬件电路集成再一起的系统。
它是一种基本的单片机应用系统,通常由主芯片,时钟电路,复位电路,电源电路,BOOT启动电路,程序下载电路,扩展接口组成,为单片机提供时钟信号、复位信号以及外设接口等必要功能。
STM32中的晶振是一个非常重要的组成部分,它为整个系统提供了一个稳定的时钟源。具体来说,晶振在STM32中的作用如下:
1. 提供时钟信号:晶振是一个振荡器,它会产生连续的脉冲信号,这些信号的频率非常稳定。STM32微控制器中的CPU和其他外围设备需要一个稳定的时钟信号来协调它们的工作。因此,晶振为整个系统提供了一个可靠的时钟源,使得各个模块能够以相同的频率工作,从而保证系统的稳定性和准确性。
2. 决定CPU的工作速度:晶振的频率决定了CPU的工作速度。频率越高,CPU的执行速度就越快;反之,频率越低,CPU的执行速度就越慢。因此,根据实际需求选择合适的晶振,可以确保系统在满足性能要求的同时,不会造成不必要的功耗浪费。
3. 确保外围设备的正常工作:除了CPU之外,系统中还有很多其他的外围设备,如串口、定时器、ADC等。这些外围设备也需要时钟信号来进行工作。晶振提供的时钟信号可以确保这些外围设备正常、准确地工作。
4. 解决电磁兼容性问题:一些外围设备在工作时可能会产生电磁干扰,这些干扰可能会影响系统的稳定性。而晶振产生的时钟信号是高度稳定的,因此使用晶振可以减少由于电磁干扰导致的系统误差或故障。
综上所述,晶振在STM32微控制器中起着非常重要的作用。它不仅为整个系统提供了一个稳定的时钟源,还决定了CPU的工作速度和外围设备的正常工作。因此,选择合适的晶振对于保证系统的稳定性和准确性至关重要。
2.3 电路基础知识
2.3.1 电子元器件
1. 上拉电阻和下拉电阻是用来控制信号的高低电平的。在数字电路中,输入端需要一个确定的电平,而不是悬空的状态。上拉电阻和下拉电阻可以将输入端连接到上电或地,以确保输入端有一个确定的电平。
2. 电容是一种存储电荷的元件,在电路中可以用来滤波、积累能量和传递信号。电容可以将电流变成电压,并且对频率有一定的响应特性。
3. 电阻是用来限制电流流动的元件,通过电阻可以改变电路中的电压和电流分布,实现对电路的调节和控制。
4. 电感是一种储存磁场能量的元件,通过电感可以抗拒电流的变化,起到滤波、储能和传递信号的作用。
5. 三极管(CMOS晶体管)是一种常用的半导体器件,具有放大和开关功能。可以将输入信号放大到较大的电流或电压输出,也可以作为开关控制电路的通断。高电平时P-MOS有效,低电平N-MOS有效。
6. 发光二极管主要是发光作用,二极管其保护整流作用。
总的来说,电容、电阻、电感和三极管在电路中起到不同的作用,可以实现信号的处理、控制和放大。
7. TTL肖特基触发器,对电平信号进行整型(产生的电平信号会出现毛刺);
2.3.2 逻辑电路
三、STM32系统入门
3.1 F1系列内核和芯片系统架构(不同芯片架构图也不同,不过类似)
3.2 存储器映像
STM32 寻址范围:2^32 = 4 * 2^10 *2^10 K = 4 * 2^10 M = 4G
地址所访问的存储单元是按字节编址的。
0x0000 0000 ~ 0xFFFF FFFF
什么是存储器映射?
存储器本身不具有地址信息,给存储器分配地址的过程称为存储器映射。
ROM:只读存储器,非易失性,掉电不丢失的存储器(类似于硬盘);
RAM:随机存储器,易失性,掉电丢失的存储器(类似内存)。
3.3 寄存器映射
寄存器本质也是内存,通过控制寄存器可以实现对外设工作的控制。
寄存器是特殊的存储器,给寄存器地址命名的过程,就叫作寄存器映射。
基地址:总线/外设/寄存器的起始地址,每款芯片的基地址不一样,可以通过芯片手册进行查找。
如何进行外设地址映射?
总线基地址 + 外设相对于总结基地址偏移量 + 寄存器相对于外设基地址偏移量总线:
AHB(Advanced High performance Bus):先进高性能总线;
APB(Advanced Perihperal Bus):先进外围总线。
四、创建HAL版本MDK
4.1 STM32Cube固件包
下载:每款芯片的固件包不太一样,可以从意法半导体官网下载对应系列的固件包,直接搜索STM32Cube会跳出一些相关的推荐,选择需要的那一款下载即可。
4.2 项目工程文件夹
模板详情点击手把手带你创建HAL版本MDK工程模板 | 良许嵌入式 (lxlinux.net)。
4.3 标准库与HAL库
1. 寄存器
寄存器众多,需要经常翻阅芯片手册,费时费力;
更大灵活性,可以随心所欲达到自己的目的;
深入理解单片机的运行原理,知其然更知其所以然。
2. 标准库
将寄存器底层操作都封装起来,提供一整套接口(API)供开发者调用
每款芯片都编写了一份库文件,也就是工程文件里stm32F1xx…之类的;
配置结构体变量成员就可以修改外设的配置寄存器,从而选择不同的功能;
大大降低单片机开发难度,但是在不同芯片间不方便移植。
3. HAL库(Hardware Abstraction Layer)
ST公司目前主力推的开发方式,新的芯片已经不再提供标准库;
为了实现在不同芯片之间移植代码;
为了兼容所有芯片,导致代码量庞大,执行效率低下。
HAL库详情点击【STM32】HAL库 STM32CubeMX系列学习教程_stm32hal库学习路线-CSDN博客。
4.4 常见文件意义
1.startup_stm32f102xb.s
启动文件,用于初始化硬件、设置堆栈和中断向量表,然后跳转到 C/C++ 的主程序入口点( main 函数)。
2.stm32f1xx_it.c
中断服务例程文件,它包含了处理各种中断的 C 语言函数实现,例如定时器中断、外部中断等。
3.system_stm32f1xx.c
定义了系统初始化函数 SystemInit 和系统时钟更新函数SystemCoreClockUpdate。
4.sm32f1xx_hal.c
HAL库初始化、系统滴答、HAL库延时等相关函数。
5.stm32f1xx_hal_cortex.c
内核通用函数定义和声明,如NVIC、MPU、系统软复位、Systick等,其实主要是对core_cm3.h 文件的相关函数再次封装。
6.stm32f1xx.h
是所有F1系列的顶层头文件,通过条件编译来包含某个芯片的头文件,定义通用枚举类型,定义通用的宏定义。
7.stm32f103xb.h
包含:中断编号定义、外设寄存器结构体类型定义、寄存器映射、寄存器位定义、外设判定。
8.stm32f1xx_hal_conf.h
HAL库的用户配置文件,用于裁剪HAL库、配置晶振参数等。
9.stm32hxx_hal_def.h
包含HAL库通用的枚举类型数据和宏定义。
10.stm32f1xx_hal_ppp.c
某任意外设驱动源码,PPP表示任意外设。
11.stm32f1xx_hal_ppp_ex.c
主要是存放外设的扩展(特殊)功能的驱动源码,PPP表示任意外设。
五、STM32启动
5.1 SMT32启动流程
1. 复位/上电
2. 根据 BOOT0/BOOT1 确定程序从哪个存储位置执行
3. 初始化 SP 及 PC 指针
将 0X08000000 位置的栈顶地址存放在 SP 指针中
将 0x08000004 位置存放的向量地址装入 PC 程序计数器
4. 初始化系统时钟
5. 初始化用户堆栈
6. 进入main函数
5.2 map文件
map 文件是 MDK 编译代码后,产生的集程序、数据及IO空间的一种映射列表文件。简单来说就是包括了:各种 .c 文件、函数、符号等的地址、大小、引用关系等信息。
作用: 用于分析各 .c 文件占用 FLASH 和 RAM 的大小,方便优化代码。(双击项目名即可打开map查看)
map 文件大致可以分为五大部分,每部分的作用如下:
1. 模块、段(入口)交叉引用(Section Cross References):描述了各文件之间函数的调用关系,有助于理解各个源文件如何相互关联。
2. 移除未使用的模块(Removing Unused input sections from the image):描述了工程中未用到而被删除的冗余程序段,有助于优化代码,降低内存负担。
3. 映射符号表(Image Symbol Table):描述了各符号(程序段、数据)在存储器中的地址、类型、大小等信息,对于理解程序的内存布局以及定位特定代码或数据段非常有用。
4. 内存(映射)分布(Memory Map of the image):描述了各程序段(函数)在存储器中的地址及占用大小,有助于分析程序的内存占用情况。
5. 映射组件大小(Image component sizes):给出了整个映像代码(.o)占用空间的汇总信息,包括代码、只读数据、已初始化读写数据、未初始化读写数据等的大小,有助于了解程序的总体内存使用情况。
MAP文件的这五部分提供了对程序内存布局、函数调用关系、以及各模块占用情况的全面分析,是STM32开发中重要的优化和调试工具。
map 文件相关的一些基本概念
段(section):描述映像文件的代码和数据块
RO:Read-Only的缩写,包括RO-data(只读数据)和RO-code(代码)
RW:Read-Write的缩写,主要是RW-data,RW-data由程序初始化初始值
ZI:Zero-initialized的缩写,主要是ZI-data,由编译器初始化为0。
.text:与RO-code同义
.constdata:与RO-data同义
.bss:与ZI-data同义
.data:与RW-data同义
六、时钟树
6.1 时钟
时钟是由电路产生的具有周期性的脉冲信号,相当于单片机的心脏,给单片机提供一个统一的信号。
要想使用单片机的外设必须开启相应的时钟。
HSE = 高速外部时钟(high speed external)
HSI = 高速内部时钟
LSI = 低速内部时钟
LSE = 低速外部时钟
6.2 系统时钟配置
#include "sys.h" void stm32_clock_init(uint32_t plln) { HAL_StatusTypeDef ret = HAL_ERROR; RCC_OscInitTypeDef rcc_osc_init = {0}; RCC_ClkInitTypeDef rcc_clk_init = {0}; rcc_osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE; rcc_osc_init.HSEState = RCC_HSE_ON; rcc_osc_init.HSEPredivValue = RCC_HSE_PREDIV_DIV1; rcc_osc_init.PLL.PLLMUL = plln; rcc_osc_init.PLL.PLLSource = RCC_PLLSOURCE_HSE; rcc_osc_init.PLL.PLLState = RCC_PLL_ON; ret = HAL_RCC_OscConfig(&rcc_osc_init );//时钟树左侧的配置 if(ret != HAL_OK) { while(1); } rcc_clk_init.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2); rcc_clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; rcc_clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1;//AHB分频 rcc_clk_init.APB1CLKDivider = RCC_HCLK_DIV2;//APB1分频 rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV1;//APB2分频 //FLASH_LATENCY_2 flash和cpu速率有差异,得分频 ret = HAL_RCC_ClockConfig(&rcc_clk_init, FLASH_LATENCY_2);//时钟树右侧的配置 if(ret != HAL_OK) { while(1); } }