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

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

[复制链接]
STMCU小助手 发布时间:2022-5-24 10:50
本文将借助STM32CubeMX来配置ADC、DMA、DAC、USART,并利用PID位置式算法实现对输出电压进行AD采集通过PID算法调节DAC,获取到我们想要的电压值。
- A. x2 s% X0 I讲解的主要知识:
$ z, T, m2 @. u- S/ J1.何为PID以及为何需要PID
/ }' T8 R1 P) d$ [4 ?+ U' z" N2.STM32CubeMX创建+ }0 l: X6 w, B' L- w/ a( w; N
3.STM32源代码
" S6 I' `1 b5 K' V8 p; F, O1、何为PID以及为何需要PID

6 _6 z3 w# W- S: B  以下是PID控制的整体框图,过程描述为:设定一个输出目标,反馈系统传回输出值,如与目标不一致,则存在一个误差,PID根据此误差调整输入值,直至输出达到设定值。5 d) M- w3 ?, B& x* R' p- X( @  _

/ y; f1 Y5 _  K4 Z: b- [) w  疑问:那我们为什么需要PID呢?比如我想要利用DAC输出一个1.65V电压,直接输出不就可以了吗?
5 o  S/ |3 A) L
  d! c8 I# k& s7 J- V3 I  这里必须要先说下我们的目标,因为我们所有的控制无非就是想输出能够达到我们的设定,即如果我们设定了一个目标电压值,那么我们想要一个什么样的电压值变化呢?
0 N/ i) p) z* A! Y' c' A
# @$ v& p  ?2 `% ]* v) e  比如设定目标电压值为1.65V,目标无非是希望其能够快速而且没有抖动的达到1.65V。
5 ?& r. S. m9 o1 u
6 f: C! Z( J8 M) p  那这样大家应该就明白,如果只是使用DAC输出一个数字信号,外界出现的误差(例如导线、噪声等原因)都可能影响到最终输出的数值,因此我们需要利用PID调节最终读取到我们想要的值。
( P4 x8 o; `- g5 ?9 M: ?/ e2 L& V2 Y% I% ]- ~8 t8 Z! w$ u: m% Z5 V
OQ9Z2CY}OLPXVSLRE2[BP.png % X* M. l$ d9 J+ S, Y

8 H6 L$ i: b$ @( B+ b' B) I* NEg:要求输出1.65V,控制输出1.65V,其实得到的是1.6V,如果控制输出1.68V,那么可能可以得到1.65V,其实就是缩减目标与实际之间的误差。
2 B; X4 r, _4 M1 R& [5 w
9 w! `7 C& ?8 ]( R" ?, y- Y2、STM32CubeMX配置:# Z' m& ^: T$ Z# s
2.1 所用工具:
: B2 Y; i: }, e) d- p9 N
( n6 {. S( C% p+ B+ L+ F. V芯片:STM32F103RCT6; K- V( E) G9 p7 }
IDE:MDK-Keil软件
% x/ b' X; }/ q; ^9 _2 lSTM32F1xxHAL库
$ [$ D; F# L8 |3 b0 ~! K3 O) Z% T
2.2 知识概括:# `% H7 j7 b$ {$ F5 T

/ }9 d8 i( O/ h, g: X, g) n6 ^STM32CubeMX创建ADC、DMA、DAC、USART例程  z: Z1 l$ p  ]+ O: {
Keil软件程序编写
4 R# D, V' l" O0 o2 u' ?- a% m/ F( P/ m' ]) s0 g2 L# }9 k
2.3 工程创建' c- |" H6 ~* _5 r

9 m- X1 S! N+ h# u+ X6 ?1、芯片选择
8 x0 Y- p0 Z  L
  芯片:STM32F103RCT6(根据自己的板子来进行选择)3 B5 K1 Q* L( T" Q. B+ {8 A5 i' r
" Y  B" }2 G5 |2 B- Z. L
SCPYX}R(LJH[(H0P0I)I~Y8.png + `; z9 D; O7 z3 p$ D/ D% N/ B; J2 y

# ]* ?: u) M, D4 ~6 R# r1 T2 d2、设置RCC
2 t$ l) B* f! W, C6 T2 A
  设置高速外部时钟HSE 选择外部时钟源2 C' j8 Y' p4 }, j6 _3 W
9 U2 \8 ]( [* u7 O
4D%~C5UBEZ}G~U]ED)GH$XW.png
: R: `  P4 S$ [' s
& h2 M; ^( y, w0 I4 q& O# H3、设置ADC引脚
1 ~5 b- m$ L( I- [8 V5 U. y0 U  ADC1通道0即PA0,开启连续转换模式,转换周期:55.5Cycles
* T6 S# O7 t3 B; W+ o
/ D) T) i6 Y2 n( h _1T}AD@$H2]B}`88J3Q4XWT.png " d3 X. u* E- O. F# ^. S3 Z

4 P) S3 j4 t, O0 s HROB(O0JEXK`3%HI23GXT)9.png
2 F* f# n. n$ ]9 P. E1 |. I, I9 s" R9 ]* n, ~, k2 g* M3 _
4、ADC利用DMA传输# i) a- n, S$ T
  设置DMA传输模式:循环传输(有数据就传输),同时设置高优先级
$ C! M8 O0 @* G; |3 v, l
% R% o$ Q3 J2 q+ |& l9 V2 L+ B XDBXKO9Z584BI6XEV_2ZWJ9.png " {' H) Z, }9 F. v9 h& q$ L  [
& V% t0 [5 l) ~; t% ?" r2 H% U# w) Y
5、开启DAC输出

8 [# E# @- D$ }8 p& ^8 T! v& H3 \$ Y4 G0 {! R7 D* h. E
%XKAH_QJ66Y{]O%}8R~G]PU.png 9 V% f* ]1 d# V" X: U- V

/ H* Q: C# ~9 s! c6、开启USART1
* M  v) r0 O5 E; S  设置异步通信,波特率115200Bits/s1 q, \  j. a3 C) w( x! _0 Q, O

6 t: ^7 e5 X$ e$ q6 H Z)$WRXVGMYG}13B%PN64O23.png 2 x$ V0 P4 ?6 w: S

  c' t, I3 f: |- W9 T7、配置时钟: d, E! m! \  L) K6 N, C
  F1系列芯片系统时钟为72MHzs
2 ?7 [: G/ e$ a. \: M/ U2 }) J6 x, w$ T& V. r. v2 a' t
A0KKLYEZ%Y}62BLIK0{HYQB.png / C- m+ Q, ]$ z3 r

5 t0 }$ M* c4 e4 @, l8 y6 y& d; V& u8、项目创建最后步骤, w' u1 H, I/ I2 C* Y5 s
. U& }9 a% E. c$ M; V
设置项目名称
1 C4 N* J/ v+ I; {设置存储路径
& \) Y5 @# h( r. A/ X2 q选择所用IDE
6 ?1 Z6 ?# P1 X# ]; @# F9 R1 h9 a. y. m
_5S6LH8B8)QT2()D1CY4)(1.png - r  T0 k# D8 c: i& s+ V
+ E4 z' B; J. y  H
9、输出文件; O( w0 c3 W# \- h1 R. u

1 W4 Y+ Q1 z6 b+ n7 I+ S  V②处:复制所用文件的.c和.h
& v( c. P4 |; B+ h; r③处:每个功能生产独立的.c和.h文件
; s6 j9 n! C2 j5 s3 z3 u
+ @- I3 s+ h* l  P OB1ZOM5~E6PG}7XDCA_091Z.png , s) o3 L8 C; J3 F

9 \+ Z. V( K& w4 }7 R: _' j10、创建工程文件* H; [6 C( M4 \  n" j
  点击GENERATE CODE 创建工程
( q5 `7 O, ?/ S% Q
: ?2 ^& E0 Y' l  k11、配置下载工具
2 Y6 k' ]0 B6 n' a* t  这里我们需要勾选上下载后直接运行
6 \( E3 a0 M1 O! c. u( Q5 o, n
: B  }4 G9 ?0 m8 _, z 20201229143824416.png
& a6 B& g! C7 k9 u$ i  c
9 |$ U& P: S' E3、STM32源代码:
1 x! w, U( m# B2 U+ Q  首先编写pid.c与pid.h文件代码,这里编写的是pid位置式算法。
; ]9 m0 Y3 P! T2 m7 |(1)pid.h! v2 P3 H0 I) o3 L3 Q& I- l( K3 u+ B

6 a4 q0 l! ?3 D/ u  v
  1. #ifndef __PID_H__7 E8 q3 n6 k: Q3 }6 _
  2. #define __PID_H__0 \4 k2 W1 n9 R9 ^
  3. #include "main.h"4 K( L* H8 e2 `
  4. : \7 G1 U6 W, L6 B
  5. typedef struct
    ) X0 D$ T( Z0 _- T
  6. {4 f8 I* \, T, P3 Z8 D7 s; K' G* a
  7.     float SetVoltage;                  //定义设定值. c2 [8 `! ?& o
  8.     float ActualVoltage;        //定义实际值/ b' v# m) r  t- ?9 n
  9.     float err;                            //定义偏差值
    1 v$ u% p+ I: [4 A
  10.     float err_last;                          //定义上一个偏差值7 h) R" u3 {. D0 ~
  11.     float Kp,Ki,Kd;                          //定义比例、积分、微分系数
    ; A/ v- G! e- ?+ H7 i
  12.     float result;                    //pid计算结果
    9 A3 O; c( D& i% _8 m1 b" _. F+ H# C' e
  13.     float voltage;                          //定义电压值(控制执行器的变量)0-5v右转 5-10v左转) g5 \( V. ?9 J7 ^- |$ d' k
  14.     float integral;                          //定义积分值
    / ~. Q, K' W) H* G2 Q# T+ B
  15. }pid_p;
    ' Q) G8 _: F5 j

  16. # W8 s- \6 n* a8 E5 m, f1 @
  17. void PID_init( void);
    + `: ~: T8 V* y' J; ]6 R
  18. float PID_realize( float v, float v_r);
    ! [/ r; a" r+ n7 K
  19. ! Q" ]- n; @, d  P7 U* E# P( E. ^
  20. #endif
复制代码
+ s' Y9 n" C7 R* m, z

* y! y1 |2 w7 z# c/ }  m(2)pid.c
) e0 c, L2 H+ ]  ^' h$ v9 m+ z
$ a5 e8 C4 j/ n( J0 |2 S
  1. #include "pid.h"
    8 h( T% S! g$ U+ w3 T) P
  2. #include "stdio.h"
    : L) T; Q9 w, R; W- G$ v1 J

  3. # C: `! [- M7 W& S+ n
  4. pid_p pid;9 m& W3 ~& f0 h
  5. 3 n$ S/ o8 W4 U3 o" M
  6. //pid位置式
    , y) ^3 T( T5 d: q/ L' f$ J$ B
  7. void PID_init()# r) f. C% Q7 W6 h
  8. {
    ) }+ {) [& ]( n' t
  9.     printf("PID_init begin \n");
    9 X+ b' c8 O6 m
  10.     pid.SetVoltage= 0.0;                          // 设定的预期电压值) R2 e6 N1 G+ ]  I* r. D
  11.     pid.ActualVoltage= 0.0;                        // adc实际电压值
      U3 z+ S# A5 ]2 i5 S2 @. p
  12.     pid.err= 0.0;                                    // 当前次实际与理想的偏差5 D8 e+ Q8 S) W: f" }
  13.     pid.err_last=0.0;                            // 上一次的偏差7 N7 x2 S4 C- F- U* ~
  14.     pid.voltage= 0.0;                            // 控制电压值# u) E8 p  W9 l) U) h
  15.     pid.integral= 0.0;                                  // 积分值
    * A: G# U% b7 A  R* T
  16.     pid.Kp= 0.2;                                    // 比例系数, D  {5 G$ v. v4 ~# t  X/ {1 H
  17.     pid.Ki= 0.15;                                    // 积分系数
    7 h; c( H8 N' L
  18.     pid.Kd= 0.2;                                    // 微分系数$ }& C! D+ F1 y, ^; z5 G; S$ P6 o
  19.     printf("PID_init end \n");9 G' s5 M. m& T6 F8 {8 f# ~/ ?% i
  20. }$ n$ o" @1 D% \+ {! ~
  21. 0 o$ s8 [% C3 P5 u! B! s1 X
  22. float PID_realize( float v, float v_r)+ V% M& u' {8 t
  23. {
    ; {. h0 e  o! o) ]: c5 b
  24.     pid.SetVoltage = v;                        // 固定电压值传入
    $ T/ z9 [# R) m  ~+ R5 I  \+ H
  25.     pid.ActualVoltage = v_r;        // 实际电压传入 = ADC_Value * 3.3f/ 4096+ z( o1 y; `+ I# l# D& F+ Q: O/ X
  26.     pid.err = pid.SetVoltage - pid.ActualVoltage;        //计算偏差
    * h0 Y/ v+ y) S/ {& |
  27.     pid.integral += pid.err;                                                //积分求和
    2 a; L3 U  L$ J; _. i. e* I
  28.     pid.result = pid.Kp * pid.err + pid.Ki * pid.integral + pid.Kd * ( pid.err - pid.err_last);//位置式公式$ p) ~% x& a+ D" F9 f& D  Q! |) q
  29.     pid.err_last = pid.err;                                //留住上一次误差! G9 C0 \; f# ~' l. T0 m
  30.     return pid.result;
    & b' c; _+ \3 D* ~4 a- e, H  c
  31. }
复制代码

; S$ V9 H5 j% Y* ?9 n1 H. {(3)在main.c加上:0 w, @0 z5 e0 y7 @7 \% ~, r
* n. Y2 t/ d/ x" ~+ Z' B7 V
  1. /* USER CODE BEGIN Includes */7 l, _  ^6 q! L5 v! h
  2. #include "stdio.h"
    2 r. I1 L) W& ~* F
  3. #include "pid.h"
    3 W: Y  p1 N7 }! I9 ^% O( n. M
  4. /* USER CODE END Includes */
复制代码
  1. /* USER CODE BEGIN PD */. s* ]2 \" z( V9 B" C' r
  2. #define ADC_Channel_MAX 2
    / O" m! z5 F( y3 K2 n
  3. /* USER CODE END PD */
复制代码
  1. /* USER CODE BEGIN PV */- \( x7 V# s% J' B6 z
  2. uint16_t ADC_DMA_Value[ADC_Channel_MAX];  // DMA得到ADC的值
    2 R/ F4 E" F4 c# Z, n/ `
  3. uint16_t ADC_Value = 0;
    2 r; Q$ T# h2 O) C
  4. uint16_t DAC_Value = 100;' N3 h5 U* y+ S# a3 \
  5. /* USER CODE END PV */
复制代码
  1. /* USER CODE BEGIN 0 */8 z* P, E% `/ [2 @0 b4 j- W
  2. /**8 E4 _6 o) D' Z' a7 ?' M/ U' G
  3.   * 函数功能: 重定向c库函数printf到DEBUG_USARTx1 q* h  y2 i8 l) W/ S7 N/ H5 }8 V
  4.   * 输入参数: 无- k3 Q7 T0 O0 O8 o' _
  5.   * 返 回 值: 无
    & v4 N6 D/ U1 W: [
  6.   * 说    明:无" E% g6 R# x2 j
  7.   */
    * I, _" |  B; w; K% A8 p
  8. int fputc(int ch, FILE *f)* X7 j4 P# ~% H3 C: k
  9. {
    $ ]" e+ P) _8 Z' F4 W9 y2 `7 A& S% ?5 u
  10.   HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
    / U" ?+ I: o2 D
  11.   return ch;
    3 }8 \) W6 T% G+ n. F
  12. }7 V3 D' Y: ~; O8 w& K, ]6 M' D% B

  13. 8 ?$ i( h4 k9 j1 t8 {8 z
  14. /**0 R% ~! G5 c% C
  15.   * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx
    3 X6 ~2 |/ F% l0 X! r9 S3 A
  16.   * 输入参数: 无
    1 ?& _* H, l$ e" ?
  17.   * 返 回 值: 无
    ) l4 w  h) M, f, N) u% v, M
  18.   * 说    明:无- H  v4 z7 r& A/ A8 x4 j( ~
  19.   */& H7 U- i/ z' v  @# V
  20. int fgetc(FILE *f)
    6 v+ i3 H: ]  ~& ?/ }, T
  21. {' w; }6 s0 e: ]. g2 ~' E9 b* N
  22.   uint8_t ch = 0;1 `, r0 T3 ^& Q
  23.   HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
    + B# \. u8 q& Z  K1 x
  24.   return ch;6 U; o' b+ X7 \2 k
  25. }7 m7 G, ^: {- ?5 a3 ~
  26. /* USER CODE END 0 */
复制代码

/ {5 c; _+ {* M, u(4)在ADC初始化之后加上AD校准函数:% H  c% R& n5 M( L: r: Q

5 A$ J4 q% }" }( f7 c
  1.   /* USER CODE BEGIN 2 */* z$ H+ `: S. V! D: F- C$ h! |! l' u4 n: R
  2.   HAL_ADCEx_Calibration_Start(&hadc1);  // f1系列需要ADC校准,f4不需要. w4 L  Y$ q% R, [8 v- G
  3.   HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&ADC_DMA_Value,ADC_Channel_MAX); // 启动ADC的DMA转换! u. S& M1 X# |1 M% \
  4.   PID_init();2 G7 M$ s: _# I3 v
  5.   /* USER CODE END 2 */
复制代码

6 q% |6 N+ m/ A- I% q! D! Y(5)while中加上:- r1 m$ e& L' e% `* ^
* C% f0 U# X! E# l
  1.       HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, DAC_Value); // 设置DAC数值! q$ `: ]. X* ]& W4 R  X7 [
  2.           HAL_DAC_Start(&hdac,DAC_CHANNEL_1);   // 开启DAC输出
    1 A- U  X9 h7 C4 t7 g$ Y
  3.           ADC_Value = ADC_DMA_Value[0];, M. [2 ]  N: ]+ L! X$ O
  4.           printf("%0.2f\r\n", ADC_Value*3.3/4096);/ |5 C' V: f: n
  5.           // 这里设置输出1.65V
    ; g. I) H: Y) A: K, ?7 s( [
  6.           DAC_Value = DAC_Value + PID_realize(2048, ADC_Value);
    6 V  M8 M8 U# K; `0 A, N: c
  7.           HAL_Delay(100);
复制代码

) Z6 `7 H% L, }$ i0 B  i% N(6)用一根杜邦线连接PA0(ADC1_IN0)与PA4(DAC),然后串口连接电脑(我这里利用USB转TLL连接电脑,RX接PA9(USART1_TX),TX接PA10(USART1_RX))3 t5 u  V( K" C  b6 Y  G( x0 p

- i5 e0 ?. b6 U" k8 `; Q(7)之后就可以完成正常读取,刚打开串口时:
: ^  V# C' X8 @' E8 z8 B8 ^1 j2 Y, V. _# i
(8)PID调节稳定后(DAC输出1.65V):
: v0 Z6 |! z8 b" ^* g0 s+ n5 u7 g. I* ~. T

- ^, L& Q; k, N) m  f总结:

& ~0 q) [; Y# r' g+ m  o8 H  以上就是利用PID调节控制获取我想要设定的电压值。
: L) W2 O  D# d! w) d
: Y- k: [3 q! g: T: G4 [4 O6 x2 \! c5 Y9 I1 `

0 d* }+ `& p: y* h
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 手机版