1.前言. T" N3 A0 A7 K
最近在做电磁炮,发现题目需要用到颜色跟踪,于是花了一点时间学了一下OpenMV,只学习OpenMV是远远不够的,还需要实现与单片机的通信,本以为很简单,在CSDN上找了一些代码,直接拿来修改粘贴,把代码看明白了,这些只花了几个小时,本以为自己已经弄明白了二者之间的通信,但是在后期把OpenMV端数据传输到单片机的时候却犯了难。我选择使用OLED显示传输的数据,在这里调试了许久,中间遇到了许多之前的学习漏洞,特在此写下博客记录学习经历。
- A0 `9 U9 t& l! Z4 b0 ] ^
0 N9 H) v+ c. e2.硬件连接
6 Z9 \; _0 O4 G+ ]$ k我所用到的材料如下: 四针IIC OLED,OpenMV(OV7725),STM32F103C8T6最小系统板,数据线N条(OpenMV的数据线只能用官方自带的,其他的基本都用不了),杜邦线若干。
8 w4 I1 H) P* q0 n5 u4 u8 Z) f* U( S7 ~2 J: F: f
1.OpenMV端:由图知UART_RX—P5 ------ UART_TX—P43 S$ v& n1 h5 O2 s
, v# u* x7 n- B" c9 q+ E1 n( T) m! S0 |4 D8 S' q
2.STM32端:USART_TX—PA9 -----USART_RX—PA108 K( W" J8 S! F5 B
4 H! C$ r m# J, Z
: S, i: h; G4 `4 ]7 | S' }8 y( a% K+ M4 _' ]% i
3.四针OLED IIC连接:SDA—PA2-----SCL—PA1 由于使用的是模拟IIC而不是硬件IIC,可以根据个人需要修改IO口来控制SDA线和SCL线,只需要简单修改一下代码即可。9 w5 T& F+ M$ r2 f9 ?2 |, F
4.STM32的TX(RX)接OpenMV的RX(TX),OLED连接到STM32即可。& ]9 U, i6 |) W, ]
0 T- r5 e3 s6 U7 z
' f4 P1 O% {/ M* x' w+ r7 G* v3.软件代码———OpenMV端
, `9 j& F8 B. I* |/ n$ j8 V( x3 D3 r- D- import sensor, image, time,math,pyb
- D: q4 p9 [6 w5 i q, [, J - from pyb import UART,LED
: ~+ j, ]: n: G+ h - import json
: w. u0 u1 Z6 I9 W: G - import ustruct) ^( b5 Q/ \* i3 I, a
6 Y' x) @1 I0 U8 o, u8 ~3 P- sensor.reset()9 j- Z5 ~6 c2 b
- sensor.set_pixformat(sensor.RGB565)
( c7 k3 b2 E% ~ - sensor.set_framesize(sensor.QVGA)* w% b0 ~8 L- n! P* `" ?8 V
- sensor.skip_frames(time = 2000)& p) i/ x6 a. i1 J: O# e
- sensor.set_auto_gain(False) # must be turned off for color tracking
$ Q; A0 c8 E3 k. V2 _; }) Y - sensor.set_auto_whitebal(False) # must be turned off for color tracking1 d% V0 |: \% y9 t( Q
- red_threshold_01=(10, 100, 127, 32, -43, 67). m+ `2 {! q6 O# q3 E
- clock = time.clock()
# X* v2 j& a5 ~, E+ x8 @+ ]" A3 E
7 W$ _5 L' k/ B3 }! j- uart = UART(3,115200) #定义串口3变量6 G( m# F# w/ v
- uart.init(115200, bits=8, parity=None, stop=1) # init with given parameters6 S* a/ n7 ?* E% L
; h V# Z. x# ~8 i1 i- def find_max(blobs): #定义寻找色块面积最大的函数
8 x) k3 P& v* H9 N. D) t' I* e( D - max_size=0
' t6 G' n: r9 s+ @ - for blob in blobs:7 O5 T8 P- X1 ?. Q2 U5 b
- if blob.pixels() > max_size: H' x% ?1 X; I% h" T
- max_blob=blob: K6 O3 f a: p f9 E
- max_size = blob.pixels()1 G+ F) n3 g( G5 M3 j; p+ f
- return max_blob5 G: o3 w: S: g5 r4 C# N* v
- - b9 V& K$ l4 J+ J! O# | X3 e
$ L# i: @2 q) r- [# P6 |- def sending_data(cx,cy,cw,ch):
! K9 w b* Q1 ?2 A2 Q0 h - global uart;
1 @$ j( w b3 u" O0 @) o, z - #frame=[0x2C,18,cx%0xff,int(cx/0xff),cy%0xff,int(cy/0xff),0x5B];# y4 X5 A! e' D. D, }' }
- #data = bytearray(frame)
7 V" G- ^: @) y' c1 e - data = ustruct.pack("<bbhhhhb", #格式为俩个字符俩个短整型(2字节)9 n+ | |4 R" j+ B8 g) I
- 0x2C, #帧头14 {+ o( t# h: b3 w+ L2 j
- 0x12, #帧头2$ \& R; u: }5 S
- int(cx), # up sample by 4 #数据14 Q5 V: O: }1 F* ?* h
- int(cy), # up sample by 4 #数据2' M" F8 T) u1 {+ ]; H# ?
- int(cw), # up sample by 4 #数据1! ^4 @( Y; K( p: O
- int(ch), # up sample by 4 #数据2
- n5 D2 I& h2 j% ]* }6 Q - 0x5B)
, Q9 {1 I4 x; |- J5 d - uart.write(data); #必须要传入一个字节数组/ T7 ^0 f& g8 O9 b1 y" A& }6 o
- ) x7 ]/ x. k3 Q, w
9 n5 g8 C% g' [! s! d. _- while(True):9 t y( i5 n' y# f9 W; y
- clock.tick()) H1 |& e, G( @4 ^6 L
- img = sensor.snapshot()5 |9 \5 n1 E/ w: ]: o8 `' d% [
- blobs = img.find_blobs([red_threshold_01])! c& @+ d1 G1 F4 y% q+ ]1 X% e# B
- cx=0;cy=0;) o' M. {1 m9 ~
- if blobs:
" A$ `5 M2 G$ G! p, X# S - max_b = find_max(blobs)
0 P) k+ Y2 ^1 F1 }3 I - #如果找到了目标颜色
' z0 J0 `' j5 N - cx=max_b[5]
# P8 w- B. }. ] - cy=max_b[6]% ?$ a0 I; U* q, s
- cw=max_b[2]
2 T% V, e7 W! g* ~. C4 i+ D - ch=max_b[3]5 t: H2 m8 R: `3 J. p! b
- img.draw_rectangle(max_b[0:4]) # rect; s3 ]8 N, U8 f* [3 |) W
- img.draw_cross(max_b[5], max_b[6]) # cx, cy4 b0 ?2 ~' }" ~2 J& l8 w% }7 |2 n
- FH = bytearray([0x2C,0x12,cx,cy,cw,ch,0x5B])
$ K$ ?3 ~% G- a) m$ P8 q1 j - #sending_data(cx,cy,cw,ch)
. I2 e3 @: ~0 i' a9 ]+ z6 Q2 h - uart.write(FH)5 s7 g" `$ J7 H. u& N. b
- print(cx,cy,cw,ch)
复制代码
0 o* `* R1 C) u, Q7 b9 M% ?4 ~$ N; sbytearray([, , ,])组合uart.write()的作用与直接调用sending_data(cx,cy,cw,ch)作用是一样的8 w3 F+ ~' s- [) I
% ]+ Z2 z+ p3 h4 [7 V
! y9 r6 p' {/ ^. \5 R4.软件代码———STM32端
4 a; `/ r) }, L( ~工程总共包含如下文件:main.c、iic.c、iic.h、oled.c、oled.h、uart.c、uart.h。由于OLED的代码存在版权问题,需要的可以邮箱私发。
! ^ j2 ?8 G8 e
# }' y& t+ I1 X) h9 h1 g/***** oled.h *****/
) Q. P w3 ~* b" N4 J6 c, X" A. |2 z5 Q: Q3 M% n" u w
- #ifndef __USART_H
. }+ D) l, V T4 Y0 F$ J - #define __USART_H
k/ t0 z3 X% D- t$ t4 V! K - #include "sys.h"+ \. ]2 X) {; _0 |& I6 @7 C0 D; v: p
- void USART1_Init(void);//串口1初始化并启动) u) ^1 A% I" ~& y; M8 k8 g
- #endif
复制代码
/ S: [3 J7 T3 ?" w0 l* Y7 F) \' {' Z/***** oled.c *****/
( H+ [& l9 u! Y' ?" k' p: J. Q: [% R- z; U: W0 p z1 Z2 Y" X
- #include "uart.h") t' a. X3 D8 ~; f" p
- #include "oled.h"# y# ~ o6 P5 e( `
- #include "stdio.h"
( |- W- ~/ H s/ G3 j) i' A; I - * b% _4 B2 _- o% Y0 k" k
- static u8 Cx=0,Cy=0,Cw=0,Ch=0;+ h: e; ?; S) u# k8 M3 U! c
- 0 v7 S' C1 N# l& D+ l( t4 I
- void USART1_Init(void)
# K I! [0 y I$ O; v q1 U - {% j, Y$ G( _& }, Z
- //USART1_TX:PA 9
. s7 j) t* {, O9 ~, x6 h - //USART1_RX:PA10$ t6 I/ m, m- M. s/ o
- GPIO_InitTypeDef GPIO_InitStructure; //串口端口配置结构体变量. V, z( Y' i7 K% r8 b; R# g
- USART_InitTypeDef USART_InitStructure; //串口参数配置结构体变量( @9 T& X& S8 u, q
- NVIC_InitTypeDef NVIC_InitStructure; //串口中断配置结构体变量
; `7 m2 o/ a* p1 l+ B+ g( | - 5 B) s1 P% P2 q- L% n
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
( y) B& l% U$ G$ c- ^- d) F - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //打开PA端口时钟
9 A5 W8 L' i2 L! _$ s - / y& h! \' J* o3 a- [2 C
- //USART1_TX PA9* i% N" D$ W9 K! d1 c( S) Z. @
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA9( U' p9 G4 j5 {2 u9 |: F- v
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设定IO口的输出速度为50MHz
* R( O1 q) s* x( i# D2 c# E5 I! f - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
+ y$ _; F2 ~! e2 Z4 ^ - GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9
9 ]7 J: {* `0 X% ]+ n - //USART1_RX PA10 Q% @/ a: Q$ D$ M+ ]/ ~
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PA107 F3 T$ x( S5 p
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入% f: {! j. F- z# G3 }+ ]0 A) T
- GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA10
# T* j, p# ~& y! j+ A# m, G* g. `7 ? x
0 g7 O4 X8 G2 C) M# Z. V' R- //USART1 NVIC 配置
. N. ^/ l) O, h" a! W* [! G - NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
2 J* b) {* J% O - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ; //抢占优先级08 l- k" w: }1 A
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级26 H; y# v$ S# n8 K
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
& L- k( Q. V$ t0 }- w, k: J: ]/ | - NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器0 J- F5 G. k( _2 D, e3 b2 A
- * v+ l" |3 |2 _. X. L
- //USART 初始化设置" G0 g9 q' w; z: s8 y6 j! b- u
- USART_InitStructure.USART_BaudRate = 115200; //串口波特率为115200
( I, E" o% w5 R2 X: M - USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式
9 @2 B% g8 m. |7 U0 D - USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位2 [% ]) V* J0 |8 d9 A
- USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
, ^/ l0 f: v8 @. K; A8 P - USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制
! R" h0 n, ?8 x8 B, @) c - USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
/ a, L) |, r0 j4 r; j) ~ - USART_Init(USART1, &USART_InitStructure); //初始化串口12 U/ P, ]( e3 Q9 L
- * E; i/ Z0 i# S: W& {- T" T
- USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能中断8 x) H) ?5 J2 V0 f
- USART_Cmd(USART1, ENABLE); //使能串口1
b _5 G- l& I" _: i' n: z% \1 T - USART_ClearFlag(USART1, USART_FLAG_TC); //清串口1发送标志' P) w2 j6 ~6 J2 k2 l
-
- o( ~0 m5 O, ]4 F$ t+ o - }
: o$ e7 R. d* E& p4 { - ' x# A1 G1 P1 Z z, @% O
- //USART1 全局中断服务函数
/ Q( D1 U0 k+ c4 b! m4 K+ Z - void USART1_IRQHandler(void) % S) M* g6 P+ W- y. p
- {
* W4 o9 A) F# Y5 @ - u8 com_data;
' T: e2 m" i$ w - u8 i;8 h" N" x0 Y2 p' f5 m, Z
- static u8 RxCounter1=0; U7 r7 S4 H" f5 N+ H
- static u16 RxBuffer1[10]={0};* _. ]* M5 a" e! U
- static u8 RxState = 0; : u7 v) d- ]" ?4 R2 R& s+ ?
- static u8 RxFlag1 = 0;3 c1 V! T- g) e( l$ f# g
, \1 }% @7 p9 _; r/ W6 z" b5 A- if( USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET) //接收中断 - G. p Z5 k2 T( W- ^- a! k
- {
% r2 }8 @" j* R0 x - USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清除中断标志* G) p8 O) V9 X3 V
- com_data = USART_ReceiveData(USART1);7 ~! e) X0 S1 ] A
- ' i" p/ `" H/ i9 T7 {9 J& F
- if(RxState==0&&com_data==0x2C) //0x2c帧头6 A V |! m# T* Q& w/ r: P. e. b" p
- {
# l- |" j2 T N8 a5 n4 K# E - RxState=1;# A' ^- x/ g# z$ n% y
- RxBuffer1[RxCounter1++]=com_data;OLED_Refresh();
6 E& ]' Z2 ]" u7 R1 _ - }
8 T. J" l% Y" }" I; M8 { - 1 S, v, j. i' V
- else if(RxState==1&&com_data==0x12) //0x12帧头- N5 |$ p d3 D2 O
- {
n, k) u3 m$ l, u1 } - RxState=2;' k7 z/ x1 k8 b/ m# ^
- RxBuffer1[RxCounter1++]=com_data;
/ N4 d! l( \3 Y - }
: @5 Q, N" x) w& Q' e( ` -
9 R, ~; C5 n- i* E9 ~ - else if(RxState==2)) l2 t" z0 ^. U/ p4 t
- {& { { Z a* I i: K
- RxBuffer1[RxCounter1++]=com_data;
$ p7 H1 f4 _6 f' \) `: X - 5 `8 ~1 ]) P# F& ], U9 W
- if(RxCounter1>=10||com_data == 0x5B) //RxBuffer1接受满了,接收数据结束
; C1 n. d! s2 P! z - {
+ I$ T! }. c# J4 f; S% y; x - RxState=3; D' ^" y5 k7 x& D0 U
- RxFlag1=1;
# t2 z% w* y% F9 u - Cx=RxBuffer1[RxCounter1-5];: w; e& i% ~* t& T \
- Cy=RxBuffer1[RxCounter1-4];! e1 j0 N. n' g
- Cw=RxBuffer1[RxCounter1-3];
3 ?" \$ W* q/ K' r* \7 c - Ch=RxBuffer1[RxCounter1-2];: C+ j$ W M7 C: y
- - x7 F X) f, g$ {0 A; z( a0 Y* b
- }
$ s, v9 R& B9 C0 d4 C - }, |* p* v& _" v
-
' T- o: E) A* ?$ u: R0 d - else if(RxState==3) //检测是否接受到结束标志! X6 {* _! ]+ E6 a& _" X
- {
: G% l* n& z* I! r" a! | - if(RxBuffer1[RxCounter1-1] == 0x5B)+ S0 w# Z- @1 ?& ~- |/ R
- {! _1 C1 G7 H8 D
- USART_ITConfig(USART1,USART_IT_RXNE,DISABLE);//关闭DTSABLE中断
+ W6 i- t1 Q7 |8 X5 ~0 H - if(RxFlag1)% I/ ?2 W$ T$ h
- {
6 j* a+ p& g. Q8 v - OLED_Refresh();9 u* P" X/ J9 h& _. V
- OLED_ShowNum(0, 0,Cx,3,16,1);
7 M- _; D& n& m/ v& H - OLED_ShowNum(0,17,Cy,3,16,1);
; e9 }0 y$ n7 ]2 I& V ]" p - OLED_ShowNum(0,33,Cw,3,16,1);$ H G! w1 Q2 @% a2 i2 }9 l
- OLED_ShowNum(0,49,Ch,3,16,1);- z3 J( a9 f6 ?& B. f. f) R
- }
8 f1 I; Q5 Q7 D( ?/ j - RxFlag1 = 0;1 w( C" V. U% L
- RxCounter1 = 0;
1 C, g" ^$ T. {9 L - RxState = 0;! w8 U3 L5 `) f0 j
- USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);( b4 m' d& m; y8 ]! N
- }
9 u& }6 H" }6 ` - else //接收错误
9 x2 y U* O# ?! `0 s' l Z - {
7 F, m. h- T0 _4 G& N( N+ F' Y - RxState = 0; q7 f8 z8 X( u+ c% t
- RxCounter1=0;
3 ^) q9 m* N5 E7 u - for(i=0;i<10;i++)& f8 }: T \# v) C C; U
- {& ?1 |; R# C. P9 b! _
- RxBuffer1<i>=0x00; //将存放数据数组清零6 L D2 M. f- p' u
- }
9 c2 f/ A, ? p" K7 w - }
4 ^8 T+ y' U8 \/ i4 [ - } - d; Z. O8 K3 S" U
-
3 W7 `0 I; m( F3 r9 O8 |1 }, c - else //接收异常, J& d8 q2 u+ p# ]0 C
- {
+ {( k4 I5 K7 [2 p* J% ?: H8 O - RxState = 0;1 O( a( X) R! B( E9 U6 @$ L
- RxCounter1=0;' Z1 W l+ X% k8 J, A
- for(i=0;i<10;i++)7 |" s& {2 S1 t! B0 Z8 ^ g
- {
# o3 v z5 J: ^5 m* D" ~: h - RxBuffer1<i>=0x00; //将存放数据数组清零+ o# a: Y5 Y) D; X
- }, p5 B/ I- r5 d" {9 s( L* p/ f8 L
- }
! s% C6 f7 P6 k `" B* J' T
* b8 B- ]- Y: H! W8 N- }
8 q6 f& M+ V4 E5 s% j -
% |- G+ t/ R' x - }</i></i>
复制代码 ( T8 g$ G% O1 c2 a! G, R- u
解释:OpenMV发送数据包给STM32,STM32利用中断接收数据并把数据存放在RxBuffer1这个数组里,并且在中断中利用OLED显示cx,cy,cw,ch四个坐标。在中断中,有如下函数:
! _7 x( N9 ]# H& K1 P6 r0 |. q5 Q4 R6 I9 k& C+ M5 o6 w( k
- else if(RxState==2)
5 U4 k3 i" O! W1 V, H6 R5 ?; m - {
& E) ~8 y4 T5 s, R* \8 n; q2 q - RxBuffer1[RxCounter1++]=com_data;
' P& U& Z+ d f3 Z/ ?
0 O$ h" y& o0 c3 \. ~- if(RxCounter1>=10||com_data == 0x5B) //RxBuffer1接受满了,接收数据结束0 N& _. `: Y/ P! U
- {
$ ~2 C& M: r5 r5 N$ p5 b+ f7 n - RxState=3;
. L7 h; |/ a; r5 U, M, w- \ - RxFlag1=1;
6 V- j9 o; c2 g; \ - Cx=RxBuffer1[RxCounter-5];
% J0 D$ o' Z5 [' D - Cy=RxBuffer1[RxCounter-4];9 b; ?+ w2 P# ~' a
- Cw=RxBuffer1[RxCounter-3];
0 B5 z* A2 r- T& ] - Ch=RxBuffer1[RxCounter1-2];
$ ]! i6 C+ J& R
! p" i0 i& F9 ?, G# |- }
) J2 H( k/ [, v. g7 w1 a - }
复制代码
2 [. U p2 L, M BRxBuffer1是一个装有接收OpenMV数据的数组,RxCounter1起着一个计数器的作用,当RxBuffer[RxCounter1-1]存放的数据为数据包的帧位时,说明已经接收成功整个数据包,此时RxBuffer[RxCounter1-2]存放ch坐标值,RxBuffer[RxCounter1-3]存放cw坐标值,RxBuffer[RxCounter1-4]存放cy坐标值,RxBuffer[RxCounter1-5]存放cx坐标值,此后在RxState=3过程中将这四个坐标显示出来即可。 z5 g6 H7 Y9 i
特别注意的是:STM32中断每发生一次,只会接收到一字节的数据,因此,进行七次才会接收完一整帧的数据包,这一点需要读者仔细揣摩,结合上文中说的静态变量关键字static,定义了:
$ W% k+ p5 j e) b2 h/ H) m6 K" w- P- k1 ]4 d, ~. d0 Y! Y
- u8 com_data; 8 t+ S" x$ Y# ]4 [6 ?: M
- u8 i;2 F- Y$ @6 n: r5 P& G! D
- static u8 RxCounter1=0;
+ g# I" E% @- O, ~* f# b8 ? - static u8 RxBuffer1[10]={0}; y" F8 B" S1 v) L5 T1 X- V
- static u8 RxState = 0; % _0 S9 w) f8 T3 \
- static u8 RxFlag1 = 0;
复制代码 9 [" G! t# Y2 ]. C( [4 H0 c
请读者仔细揣摩为什么com_data(端口接收到的数据)、i定义的是动态的(auto),而RxBuffer1(装接收到数据的静态全局数组)、RxState(状态标志变量)、RxFlag1(接受结束标志变量)定义的确实静态的,这一点并不难理解。
% f% U$ u, A; X, o) d# X& E/ W8 c3 \9 ]/ A5 L
5.利用PC端测试数据数据是否发送接收正常
7 p; E1 P& ~" `% P/ B7 X在进行OpenMV与STM32的通信测试过程中,我使用了USB转TTL模块,将OpenMV(或STM32单片机)与PC端进行通信确保数据发出或者接收正常。6 d( z) `; y7 m2 n/ ?+ e$ s
$ g3 q- a; ^/ T2 d u
OpenMV&& C
, \+ v# m# x2 r" FOpenMV_RX接模块TX
3 {2 F2 D. K* _+ @# h; U3 z+ [4 E _OpenMV_TX接模块RX7 g: I& s2 T0 V2 S, M1 {! N {$ W
OpenMV_GND接模块GND
; y* |$ n \2 y然后打开OpenMV,在大循环while(True)中使用语句:* q; W/ `8 s- U* G5 A9 x7 `
4 i; {9 ]! T& i
- DATA=bytearray[(1,2,3,4,5)]
8 s+ I8 T3 n3 n7 Y2 g! m - uart.write(DATA)
复制代码 # g4 R6 W9 z1 V/ w9 _
打开PC端串口助手,注意设置一样的波特率、停止位、发送字节数等,查看串口助手是否接受到了数据。
( G. Y$ a3 B- S, P9 y1 A' v3 R' q* F
/ D; v# Q2 T+ M% KSTM32&& C
+ `/ e9 U O0 |' |* ?. S# K+ ZSTM32_RX接模块TX
$ o) P+ N& S, |! U- O" \1 z* gSTM32_TX接模块RX
# B4 b' v) p LSTM32_GND接模块GND
# c1 j+ W8 I4 R* U8 H" B注意:不管是STM32与PC还是OpenMV与PC还是STM32与OpenMV通信,都要将二者的GND连接在一起。
( q1 F- y1 ?8 s3 C( x在main.c中先调用stdio头文件,大循环中使用如下语句:
1 W1 i% V4 Z% P% P$ M0 e- while(1)3 a8 ?8 s* j, g& u1 r
- {
# h0 e% `; v6 E% N - printf("HelloWorld!");
; b4 q3 S1 a- _5 j - }
复制代码 3 M) f) J2 n& r' I7 }& h% V+ P, N; c
打开串口助手查看是否接收到了数据。
6 w* a, c- D/ V2 m0 d
+ j3 \; r+ ?% k. T9 x9 b* U P: ?
0 ?5 g. I9 C9 K6 t3 t6.学习补充 (代码看不懂的时候可以来看一下)
6 S6 `; t; z) j U! a( p7 M补充1:static关键字(静态变量)的使用! @( u- l3 j; P& j- B8 P
$ S$ _! P1 R& G- h* _* }' V/ [
static 修饰全局函数和全局变量,只能在本源文件使用。举个例子,比如用以下语句static u8 RxBuffer[10] 定义了一个名为RxBuffer的静态数组,数组元素类型为unsigned char型。在包含Rxbuffer的源文件中,Rxbuffer相当于一个全局变量,任意地方修改RxBuffer的值,RxBuffer都会随之改变。而且包含RxBuffer的函数在多次运行后RxBuffer的值会一直保存(除非重新赋值)。在C语言学**,利用static关键字求阶乘是一个很好的例子:
! {) B9 X1 j* y/ k( [ F: C; u, m# N I& S. L5 U; m! ^* S
- #include“stdio.h”
7 C( T( P2 r* [ - long fun(int n);
! B& ~6 Z* }( s - void main()
% H3 o8 S6 h% w# ~* u6 } - {
1 H. j: k9 U" {- \) i - int i,n;
# `+ n8 s y8 w# Y - printf("input the value of n:");- p3 [2 V: Z. b8 W/ ]1 M5 [9 Z% Y8 o* Z
- scanf("%d",&n);
5 j" u% z0 F+ o! N - for(i=1;i<=n;i++)
$ K6 N- m) U9 i: Y - {0 }7 n. ]2 R9 g, C! J1 A& G+ w' f
- printf("%d! = %1d\n",i,fun(i));
$ m; Q+ \7 S4 F7 O, e; s5 x - }( u, W+ C9 W/ z3 G7 F( g
- }
0 J. U( t: q2 J$ E6 N7 o - >long fun(int n)
% i8 `/ y. p: E) u: X( d - {" [: @9 v7 v: P6 z' S/ Z* o# T1 l
- static long p=1;
4 n" h' b. D& o) b- y2 w7 o' d" E/ C - p=p*n;
' U; J# o/ `' u' O' P4 ]: m( _ - return p; f! G1 u/ \( O i, ?
- }
复制代码
8 O; i3 r' r$ y- x E7 Z: A效果为依次输出n!(n=1,2,3…n)
; J, w' u1 Y& z8 _+ w$ m1 R这个例子中,第一次p的值为1,第二次p的值变成了p x n=1 x 2=2,这个值会一直保存,如果p没有定义为静态类型,那么在第一次运算过后p的值会重新被赋值为1,这就是auto型(不声明默认为auto型)与static型的最大区别。
9 D- e, T; b/ W: X5 K6 R. K% _( G+ ?$ O; J6 c
总结:static关键字定义的变量是全局变量,在static所包含的函数多次运行时,该变量不会被多次初始化,只会初始化一次。& C+ n r0 e$ f& |3 F: r7 S0 X
9 j' v! n5 W5 x" k补充2:extern关键字(外部变量)的使用2 E! p; {9 I2 C; d& k' u8 B
程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量则称为外部变量,外部变量也就是我们所讲的全局变量。它的存储方式为静态存储,其生存周期为整个程序的生存周期。全局变量可以为本文件中的其他函数所共用,它的有效范围为从定义变量的位置开始到本源文件结束。
0 `1 s- D+ B. `如果整个工程由多个源文件组成,在一个源文件中想引用另外一个源文件中已经定义的外部变量,同样只需在引用变量的文件中用 extern 关键字加以声明即可。下面就来看一个多文件的示例:
& E3 f4 E7 N/ s: Z2 W; U) K% {
8 Y: M7 e! G" L) B- /****max.c****/( H7 o; @( ]2 t1 z8 B6 G! ? J
- #include <stdio.h>
' t: e. H- x1 E5 ?5 u, P - /*外部变量声明*/
& t2 T+ s: I+ O/ X9 I1 r - extern int g_X ;
% l6 G; r6 d. ^$ p - extern int g_Y ;
! t0 Q' Z. [" z. g/ p - int max()5 Q3 J( d+ M |
- {
& ?9 o7 }4 l; z3 A H - return (g_X > g_Y ? g_X : g_Y);
/ l7 B3 U D# s6 X - }7 n) D8 n$ D3 m
- /***main.c****/
1 J J6 O* [( f/ v/ @ - #include <stdio.h>
, i' I; v5 \& j4 D/ e - /*定义两个全局变量*/
1 b" C" m4 }) _% J - int g_X=10;+ B$ t8 B. E3 X
- int g_Y=20;( F$ S5 R6 f5 M% u
- int max();6 X6 c9 `, \1 E/ j8 x
- int main(void)3 J7 i9 y7 {& i5 Q2 j$ ^3 C/ [
- {: F- z) S% @) l* s' h& M
- int result;, |1 U$ w3 A% S
- result = max();
6 Z# {$ r/ `& I" r3 ~' D - printf("the max value is %d\n",result);6 {7 `1 n. z% n) _ O) ^) b; _
- return 0;9 u; d( b$ z3 |# X) E+ E
- }9 w3 ]+ q6 ]( m
- 运行结果为:
$ y0 O9 k7 O$ d: Z# ] - the max value is 20) O0 i4 N6 a8 U
复制代码 / O. Y- @5 N# e& [ b$ |
对于多个文件的工程,都可以采用上面这种方法来操作。对于模块化的程序文件,可在其文件中预先留好外部变量的接口,也就是只采用 extern 声明变量,而不定义变量,max.c 文件中的 g_X 与 g_Y 就是如此操作的。比如想要在主函数中调用usart.c中的变量x,usart.c中有着这样的定义:static u8 x=0在usart.h中可以这样写:extern u8 x在main.c中包含usart.h头文件,这样在编译的时候就会在main.c中调用x外部变量。
$ c: Q, r) C8 m' U( ^! D* e, ?, P$ B7 B" L( ^5 L
总结:extern关键字是外部变量,静态类型的全局变量,可以在源文件中调用其他文件中的变量,在多文件工程中配合头文件使用。
% X( E3 K' x d# N9 _
) q ]. g' M o+ c. ?补充3:MicroPython一些库函数的解释7 Y# p: l$ ~$ r, \' Y
2 Z) o# e4 R$ G* y' _; g1 u1.ustruct.pack函数:
& Y$ t5 X U& }5 N8 N, I/ wimport ustruct,在ustruct中* Q) e3 C, \3 G2 y1 P
- data = ustruct.pack("<bbhhhhb", #格式为俩个字符俩个短整型(2字节)6 X$ P/ @' r% b0 T
- 0x2C, #帧头1
- k5 f% Y# h) v8 w2 N - 0x12, #帧头2
- T" t, J1 j# }' Y, \( c) X! S - int(cx), # up sample by 4 #数据1
9 _: |; A( S# r6 O- [6 \ - int(cy), # up sample by 4 #数据2/ ^ u \5 F5 {2 ~4 R
- int(cw), # up sample by 4 #数据11 B3 v- {8 B% N; o# k6 H& Z
- int(ch), # up sample by 4 #数据2; D6 L2 p9 A! q: ~' }
- 0x5B)
复制代码
. m, g7 w3 F' W1 H4 ]""bbhhhhb"简单来说就是要发送数据的声明,bbhhhhb共七个,代表发送七个数据,对照下面的表,可以知道七个数据按时序发送为unsigner char、unsigned char、short、short、short、short、unsigned char。0x2c为数据帧的帧头,即检测到数据流的开始,但是一个帧头可能会出现偶然性,因此设置两个帧头0x2c与0x12以便在中断中检测是否检测到了帧头以便存放有用数据。0x5b为帧尾,即数据帧结束的标志。+ G% \# H+ g5 s: a- v6 w2 a+ L
% H+ X' N' z Z6 D, P3 l) z3 [% d: i
1 p3 ]/ I a2 v# g' R) I& _: A0 i0 M9 @6 a8 s5 c# l
2.bytearray([ , , , ])函数:
5 ?. v* s/ U5 C! ~用于把十六进制数据以字节形式存放到字节数组中,以便以数据帧的形式发送出去进行通信。3 c E9 _3 i. _1 E; F
4 A9 c6 Z2 n5 o( i; Z
- FH = bytearray([0x2C,0x12,cx,cy,cw,ch,0x5B])" S; G6 U; `3 r# M# @0 C
- uart,write(FH)
复制代码
( p" ]9 O5 L) }0 ~7.效果展示(可以先来看效果)
! S' ^' o; I5 d6 Q. @" z
+ f# [8 v0 v; T z+ V
2 \5 _) w: ^9 n2 Q$ D% n从上到下依次为CX,CY,CW,CH0 A5 s( c8 R0 [1 a% y1 \8 X
3 u% V5 d* A- A4 J& c& D( S. B: J
8.博客更新
- A1 R' g- M' ?0 D$ T) l2 h1.有朋友反馈OpenMv端找不到色块就会报错,解决方案如下:
9 P9 V# A1 u, J% ]. i
4 g8 ^* {9 N r; F- while(True):; U* k) J' E! b; o: h; [
- clock.tick()
, E6 U* E6 _9 L, l, w7 V+ e - img = sensor.snapshot()
( }5 D) g. G7 o, b - blobs = img.find_blobs([red_threshold_01]); y O! D( f- S9 O# i4 d. r
- cx=0;cy=0;' y9 I7 O5 k6 z; u! K
- if blobs:+ z8 B+ I1 K6 ^9 E! A8 N
- max_b = find_max(blobs)* n4 r9 J3 U5 |- i g
- #如果找到了目标颜色7 n$ ]! a! t4 r N' C) P: t
- cx=max_b[5]
+ p1 B+ z5 X- r( y2 F - cy=max_b[6]& i$ ?7 A( H6 X* i: O9 H, g
- cw=max_b[2]
6 N, d3 l1 p" c# R8 n - ch=max_b[3]' q$ Y) t1 ?' X8 R$ H, }5 u- Q
- img.draw_rectangle(max_b[0:4]) # rect/ ?' @. p8 S% T" }
- img.draw_cross(max_b[5], max_b[6]) # cx, cy( ]7 C% G' @0 t" |
- FH = bytearray([0x2C,0x12,cx,cy,cw,ch,0x5B]). m! h3 s9 k# k% v/ b
- #sending_data(cx,cy,cw,ch)' i( \# g# D: s/ x0 _
- uart.write(FH)
/ c" F6 n1 ?& V B; M; P0 q) ~5 ] - print(cx,cy,cw,ch)
复制代码 * B9 a* Z: h S; T' u7 G
在以上代码中,将max_b = find_max(blobs) 移到if blobs外即可。
- @4 N% K0 Z9 v
; j0 e& Q5 Z; i2 O2 y2.有朋友反馈OpenMV发送数据只能发送一个字节,也就是说大于255的数据无法直接通过代码完成,现在提供以下解决方案:在STM32端代码中依次保存大于255数字的高八位和低八位最后在组合在一起即可。
1 c0 O' k) v2 e, @* k' l6 J! y) Y1 j7 R6 K# L/ I& E& Z' Y% K
- #if 1
' B# w9 H2 J6 a' x+ _5 Z4 S. ]3 ^ - int main()
% _: O! W V$ |+ a - {
% u4 |( S$ M0 {, j2 o; e - #if 0
7 v4 i/ f2 I* M. _ - //字符型数据分成四个字节存放在数组中# q% Z6 D7 a: i! W7 f/ Q8 @" ]
- float m = 23.25;
) {, _7 B7 \2 ^# H) \ - unsigned char *a;7 O% [! Z/ R: g
- a = (unsigned char *)&m;* j3 B5 d% Y; i9 q! c2 l" F8 V' j
- printf("0x%x \n0x%x \n0x%x \n0x%x \n",a[0],a[1],a[2],a[3]);1 Z/ Y% L6 B7 ~# ]* Y& X
- ' d2 p! M% q7 |2 S! `& o
- #endif
2 U" b' I9 e1 I n; y - 6 j5 t8 g$ U3 Q7 N' K: d) x( h
- #if 1& t+ w: \+ n$ }/ ]+ `
- //四个字节数据合成存放在数组中5 F/ z2 _/ L: ^: T, _) i& o
- unsigned char a[]={0x00,0x00,0xba,0x41};
+ f- _! z: J* g/ L" G5 U - float BYTE;
) J8 A) o; I9 R1 l1 U' d - BYTE = *(float *)&a;
1 K! V, x+ [ @. c - printf("%f\n",BYTE);0 |( h5 `" `4 @, z1 b! t" a
- #endif
( L$ y, n1 l: H$ s+ t: w0 v/ N - }
" O `' J+ F# b7 f - #endif
复制代码 : r3 j' ]: K+ I1 X- A% |/ b" o# J9 y& ]5 R
上述代码实现了将四个字节转换为一个浮点数的功能,同时也实现了将一个浮点数拆分为四个字节功能。在Openmv传数据时,只能传输一个字节,大于255的数无法以一字节形式发送,因此可以在Openmv端将该数据拆分成两个字节,分别发送给Stm32端,同时Stm32端对传来的数据进行合成,合成并解析为对应的数据。' l- c( a& w& E$ O) \8 |" _2 w$ H
另一种解决方案:python传数据的1/2,单片机在乘2即可。
" F5 A& ~, v0 E* }" N0 {) \& }
! M: y/ I6 X3 d. I————————————————
0 |' ~" u! C' n版权声明:Wu__La
3 {% N0 C ]! B9 x$ ?; k
& k! V7 N& A* c. K+ C6 V6 O M, @# l# J6 f# F5 s( s
7 X( A- P2 L, R% D
% O# ]$ ~2 d* R) K9 B |