Project 03- STM32F4xx PID controller

本文介绍如何在 STM32F4xx 微控制器上使用 ARM 提供的数学库实现 PID 控制器。通过两个 DS18B20 温度传感器和一个直流风扇的实例演示了 PID 控制的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Project 03- STM32F4xx PID controller

CMSIS files from ARM provides ARM Math functions.

There are also PID controller functions in different formats for f32, q31 and q7.

This tutorial/project will talk about how to implement PID controller on STM32F4xx using PID functions from ARM.

PID Controller

Fast about PID controller. 

PID stands for Proportional-Integral-Derivative controller.

This is a control loop feedback mechanism widely used in industrial control systems.

It calculates the error between measured value and the desired setpoint value.

According to the error, it then calculates output value to minimize this error.

I will not go step-by-step on how PID works. More you can look on the sites below:

ARM PID library

ARM company provides 3 different PID controller functions:

  • f32: float
  • q31: integer
  • q7: char

For each of the three types, you have three functions:

  • f32
    • arm_pid_init_f32
    • arm_pid_reset_f32
    • arm_pid_f32
  • q31
    • arm_pid_init_q31
    • arm_pid_reset_q31
    • arm_pid_q31
  • q7
    • arm_pid_init_q7
    • arm_pid_reset_q7
    • arm_pid_q7

There are also ARM PID structure, where you pass PID parameters.

More in project example below.

If you need additional info about these functions, you have detailed manual here.

PID Sample project

In the project, 2 DS18B20 temperature sensors are used.

They are configured in 12bit resolution, so delay between 2 calculations is about ~750ms.

If you read about PID controller, you realize that you cannot calculate PID controller results every x microcontroller,

because it has integral part which is used to sum all the errors in period.

More calculations you have, more integral error you have.

We have one device connected as “reference” temperature.

Second sensor is placed near DC FAN,

so when DC fan is turned ON, it is cooling the second DS18B20.

The goal is, that both temperatures are the same.

In my case, it was about 23°C on the reference sensor.

Then, I touched the second DS18B20 near DC fan and I heated them. DC fan was turned on.

Everything is displayed via USART to computer.

FAN is controlled via 1kHz PWM output.

According to the error between both sensors, more error, more duty cycle to DC fan.

This is just a sample project, so PID parameters are not set optimal.

For every project you have to set parameters for specific project.

This example successfully works on all STM32F4xx development board such as Discovery and Nucleo.

In the example, 1 PID controller is used.
If you need to control more than just one thing,
you can add additional ARM PID instances and
you can use more than just one PID controller at a time.

Pinout

  • Both DS18B20 sensors are connected to the same pin: PA6
  • DC fan PWM pin (controlled via transistor): PA5
  • USART output: PB6, 115200baud

Example

/**
 *    Keil project for DS18B20 & ARM PID example
 *
 *    Before you start, select your target, on the right of the "Load" button
 *
 *    @author        Tilen Majerle
 *    @email        tilen@majerle.eu
 *    @website    http://stm32f4-discovery.com
 *    @ide        Keil uVision 5
 *    @packs        STM32F4xx Keil packs version 2.2.0 or greater required
 *    @stdperiph    STM32F4xx Standard peripheral drivers version 1.4.0 or greater required
 *
 *    Additional defines in "Options for Target" > "C/C++" > "Defines"
 *        - ARM_MATH_CM4
 *        - __FPU_PRESENT = 1
 */
/* Include core modules */
#include "stm32f4xx.h"
/* Include my libraries here */
#include "defines.h"
#include "tm_stm32f4_delay.h"
#include "tm_stm32f4_onewire.h"
#include "tm_stm32f4_ds18b20.h"
#include "tm_stm32f4_disco.h"
#include "tm_stm32f4_usart.h"
#include "tm_stm32f4_pwm.h"
#include <stdio.h>

/* Include ARM math */
#include "arm_math.h"

/* How many sensors we are expecting on 1wire bus? */
#define EXPECTING_SENSORS    2

#define TEMP_CURRENT        temps[1]    /* Temperature we actually have */
#define TEMP_WANT            temps[0]    /* Temperature we want to have */

/* Choose PID parameters */
#define PID_PARAM_KP        100            /* Proporcional */
#define PID_PARAM_KI        0.025        /* Integral */
#define PID_PARAM_KD        20            /* Derivative */

int main(void) {
    /* Timer data for PWM */
    TM_PWM_TIM_t TIM_Data;
    char buf[150];
    uint8_t devices, i, count;
    /* DS18B20 devices ID */
    uint8_t device[EXPECTING_SENSORS][8];
    /* Temperatures from 2 sensors */
    float temps[EXPECTING_SENSORS];
    /* PID error */
    float pid_error;
    /* Duty cycle for PWM */
    float duty = 0;
    /* ARM PID Instance, float_32 format */
    arm_pid_instance_f32 PID;
    /* One wire instance */
    TM_OneWire_t OneWire;
    
    /* Set PID parameters */
    /* Set this for your needs */
    PID.Kp = PID_PARAM_KP;        /* Proporcional */
    PID.Ki = PID_PARAM_KI;        /* Integral */
    PID.Kd = PID_PARAM_KD;        /* Derivative */
    
    /* Initialize PID system, float32_t format */
    arm_pid_init_f32(&PID, 1);
    
    /* Initialize system */
    SystemInit();
    
    /* Initialize delay */
    TM_DELAY_Init();
    
    /* Initialize Leds */
    TM_DISCO_LedInit();
    
    /* Initialize USART1, TX: PB6, 115200 baud */
    TM_USART_Init(USART1, TM_USART_PinsPack_2, 115200);
    
    /* Initialize TIM2, 1kHz frequency */
    TM_PWM_InitTimer(TIM2, &TIM_Data, 1000);
    
    /* Initialize TIM2, Channel 1, PinsPack 2 = PA5 */
    TM_PWM_InitChannel(&TIM_Data, TM_PWM_Channel_1, TM_PWM_PinsPack_2);
    
    /* Set default duty cycle */
    TM_PWM_SetChannelPercent(&TIM_Data, TM_PWM_Channel_1, duty);
    
    /* Initialize OneWire, pin PA6 */
    TM_OneWire_Init(&OneWire, GPIOA, GPIO_PIN_6);
    
    /* Checks for any device on 1-wire */
    count = 0;
    devices = TM_OneWire_First(&OneWire);
    while (devices) {
        /* Get full ROM value, 8 bytes, give location of first byte where to save */
        TM_OneWire_GetFullROM(&OneWire, device[count]);
        /* Increase count for devices */
        count++;
        /* Get next device */
        devices = TM_OneWire_Next(&OneWire);
    }
    
    /* We need 2 devices */
    if (count != 2) {
        TM_USART_Puts(USART1, "We expect 2 devices on 1-wire. Quiting!\n");
        
        /* While */
        while (1);
    }
    
    /* Go through all connected devices and set resolution to 12bits */
    for (i = 0; i < count; i++) {
        TM_DS18B20_SetResolution(&OneWire, &device[i][0], TM_DS18B20_Resolution_12bits);
    }
    
    /* Start temperature conversion on all devices */
    TM_DS18B20_StartAll(&OneWire);
    
    while (1) {
        /* If all sensors are done with conversion */
        /* This will happen about every 750ms, because DS18B20 needs ~750ms for conversion in 12bit resolution mode */
        if (TM_DS18B20_AllDone(&OneWire)) {            
            /* Read temperature 1, reference temperature */
            TM_DS18B20_Read(&OneWire, device[0], &TEMP_WANT);
            
            /* Read temperature 2, actual temperature */
            TM_DS18B20_Read(&OneWire, device[1], &TEMP_CURRENT);
            
            /* Start new temperature conversion on all devices */
            TM_DS18B20_StartAll(&OneWire);
            
            /* Set LEDs according to the which temperature is higher */
            if (TEMP_CURRENT > TEMP_WANT) {
                TM_DISCO_LedOn(LED_GREEN);
                TM_DISCO_LedOff(LED_RED);
            } else if (TEMP_CURRENT < TEMP_WANT) {
                TM_DISCO_LedOn(LED_RED);
                TM_DISCO_LedOff(LED_GREEN);
            } else {
                TM_DISCO_LedOff(LED_ALL);
            }
            
            /* Calculate error */
            pid_error = TEMP_CURRENT - TEMP_WANT;
            
            /* Calculate PID here, argument is error */
            /* Output data will be returned, we will use it as duty cycle parameter */
            duty = arm_pid_f32(&PID, pid_error);
            
            /* Check overflow, duty cycle in percent */
            if (duty > 100) {
                duty = 100;
            } else if (duty < 0) {
                duty = 0;
            }
            
            /* Set PWM duty cycle for DC FAN to cool down sensor for "TEMP_CURRENT" */
            TM_PWM_SetChannelPercent(&TIM_Data, TM_PWM_Channel_1, duty);
            
            /* Format string */
            sprintf(buf, "Expected:   %2.3f C\nActual:     %2.3f C\nError:      %2.3f C\nDuty cycle: %3.2f %%\n----\n", TEMP_WANT, TEMP_CURRENT, pid_error, duty);
            
            /* Send to USART */
            TM_USART_Puts(USART1, buf);
        }
    }
}

2 PID controller

2.1 Description

The proportional-integral-derivative or PID controller is commonly used in the industry.

It is a feedback loop controller that manages the process command with a view to reducing the error

between the desired set point and the measured process variable.

The following block diagram shows the parallel structure of a PID controller.

This is the structure implemented in this DSP library. 

Figure 1. Block diagram of PID controller

DSP library functions

The DSP library provides three PID functions:

● DoPID: a PID core loop coded in C (the error is computed outside the function)
● DoFullPID: a full PID controller coded in C (with error computing)
● PID_stm32: an optimized PID core loop written in assembly

 

main.c文件怎么改#include "stm32f4xx.h" #include "delay.h" #include "oled.h" #include "stdio.h" #include "stdlib.h" #include "arm_math.h" #include "pid.h" #include "./adc/bsp_adc.h" #include "tim.h" #include "flash_storage.h" #include "calibration.h" extern float voltage1, voltage2, voltage3; extern float Vout_actual; float Target= 12; // 目标输出电压 extern uint16_t adc_data_ready; extern __IO uint16_t ADC_ConvertedValue[RHEOSTAT_NOFCHANEL]; extern uint16_t TIM1_Impluse ;//高级定时器占空比 extern volatile uint8_t tim_update_flag ; extern volatile uint32_t last_adc_value; // 全局PID控制器 PID_Controller buck_pid; int main() { // 初始化时加载校准系数 float calibration_factor = load_calibration(); // 校准按钮处理 if (calibration_button_pressed) { float measured_voltage = 15.0f; // 用万用表测量的实际电压 save_calibration(calibration_factor); OLED_ShowString(0, 0, "Calibration saved!", 12); } // 初始化 OLED_Init(); Adc_Init(); TIM_Init(); // 初始化PID控制器 // 参数说明:KP, KI, KD, 最小输出%, 最大输出%, 抗饱和增益 PID_Init(&buck_pid, 0.8f, 0.05f, 0.02f, 0.0f, 95.0f, 0.5f); // 安全限制 const float min_duty = 10.0f; // 最小占空比(防止电感电流不连续) const float max_duty = 90.0f; // 最大占空比(保留裕量) while(1) { if (adc_data_ready) { // 读取ADC原始值 uint32_t raw_adc = last_adc_value; // 计算实际电压 (考虑参考电压和分压比) float adc_voltage = raw_adc * 3.3f * 0.000244140625; Vout_actual = adc_voltage * VOLTAGE_DIVIDER_RATIO; // PID控制更新 float duty_percent = PID_Update(&buck_pid, Target, Vout_actual); // 占空比安全限制 if(duty_percent < min_duty) duty_percent = min_duty; if(duty_percent > max_duty) duty_percent = max_duty; // 转换为TIM CCR值 (8400对应100%) TIM1->CCR1 = (uint16_t)(duty_percent * 84.0f); adc_data_ready = 0; } // 显示处理 static char disp_buf[3][40]; snprintf(disp_buf[0], 40, "Target: %.1fV", Target); snprintf(disp_buf[1], 40, "Vout: %.2fV", Vout_actual); // 计算实际占空比百分比 float actual_duty = (TIM1->CCR1 * 100.0f) / 8400.0f; snprintf(disp_buf[2], 40, "Duty: %.1f%%", actual_duty); OLED_ShowString(0, 0, (u8*)disp_buf[0], 12); OLED_ShowString(0, 18, (u8*)disp_buf[1], 12); OLED_ShowString(0, 36, (u8*)disp_buf[2], 12); OLED_Refresh_Gram(); delay_ms(50); } } ..\..\User\flash_storage\flash_storage.h(12): error: #20: identifier "FLASH_Sector" is undefined typedef FLASH_Sector FS_Sector; ..\..\User\calibration\calibration.c: 0 warnings, 1 error compiling flash_storage.c... ..\..\User\flash_storage\flash_storage.h(12): error: #20: identifier "FLASH_Sector" is undefined typedef FLASH_Sector FS_Sector; ..\..\User\flash_storage\flash_storage.c(12): warning: #47-D: incompatible redefinition of macro "FLASH_SR_WRPERR" (declared at line 5060 of "..\..\Libraries\CMSIS\Device\ST\STM32F4xx\Include\stm32f4xx.h") #define FLASH_SR_WRPERR FLASH_FLAG_WRPERR ..\..\User\flash_storage\flash_storage.c(13): warning: #47-D: incompatible redefinition of macro "FLASH_SR_PGAERR" (declared at line 5061 of "..\..\Libraries\CMSIS\Device\ST\STM32F4xx\Include\stm32f4xx.h") #define FLASH_SR_PGAERR FLASH_FLAG_PGAERR ..\..\User\flash_storage\flash_storage.c(14): warning: #47-D: incompatible redefinition of macro "FLASH_SR_PGPERR" (declared at line 5062 of "..\..\Libraries\CMSIS\Device\ST\STM32F4xx\Include\stm32f4xx.h") #define FLASH_SR_PGPERR FLASH_FLAG_PGPERR ..\..\User\flash_storage\flash_storage.c(15): warning: #47-D: incompatible redefinition of macro "FLASH_SR_PGSERR" (declared at line 5063 of "..\..\Libraries\CMSIS\Device\ST\STM32F4xx\Include\stm32f4xx.h") #define FLASH_SR_PGSERR FLASH_FLAG_PGSERR ..\..\User\flash_storage\flash_storage.c: 4 warnings, 1 error "..\..\Output\Template.axf" - 4 Error(s), 6 Warning(s). Target not created. Build Time Elapsed: 00:00:09
最新发布
07-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值