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

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

[复制链接]
STMCU小助手 发布时间:2022-5-24 10:50
本文将借助STM32CubeMX来配置ADC、DMA、DAC、USART,并利用PID位置式算法实现对输出电压进行AD采集通过PID算法调节DAC,获取到我们想要的电压值。
: O. N! Y% i2 k6 P$ J: f讲解的主要知识:" t9 Y/ c) a- W+ @8 E$ a
1.何为PID以及为何需要PID
& i0 Y1 `+ f" w9 {+ C2.STM32CubeMX创建7 \4 V- ]: q9 d. y, a
3.STM32源代码, F) E1 k) a9 ^
1、何为PID以及为何需要PID

7 _' r# B7 i8 L3 o) X  以下是PID控制的整体框图,过程描述为:设定一个输出目标,反馈系统传回输出值,如与目标不一致,则存在一个误差,PID根据此误差调整输入值,直至输出达到设定值。
8 M0 F# M( p" m' A, K
$ v3 V3 J  g3 Y& a) w  疑问:那我们为什么需要PID呢?比如我想要利用DAC输出一个1.65V电压,直接输出不就可以了吗?
" U; Y4 u1 U3 Z
3 P  I( _3 F% ?; Z0 @7 N  这里必须要先说下我们的目标,因为我们所有的控制无非就是想输出能够达到我们的设定,即如果我们设定了一个目标电压值,那么我们想要一个什么样的电压值变化呢?
) l" q& e# w9 }+ P  E  w4 o3 Y) n' p7 }* H! |5 \! ^
  比如设定目标电压值为1.65V,目标无非是希望其能够快速而且没有抖动的达到1.65V。
1 o7 s9 A$ Y" w$ Z3 n6 m8 m3 G. T- i
  那这样大家应该就明白,如果只是使用DAC输出一个数字信号,外界出现的误差(例如导线、噪声等原因)都可能影响到最终输出的数值,因此我们需要利用PID调节最终读取到我们想要的值。
5 U& q) @& a" Y- j0 h: D" T% G9 K5 f' W4 H
OQ9Z2CY}OLPXVSLRE2[BP.png
* y8 |+ M4 [; n- O) D3 t  p3 v
; _$ e! m5 W' ?- {% G4 [  Y+ d) ?Eg:要求输出1.65V,控制输出1.65V,其实得到的是1.6V,如果控制输出1.68V,那么可能可以得到1.65V,其实就是缩减目标与实际之间的误差。1 X% [6 _4 Y, P3 ^
% o  k! T: K; A- a- T/ y# V7 A
2、STM32CubeMX配置:  _* e6 C/ g4 l( e: l' y
2.1 所用工具:! ~8 I" b/ G1 q& \3 Z
  u. k( p+ b( R) P
芯片:STM32F103RCT6& A3 H7 F- Z! H) B; \$ ]) [0 \
IDE:MDK-Keil软件
7 I8 ]+ H% u' k+ }5 |; n. BSTM32F1xxHAL库
1 W  N) Q( h/ k* E4 r
5 J5 B1 W" m# o* |# C- ^2.2 知识概括:
3 ]; H& I5 V1 s7 P5 h: r/ J) l
% A' h) B" l. a' _6 pSTM32CubeMX创建ADC、DMA、DAC、USART例程7 Z" `! R2 U# S9 Q4 c3 t
Keil软件程序编写+ z3 V) H4 }( p2 O8 a2 L" x

, l4 c4 ^: `; H6 L6 b( ?/ o2 _2 a2.3 工程创建0 t( X$ G, W( `+ J* ~- {3 S! s. m

  E' a8 K. D" J- h3 _' v0 r+ l1、芯片选择

' ?$ d) y$ ]8 F  芯片:STM32F103RCT6(根据自己的板子来进行选择)" ^6 W5 g( k* |9 W
. r$ i- I7 G; g8 N! ]$ ~" h
SCPYX}R(LJH[(H0P0I)I~Y8.png 2 t% T0 e5 C) z
4 s: z4 {1 f8 O5 I  B$ q8 f
2、设置RCC

5 Y* {* c+ F' ~. j7 `  设置高速外部时钟HSE 选择外部时钟源
+ S5 [8 a4 q& A, H7 ?
) O! L6 w* B% X 4D%~C5UBEZ}G~U]ED)GH$XW.png
; N' V9 y7 j4 A- X* P# I2 `! v
: H0 T4 \: f) u% |5 a3、设置ADC引脚) z: z: V/ _7 q. d/ Y8 R3 }3 x- N
  ADC1通道0即PA0,开启连续转换模式,转换周期:55.5Cycles
6 {; Y& N9 h8 @( {& i: r& W( b* G& _7 W1 k5 f
_1T}AD@$H2]B}`88J3Q4XWT.png
8 p3 p  s; P1 H- Z& v: q6 ^2 S
2 v8 s7 s4 |* B" y# I  ~ HROB(O0JEXK`3%HI23GXT)9.png
9 P, H% |1 w* a* e5 z7 `$ K
3 Y" N* @: k0 Z, Z4、ADC利用DMA传输- C; [: P9 h! l$ b7 H- n& x  [
  设置DMA传输模式:循环传输(有数据就传输),同时设置高优先级2 m. K, p7 P- H

1 |* t8 H4 P$ s. Z" q XDBXKO9Z584BI6XEV_2ZWJ9.png
  k" S4 M( o* V  |
6 H& t7 H& H) d2 {$ Y, Z5、开启DAC输出
* _  w  V& i1 E
  j. A" l( @* G8 Z5 q
%XKAH_QJ66Y{]O%}8R~G]PU.png / O  p! |/ ^& I
' ~/ x  t7 X3 h4 V
6、开启USART10 G! }$ H  ~: x0 B; }0 b* N+ Q3 a
  设置异步通信,波特率115200Bits/s5 Q- |/ Z  ]  e

: t6 Y% i' n% h. Y Z)$WRXVGMYG}13B%PN64O23.png
0 e+ w, @' ~- p7 N: o' O3 g1 X. T
7、配置时钟5 X5 P1 M* f# v  \
  F1系列芯片系统时钟为72MHzs6 S: d4 L5 R  }& K5 b2 J8 v

0 A$ w9 X: i2 j# p# O A0KKLYEZ%Y}62BLIK0{HYQB.png 6 w5 p6 S9 ?! O' C( h

$ j2 [; m: Z8 z  `/ m8、项目创建最后步骤
1 M( {* K& v1 B- d4 F* h8 }4 v( R
设置项目名称
# F9 E. v9 K9 M% ^0 K1 U  E设置存储路径" r# P* P! h. c: W- H8 o$ ~
选择所用IDE
+ _6 E0 S3 w( [, j) F
% `, [5 E$ a3 W5 H1 e _5S6LH8B8)QT2()D1CY4)(1.png
/ `4 r5 h, @5 v% l$ K& q: r9 d; y: K" D" H0 A3 S% i& Y
9、输出文件9 Q, q% R) v! c; ^

: q3 y. I$ L7 }$ ^& D②处:复制所用文件的.c和.h! f2 L4 [* x, j( }0 c: s
③处:每个功能生产独立的.c和.h文件
" `0 m; t7 I* L% s
4 A. i( a% j$ I9 } OB1ZOM5~E6PG}7XDCA_091Z.png 0 u0 I* _- M) g% n% _- x" B" {
4 `0 W$ |6 |$ g
10、创建工程文件$ s0 l& g3 l8 g: K- [
  点击GENERATE CODE 创建工程) f- z' Q3 ^7 K1 m) z2 F
! ]! g/ P1 R# Q0 u- z! s2 @
11、配置下载工具
# ?: z+ x( p1 |+ A1 E( {  这里我们需要勾选上下载后直接运行* R5 ^1 V- o: E

8 a7 {0 L: e6 N7 a: [* _0 [5 x 20201229143824416.png
/ x& t: z$ j3 N- ]1 \: }0 a% ]# [' {& G
3、STM32源代码:
% E! C* Q1 {7 [  a9 U  首先编写pid.c与pid.h文件代码,这里编写的是pid位置式算法。2 d+ p) q) h0 i7 U# Z
(1)pid.h0 |! w9 {% H$ k
2 D6 v% \3 M7 i6 p
  1. #ifndef __PID_H__1 d. u) y, T2 t4 I' i( _, D
  2. #define __PID_H__+ }6 {: ]: V' B: z- q
  3. #include "main.h"2 }) K# Y: A3 ?& Y) ~- i" N3 H5 U
  4. ( m8 g; P! S: I/ {
  5. typedef struct- V% X. K* o5 q
  6. {5 A4 w. |$ R" d9 d2 e7 _* `1 u. m
  7.     float SetVoltage;                  //定义设定值6 f2 V& a! [' U% v
  8.     float ActualVoltage;        //定义实际值
    ' j8 m$ @5 @2 d' G9 e
  9.     float err;                            //定义偏差值% @# K- |' }$ R7 Z" e, m2 D/ s
  10.     float err_last;                          //定义上一个偏差值% _+ X# q# K0 e1 f- V0 W1 A
  11.     float Kp,Ki,Kd;                          //定义比例、积分、微分系数  ]2 L- L$ s/ U; d7 Q8 J$ U1 H
  12.     float result;                    //pid计算结果
    ! x9 l9 Y* ^8 b) {5 E$ V8 A+ f2 R. t. S
  13.     float voltage;                          //定义电压值(控制执行器的变量)0-5v右转 5-10v左转" }0 |' }: n2 z$ s$ I4 P/ S( ~; I$ m
  14.     float integral;                          //定义积分值
    7 E4 h0 Q6 M2 O) K: G7 y
  15. }pid_p; ; g/ ?- c* E7 G

  16. # f2 b! R9 S: G1 f4 s' n- A
  17. void PID_init( void);6 w7 R9 S- `5 b1 Y. D* z$ m$ V
  18. float PID_realize( float v, float v_r);
    : }5 i0 ?3 e* x7 Z
  19. ' y) J, m* |7 Y4 h! u& ]
  20. #endif
复制代码

2 _) j1 U7 d7 L2 N% X4 p" j3 f/ D: S/ v& W, p% i
(2)pid.c
) d- D" m/ b: V1 V9 v; M  O9 `& x1 }" ]3 ~6 m
  1. #include "pid.h"6 f3 H4 G6 L8 N/ t& \* t
  2. #include "stdio.h"
    . e3 F. Y# l* v" R4 m. Q- E" f0 k

  3.   E% l, o  n: |' Z/ K- ~$ N
  4. pid_p pid;& \2 l% g  t" @
  5. 1 c; m1 C. a0 a, p' q) c3 W/ ^
  6. //pid位置式
    & `8 c' d1 M) p! i8 o: |
  7. void PID_init()4 O8 n$ v* l- Z; L$ r
  8. {
    ( {3 _( \+ `2 E& o8 F& t! o0 o
  9.     printf("PID_init begin \n");
    4 @- {. Y7 c( f8 K8 [- \+ I' [. e
  10.     pid.SetVoltage= 0.0;                          // 设定的预期电压值; G0 V2 T) Z3 J
  11.     pid.ActualVoltage= 0.0;                        // adc实际电压值
    ( p8 N5 d. A+ W9 o/ o/ j4 Y8 Q: C
  12.     pid.err= 0.0;                                    // 当前次实际与理想的偏差
    + `' z* A; @3 |
  13.     pid.err_last=0.0;                            // 上一次的偏差
    1 D0 s+ E7 E( c5 a3 s! `
  14.     pid.voltage= 0.0;                            // 控制电压值% \/ V; b. c* P$ c
  15.     pid.integral= 0.0;                                  // 积分值7 }, Y& }9 Y4 U; y1 n0 }
  16.     pid.Kp= 0.2;                                    // 比例系数
    5 i+ A' J% `# Z' N( o& A  h
  17.     pid.Ki= 0.15;                                    // 积分系数: D0 m( |1 V* A/ o
  18.     pid.Kd= 0.2;                                    // 微分系数
    ! y- I. I1 @  s! D- O6 [- W  k, q
  19.     printf("PID_init end \n");' k. r; [% Z1 ]( @; p  L) x8 }
  20. }
    7 F* ^. A% T+ f5 H+ x- \( s- M* W! V

  21. / [- e5 ]4 R2 p: a# z. T* W" w' ^
  22. float PID_realize( float v, float v_r)6 K. N4 L( _4 Q9 `9 N- ?& t
  23. {
    ! m5 m+ N" x" ?
  24.     pid.SetVoltage = v;                        // 固定电压值传入
    5 [% o8 S  I% b4 ]; ~) A- b/ x
  25.     pid.ActualVoltage = v_r;        // 实际电压传入 = ADC_Value * 3.3f/ 40964 U6 |$ V7 V9 Z) ?
  26.     pid.err = pid.SetVoltage - pid.ActualVoltage;        //计算偏差
    " I- H1 F7 @4 Z/ U: H/ }4 U7 i1 y
  27.     pid.integral += pid.err;                                                //积分求和
    ( Q/ E; p7 u! e  o# e/ g* n/ E( E
  28.     pid.result = pid.Kp * pid.err + pid.Ki * pid.integral + pid.Kd * ( pid.err - pid.err_last);//位置式公式
    . }& |9 L" b" B& r
  29.     pid.err_last = pid.err;                                //留住上一次误差- A. |2 ~% F5 @1 i7 Y
  30.     return pid.result;1 {6 X. A. _& l- L$ y) h' l
  31. }
复制代码

, V; Y0 z3 [# L8 H" S6 l" d(3)在main.c加上:
9 a9 C# g+ K+ B- t7 A2 N
; g/ a% O: L# ]# e, y
  1. /* USER CODE BEGIN Includes */% Q- h% }3 S  n
  2. #include "stdio.h". K8 u2 G; p& t* G3 \# r; E
  3. #include "pid.h"* N. _; M/ E! E" E; O
  4. /* USER CODE END Includes */
复制代码
  1. /* USER CODE BEGIN PD */% \! R# D. J2 S* r  T  q+ F5 q) m
  2. #define ADC_Channel_MAX 2# A: R4 x, O* }8 d% a9 n* T
  3. /* USER CODE END PD */
复制代码
  1. /* USER CODE BEGIN PV */
    7 g5 |, u( o( [' }. ~
  2. uint16_t ADC_DMA_Value[ADC_Channel_MAX];  // DMA得到ADC的值
    6 Q: r' u1 B3 I  X% C1 Z
  3. uint16_t ADC_Value = 0;
    5 r# V0 b! o& U+ P6 r" j
  4. uint16_t DAC_Value = 100;
    3 K" W, g. H+ A3 R/ T
  5. /* USER CODE END PV */
复制代码
  1. /* USER CODE BEGIN 0 */
    / s& k( p4 M; S$ W) Z) O
  2. /**8 r& Q/ n, [- p, X! m: K
  3.   * 函数功能: 重定向c库函数printf到DEBUG_USARTx
    , z  Q' Y9 M* W! t7 H1 n
  4.   * 输入参数: 无
    ' w8 L& S) f( N7 f- W; t" y& q" b# k" J
  5.   * 返 回 值: 无
    " N$ m/ N$ b5 i6 s  ]6 E
  6.   * 说    明:无' k. l+ {, f5 D  M% [
  7.   */) [4 Z  h8 F! W
  8. int fputc(int ch, FILE *f)3 A: G0 \8 o! q: x
  9. {! }" \* w+ y5 p6 R
  10.   HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);) ]" Z9 T8 O/ |: M+ x
  11.   return ch;! `3 W/ I% [' k2 J: o# h
  12. }4 W' a1 j" [  }' v- I. J, b

  13. 0 {& k( U* |; E# D, X, r$ K
  14. /**
    9 v! V( Y; k, u  h% }$ A
  15.   * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx) K# D/ X. }3 i6 e6 i
  16.   * 输入参数: 无# p* i7 B  X  }7 {8 F% p: j8 e
  17.   * 返 回 值: 无
    ; M" q( P9 c6 a6 O% b, E1 V% W) B
  18.   * 说    明:无
    # g+ q/ y  t+ r: O+ a2 t  f0 d2 D7 ]
  19.   */
    / d. q6 c, V; F0 _. G
  20. int fgetc(FILE *f): b0 d) R; Q, ?2 ^  ~
  21. {4 h& N7 X+ S, B
  22.   uint8_t ch = 0;6 z2 H8 k# s9 ~% U1 Q& G1 u( q! ^
  23.   HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
    5 Q, {2 m# Z. Q6 w. O6 c3 J% f  W  y
  24.   return ch;
    - q# q' [% G1 n+ d( s0 Y
  25. }5 R. M1 A5 G5 @& V
  26. /* USER CODE END 0 */
复制代码
& d, T9 h+ r5 ]
(4)在ADC初始化之后加上AD校准函数:
: C  c9 q3 ^+ h% U* w% ^4 I4 Z0 d
- q4 z; w: f$ d/ _/ ?; V% \1 s
  1.   /* USER CODE BEGIN 2 */* q) _4 [8 E. J" q
  2.   HAL_ADCEx_Calibration_Start(&hadc1);  // f1系列需要ADC校准,f4不需要$ [; e( j# ?! b: o# j  _
  3.   HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&ADC_DMA_Value,ADC_Channel_MAX); // 启动ADC的DMA转换5 c* T+ [7 H: p: A
  4.   PID_init();7 {; _5 }; X9 M& M/ D+ j) ]. d; v+ P
  5.   /* USER CODE END 2 */
复制代码
2 t1 X/ o2 E) p* u7 s& x: O
(5)while中加上:
5 f8 a, j1 ?' c5 E0 C) @
" V, w3 s$ Y+ x( a) h
  1.       HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, DAC_Value); // 设置DAC数值# _  A& z2 k  q! G" R  h1 t! H
  2.           HAL_DAC_Start(&hdac,DAC_CHANNEL_1);   // 开启DAC输出8 M; R, _. {1 d/ i" R
  3.           ADC_Value = ADC_DMA_Value[0];  Q4 y. S* ^; W( Y* U
  4.           printf("%0.2f\r\n", ADC_Value*3.3/4096);7 U: [% B" n9 C4 g! c
  5.           // 这里设置输出1.65V
    & D/ r! Q+ D2 s; F7 c2 l
  6.           DAC_Value = DAC_Value + PID_realize(2048, ADC_Value);
    % H! }) C  C2 @  l0 E( z
  7.           HAL_Delay(100);
复制代码
. V! }7 u2 Y+ _5 c1 N4 q! e! J8 }, v
(6)用一根杜邦线连接PA0(ADC1_IN0)与PA4(DAC),然后串口连接电脑(我这里利用USB转TLL连接电脑,RX接PA9(USART1_TX),TX接PA10(USART1_RX))
  d% B- C$ W9 C3 P4 g  K
- [6 e9 C8 u0 r$ g# G/ \& {0 z(7)之后就可以完成正常读取,刚打开串口时:
4 W9 O3 [; q' t  S' O( U
0 K6 |( ~5 U6 w2 R2 X& M(8)PID调节稳定后(DAC输出1.65V):" D8 e% z5 k3 C! L2 x
( }& k1 j' C  f

8 Y# p. m$ v: T; z$ M* Z) G总结:

1 I  m# a5 M1 x- r  以上就是利用PID调节控制获取我想要设定的电压值。6 L+ }! T$ S3 R, k/ J. u

& y3 x4 u+ {/ s! a1 c4 a' }* d9 H
8 n* a) S* o# w( x8 t* e5 _( M. z7 M$ E. g9 V
EZJUEHJ_~652ABGM0M8_(BQ.png
%ZTMK}HP1{P]Z0X8@ONCUZI.png
赞 收藏 评论0 发布时间:2022-5-24 10:50

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版