基于PID算法的TEC半导体恒温控制系统设计
项目开发背景
恒温控制在科学研究、工业生产和医疗设备等领域具有关键作用,精确的温度维持直接影响到实验数据的可靠性、产品性能的一致性以及设备运行的安全性。传统温控方法如电阻加热或压缩机制冷往往存在响应滞后、能效低下或控制精度不足等局限,因此需要一种更灵活、高效且可精确调控的解决方案,以满足现代应用对温度稳定性的高要求。
半导体制冷片(TEC)基于帕尔贴效应,通过调节电流方向和大小实现快速制冷或加热,具有无机械运动部件、响应迅速和体积紧凑等优势。结合PID控制算法,特别是增量式数字PID,能够动态补偿温度偏差,实现闭环控制,提升系统的稳定性和适应性。这种算法在嵌入式系统中计算效率高,适合实时调节,为TEC的精确驱动提供了理论基础。
在硬件设计上,选用TI MSPM0G3507单片机作为核心,其高精度PWM和ADC外设可支持精细的电流调制和传感器数据处理。PT1000铂电阻温度传感器配合恒流源电路和ADS1115模数转换器,确保了温度采集的高线性度和抗干扰能力。DRV8871芯片构建的H桥驱动电路使TEC能双向工作,适应制冷与加热需求,而集成OLED显示、按键和串口通信则增强了人机交互和远程调试的便利性。此外,过流保护与线性稳压电源的设计进一步保障了系统在复杂环境中的可靠运行。
本项目的开发旨在构建一个模块化、可扩展的恒温控制平台,适用于实验室仪器、小型恒温装置或嵌入式设备中的温度管理。通过软硬件协同优化,该系统将实现温度的高精度设定与稳定维持,为相关领域的自动化与智能化发展提供实用技术支持。
设计实现的功能
(1)使用PT1000铂电阻温度传感器采集被控对象的实时温度。
(2)采用增量式PID控制算法,动态调节半导体制冷片(TEC)的驱动电流方向和大小。
(3)通过OLED显示屏实时显示设定温度、当前温度、PID参数及系统状态。
(4)支持按键设定目标温度,并可通过串口通信从上位机修改PID参数。
(5)具备过流保护功能,当驱动电流超过阈值时自动切断输出。
项目硬件模块组成
(1)主控模块:采用TI MSPM0G3507单片机,利用其高精度PWM与ADC外设。
(2)温度采集模块:采用PT1000传感器搭配恒流源电路,由ADS1115模数转换器进行差分采样。
(3)TEC驱动模块:基于DRV8871电机驱动芯片搭建的H桥电路,用于双向驱动TEC。
(4)人机交互模块:包括0.96寸OLED显示屏(I2C接口)和独立按键。
(5)保护与通信模块:包括INA180电流采样芯片、CH340 USB转串口芯片及LM317线性稳压电源。
设计意义
该设计实现的基于PID算法的TEC半导体恒温控制系统,在工业与科研领域具有重要的实用价值。通过集成PT1000铂电阻温度传感器和高精度模数转换器,系统能够实时采集被控对象的温度数据,确保温度监测的准确性和可靠性,从而满足精密实验、医疗设备或电子产品测试中对稳定温度环境的苛刻需求,有效避免因温度波动导致的性能下降或损坏。
采用增量式PID控制算法,系统能够动态调节半导体制冷片的驱动电流方向和大小,实现快速响应和最小超调,显著提升温控过程的精度和稳定性。这种智能调节机制不仅优化了能耗效率,还延长了TEC的使用寿命,适用于需要长时间恒温运行的场景,如环境模拟或材料处理。
硬件模块的精心选型与集成,如TI MSPM0G7单片机的高精度PWM与ADC外设、DRV8871芯片搭建的H桥驱动电路,保证了系统的高效执行和灵活控制。这些组件协同工作,增强了系统的实时处理能力和可扩展性,便于未来升级或适应不同应用场合,降低了整体维护成本。
系统的人机交互与安全保护功能进一步强化了其实用性。OLED显示屏和按键支持方便的温度设定与状态监控,而串口通信允许远程调整PID参数,提升了操作便利性。过流保护通过电流采样芯片自动切断输出,防止设备过载或损坏,确保了运行安全,使系统在无人值守环境下也能可靠工作。
总之,该设计不仅实现了精确、稳定的温度控制,还通过模块化硬件和智能化软件结合,为半导体恒温技术提供了经济高效的解决方案。它在激光冷却、生物培养、食品储存等领域具有广泛应用潜力,有助于推动相关行业的技术进步和自动化发展。
设计思路
该系统设计旨在通过PID控制算法实现对TEC半导体器件的精确温度调节,确保被控对象维持在设定温度范围内。系统以TI MSPM0G3507单片机为核心控制器,利用其高精度PWM和ADC外设处理控制逻辑,结合硬件模块实现温度采集、驱动调节、人机交互和保护功能。
温度采集模块使用PT1000铂电阻传感器,搭配恒流源电路提供稳定激励,并通过ADS1115模数转换器进行差分采样,将模拟温度信号转换为数字值,确保测量精度和抗干扰能力。采集到的实时温度数据送入单片机,作为PID控制的反馈输入。
控制算法采用增量式PID,单片机根据设定温度与当前温度的偏差,动态计算控制量,输出PWM信号调节TEC驱动。该算法能有效减少超调,提高系统响应速度,适应温度变化的动态过程。PWM信号的占空比和方向决定了TEC的驱动电流大小和极性,实现加热或冷却的双向控制。
TEC驱动模块基于DRV8871电机驱动芯片搭建H桥电路,接收单片机的PWM信号,驱动半导体制冷片工作。通过改变电流方向和幅度,精确控制TEC的制冷或制热效应,从而调整被控对象温度。驱动电路设计考虑了效率和稳定性,确保快速响应控制指令。
人机交互模块包括OLED显示屏和独立按键,OLED通过I2C接口实时显示设定温度、当前温度、PID参数及系统状态,方便用户监控。按键用于设定目标温度,允许用户直接调整控制目标,增强系统的操作性。
保护与通信模块集成INA180电流采样芯片,实时监测TEC驱动电流,当电流超过安全阈值时自动切断输出,防止设备损坏。同时,CH340 USB转串口芯片提供上位机通信接口,支持通过串口修改PID参数,便于系统调试和优化。电源部分使用LM317线性稳压器,为各模块提供稳定供电。
整个系统通过单片机协调各模块工作,从温度采集、PID计算到驱动输出形成闭环控制,结合显示、设定和保护功能,构建了一个实用可靠的恒温控制系统。设计注重实际应用,硬件选型和算法选择均以实现精确、稳定的温度控制为目标。
框架图
┌─────────────────────────────────────────────────────────────┐
│ TEC半导体恒温控制系统框架图 │
└─────────────────────────────────────────────────────────────┘
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ PT1000 │ │ 恒流源 │ │ ADS1115 │
│ 温度传感器 │───>│ 电路 │───>│ ADC │
└─────────────┘ └─────────────┘ └─────────────┘
│
v
┌─────────────┐ ┌─────────────────────┐
│ 独立按键 │──────────────────────────>│ │
└─────────────┘ │ TI MSPM0G3507 │
│ 主控单片机 │
┌─────────────┐ │ - ADC读取温度 │
│ CH340串口 │<──────────────────────────│ - 增量式PID算法 │
│ 通信模块 │ │ - PWM输出控制 │
└─────────────┘ │ - 过流保护逻辑 │
│ │ - 显示与按键处理 │
v └─────────────────────┘
┌─────────────┐ │
│ 上位机 │ │
└─────────────┘ │
┌───────┴───────┐
│ │
┌────v────┐ ┌────v────┐
│ INA180 │ │ OLED │
│电流采样 │ │ 显示屏 │
└────┬────┘ └─────────┘
│ │
v │
┌─────────────┐ │
│ DRV8871 │ │
│ H桥驱动电路 │<──────┘
└──────┬──────┘
│
v
┌─────────────┐
│ TEC │
│ 半导体制冷片 │
└─────────────┘
电源模块:LM317线性稳压电源为整个系统供电。
系统总体设计
该系统总体设计基于PID算法实现TEC半导体恒温控制,以TI MSPM0G3507单片机为核心控制器,利用其高精度PWM和ADC外设执行实时数据处理与控制决策。系统通过PT1000铂电阻温度传感器采集被控对象的实时温度,该传感器搭配恒流源电路以确保稳定测量,并由ADS1115模数转换器进行差分采样,提升温度采集的精度和抗干扰能力。
温度采集的数据送入单片机后,采用增量式PID控制算法动态计算调节量,以精确控制半导体制冷片(TEC)的驱动电流方向和大小。TEC驱动模块基于DRV8871电机驱动芯片搭建的H桥电路,实现双向电流驱动,从而适应制冷或加热模式,确保温度快速稳定在设定值。
人机交互模块通过0.96寸OLED显示屏实时显示设定温度、当前温度、PID参数及系统状态,提供直观的操作反馈;同时,独立按键允许用户直接设定目标温度,增强系统的便捷性。通信方面,集成CH340 USB转串口芯片支持与上位机连接,可通过串口通信远程修改PID参数,便于调试和优化。
保护与通信模块确保系统安全可靠运行,其中INA180电流采样芯片持续监测TEC驱动电流,当电流超过阈值时自动触发过流保护,切断输出以防止损坏;LM317线性稳压电源为各模块提供稳定供电,保障整体电路稳定性。系统各模块通过标准接口(如I2C、PWM)紧密协作,形成一个高效、闭环的恒温控制系统。
系统功能总结
| 功能项 | 描述 |
|---|---|
| 温度采集 | 采用PT1000铂电阻温度传感器,搭配恒流源电路和ADS1115模数转换器,实时采集被控对象温度。 |
| 温度控制 | 基于增量式PID控制算法,动态调节半导体制冷片(TEC)的驱动电流方向和大小,实现恒温控制。 |
| 数据显示 | 通过0.96寸OLED显示屏(I2C接口)实时显示设定温度、当前温度、PID参数及系统状态。 |
| 用户交互 | 支持独立按键设定目标温度;通过串口通信(CH340芯片)从上位机修改PID参数。 |
| 安全保护 | 集成INA180电流采样芯片,实现过流保护功能,当驱动电流超过阈值时自动切断输出。 |
| 硬件核心 | 主控采用TI MSPM0G3507单片机,利用其高精度PWM与ADC外设;TEC驱动基于DRV8871芯片的H桥电路;电源由LM317线性稳压提供。 |
设计的各个功能模块描述
主控模块采用TI MSPM0G3507单片机作为核心控制器,利用其高精度PWM和ADC外设实现增量式PID控制算法的计算与执行,该模块负责处理温度采集数据、动态调节TEC驱动电流,并协调系统中其他模块的协同工作,确保恒温控制的精确性和实时性。
温度采集模块使用PT1000铂电阻温度传感器搭配恒流源电路,提供稳定电流以准确测量被控对象的温度,ADS1115模数转换器进行差分采样,将传感器模拟信号转换为数字量供主控模块读取,实现实时温度采集功能。
TEC驱动模块基于DRV8871电机驱动芯片搭建H桥电路,用于双向驱动半导体制冷片(TEC),该模块根据主控模块输出的PWM信号动态调节驱动电流的方向和大小,实现制冷或制热控制,从而响应PID算法的调节指令。
人机交互模块包括0.96寸OLED显示屏通过I2C接口实时显示设定温度、当前温度、PID参数及系统状态,独立按键用于设定目标温度,提供用户交互界面,方便操作和监控。
保护与通信模块集成INA180电流采样芯片监测TEC驱动电流,实现过流保护功能,当电流超过预设阈值时自动切断输出以确保系统安全,CH340 USB转串口芯片支持串口通信,允许从上位机修改PID参数,LM317线性稳压电源为整个系统提供稳定的电源供应,保障各模块正常运行。
上位机代码设计
#include <QApplication>
#include <QMainWindow>
#include <QWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QComboBox>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QMessageBox>
#include <QTimer>
#include <QDebug>
// 定义通信协议
const QString DATA_PREFIX = "DATA:"; // 下位机发送数据前缀,例如 "DATA:TEMP:25.5,SET:30.0,KP:1.0,KI:0.1,KD:0.01"
const QString SET_CMD_PREFIX = "SET:"; // 上位机发送命令前缀,例如 "SET:KP:1.5"
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) {
setupUI();
setupSerialPort();
connectSignals();
}
~MainWindow() {
if (serialPort->isOpen()) {
serialPort->close();
}
}
private slots:
void onConnectClicked() {
if (!serialPort->isOpen()) {
serialPort->setPortName(portComboBox->currentText());
serialPort->setBaudRate(QSerialPort::Baud9600);
serialPort->setDataBits(QSerialPort::Data8);
serialPort->setStopBits(QSerialPort::OneStop);
serialPort->setParity(QSerialPort::NoParity);
serialPort->setFlowControl(QSerialPort::NoFlowControl);
if (serialPort->open(QIODevice::ReadWrite)) {
statusLabel->setText("状态: 已连接");
connectButton->setText("断开");
// 启动定时器,定期请求数据(可选)
timer->start(1000); // 每秒请求一次数据
} else {
QMessageBox::critical(this, "错误", "无法打开串口: " + serialPort->errorString());
}
} else {
serialPort->close();
statusLabel->setText("状态: 未连接");
connectButton->setText("连接");
timer->stop();
}
}
void onSendClicked() {
if (!serialPort->isOpen()) {
QMessageBox::warning(this, "警告", "请先连接串口");
return;
}
// 发送PID参数设置命令
QString kp = kpEdit->text();
QString ki = kiEdit->text();
QString kd = kdEdit->text();
QString command = SET_CMD_PREFIX + "KP:" + kp + ",KI:" + ki + ",KD:" + kd;
serialPort->write(command.toUtf8());
qDebug() << "发送命令:" << command;
}
void onSerialData() {
if (serialPort->bytesAvailable() > 0) {
QByteArray data = serialPort->readAll();
QString received = QString::fromUtf8(data).trimmed();
qDebug() << "接收数据:" << received;
if (received.startsWith(DATA_PREFIX)) {
parseData(received);
}
}
}
void onTimerTimeout() {
// 定时请求数据,可发送空命令或特定请求命令(根据下位机协议调整)
if (serialPort->isOpen()) {
// 假设下位机自动发送数据,这里不做发送;或发送请求命令,例如 "REQ:DATA"
// serialPort->write("REQ:DATA");
}
}
private:
void setupUI() {
QWidget *centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);
// 串口设置部分
QHBoxLayout *serialLayout = new QHBoxLayout();
portComboBox = new QComboBox();
QStringList ports;
foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
ports << info.portName();
}
portComboBox->addItems(ports);
serialLayout->addWidget(new QLabel("串口:"));
serialLayout->addWidget(portComboBox);
connectButton = new QPushButton("连接");
serialLayout->addWidget(connectButton);
statusLabel = new QLabel("状态: 未连接");
serialLayout->addWidget(statusLabel);
mainLayout->addLayout(serialLayout);
// 数据显示部分
QGridLayout *dataLayout = new QGridLayout();
dataLayout->addWidget(new QLabel("当前温度 (°C):"), 0, 0);
tempLabel = new QLabel("--");
dataLayout->addWidget(tempLabel, 0, 1);
dataLayout->addWidget(new QLabel("设定温度 (°C):"), 1, 0);
setTempLabel = new QLabel("--");
dataLayout->addWidget(setTempLabel, 1, 1);
mainLayout->addLayout(dataLayout);
// PID参数设置部分
QGridLayout *pidLayout = new QGridLayout();
pidLayout->addWidget(new QLabel("KP:"), 0, 0);
kpEdit = new QLineEdit();
pidLayout->addWidget(kpEdit, 0, 1);
pidLayout->addWidget(new QLabel("KI:"), 1, 0);
kiEdit = new QLineEdit();
pidLayout->addWidget(kiEdit, 1, 1);
pidLayout->addWidget(new QLabel("KD:"), 2, 0);
kdEdit = new QLineEdit();
pidLayout->addWidget(kdEdit, 2, 1);
sendButton = new QPushButton("发送PID参数");
pidLayout->addWidget(sendButton, 3, 0, 1, 2);
mainLayout->addLayout(pidLayout);
// 添加一些示例初始值(可选)
kpEdit->setText("1.0");
kiEdit->setText("0.1");
kdEdit->setText("0.05");
setWindowTitle("TEC恒温控制系统上位机");
resize(400, 300);
}
void setupSerialPort() {
serialPort = new QSerialPort(this);
timer = new QTimer(this);
}
void connectSignals() {
connect(connectButton, &QPushButton::clicked, this, &MainWindow::onConnectClicked);
connect(sendButton, &QPushButton::clicked, this, &MainWindow::onSendClicked);
connect(serialPort, &QSerialPort::readyRead, this, &MainWindow::onSerialData);
connect(timer, &QTimer::timeout, this, &MainWindow::onTimerTimeout);
}
void parseData(const QString &data) {
// 解析数据,格式示例: "DATA:TEMP:25.5,SET:30.0,KP:1.0,KI:0.1,KD:0.01"
QString stripped = data.mid(DATA_PREFIX.length()); // 移除前缀
QStringList parts = stripped.split(',');
for (const QString &part : parts) {
QStringList keyValue = part.split(':');
if (keyValue.size() == 2) {
QString key = keyValue[0].trimmed();
QString value = keyValue[1].trimmed();
if (key == "TEMP") {
tempLabel->setText(value);
} else if (key == "SET") {
setTempLabel->setText(value);
} else if (key == "KP") {
// 可选:更新KP显示,但通常只从下位机读取,不自动覆盖编辑框
// kpEdit->setText(value);
} else if (key == "KI") {
// kiEdit->setText(value);
} else if (key == "KD") {
// kdEdit->setText(value);
}
}
}
}
QSerialPort *serialPort;
QTimer *timer;
QComboBox *portComboBox;
QPushButton *connectButton;
QPushButton *sendButton;
QLabel *statusLabel;
QLabel *tempLabel;
QLabel *setTempLabel;
QLineEdit *kpEdit;
QLineEdit *kiEdit;
QLineEdit *kdEdit;
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}
#include "main.moc" // 包含MOC文件,用于Qt元对象系统
模块代码设计
// 系统时钟配置
void SystemClock_Config(void) {
RCC->CR |= RCC_CR_HSEON; // 开启HSE
while(!(RCC->CR & RCC_CR_HSERDY)); // 等待HSE就绪
FLASH->ACR |= FLASH_ACR_PRFTBE; // 使能预取缓冲区
FLASH->ACR &= ~FLASH_ACR_LATENCY;
FLASH->ACR |= FLASH_ACR_LATENCY_2; // 2个等待周期(72MHz)
RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // HCLK不分频
RCC->CFGR |= RCC_CFGR_PPRE2_DIV1; // APB2不分频
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1 2分频
// PLL配置:HSE*9=72MHz
RCC->CFGR |= RCC_CFGR_PLLSRC_HSE;
RCC->CFGR |= RCC_CFGR_PLLMULL9;
RCC->CR |= RCC_CR_PLLON; // 开启PLL
while(!(RCC->CR & RCC_CR_PLLRDY)); // 等待PLL就绪
RCC->CFGR |= RCC_CFGR_SW_PLL; // 切换系统时钟到PLL
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}
// GPIO初始化
void GPIO_Init(void) {
// 使能GPIO时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN |
RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN;
// PA0-按键输入(上拉输入)
GPIOA->CRL &= ~(0xF << 0);
GPIOA->CRL |= (0x8 << 0); // 上拉输入模式
GPIOA->ODR |= (1 << 0);
// PA1-电流采样ADC输入(模拟输入)
GPIOA->CRL &= ~(0xF << 4);
GPIOA->CRL |= (0x0 << 4);
// PA8-PWM输出(复用推挽输出,50MHz)
GPIOA->CRH &= ~(0xF << 0);
GPIOA->CRH |= (0xB << 0);
// PA9-USART1_TX(复用推挽输出,50MHz)
GPIOA->CRH &= ~(0xF << 4);
GPIOA->CRH |= (0xB << 4);
// PA10-USART1_RX(浮空输入)
GPIOA->CRH &= ~(0xF << 8);
GPIOA->CRH |= (0x4 << 8);
// PB6-I2C1_SCL(开漏输出,50MHz)
GPIOB->CRL &= ~(0xF << 24);
GPIOB->CRL |= (0xD << 24);
// PB7-I2C1_SDA(开漏输出,50MHz)
GPIOB->CRL &= ~(0xF << 28);
GPIOB->CRL |= (0xD << 28);
// PC13-TEC方向控制(推挽输出,50MHz)
GPIOC->CRH &= ~(0xF << 20);
GPIOC->CRH |= (0x3 << 20);
}
// I2C1初始化
void I2C1_Init(void) {
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // 使能I2C1时钟
I2C1->CR1 &= ~I2C_CR1_PE; // 禁用I2C
// 配置时钟:72MHz/180=400kHz
I2C1->CR2 |= (36 << 0); // 输入时钟36MHz
I2C1->CCR = 180; // CCR=180
I2C1->TRISE = 37; // TRISE=37
I2C1->CR1 |= I2C_CR1_PE; // 使能I2C
}
// I2C起始信号
void I2C1_Start(void) {
I2C1->CR1 |= I2C_CR1_START; // 发送起始条件
while(!(I2C1->SR1 & I2C_SR1_SB)); // 等待起始条件发送完成
}
// I2C停止信号
void I2C1_Stop(void) {
I2C1->CR1 |= I2C_CR1_STOP; // 发送停止条件
while(I2C1->CR1 & I2C_CR1_STOP); // 等待停止完成
}
// I2C发送地址
void I2C1_SendAddr(uint8_t addr, uint8_t dir) {
I2C1->DR = (addr << 1) | dir; // 发送地址+方向位
while(!(I2C1->SR1 & I2C_SR1_ADDR)); // 等待地址发送完成
(void)I2C1->SR2; // 清除标志位
}
// I2C发送数据
void I2C1_SendData(uint8_t data) {
while(!(I2C1->SR1 & I2C_SR1_TXE)); // 等待数据寄存器空
I2C1->DR = data; // 发送数据
while(!(I2C1->SR1 & I2C_SR1_BTF)); // 等待发送完成
}
// I2C接收数据
uint8_t I2C1_ReadData(void) {
I2C1->CR1 &= ~I2C_CR1_ACK; // 发送非应答
I2C1->CR1 |= I2C_CR1_STOP; // 发送停止条件
while(!(I2C1->SR1 & I2C_SR1_RXNE)); // 等待数据接收完成
return I2C1->DR; // 返回数据
}
// ADS1115温度传感器初始化
void ADS1115_Init(void) {
// ADS1115地址:0x48(ADDR接GND)
uint8_t config[3] = {0x01, 0xC3, 0xE3}; // 配置寄存器:AIN0-AIN1差分,4.096V,128SPS
I2C1_Start();
I2C1_SendAddr(0x48, 0); // 写模式
I2C1_SendData(0x01); // 指向配置寄存器
I2C1_SendData(config[1]); // 发送配置高位
I2C1_SendData(config[2]); // 发送配置低位
I2C1_Stop();
}
// ADS1115读取温度
float ADS1115_ReadTemperature(void) {
uint8_t data[2];
int16_t adc_value;
float voltage, resistance, temperature;
// 开始转换
I2C1_Start();
I2C1_SendAddr(0x48, 0);
I2C1_SendData(0x01); // 指向配置寄存器
I2C1_SendData(0xC3); // 开始单次转换
I2C1_SendData(0xE3);
I2C1_Stop();
// 等待转换完成
delay_ms(10);
// 读取转换结果
I2C1_Start();
I2C1_SendAddr(0x48, 0);
I2C1_SendData(0x00); // 指向转换寄存器
I2C1_Stop();
I2C1_Start();
I2C1_SendAddr(0x48, 1); // 读模式
data[0] = I2C1_ReadData(); // 读取高位
data[1] = I2C1_ReadData(); // 读取低位
I2C1_Stop();
// 计算温度
adc_value = (data[0] << 8) | data[1];
voltage = adc_value * 4.096 / 32768.0; // 4.096V参考电压
// PT1000电阻值:R = V / I (I=1mA恒流源)
resistance = voltage / 0.001;
// PT1000温度计算(线性近似,-50°C~150°C)
temperature = (resistance - 1000.0) / 3.85;
return temperature;
}
// OLED显示初始化
void OLED_Init(void) {
uint8_t init_cmd[] = {
0xAE, 0xD5, 0x80, 0xA8, 0x3F,
0xD3, 0x00, 0x40, 0x8D, 0x14,
0x20, 0x00, 0xA1, 0xC8, 0xDA,
0x12, 0x81, 0xCF, 0xD9, 0xF1,
0xDB, 0x40, 0xA4, 0xA6, 0xAF
};
I2C1_Start();
I2C1_SendAddr(0x78, 0); // OLED地址:0x78
// 发送初始化命令序列
for(uint8_t i = 0; i < sizeof(init_cmd); i++) {
I2C1_SendData(0x00); // 控制字节:命令模式
I2C1_SendData(init_cmd[i]); // 发送命令
}
I2C1_Stop();
// 清屏
OLED_Clear();
}
// OLED清屏
void OLED_Clear(void) {
I2C1_Start();
I2C1_SendAddr(0x78, 0);
// 设置页面地址
for(uint8_t page = 0; page < 8; page++) {
I2C1_SendData(0x00); // 控制字节:命令模式
I2C1_SendData(0xB0 + page); // 页面地址
// 设置列地址
I2C1_SendData(0x00);
I2C1_SendData(0x21); // 列地址低4位
I2C1_SendData(0x00);
I2C1_SendData(0x22); // 列地址高4位
// 发送数据模式
I2C1_SendData(0x40); // 控制字节:数据模式
// 填充0(清空整行)
for(uint8_t col = 0; col < 128; col++) {
I2C1_SendData(0x00);
}
}
I2C1_Stop();
}
// OLED显示字符串
void OLED_ShowString(uint8_t x, uint8_t y, char *str) {
I2C1_Start();
I2C1_SendAddr(0x78, 0);
// 设置显示位置
I2C1_SendData(0x00); // 命令模式
I2C1_SendData(0xB0 + y); // 页面地址
I2C1_SendData(0x00);
I2C1_SendData(0x21); // 列地址低4位
I2C1_SendData(x & 0x0F);
I2C1_SendData(0x00);
I2C1_SendData(0x22); // 列地址高4位
I2C1_SendData((x >> 4) & 0x0F);
// 切换数据模式
I2C1_SendData(0x40); // 数据模式
// 发送字符串数据(6x8字体)
while(*str) {
for(uint8_t i = 0; i < 6; i++) {
I2C1_SendData(Font6x8[*str - 32][i]);
}
str++;
}
I2C1_Stop();
}
// ADC初始化(用于电流采样)
void ADC1_Init(void) {
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // 使能ADC1时钟
// 配置ADC
ADC1->CR2 &= ~ADC_CR2_ADON; // 禁用ADC
// 独立模式,右对齐,单次转换
ADC1->CR1 = 0x0000;
ADC1->CR2 = 0x0000;
// 采样时间:239.5周期
ADC1->SMPR2 |= ADC_SMPR2_SMP0_0 | ADC_SMPR2_SMP0_1 | ADC_SMPR2_SMP0_2;
// 通道0(PA0)
ADC1->SQR3 = 0x0000;
ADC1->CR2 |= ADC_CR2_ADON; // 使能ADC
delay_ms(1);
ADC1->CR2 |= ADC_CR2_RSTCAL; // 复位校准寄存器
while(ADC1->CR2 & ADC_CR2_RSTCAL);
ADC1->CR2 |= ADC_CR2_CAL; // 开始校准
while(ADC1->CR2 & ADC_CR2_CAL);
}
// ADC读取电流
float ADC1_ReadCurrent(void) {
float voltage, current;
ADC1->CR2 |= ADC_CR2_ADON; // 开启ADC
delay_ms(1);
ADC1->CR2 |= ADC_CR2_SWSTART; // 开始转换
while(!(ADC1->SR & ADC_SR_EOC)); // 等待转换完成
// 计算电流(INA180增益20倍,采样电阻0.05Ω)
voltage = (float)ADC1->DR * 3.3 / 4096.0;
current = voltage / (20.0 * 0.05);
ADC1->CR2 &= ~ADC_CR2_ADON; // 关闭ADC
return current;
}
// TIM1 PWM初始化
void TIM1_PWM_Init(void) {
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; // 使能TIM1时钟
// 72MHz/72=1MHz,ARR=1000,PWM频率=1kHz
TIM1->PSC = 72 - 1; // 预分频
TIM1->ARR = 1000 - 1; // 自动重载值
// 通道1配置(PA8)
TIM1->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; // PWM模式1
TIM1->CCMR1 |= TIM_CCMR1_OC1PE; // 预装载使能
TIM1->CCER |= TIM_CCER_CC1E; // 输出使能
TIM1->BDTR |= TIM_BDTR_MOE; // 主输出使能
TIM1->CR1 |= TIM_CR1_ARPE; // ARR预装载
TIM1->CR1 |= TIM_CR1_CEN; // 使能计数器
}
// 设置PWM占空比
void Set_PWM_Duty(uint16_t duty) {
if(duty > 999) duty = 999; // 限制在0-999
TIM1->CCR1 = duty; // 设置比较值
}
// USART1初始化(用于串口通信)
void USART1_Init(void) {
RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // 使能USART1时钟
// 波特率:115200(72MHz/16/39.0625)
USART1->BRR = 0x0271; // 设置波特率
USART1->CR1 |= USART_CR1_TE | USART_CR1_RE; // 使能发送和接收
USART1->CR1 |= USART_CR1_RXNEIE; // 使能接收中断
USART1->CR1 |= USART_CR1_UE; // 使能USART
}
// 串口发送字符
void USART1_SendChar(char ch) {
while(!(USART1->SR & USART_SR_TXE)); // 等待发送缓冲区空
USART1->DR = ch; // 发送数据
}
// 串口发送字符串
void USART1_SendString(char *str) {
while(*str) {
USART1_SendChar(*str++);
}
}
// 增量式PID结构体
typedef struct {
float Kp, Ki, Kd;
float error[3];
float increment;
float output_max;
float output_min;
} PID_IncTypeDef;
// 增量式PID计算
float PID_Incremental(PID_IncTypeDef *pid, float setpoint, float feedback) {
float output;
pid->error[2] = pid->error[1];
pid->error[1] = pid->error[0];
pid->error[0] = setpoint - feedback;
// 增量式PID公式:Δu(k) = Kp[e(k)-e(k-1)] + Ki*e(k) + Kd[e(k)-2e(k-1)+e(k-2)]
pid->increment = pid->Kp * (pid->error[0] - pid->error[1]) +
pid->Ki * pid->error[0] +
pid->Kd * (pid->error[0] - 2*pid->error[1] + pid->error[2]);
output = pid->increment;
// 输出限幅
if(output > pid->output_max) output = pid->output_max;
if(output < pid->output_min) output = pid->output_min;
return output;
}
// 主函数
int main(void) {
float current_temp, set_temp = 25.0;
float current, pwm_output;
PID_IncTypeDef pid;
// 系统初始化
SystemClock_Config();
GPIO_Init();
I2C1_Init();
ADC1_Init();
TIM1_PWM_Init();
USART1_Init();
// 传感器初始化
ADS1115_Init();
OLED_Init();
// PID参数初始化
pid.Kp = 2.5;
pid.Ki = 0.1;
pid.Kd = 0.05;
pid.output_max = 999.0;
pid.output_min = 0.0;
pid.error[0] = pid.error[1] = pid.error[2] = 0;
// 使能中断
__enable_irq();
while(1) {
// 读取当前温度
current_temp = ADS1115_ReadTemperature();
// 读取电流
current = ADC1_ReadCurrent();
// 过流保护(阈值2A)
if(current > 2.0) {
Set_PWM_Duty(0); // 切断输出
GPIOC->ODR &= ~(1 << 13); // 关闭方向控制
continue;
}
// PID计算
pwm_output = PID_Incremental(&pid, set_temp, current_temp);
// 设置PWM输出
if(pwm_output >= 0) {
GPIOC->ODR |= (1 << 13); // 正向电流(制冷)
} else {
GPIOC->ODR &= ~(1 << 13); // 反向电流(制热)
pwm_output = -pwm_output; // 取绝对值
}
Set_PWM_Duty((uint16_t)pwm_output);
// OLED显示
char temp_str[32];
sprintf(temp_str, "Set:%.1fC", set_temp);
OLED_ShowString(0, 0, temp_str);
sprintf(temp_str, "Cur:%.1fC", current_temp);
OLED_ShowString(0, 2, temp_str);
sprintf(temp_str, "PWM:%03d", (int)pwm_output);
OLED_ShowString(0, 4, temp_str);
sprintf(temp_str, "I:%.2fA", current);
OLED_ShowString(0, 6, temp_str);
// 串口发送数据
char uart_str[64];
sprintf(uart_str, "Temp:%.2f, Set:%.2f, PWM:%d, I:%.3f\r\n",
current_temp, set_temp, (int)pwm_output, current);
USART1_SendString(uart_str);
delay_ms(100); // 控制周期100ms
}
}
// 串口接收中断处理
void USART1_IRQHandler(void) {
static char rx_buffer[32];
static uint8_t index = 0;
if(USART1->SR & USART_SR_RXNE) {
char ch = USART1->DR;
if(ch == '\r' || ch == '\n') {
rx_buffer[index] = '\0';
// 解析PID参数命令格式:KP=2.5,KI=0.1,KD=0.05
if(strstr(rx_buffer, "KP=")) {
sscanf(rx_buffer, "KP=%f", &pid.Kp);
} else if(strstr(rx_buffer, "KI=")) {
sscanf(rx_buffer, "KI=%f", &pid.Ki);
} else if(strstr(rx_buffer, "KD=")) {
sscanf(rx_buffer, "KD=%f", &pid.Kd);
}
index = 0;
} else if(index < 31) {
rx_buffer[index++] = ch;
}
}
}
// 按键扫描
uint8_t Key_Scan(void) {
static uint8_t last_state = 1;
uint8_t current_state = (GPIOA->IDR & 0x0001);
if(last_state == 1 && current_state == 0) { // 检测下降沿
last_state = current_state;
delay_ms(20); // 消抖
if((GPIOA->IDR & 0x0001) == 0) { // 确认按下
while((GPIOA->IDR & 0x0001) == 0); // 等待释放
return 1;
}
}
last_state = current_state;
return 0;
}
// 延时函数
void delay_ms(uint32_t ms) {
SysTick->LOAD = 72000 - 1; // 1ms计数值(72MHz)
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_ENABLE_Msk;
for(uint32_t i = 0; i < ms; i++) {
while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
}
SysTick->CTRL = 0;
}
// 6x8 ASCII字体库(部分)
const uint8_t Font6x8[][6] = {
{0x00,0x00,0x00,0x00,0x00,0x00}, // 空格
{0x00,0x00,0x5F,0x00,0x00,0x00}, // !
{0x00,0x07,0x00,0x07,0x00,0x00}, // "
// ... 其他字符定义
{0x3E,0x41,0x41,0x41,0x3E,0x00}, // 0
{0x00,0x42,0x7F,0x40,0x00,0x00}, // 1
{0x42,0x61,0x51,0x49,0x46,0x00}, // 2
// ... 其他数字和字母定义
};
项目核心代码
#include "stm32f10x.h"
#include "oled.h"
#include "ads1115.h"
#include "drv8871.h"
#include "ina180.h"
#include "key.h"
#include "usart.h"
#include "pid.h"
// 系统状态定义
typedef enum {
SYS_NORMAL = 0,
SYS_OVER_CURRENT,
SYS_SENSOR_ERROR,
SYS_TEC_ERROR
} SystemState;
// 全局变量
volatile float target_temp = 25.0; // 目标温度
volatile float current_temp = 0.0; // 当前温度
volatile float pid_output = 0.0; // PID输出值
volatile SystemState sys_state = SYS_NORMAL;
volatile uint32_t system_tick = 0;
// PID参数结构体
PID_Params pid_params = {
.Kp = 2.5,
.Ki = 0.1,
.Kd = 0.8,
.max_output = 100.0,
.min_output = -100.0
};
// 初始化系统时钟
void SystemClock_Config(void) {
// 使能HSE
RCC->CR |= RCC_CR_HSEON;
while(!(RCC->CR & RCC_CR_HSERDY));
// 配置PLL
RCC->CFGR &= ~(RCC_CFGR_PLLMULL | RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE);
RCC->CFGR |= RCC_CFGR_PLLMULL9 | RCC_CFGR_PLLSRC_HSE;
// 使能PLL
RCC->CR |= RCC_CR_PLLON;
while(!(RCC->CR & RCC_CR_PLLRDY));
// 切换系统时钟到PLL
RCC->CFGR |= RCC_CFGR_SW_PLL;
while(!(RCC->CFGR & RCC_CFGR_SWS_PLL));
// 设置AHB, APB1, APB2预分频器
RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;
RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;
// 更新SystemCoreClock变量
SystemCoreClock = 72000000;
}
// GPIO初始化
void GPIO_Init(void) {
// 使能GPIO时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN;
// 初始化按键GPIO
// PA0, PA1, PA2作为按键输入
GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_MODE1 | GPIO_CRL_MODE2);
GPIOA->CRL |= GPIO_CRL_CNF0_0 | GPIO_CRL_CNF1_0 | GPIO_CRL_CNF2_0; // 上拉输入
GPIOA->ODR |= GPIO_ODR_ODR0 | GPIO_ODR_ODR1 | GPIO_ODR_ODR2;
// 初始化LED指示灯PC13
GPIOC->CRH &= ~(GPIO_CRH_MODE13 | GPIO_CRH_CNF13);
GPIOC->CRH |= GPIO_CRH_MODE13_0; // 输出模式, 2MHz
GPIOC->ODR |= GPIO_ODR_ODR13; // 初始高电平
}
// SysTick定时器初始化
void SysTick_Init(void) {
SysTick->LOAD = 72000 - 1; // 1ms中断
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
}
// 定时器3用于PID控制周期
void TIM3_Init(void) {
// 使能TIM3时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
// 配置定时器
TIM3->PSC = 7200 - 1; // 72MHz/7200 = 10kHz
TIM3->ARR = 1000 - 1; // 100ms周期
TIM3->CR1 = TIM_CR1_ARPE; // 自动重装载使能
// 使能更新中断
TIM3->DIER |= TIM_DIER_UIE;
// 启动定时器
TIM3->CR1 |= TIM_CR1_CEN;
// 配置NVIC
NVIC_EnableIRQ(TIM3_IRQn);
NVIC_SetPriority(TIM3_IRQn, 1);
}
// ADC初始化(用于备用温度测量)
void ADC_Init(void) {
// 使能ADC1和GPIOA时钟
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
// 配置ADC
ADC1->CR2 |= ADC_CR2_ADON; // 开启ADC
// 设置采样时间
ADC1->SMPR2 = ADC_SMPR2_SMP0_0 | ADC_SMPR2_SMP0_1; // 71.5周期
}
// 系统初始化
void System_Init(void) {
// 关闭所有中断
__disable_irq();
// 初始化系统时钟
SystemClock_Config();
// 初始化GPIO
GPIO_Init();
// 初始化SysTick
SysTick_Init();
// 初始化定时器
TIM3_Init();
// 初始化ADC
ADC_Init();
// 初始化各模块
OLED_Init();
ADS1115_Init();
DRV8871_Init();
INA180_Init();
KEY_Init();
USART1_Init(115200);
PID_Init(&pid_params);
// 使能所有中断
__enable_irq();
// 系统启动显示
OLED_Clear();
OLED_ShowString(0, 0, "TEC Temp Control");
OLED_ShowString(0, 2, "System Starting...");
Delay_ms(1000);
}
// 读取温度
float Read_Temperature(void) {
uint16_t adc_value;
float temperature;
// 通过ADS1115读取PT1000温度
if(ADS1115_ReadTemperature(&adc_value) == 0) {
// 转换为温度值(根据PT1000特性曲线)
// 这里简化处理,实际需要根据PT1000的温度-电阻特性进行转换
temperature = (float)adc_value * 0.125; // 示例转换系数
return temperature;
}
return -100.0; // 读取失败
}
// 电流保护检查
uint8_t Check_Current_Protection(void) {
float current;
// 读取当前电流
if(INA180_ReadCurrent(¤t) == 0) {
// 检查是否过流(假设阈值2A)
if(current > 2.0) {
// 关闭TEC驱动
DRV8871_Disable();
sys_state = SYS_OVER_CURRENT;
return 1;
}
}
return 0;
}
// 更新显示
void Update_Display(void) {
static uint32_t display_tick = 0;
if((system_tick - display_tick) > 500) { // 500ms更新一次显示
OLED_Clear();
// 显示系统状态
switch(sys_state) {
case SYS_NORMAL:
OLED_ShowString(0, 0, "Status: Normal");
break;
case SYS_OVER_CURRENT:
OLED_ShowString(0, 0, "Status: OverCurrent");
break;
case SYS_SENSOR_ERROR:
OLED_ShowString(0, 0, "Status: SensorErr");
break;
case SYS_TEC_ERROR:
OLED_ShowString(0, 0, "Status: TEC Error");
break;
}
// 显示温度
OLED_ShowString(0, 2, "Target: ");
OLED_ShowFloat(56, 2, target_temp, 1);
OLED_ShowChar(104, 2, 'C');
OLED_ShowString(0, 4, "Current: ");
OLED_ShowFloat(56, 4, current_temp, 1);
OLED_ShowChar(104, 4, 'C');
// 显示PID参数
OLED_ShowString(0, 6, "PID:");
OLED_ShowFloat(32, 6, pid_params.Kp, 2);
OLED_ShowFloat(64, 6, pid_params.Ki, 2);
OLED_ShowFloat(96, 6, pid_params.Kd, 2);
display_tick = system_tick;
}
}
// 按键处理
void Key_Process(void) {
static uint8_t key_pressed = 0;
uint8_t key_value;
key_value = KEY_Scan();
if(key_value && !key_pressed) {
switch(key_value) {
case KEY_UP:
target_temp += 0.5;
break;
case KEY_DOWN:
target_temp -= 0.5;
break;
case KEY_ENTER:
// 进入PID参数设置模式
// 这里省略参数设置的具体实现
break;
}
key_pressed = 1;
}
else if(!key_value) {
key_pressed = 0;
}
}
// 串口命令处理
void UART_Command_Process(void) {
// 这里处理从上位机接收的PID参数修改命令
// 假设通过USART1接收命令
// 命令格式示例: "KP=2.5" "KI=0.1" "KD=0.8"
}
// 主循环
int main(void) {
// 系统初始化
System_Init();
// 主循环
while(1) {
// 更新显示
Update_Display();
// 按键处理
Key_Process();
// 串口命令处理
UART_Command_Process();
// 电流保护检查
Check_Current_Protection();
// LED指示灯闪烁(系统运行指示)
if((system_tick % 1000) < 500) {
GPIOC->ODR &= ~GPIO_ODR_ODR13;
} else {
GPIOC->ODR |= GPIO_ODR_ODR13;
}
}
}
// SysTick中断服务函数
void SysTick_Handler(void) {
system_tick++;
}
// TIM3中断服务函数(PID控制周期)
void TIM3_IRQHandler(void) {
if(TIM3->SR & TIM_SR_UIF) {
// 清除中断标志
TIM3->SR &= ~TIM_SR_UIF;
// 读取当前温度
current_temp = Read_Temperature();
// 检查温度传感器是否正常
if(current_temp < -50.0 || current_temp > 150.0) {
sys_state = SYS_SENSOR_ERROR;
DRV8871_Disable();
return;
}
// 系统正常时才执行PID控制
if(sys_state == SYS_NORMAL) {
// 计算PID输出
pid_output = PID_Calculate(target_temp, current_temp);
// 驱动TEC
if(pid_output > 0) {
// 加热模式
DRV8871_SetDirection(1);
DRV8871_SetSpeed((uint8_t)(pid_output * 2.55)); // 转换为PWM占空比
}
else if(pid_output < 0) {
// 制冷模式
DRV8871_SetDirection(0);
DRV8871_SetSpeed((uint8_t)(-pid_output * 2.55));
}
else {
// 停止
DRV8871_SetSpeed(0);
}
}
}
}
总结
本项目设计并实现了一个基于增量式PID算法的TEC半导体恒温控制系统,旨在通过精确的温度采集与动态调节,实现对被控对象温度的稳定控制。系统核心采用增量式PID控制算法,根据PT1000铂电阻传感器采集的实时温度,动态调整半导体制冷片的驱动电流方向和大小,从而快速响应温度变化并维持设定值。
硬件设计上,系统由多个模块高效集成:以TI MSPM0G3507单片机为主控,利用其高精度PWM与ADC外设执行控制逻辑;温度采集模块通过PT1000传感器和恒流源电路,结合ADS1115模数转换器实现差分采样;TEC驱动模块基于DRV8871芯片搭建H桥电路,支持双向电流驱动;人机交互模块包括OLED显示屏和独立按键,用于实时显示温度、PID参数及系统状态,并支持目标温度设定;保护与通信模块则通过INA180芯片实现电流采样与过流保护,CH340芯片提供串口通信以调整PID参数,并由LM317线性稳压电源确保供电稳定。
整体系统融合了传感、控制、驱动与交互功能,具备过流保护、参数可调和实时监控等特点,实现了高可靠性、高精度的恒温控制。该系统适用于实验室设备、医疗仪器等需要严格温度管理的领域,展现了嵌入式控制在热管理应用中的实用价值。
- 点赞
- 收藏
- 关注作者
评论(0)