【嵌入式】STM32利用arm-dsp库进行PID调节控制


在工程实际中,应用最为广泛的调节器控制规律为比例、积分、微分控制,简称 PID 控制,又称 PID调节。其原理介绍教科书以及网上已经有大量资料,本文着重介绍在嵌入式设备中,如何快速上手进行PID控制,并通过简单的单片机外设进行验证。

一、实验简介

1.原理

我们知道,可以通过调节PWM的占空比来起到调节等效电压的效果。比如U=D*Ud,其中U为输出的等效电压,D为占空比,Ud为单极性PWM的峰值电压,这常用在电机控制中。
当要维持输出电压U为某一确定值Us时,往往要形成回路以准确得控制U的大小,这就要用到ADC来回采电压Us,利用某种控制手段使得输出的电压U与确定值Us误差维持在一定范围内。其中,这种控制方法最为常用的就是PID控制。
以经典反馈控制回路为例:
在这里插入图片描述
其中给定值即为Us;控制规律为PID;执行器即为占空比的调整;过程即为PWM输出;被控变量即为U;传感器即为ADC。本系统采用PID控制时,可作以下描述:PWM输出的电压值U被ADC采样后,回环送入调节器与给定值Us相比较,获得一个偏差送入PID调节器,通过PID来控制PWM占空比的大小从而起到调节电压U的效果,最终使得电压U与给定值Us之间的误差稳定在某个设定的范围阈值之内。

2.所用外设

我们利用一个单片机核心板即可进行上述的PID调节PWM的实验。
其中,PWM由定时器输出;通过片内ADC进行电压回采、处理;通过arm-dsp库进行PID调节。
综上,本实验用到以下外设:
1.定时器的PWM输出;
2.定时器触发的ADC采样;
3.ADC采样的DMA传输;

二、代码

依旧用到了STM32CubeMX来进行驱动代码的生成。
这里规定了几个重要的参数:
1.STM32F407,配置主频160MHz;
1.PWM载波50Hz;
2.ADC采样率4000Hz;

1.PWM输出配置

使用TIMER3来输出PWM,160MHz主频下,TIMER3的时钟为80MHz,将在这个基础上分频到50Hz;
首先配置TIM3的PWM输出通道:
在这里插入图片描述
然后分频到50Hz,同时设置PWM极性高,Pulse随便设置即可,为了最大程度体现PID的效果,我们初始设置为0,在PWM极性高时,Pulse为0意味着PWM通道总是输出低,即PWM初始的等效电压为0.
在这里插入图片描述

2.定时器触发的DMA传输的ADC

这个完全参考我之前的博客即可,【嵌入式】STM32F4的ADC采样——多通道、DMA、定时器触发,唯一的区别在于此处只用了一通道,更简单了。
生成代码后,硬件上将PWM通道直接怼到ADC1的0通道即可。

3.主体代码

#include "main.h"
#include "stm32f4xx_hal.h"
#include "adc.h"
#include "dma.h"
#include "tim.h"
#include "gpio.h"
#include "arm_math.h"
void SystemClock_Config(void);


#define BUF_LEN 			400			//采样数组长度
#define PWM_PERIOD_CCR1		8000		//PWM周期-计数值
#define DES_VOL				1.0			//目标电压
#define ERR_LIMIT			0.05		//误差限制


typedef struct{
    arm_pid_instance_f32  S;
    float                 out;
}PidCtrlTypedef;						//pidt调节结构体

volatile uint8_t dma_cpl_flag = 0;		//dma传输完成标志
uint16_t adc_raw[BUF_LEN] = {0};		//adc原始采样值
uint16_t adc_raw_copy[BUF_LEN] = {0};	//adc原始采样值备份
float cur_vol = 0;						//当前电压

PidCtrlTypedef pidCtrl;					//pid调节实例

/* ADC-DMA全传输完成回调 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
	UNUSED(hadc);

	memcpy((void *)adc_raw_copy,(void *)adc_raw,sizeof(adc_raw));

	dma_cpl_flag = 1;
}

/* 根据原始采样值计算PWM等效电压值 */
float calVol()
{
	int i;
	float res;
	float sum = 0;
	float tempVal;
	for(i = 0;i < BUF_LEN; i++)
	{
		tempVal = adc_raw_copy[i] * 3.3 / 4095;
		sum += tempVal;
	}
	
	res = sum / BUF_LEN;

	cur_vol = res;

	return res;
} 

/* PID初始化 */
void pidInit()
{
    pidCtrl.S.Kp = 0.1;
    pidCtrl.S.Ki=0.1;
    pidCtrl.S.Kd = 0.1;
    arm_pid_init_f32(&pidCtrl.S,1);

    pidCtrl.out = 0;
}

/* PID执行 */
static void pidExecu(float vol)
{
    float pidErr;

    pidErr = DES_VOL - vol;	
	//误差不在允许范围内
	if(fabs(pidErr) > ERR_LIMIT)
    {
    	pidCtrl.out = arm_pid_f32(&pidCtrl.S,pidErr);
		//     vol			   pidCtrl.out
		//  ——————————   =    ————————————
		//	当前占空比			调节后占空比
		htim3.Instance->CCR1 = (uint32_t)(pidCtrl.out * (htim3.Instance->CCR1 + 1) / vol);
	}
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  *
  * @retval None
  */
int main(void)
{
	HAL_Init();

	SystemClock_Config();
	MX_GPIO_Init();
	MX_DMA_Init();
	MX_ADC1_Init();
	MX_TIM2_Init();
	MX_TIM3_Init();
	//pid初始化
	pidInit();
	//开启AD转换时钟
	HAL_TIM_Base_Start(&htim2);
	//开启PWM输出
	HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
	//开启ADC-DMA传输
	HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_raw, BUF_LEN);

	while (1)
	{
		if(dma_cpl_flag == 1)
		{
			dma_cpl_flag = 0;
			pidExecu(calVol());
		}
	}

}

以上代码即为主体逻辑代码。流程如下:
1.外设初始化;
2.PID初始化;
3.开启AD转换时钟;
4.开启PWM输出;
5.开启ADC-DMA采样;
6.每次DMA传输完成中断后,进行PWM有效值计算、PID控制、PWM占空比调节。
同时,程序中有几个关键点:
1.设置DMA的长度为400,即采样率4000Hz时、PWM载波50Hz时,采样100ms进一次DMA传输完成中断,共采了5周期的PWM,从而能直接对400个采样点取平均,得到PWM的有效值。即采样长度一定要为PWM载波的整数倍,且采样率要远大于PWM载波频率
2.利用arm-dsp库进行PID控制要在工程中加入arm-dsp的lib库,具体方法请参考博客【嵌入式】利用arm-DSP库进行FFT计算,获得信号的频谱、幅值及相位(上)的二-1小节;
3.arm_pid_f32函数声明如下:

static __INLINE float32_t arm_pid_f32(
  arm_pid_instance_f32 * S,
  float32_t in)

其中S为arm_pid_instance_f32 实例指针,在arm_pid_init_f32函数中实例化。要注意的是变量in,这个in是给定值与反馈值的误差,其在arm_math.h中做了阐述:

The PID controller calculates an "error" value as the difference between
   * the measured output and the reference input.
   * The controller attempts to minimize the error by adjusting the process control inputs.
   * The proportional value determines the reaction to the current error,
   * the integral value determines the reaction based on the sum of recent errors,
   * and the derivative value determines the reaction based on the rate at which the error has been changing.

了解了这几点,再配合上边注释详细的程序,即可理解实验过程以及PID调节的使用方法。具体Kp、Ki、Kd参数的调节,还需要根据不同场景来进行耐心调节。

三、实验结果

程序中定义了变量cur_vol以存储当前采到的PWM有效值,为了直观的体现PID对PWM的调节过程,使用STM32Studio进行变量实时调试,具体使用方法请自行百度。
可以看到,在设置期望值1V、初始PWM输出为0V、最大允许误差0.05V时,PID控制器可在2秒内平滑稳定得将输出值调节到误差0.05V之内。且人为地多次随机插拔采样通道使得采样值扰动、PWM失稳后,PID控制器总能再次平稳得将输出值再次调节到1V±0.05V之内。
这就体现了PID控制的实用价值所在,至于调节速度、震荡速度、允许误差等,可以通过实际工程需求进行整定、调整。
在这里插入图片描述
最后,贴一张调节稳定后的PWM实测波形,可以看到其占空比约为6.489/20 = 32.445%,以AD基准电压3.3V来算,其输出电压为3.3*0.32445 = 1.070685V,这个值由于逻辑分析仪的占空比采样误差、3.3V基准误差的存在,使得与1V有一定的偏差。同时程序里也规定了0.95~1.05V范围内是期望范围。
综上,PID调节起到了应有的作用。
在这里插入图片描述

<think>好的,用户现在想要在STM32F10x系列微控制器上实现PID控制算法,并且提到了代码优化和问题解决方案。之前我们讨论过多字节软件SPI收发的实现,现在用户可能是在整合这些模块,比如通过SPI与传感器通信获取数据,再用PID算法进行处理。 首先,我需要回顾用户之前的询问,他们需要的是PID实现的代码结构、数学的使用、头文件的关系以及如何与之前的多字节SPI结合。用户提到了math.h、sys.h、pid.h,这些可能涉及到浮点运算、系统初始化和PID的具体实现。 接下来,用户可能关心PID算法的优化,比如避免浮点运算以提高速度,或者积分饱和的问题。STM32F10x系列没有硬件浮点单元,所以使用定点数或查表法可能更高效。同时,他们可能需要调整PID参数,防止积分饱和,处理微分噪声。 然后,考虑到用户之前的多字节SPI收发,可能需要通过SPI读取传感器数据作为PID的输入,比如电机编码器的位置或速度。需要确保SPI的数据读取稳定,时序正确,避免在PID计算过程中出现数据错误。 另外,用户可能需要代码示例,如何初始化PID结构体,更新PID输出,以及如何将SPI数据整合进去。比如在sys.h中配置系统时钟,在pid.h中定义结构体和函数原型,在main.c中调用SPI读取数据并计算PID输出。 可能出现的问题包括:计算速度慢、积分饱和、微分噪声、SPI数据不稳定。需要提供解决方案,比如使用Q格式定点数、抗积分饱和处理、低通滤波,以及SPI传输中的中断控制和时序调整。 最后,用户可能需要相关的扩展问题,比如参数整定方法、与硬件SPI的比较、实时性保证等。需要确保回答结构清晰,结合之前的SPI内容,提供具体的代码示例和优化建议,同时引用相关的参考资料。</think>### STM32F10x PID控制实现及优化指南 #### 一、基础代码框架搭建 ```c // sys.h 系统配置文件 #include "stm32f10x.h" #define SYSTEM_SUPPORT_FPU // 启用硬件浮点加速(需检查芯片型号) // pid.h 头文件定义 typedef struct { float Kp, Ki, Kd; float integral; float prev_error; uint16_t max_output; } PID_Controller; void PID_Init(PID_Controller *pid, float p, float i, float d); float PID_Update(PID_Controller *pid, float setpoint, float measurement); ``` #### 二、核心算法实现(math.h优化) ```c // pid.c 实现文件 #include "math.h" void PID_Init(PID_Controller *pid, float p, float i, float d) { pid->Kp = p; pid->Ki = i; pid->Kd = d; pid->integral = 0; pid->prev_error = 0; pid->max_output = 4095; // 对应12位DAC } float PID_Update(PID_Controller *pid, float setpoint, float measurement) { float error = setpoint - measurement; // 比例项 float P = pid->Kp * error; // 积分项(带抗饱和) pid->integral += error; if(fabs(pid->integral) > 1000) pid->integral *= 0.9; // 积分限幅 float I = pid->Ki * pid->integral; // 微分项(带滤波) float D = pid->Kd * (error - pid->prev_error); pid->prev_error = error; // 输出限幅 float output = P + I + D; return fmax(fmin(output, pid->max_output), 0); } ``` #### 三、与多字节SPI的协同工作 ```c // 通过SPI读取传感器数据(示例:MAX6675温度传感器) uint16_t ReadTemperatureSPI(void) { uint8_t txBuf[2] = {0xFF, 0xFF}; uint8_t rxBuf[2]; SoftSPI_TransmitReceiveMulti(txBuf, rxBuf, 2); // 复用前文SPI函数 return (rxBuf[0] << 8) | rxBuf[1]; // 组合16位数据 } // 主控制循环 void ControlLoop(void) { PID_Controller tempPID; PID_Init(&tempPID, 2.5, 0.1, 0.05); while(1) { float temp = ReadTemperatureSPI() * 0.25; // MAX6675转换公式 float output = PID_Update(&tempPID, 100.0, temp); // 目标温度100℃ DAC_SetValue((uint16_t)output); // 输出到DAC HAL_Delay(10); // 10ms控制周期 } } ``` #### 四、关键优化策略 1. **定点数优化(替代float)** ```c // 使用Q15格式(节省50%计算时间) typedef int32_t q15_t; #define Q15_MUL(a,b) ((a * b) >> 15) q15_t PID_Update_Q15(PID_Controller_Q15 *pid, q15_t setpoint, q15_t measurement) { // 类似浮点版本的定点实现... } ``` 2. **时间微分优化** ```c // 微分项改进(减少噪声敏感) float dt = 0.01; // 10ms采样周期 D = pid->Kd * (error - 2*pid->prev_error + pid->prev_prev_error) / dt; pid->prev_prev_error = pid->prev_error; ``` 3. **抗积分饱和改进** ```c // 条件积分(输出未饱和时才累积) if(fabs(output) < pid->max_output) { pid->integral += error; } ``` #### 五、调试技巧 1. **示波器观测**:通过SPI读取数据波形与PID输出波形的相位关系[^1] 2. **阶跃响应测试**:记录系统响应曲线调整PID参数 3. **内存优化**:使用`-O2`编译优化等级,检查.map文件的栈使用情况 #### 六、常见问题解决 - **问题:PID输出剧烈震荡** **原因**:微分增益过大或采样周期过短 **解决**:减小Kd值,或在微分项添加低通滤波 - **问题:积分项累积溢出** **原因**:未做积分限幅 **解决**:添加积分分离算法 ```c if(fabs(error) > 50) pid->integral = 0; // 大偏差时停止积分 ``` - **问题:SPI数据读取不稳定** **原因**:PID计算中断SPI时序 **解决**:在SPI传输关键段禁用中断 ```c __disable_irq(); SoftSPI_TransmitReceiveMulti(...); __enable_irq(); ``` #### 七、进阶优化方向 1. **自适应PID**:根据误差自动调整参数 2. **模糊PID**:结合模糊控制理论 3. **代码向量化**:使用CMSIS-DSP加速运算 ```c #include "arm_math.h" arm_pid_instance_f32 PID_ARM; // CMSIS标准PID结构体 ``` ### 相关问题 1. 如何通过Ziegler-Nichols方法整定PID参数? 2. 软件SPI与硬件SPI在PID控制中的性能差异? 3. 如何保证PID算法的实时性?
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜老越

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值