原创 ✍️ | 新手零门槛,全程新建工程,手把手不踩坑!

文章标签:#stm32 #stm32cubeide #PID控制 #PWM加热 #温度控制 #闭环控制 #串口调参 #工业实战 #嵌入式

系列前置博客(必看!否则跟不上哦😜):

  1. 第1章:零基础必看,从认知到实战全解析
  2. 第2章:STM32CubeIDE 使用+点亮LED
  3. 第3章:从新建工程到LED闪烁
  4. 第4章:GPIO输入+外部中断 实现按键控制LED
  5. 第5章:GPIO内部结构 + 8种模式详解
  6. 第6章:定时器中断原理 + 精准LED闪烁
  7. 第7章:串口通信 + printf重定向打印调试
  8. 第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算法公式推导、模块化封装、参数整定实验全部讲到最细,全程用“大白话+表情包+一步一截图”,新手看完不仅能学会,还能直接用在自己的恒温项目里,爽到飞起!

🎯 二、本章核心功能目标(清晰明确,学完不迷茫)

  1. 搞懂 ✅ PWM是什么、如何产生、为什么用它控温(占空比=功率比例)
  2. 掌握 ✅ PID控制算法的三大组成部分(P、I、D分别干啥?)
  3. 学会 ✅ 位置式PID公式与代码实现(公式→变量→函数,一条龙)
  4. 学会 ✅ 定时器PWM输出配置(TIM3_CH1,占空比0~999可调)
  5. 掌握 ✅ 模块化编程思想(把PID封装成独立文件,方便移植复用)
  6. 掌握 ✅ 串口实时调参(发送指令修改P/I/D/目标温度)
  7. 实现 ✅ :
    • PT100每秒采集温度
    • PID算法自动计算加热功率
    • PWM输出控制加热器
    • 串口打印温度曲线数据(可复制到Excel绘制响应曲线)
    • LED指示加热状态(有功率输出时亮,无输出时灭)
    • 串口发送指令实时调整PID参数
  8. 理解 ✅ 为什么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 参数整定口诀

参数整定找最佳,从小到大逐步查。
先调比例后积分,最后再把微分加。
曲线震荡频繁动,比例系数要缩小。
曲线漂浮绕大弯,积分时间往下降。
曲线偏离回复慢,微分时间应加长。

简单实操步骤

  1. 先设 Ki=0, Kd=0,只调Kp,调到温度能快速接近目标但会震荡
  2. 加上Ki,慢慢增大,直到稳态误差消失
  3. 最后加Kd,抑制超调和震荡,让曲线平滑

🛠️ 七、CubeIDE 1.19.0 配置步骤(复用第8章工程,新增PWM)

重点🔥:在第8章工程基础上增量配置,保留所有原有设置。

步骤1:打开第8章工程

用STM32CubeIDE打开第8章的工程文件 PT100_ADS1232_Demo.ioc

步骤2:配置TIM3为PWM输出

  1. 左侧点击 Timers → 选择 TIM3
  2. Clock Source 选择 Internal Clock
  3. Channel1 选择 PWM Generation CH1
  4. 切换到 Parameter Settings
    • Prescaler (PSC)71
    • Counter Period (ARR)999
    • Pulse (CCR3)0(初始占空比0%)
  5. 开启TIM3完全重映射(将CH1映射到PC6):
    • 在CubeMX左侧 System Core → GPIO 中,找到 PC6,确认其功能为 TIM3_CH1
    • 通道也可以在PC6的引脚上面选择

在这里插入图片描述

步骤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.cpid.h 文件,这样代码结构清晰、方便移植到其他项目。

8.1 新建PID模块文件

在STM32CubeIDE工程中:

  1. 右键 Core/SrcNewSource File → 文件名:pid.c
  2. 右键 Core/IncNewHeader 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=0D=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   (再改回去)
?        (查看当前参数)

每次发送后立即生效,观察温度曲线的变化。

📝 十、关键功能总结(新手必背,避免踩坑!)

  1. PWM本质:通过调整占空比实现功率的连续可调,好比油门踩多深
  2. STM32 PWM配置:ARR定周期,CCR定占空比,TIM3_CH1 通过完全重映射输出到 PC6
  3. PID三大组成部分:P快速响应,I消除稳态误差,D抑制超调
  4. 积分限幅:必须限制积分范围,防止积分饱和导致失控
  5. 输出限幅:PID输出必须限制在0~999(PWM有效范围)
  6. 模块化编程:将PID封装成独立 .c/.h 文件,结构清晰、方便移植
  7. 串口调参:通过 P= / I= / D= / T= 指令实时修改参数
  8. 闭环控制流程:测温 → 算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相关学习资料包

在这里插入图片描述

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐