STM32F103热电偶采集PID温控采集系统 基于stm32设计,可以实现热电偶采集,PID...
STM32F103热电偶采集PID温控采集系统 基于stm32设计,可以实现热电偶采集,PID温度控制,注意51单片机源码基于keil开发环境,STM32源码基于IAR开发环境 提供原理图, PCB(AD格式),源代码 ,不提供,
今天来唠唠我去年攒的一个实验室必备小破烂——STM32F103 MAX6675 SSR陶瓷加热片PID温控台。用起来手感还行,冷启动焊个QFN24的小芯片3分钟就稳到380±0.5℃,偶尔换个小电阻电容预热1分钟飘到2℃以内就停了,对业余玩家做手工或者偶尔搞小批量焊接完全够用。
硬件先抓最核心的几个模块
焊这种小破烂不用太纠结复杂的外围,但得注意几个关键点,别踩我踩过的坑:
- 热电偶采集MAX6675选SPI快读慢切:新手别选那种裸K线接运放AD还要自己测冷端的,MAX6675内置了K型热电偶冷端补偿(虽然有±2℃左右的系统误差,但焊台嘛,差不多就行,校准两次能用),SPI协议极简单,唯一踩过的坑是一开始时钟线SCK拉太快(直接开STM32SPI的最高72MHz分频8也快?MAX6675好像只支持到10MHz),读出来全是FFFF或者0000,后来改成分频64(1.125MHz)瞬间世界安静。
- 加热控制别硬撸可控硅:新手别碰220V串在PCB上的双向可控硅,炸保险丝事小,烧手事大!选个3.3V/5V兼容的24V DC转DC SSR固态继电器,我用的是欧姆龙同款国产量产货,20块钱不到,陶瓷加热片也选24V的(功率80W就行,预热快),STM32只需要输出个PWM信号占SSR的控制端,占空比调0-100%,占空比越高功率越大,飘温快的时候100%,接近目标温度的时候20%、10%慢慢调,安全得很。
- STM32F103C8T6核心板就行:不用焊自己的最小系统,某宝5块钱的蓝色C8T6最小系统改改BOOT0/BOOT1跳线就能用,唯一注意点是MAX6675的SPI引脚别和JTAG/SWD冲突,我选的是PA4(片选CS)、PA5(SCK)、PA6(MISO),SPI3太靠边缘引起来麻烦,PA7可以留着以后加个OLED屏?但我嫌费眼,直接把温度通过USB虚拟串口发到电脑串口助手看就行(后面附一段IAR下虚拟串口的简化初始化,STM32CubeMX生成的太啰嗦,删了很多冗余)。
先插一段最爽的MAX6675 SPI采集简化代码
这段是我从STM32F1的HAL库SPIDemo里扒出来,然后自己改得只剩读K型温度的,好用到飞起,不用背HAL库那一堆回调函数:
#include "stm32f1xx_hal.h"
#include <math.h> // 虽然MAX6675读出来是整数,但校准的时候用用浮点数方便点
// 引脚定义(C8T6核心板默认PA4-PA6,记得在CubeMX里把这些引脚设为SPI1的复用推挽/上拉输入)
#define MAX6675_CS_PIN GPIO_PIN_4
#define MAX6675_CS_PORT GPIOA
#define SPI_HANDLE hspi1
// 读一次MAX6675,返回单位是℃的整数(MAX6675分辨率0.25℃,但焊台用整数够了)
int16_t MAX6675_Read_Temp(void)
{
uint16_t raw_data = 0;
uint8_t tx_buf[2] = {0x00, 0x00}; // SPI是全双工,但MAX6675只发不收,tx随便填
uint8_t rx_buf[2] = {0x00, 0x00};
// 片选拉低,开始读取
HAL_GPIO_WritePin(MAX6675_CS_PORT, MAX6675_CS_PIN, GPIO_PIN_RESET);
// 稍微延迟10us?有些MAX6675山寨货反应慢,正品不用,但加了总没错
for(int i=0; i<100; i++);
// 全双工收发2个字节
HAL_SPI_TransmitReceive(&SPI_HANDLE, tx_buf, rx_buf, 2, 100);
// 片选拉高,结束读取
HAL_GPIO_WritePin(MAX6675_CS_PORT, MAX6675_CS_PIN, GPIO_PIN_SET);
// 拼接原始数据,rx_buf[0]是高8位,rx_buf[1]是低8位
raw_data = (rx_buf[0] << 8) | rx_buf[1];
// 检查热电偶是否开路!MAX6675的第2位(从0数)是开路检测位,1表示开路
if(raw_data & 0x0004)
{
return -999; // 随便返回个不可能的温度表示开路
}
// 去掉开路位,然后右移3位,得到0.25℃单位的原始值
raw_data = (raw_data >> 3) & 0x0FFF;
// 转换成整数℃(精度0.25℃可以直接除以4,或者用 >>2 更快)
return (int16_t)(raw_data >> 2);
}
这段代码里有个小细节,就是加了个for(int i=0; i<100; i++);的微秒级(大概,STM32F103C8T6 72MHz时钟下,一个空循环大概10多纳秒,100次就是1微秒左右?随便凑的,让山寨MAX6675稳定就行)延迟,一开始我没加,用山寨货偶尔读出来开路,加了之后再也没出过问题。
再插一段IAR下USB虚拟串口的简化初始化
USB虚拟串口(VCP)我一开始想用CH340,但最小系统已经自带了ST-Link/V2(虽然是山寨的),而且ST-Link/V2可以直接通过USB虚拟出串口,不用额外插线,爽!这段是我从STM32CubeF1的USB Device CDC ACM Demo里删了一半冗余代码(比如USB中断处理里的一些调试代码,还有多语言字符串描述符)后的简化版本,直接复制到IAR项目里就行:
// 省略头文件,CubeMX生成的CDC_Class.h、usbd_conf.h、usbd_desc.h、usbd_core.h这些记得加
#include "usbd_cdc_if.h"
#include "usb_device.h"
#include "usbd_def.h"
// USB设备句柄
USBD_HandleTypeDef hUsbDeviceFS;
// USB初始化简化函数,只需要在main()里的HAL_Init()和SystemClock_Config()之后调用一次
void USB_VCP_Init(void)
{
// 初始化USB设备底层,比如GPIO、时钟(CubeMX生成的MX_USB_DEVICE_Init里的底层已经移到这里了,不用改)
SystemClock_Config_USB(); // 这里的SystemClock_Config_USB是CubeMX生成的,记得把USB的时钟设为48MHz!!!
// 初始化USB设备,选择CDC ACM类
USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);
// 注册CDC ACM类的接口
USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC);
// 注册CDC ACM类的应用层接口(比如发送接收函数)
USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);
// 启动USB设备
USBD_Start(&hUsbDeviceFS);
}
// 发送温度数据到电脑串口助手的简化函数,直接传整数℃就行
void USB_VCP_Send_Temp(int16_t temp)
{
char tx_buf[32];
int len;
// 格式化字符串,比如"Current Temp: 380 C\r\n"
len = sprintf(tx_buf, "Current Temp: %d C\r\n", temp);
// 发送数据,注意USBD_CDC_SetTxBuffer和USBD_CDC_TransmitPacket这两个函数必须连续调用,而且不能重复发送太快(比如每100ms发一次)
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, (uint8_t*)tx_buf, len);
USBD_CDC_TransmitPacket(&hUsbDeviceFS);
}
这里有个超级超级重要的坑,就是STM32F103的USB时钟必须设为48MHz!!!C8T6核心板默认是72MHz的系统时钟,所以USB时钟分频必须设为1.5倍(72 / 1.5 = 48),在CubeMX里的Clock Configuration里找USB Prescaler,选Divide by 1.5就行,如果选Divide by 1的话,USB设备会识别不出来,选Divide by 3的话识别出来但会报错。
最后插一段PID温控的核心代码
PID是整个系统的灵魂,别硬撸复杂的增量式或者位置式PID,用个最简单的位置式PID限幅版就行,新手一看就懂:
// PID参数定义,这个参数是我对着80W 24V DC陶瓷加热片调出来的,大家可以根据自己的加热元件微调
#define PID_KP 2.0f
#define PID_KI 0.1f
#define PID_KD 5.0f
#define PID_MAX_OUTPUT 1000 // PWM最大占空比对应的计数值(我用的是TIM3_CH2,预分频7199,自动重装载值999,所以PWM频率是10Hz,占空比0-100%对应计数值0-999)
#define PID_MIN_OUTPUT 0 // PWM最小占空比对应的计数值
#define PID_MAX_I 1000 // 积分项限幅,防止积分饱和
#define PID_MIN_I -1000
// PID全局变量
float pid_error = 0.0f; // 当前误差
float pid_last_error = 0.0f; // 上一次误差
float pid_integral = 0.0f; // 积分项
float pid_derivative = 0.0f; // 微分项
float pid_output = 0.0f; // PID输出
int16_t target_temp = 380; // 目标温度,大家可以通过USB虚拟串口或者按键修改,我嫌按键麻烦,直接在代码里改了
// PID计算简化函数,每100ms调用一次就行(和PWM频率一样,或者是整数倍)
void PID_Calculate(int16_t current_temp)
{
// 计算当前误差
pid_error = (float)target_temp - (float)current_temp;
// 计算积分项,加限幅防止积分饱和
pid_integral += pid_error;
if(pid_integral > PID_MAX_I) pid_integral = PID_MAX_I;
if(pid_integral < PID_MIN_I) pid_integral = PID_MIN_I;
// 计算微分项
pid_derivative = pid_error - pid_last_error;
// 计算PID输出
pid_output = PID_KP * pid_error + PID_KI * pid_integral + PID_KD * pid_derivative;
// PID输出限幅,防止占空比超过100%或者低于0%
if(pid_output > PID_MAX_OUTPUT) pid_output = PID_MAX_OUTPUT;
if(pid_output < PID_MIN_OUTPUT) pid_output = PID_MIN_OUTPUT;
// 更新上一次误差
pid_last_error = pid_error;
// 设置TIM3_CH2的比较值,也就是PWM占空比
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, (uint32_t)pid_output);
}
这段代码里的PID参数调节技巧是我从B站上学来的,新手记下来就行:
- 先把KI和KD设为0,只调KP,让温度震荡,然后慢慢减小KP,直到温度接近目标温度但不震荡(比如380℃,只调KP的话可能会到375℃左右停住,或者到385℃左右慢慢降下来)。
- 然后慢慢增大KI,让温度慢慢升到目标温度(比如380℃),注意KI不要调太大,否则会积分饱和,温度超过目标温度很多。
- 最后慢慢增大KD,让温度在目标温度附近的震荡幅度更小,更快稳定下来,注意KD不要调太大,否则会出现高频抖动(比如温度在379℃-381℃之间跳来跳去)。
总结一下整个系统的main()函数流程
main()函数里的流程超级简单,不用背:
- HAL_Init()初始化HAL库。
- SystemClock_Config()初始化系统时钟。
- MXGPIOInit()初始化GPIO(MAX6675的CS引脚,TIM3的CH2 PWM输出引脚)。
- MXSPI1Init()初始化SPI1(MAX6675的通信)。
- MXTIM3Init()初始化TIM3(PWM输出控制SSR)。
- USBVCPInit()初始化USB虚拟串口。
- HALTIMPWMStart(&htim3, TIMCHANNEL_2)启动PWM输出。
- 进入死循环,每100ms调用一次MAX6675ReadTemp()、PIDCalculate()、USBVCPSendTemp()。
整个系统的源码、原理图、PCB(AD格式)我本来想打包发上来的,但平台不让放链接,大家需要的话可以自己按照上面的代码和模块搭,不难的,新手大概3天就能搞定(包括焊接和PID参数调节)。
STM32F103热电偶采集PID温控采集系统 基于stm32设计,可以实现热电偶采集,PID温度控制,注意51单片机源码基于keil开发环境,STM32源码基于IAR开发环境 提供原理图, PCB(AD格式),源代码 ,不提供,

更多推荐
所有评论(0)