一、简述
1.1什么是STM32
STM32 是 STMicroelectronics(ST)的一系列 32 位微控制器,其基于 ARM架构 Cortex-M 系列的处理器内核设计。它们广泛应用于各种嵌入式系统,如物联网设备、汽车电子、工业自动化等领域。
ARM(Advanced RISC Machine)是一种低功耗、低成本、高性能的处理器架构。
"ST" 是指制造商 STMicroelectronics 的缩写。 "M" 表示其是基于 ARM 的 Cortex-M 系列核心。 "32" 则代表其为 32 位微控制器 (简称MCU)
STM32F103ZET6 ARM Cortex-M3系列(基础型, 主频: 72M(时钟的工作频率)), Z (144引脚), E (Flash 闪存 512K字节) , T( 封装性: LQFP), 6 (温度范围: -40 ~ 85)。
1.2 STM32最小系统
用最小的电路组成的可以工作的单片机叫做最小系统。
stm32f103zet6 最小系统有 5 部分组成(电源电路、时钟电路、boot 电路、下载/调试接口、复位电路)
电源供电: USB方式(USB, CH340 调试串口),最高5V电压
时钟电路: 内部时钟源(低速 LSI、高速 HSI)、外部时钟(低速 LSE 、高速 HSE)
boot 电路: 引导电路(BT1, BT0, 通过跳帽方式确定, 出厂默认即可)
下载/调试接口: ST下载器进行程序的下载(JTAG/SWD), CH340 USB串口调试
复位电路: RST按键
1.3固件库使用说明
对于MCU开发方式:汇编语言 C语言
stm32 单片机:(汇编+C语言)
实际开发编程过程中使用的方法:
-
配置 MCU 中的某个功能模块的寄存器,进行操作
-
使用 ST 官方提供的固件库驱动操作, 推荐使用 ST 官方提供了 STM32cubemx 软件,图形化配置与开发。
常用的固件库:
Standard Peripheral Library (SPL): ST Microelectronics 最初为其 STM32 微控制器系列发布的固件库。此库包含了一些方便的 C 函数,可以直接控制 STM32 的各种外设,通常称为标准库。
STM32Cube: ST Microelectronics 自 2015 年以来开始推广的一种新的固件库。
STM32Cube 包括一个嵌入式软件平台和一个独立的集成开发环境。嵌入式软件平台包括一个硬件抽象层(HAL),该层为 STM32 的各种外设提供通用的 API,并且还包含一些中间件组件(如 FreeRTOS,USB 库,TCP/IP 库等)。STM32Cube 的集成开发环境(STM32CubeIDE)则包含了代码生成器,它可以生成基于 STM32Cube HAL 的初始化代码。
LL (Low Layer) Drivers: LL 库是 STM32Cube 库的一部分,为高级用户提供了一个硬件抽象层的替代方案。LL 库提供了一组低级 API,可以让用户直接访问 STM32 外设的寄存器。这些 API 比 HAL 更加高效,但是需要更深入的硬件知识。
使用kill5和cubeMX软件
二、stm32启动
3.1系统架构
总线矩阵(Bus Matrix): 总线矩阵主要用于在多个主设备(如 CPU、DMA 等)和从设备(内存和外设)之间进行数据的路由和管理。通过 总线矩阵,主设备可以同时(并行)地访问从设备。
DMA 总线(DMA Bus): DMA,全称 Direct Memory Access,是一种让某些硬件子系统在内存和外设之间直接传输数据,而无需处理器参与的技术。在 STM32 中,DMA 总线主要用于 DMA 控制器和内存以及需要使用 DMA 服务的外设(如 USART、SPI、ADC 等)之间的数据传输。
ICode 总线(ICode Bus/IBus): ICode 总线是一个专门用于 CPU 从 Flash 内存中取指令的总线。这条总线专门优化了指令的取得,以提高 CPU 的 性能。专门用于从 Flash 内存或者其他指令存储器中获取指令,支持 CPU 以最优化的方式取指。该总线用于把指 令从存储器传输到处理器核心,以便执行,主要面向读操作。
DCode 总线(DCode Bus/DBus): DCode 总线是一个专门用于 CPU 访问数据的总线,可以从 Flash 内存或系统内存中读取数据。用于 CPU 核心的数据访问,包括对 Flash 内存和 RAM 的读写。使数据可以并行地(与指令访问操作并行)从存储器中读取或写入。
3.2存储器的架构
闪存:512KB 主存SRAM
程序存储器、数据存储器、寄存器和输入输出端口(外设)被组织在同一个 4GB 的线性地址空间内。可访问的存储器空间被分成 8 个主要块,每个块为 512MB。
3.3 stm32启动方式
跳帽:
BOOT0/BOOT1: 0 启动方式从 Flash 中启动
3.4启动文件过程
从汇编主程序开始中的复位处理(上电之后或按RST键进行的处理过程):
选择 SystemInit 系统初始化, 再调用 __main函数执行核心应用程序。
SystemInit 的主要目的是进行系统级别的初始化,包括配置内部硬件和设置系统的运行环境。
__main函数:
负责完成初始化 C 环境、设置堆栈、清零全局和静态变量等,并最终调用用户定义的 main 函数
四、stm32 系统时钟
4.1 系统时钟的时钟源
三种不同的时钟源可被用来驱动系统时钟(SYSCLK): HSI 振荡器时钟(高速系统内部时钟)、 HSE 振荡器 时钟(高速系统外部时钟)和 PLL 时钟(锁相环时钟)。
以上三种时钟都有以下 2 种二级时钟源:
-
40kHz 低速内部 RC (LSI),可以用于驱动独立看门狗和通过程序选择驱动 RTC。RTC 用于从停机/待机模式下自动唤醒系统。
-
32.768kHz 低速外部晶体也可用来通过程序选择驱动 RTC(RTC CLK)。
当不被使用时,任一个时钟源都可被独立地启动或关闭,由此优化系统功耗。
PLL 是 Phase-Locked Loop(相位锁定环)的简称,是一种控制系统的结构,常用于电子通信设备中。在微控制器和嵌入式系统中,PLL 被用作时钟系统的一部分,能够生成频率比输入时钟(通常是内部的低速时钟或者外部的 晶体振荡器提供的时钟)更高的时钟信号。
五、stm32 通用输入输出
STM32 Pin引脚 144个
通用输入输出 : GPIO(112个) , 分为A, B, C,D, E, F, G 七个组, 每一个组分16个索引。如GPIOE5 -> PE5
5.1 GPIO 框图
保护二极管: IO 引脚上下两边两个二极管用于防止引脚外部过高、过低的电压输入,当引脚电压高于 VDD_FT 时,上方的二极 管导通,当引脚电压低于 VSS 时,下方的二极管导通,防止不正常电压引入芯片导致芯片烧毁。
上拉、下拉电阻: 控制引脚默认状态的电压,开启上拉的时候引脚默认电压为高电平,开启下拉的时候引脚默认电压为低电平。
TTL 施密特/肖特基触发器: 基本原理是当输入电压高于正向阈值电压,输出为高;当输入电压低于负向阈值电压,输出为低;IO 口信号经过 触发器后,模拟信号转化为 0 和 1 的数字信号。
P-MOS 管和 N-MOS 管: 信号由 P-MOS 管和 N-MOS 管,依据两个 MOS 管的工作方式,使得 GPIO 具有"推挽输出"和"开漏输出"的模 式。P-MOS 管高电平导通,低电平关闭; N-MOS 低电平导通,高电平关闭。
5.2 GPIO 的八种工作模式
输入四种: 浮空输入, 上拉输入、下拉输入, 模拟输入
输出四种: 推挽输出、开漏输出、复用推挽输出、复用开漏输出
5.2.1 浮空输入 GPIO_MODE_IN_FLOATING
浮空输入模式下,I/O 端口的电平信号直接进入输入数据寄存器。MCU 直接读取 I/O 口电平,I/O 的电平状态是不确定的,完全由外部输入决定;如果在该引脚悬空(在无信号输入)的情况下,读取该端口的电平是不确定的,低功耗。
5.2.2 上拉输入 GPIO_MODE_IPU(In Pull Up)
IO 内部接上拉电阻,此时如果 IO 口外部没有信号输入或者引脚悬空,IO 口默认为高电平。 如果 I/O 口输入低电平,那么引脚就为低电平,MCU 读取到的就是低电平。
应用场景:钳位电平、增强驱动能力、抗干扰。可以用来检测外部信号;例如按键等
5.2.3 下拉输入 GPIO_Mode_IPD(In Pull Down)
IO 内部接下拉电阻,此时如果 IO 口外部没有信号输入或者引脚悬空,IO 口默认为低电平 如果 I/O 口输入高电平,那么引脚就为高电平,MCU 读取到的就是高电平,可以用来检测外部信号; 例如按键等;
5.2.4 模拟输入 GPIO_MODE_AIN(Analog Input)
当 GPIO 引脚用于 ADC 采集电压的输入通道时,用作"模拟输入"功能,此时信号不经过施密特触发器,直接进入 ADC 模块,并且输入数据寄存器为空 ,CPU 不能在输入数据寄存器上读到引脚状态。 当 GPIO 用于模拟功能时,引脚的上、下拉电阻是不起作用的,这个时候即使配置了上拉或下拉模式,也不会影响 到模拟信号的输入输出。 除了 ADC 和 DAC 要将 IO 配置为模拟通道之外其他外设功能一律要配置为复用功能模式,应用 ADC 模拟输 入,或者低功耗下省电。
5.2.5 推挽输出 GPIO_Mode_Out_PP(out push---pull)
在推挽输出模式时,N-MOS 管和 P-MOS 管都工作,如果我们控制输出为 0,低电平,则 P-MOS 管关闭,N-MOS管导通,使输出低电平,I/O 端口的电平就是低电平,若控制输出为 1 高电平,则 P-MOS 管导通 N-MOS 管关闭,输出高电平,I/O 端口的电平就是高电平,外部上拉和下拉的作用是控制在没有输出时 IO 口电平,此时施密特触发器是打开的,即输入可用,通过输入数据寄存器 GPIOx_IDR 可读取 I/O 的实际状态。I/O 口的电平一定是输出的电平,一般应用在输出电平为 0 和 3.3 伏而且需要高速切换开关状态的场合。
5.2.6 开漏输出 GPIO_Mode_Out_OD(out open drain)
在开漏输出模式时,只有 N-MOS 管工作,如果我们控制输出为 0,低电平,则 P-MOS 管关闭,N-MOS 管导通, 使输出低电平,I/O 端口的电平就是低电平,若控制输出为 1 时,高电平,则 P-MOS 管和 N-MOS 管都关闭,输出指令就不会起到作用,此时 I/O 端口的电平就不会由输出的高电平决定,而是由 I/O 端口外部的上拉或者下拉决定。 如果没有上拉或者下拉 IO 口就处于悬空状态。并且此时施密特触发器是打开的,即输入可用,通过输入数据寄存器 GPIOx_IDR 可读取 I/O 的实际状态。I/O 口的电平不一定是输出的电平。一般应用在 I2C、SMBUS 通讯等需要"线与"功能的总线电路中。
5.2.7 复用推挽输出 GPIO_AF_PP(alternate function open push---pull)
GPIO 复用为其他外设(如 I2C),输出数据寄存器 GPIOx_ODR 无效;输出的高低电平的来源于其它外设,施密特 触发器打开,输入可用,通过输入数据寄存器可获取 I/O 实际状态,除了输出信号的来源改变,其他与推挽输出功 能相同。应用于片内外设功能(I2C 的 SCL,SDA)等
5.2.8 复用开漏输出 GPIO_AF_OD(alternate function open drain)
GPIO 复用为其他外设,输出数据寄存器 GPIOx_ODR 无效;输出的高低电平的来源于其它外设,施密特触发器打 开,输入可用,通过输入数据寄存器可获取 I/O 实际状态,除了输出信号的来源改变 其他与开漏输出功能相同。 应用于片内外设功能(TX1,MOSI,MISO.SCK.SS)等。
六、NVIC 中断
6.1 NVIC 介绍
NVIC(Nest Vector Interrupt Controller)嵌套中断向量控制器,作用是管理中断嵌套,核心任务是管理中断优先级。
特点:
68 个可屏蔽中断通道(不包含 16 个 Cortex-M3 的中断线)
16 个可编程的优先等级(使用了二进制 4 位中断优先级)
低延迟的异常和中断处理
电源管理控制
系统控制寄存器的实现
NVIC 给每个中断赋予抢占优先级和响应(子)优先级。
关系如下:
拥有较高抢占优先级的中断可以打断抢占优先级较低的中断
若两个抢占优先级的中断同时挂起,则优先执行响应优先级较高的中断
若两个挂起的中断优先级都一致,则优先执行位于中断向量表中位置较高的中断
响应优先级不会造成中断嵌套,也就是说中断嵌套是由抢占优先级决定的
6.2 HAL中断API
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);
void HAL_NVIC_SetPriority(IRQn_Type IRQn,
uint32_t PreemptPriority,
uint32_t SubPriority);
HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_x);
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_x);
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin);
处理中断函数,需要自己依据中断线定义:
void 中断线_IRQHandler(void){ }
在main()函数下方定义即可。
6.2 示例
示例1: 按键点灯
GPIO配置:
static void MX_GPIO_Init(void)
{
?GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
?
?/* GPIO Ports Clock Enable */
?__HAL_RCC_GPIOE_CLK_ENABLE(); ?//D1 PE5, PE3按键 Key1
?
?/*Configure GPIO pin Output Level */
?HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET); ?// 0
?
?
?/*Configure GPIO pin : PE5 */
?GPIO_InitStruct.Pin = GPIO_PIN_5;
?GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; ?// 推挽输出 P-MOS/N-MOS
?GPIO_InitStruct.Pull = GPIO_NOPULL;
?GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; ?// GPIO_SPEED_FREQ_LOW == MODE: 10 10MHz
?HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
?
?
/* 配置GPIO pin: PE3 Key1 */
GPIO_InitStruct.Pin = GPIO_PIN_3;
?GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
?GPIO_InitStruct.Pull = GPIO_NOPULL;
?GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
?HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
?
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
main函数的设置中断优先级和开启中断
/* Configure the system clock */
?SystemClock_Config();
?/* USER CODE BEGIN SysInit */
?/* USER CODE END SysInit */
?/* Initialize all configured peripherals */
?MX_GPIO_Init();
?/* USER CODE BEGIN 2 */
? ?// 1. 设置中断的优先级
HAL_NVIC_SetPriority(EXTI3_IRQn, 0, 0);
// 2. 开启中断
HAL_NVIC_EnableIRQ(EXTI3_IRQn);
?/* USER CODE END 2 */
中断处理函数:
// 3. 定义中断处理程序
void EXTI3_IRQHandler(void){
?// 1) 定义变量 用于统计按下产生的中断次数
?static int n=0;
// 2) 获取PE3中断状态: SET 1, RESET 0
if( __HAL_GPIO_EXTI_GET_IT(GPIO_PIN_3) != GPIO_PIN_RESET){
n++;
// 3) 防止抖动
//if( __HAL_GPIO_EXTI_GET_IT(GPIO_PIN_3) != GPIO_PIN_RESET && n >= 5){
? ?// 4) 处理中断产生的效果 PE5灯亮/灭
if( HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_5) == GPIO_PIN_RESET){
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET);
}else{
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_RESET);
}
n = 0;
//}
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_3); // 清除中断状态
}
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3); ?// 继续请求GPIO E3的中断处理
}
示例2: 按键控制蜂鸣器
在示例1的基本之上, 将PF0使能并初始化,在main函数的while循环中:
// 2. 开启中断
HAL_NVIC_EnableIRQ(EXTI3_IRQn);
?/* USER CODE END 2 */
?/* Infinite loop */
?/* USER CODE BEGIN WHILE */
?while (1)
{
// 当灯亮时,Beep响起来
? ?while( !HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_5) ){
? HAL_GPIO_WritePin(GPIOF, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_Delay(2);
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_0, GPIO_PIN_SET);
HAL_Delay(2);
}
? ?/* USER CODE BEGIN 3 */
}