• pycharm控制STM32F103ZET6拍照并上位机接收显示(OV7670、照相机、STM32、TFTLCD)


    准备工作

    一、硬件及片上资源:
    1,串口1(波特率:921600,PA9/PA10通过usb转ttl连接电脑,或者其他方法)上传图片数据至上位机
    2,串口2(波特率:115200,PA2/PA3通过usb转ttl连接电脑,或者其他方法)控制拍照
    3,2.8寸TFTLCD模块
    4,按键KEY1(PE3)
    5,SD卡
    6,外部中断8(PA8,用于检测OV7670的帧信号)
    7,定时器6(用于打印摄像头帧率)
    8,带FIFO的OV7670摄像头模块
    9、STM32F103ET6
    10、USB转TTL模块两个
    11、STLINK(其他下载器也可以:DSP、JTAG…)
    二、软件:
    1、pycharm
    2、keil5-MDK
    3、串口调试助手(XCOM)
    三、连线:
    在代码中都有。

    最终效果

    开机的时候先检测字库,然后检测SD卡根目录是否存在PHOTO文件夹,如果不存在则创建,如果创建失败,则报错(提示拍照功能不可用)。在找到SD卡的PHOTO文件夹后,开始初始化OV7670,在初始化成功之后,就一直在TFTLCD上显示OV7670拍到的内容。当上位机按下拍照时,进行拍照,此时DS1亮,照片通过串口发送至上位机,当DS1灭之后,拍照成功。(也可以自己改一改用板子的按键控制拍照)
    1、实物图:
    在这里插入图片描述
    2、上位机效果:
    在这里插入图片描述

    一、下位机

    代码过多过长这里只展示重要的:

    1、主函数

     int main(void)
     {	 
    						 
    	u8 res;	
    	u8 *pname;				//带路径的文件名		   
    	u16 i;	
        
    	delay_init();	    	 //延时函数初始化	  
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
    	uart_init1(921600);	 	//串口初始化为115200
        uart_init2(115200);	
     	usmart_dev.init(72);		//初始化USMART		
     	LED_Init();		  			//初始化与LED连接的硬件接口
    	KEY_Init();					//初始化按键
    	LCD_Init();			   		//初始化LCD    
    	BEEP_Init();        		//蜂鸣器初始化	 
    	W25QXX_Init();				//初始化W25Q128
     	my_mem_init(SRAMIN);		//初始化内部内存池
    	exfuns_init();				//为fatfs相关变量申请内存  
     	f_mount(fs[0],"0:",1); 		//挂载SD卡 
     	f_mount(fs[1],"1:",1); 		//挂载FLASH. 
    	EXTI8_Init();				//使能定时器捕获
    	EXTIX_Init();
    	POINT_COLOR=RED;      
    
    	USART_SendData(USART2,0x31);
        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)!=SET);//等待发送结束								   						    
     	pname=mymalloc(SRAMIN,30);	//为带路径的文件名分配30个字节的内存		    
     	while(pname==NULL)			//内存分配出错
     	{	    
    		Show_Str(30,190,240,16,"内存分配失败!",16,0);
    		delay_ms(200);				  
    		LCD_Fill(30,190,240,146,WHITE);//清除显示	     
    		delay_ms(200);				  
    	}   											  
    	while(OV7670_Init())//初始化OV7670
    	{
    		Show_Str(30,190,240,16,"OV7670 错误!",16,0);
    		delay_ms(2000);
    	    LCD_Fill(30,190,239,206,WHITE);
    		delay_ms(2000);
    	}
    	delay_ms(10000);
     	Show_Str(30,190,200,16,"OV7670 normal",16,0);
    	delay_ms(14444);	
        delay_ms(14444);										  
    	
    	OV7670_Light_Mode(0);//0
    	OV7670_Color_Saturation(0);
    	OV7670_Brightness(2);//0
    	OV7670_Contrast(2);//0
    	OV7670_Special_Effects(0);
    	OV7670_Window_Set(12,176,240,320);	//设置窗口	  
      	OV7670_CS=0;				    		    
    	LCD_Clear(BLACK);
     	while(1)
    	{	
    		if(Res_com2 == 0x31)
    		{
    		   delay_ms(1800);
               Res_com2 = 0;
               //Res_com = 0;
    		   LED1=0;	//点亮DS1,提示正在拍照
    		 
    		   res=bmp_encode(pname,(lcddev.width-240)/2,(lcddev.height-320)/2,240,320,0);
    		   Show_Str(40,130,240,12,"picture_capture_finish!",12,0);	
    			LED1=1;//关闭DS1
    			delay_ms(1800);//等待1.8秒钟
    			LCD_Clear(BLACK);
    		    //jjj = 0;
    		}
    		else 
    		    delay_ms(5);
    			camera_refresh();//更新显示
    			i++;
    			if(i==10000)//DS0闪烁.
    			{
    				i=0;
    				LED0=!LED0;
    			}
    	}	   										    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    2、OV7670初始化

    u8 OV7670_Init(void)
    {
    	u8 temp;
    	u16 i=0;	  
    	//设置IO
     	GPIO_InitTypeDef  GPIO_InitStructure;
     	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOG|RCC_APB2Periph_AFIO, ENABLE);	 //使能相关端口时钟
     
    	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_8; 	//PA8 输入 上拉
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	GPIO_SetBits(GPIOA,GPIO_Pin_8);
    		
     	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4;				 // 端口配置
     	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
     	GPIO_Init(GPIOB, &GPIO_InitStructure);
     	GPIO_SetBits(GPIOB,GPIO_Pin_3|GPIO_Pin_4);	
    
    	
    	GPIO_InitStructure.GPIO_Pin  = 0xff; //PC0~7 输入 上拉
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
     	GPIO_Init(GPIOC, &GPIO_InitStructure);
    	 
    	
      GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_6;  
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
     	GPIO_Init(GPIOD, &GPIO_InitStructure);
    	GPIO_SetBits(GPIOD,GPIO_Pin_6);
    	
    	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_14|GPIO_Pin_15;  
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
     	GPIO_Init(GPIOG, &GPIO_InitStructure);
    	GPIO_SetBits(GPIOG,GPIO_Pin_14|GPIO_Pin_15);
    	
      GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);	//SWD
    
     	SCCB_Init();        		//初始化SCCB 的IO口	   	  
     	if(SCCB_WR_Reg(0x12,0x80))return 1;	//复位SCCB	  
    	delay_ms(50);  
    	//读取产品型号
     	temp=SCCB_RD_Reg(0x0b);   
    	if(temp!=0x73)return 2;  
     	temp=SCCB_RD_Reg(0x0a);   
    	if(temp!=0x76)return 2;
    	//初始化序列	  
    	for(i=0;i
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    二、上位机

    1、控制拍照

    # 和另一个.py文件一起运行,点击可视化界面的拍照即可拍照
    import serial
    import time
    import tkinter as tk
    
    def send_command():
        command_to_send = b'\x31\r\n'
        ser.write(command_to_send)
        # You can add any additional actions or updates here
    
    # Create the serial connection
    ser = serial.Serial('COM13', 115200)
    time.sleep(2)
    
    # Create the Tkinter window
    window = tk.Tk()
    window.title("Serial control take photos")
    
    # Create a button to send the command
    send_button = tk.Button(window, text="拍照", command=send_command)
    send_button.pack(pady=20)
    
    # Run the Tkinter main loop
    window.mainloop()
    
    # Close the serial connection when the window is closed
    ser.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2、接收图片数据

    # 用波特率为921600的串口接收下位机上传的图片数据,接受的图片会有一点色彩问题,怀疑是传输出现的问题,用高斯滤波就可以基本滤除。
    # 注意要连接好串口,板子上好电,这个代码才能运行不然报错找不到串口
    import serial
    import struct
    import numpy as np
    from PIL import Image
    import matplotlib.pyplot as plt
    import cv2
    import os
    
    ser = serial.Serial('COM5', 921600)
    
    # 初始化一个空的二维数组,用于存储接收到的数据
    received_data = np.zeros((320, 240), dtype=np.uint16)
    
    # 初始化图像计数器
    image_counter = 1
    # 全局变量,保存当前索引
    image_index = 0
    # 设置图像保存目录
    save_dir = "pic_receive"
    
    # 如果目录不存在,则创建目录
    if not os.path.exists(save_dir):
        os.makedirs(save_dir)
    
    while True:
        # 初始化索引和计数器
        received_index = 0
        row = 0
        col = 0
    
        # 接收数据直到收到足够的数据
        print("可以发送数据")
        while received_index < 240 * 320:
            # 读取两个字节的数据
            data = ser.read(2)
    
            # 解析uint16数据
            color_value = struct.unpack('>H', data)[0]  # '>H'表示大端字节序的uint16
    
            # 将数据存入二维数组
            received_data[row, col] = color_value
            col += 1
            received_index += 1
    
            # 判断是否接收完一行数据
            if col >= 240:
                col = 0
                row += 1
    
                # 如果接收完一帧数据,进行解析和显示
                if row >= 320:
                    # 解析RGB565格式的数据为RGB888格式
                    # 不知道什么原因发上来列发生错误,进行重组
                    selected_columns1 = received_data[:, 0:47]
                    selected_columns2 = received_data[:, 47:240]
                    merged_array = np.concatenate((selected_columns2, selected_columns1), axis=1)
    
                    rgb888_data = []
                    for i in range(320):
                        for j in range(240):
                            color_value = merged_array[i, j]
                            r = (color_value & 0xF800) >> 8
                            g = (color_value & 0x07E0) >> 3
                            b = (color_value & 0x001F) << 3
                            rgb888_data.append((r, g, b))
    
                    # 创建RGB888格式的图像对象
                    image = Image.new('RGB', (240, 320))
    
                    # 将RGB888格式的数据填充到图像对象中
                    image.putdata(rgb888_data)
    
                    # 保存图像到文件夹
                    image_filename = os.path.join(save_dir, f"{image_counter:010d}.png")
                    image.save(image_filename)
    
                    # 增加图像计数器
                    image_counter += 1
    
                    # 显示图像
                    #plt.imshow(image)
                    #plt.show()
                    # 重置二维数组,准备接收下一帧数据
                    received_data = np.zeros((320, 240), dtype=np.uint16)
                    row = 0
                    col = 0
                    # 清空串口接收缓冲区
                    ser.flushInput()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    三、资源获取

    我用夸克网盘分享了「照相机+双串口+上位机接收并显示.rar」,点击链接即可保存。打开「夸克APP」,无需下载在线播放视频,畅享原画5倍速,支持电视投屏。
    链接:https://pan.quark.cn/s/125911f5def1
    提取码:Za2E

  • 相关阅读:
    linux安装CUDA、cuDNN以及ytorch-gpu
    【Leetcode】面试题 01.09. 字符串轮转
    vue-quill-editor富文本编辑器-扩展表格、图片调整大小
    leetcode(力扣) 718. 最长重复子数组 & 1143. 最长公共子序列 (动态规划)
    【Windows 常用工具系列 12 -- win11怎么设置不睡眠熄屏 |win11设置永不睡眠的方法】
    经典双指针算法试题(一)
    黑马程序员spring+springMVC+Maven高级+springboot+MyBatisPlus总结之spring介绍和IoC
    探索人工智能的世界:构建智能问答系统之前置篇
    树和二叉树的相关概念及结构
    RabbitMQ
  • 原文地址:https://blog.csdn.net/m0_71523511/article/details/136309226