PID温控实验平台搭建(四)——PID温控系统实验代码讲解

小明 2025-05-08 01:25:40 7

PID温控实验平台搭建

(一)PID基���知识介绍

(二)PID进阶知识介绍及源码分享

(三)从零开始搭建STM32温控实验平台

(四)PID温控系统代码讲解

(五)最终实验现象与总结


文章目录

前言

一、主程序功能描述

二、部分代码讲解

1、PID程序

2、PWM输出

3、DS18B20传感器代码

4、OLED显示

总结


前言

最近,我突发奇想去翻阅了一些我本科期间所做的一些小项目,发现都挺有意思的!当年做这些项目的时候可走了很多弯路,所以想着可以将它们上传到网络上,并通过我的讲解文章可以帮助你们少走一些弯路!

今天,我要分享的是一个PID温控实验平台的搭建,因为我想要讲的详细一点,所以打算做成一个系列,手把手地教你学习和认识PID算法,了解这种算法在温度控制中的应用。由于我知识有限,如果出现一些错误,希望大家可以帮助我指出来,我们一起学习进步!!!


一、主程序功能描述

主函数的运行过程:

在开始温控之前,会先进行一段时间的软件配置,软件配置成功后。当DS18B20温度传感器检测到温度低于起始温度(30­°C)时,将会开始加热到起始温度(30­°C);当高于起始温度(30­°C)时,将会冷却等待,直到温度达到起始温度(30­°C);而温度变化数据将会实时显示在OLED屏幕上,让我们可以实时观察到控温趋势,并且通过串口发送及VOFA++上位机显示,实验数据曲线可以实时显示在电脑屏幕上,最终便于我们总结规律,得出结论。

VOFA++上位机

这是一款最直观、灵活、强大的插件驱动高自由度的上位机,我们通过特定的数据格式,来获得实时的数据曲线,便于我们直观地总结规律,归纳结论,并且VOFA++自由度很高,可以定制化数据的可视化方式,从而让我们可以更便捷地整定PID参数!

图1 VOFA++上位机

流水灯含义解释:

1)红灯闪烁代表正在进行配置所有软件;

2)绿灯闪烁代表加热棒正在工作,PID控制正在进行;

3)黄灯闪烁代表正在加热到起始温度(30­°C);

4)蓝灯闪烁代表正在冷却到起始温度(30­°C);

5)青灯亮起代表马上进入PID温控;

 

(主程序代码)

/******************main.c***********************/
// 温度
float T=0.0;
// 媒介变量
int i = 0;
// 定义PID结构体并初始化
PID pid;
// 一次PID调节的时间
#define   WAIT_TIME     20
// 采样时间
#define   SAMPLE_TIME   200
// 最大输出
#define   MAX_OUT      10000
// 数组的元素的个数
#define   ARR_NUM       4
// PID参数
const float Kp = 90;
const float Ki = 0.15;
const float Kd[ARR_NUM] = {1000,2000,3000,4000};
// 声明
void All_Soft_Config(void);
void Wait_Temperature_Init(void);
// 可以循环测试PID参数(1、参数改宏改;2、PID初始化改)
int main()
{
	// 所有软件配置
	All_Soft_Config();
	for(uint8_t n=0;n=110 || i >=(WAIT_TIME*60)*1000/SAMPLE_TIME)
			{
				for(uint8_t zero_time=0;zero_time= (2*60)*1000/SAMPLE_TIME)&&(T 30)
		{
			// 蓝灯闪烁等待温度降下来
			LED3_TOGGLE
			// DS18B20更新温度
			T = DS18B20_Update_Temperature();
			OLED_display_DS18B20(T);
			printf("温度为%.4f℃\n",T);
			SysTick_Delay_ms(1000);
		}
		// 青灯亮起
		LED_CYAN;
		SysTick_Delay_ms(1000);
	}
	// 温度小于30度
	else
	{
		// 1000 加热
		Pulse_Wave = 0.1*MAX_OUT;
		LED_YELLOW;
		// 加热到33度附近
		while(T 2*60*2)
			{
				LED_CYAN;
				printf("线掉了....\n");
				Pulse_Wave = 0;
				while(1)
				{
					;;
				}
			}
			
		}
		goto chill;
	}
}

二、部分代码讲解

1、PID程序

PID程序已经在上一节介绍过了,不再赘述!

PID温控实验平台搭建(二)——PID进阶知识介绍及源码分享https://blog.csdn.net/qq_35953617/article/details/127849549https://blog.csdn.net/qq_35953617/article/details/127849549


2、PWM输出

/******************bsp_pwm.h***********************/
/************通用定时器TIM参数定义,只限TIM2、3、4、5************/
// 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意
// 我们这里默认使用TIM3
/* ----------------   PWM信号 周期和占空比的计算--------------- */
// ARR :自动重装载寄存器的值
// CLK_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1)
// PWM 信号的周期 T = ARR * (1/CLK_cnt) = ARR*(PSC+1) / 72M
// 占空比P=CCR/(ARR+1)
/* PWM波占空比(0 ~ 10000) */
extern uint16_t Pulse_Wave;
// 选择TIM3通用定时器
#define            TEMP_PWM_TIM                   TIM3
#define            TEMP_PWM_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            TEMP_PWM_TIM_CLK               RCC_APB1Periph_TIM3
// ARR的值为 10000,实际装载 Pulse_Wave 次,频率F = 100Hz
#define            TEMP_PWM_TIM_Period            (10000-1)
// 分频因子为 72-1
#define            TEMP_PWM_TIM_Prescaler         (72-1)
// PWM的脉冲宽度为 5000,占空比得出是50%
#define            TEMP_PWM_TIM_Pulse              Pulse_Wave
#define            TEMP_PWM_TIM_CCRx               CCR1
// TIM3 输出比较通道1
#define            TEMP_PWM_TIM_CH1_GPIO_CLK      RCC_APB2Periph_GPIOC
#define            TEMP_PWM_TIM_CH1_PORT          GPIOC
#define            TEMP_PWM_TIM_CH1_PIN           GPIO_Pin_6
// TIM3中断配置
#define   		   TEMP_PWM_TIMx_IRQn              TIM3_IRQn            //中断
#define            TEMP_PWM_TIMx_IRQHandler        TIM3_IRQHandler
/******************bsp_pwm.c***********************/
/* PWM波占空比(0~10000) */
uint16_t Pulse_Wave = 0;
/**
  * @brief  TIM定时器GPIO口配置
  * @param  无
  * @retval 无
  */
static void TEMP_PWM_TIM_GPIO_Config(void) 
{
	GPIO_InitTypeDef GPIO_InitStructure;
	// 开启重映射时钟(非常重要)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	// 输出比较通道1 GPIO 初始化
	RCC_APB2PeriphClockCmd(TEMP_PWM_TIM_CH1_GPIO_CLK, ENABLE);
	GPIO_InitStructure.GPIO_Pin =  TEMP_PWM_TIM_CH1_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(TEMP_PWM_TIM_CH1_PORT, &GPIO_InitStructure);
}
/**
  * @brief  配置嵌套向量中断控制器NVIC
  * @param  无
  * @retval 无
  */
static void NVIC_Config_PWM(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  
  /* Configure one bit for preemption priority */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  
  /* 配置TIM3_IRQ中断为中断源 */
  NVIC_InitStructure.NVIC_IRQChannel = TEMP_PWM_TIMx_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}
///*
// * 注意:TIM_TimeBaseInitTypeDef结构体里面有5个成员,TIM6和TIM7的寄存器里面只有
// * TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可,
// * 另外三个成员是通用定时器和高级定时器才有.
// *-----------------------------------------------------------------------------
// *typedef struct
// *{ TIM_Prescaler            都有
// *	TIM_CounterMode			     TIMx,x[6,7]没有,其他都有
// *  TIM_Period               都有
// *  TIM_ClockDivision        TIMx,x[6,7]没有,其他都有
// *  TIM_RepetitionCounter    TIMx,x[1,8,15,16,17]才有
// *}TIM_TimeBaseInitTypeDef; 
// *-----------------------------------------------------------------------------
// */
/* ----------------   PWM信号 周期和占空比的计算--------------- */
// ARR :自动重装载寄存器的值
// CLK_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1)
// PWM 信号的周期 T = ARR * (1/CLK_cnt) = ARR*(PSC+1) / 72M
// 占空比P=CCR/(ARR+1)
static void TEMP_PWM_TIM_Mode_Config(void)
{
  // 开启定时器时钟,即内部时钟CK_INT=72M
	TEMP_PWM_TIM_APBxClock_FUN(TEMP_PWM_TIM_CLK,ENABLE);
/*--------------------时基结构体初始化-------------------------*/
	// 配置周期,这里配置为100K
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
	TIM_TimeBaseStructure.TIM_Period=TEMP_PWM_TIM_Period;	
	// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
	TIM_TimeBaseStructure.TIM_Prescaler= TEMP_PWM_TIM_Prescaler;	
	// 时钟分频因子 ,配置死区时间时需要用到
	TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;		
	// 计数器计数模式,设置为向上计数
	TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;		
	// 重复计数器的值,没用到不用管
	TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
	// 初始化定时器
	TIM_TimeBaseInit(TEMP_PWM_TIM, &TIM_TimeBaseStructure);
	
	// //改变指定管脚的映射 这里选择的是TIM3完全重映射(非常重要)
	GPIO_PinRemapConfig(GPIO_FullRemap_TIM3 ,ENABLE);
	/*--------------------输出比较结构体初始化-------------------*/	
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	// 配置为PWM模式1
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	// 输出使能
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	// 输出通道电平极性配置	
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	
	// 空闲时的电平(低电平)
	TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
	
	// 输出比较通道 1
	TIM_OCInitStructure.TIM_Pulse = TEMP_PWM_TIM_Pulse;
	TIM_OC1Init(TEMP_PWM_TIM, &TIM_OCInitStructure);
	//使能预装载
	TIM_OC1PreloadConfig(TEMP_PWM_TIM, TIM_OCPreload_Enable);
	//使能TIM重载寄存器ARR
	TIM_ARRPreloadConfig(TEMP_PWM_TIM, ENABLE);
	
	// 使能计数器
	TIM_Cmd(TEMP_PWM_TIM, ENABLE);
	
	// 主输出使能(通用定时器不需要)
//	TIM_CtrlPWMOutputs(TEMP_PWM_TIM, ENABLE);
	//使能update中断
	TIM_ITConfig(TEMP_PWM_TIM, TIM_IT_Update, ENABLE);	
	
	// PWM 中断设置
	NVIC_Config_PWM();
}
// 定时器配置
void TEMP_PWM_TIM_Init(void)
{
	// GPIO设置
	TEMP_PWM_TIM_GPIO_Config();
	// 定时器模式设置
	TEMP_PWM_TIM_Mode_Config();	
	
	// 以往脉冲存储起来
	Pulse_Update_Temp = Pulse_Wave;
}
/******************stm32f10x_it.c***********************/
extern float T;
/* PWM波中断服务函数 */
void TEMP_PWM_TIMx_IRQHandler(void)
{		
	if (TIM_GetITStatus(TEMP_PWM_TIM , TIM_IT_Update) != RESET)	//TIM_IT_Update
 	{
		// 温度达到120警戒
		if(T >=120)
		{
			printf("DS18B20温度目前超过120度,必须停下来...");
			// 让PWM波的值为0
			TEMP_PWM_TIM -> TEMP_PWM_TIM_CCRx = 0;
			LED_PURPLE;
			// 程序在这卡死
			while(1)
			{
				;;
			}
		
		}
		if (Pulse_Update_Temp != Pulse_Wave)
		{
			// 修改CCR的值
			TEMP_PWM_TIM -> TEMP_PWM_TIM_CCRx = Pulse_Wave;	//根据PWM表修改定时器的比较寄存器值
			Pulse_Update_Temp = Pulse_Wave;
		}
		TIM_ClearITPendingBit(TEMP_PWM_TIM, TIM_IT_Update);	//必须要清除中断标志位
	}
}

3、DS18B20传感器代码

/******************bsp_one_wire.h***********************/
#include "stm32f10x.h"
/************(单总线)DS18B20温度传感器相关************/
#define   		   ONE_WIRE_GPIO_PORT              GPIOB
#define   		   ONE_WIRE_GPIO_PIN               GPIO_Pin_9
#define            RCC_ONE_WIRE_CLK 	           RCC_APB2Periph_GPIOB	
/******************bsp_one_wire.c***********************/
#include "bsp_one_wire.h" 
#include "bsp_systick.h"
/**
  * @brief  (单总线)DS18B20温度传感器DQ口配置(开漏输出)
  * @param  无
  * @retval 无
  */
void DS18B20_GPIO_Config(void) 
{
	GPIO_InitTypeDef GPIO_InitStructure;
	// 输出比较通道1 GPIO 初始化
	RCC_APB2PeriphClockCmd(RCC_ONE_WIRE_CLK, ENABLE);
	GPIO_InitStructure.GPIO_Pin =  ONE_WIRE_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(ONE_WIRE_GPIO_PORT, &GPIO_InitStructure);
}
/**
  * @brief  DS18B20开始温度变换
  * @param  无
  * @retval 无
  */
void DS18B20_ConvertT(void)
{
	// GPIO配置
	DS18B20_GPIO_Config();
	OneWire_Init();
	OneWire_SendByte(DS18B20_SKIP_ROM);
	OneWire_SendByte(DS18B20_CONVERT_T);
}
/**
  * @brief  DS18B20读取温度
  * @param  无
  * @retval 温度数值
  */
float DS18B20_ReadT(void)
{
	unsigned char TLSB,TMSB;
	int Temp;
	float T;
	OneWire_Init();
	OneWire_SendByte(DS18B20_SKIP_ROM);
	OneWire_SendByte(DS18B20_READ_SCRATCHPAD);
	TLSB=OneWire_ReceiveByte();
	TMSB=OneWire_ReceiveByte();
	Temp=(TMSB4)|0x10);
	OLED_Write_Cmd((x&0x0f)|0x01);
}
 /**
  * @brief  OLED_Fill,填充整个屏幕
  * @param  fill_Data:要填充的数据
	* @retval 无
  */
void OLED_Fill(unsigned char fill_Data)//全屏填充
{
	unsigned char m,n;
	for(m=0;m
The End
微信