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