本文将借助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
% 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
+ `; z9 D; O7 z3 p$ D/ D% N/ B; J2 y
# ]* ?: u) M, D4 ~6 R# r1 T2 d2、设置RCC2 t$ l) B* f! W, C6 T2 A
设置高速外部时钟HSE 选择外部时钟源2 C' j8 Y' p4 }, j6 _3 W
9 U2 \8 ]( [* u7 O
: 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
" d3 X. u* E- O. F# ^. S3 Z
4 P) S3 j4 t, O0 s
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
" {' 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
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
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
/ 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
- 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
, 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
& 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- #ifndef __PID_H__7 E8 q3 n6 k: Q3 }6 _
- #define __PID_H__0 \4 k2 W1 n9 R9 ^
- #include "main.h"4 K( L* H8 e2 `
- : \7 G1 U6 W, L6 B
- typedef struct
) X0 D$ T( Z0 _- T - {4 f8 I* \, T, P3 Z8 D7 s; K' G* a
- float SetVoltage; //定义设定值. c2 [8 `! ?& o
- float ActualVoltage; //定义实际值/ b' v# m) r t- ?9 n
- float err; //定义偏差值
1 v$ u% p+ I: [4 A - float err_last; //定义上一个偏差值7 h) R" u3 {. D0 ~
- float Kp,Ki,Kd; //定义比例、积分、微分系数
; A/ v- G! e- ?+ H7 i - float result; //pid计算结果
9 A3 O; c( D& i% _8 m1 b" _. F+ H# C' e - float voltage; //定义电压值(控制执行器的变量)0-5v右转 5-10v左转) g5 \( V. ?9 J7 ^- |$ d' k
- float integral; //定义积分值
/ ~. Q, K' W) H* G2 Q# T+ B - }pid_p;
' Q) G8 _: F5 j
# W8 s- \6 n* a8 E5 m, f1 @- void PID_init( void);
+ `: ~: T8 V* y' J; ]6 R - float PID_realize( float v, float v_r);
! [/ r; a" r+ n7 K - ! Q" ]- n; @, d P7 U* E# P( E. ^
- #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- #include "pid.h"
8 h( T% S! g$ U+ w3 T) P - #include "stdio.h"
: L) T; Q9 w, R; W- G$ v1 J
# C: `! [- M7 W& S+ n- pid_p pid;9 m& W3 ~& f0 h
- 3 n$ S/ o8 W4 U3 o" M
- //pid位置式
, y) ^3 T( T5 d: q/ L' f$ J$ B - void PID_init()# r) f. C% Q7 W6 h
- {
) }+ {) [& ]( n' t - printf("PID_init begin \n");
9 X+ b' c8 O6 m - pid.SetVoltage= 0.0; // 设定的预期电压值) R2 e6 N1 G+ ] I* r. D
- pid.ActualVoltage= 0.0; // adc实际电压值
U3 z+ S# A5 ]2 i5 S2 @. p - pid.err= 0.0; // 当前次实际与理想的偏差5 D8 e+ Q8 S) W: f" }
- pid.err_last=0.0; // 上一次的偏差7 N7 x2 S4 C- F- U* ~
- pid.voltage= 0.0; // 控制电压值# u) E8 p W9 l) U) h
- pid.integral= 0.0; // 积分值
* A: G# U% b7 A R* T - pid.Kp= 0.2; // 比例系数, D {5 G$ v. v4 ~# t X/ {1 H
- pid.Ki= 0.15; // 积分系数
7 h; c( H8 N' L - pid.Kd= 0.2; // 微分系数$ }& C! D+ F1 y, ^; z5 G; S$ P6 o
- printf("PID_init end \n");9 G' s5 M. m& T6 F8 {8 f# ~/ ?% i
- }$ n$ o" @1 D% \+ {! ~
- 0 o$ s8 [% C3 P5 u! B! s1 X
- float PID_realize( float v, float v_r)+ V% M& u' {8 t
- {
; {. h0 e o! o) ]: c5 b - pid.SetVoltage = v; // 固定电压值传入
$ T/ z9 [# R) m ~+ R5 I \+ H - pid.ActualVoltage = v_r; // 实际电压传入 = ADC_Value * 3.3f/ 4096+ z( o1 y; `+ I# l# D& F+ Q: O/ X
- pid.err = pid.SetVoltage - pid.ActualVoltage; //计算偏差
* h0 Y/ v+ y) S/ {& | - pid.integral += pid.err; //积分求和
2 a; L3 U L$ J; _. i. e* I - 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
- pid.err_last = pid.err; //留住上一次误差! G9 C0 \; f# ~' l. T0 m
- return pid.result;
& b' c; _+ \3 D* ~4 a- e, H c - }
复制代码
; 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
- /* USER CODE BEGIN Includes */7 l, _ ^6 q! L5 v! h
- #include "stdio.h"
2 r. I1 L) W& ~* F - #include "pid.h"
3 W: Y p1 N7 }! I9 ^% O( n. M - /* USER CODE END Includes */
复制代码- /* USER CODE BEGIN PD */. s* ]2 \" z( V9 B" C' r
- #define ADC_Channel_MAX 2
/ O" m! z5 F( y3 K2 n - /* USER CODE END PD */
复制代码- /* USER CODE BEGIN PV */- \( x7 V# s% J' B6 z
- uint16_t ADC_DMA_Value[ADC_Channel_MAX]; // DMA得到ADC的值
2 R/ F4 E" F4 c# Z, n/ ` - uint16_t ADC_Value = 0;
2 r; Q$ T# h2 O) C - uint16_t DAC_Value = 100;' N3 h5 U* y+ S# a3 \
- /* USER CODE END PV */
复制代码- /* USER CODE BEGIN 0 */8 z* P, E% `/ [2 @0 b4 j- W
- /**8 E4 _6 o) D' Z' a7 ?' M/ U' G
- * 函数功能: 重定向c库函数printf到DEBUG_USARTx1 q* h y2 i8 l) W/ S7 N/ H5 }8 V
- * 输入参数: 无- k3 Q7 T0 O0 O8 o' _
- * 返 回 值: 无
& v4 N6 D/ U1 W: [ - * 说 明:无" E% g6 R# x2 j
- */
* I, _" | B; w; K% A8 p - int fputc(int ch, FILE *f)* X7 j4 P# ~% H3 C: k
- {
$ ]" e+ P) _8 Z' F4 W9 y2 `7 A& S% ?5 u - HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
/ U" ?+ I: o2 D - return ch;
3 }8 \) W6 T% G+ n. F - }7 V3 D' Y: ~; O8 w& K, ]6 M' D% B
8 ?$ i( h4 k9 j1 t8 {8 z- /**0 R% ~! G5 c% C
- * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx
3 X6 ~2 |/ F% l0 X! r9 S3 A - * 输入参数: 无
1 ?& _* H, l$ e" ? - * 返 回 值: 无
) l4 w h) M, f, N) u% v, M - * 说 明:无- H v4 z7 r& A/ A8 x4 j( ~
- */& H7 U- i/ z' v @# V
- int fgetc(FILE *f)
6 v+ i3 H: ] ~& ?/ }, T - {' w; }6 s0 e: ]. g2 ~' E9 b* N
- uint8_t ch = 0;1 `, r0 T3 ^& Q
- HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
+ B# \. u8 q& Z K1 x - return ch;6 U; o' b+ X7 \2 k
- }7 m7 G, ^: {- ?5 a3 ~
- /* 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- /* USER CODE BEGIN 2 */* z$ H+ `: S. V! D: F- C$ h! |! l' u4 n: R
- HAL_ADCEx_Calibration_Start(&hadc1); // f1系列需要ADC校准,f4不需要. w4 L Y$ q% R, [8 v- G
- HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&ADC_DMA_Value,ADC_Channel_MAX); // 启动ADC的DMA转换! u. S& M1 X# |1 M% \
- PID_init();2 G7 M$ s: _# I3 v
- /* 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
- HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, DAC_Value); // 设置DAC数值! q$ `: ]. X* ]& W4 R X7 [
- HAL_DAC_Start(&hdac,DAC_CHANNEL_1); // 开启DAC输出
1 A- U X9 h7 C4 t7 g$ Y - ADC_Value = ADC_DMA_Value[0];, M. [2 ] N: ]+ L! X$ O
- printf("%0.2f\r\n", ADC_Value*3.3/4096);/ |5 C' V: f: n
- // 这里设置输出1.65V
; g. I) H: Y) A: K, ?7 s( [ - DAC_Value = DAC_Value + PID_realize(2048, ADC_Value);
6 V M8 M8 U# K; `0 A, N: c - 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 |