• [技术干货] VisualStudio(VS)设置程序的版本信息(C-C++)
    一、前言在软件开发过程中,通常需要为生成的程序添加一些重要的元数据,如版本号、公司名称和版权信息。这些信息不仅可以提供对程序的更详细描述,还可以帮助用户了解程序的来源和使用限制。在 Visual Studio (以2017为例)中,可以轻松地设置这些信息,使应用程序具有更专业、规范的要求。本文将介绍如何在 Visual Studio 2017 中设置生成程序的版本信息、公司信息和版权信息逐步指导大家完成这个过程,无论是开发新项目,还是为现有项目添加这些重要的元数据,都能从本文中获得帮助。下面将详细说明每个步骤,并提供示例。二、添加版本信息【1】选择左边项目选项卡,鼠标点击右键,点击添加,选择新建项。【2】选项资源—>资源文件(.rc)—>最后点击添加。【3】添加之后在解决方案选项卡里就可以看到添加的资源文件,双击资源文件就可以打开编辑资源。【4】在资源编辑页面,右键,点击添加资源。【5】选择版本,点击新建。【6】填写版本信息,公司信息等数据。下面是填写好的: 我只是填写了文件版本和公司名称。【7】回到解决方案页面,重新编译生成程序。【8】在应用程序目录下,查看版本信息。把鼠标光标放在文件上就会弹出提示状态窗,看到文件信息。在文件属性里可以看到信息。三、编辑版本信息版本文件创建之后,在代码目录下可以看到刚才创建的版本资源文件。用记事本打开之后就能看到刚才填写信息,在这里更改重新编译代码也可以的(要注意格式)。// Microsoft Visual C++ generated resource script.//#include "resource.h"#define APSTUDIO_READONLY_SYMBOLS///////////////////////////////////////////////////////////////////////////////// Generated from the TEXTINCLUDE 2 resource.//#include "winres.h"/////////////////////////////////////////////////////////////////////////////#undef APSTUDIO_READONLY_SYMBOLS/////////////////////////////////////////////////////////////////////////////// 中文(简体,中国) resources#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED#ifdef APSTUDIO_INVOKED///////////////////////////////////////////////////////////////////////////////// TEXTINCLUDE//1 TEXTINCLUDE BEGIN "resource.h\0"END2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0"END3 TEXTINCLUDE BEGIN "\r\n" "\0"END#endif // APSTUDIO_INVOKED///////////////////////////////////////////////////////////////////////////////// Version//VS_VERSION_INFO VERSIONINFO FILEVERSION 2,0,0,88 PRODUCTVERSION 1,0,0,1 FILEFLAGSMASK 0x3fL#ifdef _DEBUG FILEFLAGS 0x1L#else FILEFLAGS 0x0L#endif FILEOS 0x40004L FILETYPE 0x1L FILESUBTYPE 0x0LBEGIN BLOCK "StringFileInfo" BEGIN BLOCK "080404b0" BEGIN VALUE "CompanyName", "DS小龙哥" VALUE "FileDescription", "C语言Demo" VALUE "FileVersion", "2.0.0.88" VALUE "InternalName", "ConsoleA.exe" VALUE "LegalCopyright", "Copyright (C) 2023" VALUE "OriginalFilename", "ConsoleA.exe" VALUE "ProductName", "C语言Demo" VALUE "ProductVersion", "1.0.0.1" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x804, 1200 ENDEND#endif // 中文(简体,中国) resources/////////////////////////////////////////////////////////////////////////////#ifndef APSTUDIO_INVOKED///////////////////////////////////////////////////////////////////////////////// Generated from the TEXTINCLUDE 3 resource.///////////////////////////////////////////////////////////////////////////////#endif // not APSTUDIO_INVOKED四、字段含义介绍在上面给定的版本信息中,资源字段代表的含义如下:“CompanyName”:公司名称,表示开发和拥有此程序的公司或组织。在给定的版本信息中,公司名称被设置为"DS小龙哥666"。“FileDescription”:文件描述,用于描述文件的用途或功能。在给定的版本信息中,文件描述为"C语言Demo",表明这个文件是一个C语言示例程序。“FileVersion”:文件版本,指定生成文件的具体版本号。在给定的版本信息中,文件版本被设置为"2.0.0.88"。“InternalName”:内部名称,指定生成文件的内部名称。在给定的版本信息中,内部名称被设置为"ConsoleA.exe"。“LegalCopyright”:版权信息,表示此程序的版权归属。在给定的版本信息中,版权信息被设置为"Copyright © 2023",表明此程序的版权归属于2023年。“OriginalFilename”:原始文件名,指定生成文件的原始文件名。在给定的版本信息中,原始文件名被设置为"ConsoleA.exe"。“ProductName”:产品名称,表示此程序的名称。在给定的版本信息中,产品名称被设置为"C语言Demo"。“ProductVersion”:产品版本,指定此程序的发布版本号。在给定的版本信息中,产品版本被设置为"1.0.0.1"。
  • [技术干货] Windows下读取系统的内存、CPU、GPU等使用信息
    一、前言在当今计算机应用广泛的领域中,了解系统的内存、CPU和GPU使用情况是非常重要的。对于开发人员和系统管理员来说,准确获取这些信息可以帮助他们优化软件性能、诊断问题并做出相应的调整。在Windows平台上实现这一目标会涉及到调用Windows系统API,使用合适的工具和库来获取所需的信息。本文将介绍如何使用Qt和Windows API来读取系统的内存、CPU和GPU使用详细信息。将提供一个完整的示例代码,展示了如何使用这些技术来获取系统的关键性能指标。通过阅读本文,将学习如何使用Qt框架和Windows API来实现这些功能,以及如何根据需求进行扩展和定制。二、获取系统的配置信息​#include <QApplication>​#include <QtWidgets/QApplication>#include <QtWidgets/QMainWindow>#include <QtWidgets/QLabel>#include <QSysInfo>#include <QProcess>#include <QDebug>​#include <QDebug>#include <Windows.h>​​#pragma execution_character_set("utf-8")​int main(int argc, char *argv[]){ QApplication a(argc, argv);​​ QMainWindow window; window.resize(400, 300);​ QLabel *label = new QLabel(&window); label->setAlignment(Qt::AlignCenter); label->setWordWrap(true); window.setCentralWidget(label);​ // 获取系统内存信息 QString memoryInfo = "Memory Information:\n";​ MEMORYSTATUSEX memoryStatus; memoryStatus.dwLength = sizeof(memoryStatus); if (GlobalMemoryStatusEx(&memoryStatus)) { memoryInfo+=QString("Total Physical Memory: %1 %2\n").arg(memoryStatus.ullTotalPhys / (1024 * 1024)).arg("MB"); memoryInfo+=QString("Available Physical Memory: %1 %2\n").arg(memoryStatus.ullAvailPhys / (1024 * 1024)).arg("MB"); memoryInfo+=QString("Total Virtual Memory: %1 %2\n").arg(memoryStatus.ullTotalVirtual / (1024 * 1024)).arg("MB"); memoryInfo+=QString("Available Virtual Memory: %1 %2\n").arg(memoryStatus.ullAvailVirtual / (1024 * 1024)).arg("MB"); } else { memoryInfo+=QString("无法获取内存使用情况信息。\n"); }​ // 获取CPU信息 QString cpuInfo = "CPU Information:\n"; QProcess cpuProcess; cpuProcess.start("wmic cpu get Name"); cpuProcess.waitForFinished(); QString cpuResult = cpuProcess.readAllStandardOutput(); QString cpuName = cpuResult.split("\n").at(1).trimmed(); cpuInfo += "Model: " + cpuName + "\n";​ // 获取GPU信息 QString gpuInfo = "GPU Information:\n"; QProcess gpuProcess; gpuProcess.start("wmic path win32_VideoController get Name"); gpuProcess.waitForFinished(); QString gpuResult = gpuProcess.readAllStandardOutput(); QStringList gpuList = gpuResult.split("\n", QString::SkipEmptyParts); for (int i = 1; i < gpuList.size(); i++) { QString gpuName = gpuList.at(i).trimmed(); gpuInfo += "GPU " + QString::number(i) + ": " + gpuName + "\n"; }​ // 在标签中显示系统信息 QString systemInfo = memoryInfo + "\n" + cpuInfo + "\n" + gpuInfo; label->setText(systemInfo);​ window.show();​​ //Widget w; //w.show(); return a.exec();}三、wmicwmic是Windows Management Instrumentation Command-line(WMI命令行)实用工具的缩写。它提供了一个命令行界面,可以通过WMI接口与操作系统进行交互和管理。以下是对wmic的详细介绍:【1】基本概念:Windows Management Instrumentation(WMI)是微软提供的一种标准化的系统管理技术,允许开发人员和管理员使用编程方式来监视和控制Windows操作系统上的资源。WMI提供了一个信息框架,以获取有关计算机硬件、软件和操作系统配置的详细信息。【2】功能:wmic允许用户通过命令行执行各种系统管理任务,包括查询、修改和监视操作系统中的各种设置和资源,如进程、服务、磁盘驱动器、网络适配器等。它还可以与远程计算机通信,并将结果输出为文本、XML或HTML格式。通过wmic,你可以轻松地获取系统信息、执行管理任务和编写自动化脚本。【3】语法和用法:wmic的基本语法是wmic <命令> [参数]。常用的命令包括:wmic os:获取操作系统的详细信息。wmic cpu:获取CPU的信息。wmic process:获取正在运行的进程列表。wmic service:获取系统服务的信息。wmic logicaldisk:获取逻辑磁盘驱动器的信息。wmic nicconfig:获取网络适配器配置的信息。示例用法:以下是使用wmic命令获取操作系统信息和CPU信息的示例:wmic os get Caption, Version, OSArchitecture:获取操作系统的名称、版本和体系结构。wmic cpu get Name, MaxClockSpeed, Manufacturer:获取CPU的名称、最大时钟速度和制造商。对于更复杂的查询和操作,可以使用WQL(WMI查询语言)来结合wmic命令。WQL类似于SQL,可以用于过滤和排序数据,并执行高级的系统管理任务。
  • [技术干货] 基于单片机的煤气泄漏检测报警装置设计
    一、项目介绍煤气泄漏是一种常见的危险情况,可能导致火灾、爆炸和人员伤亡。为了及时发现煤气泄漏并采取相应的安全措施,设计了一种基于单片机的煤气泄漏检测报警装置。主控芯片采用STM32F103C8T6作为主控芯片,具有强大的计算和控制能力。煤气检测传感器选择了MQ-5,它能够检测到环境中的煤气浓度,并将其转换为电信号输出。装置通过读取传感器输出的模拟信号,并经过ADC转换获得相应的数字值,实时监测煤气浓度。当检测到煤气浓度超过设定的安全阈值时,装置会触发报警机制。通过控制蜂鸣器发出高频报警声,吸引人的注意并提醒危险情况。同时,装置会控制LED灯光闪烁,以视觉方式提醒用户。这样的多重报警方式可以在不同环境中有效地引起人们的警觉。为了进一步提高报警的及时性和可靠性,还集成了SIM800C模块,用于发送报警短信给指定的联系人。当煤气浓度超标时,装置会通过SIM800C模块发送预先设定的报警短信,通知相关人员及时采取措施。通过以上设计,基于单片机的煤气泄漏检测报警装置能够实时监测环境中的煤气浓度,并在检测到异常情况时通过声光报警和短信通知提醒用户。这样的装置可以广泛应用于家庭、工业和商业环境中,为人们的生命财产安全提供有效的保障。二、硬件选型在这个设计中,选择了下面的硬件来实现煤气泄漏检测报警装置。【1】主控芯片:STM32F103C8T6。该芯片是一款强大的低功耗微控制器,具有丰富的外设和良好的性能。它集成了多个通用输入输出引脚、模数转换器 (ADC)、定时器和串行接口等功能,适合用于各种嵌入式应用。【2】煤气检测传感器:MQ-5。MQ-5是一种常见的煤气传感器,可检测到液化石油气(LPG)、天然气和甲烷等可燃气体。它基于半导体气敏材料,当检测到目标气体浓度超过设定阈值时,其电阻值发生变化。通过测量电阻值的变化,可以判断煤气的浓度是否超标。【3】蜂鸣器:用于发出报警声音,提醒人们注意。选择合适的蜂鸣器需要考虑其音量大小、工作电压和驱动方式等因素。【4】LED灯:用于提醒人们注意并指示报警状态。选择合适的LED需要考虑其亮度、工作电压和颜色等因素。【5】SIM800C模块:用于发送报警短信。SIM800C是一款支持GSM/GPRS通信的模块,可以通过AT指令与主控芯片进行通信。它具有较小的体积、低功耗和稳定的性能,适合物联网应用中的短信通信需求。三、系统设计思路软件设计思路:【1】初始化:对STM32F103C8T6单片机和外设进行初始化设置,包括引脚配置、时钟设置等。【2】煤气检测:使用ADC模块读取MQ-5传感器的模拟信号,并转换为相应的数字值。【3】煤气浓度判断:根据传感器的特性曲线,将读取到的数字值转换为实际的煤气浓度。【4】报警判断:判断煤气浓度是否超过设定的安全阈值。【5】报警处理:控制蜂鸣器发出报警声。控制LED灯光闪烁。使用SIM800C模块发送报警短信给指定的联系人。【6】主循环:在主循环中不断进行煤气检测和报警判断,保持系统的实时监测和报警功能。伪代码示例:初始化: 初始化STM32F103C8T6单片机和外设设置 主循环: while (true) { 煤气检测(); 煤气浓度判断(); 报警判断(); 延时一段时间; }​煤气检测: 读取MQ-5传感器的模拟信号; 将模拟信号转换为数字值;​煤气浓度判断: 根据传感器的特性曲线,将数字值转换为实际的煤气浓度;​报警判断: if (煤气浓度超过安全阈值) { 报警处理(); }​报警处理: 控制蜂鸣器发出报警声; 控制LED灯光闪烁; 使用SIM800C模块发送报警短信给指定的联系人;四、代码实现4.1 主程序#include "stm32f10x.h"​// 定义蜂鸣器控制引脚和LED控制引脚#define BUZZER_PIN GPIO_Pin_0#define BUZZER_PORT GPIOA​#define LED_PIN GPIO_Pin_1#define LED_PORT GPIOA​// 定义煤气传感器控制引脚#define GAS_SENSOR_PIN GPIO_Pin_2#define GAS_SENSOR_PORT GPIOA​// 定义SIM800C模块的发送引脚#define SIM800C_SEND_PIN GPIO_Pin_3#define SIM800C_SEND_PORT GPIOA​// 定义报警阈值#define THRESHOLD 1000​// 声明函数原型void GPIO_Init(void);void ADC_Init(void);void Buzzer_Alarm(void);void LED_Flash(void);void Send_SMS(void);​int main(void){ // 初始化GPIO和ADC GPIO_Init(); ADC_Init();​ while (1) { // 进行煤气浓度检测 u16 gasValue = ADC_GetConversionValue(ADC1);​ // 如果煤气浓度超过阈值,则触发报警 if (gasValue > THRESHOLD) { Buzzer_Alarm(); LED_Flash(); Send_SMS(); } }}​// GPIO初始化函数void GPIO_Init(void){ // 打开GPIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);​ GPIO_InitTypeDef gpioInitStruct;​ // 配置蜂鸣器引脚为推挽输出 gpioInitStruct.GPIO_Pin = BUZZER_PIN; gpioInitStruct.GPIO_Mode = GPIO_Mode_Out_PP; gpioInitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(BUZZER_PORT, &gpioInitStruct);​ // 配置LED引脚为推挽输出 gpioInitStruct.GPIO_Pin = LED_PIN; gpioInitStruct.GPIO_Mode = GPIO_Mode_Out_PP; gpioInitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(LED_PORT, &gpioInitStruct);​ // 配置煤气传感器引脚为模拟输入 gpioInitStruct.GPIO_Pin = GAS_SENSOR_PIN; gpioInitStruct.GPIO_Mode = GPIO_Mode_AIN; gpioInitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GAS_SENSOR_PORT, &gpioInitStruct);​ // 配置SIM800C模块的发送引脚为推挽输出 gpioInitStruct.GPIO_Pin = SIM800C_SEND_PIN; gpioInitStruct.GPIO_Mode = GPIO_Mode_Out_PP; gpioInitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(SIM800C_SEND_PORT, &gpioInitStruct);}​// ADC初始化函数void ADC_Init(void){ // 打开ADC时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);​ ADC_InitTypeDef adcInitStruct;​ // ADC配置 adcInitStruct.ADC_Mode = ADC_Mode_Independent; adcInitStruct.ADC_ScanConvMode = DISABLE; adcInitStruct.ADC_ContinuousConvMode = ENABLE; adcInitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; adcInitStruct.ADC_DataAlign = ADC_DataAlign_Right; adcInitStruct.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &adcInitStruct);​ // 配置ADC通道 ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 1, ADC_SampleTime_55Cycles5);​ // 打开ADC ADC_Cmd(ADC1, ENABLE);​ // 开始ADC转换 ADC_SoftwareStartConvCmd(ADC1, ENABLE);}​// 触发蜂鸣器报警void Buzzer_Alarm(void){ GPIO_SetBits(BUZZER_PORT, BUZZER_PIN); // 打开蜂鸣器 Delay(1000); // 延时一段时间 GPIO_ResetBits(BUZZER_PORT, BUZZER_PIN); // 关闭蜂鸣器}​// LED闪烁提醒void LED_Flash(void){ for (int i = 0; i < 10; i++) { GPIO_SetBits(LED_PORT, LED_PIN); // 打开LED Delay(500); // 延时一段时间 GPIO_ResetBits(LED_PORT, LED_PIN); // 关闭LED Delay(500); // 延时一段时间 }}​// 发送报警短信void Send_SMS(void){ // 这里编写与SIM800C模块通信的代码,发送报警短信}4.2 SIM800C代码#include "stm32f10x.h"#include <stdio.h>#include <string.h>​// 定义SIM800C模块的串口引脚#define SIM800C_USART USART2#define SIM800C_USART_GPIO GPIOA#define SIM800C_USART_TX_PIN GPIO_Pin_2#define SIM800C_USART_RX_PIN GPIO_Pin_3​// 定义发送缓冲区和接收缓冲区的大小#define TX_BUFFER_SIZE 128#define RX_BUFFER_SIZE 128​// 声明全局变量char tx_buffer[TX_BUFFER_SIZE]; // 发送缓冲区char rx_buffer[RX_BUFFER_SIZE]; // 接收缓冲区uint8_t rx_index = 0; // 接收缓冲区索引​// 函数原型void USART2_Init(void);void USART2_SendString(const char* string);void USART2_IRQHandler(void);void Send_AT_Command(const char* at_command);void Send_SMS(void);​int main(void){ USART2_Init(); while (1) { // 在主循环中调用Send_SMS函数即可触发发送短信 }}​// 初始化USART2void USART2_Init(void){ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); // 打开GPIOA和AFIO时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // 打开USART2时钟​ GPIO_InitTypeDef gpioInitStruct; USART_InitTypeDef usartInitStruct; NVIC_InitTypeDef nvicInitStruct;​ // 配置USART2的GPIO引脚 gpioInitStruct.GPIO_Pin = SIM800C_USART_TX_PIN; gpioInitStruct.GPIO_Mode = GPIO_Mode_AF_PP; gpioInitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(SIM800C_USART_GPIO, &gpioInitStruct);​ gpioInitStruct.GPIO_Pin = SIM800C_USART_RX_PIN; gpioInitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(SIM800C_USART_GPIO, &gpioInitStruct);​ // 配置USART2 usartInitStruct.USART_BaudRate = 115200; usartInitStruct.USART_WordLength = USART_WordLength_8b; usartInitStruct.USART_StopBits = USART_StopBits_1; usartInitStruct.USART_Parity = USART_Parity_No; usartInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; usartInitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_Init(SIM800C_USART, &usartInitStruct);​ // 配置USART2中断 nvicInitStruct.NVIC_IRQChannel = USART2_IRQn; nvicInitStruct.NVIC_IRQChannelPreemptionPriority = 0; nvicInitStruct.NVIC_IRQChannelSubPriority = 0; nvicInitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvicInitStruct);​ // 启动USART2接收中断 USART_ITConfig(SIM800C_USART, USART_IT_RXNE, ENABLE);​ // 启动USART2 USART_Cmd(SIM800C_USART, ENABLE);}​// 发送字符串到USART2void USART2_SendString(const char* string){ uint16_t length = strlen(string); for (uint16_t i = 0; i < length; i++) { USART_SendData(SIM800C_USART, string[i]); while (USART_GetFlagStatus(SIM800C_USART, USART_FLAG_TXE) == RESET); }}​// USART2中断处理函数void USART2_IRQHandler(void){ if (USART_GetITStatus(SIM800C_USART, USART_IT_RXNE) != RESET) { char receivedData = USART_ReceiveData(SIM800C_USART); rx_buffer[rx_index++] = receivedData;​ // 处理接收到的数据 // ...​ USART_ClearITPendingBit(SIM800C_USART, USART_IT_RXNE); }}​// 发送AT指令void Send_AT_Command(const char* at_command){ memset(rx_buffer, 0, RX_BUFFER_SIZE); // 清空接收缓冲区 rx_index = 0;​ USART2_SendString(at_command);​ // 等待模块返回数据 while (strncmp(rx_buffer, "OK", 2) != 0 && strncmp(rx_buffer, "ERROR", 5) != 0) { // 延时一段时间 Delay(10); }​ // 处理模块返回的数据 // ...}​// 发送短信void Send_SMS(void){ // 发送AT指令设置短信模式 Send_AT_Command("AT+CMGF=1\r\n");​ // 发送AT指令设置短信收件人和内容 Send_AT_Command("AT+CMGS=\"+861**********\"\r\n"); Send_AT_Command("Hello, this is a test message.\r\n");​ // 发送短信结束标志(Ctrl+Z) USART2_SendString("\x1A");}​五、总结本项目设计了一个基于单片机的煤气泄漏检测报警装置,通过使用STM32F103C8T6作为主控芯片和MQ-5煤气传感器进行煤气检测,有效地提醒用户煤气泄漏的危险。通过控制蜂鸣器和LED灯的报警机制,以及使用SIM800C模块发送报警短信,用户可以及时采取措施来避免潜在的危险。这个项目结合了硬件设计和嵌入式软件开发的知识,为用户提供了一个可靠的煤气泄漏检测报警解决方案。
  • [技术干货] 基于STM32的花卉温室控温系统设计
    一、前言随着人们对花卉养殖的需求不断增长,花卉温室的建设和管理成为了一个重要的课题。在花卉温室中,温度是一个至关重要的环境参数,对花卉的生长和发展有着直接的影响。为了提供一个稳定的生长环境,控制温室的温度变得非常重要。本项目设计一个基于STM32微控制器的花卉温室控温系统。该系统利用STM32F103C8T6作为主控芯片,通过与DS18B20温度传感器和0.96寸OLED显示屏等硬件模块的连接,实现对温室内温度的监测和控制。同时,系统还配备了两个独立按键,用于设置温度阀值。温度传感器采用DS18B20,能够准确地监测温室内的温度。通过与STM32微控制器的通信,可以实时获取温度数据。显示屏采用SPI协议的0.96寸OLED显示屏,用于显示当前环境的温度以及温度阀值。用户可以通过按键设置温度阀值,以便系统能够根据设定的阀值进行温度控制。当温度低于设定的温度阀值时,系统将通过继电器控制热风机进行加热,吹出热风来控制室温。通过实时监测温度并根据设定的阀值进行控制,系统能够保持温室内的温度在一个适宜的范围,为花卉提供一个稳定的生长环境。项目的设计用于提高花卉温室的自动化程度,减轻人工管理的负担,同时提供一个稳定的温度控制方案,以促进花卉的生长和发展。通过使用STM32微控制器和相关硬件模块,该系统能够实现温度的实时监测和自动控制,为花卉温室管理者提供了一种方便、高效的解决方案。加上远程控制之后的最终系统模型图:二、硬件选型介绍以下是基于STM32的花卉温室控温系统的硬件选型:【1】主控芯片:STM32F103C8T6STM32F103系列具有良好的性能和丰富的外设,适合嵌入式应用。STM32F103C8T6是一款32位ARM Cortex-M3内核的微控制器,具有64KB的Flash存储器和20KB的RAM。【2】温度传感器:DS18B20DS18B20是一款数字温度传感器,采用单总线接口进行通信。具有高精度、防水防尘等特点,非常适合测量温室内的温度。通过引脚连接到STM32的GPIO口,并使用OneWire协议进行数据通信。【3】显示屏:0.96寸OLED显示屏选择支持SPI协议的0.96寸OLED显示屏作为显示设备,可以方便地显示环境温度和温度阀值。OLED显示屏具有低功耗、高对比度、视角广等优点,适合嵌入式应用。【4】按键:两个独立按键选择两个独立按键用于设置温度阀值,可以通过按下按钮增加或减小温度阀值。【5】继电器:用于控制热风机加热根据温度阀值和实时温度数据,通过STM32的GPIO口控制继电器的开关,从而控制热风机的加热。继电器的选型要根据热风机的额定电流和电压来确定,确保能够正常工作。三、设计思路软件逻辑设计思路:【1】初始化STM32外设,包括GPIO、SPI、USART等。【2】设置温度阀值的初始值,并通过按键调节阀值。【3】循环读取DS18B20温度传感器的数据,并将读取到的温度值与阀值进行比较。【4】如果当前温度低于阀值,则控制继电器闭合,热风机开始加热;否则,打开继电器,停止加热。【5】将温度值和阀值显示在OLED屏幕上,通过USART串口输出给用户。【6】不断循环执行以上步骤,实现温室的自动控温功能。伪代码:// 定义变量float temperature; // 当前温度值float threshold; // 温度阀值​// 初始化硬件和外设void initialize() { initialize_GPIO(); // 初始化GPIO initialize_SPI(); // 初始化SPI initialize_USART(); // 初始化USART initialize_DS18B20(); // 初始化DS18B20 initialize_OLED(); // 初始化OLED显示屏 initialize_Button(); // 初始化按键 initialize_Relay(); // 初始化继电器}​// 读取温度值float readTemperature() { // 通过DS18B20读取温度值 // 返回温度值}​// 读取阀值float readThreshold() { // 读取按键的状态,并调节阀值 // 返回阀值}​// 控制加热器void controlHeater(float currTemperature, float currThreshold) { if (currTemperature < currThreshold) { // 温度低于阀值,控制继电器闭合,热风机加热 } else { // 温度高于或等于阀值,打开继电器,停止加热 }}​// 显示温度和阀值void displayTemperature(float currTemperature, float currThreshold) { // 在OLED屏幕上显示温度值和阀值 // 通过USART串口输出温度值和阀值}​// 主函数int main() { initialize(); // 初始化 while (1) { temperature = readTemperature(); // 读取温度值 threshold = readThreshold(); // 读取阀值 controlHeater(temperature, threshold); // 控制加热器 displayTemperature(temperature, threshold);// 显示温度和阀值 }​ return 0;}以上是基本的软件逻辑设计思路和伪代码。四、代码实现4.1 读取温度显示下面是使用STM32F103C8T6读取DS18B20温度传感器数据,并将温度显示到OLED显示屏上的实现代码:#include "stm32f10x.h"#include "delay.h"#include "onewire.h"#include "ds18b20.h"#include "ssd1306.h"​int main(void){ // 初始化延迟函数 delay_init(); // 初始化OLED显示屏 SSD1306_Init(); // 初始化DS18B20温度传感器 DS18B20_Init(); float temperature = 0.0; char tempStr[10]; while (1) { // 读取DS18B20温度传感器数据 temperature = DS18B20_GetTemp(); // 将温度转换为字符串 sprintf(tempStr, "%.2f C", temperature); // 清空OLED显示屏 SSD1306_Clear(); // 在OLED显示屏上显示温度 SSD1306_GotoXY(0, 0); SSD1306_Puts("Temperature:", &Font_7x10, SSD1306_COLOR_WHITE); SSD1306_GotoXY(0, 20); SSD1306_Puts(tempStr, &Font_11x18, SSD1306_COLOR_WHITE); // 刷新OLED显示屏 SSD1306_UpdateScreen(); // 延时一段时间 delay_ms(1000); }}代码中,使用了封装好库文件,包括延迟函数(delay.h)、OneWire总线(onewire.h)、DS18B20温度传感器(ds18b20.h)和SSD1306 OLED显示屏(ssd1306.h)的库文件。在主函数中,初始化延迟函数和OLED显示屏,初始化DS18B20温度传感器。然后进入无限循环,在循环中读取DS18B20温度传感器的温度数据,将温度显示到OLED显示屏上。温度数据通过sprintf函数转换为字符串,使用SSD1306库函数在OLED显示屏上进行显示。通过延时函数延时一段时间,实现温度的定时更新。4.2 DS18B20的代码头文件代码:#ifndef DS18B20_H#define DS18B20_H​#include "stm32f10x.h"​// DS18B20引脚定义#define DS18B20_GPIO_PORT GPIOA#define DS18B20_GPIO_PIN GPIO_Pin_0​// DS18B20函数声明void DS18B20_Init(void);void DS18B20_WriteByte(uint8_t data);uint8_t DS18B20_ReadByte(void);float DS18B20_GetTemp(void);​#endif​源文件代码:#include "ds18b20.h"#include "delay.h"​// 初始化DS18B20温度传感器void DS18B20_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; // 使能GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 配置GPIOA引脚为推挽输出 GPIO_InitStructure.GPIO_Pin = DS18B20_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DS18B20_GPIO_PORT, &GPIO_InitStructure); // 将引脚拉低一段时间 GPIO_ResetBits(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN); delay_us(500); // 将引脚拉高一段时间 GPIO_SetBits(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN); delay_us(80); // 等待DS18B20的响应 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(DS18B20_GPIO_PORT, &GPIO_InitStructure); delay_us(80);}​// 向DS18B20写入一个字节的数据void DS18B20_WriteByte(uint8_t data){ uint8_t i; // 将引脚设置为推挽输出 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = DS18B20_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DS18B20_GPIO_PORT, &GPIO_InitStructure); // 写入数据 for (i = 0; i < 8; i++) { GPIO_ResetBits(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN); delay_us(2); if (data & 0x01) { GPIO_SetBits(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN); } delay_us(60); GPIO_SetBits(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN); delay_us(2); data >>= 1; }}​// 从DS18B20读取一个字节的数据uint8_t DS18B20_ReadByte(void){ uint8_t i, data = 0; // 将引脚设置为推挽输出 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = DS18B20_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DS18B20_GPIO_PORT, &GPIO_InitStructure); // 读取数据 for (i = 0; i < 8; i++) { GPIO_ResetBits(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN); delay_us(2); GPIO_SetBits(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN); delay_us(2); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(DS18B20_GPIO_PORT, &GPIO_InitStructure); delay_us(2); data >>= 1; if (GPIO_ReadInputDataBit(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN)) { data |= 0x80; } delay_us(60); } return data;}​// 获取DS18B20温度数据float DS18B20_GetTemp(void){ uint8_t tempLSB, tempMSB; int16_t tempData; float temperature; // 发送温度转换命令 DS18B20_WriteByte(0xCC); // 跳过ROM操作 DS18B20_WriteByte(0x44); // 发送温度转换命令 // 等待温度转换完成 while (!GPIO_ReadInputDataBit(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN)); // 发送读取温度命令 DS18B20_WriteByte(0xCC); // 跳过ROM操作 DS18B20_WriteByte(0xBE); // 发送读取温度命令 // 读取温度数据 tempLSB = DS18B20_ReadByte(); tempMSB = DS18B20_ReadByte(); // 计算温度值 tempData = (tempMSB << 8) | tempLSB; if (tempData & 0x8000) // 温度为负数 { tempData = ~tempData + 1; temperature = -((float)tempData / 16.0); } else // 温度为正数 { temperature = (float)tempData / 16.0; } return temperature;}​4.3 OLED显示屏代码头文件:#ifndef SSD1306_H#define SSD1306_H​#include "stm32f10x.h"#include "fonts.h"​// SSD1306显示屏参数定义#define SSD1306_I2C_ADDR 0x78 // I2C地址#define SSD1306_WIDTH 128 // 显示屏宽度#define SSD1306_HEIGHT 64 // 显示屏高度​// SSD1306函数声明void SSD1306_Init(void);void SSD1306_Clear(void);void SSD1306_UpdateScreen(void);void SSD1306_GotoXY(uint16_t x, uint16_t y);void SSD1306_Puts(const char* str, FontDef_t* font, uint8_t color);​#endif​源文件:#include "ssd1306.h"#include "i2c.h"​static uint8_t SSD1306_Buffer[SSD1306_WIDTH * SSD1306_HEIGHT / 8];​void SSD1306_Init(void){ // 初始化I2C总线 I2C_Init(); // 向SSD1306发送初始化命令 uint8_t initCommands[] = { 0xAE, // 关闭显示 0xD5, 0x80, // 设置时钟分频因子 0xA8, 0x3F, // 设置驱动路数 0xD3, 0x00, // 设置显示偏移 0x40, // 设置显示开始行 0x8D, 0x14, // 设置电荷泵 0x20, 0x00, // 设置内存地址模式 0xA1, // 设置段重定义 0xC8, // 设置COM扫描方向 0xDA, 0x12, // 设置COM引脚配置 0x81, 0xCF, // 设置对比度控制 0xD9, 0xF1, // 设置预充电周期 0xDB, 0x40, // 设置VCOMH电压倍率 0xA4, // 全局显示开启 0xA6, // 设置显示方式 0xAF // 开启显示 }; for (uint8_t i = 0; i < sizeof(initCommands); i++) { I2C_WriteByte(SSD1306_I2C_ADDR, 0x00, initCommands[i]); } // 清空缓冲区 SSD1306_Clear(); // 更新显示屏 SSD1306_UpdateScreen();}​void SSD1306_Clear(void){ memset(SSD1306_Buffer, 0x00, sizeof(SSD1306_Buffer));}​void SSD1306_UpdateScreen(void){ for (uint8_t i = 0; i < 8; i++) { I2C_WriteBuffer(SSD1306_I2C_ADDR, 0x40, &SSD1306_Buffer[SSD1306_WIDTH * i], SSD1306_WIDTH); }}​void SSD1306_GotoXY(uint16_t x, uint16_t y){ if (x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT) return; SSD1306_Buffer[(x + (y / 8) * SSD1306_WIDTH)] |= (1 << (y % 8));}​void SSD1306_Puts(const char* str, FontDef_t* font, uint8_t color){ while (*str) { for (uint8_t i = 0; i < font->FontWidth; i++) { uint8_t temp = font->data[(*str - 32) * font->FontWidth + i]; for (uint8_t j = 0; j < font->FontHeight; j++) { if (temp & (1 << j)) { SSD1306_GotoXY(font->FontWidth * i + j, font->FontHeight * i + j); SSD1306_Buffer[(font->FontWidth * i + j + (font->FontHeight * i + j) / 8 * SSD1306_WIDTH)] |= (1 << ((font->FontHeight * i + j) % 8)); } else { SSD1306_GotoXY(font->FontWidth * i + j, font->FontHeight * i + j); SSD1306_Buffer[(font->FontWidth * i + j + (font->FontHeight * i + j) / 8 * SSD1306_WIDTH)] &= ~(1 << ((font->FontHeight * i + j) % 8)); } } } str++; }}​五、总结本项目设计了基于STM32的花卉温室控温系统,通过使用DS18B20温度传感器、OLED显示屏和继电器等硬件模块,实现了对温室内温度的监测和控制。该系统能够根据预设的温度阀值,自动控制热风机的加热,以维持温室内的适宜温度,从而保证花卉的生长环境。在软件逻辑设计方面,采用了STM32的外设和中断机制,结合合适的算法和状态判断,实现了温度数据的获取和比较,并根据结果控制继电器的开关。通过OLED显示屏和USART串口,能够及时地将温度值和阀值反馈给用户,方便用户了解当前环境并进行调节。本项目的设计和实现为温室控温系统提供了一个具体的解决方案,通过合理的硬件选型和软件逻辑设计,能够满足花卉种植对温度控制的需求。在未来的发展中,系统将在农业领域发挥重要作用,并为人们创造更舒适、高效的温控环境。
  • [技术干货] 基于STM32的蔬菜大棚温湿度智能控制系统设计
    一、前言随着人们对健康和可持续生活方式的关注不断增加,蔬菜大棚成为了现代农业中的重要组成部分。蔬菜大棚提供了一个受控的环境,使得农民能够在任何季节种植蔬菜,并根据需要进行调节。为了实现最佳的蔬菜生长和产量,对温度和湿度等环境条件的精确控制至关重要。传统的蔬菜大棚管理通常依赖于人工监测和调节。这种方法存在一些问题,例如人工监测容易出现误差和延迟,而且对于大规模的蔬菜大棚来说,人工调节工作量巨大。所以开发一种基于智能控制系统的蔬菜大棚温湿度管理方案变得非常重要。基于STM32微控制器的蔬菜大棚温湿度智能控制系统用于解决传统管理方法的问题,并提供一种自动化的解决方案。该系统利用STM32微控制器的强大计算和控制能力,结合温湿度传感器和执行器,实现对蔬菜大棚环境的精确监测和控制。通过该系统,农民可以实时监测蔬菜大棚内的温度和湿度,并根据预设的目标范围自动调节。系统可以自动控制温室内的加热器、通风设备和加湿器等设备,以维持最适宜的生长环境条件。项目的目标是提高蔬菜大棚的生产效率和质量,降低能源消耗,并减少人力投入。通过智能控制系统的应用,农民能够实现更加可持续和高效的农业生产,为社会提供更多健康的蔬菜产品。二、系统设计流程2.1 硬件选型硬件选型是设计蔬菜大棚温湿度智能控制系统的重要环节。【1】主控芯片:STM32F103ZET6 主控芯片使用STM32F103ZET6,它是一款高性能的ARM Cortex-M3内核微控制器,具有丰富的外设资源和强大的处理能力。该芯片可满足本项目对控制和数据处理的要求。【2】温湿度传感器:DHT11 空气温湿度采集选用DHT11传感器,它采用数字信号输出,具有简单、低成本和较好的精度,适合大棚环境的温湿度监测。【3】土壤湿度传感器:土壤湿度传感器 土壤湿度采集选用土壤湿度传感器,通过模拟-数字转换器(ADC)接口采集土壤湿度数据。该传感器能够准确测量土壤湿度,为农作物提供合适的灌溉水量。【4】通风风机:5V小风扇+继电器 为了实现通风控制,选择5V小风扇作为通风装置,并通过继电器控制其开关状态。根据温度数据和设定阈值,通过STM32的GPIO口控制继电器的高低电平实现通风风扇的启停控制。【5】照明灯:LED白色灯模块 为了提供适当的照明条件,选择LED白色灯模块作为照明装置。该模块使用STM32的GPIO口控制其开关状态,实现灯光的开启和关闭。【6】灌溉系统:抽水电机+继电器 灌溉系统采用抽水电机作为水源,并通过继电器控制其开启和关闭。通过单片机控制继电器的高低电平来控制抽水电机的工作状态,实现灌溉系统的自动化操作。【7】显示模块:LCD显示屏 为了方便用户观察当前的温湿度等数据,选用LCD显示屏进行数据的显示。通过STM32的数字接口与LCD显示屏进行通信,将采集到的数据实时显示在屏幕上。2.2 软件设计思路本项目的代码设计思路可以分为以下几个关键部分:【1】初始化设置:首先,需要进行主控芯片的初始化设置,包括引脚配置、时钟设置等。同时,还需要对LCD显示屏进行初始化配置,以便后续显示数据。【2】传感器数据采集:使用合适的库函数或代码,读取DHT11传感器和土壤湿度传感器的数据。通过适当的接口与主控芯片进行通信,获取温度、湿度和土壤湿度的数值。【3】数据处理与判断:根据采集到的温湿度和土壤湿度数值,进行相应的数据处理和判断。判断当前温度是否超出设定范围,以及土壤湿度是否低于设定阈值等。【4】控制执行器:根据数据处理和判断的结果,控制相应的执行器,如通风风扇、照明灯和灌溉系统。通过设置相应的引脚电平或触发继电器,实现执行器的开启或关闭。【5】LCD显示:将采集到的温湿度和土壤湿度数值通过LCD显示屏进行显示,以便用户实时监测。【6】用户交互:通过按键输入或其他方式,实现用户与系统的交互。设置土壤湿度阈值、调节温度范围等。【7】循环运行:将上述步骤组织成一个循环运行的程序,确保系统能够持续采集数据、处理判断和控制执行器的操作。三、代码实现3.1 DHT11温湿度读取读取DHT11传感器环境温湿度并通过串口打印出来。#include "stm32f10x.h"#include "stdio.h"​// 定义DHT11数据引脚#define DHT11_PIN GPIO_Pin_0#define DHT11_PORT GPIOA​// DHT11初始化函数void DHT11_Init(void){ GPIO_InitTypeDef GPIO_InitStructure;​ // 使能GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);​ // 配置DHT11引脚为推挽输出 GPIO_InitStructure.GPIO_Pin = DHT11_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DHT11_PORT, &GPIO_InitStructure);}​// 延时函数,单位为微秒void Delay_us(uint32_t nCount){ uint32_t i; for(i=0; i<nCount; i++);}​// 软件延时函数,单位为毫秒void Delay_ms(uint32_t nCount){ uint32_t i; for(i=0; i<nCount*1000; i++);}​// 从DHT11读取一位数据uint8_t DHT11_ReadBit(void){ uint8_t retries = 0; while(GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == Bit_RESET) { if (retries++ > 100) return 0; Delay_us(1); } Delay_us(40); // 延时40us if (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == Bit_SET) retries = 100; // 超时标识 else retries = 0;​ while(GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == Bit_SET) { if (retries++ > 100) return 0; Delay_us(1); }​ return 1;}​// 从DHT11读取一个字节数据uint8_t DHT11_ReadByte(void){ uint8_t i, temp = 0; for(i=0; i<8; i++) { temp <<= 1; temp |= DHT11_ReadBit(); } return temp;}​// 读取DHT11的温湿度值uint8_t DHT11_ReadData(uint8_t* temperature, uint8_t* humidity){ uint8_t data[5], checksum;​ // 主机将总线拉低至少18ms GPIO_InitTypeDef GPIO_InitStructure;​ GPIO_InitStructure.GPIO_Pin = DHT11_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DHT11_PORT, &GPIO_InitStructure);​ GPIO_ResetBits(DHT11_PORT, DHT11_PIN); Delay_ms(20); GPIO_SetBits(DHT11_PORT, DHT11_PIN); Delay_us(30);​ // 设置为输入模式 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(DHT11_PORT, &GPIO_InitStructure); // 等待 DHT11 响应 if (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == Bit_RESET) { while(GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == Bit_RESET); while(GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == Bit_SET); // 读取5字节数据 for(uint8_t i=0; i<5; i++) data[i] = DHT11_ReadByte(); // 读取校验和 checksum = DHT11_ReadByte();​ // 校验数据 if((data[0] + data[1] + data[2] + data[3]) != checksum) return 0; *humidity = data[0]; *temperature = data[2];​ return 1; } else { return 0; }}​// 初始化USART1void USART1_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure;​ // 使能USART1和GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);​ // 配置USART1的引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // TX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);​ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // RX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure);​ // 配置USART1 USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure);​ // 使能USART1 USART_Cmd(USART1, ENABLE);}​// 发送字符到USART1void USART1_SendChar(char ch){ while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, (uint8_t)ch);}​// 发送字符串到USART1void USART1_SendString(const char* str){ while(*str) { USART1_SendChar(*str++); }}​int main(void){ uint8_t temperature, humidity;​ // 初始化DHT11和USART1 DHT11_Init(); USART1_Init();​ while(1) { if (DHT11_ReadData(&temperature, &humidity)) { // 发送温湿度数据到串口 char buffer[50]; sprintf(buffer, "Temperature: %d°C, Humidity: %d%%\r\n", temperature, humidity); USART1_SendString(buffer); }​ Delay_ms(2000); // 2秒钟读取一次数据 }}将代码下载到STM32F103ZET6开发板上,接上DHT11。当成功运行时,环境温湿度数据会通过USART1串口打印出来。3.2 读取土壤湿度值通过ADC1的通道1采集土壤传感器的湿度值,打印到串口.#include "stm32f10x.h"#include "stdio.h"​// 函数声明void ADC_Configuration(void);void UART_Configuration(void);void USART1_SendChar(char ch);​int main(void){ // 初始化ADC和串口 ADC_Configuration(); UART_Configuration();​ while (1) { // 启动ADC转换 ADC_SoftwareStartConvCmd(ADC1, ENABLE);​ // 等待转换完成 while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));​ // 读取ADC值 uint16_t adcValue = ADC_GetConversionValue(ADC1);​ // 将ADC值转换为湿度百分比 float humidity = (float)adcValue / 4095 * 100;​ // 将湿度值打印到串口 char buffer[20]; sprintf(buffer, "Humidity: %.2f%%\r\n", humidity); for (int i = 0; buffer[i] != '\0'; i++) { USART1_SendChar(buffer[i]); }​ // 延时一段时间 for (int i = 0; i < 1000000; i++); }}​// ADC配置void ADC_Configuration(void){ ADC_InitTypeDef ADC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure;​ // 使能ADC1和GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);​ // 配置GPIOA.1为模拟输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure);​ // ADC配置 ADC_DeInit(ADC1); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure);​ // 配置ADC1的通道1为采样通道 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5);​ // 使能ADC1 ADC_Cmd(ADC1, ENABLE);​ // ADC校准 ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1));}​// 串口配置void UART_Configuration(void){ GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure;​ // 使能USART1和GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);​ // 配置USART1引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);​ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure);​ // USART配置 USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure);​ // 使能USART1 USART_Cmd(USART1, ENABLE);}​// 发送字符到USART1void USART1_SendChar(char ch){ USART_SendData(USART1, (uint8_t)ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);}以上代码使用STM32的标准库函数进行配置和操作。在ADC_Configuration函数中进行ADC的初始化配置,包括GPIO引脚配置、ADC时钟使能、通道配置等。 在UART_Configuration函数中进行串口USART1的初始化配置,包括GPIO引脚配置、波特率设置等。在主函数中,进入一个无限循环。在循环中,启动ADC转换,并等待转换完成。 通过ADC_GetConversionValue函数读取ADC转换结果,将其转换为湿度百分比。 使用sprintf函数将湿度值格式化为字符串,并使用USART1_SendChar函数将字符串逐个字符发送到USART1串口。 通过延时函数进行一段时间的延时,以控制打印速率。3.3 大棚补光灯控制以下是使用STM32F103ZET6读取BH1750光照传感器输出的光照强度,并根据阈值控制LED补光灯灯开关实现代码:#include "stm32f10x.h"#include "i2c.h"#include "delay.h"​#define BH1750_ADDRESS 0x23​void BH1750_Init(){ // 初始化I2C总线 I2C_Init();}​void BH1750_Start(){ // 启动BH1750测量 uint8_t cmd = 0x01; // 单次高分辨率模式 I2C_Start(); I2C_SendByte(BH1750_ADDRESS); I2C_WaitAck(); I2C_SendByte(cmd); I2C_WaitAck(); I2C_Stop();}​uint16_t BH1750_Read(){ // 读取BH1750测量结果 uint16_t lux; I2C_Start(); I2C_SendByte(BH1750_ADDRESS + 1); // 发送读命令 I2C_WaitAck(); lux = I2C_ReceiveByte() << 8; // 读取高字节 I2C_Ack(); lux |= I2C_ReceiveByte(); // 读取低字节 I2C_NAck(); I2C_Stop(); return lux;}​void LED_Control(uint8_t state){ // 控制LED照明灯开关 if (state) GPIO_SetBits(GPIOA, GPIO_Pin_8); // 打开LED else GPIO_ResetBits(GPIOA, GPIO_Pin_8); // 关闭LED}​int main(void){ // 初始化GPIO口 GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);​ // 初始化BH1750传感器 BH1750_Init();​ while (1) { // 启动测量 BH1750_Start();​ // 延时等待测量完成 DelayMs(200);​ // 读取光照强度 uint16_t lux = BH1750_Read();​ // 判断阈值并控制LED if (lux > 1000) LED_Control(1); // 光照强度高于阈值,打开LED else LED_Control(0); // 光照强度低于阈值,关闭LED }}代码中初始化I2C总线和BH1750传感器,通过BH1750_Init()函数实现。在主循环中,启动测量延时等待测量完成。使用BH1750_Read()函数读取测量结果,即光照强度。根据阈值判断光照强度是否高于设定值,通过LED_Control()函数控制LED的开关状态。四、总结本项目基于STM32微控制器实现了一个蔬菜大棚温湿度智能控制系统。系统的主控芯片采用了STM32F103ZET6,用于控制和协调各个硬件模块的工作。系统包括空气温湿度采集模块(DHT11)、土壤湿度采集模块(ADC接口)、通风风机(5V小风扇+继电器控制)、照明灯(LED白色灯模块)、灌溉系统(抽水电机+继电器控制)以及LCD显示屏。系统的功能包括温湿度的实时监测、土壤湿度的检测、通风风扇的自动控制、灌溉系统的自动控制和数据的显示。通过按键设置土壤湿度阈值,实现自动浇水功能,当土壤湿度低于阈值时,系统自动开启灌溉系统进行浇水。同时,根据设定的温度阈值,系统自动控制通风风扇进行降温。蔬菜大棚温湿度智能控制系统利用STM32微控制器和各种传感器实现了对环境参数的监测和控制,提高了蔬菜大棚的自动化程度和生产效率。同时,通过自动控制灌溉和通风系统,能够更好地满足蔬菜生长的需求,提高农作物的产量和质量。
  • [技术干货] Qt加载本地图片转为YUV420P格式数据
    一、背景介绍在流媒体应用中,视频编码是必不可少的一环。视频编码的作用是将高带宽、高码率的原始视频流压缩成低带宽、低码率的码流,以便于传输和存储。H264是一种高效的视频编码标准,具有良好的压缩性能和广泛的应用范围,在实时流媒体应用中得到了广泛的应用。在将本地图片编码成H264并通过RTMP推流到流媒体服务器时,需要经过以下步骤:【1】使用图像处理库(如Qt)加载本地图片,并将其转换为YUV420P格式。转换后的YUV420P数据可以作为H264编码器的输入。【2】使用H264编码器对YUV420P数据进行编码。H264编码器将YUV420P数据压缩成H264码流,并将码流输出。【3】使用RTMP协议将H264码流推送到流媒体服务器。RTMP协议是一种实时流媒体传输协议,可以将音视频数据推送到流媒体服务器,并提供流媒体回放和点播功能。在实现上述功能时,使用第三方库(FFmpeg)来完成H264编码和RTMP推流的功能。FFmpeg是一种跨平台的开源多媒体框架,它提供了丰富的音视频处理功能,包括视频编码、解码、转换、推流、拉流等功能。使用FFmpeg,可以方便地将本地图片编码成H264,并通过RTMP协议推流到流媒体服务器。二、YUV420P格式介绍YUV420P和RGB888都是常见的像素格式,分别代表了不同的色彩空间表示方式。RGB888是一种直接将像素的颜色信息表示为红、绿、蓝三种颜色通道的格式。它使用24位(3字节)来表示一个像素,其中每个字节表示一个颜色通道的强度,取值范围为0~255。因此,RGB888格式的像素可以表示16777216种不同的颜色。YUV420P则是一种将像素的颜色信息表示为亮度和色度两个分量的格式。其中,Y代表亮度分量(Luma),U和V代表色度分量(Chroma)。它使用12位(1.5字节)来表示一个像素,其中前面的8位(1字节)表示亮度分量Y,后面的4位(0.5字节)分别表示U和V色度分量。YUV420P将颜色信息分成了两个部分,亮度信息占据了大部分数据,而色度信息则只占据了一小部分。YUV420P格式的设计是为了在视频压缩中提高压缩率,因为在视频中,相邻像素的颜色通常非常接近。YUV420P将亮度信息和色度信息分开存储,可以在保证图像质量的前提下,使压缩率更高。同时,它也比RGB888格式更适合在视频传输和处理中使用,因为它的数据量更小,传输和处理的效率更高。YUV420P和RGB888是不同的色彩空间表示方式,它们的值域范围和表示方式也不同。在将YUV420P转换为RGB888时,需要使用一定的算法进行转换,因为YUV420P和RGB888之间存在非线性的转换关系。三、图片转为YUV420P下面通过Qt代码实现加载本地图片、提取RGB数据并将其转换为YUV420P格式。使用Qt中的QImage和QByteArray类来实现:#include <QtGui/QImage>#include <QtCore/QByteArray>​void convertRGBToYUV420P(const QString& imagePath, int width, int height, QByteArray& yuvData){ QImage image(imagePath); if (image.isNull()) { // 加载图片失败 return; }​ // 将图片转换为指定大小 image = image.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);​ // 提取RGB数据 QByteArray rgbData; const int bytesPerLine = image.width() * 3; for (int y = 0; y < image.height(); ++y) { const uchar* scanline = image.constScanLine(y); rgbData.append(reinterpret_cast<const char*>(scanline), bytesPerLine); }​ // 将RGB数据转换为YUV420P yuvData.resize(width * height * 3 / 2); uchar* yuvPtr = reinterpret_cast<uchar*>(yuvData.data()); const uchar* rgbPtr = reinterpret_cast<const uchar*>(rgbData.constData()); const int uvOffset = width * height;​ for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { const int yIndex = y * width + x; const int uvIndex = uvOffset + (y / 2) * (width / 2) + (x / 2) * 2;​ const int r = *rgbPtr++; const int g = *rgbPtr++; const int b = *rgbPtr++;​ // 计算Y分量 yuvPtr[yIndex] = static_cast<uchar>((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;​ // 计算U和V分量 if ((y % 2 == 0) && (x % 2 == 0)) { yuvPtr[uvIndex] = static_cast<uchar>((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128; yuvPtr[uvIndex + 1] = static_cast<uchar>((112 * r - 94 * g - 18 * b + 128) >> 8) + 128; } } }}调用函数时,需要传入要加载的图片的路径、目标宽度和高度,以及一个用于存储YUV420P数据的QByteArray对象:QByteArray yuvData;convertRGBToYUV420P("path/to/image.png", 640, 480, yuvData);在函数内部,使用QImage类加载指定路径的图片。如果加载失败,直接返回。然后,将图片缩放到指定的大小,并使用一个QByteArray对象存储提取出的RGB数据。为了提高效率,使用了QImage的constScanLine()函数来遍历每一行像素数据,并将其追加到QByteArray对象中。将RGB数据转换为YUV420P格式时,使用QByteArray::resize()函数调整QByteArray对象的大小,以便能够存储YUV420P数据。然后,使用两个指针分别指向目标YUV420P数据和源RGB数据的开头。使用两个嵌套的循环遍历每个像素,并将其转换为YUV420P格式。在计算Y分量时,使用的公式:Y = (66 * R + 129 * G + 25 * B + 128) >> 8 + 16在计算U和V分量时,我们使用以下公式:U = (-38 * R - 74 * G + 112 * B + 128) >> 8 + 128V = (112 * R - 94 * G - 18 * B + 128) >> 8 + 128最后,将转换后的YUV420P数据存储在传入的QByteArray对象中,并在函数返回前完成。
  • [技术干货] NV12数据格式转H265编码格式实现过程
    一、需求在视频处理和传输应用中,将视频数据编码为高效的格式是非常重要的。H.265(也称为HEVC)是一种先进的视频编码标准,具有更好的压缩性能和图像质量,相比于传统的编码标准(如H.264),可以显著减少视频的带宽和存储需求。NV12是一种常见的视频格式,用于表示YUV图像数据,尤其在实时视频处理中广泛使用。它将亮度(Y)和色度(UV)分量分开存储,其中Y分量占据连续的内存块,而UV分量交错存储在另一个连续的内存块中。本需求实现将NV12格式的视频数据转换为H.265格式的数据,并将其保存在内存中。这样可以方便地对视频数据进行后续处理,如网络传输、存储或实时流媒体传输等。为了实现这一需求,使用了C语言和FFmpeg库。FFmpeg是一个强大的开源多媒体处理库,提供了丰富的功能和编解码器,包括H.265编码器。下面代码实现了如何使用FFmpeg库将NV12格式的视频数据编码为H.265格式的数据,并将其保存在内存中。函数接受NV12数据、宽度和高度作为输入,并返回编码后的H.265数据和数据大小。这样,用户可以方便地将NV12格式的视频数据转换为H.265格式,并在内存中进行进一步处理或传输。同时也提供了文件的方式。这个功能可以在各种视频处理应用中使用,如视频编辑软件、实时视频流处理系统、视频通信应用等。通过使用H.265编码,可以提高视频传输的效率和质量,减少带宽和存储需求,同时保持良好的视觉体验。二、NV12和H265格式详细介绍NV12和H265都是视频编码中经常使用的像素格式,下面分别介绍这两种格式的特点和使用场景。【1】NV12像素格式NV12是一种YUV像素格式,常用于视频编码和解码过程中。它是一种planar格式,即Y和UV分量分别存储在不同的平面中。其中,Y分量表示亮度信息,UV分量表示色度信息。在NV12格式中,UV分量的采样率为4:2:0,即每4个Y像素共用一个U和一个V像素。这种采样方式可以有效地减小数据量,同时保持视频质量。NV12格式的存储顺序为:先存储所有的Y分量,然后存储所有的U和V分量,U和V交错存储。因此,NV12格式的数据大小为(1.5 x 图像宽度 x 图像高度)字节。NV12格式常用于视频流传输和视频编解码器中,例如在H.264视频编解码器和DirectShow视频开发中都广泛使用。【2】H265像素格式H265(又称HEVC)是一种高效的视频编码标准,它可以在相同视频质量的情况下大幅度减小视频文件的大小。H265支持多种像素格式,其中最常用的是YUV 4:2:0和YUV 4:2:2。YUV 4:2:0格式与NV12类似,也是一种planar格式,其中Y分量存储亮度信息,UV分量采用4:2:0采样方式存储色度信息。YUV 4:2:2格式则采用4:2:2的采样方式存储UV分量,即每2个Y像素共用一个U和一个V像素。与H264相比,H265的主要改进在于更高的压缩率和更低的比特率,同时保持相同质量的视频输出。因此,H265格式可以在同样的视频质量下使用更低的比特率进行编码,达到更小的文件大小。H265格式常用于网络视频流媒体传输、4K和8K高清视频等领域。三、代码实现【1】内存数据处理要将NV12格式的数据转换为H.265格式的数据并保存在内存中,可以使用FFmpeg库来实现编码操作。#include <stdio.h>#include <stdlib.h>#include <stdint.h>#include <string.h>#include <libavcodec/avcodec.h>​// 将NV12数据编码为H.265并保存在内存中int encodeNV12toH265(uint8_t* nv12Data, int width, int height, uint8_t** h265Data, int* h265Size) { int ret = 0; AVCodec* codec = NULL; AVCodecContext* codecContext = NULL; AVFrame* frame = NULL; AVPacket* packet = NULL;​ // 注册所有的编解码器 avcodec_register_all();​ // 查找H.265编码器 codec = avcodec_find_encoder(AV_CODEC_ID_H265); if (!codec) { fprintf(stderr, "找不到H.265编码器\n"); ret = -1; goto cleanup; }​ // 创建编码器上下文 codecContext = avcodec_alloc_context3(codec); if (!codecContext) { fprintf(stderr, "无法分配编码器上下文\n"); ret = -1; goto cleanup; }​ // 配置编码器参数 codecContext->width = width; codecContext->height = height; codecContext->pix_fmt = AV_PIX_FMT_NV12; codecContext->codec_id = AV_CODEC_ID_H265; codecContext->codec_type = AVMEDIA_TYPE_VIDEO; codecContext->time_base.num = 1; codecContext->time_base.den = 25; // 假设帧率为25fps​ // 打开编码器 if (avcodec_open2(codecContext, codec, NULL) < 0) { fprintf(stderr, "无法打开编码器\n"); ret = -1; goto cleanup; }​ // 创建输入帧 frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "无法分配输入帧\n"); ret = -1; goto cleanup; }​ // 设置输入帧的属性 frame->format = codecContext->pix_fmt; frame->width = codecContext->width; frame->height = codecContext->height;​ // 分配输入帧的数据缓冲区 ret = av_frame_get_buffer(frame, 32); if (ret < 0) { fprintf(stderr, "无法分配输入帧的数据缓冲区\n"); ret = -1; goto cleanup; }​ // 将NV12数据复制到输入帧的数据缓冲区 memcpy(frame->data[0], nv12Data, width * height); // Y分量 memcpy(frame->data[1], nv12Data + width * height, width * height / 2); // UV分量​ // 创建输出数据包 packet = av_packet_alloc(); if (!packet) { fprintf(stderr, "无法分配输出数据包\n"); ret = -1; goto cleanup; }​ // 发送输入帧给编码器 ret = avcodec_send_frame(codecContext, frame); if (ret < 0) { fprintf(stderr, "无法发送输入帧给编码器\n"); ret = -1; goto cleanup; }​ // 循环从编码器接收输出数据包 while (ret >= 0) { ret = avcodec_receive_packet(codecContext, packet); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; else if (ret < 0) { fprintf(stderr, "从编码器接收输出数据包时发生错误\n"); ret = -1; goto cleanup; }​ // 分配内存并复制输出数据包的数据 *h265Data = (uint8_t*)malloc(packet->size); if (!*h265Data) { fprintf(stderr, "无法分配内存\n"); ret = -1; goto cleanup; } memcpy(*h265Data, packet->data, packet->size); *h265Size = packet->size;​ // 释放输出数据包 av_packet_unref(packet); }​cleanup: // 释放资源 if (packet) av_packet_free(&packet); if (frame) av_frame_free(&frame); if (codecContext) avcodec_free_context(&codecContext);​ return ret;}​使用以上的封装函数,可以将NV12格式的数据传入函数中,函数会将其编码为H.265格式的数据并保存在内存中。编码后的H.265数据存储在h265Data指针指向的内存中,数据的大小保存在h265Size中。【2】文件数据处理#include <stdio.h>#include <stdlib.h>#include <string.h>#include <libavcodec/avcodec.h>#include <libavutil/opt.h>​// 将NV12数据编码为H.265格式int encodeNV12ToH265(const char* nv12FilePath, const char* h265FilePath, int width, int height){ // 注册所有的编解码器 avcodec_register_all();​ // 打开编码器 AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H265); if (!codec) { fprintf(stderr, "Failed to find H.265 encoder\n"); return -1; }​ AVCodecContext* codecContext = avcodec_alloc_context3(codec); if (!codecContext) { fprintf(stderr, "Failed to allocate codec context\n"); return -1; }​ // 配置编码器参数 codecContext->width = width; codecContext->height = height; codecContext->time_base = (AVRational){1, 25}; // 帧率为25fps codecContext->framerate = (AVRational){25, 1}; codecContext->pix_fmt = AV_PIX_FMT_NV12;​ // 打开编码器上下文 if (avcodec_open2(codecContext, codec, NULL) < 0) { fprintf(stderr, "Failed to open codec\n"); return -1; }​ // 创建输入文件的AVFrame AVFrame* frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "Failed to allocate frame\n"); return -1; }​ frame->format = codecContext->pix_fmt; frame->width = codecContext->width; frame->height = codecContext->height;​ // 分配输入帧缓冲区 int ret = av_frame_get_buffer(frame, 32); if (ret < 0) { fprintf(stderr, "Failed to allocate frame buffer\n"); return -1; }​ // 打开输入文件 FILE* nv12File = fopen(nv12FilePath, "rb"); if (!nv12File) { fprintf(stderr, "Failed to open NV12 file\n"); return -1; }​ // 打开输出文件 FILE* h265File = fopen(h265FilePath, "wb"); if (!h265File) { fprintf(stderr, "Failed to open H.265 file\n"); return -1; }​ // 创建编码用的AVPacket AVPacket* packet = av_packet_alloc(); if (!packet) { fprintf(stderr, "Failed to allocate packet\n"); return -1; }​ int frameCount = 0; while (1) { // 从输入文件读取NV12数据到AVFrame if (fread(frame->data[0], 1, width * height, nv12File) != width * height) { break; } if (fread(frame->data[1], 1, width * height / 2, nv12File) != width * height / 2) { break; }​ frame->pts = frameCount++; // 设置帧的显示时间戳​ // 发送AVFrame到编码器 ret = avcodec_send_frame(codecContext, frame); if (ret < 0) { fprintf(stderr, "Error sending frame to codec: %s\n", av_err2str(ret)); return -1; }​ // 从编码器接收编码后的数据包 while (ret >= 0) { ret = avcodec_receive_packet(codecContext, packet); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { fprintf(stderr, "Error receiving packet from codec: %s\n", av_err2str(ret)); return -1; }​ // 将编码后的数据包写入输出文件 fwrite(packet->data, 1, packet->size, h265File); av_packet_unref(packet); } }​ // 刷新编码器 ret = avcodec_send_frame(codecContext, NULL); if (ret < 0) { fprintf(stderr, "Error sending flush frame to codec: %s\n", av_err2str(ret)); return -1; }​ while (ret >= 0) { ret = avcodec_receive_packet(codecContext, packet); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { fprintf(stderr, "Error receiving packet from codec: %s\n", av_err2str(ret)); return -1; }​ // 将编码后的数据包写入输出文件 fwrite(packet->data, 1, packet->size, h265File); av_packet_unref(packet); }​ // 释放资源 fclose(nv12File); fclose(h265File); av_frame_free(&frame); av_packet_free(&packet); avcodec_free_context(&codecContext);​ return 0;}​int main(){ const char* nv12FilePath = "input.nv12"; const char* h265FilePath = "output.h265"; int width = 1920; int height = 1080;​ int ret = encodeNV12ToH265(nv12FilePath, h265FilePath, width, height); if (ret < 0) { fprintf(stderr, "Failed to encode NV12 to H.265\n"); return -1; }​ printf("Encoding complete\n");​ return 0;}​要确保已经正确安装了FFmpeg库,并在编译选项中包含了FFmpeg的头文件和库文件。需要提供NV12格式的输入文件路径、输出H.265格式文件路径以及图像的宽度和高度。
  • [专题汇总] 9月嵌入式项目开发专题总汇
    一、前言近年来,随着科技的飞速发展和不断创新,基于单片机或嵌入式系统的项目受到了越来越多的关注。本文将介绍一系列基于C语言和单片机的实例项目,这些项目包括对异或校验算法、GPS源数据解析、内存管理、双向链表操作和文件加密解密等的理解和实现。这些示例可以帮助读者更好地理解和掌握C语言的使用,同时展示了单片机在各种领域的应用。其中一系列基于STM32单片机和智能物联网设备的家居和环境监测等方面的设计。例如,基于STM32设计的智能台灯、数字温度计、智能小车和遥控器等,这些项目展示了嵌入式系统在家居、环境监测和无线通信等方面的应用。最后介绍一些基于单片机的控制系统设计,如串行通信发射机、简易智能电动车和太阳能热水器控制器等。这些示例项目展示了单片机在自动化控制和能源管理等领域的应用。通过本文的介绍,大家将了解到C语言在嵌入式系统开发中的重要性和不同项目的设计思路以及应用场景。同时,也可以从这些项目中获得实践经验,提升自己在软硬件开发方面的能力。这些项目既涉及到软件开发,也需要硬件电路设计与实现,将计算机科学与电子工程相结合,可以为生活带来便利和创新。二、项目目录【1】C语言实例_异或校验算法cid:link_1异或校验算法(XOR校验)是一种简单的校验算法,用于检测数据在传输或存储过程中是否发生了错误。通过将数据中的所有比特位相异或,生成一个校验码,然后将该校验码与接收到的数据进行比较,以确定数据是否被修改或损坏。异或校验算法的计算过程如下:(1)将待校验的数据按比特位进行异或操作。(2)将得到的结果作为校验码。在接收端,通过执行相同的异或校验算法,将接收到的数据再次计算校验码,并将其与发送端生成的校验码进行比较。如果两个校验码一致,说明数据传输或存储没有发生错误;如果校验码不一致,则表明数据可能遭到了篡改或传输过程中发生了错误。异或校验算法通常用于简单的数据完整性校验,例如:(1)串口通信:在串口通信中,异或校验可以用于检测数据是否正确地从发送端传输到接收端。(2)存储校验:在存储介质中,可以使用异或校验来验证数据的完整性,确保数据在读写过程中没有发生损坏。(3)网络通信中的校验:在某些通信协议中,也会使用异或校验来验证数据的正确性。异或校验算法只能检测到奇数位的错误。如果传输或存储过程中发生了偶数位错误,该算法无法发现并纠正错误。因此,在更复杂的应用场景中,可能需要使用更强大的校验算法,如循环冗余校验(CRC)来提高错误检测的可靠性和纠错能力。【2】C语言实例_解析GPS源数据cid:link_2GPS(全球定位系统)数据格式常见的是NMEA 0183格式,NMEA 0183格式是一种用于导航设备间传输数据的标准格式,定义了一套规范,使得不同厂商的设备可以通过串行通信接口(常见的是RS-232)进行数据交换。这个标准最初由美国航海电子协会(National Marine Electronics Association,简称NMEA)在1980年推出,并被广泛应用于全球的导航系统。NMEA 0183格式的数据通常以ASCII字符流的形式传输,每条数据都以$开始,以回车符(\r)和换行符(\n)结束。数据被分为不同的消息类型,每个消息类型都有特定的字段和含义。在导航中,最常见的NMEA 0183消息类型包括:GGA(Global Positioning System Fix Data):包含定位相关的信息,如纬度、经度、定位质量指示、使用卫星数量、水平定位精度因子等。 GLL(Geographic Position – Latitude/Longitude):提供纬度、经度和时间信息。 GSA(GNSS DOP and Active Satellites):包含定位模式、使用卫星编号和位置精度因子等信息。 GSV(GNSS Satellites in View):提供可见卫星的信息,包括卫星编号、仰角、方位角和信噪比等。 RMC(Recommended Minimum Specific GNSS Data):包含定位状态、纬度、经度、地面速度、地面航向等。 VTG(Course Over Ground and Ground Speed):提供地面航向和速度信息。 ZDA(Time and Date):包含UTC时间和日期信息。 这些消息类型涵盖了定位、导航和时间相关的数据,可以用于实时定位、航行导航以及时间同步等应用。NMEA 0183格式的数据通常由GPS接收器、导航仪、自动驾驶系统等设备产生,并通过串口输出。其他设备可以通过读取串口数据,并按照NMEA 0183的规范解析数据。这样,不同设备之间就可以进行数据交换和共享,实现设备之间的互操作性。随着技术的发展,新一代的GPS设备也开始采用更高级的数据格式,例如NMEA 2000。然而,由于广泛应用和兼容性的要求,NMEA 0183仍然被广泛支持,并被许多设备和导航系统所使用。【3】C语言实例_实现malloc与free函数完成内存管理cid:link_3malloc函数用于在堆(heap)中分配指定大小的内存空间,并返回一个指向该内存块的指针。 free函数用于释放之前通过malloc或calloc函数动态分配的内存空间。【4】C语言实例_数据压缩与解压cid:link_4数据压缩是通过一系列的算法和技术将原始数据转换为更紧凑的表示形式,以减少数据占用的存储空间。数据解压缩则是将压缩后的数据恢复到原始的表示形式。数据可以被压缩打包并减少空间占用的原因有以下几个方面:(1)无效数据的消除:在数据中可能存在大量冗余、重复或无效的信息。压缩算法可以通过识别和移除这些无效数据,从而减小数据的大小。(2)统计特性的利用:数据通常具有某种统计特性,例如频繁出现的模式、重复的字节序列等。压缩算法可以利用这些统计特性来编码数据,从而达到更高的压缩比率。(3)信息编码:压缩算法使用不同的编码方式来表示源数据,在保证数据可还原的前提下,使用更少的位数来表示信息。例如,Huffman编码、LZW编码等。常见的应用场景中会使用到数据压缩和解压功能,例如:(1)存储媒体:在硬盘、闪存等存储介质上,压缩可以节省存储空间,并提高存储效率。尤其在大规模的数据中心、云存储环境中,数据压缩可以显著减少存储成本。(2)网络传输:在网络通信中,压缩可以减少数据传输的带宽消耗,提高传输速度。尤其在低带宽、高延迟的网络环境中,压缩可以显著改善传输性能。(3)文件压缩:压缩工具如ZIP、RAR等常用于对文件进行打包和压缩,以减小文件的大小,便于存储和传输。这在文件传输、备份和归档中非常常见。(4)多媒体编码:音频、图像、视频等多媒体数据往往具有较高的冗余性,压缩算法可以大幅减小文件大小,例如MP3、JPEG、H.264等压缩算法。【5】C语言实例_双向链表增删改查cid:link_5双向链表(Doubly Linked List)是一种常见的数据结构,在单链表的基础上增加了向前遍历的功能。与单向链表不同,双向链表的每个节点除了包含指向下一个节点的指针外,还包含指向前一个节点的指针。作用和原理:(1)插入和删除操作:由于双向链表中每个节点都有指向前一个节点的指针,所以在双向链表中进行插入或删除操作时,相对于单向链表更加高效。可以通过修改前后节点的指针来完成插入和删除,而无需遍历链表。(2)双向遍历:双向链表支持从头部到尾部以及从尾部到头部的双向遍历。这在某些场景下非常有用,例如需要反向查找、删除最后一个节点等。(3)增加了灵活性:由于每个节点都具有指向前一个节点和后一个节点的指针,双向链表在某些特定场景下更灵活。例如,需要在链表中间插入或删除节点,或者需要修改前一个节点的信息。双向链表的原理很简单。每个节点由数据域和两个指针组成,其中一个指针指向前一个节点,一个指针指向后一个节点。头节点指向链表的第一个节点,尾节点指向链表的最后一个节点。通过调整节点之间的指针,可以在双向链表中执行插入、删除和遍历等操作。使用场景:(1)编辑器中的撤销和重做功能:双向链表可以用于实现撤销和重做功能,每次编辑操作都将其结果存储为一个节点,并使用指针链接起来。通过双向链表,可以方便地在撤销和重做之间进行切换。(2)浏览器的导航历史:浏览器的导航历史可以使用双向链表来保存已访问的页面,每个页面作为一个节点,并使用指针链接起来,以便进行前进和后退操作。(3)实现LRU缓存替换算法:LRU缓存中,最近最少使用的数据被淘汰,可以使用双向链表来维护缓存中的数据,最近访问的数据位于链表的头部,最久未访问的数据位于链表的尾部。(4)实现双向队列:双向链表可以用于实现双向队列(Dequeue),支持在队列的两端进行插入和删除操作。双向链表提供了更多的灵活性和功能,特别是当需要在双向遍历、频繁的插入和删除操作等场景下使用。在许多常见的数据结构和算法中都有广泛的应用。【6】C语言实例_文件内容加密与解密cid:link_6文件内容需要加密与解密功能的原因主要有两个方面:保护数据安全和确保数据完整性。(1)保护数据安全:加密可以将文件内容转化为不可读或难以理解的形式,防止未经授权的人员获取敏感信息。只有拥有正确解密密钥的人员才能还原出可读的文件内容。这样可以有效地防止数据泄露、窃取或篡改,保护用户的隐私和机密信息。(2)确保数据完整性:加密还能够通过添加校验和或数字签名等技术,验证文件内容是否在传输或存储过程中被篡改。解密时,可以对文件内容进行校验,如果校验失败则表明文件可能被篡改,从而保证了数据的完整性。【7】基于STM32设计的智能台灯cid:link_7智能家居设备在现代生活中起着越来越重要的作用。智能台灯作为其中的一种,具有调节光照亮度、色温等功能,更加符合人们对于光照环境的个性化需求。当前设计一款基于STM32微控制器设计的智能台灯,该台灯具备可调节亮度和色温的特点,为用户提供了更加舒适的使用体验。【8】基于单片机的数字温度计设计cid:link_8数字温度计是一种用于测量和显示环境温度的设备。本文章介绍基于STC89C52主控芯片的数字温度计的设计过程和实现原理。该设计采用DS18B20温度传感器进行温度采集,使用LCD1602显示屏进行温度显示,通过按键设置温度的上限和下限阀值,并通过蜂鸣器进行报警。【9】基于单片机的智能小车设计cid:link_9随着科技的发展,智能机器人在日常生活中的应用越来越广泛。智能小车作为智能机器人的一种,具有便携性和多功能的特点,在教育、娱乐和工业等领域得到了广泛关注和应用。智能小车可以通过远程控制实现各种动作,如前进、后退、转弯等,并且可以通过搭载传感器实现避障、测距等功能。智能小车是一种通过采用主控芯片、蓝牙模块、电机驱动以及传感器等组件实现远程控制和避障功能的机器人。当前文章介绍基于STC89C52单片机的智能小车设计方案,提供详细的硬件和软件设计内容。【10】基于单片机的遥控器设计cid:link_0随着科技的不断发展,红外遥控器已经成为我们日常生活中普遍使用的一种电子设备。它能够给我们带来便捷和舒适,减少人工操作的繁琐性。然而,在实际应用中,有时候我们可能需要制作一个自己的红外遥控器,以便于更好地满足个性化需求。这样的需求可能来自于家庭影音设备的控制、智能家居系统的控制,或者其他自动化控制方案等。本项目的目标是设计一个简单且易于实现的单片机红外遥控器,使用户能够自己定制并控制各种电子设备。通过使用键盘矩阵和红外发射二极管,用户只需按下相应的按键即可发送红外信号,从而实现对电子设备的控制。此外,为了方便用户知道当前按下的键值,我们还添加了数码管显示的功能,使用户可以直观地看到自己所按下按键的值。通过这个项目,可以学习到单片机的基本原理和应用、键盘矩阵和红外遥控的工作原理、数码管的驱动方式等知识。并且,还可以根据自己的需求进行各种扩展和改进,如增加更多按键、添加更多的电子设备控制功能等。【11】基于单片机的串行通信发射机设计cid:link_10串行通信是一种常见的数据传输方式,允许将数据以比特流的形式在发送端和接收端之间传输。当前实现基于STC89C52单片机的串行通信发射机,通过红外发射管和接收头实现自定义协议的数据无线传输。【12】基于单片机的简易智能电动车设计cid:link_11智能交通工具在现代社会中起着越来越重要的作用,电动车作为一种环保、便捷的交通工具,受到了广泛的关注和应用。本设计基于单片机技术,设计一款简易智能电动车,实现基本的控制和功能,并提供良好的用户体验。【13】基于单片机的太阳能热水器控制器设计cid:link_12随着环保意识的逐渐增强,太阳能热水器作为一种清洁能源应用得越来越广泛。然而,传统的太阳能热水器控制器通常采用机械式或电子式温控器,存在精度低、控制不稳定等问题。为了解决这些问题,本项目基于单片机技术设计了一款太阳能热水器控制器,主控芯片采用STC89C52。该控制器可以实现对太阳能热水器的水温、水位等参数进行准确、稳定的控制,提高了太阳能热水器的能源利用效率和使用寿命,同时也符合节能环保的社会需求。【14】MCS-51单片机温度控制系统的设计cid:link_13注塑机是一种常用的制造设备,用于生产塑料制品。在注塑机的工作过程中,溶胶必须达到一定的温度才能被注入模具中进行成型。因此,在注塑机的生产过程中,温度控制是非常重要的一环。本项目基于MCS-51单片机设计了一款温度控制系统,主控芯片采用STC89C52,温度传感器采用铂电阻。该系统主要应用于注塑机的溶胶射嘴头上进行加热控制,利用继电器控制加热器实现温度加热,控制系统检测温度是否到达设定阀值来控制继电器。本项目的设计思路是,利用铂电阻温度传感器对溶胶进行实时温度监测,并将监测到的温度值通过LCD显示屏实时显示。控制器采用PID算法对溶胶温度进行精准控制,当温度低于设定阀值时,控制器将通过继电器控制加热器进行加热操作,直到温度达到设定阀值后停止加热操作。通过本项目的应用,可以实现对注塑机的溶胶温度进行精准控制,从而提高注塑机的生产效率和产品质量。同时,该系统控制方式简单,易于操作和维护,具有较高的实用性和可靠性。【15】基于STM32设计的生理监测装置cid:link_14设计并制作一个生理监测装置,能够实时监测人体的心电图、呼吸和温度,并在LCD液晶显示屏上显示相关数据。随着现代生活节奏的加快和环境的变化,人们对身体健康的关注程度越来越高。为了及时掌握自身的生理状况,进行健康管理和疾病预防,监测身体的生理参数成为一种重要的需求。因此,设计一个能够实时监测人体的心电图、呼吸和温度的生理监测装置具有重要的意义。该生理监测装置主要用于个人健康管理和远程监护等应用场景。个人健康管理方面,用户可以通过这个装置了解自己的心电图、呼吸和体温等生理参数,及时发现异常情况并采取相应的措施,如调整生活习惯、咨询医生等。远程监护方面,装置可以将实时的生理参数数据传输到云端或其他设备,供医生或家属远程查看,以便及时干预和诊断。
  • [技术干货] 基于STM32设计的生理监测装置
    一、项目功能要求设计并制作一个生理监测装置,能够实时监测人体的心电图、呼吸和温度,并在LCD液晶显示屏上显示相关数据。随着现代生活节奏的加快和环境的变化,人们对身体健康的关注程度越来越高。为了及时掌握自身的生理状况,进行健康管理和疾病预防,监测身体的生理参数成为一种重要的需求。因此,设计一个能够实时监测人体的心电图、呼吸和温度的生理监测装置具有重要的意义。该生理监测装置主要用于个人健康管理和远程监护等应用场景。个人健康管理方面,用户可以通过这个装置了解自己的心电图、呼吸和体温等生理参数,及时发现异常情况并采取相应的措施,如调整生活习惯、咨询医生等。远程监护方面,装置可以将实时的生理参数数据传输到云端或其他设备,供医生或家属远程查看,以便及时干预和诊断。与传统的生理监测设备相比,该装置具有以下优势:实时性:装置能够实时监测和显示心电图、呼吸和温度等生理参数,用户可以随时了解自己的身体状况。简便性:装置采用便携式设计,用户可以随身携带,方便随时监测。实用功能:通过对采集到的数据进行分析和判断,装置可以提供简单的健康状况提示,帮助用户及时发现问题并采取措施。扩展性:装置可以添加报警功能、存储功能和无线通信功能等增强功能,满足不同用户的需求。这个生理监测装置的设计和制作有助于提高个人健康管理的水平,为用户提供及时、准确的生理参数信息,以便更好地保护身体健康。同时,它也可以为医生和家属提供远程监护的手段,帮助他们随时了解病人的生理状况。该装置在现代健康管理和医疗保健领域具有广阔的应用前景和市场潜力。二、基本要求【1】心电信号监测:采用PulseSensor传感器获取心电信号。进行AD转换,将模拟信号转换为数字信号。使用STM32F103C8T6单片机进行数据处理。在LCD显示屏上显示心电图。【2】呼吸信号监测:采用PulseSensor传感器获取呼吸信号。进行AD转换,将模拟信号转换为数字信号。使用STM32F103C8T6单片机进行数据处理。在LCD显示屏上显示呼吸数据。【3】温度监测:采用MT70传感器测量人体温度。进行AD转换,将模拟信号转换为数字信号。使用STM32F103C8T6单片机进行数据处理。在LCD显示屏上显示温度数据,测量精度不大于0.10℃。【4】人体健康状况判断:根据测量到的生理参数数据,进行简单的健康状况判断。使用STM32F103C8T6单片机进行数据分析与判断。二、发挥部分健康状况判断:分析心电图、呼吸和温度等数据,根据预设的阈值判断是否存在异常情况。在LCD显示屏上显示人体健康状况的简单提示信息。其他增强功能:可以添加报警功能,当监测到异常情况时,通过声音或震动提醒用户。可以存储和记录历史数据,以便后续分析和参考。可以添加无线通信模块,将实时数据传输到其他设备或云端进行远程监测。三、设计方案【1】主控芯片:选择STM32F103C8T6单片机作为主控芯片,具有足够的GPIO、ADC等功能,并可方便地集成硬件模块。【2】显示屏:选择0.96寸IIC接口的OLED显示屏,具有高分辨率和低功耗的特点,适合用于显示监测数据。【3】传感器:心电信号采集使用PulseSensor传感器输出。呼吸信号采集使用PulseSensor传感器输出。温度测量使用MT70传感器。【4】AD转换:选择ADS1292作为心电信号和呼吸信号的AD转换芯片。在STM32F103C8T6单片机上配置ADC,用于温度传感器的AD转换。【5】数据处理与显示:使用STM32F103C8T6单片机进行数据处理和健康状况判断。通过IIC接口将数据发送给OLED显示屏进行实时显示。【6】健康状况判断算法:根据心电图、呼吸和温度数据的变化趋势和预设的阈值进行简单的健康状况判断。四、代码实现4.1 采集代码ADS1292模块,进行3路模拟信号采集转换实现代码。#include "stm32f10x.h"​// 定义SPI接口引脚#define ADS1292_SPI SPI1#define ADS1292_CS_PIN GPIO_Pin_4#define ADS1292_CS_PORT GPIOA​// 定义命令字节#define ADS1292_CMD_SDATAC 0x11 // 停止连续数据传输命令#define ADS1292_CMD_RREG 0x20 // 读寄存器命令#define ADS1292_CMD_WREG 0x40 // 写寄存器命令#define ADS1292_CMD_START 0x08 // 启动数据转换命令​// 函数声明void ADS1292_SPI_Config(void);void ADS1292_Start_Conversion(void);​int main(void){ // 初始化系统时钟、GPIO等 // ...​ // 配置ADS1292的SPI接口 ADS1292_SPI_Config();​ // 启动ADS1292的数据转换 ADS1292_Start_Conversion();​ // 定义读取数据的命令字节#define ADS1292_CMD_RDATAC 0x10​// 定义数据缓冲区大小#define BUFFER_SIZE 100​// 数据缓冲区uint8_t dataBuffer[BUFFER_SIZE];​while (1){ // 启动数据转换 ADS1292_Start_Conversion();​ // 等待一段时间,确保数据转换完成 // 这里可以根据具体情况调整延时时间 Delay(100); // 假设延时100毫秒​ // 读取采集到的数据 GPIO_ResetBits(ADS1292_CS_PORT, ADS1292_CS_PIN); SPI_SendData(ADS1292_SPI, ADS1292_CMD_RDATAC); while (SPI_I2S_GetFlagStatus(ADS1292_SPI, SPI_I2S_FLAG_BSY) == SET); for (int i = 0; i < BUFFER_SIZE; i++) { SPI_SendData(ADS1292_SPI, 0xFF); // 发送一个无关的字节以触发数据传输 while (SPI_I2S_GetFlagStatus(ADS1292_SPI, SPI_I2S_FLAG_RXNE) == RESET); dataBuffer[i] = SPI_ReceiveData(ADS1292_SPI); // 读取接收到的数据 } GPIO_SetBits(ADS1292_CS_PORT, ADS1292_CS_PIN);​ // 处理采集到的数据 // ...​ // 循环进行其他操作 // ...}}​// 配置ADS1292的SPI接口void ADS1292_SPI_Config(void){ GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure;​ // 使能SPI时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);​ // 配置CS引脚为推挽输出 GPIO_InitStructure.GPIO_Pin = ADS1292_CS_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(ADS1292_CS_PORT, &GPIO_InitStructure);​ // 配置SPI引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);​ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);​ // 配置SPI参数 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(ADS1292_SPI, &SPI_InitStructure);​ // 使能SPI SPI_Cmd(ADS1292_SPI, ENABLE);}​// 启动ADS1292的数据转换void ADS1292_Start_Conversion(void){ // 禁用ADS1292的连续数据传输模式 GPIO_ResetBits(ADS1292_CS_PORT, ADS1292_CS_PIN); SPI_SendData(ADS1292_SPI, ADS1292_CMD_SDATAC); while (SPI_I2S_GetFlagStatus(ADS1292_SPI, SPI_I2S_FLAG_BSY) == SET); GPIO_SetBits(ADS1292_CS_PORT, ADS1292_CS_PIN);​ // 发送启动转换命令 GPIO_ResetBits(ADS1292_CS_PORT, ADS1292_CS_PIN); SPI_SendData(ADS1292_SPI, ADS1292_CMD_START); while (SPI_I2S_GetFlagStatus(ADS1292_SPI, SPI_I2S_FLAG_BSY) == SET); GPIO_SetBits(ADS1292_CS_PORT, ADS1292_CS_PIN);}​代码里调用ADS1292_Start_Conversion()函数启动数据转换,等待一段时间确保数据转换完成。通过发送ADS1292_CMD_RDATAC命令并读取数据缓冲区,从ADS1292模块中读取采集到的数据。4.2 OLED显示屏驱动代码包含了基本的初始化、清屏、设置位置、显示字符串、显示数字和显示浮点数等功能。#include "stm32f10x.h"#include "delay.h"#include "i2c.h"​#define OLED_ADDRESS 0x78 // OLED显示屏的I2C地址​// OLED缓存数组(128x64像素,每个字节代表8个像素)unsigned char OLED_Buffer[128 * 8];​// 向OLED显示屏发送命令void OLED_WriteCmd(unsigned char cmd) { I2C_Start(); I2C_SendByte(OLED_ADDRESS); I2C_SendByte(0x00); // 发送命令标志位 I2C_SendByte(cmd); I2C_Stop();}​// 向OLED显示屏发送数据void OLED_WriteData(unsigned char data) { I2C_Start(); I2C_SendByte(OLED_ADDRESS); I2C_SendByte(0x40); // 发送数据标志位 I2C_SendByte(data); I2C_Stop();}​// 初始化OLED显示屏void OLED_Init() { // 初始化I2C总线 I2C_Init(); // 初始化OLED显示屏 OLED_WriteCmd(0xAE); // 关闭显示 OLED_WriteCmd(0xD5); // 设置时钟分频因子 OLED_WriteCmd(0x80); // 默认值 OLED_WriteCmd(0xA8); // 设置驱动路数 OLED_WriteCmd(0x3F); // 1/64 驱动 OLED_WriteCmd(0xD3); // 设置显示偏移 OLED_WriteCmd(0x00); // 默认值 OLED_WriteCmd(0x40); // 设置显示开始行 OLED_WriteCmd(0x8D); // 设置电荷泵 OLED_WriteCmd(0x14); // 使能电荷泵 OLED_WriteCmd(0x20); // 设置内存地址模式 OLED_WriteCmd(0x00); // 水平寻址模式 OLED_WriteCmd(0xA1); // 设置段重定义 OLED_WriteCmd(0xC8); // 设置COM扫描方向 OLED_WriteCmd(0xDA); // 设置COM硬件引脚配置 OLED_WriteCmd(0x12); // 默认值 OLED_WriteCmd(0x81); // 设置对比度控制 OLED_WriteCmd(0xCF); // 默认值 OLED_WriteCmd(0xD9); // 设置预充电周期 OLED_WriteCmd(0xF1); // 默认值 OLED_WriteCmd(0xDB); // 设置VCOMH Deselect Level OLED_WriteCmd(0x40); // 默认值 OLED_WriteCmd(0xA4); // 设置全局显示 OLED_WriteCmd(0xA6); // 设置显示方式,默认正常显示 OLED_Clear(); // 清屏 OLED_WriteCmd(0xAF); // 打开显示}​// 清屏void OLED_Clear() { for (int i = 0; i < 8; i++) { OLED_WriteCmd(0xB0 + i); // 设置页地址 for (int j = 0; j < 128; j++) { OLED_WriteCmd(0x00); // 清空数据 OLED_Buffer[j + i * 128] = 0x00; } }}​// 设置显示位置void OLED_SetPos(unsigned char row, unsigned char column) { OLED_WriteCmd(0xB0 + row); // 设置页地址 OLED_WriteCmd(0x00 + (8 * column & 0x0F)); // 设置列低地址 OLED_WriteCmd(0x10 + ((8 * column >> 4) & 0x0F)); // 设置列高地址}​// 显示字符串void OLED_ShowString(const char* str) { while (*str) { for (int i = 0; i < 8; i++) { OLED_WriteData(font8x16[(*str - ' ')*16 + i]); // 显示字体数据 OLED_Buffer[column + row * 128] = font8x16[(*str - ' ')*16 + i]; // 更新缓存 column++; } str++; }}​// 显示数字void OLED_ShowNum(int num, unsigned char digit) { char str[10]; sprintf(str, "%d", num); OLED_ShowString(str);}​// 显示浮点数void OLED_ShowFloat(float num, unsigned char decimal) { char str[10]; sprintf(str, "%.*f", decimal, num); OLED_ShowString(str);}​4.3 OLED显示体温、心率#include "stm32f10x.h"#include "delay.h"#include "oled.h"​// 定义体温值和心率值float temperature = 37.6;int heartRate = 90;​int main(void) { // 初始化OLED显示屏 OLED_Init(); // 清屏 OLED_Clear(); // 设置字体大小 OLED_SetFontSize(16); // 设置显示位置 OLED_SetPos(0, 0); // 显示体温值 OLED_ShowString("Temperature: "); OLED_ShowFloat(temperature, 1); // 设置显示位置 OLED_SetPos(2, 0); // 显示心率值 OLED_ShowString("Heart Rate: "); OLED_ShowNum(heartRate, 0); while (1) { // 主循环 }}​五、总结本文章描述了生理监测装置整个项目的设计方案,设计过程;通过采集心电图、呼吸和温度数据,并使用STM32F103C8T6单片机进行数据处理和显示,实现了实时监测和显示生理参数的功能。提出了健康状况判断和其他增强功能的设计思路。该装置可以用于个人的健康监测和远程监护等场景,具有一定的实用性和扩展性。
  • [技术干货] MCS-51单片机温度控制系统的设计
    一、项目介绍注塑机是一种常用的制造设备,用于生产塑料制品。在注塑机的工作过程中,溶胶必须达到一定的温度才能被注入模具中进行成型。因此,在注塑机的生产过程中,温度控制是非常重要的一环。本项目基于MCS-51单片机设计了一款温度控制系统,主控芯片采用STC89C52,温度传感器采用铂电阻。该系统主要应用于注塑机的溶胶射嘴头上进行加热控制,利用继电器控制加热器实现温度加热,控制系统检测温度是否到达设定阀值来控制继电器。本项目的设计思路是,利用铂电阻温度传感器对溶胶进行实时温度监测,并将监测到的温度值通过LCD显示屏实时显示。控制器采用PID算法对溶胶温度进行精准控制,当温度低于设定阀值时,控制器将通过继电器控制加热器进行加热操作,直到温度达到设定阀值后停止加热操作。通过本项目的应用,可以实现对注塑机的溶胶温度进行精准控制,从而提高注塑机的生产效率和产品质量。同时,该系统控制方式简单,易于操作和维护,具有较高的实用性和可靠性。二、技术说明和功能描述【1】STC89C52单片机作为主控芯片,具有高性能和丰富的外设接口。【2】铂电阻温度传感器用于测量溶胶射嘴头的温度,并将数据传输给单片机。【3】继电器用于控制加热器的通断,实现温度加热。【4】温度控制系统可以根据设定的温度阈值来判断是否需要进行加热,从而控制继电器的状态。【5】系统可以通过LCD显示屏显示当前温度和设定的目标温度。【6】当温度超过或低于设定的阈值时,系统可以触发报警装置进行警示。三、系统设计思路3.1 硬件选型说明【1】主控芯片:STC89C52单片机STC89C52是一款具有高性能和丰富外设接口的经典51系列单片机,适合中小型嵌入式系统应用。它具有8位CPU、8KB的内部FLASH存储器、256字节的RAM、3个定时器/计数器、串行通信接口等功能。这款单片机运算速度快,响应迅速,可满足本项目对性能和实时性的要求。【2】温度传感器:铂电阻温度传感器铂电阻温度传感器是一种常见的温度传感器,具有稳定性好、精度高的特点。它的工作原理是通过测量电阻值的变化来确定温度变化,传感器的电阻值与温度呈线性关系。在本项目中,铂电阻温度传感器被用于测量溶胶射嘴头的温度,提供实时的温度数据给单片机进行控制。【3】继电器:用于控制加热器继电器是一种电子开关设备,能够在小电流的控制信号下控制大电流的通断。在本项目中,继电器被用来控制加热器的通断状态,根据温度控制的需要进行加热或停止加热操作。【4】LCD显示屏:用于显示温度和设定值LCD显示屏是一种常见的数字显示装置,具有低功耗、可视角度广、反应快速等特点。在本项目中,LCD显示屏用于显示当前实际温度和设定的目标温度阈值,方便操作员进行观察和设置。【5】按钮开关:用于设定目标温度阈值按钮开关是一种常用的输入设备,用于实现用户与系统的交互。在本项目中,按钮开关用于更新设定的目标温度阈值,供操作员根据需要进行调整。【6】报警装置:用于温度异常警示报警装置能够发出声音或光信号,用于警示操作员温度超过或低于设定的阈值。在本项目中,报警装置用于提醒操作员注意温度异常,保证工作安全和质量。3.2 设计思路【1】硬件连接:将铂电阻温度传感器连接到单片机的模拟输入端口,将LCD显示屏连接到单片机的数据口,将继电器接在单片机的输出端口,通过继电器控制加热器的电源。【2】温度采集:通过铂电阻温度传感器实时采集溶胶的温度信号,将信号转换为数字信号,通过单片机的模拟输入端口输入到单片机中。【3】温度控制:使用PID算法对溶胶的温度进行精准控制。PID算法是一种经典的控制算法,通过对比实际温度和设定温度的差异,计算出控制器输出控制信号的大小来控制继电器的开关状态,从而实现对加热器的控制。【4】温度显示:将温度值通过LCD显示屏实时显示,方便操作人员监测温度变化。【5】控制器编程:使用C语言编写单片机的控制程序,实现温度采集、PID算法控制、温度显示等功能。本项目的设计思路是基于MCS-51单片机和PID算法实现温度控制系统,通过铂电阻温度传感器实时采集温度信号,通过PID算法实现温度控制,通过LCD显示屏实现温度显示,最终通过继电器控制加热器实现温度加热控制。四、代码实现4.1 温度控制系统实现(PID算法)使用STC89C52单片机、铂电阻温度传感器、PCF8591模数转换器和PID算法实现温度控制并控制继电器:#include <reg52.h>​#define RELAY_PIN P1 // 继电器控制引脚​// 温度传感器连接引脚sbit TEMP_SENSOR_PIN = P2^0;​// PCF8591模数转换器连接引脚sbit PCF_SDA = P2^1; // I2C数据线sbit PCF_SCL = P2^2; // I2C时钟线sbit PCF_EOC = P2^3; // 转换结束标志​// PID参数float kp = 1.0; // 比例系数float ki = 0.5; // 积分系数float kd = 0.2; // 微分系数​// 温度目标值float targetTemp = 100.0;​// PID控制误差相关变量float error = 0.0;float prevError = 0.0;float integral = 0.0;float derivative = 0.0;​// PID控制输出float output = 0.0;​// 设置PWM占空比函数void setPwmDutyCycle(unsigned char dutyCycle) { TH0 = 256 - dutyCycle; // 设置高位 TL0 = 256 - dutyCycle; // 设置低位}​// 定时器0初始化函数void timer0Init() { TMOD = 0x01; // 定时器0工作在模式1(16位定时器) TH0 = 0; // 最初赋初值 TL0 = 0; TR0 = 1; // 定时器0开始计时}​// I2C总线开始信号函数void i2cStart() { PCF_SDA = 1; PCF_SCL = 1; PCF_SDA = 0; PCF_SCL = 0;}​// I2C总线停止信号函数void i2cStop() { PCF_SDA = 0; PCF_SCL = 1; PCF_SDA = 1;}​// I2C写数据函数void i2cWriteByte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { PCF_SDA = (dat & 0x80) ? 1 : 0; PCF_SCL = 1; PCF_SCL = 0; dat <<= 1; } PCF_SCL = 1; PCF_SCL = 0; PCF_SDA = 1;}​// 从PCF8591读取温度值unsigned char readTemperatureValue() { unsigned char tempValue; i2cStart(); i2cWriteByte(0x90); // PCF8591地址 + 写操作 i2cWriteByte(0x00); // 设置输入通道为0 i2cStart(); i2cWriteByte(0x91); // PCF8591地址 + 读操作 tempValue = P0; // 读取温度值 i2cStop(); return tempValue;}​// PID控制函数void performPIDControl() { error = targetTemp - readTemperatureValue(); // 计算误差 integral += error; // 积分项 derivative = error - prevError; // 微分项 output = kp * error + ki * integral + kd * derivative; // PID输出 if (output < 0) { output = 0; } else if (output > 255) { output = 255; } setPwmDutyCycle((unsigned char)output); // 设置PWM占空比 prevError = error; // 更新上一次误差}​void main() { timer0Init(); // 初始化定时器0 setPwmDutyCycle(0); // 初始化PWM占空比为0​ while (1) { performPIDControl(); // 进行PID控制​ if (readTemperatureValue() >= targetTemp) { RELAY_PIN = 0; // 继电器断开,停止加热 } else { RELAY_PIN = 1; // 继电器闭合,进行加热 } }}以上代码是温度控制系统实现代码,使用PID算法根据目标温度和当前温度进行控制,并通过继电器控制加热器的通断。需要通过I2C总线与PCF8591模数转换器进行通信,读取铂电阻温度传感器的数据。4.2 LCD1602显示屏下面是使用STC89C52单片机和LCD1602液晶显示屏实现数字显示并封装为函数调用的代码:#include <reg52.h>​#define LCD_RS P2_0 // 液晶RS引脚#define LCD_RW P2_1 // 液晶RW引脚#define LCD_EN P2_2 // 液晶EN引脚#define LCD_DATA P0 // 液晶数据总线​// 延时函数void delay(unsigned int t) { unsigned int i, j; for (i = 0; i < t; i++) { for (j = 0; j < 110; j++); }}​// 液晶写命令函数void lcdWriteCmd(unsigned char cmd) { LCD_RS = 0; // 设置为写命令模式 LCD_RW = 0; // 设置为写入模式 LCD_DATA = cmd; // 写入命令 LCD_EN = 1; // 使能 delay(1); // 延时 LCD_EN = 0; // 禁止 delay(1); // 延时}​// 液晶写数据函数void lcdWriteData(unsigned char dat) { LCD_RS = 1; // 设置为写数据模式 LCD_RW = 0; // 设置为写入模式 LCD_DATA = dat; // 写入数据 LCD_EN = 1; // 使能 delay(1); // 延时 LCD_EN = 0; // 禁止 delay(1); // 延时}​// 液晶初始化函数void lcdInit() { lcdWriteCmd(0x38); // 设置16x2显示,5x7点阵,8位数据接口 lcdWriteCmd(0x0C); // 显示开,光标关闭 lcdWriteCmd(0x06); // 光标右移 lcdWriteCmd(0x01); // 清屏}​// 在液晶上显示数字函数void lcdDisplayNumber(unsigned int num) { unsigned char i; unsigned char buffer[5]; // 缓冲区,最大支持5位数​ if (num == 0) { lcdWriteData('0'); // 数字0特殊处理 return; }​ for (i = 0; i < 5; i++) { buffer[i] = num % 10; // 从低位到高位依次取余数 num /= 10; }​ for (i = 5; i > 0; i--) { if (buffer[i - 1] != 0 || i == 1) { // 从高位开始显示直到遇到非零数字或者个位数 lcdWriteData(buffer[i - 1] + '0'); // 显示数字 } }}​void main() { lcdInit(); // 初始化液晶​ while (1) { unsigned int num = 12345; // 要显示的数字 lcdWriteCmd(0x80); // 设置光标位置为第一行第一个字符 lcdDisplayNumber(num); // 显示数字​ while (1); // 循环显示 }}以上代码是LCD数字显示程序,使用LCD1602液晶显示屏和STC89C52单片机,通过封装函数调用来实现数字在液晶屏上的显示。需要进行液晶的初始化操作,使用lcdDisplayNumber函数将要显示的数字传入。在main函数中给出了一个例子,以连续循环显示数字12345为示例。
  • [问题求助] Hi3861V100的tick时长修改
    如题,Hi3861V100的tick时长怎么修改?(默认为10ms)
  • [技术干货] 基于单片机的太阳能热水器控制器设计
    一、项目介绍随着环保意识的逐渐增强,太阳能热水器作为一种清洁能源应用得越来越广泛。然而,传统的太阳能热水器控制器通常采用机械式或电子式温控器,存在精度低、控制不稳定等问题。为了解决这些问题,本项目基于单片机技术设计了一款太阳能热水器控制器,主控芯片采用STC89C52。该控制器可以实现对太阳能热水器的水温、水位等参数进行准确、稳定的控制,提高了太阳能热水器的能源利用效率和使用寿命,同时也符合节能环保的社会需求。二、系统构架2.1 系统设计本系统采用主从结构,由STC89C52单片机作为主控芯片,负责控制整个太阳能热水器的运行。系统包括传感器模块、驱动模块和用户界面模块。传感器模块包括温度传感器和光照传感器,用于实时监测水温和太阳辐射强度。驱动模块包括电磁阀和水泵,用于控制水流和热水的循环。用户界面模块包括液晶显示屏和按键,用于显示当前状态和提供用户交互。2.2 功能设计本设计的太阳能热水器控制器功能:温度控制:通过温度传感器实时监测水温,并根据设定的阈值控制电磁阀和水泵,以保持热水器水温在设定范围内。光照控制:通过光照传感器实时监测太阳辐射强度,判断当前是否有足够的太阳能供给,若不足,则停止水泵运行,以节约能源。时间控制:设置定时计划,控制热水器在指定时间段内工作或停止工作。用户交互:通过液晶显示屏显示当前温度、工作状态等信息,并通过按键设定参数和操作热水器。2.3 硬件设计硬件设计包括电路连接和外围模块选择。主控芯片STC89C52与传感器模块、驱动模块和用户界面模块通过IO口进行连接。温度传感器采用DS18B20数字温度传感器,光照传感器采用光敏电阻。2.4 软件设计软件设计主要包括系统初始化、传感器数据采集、控制算法和用户交互等部分。系统初始化包括IO口配置、定时器设置等。传感器数据采集通过相应的接口获取温度和光照传感器数据。控制算法根据采集到的数据进行温度和光照控制,并控制电磁阀和水泵的开关。用户交互通过液晶显示屏和按键实现,用户可以通过按键设置参数和操作热水器。2.5 设计思路本项目的控制器主要包括传感器模块、控制模块和显示模块三部分。其中,传感器模块用于实时检测太阳能热水器的水温、水位等参数;控制模块将传感器采集到的数据进行处理,并通过控制水泵、电磁阀等执行器来实现对太阳能热水器的水温、水位等参数进行准确、稳定的控制;显示模块则用于显示当前的水温、水位等参数。具体的设计流程如下:【1】确定硬件平台:采用STC89C52单片机作为主控芯片,搭建传感器模块和执行器模块,通过串口通信与PC机连接。【2】确定传感器类型:选择DS18B20温度传感器和液位传感器作为检测太阳能热水器水温、水位的传感器。【3】确定控制策略:根据太阳能热水器的实际情况,设计PID控制算法,通过控制水泵、电磁阀等执行器来实现对太阳能热水器的水温、水位等参数进行准确、稳定的控制。【4】编写程序:根据硬件平台和控制策略,编写程序实现数据采集、处理和控制等功能。【5】调试测试:将设计好的控制器与太阳能热水器进行连接测试,检查数据采集、处理和控制等功能是否正常。2.6 实现效果本项目设计的太阳能热水器控制器实现了对太阳能热水器的水温、水位等参数进行准确、稳定的控制。控制器的特点:【1】精度高:采用PID控制算法,能够对太阳能热水器的水温、水位等参数进行精确控制。【2】控制稳定:通过控制水泵、电磁阀等执行器来实现对太阳能热水器的水温、水位等参数进行稳定控制。【3】显示直观:通过显示模块可以直观地显示当前的水温、水位等参数。三、代码实现3.1 DS18B20读取温度以下是基于STC89C52单片机和DS18B20温度传感器实现读取温度值并打印到串口的示例:#include <reg52.h>#include <intrins.h>​#define DQ P3_7​typedef unsigned char uchar;typedef unsigned int uint;​sbit LED=P1^0;​void Delay1ms(uint);void Delay10us(uint);uchar Init_DS18B20();void Write_DS18B20(uchar dat);uchar Read_DS18B20();int Get_Temp();​void main(){ uchar temp; int temperature;​ TMOD = 0x20; //定时器1工作在方式2 TH1 = 0xfd; //波特率9600 TL1 = 0xfd; PCON = 0x00; //波特率不加倍 SCON = 0x50; //串口方式1,允许接收 TR1 = 1; //定时器1开始计时 ES = 1; //允许串口中断​ while(1) { temp = Get_Temp(); temperature = (int)temp * 0.0625 * 100; //将温度值转换为实际温度,单位为°C printf("Temperature: %d.%dC \r\n", temperature / 100, temperature % 100); Delay1ms(500); //每隔500ms读取一次温度值并打印到串口 }}​void Delay1ms(uint cnt){ uint i, j;​ for (i = 0; i < cnt; i++) { for (j = 0; j < 110; j++); }}​void Delay10us(uint cnt){ while(cnt--);}​uchar Init_DS18B20(){ uchar i;​ DQ = 1; Delay10us(5); DQ = 0; Delay10us(80); DQ = 1; Delay10us(5); i = DQ; Delay10us(20); return i;}​void Write_DS18B20(uchar dat){ uchar i;​ for (i = 0; i < 8; i++) { DQ = 0; _nop_(); DQ = dat & 0x01; Delay10us(5); DQ = 1; dat >>= 1; }}​uchar Read_DS18B20(){ uchar i, j, dat = 0;​ for (i = 0; i < 8; i++) { DQ = 0; _nop_(); DQ = 1; _nop_(); j = DQ; Delay10us(5); dat = (j << 7) | (dat >> 1); }​ return dat;}​int Get_Temp(){ uchar TL, TH; int temp;​ Init_DS18B20(); Write_DS18B20(0xcc); Write_DS18B20(0x44); Delay1ms(750); Init_DS18B20(); Write_DS18B20(0xcc); Write_DS18B20(0xbe); TL = Read_DS18B20(); TH = Read_DS18B20(); temp = TH; temp <<= 8; temp |= TL; return temp;}​void UART_Isr() interrupt 4{ if (RI == 1) { RI = 0; }​ if (TI == 1) { TI = 0; }}代码中使用了定时器和串口中断,要注意DS18B20的引脚连接和串口通信的波特率设置。3.2 PID算法控制温度以下是使用STC89C52单片机和DS18B20温度传感器通过PID算法实现热水器恒温控制的代码:#include <reg52.h>#include <intrins.h>​#define uchar unsigned char#define uint unsigned int​sbit Relay = P1^0; // 继电器控制引脚​// 温度传感器DS18B20相关宏定义sbit DQ = P2^7; // DS18B20数据线引脚#define DQ_OUT P2 &= 0x7F#define DQ_IN P2 |= 0x80​// PID参数定义float Kp = 1.0; // PID比例系数float Ki = 0.5; // PID积分系数float Kd = 0.2; // PID微分系数​// 温度控制参数定义float setTemp = 40.0; // 设定的目标温度float curTemp = 0.0; // 当前温度float lastTemp = 0.0; // 上一次的温度float error = 0.0; // 温度误差float integral = 0.0; // 积分项float derivative = 0.0; // 微分项float output = 0.0; // 控制输出​// 延时函数void delay(uint t) { while (t--);}​// DS18B20初始化uchar Init_DS18B20() { uchar presence = 0; DQ_OUT; DQ = 0; delay(480); // 延时480us DQ = 1; delay(60); // 延时60us DQ_IN; presence = DQ; delay(420); // 延时420us return presence;}​// DS18B20读取一个字节uchar Read_DS18B20() { uchar i, j, dat = 0; for (i = 8; i > 0; i--) { DQ_OUT; DQ = 0; dat >>= 1; _nop_(); _nop_(); _nop_(); DQ = 1; DQ_IN; if (DQ) { dat |= 0x80; } delay(120); // 延时120us } return dat;}​// DS18B20写入一个字节void Write_DS18B20(uchar dat) { uchar i; for (i = 8; i > 0; i--) { DQ_OUT; DQ = 0; DQ = dat & 0x01; delay(120); // 延时120us DQ = 1; dat >>= 1; }}​// DS18B20温度转换void Convert_DS18B20() { Init_DS18B20(); Write_DS18B20(0xCC); // 跳过ROM操作 Write_DS18B20(0x44); // 启动温度转换}​// 获取DS18B20温度值float Get_DS18B20_Temp() { uchar TL, TH; int temp = 0; Init_DS18B20(); Write_DS18B20(0xCC); // 跳过ROM操作 Write_DS18B20(0xBE); // 发送读取命令 TL = Read_DS18B20(); // 读取温度低字节 TH = Read_DS18B20(); // 读取温度高字节 temp = TH; temp <<= 8; temp |= TL; return (float)temp / 16.0; // 返回温度值}​// PID控制算法float PID_Control(float setValue, float currentValue) { error = setValue - currentValue; integral += error; derivative = currentValue - lastTemp; output = Kp * error + Ki * integral + Kd * derivative; lastTemp = currentValue; return output;}​void main() { while (1) { curTemp = Get_DS18B20_Temp(); // 获取当前温度 output = PID_Control(setTemp, curTemp); // PID控制计算 if (output > 0) { Relay = 0; // 继电器闭合,加热器工作 } else { Relay = 1; // 继电器断开,加热器停止工作 } delay(1000); // 延时1s }}3.3 驱动BH1750光敏传感器使用STC89C52单片机读取BH1750光敏传感器值通过串口打印的代码:#include <reg52.h>#include <intrins.h>​#define uchar unsigned char#define uint unsigned int​sbit SDA = P2^7; // IIC总线数据线引脚sbit SCL = P2^6; // IIC总线时钟线引脚​// BH1750光敏传感器相关宏定义#define BH1750_ADDR 0x23 // BH1750设备地址#define BH1750_ON 0x01 // BH1750上电命令#define BH1750_OFF 0x00 // BH1750下电命令#define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10 // BH1750连续高分辨率模式​// 延时函数void delay(uint t) { while (t--);}​// IIC总线起始信号void I2C_Start() { SDA = 1; delay(1); SCL = 1; delay(1); SDA = 0; delay(1); SCL = 0; delay(1);}​// IIC总线停止信号void I2C_Stop() { SDA = 0; delay(1); SCL = 1; delay(1); SDA = 1; delay(1);}​// IIC总线发送应答信号void I2C_Ack() { SDA = 0; delay(1); SCL = 1; delay(1); SCL = 0; delay(1); SDA = 1; delay(1);}​// IIC总线发送不应答信号void I2C_NAck() { SDA = 1; delay(1); SCL = 1; delay(1); SCL = 0; delay(1);}​// IIC总线接收应答信号bit I2C_WaitAck() { bit ack; SDA = 1; delay(1); SCL = 1; delay(1); ack = SDA; SCL = 0; delay(1); return ack;}​// IIC总线发送一个字节void I2C_WriteByte(uchar dat) { uchar i; for (i = 0; i < 8; i++) { SDA = (dat & 0x80) >> 7; dat <<= 1; delay(1); SCL = 1; delay(1); SCL = 0; delay(1); }}​// IIC总线读取一个字节uchar I2C_ReadByte() { uchar i, dat = 0; SDA = 1; delay(1); for (i = 0; i < 8; i++) { SCL = 1; delay(1); dat = (dat << 1) | SDA; SCL = 0; delay(1); } return dat;}​// 初始化BH1750光敏传感器void Init_BH1750() { I2C_Start(); I2C_WriteByte(BH1750_ADDR); // 发送设备地址 I2C_WaitAck(); I2C_WriteByte(BH1750_ON); // 上电 I2C_WaitAck(); I2C_Stop(); delay(5);}​// 启动BH1750测量void Start_BH1750() { I2C_Start(); I2C_WriteByte(BH1750_ADDR); // 发送设备地址 I2C_WaitAck(); I2C_WriteByte(BH1750_CONTINUOUS_HIGH_RES_MODE); // 选择连续高分辨率模式 I2C_WaitAck(); I2C_Stop(); delay(180);}​// 读取BH1750测量结果uint Read_BH1750() { uint value; I2C_Start(); I2C_WriteByte(BH1750_ADDR + 1); // 发送设备地址,读模式 I2C_WaitAck(); value = ((uint)I2C_ReadByte() << 8) | (uint)I2C_ReadByte(); // 读取两个字节的数据 I2C_NAck(); I2C_Stop(); return value;}​// 串口发送一个字符void UART_SendChar(uchar chr) { SBUF = chr; while (!TI); TI = 0;}​// 串口发送字符串void UART_SendString(const uchar *str) { while (*str) { UART_SendChar(*str++); }}​// 串口发送一个无符号整数void UART_SendUInt(uint val) { uchar i, len; uchar buf[5]; len = 0; do { buf[len++] = val % 10 + '0'; val /= 10; } while (val); for (i = len; i > 0; i--) { UART_SendChar(buf[i-1]); }}​void main() { uint lightValue;​ Init_BH1750(); // 初始化BH1750光敏传感器 // 串口初始化, 波特率9600 TMOD = 0x20; TH1 = 0xFD; TL1 = 0xFD; SCON = 0x50; TR1 = 1;​ while (1) { Start_BH1750(); // 启动测量 lightValue = Read_BH1750(); // 读取测量结果 UART_SendString("Light value: "); UART_SendUInt(lightValue); UART_SendString("\r\n"); delay(1000); // 延时1s }}在程序中,初始化了BH1750光敏传感器,使用Start_BH1750()函数启动测量,通过Read_BH1750()函数读取测量结果,在串口上打印出来。串口的初始化设置为波特率9600,发送数据时使用UART_SendString()和UART_SendUInt()函数。四、总结本设计基于STC89C52单片机实现了一个功能完善的太阳能热水器控制器。该控制器具有温度控制、光照控制、时间控制和用户交互等功能,可以提高太阳能热水器的性能和便捷程度。通过合理的硬件选型和软件设计,使得系统能够准确、稳定地实现对太阳能热水器的控制,提高能源利用效率,并为用户提供便利的操作界面。未来可以进一步优化和拓展该控制器,如增加远程控制功能、与智能家居系统的连接等,以满足不同用户的需求。
  • [问题求助] 小熊派·鸿蒙·季PWM问题
    主控芯片型号为Hi3861V100的芯片的小熊派·鸿蒙·季板子的PWM频率是160MHz除16位的分频系数(最大65535),也就是说最小输出频率是两千多Hz。这个输出频率不能再低了吗?如果可以的话怎么变低?
  • [技术干货] 基于单片机的简易智能电动车设计
    一、项目介绍智能交通工具在现代社会中起着越来越重要的作用,电动车作为一种环保、便捷的交通工具,受到了广泛的关注和应用。本设计基于单片机技术,设计一款简易智能电动车,实现基本的控制和功能,并提供良好的用户体验。二、硬件选型【1】主控芯片:STC89C52STC89C52是一款功能强大的单片机,具有51系列兼容性和丰富的外设接口。它具有8位CPU,可运行高达12MHz的时钟频率,提供了丰富的IO口、定时器和串口等功能,非常适合用于电动车控制。STC89C52具有低功耗和高性能的特点,能够满足电动车系统的控制需求。【2】电动机驱动芯片:选择L298N驱动芯片根据电动机的参数,如电压、电流和功率进行匹配选择L298N驱动芯片。常用的电动机驱动芯片有L298N、TB6612FNG等,都具有较高的工作电压和电流能力,适合于小型电动车的驱动。【3】电源管理模块:电源管理模块用于电动车的电源供应和电池管理。选择TP4056电源管理芯片,TP4056充电管理芯片,用于电池的充电管理和保护。使用LM2596开关电源芯片,LM2596可以提供稳定的电源电压给各个模块。【4】用户界面:用户界面模块采用LCD显示屏和按键元件。LCD显示屏选择LCD1602字符型液晶显示屏,用于显示电动车的状态信息。按键用于用户的输入和操作。【5】传感器:传感器模块用于实时监测电动车的状态和环境信息。选择速度传感器、温度传感器、倾斜传感器。速度传感器选择霍尔传感器,用于测量电动车的速度。温度传感器选择DS18B20数字温度传感器,用于测量电动车的温度。倾斜传感器选择倾斜传感器模块,用于检测电动车的倾斜状态。三、系统框架总结【1】主控单元 主控单元使用STC89C52单片机,负责整个系统的控制和协调。通过编程控制IO口和定时器等功能,实现电动车的速度控制、转向控制和状态监测等操作。主控单元还负责与其他模块之间的通信和数据交换。【2】电动机驱动 电动机驱动模块使用适当的电机驱动芯片,根据主控单元的指令控制电动机的启动、停止和速度调节。通过PWM信号调节电机的转速,实现电动车的前进、后退和制动等功能。【3】电源管理 电源管理模块负责电动车的电源供应和电池管理。包括电池充电管理、电池电量检测和电源开关控制等功能。通过合理管理电池的使用和充电,保证电动车的正常运行和安全性。【4】用户界面 用户界面模块提供给用户操作和显示的接口。采用LCD显示屏和按键元件,用于显示电动车的状态信息和用户输入的指令。用户可以通过按键来控制电动车的启动、停止和速度调节等操作。【5】传感器 传感器模块用于实时监测电动车的状态和环境信息。选择速度传感器、温度传感器和倾斜传感器。通过传感器获取的数据,可以用于电动车的自动控制和保护。【6】功能实现 本设计的功能包括电动车的启动和停止、速度调节、转向控制和状态监测等。用户可以通过按键来启动和停止电动车,通过调节速度控制电动车的前进和后退,通过转向控制实现电动车的转向操作。同时,系统可以实时监测电动车的状态,如电池电量、速度和温度等,并进行相应的保护和提示。四、代码实现4.1 电机控制代码#include <reg52.h> // 引入STC89C52头文件​// 定义IO口连接sbit motorPin1 = P1^0; // 电动机引脚1sbit motorPin2 = P1^1; // 电动机引脚2sbit buttonStart = P2^0; // 启动按钮sbit buttonStop = P2^1; // 停止按钮​// 定义全局变量bit isRunning = 0; // 电动车运行状态​// 函数声明void delay(unsigned int time);void motorForward();void motorBackward();void motorStop();​// 主函数void main() { buttonStart = 1; // 设置启动按钮为输入 buttonStop = 1; // 设置停止按钮为输入​ while (1) { if (buttonStart == 0) { // 按下启动按钮 motorForward(); // 电动车前进 isRunning = 1; // 设置运行状态为1 }​ if (buttonStop == 0) { // 按下停止按钮 motorStop(); // 电动车停止 isRunning = 0; // 设置运行状态为0 }​ if (isRunning) { // 电动车正在运行,可以进行其他操作 // 可以根据需要添加其他功能的代码 } }}​// 延时函数void delay(unsigned int time) { unsigned int i, j; for (i = 0; i < time; i++) for (j = 0; j < 120; j++);}​// 电动车前进函数void motorForward() { motorPin1 = 1; // 设置电动机引脚1为高电平 motorPin2 = 0; // 设置电动机引脚2为低电平}​// 电动车后退函数void motorBackward() { motorPin1 = 0; // 设置电动机引脚1为低电平 motorPin2 = 1; // 设置电动机引脚2为高电平}​// 电动车停止函数void motorStop() { motorPin1 = 0; // 设置电动机引脚1为低电平 motorPin2 = 0; // 设置电动机引脚2为低电平}​4.2 LCD1602显示屏驱动代码#include <reg52.h> // 引入STC89C52头文件​// 定义LCD1602连接的引脚sbit RS = P1^0; // RS引脚sbit EN = P1^1; // EN引脚sbit D4 = P2^4; // 数据线D4引脚sbit D5 = P2^5; // 数据线D5引脚sbit D6 = P2^6; // 数据线D6引脚sbit D7 = P2^7; // 数据线D7引脚​// 函数声明void delay(unsigned int time);void lcdCommand(unsigned char command);void lcdData(unsigned char data);void lcdInit();void lcdDisplayString(char *string);​// 主函数void main() { lcdInit(); // 初始化LCD1602显示屏​ // 显示字符 "20k/h" lcdDisplayString("20k/h");​ while (1) { // 可以在此处添加其他代码,实现其他功能 }}​// 延时函数void delay(unsigned int time) { unsigned int i, j; for (i = 0; i < time; i++) for (j = 0; j < 120; j++);}​// 发送指令到LCD1602void lcdCommand(unsigned char command) { RS = 0; // 设置RS引脚为低电平,表示发送指令 EN = 1; // 设置EN引脚为高电平,使能LCD P2 = command; // 将指令写入数据线 delay(1); // 延时一段时间 EN = 0; // 设置EN引脚为低电平,禁止LCD}​// 发送数据到LCD1602void lcdData(unsigned char data) { RS = 1; // 设置RS引脚为高电平,表示发送数据 EN = 1; // 设置EN引脚为高电平,使能LCD P2 = data; // 将数据写入数据线 delay(1); // 延时一段时间 EN = 0; // 设置EN引脚为低电平,禁止LCD}​// 初始化LCD1602void lcdInit() { lcdCommand(0x38); // 初始化,设置显示模式为2行、5x8点阵 lcdCommand(0x0C); // 开启显示,关闭光标 lcdCommand(0x06); // 设置光标移动方向为右移 lcdCommand(0x01); // 清屏}​// 在LCD1602显示屏上显示字符串void lcdDisplayString(char *string) { while (*string) { lcdData(*string++); }}​4.3 MPU6050驱动代码#include <reg52.h> // 引入STC89C52头文件​// 定义MPU6050连接的引脚sbit SDA = P2^0; // I2C数据线引脚sbit SCL = P2^1; // I2C时钟线引脚​// 定义MPU6050的地址#define MPU6050_ADDRESS 0xD0​// 函数声明void delay(unsigned int time);void i2cStart();void i2cStop();bit i2cSendByte(unsigned char byte);unsigned char i2cReceiveByte();void mpu6050Init();void mpu6050ReadData(short *accelData, short *gyroData);​// 主函数void main() { short accelData[3]; // 存储加速度值的数组 short gyroData[3]; // 存储陀螺仪值的数组​ mpu6050Init(); // 初始化MPU6050模块​ while (1) { mpu6050ReadData(accelData, gyroData); // 读取加速度值和陀螺仪值​ // 打印加速度值和陀螺仪值到串口 printf("Accelerometer: X=%d, Y=%d, Z=%d\r\n", accelData[0], accelData[1], accelData[2]); printf("Gyroscope: X=%d, Y=%d, Z=%d\r\n", gyroData[0], gyroData[1], gyroData[2]);​ delay(1000); // 延时一段时间 }}​// 延时函数void delay(unsigned int time) { unsigned int i, j; for (i = 0; i < time; i++) for (j = 0; j < 120; j++);}​// I2C起始信号void i2cStart() { SDA = 1; SCL = 1; delay(1); SDA = 0; delay(1); SCL = 0;}​// I2C停止信号void i2cStop() { SDA = 0; SCL = 1; delay(1); SDA = 1; delay(1);}​// I2C发送字节bit i2cSendByte(unsigned char byte) { unsigned char i; bit ack;​ for (i = 0; i < 8; i++) { if ((byte & 0x80) == 0x80) SDA = 1; else SDA = 0;​ SCL = 1; delay(1); SCL = 0; delay(1);​ byte <<= 1; }​ SDA = 1; SCL = 1; ack = SDA; delay(1); SCL = 0; delay(1);​ return ack;}​// I2C接收字节unsigned char i2cReceiveByte() { unsigned char i, byte = 0;​ SDA = 1; for (i = 0; i < 8; i++) { byte <<= 1; SCL = 1; delay(1); byte |= SDA; SCL = 0; delay(1); }​ return byte;}​// 初始化MPU6050模块void mpu6050Init() { i2cStart(); i2cSendByte(MPU6050_ADDRESS); i2cSendByte(0x6B); // PWR_MGMT_1寄存器地址 i2cSendByte(0x00); // 将PWR_MGMT_1寄存器写为0,唤醒MPU6050 i2cStop();}​// 读取MPU6050的加速度值和陀螺仪值void mpu6050ReadData(short *accelData, short *gyroData) { unsigned char i;​ i2cStart(); i2cSendByte(MPU6050_ADDRESS); i2cSendByte(0x3B); // ACCEL_XOUT_H寄存器地址 i2cStop();​ i2cStart(); i2cSendByte(MPU6050_ADDRESS | 0x01); // 切换到读模式 for (i = 0; i < 6; i++) { if (i < 5) accelData[i] = (i2cReceiveByte() << 8) | i2cReceiveByte(); else gyroData[i - 5] = (i2cReceiveByte() << 8) | i2cReceiveByte();​ if (i < 5) i2cSendByte(0x00); // 给出ACK,继续读取下一个数据 else i2cSendByte(0x01); // 给出NACK,停止读取 } i2cStop();}​代码实现说明:【1】引入头文件和定义引脚:引入了 reg52.h 头文件,该头文件包含了 STC89C52 的寄存器定义和常用函数。使用 sbit 定义了 MPU6050 模块的 SDA 和 SCL 引脚。【2】延时函数:delay 函数用于产生一段延时,具体延时时间根据实际情况进行调整。【3】I2C通信函数:i2cStart 函数用于发送 I2C 总线的起始信号。i2cStop 函数用于发送 I2C 总线的停止信号。i2cSendByte 函数用于通过 I2C 总线发送一个字节的数据,并返回从设备的应答状态。i2cReceiveByte 函数用于通过 I2C 总线接收一个字节的数据。【4】初始化 MPU6050 模块:mpu6050Init 函数通过 I2C 总线向 MPU6050 发送初始化命令,唤醒 MPU6050 模块。【5】读取 MPU6050 的加速度值和陀螺仪值:mpu6050ReadData 函数通过 I2C 总线向 MPU6050 发送读取命令,并接收加速度值和陀螺仪值。通过 i2cSendByte 发送寄存器地址,然后通过 i2cReceiveByte 接收数据。加速度值和陀螺仪值分别存储在 accelData 和 gyroData 数组中。【6】主函数:在 main 函数中,首先声明了存储加速度值和陀螺仪值的数组。调用 mpu6050Init 函数初始化 MPU6050 模块。进入无限循环,循环中调用 mpu6050ReadData 函数读取加速度值和陀螺仪值,并通过串口打印输出。使用 delay 函数进行延时。
  • [技术干货] 基于单片机的串行通信发射机设计
    一、项目介绍串行通信是一种常见的数据传输方式,允许将数据以比特流的形式在发送端和接收端之间传输。当前实现基于STC89C52单片机的串行通信发射机,通过红外发射管和接收头实现自定义协议的数据无线传输。二、系统设计2.1 单片机选择在本设计中,选择了STC89C52作为主控芯片。单片机具有较高的性能和丰富的外设资源,适合实现串行通信发射机功能。2.2 矩阵键盘采用4x4的矩阵键盘,用于接收用户输入的指令。通过扫描矩阵键盘的按键状态,可以获取用户需要发送的数据。2.3 红外发射管和接收头选择具有较高发射功率和较长发射距离的红外发射管,并配合红外接收头进行数据传输。当红外接收头检测到红外光时,输出低电平;没有检测到红外光时,输出高电平。2.4 矩阵键盘扫描利用矩阵键盘的行列扫描原理,实时检测用户按键状态,并将按键值保存在变量中供后续使用。2.5 数据转换和红外发送根据自定义的协议格式,将用户按键值转换为红外控制码。通过IO口驱动红外发射管发送红外控制码。三、协议的约定【1】自定义发送协议: 自定义发送协议需要约定以下内容:帧格式:确定每一帧数据的起始标志、数据长度和校验信息等。常见的帧格式包括起始位、数据位、停止位和校验位。数据编码:确定将要发送的数据转换为比特流进行传输的方式。常见的编码方式有Manchester编码和Pulse-Width Modulation(PWM)编码。校验机制:确定是否需要添加校验位,以保证数据传输的准确性和完整性。常见的校验方式有奇偶校验、循环冗余校验(CRC)等。例如,可以采用以下的帧格式作为示例:帧头:起始位,一个特定的比特用于标识帧的开始。数据字段:包含要发送的数据。校验位:用于检验帧数据的准确性。帧尾:停止位,一个特定的比特用于标识帧的结束。【2】接收原理: 接收端通过红外接收头实现对发送端发送的红外控制码的接收和解码。接收原理包括以下步骤:红外信号接收:红外接收头接收红外光,并将接收到的光信号转换为电流信号。弱信号放大:对接收到的电流信号进行放大,以便进行后续处理。数据解码:根据约定的帧格式和编码方式,将接收到的比特流解码为原始数据。校验校准:对接收到的数据进行校验和校准,确保数据的准确性。下面是发送端和接收端的代码:发送端代码:#include <reg52.h>​// 定义红外发射管IO口#define IR_LED P1​// 发送一帧数据void sendFrame(unsigned char data) { unsigned char i;​ // 发送起始位 IR_LED = 0; DelayUs(300);​ for (i = 0; i < 8; i++) { // 发送数据位 IR_LED = data & 0x01; DelayUs(300); data >>= 1; }​ // 发送停止位 IR_LED = 1; DelayUs(300);}​// 主函数void main() { unsigned char sendData = 0x55; // 要发送的数据​ while (1) { sendFrame(sendData); // 发送一帧数据 DelayMs(1000); }}接收端代码:#include <reg52.h>​// 定义红外接收头IO口#define IR_RECV P2​// 接收一帧数据unsigned char receiveFrame() { unsigned char i; unsigned char data = 0;​ while (IR_RECV); // 等待起始位​ DelayUs(150);​ for (i = 0; i < 8; i++) { DelayUs(300); data >>= 1; if (IR_RECV) { data |= 0x80; } }​ return data;}​// 主函数void main() { unsigned char receivedData;​ while (1) { receivedData = receiveFrame(); // 接收一帧数据 // 处理接收到的数据 }}四、代码实现下面是基于STC89C52单片机的串行通信发射机和接收机的整体代码,其中包括了4x4矩阵键盘的读取和红外数据传输的功能:发射机代码:#include <reg52.h>​#define IR_LED P1#define KEYBOARD P2​// 发送一帧数据void sendFrame(unsigned char data) { unsigned char i;​ // 发送起始位 IR_LED = 0; DelayUs(300);​ for (i = 0; i < 8; i++) { // 发送数据位 IR_LED = data & 0x01; DelayUs(300); data >>= 1; }​ // 发送停止位 IR_LED = 1; DelayUs(300);}​// 读取矩阵键盘unsigned char readKeyboard() { unsigned char row, col, keyVal;​ KEYBOARD = 0xF0; // 设置行为高电平,列为低电平​ if (KEYBOARD != 0xF0) { // 检测是否有按键按下 keyVal = KEYBOARD; switch (keyVal) { case 0xE0: row = 0; break; case 0xD0: row = 1; break; case 0xB0: row = 2; break; case 0x70: row = 3; break; default: return 0xFF; }​ KEYBOARD = 0x0F; // 设置列为高电平,行为低电平 keyVal = KEYBOARD; switch (keyVal) { case 0x0E: col = 0; break; case 0x0D: col = 1; break; case 0x0B: col = 2; break; case 0x07: col = 3; break; default: return 0xFF; }​ // 根据行列计算键值 return 4 * row + col + 1; }​ return 0xFF; // 返回无效键值}​// 主函数void main() { unsigned char sendData;​ while (1) { sendData = readKeyboard(); // 读取键盘数据 if (sendData != 0xFF) { sendFrame(sendData); // 发送一帧数据 } }}接收机代码:#include <reg52.h>​#define IR_RECV P3​// 接收一帧数据unsigned char receiveFrame() { unsigned char i; unsigned char data = 0;​ while (IR_RECV); // 等待起始位​ DelayUs(150);​ for (i = 0; i < 8; i++) { DelayUs(300); data >>= 1; if (IR_RECV) { data |= 0x80; } }​ return data;}​// 主函数void main() { unsigned char receivedData;​ while (1) { receivedData = receiveFrame(); // 接收一帧数据 // 处理接收到的数据 }}