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