最近在做电磁炮,发现题目需要用到颜色跟踪,于是花了一点时间学了一下OpenMV,只学习OpenMV是远远不够的,还需要实现与单片机的通信。我选择使用OLED显示传输的数据,在这里调试了许久,中间遇到了许多之前的学习漏洞,特在此写下博客记录学习经历。*
2.硬件连接
我所用到的材料如下: 四针IIC OLED,OpenMV(OV7725),STM32F103C8T6最小系统板,数据线N条(OpenMV的数据线只能用官方自带的,其他的基本都用不了),杜邦线若干。1.OpenMV端:由图知UART_RX—P5 ------ UART_TX—P4
2.STM32端:USART_TX—PA9 -----USART_RX—PA10
3.四针OLED IIC连接:SDA—PA2-----SCL—PA1 由于使用的是模拟IIC而不是硬件IIC,可以根据个人需要修改IO口来控制SDA线和SCL线,只需要简单修改一下代码即可。
4.STM32的TX(RX)接OpenMV的RX(TX),OLED连接到STM32即可。
3.软件代码———OpenMV端
- import sensor, image, time,math,pyb
- from pyb import UART,LED
- import json
- import ustruct
- sensor.reset()
- sensor.set_pixformat(sensor.RGB565)
- sensor.set_framesize(sensor.QVGA)
- sensor.skip_frames(time = 2000)
- sensor.set_auto_gain(False)
- sensor.set_auto_whitebal(False)
- red_threshold_01=(10, 100, 127, 32, -43, 67)
- clock = time.clock()
- uart = UART(3,115200)
- uart.init(115200, bits=8, parity=None, stop=1)
- def find_max(blobs):
- max_size=0
- for blob in blobs:
- if blob.pixels() > max_size:
- max_blob=blob
- max_size = blob.pixels()
- return max_blob
- def sending_data(cx,cy,cw,ch):
- global uart;
- data = ustruct.pack("<bbhhhhb",
- 0x2C,
- 0x12,
- int(cx),
- int(cy),
- int(cw),
- int(ch),
- 0x5B)
- uart.write(data);
- while(True):
- clock.tick()
- img = sensor.snapshot()
- blobs = img.find_blobs([red_threshold_01])
- cx=0;cy=0;
- if blobs:
- max_b = find_max(blobs)
- cx=max_b[5]
- cy=max_b[6]
- cw=max_b[2]
- ch=max_b[3]
- img.draw_rectangle(max_b[0:4])
- img.draw_cross(max_b[5], max_b[6])
- FH = bytearray([0x2C,0x12,cx,cy,cw,ch,0x5B])
- uart.write(FH)
- print(cx,cy,cw,ch)
4.软件代码———STM32端
工程总共包含如下文件:main.c、iic.c、iic.h、oled.c、oled.h、uart.c、uart.h。由于OLED的代码存在版权问题,需要的可以邮箱私发。
- /***** oled.h *****/
- void USART1_Init(void);//串口1初始化并启动
- /***** oled.c *****/
- static u8 Cx=0,Cy=0,Cw=0,Ch=0;
- void USART1_Init(void)
- {
- //USART1_TX:PA 9
- //USART1_RX:PA10
- GPIO_InitTypeDef GPIO_InitStructure; //串口端口配置结构体变量
- USART_InitTypeDef USART_InitStructure; //串口参数配置结构体变量
- NVIC_InitTypeDef NVIC_InitStructure; //串口中断配置结构体变量
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //打开PA端口时钟
- //USART1_TX PA9
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA9
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设定IO口的输出速度为50MHz
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
- GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9
- //USART1_RX PA10
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PA10
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
- GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA10
- //USART1 NVIC 配置
- NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ; //抢占优先级0
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级2
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
- NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
- //USART 初始化设置
- USART_InitStructure.USART_BaudRate = 115200; //串口波特率为115200
- USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式
- USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位
- USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
- USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制
- USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
- USART_Init(USART1, &USART_InitStructure); //初始化串口1
- USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能中断
- USART_Cmd(USART1, ENABLE); //使能串口1
- USART_ClearFlag(USART1, USART_FLAG_TC); //清串口1发送标志
- }
- //USART1 全局中断服务函数
- void USART1_IRQHandler(void)
- {
- u8 com_data;
- u8 i;
- static u8 RxCounter1=0;
- static u16 RxBuffer1[10]={0};
- static u8 RxState = 0;
- static u8 RxFlag1 = 0;
- if( USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET) //接收中断
- {
- USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清除中断标志
- com_data = USART_ReceiveData(USART1);
- if(RxState==0&&com_data==0x2C) //0x2c帧头
- {
- RxState=1;
- RxBuffer1[RxCounter1++]=com_data;OLED_Refresh();
- }
- else if(RxState==1&&com_data==0x12) //0x12帧头
- {
- RxState=2;
- RxBuffer1[RxCounter1++]=com_data;
- }
- else if(RxState==2)
- {
- RxBuffer1[RxCounter1++]=com_data;
- if(RxCounter1>=10||com_data == 0x5B) //RxBuffer1接受满了,接收数据结束
- {
- RxState=3;
- RxFlag1=1;
- Cx=RxBuffer1[RxCounter1-5];
- Cy=RxBuffer1[RxCounter1-4];
- Cw=RxBuffer1[RxCounter1-3];
- Ch=RxBuffer1[RxCounter1-2];
- }
- }
- else if(RxState==3) //检测是否接受到结束标志
- {
- if(RxBuffer1[RxCounter1-1] == 0x5B)
- {
- USART_ITConfig(USART1,USART_IT_RXNE,DISABLE);//关闭DTSABLE中断
- if(RxFlag1)
- {
- OLED_Refresh();
- OLED_ShowNum(0, 0,Cx,3,16,1);
- OLED_ShowNum(0,17,Cy,3,16,1);
- OLED_ShowNum(0,33,Cw,3,16,1);
- OLED_ShowNum(0,49,Ch,3,16,1);
- }
- RxFlag1 = 0;
- RxCounter1 = 0;
- RxState = 0;
- USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
- }
- else //接收错误
- {
- RxState = 0;
- RxCounter1=0;
- for(i=0;i<10;i++)
- {
- RxBuffer1[i]=0x00; //将存放数据数组清零
- }
- }
- }
- else //接收异常
- {
- RxState = 0;
- RxCounter1=0;
- for(i=0;i<10;i++)
- {
- RxBuffer1[i]=0x00; //将存放数据数组清零
- }
- }
- }
- }
- else if(RxState==2)
- {
- RxBuffer1[RxCounter1++]=com_data;
- if(RxCounter1>=10||com_data == 0x5B) //RxBuffer1接受满了,接收数据结束
- {
- RxState=3;
- RxFlag1=1;
- Cx=RxBuffer1[RxCounter-5];
- Cy=RxBuffer1[RxCounter-4];
- Cw=RxBuffer1[RxCounter-3];
- Ch=RxBuffer1[RxCounter1-2];
- }
- }
特别注意的是:STM32中断每发生一次,只会接收到一字节的数据,因此,进行七次才会接收完一整帧的数据包,这一点需要读者仔细揣摩,结合上文中说的静态变量关键字static,定义了:
- u8 com_data;
- u8 i;
- static u8 RxCounter1=0;
- static u8 RxBuffer1[10]={0};
- static u8 RxState = 0;
- static u8 RxFlag1 = 0;
5.利用PC端测试数据数据是否发送接收正常
在进行OpenMV与STM32的通信测试过程中,我使用了USB转TTL模块,将OpenMV(或STM32单片机)与PC端进行通信确保数据发出或者接收正常。
OpenMV&&PC
OpenMV_RX接模块TX
OpenMV_TX接模块RX
OpenMV_GND接模块GND
然后打开OpenMV,在大循环while(True)中使用语句:
- DATA=bytearray[(1,2,3,4,5)]
- uart.write(DATA)
STM32&&PC
STM32_RX接模块TX
STM32_TX接模块RX
STM32_GND接模块GND
注意:不管是STM32与PC还是OpenMV与PC还是STM32与OpenMV通信,都要将二者的GND连接在一起。
在main.c中先调用stdio头文件,大循环中使用如下语句:
- while(1)
- {
- printf("HelloWorld!");
- }
6.学习补充 (代码看不懂的时候可以来看一下)
补充1:static关键字(静态变量)的使用static 修饰全局函数和全局变量,只能在本源文件使用。举个例子,比如用以下语句static u8 RxBuffer[10] 定义了一个名为RxBuffer的静态数组,数组元素类型为unsigned char型。在包含Rxbuffer的源文件中,Rxbuffer相当于一个全局变量,任意地方修改RxBuffer的值,RxBuffer都会随之改变。而且包含RxBuffer的函数在多次运行后RxBuffer的值会一直保存(除非重新赋值)。在C语言学习中,利用static关键字求阶乘是一个很好的例子:
- long fun(int n);
- void main()
- {
- int i,n;
- printf("input the value of n:");
- scanf("%d",&n);
- for(i=1;i<=n;i++)
- {
- printf("%d! = %1d\n",i,fun(i));
- }
- }
- >long fun(int n)
- {
- static long p=1;
- p=p*n;
- return p;
- }
这个例子中,第一次p的值为1,第二次p的值变成了p x n=1 x 2=2,这个值会一直保存,如果p没有定义为静态类型,那么在第一次运算过后p的值会重新被赋值为1,这就是auto型(不声明默认为auto型)与static型的最大区别。
总结:static关键字定义的变量是全局变量,在static所包含的函数多次运行时,该变量不会被多次初始化,只会初始化一次。
补充2:extern关键字(外部变量)的使用程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量则称为外部变量,外部变量也就是我们所讲的全局变量。它的存储方式为静态存储,其生存周期为整个程序的生存周期。全局变量可以为本文件中的其他函数所共用,它的有效范围为从定义变量的位置开始到本源文件结束。
如果整个工程由多个源文件组成,在一个源文件中想引用另外一个源文件中已经定义的外部变量,同样只需在引用变量的文件中用 extern 关键字加以声明即可。下面就来看一个多文件的示例:
- #include <stdio.h>
- extern int g_X ;
- extern int g_Y ;
- int max()
- {
- return (g_X > g_Y ? g_X : g_Y);
- }
- #include <stdio.h>
- int g_X=10;
- int g_Y=20;
- int max();
- int main(void)
- {
- int result;
- result = max();
- printf("the max value is %d\n",result);
- return 0;
- }
- 运行结果为:
- the max value is 20
总结:extern关键字是外部变量,静态类型的全局变量,可以在源文件中调用其他文件中的变量,在多文件工程中配合头文件使用。
补充3:MicroPython一些库函数的解释1.ustruct.pack函数:
import ustruct,在ustruct中
- data = ustruct.pack("<bbhhhhb", #格式为俩个字符俩个短整型(2字节)
- 0x2C, #帧头1
- 0x12, #帧头2
- int(cx), # up sample by 4 #数据1
- int(cy), # up sample by 4 #数据2
- int(cw), # up sample by 4 #数据1
- int(ch), # up sample by 4 #数据2
- 0x5B)
用于把十六进制数据以字节形式存放到字节数组中,以便以数据帧的形式发送出去进行通信。
- FH = bytearray([0x2C,0x12,cx,cy,cw,ch,0x5B])
- uart,write(FH)
- 12
7.效果展示(可以先来看效果)
从上到下依次为CX,CY,CW,CH
8.博客更新
1.有朋友反馈OpenMv端找不到色块就会报错,解决方案如下:
- while(True):
- clock.tick()
- img = sensor.snapshot()
- blobs = img.find_blobs([red_threshold_01])
- cx=0;cy=0;
- if blobs:
- max_b = find_max(blobs)
- #如果找到了目标颜色
- cx=max_b[5]
- cy=max_b[6]
- cw=max_b[2]
- ch=max_b[3]
- img.draw_rectangle(max_b[0:4]) # rect
- img.draw_cross(max_b[5], max_b[6]) # cx, cy
- FH = bytearray([0x2C,0x12,cx,cy,cw,ch,0x5B])
- #sending_data(cx,cy,cw,ch)
- uart.write(FH)
- print(cx,cy,cw,ch)
2.有朋友反馈OpenMV发送数据只能发送一个字节,也就是说大于255的数据无法直接通过代码完成,现在提供以下解决方案:在STM32端代码中依次保存大于255数字的高八位和低八位最后在组合在一起即可。
2021/9/15更新 4字节与浮点数之间的转换(参考)
- #if 1
- int main()
- {
- #if 0
- float m = 23.25;
- unsigned char *a;
- a = (unsigned char *)&m;
- printf("0x%x \n0x%x \n0x%x \n0x%x \n",a[0],a[1],a[2],a[3]);
- #endif
- #if 1
- unsigned char a[]={0x00,0x00,0xba,0x41};
- float BYTE;
- BYTE = *(float *)&a;
- printf("%f\n",BYTE);
- #endif
- }
- #endif
另一种解决方案:python传数据的1/2,单片机在乘2即可。
9.参考链接[1]extern外部变量参考链接
[2]星瞳科技OpenMV中文参考手册官方
[3]MicroPython函数库
10.完整版代码链接完整版代码链接(点赞收藏免费哦)
免费啦
链接:
https://pan.baidu.com/s/1rCocKyECcyssLqFs3xWlvA
提取码:hsg6