1038|1

43

帖子

2

TA的资源

一粒金砂(高级)

楼主
 

【DigiKey创意大赛】基于openmv的光线传感功能的光线区域探测 [复制链接]

  本帖最后由 Zhao_kar 于 2024-1-14 03:22 编辑
DigiKey创意大赛】基于openmv的光线传感功能的光线区域探测
作者:Zhao_kar

一、作品简介

本项目是一个基于openmv的光线区域探测器是一个基于多光线传感器的智能系统,旨在实时监测并判别环境中的光照强度分布,从而判断所处区域。通过使用四个光线传感器,系统能够感知不同方向的光线,并利用算法确定环境的光照情况。同时,项目整合了OpenMV摄像头,将图像转换为灰度值,并通过映射函数将其转化为与光线传感器相一致的亮度百分比。

二、系统框图
1、设计思路:
  • 根据原先的安排,确实是打算做一个光线传感器的反应器,但是经过研究后,发现单独的使用openmv的灰度去计算并且加自适应算法并不合适,而且最重要的一点是,因为传感器的问题,导致进光这一点很尴尬,比如强光远会导致得到低灰度等不稳定情况,经过综合考量,变更为一个基于光线传感器的区域检测,并且使用三个外接传感器和ADC的同步采样进行调试。
  • 具体展示为:使用四个光线传感器分布在环形的不同位置,每个传感器对应一个区域(A、B、C、OpenMV中心点)。OpenMV摄像头位于中心,用于获取图像灰度值。
  • 补充说明:具体为STM32采集ADC,要做同步采样,因为只有F1且他不能三路同步DMA,所以使用两路+串口触发openmv的ADC去做第三路采样,最后经过算法和映射,得到数据,根据数据得到区域值,并且由串口+LED的形式进行反馈。
2、系统软硬件介绍(比较简单):
  • 基于光敏电阻的光线传感器*1
  • 光电二极管的光线传感器*2
  • Openmv核心板*1
  • STM32F1核心板*1
3、代码流程图+实现框图
(1)、openmv端的区域检测
图1——openmv端
(2)STM32的ADC同步采样
图2——ADC同步采样
三、各部分功能说明(图文结合)
(各部分实现的功能说明及讲解,以图文结合的展示)
  1. 当光线聚集于中心时,可以看到,此时LED没有反应,且IDE的串口可以看到是返回MAX。
  2. 当光线在A区时,此时有红灯+返回A。
  3. 同理在B区时,蓝灯+返回B。
  4. 同理在C区时,绿灯+返回C。

           

四、作品源码

  1. 补充:本次项目代码会全部上传,下面为STM32的主要代码部分+openmv的主要部分,重点讲这部分的思路和实现,我会把完整的实现流程和教程放在六和七两个地方。(word我没插件,代码会放在网页里面编辑)
  2. 下载的源码:download.eeworld.com.cn/detail/Zhao_kar/630774
  3. STM32的ADC初始化:
      /* USER CODE BEGIN 2 */
    	//dac_test
    	HAL_TIM_Base_Start(&htim2);//弿启定时器2
    	HAL_DAC_Start_DMA(&hdac,DAC_CHANNEL_1,(uint32_t*)Sine12bit,100,DAC_ALIGN_12B_R);//弿启输凿
    	
    	//adc
    	HAL_ADCEx_Calibration_Start(&hadc1);                  //AD校准,F4不用校准没用这行函数〿
    	HAL_ADCEx_Calibration_Start(&hadc3);   
    	
    	HAL_ADC_Start_DMA(&hadc1, (uint32_t *)ADC1_BUFF_16, 200); //让ADC1去采雿200个数,存放到adc_buff数组釿
    	HAL_ADC_Start_DMA(&hadc3, (uint32_t *)ADC3_BUFF_16, 200); //让ADC1去采雿200个数,存放到adc_buff数组釿
    	HAL_TIM_Base_Start(&htim8);                           //弿启定时噿3
    //while (!AdcConvEnd)                                   //等待转换完毕
    //    ;
    //for (uint16_t i = 0; i < 200; i++)
    //{
    //    printf("%.3f\n", adc_buff[i] * 3.3 / 4095); //数据打印,查看结枿
    //}
    //Usart2Printf("hello usart%d\n",2);
    //Usart1Printf("hello usart%d\n",1);
    
      /* USER CODE END 2 */
    
    

     

  4. STM32的循环函数(补充:ADC同步采样见后续):
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
    		// 在某个条件下判断是否霿要处理ADC采集到的数据
        if (AdcConvEnd) {
            // 处理ADC采集到的数据,例如打印到串口
            for (uint16_t i = 0; i < 200; i++) {
    					
    					float reversedVoltage_1 = 3.3f - (ADC1_BUFF_16[i] * 3.3f / 4095.0f); // 反转电压倿
    					hundred_data_1= reversedVoltage_1 / 3.3f * 100.0f;
    					float reversedVoltage_2 = 3.3f - (ADC3_BUFF_16[i] * 3.3f / 4095.0f); // 反转电压倿
    					hundred_data_2 = reversedVoltage_2 / 3.3f * 100.0f;	
    				}
    				Usart1Printf("%.3f\n", hundred_data_1);  // 数据打印,查看结枿
    				Usart1Printf("%.3f\n", hundred_data_2);  // 数据打印,查看结枿
            // 清除标志,准备下丿轮采雿
            AdcConvEnd = 0;
    				HAL_TIM_Base_Stop(&htim8);
    				HAL_ADC_Start_DMA(&hadc1, (uint32_t *)ADC1_BUFF_16, 200);
    				HAL_ADC_Start_DMA(&hadc3, (uint32_t *)ADC3_BUFF_16, 200);
    				HAL_TIM_Base_Start(&htim8);                           //弿启定时噿3
    				HAL_Delay(2000);
    			}
      }
      /* USER CODE END 3 */
    }

     

  5. OPENMV的完整代码:
    import time
    import sensor
    import image
    from pyb import UART, ADC, LED
    
    # 初始化
    sensor.reset() # 初始化感光
    sensor.set_pixformat(sensor.GRAYSCALE)#设置像素模式,这里作为测试,用的是灰度
    sensor.set_framesize(sensor.QVGA)# 图像大小,320——240
    sensor.skip_frames(time=2000)# 跳过一些帧
    clock = time.clock() # 帧率
    
    # 初始化串口
    uart = UART(3, 115200)  # 3是串口号,115200是波特率
    
    # 初始化ADC
    adc = ADC("P6")  # 根据实际连接的引脚修改
    
    # 初始化LED
    red_led = LED(1)  # 红色LED
    blue_led = LED(3)  # 蓝色LED
    green_led = LED(2)  # 绿色LED
    
    # 映射函数,将ADC采集的电压映射到0-100的范围
    def map_to_percentage(voltage):
        min_voltage = 0.0
        max_voltage = 3.3
        min_percentage = 0.0
        max_percentage = 100.0
    
        percentage = ((voltage - min_voltage) / (max_voltage - min_voltage)) * (max_percentage - min_percentage) + min_percentage
        return percentage
    
    def map_gray_to_percentage(value):
        min_value = 0
        max_value = 255
        min_percentage = 0.0
        max_percentage = 100.0
    
        # 映射到0-100的范围
        percentage = ((value - min_value) / (max_value - min_value)) * (max_percentage - min_percentage) + min_percentage
        return min(percentage*2, 100.0)
    
    def adc_sample():
        adc_value = adc.read()  # 读取ADC的值
        voltage = adc_value * 3.3 / 4095  # 将ADC值转换为电压值
        return voltage
    
    def get_average_brightness():
        clock.tick()
        img = sensor.snapshot()
        # 获取图像统计信息
        stats = img.get_statistics()
        # 获取平均灰度值
        average_gray = stats.mean()
        #可以看到,此时不用遍历方法,使用官方的函数仍能得到想要的值,同时帧率也很高
        #print("Average Gray Value:", average_gray)
        #print(clock.fps())             #显示帧率
        return average_gray
    
    
    def receive_and_sample_data():
        buffer = ""  # 用于存储接收到的数据
        receive_data1 = None  # 存储第一个数据
        receive_data2 = None  # 存储第二个数据
        adc_result = None  # 存储ADC采样值
        while True:
            average_gray = get_average_brightness()
            center_gray_data = map_gray_to_percentage(average_gray)
            if uart.any():  # 检查是否有数据可用
                char = uart.read(1).decode('utf-8')  # 逐个字符读取并解码
                buffer += char  # 将字符追加到缓冲区
    
                if char == '\n' and receive_data1 is None:  # 如果遇到第一个换行符
                    try:
                        receive_data1 = float(buffer.strip())  # 尝试将接收到的字符串转换为浮点数
                        adc_result = adc_sample()  # 对ADC进行采样
                        mapped_percentage = map_to_percentage(adc_result)  # 映射到0-100的范围
    
    
                    except ValueError:
                        print("Invalid Data 1: ", buffer)
                    buffer = ""  # 重置缓冲区
                elif char == '\n' and receive_data2 is None:  # 如果遇到第二个换行符
                    try:
                        receive_data2 = float(buffer.strip())  # 尝试将接收到的字符串转换为浮点数
                        print("ADC Percentage 1: %.3f" % mapped_percentage)
                        print("Received Data 1: %.3f" % receive_data1)
                        print("Received Data 2: %.3f" % receive_data2)
                        print("Center_gray_data: %.3f" % center_gray_data)
                    except ValueError:
                        print("Invalid Data 2: ", buffer)
                    buffer = ""  # 重置缓冲区
    
                    # 执行判别逻辑
                    min_value = min(mapped_percentage, receive_data2, receive_data1)
                    max_value = max(mapped_percentage, receive_data2, receive_data1,center_gray_data)
                    #Center_max_value = max(min_value,center_gray_data)
                    # 控制LED颜色
                    if max_value == center_gray_data:
                        red_led.off()
                        blue_led.off()
                        green_led.off()
                        print("Result:MAX")
                    elif min_value == receive_data2:
                        red_led.on()  # A,红色
                        blue_led.off()
                        green_led.off()
                        print("Result: A")
                    elif min_value == mapped_percentage:
                        red_led.off()
                        blue_led.on()  # B,蓝色
                        green_led.off()
                        print("Result: B")
                    elif min_value == receive_data1:
                        red_led.off()
                        blue_led.off()
                        green_led.on()  # C,绿色
                        print("Result: C")
    
                    # 重置数据,为下一次接收做准备
                    receive_data1 = None
                    receive_data2 = None
                    adc_result = None
    
                    # 等待一段时间,观察LED颜色变化
                    time.sleep(5)
    
                    # 关闭LED
                    red_led.off()
                    blue_led.off()
                    green_led.off()
    
    # 在主程序中调用串口接收和ADC采样函数
    receive_and_sample_data()
    

     

五、作品功能演示视频
(视频简介+链接,视频链接:可上传到EEWorld大学堂,观看链接粘贴到作品文档和作品提交帖中,也可直接上传到作品帖中)
(我就把链接放到帖子里面了,文档我就不放了)
  1. 视频链接见:【DigiKey创意大赛】基于openmv的光线传感功能的光线区域探测-EEWORLD大学堂
  2. 视频简介:
(1)、特性:
  • 光线传感器: 通过使用三个光线传感器,能够实现对特定区域的快速、准确的光照检测。
  • STM32同步ADC采样: 借助STM32微控制器,实现了同步ADC采样,能够获取环境中的光线数据。
  • OpenMV集成:整合了OpenMV,将其作为第四个虚拟光线传感器,通过图像处理获取灰度信息,进而实现对环境亮度的感知。
  • 智能区域识别: 利用采集到的数据,我们实现了智能区域识别,可以分辨出A、B、C三个区域,并以LED灯光的形式进行直观展示。
  • USART串口通信: 通过串口通信,我们将采集到的数据传送至其他设备,实现实时监测和数据传输。
    (2)、演示流程:
  • 系统启动: 演示开始时,系统会进行初始化,激活四个光线传感器、STM32的ADC采样以及OpenMV的图像处理。
  • 环境光检测: 展示系统对环境中光线的实时检测,LED灯光会根据亮度变化进行相应调整。
  • 区域识别: 模拟A、B、C三个区域,展示系统的智能区域识别功能,LED灯光会指示当前所在区域。
  • OpenMV图像处理: 通过OpenMV获取图像的灰度信息,加入第四个虚拟光线传感器,进一步提升环境感知的准确性。
  • 数据传输: 通过USART串口通信,将采集到的数据传输至其他设备,实现对环境状况的监测和记录。
六、项目总结
(项目文字总结+帖子分享链接汇总)
  1. 帖子分享链接因为我没按照要求,发的原创,就不分享了,并且因为考试周,我从12月底就已经做完的项目,因为考试迟迟没有写帖子,实在很抱歉,接下来直接上总结和教程。
  2. 首先本次项目主要涉及的知识点为ADC的同步采样+灰度处理算法,并且因为光照度LUX没办法直接量化,只能对ADC的值进行百分比转换,从而进行比较,又因为F1没办法开ADC2的DMA去做同步采样,且我身边没有F4或者H7,所以临时改了一下方案,用openmv的ADC加串口触发采集,这样子会导致没办法同步,但是这个也是没办法的事情了。
  3. 本次项目的问题大部分还是出在了ADC同步采样问题,以及串口通信问题,因为我没用过openmv,所以串口通信这一块花了一点时间,而且我鼓捣完多串口通信后才发现openmv只有一个串口,只能使用分隔符判别的方法,放在python里面实现。
  4. 本次项目的精度很低,首先是传感器我买了三个,经过测试,最大的问题是几块钱的光敏电阻的准度很低,如果使用高频率adc不断采样,会导致错误变换,经过思考后,我觉得一个可行的方案就是搞一个光敏电阻的矩阵,类似声场那种,这样子应该行得通,但是还没去做。
  5. 最后简单说说能实现的内容:通过STM32微控制器,实现同步ADC采样,以数字化方式获取传感器采集到的模拟信号,提高数据的精确度。再利用采集到的数据,去识别,项目成功实现了对A、B、C三个区域的智能识别,以及openmv的核心区快的识别,由LED灯光直观地展示了当前环境所处的区域。
七、其他
补充,按照前面说的,接下来我会做一个简单的教程
  1. ADC的基本内容:
    (1)、首先具体的知识我就不细说了,借用我学长之前教我的时候的一个例子,以万用表为例,可以简单理解为他是感知电压大小的一个工具,那么对于ADC其实大差不差,我们需要知道的是位数,比如12位ADC就是2的12次方,也就是4096,也就是他是一个把电压量化成这个范围的一个工具,但是有限制,比如单片机的adc的0-3.3。然后补充一个公式:ADC测量值=待测电压/3.3*4096.那么通过反推即可得到待测电压。
    2)、同步采样
    我个人认为这部分是比较麻烦的地方,首先要知道正常的ADC+DMA采集,一般需要一个时钟去做触发,但是使用同步需要配置模式,会比较麻烦,所以这里靠代码的定时器去做限制,比如下图:
    在begin2有过一样的操作,首先是关闭定时器,否则adc一开启就会采样,此时会有一行代码执行顺序的延时,就不同步了,所以先关,然后开ADC,再开TIM,这样子就可以实现同步采样了。
  2. 基本概念懂了就可以直接开始了
(1)、cubemx配置
  • ADC的时钟触发
  • DMA开启和模式,用normal
  • TIM8的配置
  • 补充:DAC和TIM2和3是我刚开始测试同步采样用的,我只有一个简易的单通道信号发生器,所以波形2由dac自产测量。
  • Usart2是刚开始我打算做多串口分布工作的,后面注意到openmv只有一个串口所以没用上。
  • 时钟默认72拉满就行了,这里72M/720=100k采样率,注意一下。
(2)、keil的代码部分
  • 串口重定向配置,2h文件声明,图一在c文件里面,注:魔术棒勾选microLIB
#include <stdarg.h>
#include <stdio.h>

uint8_t UartTxBuf[200];
void Usart2Printf(const char *format,...)
{
	uint16_t len;
	va_list args;	
	va_start(args,format);
	len = vsnprintf((char*)UartTxBuf,sizeof(UartTxBuf)+1,(char*)format,args);
	va_end(args);
	HAL_UART_Transmit(&huart2, UartTxBuf, len,0xff);
}
void Usart1Printf(const char *format,...)
{
	uint16_t len;
	va_list args;	
	va_start(args,format);
	len = vsnprintf((char*)UartTxBuf,sizeof(UartTxBuf)+1,(char*)format,args);
	va_end(args);
	HAL_UART_Transmit(&huart1, UartTxBuf, len,0xff);
}


//h文件部分补充声明哦
/* USER CODE BEGIN Private defines */
void Usart2Printf(const char *format,...);
void Usart1Printf(const char *format,...);
/* USER CODE END Private defines */
  • 在下图文件加一个全局变量,用于adc的控制标志(extern uint8_t AdcConvEnd;//引入外部变量):
  • 一个用于映射函数库,一个是串口函数库,还有采样点数

    #include <stdio.h>
    #include <stdint.h>

    #define SAMP_NUM 200 //采样点数
  • 变量部分
  • 前面的配置代码:
    /* USER CODE BEGIN Includes */
    #include <stdio.h>
    #include <stdint.h>
    /* USER CODE END Includes */
    
    /* USER CODE BEGIN PD */
    #define SAMP_NUM 200 //采样点数
    /* USER CODE END PD */
    
    
    /* USER CODE BEGIN PV */
    //uint16_t adc_buff[200];//存放ADC采集的数捿
    
    uint16_t ADC1_BUFF_16[SAMP_NUM]; // ADC1的数据缓冲区
    uint16_t ADC3_BUFF_16[SAMP_NUM]; // ADC1的数据缓冲区
    float hundred_data_1;
    float hundred_data_2;
    /* 
    AdcConvEnd用来棿测ADC是否采集完毕
    0:没有采集完毿
    1:采集完毕,在stm32f1xx_it里的DMA完成中断进行修改
     */
    __IO uint8_t AdcConvEnd = 0;
    uint16_t test = 10;
    //dac_data
    const uint16_t Sine12bit[100]={	
    0x0800,0x0881,0x0901,0x0980,0x09FD,0x0A79,0x0AF2,0x0B68,0x0BDA,0x0C49,
    0x0CB3,0x0D19,0x0D79,0x0DD4,0x0E29,0x0E78,0x0EC0,0x0F02,0x0F3C,0x0F6F,	
    0x0F9B,0x0FBF,0x0FDB,0x0FEF,0x0FFB,0x0FFF,0x0FFB,0x0FEF,0x0FDB,0x0FBF,
    0x0F9B,0x0F6F,0x0F3C,0x0F02,0x0EC0,0x0E78,0x0E29,0x0DD4,0x0D79,0x0D19,
    0x0CB3,0x0C49,0x0BDA,0x0B68,0x0AF2,0x0A79,0x09FD,0x0980,0x0901,0x0881,
    0x0800,0x077F,0x06FF,0x0680,0x0603,0x0587,0x050E,0x0498,0x0426,0x03B7,	
    0x034D,0x02E7,0x0287,0x022C,0x01D7,0x0188,0x0140,0x00FE,0x00C4,0x0091,
    0x0065,0x0041,0x0025,0x0011,0x0005,0x0001,0x0005,0x0011,0x0025,0x0041,
    0x0065,0x0091,0x00C4,0x00FE,0x0140,0x0188,0x01D7,0x022C,0x0287,0x02E7,
    0x034D,0x03B7,0x0426,0x0498,0x050E,0x0587,0x0603,0x0680,0x06FF,0x077F,
    };
    
    /* USER CODE END PV */

     

  • 定时器和adc初始化
  • 循环部分
  • 串口函数说明:因为重定向,所以向那个串口发送数据,就用Usart几printf就行了。
  • 前面已经说过了原先是打算分开两个发送两个接收,但是因为openmv只有一个,所以要一个分两次发,后面在openmv那边讲怎么处理这两个数据
  • 映射函数见代码,其实就是0-3.3变0-100,而且因为这两个传感器是越亮adc值越低,所以要反转电压。
(3)、openmv部分,见下几个函数:
  • 首先stm32已经采集完并且转换了数据,那么openmv这边需要接收两个数据,为了区分,所以用换行符做一个判别,如下部分:
  • 然后是adc的采集,这边使用串口来触发
  • 第一部分详见:
  • 然后是adc采集函数和转换函数,这边四个函数有两个是adc的采集+转换,两个是灰度的采集加转换
  • 然后初始化部分
  • 最后是判别部分
  • 补充硬件连接,stm32两个adc,然后串口给openmv
  • Openmv连接一个adc+接收stm32的串口数据。
补充:附件 得捷大赛作品提交.doc (3.78 MB, 下载次数: 3)

最新回复

看着非常复杂,楼主花了不少心思来构建这个作品吧,辛苦了。   详情 回复 发表于 2024-1-14 09:42
点赞 关注
 
 

回复
举报

6841

帖子

11

TA的资源

版主

沙发
 

看着非常复杂,楼主花了不少心思来构建这个作品吧,辛苦了。

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表