简易摄像机制作

利用STM32和openmv制作简易摄像机

单片机部分

介绍

  • 单片机在该项目中作为上位机控制openmv
  • 单击按钮为拍摄照片,双击按钮为开启闪光灯拍照

即按下按钮后一秒内再按下可开启闪光灯

流程图部分

代码主要集中于中断,所以主函数的流程图就一笔带过

  • 主函数
    img
  • 中断部分

10:15线外部中断
img
定时器中断
img

初始化硬件部分

  • 选择自己的芯片/开发板,这里以我自己的为例
    img

配置时钟

  • 使能RCC,选择高速外部晶振
    img
  • 将时钟树按如下方式配置,使APB2频率为84MHz
    img

配置GPIO

  • 将PA1, PA2设为上拉推挽输出模式
  • PC13设为下降沿触发外部中断模式
  • 将PA5设置为浮空态输出模式
    img
  • 使能10:15线外部中断
    img

配置定时器

  • 将时钟源设置为内部时钟
  • 将定时器3预分频设为840 - 1,重装载值设置为13000 - 1,向上计数
    img

即将上位机的摄像模式保持1.3秒,防止下位机检测到摄像信号时间过短

  • 使能定时器3中断
    img

配置中断向量控制器

  • 将优先级组设为1位
  • 将定时器3中断设为1抢占优先级,0子优先级
  • 将10:15线外部中断设为0抢占优先级,0子优先级
    img

代码部分

  • 在外部10:15线中断函数中写入以下内容:
/**
  * @brief This function handles EXTI line[15:10] interrupts.
  */
void EXTI15_10_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI15_10_IRQn 0 */
	//判断当前摄像机状态
	if(shutting_mode){
		if(!flash_mode){//判断是否开启闪光灯
			flash_mode = true;
			HAL_GPIO_WritePin(Flash_CMD_GPIO_Port, Flash_CMD_Pin, GPIO_PIN_SET);
		}
	}
		else{
		shutting_mode = true;//将状态设为摄影中
		HAL_GPIO_WritePin(Shut_CMD_GPIO_Port, Shut_CMD_Pin, GPIO_PIN_SET);
		HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
		HAL_TIM_Base_Start_IT(&htim3);
	}
  /* USER CODE END EXTI15_10_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(B1_Pin);
  /* USER CODE BEGIN EXTI15_10_IRQn 1 */

  /* USER CODE END EXTI15_10_IRQn 1 */
}
  • 在定时器中断中写入以下内容:
/**
  * @brief This function handles TIM3 global interrupt.
  */
void TIM3_IRQHandler(void)
{
  /* USER CODE BEGIN TIM3_IRQn 0 */
	if(flash_mode){//判断是否开启闪光灯
		HAL_GPIO_WritePin(Flash_CMD_GPIO_Port, Flash_CMD_Pin, GPIO_PIN_RESET);
		flash_mode = false;
	}
	shutting_mode = false;
    //置低PA1,PA2电平
	HAL_GPIO_WritePin(Shut_CMD_GPIO_Port, Shut_CMD_Pin, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
  /* USER CODE END TIM3_IRQn 0 */
  HAL_TIM_IRQHandler(&htim3);
  /* USER CODE BEGIN TIM3_IRQn 1 */
  HAL_TIM_Base_Stop_IT(&htim3);//停止计时,注意一定要写在HAL_TIM_IRQHandler后方!
  /* USER CODE END TIM3_IRQn 1 */
}

OpenMV部分

功能介绍

  • OpenMV作为下位机,负责拍摄和开机相机的闪光灯(即OpenMV内置LED灯)
  • P0脚负责接收摄像信号,高电平触发
  • P1脚负责接收闪光灯信号,高电平处触发
  • 在接收到摄像信号一秒内,若接收到闪光灯信号则开启闪光灯
  • 每次拍摄完成后,将图像保存至SD卡

代码部分

  • 在编译器中写入以下内容
"""
OpenMV相机控制 - 使用内置RGB LED作为补光灯
功能:
1. P1: 外部单片机控制补光灯(输入检测,高电平时点亮内置LED白光)
2. P0: 外部单片机触发拍照(输入检测,高电平持续1秒触发)
3. 图片保存到SD卡
4. 使用内置RGB LED同时点亮作为白光补光灯
"""

import sensor
import image
import time
import os
import pyb
from machine import Pin

# ==================== 系统初始化 ====================
print("=== OpenMV相机系统(内置LED补光)===")
print("引脚配置:")
print("P0: 拍照触发输入(高电平持续1秒)")
print("P1: 补光灯控制输入(高电平开启)")
print("补光灯: 使用内置RGB LED(同时点亮)")
print("-" * 40)

# 摄像头初始化
sensor.reset()
sensor.set_pixformat(sensor.RGB565)  # 彩色图片
sensor.set_framesize(sensor.QVGA)    # 320x240分辨率
sensor.skip_frames(time=2000)        # 等待摄像头稳定

# ==================== GPIO配置 ====================
# 输入引脚(由外部单片机控制)
trigger_pin = Pin('P0', Pin.IN, Pin.PULL_DOWN)    # 拍照触发输入
light_control_pin = Pin('P1', Pin.IN, Pin.PULL_DOWN)  # 补光灯控制输入

# 内置LED配置
# OpenMV H7/H7 Plus有3个内置LED:
# LED(1): 红色 (引脚P6)
# LED(2): 绿色 (引脚P7)
# LED(3): 蓝色 (引脚P8)
# 同时点亮这三个LED可模拟白光
red_led = pyb.LED(1)   # 红色LED
green_led = pyb.LED(2) # 绿色LED
blue_led = pyb.LED(3)  # 蓝色LED

# 初始状态:关闭所有LED
red_led.off()
green_led.off()
blue_led.off()

# 补光灯状态
light_status = False

# ==================== 存储初始化 ====================
def init_storage():
    """初始化存储设备"""
    print("初始化存储设备...")

    try:
        # 检查SD卡
        if 'sd' in os.listdir('/'):
            # 创建保存目录
            save_dir = "/sd/captures"
            try:
                os.mkdir(save_dir)
                print(f"创建目录: {save_dir}")
            except OSError:
                print(f"使用目录: {save_dir}")

            # 检查可用空间
            import uos
            stat = uos.statvfs('/sd')
            free_kb = (stat[0] * stat[3]) / 1024
            print(f"SD卡可用空间: {free_kb:.1f} KB")

            return save_dir
        else:
            print("警告: 未检测到SD卡,使用内部Flash")
            return "/"

    except Exception as e:
        print(f"存储初始化失败: {e}")
        return "/"

# 初始化存储
save_path = init_storage()
print(f"图片保存路径: {save_path}")

# ==================== 内置LED控制函数 ====================
def set_builtin_light(state):
    """控制内置LED作为补光灯"""
    global light_status
    light_status = state

    if state:
        # 同时点亮三个LED(红+绿+蓝 ≈ 白光)
        red_led.on()
        green_led.on()
        blue_led.on()
        print("内置补光灯: 开启")
    else:
        # 关闭所有LED
        red_led.off()
        green_led.off()
        blue_led.off()
        print("内置补光灯: 关闭")

def update_light_status():
    """根据P1输入状态更新内置补光灯"""
    # 读取P1引脚状态
    new_status = (light_control_pin.value() == 1)

    # 状态变化时更新
    if new_status != light_status:
        set_builtin_light(new_status)

# ==================== 拍照功能 ====================
photo_count = 0
last_photo_time = 0
TRIGGER_DURATION = 1000  # 触发持续时间:1秒

def take_photo():
    """拍照并保存到存储"""
    global photo_count, last_photo_time

    # 防重复触发检查(至少间隔0.5秒)
    current_time = time.ticks_ms()
    if current_time - last_photo_time < 500:
        print("防重复触发,请稍候...")
        return False

    last_photo_time = current_time

    try:
        # 记录补光灯原始状态
        original_light_state = light_status


        # 拍照
        print("正在拍照...")
        img = sensor.snapshot()

        # 生成带时间戳的文件名
        photo_count += 1
        timestamp = time.localtime()
        filename = f"IMG_{timestamp[0]:04d}{timestamp[1]:02d}{timestamp[2]:02d}_"
        filename += f"{timestamp[3]:02d}{timestamp[4]:02d}{timestamp[5]:02d}.jpg"

        # 完整保存路径
        if save_path == "/":
            full_path = filename
        else:
            full_path = f"{save_path}/{filename}"

        # 保存图片(质量85%,兼顾质量和文件大小)
        img.save(full_path, quality=85)

        # 获取文件大小
        try:
            file_size = os.stat(full_path)[6]
            print(f"✓ 已保存: {filename}")
            print(f"  大小: {file_size/1024:.1f} KB")
            print(f"  累计照片: {photo_count}")
        except:
            print(f"✓ 已保存: {filename}")

        # 恢复补光灯原始状态
        if not original_light_state:
            time.sleep_ms(100)  # 拍照后保持补光灯短暂亮起
            set_builtin_light(False)

        return True

    except Exception as e:
        print(f"✗ 拍照失败: {e}")
        # 确保补光灯状态恢复
        if not original_light_state:
            set_builtin_light(False)
        return False

# ==================== 主控制逻辑 ====================
print("\n系统初始化完成")
print("等待外部控制信号...")
print("控制方式:")
print("1. 设置P1为高电平 -> 打开内置补光灯")
print("2. 设置P0为高电平并保持1秒 -> 触发拍照")
print("-" * 30)

# 触发状态变量
trigger_active = False
trigger_start_time = 0
last_trigger_progress = 0

# 主循环
try:
    while True:
        # 1. 更新补光灯状态
        update_light_status()

        # 2. 检测拍照触发
        trigger_state = trigger_pin.value()

        if trigger_state == 1:  # 检测到高电平
            if not trigger_active:
                # 开始触发计时
                trigger_start_time = time.ticks_ms()
                trigger_active = True
                last_trigger_progress = 0
                print("检测到拍照触发信号,请保持高电平1秒...")

            # 计算持续时间
            current_time = time.ticks_ms()
            duration = time.ticks_diff(current_time, trigger_start_time)

            # 显示进度(每10%更新一次)
            current_progress = int(min(duration / TRIGGER_DURATION * 10, 10))
            if current_progress > last_trigger_progress:
                last_trigger_progress = current_progress
                if current_progress < 10:
                    print(f"触发进度: {current_progress * 10}%")

            # 触发条件满足
            if duration >= TRIGGER_DURATION:
                print("触发条件满足,正在拍照...")
                if take_photo():
                    print("拍照完成")
                else:
                    print("拍照失败")

                # 触发完成,等待引脚释放
                trigger_active = False
                time.sleep_ms(100)  # 短暂延时

                # 等待引脚变为低电平
                while trigger_pin.value() == 1:
                    time.sleep_ms(10)

                print("等待下一次触发...")

        else:  # 低电平
            if trigger_active:
                # 触发中断
                trigger_active = False
                if time.ticks_diff(time.ticks_ms(), trigger_start_time) > 100:
                    print("触发中断,重新等待...")

        # 3. 短暂延时,降低CPU使用率
        time.sleep_ms(20)

except KeyboardInterrupt:
    print("\n手动停止程序")

except Exception as e:
    print(f"\n程序异常: {e}")

finally:
    # 确保补光灯关闭
    set_builtin_light(False)
    print("补光灯已关闭")
    print("程序结束")

拼接线路

  • 将单片机的PA1与OpenMV的P0连接
  • 将单片机的PA2与OpenMV的P1连接

实验效果

  • 单击单片机按钮,仅拍照(红灯为保存失败,因为没接SD卡)
    img
  • 双击单片机按钮,开启闪光灯并拍照
    img
  • 截获图片:
    img
posted @ 2026-03-12 20:05  奶龙大王  阅读(25)  评论(0)    收藏  举报