概述
# e; I& P9 Z% `; A: i小车外形:2 h# f) z } I4 R7 R3 W; C
2 n* u# Y& }' m
+ f5 `* @2 E! z1 H" ~& i
) [$ ^# H- M+ s4 X3 n( l& w功能简介
. e/ \1 y$ n' N" u# O# x, Y- F# a利用摄像头识别前车尾部的AprilTag,得到前车位置,传回stm32主控板处理,使两车在行驶时保持恒定距离,实现自动跟车。
5 `. Y( g/ B+ D7 ^7 v3 Y* W
4 k M! y6 ]- O& y1 Z4 B, ], { f. R" @7 ?3 ?- A- J: v! D3 l4 r/ F
" R0 ]% o: W8 v; K9 u8 I! L s$ o$ p8 w' y6 V% `1 [
openMV4摄像头% z) g4 Q+ M' u+ a8 A
1.1 Apriltag识别与串口传输7 n1 V0 K" ~ f4 b3 }' Q) Y
Q* u. j7 T$ _1 ?$ N1 T
5 N* [. N' g" e" o' xAprilTag是一个视觉基准库,在AR,机器人,相机校准领域广泛使用。通过特定的标志(与二维码相似,但是降低了复杂度以满足实时性要求),可以快速地检测标志,并计算相对位置。
) `" ]) G9 L0 p1 o
1 R3 ^0 b$ Y$ |) N/ \5 Q* E3 D0 w: g; X; u5 C4 A' k6 [
Apriltag示例: h( x2 N" Y/ h4 ^! Y
6 F& V. F o3 A) E3 u x
通过识别Apriltag,可以得到x,y,z三个方向的距离以及偏移角度。这里只需要三维的距离即可,通过串口传回stm32.
5 j- w7 M, a3 i$ a- <font face="微软雅黑" size="3">
( U1 {: Y* t& C/ A) }" r - import sensor, image, time, math,pyb
, C2 y2 ?; M8 l! ~; l7 L - from pyb import UART) N( |& Q9 @$ c- |4 H
?: A6 W" c! ?" L! C- sensor.reset()6 S S. q. Y; j/ G6 f
- sensor.set_pixformat(sensor.RGB565)
# ]7 `+ J% ^- \+ t; ], K2 ` l" M - sensor.set_framesize(sensor.QQVGA) # we run out of memory if the resolution is much bigger...
$ |% Z# y$ b3 m - sensor.skip_frames(time = 2000)1 k! ^6 v3 Y7 _' Y# L0 V
- sensor.set_auto_gain(False) # must turn this off to prevent image washout...
& I; C2 M' ]3 U/ ]7 D - sensor.set_auto_whitebal(False) # must turn this off to prevent image washout...
/ d R( V8 v( B/ m& C7 |& u - clock = time.clock()0 \+ o* `9 y* E" {. V. ~ K8 t
- uart = UART(3, 115200)#串口波特率6 p0 e0 }# ]0 x) y( L+ j
~. j' t& |: u4 O4 K+ k! f1 d: p- f_x = (2.8 / 3.984) * 160 # find_apriltags defaults to this if not set' ^8 w7 V- I l/ N3 K
- f_y = (2.8 / 2.952) * 120 # find_apriltags defaults to this if not set! F6 ?7 O! a. |. Y+ V2 s k' J5 I
- c_x = 160 * 0.5 # find_apriltags defaults to this if not set (the image.w * 0.5)
- ^; L, Q/ U7 Z7 Q: u - c_y = 120 * 0.5 # find_apriltags defaults to this if not set (the image.h * 0.5)
7 m0 G6 E+ A6 r* `2 m, \- m9 t - 3 \/ v8 M0 W+ F- X( Z, U: x! }+ u
- def degrees(radians):
: A9 [, v- i5 M1 H - return (180 * radians) / math.pi+ g7 S- u' F" ? x5 q9 o7 }
1 u* S( i: a6 t- Y- while(True): y y) S, _ B2 \# O! x
- clock.tick()
. g0 R8 ~+ u1 ] - img = sensor.snapshot()
3 j& S/ `1 n5 c7 e! t - for tag in img.find_apriltags(fx=f_x, fy=f_y, cx=c_x, cy=c_y): # defaults to TAG36H114 r; N$ [. u, I
- img.draw_rectangle(tag.rect(), color = (255, 0, 0))) L, q* \3 |7 _8 z5 \
- img.draw_cross(tag.cx(), tag.cy(), color = (0, 255, 0))& b4 t$ y7 h1 s% p' i8 H! N- U% \
- print_args = (tag.x_translation(), tag.y_translation(), tag.z_translation())
6 S0 Z! t% ?8 h8 O1 b - #degrees(tag.x_rotation()), degrees(tag.y_rotation()), degrees(tag.z_rotation()))
* F* W' a6 o5 c* g - # Translation units are unknown. Rotation units are in degrees.
1 S- r& o b# p( w; B3 h - # print("Tx %f, Ty %f, Tz %f" % print_args)9 M0 z8 W5 P( ^+ p' ?
- uart.write("A%.2f,B%.2f,C%.2f," % print_args+'\r\n')#设置特定格式,以便于stm32分割取得数据
+ u/ g. r. u3 z! Y% o2 M# J7 o - #pyb.delay(500)
h1 {' } K1 ]( V, Y - # print(clock.fps())</font>
复制代码 STM32主控板(型号为F407)6 J7 I. Z: X# }+ h( c9 {1 K
) [% C0 Q8 D) ^' d2 i, ?0 b- _0 G; X4 E9 s! F* X+ C, c
2.1 时钟与中断配置; }* }) {$ r1 x6 t' V( }
5 C' s3 s; p5 {, S, S g( d& O& k- {8 S( Y! s, A
附上stm32时钟示意图:
, _+ F, _1 Y2 n8 Y4 V1 l
" V3 |! h5 w( s; p2 F+ S定时器示意图:
/ J) m+ A' s& N& V1 `8 J
5 A% j% T; D' {) s8 S( V2 {
定时器分配:
4 I) _) P& E4 d5 ?) P/ U
0 V5 X) P" v: o- ~8 V8 M3 d; M# k
所有时钟初始化的函数:(每个函数的详细内容在后面)
3 h e$ `% g# u* j5 l0 G+ J- J/ e- <font face="微软雅黑" size="3">3 d9 Q% |8 S+ c0 q$ d' [) Y R; f0 \
- TIM8_PWM_Init(400-1,20-1); //用于控制电机,168M/20=8.4Mhz的计数频率,重装载值400,所以PWM频率为 8.4M/400=21Khz.
8 `+ r c. ]1 k; N# S - TIM3_PWM_Init(200-1,8400-1);//用于控制舵机,50HZ1 P h% {; B. s) y4 i0 e
- TIM2_Int_Init(400-1,20-1);//定时中断,21KHZ
% t8 C8 c+ ?5 f1 h' k - TIM7_Int_Init(500-1,8400-1);//用于编码器计数,20HZ,50ms中断一次. t9 o. W. ?! U3 }, {! M
- uart_init(115200); //初始化串口1波特率为115200
4 V6 }" n, n0 [+ w! r: H - Encoder_Init_TIM4();//编码器接口初始化</font>
复制代码 2.2 串口收发与数据处理8 T" O* K, i' ]; O) p
; y# s1 `/ O, _. j! w8 A. N$ Y, J* [. c, n6 P1 Q8 M" M
串口中断:USART1,USART24 e# ?; Q6 y. }7 U
串口初始化函数(以USART1为例):7 L9 m1 c( A! l( t; [ m
- <font face="微软雅黑" size="3">4 q3 [7 D+ ~: k+ t& @
- void uart_init(u32 bound){* ?+ h2 e' \0 B9 q+ D
- //GPIO端口设置' I3 L# Q% U* r: W4 N& f
- GPIO_InitTypeDef GPIO_InitStructure;& N+ D& M8 b! ~, e: _# X
- USART_InitTypeDef USART_InitStructure;. o. w# \6 @0 _* c( G) D: S# l! m
- NVIC_InitTypeDef NVIC_InitStructure;# _- H: N% t, Y2 l/ E% X
- 9 h" U* w0 @ O2 A
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟( _5 \- o& y) S, `" y) I
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
u; r4 v1 l3 P) y -
8 [6 N# F# c4 S - //串口1对应引脚复用映射9 E' B2 U$ }, c' g
- GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART12 _$ o5 l1 }7 C4 y
- GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1
Y" T, G. ?5 K$ e, c5 Z - 1 O0 C$ ?4 n8 e7 e: w9 t% t
- //USART1端口配置
7 E4 m: n' d1 g2 y' |. ^" S/ E# P - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10$ [. a8 W# X4 n) N0 U( n* i1 p8 e
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
7 U. j7 \$ B0 S+ L# \9 o0 S5 w - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz. Y: o2 N( J; c T
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出0 i6 F/ h0 a0 Y- D
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉( i/ t* j5 ~; v, Q. f& W/ G6 T4 }
- GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA100 |- Y% @& S0 p( D- ?
- & q! }6 q- [0 T6 R: ^! i
- //USART1 初始化设置
) w4 M" H+ G) h4 I7 [! n - USART_InitStructure.USART_BaudRate = bound;//波特率设置
* D/ M: t O- \4 ^' E: ]9 ]+ F0 ? - USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式* b8 \- l) I! Z# d/ L7 h6 V
- USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位# Q: h, W; R% S/ z! ^
- USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
1 T* Q( Q) E4 U' V, h0 Q' B - USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制- Q6 j! y+ C, B1 ?
- USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式. F# f" ]( F5 j* p" Q4 i, g
- USART_Init(USART1, &USART_InitStructure); //初始化串口1
Z( Q4 @3 U0 X7 J7 C0 y - % u2 a4 _- K4 z+ v- M
- USART_Cmd(USART1, ENABLE); //使能串口1 1 z/ p! y( e/ m) Z/ U1 h
- $ g% z. C" q2 |2 j0 a. c
- USART_ClearFlag(USART1, USART_FLAG_TC);
. i+ B& Z j# [6 S) R - . _6 B. l/ i7 ?' L; F5 E
- #if EN_USART1_RX ( X$ j& B) ?5 t/ f
- USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启相关中断
' T, z8 }8 O9 Q' ~9 z% {3 b1 u* F
* n3 N' I, D+ r! Z! s- //Usart1 NVIC 配置5 V( j' n9 J: N4 P" T- S
- NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
* x% J2 j; M8 y& H+ C% N - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级36 O6 g. M, ?3 B5 I7 g* k5 k
- NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3/ |2 U( m, H) s1 [5 n
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能5 z, |$ N* D5 l
- NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、7 W. F9 Z+ o: O4 u( V0 }
- & B8 T! A( _3 {; |4 A
- #endif
?8 u9 q* j3 F* {8 ^8 d) s7 y - * f3 J& U5 c+ g! W7 M
- }</font>
复制代码 串口中断处理函数:) r$ U$ r) K' w7 E9 j/ l6 v# d; T
- <font face="微软雅黑" size="3">
: p; w/ x( D2 n2 f* J - void USART1_IRQHandler(void) //串口1中断服务程序
% @( z7 ~) i' H - {: D. n5 s: q/ j! n5 x& a
- u8 Res;4 U4 j- {0 u0 ?& `0 l: p
- #ifdef OS_TICKS_PER_SEC //如果时钟节拍数定义了,说明要使用ucosII了.) w& c4 A1 e) b. M: K# p! A+ {" {
- OSIntEnter();
; U4 {. [: c% [) Z - #endif
. ~0 L7 A$ X' R - if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)( K! g2 }. U4 @ h2 V
- {/ x% D3 i4 Y; D- j
- Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
9 }4 W% H: s: [! i. d" [ - 8 Z" U' z' `4 Y0 ~
- if((USART_RX_STA&0x8000)==0)//接收未完成
! t7 I2 R/ R' b* { - {) J- e* A9 d4 w. _. d8 s# @
- if(USART_RX_STA&0x4000)//接收到了0x0d/ Z+ ^1 P5 W6 Q% L; }0 c
- {
. b. ^) I* X5 I# x& u A% R4 n - if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始5 l1 |& V: I: y, T. G) V8 |
- else USART_RX_STA|=0x8000; //接收完成了
, b4 Q, X; S: d% r! K6 _3 G - }
; V# W" |# W! \% g% K" A) Z- S - else //还没收到0X0D: x; p- t; |: B' x/ V
- { / B( b7 k! \4 ? U' N. b
- if(Res==0x0d)USART_RX_STA|=0x4000;1 T E0 F" m- Y0 r5 H6 q+ f' X# B
- else
+ d7 }* N# P6 S# w! @ - {9 d2 |* L7 A# C$ V5 M. t9 D
- USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;6 [" a, U, d" O B- S
- USART_RX_STA++;6 ^% d9 z- s2 ]) W6 j7 E
- if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收 8 t( R" P* A) S1 X' k9 @$ R$ y
- }
5 r i3 p2 O; m% t4 y/ G - }! Z" k" ~, [7 m# N
- }
4 j( u0 k6 R4 d - } + i5 v+ z/ x2 U, q# `( O! h$ R
- #ifdef OS_TICKS_PER_SEC //如果时钟节拍数定义了,说明要使用ucosII了.
( W3 G% a3 U2 x8 i5 A( r - OSIntExit(); ( |; ^ ^4 f1 O+ x: W, r; y) g
- #endif: G# X* k& E" q1 S
- }
2 W& S4 N7 ]& O - #endif </font>
复制代码 字符串接收与处理(从openMV接收到的数据):' {$ J) i: A6 I
- <font face="微软雅黑" size="3">/*涉及到的全局变量7 j% l2 J* S! b# o% j1 k
- float data[3];//x,y,z方向的距离,浮点数形式! Q$ k3 Q; K1 a0 g0 e
- unsigned char data_string[3][7];//x,y,z方向的距离,字符串形式. V! A1 ^ ~( z; V. W3 \2 O4 u
- */( N+ o' f+ b- w+ D* a
- if(USART_RX_STA&0x8000)* Z- h) I$ B! P+ ^8 q1 D# z
- { : z7 {8 ~2 I9 k: k" N
- //清空字符串 b+ O& ]! I! N6 t( P
- for(i=0;i<2;i++)
% } u3 H; \2 y, D - {; Z5 ^7 S2 _! w$ C" j, `, c
- for(j=0;j<6;j++)
! h- g" l) Z; ~( r& E% O/ Z8 T+ {+ W - {! [$ h/ l: A. i3 Q4 |0 w- K5 F
- data_string[i][j]=' ';
5 ^3 I# X2 R9 x" P( x - }
* r( `, V- n+ R5 P- c$ ^1 E - }; K, O: z0 X5 u
- len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
8 `! q, |$ D7 v$ `+ m+ V D3 t - for(t=0,j=0;t<len;t++)% {. o: I; V; p, r1 T
- {: i/ {6 ]- m3 o% r& Q0 N
- j=0;0 p# a8 ?3 Y: L; k. F7 P
- if(USART_RX_BUF[t]=='A')
, [6 R% h' E! u6 g6 ^ - {
: E" U4 T E5 r4 e7 R2 u - t++;//去掉首字母3 u4 m% u7 u( h' I% z' S
- while(USART_RX_BUF[t]!=',')3 D* p. Z8 h- o
- {
. k5 P( V% N1 J4 y- k - data_string[0][j]=USART_RX_BUF[t];
5 \9 q* E& v) [5 k' \6 v - t++;
' e( v$ U) n: c. W - j++;
% m+ X0 O3 y: B! j; P+ U1 L: J - }" R/ b9 Q# H; j3 K4 a3 k0 x. z) V
- }
) p- w1 q* x0 a, c3 r - if(USART_RX_BUF[t]=='B')0 @% Z, ]* w+ g+ N4 f! ?: h
- {1 \% b0 a# }9 {5 |, k+ r
- t++;//去掉首字母. Z0 x7 ]: ~2 a4 h1 E) Q. [
- while(USART_RX_BUF[t]!=',')
: f# t4 Y# J7 s - {! M7 L8 E7 y* i7 `9 I# |
- data_string[1][j]=USART_RX_BUF[t];
9 _& J$ r6 Q: D* f( K, j- N5 j1 z - t++;
, F( C! x# n" F7 |( m - j++;
' v' x5 X0 g3 `! Q V2 C - }
) A3 D+ j. T3 R$ ]7 Y: B - }3 w4 N/ A7 L) Y7 I/ C" ^7 @4 s
- if(USART_RX_BUF[t]=='C')
+ _- Y1 Z+ m' o/ H% @# k" w. t# T - {- } p8 Z5 i" u# T+ H
- t++;//去掉首字母
2 x: S" @' \( A8 D/ N - while(USART_RX_BUF[t]!=',')
9 t) j* P& n$ W. J% _! P - {! |' ]& L1 d. w0 z- L, B3 I1 y5 o
- data_string[2][j]=USART_RX_BUF[t];
3 M1 A% ]) p0 M3 X4 ]" J - t++;
2 O& s* P! k6 [% O/ f - j++; n$ D9 E, z5 |9 v4 y5 r5 B+ G7 e
- }
U% ]. f9 }& m: _! ^5 L - }2 Y7 G/ T$ {+ {4 C m+ z
- }
2 s1 Q. v$ v4 b* y5 A - //把字符串转化为浮点数
1 U; i2 f; g4 k! A% l4 D1 j! J) V - data[0]=myatof(data_string[0])/100.0;//x) {* W3 {3 O( a" H' Q
- data[1]=myatof(data_string[1])/100.0;//y
1 N; R9 B0 c( { - data[2]=myatof(data_string[2])*(-1)/100.0;//z,输入是负数,转换为正
8 {) l" V/ S ~' _2 r - //USART2发送数据5 z- i; i) F; K( {# W# X) g I/ M$ [
- // Usart_SendString( RS232_USART, (uint8_t *)USART_RX_BUF );
' _/ \7 }6 U o# w1 M( \ -
" J$ y' }& n9 j, m& \ - //LCD更新显示0 j2 @' d( }+ `* Z4 g
- //显示xyz
" |& K6 [" Z) k8 k! [7 i - // CLEAR(10);
- j: s' [+ G E) e* _ - // Gui_DrawFont_GBK16(30,0,BLACK,WHITE,clear);6 h9 _6 p; X/ t( T* _: ~& g6 {
- // Gui_DrawFont_GBK16(30,0,BLACK,WHITE,data_string[0]);9 N I' h8 U& j; S3 M% U) Q
- // Gui_DrawFont_GBK16(30,20,BLACK,WHITE,clear);5 B6 @/ N1 b p- u
- // Gui_DrawFont_GBK16(30,20,BLACK,WHITE,data_string[1]);
$ S$ M9 V% N) r; C" D - // Gui_DrawFont_GBK16(30,40,BLACK,WHITE,clear);4 m: \/ j3 ^: q& u5 B
- // Gui_DrawFont_GBK16(30,40,BLACK,WHITE,data_string[2]);
! o+ c9 y9 p! ]" M6 t( n' ` -
. G T% _4 l" f" {' C( ]# ]1 a& _ - USART_RX_STA=0;//清除标志位
) R5 i( E9 p$ [( m. U* D - }
9 i( J% s0 m0 {) D: L* t& e% `+ X - }+ [8 l2 C* @4 ] j
- </font>
复制代码 字符串转化为两位小数浮点数(用于后续PID控制):8 ]9 q% n+ ]( w; a7 ^0 ?6 a- m; G
7 |0 S7 d8 {( z9 M( l- <font face="微软雅黑" size="3">int myatof(const char *str)//此函数仅适用于两位小数的浮点数,返回的是乘100后的int值,因float返回有错误
& t8 a! ]# ?/ V - {5 Z6 J! g' U2 e3 @* B
- int flag = 1;//表示正数, t9 l5 V% j- c
- int res =0;
. k2 y( L2 C% F3 r; i1 C - u8 i=1; //小数点后两位
0 W* U! S4 u& X1 R( W/ S/ { - while(*str != '\0')2 }) M* l h( `0 d: R' a, \# d, B
- {
7 T' W3 |. f T9 E, }" @ - if( !(*str >= '0' && *str <= '9'))//找到字符串中的第一个数字
( q. E4 S* j+ z, M! Y - {
. a: a2 q# T6 W2 g: Q7 \3 L - str++;
# M9 h' t, g3 Q - continue;
& ]3 E$ D! b9 J$ a4 I+ j& x) j) a! r - }. T& H# W1 |4 [
- if(*(str-1) == '-')% ?$ ]* i5 d0 A3 e8 j
- {
a6 ~% r) R; f - flag=-1;//表示是一个负数
3 O1 }- e% c0 @; i - }3 y0 `$ p7 g. C* T6 q) \
- 7 e* _" G' L h3 w
- while(*str >= '0' && *str <= '9')
" N4 A( n# \, X2 ] - {! E( O' g* e: P) |4 Y/ o3 l
- res = res *10 + (*str - '0');
' M0 [7 F0 v M G5 {+ q" t- f - str++;
, d' q9 p+ c: q# l - }3 ?2 R. i0 k/ F2 M5 k4 `2 `
- if(*str == '.')
& T9 F; \" T/ \1 o' G' u: r - {
1 r6 e8 ?5 Z5 k3 ^ - str++;0 \! }. k5 W) x$ N8 d8 F
- res = res *10 + (*str - '0');5 |( b/ d* M p4 T/ J
- str++;) x. c0 o+ e" {
- res = res *10 + (*str - '0');//保留两位,故加两次
$ m* k# N. C* L S. V# s- F: z: p& K - return res*flag;
* [6 |/ U4 C' _ - }1 r C" n$ Z# d/ E# ^
- }9 O$ ?; r+ V6 h7 a
- }
% L7 M P3 G9 w' c: d - : t; G. m: H6 U% g
3 w; z- F6 S3 x+ }/ J- 2.3 LCD显示模块
w* e; h+ a+ R4 p2 J8 q) ~
% `: \! C8 U9 t- u# \- LCD模块用于调试时观察数据,调试完成可以删去,因为显示屏很耗时,使处理速度变慢
9 E/ u6 S9 W5 H9 n; C - 驱动函数总览:
6 e; u3 u; M. Z/ c" G7 a - ! Y* d* X! U1 b. f! R3 r
1 q5 k! E) b+ I1 X9 l- 0 g* o. y6 E( x- n8 T1 _& M5 s
- void LCD_GPIO_Init(void);
- ?$ c4 Y! ?( ]7 }. J7 d - void Lcd_WriteIndex(u8 Index);3 u7 q3 \* Y) `& n( G; q) A
- void Lcd_WriteData(u8 Data);
7 {1 k1 G7 v$ N - void Lcd_WriteReg(u8 Index,u8 Data);& d! H0 { r5 ]) v7 J. M( M
- u16 Lcd_ReadReg(u8 LCD_Reg);9 z4 U- g. u7 F- L# X0 ]
- void Lcd_Reset(void);
- B' O) \7 Y% K' |1 x - void Lcd_Init(void);! r; n- b0 Z" P: s2 k
- void Lcd_Clear(u16 Color);) K4 Z: X$ c5 Y# i* X8 Z x
- void Lcd_SetXY(u16 x,u16 y);, [# o+ ^/ @ x8 \0 E; T! d
- void Gui_DrawPoint(u16 x,u16 y,u16 Data);% ] N9 P6 J& N0 ^; T2 _/ s
- unsigned int Lcd_ReadPoint(u16 x,u16 y);! v, B0 u2 z! n8 q) G5 Y
- void Lcd_SetRegion(u16 x_start,u16 y_start,u16 x_end,u16 y_end);4 P! G9 S% W6 P
- void LCD_WriteData_16Bit(u16 Data);</font>
复制代码
; l& ^6 p0 m1 I0 S% M! c r/ j* g! b) g/ F8 h5 P
TFT屏幕初始化:
; R! d0 C5 t' ~2 N# g6 k
; R$ e9 T3 }* M2 o& H4 ^- <font face="微软雅黑" size="3">void TFT_Init_Show(void)
7 G( F% t$ y+ d/ ~4 ^ - {
* z8 X6 B8 ]6 [ a) d0 _ - Lcd_Clear(WHITE);
) E$ M. _. R" Z4 o5 F* J5 [ - Gui_DrawFont_GBK16(16,70,BLACK,WHITE,"by WILL CHAN");+ {8 n) O: ~! B! g4 k6 f
- delay_ms(1000);
! ]) }% x/ S4 {5 s - Lcd_Clear(WHITE);
* d9 R) ~$ ?; x. @2 w - Gui_DrawFont_GBK16(3,0,RED,WHITE,"X:");
! w |$ m8 W0 S; t - Gui_DrawFont_GBK16(3,20,RED,WHITE,"Y:");0 d$ O' c9 b$ T# |9 K9 W
- Gui_DrawFont_GBK16(3,40,RED,WHITE,"Z:");
# J+ ]6 I+ z; N# Z7 O: J - Gui_DrawFont_GBK16(3,60,RED,WHITE,"speed:");
& I0 H9 D( m1 S* D/ ` - }</font>
复制代码 字符串显示函数;# o8 R8 Y+ F3 }5 c* b
- <font face="微软雅黑" size="3">
& d% Q! n) Z D Z" U( E) i - void Gui_DrawFont_GBK16(u16 x, u16 y, u16 fc, u16 bc, u8 *s)4 e4 O8 ] }, h- e8 ^
- {' ]: d' R8 ?. U6 q
- unsigned char i,j;
" P1 K# X" i* Z# I - unsigned short k,x0;
8 f6 `# e7 J0 \* u - x0=x;8 ^0 J7 l/ @: m9 y( P
7 y7 d: j7 s0 v2 z. g& M- while(*s) # X, o8 [2 r7 N0 h& S
- {
% \: }: b2 s7 O - if((*s) < 128) 8 F& f9 H0 @7 p9 a
- {+ n3 ]! l- Z) R! \2 W( ?; A
- k=*s;5 o% N7 C1 |+ Y1 z; ?
- if (k==13) # t+ R( |# G5 U
- {6 G" N# k+ y" r
- x=x0;3 Y, M& N- Y% W7 X2 n" k
- y+=16;: Y6 {# X q; j* z
- }
* q8 s: z" W. d) }" K - else
; K* _5 q# M" {/ V - {; s9 _0 e# e6 |( k3 c
- if (k>32) k-=32; else k=0;
0 e- _: H* h8 ? c - & j* G+ W: F' h; ^$ [
- for(i=0;i<16;i++)3 t! a: c/ b4 W" |! S
- for(j=0;j<8;j++) 9 T# r: S9 N2 e* o
- {
5 u! q/ N8 X" } - if(asc16[k*16+i]&(0x80>>j)) Gui_DrawPoint(x+j,y+i,fc);( R& \3 l1 E7 |& ~& U0 b/ a4 ?
- else : S) | N9 a6 Z4 H
- {
+ v0 ~% z$ l& J; k6 _% e - if (fc!=bc) Gui_DrawPoint(x+j,y+i,bc);: g% P* s7 S# n% `4 H' h1 s
- }
4 K5 N" [ j+ w% ^- e+ I' s. {3 j1 W - }
. g' b& R/ T" L# C+ c - x+=8;
2 R7 ]* J' x8 s5 F) w! c6 g - }; X8 {# U( ^( g V0 ?" w& t# F$ }
- s++;" g; m9 s, P) O0 u
- }0 e2 L7 Z* W/ i# [0 r
-
/ K' a. U) C1 z4 J8 Y, h - else
1 A, w9 y" R* t% V% v ^1 Y! V. P - {
! I! q5 m% i0 I - 8 |8 b k$ G7 v8 ~* l
2 j, y4 G+ a7 R* s# f- F- y5 R- for (k=0;k<hz16_num;k++)
# U" ]% B/ q' j- v6 q, h" U1 S - {
% _: o. W+ _. P; d - if ((hz16[k].Index[0]==*(s))&&(hz16[k].Index[1]==*(s+1)))
- v' x) G# O& e9 { - {
, E8 a% K# W) l& J+ D - for(i=0;i<16;i++)
8 g* @6 {. Y( Q - {+ {7 ~8 u8 o9 b% v7 _$ P
- for(j=0;j<8;j++) ) k/ z V4 {7 B2 Z# T* F# S
- {* z: y; X$ S/ Q" C4 o
- if(hz16[k].Msk[i*2]&(0x80>>j)) Gui_DrawPoint(x+j,y+i,fc);
' c$ ` O7 b& n - else {+ `2 V [4 ?3 P/ R
- if (fc!=bc) Gui_DrawPoint(x+j,y+i,bc);8 r* L9 E( g2 l2 [( K: A/ b& T
- }& n0 M7 I4 o& X& M2 n. E* R+ n
- }
, |' @ e; M* x0 [ - for(j=0;j<8;j++) & A$ |' W' _# q! M7 }8 H& d$ I
- {
9 g- m7 Y5 [; f( B) p. R0 y6 R. ? - if(hz16[k].Msk[i*2+1]&(0x80>>j)) Gui_DrawPoint(x+j+8,y+i,fc);
+ L! Q% W, H- q& L6 b - else
: ?4 [' G" E& t - {
1 Y# m- Q0 T1 o - if (fc!=bc) Gui_DrawPoint(x+j+8,y+i,bc);
3 i- q& m5 d7 Z5 W$ p* ^5 N, I* C - }
6 }8 W- ?3 Q4 P3 d0 U) z - }' B2 y. \+ t; g
- }
4 t( j2 m8 x) [0 I - }9 v2 `% O* C) J* L! r1 Y
- }4 h& E- k: Y5 ~! Y% H
- s+=2;x+=16;
+ y5 n. e2 J K: }0 T& l - } * v+ G) v0 h& w: U
- : z1 f0 S f" C; w7 O5 Q- V
- }
c! X7 a- e5 [& u. B5 S, a - }
# g& X _6 e: m0 X, ? - </font>
复制代码 2.4 电机、舵机与编码器7 j, ? e0 s. \+ C; ~
% F8 }* A9 T( f
3 r/ f0 O4 p) K c4 |' ~定时中断:TIM2,用于修改电机和舵机的PWM占空比
8 L- S) ]! @ ^初始化函数:* S `' S$ p% i& z/ E1 d% j9 `# Y- u
% s+ M. i' a! }& a; P7 t; [- <font face="微软雅黑" size="3">
: t1 x- ~' \' R: q* i& ? - //通用定时器2中断初始化( c+ x8 m/ ^3 O; _: P) d5 ^+ N1 Y
- //arr:自动重装值。
% X, e, c( R! M2 }! j" ? - //psc:时钟预分频数
; ]% U% ]4 H0 H# @& D - //定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
5 n$ ] _ h6 p, ^% G7 B% f! e - //Ft=定时器工作频率,单位:Mhz! q+ k( J. c/ d: N
- //这里使用的是定时器2!
7 }# z& d( Q' @; N# Y5 _2 U - void TIM2_Int_Init(u16 arr,u16 psc) v* o. W. t) ]! o) y
- { D$ f! o/ K* a1 s% y p
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;5 |% w3 d, N4 r* K0 e, _8 Q1 y8 }
- NVIC_InitTypeDef NVIC_InitStructure;; j- }3 P& i0 e3 r" T* ^
- ; \# U/ {$ A( {! E3 ]( f+ v+ J
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); ///使能TIM2时钟
1 G+ J8 K. ?* k -
) a* I* z( I; C ?6 y - TIM_TimeBaseInitStructure.TIM_Period = arr; //自动重装载值
+ l7 v% [3 ~6 N+ E/ y' w - TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //定时器分频
& d. a( k) z& Q- j3 j: G - TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式0 M) G+ {$ |: B) m3 q5 m& r
- TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
1 J2 S& r1 k1 ^) P; x0 @ - - r- R5 a: k% x2 E% g" U
- TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//初始化TIM3
3 G; D: N: F$ v) q -
- u7 _# \2 S+ c, f5 U- @ - TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //允许定时器2更新中断" X$ g m; y2 ]6 j/ E5 t+ f2 `1 ?
- TIM_Cmd(TIM2,ENABLE); //使能定时器2( p& Y; z3 V# G/ O* h0 c) q
- , H" m2 U6 a2 N
- NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn; //定时器2中断
, I& e$ r- Y% L+ s$ f8 M i: W - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; //抢占优先级1/ M: k/ g# F& d
- NVIC_InitStructure.NVIC_IRQChannelSubPriority=2; //子优先级3! M$ X( g" U$ u7 Q
- NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
8 l$ G2 J' H+ ] - NVIC_Init(&NVIC_InitStructure);
+ j7 {- e9 }" z: k; S7 B - 4 b' C( `; u: ]6 M. {0 N
- }</font>
复制代码 TIM2中断处理函数:
% t. w! T9 n" Y! h' X' K) n0 R- <font face="微软雅黑" size="3">
* w8 [4 Y& H' a; b) @" J. M1 I4 d6 N - void TIM2_IRQHandler(void)
6 k4 K$ O" B; U! o6 k0 R - { 8 r; J4 V9 j9 Q! ]# \: U# e
- if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)//溢出中断
3 Q" r7 n2 Z6 W0 U( i - {6 F; [2 l5 j- _: D( z7 Z
- if(motor_flag==1)//反转
+ {! F# V# F! k$ W - {- v& b1 {6 _/ [5 ]0 Q8 _& j: O7 U
- TIM_SetCompare1(TIM8,motor_duty*PID_val_motor*400.0);//和定时器的自动重装载值进行比较,来设置占空比,引脚:PC6/ y4 M ?# [; g R2 V$ i
- TIM_SetCompare2(TIM8,0);
$ [) S4 P. C$ U) M$ x - }' h) v1 D6 a4 Z7 d4 f
- if(motor_flag==0)//正转4 h0 q2 n: u* s* g/ ]. |2 I9 V$ B
- {
% g( h* r* ?4 z9 ^- ]: k& @ - TIM_SetCompare1(TIM8,0);
+ F% d6 m# `! b' p9 S/ b j; k9 Q - TIM_SetCompare2(TIM8,motor_duty*PID_val_motor*400.0);//和定时器的自动重装载值进行比较,来设置占空比,引脚:PC7
0 h; L1 T8 ?: d2 o - }
) O5 S' H6 @9 U t - TIM_SetCompare1(TIM3,200-(servo_angle/45.0+1)*5);//设置舵机角度,引脚:PA6. V" w4 N9 L5 E1 A$ X4 N6 ^" `! j7 W6 O# S
- }7 O# C3 v, o( e
- TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除中断标志位
8 s) V& \* s; p. n7 U1 ] - }* y4 @9 W+ j( f- t0 g$ f
- </font>
复制代码 PWM输出:TIM3(舵机),TIM8(电机)
, ?. w& {. r) u8 A+ p2 R8 Y初始化函数(以TIM8为例):
$ o m3 J/ o, L; g; N& `/ B1 u: a: R# k/ a5 @- r
- <font face="微软雅黑" size="3">0 G7 G9 w6 R% q3 T
- void TIM8_PWM_Init(u32 arr,u32 psc)' G, c7 q8 i- C% w5 U; e" S
- { % N0 p0 x( V! _/ _; {# Y; N
- //此部分需手动修改IO口设置2 h, c* U8 E. S* Z( e* j( |# j
- ) d+ a7 k' t) P7 z( L7 Y# V% V3 t
- GPIO_InitTypeDef GPIO_InitStructure;
8 J1 w8 W2 u0 d; Q1 G6 |1 S - TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;7 ]4 |& E& p7 a9 x2 k
- TIM_OCInitTypeDef TIM_OCInitStructure;
1 l8 u3 l4 Z* i5 W* r* o+ A' I - TIM_BDTRInitTypeDef TIM_BDTRInitStructure;! [ ?( n2 z- `- U2 Z
-
7 P; q+ Y- T, L( N7 t, t" \$ w3 n6 M - RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8,ENABLE); //TIM8时钟使能 ) C0 j0 D% W5 F1 A
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC, ENABLE); //使能PORTA时钟 # n; E+ |; O# ]* S3 r$ Q! G% c8 V3 W/ c
-
/ _6 q! F) }: m* t" @+ | - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //GPIOFA: s4 P p4 a; m) m/ x7 J; _. E
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能) ^( H2 E/ f, x- N1 Q3 Z
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度100MHz9 p) l3 U! p- x, \( ?# f8 u7 L
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
% X0 [- F; Q/ E0 t - GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
: j* G4 O% ~4 G9 X. I! j - GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA7
8 S& G6 `/ W7 {3 X# A - s; C6 t9 r( V2 y' ^+ @3 o' g# v
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;' E; B# O, j6 N: h
- GPIO_Init(GPIOB,&GPIO_InitStructure);
2 I! P% ?. w; p9 }( l, T -
, B3 s: n/ _* ?! g' x - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8;
$ K7 @" q& L- }! e! M# O1 O! g - GPIO_Init(GPIOC,&GPIO_InitStructure);
0 e8 a; J$ r6 G* M -
, ^) J. H# e6 `# Q - GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_TIM8); //GPIOA8复用为定时器1
' S8 F& k; i2 q7 Z' L1 ]* P - GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_TIM8); //GPIOA9复用为定时器1
, A( t3 z0 K# h n - GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_TIM8); //GPIOA10复用为定时器1
( h @' X7 Y1 n+ T, `: M7 k* ^( q - GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_TIM8); //GPIOB13复用为定时器1! G* I7 [* y8 K) D3 l* f D& l
- GPIO_PinAFConfig(GPIOB,GPIO_PinSource0,GPIO_AF_TIM8); //GPIOB14复用为定时器1
0 ^2 Q$ ?7 c* c6 i5 o% n - GPIO_PinAFConfig(GPIOB,GPIO_PinSource1,GPIO_AF_TIM8); //GPIOB15复用为定时器15 v4 {0 e7 \: a; I
- $ d: x7 @) N- n5 ?
- TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频! o5 d3 y" m9 j; d4 T) e. S
- TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式' r, C+ M; j0 F& Z8 F
- TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值( f. w0 F. c$ o# U% R! M
- TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; ( M2 j/ D# m9 \/ _# \; `6 ~ x
-
2 v d8 j+ z; Q - TIM_TimeBaseInit(TIM8,&TIM_TimeBaseStructure);//初始化定时器1, W& k3 e/ k% `$ |, Z5 V& C" V+ c
-
. g1 l+ ^" Q. [- N - //初始化PWM模式 5 D) G7 ?7 v! F( i
- TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;1 a6 B8 z. t! c# {+ _
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;! Q0 H; n9 }2 U p1 z
- TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;2 d; k' w( G& a- r. u; f* d
- TIM_OCInitStructure.TIM_Pulse = 0;
0 m& f8 O- [" y/ C3 a - TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;9 v8 Y' g+ M/ A
- TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
4 F. t! q | S. E4 B8 V - TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
9 \: P! w- A- n( T' ]1 N9 y. @' l - TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;4 B4 k# ^, w$ d% v% H
& z) s o3 E1 q5 d8 P- TIM_OC1Init(TIM8, &TIM_OCInitStructure);
2 q0 v& E% X( @ - TIM_OC2Init(TIM8, &TIM_OCInitStructure);
. s9 l% q+ p% o& p$ [ - TIM_OC3Init(TIM8, &TIM_OCInitStructure); b# @+ a) p t( W- q! Z
- 7 l" C9 I2 U, {' W$ K% E
- TIM_OC1PreloadConfig(TIM8,TIM_OCPreload_Enable);! w" g' u" B! S' ^& t2 R( j4 r F" i
- TIM_OC2PreloadConfig(TIM8,TIM_OCPreload_Enable);' ?; {% Z$ z" L4 f" R# z V
- TIM_OC3PreloadConfig(TIM8,TIM_OCPreload_Enable);' ?) a9 K. I9 n5 g% \. d% `7 u
- & x+ f7 X' c8 m' X4 \3 G) R
- TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;# ]% {0 p9 b" M( i: n
- TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;$ I4 I* t* r2 d& M- O$ ^9 i w
- TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_OFF;6 Q; v# G# i" x' J- ^# I8 U1 w
- TIM_BDTRInitStructure.TIM_DeadTime = 0;! i' V8 J3 |, J- [. H, m7 S9 Z
- TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable; 5 t' q: t5 y! r% M2 p
- TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_Low;
& J9 z. c& t% E9 f+ I - TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Disable;
2 e" P3 `* M g# V- k7 {- W - TIM_BDTRConfig(TIM8,&TIM_BDTRInitStructure);
5 g. i( m1 z( {9 S2 |+ Y4 ^
/ H" t3 }6 l3 |) A5 u, e, I& u- TIM_Cmd(TIM8,ENABLE);# m; ]% }6 Q6 S& X; C5 j
- TIM_CCPreloadControl(TIM8,ENABLE);; W/ Q" O* H& B9 J) w: z! h
- TIM_CtrlPWMOutputs(TIM8,ENABLE);. _, k( `4 Z1 R: q V0 M
- 1 O4 H: W+ C0 T6 [' R
- }</font>
复制代码 编码器初始化函数:
) r, F" l/ r' n6 V, o& `- <font face="微软雅黑" size="3">
) M, p( U5 b" O3 G6 S - void Encoder_Init_TIM4(void)
* _7 Q& R s9 S! M - {+ E- r4 u" i. e& z9 i
- GPIO_InitTypeDef GPIO_InitStructure; 0 Y4 v* U4 B( _' Z- ]: _
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
1 k8 Z0 b3 x( Q- {! j1 }' c - TIM_ICInitTypeDef TIM_ICInitStructure;
" m* T# j0 s W- o! f) c - NVIC_InitTypeDef NVIC_InitStructure;
) R3 M' g; M. S5 _ -
+ \, B& [6 a/ j2 {' }- I - RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);//开启TIM4时钟, S( s6 V B$ w a! r
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//开启GPIOB时钟$ {/ O# `+ T' P
- & p3 b; l% D1 z7 n, d" r2 |
- GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_TIM4);//PB6引脚复用
- A) Z! |+ X4 q) {! i - GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_TIM4);//PB7引脚服用
& f( b3 W" N9 X8 X -
# I: n/ L- o: [8 Y& I& H7 J - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //GPIOB6,GPIOB7
: L7 x0 m6 V/ x5 m3 a/ e) V - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;. \4 {! T' |1 \
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;# U8 }0 x* H) J
- GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;7 D. |7 M# y4 s/ n0 j
- //GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
( C& _) ?$ [4 h - GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
, I3 U6 i* C; ]- a g6 e& ~ - GPIO_Init(GPIOB,&GPIO_InitStructure); . A+ {% V3 T8 W
-
' W; i! E J* P - NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;, z$ w2 y: q: n/ ~5 [1 f
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; /*它的抢占优先级可以尽量设置低点*/
) r, _! _2 ?, U: e3 B7 M - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
: @/ C. u; K# _( `9 `. m - NVIC_InitStructure.NVIC_IRQChannelCmd = DISABLE;//禁用中断,防止计数溢出而没有相应函数,造成卡死
9 r: q6 e' {/ a1 X x3 n - NVIC_Init(&NVIC_InitStructure);/ G: q k7 h9 ^8 b/ \: O
- # t1 @, ]5 P+ A/ K
- 6 n D; ~. K2 r/ X5 O
- ) r6 f5 M8 F- ?3 s% L
- TIM_TimeBaseStructure.TIM_Period = 4095; //设置下一个更新事件装入活动的自动重装载寄存器周期的值
7 h9 b- A8 ~% Z3 x# E* S - TIM_TimeBaseStructure.TIM_Prescaler = 0; //设置用来作为TIMx时钟频率除数的预分频值 不分频
$ Z( }0 }3 X1 D3 k% _: @0 y - TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim1 z: q3 m% H! G$ S- |8 W
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
, X% F2 y; U7 W+ H& T, a - TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
2 |* p* X% Q- \6 |# j -
" C8 s; \- ] Z6 o$ ~( a - TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Falling);8 G/ {. ^+ r0 \' A3 l3 @
- TIM_ICStructInit(&TIM_ICInitStructure);
0 ]/ Z5 x: A8 x' n" f. r: R V - TIM_ICInitStructure.TIM_ICFilter = 10; //输入滤波器
4 t. R+ i$ N* K6 H8 A2 u - TIM_ICInit(TIM4, &TIM_ICInitStructure);7 S* q9 M; e# _* O* h1 L
- TIM_ClearFlag(TIM4, TIM_FLAG_Update); //清除所有标志位6 E- l6 ~7 ]; T% O) K5 G0 B
- TIM_ITConfig(TIM4, TIM_IT_Update, DISABLE); //允许中断更新
9 P7 _8 [& C' E4 ]6 | - TIM4->CNT = 0;
. V, g1 p% O' o3 J y - TIM_Cmd(TIM4, ENABLE); //使能TIM4
3 n% c% j7 I; z+ ?7 z - }
0 l6 I4 C5 w" j+ v' I; x - </font>
复制代码 编码器返回速度值:( b0 D1 W' q2 l/ x3 A( z
- <font face="微软雅黑" size="3">/**************************************************************************
6 ]- c- |; r& q - 函数功能:单位时间读取编码器计数
! A# L i! P0 |" K D( m% M& X. V7 i - 入口参数:定时器& d, e) t5 a/ ~8 V) b a
- 返回 值:速度值
% w, U2 E& t' k' _$ |; A/ ^6 q# | - **************************************************************************/- D6 w( s, t! {+ e5 v
- float Read_Encoder_Speed(uint8_t TIMX)
7 n# h/ ?/ p$ {3 k9 H - {9 U$ k# c0 o* ~
- int32_t Encoder_TIM;
6 e9 ?. ~, d; Z6 F - float res = 0;
; V% N8 q$ j/ A' V; K - switch (TIMX)& I/ g/ q) x" a7 e
- {4 S9 p X, l: K6 {. N, D8 t
- case 5:/ V& }6 I9 c) m3 u* F
- Encoder_TIM = TIM_GetCounter(TIM5);7 H) A+ z2 @3 f/ t
- TIM5->CNT = ENCODER_BASE_COUNT; " X. z$ k% |8 d+ F) u$ t
- res = (int32_t)Encoder_TIM - ENCODER_BASE_COUNT;
/ w; K* ?& N" G( A' p( f% b - break;
2 [+ I$ s! Y( c; C' i/ B7 n: P. e. C - case 4:% P$ E& |9 S' Z, |1 b
- Encoder_TIM = TIM_GetCounter(TIM4);: m. y$ W' S s# b" N8 P
- TIM4->CNT = ENCODER_BASE_COUNT;
$ Y) ^8 P1 I5 \1 d* X, P - res = (int32_t)Encoder_TIM - ENCODER_BASE_COUNT;( _( O2 |: q) M
- break; y, ~* {3 E9 ]8 _4 }
- default:
" |. A- V* s d+ i" D - Encoder_TIM = 0;
+ [1 H' R: @# f) T2 ] - res = 0;7 a( z; z3 u2 L: C8 }* Z. U
- }
" B% @. E" [% x1 M8 t7 T9 g7 j ~, u' u - if(res>2048.0f)
d, w( m! e+ w% p+ ] - res-=4096.0f;
4 D+ w7 Q$ W- U" l6 \ - return res*360.0f/4096.0f;
" H! A% B5 r% r" h- R - }0 X9 P2 [2 t i7 l0 |
- </font>
复制代码 定时从编码器取数,注意,时间不一样,取回的数值也不一样,取决于实际速度以及编码器线数。这里50ms取一次:& w" p: A7 s" @7 f& g
- <font face="微软雅黑" size="3">$ j5 M6 x# j: k: o
- void TIM7_IRQHandler(void)//频率20HZ,用于编码器计数2 V& ^7 [* d Y1 U: m
- { 3 w; R. t% \, R2 R5 ^% o' C
- if(TIM_GetITStatus(TIM7,TIM_IT_Update)==SET)//溢出中断, O _% o5 v6 V
- {) r4 j% u$ v6 f
- speed=Read_Encoder_Speed(4);& v, ]$ e( b% ~$ V3 B( ^# U4 E1 L
- }$ T: ?# b1 |2 b
- TIM_ClearITPendingBit(TIM7,TIM_IT_Update); //清除中断标志位
/ w1 Y( K* p( y2 U( @+ V! o - }</font>
复制代码 2.5 PID控制
i9 e2 E$ j+ @& L& {! d. i0 |* |
$ c5 v: {* t. X+ R6 _ q$ }2 k
, n& z, e6 B n$ `) a$ _. JPID库函数:2 {" J G/ ~, q1 A
6 x$ P% N; T" a% Z4 S- ~' E4 _" S. w- <font face="微软雅黑" size="3">0 S! f, |0 a, [- G- I3 |
- #define N 2 //需要对多少变量进行pid调节
# T9 y/ [0 v1 Z& s2 w/ ` - - {6 h4 i$ w X+ r7 y8 b0 I
- const float KP[N]={1.3,1.0};//这里只用了比例调节
+ T3 k7 ?1 `+ I - const float KI[N]={0,0};6 J" r/ c9 C: G( t/ l% V
- const float KD[N]={0,0};) ~* i6 X7 _7 h1 @. d
/ C% H1 m* p3 _4 v: C+ O; Q
# h) O+ c( G( C, V6 A% l- struct _pid{/ z) }/ s, z, e5 c: [
- float SetVol; //定义设定值
: n! e# ^2 S" \ - float ActVol; //定义实际值% F5 ^/ p1 z; o6 V
- float Err; //定义误差9 Y3 G- l6 I* ^% w3 R% z k. D
- float Err_Next; //定义上一个误差
7 G6 L$ w$ U/ T4 S/ ] U - float Err_Last; //定义上上一个误差 V( ]* M8 b7 I
- float Kp,Ki,Kd; //定义比例、积分、微分系数
+ \8 i) h2 M% e; G3 d% v" j - float integral; //定义积分值
% {. @. Q, m* D+ Y9 p4 ? - float actuator; //定义控制器执行变量$ T; ~& m8 h3 P: E5 i% D c! X! z
- }pid[N];
9 s5 N0 X. h G% P K3 F% Q5 ?9 a - 8 v- }8 e$ ?, \$ b' ~
- void PID_Init(void), [. F( e8 F. ^0 _
- {
) H* O/ E! W4 f - for(int i=0;i<N;i++)
: H) q- }) @/ i - {4 T/ X+ d# s" J, k
- pid[i].SetVol=0.0;6 ?; ^2 o) K& V' Z! j- m
- pid[i].ActVol=0.0;
; C9 w4 B& S, t% ?8 N8 U - pid[i].Err=0.0;
1 G0 Z5 ^( U7 j1 w8 m5 _ - pid[i].Err_Next=0.0;
2 V& E/ q" c2 Y& M) S1 r7 z9 Y \ - pid[i].Err_Last=0.0;8 X( v. v" x* B* u3 H9 K' E
- pid[i].integral=0.0;' W1 T0 }. g; K" R/ y
- pid[i].actuator=0.0;5 E% C3 O2 t+ T2 i
- pid[i].Kp=KP[i];. [, m7 S5 u; w% ~) t. a3 U" Z
- pid[i].Ki=KI[i];
3 }1 ]& \7 A6 ~+ U; l2 N, K - pid[i].Kd=KD[i];
/ c% {1 w; Z, \' x" @ K5 e( F - }
7 y+ w' X6 r5 t3 X1 F - }3 e' K' o5 M& f
- 8 c" G6 a5 E$ j2 B$ E) I2 S4 `. t
- float PID_realize(float set_val,float get_val,int i) //位置型PID算法实现8 _; ~0 ~" X6 z9 J9 b# R$ i
- {
! C+ Z$ ?+ N' D: g. a* q/ A4 Z - pid[i].SetVol=set_val;) l% k7 N! |5 d+ V7 P! |
- pid[i].ActVol=get_val;' Y @) @" b. }
- pid[i].Err=pid[i].SetVol-pid[i].ActVol;; {. k/ F. F1 R5 O$ h4 Z
- float IncVol; //定义增量& S; S4 i/ X' s) ?9 R; L' ?
- pid[i].integral+=pid[i].Err;3 I8 T6 i% u. G' k
- // IncVol=pid[i].Kp*(pid[i].Err-pid[i].Err_Next)+pid[i].Ki*pid[i].Err+pid[i].Kd*(pid[i].Err-2*pid[i].Err_Next+pid[i].Err_Last);
H5 Z& `! |& U8 B* F* Z1 g# D* o h8 P - pid[i].actuator=pid[i].Kp* pid[i].Err+pid[i].Ki*pid[i].integral+pid[i].Kd*(pid[i].Err-pid[i].Err_Next);
+ m( e' _" J3 p - // pid[i].actuator=adc_val+IncVol;
' O5 W, ~* k0 P$ S: U1 N3 H - pid[i].ActVol=pid[i].actuator;
" D0 {$ }, x- H - pid[i].Err_Last=pid[i].Err_Next;4 x; f/ S. x" |( o) P9 a/ w
- pid[i].Err_Next=pid[i].Err;
" c4 _+ J* f2 T1 q - 8 k7 _% q. w, x6 Z
- return pid[i].actuator;: W7 W0 E/ J" O3 X# ]
- }</font>
复制代码 主函数中的PID调节:" {; ^- k4 N; i: |% h% e. y
& e( N+ Z, b4 G- <font face="微软雅黑" size="3">
6 W# k) K2 M( j$ [+ } - z_get=data[2];
8 o/ _9 l" M( J4 J - x_get=data[0];. {8 \7 y# A1 i8 R7 C/ C
- if(z_get-z_set>0.5||z_get-z_set<-0.5)//电机PID! S! a4 l: E0 U- o
- {; ^% B& ^8 g% d
- LED1=0; //调节时灯亮5 v [5 K4 p" {! ^) |# k0 H
- PID_val_motor=PID_realize(z_set,z_get,0);
- M; A. [( H0 i7 U! H' Z" U - PID_val_motor=PID_val_motor/10.0;9 A8 b2 C/ `1 E h. w; B* x
- if(PID_val_motor<=0)* R0 ^/ T4 b/ T+ _. O4 K
- motor_flag=0;//motor_flag控制电机正反转,PID_val_motor用于改变占空比,范围0~1! S) o/ d7 e' g( N! n
- if(PID_val_motor>0)
2 o, f$ Q& C5 ]; P0 D - motor_flag=1;
2 i" Z l! ~0 K% ]) Q: ? - PID_val_motor=abs_float(PID_val_motor);8 b9 M6 B0 u* V, V" V
- if(PID_val_motor>2)PID_val_motor=0;//标志太远,让车停止
. g$ q4 Z& |7 q/ E5 G& H% |: i" F - if(PID_val_motor>1&&PID_val_motor<=2)PID_val_motor=1;: M% A6 x; H) R6 s/ f
- if(PID_val_motor<0.2)PID_val_motor=0;
0 @9 G- D! t3 E9 r - }
- ?) B5 x+ @& p/ S; U5 G - if(x_get-x_set>0.1||x_get-x_set<-0.1)//舵机PID8 f1 I: q" c# y# B
- {
. f: a' c7 j! G. D9 L9 D6 t8 S - LED1=0; # w, V: A7 l* l0 K0 m/ q% v* S; B
- PID_val_servo=PID_realize(x_set,x_get,1);$ I# z: W! q$ g$ a# o8 J
- servo_angle=((140-35)/6)*PID_val_servo+35;//线性映射,把PID的值转化为角度35~140的舵机转角- u/ |0 D# R2 U0 u
- if(servo_angle<35)servo_angle=35;3 W3 Z8 c/ V0 B z) _8 e9 @
- if(servo_angle>140)servo_angle=140;3 }3 N! `0 n0 P
- }
6 H: v4 c$ W. l+ N - LED1=1;</font>
复制代码 定时器TIM2中断里改变占空比:
+ X0 z, z) }6 a2 N) X% k- <font face="微软雅黑" size="3">
0 D+ }' ^5 k; V% C5 n9 t - void TIM2_IRQHandler(void)
# a8 l$ {1 Y- \6 M8 p' \ - { 0 ^7 F4 z- ^" N' b4 ^! w8 W
- if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)//溢出中断
, ^+ K J$ h+ q1 e7 ~ - {' j4 y- t7 l1 g5 ?% Z
- if(motor_flag==1)//反转
1 V- D1 V1 b: L& ]9 P. C! b9 M& J. \ - {
& m0 S* h4 d5 @ - TIM_SetCompare1(TIM8,motor_duty*PID_val_motor*400.0);//和定时器的自动重装载值进行比较,来设置占空比,引脚:PC6
, ? ~5 Q) \: f - TIM_SetCompare2(TIM8,0);" r7 t0 [* }) I5 ]0 r! n: q
- }/ U8 |+ x( e7 n7 K
- if(motor_flag==0)//正转
% X' ?6 Q" f3 Z; W0 G8 }/ O2 ^ - {
6 y5 M# Y0 o0 y) E - TIM_SetCompare1(TIM8,0);
" Q+ d7 f# j% ` - TIM_SetCompare2(TIM8,motor_duty*PID_val_motor*400.0);//和定时器的自动重装载值进行比较,来设置占空比,引脚:PC7/ m, y7 P, \4 O( B" ^/ c
- }
: _1 `' C" p; y" U- [, N - TIM_SetCompare1(TIM3,200-(servo_angle/45.0+1)*5);//设置舵机角度,根据舵机手册得到占空比与转角的关系,引脚:PA60 c2 {. W" | ?
- }
7 v& m/ k, r% F5 D - TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除中断标志位6 h* B+ G) {4 f" l& t% ~8 d1 P' p
- }
8 w& j8 U! M. I9 B# R - </font>
复制代码 & a) U3 [6 {5 O1 Y0 \( K
$ f/ R7 m0 s! y2 f% T- y$ Q5 r% X1 U# L; p' Q
|