【STM32项目】智能物联网驱动的生物样本培育与管理辅助系统(完整工程资料源码)

视频功能演示:

智能物联网驱动的生物样本培育与管理辅助系统

目录:

目录

视频功能演示:

目录:

项目简介:

一、项目目的:

[1.1 项目背景](#1.1 项目背景)

[1.2 设计意义:](#1.2 设计意义:)

二、项目内容:

[2.1 实时监控:](#2.1 实时监控:)

[2.2 光照远程控制](#2.2 光照远程控制)

[2.3 投食模块的独特设计](#2.3 投食模块的独特设计)

[2.4 温湿度监测及控制](#2.4 温湿度监测及控制)

[2.5 远程互联](#2.5 远程互联)

[2.6 系统结构](#2.6 系统结构)

三、软件开发:

[3.1 stm32代码分析:](#3.1 stm32代码分析:)

[3.1.1 硬件连接概述](#3.1.1 硬件连接概述)

[3.1.2 系统初始化与硬件配置](#3.1.2 系统初始化与硬件配置)

[3.1.3 温湿度数据处理](#3.1.3 温湿度数据处理)

[3.1.4. OLED 显示与 HMI 数据发送](#3.1.4. OLED 显示与 HMI 数据发送)

[3.1.5. HMI 命令解析](#3.1.5. HMI 命令解析)

[3.1.6. PWM 控制](#3.1.6. PWM 控制)

[3.1.7. 十六进制转十进制](#3.1.7. 十六进制转十进制)

[3.1.8. 数据发送与控制](#3.1.8. 数据发送与控制)

[3.1.9. 异常处理和错误码](#3.1.9. 异常处理和错误码)

[3.1.10. 延时与通信时序](#3.1.10. 延时与通信时序)

[3.2 摄像头源码分析:](#3.2 摄像头源码分析:)

[3.2.1 包含的库:](#3.2.1 包含的库:)

[3.2.2 结构体定义](#3.2.2 结构体定义)

[3.2.3 常量定义](#3.2.3 常量定义)

[3.2.4 全局变量](#3.2.4 全局变量)

[3.2.5 函数分析](#3.2.5 函数分析)

[3.2.6 startCameraServer 函数](#3.2.6 startCameraServer 函数)

四、尾言:

资源获取连接:

毕设&课设&项目&实训-生物样本饲养辅助系统的研究.zip资源-CSDN文库

详细资源:

摄像头源码:

stm32源码:


项目简介:

****本项目旨在为医学实验室中的生物样本提供智能化、自动化的饲养辅助系统,以提高实验数据的稳定性与可控性。系统核心功能包括实时监控自动投食模块光照模块温湿度监测及控制 、以及远程互联 。通过摄像头实现远程视频监控,支持用户通过手机或电脑端实时查看动物饲养环境,同时具备生物识别和轨迹测算功能。

自动投食模块 通过精确的机械结构确保投食的准确性,并将投食时间数据上传至云端,便于后期分析。光照模块采用可调节日光灯,通过继电器和主控芯片精确控制光照强度与时长,确保光照变量的稳定性并记录数据上传云端。

温湿度监测及控制 系统实时反馈环境数据,能够通过自动调节风扇和升温模块维持适宜的环境条件,或者通过SIM900a模块向实验人员报警。项目还实现了远程互联,通过EWelink手机平台和OneNet云平台进行远程控制与数据上传,支持分区控制功能,使不同实验动物的环境条件得到个性化管理。

同时,所有实验数据都被上传至云端,进行大数据分析,提供可视化模型,便于后期实验数据的进一步整理和分析。通过此系统,实验室能够实现自动化控制数据化管理远程监控,提升实验效率与数据精确度,减少人为误差,极大地便利了实验过程。

一、项目目的:

1.1 项目背景

随着生命科学事业的迅速发展 ,生命科学实验室已成为一个热门领域。生命科学的进步依托于长期的动物实验过程 ,这些实验为生物医学科学奠定了基础。然而,随着生命科学的不断发展,实验动物的研究价值已不仅局限于生物学领域,它已经深入到多个学科的科研中,成为现代科学实验中不可或缺的重要组成部分

在许多科研领域中,实验动物扮演着安全试验效果试验标准试验 等多重角色,确保实验结果的可靠性和科学性。新冠疫情的爆发 更加强调了医疗实验在国家和社会中的重要性,早期的疫苗研究正是通过对医学样本(如小白鼠)的实验获得了关键数据并进行改良。因此,实验动物科学对医疗事业生命科学事业的发展 具有重要意义,直接推动了国家医疗事业的进步

与此同时,作为生物实验中的关键对象,实验动物的日常饲养管理成为一个亟待解决的难题 。由于实验动物饲养具有严格的变量控制要求 ,且需综合考虑动物的生物特征与日常习性,传统的人工饲养方法在精确控制变量、数据量化处理方面存在局限性和偶然性 ,容易产生误差并影响实验结果。而通过建立生物样本饲养辅助系统 ,可以实现对饲养过程的自动化管理数据化控制 ,使用者只需监测量化数据,便可实时了解动物的饲养状态,从而减少人工操作带来的误差,同时节省劳动力,提高饲养精度和实验结果的可靠性。

1.2 设计意义:

通过构建一个智能化的生物样本饲养辅助系统 ,实现对实验动物的自动化管理精确控制 ,以保证实验数据的稳定性准确性 。系统通过集成多个模块,包括实时监控自动投食模块光照控制温湿度监测与控制 以及远程互联,为实验提供全方位的支持。

通过实时监控 功能,用户能够通过手机或电脑端实时查看动物生存空间的状态,确保动物环境的可视化远程监控自动投食模块 确保动物的进食量准确可控,并且每次投食数据将上传至云端,便于数据追踪后期分析

光照模块 则通过精确的光照控制,保持实验环境的一致性稳定性 ,避免人为调节带来的误差,确保实验结果的客观性温湿度监测及控制 模块实时反馈环境变化,并能自动调节环境条件,确保动物的生活环境符合实验要求,并通过报警系统及时通知操作人员。

最后,通过远程互联 与云平台的连接,系统支持分区控制 ,实现对不同动物生存空间的个性化管理,同时将数据上传至云端进行大数据分析 ,为后期实验的优化和精准数据支持提供保障。

二、项目内容:

2.1 实时监控:

通过摄像头 实现对动物生存空间内部的远程监控 ,用户能够实时查看动物所在环境的变化情况,从而确保实验动物的生活条件符合预设的标准和需求。通过访问树莓派的IP地址 ,用户可以在手机端或电脑端上轻松获取动物生存空间的影像数据,进行时事传输 。这种远程监控功能不仅使得实验人员无需现场操作即可获取实时影像,节省了大量的时间和人力成本,还能够在不同地点实现对动物生存环境的持续监控,保证实验数据的稳定性连续性

为了进一步提升监控系统的智能化水平,基于OpenCV 技术的视频监控功能可以实现生物识别轨迹测算等功能。通过生物识别技术,系统能够自动识别和区分不同种类或个体的实验动物,并对其进行精准的追踪和标定,确保每一只动物的行为和状态都能被精确记录。

轨迹测算功能则能够实时计算动物的活动轨迹,分析其行为模式,以便根据实验需求进行进一步的优化调整。例如,通过分析动物的活动轨迹,可以了解其在实验中的状态变化,是否存在异常行为,进而帮助实验人员及时调整饲养环境或实验设计。

所有这些功能都可以根据实际需求进行后续开发,进一步增强系统的智能化和可扩展性,使得实验过程更加高效、精准和可控。远程监控 不仅能提升实验室管理效率,也为实验数据的动态调整长期追踪提供了坚实的技术支持。

2.2 光照远程控制

光照采用日光灯 代替太阳光源,可以使光照更加均匀可控 ,从而确保实验环境的光照条件符合实验要求,避免外界光照变化对实验结果造成干扰,保证实验变量的唯一性客观性 。为了精确控制光照的强度与时长,系统采用继电器 对日光灯的工作电流进行精确控制,继电器的开关状态由主控芯片进行调节,实现对日光灯的开关与亮度的精准控制。

通过这样的控制方式,系统能够自动调节光照时长光照间隔 ,避免了传统手动调节的繁琐误差 ,提高了光照控制的稳定性一致性,从而为实验动物创造一个稳定的光照环境,有效避免了人为操作带来的不确定性。

光照时间及相关数据可通过云平台 进行上传和记录,确保每一项操作都能留下长期记录 ,为实验数据的回溯调控 提供充分的依据。这些数据也可用于后期的数据分析实验优化,帮助研究人员更好地调控光照条件,提高实验结果的准确性和可靠性。

此外,结合ESP8266模块 ,系统能够通过无线网络与云端平台进行无缝对接,进一步提升光照控制的远程操作性。通过这种方式,研究人员可以随时随地远程调节光照的状态,监控光照的变化过程,而不必依赖现场操作。

继电器与ESP8266的结合不仅为光照控制提供了更高的精度和灵活性,还实现了对环境光照的有效模拟,进一步增强了实验室自动化管理的智能化水平,确保了实验的可重复性和数据的高精度。

2.3 投食模块的独特设计

进食区采用独特投食机构 ,通过挡板叶轮的旋转配合 ,能够实现每次投食的精确控制,确保动物能够按时、按量地获得食物,避免了传统投食方法中可能存在的不准确性和不稳定性。该投食机构通过挡板控制食物的流量和投放时间,叶轮则负责推动食物的均匀投放,从而保证每次投食量的一致性,避免过多或过少投喂带来的影响。

为了提供更高的灵活性,饲养辅助系统同时配备了投食模块 ,用户可以通过定时控制按键操作远程操作 等多种方式对投食系统进行控制,满足不同实验需求的个性化管理。在投食过程中,为了确保进食量的准确性,系统通过主控芯片实时监控和调节投食过程,确保每次投喂食物的量和时间都能够严格符合预定标准。

同时,系统会将每次投食的具体时间、数量以及投食方式等数据记录下来,经过主控芯片的数据处理 后,自动汇总并上传至云端 进行存储和管理。这些数据不仅可以为后期的数据分析 提供详细的参考,也为实验的优化调整提供了精准依据。

通过将投食数据上传至云端,实验人员可以随时查看投食记录,进行远程监控数据调控 ,进一步提高实验的可控性和可靠性。此外,云端平台的数据存储和备份 功能确保了所有投食记录的长期保存,方便日后进行数据的回溯分析 ,从而支持实验中的精准调控行为分析,确保实验动物的营养摄入与实验设计的高度一致。

2.4 温湿度监测及控制

通过DHT11模块 进行温度监测,系统能够实时采集环境中的温度湿度 数据,并对实验动物的生活环境进行精准调控。实验动物的日常饲养环境对于实验的成功至关重要,动物的温湿度环境 必须严格符合其生理需求,以确保实验的科学性与可重复性,减少环境变量对实验结果的影响。

为此,饲养辅助系统内配备了温湿度监测板块 ,能够对动物生存空间内的温湿度进行持续监测,并将实时数据传输回主控芯片,显示在外接屏幕上,便于实验人员随时查看环境状态。系统能够通过智能调节控制机制,根据实时监测到的温湿度数据,自动采取相应的应对措施。

温度高于预设值 时,系统会通过启动风扇 加速空气流通,降低环境温度 ,确保温度在安全范围内;当温度低于预设值 时,系统会自动启动升温模块,通过加热设备提高室内温度,保证动物的活动环境处于适宜的温度范围。

此外,系统还配备了SIM900a模块 ,当环境温湿度数据与预设值 存在较大偏差时,系统会自动触发报警功能,通过短信或电话通知实验操作人员,提醒他们及时采取措施调整环境,以避免因环境变化引发的实验误差。通过这种自动化温湿度调控远程报警系统 ,饲养辅助系统能够确保实验动物始终处于最适宜的生活环境中,从而为实验的顺利进行提供保障,并提高实验数据的准确性可靠性

2.5 远程互联

通过ESP8266 WiFi模块 ,系统实现了远程互联功能,确保实验动物生存环境的远程控制与监控。结合手机APP控制 ,实验人员可以通过智能手机随时随地对实验环境进行操作与调节,极大提高了操作的灵活性与便利性。同时,系统还与EWelink手机平台OneNet云平台实现了无缝连接,使得实验动物的生存空间控制更加智能化与精确化。

实验人员不仅能够通过手机端控制环境内的各个模块,如温湿度调节、光照强度、投食量等,还能实时查看相关数据,确保实验环境始终保持在预设的标准范围内。通过云平台的数据统计与分析 功能,所有实验数据都会被自动收集、整理,并上传至OneNet云端 进行统一存储。云端平台不仅能够进行数据整理 ,还具备强大的数据分析能力,可以根据实时数据生成动态可视化模型,帮助实验人员直观了解动物生存环境的变化趋势。

这种基于云平台的数据展示为后期实验数据的分析、回溯和优化提供了便捷的条件。值得一提的是,系统采用了分区控制机制 ,可以针对不同类型或不同组别的实验动物,分别控制其生存空间内的环境因素,如温湿度、光照和投食等,确保每一组实验动物都能在最适宜的环境中生长,进一步提高了实验结果的准确性可靠性

同时,利用云服务器的大数据分析和测算能力,实验数据能够得到更加精准的计算与评估,使得实验过程中的每一项变量都能够被实时监控和优化,提升了实验的科学性高效性 。通过这种高度集成的智能化管理,系统不仅让实验过程变得更加方便快捷 ,还为实验数据的精准获取优化调整提供了强大的技术支持,确保实验的顺利进行。

2.6 系统结构

系统的主要架构如 所示,整体结构通过主控系统 协调各类模块和传感器之间的协作,确保各传感器能够顺利衔接并高效配合工作。主控系统作为核心枢纽,负责调度各个模块的运行,并对来自不同传感器的数据进行实时采集与处理。这些数据包括温度、湿度、光照强度、食物投放量等各项环境参数,以及生物样本的活动和健康状态。

所有传感器的协同工作确保了数据的高精度与可靠性,最大限度地避免了实验环境的变动对实验结果的影响。接下来,这些采集到的原始数据将被系统汇总和整合,并通过云端平台上传进行测算分析 。云平台对数据进行高效处理,并利用强大的计算能力得出所需的结论和具象化的数据图谱,这些图谱能够直观地展示动物生存环境的变化趋势以及实验系统的运行状况。

通过数据可视化,实验人员不仅能够快速获取动物生活环境的各项变化指标,还能够轻松识别系统运行的效率和稳定性,及时发现潜在的异常情况。在实际操作中,用户可以通过终端屏幕实时观察数据的变化,系统界面清晰、直观,使得数据监控变得更加方便。

同时,系统也提供了多种登录方式,允许用户通过不同的终端设备(如PC、移动设备等)访问云平台,随时随地查看系统数据和实验动物的健康状况。通过这些方式,实验操作人员能够更快捷、方便地获取所需信息,进而根据实时数据进行相应的调整和优化,确保实验的高效性与准确性。

云端平台的数据存储与备份 功能进一步保证了数据的长期有效性,便于日后进行历史数据的回溯分析。这种全面而灵活的监控手段,不仅提升了实验的可控性和可靠性,也极大地增强了实验过程中的实时反馈与调整能力

三、软件开发:

3.1 stm32代码分析:

3.1.1 硬件连接概述

硬件模块与 STM32 的连接方式:

  • OLED 显示屏通过 I2C 接口连接(SCL - PB13, SDA - PB15),需要提供 3.3V 或 5V 电源。
  • 温湿度传感器 SHT3x通过 I2C 接口连接(SCL - PA7, SDA - PA6)。
  • 蓝牙模块通过串口通信连接(RXD - PA9, TXD - PA10),并使用 5V 电源。

3.1.2 系统初始化与硬件配置

main() 函数中,系统初始化和硬件配置被按顺序进行:

  • delay_init():初始化延时功能。
  • uart_init(9600):初始化串口,波特率设置为 9600。
  • SHT3X_Init(0x44):初始化温湿度传感器,地址为 0x44
  • HMISendstart():启动 HMI 通信。
  • OLED_Init()OLED_Clear():初始化 OLED 显示屏,并清空显示内容。
  • TIM_PWM_Init(190, 7199):初始化 PWM 控制器,用于控制外部硬件(如蜂鸣器或其他设备)。

3.1.3 温湿度数据处理

每次进入 while(1) 循环时,程序获取温湿度数据:

  • SHT3X_GetTempAndHumi(&temperature, &humidity, REPEATAB_HIGH, MODE_CLKSTRETCH, 50):通过 I2C 从 SHT3x 传感器获取温湿度数据,分别存储在 temperaturehumidity 变量中。
  • tem = temperature * 10hum = humidity * 10:将温湿度数据从浮动精度转为整数(精确到 0.1°C 和 0.1% RH),便于后续处理和显示。

3.1.4. OLED 显示与 HMI 数据发送

程序使用 OLED 显示器实时显示温湿度数据:

  • OLED_Clear():每次循环时清除 OLED 屏幕。
  • OLED_ShowNum(100, 5, 1, 2, 16):在屏幕上显示一些测试数据。这里的 100, 5, 1, 2, 16 是显示的坐标和数据格式。
  • HMISends(a)HMISends(b):将温度和湿度数据发送到 HMI 界面。

HMI 通信通过串口进行,数据通过格式化字符串发送,字符串的格式是:

cpp 复制代码
"page0.x0.val=%d\xff\xff\xff"

其中 \xFF\xFF\xFF 是结束符。

3.1.5. HMI 命令解析

程序通过检测从 HMI 接收到的命令,并根据命令执行相应的操作:

  • table_cp[0] == 0x2c 判断是否是合法的 HMI 命令。
  • num = hextoDec(table_cp[2]):将接收到的命令编号从十六进制转换为十进制。
  • 根据命令编号,执行不同的操作:
    • case 1:设置温度阈值。
    • case 2:设置湿度阈值。
    • case 3:执行其他操作,可能涉及 PWM 控制等。

3.1.6. PWM 控制

PWM 用于控制外部硬件的操作,如蜂鸣器的音量调节等。在 case 3 中:

  • TIM_SetCompare1(TIM4, 185)TIM_SetCompare1(TIM4, 175) 控制 PWM 信号的占空比,可能用于蜂鸣器的鸣响。

3.1.7. 十六进制转十进制

hextoDec(int hex) 是将十六进制数转换为十进制的函数。该函数逐位提取十六进制数的每一位,并将其累加为十进制数。以下是算法:

  • 对十六进制数进行逐位取余,获取每一位的值。
  • 根据位数(幂次)将每一位乘上相应的 16 的幂,最后累加得到十进制值。

3.1.8. 数据发送与控制

HMISends(char *buf1)HMISendb(u8 k) 是用于将数据通过 USART1 串口发送到 HMI 或其他外部设备的函数:

  • HMISends() 用于发送字符串数据。
  • HMISendb() 用于发送单个字节的数据。

beepms(u16 va) 控制蜂鸣器的开关,在一定延时内启动和关闭蜂鸣器。

3.1.9. 异常处理和错误码

在代码开头定义了错误码:

cpp 复制代码
typedef enum
{
    NO_ERROR = 0x00,  // 无错误
    ACK_ERROR = 0x01,  // 无应答错误
    CHECKSUM_ERROR = 0x02,  // 校验和错误
    TIMEOUT_ERROR = 0x04,  // 超时错误
    PARM_ERROR = 0x80,  // 参数超出范围错误
} etError;

这些错误码用于在实际使用中进行异常处理或通信失败的反馈。

3.1.10. 延时与通信时序

整个系统依赖于延时控制和时序:

  • delay_ms() 用于执行必要的延时,确保硬件在合适的时间点进行操作。
  • USART_GetFlagStatus(USART1, USART_FLAG_TXE) 用于检测 USART1 是否可以继续发送数据,保证串口发送的稳定性。

基于 STM32F103 的嵌入式系统应用,集成了温湿度传感器数据采集、OLED 显示、HMI 控制以及蓝牙通信功能。它通过串口与外部 HMI 界面交换数据,并利用 PWM 控制外部设备(如蜂鸣器)。该程序通过 I2C 和 UART 等通信协议与外部硬件进行交互,展示了典型的嵌入式系统开发流程。以下是整个核心代码。

cpp 复制代码
/*
------------------------------------------------------------
						´®¿ÚºÅ			RXD		TXD
						USART1	  PA10  PA9
						USART2		PA3		PA2
						USART3		PB11	PB10
						USART4		PC11	PC10
						USART5		PD2		PC12
------------------------------------------------------------
0.96 OLED/液晶显示屏连接:
						GND 	  接地
						VCC   	连接5V或3.3V电源
						SCL   	连接PB13(SCL)
						SDA			连接PB15(SDA) 
            RES   	连接PB11,若使用I2C接口可以不接此引脚
------------------------------------------------------------
温湿度传感器SHT3x连接:
            SCL     PA7
            SDA     PA6
            GND     接地
            VCC     3.3V
------------------------------------------------------------
蓝牙模块连接:
            RXD			PA9
            TXD			PA10
            GND			接地
            VCC			5V
------------------------------------------------------------
*/
#include "delay.h"  // 延时函数库
#include "sys.h"  // 系统初始化函数库
#include "sht3x.h"  // 温湿度传感器头文件
#include "bmp.h"  // BMP传感器头文件(此部分代码未完全展示)
#include "stm32f10x.h"  // STM32F10x系列芯片的标准库
#include "usart.h"  // 串口通信初始化
#include "system.h"  // 系统配置文件
#include <string.h>  // 字符串处理库
#include "bsp_pwm.h"  // PWM波控制库
#include "oled.h"  // OLED显示控制库

u8 idfind(u8 *buf,u8 *val,u8 len);  // 用于在缓冲区查找特定ID的函数声明

int hextoDec(int hex);  // 十六进制转十进制函数声明
void HMISends(char *buf1);  // 发送数据到HMI的函数声明
void HMISendb(u8 buf);  // 发送单字节数据到HMI的函数声明
void beepms(u16 va);  // 蜂鸣器控制函数声明
void HMISendstart(void);  // 初始化HMI通信的函数声明

u8 key, beep;  // 定义按键和蜂鸣器控制标志
int tem, hum, a = 0;  // 温湿度的整数值
float temperature;  // 温度(摄氏度)
float humidity;  // 湿度(%RH)
float setH_temperature = 0, setL_temperature = 0;  // 高低温阈值
float setH_humidity = 0, setL_humidity = 0;  // 高低湿度阈值
float b_temperature = 0;  // 设置的温度值
float b_humidity = 0;  // 设置的湿度值

int main(void)
{	
    typedef enum
    {
        NO_ERROR = 0x00,  // 无错误
        ACK_ERROR = 0x01,  // 无应答错误
        CHECKSUM_ERROR = 0x02,  // 校验和错误
        TIMEOUT_ERROR = 0x04,  // 超时错误
        PARM_ERROR = 0x80,  // 参数超出范围错误
    } etError;

    u16 num;  // 用于存储从HMI接收到的数据

    delay_init();  // 初始化延时功能
    uart_init(9600);  // 初始化串口通信,波特率为9600
    SHT3X_Init(0x44);  // 初始化温湿度传感器,地址为0x44
    delay_ms(50);  // 延时50毫秒,确保传感器初始化完成

    delay_init();  // 再次初始化延时
    NVIC_Configuration();  // 配置中断控制器(启用中断)
    HMISendstart();  // 启动HMI通信
    HMISendb(0xff);  // 发送0xFF,清空缓存
    delay_ms(1000);  // 延时1秒,等待处理
    TIM_PWM_Init(190, 7199);  // 初始化PWM控制,设定频率
    OLED_Init();  // 初始化OLED显示屏
    OLED_Clear();  // 清空OLED屏幕

    while (1) 
    {		
        char a[100], b[100];  // 用于存储发送给HMI的数据
        SHT3X_GetTempAndHumi(&temperature, &humidity, REPEATAB_HIGH, MODE_CLKSTRETCH, 50);  // 获取温湿度数据
        tem = temperature * 10;  // 将温度转换为整数值(精确到0.1°C)
        hum = humidity * 10;  // 将湿度转换为整数值(精确到0.1% RH)
		
        OLED_Clear();  // 清空OLED显示
        OLED_ShowNum(100, 5, 1, 2, 16);  // 在OLED上显示数字(测试显示)
        delay_ms(2000);  // 延时2秒,观察效果

        // 将温湿度数据发送给HMI界面
        sprintf(a, "page0.x0.val=%d\xff\xff\xff", tem);
        HMISends(a);  // 发送温度数据
        HMISendb(0xff);  // 发送结束符
        sprintf(b, "page0.x1.val=%d\xff\xff\xff", hum);
        HMISends(b);  // 发送湿度数据
        HMISendb(0xff);  // 发送结束符

        // 处理从HMI界面接收到的命令
        if (table_cp[0] == 0x2c)
        {
            OLED_ShowNum(100, 5, 10, 2, 16);  // 在OLED上显示数据
            delay_ms(2000);  // 延时2秒

            num = hextoDec(table_cp[2]);  // 将十六进制数据转换为十进制
            switch (num)
            {	
                case 1:  // 设置温度
                    setL_temperature = hextoDec(table_cp[3]);  // 获取低温阈值
                    setH_temperature = hextoDec(table_cp[4]);  // 获取高温阈值
                    b_temperature = setH_temperature * 256 + setL_temperature;  // 合并温度阈值
                    b_temperature = b_temperature * 0.1;  // 转换为实际温度
                    break;
                case 2:  // 设置湿度
                    setL_humidity = hextoDec(table_cp[3]);  // 获取低湿度阈值
                    setH_humidity = hextoDec(table_cp[4]);  // 获取高湿度阈值
                    b_humidity = setH_humidity * 256 + setL_humidity;  // 合并湿度阈值
                    b_humidity = b_humidity * 0.1;  // 转换为实际湿度
                    break;
                case 3:  // 其他操作
                    OLED_ShowString(1, 1, "OK:", 16);  // 在OLED上显示OK
                    OLED_ShowNum(100, 5, num, 2, 16);  // 显示命令编号
                    delay_ms(1000);  // 延时1秒
                    TIM_SetCompare1(TIM4, 185);  // 设置PWM值
                    delay_ms(1000);  // 延时1秒
                    TIM_SetCompare1(TIM4, 175);  // 另一个PWM值
                    for (int i = 0; i < 9; ++i)
                    {
                        table_cp[i] = 0;  // 清空接收到的数据缓冲区
                    }
                    break;
                default:
                    break;
            }
        }
        OLED_ShowNum(100, 5, 15, 2, 16);  // 显示默认数据
        delay_ms(2000);  // 延时2秒
    }
}	

// 发送数据到HMI界面的函数
void HMISends(char *buf1)		  
{
	u8 i = 0;
	while(1)
	{
		if(buf1[i] != 0)
	 	{
			USART_SendData(USART1, buf1[i]);  // 发送一个字节数据
			while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET) {}  // 等待数据发送完成
			i++;
		}
		else
		{
			return;
		}
	}
}

// 蜂鸣器控制函数
void beepms(u16 va)
{
	beep = 1;  // 启动蜂鸣器
	delay_ms(va);  // 延时指定时间
	beep = 0;  // 关闭蜂鸣器
}

// 启动HMI通信的函数
void HMISendstart(void)
{
    delay_ms(200);  // 延时200毫秒
    HMISendb(0xff);  // 发送0xFF数据,作为HMI通信的启动信号
    delay_ms(200);  // 再延时200毫秒,确保HMI已经准备好接收数据
}

// 发送单个字节数据到HMI的函数
void HMISendb(u8 k)
{
    u8 i;
    for (i = 0; i < 3; i++)  // 为了确保数据发送成功,进行三次尝试
    {
        if (k != 0)  // 如果发送的数据不为0
        {
            USART_SendData(USART1, k);  // 通过USART1发送一个字节数据
            // 等待数据发送完毕,USART_FLAG_TXE标志位为RESET表示发送缓冲区为空,表示可以继续发送下一个字节
            while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET) {}
        }
        else  // 如果数据是0,直接返回
        {
            return;
        }
    }
}

// 将十六进制数转换为十进制数的函数
int hextoDec(int hex)
{
    int sum = 0, mul = 1;
    int i, r;
    int count = 0;  // 计数器,用于确定位数

    do {
        r = hex % 16;  // 获取十六进制数的最低位
        for (i = 0; i < count; i++)  // 根据当前位数,将16的幂次乘上
            mul *= 16;
        mul *= r;  // 将当前位的十六进制数值转换为十进制
        sum += mul;  // 累加得到最终的十进制数
        mul = 1;  // 重置乘数
        count++;  // 位数增加
    } while (hex /= 16);  // 当十六进制数为0时,退出循环

    return sum;  // 返回计算后的十进制数
}

3.2 摄像头源码分析:

3.2.1 包含的库:

  • esp_http_server.h: 用于处理HTTP请求和响应的ESP32库。
  • esp_timer.h: 提供ESP32的定时器功能,帮助进行时间测量。
  • esp_camera.h: 控制ESP32摄像头模块的库。
  • img_converters.h: 图像格式转换的库。
  • camera_index.h: 摄像头配置相关的库。
  • Arduino.h: 提供Arduino的基本功能。

3.2.2 结构体定义

  • ra_filter_t: 用于实现一个简单的滑动窗口滤波器。可以用于平滑处理摄像头帧率的变化。

    • size: 滤波器窗口的大小。
    • index: 当前索引。
    • count: 当前已填充的值计数。
    • sum: 当前值的总和。
    • values: 保存值的数组。
  • jpg_chunking_t: 用于在HTTP响应中处理JPEG数据。

    • req: HTTP请求句柄。
    • len: JPEG数据的总长度。

3.2.3 常量定义

  • 颜色定义: 用于在人脸检测和识别时,在图像上绘制不同颜色的方框。
  • PART_BOUNDARY: 定义HTTP多部分响应的边界标识符。

3.2.4 全局变量

  • ra_filter: 用于计算帧率的滤波器对象。
  • stream_httpdcamera_httpd: 分别是流媒体和摄像头HTTP服务器的句柄。

3.2.5 函数分析

  • ra_filter_init: 初始化滑动窗口滤波器。

    • 分配内存并初始化为零。
  • ra_filter_run: 运行滤波器以计算平均值。

    • 对输入的数值进行平滑处理。
  • rgb_print: 在图像上打印文本。

    • 使用给定的颜色和位置在图像上绘制文本。
  • rgb_printf : 类似于printf,但输出结果在图像上显示。

    • 通过可变参数传入格式化字符串。
  • draw_face_boxes: 在图像上绘制人脸检测到的方框。

    • 根据识别结果选择方框颜色。
  • run_face_recognition: 执行人脸识别。

    • 检测到人脸后进行识别,并根据结果进行相应处理。
  • jpg_encode_stream: 处理JPEG数据并发送到HTTP响应中。

    • 将JPEG数据分块发送。
  • capture_handler: 处理捕获图像的HTTP请求。

    • 从摄像头获取图片并返回给客户端。
  • stream_handler: 处理流媒体请求。

    • 不同于静态图片,负责连续发送视频流。
  • cmd_handler: 处理客户端命令请求。

    • 修改摄像头参数或功能状态。
  • status_handler: 处理状态请求。

    • 返回当前摄像头的状态信息。
  • index_handler: 处理首页请求。

    • 返回首页的HTML内容。

3.2.6 startCameraServer 函数

负责启动并配置摄像头和流媒体HTTP服务器:

  1. 配置HTTP服务器: 设置服务器参数。
  2. 注册URI: 注册不同的URI处理函数来响应HTTP请求。
  3. 初始化滤波器和人脸检测参数
  4. 启动HTTP服务器: 启动服务器并监听传入的HTTP请求。

以下代码提供了一个完整的框架,能够在ESP32上实现一个具有人脸识别功能的网络摄像头。通过处理HTTP请求,可以捕获图像、进行流媒体传输以及调整摄像头参数,适用于需要远程监控和人脸识别的应用场景。

cpp 复制代码
#include "esp_http_server.h" // 包含ESP32的HTTP服务器库
#include "esp_timer.h" // 包含ESP32的定时器库
#include "esp_camera.h" // 包含ESP32的摄像头库
#include "img_converters.h" // 包含图像转换库
#include "camera_index.h" // 包含摄像头索引库
#include "Arduino.h" // 包含Arduino库

#include "fb_gfx.h" // 包含帧缓冲区图形库
#include "fd_forward.h" // 包含人脸检测库
#include "fr_forward.h" // 包含人脸识别库

// 定义常量
#define ENROLL_CONFIRM_TIMES 5 // 注册人脸时需要确认的次数
#define FACE_ID_SAVE_NUMBER 7 // 保存的人脸ID数量

// 定义颜色常量
#define FACE_COLOR_WHITE  0x00FFFFFF // 白色
#define FACE_COLOR_BLACK  0x00000000 // 黑色
#define FACE_COLOR_RED    0x000000FF // 红色
#define FACE_COLOR_GREEN  0x0000FF00 // 绿色
#define FACE_COLOR_BLUE   0x00FF0000 // 蓝色
#define FACE_COLOR_YELLOW (FACE_COLOR_RED | FACE_COLOR_GREEN) // 黄色
#define FACE_COLOR_CYAN   (FACE_COLOR_BLUE | FACE_COLOR_GREEN) // 青色
#define FACE_COLOR_PURPLE (FACE_COLOR_BLUE | FACE_COLOR_RED) // 紫色

// 定义ra_filter_t结构体,用于滤波
typedef struct {
    size_t size; // 滤波器中使用的值数量
    size_t index; // 当前值索引
    size_t count; // 值计数
    int sum; // 值总和
    int * values; // 存储值的数组
} ra_filter_t;

// 定义jpg_chunking_t结构体,用于处理JPEG数据
typedef struct {
    httpd_req_t *req; // HTTP请求
    size_t len; // 数据长度
} jpg_chunking_t;

// 定义PART_BOUNDARY常量,用于多部分类型数据
#define PART_BOUNDARY "123456789000000000000987654321"
// 定义_STREAM_CONTENT_TYPE常量,用于流媒体内容类型
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
// 定义_STREAM_BOUNDARY常量,用于流媒体边界
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
// 定义_STREAM_PART常量,用于流媒体部分
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";

// 全局变量
static ra_filter_t ra_filter; // 滤波器
httpd_handle_t stream_httpd = NULL; // 流媒体服务器句柄
httpd_handle_t camera_httpd = NULL; // 摄像头服务器句柄

// 其他全局变量,如mtmn_config、detection_enabled、recognition_enabled等,用于配置和状态

// ra_filter_t结构体的初始化函数
static ra_filter_t * ra_filter_init(ra_filter_t * filter, size_t sample_size){
    memset(filter, 0, sizeof(ra_filter_t)); // 初始化结构体
    filter->values = (int *)malloc(sample_size * sizeof(int)); // 分配数组空间
    if(!filter->values){
        return NULL; // 如果分配失败,返回NULL
    }
    memset(filter->values, 0, sample_size * sizeof(int)); // 初始化数组
    filter->size = sample_size; // 设置数组大小
    return filter;
}

// ra_filter_t结构体的运行函数,用于滤波
static int ra_filter_run(ra_filter_t * filter, int value){
    if(!filter->values){
        return value; // 如果数组为空,返回值
    }
    filter->sum -= filter->values[filter->index]; // 更新总和
    filter->values[filter->index] = value; // 更新值
    filter->sum += filter->values[filter->index]; // 更新总和
    filter->index++; // 更新索引
    filter->index = filter->index % filter->size; // 循环索引
    if (filter->count < filter->size) {
        filter->count++; // 更新计数
    }
    return filter->sum / filter->count; // 返回平均值
}

// rgb_print函数,用于在图像上打印文本
static void rgb_print(dl_matrix3du_t *image_matrix, uint32_t color, const char * str){
    fb_data_t fb;
    fb.width = image_matrix->w;
    fb.height = image_matrix->h;
    fb.data = image_matrix->item;
    fb.bytes_per_pixel = 3;
    fb.format = FB_BGR888;
    fb_gfx_print(&fb, (fb.width - (strlen(str) * 14)) / 2, 10, color, str);
}

// rgb_printf函数,用于格式化打印文本
static int rgb_printf(dl_matrix3du_t *image_matrix, uint32_t color, const char *format, ...){
    char loc_buf[64];
    char * temp = loc_buf;
    int len;
    va_list arg;
    va_list copy;
    va_start(arg, format);
    va_copy(copy, arg);
    len = vsnprintf(loc_buf, sizeof(loc_buf), format, arg);
    va_end(copy);
    if(len >= sizeof(loc_buf)){
        temp = (char*)malloc(len+1);
        if(temp == NULL) {
            return 0;
        }
    }
    vsnprintf(temp, len+1, format, arg);
    va_end(arg);
    rgb_print(image_matrix, color, temp);
    if(len > 64){
        free(temp);
    }
    return len;
}

// draw_face_boxes函数,用于在图像上绘制人脸框
static void draw_face_boxes(dl_matrix3du_t *image_matrix, box_array_t *boxes, int face_id){
    int x, y, w, h, i;
    uint32_t color = FACE_COLOR_YELLOW;
    if(face_id < 0){
        color = FACE_COLOR_RED;
    } else if(face_id > 0){
        color = FACE_COLOR_GREEN;
    }
    fb_data_t fb;
    fb.width = image_matrix->w;
    fb.height = image_matrix->h;
    fb.data = image_matrix->item;
    fb.bytes_per_pixel = 3;
    fb.format = FB_BGR888;
    for (i = 0; i < boxes->len; i++){
        // 绘制矩形框
        x = (int)boxes->box[i].box_p[0];
        y = (int)boxes->box[i].box_p[1];
        w = (int)boxes->box[i].box_p[2] - x + 1;
        h = (int)boxes->box[i].box_p[3] - y + 1;
        fb_gfx_drawFastHLine(&fb, x, y, w, color);
        fb_gfx_drawFastHLine(&fb, x, y+h-1, w, color);
        fb_gfx_drawFastVLine(&fb, x, y, h, color);
        fb_gfx_drawFastVLine(&fb, x+w-1, y, h, color);
        // 绘制关键点
#if 0
        int x0, y0, j;
        for (j = 0; j < 10; j+=2) {
            x0 = (int)boxes->landmark[i].landmark_p[j];
            y0 = (int)boxes->landmark[i].landmark_p[j+1];
            fb_gfx_fillRect(&fb, x0, y0, 3, 3, color);
        }
#endif
    }
}

// run_face_recognition函数,用于运行人脸识别
static int run_face_recognition(dl_matrix3du_t *image_matrix, box_array_t *net_boxes){
    dl_matrix3du_t *aligned_face = NULL;
    int matched_id = 0;

    aligned_face = dl_matrix3du_alloc(1, FACE_WIDTH, FACE_HEIGHT, 3);
    if(!aligned_face){
        Serial.println("Could not allocate face recognition buffer");
        return matched_id;
    }
    if (align_face(net_boxes, image_matrix, aligned_face) == ESP_OK){
        if (is_enrolling == 1){
            int8_t left_sample_face = enroll_face(&id_list, aligned_face);

            if(left_sample_face == (ENROLL_CONFIRM_TIMES - 1)){
                Serial.printf("Enrolling Face ID: %d\n", id_list.tail);
            }
            Serial.printf("Enrolling Face ID: %d sample %d\n", id_list.tail, ENROLL_CONFIRM_TIMES - left_sample_face);
            rgb_printf(image_matrix, FACE_COLOR_CYAN, "ID[%u] Sample[%u]", id_list.tail, ENROLL_CONFIRM_TIMES - left_sample_face);
            if (left_sample_face == 0){
                is_enrolling = 0;
                Serial.printf("Enrolled Face ID: %d\n", id_list.tail);
            }
        } else {
            matched_id = recognize_face(&id_list, aligned_face);
            if (matched_id >= 0) {
                Serial.printf("Match Face ID: %u\n", matched_id);
                rgb_printf(image_matrix, FACE_COLOR_GREEN, "Hello Subject %u", matched_id);
            } else {
                Serial.println("No Match Found");
                rgb_print(image_matrix, FACE_COLOR_RED, "Intruder Alert!");
                matched_id = -1;
            }
        }
    } else {
        Serial.println("Face Not Aligned");
        //rgb_print(image_matrix, FACE_COLOR_YELLOW, "Human Detected");
    }

    dl_matrix3du_free(aligned_face);
    return matched_id;
}

// jpg_encode_stream函数,用于处理JPEG数据
static size_t jpg_encode_stream(void * arg, size_t index, const void* data, size_t len){
    jpg_chunking_t *j = (jpg_chunking_t *)arg;
    if(!index){
        j->len = 0;
    }
    if(httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK){
        return 0;
    }
    j->len += len;
    return len;
}

// capture_handler函数,用于处理图像捕获请求
static esp_err_t capture_handler(httpd_req_t *req){
    camera_fb_t * fb = NULL;
    esp_err_t res = ESP_OK;
    int64_t fr_start = esp_timer_get_time();

    fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Camera capture failed");
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    httpd_resp_set_type(req, "image/jpeg");
    httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
    httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");

    size_t out_len, out_width, out_height;
    uint8_t * out_buf;
    bool s;
    bool detected = false;
    int face_id = 0;
    if(!detection_enabled || fb->width > 400){
        size_t fb_len = 0;
        if(fb->format == PIXFORMAT_JPEG){
            fb_len = fb->len;
            res = httpd_resp_send(req, (const char *)fb->buf, fb->len);
        } else {
            jpg_chunking_t jchunk = {req, 0};
            res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk)?ESP_OK:ESP_FAIL;
            httpd_resp_send_chunk(req, NULL, 0);
            fb_len = jchunk.len;
        }
        esp_camera_fb_return(fb);
        int64_t fr_end = esp_timer_get_time();
        Serial.printf("JPG: %uB %ums\n", (uint32_t)(fb_len), (uint32_t)((fr_end - fr_start)/1000));
        return res;
    }

    dl_matrix3du_t *image_matrix = dl_matrix3du_alloc(1, fb->width, fb->height, 3);
    if (!image_matrix) {
        esp_camera_fb_return(fb);
        Serial.println("dl_matrix3du_alloc failed");
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    out_buf = image_matrix->item;
    out_len = fb->width * fb->height * 3;
    out_width = fb->width;
    out_height = fb->height;

    s = fmt2rgb888(fb->buf, fb->len, fb->format, out_buf);
    esp_camera_fb_return(fb);
    if(!s){
        dl_matrix3du_free(image_matrix);
        Serial.println("to rgb888 failed");
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    box_array_t *net_boxes = face_detect(image_matrix, &mtmn_config);

    if (net_boxes){
        detected = true;
        if(recognition_enabled){
            face_id = run_face_recognition(image_matrix, net_boxes);
        }
        draw_face_boxes(image_matrix, net_boxes, face_id);
        free(net_boxes->score);
        free(net_boxes->box);
        free(net_boxes->landmark);
        free(net_boxes);
    }

    jpg_chunking_t jchunk = {req, 0};
    s = fmt2jpg_cb(out_buf, out_len, out_width, out_height, PIXFORMAT_RGB888, 90, jpg_encode_stream, &jchunk);
    dl_matrix3du_free(image_matrix);
    if(!s){
        Serial.println("JPEG compression failed");
        return ESP_FAIL;
    }

    int64_t fr_end = esp_timer_get_time();
    Serial.printf("FACE: %uB %ums %s%d\n", (uint32_t)(jchunk.len), (uint32_t)((fr_end - fr_start)/1000), detected?"DETECTED ":"", face_id);
    return res;
}

// stream_handler函数,用于处理流媒体请求
static esp_err_t stream_handler(httpd_req_t *req){
    // ... (与capture_handler函数类似,处理流媒体数据)
}

// cmd_handler函数,用于处理命令请求
static esp_err_t cmd_handler(httpd_req_t *req){
    // ... (处理来自客户端的命令,如设置摄像头参数)
}

// status_handler函数,用于处理状态请求
static esp_err_t status_handler(httpd_req_t *req){
    // ... (返回摄像头状态信息)
}

// index_handler函数,用于处理首页请求
static esp_err_t index_handler(httpd_req_t *req){
    // ... (返回摄像头设置页面的HTML内容)
}

// startCameraServer函数,用于启动摄像头服务器
void startCameraServer(){
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();

    httpd_uri_t index_uri = {
        .uri       = "/",
        .method    = HTTP_GET,
        .handler   = index_handler,
        .user_ctx  = NULL
    };

    httpd_uri_t status_uri = {
        .uri       = "/status",
        .method    = HTTP_GET,
        .handler   = status_handler,
        .user_ctx  = NULL
    };

    httpd_uri_t cmd_uri = {
        .uri       = "/control",
        .method    = HTTP_GET,
        .handler   = cmd_handler,
        .user_ctx  = NULL
    };

    httpd_uri_t capture_uri = {
        .uri       = "/capture",
        .method    = HTTP_GET,
        .handler   = capture_handler,
        .user_ctx  = NULL
    };

    httpd_uri_t stream_uri = {
        .uri       = "/stream",
        .method    = HTTP_GET,
        .handler   = stream_handler,
        .user_ctx  = NULL
    };

    ra_filter_init(&ra_filter, 20);

    mtmn_config.type = FAST;
    mtmn_config.min_face = 80;
    mtmn_config.pyramid = 0.707;
    mtmn_config.pyramid_times = 4;
    mtmn_config.p_threshold.score = 0.6;
    mtmn_config.p_threshold.nms = 0.7;
    mtmn_config.p_threshold.candidate_number = 20;
    mtmn_config.r_threshold.score = 0.7;
    mtmn_config.r_threshold.nms = 0.7;
    mtmn_config.r_threshold.candidate_number = 10;
    mtmn_config.o_threshold.score = 0.7;
    mtmn_config.o_threshold.nms = 0.7;
    mtmn_config.o_threshold.candidate_number = 1;

    face_id_init(&id_list, FACE_ID_SAVE_NUMBER, ENROLL_CONFIRM_TIMES);

    Serial.printf("Starting web server on port: '%d'\n", config.server_port);
    if (httpd_start(&camera_httpd, &config) == ESP_OK) {
        httpd_register_uri_handler(camera_httpd, &index_uri);
        httpd_register_uri_handler(camera_httpd, &cmd_uri);
        httpd_register_uri_handler(camera_httpd, &status_uri);
        httpd_register_uri_handler(camera_httpd, &capture_uri);
    }

    config.server_port += 1;
    config.ctrl_port += 1;
    Serial.printf("Starting stream server on port: '%d'\n", config.server_port);
    if (httpd_start(&stream_httpd, &config) == ESP_OK) {
        httpd_register_uri_handler(stream_httpd, &stream_uri);
    }
}

四、尾言:

在本项目的实施过程中,我们成功构建了一个智能化、自动化的饲养辅助系统 ,专门为医学实验室中的生物样本设计。系统的核心成就是实现了对动物饲养环境的实时监控 ,通过摄像头提供远程视频监控能力,用户可以通过手机或电脑端随时查看,确保实验动物的安全和实验环境的稳定。自动投食模块通过精确的机械结构实现了定时、定量的投食,所有投食数据均上传至云端,为后续分析提供了详细的实验记录。

光照控制模块 采用可调节的日光灯和先进的控制技术,确保实验环境光照条件的可控性,并将光照强度和时长的数据上传进行记录。温湿度监测与控制系统则通过实时监测环境数据,自动调节风扇和加热模块以维持适宜的实验条件,通过SIM900a模块在出现异常时向实验人员报警。该系统通过EWelink和OneNet平台实现了远程控制和数据上传,并支持分区管理,为不同实验动物的提供个性化的环境条件管理,所有数据都上传到云端进行大数据分析,并通过可视化模型呈现,帮助实验人员更好地理解和管理实验数据。

未来,我们将进一步优化系统的稳定性和响应速度,扩展系统功能模块,提升用户界面和体验,加强网络安全措施,期待本系统能够在更广泛的医学实验室应用中发挥作用,提升实验效率和数据精确度,为科研人员提供有力支持,推动科学研究的进步。

资源获取连接:

毕设&课设&项目&实训-生物样本饲养辅助系统的研究.zip资源-CSDN文库

详细资源:

摄像头源码:

stm32源码:

相关推荐
浅陌pa6 分钟前
I2C(一):存储器模式:stm32作为主机对AT24C02写读数据
c语言·stm32·单片机·嵌入式硬件
DS小龙哥11 分钟前
基于物联网疫苗冷链物流监测系统设计
物联网·struts·servlet
半个番茄1 小时前
STM32 和 ESP32
stm32·单片机·嵌入式硬件
嵌入式OG2 小时前
硬件-射频-PCB-常见天线分类-ESP32实例
嵌入式硬件·硬件工程·智能硬件·pcb工艺
JoneMaster2 小时前
[读书日志]从零开始学习Chisel 第一篇:书籍介绍,Scala与Chisel概述,Scala安装运行(敏捷硬件开发语言Chisel与数字系统设计)
开发语言·后端·嵌入式硬件·fpga开发·架构·scala
code .2 小时前
STM32G431收发CAN
单片机·嵌入式硬件·can·canfd·stm32g4
xachary2 小时前
Arduino 小白的 DIY 空气质量检测仪(5)- OLED显示模块、按钮模块
物联网·嵌入式·arduino
V+zmm101343 小时前
基于微信小程序投票评选系统的设计与实现ssm+论文源码调试讲解
java·数据库·微信小程序·小程序·毕业设计·ssm
无情大菜刀3 小时前
EPS32基础篇开发
单片机·嵌入式硬件
赵谨言4 小时前
基于 Python 虎扑网站的 NBA 球员大数据分析与可视化
经验分享·python·毕业设计