本文将借助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
* 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
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
; 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
8 p3 p s; P1 H- Z& v: q6 ^2 S
2 v8 s7 s4 |* B" y# I ~
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
k" S4 M( o* V |
6 H& t7 H& H) d2 {$ Y, Z5、开启DAC输出* _ w V& i1 E
j. A" l( @* G8 Z5 q
/ 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
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
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
/ `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 }
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
/ 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
- #ifndef __PID_H__1 d. u) y, T2 t4 I' i( _, D
- #define __PID_H__+ }6 {: ]: V' B: z- q
- #include "main.h"2 }) K# Y: A3 ?& Y) ~- i" N3 H5 U
- ( m8 g; P! S: I/ {
- typedef struct- V% X. K* o5 q
- {5 A4 w. |$ R" d9 d2 e7 _* `1 u. m
- float SetVoltage; //定义设定值6 f2 V& a! [' U% v
- float ActualVoltage; //定义实际值
' j8 m$ @5 @2 d' G9 e - float err; //定义偏差值% @# K- |' }$ R7 Z" e, m2 D/ s
- float err_last; //定义上一个偏差值% _+ X# q# K0 e1 f- V0 W1 A
- float Kp,Ki,Kd; //定义比例、积分、微分系数 ]2 L- L$ s/ U; d7 Q8 J$ U1 H
- float result; //pid计算结果
! x9 l9 Y* ^8 b) {5 E$ V8 A+ f2 R. t. S - float voltage; //定义电压值(控制执行器的变量)0-5v右转 5-10v左转" }0 |' }: n2 z$ s$ I4 P/ S( ~; I$ m
- float integral; //定义积分值
7 E4 h0 Q6 M2 O) K: G7 y - }pid_p; ; g/ ?- c* E7 G
# f2 b! R9 S: G1 f4 s' n- A- void PID_init( void);6 w7 R9 S- `5 b1 Y. D* z$ m$ V
- float PID_realize( float v, float v_r);
: }5 i0 ?3 e* x7 Z - ' y) J, m* |7 Y4 h! u& ]
- #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
- #include "pid.h"6 f3 H4 G6 L8 N/ t& \* t
- #include "stdio.h"
. e3 F. Y# l* v" R4 m. Q- E" f0 k
E% l, o n: |' Z/ K- ~$ N- pid_p pid;& \2 l% g t" @
- 1 c; m1 C. a0 a, p' q) c3 W/ ^
- //pid位置式
& `8 c' d1 M) p! i8 o: | - void PID_init()4 O8 n$ v* l- Z; L$ r
- {
( {3 _( \+ `2 E& o8 F& t! o0 o - printf("PID_init begin \n");
4 @- {. Y7 c( f8 K8 [- \+ I' [. e - pid.SetVoltage= 0.0; // 设定的预期电压值; G0 V2 T) Z3 J
- pid.ActualVoltage= 0.0; // adc实际电压值
( p8 N5 d. A+ W9 o/ o/ j4 Y8 Q: C - pid.err= 0.0; // 当前次实际与理想的偏差
+ `' z* A; @3 | - pid.err_last=0.0; // 上一次的偏差
1 D0 s+ E7 E( c5 a3 s! ` - pid.voltage= 0.0; // 控制电压值% \/ V; b. c* P$ c
- pid.integral= 0.0; // 积分值7 }, Y& }9 Y4 U; y1 n0 }
- pid.Kp= 0.2; // 比例系数
5 i+ A' J% `# Z' N( o& A h - pid.Ki= 0.15; // 积分系数: D0 m( |1 V* A/ o
- pid.Kd= 0.2; // 微分系数
! y- I. I1 @ s! D- O6 [- W k, q - printf("PID_init end \n");' k. r; [% Z1 ]( @; p L) x8 }
- }
7 F* ^. A% T+ f5 H+ x- \( s- M* W! V
/ [- e5 ]4 R2 p: a# z. T* W" w' ^- float PID_realize( float v, float v_r)6 K. N4 L( _4 Q9 `9 N- ?& t
- {
! m5 m+ N" x" ? - pid.SetVoltage = v; // 固定电压值传入
5 [% o8 S I% b4 ]; ~) A- b/ x - pid.ActualVoltage = v_r; // 实际电压传入 = ADC_Value * 3.3f/ 40964 U6 |$ V7 V9 Z) ?
- pid.err = pid.SetVoltage - pid.ActualVoltage; //计算偏差
" I- H1 F7 @4 Z/ U: H/ }4 U7 i1 y - pid.integral += pid.err; //积分求和
( Q/ E; p7 u! e o# e/ g* n/ E( E - pid.result = pid.Kp * pid.err + pid.Ki * pid.integral + pid.Kd * ( pid.err - pid.err_last);//位置式公式
. }& |9 L" b" B& r - pid.err_last = pid.err; //留住上一次误差- A. |2 ~% F5 @1 i7 Y
- return pid.result;1 {6 X. A. _& l- L$ y) h' l
- }
复制代码
, 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- /* USER CODE BEGIN Includes */% Q- h% }3 S n
- #include "stdio.h". K8 u2 G; p& t* G3 \# r; E
- #include "pid.h"* N. _; M/ E! E" E; O
- /* USER CODE END Includes */
复制代码- /* USER CODE BEGIN PD */% \! R# D. J2 S* r T q+ F5 q) m
- #define ADC_Channel_MAX 2# A: R4 x, O* }8 d% a9 n* T
- /* USER CODE END PD */
复制代码- /* USER CODE BEGIN PV */
7 g5 |, u( o( [' }. ~ - uint16_t ADC_DMA_Value[ADC_Channel_MAX]; // DMA得到ADC的值
6 Q: r' u1 B3 I X% C1 Z - uint16_t ADC_Value = 0;
5 r# V0 b! o& U+ P6 r" j - uint16_t DAC_Value = 100;
3 K" W, g. H+ A3 R/ T - /* USER CODE END PV */
复制代码- /* USER CODE BEGIN 0 */
/ s& k( p4 M; S$ W) Z) O - /**8 r& Q/ n, [- p, X! m: K
- * 函数功能: 重定向c库函数printf到DEBUG_USARTx
, z Q' Y9 M* W! t7 H1 n - * 输入参数: 无
' w8 L& S) f( N7 f- W; t" y& q" b# k" J - * 返 回 值: 无
" N$ m/ N$ b5 i6 s ]6 E - * 说 明:无' k. l+ {, f5 D M% [
- */) [4 Z h8 F! W
- int fputc(int ch, FILE *f)3 A: G0 \8 o! q: x
- {! }" \* w+ y5 p6 R
- HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);) ]" Z9 T8 O/ |: M+ x
- return ch;! `3 W/ I% [' k2 J: o# h
- }4 W' a1 j" [ }' v- I. J, b
0 {& k( U* |; E# D, X, r$ K- /**
9 v! V( Y; k, u h% }$ A - * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx) K# D/ X. }3 i6 e6 i
- * 输入参数: 无# p* i7 B X }7 {8 F% p: j8 e
- * 返 回 值: 无
; M" q( P9 c6 a6 O% b, E1 V% W) B - * 说 明:无
# g+ q/ y t+ r: O+ a2 t f0 d2 D7 ] - */
/ d. q6 c, V; F0 _. G - int fgetc(FILE *f): b0 d) R; Q, ?2 ^ ~
- {4 h& N7 X+ S, B
- uint8_t ch = 0;6 z2 H8 k# s9 ~% U1 Q& G1 u( q! ^
- HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
5 Q, {2 m# Z. Q6 w. O6 c3 J% f W y - return ch;
- q# q' [% G1 n+ d( s0 Y - }5 R. M1 A5 G5 @& V
- /* 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- /* USER CODE BEGIN 2 */* q) _4 [8 E. J" q
- HAL_ADCEx_Calibration_Start(&hadc1); // f1系列需要ADC校准,f4不需要$ [; e( j# ?! b: o# j _
- HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&ADC_DMA_Value,ADC_Channel_MAX); // 启动ADC的DMA转换5 c* T+ [7 H: p: A
- PID_init();7 {; _5 }; X9 M& M/ D+ j) ]. d; v+ P
- /* 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- HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, DAC_Value); // 设置DAC数值# _ A& z2 k q! G" R h1 t! H
- HAL_DAC_Start(&hdac,DAC_CHANNEL_1); // 开启DAC输出8 M; R, _. {1 d/ i" R
- ADC_Value = ADC_DMA_Value[0]; Q4 y. S* ^; W( Y* U
- printf("%0.2f\r\n", ADC_Value*3.3/4096);7 U: [% B" n9 C4 g! c
- // 这里设置输出1.65V
& D/ r! Q+ D2 s; F7 c2 l - DAC_Value = DAC_Value + PID_realize(2048, ADC_Value);
% H! }) C C2 @ l0 E( z - 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
|