-
一、项目背景随着人们生活水平和健康意识的提高,越来越多的人开始注重自己的饮食健康。在此背景下,智能营养秤系统应运而生,成为了一种非常实用的工具。本项目基于51单片机设计和实现一种智能营养秤系统,通过该系统可准确地测量食物的重量并计算其热量、蛋白质、脂肪、碳水化合物等营养成分含量。当前系统采用了STC89C52单片机作为主控芯片,预置了多种食材的营养成分数据。用户只需要使用矩阵键盘输入食材编号,将需要称重的食材放置在重力传感器上进行依次称重,系统就可以自动计算出所有食材的各类营养含量总值,并通过液晶屏显示出来。同时,系统根据预设的营养指标,对不达标或超标的食材进行对应的声光提示,提醒用户注意饮食健康。当前系统还配备了无线WIFI模块,可以将当前营养数据上传到手机端实时显示,并给出营养建议。这使得用户可以随时1了解自己的饮食情况,及时进行调整,从而达到更好的健康效果。本项目的设计和实现是为了满足人们对于饮食健康的需求,帮助人们更好地控制自己的饮食,达到健康瘦身的目的。同时,由于采用了51单片机的设计方案,具有成本低、易于制作、易于维护等优点,具有广泛的应用前景。二、系统设计过程2.1 硬件组成【1】STC89C52单片机作为主控芯片。【2】4x4电容矩阵键盘用于输入食材编号。【3】HX711重力传感器用来进行多种食材的称重。【4】1.44寸LCD显示屏用来显示所有食材的各类营养含量总值。【5】ESP8266无线WIFI模块用于将当前营养数据上传到手机端实时显示。2.2 系统框架组成【1】输入:使用4x4电容矩阵键盘输入食材编号,触发称重功能。【2】称重:根据输入的食材编号,通过HX711重力传感器对多种食材依次称重。【3】计算:系统自动计算所有食材的各类营养含量总值,并在1.44寸LCD显示屏上显示。【4】判断:根据系统预设的营养指标,判断当前营养数据是否达标或超标。【5】提示:若不达标或超标,系统进行相应的声光提示。【6】数据上传:通过ESP8266无线WIFI模块将当前营养数据上传到手机端实时显示,并给出营养建议。2.3 系统模块设计【1】系统硬件设计采用了51单片机作为主控芯片,重力传感器用于称重,矩阵键盘用于输入食材编号,液晶屏用于显示数据。同时,为了实现无线上传功能,还需要添加WIFI模块。【2】系统软件设计系统的软件设计主要包括两个方面,即驱动程序和应用程序。其中,驱动程序负责与各个硬件模块进行通信,读取和处理相关数据;应用程序则负责实现具体的计算和控制逻辑。【3】食材营养成分数据预置预先测量并记录多种食材的重量和营养成分含量,并将这些数据存储在系统中供后续使用。【4】食材识别和称重当用户输入食材编号后,系统自动从预置的数据中查找对应的营养成分信息。然后,用户将需要称重的食材放置在重力传感器上,系统开始进行称重并输出重量数据。【5】营养计算和指标判断系统根据已知的食材重量和营养成分数据,计算出当前食物的各类营养含量总值。同时,根据预设的营养指标,判断当前食物是否达标或超标,并进行相应的声光提示。【6】数据传输和显示将当前的营养数据通过WIFI模块上传到手机端实时显示,并根据用户的身体数据和运动情况,推荐合适的饮食方案。【7】整体测试和优化:对系统进行整体测试和优化,确保系统能够正常工作并满足设计要求。2.4 程序设计思路【1】定义多种食材的营养成分数据,存储在程序中。【2】初始化电容矩阵键盘和HX711重力传感器。【3】等待用户输入食材编号。一旦检测到有效输入,记录食材编号并触发称重功能。【4】根据输入的食材编号,依次使用HX711重力传感器进行称重,并根据对应的营养成分数据进行计算,得出每种营养成分的总值。【5】将所有食材的营养成分总值通过1.44寸LCD显示屏展示给用户。【6】根据系统预设的营养指标,判断当前营养数据是否达标或超标。如果不达标或超标,则进行相应的声光提示。【7】通过ESP8266无线WIFI模块将当前营养数据上传到手机端实时显示,并给出营养建议。三、程序代码实现3.1 HX711称重传感器代码下面是STC89C52单片机读取HX711称重传感器的值,得到最终的重量,打印到串口的完整代码: #include <reg52.h> #include <intrins.h> // HX711引脚定义 sbit HX711_DOUT = P1^0; // 数据输出引脚 sbit HX711_SCK = P1^1; // 时钟输入引脚 typedef unsigned char uchar; typedef unsigned int uint; uchar WeiLai, OldData; // 定义两个变量,用于保存数据 uchar Data[3]; // 存放读取的数据 long result = 0; // 定义长整型变量,用于存放最终的重量值 void delay_us(uint us) // 延时函数(微秒级) { while(us--) { _nop_(); // 空操作语句,延时一微秒 _nop_(); _nop_(); _nop_(); } } void Read_HX711() // 读HX711函数 { uchar i; HX711_DOUT = 1; // 先将DOUT置为高电平 delay_us(1); // 延时1微秒 HX711_SCK = 0; // 将SCK置为低电平 delay_us(1); // 延时1微秒 for(i=0;i<24;i++) // 循环24次,读取数据 { HX711_SCK = 1; // 将SCK置为高电平 delay_us(1); // 延时1微秒 WeiLai = HX711_DOUT; // 读取DOUT引脚上的数据 result <<= 1; // 左移一位 if(WeiLai == 1) // 如果DOUT为1,将result的最低位赋值为1 { result++; } HX711_SCK = 0; // 将SCK置为低电平 delay_us(1); // 延时1微秒 } WeiLai = OldData; // 将OldData的值赋给WeiLai Data[2] = result; // 存储重量值的最高字节 Data[1] = result>>8; // 存储重量值的中间字节 Data[0] = result>>16; // 存储重量值的最低字节 } void main() { TMOD = 0x20; // 定时器T1工作模式设置 TH1 = 0xfd; // 波特率9600 TL1 = 0xfd; // 波特率9600 TR1 = 1; // 启动定时器T1 SCON = 0x50; // 设置串口工作方式 while(1) { Read_HX711(); // 调用读HX711函数 // 将读取到的数据打印到串口 SBUF = Data[0]; while(TI == 0); TI = 0; SBUF = Data[1]; while(TI == 0); TI = 0; SBUF = Data[2]; while(TI == 0); TI = 0; } }3.2 ESP82660-WIFI配置代码以下是STC89C52单片机控制ESP8266,配置成AP模式,开启TCP服务器,等待客户端连接上来的完整代码: #include <reg52.h> #include <intrins.h> #define RXD P3_0 // 串口接收引脚 #define TXD P3_1 // 串口发送引脚 typedef unsigned char uchar; typedef unsigned int uint; bit rcvflag; // 接收标志位 uchar idata RcvBuf; // 存储接收到的数据 uchar len; // 存储接收到的数据长度 uchar AT_OK; // 存储AT指令执行结果 /* 延时函数 */ void Delayms(uint ms) { uchar i, j; for(i=0;i<ms;i++) { for(j=0;j<110;j++); } } /* 发送一个字节的数据 */ void SendByte(uchar dat) { SBUF = dat; while(!TI); TI = 0; } /* 发送字符串 */ void SendString(char *str) { while(*str) { SendByte(*str++); } } /* 发送AT指令 */ void SendATCmd(char *str) { SendString(str); SendString("\r\n"); Delayms(20); // 延时20ms等待返回数据 } /* 接收一个字节的数据 */ void Uart_Rcv() interrupt 4 using 1 { if(RI) // 判断是否接收到数据 { RI = 0; // 清除RI标志位 RcvBuf = SBUF; // 存储接收到的数据 rcvflag = 1; // 置接收标志位 } } /* 检查AT指令执行结果 */ void Check_AT_Cmd() { uchar i; if(rcvflag == 0) // 如果没有接收到数据 { AT_OK = 0xff; // 执行AT指令失败 return; } len = 0; do // 接收完整的一行数据 { i = RcvBuf; RcvBuf = 0; if(i == '\r') break; // 遇到回车符则结束接收 if(i != '\n') // 跳过换行符 { len++; } }while(1); if(len != 0) // 如果有接收到数据 { AT_OK = 0; // 执行AT指令成功 } rcvflag = 0; // 清除接收标志位 } /* 配置ESP8266模块为AP模式 */ void Set_AP_Mode(char *ssid, char *pwd) { SendATCmd("AT+RST"); // 复位ESP8266模块 Check_AT_Cmd(); SendATCmd("AT+CWMODE=2"); // 配置模块为AP模式 Check_AT_Cmd(); SendATCmd("AT+CWSAP=""); SendString(ssid); // 设置SSID SendString("",""); SendString(pwd); // 设置密码 SendString("",5,3"); // 设置加密方式为WPA2_PSK Check_AT_Cmd(); } /* 开启TCP服务器 */ void TCP_Server_Open() { SendATCmd("AT+CIPMUX=1"); // 开启多连接模式 Check_AT_Cmd(); SendATCmd("AT+CIPSERVER=1,8888"); // 开启TCP服务器,端口号为8888 Check_AT_Cmd(); } /* 等待客户端连接 */ void Wait_For_Client_Connect() { uchar i; do { if(rcvflag == 0) continue; // 没有接收到数据则继续等待 len = 0; do { i = RcvBuf; RcvBuf = 0; if(i == '\r') break; if(i != '\n') { len++; } }while(1); rcvflag = 0; // 清除接收标志位 if(len != 0 && RcvBuf == '+') { SendString("AT+CIPSEND=0,12"); // 发送数据,长度为12 SendString("\r\n"); Delayms(10); if(rcvflag && RcvBuf == '>') // 等待">"符号 { SendString("Hello World!"); // 发送数据 SendByte(0x1a); // 发送结束符 } while(!rcvflag); // 等待数据发送完成 len = 0; do { i = RcvBuf; RcvBuf = 0; if(i == '\r') break; if(i != '\n') { len++; } }while(1); rcvflag = 0; // 清除接收标志位 if(len != 0 && RcvBuf == 'S') // 如果接收到"SEND OK"说明数据发送成功 { SendString("Data send success!"); SendString("\r\n"); } } }while(1); } /* 主函数 */ void main() { TMOD = 0x20; // 定时器T1工作模式设置 TH1 = 0xfd; // 波特率9600 TL1 = 0xfd; // 波特率9600 TR1 = 1; // 启动定时器T1 SCON = 0x50; // 设置串口工作方式 EA = 1; // 允许中断 ES = 1; // 允许串口中断 Set_AP_Mode("MyAP", "12345678"); // 配置模块为AP模式,设置SSID和密码 TCP_Server_Open(); // 开启TCP服务器 Wait_For_Client_Connect(); // 等待客户端连接 }
-
简介CodeArts IDE是一个集成开发环境(IDE),它提供了开发语言和调试服务。本文主要介绍CodeArts IDE for C/C++的基本功能。1.下载安装CodeArts IDE for C/C++ 已开放公测,下载获取免费体验2.新建C/C++工程CodeArts IDE for C/C++ 提供了创建C或C++工程的能力,可参考以下步骤进行创建:1. 点击顶部菜单 File -> New -> Project...2. 选择 C/C++3. 填写表单并点击创建按钮4. 等待工程创建完成并打开项目3.C/C++代码编写3.1编码基础操作CodeArts IDE for C/C++ 包含了内置的语法着色,定义预览,跳转定义,类继承关系图,调用关系图等一些编码基础功能。语法着色 - 该功能可对函数,类型,局部变量,全部变量,宏,枚举,成员变量等上色。跳转定义 - Ctrl+点击或者F12跳转到定义,或者使用Ctrl+Alt+点击会打开定义到旁边。定义预览 - 当光标移至符号处,则会有符号定义的悬停预览,也可以用alt+F12的快捷键进行文件内的符号预览。查找所有引用 - 当光标点击或者选择到需要查找的符号,右键菜单->查找所有引用或者使用快捷键Shift+Alt+F12会打开定义在页面左侧。调用关系图 - 当光标点击或选中需要调用关系图的函数时,右键菜单->调用关系图,或可以使用快捷键Shift+Alt+H调出。在关系图中,也可以点击需要查看的函数并导航到该函数,同时也能够查看子类和基类。符号大纲 - 左侧工具->右上角三个点->大纲即可打开符号大纲,或者使用快捷键Ctrl+Shift+B打开工具栏。打开大纲后,双击函数即可到达函数定义的位置,并且当前符号大纲可跟随光标移动(此功能需要在大纲菜单栏中打开跟随光标选项)。3.2 代码编写操作CodeArts IDE for C/C++ 包含了内置的符号重命名,提取重构,代码补全/提示,实时语法检查等一些高级代码编写功能。符号重命名(Rename symbol)最基础的重构之一,但是变量或方法名字的可读性非常重要。在光标选中某个变量或方法后,右键单击以调出编辑器上下文菜单并且选择重命名符号或直接按F2,来重命名整个 C/C++ 项目中所有用到该命名的地方。提取重构(Extraction refactoring)CodeArts IDE for C/C++ 支持将字段,方法和参数提取到新类中,根据提取的内容会提供不同的重构类型。可用的 C/C++ 重构类型包括:提取函数/方法(Extract method)- 将选定的语句或表达式提取到文件中的新方法或新函数。在选择提取方法(Extract method)重构后,输入提取的的方法/函数的名称。提取表达式到变量(Extract subexpression to variable)- 将选定的表达式提取为文件中的新变量。代码补全/提示(Code Completion/Hinting)CodeArts IDE for C/C++ 代码补全包含了各种代码编辑功能,包括:代码完成,快速信息,成员列表以及参数信息。当您输入字符时,代码补全若知道可能的补全选项,则会自动弹出成员列表。如果您继续输入字符,成员列表(变量,方法等)将被过滤为仅包含您输入字符的成员。您可通过光标点击或者按Enter或Tab键插入选定的成员名称。该功能会提供各种提示信息帮助您更加方便快速的编辑代码。全局符号搜索(Global Symbol Search)Ctrl+T导出搜索框,输入需要查找的符号,页面会显示出当前文件夹所有包含此符号的文件,点击即可跳转。或者按向上或向下选择并按Enter导航到您想要的位置。实时检查编译错误(该功能依赖compile_commands.json文件)实时检查编译错误是解决编码错误的建议编辑,包括自动补全,实时语法检查等。当编译错误时,会在错误处出现波浪线。可将光标移动或点击到C/C++的代码错误上时,会显示黄色灯泡,表示可以使用快速修复。点击灯泡或按Ctrl+。会显示可用的快速修复和重构列表。Compile_commands.json 管理功能Compiler 模式功能全面,但需要compile_commands.json文件编译数据库才能正常工作,可使用三种方式获取该文件。使用内置 CMake Build Tool 插件(推荐)。构建 CMake 项目,会自动生成cmake-build-debug/compile_commands.json文件, 并且插件会自动将该文件导入到 .arts文件夹。使用 CMake 生成。 如果当前工程是 CMake 工程,可以通过添加参数-DCMAKE_EXPORT_COMPILE_COMMANDS=1生成 compile_commands.json,并通过帮助->显示所有命令->Huawei C/C++:导入编译数据库文件命令导入。使用 Huawei C/C++ 提供的Generate命令。可通过帮助->显示所有命令->Huawei C/C++:生成编译数据库文件,并选择存放源文件的文件夹,该方法分析头文件生成对应的编译数据库。同时 Huawei C/C++也支持以下功能:通过命令或 API 导入compile_commands.json文件(帮助->显示所有命令->Huawei C/C++:导入编译数据库文件)合并多个 compile_commands.json 文件.移除 compile_command.json 文件中重复的命令.导入时为 clangd 提供额外的参数设置.索引更新命令同步工程索引(帮助->显示所有命令->Huawei C/C++:同步工程索引)同步文件夹索引(资源管理器右键菜单->Huawei C/C++:同步文件夹索引)同步文件索引(资源管理器右键菜单->Huawei C/C++:同步当前文件索引)重置工程索引(帮助->显示所有命令->Huawei C/C++:重建全项目索引)编辑源文件的编译选项并刷新索引(右键菜单->编辑编译参数)以上命令和功能在 Compiler 模式或 Hybrid 模式均有效。3.3 代码重构操作重构是通过改变现有程序结构而不改变其功能和用途来提高代码的可重用性和可维护性。CodeArts IDE 支持重构操作,提供了多种重要的重构类型,来改变编辑器中的代码库。CodeArts IDE for C/C++ 内置了对 C/C++ 重构的支持,在本专题中,我们将展示 C/C++ 语言服务的重构支持。定义构造函数(Define constructor)在每次创建类时,可以自动定义类的构造函数,并且初始化成员。当点击或选中类名时,可以点击左侧黄色灯泡选择定义构造函数。根据声明顺序排序函数(Sort functions to declarations)根据头文件中的声明顺序,排序当前定义函数/方法的顺序。当点击或选中当前函数/方法定义时,重构选项可用。将定义添加到实现文件(Add definition to implementation file)将头文件的定义添加到实现文件中。当点击或选中当前函数/方法时,重构选项可用。交换 if 分支(Swap if branches)若当前条件只有if和else分支,选中代码片段后,选择交换 if 分支(Swap if branches),可自动交换if和else分支。内联变量(Inline variable)该功能可以用相应的值替换所有引用。假设计算值总是产生相同的结果。选中需要替换的内容,重构选项可用。内联函数(Inline function)该功能尝试使用适当的代码内联所有函数用法。它只能处理简单的功能,不支持内联方法、函数模板、主函数和在系统头文件中声明的函数。该功能可以内联所有函数引用。生成 getter 和 setter(Generate getter and setter)通过为其生成getter和setter(Generate getter and setter)来封装选定的类属性。同时也可以选择只生成getter(Generate getter)或者生成setter(Generate setter)选项。声明隐式成员(Declare implicit members)此选项会将类的隐式成员在类中声明,当选中类名时,重构选项可用。填充 switch 语句(Populate switch)该功能可以自动填充switch语句。选中任意switch字段,并且点击黄色灯泡,选择填充switch语句。移除 namespace(Remove using namespace)移除namespace功能,会自动移除所有使用到的namespace。当光标点击或选中namesapace关键字时,重构选项可用。移动函数体到声明处(Move function body to out-of-line)将函数/方法定义移动到它声明的位置。在内部添加定义(Add definition in-place)在当前函数/方法并且在类内部生成函数定义。当光标移动到函数/方法时,点击黄色灯泡,重构选项可用。在外部添加定义(Add definition out-of-place)在类外部生成当前函数/方法的函数定义。当光标移动到函数/方法时,点击黄色灯泡,重构选项可用。展开宏(Expand macro)在页面上添加展开宏(Expand macro),以便在可扩展/可折叠的部分提供内容。展开 auto(Expand auto type)展开 auto type所隐藏的变量类型。函数定义外移(Move function body to declaration)该功能会将函数/方法的定义移动到声明的位置。函数定义内移(Move function body to out-of-line)该功能会将函数/方法的定义移动到对应的文件中。转为原始字符串(Convert to raw string)此方法可以将转义后的字符串转换为原始的字符串。当点击或选择了当前字符串,点击黄色灯泡,重构选项可用。快速修复(Quick fixes)快速修复是解决简单编码错误的建议编辑,包括自动补全,实时语法检查等。当光标移动或点击到C/C++的代码错误上时,会显示黄色灯泡,表示可以使用快速修复。点击灯泡或按Ctrl+.会显示可用的快速修复和重构列表。
HuaweiCloud开发工具
发表于2023-05-12 15:42:44
2023-05-12 15:42:44
最后回复
yd_257392961
2025-05-08 16:36:34
4438 14 -
一、STM32在物联网产品里的开发作用STM32在物联网产品中具有重要的开发作用,主要包括以下几个方面:嵌入式控制:STM32作为一款强大的嵌入式微控制器,可用于控制各种不同类型的物联网设备。它可以轻松读取传感器数据、实现数据采集、进行控制操作等,从而实现物联网设备的智能化控制。 低功耗设计:STM32在能耗方面表现出色,采用它进行的物联网设备开发可以极大地降低设备的功耗,从而延长电池寿命,提高设备的运行稳定性。 连接网络:为了实现硬件设备的联网功能,STM32在物联网产品中扮演了重要的角色。它可以通过网络接口以太网、Wi-Fi、蓝牙等方式实现无线连接,从而实现物联网设备与互联网的连接。 丰富的软件支持:STM32的开发板提供了丰富的软件支持,包括各种开发工具、SDK以及驱动程序等,这些软件工具能够帮助开发人员快速进行开发,大大简化了物联网产品的开发流程。 二、开发案例STM32配合W5500网卡连接华为云物联网平台通信 https://bbs.huaweicloud.com/forum/thread-0214117966487710039-1-1.html 基于STM32设计的智能灌溉控制系统 https://bbs.huaweicloud.com/forum/thread-0258117964156424037-1-1.html C语言代码封装MQTT协议报文,了解MQTT协议通信过程 https://bbs.huaweicloud.com/forum/thread-0262117963321259034-1-1.html STM32单片机上RGB数据转为JPEG格式办法 https://bbs.huaweicloud.com/forum/thread-0221117960587791024-1-1.html STM32+DHT11监测环境的温湿度 https://bbs.huaweicloud.com/forum/thread-0214117959718389037-1-1.html STM32通过ADC1读取光敏电阻的值转换光照强度 https://bbs.huaweicloud.com/forum/thread-0287117959317499038-1-1.html STM32读取MQ2烟雾浓度数据判断烟雾是否超标 https://bbs.huaweicloud.com/forum/thread-02104117958892070025-1-1.html STM32读取BH1750光照强度数据打印到串口 https://bbs.huaweicloud.com/forum/thread-0214117958650297035-1-1.html ESP8266调用NTP服务器进行时间校准 https://bbs.huaweicloud.com/forum/thread-02104117957509416024-1-1.html ESP8266获取天气预报信息,并使用CJSON解析天气预报数据 https://bbs.huaweicloud.com/forum/thread-0201117956617429025-1-1.html 基于STM32+华为云IOT设计的智能温室大棚监控系统 https://bbs.huaweicloud.com/forum/thread-0262117947916012031-1-1.html 基于STM32+NBIOT+华为云IOT设计的智能井盖 https://bbs.huaweicloud.com/forum/thread-0201117947554019022-1-1.html 基于STM32+华为云设计的智慧烟感系统 https://bbs.huaweicloud.com/forum/thread-0201117944388992019-1-1.html 基于STM32+RC522设计的门禁系统 https://bbs.huaweicloud.com/forum/thread-0262117944289191030-1-1.html
-
【1】BH1750是什么?BH1750是一种数字式环境光强度传感器(Digital Light Sensor),也称为其他名称,例如GY-302传感器、BH1750FVI传感器等。它的工作原理是通过收集光线照射到传感器上的量来测量环境亮度。使用I2C(Inter-Integrated Circuit)接口,BH1750可以轻松地接入到各种嵌入式系统中,并提供实时的环境光强度数据。其度量范围是1-65535 lux,测量精度可以达到每个范围16位,使其成为许多应用中的理想选择。例如,自动照明控制、日光灯节能控制、智能家居、汽车照明系统和摄影中的曝光控制等等。BH1750还有一些其他优点。例如,其本身具有非常低的功率消耗(例如小于1μA),这意味着它可以轻松地集成在嵌入式系统中,并且非常适用于电池供电的系统。并且它是一种数字式光强度传感器,相比于模拟式光强度传感器,它的抗干扰性能更好,并且可以一次完成多种测量,例如高分辨率的光强测量、低光强测量等。【2】什么是IIC协议?IIC(Inter-Integrated Circuit)协议也称为I2C协议,是一种串行通信协议,由Philips公司(现在的NXP公司)于1980年代初期开发。它是一种双向、两线式的串行通信协议,通常被用于板间通信以及嵌入式系统中的设备之间的通信。IIC协议由两根线构成:数据线(SDA)和时钟线(SCL)。SDA线负责传输数据,而SCL线则负责传输时钟信号。在IIC总线上,多个设备可以连接到同一根时钟线和数据线上,通过设置每个设备的唯一地址来进行通信。IIC协议支持多主机和多从机的通信,也支持多种通信速率(通常为100kHz或400kHz)。IIC协议是一种简单易用的通信协议,因此被广泛应用于各种嵌入式系统和电子设备中,例如SMBus、PMbus、I2C EEPROM、I2C LCD、I2C ADC、I2C DAC、I2C RTC等等。【2】STM32读取BH1750数据下面是使用标准库函数STM32F103C8T6读取BH1750光照强度数据并打印到串口的代码: #include "main.h" #include "stm32f1xx_hal.h" #include "stdio.h" #define BH1750_ADDRESS 0x23 //BH1750地址 I2C_HandleTypeDef hi2c1; //IIC外设句柄 void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART1_UART_Init(void); static void MX_I2C1_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_I2C1_Init(); uint8_t buf[2]; uint16_t value = 0; //初始化BH1750 buf[0] = 0x01; //使用高分辨率模式 HAL_I2C_Master_Transmit(&hi2c1, BH1750_ADDRESS << 1, buf, 1, 100); HAL_Delay(100); while (1) { //读取光照强度数据 buf[0] = 0x00; //高8位 HAL_I2C_Master_Transmit(&hi2c1, BH1750_ADDRESS << 1, buf, 1, 100); HAL_I2C_Master_Receive(&hi2c1, BH1750_ADDRESS << 1, buf, 2, 100); value = (buf[0] << 8) | buf[1]; value = value/1.2; //单位转换,参考BH1750手册 printf("Light intensity: %d lux\n", value); HAL_Delay(1000); } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } RCC_ClkInitStruct.ClockType = RCC static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); /*Configure GPIO pin : PA1 */ GPIO_InitStruct.Pin = GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } static void MX_I2C1_Init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } } void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } } void Error_Handler(void) { __disable_irq(); while (1) { } }在代码中,使用HAL库函数初始化了I2C接口和USART串口,同时使用了STM32提供的延时库函数HAL_Delay()。在主函数中,首先定义了一个缓冲区buf[2]和一个变量value,缓冲区buf[2]用于存储从BH1750读取的光照强度数据。变量value用于存储经过单位转换后的光照强度值。接着,使用HAL_I2C_Master_Transmit()函数向BH1750传输一个命令,以初始化BH1750。在这里,将BH1750设置为使用高分辨率模式,以获得更高的测量精度。紧接着,使用HAL_Delay()函数延时100毫秒,以确保BH1750设备初始化成功。然后,在while循环中,使用HAL_I2C_Master_Transmit()和HAL_I2C_Master_Receive()函数从BH1750读取光照强度数据。读取的光照强度值存储在缓冲区buf[2]中,并进行了单位转换,最后通过printf()函数打印到串口。在此示例中,使用了printf()函数将光照强度值打印到串口,因此需要在调试器中打开串口窗口才能看到打印的数据。为了使代码正常工作,应在stm32f1xx_hal_conf.h头文件中将USE_HAL_DRIVER宏定义设置为1。
-
一、项目背景门禁系统是现代社会中非常重要的安全控制系统之一,其功能是在保障建筑物安全的同时,为合法用户提供便利。当前设计一种基于STM32+RC522的门禁系统设计方案,通过RFID-RC522模块实现了对用户卡的注册、识别及身份验证,通过控制SG90舵机实现门锁的开关,具有较高的安全性和可靠性。实验结果表明,该门禁系统可以有效地保障建筑物的安全性。门禁系统广泛应用于各种建筑物、企事业单位,用于管理人员的进出、控制人员活动范围、实现安全监控等功能。传统的门禁系统采用密码输入或刷卡的方式进行身份验证,但存在易被破解的风险。基于RFID的门禁系统已经成为一种相对先进的安全控制方案。本次设计的STM32+RC522门禁系统,通过RFID-RC522模块对用户的卡进行注册、识别完成身份识别,对门锁进行开关。系统带了OLED显示屏,输入用户密码登录之后,可以对新卡片进行注册,添加新卡片,对不使用的卡片进行注销。在系统里,IC卡的数据都存储在卡的内部扇区里,通过卡的内部空间进行管理。采用5V-步进电机的版本:二、系统设计门禁系统由STM32F103C8T6单片机、RFID-RC522模块、SG90舵机、LCD1602液晶显示屏、键盘模块等组成。其中,STM32F103C8T6单片机作为系统的核心控制器,控制程序的执行;RFID-RC522模块作为识别用户卡片的设备;SG90舵机作为门锁控制设备;OLED显示屏提供用户输入信息和系统信息的显示;键盘模块方便用户进行密码和卡片信息的输入。2.1 软件设计【1】RFID卡信息管理本系统采用卡的内部空间进行IC卡信息的管理。每个IC卡可以分为多个扇区,每个扇区包含多个块,每个块包含16个字节。扇区0是厂家已经预留好的,用于存储卡片的序列号,扇区1-15可以由用户自己配置,用于存储一些私有数据,如用户身份、车牌号、员工编号等。在本系统中,IC卡信息的管理主要包括三个方面:新卡片注册、卡片识别和注销卡片。对于新卡片的注册,用户需要按下键盘上的“#”键进入注册模式,接着输入管理员密码,然后将新卡放到RFID读写器上,系统将读取卡片序列号,并在卡片的扇区中存储用户名和密码信息等。对于卡片的识别,当用户按下门禁系统的确认键时,系统将读取RFID模块中读取的卡片序列号,并去卡片扇区中查询用户名和密码信息,进行身份验证。如果卡片识别成功,系统将控制舵机旋转一圈实现开锁功能。对于注销卡片,管理员需要输入密码进行身份验证后,再将要注销的卡片放到RFID读写器上,系统将清空该卡片的扇区内所有数据。【2】门禁系统安全控制本门禁系统采用密码验证和卡片识别相结合的方式,提高了系统的安全性。具体来说,系统要求用户输入密码或刷卡进行身份验证,只有在验证成功后才能控制门锁进行开关操作。同时,系统还可以记录每一次开启门锁的时间和用户信息,以便管理员进行安全监控。【3】门锁控制本门禁系统采用SG90舵机控制门锁的开关,具有结构简单,控制方便的优点。在门锁控制过程中,系统对舵机控制信号的频率和占空比进行精细控制,以实现门锁的准确开关。2.2 硬件设计【1】STM32F103C8T6单片机STM32F103C8T6单片机是ST公司推出的一款基于Cortex-M3内核的可编程32位单片机,常常被广泛应用于工业控制、智能家居、嵌入式控制等领域。它的主要特点包括: 1. Cortex-M3内核:STM32F103C8T6使用Cortex-M3内核,具有高性能、低功耗、硬实时等特点,可支持多个串口、I2C、SPI、USB等外设,为使用者带来更大的灵活性。 2. 32位处理能力:STM32F103C8T6是一款32位单片机,具有比8位、16位单片机更高的数据运算能力、编程灵活度和计算精度。 3. 较强的系统时间管理能力:STM32F103C8T6内部具备RTC实时时钟模块,可实现精准的时间管理和时间标记功能,在一些需要时间同步的应用场景下具有较大的优势。 4. 大存储容量:STM32F103C8T6内置64K字节的闪存和20K字节的SRAM,能够满足大型嵌入式应用的存储需求。 5. 丰富的外设接口:STM32F103C8T6支持多个外设接口,如SPI、I2C、CAN总线等,方便开发者扩展相关应用场景。 6. 代码可移植性强:由于该芯片应用广泛,可以使用多种开发工具进行开发,例如Keil、STM32CubeMX等,而且支持多种编程语言,如C语言、C++等,因此优点很容易在不同的平台、不同开发者之间实现代码的移植。【2】RFID-RC522模块RFID-RC522模块是一种低成本、高性价比的RFID读写模块。它具有高精度、快速读取等特点,广泛应用于门禁系统、智能卡管理、物流追踪等领域。RFID-RC522模块的特点如下: 1. 高精度:RFID-RC522模块采用射频感应技术进行信号传输和读写,具有高精度、稳定性强等优点。 2. 快速读取:RFID-RC522模块读取速度快,一般只需0.1秒左右就可以完成读取操作。 3. 支持多种协议:RFID-RC522模块支持ISO14443A/B、FeliCa等多种RFID协议,可满足不同应用场合的需求。 4. 低功耗:RFID-RC522模块功耗低,工作电流为13-26mA,待机电流为10A。 5. 接口简单:RFID-RC522模块采用SPI接口进行通信,模块上的引脚有7个,具有很好的兼容性。 6. 支持多种开发语言:RFID-RC522模块支持多种开发语言,如C++、Python等,方便开发者进行二次开发。RFID-RC522模块的使用需要配合相关的库文件,在Arduino、Raspberry Pi等开发板上进行代码编写和开发。常见的使用场景包括门禁系统、智能卡管理、出入库管理、物流追踪等领域。【3】SG90舵机该舵机小巧耐用,可以精确地控制门锁的开关。SG90舵机是一种小型舵机,体积小、重量轻、价格低廉,常常被用于模型飞机、小型机械臂、玩具模型等领域。它采用了直流电机,利用PID控制技术,以及精密的小齿轮减速箱实现转向角的控制。SG90舵机的特点如下: 1. 小体积:SG90舵机体积为23mm * 12.2mm * 29mm,重量仅为9g,非常适合小型电子设备。 2. 高精度:SG90舵机的控制精度比较高,可控制角度范围为0 ~ 180度,分辨率为1度,可以实现精确到角度的控制。 3. 低噪音:SG90舵机采用了精密减速齿轮箱,转动非常平稳,并且噪音非常低。 4. 低功耗:SG90舵机的电机非常省电,一般使用3V到6V的电源,仅需20 mA的电流,可大大节省电力消耗。 5. 价格适中:SG90舵机价格相对较低,非常适合初学者或需求量较大的用户使用。SG90舵机在使用时需要通过PWM信号进行控制。【4】0.96寸OLED显示屏0.96寸SPI接口OLED显示屏是一种小型化的屏幕,属于OLED显示技术,采用SPI接口连接,外观尺寸约为12mm * 12mm,分辨率一般为128 * 64或者128 * 32。它可以用于各种小型电子设备,例如手持设备、小型仪器、智能家居控制面板等等。OLED即有机发光二极管,与传统的液晶显示屏相比,OLED具有响应速度快、视角范围广、色彩鲜艳、亮度高等优势。SPI接口则是一种串行外设接口,具有简单、灵活、高速等特点。0.96寸SPI接口OLED显示屏的驱动芯片一般为SSD1306,有128个列和64个行的像素,还有一些有128个列和32个行的像素。其中,128 * 64像素的屏幕显示面积较大,在显示图像和文字时更加清晰和细腻。0.96寸SPI接口OLED显示屏具有小巧、高清、高速等优点,被广泛使用在各种小型电子设备中。【5】键盘模块该模块可以方便地输入密码和卡片信息。IIC接口的4x4电容矩阵键盘模块是一种基于IIC总线通信的电容式按键模块,常常被应用在工控、家电、医疗器械等领域。它的主要特点包括: 1. 采用IIC总线通信:IIC接口的4x4电容矩阵键盘模块通过IIC总线通信连接到MCU,简化了连接方式,方便使用。 2. 采用电容式按键设计:每个按键上放置一个电容器,当手指触摸到按键时,电容器的电容值发生变化,通过检测电容的变化实现按键检测。 3. 4x4矩阵排列式设计:4x4电容矩阵键盘模块采用矩阵排列式设计,一共有16个按键,可以满足较为复杂的应用场景。 4. 接口简单:IIC接口的4x4电容矩阵键盘模块只需要SCL和SDA两条线连接到MCU即可。 5. 高灵敏度:电容式按键设计使得按键检测更加灵敏,而且不会产生按键轻微弹起的误触情况,使用更加舒适。 6. 代码简洁:使用该模块并不需要编写复杂的按键扫描程序,只需要通过读取IIC总线上的按键值即可。IIC接口的4x4电容矩阵键盘模块是一种方便易用、高灵敏度的按键模块,通过电容式按键设计实现按键的检测和响应,并且通过IIC总线通信简化了连接方式。它适合于应用于许多领域,如工控、家电和医疗器械等,能够为使用者的产品带来更为方便和高效的控制方式。三、核心代码3.1 SG90舵机控制代码下面是基于GPIO模拟时序控制STM32F103C8T6驱动SG90舵机旋转指定的角度的代码,并封装成子函数调用。 #include "stm32f10x.h" #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h" #include "delay.h" #define Servo_pin GPIO_Pin_5 #define Servo_port GPIOA void SG90_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = Servo_pin; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(Servo_port, &GPIO_InitStructure); } void SG90_SetAngle(uint8_t angle) { if(angle>180) angle=180; if(angle<0) angle = 0; uint8_t temp = angle/2 + 15; for(int i=0;i<5;i++) { GPIO_SetBits(Servo_port, Servo_pin); delay_us(temp); GPIO_ResetBits(Servo_port, Servo_pin); delay_us(20000-temp); } } int main(void) { SystemInit(); delay_init(); SG90_Init(); while(1) { for(int i=0;i<=180;i+=10) { SG90_SetAngle(i); delay_ms(500); } } }其中,SG90_Init()函数用于初始化PA5口,并将其配置为输出模式。SG90_SetAngle()函数用于驱动舵机旋转到指定角度。在该函数中,首先根据所给的角度值计算出延时的时间temp(单位为微秒),然后使用GPIO口控制SG90舵机在temp延时时间内输出高电平,其余时间输出低电平。通过调整延时时间和按角度分配脉冲宽度,达到驱动SG90舵机旋转的目的。main()函数中的for循环控制舵机从0度到180度的循环旋转。代码中用到了delay_init()函数和delay_ms()、delay_us()函数。它们是自行编写的延时函数,可以实现毫秒和微秒级别的延时,具体代码如下: #include "stm32f10x.h" void delay_init(void) { if (SysTick_Config(SystemCoreClock / 1000000)){ while(1); } } static __IO uint32_t delay_us_tick; void delay_us(uint32_t nUs) { delay_us_tick = nUs; while (delay_us_tick); } static __IO uint32_t delay_ms_tick; void delay_ms(uint32_t nMs) { delay_ms_tick = nMs; while (delay_ms_tick); } void SysTick_Handler(void) { if (delay_us_tick > 0){ delay_us_tick--; } if (delay_ms_tick > 0){ delay_ms_tick--; } }其中,delay_init()函数用于配置系统时钟源和SysTick定时器,实现每个SysTick时钟产生一个中断的功能。delay_us()函数和delay_ms()函数分别用于实现微秒级别和毫秒级别的延时,通过限制delay_us_tick和delay_ms_tick的值实现延时的效果。SysTick_Handler()为中断处理函数,每次SysTick定时器计数减1,当减到0时,相应的delay_us_tick或delay_ms_tick也减1,通过循环等待该值为0实现延时。在代码中的SG90_SetAngle()函数中,需要精确控制GPIO的电平时间,使其产生相应的脉冲宽度,从而控制舵机转动角度。因此,需要配置GPIO口的输出模式和速度、设定delay_us()函数中根据角度计算的电平时间,使得舵机能够准确地执行旋转。3.2 RC522读写代码下面是基于SPI接口控制STM32F103C8T6驱动RFID-RC522模块完成卡片识别和扇区读写的代码示例。在该代码中,使用的是SPI1的接口,RFID-RC522模块通过SPI1接口连接到STM32F103C8T6。代码中通过封装SPI相关操作和MFRC522库函数,实现了读取卡片信息和完成扇区读写的功能。 #include "stm32f10x.h" #include "stm32f10x_spi.h" #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h" #include "delay.h" #include "mfrc522.h" #include "stdio.h" #define SPI_CE_LOW() GPIO_ResetBits(GPIOA,GPIO_Pin_4) #define SPI_CE_HIGH() GPIO_SetBits(GPIOA,GPIO_Pin_4) void SPI1_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | 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_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); SPI_InitTypeDef SPI_InitStructure; 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_2; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); } uint8_t SPI1_SendByte(uint8_t byte) { while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI_I2S_SendData(SPI1, byte); while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); return SPI_I2S_ReceiveData(SPI1); } void MFRC522_Reset(void) { SPI_CE_LOW(); SPI1_SendByte(0x1B); SPI_CE_HIGH(); } uint8_t MFRC522_ReadRegister(uint8_t addr) { SPI_CE_LOW(); uint8_t data; SPI1_SendByte(0x80 | addr); data = SPI1_SendByte(0x00); SPI_CE_HIGH(); return data; } void MFRC522_WriteRegister(uint8_t addr, uint8_t val) { SPI_CE_LOW(); SPI1_SendByte(0x7F & addr); SPI1_SendByte(val); SPI_CE_HIGH(); } void MFRC522_ReadRegisters(uint8_t addr, uint8_t count, uint8_t *values) { SPI_CE_LOW(); SPI1_SendByte(0x80 | addr); for(uint8_t i=0;i<count;i++) { values[i] = SPI1_SendByte(0x00); } SPI_CE_HIGH(); } void MFRC522_WriteRegisters(uint8_t addr, uint8_t count, uint8_t *values) { SPI_CE_LOW(); SPI1_SendByte(0x7F & addr); for(uint8_t i=0;i<count;i++) { SPI1_SendByte(values[i]); } SPI_CE_HIGH(); } void MFRC522_SetBitMask(uint8_t reg, uint8_t mask) { uint8_t tmp = MFRC522_ReadRegister(reg); MFRC522_WriteRegister(reg, tmp | mask); } void MFRC522_ClearBitMask(uint8_t reg, uint8_t mask) { uint8_t tmp = MFRC522_ReadRegister(reg); MFRC522_WriteRegister(reg, tmp & (~mask)); } void MFRC522_AntennaOn(void) { uint8_t temp; temp = MFRC522_ReadRegister(MFRC522_REG_TX_CONTROL); if(!(temp & 0x03)) { MFRC522_SetBitMask(MFRC522_REG_TX_CONTROL, 0x03); } } void MFRC522_Init(void) { MFRC522_Reset(); MFRC522_WriteRegister(MFRC522_REG_T_MODE, 0x8D); MFRC522_WriteRegister(MFRC522_REG_T_PRESCALER, 0x3E); MFRC522_WriteRegister(MFRC522_REG_T_RELOAD_L, 30); MFRC522_WriteRegister(MFRC522_REG_T_RELOAD_H, 0); MFRC522_WriteRegister(MFRC522_REG_TX_CONTROL, 0x00); MFRC522_WriteRegister(MFRC522_REG_RX_CONTROL, 0x00); MFRC522_WriteRegister(MFRC522_REG_MODE, 0x0D); MFRC522_AntennaOn(); } void MFRC522_ResetSec(void) { uint8_t i; uint8_t buff[12]; MFRC522_ClearBitMask(MFRC522_REG_STATUS2, 0x08); buff[0] = 0x40; buff[1] = 0x01; buff[2] = 0x02; buff[3] = 0x03; buff[4] = 0x04; buff[5] = 0x05; buff[6] = 0x06; buff[7] = 0x07; buff[8] = 0x08; buff[9] = 0x09; buff[10] = 0x0A; buff[11] = 0x0B; for(i=0;i<12;i++) { MFRC522_WriteRegister((MFRC522_REG_SEC_ADD+i), buff[i]); } MFRC522_WriteRegister(MFRC522_REG_STATUS2, 0x08); } void MFRC522_ClearSec(void) { uint8_t i; MFRC522_ClearBitMask(MFRC522_REG_STATUS2, 0x08); for(i=0;i<12;i++) { MFRC522_WriteRegister((MFRC522_REG_SEC_ADD+i), 0x00); } MFRC522_WriteRegister(MFRC522_REG_STATUS2, 0x08); } void MFRC522_Auth(uint8_t authMode, uint8_t BlockAddr, uint8_t *Sectorkey, uint8_t *Uid) { uint8_t buff[12]; uint8_t i; buff[0] = authMode; buff[1] = BlockAddr; for(i=0;i<6;i++) { buff[2+i] = Sectorkey[i]; } for(i=0;i<4;i++) { buff[8+i] = Uid[i]; } MFRC522_ClearBitMask(MFRC522_REG_STATUS2, 0x08); for(i=0;i<12;i++) { MFRC522_WriteRegister((MFRC522_REG_BAKFIFO+i), buff[i]); } MFRC522_WriteRegister(MFRC522_REG_COMMAND, MFRC522_CMD_AUTHENT); i = 0xFF; do { i--; } while((i!=0) && (!(MFRC522_ReadRegister(MFRC522_REG_STATUS2) & 0x08))); if(i == 0) { printf("Auth timeout\n"); } if(!(MFRC522_ReadRegister(MFRC522_REG_ERROR) & 0x01)) { printf("Auth error\n"); } if(MFRC522_ReadRegister(MFRC522_REG_STATUS2) & 0x08) { printf("Auth success\n"); } } uint8_t MFRC522_Read(uint8_t blockAddr, uint8_t *recvData) { uint8_t result; uint8_t i; uint8_t buff[18]; buff[0] = MFRC522_CMD_READ; buff[1] = blockAddr; MFRC522_CalculateCRC(buff, 2, &buff[2]); MFRC522_ClearBitMask(MFRC522_REG_STATUS2, 0x08); MFRC522_WriteRegister(MFRC522_REG_COMMAND, MFRC522_CMD_TRANSCEIVE); i = 0xFF; do { i--; } while((i!=0) && (!(MFRC522_ReadRegister(MFRC522_REG_STATUS2) & 0x08))); if(i == 0) { printf("Read timeout\n"); } if(!(MFRC522_ReadRegister(MFRC522_REG_ERROR) & 0x01)) { result = MFRC522_STATUS_OK; for(i=0;i<16;i++) { recvData[i] = MFRC522_ReadRegister(MFRC522_REG_FIFO_DATA); } } else { result = MFRC522_STATUS_ERROR; } return result;}uint8_t MFRC522_Write(uint8_t blockAddr, uint8_t *writeData) { uint8_t result; uint8_t i; uint8_t buff[18]; buff[0] = MFRC522_CMD_WRITE; buff[1] = blockAddr; MFRC522_CalculateCRC(buff, 2, &buff[2]); MFRC522_ClearBitMask(MFRC522_REG_STATUS2, 0x08); MFRC522_WriteRegister(MFRC522_REG_COMMAND, MFRC522_CMD_TRANSCEIVE); i = 0xFF; do { i--; } while((i!=0) && (!(MFRC522_ReadRegister(MFRC522_REG_STATUS2) & 0x08))); if(i == 0) { printf("Write timeout\n"); } if(!(MFRC522_ReadRegister(MFRC522_REG_ERROR) & 0x01)) { result = MFRC522_STATUS_OK; buff[0] = 0; buff[1] = 0; for(i=0;i<16;i++) { buff[i+2] = writeData[i]; } MFRC522_CalculateCRC(buff, 18, &buff[18]); MFRC522_WriteRegister(MFRC522_REG_COMMAND, MFRC522_CMD_TRANSCEIVE); i = 0xFF; do { i--; } while((i!=0) && (!(MFRC522_ReadRegister(MFRC522_REG_STATUS2) & 0x08))); if(i == 0) { printf("Write timeout2\n"); } if((MFRC522_ReadRegister(MFRC522_REG_ERROR) & 0x1B) == 0x0A) { result = MFRC522_STATUS_OK; } else { result = MFRC522_STATUS_ERROR; } } else { result = MFRC522_STATUS_ERROR; } return result; } uint8_t main(void) { uint8_t status; uint8_t buffer[18]; uint8_t str[MAX_LEN]; MFRC522_Init(); while (1) { status = MFRC522_Request(PICC_REQIDL, buffer); if (status == MFRC522_STATUS_OK) { status = MFRC522_Anticoll(buffer); memcpy(str, buffer, 5); if (status == MFRC522_STATUS_OK) { status = MFRC522_SelectTag(buffer); if (status == MFRC522_STATUS_OK) { printf("Card uuid: %X-%X-%X-%X\n", str[0], str[1], str[2], str[3]); uint8_t key[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; MFRC522_Auth(0x60, 0x12, key, str); //Authenticate with Block 18 status = MFRC522_Read(0x12, buffer); if (status == MFRC522_STATUS_OK) { printf("Block 12:"); for (int i = 0; i < 16; i++) { printf(" %02x", buffer[i]); } printf("\n"); } else { printf("Read failed\n"); } } } } MFRC522_Halt(); }解释代码的思路:该代码实现了读取 RFID 标签(卡片)的数据功能,主要利用 MFRC522 RFID 模块与单片机的通信,对标签发送指令并读取标签数据。整个过程主要分为以下几步: 1. 初始化 MFRC522 模块。 2. 检测是否存在标签,并获取标签类型和 ID 信息。 3. 在多张同种类型的标签中,使用防冲撞机制选取一张进行操作。 4. 鉴权,使用指定的密钥验证对应的扇区,并获取读写操作的权限。 5. 读取数据,将指定的块的内容读取到缓冲区。 6. 解析标签数据并进行相应操作,例如打印块数据。 7. 暂停 RFID 模块,等待下一次操作。代码中的注释也详细说明了每个函数和变量的作用和用法。3.3 0.96寸OLED-SPI接口代码以下是使用STM32F103C8T6驱动SPI接口的0.96寸OLED显示字符串的示例代码,该代码使用了HAL库: cCopy Code#include "main.h" #include "spi.h" #include "gpio.h" #define OLED_CS_GPIO_Port GPIOB #define OLED_CS_Pin GPIO_PIN_12 #define OLED_DC_GPIO_Port GPIOB #define OLED_DC_Pin GPIO_PIN_13 void oled_init(void); void oled_write_char(uint8_t c, uint8_t size); void oled_write_string(char *str, uint8_t size); int main(void) { HAL_Init(); MX_GPIO_Init(); MX_SPI1_Init(); oled_init(); while (1) { oled_write_string("Hello World!", 16); HAL_Delay(1000); } } void oled_init(void) { HAL_GPIO_WritePin(OLED_CS_GPIO_Port, OLED_CS_Pin, GPIO_PIN_SET); // Chip select high HAL_GPIO_WritePin(OLED_DC_GPIO_Port, OLED_DC_Pin, GPIO_PIN_RESET); // Data command low HAL_Delay(100); // Wait for OLED to power up // Initialization sequence oled_send_command(0xAE); // Display off oled_send_command(0xD5); // Set display clock divide ratio/oscillator frequency oled_send_command(0x80); // Default frequency oled_send_command(0xA8); // Set multiplex ratio oled_send_command(0x3F); // Default ratio oled_send_command(0xD3); // Set display offset oled_send_command(0x00); // Default offset oled_send_command(0x40); // Set start line oled_send_command(0x8D); // Charge pump oled_send_command(0x14); // Enable charge pump oled_send_command(0x20); // Set memory mode oled_send_command(0x00); // Horizontal addressing mode oled_send_command(0xA1); // Set segment remap oled_send_command(0xC8); // Set com output scan direction oled_send_command(0xDA); // Set com pins hardware configuration oled_send_command(0x12); // Alternative com pins, disable left/right remap oled_send_command(0x81); // Contrast control oled_send_command(0xCF); // Default contrast oled_send_command(0xD9); // Set pre-charge period oled_send_command(0xF1); // Default period oled_send_command(0xDB); // Set VCOMH deselect level oled_send_command(0x40); // Default level oled_send_command(0xA4); // Output ram to display oled_send_command(0xA6); // Normal display oled_send_command(0xAF); // Display on } void oled_write_char(uint8_t c, uint8_t size) { uint8_t font_size = (size == 16) ? 16 : 12; const uint8_t *font = (size == 16) ? Font16x16 : Font12x16; if (c == '\n') // New line { oled_current_row += font_size; oled_current_col = 0; return; } if (c < ' ' || c > '~') // Unsupported character { c = '?'; } uint8_t b = c - ' '; const uint8_t *glyph = font + (b * font_size); for (uint8_t i = 0; i < font_size; i++) { oled_send_data(*glyph++); } oled_current_col += size; } void oled_write_string(char *str, uint8_t size) { while (*str) { oled_write_char(*str++, size); } }在该示例代码中,oled_init()函数用于初始化OLED显示屏。oled_write_char()函数用于向屏幕写入一个字符。oled_write_string()函数用于向屏幕写入一个字符串。在主循环中,每隔1秒钟向屏幕写入一次字符串"Hello World!"。值得注意的是,该示例代码使用了字体库文件Font12x16.c和Font16x16.c,这两个文件包含了12x16像素和16x16像素字体的数据。这些字体数据可以在OLED显示屏上显示ASCII码字符。另外,该示例代码还使用了一个SPI接口来与OLED显示屏通信。在HAL库中,SPI接口的初始化函数为MX_SPI1_Init(),而发送数据和命令的函数则分别为oled_send_data()和oled_send_command()。四、实验结果为验证本门禁系统的安全性和可靠性,我们进行了一系列实验。实验结果表明,门禁系统具有较好的性能,可以有效地保障建筑物的安全。对于卡片的注册、识别和注销,实验结果都表明门禁系统能够精确地识别卡片信息,并在扇区中进行数据的读写。对于门锁的控制,实验结果表明系统可以准确地控制舵机的运动,实现门锁的准确开关。对于密码的输入和验证,实验结果显示系统的密码验证功能可以实现身份的有效认证,确保门锁的开启只有授权用户可以进行。同时,门禁系统还具有较强的实用性。用户可以通过LCD显示屏进行输入和输出,方便进行操作和查看系统信息。五、结论当前提出的基于STM32+RC522的门禁系统设计方案,实现了对RFID卡的注册、识别以及身份验证,进而控制门锁的开关。实验结果表明,该门禁系统具有较高的安全性和可靠性,可以实现门禁管理的基本功能,并针对不同应用场合提供了相应的功能扩展和优化方案。未来该门禁系统可以进一步改进和完善,扩展其适用范围和功能。
-
28BYJ-48步进电机是一种直流(DC)步进电机,常被用于小型电子设备中,如打印机、扫描仪、摄像头云台等。该电机具有28mm长、BYJ系列、48个步进角度的特点。步进电机是指将电脉冲转化为精确机械运动的电机类型,每次接收到一个脉冲信号就会以固定的步进角度进行旋转。28BYJ-48步进电机广泛应用于各种需要精准控制的场合,因为它能够提供高精度和平稳的运动,同时也具有结构简单、可靠性高、噪音小等特点。该电机通常由四个线圈控制,驱动板通过依次激活这些线圈来使电机旋转,并且可以通过改变电路中脉冲的频率和方向来控制电机的速度和方向。28BYJ-48步进电机的特点如下: 结构简单:28BYJ-48步进电机由几个基本部件组成,包括一个转子、定子、驱动板和几根引线。由于其结构简单,维护和使用成本相对较低。 体积小:该电机的尺寸较小,可轻松安装在各种小型机械装置中,并占用较少的空间。 精度高:28BYJ-48步进电机的控制方式可以实现高精度的定位和控制,其转动角度通常为固定的步进角度,例如5.625度/步或7.5度/步。 稳定性好:该电机的速度稳定性较好,能够提供平滑的运动,并且转速较低时噪音较小。 基于以上特点,28BYJ-48步进电机常常被应用于以下场景: 小型机器人:这种电机广泛应用于各种小型机器人中,例如小车、智能家居等。 打印机:28BYJ-48步进电机也被广泛应用于打印机中,例如控制打印头进行移动和打印纸张进给等。 相机控制:28BYJ-48步进电机可以用于控制相机的旋转、平移等动作,例如自拍杆、云台等。 小型家电:由于该电机体积小、功耗低,因此也广泛应用于各种小型家电中,例如微波炉、洗衣机、智能门锁等。 下面是使用STM32F103RCT6控制28BYJ-48步进电机的示例代码#include "stm32f10x.h"// 定义28BYJ-48步进电机控制引脚#define IN1_PIN GPIO_Pin_0#define IN2_PIN GPIO_Pin_1#define IN3_PIN GPIO_Pin_2#define IN4_PIN GPIO_Pin_3#define IN_PORT GPIOA// 定义步进角度和对应的控制信号#define STEP_ANGLE 5.625#define PHASE_A 0b0001#define PHASE_B 0b0010#define PHASE_C 0b0100#define PHASE_D 0b1000// 配置GPIO引脚void GPIO_Init(void){ // 使能GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 配置IN1~IN4为推挽输出 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = IN1_PIN | IN2_PIN | IN3_PIN | IN4_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(IN_PORT, &GPIO_InitStructure);}// 控制28BYJ-48步进电机正转一定角度void StepCW(uint16_t steps){ uint8_t i, j; for (i = 0; i < steps; i++) { for (j = 0; j < 4; j++) { switch (j) { case 0: GPIO_Write(IN_PORT, PHASE_A); break; case 1: GPIO_Write(IN_PORT, PHASE_B); break; case 2: GPIO_Write(IN_PORT, PHASE_C); break; case 3: GPIO_Write(IN_PORT, PHASE_D); break; } Delay_ms(1); // 等待1毫秒 } }}// 控制28BYJ-48步进电机反转一定角度void StepCCW(uint16_t steps){ uint8_t i, j; for (i = 0; i < steps; i++) { for (j = 3; j >= 0; j--) { switch (j) { case 0: GPIO_Write(IN_PORT, PHASE_A); break; case 1: GPIO_Write(IN_PORT, PHASE_B); break; case 2: GPIO_Write(IN_PORT, PHASE_C); break; case 3: GPIO_Write(IN_PORT, PHASE_D); break; } Delay_ms(1); // 等待1毫秒 } }}int main(void){ GPIO_Init(); while(1) { StepCW(512); // 正转360度 Delay_ms(1000); // 等待1秒 StepCCW(512); // 反转360度 Delay_ms(1000); // 等待1秒 }}在上述代码中,使用GPIO_Init函数初始化了GPIO引脚,然后定义了StepCW和StepCCW两个子函数来分别实现控制电机正转一定角度和反转一定角度的功能。这些子函数使用了一个for循环来依次生成四个脉冲信号,实现了28BYJ-48步进电机的控制。只需要在主函数中调用这些子函数即可完成对电机的控制。
-
SG90是一种微型舵机,也被称为伺服电机。它是一种小型、低成本的直流电机,通常用于模型和机器人控制等应用中。SG90舵机通常能够旋转大约180度,并且可以通过电子信号来控制其精确的位置和速度。它具有体积小、重量轻、响应快等特点,因此在各种小型机械设备上得到了广泛应用。SG90舵机通常用于各种小型机械设备中,例如: 模型控制:SG90舵机可以用于遥控汽车、飞机、船只和其他模型的转向、加速和刹车等控制。 机器人控制:SG90舵机也广泛应用于机器人领域,例如可以控制机器人的头部旋转、臂部移动等。 相机云台:SG90舵机可以用于控制相机的运动,例如实现云台的左右旋转和上下移动。 自动化系统:在一些自动化系统中,SG90舵机可以用来控制小型机械臂或手指的运动。 总之,SG90舵机适用于需要精确定位和紧凑设计的应用场景。 下面使用STM32F103RCT6来驱动SG90舵机。完成电机的正转、反转控制。#include "stm32f10x.h"// 定义SG90舵机控制引脚#define SG90_PIN GPIO_Pin_0#define SG90_PORT GPIOA// 定义时钟周期和PWM周期#define SYS_CLK 72000000#define PWM_PERIOD 20000#define PWM_PRESCALER 36// 定义角度范围和对应的占空比#define ANGLE_MIN 0#define ANGLE_MAX 180#define DUTY_MIN 500#define DUTY_MAX 2500// 初始化PWM输出void PWM_Init(void){ // 使能GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能TIM3时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 配置PA6为复用推挽输出 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = SG90_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(SG90_PORT, &GPIO_InitStructure); // 配置TIM3为PWM模式 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period = PWM_PERIOD - 1; TIM_TimeBaseStructure.TIM_Prescaler = PWM_PRESCALER - 1; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // 配置TIM3通道1为PWM输出 TIM_OCInitTypeDef TIM_OCInitStructure; TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = DUTY_MIN; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM3, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); // 启动TIM3 TIM_Cmd(TIM3, ENABLE);}// 将角度转换为对应的占空比uint16_t AngleToDuty(uint8_t angle){ return (DUTY_MAX - DUTY_MIN) * angle / (ANGLE_MAX - ANGLE_MIN) + DUTY_MIN;}// 控制SG90舵机转动到指定角度void SetAngle(uint8_t angle){ uint16_t duty_cycle = AngleToDuty(angle); TIM_SetCompare1(TIM3, duty_cycle);}// 控制SG90舵机正转一定角度void RotateCW(uint8_t angle){ uint8_t i; for (i = 0; i < angle; i++) { SetAngle(i); Delay_ms(10); // 等待10毫秒 }}// 控制SG90舵机反转一定角度void RotateCCW(uint8_t angle){ uint8_t i; for (i = angle; i > 0; i--) { SetAngle(i); Delay_ms(10); // 等待10毫秒 }}int main(void){ PWM_Init(); while(1) { RotateCW(90); // 正转90度 Delay_ms(1000); // 等待1秒 RotateCCW(90); // 反转90度 Delay_ms(1000); // 等待1秒 }}
-
1. 前言平时工作中编写开发技术文档,或者学生在编写论文时,经常会上网搜索一些参考文献、文档。比如: 上网搜索相似的内容参考一下或者引用别人的一段文字,有时候看到一篇较好的内容想要保存等等。这个过程中会发现,很多网站的提供的页面都是不能复制粘贴的,或者直接是图片形式提供,为了方便能获取这些文字,当前就利用华为云提供的 通用文字识别接口,识别图片里的文本内容,方便复制文字。这个功能QQ上也集成了,使用很方便,这里利用华为云的接口实现一个与QQ类似的功能,截图之后识别图片里包含的文本内容。这个文字识别接口里不仅仅有通用文字识别功能,还支持很多其他功能:比如身份证、驾驶证、保险单、手写文本、火车票,行驶证.......等等功能。还支持用户自定义识别模板,指定需要识别的关键字段,实现用户特定格式图片的自动识别和结构化提取。2. 文本识别接口使用介绍2.1 开通服务地址: cid:link_1这个文字识别服务是按调用次数计费的,每个用户每月有1000次的免费调用次数,开通服务后就可以使用。2.2 接口地址官网帮助文档: cid:link_2 POST https://{endpoint}/v2/{project_id}/ocr/general-text 示例: https://ocr.cn-north-4.myhuaweicloud.com/v2/0e5957be8a00f53c2fa7c0045e4d8fbf/ocr/general-text 请求头: { "X-Auth-Token": "******", "Content-Type": "application/json;charset=UTF-8" } 请求体: { "image": ----这是图片的bas64编码 } 响应结果: { "result": { "words_block_count": 13, "words_block_list": [ { "words": "撤,还是不撤?", "location": [ [ 43, 39 ], [ 161, 39 ], [ 161, 60 ], [ 43, 60 ] ] }, { "words": "让我更骄傲的是公司在大灾面前的表现。", "location": [ [ 72, 95 ], [ 332, 95 ], [ 332, 113 ], [ 72, 113 ] ] }, { "words": "2011年3月11日14时46分,日本东北部海域发生里氏9.0级", "location": [ [ 71, 122 ], [ 482, 122 ], [ 482, 142 ], [ 71, 142 ] ] }, { "words": "地震并引发海啸。那一刻,我们正在距离东京100公里的热海开会,", "location": [ [ 41, 149 ], [ 481, 149 ], [ 481, 171 ], [ 41, 171 ] ] }, { "words": "感觉“咚”", "location": [ [ 42, 180 ], [ 114, 180 ], [ 114, 199 ], [ 42, 199 ] ] }, { "words": "地被震了一下。面对地震,", "location": [ [ 115, 178 ], [ 296, 178 ], [ 296, 199 ], [ 115, 199 ] ] }, { "words": "大家都很镇定,", "location": [ [ 300, 179 ], [ 400, 179 ], [ 400, 197 ], [ 300, 197 ] ] }, { "words": "直到看到电", "location": [ [ 405, 179 ], [ 483, 179 ], [ 483, 196 ], [ 405, 196 ] ] }, { "words": "视上触目惊心的画面:15时 25 分,海啸到达陆前高田市海岸;15时", "location": [ [ 41, 206 ], [ 485, 206 ], [ 485, 228 ], [ 41, 228 ] ] }, { "words": "26分,海啸到达陆前高田市中心;15时43分,陆前高田市依稀只能", "location": [ [ 40, 234 ], [ 486, 234 ], [ 486, 258 ], [ 40, 258 ] ] }, { "words": "看到四层高的市府大楼的屋顶,一瞬间,城镇就变成了汪洋……对", "location": [ [ 40, 262 ], [ 487, 262 ], [ 487, 287 ], [ 40, 287 ] ] }, { "words": "我来说,地震跟家常便饭一样,可眼前的灾难比以往任何一次都要", "location": [ [ 40, 292 ], [ 487, 292 ], [ 487, 317 ], [ 40, 317 ] ] }, { "words": "惨烈,完全超出了我的预期。", "location": [ [ 41, 326 ], [ 231, 326 ], [ 231, 345 ], [ 41, 345 ] ] } ], "direction": -1 } }在请求参数里的X-Auth-Token参数比较重要,调用华为云的任何API接口都需要这个参数,获取方式可以看前面的文章。比如这篇文章: cid:link_32.3 在线调试接口地址: https://apiexplorer.developer.huaweicloud.com/apiexplorer/debug?product=OCR&api=RecognizeGeneralText使用调试接口想体验识别效果,图片的数据支持base64编码、http网络图片地址传入,测试非常方便。关于获取图片base64编码的方式,在文档里也有介绍,直接通过浏览器获取。3. 实现代码代码采用QT编写的,请求API接口实现调用。其他语言方法是一样的。3.1 实现效果3.2 核心代码//解析反馈结果 void Widget::replyFinished(QNetworkReply *reply) { QString displayInfo=""; int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); //读取所有数据 QByteArray replyData = reply->readAll(); qDebug()<<"状态码:"<<statusCode; qDebug()<<"反馈的数据:"<<QString(replyData); //更新token if(function_select==3) { displayInfo="token 更新失败."; //读取HTTP响应头的数据 QList<QNetworkReply::RawHeaderPair> RawHeader=reply->rawHeaderPairs(); qDebug()<<"HTTP响应头数量:"<<RawHeader.size(); for(int i=0;i<RawHeader.size();i++) { QString first=RawHeader.at(i).first; QString second=RawHeader.at(i).second; if(first=="X-Subject-Token") { Token=second.toUtf8(); displayInfo="token 更新成功."; //保存到文件 SaveDataToFile(Token); break; } } QMessageBox::information(this,"提示",displayInfo,QMessageBox::Ok,QMessageBox::Ok); return; } //判断状态码 if(200 != statusCode) { //解析数据 QJsonParseError json_error; QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error); if(json_error.error == QJsonParseError::NoError) { //判断是否是对象,然后开始解析数据 if(document.isObject()) { QString error_str=""; QJsonObject obj = document.object(); QString error_code; //解析错误代码 if(obj.contains("error_code")) { error_code=obj.take("error_code").toString(); error_str+="错误代码:"; error_str+=error_code; error_str+="\n"; } if(obj.contains("error_msg")) { error_str+="错误消息:"; error_str+=obj.take("error_msg").toString(); error_str+="\n"; } //显示错误代码 QMessageBox::information(this,"提示",error_str,QMessageBox::Ok,QMessageBox::Ok); } } return; } //结果返回 if(function_select==1) { //解析数据 QJsonParseError json_error; QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error); if(json_error.error == QJsonParseError::NoError) { //判断是否是对象,然后开始解析数据 if(document.isObject()) { QJsonObject obj = document.object(); QString error_code; //解析 if(obj.contains("result")) { QJsonObject obj1=obj.take("result").toObject(); QString bank_name; QString card_number; QString type; QString text; if(obj1.contains("bank_name")) { bank_name=obj1.take("bank_name").toString(); } if(obj1.contains("card_number")) { card_number=obj1.take("card_number").toString(); } if(obj1.contains("type")) { type=obj1.take("type").toString(); } text="发卡行:"+bank_name+"\n"; text+="卡号:"+card_number+"\n"; text+="卡类型:"+type+"\n"; ui->plainTextEdit->setPlainText(text); } } } } //结果返回 if(function_select==2) { //解析数据 QJsonParseError json_error; QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error); if(json_error.error == QJsonParseError::NoError) { //判断是否是对象,然后开始解析数据 if(document.isObject()) { QJsonObject obj = document.object(); QString error_code; //解析 if(obj.contains("result")) { QJsonObject obj1=obj.take("result").toObject(); int words_block_count; QString text=""; if(obj1.contains("words_block_count")) { words_block_count=obj1.take("words_block_count").toInt(); // text=QString("识别到%1行文本.\n").arg(words_block_count); } if(obj1.contains("words_block_list")) { QJsonArray array=obj1.take("words_block_list").toArray(); for(int i=0;i<array.size();i++) { QJsonObject obj2=array.at(i).toObject(); if(obj2.contains("words")) { text+=obj2.take("words").toString(); text+="\n"; } } } ui->plainTextEdit->setPlainText(text); } } } } } /* 功能: 获取token */ void Widget::GetToken() { //表示获取token function_select=3; QString requestUrl; QNetworkRequest request; //设置请求地址 QUrl url; //获取token请求地址 requestUrl = QString("https://iam.%1.myhuaweicloud.com/v3/auth/tokens") .arg(SERVER_ID); //自己创建的TCP服务器,测试用 //requestUrl="http://10.0.0.6:8080"; //设置数据提交格式 request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json;charset=UTF-8")); //构造请求 url.setUrl(requestUrl); request.setUrl(url); QString text =QString("{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":" "{\"user\":{\"domain\": {" "\"name\":\"a466c06-dfd6-4a86-931d-2a23386b8f3a"},\"name\": \"/a466c06-dfd6-4a86-931d-2a23386b8f3a",\"password\": \"?a466c06-dfd6-4a86-931d-2a23386b8f3a"}}}," "\"scope\":{\"project\":{\"name\":\"Oa466c06-dfd6-4a86-931d-2a23386b8f3a"}}}}") .arg(MAIN_USER) .arg(IAM_USER) .arg(IAM_PASSWORD) .arg(SERVER_ID); //发送请求 manager->post(request, text.toUtf8()); } //粘贴图片 void Widget::on_pushButton_copy_clicked() { QClipboard *clipboard = QApplication::clipboard(); const QMimeData *mimeData = clipboard->mimeData(); if (mimeData->hasImage()) { //将图片数据转为QImage QImage img = qvariant_cast<QImage>(mimeData->imageData()); if(!img.isNull()) { ui->widget->SetImage(img); } } } //获取图片里的文字信息 void Widget::getTextInfo(QImage image) { function_select=2; QString requestUrl; QNetworkRequest request; //存放图片BASE64编码 QString imgData; //设置请求地址 QUrl url; //人脸搜索请求地址 requestUrl = QString("https://ocr.%1.myhuaweicloud.com/v2/%2/ocr/general-text") .arg(SERVER_ID) .arg(PROJECT_ID); //设置数据提交格式 request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json;charset=UTF-8")); //将图片进行Base64编码 imgData = QString(toBase64(image)); //编码后的图片大小不超过2M //设置token request.setRawHeader("X-Auth-Token",Token); //构造请求 url.setUrl(requestUrl); request.setUrl(url); QString post_param=QString ("{" "\"image\": \"a466c06-dfd6-4a86-931d-2a23386b8f3a"" "}").arg(imgData); //发送请求 manager->post(request, post_param.toUtf8()); }
-
一、前言最近几年物联网发展的比较迅速,国内各大厂商都推出物联网服务器,面向设备厂商、个人开发者、提供云端一体的设备智能化服务,利用现成的物联网服务器可以快速实现IoT设备智能化的需求。方便企业、个人接入设备,低成本完成物联网开发。比如:阿里云、百度云、华为云、腾讯云、电信云、中国移动OneNet、原子云、机智云。不仅仅能支持设备接入、数据处理、数据分析、价值转换、还支持网页设计、公版APP设计、公版微信小程序设计,对企业、个人开发都非常方便。这些物联网云平台都支持标准的MQTT协议接入,对个人开发者而言,在学习阶段手上没有合适的硬件,或者说使用硬件的门槛较高,又想要快速体验一遍设备通过MQTT协议接入服务器完成通信的过程,那么这篇文章就介绍一款MQTT客户端软件的设计过程。 使用Qt设计一款MQTT客户端软件,利用MQTT协议接入各大物联网服务器,模拟完成硬件设备上云,实现与服务器的数据交互:主题订阅、主题发布等操作。MQTT协议官方帮助文档:cid:link_1 MQTT是机器对机器(M2M)/物联网(IoT)连接协议。它被设计为一个极其轻量级的发布/订阅消息传输协议。对于需要较小代码占用空间和/或网络带宽非常宝贵的远程连接非常有用,是专为受限设备和低带宽、高延迟或不可靠的网络而设计。这些原则也使该协议成为新兴的“机器到机器”(M2M)或物联网(IoT)世界的连接设备,以及带宽和电池功率非常高的移动应用的理想选择。例如,它已被用于通过卫星链路与代理通信的传感器、与医疗服务提供者的拨号连接,以及一系列家庭自动化和小型设备场景。它也是移动应用的理想选择,因为它体积小,功耗低,数据包最小,并且可以有效地将信息分配给一个或多个接收器。 特点开放消息协议,简单易实现发布订阅模式,一对多消息发布基于TCP/IP网络连接,提供有序,无损,双向连接。1字节固定报头,2字节心跳报文,最小化传输开销和协议交换,有效减少网络流量。消息QoS支持,可靠传输保证应用MQTT协议广泛应用于物联网、移动互联网、智能硬件、车联网、电力能源等领域。物联网M2M通信,物联网大数据采集Android消息推送,WEB消息推送移动即时消息,例如Facebook Messenger智能硬件、智能家具、智能电器车联网通信,电动车站桩采集智慧城市、远程医疗、远程教育电力、石油与能源等行业市场二、搭建Qt开发环境软件采用Qt框架设计,Qt是一个跨平台的C++应用程序开发框架,由挪威公司Trolltech(现在是Digia公司)开发。它提供了一套丰富的类库和工具集,用于构建图形用户界面、网络应用程序等各种类型的应用程序。Qt框架的主要特点是其跨平台性能和易于使用性。Qt支持多种操作系统,包括Windows、Mac OS X、Linux和Unix等,并且可以编写用于移动设备的应用程序。Qt提供了一个称为信号槽(Signal/Slot)的机制,它使得应用程序开发变得非常容易,并且可以有效地避免了许多与事件处理相关的问题。Qt也提供了大量的类库,包括GUI、网络、数据库、XML、OpenGL、音频和视频等领域。这些类库使得开发人员可以轻松地实现复杂的功能,并且无需重新实现已有的功能。总之,Qt是一个强大的、易于使用和跨平台的C++应用程序开发框架,被广泛应用于图形用户界面、网络应用程序、游戏、嵌入式系统等各种领域。QT5.12.6的下载地址:cid:link_0打开下载链接后选择下面的版本进行下载:qt-opensource-windows-x86-5.12.6.exe 13-Nov-2019 07:28 3.7G Details软件安装时断网安装,否则会提示输入账户。安装的时候,第一个复选框里勾选一个mingw 32编译器即可,其他的不管默认就行,直接点击下一步继续安装。三、设计过程【1】环境介绍我的开发环境:QT的版本是5.12.6 、编译器MinGW32位(这与编译器没多大关系)。当前的项目代码是采用标准的Qt框架编写,没有依赖其他任何第三方库,只是用到了Qt里的网络通信模块。MQTT协议是根据MQTT官方文档,自己进行协议封包,利用QT网络通信模块建立TCP协议完成与物联网平台的通信。 而Qt本身也是跨平台的框架,所以,这份代码可以运行在Android、IOS、windows、Linux、macOS等等环境下。 通过这份项目的学习可以充分搞清楚什么是MQTT协议,了解协议如何封包、解包等过程。【2】整体的项目工程【3】MQTT协议登录过程/* 函数功能: 登录服务器 函数返回值: 0表示成功 1表示失败 */ quint8 MQTT_WorkClass::MQTT_Connect(char *ClientID,char *Username,char *Password) { quint8 i,j; int ClientIDLen = strlen(ClientID); int UsernameLen = strlen(Username); int PasswordLen = strlen(Password); int DataLen; mqtt_txlen=0; //可变报头+Payload 每个字段包含两个字节的长度标识 DataLen = 10 + (ClientIDLen+2) + (UsernameLen+2) + (PasswordLen+2); //固定报头 //控制报文类型 mqtt_txbuf[mqtt_txlen++] = 0x10; //MQTT Message Type CONNECT //剩余长度(不包括固定头部) do { quint8 encodedByte = DataLen % 128; DataLen = DataLen / 128; // if there are more data to encode, set the top bit of this byte if ( DataLen > 0 ) encodedByte = encodedByte | 128; mqtt_txbuf[mqtt_txlen++] = encodedByte; }while ( DataLen > 0 ); //可变报头 //协议名 mqtt_txbuf[mqtt_txlen++] = 0; // Protocol Name Length MSB mqtt_txbuf[mqtt_txlen++] = 4; // Protocol Name Length LSB mqtt_txbuf[mqtt_txlen++] = 'M'; // ASCII Code for M mqtt_txbuf[mqtt_txlen++] = 'Q'; // ASCII Code for Q mqtt_txbuf[mqtt_txlen++] = 'T'; // ASCII Code for T mqtt_txbuf[mqtt_txlen++] = 'T'; // ASCII Code for T //协议级别 mqtt_txbuf[mqtt_txlen++] = 4; // MQTT Protocol version = 4 对于 3.1.1 版协议,协议级别字段的值是 4(0x04) //连接标志 mqtt_txbuf[mqtt_txlen++] = 0xc2; // conn flags mqtt_txbuf[mqtt_txlen++] = 0; // Keep-alive Time Length MSB mqtt_txbuf[mqtt_txlen++] = 100; // Keep-alive Time Length LSB 100S心跳包 保活时间 mqtt_txbuf[mqtt_txlen++] = BYTE1(ClientIDLen);// Client ID length MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(ClientIDLen);// Client ID length LSB memcpy(&mqtt_txbuf[mqtt_txlen],ClientID,ClientIDLen); mqtt_txlen += ClientIDLen; if(UsernameLen > 0) { mqtt_txbuf[mqtt_txlen++] = BYTE1(UsernameLen); //username length MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(UsernameLen); //username length LSB memcpy(&mqtt_txbuf[mqtt_txlen],Username,UsernameLen); mqtt_txlen += UsernameLen; } if(PasswordLen > 0) { mqtt_txbuf[mqtt_txlen++] = BYTE1(PasswordLen); //password length MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(PasswordLen); //password length LSB memcpy(&mqtt_txbuf[mqtt_txlen],Password,PasswordLen); mqtt_txlen += PasswordLen; } //清空数据 memset(mqtt_rxbuf,0,mqtt_rxlen); ReadData.clear(); MQTT_SendBuf(mqtt_txbuf,mqtt_txlen); //开始事件循环 StartEvenLoop(); if(ReadData.length()==0) { //开始事件循环 StartEvenLoop(); } memcpy((char *)mqtt_rxbuf,ReadData.data(),ReadData.length()); //CONNECT if(mqtt_rxbuf[0]==parket_connetAck[0] && mqtt_rxbuf[1]==parket_connetAck[1]) //连接成功 { return 0;//连接成功 } return 1; }【4】MQTT取消订阅与订阅主题/* 函数功能: MQTT订阅/取消订阅数据打包函数 函数参数: topic 主题 qos 消息等级 0:最多分发一次 1: 至少分发一次 2: 仅分发一次 whether 订阅/取消订阅请求包 (1表示订阅,0表示取消订阅) 返回值: 0表示成功 1表示失败 */ quint8 MQTT_WorkClass::MQTT_SubscribeTopic(char *topic,quint8 qos,quint8 whether) { quint8 i,j; mqtt_txlen=0; int topiclen = strlen(topic); int DataLen = 2 + (topiclen+2) + (whether?1:0);//可变报头的长度(2字节)加上有效载荷的长度 //固定报头 //控制报文类型 if(whether)mqtt_txbuf[mqtt_txlen++] = 0x82; //消息类型和标志订阅 else mqtt_txbuf[mqtt_txlen++] = 0xA2; //取消订阅 //剩余长度 do { quint8 encodedByte = DataLen % 128; DataLen = DataLen / 128; // if there are more data to encode, set the top bit of this byte if ( DataLen > 0 ) encodedByte = encodedByte | 128; mqtt_txbuf[mqtt_txlen++] = encodedByte; }while ( DataLen > 0 ); //可变报头 mqtt_txbuf[mqtt_txlen++] = 0; //消息标识符 MSB mqtt_txbuf[mqtt_txlen++] = 0x0A; //消息标识符 LSB //有效载荷 mqtt_txbuf[mqtt_txlen++] = BYTE1(topiclen);//主题长度 MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(topiclen);//主题长度 LSB memcpy(&mqtt_txbuf[mqtt_txlen],topic,topiclen); mqtt_txlen += topiclen; if(whether) { mqtt_txbuf[mqtt_txlen++] = qos;//QoS级别 } ReadData.clear(); MQTT_SendBuf(mqtt_txbuf,mqtt_txlen); //开始事件循环 StartEvenLoop(); if(ReadData.length()==0) { //开始事件循环 StartEvenLoop(); } memcpy((char *)mqtt_rxbuf,ReadData.data(),ReadData.length()); if(mqtt_rxbuf[0]==parket_subAck[0] && mqtt_rxbuf[1]==parket_subAck[1]) //订阅成功 { return 0;//订阅成功 } return 1; //失败 }【5】MQTT主题数据发布//MQTT发布数据打包函数 //topic 主题 //message 消息 //qos 消息等级 quint8 MQTT_WorkClass::MQTT_PublishData(char *topic, char *message, quint8 qos) { int topicLength = strlen(topic); int messageLength = strlen(message); static quint16 id=0; int DataLen; mqtt_txlen=0; //有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度 //QOS为0时没有标识符 //数据长度 主题名 报文标识符 有效载荷 if(qos) DataLen = (2+topicLength) + 2 + messageLength; else DataLen = (2+topicLength) + messageLength; //固定报头 //控制报文类型 这是发送包第1个字节 //报文的字节位: 7654 3 21 0 /* 第7、6、5、4位 表示是PUBLISH 报文 固定填0011 第3位 是DUP位,重发标志,如果是重发这个位就需要置位1,第一次发生置位0 主要是客户端重发时,防止服务器重复收到多个相同的消息 第2、1位 QoS等级. 分别可以填 00 01 10 ---对应就是0 1 2 第0位 是RETAIN位, 如果客户端发给服务端的 PUBLISH 报文的保留(RETAIN) 标志被设置为 1, 服务端必须存储这个应用消息和它的服务质量等级(QoS) */ //注意: 我这里没有增加DUP位的设置,如果要增加,可以给当前函数增加一个形参 //标识当前发送是第一次发送,还是重复发送 if(qos==0) { //0011 0000 mqtt_txbuf[mqtt_txlen++] = 0x30; // MQTT Message Type PUBLISH } else if(qos==1) { //0011 0011 mqtt_txbuf[mqtt_txlen++] = 0x33; // MQTT Message Type PUBLISH } else if(qos==2) { //0011 0101 mqtt_txbuf[mqtt_txlen++] = 0x35; // MQTT Message Type PUBLISH } LogSend(QString("消息质量等级:d78bfb9-77b6-4fc3-a66c-4fa4414a7687n").arg(qos)); //剩余长度---这是第2个字节 do { quint8 encodedByte = DataLen % 128; DataLen = DataLen / 128; // if there are more data to encode, set the top bit of this byte if ( DataLen > 0 ) encodedByte = encodedByte | 128; mqtt_txbuf[mqtt_txlen++] = encodedByte; }while ( DataLen > 0 ); // qDebug()<<"mqtt_txlen:"<<mqtt_txlen; //3 //主题名称1、2 字节 mqtt_txbuf[mqtt_txlen++] = BYTE1(topicLength);//主题长度MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(topicLength);//主题长度LSB memcpy(&mqtt_txbuf[mqtt_txlen],topic,topicLength);//拷贝主题 mqtt_txlen += topicLength; //报文标识符 if(qos) { //第6、7字节 mqtt_txbuf[mqtt_txlen++] = BYTE1(id); mqtt_txbuf[mqtt_txlen++] = BYTE0(id); id++; } memcpy(&mqtt_txbuf[mqtt_txlen],message,messageLength); mqtt_txlen += messageLength; ReadData.clear(); MQTT_SendBuf(mqtt_txbuf,mqtt_txlen); //如果订阅了主题 // 订阅者:设备 平台下发消息给设备 // 这里就可以收到MQTT服务器的响应返回值 // QOS==1 服务器响应: "P\x02\x00\x07" // QOS==2 服务器响应: "@\x02\x00\x18" 最后一个是次数 //开始事件循环 StartEvenLoop(); return mqtt_txlen; }六、总结物联网是通过约定的协议将原本独立存在的设备相互连接起来,并最终实现智能识别、定位、跟踪、监测、控制和管理的一种网络,无需人与人、或人与设备的互动。通俗来说物联网就是“物物相连的网”,主要应用于智能交通、智能医疗、智能家居、智能物流、智能电力等领域。目前物联网产业正在飞快发展着,从智能电视、智能家居、智能汽车、医疗健康、智能玩具、机器人等延伸到可穿戴设备领域。 物联网将赋能智能硬件向多元的消费场景渗透,从而创造更加便捷、舒适、安全、节能的生活环境。以智能家居为例,我们借助物联网可远程控制家庭里面的每一件智能家居,像电灯,电视,空调等,给我们的生活带来更多的便利。随着5G 技术的快速发展,物联网将应用到更多领域,由此可见物联网产业仍具有较大的发展空间。
-
一、前言做音视频开发过程中,经常需要获取媒体文件的详细信息。比如:获取视频文件的总时间、帧率、尺寸、码率等等信息。 获取音频文件的的总时间、帧率、码率,声道等信息。 这篇文章贴出2个我封装好的函数,直接调用就能获取媒体信息返回,copy过去就能使用,非常方便。如果要获取详细信息,可以使用ffprobe实现,也可以调用ffmpeg函数直接打开视频解析获取。下面会演示两种方式,一种直接调用 ffprobe.exe实现,一种是调用ffmpeg函数直接打开视频解析获取。如果调用ffprobe.exe实现,可以编译ffmpeg源码,以静态方式编译ffprobe.exe,这样调用起来比较方便,不需要带任何的依赖库。下面 调用ffprobe.exe以JSON形式输出媒体文件的详细信息。ffprobe -v quiet -of json -i D:/123.mp4 -show_streams执行之后直接通过JSON格式输出:C:\Users\11266>ffprobe -v quiet -of json -i D:/123.mp4 -show_streams { "streams": [ { "index": 0, "codec_name": "aac", "codec_long_name": "AAC (Advanced Audio Coding)", "profile": "LC", "codec_type": "audio", "codec_time_base": "1/88200", "codec_tag_string": "mp4a", "codec_tag": "0x6134706d", "sample_fmt": "fltp", "sample_rate": "88200", "channels": 2, "channel_layout": "stereo", "bits_per_sample": 0, "r_frame_rate": "0/0", "avg_frame_rate": "0/0", "time_base": "1/44100", "start_pts": 0, "start_time": "0.000000", "duration_ts": 4141046, "duration": "93.901270", "bit_rate": "127948", "max_bit_rate": "132760", "nb_frames": "4045", "disposition": { "default": 1, "dub": 0, "original": 0, "comment": 0, "lyrics": 0, "karaoke": 0, "forced": 0, "hearing_impaired": 0, "visual_impaired": 0, "clean_effects": 0, "attached_pic": 0, "timed_thumbnails": 0 }, "tags": { "creation_time": "2015-04-30T02:43:22.000000Z", "language": "und", "handler_name": "GPAC ISO Audio Handler" } }, { "index": 1, "codec_name": "h264", "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", "profile": "Main", "codec_type": "video", "codec_time_base": "2349/70450", "codec_tag_string": "avc1", "codec_tag": "0x31637661", "width": 1280, "height": 720, "coded_width": 1280, "coded_height": 720, "has_b_frames": 0, "sample_aspect_ratio": "1:1", "display_aspect_ratio": "16:9", "pix_fmt": "yuv420p", "level": 51, "chroma_location": "left", "refs": 1, "is_avc": "true", "nal_length_size": "4", "r_frame_rate": "25/1", "avg_frame_rate": "35225/2349", "time_base": "1/30000", "start_pts": 0, "start_time": "0.000000", "duration_ts": 2816400, "duration": "93.880000", "bit_rate": "582474", "bits_per_raw_sample": "8", "nb_frames": "1409", "disposition": { "default": 1, "dub": 0, "original": 0, "comment": 0, "lyrics": 0, "karaoke": 0, "forced": 0, "hearing_impaired": 0, "visual_impaired": 0, "clean_effects": 0, "attached_pic": 0, "timed_thumbnails": 0 }, "tags": { "creation_time": "2015-04-30T02:43:23.000000Z", "language": "und", "handler_name": "GPAC ISO Video Handler" } } ] }如果只是想要得到 媒体的总时长、尺寸信息,那么执行下面命令即可:C:\Users\11266>ffprobe -i D:/123.mp4 ffprobe version 4.2.2 Copyright (c) 2007-2019 the FFmpeg developers built with gcc 9.2.1 (GCC) 20200122 configuration: --disable-static --enable-shared --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt libavutil 56. 31.100 / 56. 31.100 libavcodec 58. 54.100 / 58. 54.100 libavformat 58. 29.100 / 58. 29.100 libavdevice 58. 8.100 / 58. 8.100 libavfilter 7. 57.100 / 7. 57.100 libswscale 5. 5.100 / 5. 5.100 libswresample 3. 5.100 / 3. 5.100 libpostproc 55. 5.100 / 55. 5.100 Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'D:/123.mp4': Metadata: major_brand : mp42 minor_version : 0 compatible_brands: mp42isom creation_time : 2015-04-30T02:43:22.000000Z Duration: 00:01:33.90, start: 0.000000, bitrate: 715 kb/s Stream #0:0(und): Audio: aac (LC) (mp4a / 0x6134706D), 88200 Hz, stereo, fltp, 127 kb/s (default) Metadata: creation_time : 2015-04-30T02:43:22.000000Z handler_name : GPAC ISO Audio Handler Stream #0:1(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 582 kb/s, 15 fps, 25 tbr, 30k tbn, 20000k tbc (default) Metadata: creation_time : 2015-04-30T02:43:23.000000Z handler_name : GPAC ISO Video Handler二、调用ffprobe获取媒体信息下面利用Qt编写代码调用ffprobe可执行文件,解析媒体信息输出。下面封装了2个函数,完整媒体信息的解析返回。【1】获取尺寸和时长//媒体信息 struct MEDIA_INFO { int width; //宽度 int height; //高度 qint64 duration;//视频总时长--毫秒 }; //获取视频的尺寸和总时间信息 struct MEDIA_INFO GetVideo_SizeInfo(QString file) { int w, h; struct MEDIA_INFO info = {0,0,0}; //拼接ffmpge的路径 QString cmd = QString("%1 -i \"-07e2b72-d5f3-497f-98d9-a12cdb25f805"").arg(FFPROBE_NAME).arg(file); QProcess process; process.setProcessChannelMode(QProcess::MergedChannels); process.start(cmd.toUtf8()); process.waitForFinished(); process.waitForReadyRead(); //qDebug() << "cmd:" << cmd; if (process.exitCode() == 0) { log_printf(QString("Run Success")); QString qba = process.readAll(); QByteArray utf8_str = qba.toUtf8(); // Match duration QRegularExpression reDuration("Duration: (\\d{2}:\\d{2}:\\d{2}\\.\\d{2})"); QRegularExpressionMatch matchDuration = reDuration.match(utf8_str); if (matchDuration.hasMatch()) { QString duration = matchDuration.captured(1); // "00:06:37.15" qDebug() << "视频总时间:" << duration; int hour=duration.section(":", 0, 0).toInt(); int minute = duration.section(":", 1, 1).toInt(); int second = duration.section(":", 2, 3).section(".",0,0).toInt(); int ms = duration.section(":", 2, 3).section(".", 1, 1).toInt(); info.duration= hour * 60 * 60 *1000 + minute * 60 *1000 + second*1000 + ms; } else { qDebug() << "No duration match found."; } // Match resolution QRegularExpression reResolution("\\d{3,4}x\\d{3,4}"); QRegularExpressionMatch matchResolution = reResolution.match(utf8_str); if (matchResolution.hasMatch()) { QString resolution = matchResolution.captured(0); //qDebug() << "视频尺寸:" << resolution; //qDebug() << "视频尺寸--w:" << resolution.section("x", 0, 0); //qDebug() << "视频尺寸--h:" << resolution.section("x", 1, 1); info.width = resolution.section("x", 0, 0).toInt(); info.height = resolution.section("x", 1, 1).toInt(); } else { qDebug() << "No resolution match found."; } } else { log_printf(QString("Run ERROR")); return info; } return info; }【2】获取媒体详细并解析出来// 定义用于存储解析结果的结构体 struct Stream { int index; QString codecName; QString codecLongName; QString profile; QString codecType; QString codecTimeBase; QString codecTagString; QString codecTag; int width; int height; int codedWidth; int codedHeight; bool hasBFrames; QString pixFmt; int level; QString colorRange; QString colorSpace; QString colorTransfer; QString colorPrimaries; QString chromaLocation; int refs; bool isAVC; int nalLengthSize; QString rFrameRate; QString avgFrameRate; QString timeBase; qint64 startPts; QString startTime; qint64 durationTs; QString duration; int bitRate; int bitsPerRawSample; int nbFrames; struct Disposition { int defaultValue; int dub; int original; int comment; int lyrics; int karaoke; int forced; int hearingImpaired; int visualImpaired; int cleanEffects; int attachedPic; int timedThumbnails; } disposition; struct Tags { QString language; QString handlerName; } tags; }; //解析存放媒体信息的JSON结构 QVector<Stream> DecodeMediaInfo(QString mediafile) { QByteArray byte_data=mediafile.toUtf8(); //获取媒体信息 QByteArray jsonStr = GetMediaInfo(byte_data.data()); // 将json字符串转换为json文档对象 QJsonDocument doc = QJsonDocument::fromJson(jsonStr); // 获取顶层json对象 QJsonObject topLevelObj = doc.object(); // 获取streams数组 QJsonArray streamsArray = topLevelObj.value("streams").toArray(); // 遍历streams数组,将每个元素转换为Stream结构体 QVector<Stream> streamVec; for (const QJsonValue & streamValue : streamsArray) { QJsonObject streamObj = streamValue.toObject(); // 创建新的Stream实例,并设置属性值 Stream stream; stream.index = streamObj.value("index").toInt(); stream.codecName = streamObj.value("codec_name").toString(); stream.codecLongName = streamObj.value("codec_long_name").toString(); stream.profile = streamObj.value("profile").toString(); stream.codecType = streamObj.value("codec_type").toString(); stream.codecTimeBase = streamObj.value("codec_time_base").toString(); stream.codecTagString = streamObj.value("codec_tag_string").toString(); stream.codecTag = streamObj.value("codec_tag").toString(); stream.width = streamObj.value("width").toInt(); stream.height = streamObj.value("height").toInt(); stream.codedWidth = streamObj.value("coded_width").toInt(); stream.codedHeight = streamObj.value("coded_height").toInt(); stream.hasBFrames = streamObj.value("has_b_frames").toBool(); stream.pixFmt = streamObj.value("pix_fmt").toString(); stream.level = streamObj.value("level").toInt(); stream.colorRange = streamObj.value("color_range").toString(); stream.colorSpace = streamObj.value("color_space").toString(); stream.colorTransfer = streamObj.value("color_transfer").toString(); stream.colorPrimaries = streamObj.value("color_primaries").toString(); stream.chromaLocation = streamObj.value("chroma_location").toString(); stream.refs = streamObj.value("refs").toInt(); stream.isAVC = streamObj.value("is_avc").toBool(); stream.nalLengthSize = streamObj.value("nal_length_size").toInt(); stream.rFrameRate = streamObj.value("r_frame_rate").toString(); stream.avgFrameRate = streamObj.value("avg_frame_rate").toString(); stream.timeBase = streamObj.value("time_base").toString(); stream.startPts = streamObj.value("start_pts").toInt(); stream.startTime = streamObj.value("start_time").toString(); stream.durationTs = streamObj.value("duration_ts").toInt(); stream.duration = streamObj.value("duration").toString(); stream.bitRate = streamObj.value("bit_rate").toInt(); stream.bitsPerRawSample = streamObj.value("bits_per_raw_sample").toInt(); stream.nbFrames = streamObj.value("nb_frames").toInt(); // 解析disposition对象 QJsonObject dispositionObj = streamObj.value("disposition").toObject(); stream.disposition.defaultValue = dispositionObj.value("default").toInt(); stream.disposition.dub = dispositionObj.value("dub").toInt(); stream.disposition.original = dispositionObj.value("original").toInt(); stream.disposition.comment = dispositionObj.value("comment").toInt(); stream.disposition.lyrics = dispositionObj.value("lyrics").toInt(); stream.disposition.karaoke = dispositionObj.value("karaoke").toInt(); stream.disposition.forced = dispositionObj.value("forced").toInt(); stream.disposition.hearingImpaired = dispositionObj.value("hearing_impaired").toInt(); stream.disposition.visualImpaired = dispositionObj.value("visual_impaired").toInt(); stream.disposition.cleanEffects = dispositionObj.value("clean_effects").toInt(); stream.disposition.attachedPic = dispositionObj.value("attached_pic").toInt(); stream.disposition.timedThumbnails = dispositionObj.value("timed_thumbnails").toInt(); // 解析tags对象 QJsonObject tagsObj = streamObj.value("tags").toObject(); stream.tags.language = tagsObj.value("language").toString(); stream.tags.handlerName = tagsObj.value("handler_name").toString(); // 将Stream实例添加到vector中 streamVec.append(stream); // 打印解析结果 for (const Stream & stream : streamVec) { qDebug() << "Index:" << stream.index << "Codec Name:" << stream.codecName << "Codec Long Name:" << stream.codecLongName << "Profile:" << stream.profile << "Codec Type:" << stream.codecType << "Codec Time Base:" << stream.codecTimeBase << "Codec Tag String:" << stream.codecTagString << "Codec Tag:" << stream.codecTag << "Width:" << stream.width << "Height:" << stream.height << "Coded Width:" << stream.codedWidth << "Coded Height:" << stream.codedHeight << "Has B Frames:" << stream.hasBFrames << "Pixel Format:" << stream.pixFmt << "Level:" << stream.level << "Color Range:" << stream.colorRange << "Color Space:" << stream.colorSpace << "Color Transfer:" << stream.colorTransfer << "Color Primaries:" << stream.colorPrimaries << "Chroma Location:" << stream.chromaLocation << "Refs:" << stream.refs << "Is AVC:" << stream.isAVC << "NAL Length Size:" << stream.nalLengthSize << "R Frame Rate:" << stream.rFrameRate << "Avg Frame Rate:" << stream.avgFrameRate << "Time Base:" << stream.timeBase << "Start PTS:" << stream.startPts << "Start Time:" << stream.startTime << "Duration TS:" << stream.durationTs << "Duration:" << stream.duration << "Bitrate:" << stream.bitRate << "Bits per Raw Sample:" << stream.bitsPerRawSample << "Number of Frames:" << stream.nbFrames << "Disposition Default Value:" << stream.disposition.defaultValue << "Disposition Dub:" << stream.disposition.dub << "Disposition Original:" << stream.disposition.original << "Disposition Comment:" << stream.disposition.comment << "Disposition Lyrics:" << stream.disposition.lyrics << "Disposition Karaoke:" << stream.disposition.karaoke << "Disposition Forced:" << stream.disposition.forced << "Disposition Hearing Impaired:" << stream.disposition.hearingImpaired << "Disposition Visual Impaired:" << stream.disposition.visualImpaired << "Disposition Clean Effects:" << stream.disposition.cleanEffects << "Disposition Attached Pic:" << stream.disposition.attachedPic << "Disposition Timed Thumbnails:" << stream.disposition.timedThumbnails << "Tags Language:" << stream.tags.language << "Tags Handler Name:" << stream.tags.handlerName; } } return streamVec; }三、调用ffmpeg函数获取媒体信息如果在程序里不方便调用ffprobe.exe,那么也可以直接调用ffmpeg的函数,打开视频、音频解析媒体数据。【1】获取视频信息下面给出代码:#include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/dict.h> int main() { AVFormatContext *format_ctx = NULL; int ret; // 打开视频文件 ret = avformat_open_input(&format_ctx, "video.mp4", NULL, NULL); if (ret != 0) { printf("无法打开视频文件\n"); return -1; } // 获取视频文件中每个流的详细信息 ret = avformat_find_stream_info(format_ctx, NULL); if (ret < 0) { printf("无法获取视频流信息\n"); return -1; } // 输出视频流的详细信息 for (int i = 0; i < format_ctx->nb_streams; i++) { AVStream *stream = format_ctx->streams[i]; AVCodecParameters *params = stream->codecpar; AVRational time_base = stream->time_base; printf("流%d:\n", i); printf(" 时间基数:%d/Ý07e2b72-d5f3-497f-98d9-a12cdb25f805n", time_base.num, time_base.den); printf(" 编码器ID:Ý07e2b72-d5f3-497f-98d9-a12cdb25f805n", params->codec_id); printf(" 视频宽度:Ý07e2b72-d5f3-497f-98d9-a12cdb25f805n", params->width); printf(" 视频高度:Ý07e2b72-d5f3-497f-98d9-a12cdb25f805n", params->height); printf(" 帧率:%d/Ý07e2b72-d5f3-497f-98d9-a12cdb25f805n", stream->avg_frame_rate.num, stream->avg_frame_rate.den); } // 获取元数据(metadata) AVDictionaryEntry *tag = NULL; while ((tag = av_dict_get(format_ctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { printf("%s=%s\n", tag->key, tag->value); } // 关闭输入文件 avformat_close_input(&format_ctx); return 0; }这个代码片段可以在Linux或Windows操作系统上编译,并且需要在编译时链接FFmpeg库。【2】获取视频、音频详细信息#include <stdio.h> #include <libavformat/avformat.h> int main(int argc, char **argv) { AVFormatContext *fmt_ctx = NULL; AVDictionaryEntry *tag = NULL; // 打开输入媒体文件 if (avformat_open_input(&fmt_ctx, argv[1], NULL, NULL) < 0) { fprintf(stderr, "Cannot open input file\n"); return -1; } // 获取媒体文件信息 if (avformat_find_stream_info(fmt_ctx, NULL) < 0) { fprintf(stderr, "Cannot find stream information\n"); avformat_close_input(&fmt_ctx); return -1; } // 输出媒体文件信息 printf("File: %s\n", argv[1]); printf("Format: %s\n", fmt_ctx->iformat->name); printf("Duration: %lld seconds\n", fmt_ctx->duration / AV_TIME_BASE); for (int i = 0; i < fmt_ctx->nb_streams; i++) { AVStream *stream = fmt_ctx->streams[i]; const char *type = "Unknown"; if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { type = "Video"; printf("\n%s Stream #%d:\n", type, i); printf("Codec: %s\n", avcodec_get_name(stream->codecpar->codec_id)); printf("Resolution: %dxÝ07e2b72-d5f3-497f-98d9-a12cdb25f805n", stream->codecpar->width, stream->codecpar->height); printf("Frame Rate: %.2f fps\n", av_q2d(stream->avg_frame_rate)); printf("Bit Rate: %lld kbps\n", stream->codecpar->bit_rate / 1000); } else if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { type = "Audio"; printf("\n%s Stream #%d:\n", type, i); printf("Codec: %s\n", avcodec_get_name(stream->codecpar->codec_id)); printf("Sample Rate: %d Hz\n", stream->codecpar->sample_rate); printf("Channels: Ý07e2b72-d5f3-497f-98d9-a12cdb25f805n", stream->codecpar->channels); printf("Bit Rate: %lld kbps\n", stream->codecpar->bit_rate / 1000); } // 输出流的元数据信息 while ((tag = av_dict_get(stream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { printf("%s=%s\n", tag->key, tag->value); } } // 关闭输入媒体文件 avformat_close_input(&fmt_ctx); return 0; }使用方法:将示例代码保存为ffprobe.c文件。在命令行中进入该文件所在目录,执行以下命令进行编译:gcc -o ffprobe ffprobe.c -lavformat -lavcodec -lavutil执行以下命令获取视频或音频文件的全部参数信息:./ffprobe [input_file]其中,[input_file]是输入的视频或音频文件路径。例如,执行以下命令获取test.mp4视频文件的全部参数信息:./ffprobe test.mp4
-
一般在处理时间的时候,界面上显示,打印输出这些场景下,左边补0或者补空格占位是很常见的。补0或者补空格之后,长度是固定的;这样显示更加美观、不会因为数字变短、变长造成闪烁感。示例代码:int main() { printf("Ü9de7ea9-554e-479d-a4be-5ff20925f61dn",12345); //正常打印 printf("d\n",12345); //右对齐.位数不够,左边自动补空格 printf("%-10d,Ì9de7ea9-554e-479d-a4be-5ff20925f61dn", 12345,'A');//左对齐.位数不够,右边自动补空格 printf("0d\n",12345); //右对齐.位数不够,左边自动补0 //sprintf用法一样. return 0; } 输出结果: 12345 12345 12345 ,A 0000012345在vs2017里使用sprintf需要在属性--C/C++---预处理器---增加(_CRT_SECURE_NO_WARNINGS)案例: 将ms时间转为时分秒. 控制位数std::string MStoString(long nMicroSecond) { int second = nMicroSecond / 1000; int hours, mins, secs, minSecs; secs = second % 60; mins = (second / 60) % 60; hours = second / 3600; minSecs = nMicroSecond - (hours * 3600 + mins * 60 + secs) * 1000; char buff[1024]; //sprintf数字补0 sprintf(buff,"d:d:d.d", hours, mins, secs, minSecs); std::string strTime = buff; return strTime; } int main() { printf("%s\n", MStoString(50000).c_str()); return 0; }
-
一、环境介绍MCU: STM32F103ZET6光敏传感器: BH1750数字传感器(IIC接口)开发软件: Keil5代码说明: 使用IIC模拟时序驱动,方便移植到其他平台,采集的光照度比较灵敏. 合成的光照度返回值范围是 0~255。 0表示全黑 255表示很亮。实测: 手机闪光灯照着的状态返回值是245左右,手捂着的状态返回值是10左右. 二、BH1750介绍三、核心代码BH1750说明: ADDR引脚接地,地址就是0x463.1 iic.c#include "iic.h" /* 函数功能:IIC接口初始化 硬件连接: SDA:PB7 SCL:PB6 */ void IIC_Init(void) { RCC->APB2ENR|=1<<3;//PB GPIOB->CRL&=0x00FFFFFF; GPIOB->CRL|=0x33000000; GPIOB->ODR|=0x3<<6; } /* 函数功能:IIC总线起始信号 */ void IIC_Start(void) { IIC_SDA_OUTMODE(); //初始化SDA为输出模式 IIC_SDA_OUT=1; //数据线拉高 IIC_SCL=1; //时钟线拉高 DelayUs(4); //电平保持时间 IIC_SDA_OUT=0; //数据线拉低 DelayUs(4); //电平保持时间 IIC_SCL=0; //时钟线拉低 } /* 函数功能:IIC总线停止信号 */ void IIC_Stop(void) { IIC_SDA_OUTMODE(); //初始化SDA为输出模式 IIC_SDA_OUT=0; //数据线拉低 IIC_SCL=0; //时钟线拉低 DelayUs(4); //电平保持时间 IIC_SCL=1; //时钟线拉高 DelayUs(4); //电平保持时间 IIC_SDA_OUT=1; //数据线拉高 } /* 函数功能:获取应答信号 返 回 值:1表示失败,0表示成功 */ u8 IIC_GetACK(void) { u8 cnt=0; IIC_SDA_INPUTMODE();//初始化SDA为输入模式 IIC_SDA_OUT=1; //数据线上拉 DelayUs(2); //电平保持时间 IIC_SCL=0; //时钟线拉低,告诉从机,主机需要数据 DelayUs(2); //电平保持时间,等待从机发送数据 IIC_SCL=1; //时钟线拉高,告诉从机,主机现在开始读取数据 while(IIC_SDA_IN) //等待从机应答信号 { cnt++; if(cnt>250)return 1; } IIC_SCL=0; //时钟线拉低,告诉从机,主机需要数据 return 0; } /* 函数功能:主机向从机发送应答信号 函数形参:0表示应答,1表示非应答 */ void IIC_SendACK(u8 stat) { IIC_SDA_OUTMODE(); //初始化SDA为输出模式 IIC_SCL=0; //时钟线拉低,告诉从机,主机需要发送数据 if(stat)IIC_SDA_OUT=1; //数据线拉高,发送非应答信号 else IIC_SDA_OUT=0; //数据线拉低,发送应答信号 DelayUs(2); //电平保持时间,等待时钟线稳定 IIC_SCL=1; //时钟线拉高,告诉从机,主机数据发送完毕 DelayUs(2); //电平保持时间,等待从机接收数据 IIC_SCL=0; //时钟线拉低,告诉从机,主机需要数据 } /* 函数功能:IIC发送1个字节数据 函数形参:将要发送的数据 */ void IIC_WriteOneByteData(u8 data) { u8 i; IIC_SDA_OUTMODE(); //初始化SDA为输出模式 IIC_SCL=0; //时钟线拉低,告诉从机,主机需要发送数据 for(i=0;i<8;i++) { if(data&0x80)IIC_SDA_OUT=1; //数据线拉高,发送1 else IIC_SDA_OUT=0; //数据线拉低,发送0 IIC_SCL=1; //时钟线拉高,告诉从机,主机数据发送完毕 DelayUs(2); //电平保持时间,等待从机接收数据 IIC_SCL=0; //时钟线拉低,告诉从机,主机需要发送数据 DelayUs(2); //电平保持时间,等待时钟线稳定 data<<=1; //先发高位 } } /* 函数功能:IIC接收1个字节数据 返 回 值:收到的数据 */ u8 IIC_ReadOneByteData(void) { u8 i,data; IIC_SDA_INPUTMODE();//初始化SDA为输入模式 for(i=0;i<8;i++) { IIC_SCL=0; //时钟线拉低,告诉从机,主机需要数据 DelayUs(2); //电平保持时间,等待从机发送数据 IIC_SCL=1; //时钟线拉高,告诉从机,主机现在正在读取数据 data<<=1; if(IIC_SDA_IN)data|=0x01; DelayUs(2); //电平保持时间,等待时钟线稳定 } IIC_SCL=0; //时钟线拉低,告诉从机,主机需要数据 (必须拉低,否则将会识别为停止信号) return data; }3.2 iic.h#ifndef _IIC_H #define _IIC_H #include "stm32f10x.h" #include "sys.h" #include "delay.h" #define IIC_SDA_OUTMODE() {GPIOB->CRL&=0x0FFFFFFF;GPIOB->CRL|=0x30000000;} #define IIC_SDA_INPUTMODE() {GPIOB->CRL&=0x0FFFFFFF;GPIOB->CRL|=0x80000000;} #define IIC_SDA_OUT PBout(7) //数据线输出 #define IIC_SDA_IN PBin(7) //数据线输入 #define IIC_SCL PBout(6) //时钟线 void IIC_Init(void); void IIC_Start(void); void IIC_Stop(void); u8 IIC_GetACK(void); void IIC_SendACK(u8 stat); void IIC_WriteOneByteData(u8 data); u8 IIC_ReadOneByteData(void); #endif3.3 BH1750.h#ifndef _BH1750_H #define _BH1750_H #include "delay.h" #include "iic.h" #include "usart.h" u8 Read_BH1750_Data(void); #endif3.4 BH1750.c#include "bh1750.h" u8 Read_BH1750_Data() { unsigned char t0; unsigned char t1; unsigned char t; u8 r_s=0; IIC_Start(); //发送起始信号 IIC_WriteOneByteData(0x46); r_s=IIC_GetACK();//获取应答 if(r_s)printf("error:1\r\n"); IIC_WriteOneByteData(0x01); r_s=IIC_GetACK();//获取应答 if(r_s)printf("error:2\r\n"); IIC_Stop(); //停止信号 IIC_Start(); //发送起始信号 IIC_WriteOneByteData(0x46); r_s=IIC_GetACK();//获取应答 if(r_s)printf("error:3\r\n"); IIC_WriteOneByteData(0x01); r_s=IIC_GetACK();//获取应答 if(r_s)printf("error:4\r\n"); IIC_Stop(); //停止信号 IIC_Start(); //发送起始信号 IIC_WriteOneByteData(0x46); r_s=IIC_GetACK();//获取应答 if(r_s)printf("error:5\r\n"); IIC_WriteOneByteData(0x10); r_s=IIC_GetACK();//获取应答 if(r_s)printf("error:6\r\n"); IIC_Stop(); //停止信号 DelayMs(300); //等待 IIC_Start(); //发送起始信号 IIC_WriteOneByteData(0x47); r_s=IIC_GetACK();//获取应答 if(r_s)printf("error:7\r\n"); t0=IIC_ReadOneByteData(); //接收数据 IIC_SendACK(0); //发送应答信号 t1=IIC_ReadOneByteData(); //接收数据 IIC_SendACK(1); //发送非应答信号 IIC_Stop(); //停止信号 t=(((t0<<8)|t1)/1.2); return t; }3.5 main.c#include "stm32f10x.h" #include "led.h" #include "delay.h" #include "key.h" #include "usart.h" #include "at24c02.h" #include "bh1750.h" int main() { u8 val; LED_Init(); BEEP_Init(); KeyInit(); USARTx_Init(USART1,72,115200); IIC_Init(); while(1) { val=KeyScan(); if(val) { val=Read_BH1750_Data(); printf("光照强度=Ý4e5bbd4-21d6-4ce1-a196-44124210eb90r\n",val); // BEEP=!BEEP; LED0=!LED0; LED1=!LED1; } } } 3.6 运行效果图
-
一、环境介绍操作系统: win10 (64位)二、FTP介绍FTP (File Transfer Protocol) 可说是最古老的协议之一了,主要是用来进行文件的传输,尤其是大型文件的传输使用 FTP 更是方便。在FTP的使用当中,用户经常遇到两个概念:"下载"(Download)和"上载"(Upload)。"下载"文件就是从远程主机拷贝文件至自己的计算机上;"上载"文件就是将文件从自己的计算机中拷贝至远程主机上。用Internet 语言来说,用户可通过客户机程序向(从)远程主机上载(下载)文件。TCP/IP 协议中,FTP 标准命令 TCP 端口号为 21,Port 方式数据端口为 20。FTP 协议的任务是从一台计算机将文件传送到另一台计算机,它与这两台计算机所处的位置、联接的方式、甚至是是否使用相同的操作系统无关。假设两台计算机通过 ftp 协议对话,并且能访问 Internet, 你可以用 ftp 命令来传输文件。每种操作系统使用上有某一些细微差别,但是每种协议基本的命令结构是相同的。三、win10系统下搭建FTP服务器3.1 开启FTP服务器鼠标放在此电脑选项上,鼠标右键选择属性:进入控制面板:进入程序更改页面\启动windows自带的功能启动FTP服务器与客户端程序功能安装成功进入控制面板页面\所有控制面板选项: 选择管理工具:选择Internet管理器:鼠标右键选择添加FTP站点:设置站点名称与物理路径:设置本机IP地址:设置登录的用户权限查看FTP服务器状态3.2 登录FTP访问文件浏览器上直接访问FTP服务器站点:在浏览器上直接下载FTP站点的内容:电脑文件管理系统里访问FTP站点:3.3 安装FileZilla FTP客户端软件登录FTP服务器下载地址: cid:link_03.4 FTP服务器设置指定用户登录要设置FTP服务器使用指定的账户登录,需要先在windows上创建一个本地的新账户或者原来电脑的本地账户,用于FTP服务器登录。3.5 linux下登录FTP服务器站点(浏览器方式)说明: 下面Linux系统以Redhat6.3为例。如果Linux系统跑在VM虚拟机环境下,想要与windows系统进行通信,需要设置VM桥接到windows当前使用的网卡即可,可以手动设置IP地址在同一个网段。比如: windows系统当前使用的WIFI方式上网,IP地址为172.16.21.69。那么在VM虚拟机里就设置桥接模式,桥接到WIFI网卡上。在虚拟机设置里也设置成桥接模式。然后在命令行手动设置网卡IP地址:[wbyq@wbyq ~]$ sudo ifconfig eth0 172.16.21.123完支持ping一下windows的IP地址,测试网络是否畅通。能ping通windowsIP地址,就可以打开浏览器,直接访问FTP站点。3.6 linux系统下安装FTP软件登录FTP服务器站点3.6.1 安装FTP客户端软件红帽 6.3 系统光盘中自带 ftp 安装包,挂载红帽 6.3 光盘,找到 ftp 安装包安装即可。软件安装之后,在命令行就多了一个可用的ftp命令,用于登录FTP服务器站点。查看命令的帮助:[wbyq@wbyq ~]$ man ftp3.6.2 FTP命令登录FTP服务器[wbyq@wbyq ~]$ ftp <服务器的IP地址> 实名用户登录首先#ftp +IP(server)输入用户名(server的用户名)输入密码(server的密码)匿名用户登录#ftp +IP(server)用户名:anonymous (匿名用户固定的名字)密码:直接回车 (不用输入密码)实例:3.6.3 查看FTP命令帮助进入FTP命令行之后,输入一个?号即可看当前命令行支持的功能命令。3.6.4 文件的上传和下载文件的上传:#put filename(上传登录之前所在目录的内容)文件的下载:#get filename不允许下载目录,如果想操作目录,得先打包文件在登陆之前先确保当前所在目录3.6.5 退出服务器#bye #quit#exit 3.7 linux系统下安装lftp工具登录FTP服务器3.7.1 安装lftp工具#lftp 服务器ip //第一步#login //第二步#pwd#put //上传文件#mput filename filename //同时上传同个文件#get 下载文件#mget 下载多个文件#mirror 下载整个目录及其子目录#mirror -R 上传整个目录及其子目录3.7.2 登录FTP服务器站点如果FTP服务器支持匿名用户登录,直接输入服务器IP地址即可登录。2.7.3 文件和目录的上传上传单个文件使用put命令,用法格式: put <本地目录路径下将要上传的文件>示例:lftp 172.16.21.69:/> put ../work/nfs_restart.sh112 bytes transferred多个文件使用mput命令,用法格式: mput <本地文件1> <本地文件2> … ….示例:lftp 172.16.21.69:/> mput x264-master/x264.c x264-master/libx264.a9111669 bytes transferred in 1 second (8.32M/s) Total 2 files transferred整个目录使用mirror命令,加上-R参数。用法格式:mirror -R <本地目录路径>示例:lftp 172.16.21.69:/> mirror -R x264-master/New: 372 files, 0 symlinks60426636 bytes transferred in 8 seconds (6.98M/s)3.7.4 文件和目录的下载下载单个文件使用get命令,用法格式:get <服务器上的xx文件>示例:lftp 172.16.21.69:/> get F407-霸天虎原理图.pdf253762 bytes transferred下载多个文件使用mget命令,用法格式:mget <服务器上的xx文件1> <服务器上的xx文件1> …示例:lftp 172.16.21.69:/> mget libx264.a 123.h264 x264.c9392401 bytes transferred in 62 seconds (147.3K/s) Total 3 files transferred下载目录使用mirror命令,用法格式:mirror <服务器上的xx目录路径>示例:lftp 172.16.21.69:/> mirror X264_X86_Video/3.7.5 输入指定的用户名和密码登录FTP服务器如果访问的FTP服务器不支持匿名登录,就需要输入指定的账号密码登录.方式1: 直接登录格式: lftp 用户名:密码@ftp地址:传送端口(默认21-可以不填)示例: lftp 1126626497@qq.com:123456@192.168.2.16方式2: 使用命令行的login 命令登录[wbyq@wbyq mnt]$ lftp 192.168.2.16lftp 192.168.2.16:~> login 1126626497@qq.com 1234563.8 (关闭匿名登录)windows 下创建FTP服务器3.8.1 查看当前电脑的上的账号也可以创建新的账号专门用于FTP服务器访问。3.8.2 关闭匿名账号使用普通账号登录
-
Linux 是一个开源操作系统,可以移植到任意硬件平台,目前有很多物联网操作系统都基于Linux进行开发,从广义角度来看,Linux 是物联网生态系统的核心,从最小的物联网设备到边缘网关和云。最近一项由 Eclipse IoT 工作组、AGILE IoT、IEEE 和开放移动联盟赞助的在线调查发现,在物联网开发人员中,大约 72% 的受访者将 Linux 用于他们的物联网设备。其开源操作系统、可扩展性、安全特性和广泛的发行版等因素使 Linux 成为物联网开发的热门选择。物联网是通过约定的协议将原本独立存在的设备相互连接起来,并最终实现智能识别、定位、跟踪、监测、控制和管理的一种网络,无需人与人、或人与设备的互动。通俗来说物联网就是“物物相连的网”,主要应用于智能交通、智能医疗、智能家居、智能物流、智能电力等领域。目前物联网产业正在飞快发展着,从智能电视、智能家居、智能汽车、医疗健康、智能玩具、机器人等延伸到可穿戴设备领域。 物联网将赋能智能硬件向多元的消费场景渗透,从而创造更加便捷、舒适、安全、节能的生活环境。以智能家居为例,我们借助物联网可远程控制家庭里面的每一件智能家居,像电灯,电视,空调等,给我们的生活带来更多的便利。随着5G 技术的快速发展,物联网将应用到更多领域,由此可见物联网产业仍具有较大的发展空间。下面列出本月Linux驱动开发的帖子总汇地址:【1】Linux驱动开发-编写按键驱动 https://bbs.huaweicloud.com/forum/thread-0241111226447836021-1-1.html这篇文章介绍,如何使用杂项设备框架编写一个简单的按键驱动,完成编写、编译、安装、测试等流程,了解一个杂项字符设备驱动的开发流程。【2】Linux驱动开发-编写W25Q64(Flash)驱动 W25Q64是一颗SPI接口的Flash存储芯片,是华邦W25QXX系列里的一个具体型号,这个系列里包含了W25Q16,W25Q32,W25Q64,W5Q128等等。编程代码逻辑都差不多,主要是容量的区别。本篇文章就介绍如何在Linux系统下编写W25Q64芯片的驱动,完成数据存储,W25Q64支持标准SPI总线,当前驱动程序底层的代码写了两种方式,一种是采用内核提供的SPI子系统框架,一种直接采用软件模拟SPI时序的方式驱动。https://bbs.huaweicloud.com/forum/thread-0223111226920817030-1-1.html【3】Linux驱动开发-编写VS1053芯片音频驱动 VS1053是一款硬件编解码的音频芯片,提供SPI接口和IIS接口两种通信协议,这篇文章是介绍在Linux下如果模拟SPI时序来操作VS1053完成录音、播放音频歌曲功能。但是没有注册标准的音频驱动,没有对接音频框架,只是在驱动层完成VS1053的直接控制,本篇的重点主要是介绍如何初始化开发板的GPIO口,使用Linux的延时函数,模拟SPI时序,代码写了两种版本,一种是直接通过ioremap直接映射GPIO口地址,完成配置,一种是直接调用官方内核提供的库函数接口,完成GPIO口初始化,控制。当前采用的开发板是友善之臂的Tiny4412,芯片是三星的EXYNOS4412,这款芯片出来有很长一段时间了,之前用在三星的S系列手机上的,最高主频是1.5GZ,稳定推荐主频是1.4GHZ,内核是三星提供的demon,友善之臂在基础上完成了移植适配,也就是现在拿到的Tiny4412开发板内核,Linux 版本是3.5,不支持设备树。https://bbs.huaweicloud.com/forum/thread-0288111227483839022-1-1.html【4】Linux驱动开发-编写RFID-RC522射频刷卡模块驱动 MFRC522是应用于13.56MHz非接触式通信中高集成度的读写卡芯片,针对“三表”应用推出的一款低电压、低成本、体积小的非接触式读写卡芯片,是智能仪表和便携式手持设备研发的较好选择。便携式手持设备研发的较好选择。MFRC522利用了先进的调制和解调概念,集成了在13.56MHz下所有类型的被动非接触式通信方式和协议。支持14443A兼容应答器信号。数字部分处理ISO14443A帧和错误检测。此外,还支持快速CRYPTO1加密算法,用语验证MIFARE系列产品。MFRC522支持MI FARE系列更高速的非接触式通信,双向数据传输速率高达424kbit/s。作为13.56MHz高集成度读写卡系列芯片族的新成员,MFRC522与MF RC500和MFRC530有不少相似之处,同时也具备许多特点和差异。它与主机间通信采用SPI模式,有利于减少连线,缩小PCB板体积,降低成本。https://bbs.huaweicloud.com/forum/thread-0270111227547670025-1-1.html【5】Linux驱动开发-编写PCF8591(ADC)芯片驱动 PCF8591是一个IIC总线接口的ADC/DAC转换芯片,功能比较强大,这篇文章就介绍在Linux系统里如何编写一个PCF8591的驱动,完成ADC数据采集,DAC数据输出。https://bbs.huaweicloud.com/forum/thread-0223111227661816031-1-1.html【6】Linux驱动开发-编写OLED显示屏驱动 OLED显示屏在是智能手环,智能手表上用的非常的多,功耗低,不刺眼,优点特别多。本篇文章就介绍,在Linux系统里如何使用OLED显示屏,要使用OLED显示屏,大致分为两步: (1) 针对OLED显示屏编写一个驱动 (2) 编写应用层程序进行测试。采用的OLED显示屏是0.96寸SPI接口显示屏,分辨率是128*64,比较便宜,淘宝上非常多。测试开发板采用友善之臂Tiny4412,三星的EXYNOS-4412芯片,4核1.5GHZ,板载8G-EMMC,2G-DDR。https://bbs.huaweicloud.com/forum/thread-0201111227856826018-1-1.html【7】采用华为云IOT平台设计的高速公路多节点温度采集系统(STM32+NBIOT) 当前的场景是,在高速公路上部署温度采集设备,在高速路地表安装温度检测传感器,检测当前路段的路面实际温度。一段高速路上有多个地点需要采集温度数据。 采集温度数据需要上传到云平台进行数据存储,并且通过可视化界面展示温度变化曲线,支持查询最近几天的温度信息。https://bbs.huaweicloud.com/forum/thread-0254111722643458007-1-1.html【8】Linux驱动开发-编写NEC红外线协议解码驱动 光谱中波长自760nm至400um的电磁波称为红外线,它是一种不可见光。目前几乎所有的视频和音频设备都可以通过红外遥控的方式进行遥控,比如电视机、空调、影碟机等,都可以见到红外遥控的影子。这种技术应用广泛,相应的应用器件都十分廉价,因此红外遥控是我们日常设备控制的理想方式。红外线遥控是目前使用最广泛的一种通信和遥控手段。由于红外线遥控装置具有体积小、功耗低、功能强、 成本低等特点,因而,继彩电、录像机之后,在录音机、音响设备、空凋机以及玩具等其它小型电器装置上也纷 纷采用红外线遥控。工业设备中,在高压、辐射、有毒气体、粉尘等环境下,采用红外线遥控不仅完全可靠而且 能有效地隔离电气干扰。NEC协议是众多红外线协议中的一种,以前广泛用在电视机,投影仪设备里,很早之前经常说的万能电视遥控器就是NEC协议的。当前文章就介绍如何在Linux下通过红外线接收模块,编写一个NEC协议的红外线解码驱动,解析遥控器传输过来的各种控制指令,完成对应的动作响应;驱动里用到了外部中断接收数据,通过定时器计算间隔时间完成解码。https://bbs.huaweicloud.com/forum/thread-0234112465202130033-1-1.html【9】Linux驱动开发-编写MMA7660三轴加速度传感器 MMA7660FC 是 ± 1.5 克的三轴数字输出、超低功率、紧凑型电容式微电机的三轴加速度计,是非常低功耗,小型容性 MEMS 的传感器。具有低通滤波器,用于偏移和增益误差补偿, 以及用户可配置的转换成 6 位分辨率,用户可配置输出速率等功能。MMA7660芯片可以通过中断引脚(INT)向外通知传感器数据变化、方向、姿态识别等信息。模拟工作电压范围是 2.4V 至 3.6V,数字工作电压范围是 1.71V 到 3.6V 。常用在手机、掌上电脑、车载导航,便携式电脑的防盗,自动自行车刹车灯、运动检测手环、数码机、自动叫醒闹钟里等等。特别是计步的功能是现在最常见,不管是智能手环、还是手机都带有三轴加速度计,可以记录每天的步数,计算运动量等。现在很多的不倒翁,无人机、相机云台,很多常见的产品里都能看到三轴加速计的身影。通过MMA7660可以做出很多项目: 比如: 老人防跌倒手环、自行车自动刹车灯,智能闹钟,烤火炉跌倒自动断电、运动手环等等。这篇文章就介绍如何在Linux下编写MMA7660三轴加速度芯片的驱动,读取当前芯片的方向姿态,得到X,Y,Z三个轴的数据。MMA7660是IIC接口的,当前驱动就采用标准的IIC子系统编写驱动,使用字符设备框架将得到的数据上传递给应用层。https://bbs.huaweicloud.com/forum/thread-0234112465325735034-1-1.html【10】Linux驱动开发-编写FT5X06触摸屏驱动 这篇文章介绍在Linux下如何编写FT5X06系列芯片驱动,完成触摸屏的驱动开发, FT5X06是一个系列,当前使用的具体型号是FT5206,它是一个电容屏的触摸芯片,内置了8位的单片机(8051内核),完成了坐标换算等很多处理,在通过IIC,SPI方式传递给外部单片机。https://bbs.huaweicloud.com/forum/thread-0234112931665585061-1-1.html【11】Linux驱动开发-编写DS18B20驱动 当前文章介绍如何在Linux系统下编写一个DS18B20温度传感器驱动,测量环境温度,并将DS18B20注册成字符设备,通过文件接口将温度数据传递给应用层。当前使用的开发板是友善之臂的Tiny4412开发板,CPU是三星的Exynos-4412,主频是4核1.5GHZ,当前运行的Linux内核版本是3.5。使用的温度传感器是DS18B20,是一个数字温度传感器,非常经典的一款温度传感器,常年应用在各大高校毕设、实验室、毕设、课设场景。DS1820接线比较简单,只需要一根线就行,加上两根电源线,一共3根线,并且DS18B20支持硬件序列号寻址,支持一个IO口上挂载多个DS18B20。https://bbs.huaweicloud.com/forum/thread-0275112931782443061-1-1.html【12】Linux驱动开发-编写(EEPROM)AT24C02驱动 AT24C02是IIC接口的EEPROM存储芯片,这颗芯片非常经典,百度搜索可以找到非常多的资料,大多都是51、STM32单片机的示例代码,大多采用模拟时序、裸机系统运行。当前文章介绍在Linux系统里如何编写AT24C02的驱动,并且在应用层完成驱动读写测试,将AT24C02的存储空间映射成文件,在应用层,用户可以直接将AT24C02当做一个普通文件的形式进行读写,偏移文件指针;在Linux内核里有一套标准的IIC子系统框架专门读写IIC接口设备,采用平台设备模型框架,编写驱动非常方便。当前开发板采用友善之臂的Tiny4412,CPU是三星的EXYNOS4412,4412是三星的第一款四核处理器,主频是1.5GHZ,稳定频率是1.4GHZ。https://bbs.huaweicloud.com/forum/thread-0275112932059972062-1-1.html【13】Linux驱动开发-安装驱动参数传递 在Linux下进行C语言开发时,经常在命令行传递参数给C程序,常见的Linux命令也是需要传参的,这样用起来就很灵活,根据不同的参数可以执行不同的效果。Linux驱动安装时也支持传递参数,和命令行上运行的命令原理类似。只不过在编写驱动的时候,需要在驱动代码里提前将相关信息声明好才可以使用。这篇文章就介绍如果在命令安装驱动时,传递参数给驱动代码,演示各种类型的参数传输情况。https://bbs.huaweicloud.com/forum/thread-0267112932162172062-1-1.html【14】Linux驱动开发-proc接口介绍 Linux系统上的/proc目录是一种文件系统,即proc文件系统。与其它常见的文件系统不同的是,/proc是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态。当前的实验平台是嵌入式Linux开发板,根文件系统挂载成功后,进入命令就能看到proc目录,这个目录里正常情况下已经生成了很多文件。通过cat命令读取这些文件,可以得到很多内核的信息。比如:查看中断有哪些注册了,中断从上电到现在响应了多少次,杂项设备注册了哪些,帧缓冲节点有哪些,RTC时间查看,等等。https://bbs.huaweicloud.com/forum/thread-0236112948111487053-1-1.html
-
1. 前言Linux系统上的/proc目录是一种文件系统,即proc文件系统。与其它常见的文件系统不同的是,/proc是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态。当前的实验平台是嵌入式Linux开发板,根文件系统挂载成功后,进入命令就能看到proc目录,这个目录里正常情况下已经生成了很多文件。通过cat命令读取这些文件,可以得到很多内核的信息。比如:查看中断有哪些注册了,中断从上电到现在响应了多少次,杂项设备注册了哪些,帧缓冲节点有哪些,RTC时间查看,等等。下面是proc目录下文件的功能的详细介绍(资源来源与网络):2.1、/proc/apm 高级电源管理(APM)版本信息及电池相关状态信息,通常由apm命令使用;2.2、/proc/buddyinfo 用于诊断内存碎片问题的相关信息文件;2.3、/proc/cmdline 在启动时传递至内核的相关参数信息,这些信息通常由lilo或grub等启动管理工具进行传递;2.4、/proc/cpuinfo 处理器的相关信息的文件;2.5、/proc/crypto 系统上已安装的内核使用的密码算法及每个算法的详细信息列表;2.6、/proc/devices 系统已经加载的所有块设备和字符设备的信息,包含主设备号和设备组(与主设备号对应的设备类型)名;2.7、/proc/diskstats 每块磁盘设备的磁盘I/O统计信息列表;(内核2.5.69以后的版本支持此功能)2.8、/proc/dma 每个正在使用且注册的ISA DMA通道的信息列表;2.9、/proc/execdomains 内核当前支持的执行域(每种操作系特“个性”)信息列表;2.10、/proc/fb 帧缓冲设备列表文件,包含帧缓冲设备的设备号和相关驱动信息;2.11、/proc/filesystems 当前被内核支持的文件系统类型列表文件,被标示为nodev的文件系统表示不需要块设备的支持;通常mount一个设备时,如果没有指定文件系统类型将通过此文件来决定其所需文件系统的类型;2.12、/proc/interrupts X86或X86_64体系架构系统上每个IRQ相关的中断号列表;多路处理器平台上每个CPU对于每个I/O设备均有自己的中断号;2.13、/proc/iomem 每个物理设备上的记忆体(RAM或者ROM)在系统内存中的映射信息;2.14、/proc/ioports 当前正在使用且已经注册过的与物理设备进行通讯的输入-输出端口范围信息列表;如下面所示,第一列表示注册的I/O端口范围,其后表示相关的设备;2.15、/proc/kallsyms 模块管理工具用来动态链接或绑定可装载模块的符号定义,由内核输出;(内核2.5.71以后的版本支持此功能);通常这个文件中的信息量相当大;2.16、/proc/kcore 系统使用的物理内存,以ELF核心文件(core file)格式存储,其文件大小为已使用的物理内存(RAM)加上4KB;这个文件用来检查内核数据结构的当前状态,因此,通常由GBD通常调试工具使用,但不能使用文件查看命令打开此文件;2.17、/proc/kmsg 此文件用来保存由内核输出的信息,通常由/sbin/klogd或/bin/dmsg等程序使用,不要试图使用查看命令打开此文件;2.18、/proc/loadavg 保存关于CPU和磁盘I/O的负载平均值,其前三列分别表示每1秒钟、每5秒钟及每15秒的负载平均值,类似于uptime命令输出的相关信息;第四列是由斜线隔开的两个数值,前者表示当前正由内核调度的实体(进程和线程)的数目,后者表示系统当前存活的内核调度实体的数目;第五列表示此文件被查看前最近一个由内核创建的进程的PID;2.19、/proc/locks 保存当前由内核锁定的文件的相关信息,包含内核内部的调试数据;每个锁定占据一行,且具有一个惟一的编号;如下输出信息中每行的第二列表示当前锁定使用的锁定类别,POSIX表示目前较新类型的文件锁,由lockf系统调用产生,FLOCK是传统的UNIX文件锁,由flock系统调用产生;第三列也通常由两种类型,ADVISORY表示不允许其他用户锁定此文件,但允许读取,MANDATORY表示此文件锁定期间不允许其他用户任何形式的访问;2.20、/proc/mdstat 保存RAID相关的多块磁盘的当前状态信息,在没有使用RAID机器上,其显示为如下状态:2.21、/proc/meminfo 系统中关于当前内存的利用状况等的信息,常由free命令使用;可以使用文件查看命令直接读取此文件,其内容显示为两列,前者为统计属性,后者为对应的值;2.22、/proc/mounts 在内核2.4.29版本以前,此文件的内容为系统当前挂载的所有文件系统,在2.4.19以后的内核中引进了每个进程使用独立挂载名称空间的方式,此文件则随之变成了指向/proc/self/mounts(每个进程自身挂载名称空间中的所有挂载点列表)文件的符号链接;/proc/self是一个独特的目录,后文中会对此目录进行介绍;2.23、/proc/modules 当前装入内核的所有模块名称列表,可以由lsmod命令使用,也可以直接查看;如下所示,其中第一列表示模块名,第二列表示此模块占用内存空间大小,第三列表示此模块有多少实例被装入,第四列表示此模块依赖于其它哪些模块,第五列表示此模块的装载状态(Live:已经装入;Loading:正在装入;Unloading:正在卸载),第六列表示此模块在内核内存(kernel memory)中的偏移量;2.24、/proc/partitions 块设备每个分区的主设备号(major)和次设备号(minor)等信息,同时包括每个分区所包含的块(block)数目(如下面输出中第三列所示);2.25、/proc/pci 内核初始化时发现的所有PCI设备及其配置信息列表,其配置信息多为某PCI设备相关IRQ信息,可读性不高,可以用“/sbin/lspci –vb”命令获得较易理解的相关信息;在2.6内核以后,此文件已为/proc/bus/pci目录及其下的文件代替;2.26、/proc/slabinfo 在内核中频繁使用的对象(如inode、dentry等)都有自己的cache,即slab pool,而/proc/slabinfo文件列出了这些对象相关slap的信息;详情可以参见内核文档中slapinfo的手册页;2.27、/proc/stat 实时追踪自系统上次启动以来的多种统计信息;如下所示,其中, “cpu”行后的八个值分别表示以1/100(jiffies)秒为单位的统计值(包括系统运行于用户模式、低优先级用户模式,运系统模式、空闲模式、I/O等待模式的时间等); “intr”行给出中断的信息,第一个为自系统启动以来,发生的所有的中断的次数;然后每个数对应一个特定的中断自系统启动以来所发生的次数; “ctxt”给出了自系统启动以来CPU发生的上下文交换的次数。 “btime”给出了从系统启动到现在为止的时间,单位为秒; “processes (total_forks) 自系统启动以来所创建的任务的个数目; “procs_running”:当前运行队列的任务的数目; “procs_blocked”:当前被阻塞的任务的数目;2.28、/proc/swaps 当前系统上的交换分区及其空间利用信息,如果有多个交换分区的话,则会每个交换分区的信息分别存储于/proc/swap目录中的单独文件中,而其优先级数字越低,被使用到的可能性越大;下面是作者系统中只有一个交换分区时的输出信息;2.29、/proc/uptime 系统上次启动以来的运行时间,如下所示,其第一个数字表示系统运行时间,第二个数字表示系统空闲时间,单位是秒;2.30、/proc/version 当前系统运行的内核版本号,在作者的RHEL5.3上还会显示系统安装的gcc版本,如下所示;2.31、/proc/vmstat 当前系统虚拟内存的多种统计数据,信息量可能会比较大,这因系统而有所不同,可读性较好;下面为作者机器上输出信息的一个片段;(2.6以后的内核支持此文件)2.32、/proc/zoneinfo 内存区域(zone)的详细信息列表,信息量较大2. 获取CPU使用率下面这份代码是利用/proc/stat文件获取当前CPU的占用率详细信息,通过C语言代码读取数据后,进行分析,处理。#include <stdio.h> #include <stdlib.h> #include <unistd.h> typedef struct cpu_occupy_ //定义一个cpu occupy的结构体 { char name[20]; //定义一个char类型的数组名name有20个元素 unsigned int user; //定义一个无符号的int类型的user unsigned int nice; //定义一个无符号的int类型的nice unsigned int system; //定义一个无符号的int类型的system unsigned int idle; //定义一个无符号的int类型的idle unsigned int iowait; unsigned int irq; unsigned int softirq; }cpu_occupy_t; double cal_cpuoccupy (cpu_occupy_t *o, cpu_occupy_t *n) { double od, nd; double id, sd; double cpu_use ; od = (double) (o->user + o->nice + o->system +o->idle+o->softirq+o->iowait+o->irq);//第一次(用户+优先级+系统+空闲)的时间再赋给od nd = (double) (n->user + n->nice + n->system +n->idle+n->softirq+n->iowait+n->irq);//第二次(用户+优先级+系统+空闲)的时间再赋给od id = (double) (n->idle); //用户第一次和第二次的时间之差再赋给id sd = (double) (o->idle) ; //系统第一次和第二次的时间之差再赋给sd if((nd-od) != 0) cpu_use =100.0 - ((id-sd))/(nd-od)*100.00; //((用户+系统)乖100)除(第一次和第二次的时间差)再赋给g_cpu_used else cpu_use = 0; return cpu_use; } void get_cpuoccupy (cpu_occupy_t *cpust) { FILE *fd; int n; char buff[256]; cpu_occupy_t *cpu_occupy; cpu_occupy=cpust; fd = fopen ("/proc/stat", "r"); if(fd == NULL) { perror("fopen:"); exit (0); } fgets (buff, sizeof(buff), fd); sscanf (buff, "%s %u %u %u %u %u %u %u", cpu_occupy->name, &cpu_occupy->user, &cpu_occupy->nice,&cpu_occupy->system, &cpu_occupy->idle ,&cpu_occupy->iowait,&cpu_occupy->irq,&cpu_occupy->softirq); fclose(fd); } double get_sysCpuUsage() { cpu_occupy_t cpu_stat1; cpu_occupy_t cpu_stat2; double cpu; get_cpuoccupy((cpu_occupy_t *)&cpu_stat1); sleep(1); //第二次获取cpu使用情况 get_cpuoccupy((cpu_occupy_t *)&cpu_stat2); //计算cpu使用率 cpu = cal_cpuoccupy ((cpu_occupy_t *)&cpu_stat1, (cpu_occupy_t *)&cpu_stat2); return cpu; } int main(int argc,char **argv) { while(1) { printf("CPU占用率:õ36ecc0e-ec3a-4858-a0b4-b5817b482b88n",get_sysCpuUsage()); } return 0; }3. proc驱动相关接口Proc文件接口,主要用于驱动代码调试,获取内核信息,可以直接使用cat命令访问proc目录下的对应文件接口即可。需要使用的头文件:#include <linux/proc_fs.h> #include <linux/fs.h>下面介绍内核里proc接口实现的相关函数接口:1. 在proc目录下创建子目录函数 static inline struct proc_dir_entry *proc_mkdir(const char *name,struct proc_dir_entry *parent) 示例: //注意只能创建单层目录 //在proc目录下创建aaa文件夹 proc_mkdir("aaa",NULL); 2. 在proc目录下创建文件 static inline struct proc_dir_entry *proc_create(const char *name, //文件名称 umode_t mode, //模式,默认为0 struct proc_dir_entry *parent, //父目录结构 const struct file_operations *proc_fops) //文件集合 示例: //在proc目录下创建一个文件 proc_create("aaa/tiny4412_proc_test", 0, NULL, &fops_proc); 3. 删除proc目录下之前创建的文件或者目录 void remove_proc_entry(const char *name, //文件的路径 struct proc_dir_entry *parent //父目录结构 ) 示例: remove_proc_entry("aaa/tiny4412_proc_test", NULL); 注意: 如果是删除目录,需要先把目录下的文件删除掉,每次删除必须保证目录是空的。4. 编写proc接口测试驱动4.1 案例1下面驱动代码注册之后,会在proc目录下创建一个tiny4412_proc文件,通过cat读取这个文件,可以打印驱动代码里设置好的信息。驱动卸载时会删除这个tiny4412_proc文件。#include <linux/kernel.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/delay.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/proc_fs.h> static int tiny4412_open(struct inode *inode, struct file *file) { printk("tiny4412_open ok\n"); return 0; } static ssize_t tiny4412_read(struct file *file, char __user *buf, size_t cnt, loff_t *loff) { copy_to_user(buf,"123456",6); printk("tiny4412_read调用成功.\n"); return 0; } static int tiny4412_release(struct inode *inode, struct file *file) { return 0; } static struct file_operations tiny4412_fops= { .open=tiny4412_open, .read=tiny4412_read, .release=tiny4412_release, }; static int __init tiny4412_init(void) { proc_mkdir("wbyq",0); /*创建内核接口: proc 存放内核信息*/ proc_create("wbyq/tiny4412_proc",0, NULL, &tiny4412_fops); printk("驱动安装成功.\n"); return 0; } static void __exit tiny4412_exit(void) { remove_proc_entry("wbyq/tiny4412_proc", NULL); remove_proc_entry("wbyq", NULL); printk("驱动卸载成功.\n"); } /*驱动的入口:insmod xxx.ko*/ module_init(tiny4412_init); /*驱动的出口: rmmod xxx.ko*/ module_exit(tiny4412_exit); /*模块的许可证*/ MODULE_LICENSE("GPL"); /*模块的作者*/ MODULE_AUTHOR("wbyq");4.2 案例2下面这份代码是在字符设备框架代码里增加了proc接口,驱动安装之后,会在proc目录下创建tiny4412_proc文件,通过cat命令读取tiny4412_proc文件,可以打印出当前主设备号下所有的子设备信息。#include <linux/kernel.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/io.h> #include <asm/uaccess.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/device.h> #include <linux/mutex.h> #include <linux/list.h> #include <linux/proc_fs.h> static struct class *tiny4412_beep_class; static unsigned int major=0; //主设备号 static LIST_HEAD(tiny4412_beep_list); //链表头 static DEFINE_MUTEX(tiny4412_beep_mtx); //互斥锁 #define DYNAMIC_MINORS 64 /* like dynamic majors */ static DECLARE_BITMAP(beep_minors, DYNAMIC_MINORS); struct tiny4412_beep_device { int minor; /*次设备号*/ const char *name; /*设备节点的名称*/ const struct file_operations *fops; /*文件操作集合*/ struct list_head list; //链表 }; int tiny4412_beep_register(struct tiny4412_beep_device *beep_dev) { struct tiny4412_beep_device *c; dev_t dev; INIT_LIST_HEAD(&beep_dev->list); mutex_lock(&tiny4412_beep_mtx); //查找传入的次设备号是否冲突 list_for_each_entry(c, &tiny4412_beep_list, list) { if(c->minor == beep_dev->minor) { mutex_unlock(&tiny4412_beep_mtx); return -EBUSY; } } //自动分配 if(beep_dev->minor == MISC_DYNAMIC_MINOR) { int i = find_first_zero_bit(beep_minors,DYNAMIC_MINORS); if (i >= DYNAMIC_MINORS) { mutex_unlock(&tiny4412_beep_mtx); return -EBUSY; } beep_dev->minor = DYNAMIC_MINORS - i - 1; set_bit(i,beep_minors); } //合成设备号 dev = MKDEV(major, beep_dev->minor); //创建设备节点 device_create(tiny4412_beep_class,NULL,dev,NULL,"%s", beep_dev->name); list_add(&beep_dev->list,&tiny4412_beep_list); //解锁 mutex_unlock(&tiny4412_beep_mtx); return 0; } int tiny4412_beep_deregister(struct tiny4412_beep_device *beep_dev) { int i = DYNAMIC_MINORS - beep_dev->minor - 1; mutex_lock(&tiny4412_beep_mtx); list_del(&beep_dev->list); //将dev目录下的文件删除掉 device_destroy(tiny4412_beep_class, MKDEV(major, beep_dev->minor)); if (i < DYNAMIC_MINORS && i >= 0) clear_bit(i, beep_minors); mutex_unlock(&tiny4412_beep_mtx); return 0; } EXPORT_SYMBOL_GPL(tiny4412_beep_register); EXPORT_SYMBOL_GPL(tiny4412_beep_deregister); //底层open函数 static int tiny4412_beep_open(struct inode * inode, struct file * file) { //得到次设备号 int minor = iminor(inode); struct tiny4412_beep_device *c; struct file_operations *new_fops,*old_fops; mutex_lock(&tiny4412_beep_mtx); //遍历链表--找到链表里相同的次设备号 list_for_each_entry(c,&tiny4412_beep_list, list) { if (c->minor == minor) { new_fops = fops_get(c->fops); //得到47次设备号对应的结构体地址 break; } } file->f_op = new_fops; //改变指向--文件操作集合的指向 if(file->f_op->open) { file->f_op->open(inode,file); } fops_put(old_fops); mutex_unlock(&tiny4412_beep_mtx); return 0; } static const struct file_operations tiny4412_beep_fops = { .owner = THIS_MODULE, .open = tiny4412_beep_open, }; static ssize_t tiny4412_read(struct file *file, char __user *buf, size_t cnt, loff_t *loff) { struct tiny4412_beep_device *c; //遍历链表--找到链表里相同的次设备号 list_for_each_entry(c,&tiny4412_beep_list, list) { printk("%d %s\n",c->minor,c->name); } return 0; } static struct file_operations tiny4412_fops= { .read=tiny4412_read, }; static int __init tiny4412_beep_class_init(void) { /*1. 创建设备类*/ tiny4412_beep_class=class_create(THIS_MODULE,"tiny4412_beep"); /*2. 注册字符设备*/ major=register_chrdev(0,"tiny4412_beep",&tiny4412_beep_fops); proc_mkdir("wbyq",0); /*创建内核接口: proc 存放内核信息*/ proc_create("wbyq/tiny4412_proc",0, NULL, &tiny4412_fops); return 0; } static void __exit tiny4412_beep_class_cleanup(void) { remove_proc_entry("wbyq/tiny4412_proc", NULL); remove_proc_entry("wbyq", NULL); //注销设备类 class_destroy(tiny4412_beep_class); //注销字符设备 unregister_chrdev(major,"tiny4412_beep"); } module_init(tiny4412_beep_class_init); module_exit(tiny4412_beep_class_cleanup); MODULE_LICENSE("GPL"); MODULE_AUTHOR("wbyq");
推荐直播
-
华为云码道 × 仓颉编程:工程化AI编码探索2026/05/27 周三 19:00-21:00
刘俊杰-华为云仓颉语言专家/李炎-华为云码道技术专家/王智鹏-OpenCangjie开源社区发起人
本场直播围绕华为云仓颉语言与华为云码道的深度结合,展示华为云智能编程从零基础到高效落地的完整生态能力。以华为云码道为引擎,仓颉语言为载体,带给大家日常提效、趣味创新到极速量产的开发体验。
回顾中
热门标签