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