完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
1.前言(闲话)
最近在做电磁炮,发现题目需要用到颜色跟踪,于是花了一点时间学了一下OpenMV,只学习OpenMV是远远不够的,还需要实现与单片机的通信,本以为很简单,在CSDN上找了一些代码,直接拿来修改粘贴,把代码看明白了,这些只花了几个小时,本以为自己已经弄明白了二者之间的通信,但是在后期把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) # must be turned off for color tracking sensor.set_auto_whitebal(False) # must be turned off for color tracking red_threshold_01=(10, 100, 127, 32, -43, 67) clock = time.clock() uart = UART(3,115200) #定义串口3变量 uart.init(115200, bits=8, parity=None, stop=1) # init with given parameters 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; #frame=[0x2C,18,cx%0xff,int(cx/0xff),cy%0xff,int(cy/0xff),0x5B]; #data = bytearray(frame) data = ustruct.pack(" 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) 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]) # 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) bytearray([, , ,])组合uart.write()的作用与直接调用sending_data(cx,cy,cw,ch)作用是一样的 4.软件代码———STM32端 工程总共包含如下文件:main.c、iic.c、iic.h、oled.c、oled.h、uart.c、uart.h。由于OLED的代码存在版权问题,需要的可以邮箱私发。 /***** oled.h *****/ #ifndef __USART_H #define __USART_H #include "sys.h" void USART1_Init(void);//串口1初始化并启动 #endif 1 2 3 4 5 /***** oled.c *****/ #include "uart.h" #include "oled.h" #include "stdio.h" 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=0x00; //将存放数据数组清零 } } } else //接收异常 { RxState = 0; RxCounter1=0; for(i=0;i<10;i++) { RxBuffer1=0x00; //将存放数据数组清零 } } } } 解释:OpenMV发送数据包给STM32,STM32利用中断接收数据并把数据存放在RxBuffer1这个数组里,并且在中断中利用OLED显示cx,cy,cw,ch四个坐标。在中断中,有如下函数: 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]; } } RxBuffer1是一个装有接收OpenMV数据的数组,RxCounter1起着一个计数器的作用,当RxBuffer[RxCounter1-1]存放的数据为数据包的帧位时,说明已经接收成功整个数据包,此时RxBuffer[RxCounter1-2]存放ch坐标值,RxBuffer[RxCounter1-3]存放cw坐标值,RxBuffer[RxCounter1-4]存放cy坐标值,RxBuffer[RxCounter1-5]存放cx坐标值,此后在RxState=3过程中将这四个坐标显示出来即可。 特别注意的是:STM32中断每发生一次,只会接收到一字节的数据,因此,进行七次才会接收完一整帧的数据包,这一点需要读者仔细揣摩,结合上文中说的静态变量关键字static,定义了: u8 com_data; u8 i; static u8 RxCounter1=0; static u8 RxBuffer1[10]={0}; static u8 RxState = 0; static u8 RxFlag1 = 0; 请读者仔细揣摩为什么com_data(端口接收到的数据)、i定义的是动态的(auto),而RxBuffer1(装接收到数据的静态全局数组)、RxState(状态标志变量)、RxFlag1(接受结束标志变量)定义的确实静态的,这一点并不难理解。 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) 1 2 打开PC端串口助手,注意设置一样的波特率、停止位、发送字节数等,查看串口助手是否接受到了数据。 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关键字求阶乘是一个很好的例子: #include“stdio.h” 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! = %1dn",i,fun(i)); } } >long fun(int n) { static long p=1; p=p*n; return p; } 效果为依次输出n!(n=1,2,3…n) 这个例子中,第一次p的值为1,第二次p的值变成了p x n=1 x 2=2,这个值会一直保存,如果p没有定义为静态类型,那么在第一次运算过后p的值会重新被赋值为1,这就是auto型(不声明默认为auto型)与static型的最大区别。 总结:static关键字定义的变量是全局变量,在static所包含的函数多次运行时,该变量不会被多次初始化,只会初始化一次。 补充2:extern关键字(外部变量)的使用 程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量则称为外部变量,外部变量也就是我们所讲的全局变量。它的存储方式为静态存储,其生存周期为整个程序的生存周期。全局变量可以为本文件中的其他函数所共用,它的有效范围为从定义变量的位置开始到本源文件结束。 如果整个工程由多个源文件组成,在一个源文件中想引用另外一个源文件中已经定义的外部变量,同样只需在引用变量的文件中用 extern 关键字加以声明即可。下面就来看一个多文件的示例: /****max.c****/ #include /*外部变量声明*/ extern int g_X ; extern int g_Y ; int max() { return (g_X > g_Y ? g_X : g_Y); } /***main.c****/ #include /*定义两个全局变量*/ int g_X=10; int g_Y=20; int max(); int main(void) { int result; result = max(); printf("the max value is %dn",result); return 0; } 运行结果为: the max value is 20 对于多个文件的工程,都可以采用上面这种方法来操作。对于模块化的程序文件,可在其文件中预先留好外部变量的接口,也就是只采用 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外部变量。 总结:extern关键字是外部变量,静态类型的全局变量,可以在源文件中调用其他文件中的变量,在多文件工程中配合头文件使用。 补充3:MicroPython一些库函数的解释 1.ustruct.pack函数: import ustruct,在ustruct中 data = ustruct.pack(" 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) ""bbhhhhb"简单来说就是要发送数据的声明,bbhhhhb共七个,代表发送七个数据,对照下面的表,可以知道七个数据按时序发送为unsigner char、unsigned char、short、short、short、short、unsigned char。0x2c为数据帧的帧头,即检测到数据流的开始,但是一个帧头可能会出现偶然性,因此设置两个帧头0x2c与0x12以便在中断中检测是否检测到了帧头以便存放有用数据。0x5b为帧尾,即数据帧结束的标志。 2.bytearray([ , , , ])函数: 用于把十六进制数据以字节形式存放到字节数组中,以便以数据帧的形式发送出去进行通信。 FH = bytearray([0x2C,0x12,cx,cy,cw,ch,0x5B]) uart,write(FH) 7.效果展示(可以先来看效果) 从上到下依次为CX,CY,CW,CH 8.参考链接 [1]extern外部变量参考链接 [2]星瞳科技OpenMV中文参考手册官方 [3]MicroPython函数库 9.完整版代码链接 完整版代码链接(也可以直接私发我要哦) 最新博客:《陀螺仪MPU6050模块输出姿态角》 欢迎大家浏览支持! |
|
|
|
只有小组成员才能发言,加入小组>>
2553 浏览 0 评论
1152浏览 2评论
750浏览 1评论
504浏览 0评论
269浏览 0评论
433浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-23 22:04 , Processed in 1.334625 second(s), Total 80, Slave 61 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号