PID温控实验平台搭建(四)——PID温控系统实验代码讲解
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