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