STM32 PID调节输出电压

一、简介

        关于PID调节的这里不做详解,就简单说下,其实就是先设定好一个期望,通过反馈系统返回输出值,然后判断这个输出实际输出的值,和我们的期望值的误差,然后PID算法根据这个误差,去调整我们的输出值,直到输出达到我们的期望值。

        那么我们为啥需要我们需要用PID去调整输出值呢,不能直接就设定一直输出期望值吗,这里就要说一下,以我们使用的STM32为例,我们要控制输出电压是利用DAC输出一个数字信号,但是有时候外界会出现一些误差,如噪声啊,甚至说打雷,漏电了,这些都可能让我们的输出值受到影响,因此这里就到我们的PID出马了,去不断的通过反馈系统去判断,然后根据误差去调输出值,让我们快速且平稳的达到我们要的输出值。

        比如设定目标电压值为1.65V,目标无非是希望其能够快速而且没有抖动的达到1.65V。

 

二、工程创建

1、芯片选择
  芯片:STM32F103RCT6(根据自己的板子来进行选择)

在这里插入图片描述

 2、设置RCC
  设置高速外部时钟HSE 选择外部时钟源

 3、设置ADC引脚
  ADC1通道0即PA0,开启连续转换模式,转换周期:55.5Cycles

 4、ADC利用DMA传输
  设置DMA传输模式:循环传输(有数据就传输),同时设置高优先级

 5、开启DAC输出

 6、开启USART1
  设置异步通信,波特率115200Bits/s

 7、配置时钟
  F1系列芯片系统时钟为72MHzs

 

三、实验代码

首先编写pid.c与pid.h文件代码,这里编写的是pid位置式算法。
(1)pid.h

#ifndef __PID_H__
#define __PID_H__
#include "main.h"

typedef struct
{
    float SetVoltage;	  	//定义设定值
    float ActualVoltage;	//定义实际值
    float err;			    //定义偏差值
    float err_last;		  	//定义上一个偏差值
    float Kp,Ki,Kd;		  	//定义比例、积分、微分系数
    float result;		    //pid计算结果
    float voltage;		  	//定义电压值(控制执行器的变量)0-5v右转 5-10v左转
    float integral;		  	//定义积分值
}pid_p; 

void PID_init( void);
float PID_realize( float v, float v_r);

#endif

(2)pid.c

#include "pid.h"
#include "stdio.h"

pid_p pid;

//pid位置式
void PID_init()
{
    printf("PID_init begin \n");
    pid.SetVoltage= 0.0;		  	// 设定的预期电压值
    pid.ActualVoltage= 0.0;			// adc实际电压值
    pid.err= 0.0;				    // 当前次实际与理想的偏差
    pid.err_last=0.0;			    // 上一次的偏差
    pid.voltage= 0.0;			    // 控制电压值
    pid.integral= 0.0;			  	// 积分值
    pid.Kp= 0.2;				    // 比例系数
    pid.Ki= 0.15;				    // 积分系数
    pid.Kd= 0.2;				    // 微分系数
    printf("PID_init end \n");
}

float PID_realize( float v, float v_r)
{
    pid.SetVoltage = v;			// 固定电压值传入
    pid.ActualVoltage = v_r;	// 实际电压传入 = ADC_Value * 3.3f/ 4096
    pid.err = pid.SetVoltage - pid.ActualVoltage;	//计算偏差
    pid.integral += pid.err;						//积分求和
    pid.result = pid.Kp * pid.err + pid.Ki * pid.integral + pid.Kd * ( pid.err - pid.err_last);//位置式公式
    pid.err_last = pid.err;				//留住上一次误差
    return pid.result;
}

(3)在main.c加上:

/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "pid.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
uint16_t ADC_DMA_Value[ADC_Channel_MAX];  // DMA得到ADC的值
uint16_t ADC_Value = 0;
uint16_t DAC_Value = 100;
/* USER CODE END PV */
/* USER CODE BEGIN PD */
#define ADC_Channel_MAX 2
/* USER CODE END PD */
/* USER CODE BEGIN 0 */
/**
  * 函数功能: 重定向c库函数printf到DEBUG_USARTx
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
  return ch;
}
 
/**
  * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
int fgetc(FILE *f)
{
  uint8_t ch = 0;
  HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
  return ch;
}
/* USER CODE END 0 */

(4)在ADC初始化之后加上AD校准函数:

  /* USER CODE BEGIN 2 */
  HAL_ADCEx_Calibration_Start(&hadc1);  // f1系列需要ADC校准,f4不需要
  HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&ADC_DMA_Value,ADC_Channel_MAX); // 启动ADC的DMA转换
  PID_init();
  /* USER CODE END 2 */

(5)while中加上:

      HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, DAC_Value); // 设置DAC数值
	  HAL_DAC_Start(&hdac,DAC_CHANNEL_1);   // 开启DAC输出
	  ADC_Value = ADC_DMA_Value[0];
	  printf("%0.2f\r\n", ADC_Value*3.3/4096);
	  // 这里设置输出1.65V
	  DAC_Value = DAC_Value + PID_realize(2048, ADC_Value);
	  HAL_Delay(100);

        用一根杜邦线连接PA0(ADC1_IN0)与PA4(DAC),然后串口连接电脑(我这里利用USB转TLL连接电脑,RX接PA9(USART1_TX),TX接PA10(USART1_RX))之后就可以完成正常读取,刚打开串口时:

 

 

  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值