本文将借助STM32CubeMX来配置ADC、DMA、DAC、USART,并利用PID位置式算法实现对输出电压进行AD采集通过PID算法调节DAC,获取到我们想要的电压值。9 O3 O$ J% S, f
讲解的主要知识:
, @: s, j, {& i7 ]1.何为PID以及为何需要PID
$ o+ U3 ~ Q) d$ m2.STM32CubeMX创建; F& k4 \: o$ O2 T" O1 O! p
3.STM32源代码: B. ]: p& h p( b3 o
1、何为PID以及为何需要PID! ?$ k* {- N4 i/ p
以下是PID控制的整体框图,过程描述为:设定一个输出目标,反馈系统传回输出值,如与目标不一致,则存在一个误差,PID根据此误差调整输入值,直至输出达到设定值。
% t* g4 r( D, R) X$ v# E
; t* q$ G2 S4 U; H& `" d4 [1 u/ } 疑问:那我们为什么需要PID呢?比如我想要利用DAC输出一个1.65V电压,直接输出不就可以了吗?) {. X0 j3 u- U3 U/ q; n5 |
+ T! [1 E- |, \6 A! S8 u" D
这里必须要先说下我们的目标,因为我们所有的控制无非就是想输出能够达到我们的设定,即如果我们设定了一个目标电压值,那么我们想要一个什么样的电压值变化呢?1 c, u' s% u$ p
, H8 q. V9 T9 `1 O 比如设定目标电压值为1.65V,目标无非是希望其能够快速而且没有抖动的达到1.65V。
+ ~; g/ ^. m0 {6 R! W. r t' l
( ]- {% s2 O0 a2 J0 r: G( [# M 那这样大家应该就明白,如果只是使用DAC输出一个数字信号,外界出现的误差(例如导线、噪声等原因)都可能影响到最终输出的数值,因此我们需要利用PID调节最终读取到我们想要的值。
; W& I; O) u/ _8 K1 H7 i2 o2 M3 J
% C, [6 ^4 H6 c% j: g, Y3 E6 o; X# L
5 @- ?3 N9 c- h/ u
4 l! x5 g* E2 M& ~; Z: _+ s5 zEg:要求输出1.65V,控制输出1.65V,其实得到的是1.6V,如果控制输出1.68V,那么可能可以得到1.65V,其实就是缩减目标与实际之间的误差。' M( y! N2 z: l; Y+ R% H
2 a2 u+ g! w) g! {5 g: X
2、STM32CubeMX配置:
' z7 ~! j7 K+ {1 D1 F, ^1 F2.1 所用工具:3 c- R3 ^! Q0 o) ?0 W- j4 E1 j
, \+ }" B) ]' t; A0 y% p4 H+ k" g. f芯片:STM32F103RCT65 S6 q& ^7 t8 |% |( f( K& r) {
IDE:MDK-Keil软件+ R1 Q6 c1 Y" g) L
STM32F1xxHAL库) ]* ?/ v& u% s% |9 c
/ [% h* m; Q- ~* y6 C, k5 n) L" d2.2 知识概括:
r+ Q* z' a+ A2 P
% n4 ~4 K$ h5 hSTM32CubeMX创建ADC、DMA、DAC、USART例程0 ?9 Z* n" J6 j
Keil软件程序编写
w' T: ?8 s6 k% B( _# o- P" p2 K. x" E( h0 B( f; b+ U3 l' m
2.3 工程创建3 O5 i/ J* a0 Q$ y3 u6 l
1 M( V0 R0 y2 k# ]* N" ?
1、芯片选择
9 ^/ M8 V' G4 a# S/ R 芯片:STM32F103RCT6(根据自己的板子来进行选择)( c3 P! H. K2 R' E. H6 ?
' n3 ?9 N3 Z) j2 g" ?- w
+ D" I& h3 ?8 t+ b& k. i8 c; X
- Y+ n9 H7 ?. e' e/ `/ l i
2、设置RCC
3 S' ^9 N& a' W/ t; [9 X* l c1 b7 k( v 设置高速外部时钟HSE 选择外部时钟源1 b- A& y8 H( ]5 Z6 V
4 E2 F' f) S* ^% C
% c% J% v5 Z7 o( _7 h- B6 J/ M
% E2 Q; \% n% J5 l
3、设置ADC引脚
, r! |1 v0 P" |- H4 h) F ADC1通道0即PA0,开启连续转换模式,转换周期:55.5Cycles/ }) G9 Y: {0 x; v% L
! d; y5 f" T5 _2 Z: _
% l! R& z4 }- g1 k8 K! \
# Q7 r1 F3 n- B; b' s) S6 h+ Q R
1 \2 V& x. X1 G/ `8 b* q
+ _3 L: }0 G! U- ?( g9 z) I4、ADC利用DMA传输
- d3 _9 O* v# s" U, h 设置DMA传输模式:循环传输(有数据就传输),同时设置高优先级1 n. G2 y, y5 D& q
0 ^4 Q; s" R% d! f" z3 F7 S
8 I/ z+ v$ x- h* ], J* k( i( t7 k) |$ r- o# [, u. l
5、开启DAC输出
; X) W+ T5 m! s0 B- `3 w: ^: Z# K R3 C( K
T6 H! A( V) ~ a( m r9 f, o& j
; v) l k w" i. s) |5 A6、开启USART1
! M/ R% f! G$ @' ?( e 设置异步通信,波特率115200Bits/s
$ Q% t- W8 o: J2 w% F7 S5 W: c! _4 c7 ^! |7 ? T
0 g3 _( }0 a* \% _! n6 b1 M3 R* p, Y) |# E# Y- x* U
7、配置时钟
) m) e9 h B% x) ` F1系列芯片系统时钟为72MHzs
8 C( Y7 K: T7 j: V4 q: s; i% H, g7 M5 [
1 N* x5 y' q5 `
+ B8 \+ i0 w N8、项目创建最后步骤
% G6 N0 \& c6 T4 _+ n+ \1 H
# v M' i' C0 o; H设置项目名称4 C, b5 l2 i' x
设置存储路径
, w" B' }: Y7 H4 p4 |( {选择所用IDE) L9 r: [# b( m: f. u7 f' D2 Y
* |" J) i$ e0 U) o
3 F. J& J# L) I6 u* i$ X# k; h" o' Y R
9、输出文件
9 |5 t/ Q- T2 U! P, B& u ]% I' X5 {& o( Y( O0 C
②处:复制所用文件的.c和.h, q2 V, f* a. H, U- m- v' c4 T
③处:每个功能生产独立的.c和.h文件; ^3 y* \$ m. b1 K* ?' o. e0 n
" g: A! ?# z% R k; ?
% |7 ^. d/ |2 E, G$ ]9 ]% Y" E& ]0 y3 N2 j8 w4 Q
10、创建工程文件7 I0 D/ f j y4 F
点击GENERATE CODE 创建工程% |/ K% D1 m9 P' q
$ C/ \2 t* V2 `3 X
11、配置下载工具 g5 ~+ B) [- p* g$ d, Y
这里我们需要勾选上下载后直接运行4 p6 B4 H- p) y; w+ d( C% w) m
3 B$ U/ i- r$ x$ i% `7 F; O
2 T9 j0 R+ p/ d6 N; r/ P9 B4 T# |# Y* j# s: T8 A
3、STM32源代码:
7 r4 ]( ~4 U: X3 f* ~ 首先编写pid.c与pid.h文件代码,这里编写的是pid位置式算法。
0 L# E, ~+ j2 B- E(1)pid.h! K; B- t2 |; ~( h/ u2 T: L
! L/ i1 c6 q' }) D$ C0 E- #ifndef __PID_H__# R. f! O! `" c4 H' [
- #define __PID_H__
- \7 [/ |7 D, p8 m - #include "main.h"; F$ B5 I" G5 c0 |' J2 i
8 N' J! R' R& P/ L4 @- typedef struct
( z0 }/ `+ L% t4 I: d& H. [ - {
2 }9 r* X& ]7 L' e+ k1 `7 P - float SetVoltage; //定义设定值
( ?, [! h* u* o5 z, I/ B" N7 q% F - float ActualVoltage; //定义实际值. n5 s# [8 ?* x, u; P2 y2 p5 i/ H
- float err; //定义偏差值9 Y1 M! J! q3 A/ y: Q$ {3 v: ^; h
- float err_last; //定义上一个偏差值! n$ ]* X Z$ v1 n$ r
- float Kp,Ki,Kd; //定义比例、积分、微分系数0 \3 u# w( ~( B4 q4 ]! a2 A" u
- float result; //pid计算结果
6 L+ P. H* V' S3 N7 y. C# q - float voltage; //定义电压值(控制执行器的变量)0-5v右转 5-10v左转
$ I8 U, n3 f2 P. C# I' {( o5 n1 J - float integral; //定义积分值
' p' I2 j8 l# q* | M - }pid_p;
( i: A5 d$ I& M+ v
& m1 j: i/ n% @; G- void PID_init( void);
6 ?$ d; ~+ A; {9 L. n2 l - float PID_realize( float v, float v_r);7 ]( V9 d7 Z$ r+ \/ u% f( O0 `
- 8 y0 f3 ]$ s! v0 r4 x& D+ Y& o
- #endif
复制代码 8 C. O3 t" [# P+ v' E- c; W
9 w _0 e- e1 ]- E
(2)pid.c
( J% F- d4 B( C2 d4 v; c+ [: K/ |3 q0 K2 @' R2 G
- #include "pid.h"1 q- Y, {: o8 K- F% ^
- #include "stdio.h") G/ B6 B" ~5 }8 b& P1 ~, T( ~
/ f! i9 g. T/ y9 q# @/ ~2 K- pid_p pid;2 z7 d* B8 r6 p5 U( T
! G7 i* k; I& }0 K9 y4 r" w- //pid位置式
5 K! X$ W; c2 {1 ~1 f - void PID_init()
' T0 [8 y* P, j; I* |- p1 U1 H8 a - {
" u5 q8 }5 J$ r7 {) Y, w. A0 n - printf("PID_init begin \n");( s7 P; a# d, |4 E6 E
- pid.SetVoltage= 0.0; // 设定的预期电压值9 h4 c+ V; ^" |5 i( A+ u
- pid.ActualVoltage= 0.0; // adc实际电压值" R, m% w( t& ?
- pid.err= 0.0; // 当前次实际与理想的偏差0 z+ q% `* H3 ~9 N, b" R
- pid.err_last=0.0; // 上一次的偏差
( p- ^3 b0 i( b) r7 |" i8 o - pid.voltage= 0.0; // 控制电压值, D) ?. T( ?6 \% R5 J
- pid.integral= 0.0; // 积分值
7 o$ C* [- e: `- g, H - pid.Kp= 0.2; // 比例系数% e1 R4 Y- a+ @7 z4 e
- pid.Ki= 0.15; // 积分系数
( m4 [# A* m/ S: [0 u - pid.Kd= 0.2; // 微分系数
9 d9 x1 s. X& Y5 b j1 u. r' Y - printf("PID_init end \n");
; [5 s* _1 X" T# g - }
`0 {8 L$ E- c
% w0 s+ X/ l$ L* U( L, t- float PID_realize( float v, float v_r)
8 ]0 T; K- W+ K- ^ - {- p% x& {& R# d3 s9 g# m5 e
- pid.SetVoltage = v; // 固定电压值传入# _0 z, o# r) N* ~
- pid.ActualVoltage = v_r; // 实际电压传入 = ADC_Value * 3.3f/ 4096
: N0 C5 x9 {. d, Y, C$ N - pid.err = pid.SetVoltage - pid.ActualVoltage; //计算偏差
% [ V. t1 W3 \5 X5 P - pid.integral += pid.err; //积分求和, |: x! Y9 i0 ]$ m; q
- pid.result = pid.Kp * pid.err + pid.Ki * pid.integral + pid.Kd * ( pid.err - pid.err_last);//位置式公式
* Q+ a0 T4 o6 h" b: ]0 p+ h2 [1 Q - pid.err_last = pid.err; //留住上一次误差
! @ D6 I! J- o; D4 f \6 O - return pid.result;
7 u' q" b& S7 Q/ k) v! x- U+ O/ p - }
复制代码
. J7 N9 y$ h" }, @0 \ w) y(3)在main.c加上:
* d8 o% R8 r- B2 O
7 l; j, y" w1 T2 @- ?& G. C- /* USER CODE BEGIN Includes */
) W! [5 q4 D. I# | - #include "stdio.h"7 F) p- V% D b3 C1 d
- #include "pid.h"
8 a' ~2 Y3 v1 i" ~8 D4 m5 r" N - /* USER CODE END Includes */
复制代码- /* USER CODE BEGIN PD */
5 P& I. o0 ]$ A; l8 { - #define ADC_Channel_MAX 2
) l: l1 T* ?9 w& N2 Y3 S - /* USER CODE END PD */
复制代码- /* USER CODE BEGIN PV */* D8 h2 r8 {4 V% T$ ]
- uint16_t ADC_DMA_Value[ADC_Channel_MAX]; // DMA得到ADC的值
3 G' Q# k3 \4 @6 @5 N - uint16_t ADC_Value = 0;
, d6 m! z8 @7 }! y; X+ D5 ] - uint16_t DAC_Value = 100;" J5 f2 k! L6 @0 J7 o9 i
- /* USER CODE END PV */
复制代码- /* USER CODE BEGIN 0 */
' C1 ~6 Z" ~% o. Z* L* K, U - /**
3 n( p% ]5 n' O - * 函数功能: 重定向c库函数printf到DEBUG_USARTx
( r( Y3 {+ [+ _ g; M - * 输入参数: 无
1 k& t* q- h4 L - * 返 回 值: 无
( {" N7 _0 O3 o8 Q7 a* e - * 说 明:无
$ ^9 D$ [, B* K2 w) [. p' ~ - */
5 e4 f0 t2 _ L) f$ ] H - int fputc(int ch, FILE *f)
9 _$ a% I- ^1 y ^1 v( `% K - {$ `+ S; N! x( _+ J5 _
- HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);; g& N2 X; N( f! y/ T9 |% ^
- return ch;$ E' R: N# O" A r
- }. J! G1 {! L( e/ T! s+ t
- # p& ]( }% |8 P5 M( x
- /**
F& w! N) l7 x4 z7 I p - * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx7 m; y/ r$ v/ @) j& ?
- * 输入参数: 无
5 E( [( u$ U$ a& O2 j+ }1 o - * 返 回 值: 无& e( o2 S1 ]7 |5 ~5 k0 l
- * 说 明:无2 f1 r8 b B( t' K
- */$ h3 Q! B0 V$ t2 k( M/ Q
- int fgetc(FILE *f)+ H0 u. k& T) z) C. z% Q2 @
- {
: ]6 Y( ~8 b1 G$ t/ B - uint8_t ch = 0;
* }9 X% B0 w1 S, O" Z - HAL_UART_Receive(&huart1, &ch, 1, 0xffff);5 j3 o6 m4 Q7 u% Y6 [9 q3 _! O
- return ch;) E+ A9 r3 y! G
- }: L4 M5 p8 l5 {0 L" W
- /* USER CODE END 0 */
复制代码
- a# {5 k- O5 q O. c" j(4)在ADC初始化之后加上AD校准函数:9 ~$ O1 a7 f1 v& n3 u1 Y' E
" J' ^% @, K# k+ g) w; H6 o O
- /* USER CODE BEGIN 2 */3 o8 E7 C* Z( X- ]
- HAL_ADCEx_Calibration_Start(&hadc1); // f1系列需要ADC校准,f4不需要8 f/ Y J1 h2 j5 N+ c5 u% U# @' n% \
- HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&ADC_DMA_Value,ADC_Channel_MAX); // 启动ADC的DMA转换
# z0 c! Z* [* o0 `) t0 l4 x" f - PID_init();
) V2 F6 e6 t7 s- Z1 S - /* USER CODE END 2 */
复制代码 3 f+ X; Z, O) v8 R P; ~1 B
(5)while中加上:
4 [/ T8 B# S' `/ Z5 Q: Y( q; ?* }: F% n! C3 @4 G/ U) x c+ f6 T3 P
- HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, DAC_Value); // 设置DAC数值
g( b) c6 r( _9 ?: z - HAL_DAC_Start(&hdac,DAC_CHANNEL_1); // 开启DAC输出$ ]8 U) n/ H" F9 A+ _
- ADC_Value = ADC_DMA_Value[0];: L; m% y# j( b9 s0 e$ K, z
- printf("%0.2f\r\n", ADC_Value*3.3/4096);: e, Q' b! ?) d
- // 这里设置输出1.65V
0 Y8 b) l2 p$ `) l3 ] - DAC_Value = DAC_Value + PID_realize(2048, ADC_Value);4 a/ P+ e; c+ l" n, ]" K3 H) q( l
- HAL_Delay(100);
复制代码
( \" E* C8 Z- \8 p4 n3 [7 T(6)用一根杜邦线连接PA0(ADC1_IN0)与PA4(DAC),然后串口连接电脑(我这里利用USB转TLL连接电脑,RX接PA9(USART1_TX),TX接PA10(USART1_RX))5 ^/ z3 Q: J% z+ |3 d/ R
$ Y' r f, T: _
(7)之后就可以完成正常读取,刚打开串口时:9 o" `% G# O$ O6 n& g; }
- r* D- n9 c3 _( V6 O
(8)PID调节稳定后(DAC输出1.65V):& I) A2 ~. K5 w1 r
. q4 N7 |, s1 x1 s% J% i
+ o( T& w! z( q
总结:" u- y" Y; B8 v+ T
以上就是利用PID调节控制获取我想要设定的电压值。
. O) B- X7 B" d. }! ]9 I3 M
& ?3 q: U" |0 `: Z8 O- z" g; f" @1 e$ {0 ~9 I6 E
6 ^8 X) x1 b4 ^' \1 i
|