STM32保姆级入门教程|第9章:工业PID温度控制实战 + PWM加热驱动 + 串口实时调参(手把手避坑,完整闭环控制)
原创 ✍️ | 新手零门槛,全程新建工程,手把手不踩坑!
文章标签:#stm32 #stm32cubeide #PID控制 #PWM加热 #温度控制 #闭环控制 #串口调参 #工业实战 #嵌入式
系列前置博客(必看!否则跟不上哦😜):
- 第1章:零基础必看,从认知到实战全解析
- 第2章:STM32CubeIDE 使用+点亮LED
- 第3章:从新建工程到LED闪烁
- 第4章:GPIO输入+外部中断 实现按键控制LED
- 第5章:GPIO内部结构 + 8种模式详解
- 第6章:定时器中断原理 + 精准LED闪烁
- 第7章:串口通信 + printf重定向打印调试
- 第8章:PT100高精度测温实战 + ADS1232驱动 + 24位ADC数据解析(本章续篇)
💡 一、前言(为什么学PID?从测温到控温的质变!)
大家好,我是BackCatK Chen😎!
第8章我们搞定了PT100高精度测温,能精确到0.1℃。但接下来问题来了:光是会测温度有什么用?
真正有用的,是控制温度!
想一想你身边需要恒温的场景:热水器要把水温恒定在42℃、恒温箱要保持在37℃、3D打印机热床要稳定在60℃……这些都需要一个聪明的“大脑”,根据当前温度和设定目标的差距,自动调节加热功率,让温度稳稳地停在目标值上。
这个“大脑”,就是PID控制算法——工业控制领域应用最广、最经典、最可靠的算法,没有之一!
重点说明💥:本章在第8章工程基础上增量开发,保留原有的PT100测温功能,新增PWM加热驱动和PID控制算法,并支持串口实时调参——你可以在串口助手中动态修改P/I/D参数和目标温度,观察控制效果的变化。
特别强调🔧:本章我们将学习嵌入式开发的重要技能——模块化编程,把PID控制逻辑独立封装成 .c 和 .h 文件。学会这一招,以后写项目代码结构清晰、复用方便,再也不怕main.c超过一千行!
本章我把PWM原理、PID算法公式推导、模块化封装、参数整定实验全部讲到最细,全程用“大白话+表情包+一步一截图”,新手看完不仅能学会,还能直接用在自己的恒温项目里,爽到飞起!
🎯 二、本章核心功能目标(清晰明确,学完不迷茫)
- 搞懂 ✅ PWM是什么、如何产生、为什么用它控温(占空比=功率比例)
- 掌握 ✅ PID控制算法的三大组成部分(P、I、D分别干啥?)
- 学会 ✅ 位置式PID公式与代码实现(公式→变量→函数,一条龙)
- 学会 ✅ 定时器PWM输出配置(TIM3_CH1,占空比0~999可调)
- 掌握 ✅ 模块化编程思想(把PID封装成独立文件,方便移植复用)
- 掌握 ✅ 串口实时调参(发送指令修改P/I/D/目标温度)
- 实现 ✅ :
- PT100每秒采集温度
- PID算法自动计算加热功率
- PWM输出控制加热器
- 串口打印温度曲线数据(可复制到Excel绘制响应曲线)
- LED指示加热状态(有功率输出时亮,无输出时灭)
- 串口发送指令实时调整PID参数
- 理解 ✅ 为什么PID能让温度稳稳停在目标值(闭环控制的精髓)
🔌 三、硬件准备与接线(加热电路详解)
3.1 所需硬件清单
| 硬件 | 型号/参数 | 说明 |
|---|---|---|
| 主控板 | STM32F103RCT6 核心板 | 与前8章一致 |
| 温度传感器 | PT100 + ADS1232测温模块 | 第8章已使用 |
| 加热元件 | PTC加热片 或 5W功率电阻 | 5V或12V均可 |
| MOS管 | IRLZ44N(N-MOS,TO-220封装) | 驱动加热器用 |
| 二极管 | 1N4007 | 续流保护 |
| USB转TTL模块 | CH340/CP2102 | 串口打印与调参 |
| 12V电源 | 根据加热器功率选择 | 给加热器独立供电 |
3.2 MOS管加热驱动电路
STM32 PC6 (PWM) ─── 1kΩ电阻 ─── IRLZ44N Gate (G)
│
12V电源(+) ─── 加热器 ─── IRLZ44N Drain (D)
│
12V电源(-) ──────────────── IRLZ44N Source (S) ─── GND
│
1N4007续流二极管反向并在加热器两端
(二极管阴极接12V+,阳极接Drain端)
关键说明:
- IRLZ44N是逻辑电平MOS管,3.3V的PWM信号就能完全导通
- 加热器必须用独立12V电源,不要用STM32的3.3V供电
- STM32的GND必须与12V电源的GND共地
3.3 完整接线表
| STM32F103RCT6 引脚 | 连接对象 | 功能说明 |
|---|---|---|
| PB12 | ADS1232模块 A0 | 通道选择 |
| PB13 | ADS1232模块 CS | 掉电控制 |
| PB14 | ADS1232模块 SCK | 串行时钟 |
| PB15 | ADS1232模块 DOUT | 数据输出 |
| PC6 | IRLZ44N Gate(串1kΩ电阻) | PWM加热控制 |
| PA9 (TX) | USB转TTL RX | 串口打印 |
| PA10 (RX) | USB转TTL TX | 串口接收调参指令 |
| PA0 | LED(阴极) | 加热状态指示 |
| 3.3V | 各模块 VCC | 电源 |
| GND | 各模块 GND + 12V电源GND | 共地 |
🏗️ 四、系统总体架构(一张图看懂控制信号流向)
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 串口助手 │◄──►│ STM32 │───►│ PID算法 │───►│ PWM输出 │
│ (调参/打印)│ │ F103 │ │ (pid.c) │ │ (TIM3_CH1)│
└──────────┘ └────┬─────┘ └──────────┘ └────┬─────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ ADS1232 │ │ IRLZ44N │
│ + PT100 │ │ + 加热器 │
└──────────┘ └──────────┘
▲ │
└────────── 温度反馈 ────────────┘
📌 核心思想:设定目标温度 → PID根据偏差计算输出功率 → PWM控制加热 → 温度反馈给PID → 持续修正,形成闭环!
⚡ 五、PWM功能介绍(大白话讲透,为什么用PWM控温?)
5.1 PWM是什么?
PWM = Pulse Width Modulation(脉冲宽度调制)
大白话:PWM是一个“快快的开关”,每秒开关几千次。通过调整“开”的时间比例,就能控制输出的平均功率。
好比开车:GPIO开关相当于油门要么踩死、要么松开,车子要么猛冲、要么停住,没法平稳驾驶。而PWM相当于油门可以踩到任意深度——轻踩就走得慢,深踩就走得快,控制细腻平滑。这才是温度控制需要的“精准油门”。
5.2 占空比是什么?
占空比 = 一个周期内,高电平时间占整个周期的比例。
| 占空比 | 加热功率 | 效果 |
|---|---|---|
| 0% | 0W | 完全不加热 |
| 25% | 1/4功率 | 微热 |
| 50% | 1/2功率 | 温热 |
| 75% | 3/4功率 | 较烫 |
| 100% | 全功率 | 最烫 |
5.3 STM32如何产生PWM?
STM32的定时器(TIM)有一种 PWM输出模式,可以自动产生方波信号,不需要CPU干预。我们只需配置两个寄存器:
- ARR(自动重装载值):决定PWM周期
- CCR(比较寄存器值):决定占空比
工作原理:定时器从0开始往上计数,当计数值小于CCR时输出高电平,大于CCR时输出低电平。计数到ARR后自动归零,重新开始。整个过程由定时器硬件自动完成,不占用CPU!
计数值: 0 → 1 → 2 → ... → CCR → ... → ARR → 0 → ...
┌──────┐ ┌──────┐
输出引脚: ──────┘ └─────────┘ └──────
← 高电平 → ← 低电平 →
←────── 一个PWM周期 ──────→
5.4 为什么用PWM控制加热?
如果只用GPIO开关(开/关),加热器只有两种状态,温度控制肯定会忽高忽低。用PWM,我们可以输出0%~100%之间任意比例的功率,让加热器“温柔”地调节温度,实现平滑稳定的控制。
5.5 本章PWM配置参数
| 参数 | 数值 | 说明 |
|---|---|---|
| 定时器 | TIM3 | 通用定时器 |
| 通道 | CH1 | 对应引脚 PC6 |
| 时钟频率 | 72MHz | 系统时钟(HSI) |
| 预分频器(PSC) | 71 | 72MHz / 72 = 1MHz |
| 自动重装载(ARR) | 999 | PWM分辨率 = 1000(0~999) |
| PWM频率 | 1kHz | 1MHz / 1000 = 1kHz |
| 占空比范围 | 0~999 | 对应 0%~99.9% 功率 |
🧠 六、PID算法通俗讲解(生活比喻+公式拆解,不烧脑!)
6.1 生活比喻:洗澡调水温
想象你在调节淋浴水温,目标是把水温稳定在舒服的38℃。你一边用手感受温度,一边拧水龙头。
P(比例):眼看水温不对,立刻拧
- 水太冷就往热水的方向拧大一点,太热就往冷水方向拧
- 温差越大,拧的幅度越大
- 这就是比例控制——快速响应,但只靠P温度会一直震荡,稳定不下来。就像手抖的人在调水温,总是调过头。
I(积分):水温一直差一点点?慢慢补上
- 你发现虽然一直在调,水温始终比目标低1℃,怎么都上不去
- 你很耐心,慢慢地把热水开关再拧大一点
- 积分的作用就是累积误差,把长期存在的小偏差“补”上去,最终消除稳态误差。
D(微分):眼看要冲过头,赶紧刹车
- 水温升得太快了,眼看就要超过目标值
- 你赶紧往回拧一点,避免冲过头
- 微分就是提前刹车,根据误差的变化速度来抑制超调。
6.2 位置式PID公式
标准公式:输出 = Kp × 误差 + Ki × 误差累积 + Kd × 误差变化
写成代码:
error = target - actual; // 当前误差:目标值 - 实际值
integral += error; // 误差累积(积分)- 把小偏差加在一起
derivative = error - prev_error; // 误差变化(微分)- 误差变化有多快
output = Kp*error + Ki*integral + Kd*derivative; // 三项加权求和
prev_error = error; // 保存本次误差,供下次计算微分用
逐项解释:
error:当前温度离目标还差多少。正数表示温度低了需要加热,负数表示温度高了需要降温integral:从开始到现在所有误差的累加。如果始终差1℃,积分项会越来越大,直到把偏差消除derivative:误差的变化速度。如果温度飙升,微分项会抑制输出,防止超调
6.3 积分限幅与输出限幅(两个必须做的保护!)
实际应用中,两个保护措施必不可少:
① 积分限幅:积分可能会无限累积导致失控(称为“积分饱和”),必须限制积分范围。
if(integral > 500) integral = 500; // 上限
if(integral < -500) integral = -500; // 下限
② 输出限幅:输出必须限制在PWM的有效范围(0~999)。
if(output > 999) output = 999;
if(output < 0) output = 0;
6.4 参数整定口诀
参数整定找最佳,从小到大逐步查。
先调比例后积分,最后再把微分加。
曲线震荡频繁动,比例系数要缩小。
曲线漂浮绕大弯,积分时间往下降。
曲线偏离回复慢,微分时间应加长。
简单实操步骤:
- 先设
Ki=0, Kd=0,只调Kp,调到温度能快速接近目标但会震荡 - 加上
Ki,慢慢增大,直到稳态误差消失 - 最后加
Kd,抑制超调和震荡,让曲线平滑
🛠️ 七、CubeIDE 1.19.0 配置步骤(复用第8章工程,新增PWM)
重点🔥:在第8章工程基础上增量配置,保留所有原有设置。
步骤1:打开第8章工程
用STM32CubeIDE打开第8章的工程文件 PT100_ADS1232_Demo.ioc。
步骤2:配置TIM3为PWM输出
- 左侧点击
Timers→ 选择TIM3 Clock Source选择Internal ClockChannel1选择PWM Generation CH1- 切换到
Parameter Settings:Prescaler (PSC):71Counter Period (ARR):999Pulse (CCR3):0(初始占空比0%)
- 开启TIM3完全重映射(将CH1映射到PC6):
- 在CubeMX左侧
System Core→ GPIO 中,找到PC6,确认其功能为TIM3_CH1 - 通道也可以在PC6的引脚上面选择
- 在CubeMX左侧

步骤3:确认PB0引脚功能
检查引脚图中 PC6 是否显示为 TIM3_CH1。如果没有自动配置,手动点击 PC6 选择 TIM3_CH1。
步骤4:串口中断接收(保持第7章配置)
USART1 已经配置了中断,NVIC中 USART1 global interrupt 保持 Enable,无需改动。
步骤5:重新生成代码
点击右上角 GENERATE CODE 按钮,增量生成代码。CubeIDE会保留 main.c 中用户代码区域的内容,新增TIM3的初始化代码。
💻 八、代码功能详解(逐行拆解,新手也能懂)
✅ 本章重点学习模块化编程。我们将PID控制逻辑独立封装成
pid.c和pid.h文件,这样代码结构清晰、方便移植到其他项目。
8.1 新建PID模块文件
在STM32CubeIDE工程中:
- 右键
Core/Src→New→Source File→ 文件名:pid.c - 右键
Core/Inc→New→Header File→ 文件名:pid.h
创建完成后的工程文件结构:
PT100_ADS1232_Demo
├── Core
│ ├── Inc
│ │ ├── main.h
│ │ ├── bitband.h
│ │ └── pid.h ← 新建的PID头文件
│ └── Src
│ ├── main.c
│ └── pid.c ← 新建的PID源文件

8.2 pid.h —— PID模块头文件
#ifndef _PID_H
#define _PID_H
#include "stdint.h"
// PID控制器结构体
typedef struct {
float Kp, Ki, Kd; // PID三个参数
float target; // 目标温度(℃)
float integral; // 积分累加值
float prev_error; // 上一次误差(用于计算微分)
float integral_limit; // 积分限幅值
int32_t output; // PID输出(0~999,对应PWM占空比)
} PID_Controller;
// 外部声明:PID控制器实例,供 main.c 使用
extern PID_Controller pid;
// 函数声明
void PID_Init(void); // 初始化PID参数
void PID_Calculate(float actual_temp); // PID核心计算
void PID_SetTarget(float temp); // 修改目标温度
void PID_SetParams(float kp, float ki, float kd); // 修改PID参数
void PID_PrintParams(void); // 打印当前参数
#endif
📌 头文件的作用:声明了数据结构和函数接口。其他 .c 文件只需 #include "pid.h" 就能使用PID功能,无需知道内部实现细节。这就是封装的思想。
8.3 pid.c —— PID模块源文件
#include "pid.h"
#include "stdio.h"
// PID控制器实例(全局变量,供main.c引用)
PID_Controller pid;
// PID参数初始化
void PID_Init(void) {
pid.Kp = 50.0f; // 初始比例系数
pid.Ki = 2.0f; // 初始积分系数
pid.Kd = 10.0f; // 初始微分系数
pid.target = 40.0f; // 默认目标温度40℃
pid.integral = 0.0f;
pid.prev_error = 0.0f;
pid.integral_limit = 500.0f; // 积分上限
pid.output = 0;
}
// PID核心计算函数
void PID_Calculate(float actual_temp) {
float error = pid.target - actual_temp; // ① 计算当前误差
float derivative; // ② 微分项
// ③ 积分项累加,并做限幅处理
pid.integral += error;
if(pid.integral > pid.integral_limit)
pid.integral = pid.integral_limit;
if(pid.integral < -pid.integral_limit)
pid.integral = -pid.integral_limit;
// ④ 微分项 = 本次误差 - 上次误差(误差变化速度)
derivative = error - pid.prev_error;
pid.prev_error = error; // 保存误差,供下次使用
// ⑤ PID输出 = P项 + I项 + D项
float output_f = pid.Kp * error
+ pid.Ki * pid.integral
+ pid.Kd * derivative;
// ⑥ 输出限幅:限制在0~999(PWM有效范围)
if(output_f > 999) output_f = 999;
if(output_f < 0) output_f = 0;
pid.output = (int32_t)output_f;
}
// 设置目标温度
void PID_SetTarget(float temp) {
pid.target = temp;
pid.integral = 0; // 切换目标时清空积分,防止突变
printf("Target set to: %.1f deg C\r\n", pid.target);
}
// 设置PID参数
void PID_SetParams(float kp, float ki, float kd) {
pid.Kp = kp;
pid.Ki = ki;
pid.Kd = kd;
printf("PID Params: Kp=%.1f Ki=%.1f Kd=%.1f\r\n", pid.Kp, pid.Ki, pid.Kd);
}
// 打印当前参数
void PID_PrintParams(void) {
printf("--- PID Status ---\r\n");
printf("Target: %.1f deg C\r\n", pid.target);
printf("Kp=%.1f Ki=%.1f Kd=%.1f\r\n", pid.Kp, pid.Ki, pid.Kd);
printf("Output: %ld\r\n", pid.output);
printf("-------------------\r\n");
}
8.4 main.c —— 主文件修改
在 main.c 顶部包含我们的PID头文件:
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "bitband.h"
#include "pid.h" // ← 新增:包含PID模块头文件
#include "string.h"
/* USER CODE END Includes */

8.5 main.c —— 串口调参相关变量
/* USER CODE BEGIN PV */
// —— 第8章原有变量(保持不变)——
unsigned char Count0, Step0, ADS_CH, ADC_Cnt;
int temperature = 0;
unsigned int const ADS1232_Tab[] = { /* 完整分度表,同第8章 */ };
#define TABLE_SIZE (sizeof(ADS1232_Tab) / sizeof(ADS1232_Tab[0]))
// —— 本章新增:串口接收缓冲区 ——
uint8_t rx_buff[64];
uint8_t rx_index = 0;
uint8_t rx_complete = 0;
/* USER CODE END PV */

8.6 main.c —— PWM输出函数与串口指令解析
/* USER CODE BEGIN 4 */
// 设置PWM占空比(0~999)
void Heat1_SetPower(uint16_t power) {
if(power > 999) power = 999;
TIM3->CCR1 = power; // 直接设置比较寄存器
}
// 串口指令解析函数
void ParseCommand(uint8_t *cmd) {
char cmd_str[64];
strcpy(cmd_str, (char*)cmd);
if(strncmp(cmd_str, "P=", 2) == 0) {
float val = atof(cmd_str + 2);
PID_SetParams(val, pid.Ki, pid.Kd); // 修改Kp,保持其他不变
}
else if(strncmp(cmd_str, "I=", 2) == 0) {
float val = atof(cmd_str + 2);
PID_SetParams(pid.Kp, val, pid.Kd); // 修改Ki
}
else if(strncmp(cmd_str, "D=", 2) == 0) {
float val = atof(cmd_str + 2);
PID_SetParams(pid.Kp, pid.Ki, val); // 修改Kd
}
else if(strncmp(cmd_str, "T=", 2) == 0) {
float val = atof(cmd_str + 2);
PID_SetTarget(val); // 修改目标温度
}
else if(strcmp(cmd_str, "?") == 0) {
PID_PrintParams(); // 打印当前状态
}
else {
printf("Cmd: P=xx / I=xx / D=xx / T=xx / ?\r\n");
}
}
// 串口接收中断回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
uint8_t ch;
HAL_UART_Receive_IT(&huart1, &ch, 1); // 重新开启接收
if(ch == '\r' || ch == '\n') { // 换行表示指令结束
if(rx_index > 0) {
rx_buff[rx_index] = '\0';
rx_complete = 1;
rx_index = 0;
}
} else {
if(rx_index < 63) {
rx_buff[rx_index++] = ch;
}
}
}
}
/* USER CODE END 4 */

8.7 main.c —— 主函数
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM3_Init(); // ← 新增:CubeIDE自动生成的TIM3初始化
ADS1232_Init();
PID_Init(); // ← 新增:初始化PID参数
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // ← 新增:启动PWM输出
Heat1_SetPower(0); // 初始关闭加热
// 启动串口接收
uint8_t ch;
HAL_UART_Receive_IT(&huart1, &ch, 1);
printf("\r\n===== PID 温度控制系统 =====\r\n");
PID_PrintParams();
printf("指令: P=xx / I=xx / D=xx / T=xx / ?\r\n\r\n");
while(1) {
int32_t adc = ADS1232_ReadData();
float current_temp = (float)ADS1232_Tab[adc] / 10.0f;
PID_Calculate(current_temp); // ← 调用PID计算
Heat1_SetPower(pid.output); // ← 更新PWM输出
// 打印温度数据(格式可复制到Excel绘图)
printf("DATA: %.1f, %.1f, %ld\r\n", current_temp, pid.target, pid.output);
// 处理串口调参指令
if(rx_complete) {
rx_complete = 0;
ParseCommand(rx_buff);
}
// LED指示加热状态
if(pid.output > 0)
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
else
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
HAL_Delay(1000);
}
}
✨ 九、下载验证与参数整定实验(见证控制效果!)
9.1 开环测试(验证PWM输出)
先在代码中写固定值 Heat1_SetPower(500),用万用表测量PC6引脚应该有约50%占空比的方波信号,加热器应温热。确认PWM输出正常后再恢复PID控制。
9.2 纯P控制实验
在串口助手发送 I=0、D=0,只留比例控制。观察串口输出的温度曲线:
理论数据这是这样的规律
DATA: 25.3, 40.0, 999
DATA: 25.3, 40.0, 735
DATA: 28.1, 40.0, 595
DATA: 32.4, 40.0, 380
DATA: 38.7, 40.0, 65
DATA: 42.5, 40.0, 0
DATA: 39.8, 40.0, 10
DATA: 41.2, 40.0, 0
... 反复震荡,始终停不下来
我这边温度功率比较低 所以反应比较慢 但总体的趋势是跟上面是一样的。 有可能会过冲,这个需要根据自己设备攻略和热容去调pid的数值。
分析:纯P控制响应快,但存在稳态误差和持续震荡。
9.3 PI控制实验
发送 I=2 加入积分。温度最终可以稳在40℃,但可能会超调。
9.4 完整PID控制实验
发送 D=8 加入微分。温度快速到达目标、几乎不超调、稳定在40℃±0.2℃。
9.5 串口实时调参演示
在串口助手依次发送:
T=50.0 (修改目标温度)
T=30.0 (再改回去)
? (查看当前参数)
每次发送后立即生效,观察温度曲线的变化。
📝 十、关键功能总结(新手必背,避免踩坑!)
- PWM本质:通过调整占空比实现功率的连续可调,好比油门踩多深
- STM32 PWM配置:ARR定周期,CCR定占空比,TIM3_CH1 通过完全重映射输出到 PC6
- PID三大组成部分:P快速响应,I消除稳态误差,D抑制超调
- 积分限幅:必须限制积分范围,防止积分饱和导致失控
- 输出限幅:PID输出必须限制在0~999(PWM有效范围)
- 模块化编程:将PID封装成独立
.c/.h文件,结构清晰、方便移植 - 串口调参:通过
P= / I= / D= / T=指令实时修改参数 - 闭环控制流程:测温 → 算PID → 更新PWM → 再测温,循环往复
❌ 十一、常见问题排查(遇到问题不用慌,对照排查!)
| 现象 | 可能原因 |
|---|---|
| 温度不上升 | 加热器未供电、MOS管未导通、PWM未输出(检查HAL_TIM_PWM_Start) |
| 温度一直震荡 | Kp太大或Ki太小,先降低Kp |
| 温度稳定但有偏差 | Ki太小或积分限幅过小,增大Ki |
| 温度超调严重 | Kd太小,加入微分或增大Kd |
| 串口调参无效 | 指令格式错误(正确格式:P=30.0,注意大写字母) |
| PWM无输出 | MX_TIM3_Init 是否生成?有没有调 HAL_TIM_PWM_Start |
| LED不亮 | 引脚配置错误或输出极性反了 |
编译报错 undefined reference to pid |
检查 pid.c 是否添加到编译链中 |
| printf浮点数打印异常 | 添加 -u _printf_float 链接器标志 |
| PWM无输出,PC6始终低电平 | 是否开启了TIM3完全重映射?检查 __HAL_AFIO_REMAP_TIM3_ENABLE() |
📢 十二、下篇预告(精彩不容错过!新手必追)
STM32保姆级入门教程|第10章:I2C总线协议详解 + EEPROM读写
手把手教你掌握I2C通信、读写AT24C256C存储器!
原创不易,创作花费大量时间和精力💦,如果本文对你有帮助,欢迎
点赞👍、收藏⭐、关注➕,有任何问题,评论区留言,我会一一回复!你的支持,就是我持续更新的动力~
本文所使用的工程文件已上传至配套资源中,如有需要可自行下载。也可关注博主后留言获取
🎁 欢迎关注公众号,获取更多技术干货!
博主准备的资料包涵盖了从硬件电路设计到STM32单片机开发,再到Linux系统学习的全链路内容,适合不同阶段的学习者。

📂资料包目录
- 00-STM32单片机环境搭建
- 01-硬件电路合集
- 02-硬件设计开发工具包
- 03-C语言学习资料包
- 04-STM32单片机开发工具包
- 05-STM32传感器模块合集
- 06-STM32项目合集
- 07-STM32单片机书籍&芯片手册
- 08-Linux相关学习资料包

更多推荐

所有评论(0)