赞
踩
最近要准备工巡赛,突然要发现需要进行视觉传动,所以我最近几天又温顾了一下Openmv,以前学习Openmv都是通过电脑对其进行控制,但是这样学习OpenMV是远远不够的,还需要实现与单片机的通信,本以为很简单,在CSDN,github上找了一些开源代码,然后进行复制与粘贴,原本我以为这就掌握了,但是在后期的传输我犯了许多低级的错误,中间也反映了我的一些不足,我最后通过OLED连进行数据传输,调试后,我特地写下此博客来记录我自己的学习经历。
我选择的是openmv4 CamH7智能摄像头,OLED, stm32f104c8t6, ST-JINK, 若干数据线和杜邦线
这是openmv4 CamH7智能摄像头,由图可知openmv4 CamH7只有1个串口,USART3,
由图知UART_RX—P5 UART_TX—P4
那么在STM32中
我们选择PB10,PB11,来进行串口选择USART3.
STM32的TX(RX)接OpenMV的RX(TX),OLED连接到STM32即可
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("<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) 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)
在这里我借鉴了这位博主的代码乌拉
对于为什么不直接使用send()函数,在这里他是这么认为的
bytearray([, , ,])组合uart.write()的作用与直接调用sending_data(cx,cy,cw,ch)作用是一样的
我看来都无所谓,我是使用了send()函数,其实方法都无所谓的,只要能够传输数据就行。
在这里我们通过openmv对电脑进行串口通信来判断openmv是否通信成功,将openmv与TTL连接,打开串口助手XCOM来进行查看数据是否传输成功
串口通信成功,代码有效
让我们看看STM32的程序,我们要打开串口的驱动,我们要打开的串口驱动为USART3,
#include "uart.h" #include "show.h" #include "gpio.h" u8 Cx=0,Cy=0,Cw=0,Ch=0; void uart3_Init(void) { //USART3_TX PB10 //USART3_RX PB11 GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_InitStructure); NVIC_InitStructure.NVIC_IRQChannel=USART3_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; NVIC_InitStructure.NVIC_IRQChannelSubPriority=2; NVIC_Init(&NVIC_InitStructure); 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(USART3, &USART_InitStructure); USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); //使能中断 USART_Cmd(USART3, ENABLE); //使能串口3 USART_ClearFlag(USART3, USART_FLAG_TC); //清串口3发送标志 } void USART3_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(USART3,USART_IT_RXNE)!=RESET) //接收中断 { USART_ClearITPendingBit(USART3,USART_IT_RXNE); //清除中断标志 com_data = USART_ReceiveData(USART3); //串口3接收数据 if(RxState==0&&com_data==0x2C) //0x2c帧头开始数据接收处理 { RxState=1; RxBuffer1[RxCounter1++]=com_data; OLED_Refresh_Gram(); // GPIO_SetBits(GPIOB,GPIO_Pin_4); } 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(USART3,USART_IT_RXNE,DISABLE);//关闭DTSABLE中断 if(RxFlag1) { OLED_Refresh_Gram(); OLED_ShowNumber(0, 0,Cx,3,16); OLED_ShowNumber(0,17,Cy,3,16); OLED_ShowNumber(0,33,Cw,3,16); OLED_ShowNumber(0,49,Ch,3,16); oled_show(); } RxFlag1 = 0; RxCounter1 = 0; RxState = 0; USART_ITConfig(USART3,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; //将存放数据数组清零 } } } }
代码解析:RxBuffer1[]数组是用来存储数据的,先存储帧头,后存储数据,帧头的数值可以任选,0x2c为数据帧的帧头,即检测到数据流的开始,但是一个帧头可能会出现偶然性,因此设置两个帧头0x2c与0x12以便在中断中检测是否检测到了帧头以便存放有用数据。0x5b为帧尾,即数据帧结束的标志。我们储存数据完毕需要7次中断,进行完7次中断后才能将数据输入进去。
OLED模块由于代码的数据量过多,而且我使用的是六针OLED,对于OLED的驱动可能会与市场常见的OLED可能不一样,在这里我先把OLED的驱动填写上,
#include "oled.h" #include "stdlib.h" #include "oledfont.h" #include "delay.h" u8 OLED_GRAM[128][8]; /************************************************************************** Function: Refresh the OLED screen Input : none Output : none 函数功能:刷新OLED屏幕,更新缓存,显示内容 入口参数:无 返回 值:无 **************************************************************************/ void OLED_Refresh_Gram(void) { u8 i,n; for(i=0;i<8;i++) { OLED_WR_Byte (0xb0+i,OLED_CMD); //设置页地址(0~7) OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址 OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址 for(n=0;n<128;n++) OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA); } } /************************************************************************** Function: Refresh the OLED screen Input : Dat: data/command to write, CMD: data/command flag 0, represents the command;1, represents data Output : none 函数功能:向OLED写入一个字节 入口参数:dat:要写入的数据/命令,cmd:数据/命令标志 0,表示命令;1,表示数据 返回 值:无 **************************************************************************/ void OLED_WR_Byte(u8 dat,u8 cmd) { u8 i; if(cmd) //判断是命令还是写数据 OLED_RS_Set(); //DC判断 else OLED_RS_Clr(); //DC判断 for(i=0;i<8;i++) //数据输入 { OLED_SCLK_Clr(); if(dat&0x80) OLED_SDIN_Set(); else OLED_SDIN_Clr(); OLED_SCLK_Set(); dat<<=1; } OLED_RS_Set(); } /************************************************************************** Function: Turn on the OLED display Input : none Output : none 函数功能:开启OLED显示 入口参数:无 返回 值:无 **************************************************************************/ void OLED_Display_On(void) { OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令 OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON } /************************************************************************** Function: Turn off the OLED display Input : none Output : none 函数功能:关闭OLED显示 入口参数:无 返回 值:无 **************************************************************************/ void OLED_Display_Off(void) { OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令 OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF } /************************************************************************** Function: Screen clear function, clear the screen, the entire screen is black, and did not light up the same Input : none Output : none 函数功能:清屏函数,清完屏,整个屏幕是黑色的,和没点亮一样 入口参数:无 返回 值:无 **************************************************************************/ void OLED_Clear(void) { u8 i,n; for(i=0;i<8;i++)for(n=0;n<128;n++)OLED_GRAM[n][i]=0X00; OLED_Refresh_Gram();//更新显示 } /************************************************************************** Function: Draw point Input : x,y: starting coordinate;T :1, fill,0, empty Output : none 函数功能:画点 入口参数:x,y :起点坐标; t:1,填充,0,清空 返回 值:无 **************************************************************************/ void OLED_DrawPoint(u8 x,u8 y,u8 t) { u8 pos,bx,temp=0; if(x>127||y>63)return;//超出范围了. pos=7-y/8; bx=y%8; temp=1<<(7-bx); if(t) OLED_GRAM[x][pos]|=temp; else OLED_GRAM[x][pos]&=~temp; } /************************************************************************** Function: Displays a character, including partial characters, at the specified position Input : x,y: starting coordinate;Len: The number of digits;Size: font size;Mode :0, anti-white display,1, normal display Output : none 函数功能:在指定位置显示一个字符,包括部分字符 入口参数:x,y :起点坐标; len :数字的位数; size:字体大小; mode:0,反白显示,1,正常显示 返回 值:无 **************************************************************************/ void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode) { u8 temp,t,t1; u8 y0=y; chr=chr-' '; //得到偏移后的值,ASALL寻找位置 ,减一去零 for(t=0;t<size;t++) { if(size==12) temp=oled_asc2_1206[chr][t]; //调用1206字体 else temp=oled_asc2_1608[chr][t]; //调用1608字体 高16宽8 for(t1=0;t1<8;t1++) { if(temp&0x80) OLED_DrawPoint(x,y,mode); //标点 else OLED_DrawPoint(x,y,!mode); temp<<=1; y++; if((y-y0)==size) //1206的控制,如果控制到12位就停止 { y=y0; x++; break; } } } } /************************************************************************** Function: Find m to the NTH power Input : m: base number, n: power number Output : none 函数功能:求m的n次方的函数 入口参数:m:底数,n:次方数 返回 值:无 **************************************************************************/ u32 oled_pow(u8 m,u8 n) { u32 result=1; while(n--)result*=m; return result; } /************************************************************************** Function: Displays 2 numbers Input : x,y: starting coordinate;Len: The number of digits;Size: font size;Mode: mode, 0, fill mode, 1, overlay mode;Num: value (0 ~ 4294967295); Output : none 函数功能:显示2个数字 入口参数:x,y :起点坐标; len :数字的位数; size:字体大小; mode:模式, 0,填充模式, 1,叠加模式; num:数值(0~4294967295); 返回 值:无 **************************************************************************/ void OLED_ShowNumber(u8 x,u8 y,u32 num,u8 len,u8 size) { u8 t,temp; u8 enshow=0; for(t=0;t<len;t++) { temp=(num/oled_pow(10,len-t-1))%10; if(enshow==0&&t<(len-1)) { if(temp==0) { OLED_ShowChar(x+(size/2)*t,y,' ',size,1); continue; } else enshow=1; } OLED_ShowChar(x+(size/2)*t,y,temp+'0',size,1); } } /************************************************************************** Function: Display string Input : x,y: starting coordinate;*p: starting address of the string Output : none 函数功能:显示字符串 入口参数:x,y :起点坐标; *p:字符串起始地址 返回 值:无 **************************************************************************/ //用16字体 void OLED_ShowString(u8 x,u8 y,const u8 *p) { #define MAX_CHAR_POSX 122 #define MAX_CHAR_POSY 58 while(*p!='\0') { if(x>MAX_CHAR_POSX){x=0;y+=16;} if(y>MAX_CHAR_POSY){y=x=0;OLED_Clear();} OLED_ShowChar(x,y,*p,12,1); x+=8; p++; } } /************************************************************************** Function: Initialize the OLED Input : none Output : none 函数功能:初始化OLED 入口参数: 无 返回 值:无 **************************************************************************/ void OLED_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能PB端口时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; //2M GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIO RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开A口时钟。 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; //设为输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); PWR_BackupAccessCmd(ENABLE); //允许修改RTC 和后备寄存器 RCC_LSEConfig(RCC_LSE_OFF); //关闭外部低速外部时钟信号功能 后,PC13 PC14 PC15 才可以当普通IO用。 BKP_TamperPinCmd(DISABLE); //关闭入侵检测功能,也就是 PC13,也可以当普通IO 使用 PWR_BackupAccessCmd(DISABLE);//禁止修改后备寄存器 OLED_RST_Clr(); delay_ms(100); OLED_RST_Set(); OLED_WR_Byte(0xAE,OLED_CMD); //关闭显示 OLED_WR_Byte(0xD5,OLED_CMD); //设置时钟分频因子,震荡频率 OLED_WR_Byte(80,OLED_CMD); //[3:0],分频因子;[7:4],震荡频率 OLED_WR_Byte(0xA8,OLED_CMD); //设置驱动路数 OLED_WR_Byte(0X3F,OLED_CMD); //默认0X3F(1/64) OLED_WR_Byte(0xD3,OLED_CMD); //设置显示偏移 OLED_WR_Byte(0X00,OLED_CMD); //默认为0 OLED_WR_Byte(0x40,OLED_CMD); //设置显示开始行 [5:0],行数. OLED_WR_Byte(0x8D,OLED_CMD); //电荷泵设置 OLED_WR_Byte(0x14,OLED_CMD); //bit2,开启/关闭 OLED_WR_Byte(0x20,OLED_CMD); //设置内存地址模式 OLED_WR_Byte(0x02,OLED_CMD); //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10; OLED_WR_Byte(0xA1,OLED_CMD); //段重定义设置,bit0:0,0->0;1,0->127; OLED_WR_Byte(0xC0,OLED_CMD); //设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 OLED_WR_Byte(0xDA,OLED_CMD); //设置COM硬件引脚配置 OLED_WR_Byte(0x12,OLED_CMD); //[5:4]配置 OLED_WR_Byte(0x81,OLED_CMD); //对比度设置 OLED_WR_Byte(0xEF,OLED_CMD); //1~255;默认0X7F (亮度设置,越大越亮) OLED_WR_Byte(0xD9,OLED_CMD); //设置预充电周期 OLED_WR_Byte(0xf1,OLED_CMD); //[3:0],PHASE 1;[7:4],PHASE 2; OLED_WR_Byte(0xDB,OLED_CMD); //设置VCOMH 电压倍率 OLED_WR_Byte(0x30,OLED_CMD); //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc; OLED_WR_Byte(0xA4,OLED_CMD); //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) OLED_WR_Byte(0xA6,OLED_CMD); //设置显示方式;bit0:1,反相显示;0,正常显示 OLED_WR_Byte(0xAF,OLED_CMD); //开启显示 OLED_Clear(); }
这是我OLED的驱动,针对的是0.96寸的6针OLED驱动屏幕,
CX,CY,CW,CH都在OLED中展现出来
最后,感觉我自己写的博客不咋样啊,我还是把我的代码分享给大家吧,
链接:https://pan.baidu.com/s/1uObuYJZnwb1hMOb5AYUdQw
提取码:1234
–来自百度网盘超级会员V3的分享
新手初次写博客,希望大家能够一起交流
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。