一、成品简介

1.实物成品

   成品展示:

2.物联网APP

        物联网APP界面:

        物联网显示界面:

3.SD卡存储

        SD卡文本文档打开界面:

二、项目简介

1.功能详解

基于单片机的智能仓库环境监控系统(语音)

  • 环境参数采集:使用DHT11温湿度传感器采集仓库内温度和湿度;TVOC-301气体检测模块采集甲醛和CO₂浓度;SR-04超声波测距模块用于模拟货架间距或货物堆放距离测量。

  • 本地实时显示:0.96寸OLED屏幕实时显示当前温湿度、气体浓度及距离数值,方便现场巡查。

  • 数据本地存储:通过SDIO接口连接Micro SD卡,并移植FATFS文件系统,将环境数据以文本日志形式追加写入date.txt文件,实现断电不丢失的历史记录存储。

  • 远程数据上传:ESP8266 Wi-Fi模块连接无线网络,通过MQTT协议将采集数据上传至OneNet物联网云平台,支持JSON格式数据包定时发送。

  • 远程监控与可视化:手机App(基于uni-app框架)通过调用OneNet API接口,实时拉取并显示温度、湿度、甲醛、CO₂等参数,实现远程随时查看仓库环境状态。

  • 异常预警与报警:在OneNet平台配置规则引擎,当温湿度或气体浓度超出预设阈值时,自动触发邮件报警,同时本地蜂鸣器发声提醒,实现双重告警。

  • 数据可靠性处理:对传感器数据进行校验和检查(如TVOC-301帧校验)及低通滤波(超声波测距),确保采集数据的准确性和稳定性。

        硬件框图:

        流程图:

2.主要器件

  • STM32F103ZeT6单片机
  • DHT11温湿度传感器

  • TVOC-301平面气体传感器模块(可检测甲醛和CO₂)

  • SR-04超声波测距模块

  • ESP8266-01S Wi-Fi模块

  • 0.96寸OLED显示屏(I²C接口)

  • Micro SD卡(用于本地数据存储)

  • 有源蜂鸣器(用于本地声音报警)

3.原理图设计

        该系统供电没有额外设计,将充电宝或者充电线插入单片机输入端即可供电。使用需注意SD卡,上电会先检测SD卡,当SD卡未插入不记录数据。

4.PCB设计

三、主要模块

1.ESP8266物联网模块

        ESP8266-01S是一款高度集成的串口转Wi-Fi模块,内置TCP/IP协议栈,通过串口和STM32F103ZET6连接,波特率为115200,通过发送标准AT指令集对模块进行控制

        使用中需要注意:模块的串口发送缓冲区有限,单次发送的JSON数据包不可过大,避免发送导致栈或者数组溢出;所有AT指令以“\r\n”结尾,

2.SR-04超声波模块

        SR-04是一款利用声波反射原理进行非接触式测距的常用模块,测量范围2cm~300cm,使用时,主控需在Trig引脚产生一个持续10微秒以上的高电平脉冲,模块便会自动发射40kHz的超声波并开始计时;当接收到回波后,Echo引脚会输出一个高电平,其持续时间即为声波往返的时间。根据公式“距离 = 高电平时间 × 声速(340m/s)/ 2”计算出实际距离,单次测量值会出现跳变,因此代码中采用了一阶低通滤波算法对原始距离进行平滑处理。

3.DHT11模块

        DHT11是一款数字式温湿度复合传感器,内部集成电容式湿敏元件和热敏电阻,采用单总线(One-Wire)协议与主控通信,能够同时输出温度和湿度数据。该模块对时序精度要求较高,建议使用硬件定时器或精确的微秒级延时函数。

4.TVOC-301平面气体传感器模块

        TVOC-301是一款微型空气质量检测模组,可同时检测甲醛(量程0~2000μg/m³)和二氧化碳(量程0~5000ppm)浓度,该模块采用UART串口通信,波特率固定为9600。模块每秒钟输出一帧固定长度为9字节的数据包,帧头固定为0x2C和0xE4,随后依次为TVOC浓度高字节、低字节,甲醛浓度高字节、低字节,CO₂浓度高字节、低字节,以及最后1字节的校验和(计算方法为前面8个字节的累加和取低8位)。STM32通过串口中断逐字节接收,状态机判断当前字节所属字段。

5.Micro SD卡本地存储模块

        Micro SD卡作为本地数据存储介质,用于实现环境监测日志的持久化保存,即使在网络中断的情况下也能完整记录历史数据。本系统通过STM32F103ZET6自带的SDIO接口与Micro SD卡进行通信,每次写入操作后必须及时关闭文件,因为FATFS在写入时会将数据暂存在缓存中,若意外断电不关闭文件,不仅当前数据会丢失,还可能导致文件分配表损坏。

6.单片机主控

        STM32F103ZET6 是 意法半导体公司所推出的一款基于 ARM Cortex-M3 内核的高性能微控制器,主频具有 72MHz,具备 512KB Flash 和 64KB RAM,支持丰富的通信外设接口,包括多个 UART、I²C、SPI、CAN 以及 SDIO 接口,非常适合本系统中多传感器数据采集、SD 卡存储与 Wi-Fi 通信的需求。

四、写作内容参考

1.摘要参考内容

随着现代物流和仓储行业的快速发展,传统依赖人工巡检的仓库环境监控方式难以满足对温湿度、空气质量等关键参数实时性、准确性和智能化管理的需求。为此,本设计提出一种基于STM32的智能仓库环境监控系统,在实现对仓储环境的全面管理、本地存储与远程预警。

本设计包含两大部分:

第一部分:系统硬件选型设计,以STM32F103ZET6单片机为主控,使用DHT11温湿度传感器、TVOC-301气体检测模块、SR-04超声波测距模块,用于采集环境管理的数据;通过ESP8266 Wi-Fi模块实现与OneNet物联网平台的通信;用 SD卡进行数据本地的存储。

第二部分:系统软件选型设计,完成传感器数据采集、校验与滤波处理;用FATFS文件系统去实现SD卡的日志记录;通过ESP8266模块把数据上传至OneNet平台,通过邮箱实现异常报警的自动邮件告警;通过手机App实现远程数据可视化与实时状态查看。

2.目录参考内容

五、项目主要源码 

5.1.单片机源码

5.1.1主程序

#include "bord.h"
void boid_level_init(void)
{
    //
}
uint8_t oled_buffer[32];
extern uint64_t Mytotal_size ;
extern uint64_t Myfree_size ;
int8_t wifi_error=0;int8_t mqtt_error=0;
uint32_t co2_value;
uint32_t JiaQuan_value;
uint32_t distance_value;
uint32_t sys_10ms;
uint8_t value_error;
uint8_t distance_error;
//阈值设置
uint16_t dh11_humi_max =90;
uint16_t dh11_temp_min =5;
uint16_t co2_max      =1000;
uint16_t disance_max  =50;
uint16_t jiaquan_max  =100;
void bord_init(void)
{
    debug_init();
    led_init(&beep_pb1, BEEP_GPIO_Port, BEEP_Pin, 1);
    led_off(&beep_pb1);
    led_init(&led_pc13, LED_GPIO_Port, LED_Pin, 1);
    led_on(&led_pc13);
    delay_init();
    OLED_Init();
		OLED_NewFrame();
		OLED_PrintString(0, 0, "系统初始化...", &font16x16, OLED_COLOR_NORMAL);
		OLED_PrintString(0, 16, "请等候...", &font16x16, OLED_COLOR_NORMAL);
		OLED_ShowFrame();
		HAL_Delay(500);
    sdio_fats_init();HAL_Delay(50);
		OLED_NewFrame();
	  sprintf((char *)oled_buffer, "Total: %llu MB",   Mytotal_size / (1024 * 1024)  );
		OLED_PrintString(0, 0,(char *) oled_buffer, &font16x16, OLED_COLOR_NORMAL);
		sprintf((char *)oled_buffer, "Free: %llu MB", Myfree_size / (1024 * 1024));
		OLED_PrintString(0, 16, (char *) oled_buffer, &font16x16, OLED_COLOR_NORMAL);
		OLED_ShowFrame();
		HAL_Delay(1000);
    co2_init();
    led_off(&led_pc13);
		esp8266_rx_init();
		OLED_NewFrame();
		OLED_PrintString(0, 0, (char *) "OneNetConnect...", &font16x16, OLED_COLOR_NORMAL);
		OLED_ShowFrame();
//    if(wifi_init()!=0){wifi_error=1;}
//		if(wifi_error==1){OLED_PrintString(0, 16,(char *) "WIFI_Error!", &font16x16, OLED_COLOR_NORMAL);}
//		else             {OLED_PrintString(0, 16,(char *) "WIFI_OK!", &font16x16, OLED_COLOR_NORMAL);}
		wifi_init();
		OLED_PrintString(0, 16,(char *) "WIFI_OK!", &font16x16, OLED_COLOR_NORMAL);
		OLED_ShowFrame();
		mqtt_init();
		OLED_PrintString(0, 32,(char *) "MQTT_OK!", &font16x16, OLED_COLOR_NORMAL);
    OLED_ShowFrame();
		HAL_Delay(500);
		OLED_NewFrame();
		cs_init();
}
DHT11_Data_TypeDef dh11_temp;
DHT11_Data_TypeDef dh11_date;
int8_t dh11_date_error=0;
int8_t CO2_date_error=0;
int8_t JiaQuan_date_error=0;
extern uint16_t formaldehyde_concentration ; // 甲醛浓度 (ug/m³)
extern uint16_t eco2_concentration ;       // 等效eCO2浓度 (PPM)
uint8_t count;
void bord_loop_task(void)
{

		// 数据显示
	sprintf((char *)oled_buffer,"温度:%d",dh11_date.temp_int);
	OLED_PrintString(0, 0, (char *)oled_buffer, &font16x16, OLED_COLOR_NORMAL);
	sprintf((char *)oled_buffer,"湿度:%d",dh11_date.humi_int);
	OLED_PrintString(64, 0, (char *)oled_buffer, &font16x16, OLED_COLOR_NORMAL);
	sprintf((char *)oled_buffer,"CO2:%d ppm",co2_value);
	OLED_PrintString(0, 16, (char *)oled_buffer, &font16x16, OLED_COLOR_NORMAL);
	sprintf((char *)oled_buffer,"甲醛:%d ug/m",JiaQuan_value);
	OLED_PrintString(0, 32, (char *)oled_buffer, &font16x16, OLED_COLOR_NORMAL);
	sprintf((char *)oled_buffer,"距离:%d cm",distance_value);
	OLED_PrintString(0,48, (char *)oled_buffer, &font16x16, OLED_COLOR_NORMAL);
	OLED_ShowFrame();
    // 湿度温度采集
    if (DHT11_ReadData(&dh11_temp)) 
    {
//        debug_send("Humi%d RH,Temperture%d℃\r\n", dh11_temp.humi_int, dh11_temp.temp_int);
        dh11_date = dh11_temp;
        sdio_fats_write(dh11_date.temp_int ,dh11_date.humi_int) ;
    }
    // 距离采集distance
    cs_read();
//    debug_send("distance%.2f cm\r\n", distance);
		if(distance<256)
		{
			distance_value=low_pass_filter(distance);
		}	 
    //CO2采集 甲醛测量
		co2_value=eco2_concentration;
		JiaQuan_value=formaldehyde_concentration;
    // 数据错误处理 
    if(dh11_date.humi_int>dh11_humi_max||dh11_date.temp_int<dh11_temp_min){dh11_date_error=1;}
		else{dh11_date_error=0;}
		 if(co2_value>co2_max){CO2_date_error=1;}	else{CO2_date_error=0;}
		 if(JiaQuan_value>jiaquan_max){JiaQuan_date_error=1;}	else{JiaQuan_date_error=0;}
		 if(distance_value>disance_max){distance_error=1;}	else{distance_error=0;}
		// 数据异常
		if(dh11_date_error==1||CO2_date_error==1|| JiaQuan_date_error==1|| distance_error==1){value_error=1;}
		if(value_error==1)
		{
			value_error=0;
			HAL_Delay(1000);
			OLED_NewFrame();
			if(dh11_date_error==1)
			{OLED_PrintString(0,0, (char *)"温湿度数据错误!", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				led_on(&beep_pb1);
				HAL_Delay(1000);
				led_off(&beep_pb1);
				OLED_PrintString(0,0, (char *)"                ", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				led_on(&beep_pb1);
				HAL_Delay(1000);
				led_off(&beep_pb1);
				OLED_PrintString(0,0, (char *)"温湿度数据错误!", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				led_on(&beep_pb1);
				HAL_Delay(1000);
				led_off(&beep_pb1);
				OLED_PrintString(0,0, (char *)"                ", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				HAL_Delay(1000);
			}
			if(CO2_date_error==1)
			{OLED_PrintString(0,16, (char *)"二氧化碳数据错误!", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				led_on(&beep_pb1);
				HAL_Delay(1000);
				led_off(&beep_pb1);
				OLED_PrintString(0,16, (char *)"                  ", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				led_on(&beep_pb1);
				HAL_Delay(1000);
				led_off(&beep_pb1);
				OLED_PrintString(0,16, (char *)"二氧化碳数据错误!", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				led_on(&beep_pb1);
				HAL_Delay(1000);
				led_off(&beep_pb1);
				OLED_PrintString(0,16, (char *)"                   ", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				HAL_Delay(1000);
			}
			if(JiaQuan_date_error==1)
			{OLED_PrintString(0,32, (char *)"甲醛数据错误!", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				led_on(&beep_pb1);
				HAL_Delay(1000);
				led_off(&beep_pb1);
				OLED_PrintString(0,32, (char *)"              ", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				led_on(&beep_pb1);
				HAL_Delay(1000);
				led_off(&beep_pb1);
				OLED_PrintString(0,32, (char *)"甲醛数据错误!", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				led_on(&beep_pb1);
				HAL_Delay(1000);
				led_off(&beep_pb1);
				OLED_PrintString(0,16, (char *)"               ", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				HAL_Delay(1000);
			}
			if(distance_error==1)
			{
				OLED_PrintString(0,32, (char *)"距离数据错误!", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				led_on(&beep_pb1);
				HAL_Delay(1000);
				led_off(&beep_pb1);
				OLED_PrintString(0,32, (char *)"              ", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				led_on(&beep_pb1);
				HAL_Delay(1000);
				led_off(&beep_pb1);
				OLED_PrintString(0,32, (char *)"距离数据错误!", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				led_on(&beep_pb1);
				HAL_Delay(1000);
				led_off(&beep_pb1);
				OLED_PrintString(0,16, (char *)"               ", &font16x16, OLED_COLOR_NORMAL);
				OLED_ShowFrame();
				HAL_Delay(1000);
			}
		}

	HAL_Delay(10);
	sys_10ms++;
	// WIFI任务
	if(sys_10ms>=20)
	{sys_10ms=0;
		count=(count+1)%4;
		switch(count)
		{
			case 0:esp8266_connect_send("Humi",dh11_date.humi_int, 100) ; break;
			case 1:esp8266_connect_send("JiaQuan",JiaQuan_value, 100) ; break;
			case 2:esp8266_connect_send("CO2",co2_value, 100) ; break;
			case 3:esp8266_connect_send("Temp",dh11_date.temp_int, 100) ; break;
		}
	}
}

5.1.2 DHT11代码

#include "dt11.h"

static void DHT11_PP_OUT(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.Pin = DHT11_PIN;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;	
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(DHT11_PORT, &GPIO_InitStruct);
}

/**
 * ************************************************************************
 * @brief 将DHT11配置为上拉输入模式
 * ************************************************************************
 */
static void DHT11_UP_IN(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.Pin = DHT11_PIN;
	GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
	GPIO_InitStruct.Pull = GPIO_PULLUP;	//上拉
	HAL_GPIO_Init(DHT11_PORT, &GPIO_InitStruct);
}

/**
 * ************************************************************************
 * @brief 读取字节
 * @return temp
 * ************************************************************************
 */
uint8_t DHT11_ReadByte(void)
{
	uint8_t i, temp = 0;

	for (i = 0; i < 8; i++)
	{
		while (DHT11_ReadPin == 0);		// 等待低电平结束
		
		delay_us(40);			//	延时 40 微秒
		
		if (DHT11_ReadPin == 1)
		{
			while (DHT11_ReadPin == 1);	// 等待高电平结束
			
			temp |= (uint8_t)(0X01 << (7 - i));			// 先发送高位
		}
		else
		{
			temp &= (uint8_t)~(0X01 << (7 - i));
		}
	}
	return temp;
}

/**
 * ************************************************************************
 * @brief 读取一次数据
 * @param[in] DHT11_Data  定义的结构体变量
 * @return 0或1(数据校验是否成功)
 * @note 它首先向DHT11发送启动信号,然后等待DHT11的应答。如果DHT11正确应答,
 * 		 则继续读取湿度整数、湿度小数、温度整数、温度小数和校验和数据,
 * 		 并计算校验和以进行数据校验
 * ************************************************************************
 */
uint8_t DHT11_ReadData(DHT11_Data_TypeDef *DHT11_Data)
{
	DHT11_PP_OUT();			// 主机输出,主机拉低
	DHT11_PULL_0;	
	HAL_Delay(18);				// 延时 18 ms
	
	DHT11_PULL_1;					// 主机拉高,延时 30 us
	delay_us(30);	

	DHT11_UP_IN();				// 主机输入,获取 DHT11 数据
	
	if (DHT11_ReadPin == 0)				// 收到从机应答
	{
		while (DHT11_ReadPin == 0);		// 等待从机应答的低电平结束
		
		while (DHT11_ReadPin == 1);		// 等待从机应答的高电平结束
		
		/*开始接收数据*/   
		DHT11_Data->humi_int  = DHT11_ReadByte();
		DHT11_Data->humi_dec = DHT11_ReadByte();
		DHT11_Data->temp_int  = DHT11_ReadByte();
		DHT11_Data->temp_dec = DHT11_ReadByte();
		DHT11_Data->check_sum = DHT11_ReadByte();
		
		DHT11_PP_OUT();		// 读取结束,主机拉高
		DHT11_PULL_1;	
		
		// 数据校验
		if (DHT11_Data->check_sum == DHT11_Data->humi_int + DHT11_Data->humi_dec + DHT11_Data->temp_int + DHT11_Data->temp_dec)	
		{
			return 1;
		}		
		else
		{
			return 0;
		}
	}
	else		// 未收到从机应答
	{
		return 0;
	}
}

5.1.3 TVOC-301代码

#include "co2.h"
uint8_t co2_rx;  // 存储每次中断接收的字节
uint16_t tvoc_concentration = 0;       // TVOC浓度 (ug/m³)
uint16_t formaldehyde_concentration = 0; // 甲醛浓度 (ug/m³)
uint16_t eco2_concentration = 0;       // 等效eCO2浓度 (PPM)
uint8_t co2_data_ready = 0;            // 数据解析完成标志

void co2_init(void)
{
    HAL_UART_Receive_IT(&huart3, &co2_rx, 1);
}

void co2_it_get(void)
{
  static uint8_t co2_state = 0;
    static uint8_t tvoc_temp[2];          // TVOC高低字节
    static uint8_t formaldehyde_temp[2];  // 甲醛高低字节
    static uint8_t eco2_temp[2];          // eCO2高低字节
    static uint8_t sum = 0;               // 校验和(只保留低8位)

    switch (co2_state)
    {
        case 0: // 等待帧头 0x2C
            if (co2_rx == 0x2C) {
                sum = 0x2C;
                co2_state = 1;
            }
            break;

        case 1: // 等待 0xE4
            if (co2_rx == 0xE4) {
                sum += 0xE4;
                co2_state = 2;
            } else {
                co2_state = 0; // 帧头错误,重置
            }
            break;

        case 2: // TVOC 高字节
            tvoc_temp[0] = co2_rx;
            sum += co2_rx;
            co2_state = 3;
            break;

        case 3: // TVOC 低字节
            tvoc_temp[1] = co2_rx;
            sum += co2_rx;
            co2_state = 4;
            break;

        case 4: // 甲醛 高字节
            formaldehyde_temp[0] = co2_rx;
            sum += co2_rx;
            co2_state = 5;
            break;

        case 5: // 甲醛 低字节
            formaldehyde_temp[1] = co2_rx;
            sum += co2_rx;
            co2_state = 6;
            break;

        case 6: // eCO2 高字节
            eco2_temp[0] = co2_rx;
            sum += co2_rx;
            co2_state = 7;
            break;

        case 7: // eCO2 低字节
            eco2_temp[1] = co2_rx;
            sum += co2_rx;
            co2_state = 8;
            break;

        case 8: // 校验和(第9字节)
            sum &= 0xFF; // 只保留低8位(重要!)
            if (sum == co2_rx) {
                // 解析成功
                tvoc_concentration = (tvoc_temp[0] << 8) | tvoc_temp[1];
                formaldehyde_concentration = (formaldehyde_temp[0] << 8) | formaldehyde_temp[1];
                eco2_concentration = (eco2_temp[0] << 8) | eco2_temp[1];
                co2_data_ready = 1;
            } else {
                co2_data_ready = 0; // 校验失败
            }
            // 重置状态,准备下一帧
            co2_state = 0;
            sum = 0;
            break;

        default:
            co2_state = 0;
            sum = 0;
            break;
    }

		debug_send("%c",co2_rx);
    HAL_UART_Receive_IT(&huart3, &co2_rx, 1); // 启动下一次中断接收
}

5.1.4 SD卡代码

 #include "sd.h"
 extern	uint8_t retSD;    /* Return value for SD */
 extern	char SDPath[4];   /* SD logical drive path */
 extern	FATFS SDFatFS;    /* File system object for SD logical drive */
 extern	FIL SDFile;       /* File object for SD */
uint64_t Mytotal_size ;
uint64_t Myfree_size ;
/**
 * @brief SD卡FATFS初始化,挂载SD卡并打印内存信息
 */
void sdio_fats_init() {
    FRESULT fre;
    DWORD total_clusters, free_clusters;
    UINT sector_size;
    char info_buf[128];
    FATFS *p_fs;  // 用于接收f_getfree返回的文件系统指针

    // 挂载SD卡
    fre = f_mount(&SDFatFS, SDPath, 1);
    if (fre == FR_OK) {
        HAL_UART_Transmit(&huart1, (uint8_t*)"SD mount ok\r\n", sizeof("SD mount ok\r\n"), 1000);

        // 获取SD卡内存信息
        fre = f_getfree(SDPath, &free_clusters, &p_fs);
        if (fre == FR_OK) {
            // 从返回的文件系统指针获取参数,SD卡标准扇区大小为512字节(替代ssize)
            total_clusters = (p_fs->n_fatent - 2);  // 总簇数(减去保留簇)
            sector_size = 512;  // SD卡默认扇区大小为512字节,修复ssize不存在的问题
            uint64_t total_size = (uint64_t)total_clusters * p_fs->csize * sector_size;
            uint64_t free_size = (uint64_t)free_clusters * p_fs->csize * sector_size;

					
					 Mytotal_size =total_size;
						Myfree_size=free_size ;
					
            // 格式化容量为MB
            sprintf(info_buf, "Total: %llu MB, Free: %llu MB\r\n", 
                    total_size / (1024 * 1024), 
                    free_size / (1024 * 1024));
            HAL_UART_Transmit(&huart1, (uint8_t*)info_buf, strlen(info_buf), 1000);
        } else {
            HAL_UART_Transmit(&huart1, (uint8_t*)"Get SD info error\r\n", sizeof("Get SD info error\r\n"), 1000);
        }
    } else {
        HAL_UART_Transmit(&huart1, (uint8_t*)"SD mount error\r\n", sizeof("SD mount error\r\n"), 1000);
    }
}

/**
 * @brief 向date.txt追加写入温湿度数据
 * @param temp 温度值
 * @param humi 湿度值
 */
void sdio_fats_write(uint32_t temp, uint32_t humi) {
    FRESULT fre;
    UINT bw;
    char data_buf[64];

    // 格式化数据(%lu适配uint32_t)
    sprintf(data_buf, "Temp:%d,Humi:%d\r\n", temp, humi);

    // 打开文件:不存在则创建,存在则以写模式打开
    fre = f_open(&SDFile, "date.txt", FA_OPEN_ALWAYS | FA_WRITE);
    if (fre == FR_OK) {
        // 移动文件指针到末尾,实现追加效果
        f_lseek(&SDFile, f_size(&SDFile));

        // 写入数据
        fre = f_write(&SDFile, data_buf, strlen(data_buf), &bw);
        if (fre == FR_OK && bw == strlen(data_buf)) {
            HAL_UART_Transmit(&huart1, (uint8_t*)"Write success\r\n", sizeof("Write success\r\n"), 1000);
        } else {
            HAL_UART_Transmit(&huart1, (uint8_t*)"Write error\r\n", sizeof("Write error\r\n"), 1000);
        }
        // 关闭文件
        f_close(&SDFile);
    } else {
        HAL_UART_Transmit(&huart1, (uint8_t*)"Open file error\r\n", sizeof("Open file error\r\n"), 1000);
    }
}

六、工程主要内容

   工程的具体内容包括但不限于原理图、程序代码、流程图等等。完整工程、原理图、APP 源码我已经开源到 Gitee,需要的同学可以评论区留言或者私信,004-基于STM32的智能仓库环境监控系统。

开源主要内容:STM32毕设项目分享:基于STM32的智能仓库环境监控系统(物联网、FATFS文件操作系统)

更多推荐