基于 STM32 的高精度 PID 温控系统设计与实现:采用 Pt1000 温度传感器与 PWM 控制技术
一、项目概述
随着科技的发展和工业自动化的普及,高精度的温度控制系统在各个领域的应用越来越广泛,如实验室设备、食品加工、电子元器件测试等。本项目基于 STM32 微控制器设计了一种快速、高精度的 PID 温控系统。该系统采用比例积分微分(PID)控制算法,能够实时监测和调整温度,确保控制精度。
项目目标
本项目旨在开发一个能够快速响应并高精度控制温度的系统,适用于对温控的响应速度和精度要求较高的场合。系统将通过上位机进行设定温度输入,同时监控系统的运行状态。
技术栈关键词
-
硬件:STM32 微控制器、Pt1000 温度传感器、TLP521-1 光电耦合器、BD237 功率晶体管、FTA951 热电冷却器(TEC)
-
软件:PID 控制算法、PWM 控制、串口通信
-
开发环境:Keil MDK,STM32CubeMX
二、系统架构
设计系统架构
本系统采用 STM32 作为主控单元,温度采集电路通过 Pt1000 电阻温度计进行温度传感。TLP521-1 光电耦合器与 BD237 功率晶体管的组合用于驱动 TEC。PWM 信号用于控制 TEC 的工作状态,进而实现温度调节。
系统架构图
组件选择
-
STM32 微控制器:选择 STM32F103C8T6,具有较强的处理能力和丰富的外设接口。
-
Pt1000 温度传感器:高精度,适用于温度测量。
-
TLP521-1 光电耦合器:用于隔离控制信号和功率电路。
-
BD237 功率晶体管:用于驱动 TEC,控制大电流。
-
FTA951 热电冷却器:用于温度控制,快速响应。
三、环境搭建和注意事项
环境搭建
-
硬件连接:
-
将 Pt1000 温度传感器连接到 STM32 的 ADC 引脚。
-
使用 TLP521-1 光电耦合器将 STM32 的 PWM 输出连接到 BD237 功率晶体管。
-
将 BD237 的输出连接到 FTA951 热电冷却器。
-
-
软件准备:
-
安装 STM32CubeMX 和 Keil MDK 开发环境。
-
配置 STM32 的 ADC、PWM、串口等外设。
-
注意事项
-
确保电路连接正确,避免短路和干扰。
-
在调试过程中,注意温度传感器的校准,确保测量准确。
-
避免 TEC 超负荷工作,以免损坏。
四、代码实现过程
在本节中,我们将详细介绍基于 STM32 的 PID 温控系统的代码实现过程,包括温度采集、PID 控制算法的实现、PWM 输出控制等。每个模块将配有代码示例和详细说明,以确保代码逻辑清晰、易于理解和维护。
1.主程序框架
代码示例
以下是 STM32 PID 温控系统的主程序框架,包含温度采集、PID 控制和 PWM 输出的核心代码。
#include "stm32f4xx.h"
#include "pid.h"
#include "temperature.h"
#include "pwm.h"
#include "usart.h"
// PID 控制参数和目标温度
float setpoint = 25.0; // 设定温度(目标温度)
PID_Controller pid; // PID 控制器实例
void setup() {
USART_Init(); // 初始化串口
ADC_Init(); // 初始化ADC,用于温度采集
PWM_Init(); // 初始化PWM,用于控制TEC
PID_Init(&pid, 1.0, 0.1, 0.01); // 初始化PID控制器,设置Kp, Ki, Kd
}
int main() {
setup(); // 进行系统初始化
while (1) {
// 获取当前温度
float current_temp = Get_Temperature();
// 计算PWM值
float pwm_value = PID_Compute(&pid, setpoint, current_temp);
// 根据计算得到的PWM值进行控制
Set_PWM(pwm_value);
// 通过串口输出当前温度和PWM值,便于调试
USART_Send(current_temp, pwm_value);
// 延时,避免程序过快循环
HAL_Delay(1000); // 每秒更新一次
}
}
代码说明
-
库文件引用:
-
#include "stm32f4xx.h"
:包含 STM32F4 系列的标准库。 -
其他头文件如
pid.h
、temperature.h
、pwm.h
和usart.h
分别用于 PID 控制、温度读取、PWM 控制和串口通信。
-
-
全局变量定义:
-
float setpoint = 25.0;
:设定温度为 25°C,用户可以根据需求调整此值。 -
PID_Controller pid;
:创建一个 PID 控制器实例,用于管理 PID 控制的参数和状态。
-
-
系统初始化:
-
USART_Init()
:初始化串口,用于数据传输和调试。 -
ADC_Init()
:初始化 ADC 模块,用于读取温度传感器的数据。 -
PWM_Init()
:初始化 PWM 模块,用于控制热电冷却器(TEC)的输出。 -
PID_Init(&pid, 1.0, 0.1, 0.01)
:初始化 PID 控制器,并设置比例(Kp)、积分(Ki)和微分(Kd)参数。
-
-
主循环:
-
Get_Temperature()
:调用温度读取函数,获取当前温度值。 -
PID_Compute(&pid, setpoint, current_temp)
:根据设定温度和当前温度计算 PWM 控制值。 -
Set_PWM(pwm_value)
:根据 PID 计算得到的 PWM 值调整 TEC 的工作状态。 -
USART_Send(current_temp, pwm_value)
:通过串口发送当前温度和 PWM 值,便于监控和调试。 -
HAL_Delay(1000)
:延时 1 秒,以避免循环过快导致 CPU 占用过高。
-
2.温度读取模块
代码示例
以下是温度读取模块代码,使用 ADC 读取 Pt1000 温度传感器的温度。
#include "temperature.h"
float Get_Temperature() {
// 启动 ADC 转换
HAL_ADC_Start(&hadc1);
// 等待 ADC 转换完成
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
// 获取 ADC 转换结果
uint32_t adc_value = HAL_ADC_GetValue(&hadc1);
// 将 ADC 值转换为温度(假设线性关系)
float temperature = (adc_value / 4095.0) * 100.0; // 线性转换,假设满量程为 100°C
return temperature; // 返回温度值
}
代码说明
-
函数定义:
float Get_Temperature()
:定义一个函数,用于读取温度传感器的值并返回当前温度。
-
ADC 启动和转换:
-
HAL_ADC_Start(&hadc1)
:启动 ADC 转换。hadc1
是 ADC 的句柄,需根据具体配置进行定义。 -
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY)
:等待 ADC 转换完成。此函数会阻塞,直到转换结束。
-
-
获取 ADC 值:
uint32_t adc_value = HAL_ADC_GetValue(&hadc1)
:获取 ADC 转换的结果,返回值为 ADC 的数字表示(0 到 4095,通常为 12 位分辨率)。
-
温度转换:
float temperature = (adc_value / 4095.0) * 100.0
:将 ADC 值转换为温度值。这里假设 Pt1000 温度传感器的输出为线性关系,满量程为 100°C,具体转换关系需根据温度传感器的特性曲线进行调整。
-
返回温度值:
return temperature
:返回计算得到的温度值。
3.PID 控制模块
代码示例
以下是 PID 控制模块的代码,实现 PID 控制算法。
#include "pid.h"
// 初始化 PID 控制器
void PID_Init(PID_Controller *pid, float Kp, float Ki, float Kd) {
pid->Kp = Kp; // 比例增益
pid->Ki = Ki; // 积分增益
pid->Kd = Kd; // 微分增益
pid->previous_error = 0.0; // 初始化误差
pid->integral = 0.0; // 初始化积分
}
// 计算 PID 控制输出
float PID_Compute(PID_Controller *pid, float setpoint, float measured_value) {
// 计算当前误差
float error = setpoint - measured_value;
// 计算积分
pid->integral += error;
// 计算微分
float derivative = error - pid->previous_error;
// 计算 PID 输出
float output = pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative;
// 更新上一误差
pid->previous_error = error;
// 输出限制(根据系统要求进行限制)
if (output > 100.0) output = 100.0; // 限制最大输出
if (output < 0.0) output = 0.0; // 限制最小输出
return output; // 返回控制输出
}
代码说明
-
PID 控制器结构体:
PID_Controller
结构体包含 PID 控制器的参数和状态变量(如比例增益 Kp、积分增益 Ki、微分增益 Kd、上次误差、积分值等)。
-
初始化 PID 控制器:
void PID_Init(PID_Controller *pid, float Kp, float Ki, float Kd)
:初始化 PID 控制器的参数,并将误差和积分初始化为 0。
-
计算 PID 控制输出:
-
float PID_Compute(PID_Controller *pid, float setpoint, float measured_value)
:根据设定值和测量值计算 PID 控制输出。 -
float error = setpoint - measured_value
:计算当前误差。 -
pid->integral += error
:累加误差,计算积分项。 -
float derivative = error - pid->previous_error
:计算微分项,表示误差的变化率。 -
float output = pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative
:计算 PID 控制输出。 -
pid->previous_error = error
:更新上一误差,用于下次计算微分项。
-
-
输出限制:为了防止输出超出系统控制范围,设置了输出的最大值和最小值。
4.PWM 控制模块
代码示例
以下是 PWM 控制模块的代码,负责根据计算得到的 PWM 值控制 TEC 的工作。
#include "pwm.h"
// 初始化 PWM
void PWM_Init() {
// 1. 定义定时器句柄
TIM_HandleTypeDef htim2;
// 2. 启用定时器时钟
__HAL_RCC_TIM2_CLK_ENABLE();
// 3. 设置定时器基础参数
htim2.Instance = TIM2; // 选择定时器2
htim2.Init.Prescaler = 84 - 1; // 预分频器设置(假设72MHz的时钟频率,分频到1MHz)
htim2.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数模式
htim2.Init.Period = 1000 - 1; // 设定PWM周期(1ms)
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
// 4. 初始化定时器
HAL_TIM_PWM_Init(&htim2);
// 5. PWM输出通道配置
TIM_OC_InitTypeDef sConfigOC;
sConfigOC.OCMode = TIM_OCMODE_PWM1; // PWM模式1
sConfigOC.Pulse = 0; // 初始占空比为0%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; // 输出极性:高电平有效
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; // 禁用快速模式
// 6. 启用PWM通道
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // 启动PWM通道
}
// 设置PWM输出
void Set_PWM(float pwm_value) {
// 限制PWM值在0到1000之间
if (pwm_value > 1000.0) pwm_value = 1000.0;
if (pwm_value < 0.0) pwm_value = 0.0;
// 设置PWM的占空比
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, (uint32_t)pwm_value);
}
代码说明
-
PWM初始化:
-
void PWM_Init()
:初始化 PWM 功能。 -
TIM_HandleTypeDef htim2;
:定义一个定时器句柄,用于配置和控制定时器。 -
__HAL_RCC_TIM2_CLK_ENABLE();
:启用 TIM2 定时器的时钟。 -
htim2.Init.Prescaler = 84 - 1;
:设置预分频器,将 72MHz 的时钟频率分频到 1MHz。 -
htim2.Init.Period = 1000 - 1;
:设定 PWM 周期为 1ms,即 1000 个时钟周期。
-
-
初始化定时器:
HAL_TIM_PWM_Init(&htim2);
:初始化定时器以支持 PWM 功能。
-
PWM输出通道配置:
-
TIM_OC_InitTypeDef sConfigOC;
:定义 PWM 输出通道的配置结构体。 -
sConfigOC.OCMode = TIM_OCMODE_PWM1;
:设置为 PWM 模式 1。 -
sConfigOC.Pulse = 0;
:初始占空比为 0%。 -
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
:配置 PWM 输出通道。
-
-
启动 PWM 通道:
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
:启动 PWM 输出通道。
-
设置 PWM 输出:
-
void Set_PWM(float pwm_value)
:根据 PID 计算结果设置 PWM 输出。 -
if (pwm_value > 1000.0) pwm_value = 1000.0;
:限制 PWM 值在 0 到 1000 之间,确保其在可接受范围内。 -
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, (uint32_t)pwm_value);
:设置 PWM 的占空比,以控制 TEC 的功率。
-
5. 串口通信模块
代码示例
以下是串口通信模块的代码,用于发送当前温度和 PWM 值,便于监控和调试。
#include "usart.h"
// 初始化串口
void USART_Init() {
// 1. 定义串口句柄
UART_HandleTypeDef huart1;
// 2. 启用USART1时钟
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA时钟
// 3. 配置GPIO引脚
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10; // TX和RX引脚
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL; // 无上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; // 高速
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 4. 配置USART参数
huart1.Instance = USART1;
huart1.Init.BaudRate = 9600; // 波特率
huart1.Init.WordLength = UART_WORDLENGTH_8B; // 8位数据位
huart1.Init.StopBits = UART_STOPBITS_1; // 1位停止位
huart1.Init.Parity = UART_PARITY_NONE; // 无奇偶校验
huart1.Init.Mode = UART_MODE_TX_RX; // 收发模式
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 无硬件流控制
huart1.Init.OverSampling = UART_OVERSAMPLING_16; // 16倍过采样
HAL_UART_Init(&huart1); // 初始化UART
}
// 发送数据通过串口
void USART_Send(float temperature, float pwm_value) {
char buffer[100];
int len = sprintf(buffer, "Current Temp: %.2f °C, PWM Value: %.2f\r\n", temperature, pwm_value); // 格式化字符串
HAL_UART_Transmit(&huart1, (uint8_t *)buffer, len, HAL_MAX_DELAY); // 发送数据
}
代码说明
-
串口初始化:
-
void USART_Init()
:该函数用于配置和初始化串口。 -
UART_HandleTypeDef huart1;
:定义一个串口句柄,用于管理 USART1 的配置。
-
-
启用时钟:
-
__HAL_RCC_USART1_CLK_ENABLE();
:使能 USART1 时钟。 -
__HAL_RCC_GPIOA_CLK_ENABLE();
:使能 GPIOA 时钟,用于串口引脚配置。
-
-
配置 GPIO 引脚:
-
GPIO_InitTypeDef GPIO_InitStruct;
:定义 GPIO 初始化结构体。 -
GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10;
:TX(发送)和 RX(接收)引脚的配置。 -
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
:设置引脚为复用推挽输出模式。 -
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
:初始化 GPIO 引脚。
-
-
配置 USART 参数:
-
huart1.Init.BaudRate = 9600;
:设置波特率为 9600。 -
huart1.Init.WordLength = UART_WORDLENGTH_8B;
:数据位长度为 8 位。 -
huart1.Init.StopBits = UART_STOPBITS_1;
:设置 1 位停止位。 -
huart1.Init.Parity = UART_PARITY_NONE;
:设置无奇偶校验。 -
HAL_UART_Init(&huart1);
:根据配置初始化 USART1。
-
-
发送数据:
-
void USART_Send(float temperature, float pwm_value)
:该函数用于通过串口发送当前温度和 PWM 值。 -
char buffer[100];
:定义字符数组用于存储发送的数据。 -
int len = sprintf(buffer, "Current Temp: %.2f °C, PWM Value: %.2f\r\n", temperature, pwm_value);
:格式化输出字符串。 -
HAL_UART_Transmit(&huart1, (uint8_t *)buffer, len, HAL_MAX_DELAY);
:通过 USART 发送数据。
-