引言
本文以STM32F103C8T6芯片为基础,汇总了基础的单片机知识,基于HAL库,编译软件选择STM32CubeIDE,便于配置引脚。
当然了,现在是AI的时代,Clion等强大的编译软件能大幅简化我们的工作,但是知其然知其所以然,AI只是助手,决策,规划和调试修正仍然需要人的参与,理解寄存器和数据手册才是真正学会。
这篇文章仅仅提供我在嵌入式开发学习中的一点点思考和总结,后续的FreeRtos,pid,上位机等知识仍然需要探索。
HAL库
HAL库是ST官方主推的STM32开发库,它以句柄、超时、回调、多模式等机制,大大降低了外设驱动的开发难度,最大的特点是抽象和可移植,同时保持了代码的可移植性和可维护性。
设计哲学
-
分层:将外设驱动分为三层:用户应用程序 → HAL API → 硬件寄存器。
-
面向对象风格:使用“句柄”(Handle)结构体来封装外设的状态、配置和底层资源。
-
超时保护:所有可能阻塞的操作(如等待标志位)都提供了超时参数,防止程序卡死。
-
中断与轮询统一:支持轮询、中断、DMA三种模式,切换模式只需改变初始化参数,API命名规范一致。
-
弱定义回调:允许用户在应用程序中轻松扩展中断处理,无需修改库代码。
单片机结构
定义
单片机(Microcontroller Unit,MCU)是一种将中央处理器(CPU)、存储器(RAM/ROM)、输入输出接口(GPIO)、定时器/计数器、通信接口(如串口、I2C、SPI)等集成在一个芯片上的微型计算机。简单来说,它就是一台单芯片的计算机,专门用于控制电子设备的运行。
特点
集成度高:单芯片集成计算机核心部件,体积小、功耗低。
专用性强:针对特定控制任务设计。
实时性:能快速响应外部事件(中断机制)。
成本低:适合大批量嵌入式应用。
工作流程
- 上电复位后,CPU从ROM读取第一条指令。
- 根据程序逻辑,通过GPIO控制外部设备(如LED、电机)。
- 通过外设采集数据(如ADC读取传感器电压,串口接收数据)。
- 处理数据并做出决策(如通过PWM调整电机转速)。
- 使用定时器和中断实现精确时序控制和多任务处理。
单片机的核心运行模式就是:上电复位 → 配置时钟树 → 初始化外设 → 进入无限循环,并随时响应中断。时钟树并非与上电同时启动,而是复位后先使用内部默认时钟,再通过软件配置最终时钟;无限循环是主程序的结构,但中断机制使其能够实时处理外部事件。
底层逻辑
单片机上电启动后的工作流程可以总结为以下几个核心步骤:
1. 硬件复位与启动
上电/复位:电源稳定后,芯片内部复位电路释放复位信号,CPU内核从固定地址(如0x00000000)加载主堆栈指针(MSP),并从0x00000004加载复位向量(即复位程序的入口地址),然后跳转执行。
2. 初始化
-
基础时钟启动:复位后,CPU默认使用内部RC振荡器(如HSI,上电后默认启用,速度较快(如8MHz或16MHz),但精度一般。优点是无需外部晶振。)作为系统时钟源,以保证最基本运行。
-
执行复位程序:运行启动文件(如
startup_xx.s)中的Reset_Handler,完成:-
全局/静态变量初始化(复制
.data段、清零.bss段)。 -
调用
SystemInit函数,配置PLL锁相环(一个倍频器,可以把较低的输入时钟(如8MHz)乘以一个系数(如9倍),生成一个高频时钟(如72MHz)供CPU内核和高速外设使用。),将系统时钟切换到外部晶振(HSE,需外接石英晶体(如8MHz),精度高,通常用于需要精准时序的场合)并倍频至目标频率(如72MHz),同时配置总线时钟(通过AHB预分频器和APB预分频器,将SYSCLK分频后,供给各个外设总线)——此时时钟树完整建立。 -
跳转到
main函数。
-
3. 主程序执行
-
外设初始化:在
main函数开头,使能各外设时钟(通过RCC寄存器),配置GPIO、UART、定时器等的工作模式。 -
进入无限循环:执行
while(1)或for(;;),循环处理应用程序逻辑(如轮询标志位、状态机等)。这是单片机工作的主体,除非断电或复位,否则永不退出。
4. 中断并发处理
-
异步事件响应:在无限循环执行过程中,若外设触发中断(如UART接收完成、定时器溢出),CPU在当前指令完成后,由NVIC(嵌套向量中断控制器,Cortex-M内核中专门管理中断的模块)硬件自动保存现场、查找向量表并跳转到对应的中断服务函数(ISR)。ISR执行完毕后硬件恢复现场,回到主循环被打断的位置继续执行。
外设模块
输入/输出类
- GPIO:复习输入模式(浮空/上拉/下拉)、输出模式(推挽/开漏)、复用功能配置。
- PWM:要素是占空比、频率与定时器,例如通过PWM控制电机/灯光亮度。
- ADC:分辨率(如12位)、采样时间、参考电压、多通道扫描、DMA传输(可选)。
通信接口类
- UART(串口):波特率、数据位/停止位/校验位配置,与蓝牙模块(HC-05/HC-06)的交互。
- I2C/SPI:总线协议、时序、与OLED/传感器(如MPU6050)的通信。
- 蓝牙:无线串口,通过AT指令配置模块,实现无线数据传输。
时间与事件类
- 定时器:基本定时、输入捕获(测频率/脉宽)、输出比较(PWM)、编码器接口。
- RTC:理解时钟源(外部晶振/内部RC)、时间寄存器操作、闹钟/唤醒功能。
- 中断:中断优先级、嵌套向量中断控制器(NVIC)、中断服务函数编写注意事项(快进快出)。
显示类
- OLED:通常通过I2C/SPI驱动,复习显示初始化、显存操作、字符/图形绘制。
GPIO
定义
即通用输入输出引脚,是单片机与外界交换数字信号的最基本途径。
引脚可独立配置
-
输入模式:读取外部信号(如按键)。
-
输出模式:驱动LED、蜂鸣器等(推挽/开漏)。
-
复用功能:作为UART、I2C等外设的引脚。
-
模拟模式:用于ADC输入。
核心操作:时钟使能 → 模式配置 → 数据读写。
内部结构
STM32的GPIO内部包含保护二极管、施密特触发器、输出驱动电路及一系列控制寄存器(CRL/CRH、IDR、ODR、BSRR等)。
GPIO引脚内部
包含
-
保护二极管(防止过高/过低电压损坏芯片)。
-
输出驱动器:可配置为推挽输出(能输出强高、低电平)或开漏输出(需要外接上拉电阻)。
-
输入施密特触发器:对输入信号进行整形,避免抖动。
-
复用功能选择器:将引脚连接到其他外设模块。
核心寄存器
(以STM32F1系列为例,每组GPIO有7个寄存器):
-
CRL/CRH(端口配置寄存器低/高):配置每个引脚的模式(输入、输出、复用、模拟)和输出类型/速度。
-
IDR(输入数据寄存器):只读,反映引脚当前的电平状态。
-
ODR(输出数据寄存器):读写,控制引脚输出电平(但修改时会可能引起中间态,一般用BSRR替代)。
-
BSRR(位设置/清除寄存器):写1对应位置位(输出高)或复位(输出低),原子操作,无中断风险。
-
BRR(位清除寄存器):仅用于复位。
-
LCKR(锁定寄存器):锁定配置,防止误改。
开发流程
新建工程且选择芯片
-
打开STM32CubeIDE,新建STM32项目,选择MCU型号(如STM32F103C8)。
-
系统会自动初始化时钟和基本外设。
配置GPIO
在图形界面中:
-
找到要控制的引脚(如PA5),在Pinout视图中将其设置为GPIO_Output。
-
在Configuration > GPIO中,可以详细设置:
-
GPIO output level:初始电平(High/Low)。
-
GPIO mode:推挽输出(Push-pull)或开漏输出(Open-drain)。
-
GPIO Pull-up/Pull-down:是否使能内部上拉/下拉(输出模式通常不使能)。
-
Maximum output speed:根据应用选择(如Low, Medium, High),影响EMI和功耗。
-
配置时钟
-
在Clock Configuration标签页,设置系统时钟(如HSE+PLL倍频至72MHz),HAL库会自动生成
SystemClock_Config()函数。
编译代码
以PA5控制LED为例
点击Ctrl+S保存,CubeIDE会生成初始化代码,包括:
-
main.c:包含main函数和初始化调用。 -
gpio.c/.h:GPIO初始化函数MX_GPIO_Init()。
时钟使能
在MX_GPIO_Init()中,首先使能GPIOA时钟:
__HAL_RCC_GPIOA_CLK_ENABLE();
(向RCC寄存器(RCC_APB2ENR)对应位写1,打开GPIOA模块的时钟门。只有时钟开启,才能访问GPIO的寄存器。)
GPIO初始化结构体
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_5; // 引脚号
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL; // 无上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;// 低速(根据需求调整)
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_GPIO_Init()会根据结构体配置,设置GPIOx的CRL/CRH寄存器(低8位引脚用CRL,高8位用CRH):
-
对于PA5(属于低8位),CRL寄存器的[23:20]位被写入
MODE5和CNF5。-
MODE设置速度(01=10MHz, 10=2MHz, 11=50MHz),对应结构体中的Speed。 -
CNF设置模式(00=推挽输出,01=开漏输出等),对应Mode和Pull的组合。
-
控制输出电平
-
置高:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
底层:向BSRR寄存器的低16位(BS5)写1,引脚输出高电平。 -
置低:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
底层:向BSRR寄存器的高16位(BR5)写1,引脚输出低电平(实际是BSRR的高16位用于复位,也可用BRR寄存器)。 -
翻转:
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
底层:读取ODR寄存器当前状态,取反后写回ODR(或使用BSRR原子操作实现)。注意翻转非原子操作,但HAL实现中用了BSRR来避免风险。
以下是LED闪烁的代码参考
int main(void)
{
HAL_Init(); // 初始化HAL库(配置SysTick定时器作为时基)
SystemClock_Config(); // 配置系统时钟(由CubeMX生成)
MX_GPIO_Init(); // 初始化GPIO
while (1)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // 翻转LED
HAL_Delay(500); // 延时500ms(依赖SysTick中断)
}
}
一般配置时钟和初始化HAL,GPIO系统会自动完成,编译.h件和while(1)函数中的的代码即可
工作流程
-
上电复位后,CPU从Flash加载复位向量,执行启动文件中的
Reset_Handler。 -
Reset_Handler调用SystemInit(设置基本的时钟,如Flash等待周期),然后跳转到main。 -
main中调用HAL_Init(初始化SysTick定时器,用于HAL_Delay),接着SystemClock_Config配置PLL得到72MHz系统时钟,然后MX_GPIO_Init配置PA5为推挽输出。 -
进入无限循环,每500ms翻转LED状态。
7809

被折叠的 条评论
为什么被折叠?



