你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32HAL库 位置式PID调节控制输出电压(超详解)

[复制链接]
STMCU小助手 发布时间:2022-5-24 10:50
本文将借助STM32CubeMX来配置ADC、DMA、DAC、USART,并利用PID位置式算法实现对输出电压进行AD采集通过PID算法调节DAC,获取到我们想要的电压值。
讲解的主要知识:
1.何为PID以及为何需要PID
2.STM32CubeMX创建
3.STM32源代码
1、何为PID以及为何需要PID

  以下是PID控制的整体框图,过程描述为:设定一个输出目标,反馈系统传回输出值,如与目标不一致,则存在一个误差,PID根据此误差调整输入值,直至输出达到设定值。

  疑问:那我们为什么需要PID呢?比如我想要利用DAC输出一个1.65V电压,直接输出不就可以了吗?

  这里必须要先说下我们的目标,因为我们所有的控制无非就是想输出能够达到我们的设定,即如果我们设定了一个目标电压值,那么我们想要一个什么样的电压值变化呢?

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

  那这样大家应该就明白,如果只是使用DAC输出一个数字信号,外界出现的误差(例如导线、噪声等原因)都可能影响到最终输出的数值,因此我们需要利用PID调节最终读取到我们想要的值。

OQ9Z2CY}OLPXVSLRE2[BP.png

Eg:要求输出1.65V,控制输出1.65V,其实得到的是1.6V,如果控制输出1.68V,那么可能可以得到1.65V,其实就是缩减目标与实际之间的误差。

2、STM32CubeMX配置:
2.1 所用工具:

芯片:STM32F103RCT6
IDE:MDK-Keil软件
STM32F1xxHAL库

2.2 知识概括:

STM32CubeMX创建ADC、DMA、DAC、USART例程
Keil软件程序编写

2.3 工程创建

1、芯片选择

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

SCPYX}R(LJH[(H0P0I)I~Y8.png

2、设置RCC

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

4D%~C5UBEZ}G~U]ED)GH$XW.png

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

_1T}AD@$H2]B}`88J3Q4XWT.png

HROB(O0JEXK`3%HI23GXT)9.png

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

XDBXKO9Z584BI6XEV_2ZWJ9.png

5、开启DAC输出


%XKAH_QJ66Y{]O%}8R~G]PU.png

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

Z)$WRXVGMYG}13B%PN64O23.png

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

A0KKLYEZ%Y}62BLIK0{HYQB.png

8、项目创建最后步骤

设置项目名称
设置存储路径
选择所用IDE

_5S6LH8B8)QT2()D1CY4)(1.png

9、输出文件

②处:复制所用文件的.c和.h
③处:每个功能生产独立的.c和.h文件

OB1ZOM5~E6PG}7XDCA_091Z.png

10、创建工程文件
  点击GENERATE CODE 创建工程

11、配置下载工具
  这里我们需要勾选上下载后直接运行

20201229143824416.png

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

  1. #ifndef __PID_H__
  2. #define __PID_H__
  3. #include "main.h"

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

  15. void PID_init( void);
  16. float PID_realize( float v, float v_r);

  17. #endif
复制代码


(2)pid.c

  1. #include "pid.h"
  2. #include "stdio.h"

  3. pid_p pid;

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

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

(3)在main.c加上:

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

  13. /**
  14.   * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx
  15.   * 输入参数: 无
  16.   * 返 回 值: 无
  17.   * 说    明:无
  18.   */
  19. int fgetc(FILE *f)
  20. {
  21.   uint8_t ch = 0;
  22.   HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
  23.   return ch;
  24. }
  25. /* USER CODE END 0 */
复制代码

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

  1.   /* USER CODE BEGIN 2 */
  2.   HAL_ADCEx_Calibration_Start(&hadc1);  // f1系列需要ADC校准,f4不需要
  3.   HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&ADC_DMA_Value,ADC_Channel_MAX); // 启动ADC的DMA转换
  4.   PID_init();
  5.   /* USER CODE END 2 */
复制代码

(5)while中加上:

  1.       HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, DAC_Value); // 设置DAC数值
  2.           HAL_DAC_Start(&hdac,DAC_CHANNEL_1);   // 开启DAC输出
  3.           ADC_Value = ADC_DMA_Value[0];
  4.           printf("%0.2f\r\n", ADC_Value*3.3/4096);
  5.           // 这里设置输出1.65V
  6.           DAC_Value = DAC_Value + PID_realize(2048, ADC_Value);
  7.           HAL_Delay(100);
复制代码

(6)用一根杜邦线连接PA0(ADC1_IN0)与PA4(DAC),然后串口连接电脑(我这里利用USB转TLL连接电脑,RX接PA9(USART1_TX),TX接PA10(USART1_RX))

(7)之后就可以完成正常读取,刚打开串口时:

(8)PID调节稳定后(DAC输出1.65V):


总结:

  以上就是利用PID调节控制获取我想要设定的电压值。



EZJUEHJ_~652ABGM0M8_(BQ.png
%ZTMK}HP1{P]Z0X8@ONCUZI.png
收藏 评论0 发布时间:2022-5-24 10:50

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版