• [技术干货] 单片机与阿里云的mqtt简单通信(塔石nb-iot篇)-转载
     这篇文章主要记录了一次单片机通过塔石的模块,实现与阿里云的mqtt通信(仅能简单传递信息,不涉及更深入操作)的过程。  其中会包括如,阿里云物联网平台中“产品”与“设备”的创建,塔石nb-iot模块的使用等。  前期准备: 1.塔石nb-iot模块 (E33V-DTU带天线)  2.stm32g431(stm32F1系列等均可)  3.阿里云账号  4.一张流量卡(主要为了塔石模块能连网)  阿里云部分 1.登录阿里云后,在产品中查找“物联网平台”并进入。    2.进入“管理控制台”   3.进入“公共实例”   4.创建一个新“产品”   5.给你的产品取一个名字(例如我的“HDUGEEK”), 同时因为我们只打算实现简单的通信,所以所属品类自定义即可。    6.为了让之后我们的通信过程更“简单”(自认为),点击产品右边的“查看”,再找到“Topic类列表”“自定义Topic”,添加两个具有“发布和订阅”权限的topic,“read”,“say”分别用于接受单片机的信息,向单片机传递信息。  如果想了解更多有关Topic内容,可以看看推荐视频的 P1 ~ P3 ,虽然很长但都是干货,看完能帮你迅速理解mqtt原理。  推荐视频:【物联网MQTT协议解析、报文构造、程序设计、项目实战】 https://www.bilibili.com/video/BV1Jz4y1X7aH/?share_source=copy_web&vd_source=e1c657085d91a6a8e457baf141d1a49a    7.接下来我们要创建属于该产品下的设备。(我的设备取名“geek”)  当设备刚创建时会显示“未激活”,不过等我们第一次连上设备后就不会再显示这个了。    8.同样查看我们的设备信息,找到“MQTT连接参数”,这是后面会用到的重要数据。    塔石部分 (有关塔石模块的资料可以找商家要)  1.为我们的塔石模块连好天线,插上流量卡。根据“产品手册”中的引脚描述,将塔石通过usb转ttl模块与电脑相连。      2.打开塔石的配置工具,选择产品型号后进行参数配置。    3.把通道1的工作模式改为MQTT透传,将之前设备的MQTT参数填入对应位置。   4.将之前产品中我们自定义的Topic“say”复制到订阅参数中,“read”复制到推送参数中。记得把${deviceName}改为自己的设备名(如"geek")       5.打开串口,点击"进入配置状态""一键配置参数"来保存配置,再“退出配置状态”,等待塔石模块自动连上设备。   6. 回到我们阿里云设备的界面,找到Topic列表,发布一个消息(如“123”)。  可以看到在塔石的配置工具中我们成功收到了这个消息。     7.直接在配置工具中发送4567,接着就能在日志服务中看到塔石发送到阿里云的消息了    单片机部分         我们已经成功在电脑上利用配置工具实现了塔石与阿里云的简单通讯,也完成了对塔石模块的参数配置,接下来就可以尝试直接把塔石连接到单片机上。          单片机与塔石可以采用串口通信,这样之后,单片机传给塔石模块的信息就会“原封不动”的传给阿里云,而塔石模块收到的,来自阿里云的信息也会通过串口传给单片机。          实现这一切只需要你熟悉单片机的串口发送与串口接受,因为比较简单,所以不再赘述。  虽然我们只是简单实现了“对话”,但大家想必也能感受到其中巨大的可能性。希望未来你实现了更加复杂,更加实用的项目时,这篇文章曾为你提供过一点点帮助。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/HDUGEEK/article/details/130304819 
  • [技术干货] 传感器技术:STC89C52单片机读取MPU6050姿态数据
    MPU6050是一个6轴姿态传感器,能够测量芯片自身的X、Y、Z轴的加速度以及角速度参数。通过数据融合,它可以进一步得到姿态角。这种传感器常应用于平衡车、飞行器等需要检测自身姿态的场景。MPU6050由两部分组成:3轴加速度计和3轴陀螺仪传感器。3轴加速度计用于测量X、Y、Z轴的加速度,而3轴陀螺仪传感器则用于测量X、Y、Z轴的角速度。这使得MPU6050能够全面感知设备的运动状态。在MPU6050中,陀螺仪的工作原理基于一个物理原理:一个旋转物体的旋转轴所指的方向在不受外力影响时是不会改变的。这个原理使得陀螺仪能够保持方向,进而读取轴所指示的方向,并自动将数据信号传给控制系统。而加速度传感器则是一种能够测量加速度的传感器,通常由质量块、阻尼器、弹性元件、敏感元件和适调电路等部分组成。在加速过程中,通过对质量块所受惯性力的测量,利用牛顿第二定律,传感器能够获得加速度值。此外,MPU6050还具有一些重要参数。它采用16位ADC采集传感器的模拟信号,其量化范围为-32768到32767。加速度计和陀螺仪都有多种满量程选择,可以根据实际应用场景进行调整。MPU6050还具有可配置的数字低通滤波器、时钟源和采样分频等功能,这些功能使得MPU6050能够适应不同的应用需求。由于其高性能、小型化、低功耗等特点,MPU6050被广泛应用于各种需要运动跟踪和姿态解算的智能设备中,如无人机、机器人、智能手机、平板电脑和可穿戴设备等。在无人机中,MPU6050可以提供角速度和加速度数据,帮助无人机实现精确的运动控制;在机器人中,MPU6050可以实现姿态感知和运动控制。以下是使用STC89C52单片机读取MPU6050姿态数据的简单示例代码:#include <reg52.h>#include <intrins.h>#define MPU6050_ADDRESS 0xD0 // MPU6050 I2C地址(写入模式)#define MPU6050_RA_ACCEL_XOUT_H 0x3B // 加速度传感器X轴高位寄存器地址#define MPU6050_RA_PWR_MGMT_1 0x6B // 电源管理寄存器地址#define MPU6050_PWR1_CLKSEL_BIT 2 // CLKSEL位在电源管理寄存器中的位置#define MPU6050_PWR1_CLKSEL_LENGTH 3 // CLKSEL位的位长#define MPU6050_CLOCK_PLL_XGYRO 0x01 // PLL以X轴陀螺仪为时钟源sbit SCL = P2^1; // I2C时钟线sbit SDA = P2^0; // I2C数据线void delay_ms(unsigned int ms) { unsigned int i, j; for (i = 0; i < ms; i++) for (j = 0; j < 110; j++);}void I2C_start() { SDA = 1; _nop_(); SCL = 1; _nop_(); SDA = 0; _nop_(); SCL = 0; _nop_();}void I2C_stop() { SDA = 0; _nop_(); SCL = 1; _nop_(); SDA = 1; _nop_(); SCL = 0; _nop_();}void I2C_write(uint8_t data) { uint8_t i; for (i = 0; i < 8; i++) { SDA = (data & 0x80) >> 7; _nop_(); SCL = 1; _nop_(); SCL = 0; _nop_(); data <<= 1; } SDA = 1; _nop_(); SCL = 1; _nop_(); SCL = 0; _nop_();}uint8_t I2C_read() { uint8_t i, data = 0; SDA = 1; for (i = 0; i < 8; i++) { _nop_(); SCL = 1; _nop_(); data = (data << 1) | SDA; _nop_(); SCL = 0; } return data;}void MPU6050_init() { I2C_start(); I2C_write(MPU6050_ADDRESS); I2C_write(MPU6050_RA_PWR_MGMT_1); I2C_write(MPU6050_CLOCK_PLL_XGYRO); I2C_stop();}int read_accel_x() { int accel_x; I2C_start(); I2C_write(MPU6050_ADDRESS); I2C_write(MPU6050_RA_ACCEL_XOUT_H); I2C_start(); I2C_write(MPU6050_ADDRESS | 1); accel_x = ((int)I2C_read() << 8) | I2C_read(); I2C_stop(); return accel_x;}void main() { int accel_x; MPU6050_init(); while (1) { accel_x = read_accel_x(); // 处理加速度X轴数据 // 在这里可以添加你的代码来处理加速度数据,比如计算姿态等 // 注意:这里仅仅读取了加速度X轴数据,如果需要完整的姿态数据,需要读取加速度Y轴和Z轴数据以及陀螺仪数据,并进行相应的处理 delay_ms(100); // 延时等待,可以根据需要调整 }}
  • [技术干货] 物联网里常用的协议介绍
    物联网中常用的协议包括多种,每种协议都有其特定的应用场景和优势。以下是一些主要的物联网协议及其详细介绍:MQTT协议:MQTT(Message Queuing Telemetry Transport)是一种轻量级的发布/订阅模式的消息传输协议,专为低带宽、高延迟或不稳定的网络环境设计。它工作在TCP/IP协议族上,以极少的代码和带宽为远程设备提供实时可靠的消息服务。MQTT协议广泛应用于物联网、移动应用、小型设备、智能家电、传感器等领域。CoAP协议:CoAP(Constrained Application Protocol)是一种针对受限设备的应用层协议,特别适用于低功耗、低带宽的物联网环境。它类似于Web的HTTP协议,但为物联网设备做了优化,支持请求/响应模型,并提供了可靠传输、数据重传和块传输等功能。CoAP适用于资源受限的物联网设备,如传感器、智能电表等。LWM2M协议:LWM2M(Lightweight Machine to Machine)是一种物联网设备管理协议,用于远程管理和监控物联网设备。它定义了设备管理的基本功能和接口,使得物联网设备可以被远程配置、监控和更新。ZigBee协议:ZigBee是一种低功耗、近距离的无线通信协议,适用于家庭自动化和工业控制领域。它采用RF频段进行通信,能够实现设备之间的互联和远程控制,具有低复杂度、自组织、高可靠性和低成本等特点。Bluetooth协议:Bluetooth是一种短距离无线通信协议,广泛应用于手机、电脑、耳机等个人消费电子设备之间的通信。蓝牙技术提供了设备间的无线连接和数据传输功能,使得设备可以方便地进行数据交换和协同工作。物联网中还有其他一些常用的协议,如LoRaWAN(用于低功耗广域网通信)、Wi-Fi(用于无线局域网通信)、RFID(无线射频识别)等。实现这些物联网协议时,具体的传感器选择会根据应用场景、设备特性以及协议的要求而有所不同。以下是一些常见的传感器,以及它们如何与上述协议结合使用:MQTT协议MQTT协议由于其轻量级和发布/订阅的特性,适用于多种传感器类型。例如:温度传感器:用于监测环境温度,通过MQTT协议发布温度数据。湿度传感器:用于监测环境湿度,同样可以通过MQTT协议发布数据。运动传感器:用于检测物体的移动或人体的活动,通过MQTT发送触发信号。CoAP协议由于CoAP是为受限设备设计的,它特别适用于低功耗、低带宽的传感器。例如:光照传感器:用于检测环境光照强度,并通过CoAP协议与其他设备通信。土壤湿度传感器:在农业物联网中,用于监测土壤湿度,通过CoAP协议发送数据。LWM2M协议LWM2M协议通常用于需要远程管理和监控的设备。以下传感器可以与其结合使用:压力传感器:用于监测管道压力、气体压力等,数据通过LWM2M协议上传至远程服务器。电量传感器:用于监测电池电量或设备的能耗,通过LWM2M进行远程监控。ZigBee协议ZigBee协议因其低功耗和近距离通信的特性,常与以下传感器结合使用:烟雾传感器:用于检测烟雾,通常在火灾报警系统中使用,通过ZigBee与其他报警设备联动。气体传感器:用于检测空气中的有害气体,通过ZigBee协议发送警报信号。Bluetooth协议Bluetooth协议常用于个人消费电子设备,因此与以下传感器结合较多:心率传感器:在健身设备或健康监测设备中使用,通过Bluetooth将数据传输至手机或电脑。位置传感器:如GPS模块,通过Bluetooth与其他设备共享位置信息。
  • [技术干货] 基于单片机设计的指纹锁(读取、录入、验证指纹)
    一、前言指纹识别技术是一种常见的生物识别技术,利用每个人指纹的唯一性进行身份认证。相比于传统的密码锁或者钥匙锁,指纹锁具有更高的安全性和便利性,以及防止钥匙丢失或密码泄露的优势。基于单片机设计的指纹锁项目是利用STC89C52作为主控芯片,结合AS608光学指纹识别模块和LCD1602显示屏,实现了指纹的读取、录入和验证功能。用户可以通过按键来进行指纹的录入和删除操作,并通过LCD显示屏来查看指纹识别的状态。在该项目中,AS608光学指纹识别模块是核心部件。它使用光学传感器采集指纹图像,然后通过算法进行特征提取和比对,最终判断指纹是否匹配。AS608模块具有高精度和高速的指纹识别能力,并且支持多种功能指令,如添加指纹、删除指纹和验证指纹等。另外,LCD1602显示屏提供了可视化的界面,能够直观地显示指纹识别的状态信息。用户可以通过观察LCD显示屏上的提示信息,了解指纹录入、删除和验证的结果。步进电机是用于模拟开锁操作的部件,通过正反转来实现门锁的解锁和上锁。当指纹验证成功时,步进电机会进行适当的旋转,使门锁打开,允许用户进入。该项目的背景是为了满足人们对安全性和便利性的需求,提供一种高效且可靠的门禁系统。通过指纹识别技术,可以确保只有授权的人员才能够进入特定区域,避免了传统钥匙的遗失和密码的泄露问题。同时,单片机作为主控芯片,具有低功耗、稳定性强等特点,非常适合用于嵌入式系统的设计与开发。这个项目的实施背景可以是家庭门禁系统、办公场所门禁系统、学校宿舍门禁系统等各种需要实现安全控制的场景。二、项目整体设计思路2.1 硬件设计思路(1)主控芯片:选择STC89C52作为主控芯片,它是一款功能强大且广泛使用的单片机。它具有丰富的GPIO口、中断和定时器等功能,能够满足指纹锁项目的需求。(2)光学指纹识别模块:采用AS608光学指纹识别模块作为指纹识别设备,该模块具有高精度的指纹识别能力。它通过光学传感器采集指纹图像,并通过算法进行特征提取和比对,最终实现指纹的识别和验证。(3)显示屏:选用LCD1602液晶显示屏作为人机交互的界面,该显示屏可以直观地显示指纹识别的状态信息,提供用户友好的操作界面。(4)按键:通过按键实现指纹的录入、删除和管理等操作。按键可以设置为添加指纹、删除指纹和确认等功能,方便用户进行指纹的管理。(5)步进电机:使用28BYJ-48步进电机来模拟开锁操作,根据指纹识别结果控制步进电机的正反转。当指纹验证成功时,步进电机会旋转,使门锁打开。2.2 软件设计思路:(1)初始化:在系统启动时,进行相关硬件的初始化操作,包括主控芯片、指纹识别模块、显示屏和步进电机等。(2)指纹录入功能:当用户选择指纹录入操作时,系统会提示用户按下指纹,然后通过光学传感器采集指纹图像,并提取特征信息。将提取的指纹特征存储在芯片的存储器中,以备后续的指纹验证使用。(3)指纹删除功能:用户可以选择删除已录入的指纹,系统会提示用户选择要删除的指纹,并进行相应的删除操作。(4)指纹验证功能:当用户选择指纹验证操作时,系统会提示用户按下指纹,然后通过光学传感器采集指纹图像,并提取特征信息。将提取的特征信息与存储在芯片存储器中的指纹特征进行比对,判断指纹是否匹配。如果匹配成功,则触发步进电机旋转,打开门锁;否则,提示验证失败。(5)显示功能:通过LCD1602显示屏展示指纹验证的状态信息,包括录入、删除和验证等操作的结果。例如,显示指纹录入成功、删除成功或验证成功等信息。(6)错误处理:在系统运行过程中,需要对各种可能出现的错误进行处理,如指纹录入失败、删除失败或验证失败等情况。系统需要及时给出相应的提示信息,以便用户了解具体的错误原因。硬件设计上,主要选用适合的单片机、指纹识别模块、显示屏和步进电机等组件,搭建起指纹锁的硬件平台;软件设计上,利用主控芯片进行指纹录入、删除和验证的功能实现,并通过显示屏展示相关信息,实现一个完整的指纹锁系统。三、硬件连线说明以下是指纹识别模块、显示屏、按键和步进电机的引脚连接:模块引脚连接到单片机的IO口指纹识别模块 AS608VCC5VGNDGNDTXDP1.0 (串口发送)RXDP1.1 (串口接收)液晶显示屏 LCD1602VCC5VGNDGNDSDAP2.0 (I2C总线数据)SCLP2.1 (I2C总线时钟)RSP3.0RWP3.1EP3.2D4-D7P4.0-P4.3按键添加指纹P0.0删除指纹P0.1确认P0.2步进电机 28BYJ-48IN1P5.0IN2P5.1IN3P5.2IN4P5.3四、项目代码设计#include <reg52.h>#include <stdio.h>#include <intrins.h>#include "lcd1602.h"​#define uchar unsigned char#define uint unsigned int​// 指纹识别相关定义#define FINGERPRINT_ADD 0x01 // 添加指纹命令#define FINGERPRINT_DEL 0x02 // 删除指纹命令#define FINGERPRINT_VERIFY 0x03 // 验证指纹命令#define FINGERPRINT_SUCCESS 0x00 // 指纹验证成功返回值#define FINGERPRINT_FAIL 0x01 // 指纹验证失败返回值​// 步进电机相关定义sbit IN1 = P2^0;sbit IN2 = P2^1;sbit IN3 = P2^2;sbit IN4 = P2^3;​// 按键相关定义sbit Key1 = P1^0; // 录入指纹按键sbit Key2 = P1^1; // 删除指纹按键​uchar comdata[19] = {0}; // 串口接收数据缓冲区uchar fingerStatus = 0; // 指纹识别状态,0:未识别,1:已识别uchar fingerprintIndex = 1; // 指纹索引​void delay(uint ms) { while(ms--) { uint xms = 120; // 延时大概1ms while(xms--); }}​void uartInit() { TMOD = 0x20; // 设置T1工作在方式2,8位定时/计数器 SCON = 0x50; // 设置串口工作在模式1,波特率9600 TH1 = 0xFD; // 波特率为9600时的T1重装值 TL1 = 0xFD; TR1 = 1; // 启动T1}​void uartSendByte(uchar dat) { SBUF = dat; // 将数据送入UART发送缓冲区 while(!TI); // 等待UART发送完毕 TI = 0; // 清除发送完成标志位}​void uartSendString(uchar *str) { while(*str) { uartSendByte(*str); str++; }}​void uartInterrupt() interrupt 4 using 1 { uchar c; if (RI) { // 接收到了数据 RI = 0; // 清除接收中断标志位 c = SBUF; // 读取接收到的数据​ if (c == 0xEF && comdata[0] != 0xEF) { // 接收到帧头 comdata[0] = 0xEF; comdata[1] = c; } else if (comdata[1] == 0x01 && comdata[2] == 0xF5) { // 接收到返回数据长度 comdata[2] = c; } else if (comdata[2] != 0) { // 接收到数据 comdata[comdata[2]] = c; if (comdata[2] == 18) { // 数据接收完毕 fingerStatus = comdata[9]; } } }}​void fingerPrintAdd(uchar index) { uchar temp[] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x07, 0x09, index, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x1E, 0x00, 0x2C}; uchar checkSum = 0; for (uint i = 2; i < 19; i++) { checkSum += temp[i]; uartSendByte(temp[i]); } temp[19] = (~checkSum) + 1; uartSendByte(temp[19]);}​void fingerPrintDelete(uchar index) { uchar temp[] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0x00, 0x03, 0x0C, index, 0x00, 0x10}; uchar checkSum = 0; for (uint i = 2; i < 13; i++) { checkSum += temp[i]; uartSendByte(temp[i]); } temp[13] = (~checkSum) + 1; uartSendByte(temp[13]);}​void fingerPrintVerify() { uchar temp[] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x00, 0x03, 0x03, 0x00, 0x07}; uchar checkSum = 0; for (uint i = 2; i < 12; i++) { checkSum += temp[i]; uartSendByte(temp[i]); } temp[12] = (~checkSum) + 1; uartSendByte(temp[12]);}​void stepperMotorRotate(uint steps, uchar direction) { uint i;​ for (i = 0; i < steps; i++) { IN1 = 1; delay(1); IN1 = 0; IN2 = 1; delay(1); IN2 = 0; IN3 = 1; delay(1); IN3 = 0; IN4 = 1; delay(1); IN4 = 0; }​ delay(1000);​ if (direction == 1) { // 正转 for (i = 0; i < 512; i++) { IN4 = 1; delay(1); IN4 = 0; IN3 = 1; delay(1); IN3 = 0; IN2 = 1; delay(1); IN2 = 0; IN1 = 1; delay(1); IN1 = 0; } } else { // 反转 for (i = 0; i < 512; i++) { IN1 = 1; delay(1); IN1 = 0; IN2 = 1; delay(1); IN2 = 0; IN3 = 1; delay(1); IN3 = 0; IN4 = 1; delay(1); IN4 = 0; } }}​void main() { lcdInit(); uartInit();​ EA = 1; // 开启总中断 ES = 1; // 开启串口中断​ while (1) { if (Key1 == 0) { // 录入指纹按键按下 fingerPrintAdd(fingerprintIndex); lcdClear(); lcdSetCursor(0, 0); lcdPrint("Add Finger"); delay(1000); if (fingerStatus == FINGERPRINT_SUCCESS) { // 指纹录入成功 lcdClear(); lcdSetCursor(0, 0); lcdPrint("Add Success"); delay(1000); fingerprintIndex++; } else { // 指纹录入失败 lcdClear(); lcdSetCursor(0, 0); lcdPrint("Add Fail"); delay(1000); } while (Key1 == 0); // 等待按键释放 }​ if (Key2 == 0) { // 删除指纹按键按下 fingerPrintDelete(fingerprintIndex - 1); lcdClear(); lcdSetCursor(0, 0); lcdPrint("Del Finger"); delay(1000); if (fingerStatus == FINGERPRINT_SUCCESS) { // 指纹删除成功 lcdClear(); lcdSetCursor(0, 0); lcdPrint("Del Success"); delay(1000); if (fingerprintIndex > 1) { fingerprintIndex--; } } else { // 指纹删除失败 lcdClear(); lcdSetCursor(0, 0); lcdPrint("Del Fail"); delay(1000); } while (Key2 == 0); // 等待按键释放 }​ fingerPrintVerify(); if (fingerStatus == FINGERPRINT_SUCCESS) { // 指纹验证成功 lcdClear(); lcdSetCursor(0, 0); lcdPrint("Success"); delay(500); stepperMotorRotate(64, 1); // 正转开锁 } else if (fingerStatus == FINGERPRINT_FAIL) { // 指纹验证失败 lcdClear(); lcdSetCursor(0, 0); lcdPrint("Fail"); delay(500); } }}​五、总结项目基于STC89C52单片机设计了一款指纹锁,能够实现指纹的读取、录入和验证等功能。AS608光学指纹识别模块提供了高精度的指纹识别能力,而LCD1602显示屏和按键配合完成了人机交互的功能设计。门锁则通过28BYJ-48步进电机实现正反转模拟开锁的操作。该项目完全自主设计制造,具有较高的实用性和创新性。
  • [技术干货] 基于单片机设计的激光测距仪(采用XKC-Kl200模块)
    一、前言随着科技的不断进步和应用需求的增加,测距仪成为了许多领域必备的工具之一。传统的测距仪价格昂贵、体积庞大,使用起来不够方便。本项目采用STC89C52单片机作为主控芯片,结合XKC-KL200激光测距模块和LCD1602显示器,实现了一个简易且高效的激光测距仪。这个测距仪可以帮助用户快速准确地测量目标与测距仪之间的距离,并将结果通过LCD1602显示器直观地展示出来。目前很多测距仪主要采用超声波或红外线等技术进行测量,但这些方法存在一定的局限性,比如受到环境干扰、测量距离有限等问题。而激光测距技术在测量精度和稳定性方面具有显著优势。XKC-KL200模块是一款基于激光测距原理的模块,具有高精度、快速测量等特点。通过与STC89C52单片机相结合,能够利用模块提供的数据和功能,快速实现一个功能完善的激光测距仪。LCD1602显示器作为输出设备,能够直观地显示测得的距离信息,具有体积小巧、低功耗、易于集成等特点,非常适合作为测距仪的显示屏。通过将测量结果转换为字符串,并利用LCD1602的命令和数据写入函数,可以在显示器上清晰地展示出测得的距离值。基于STC89C52单片机和XKC-KL200激光测距模块的激光测距仪项目,结合了激光测距技术和单片机控制技术,通过LCD1602显示器直观地展示出测得的距离信息。这个项目不仅满足了测距需求,而且具有成本低、体积小、使用方便等优势,无论是在建筑、工程、地理测量还是运动、航空等领域,这个激光测距仪都可以正常使用。二、硬件连线说明【1】LCD1602模块(1)VSS 引脚连接到单片机的GND引脚(地线)(2)VDD 引脚连接到单片机的5V引脚(正电源)(3)VO 引脚可以通过一个10K电位器连接到单片机的GND引脚,用于调节背光亮度(4)RS 引脚连接到单片机的P0口(作为命令/数据选择引脚)(5)RW 引脚连接到单片机的GND引脚(将LCD设为写模式)(6)E 引脚连接到单片机的P1口(作为使能引脚)(7)D0 ~ D7 引脚分别连接到单片机的P2 ~ P7口(作为数据引脚)【2】XKC-KL200激光测距模块(1)VCC 引脚连接到单片机的5V引脚(正电源)(2)GND 引脚连接到单片机的GND引脚(地线)(3)TX 引脚连接到单片机的RXD引脚(串口接收引脚)(4)RX 引脚连接到单片机的TXD引脚(串口发送引脚)三、XKC-KL200激光测距模块XKC-KL200 是一款智能非接触式开关、带有 UART 串口,高低电平或 NPN 驱动输出的激光测距传感器。该传感器利用激光对物体漫反射原理:当人或物体进入传感器设定的感应区域,传感器输出信号,同时能精确输出 距离;人或物体离开感应区后传感器则关闭输出。通信协议硬件采用 uart。 棕色(VCC)、黄色(信号输出)蓝色(GND)、黑色(RXD)供电 5~24V在客户 MCU 电源与 OUTPUT(黄线)之间跨接一个 1K 左右的上拉电阻。串口默认配置: 波特率:9600 、数据位:8 、校验位:无 、停止位:1应用范围(1)智能感应洁具。(2)家居安防。(3)智能检测,智能控制。(4)机器人障碍识别。(5)实时显示距离。(6)水龙头感应、大小便斗自动冲水、自动烘手机、防盗器、自动门铃、楼梯过道感应、电视近距离收看电视提醒器、自动门、广告灯箱、自动垃圾箱。四、项目代码设计#include <reg52.h>#include <stdio.h>​// LCD1602引脚连接sbit LCD_RS = P0^0; // RS引脚接口定义sbit LCD_E = P1^0; // E引脚接口定义sbit LCD_D4 = P2^4; // D4引脚接口定义sbit LCD_D5 = P2^5; // D5引脚接口定义sbit LCD_D6 = P2^6; // D6引脚接口定义sbit LCD_D7 = P2^7; // D7引脚接口定义​// 激光测距模块引脚连接sbit laser_TX = P3^0; // TX引脚接口定义sbit laser_RX = P3^1; // RX引脚接口定义​// LCD1602初始化void LCD_Init() { LCD_WriteCommand(0x02); // 回到Home位置 LCD_WriteCommand(0x28); // 设置4位数据总线、2行显示、5x8点阵 LCD_WriteCommand(0x0C); // 显示开,关游标 LCD_WriteCommand(0x06); // 光标右移 LCD_WriteCommand(0x01); // 清屏}​// 向LCD写入命令void LCD_WriteCommand(unsigned char command) { LCD_RS = 0; // 将RS置低,指定为写入命令 LCD_E = 0; // 拉低E线,准备写入 LCD_D4 = command >> 4 & 0x01; // 写入高4位数据 LCD_D5 = command >> 5 & 0x01; LCD_D6 = command >> 6 & 0x01; LCD_D7 = command >> 7 & 0x01; LCD_E = 1; // 拉高E线,写入命令 DelayMs(1); // 延时等待 LCD_E = 0; // 拉低E线,结束写入 LCD_D4 = command >> 0 & 0x01; // 写入低4位数据 LCD_D5 = command >> 1 & 0x01; LCD_D6 = command >> 2 & 0x01; LCD_D7 = command >> 3 & 0x01; LCD_E = 1; // 拉高E线,写入命令 DelayMs(1); // 延时等待 LCD_E = 0; // 拉低E线,结束写入}​// 向LCD写入数据void LCD_WriteData(unsigned char dat) { LCD_RS = 1; // 将RS置高,指定为写入数据 LCD_E = 0; // 拉低E线,准备写入 LCD_D4 = dat >> 4 & 0x01; // 写入高4位数据 LCD_D5 = dat >> 5 & 0x01; LCD_D6 = dat >> 6 & 0x01; LCD_D7 = dat >> 7 & 0x01; LCD_E = 1; // 拉高E线,写入数据 DelayMs(1); // 延时等待 LCD_E = 0; // 拉低E线,结束写入 LCD_D4 = dat >> 0 & 0x01; // 写入低4位数据 LCD_D5 = dat >> 1 & 0x01; LCD_D6 = dat >> 2 & 0x01; LCD_D7 = dat >> 3 & 0x01; LCD_E = 1; // 拉高E线,写入数据 DelayMs(1); // 延时等待 LCD_E = 0; // 拉低E线,结束写入}​// 清空LCD显示void LCD_Clear() { LCD_WriteCommand(0x01); // 清屏指令 DelayMs(2); // 延时等待}​// 在指定位置显示字符串void LCD_DisplayString(unsigned char x, unsigned char y, unsigned char *str) { unsigned char addr; if (y == 0) { addr = 0x80 + x; // 第一行地址计算 } else { addr = 0xC0 + x; // 第二行地址计算 } LCD_WriteCommand(addr); // 设置显示位置 while (*str != '\0') { LCD_WriteData(*str); // 逐个显示字符 str++; }}​// 毫秒级延时函数void DelayMs(unsigned int ms) { unsigned int i, j; for (i = ms; i > 0; i--) { for (j = 110; j > 0; j--); }}​// 串口初始化void UART_Init() { TMOD = 0x20; // 设置定时器1为模式2 TH1 = 0xFD; // 设置波特率为9600 TL1 = 0xFD; TR1 = 1; // 启动定时器1开始工作 SCON = 0x50; // 设置UART为模式1,允许接收}​// 串口数据接收unsigned char UART_Receive() { while (!RI); // 等待接收完成 RI = 0; return SBUF; // 返回接收到的数据}​五、总结在本项目中,成功设计了一个激光测距仪,使用STC89C52作为主控芯片,搭配XKC-Kl200激光测距模块。通过串口通信的方式,能够获取到被测物体与激光测距模块之间的距离,并将其实时显示在LCD1602液晶显示屏上。项目的实现过程中,先进行硬件连接,将STC89C52与XKC-Kl200模块通过串口相连,连接了LCD1602显示屏。编写程序代码,LCD1602和串口通信的初始化函数,以及数据的接收和显示函数。通过激光测距仪,可以方便地获得不同物体与测距模块之间的距离信息,并通过LCD1602显示出来。这为测量工作提供了便利,无论是在科研实验、工程测量还是日常生活中,都具有广泛的应用前景。
  • [技术干货] 基于STM32设计的格力空调遥控器
    一、格力空调协议介绍格力空调的红外控制协议被称为格力红外通讯协议或者格力红外遥控协议。这个协议定义了一系列红外信号,可以用来控制格力空调的各种操作,例如开关、温度控制、模式选择、风速控制等等。格力空调的红外控制协议是一种自定义协议,它并没有像NEC、RC5、RC6等协议一样被广泛应用。因此,不同型号的格力空调可能会有不同的红外控制协议。如果想要使用红外发送器控制格力空调,需要先了解当前空调使用的是哪种红外控制协议。一般来说,格力空调的红外控制协议包含一个头部和一系列数据位。头部通常由一个起始位和一个引导位组成。数据位通常包括操作码、温度、模式、风速等信息。下面是一个格力空调红外控制信号:Start Bit: 9000usLead-in: 4500us, 4500usData: 0x88, 0x20, 0x00, 0x02, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00在这个示例中,红外控制信号的起始位持续时间为9000微秒。接下来是一个引导位,持续时间为4500微秒和4500微秒。引导位后面是一系列数据位,包括操作码、温度、模式、风速等信息。在这个示例中,操作码为0x88,表示将空调设置为制冷模式,并把温度设置为32度。要控制格力空调,需要使用一个红外发送器,将这个信号通过红外光线发送到空调控制器。可以使用STM32F103ZET6的GPIO引脚控制红外线发送管来发送这个信号。二、代码实现2.1 代码实现#include "stm32f10x.h"​// 定义红外发送器引脚#define IR_SENDER_GPIO_PORT GPIOA#define IR_SENDER_GPIO_PIN GPIO_Pin_1​// 定义红外发送器协议参数#define IR_PROTOCOL_FREQ 38000 // 红外协议频率#define IR_PROTOCOL_START_DURATION 9000 // 红外协议起始位持续时间#define IR_PROTOCOL_LEADIN_DURATION 4500 // 红外协议引导位持续时间#define IR_PROTOCOL_DATA_BIT_0_DURATION 560 // 红外协议数据位0持续时间#define IR_PROTOCOL_DATA_BIT_1_DURATION 1690 // 红外协议数据位1持续时间​// 发送一个红外协议信号void IR_SendProtocol(uint32_t protocol){ uint8_t i, j; uint32_t data_bit;​ // 发送起始位 GPIO_SetBits(IR_SENDER_GPIO_PORT, IR_SENDER_GPIO_PIN); delay_us(IR_PROTOCOL_START_DURATION);​ // 发送引导位 GPIO_ResetBits(IR_SENDER_GPIO_PORT, IR_SENDER_GPIO_PIN); delay_us(IR_PROTOCOL_LEADIN_DURATION); GPIO_SetBits(IR_SENDER_GPIO_PORT, IR_SENDER_GPIO_PIN); delay_us(IR_PROTOCOL_LEADIN_DURATION);​ // 逐位发送协议数据 for (i = 0; i < 14; i++) { data_bit = (protocol >> i) & 0x01; // 获取当前位的值​ // 发送数据位 GPIO_ResetBits(IR_SENDER_GPIO_PORT, IR_SENDER_GPIO_PIN); if (data_bit) { delay_us(IR_PROTOCOL_DATA_BIT_1_DURATION); } else { delay_us(IR_PROTOCOL_DATA_BIT_0_DURATION); } GPIO_SetBits(IR_SENDER_GPIO_PORT, IR_SENDER_GPIO_PIN); delay_us(IR_PROTOCOL_DATA_BIT_0_DURATION); }}​// 控制格力空调开关void IR_ControlPower(int on_off){ uint32_t protocol;​ if (on_off) { // 开机 protocol = 0x8820000200200000; } else { // 关机 protocol = 0x8820000000200000; }​ IR_SendProtocol(protocol);}​// 控制格力空调温度void IR_ControlTemperature(int temperature){ uint32_t protocol;​ if (temperature < 16 || temperature > 31) { return; // 温度范围无效 }​ // 温度码为0x20加上实际温度值 protocol = 0x8820002020000000 | (temperature - 16);​ IR_SendProtocol(protocol);}​// 控制格力空调模式void IR_ControlMode(int mode){ uint32_t protocol;​ switch (mode) { case 0: // 制冷 protocol = 0x8820000200200000; break; case 1: // 制热 protocol = 0x8820000400200000; break; case 2: // 自动 protocol = 0x8820000800200000; break; case 3: // 送风 protocol = 0x8820001000200000; break; default: return; // 模式无效 }​ IR_SendProtocol(protocol);}​// 控制格力空调风速void IR_ControlFanSpeed(int fan_speed){ uint32_t protocol;​ switch (fan_speed) { case 0: // 自动风速 protocol = 0x8820002000200000; break; case 1: // 低风速 protocol = 0x8820004000200000; break; case 2: // 中风速 protocol = 0x8820008000200000; break; case 3: // 高风速 protocol = 0x8820010000200000; break; default: return; // 风速无效 }​ IR_SendProtocol(protocol);}在代码中,定义了一些常量来表示格力空调红外控制协议的参数,例如红外协议频率、起始位持续时间、引导位持续时间、数据位0和数据位1的持续时间。也定义了一些函数来控制格力空调的各种操作,例如控制开关、温度、模式和风速。这些函数调用了IR_SendProtocol()函数来发送适当的红外信号。在IR_SendProtocol()函数中,先发送起始位和引导位。然后,逐位发送协议数据,根据数据位的值发送适当的红外信号。在代码中,使用delay_us()函数来延迟一定的时间来模拟红外信号的持续时间。2.2 常见的控制码以下是一些常见的格力空调红外控制协议:【1】控制开关// 开机0x8820000200200000​// 关机0x8820000000200000【2】控制温度// 温度16度0x8820002020000000​// 温度17度0x8820002120000000​// 温度18度0x8820002220000000​// ...​// 温度31度0x8820003F20000000【3】控制模式// 制冷0x8820000200200000​// 制热0x8820000400200000​// 自动0x8820000800200000​// 送风0x8820001000200000【4】控制风速// 自动风速0x8820002000200000​// 低风速0x8820004000200000​// 中风速0x8820008000200000​// 高风速0x8820010000200000【5】控制扫风// 扫风开0x8820080000200000​// 扫风关0x8820100000200000【6】控制节能模式// 节能开0x8820200000200000​// 节能关0x8820400000200000【7】控制睡眠模式// 睡眠开0x8821000000200000// 睡眠关0x8822000000200000【8】控制定时开关机// 定时开机,时间为1小时0x8824000000200000// 定时开机,时间为2小时0x8828000000200000// 定时开机,时间为3小时0x8830000000200000// ...// 定时开机,时间为24小时0x8878000000200000// 取消定时开关机0x8820000000400000【9】控制清新功能// 清新开0x8820000002200000// 清新关0x8820000004200000【10】控制快速冷热// 快速冷0x8820000080200000// 快速热0x8820000040200000【11】控制干燥功能// 干燥开0x8820000001200000// 干燥关0x8820000002200000【12】控制自清洁功能// 自清洁开0x8820000008200000// 自清洁关0x8820000010200000【13】控制室内外循环功能// 室内外循环开0x8820020000200000// 室内外循环关0x8820040000200000
  • Huawei LiteOS编译时出错
    在执行编译时出现了以下错误make[1]: Entering directory 'e:/HuaweiEmbed/pj2' python  e:/HuaweiEmbed/pj2/tools/menuconfig/usr_config.py savemenuconfig process_begin: CreateProcess(NULL, python e:/HuaweiEmbed/pj2/tools/menuconfig/usr_config.py savemenuconfig, ...) failed. make (e=2): 系统找不到指定的文件。 make[1]: *** [tools/menuconfig/Makefile.kconfig:12: savemenuconfig] Error 2 make[1]: Leaving directory 'e:/HuaweiEmbed/pj2' make: *** [Makefile:53: e:/HuaweiEmbed/pj2/targets/menuconfig.h] Error 2求大佬解答
  • [问题求助] 构建环境时在MobaXterm中一直卡在获取源码上无法installed
    如图,在输入指令hpm i @bearpi/bearpi_hm_nano后,一直都卡在这里动不了,无法弹出installed,也无法输入下一步指令网络连接:正常ip地址:用Ubuntu获取的及时ip在虚拟盘里确实已经建立了code文件夹了,如图想问下应该如何继续呢
  • [技术干货] 基于STM32设计的环境检测设备
    ## 1. 前言 随着人们生活质量的提高,对于生活环境的问题,人们的关注度进一步提高,同时政府部门采取了许多措施来改善环境状况。但是总体上来说我国的环境监测技术水平比较落后,传统上的监测手段比较单一,监测数据也不够准确,耗尽了大量的人力和财力,却成效不高。 针对上述缺点,当前文章综合了嵌入式处理技术、传感器技术、无线网络通信等技术,设计了一个基于STM32的无线环境监测系统,系统主要实现了对湿度、温度、有毒气体、烟雾浓度、空气质量等参数进行实时监测的功能。为了实现无线数据传输功能,采用了无线wifi技术。系统的测试分析表明系统整体数据采集性能良好,数据传输稳定性可靠,到达了预期目标。 系统与传统的监测技术相比,具有监测数据准确,监测范围广,智能化高等特点。且系统具有一定的创新性,在实际的工程运用和理论研究上体现出了一定的研究价值最后通过实物的调试,各项参数及功能符合设计要求,能达到预期的目的。 设计以STM32微控制器为平台,采用DHT11温湿度传感器、烟雾传感器MQ-2、易燃气体传感器MQ-4、空气质量检测传感器MQ-135对室内温湿度和危险气体进行采集。通过wifi无线网络将数据传送给微控制器,STM32微控制器处理数据后,由自带oled液晶屏显示。当室内温度达到预警值或有危险气体时,系统将会自动警报并将警报信息通过wifi网络传输给客户手机。且每隔一段时间会通过wifi自动发送监测信息到手机,从而实现对室内环境的监测及报警功能。 [基于STM32设计的环境检测设备视频演示地址](https://live.csdn.net/v/182605) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/9/1660008434916542173.png) ## 2. 实现功能与整体框架图 开发板采用STM32最小系统板,主控CPU采用STM32F103C8T6,其他传感器采用模块的形式连接到开发板。 **主要实现以下功能实现:** 1、通过DHT11温湿度传感器、烟雾传感器MQ-2、易燃气体传感器MQ-4、空气质量检测传感器MQ-135对室内温湿度和危险气体进行采集。 2、通过传感器用ADC模拟数字的转换,采集到的数据显示在oled屏幕上。 3、当检测到的数据超过设定的安全值时,屏幕上会显示警报。 4、检测到的数据能定时通过ESP8266 wifi无线传输发送到所连接的用户的手机上,实现监测功能。 **系统框架图如下:** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/9/1660008470754409632.png) ## 3. 硬件特点介绍 **(1) 温湿度传感器** 温湿度传感器采用DHT11,这是一款直接输出数字信号的温湿度传感器;其精度湿度±5%RH, 温度±2℃,量程湿度5~95%RH, 温度-20~+60℃。通过单总线时序输出,占用的IO口也比较少,工作电压3V~5V,单片机连接控制很方便。 **(2) MQ系列的气体检测传感器** 烟雾传感器MQ-2、易燃气体传感器MQ-4、空气质量检测传感器MQ-135,这些传感器都是输出模拟信号。 配置好STM32的ADC采集接口,采集数据进行处理即可。 **(3) ESP8266 WIFI** 联网的模块采用ESP8266 WIFI,ESP8266在物联网里使用非常多,有很多成熟的案例.WIFI本身也支持二次开发,默认集成的SDK支持AT指令控制,单片机可以通过串口方式控制ESP8266完成网络通信,非常方便. **(4) OLED显示屏** OLED显示屏采用中景园电子的0.96寸OLED,分辨率是128x64,使用的SPI引脚接口屏幕,刷屏速度很快,控制简单 **(5) 上位机设计** 手机APP和PC端没有单独设计精美的界面,只是简单的展示了数据显示。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/9/1660008485937886200.png) ## 4. 核心源码 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/9/1660008499397380289.png) ### 4.1 DHT11温湿度代码 ```cpp #include "dht11.h" #include "delay.h" //复位DHT11 void DHT11_Rst(void) { DHT11_IO_OUT(); //SET OUTPUT DHT11_DQ_OUT=0; //拉低DQ DelayMs(20); //拉低至少18ms DHT11_DQ_OUT=1; //DQ=1 delay_us(30); //主机拉高20~40us } //等待DHT11的回应 //返回1:未检测到DHT11的存在 //返回0:存在 u8 DHT11_Check(void) { u8 retry=0; DHT11_IO_IN();//SET INPUT while (DHT11_DQ_IN&&retry100)//DHT11会拉低40~80us { retry++; delay_us(1); }; if(retry>=100)return 1; else retry=0; while (!DHT11_DQ_IN&&retry100)//DHT11拉低后会再次拉高40~80us { retry++; delay_us(1); }; if(retry>=100)return 1; return 0; } //从DHT11读取一个位 //返回值:1/0 u8 DHT11_Read_Bit(void) { u8 retry=0; while(DHT11_DQ_IN&&retry100)//等待变为低电平 { retry++; delay_us(1); } retry=0; while(!DHT11_DQ_IN&&retry100)//等待变高电平 { retry++; delay_us(1); } delay_us(40);//等待40us if(DHT11_DQ_IN)return 1; else return 0; } //从DHT11读取一个字节 //返回值:读到的数据 u8 DHT11_Read_Byte(void) { u8 i,dat; dat=0; for (i=0;i8;i++) { dat=1; dat|=DHT11_Read_Bit(); } return dat; } //从DHT11读取一次数据 //temp:温度值(范围:0~50°) //humi:湿度值(范围:20%~90%) //返回值:0,正常;1,读取失败 u8 DHT11_Read_Data(u8 *temp,u8 *humi) { u8 buf[5]; u8 i; DHT11_Rst(); //printf("------------------------\r\n"); if(DHT11_Check()==0) { for(i=0;i5;i++)//读取40位数据 { buf[i]=DHT11_Read_Byte(); } if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]) { *humi=buf[0]; *temp=buf[2]; } }else return 1; return 0; } //初始化DHT11的IO口 DQ 同时检测DHT11的存在 //返回1:不存在 //返回0:存在 u8 DHT11_Init(void) { RCC->APB2ENR|=12; //使能PORTG口时钟 GPIOA->CRL&=0XFF0FFFFF;//PORTG.11 推挽输出 GPIOA->CRL|=0X00300000; GPIOA->ODR|=15; //输出1 DHT11_Rst(); return DHT11_Check(); } ``` ### 4.2 ESP8266代码 ```cpp #include "esp8266.h" extern u8 USART3_RX_BUF[USART3_MAX_RECV_LEN]; //接收缓冲,最大USART3_MAX_RECV_LEN字节 extern u8 USART3_TX_BUF[USART3_MAX_SEND_LEN]; //发送缓冲,最大USART3_MAX_SEND_LEN字节 extern vu16 USART3_RX_STA; //接收数据状态 /////////////////////////////////////////////////////////////////////////////////////////////////////////// //用户配置区 //连接端口号:8086,可自行修改为其他端口. const u8 portnum[]="8089"; //WIFI STA模式,设置要去连接的路由器无线参数,请根据你自己的路由器设置,自行修改. const u8 wifista_ssid[]="wbyq1"; //路由器SSID号 const u8 wifista_encryption[]="wpa2_aes"; //wpa/wpa2 aes加密方式 const u8 wifista_password[]="123456789"; //连接密码 //WIFI AP模式,模块对外的无线参数,可自行修改. const u8 wifiap_ssid[]="Cortex_M3"; //对外SSID号 const u8 wifiap_encryption[]="wpawpa2_aes"; //wpa/wpa2 aes加密方式 const u8 wifiap_password[]="12345678"; //连接密码 /* 函数功能:向ESP82668266发送命令 函数参数: cmd:发送的命令字符串 ack:期待的应答结果,如果为空,则表示不需要等待应答 waittime:等待时间(单位:10ms) 返 回 值: 0,发送成功(得到了期待的应答结果) 1,发送失败 */ u8 ESP8266_SendCmd(u8 *cmd,u8 *ack,u16 waittime) { u8 res=0; USART3_RX_STA=0; UsartStringSend(USART3,cmd);//发送命令 if(ack&&waittime) //需要等待应答 { while(--waittime) //等待倒计时 { DelayMs(10); if(USART3_RX_STA&0X8000)//接收到期待的应答结果 { if(ESP8266_CheckCmd(ack)) { res=0; //printf("cmd->ack:%s,%s\r\n",cmd,(u8*)ack); break;//得到有效数据 } USART3_RX_STA=0; } } if(waittime==0)res=1; } return res; } /* 函数功能:ESP8266发送命令后,检测接收到的应答 函数参数:str:期待的应答结果 返 回 值:0,没有得到期待的应答结果 其他,期待应答结果的位置(str的位置) */ u8* ESP8266_CheckCmd(u8 *str) { char *strx=0; if(USART3_RX_STA&0X8000) //接收到一次数据了 { USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符 strx=strstr((const char*)USART3_RX_BUF,(const char*)str); //查找是否应答成功 printf("RX=%s",USART3_RX_BUF); } return (u8*)strx; } /* 函数功能:向ESP8266发送指定数据 函数参数: data:发送的数据(不需要添加回车) ack:期待的应答结果,如果为空,则表示不需要等待应答 waittime:等待时间(单位:10ms) 返 回 值:0,发送成功(得到了期待的应答结果)luojian */ u8 ESP8266_SendData(u8 *data,u8 *ack,u16 waittime) { u8 res=0; USART3_RX_STA=0; UsartStringSend(USART3,data);//发送数据 if(ack&&waittime) //需要等待应答 { while(--waittime) //等待倒计时 { DelayMs(10); if(USART3_RX_STA&0X8000)//接收到期待的应答结果 { if(ESP8266_CheckCmd(ack))break;//得到有效数据 USART3_RX_STA=0; } } if(waittime==0)res=1; } return res; } /* 函数功能:ESP8266退出透传模式 返 回 值:0,退出成功; 1,退出失败 */ u8 ESP8266_QuitTrans(void) { while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(15); //大于串口组帧时间(10ms) while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(15); //大于串口组帧时间(10ms) while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(500); //等待500ms return ESP8266_SendCmd("AT","OK",20);//退出透传判断. } /* 函数功能:获取ESP82668266模块的AP+STA连接状态 返 回 值:0,未连接;1,连接成功 */ u8 ESP8266_ApStaCheck(void) { if(ESP8266_QuitTrans())return 0; //退出透传 ESP8266_SendCmd("AT+CIPSTATUS",":",50); //发送AT+CIPSTATUS指令,查询连接状态 if(ESP8266_CheckCmd("+CIPSTATUS:0")&& ESP8266_CheckCmd("+CIPSTATUS:1")&& ESP8266_CheckCmd("+CIPSTATUS:2")&& ESP8266_CheckCmd("+CIPSTATUS:4")) return 0; else return 1; } /* 函数功能:获取ESP8266模块的连接状态 返 回 值:0,未连接;1,连接成功. */ u8 ESP8266_ConstaCheck(void) { u8 *p; u8 res; if(ESP8266_QuitTrans())return 0; //退出透传 ESP8266_SendCmd("AT+CIPSTATUS",":",50); //发送AT+CIPSTATUS指令,查询连接状态 p=ESP8266_CheckCmd("+CIPSTATUS:"); res=*p; //得到连接状态 return res; } /* 函数功能:获取ip地址 函数参数:ipbuf:ip地址输出缓存区 */ void ESP8266_GetWanip(u8* ipbuf) { u8 *p,*p1; if(ESP8266_SendCmd("AT+CIFSR\r\n","OK",50))//获取WAN IP地址失败 { ipbuf[0]=0; return; } p=ESP8266_CheckCmd("\""); p1=(u8*)strstr((const char*)(p+1),"\""); *p1=0; sprintf((char*)ipbuf,"%s",p+1); } /* 函数功能:将收到的AT指令应答数据返回给电脑串口 参 数:mode:0,不清零USART3_RX_STA; 1,清零USART3_RX_STA; */ void ESP8266_AtResponse(u8 mode) { if(USART3_RX_STA&0X8000) //接收到一次数据了 { USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符 printf("%s",USART3_RX_BUF); //发送到串口 if(mode)USART3_RX_STA=0; } } /* 函数功能:ESP8266 AP模式+TCP服务器模式测试 */ void ESP8266_APorServer(void) { u8 p[100]; u8 ipbuf[20]; while(ESP8266_SendCmd("AT\r\n","OK",20))//检查WIFI模块是否在线 { ESP8266_QuitTrans();//退出透传 ESP8266_SendCmd("AT+CIPMODE=0\r\n","OK",200); //关闭透传模式 printf("未检测到模块,正在尝试连接模块...\r\n"); DelayMs(800); } printf("ESP8266模块检测OK!\r\n"); while(ESP8266_SendCmd("ATE0\r\n","OK",20)); //关闭回显 printf("请用设备连接WIFI热点:%s,%s,%ss\r\n",(u8*)wifiap_ssid,(u8*)wifiap_encryption,(u8*)wifiap_password); /*1. 设置WIFI AP模式 */ ESP8266_SendCmd("AT+CWMODE=2\r\n","OK",50); /*2. 重启模块 */ ESP8266_SendCmd("AT+RST\r\n","OK",20); /*3. 延时3S等待重启成功*/ DelayMs(1000); DelayMs(1000); DelayMs(1000); /*5. 配置模块AP模式无线参数*/ sprintf((char*)p,"AT+CWSAP=\"%s\",\"%s\",1,4\r\n",wifiap_ssid,wifiap_password); ESP8266_SendCmd(p,"OK",1000); /*4. 设置多连接模式:0单连接,1多连接(服务器模式必须开启)*/ ESP8266_SendCmd("AT+CIPMUX=1\r\n","OK",20); /*5. 开启Server模式(0,关闭;1,打开),端口号为portnum */ sprintf((char*)p,"AT+CIPSERVER=1,%s\r\n",(u8*)portnum); ESP8266_SendCmd(p,"OK",50); /*6. 获取当前模块的IP*/ ESP8266_GetWanip(ipbuf);// printf("IP地址:%s 端口:%s",ipbuf,(u8*)portnum); USART3_RX_STA=0; //清空串口的接收标志位 // while(1) // { // key=GetKeyVal(1);//退出测试 // if(key==1) // { // printf("退出测试!\r\n"); // ESP8266_QuitTrans(); //退出透传 // ESP8266_SendCmd("AT+CIPMODE=0","OK",20); //关闭透传模式 // break; // } // else if(key==2) //发送数据 // { // ESP8266_SendCmd("AT+CIPSEND=0,12\r\n","OK",200); //设置发送数据长度为12个 // ESP8266_SendData("ESP8266测试!","OK",100); //发送指定长度的数据 // DelayMs(200); // } // t++; // DelayMs(10); // if(USART3_RX_STA&0X8000) //接收到一次数据了 // { // rlen=USART3_RX_STA&0X7FFF; //得到本次接收到的数据长度 // USART3_RX_BUF[rlen]=0; //添加结束符 // printf("接收的数据: rlen=%d,%s",rlen,USART3_RX_BUF); //发送到串口 // USART3_RX_STA=0; // if(constate!=3)t=1000; //状态为还未连接,立即更新连接状态 // else t=0; //状态为已经连接了,10秒后再检查 // } // if(t==1000)//连续10秒钟没有收到任何数据,检查连接是不是还存在. // { //// constate=ESP8266_ConstaCheck();//得到连接状态 //// if(!constate)printf("连接失败!\r\n"); // t=0; // } // if((t%20)==0)LED2=!LED2; // ESP8266_AtResponse(1); // } } ```
  • [技术干货] STM32入门开发:编写XPT2046电阻触摸屏驱动(模拟SPI)
    XPT2046是一颗12位的ADC芯片,可以当做普通的ADC芯片使用,但是一般都是用在电阻触摸屏上,方便定位触摸屏坐标。 这篇文章介绍XPT2046芯片使用方法,介绍内部的时序,指令,使用场景,利用STM32读取XPT2046采集的触摸屏坐标。 # 一、环境介绍 **单片机采用:** STM32F103ZET6 **编程软件:** keil5 **编程语言:** C语言 **编程风格:** 寄存器开发. **目标芯片:** XPT2046---标准SPI接口时序 # 二、XPT2046芯片介绍 ## **2.1 功能** XPT2046是一颗12位的ADC芯片,可以当做普通的ADC芯片使用,但是一般都是用在电阻触摸屏上,方便定位触摸屏坐标。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/5/1659664901616449566.png) **图1: XPT2046内部原理图** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/5/1659664922897379399.png) **图2:电阻触摸屏---引出的4条线就接在XPT2046的YN\XN\YP\XP上** **(XPT2046支持笔中断输出--低电平有效,这个引脚可以配置到单片机的中断脚上,或者轮询判断这个引脚状态,判断触摸屏是否已经按下)** **可以单独买一个触摸屏+一个XPT2046就可以自己做手画板、触摸按键(自己用一张纸在下面画个模型就行)、等等很多小玩意。** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/5/1659664936908567976.png) **图3:采用的电阻触摸屏的LCD屏(上面盖的哪一层薄膜就是触摸用的)** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/5/1659664947556195510.png) ## **2.2 特性** \1. 工作电压范围为 2.2V~5.25V \2. 支持 1.5V~5.25V 的数字 I/O 口 \3. 内建 2.5V 参考电压源 \4. 电源电压测量(0V~6) \5. 内建温度测量功能 \6. 触摸压力测量 \7. 采用 SPI 3线控制通信接口 \8. 具有自动 power-down 功能 \9. 封装:QFN-16、 TSSOP-16 和 VFBGA-48与 TSC2046、 AK4182A 完全兼容 \10. XPT2046 在 125KHz 转换速率和 2.7V 电压下的功耗仅为750 µW。 XPT2046 11. 以其低功耗和高速率等特性,被广泛应用在采用电池供电的小型手持设备上,比如 PDA、手机等。 \12. XPT2046 有 TSSOP-16、 QFN-16 和 VFBGA 三种封装形 式,温度范围是 - 40 ~ + 85℃ 。 ## 2.3 工作原理 XPT2046 是一种典型的逐次逼近型模数转换器(SAR ADC),包含了采样/保持、模数转换、串口数据输出等功能。同时芯片集成有一个 2.5V的内部参考电压源、温度检测电路,工作时使用外部时钟。 XPT2046 可以单电源供电,电源电压范围为 2.7V~5.5V。参考电压值直接决定ADC的输入范围,参考电压可以使用内部参考电压,也可以从外部直接输入1V~VCC范围内的参考电压(要求外部参考电压源输出阻抗低)。 X、 Y、 Z、 VBAT、 Temp和AUX模拟信号经过片内的 控制寄存器选择后进入ADC, ADC可以配置为**单端或差分模式**。选择VBAT、 Temp和AUX时可以配置为单端模式;作为触摸屏应用时,可以配置为差分模式,这可有效消除由于驱动开关的寄生电阻及外部的干扰带来的测量误差,提高转换准确度。 **典型的应用:** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/5/1659664969418770896.png) **单端工作模式** SER/DFR置为高电平时, XPT2046 工作在为单端模式,单端工作模式的应用原理如下图所示。 单端模式简单,在采样过程完成后,转换过程中可以关闭驱动开关,降低功耗。但这种模式的缺点是精度直接受参考电压源的精度限制,同时由于内部驱动开关的导通电阻存在,导通电阻与触摸屏电阻的分压作用,也会带来测量误差。 **(图片里的A2 A1 A0 ,还有上面说的SER/DFR就是XPT2046的配置命令,具体使用方法在后面会讲到)** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/5/1659664992988415435.png) **差分工作模式** SER/DFR置为低电平时, XPT2046 为差分工作模式. 差分模式的优点是: +REF 和-REF 的输入分别直接接到 YP、 YN 上,可消除由于驱动开关的导通电阻引入的坐标测量误差。 缺点是:无论是采样还是转换过程中,驱动开关都需要接通,相对单端模式而言,功耗增加了。 如果不考虑功耗的话,当前就选择差分工作模式了。 **(图片里的A2 A1 A0 ,还有上面说的SER/DFR就是XPT2046的配置命令,具体使用方法在后面会讲到)** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/5/1659665055454647655.png) ## 2.3 XPT2046采集并转换一次数据的时序介绍 XPT2046 数据接口是串行接口,处理器和转换器之间的通信需要 8 个时钟周期,可采用 SPI、 SSI 和 Microwire 等同步串行接口。一次完整的转换需要 24 个串行同步时钟(DCLK)来完成。 前 8 个时钟用来通过DIN引脚输入控制字节。当转换器获取有关下一次转换的足够信息后,接着根据获得的信息设置输入多路选择器和参考源输入,并进入采样模式,如果需要,将启动触摸面板驱动器。 3 个多时钟周期后,控制字节设置完成,转换器进入转换状态。这时,输入采样-保持器进入保持状态,触摸面板驱动器停止工作(单端工作模式)。 接着的12 个时钟周期将完成真正的模数转换。如果是度量比率转换方式(SER/DFR ——=0),驱动器在转换过程中将一直工作,第13 个时钟将输出转换结果的最后一位。剩下的 3 个多时钟周期将用来完成被转换器忽略的最后字节(DOUT置低)。 **时序图如下:** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/5/1659665066593693317.png) **时序图里的控制命令字节:** | 位 7(MSB) | 位 6 | 位 5 | 位 4 | 位 3 | 位 2 | 位 1 | 位 0(LSB) | | --------- | ---- | ---- | ---- | ---- | ------- | ---- | --------- | | S | A2 | A1 | A0 | MODE | SER/DFR | PD1 | PD0 | **控制字节每个位的含义如下:** | 位 | 名称 | 功能描述 | | ---- | ------- | ------------------------------------------------------------ | | 7 | S | 开始位。为 1 表示一个新的控制字节到来,为 0 则忽略 PIN 引脚上数据 | | 6-4 | A2-A0 | 通道选择位。这个在上面已经介绍过了 | | 3 | MODE | 12 位/8 位转换分辨率选择位。为 1 选择 8 位为转换分辨率,为 0 选择 12 位分辨率 | | 2 | SER/DFR | 单端输入方式/ 差分输入方式选择位。为 1 是单端输入方式,为 0 是差分输入方式 | | 1-0 | PD1-PD0 | 低功率模式选择位。若为11,器件总处于供电状态;若为00,器件在变换之间处于低 功率模式 | **注意: 差分模式仅用于 X 坐标、 Y 坐标和触摸压力的测量,其它测量要求采用单端模式。** **根据上面表格的介绍,可以得到在差分模式下,选择12位分辨率,测量X和Y坐标的两个命令:0xD0 和 0x90** XPT2046还有其他模式,可以测量温度,笔中断的开关(默认是开着的),16时钟周期转换,15时钟周期转换,这些就不再介绍。 根据前面的介绍用在触摸屏上测量XY坐标的功能已经满足了。 ## 2.4 SPI时序介绍 这里的XPT2046支持标准3线SPI接口,关于SPI时序的介绍,在前面文章里有介绍过。 ## 2.5 物理坐标与屏幕坐标的转换 正常在LCD屏上使用触摸屏,肯定是需要将采集的原始X、Y值转为LCD屏的屏幕坐标才好使用。 转换的方法有很多,这里采用最简单的角系数计算方法转换。 **比如,我使用的LCD屏是3.5寸的,分辨率是320\*480。** **1. 得到触摸屏左上角和右下角的坐标XY极限值** x=3831,y=3934 x=155,y=168 **2. 转换坐标值** x坐标:3831~155 --> 3676~0 y坐标:3934~168 --> 3766~0 **3. 计算斜率** x坐标的斜率: 3676/320=11.4875 y坐标的斜率: 3766/480=7.84583 **4. 得到实际的像素坐标** x坐标: 320-(实时采集的当前X模拟量-155)/11.4875 y坐标: 480-(实时采集的当前Y模拟量-168)/7.84583 **这里相减的原因: 因为我测试用的触摸屏采集出来的X、Y值大小和LCD屏的屏幕坐标值大小是反过来的。** # 三、示例代码 采用SPI模拟时序驱动,其他平台都可以移植。 ## 3.1 xpt2046.c ```cpp #include "xpt2046_touch.h" struct XPT2046_TOUCH xpt2046_touch; /* 函数功能: 初始化 硬件连接: T_MOSI--PF9 T_MISO--PB2 T_SCK---PB1 T_PEN---PF10 T_CS----PF11 */ void XPT2046_TouchInit(void) { /*1. 时钟初始化*/ RCC->APB2ENR|=13; //PB RCC->APB2ENR|=17; //PF /*2. 初始化GPIO口*/ GPIOB->CRL&=0xFFFFF00F; GPIOB->CRL|=0x00000830; GPIOF->CRH&=0xFFFF000F; GPIOF->CRH|=0x00003830; /*3. 上拉*/ GPIOB->ODR|=0x31; GPIOF->ODR|=0x79; } /* 函数功能: SPI底层写一个字节 */ void XPT2046_SPI_WriteOneByte(u8 cmd) { u8 i; for(i=0;i8;i++) { XPT2046_SCK=0; //低电平写 if(cmd&0x80)XPT2046_MOSI=1; else XPT2046_MOSI=0; cmd=1; XPT2046_SCK=1; //高电平读,保证数据线稳定 } } /* 函数功能: 读2个字节 说明: 读取16位数据,最低4位数据无效,有效数据是高12位 */ u16 XPT2046_ReadData(u8 cmd) { u16 data; u8 i; XPT2046_CS=0; //选中XPT2046 XPT2046_MOSI=0; XPT2046_SCK=0; XPT2046_SPI_WriteOneByte(cmd); DelayUs(8); //0.008ms ,等待XPT2046转换完成。 //消除忙信号 XPT2046_SCK=0; DelayUs(1); XPT2046_SCK=1; //连续读取16位的数据 for(i=0;i16;i++) { XPT2046_SCK=0; //通知XPT2046,主机需要数据 XPT2046_SCK=1; data=1; if(XPT2046_MISO)data|=0x01; } data>>=4; //丢弃最低4位 XPT2046_CS=1; //取消选中 return data; } /* XPT2046的命令: 10010000 :测试Y的坐标 0x90 11010000 :测试X的坐标 0xD0 返回值: 0表示没有读取到坐标,1表示读取到当前坐标 //1. 得到左上角和右下角的坐标XY极限值 x=3831,y=3934 x=155,y=168 //2. 转换坐标值 x坐标:3831~155 --> 3676~0 y坐标:3934~168 --> 3766~0 //3. 计算斜率 x坐标的斜率: 3676/320=11.4875 y坐标的斜率: 3766/480=7.84583 //4. 得到实际的像素坐标 x坐标: 320-(模拟量-155)/11.4875 y坐标: 480-(模拟量-168)/7.84583 */ u8 XPT2046_ReadXY(void) { if(XPT2046_PEN==0) //判断触摸屏是否按下 { /*1. 得到物理坐标*/ xpt2046_touch.x0=XPT2046_ReadData(0xD0); xpt2046_touch.y0=XPT2046_ReadData(0x90); /*2. 得到像素坐标*/ xpt2046_touch.x=320-(xpt2046_touch.x0-155)/11.4875; xpt2046_touch.y=480-(xpt2046_touch.y0-168)/7.84583; return 1; } return 0; } ``` ## 3.2 xpt2046.h ```cpp #ifndef XPT2046_TOUCH_H #define XPT2046_TOUCH_H #include "stm32f10x.h" #include "sys.h" #include "delay.h" //触摸屏引脚定义 #define XPT2046_MOSI PFout(9) #define XPT2046_MISO PBin(2) #define XPT2046_SCK PBout(1) #define XPT2046_CS PFout(11) #define XPT2046_PEN PFin(10) //函数声明 void XPT2046_TouchInit(void); void XPT2046_SPI_WriteOneByte(u8 cmd); u8 XPT2046_ReadXY(void); //存放触摸屏信息的结构体 struct XPT2046_TOUCH { u16 x0; //物理坐标x u16 y0; //物理坐标y u16 x; //像素坐标x u16 y; //像素坐标y }; extern struct XPT2046_TOUCH xpt2046_touch; #endif ```
  • [技术干货] STM32入门开发 制作红外线遥控器(智能居家-万能遥控器)
    # 一、环境介绍 **MCU:** STM32F103ZET6 **编程软件环境:** keil5 **红外线传输协议:** NEC协议---38KHZ载波:。NEC协议是红外遥控协议中常见的一种。 **编码发送思路:** 延时函数模拟38KHZ + PWM产生38KHZ两种方式 **代码风格:** 模块化编程,寄存器直接操作方式 # 二、NEC协议与相关硬件介绍 ## **2.1 NEC协议介绍** 红外线协议有很多,本章节主要是针对NEC协议讲解,只要把NEC协议原理搞懂了,其他协议都是一样的使用;如果想要模拟空调遥控器,去控制美的空调、格力空调这些设备,就需要按照美的、格力空调的协议发送;如果不知道协议长什么样,可以将逻辑分析仪插在红外线接收头的引脚上,拿个正常的空调遥控器对着接收头按一下,然后采集数据分析,即可得到协议规律,然后网络上也有空调按键值功能的说明文档,调试一下即可。 ## 2. 2 使用的相关硬件 因为要模拟红外线遥控器,就需要一个红外线发射管;在学习阶段,如果不想自己搭建电路,可以买现成的模块。 买模块连接也是比较稳定,接线也比较简单,VCC和GND接好之后,把DAT引脚接到STM32任意一个IO口上即可,如果想用硬件PWM控制发送,那么引脚接到STM32的PWM输出脚即可。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/4/1659578809866557777.png) ## **2.3 完成NEC协议编码发送** **先看一段红外线接收头引脚上采集的NEC协议的电平: 这是接收端采集的。** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/4/1659578823013799853.png) **红外线接收头的硬件特性:** (注意: 这里是针对NEC遥控器协议来说明),下图就是当前使用的红外线接收头。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/4/1659578836334252529.png) 收到38KHZ的红外光,IN引脚就输出低电平;没有收到IN引脚就输出高电平。 **NEC红外线协议说明:(这是站在接收端解码的角度分析的)** 一段独立的NEC协议数据包由引导码+32位数据组成。 引导码: 9ms的高电平 + 4.5ms 低电平组成。 32位数据就是: 8位用户码+ 8位用户反码+ 8位按键码+8位按键反码 每个数据位之间的间隔时间是0.56ms(低电平) NEC协议是依靠收到的高电平持续时间来判断数据0和数据1;高电平持续时间是0.56ms表示数据0,高电平持续时间是1.68ms表示数据1。 只要明白上面说的两个特点,就可以写程序,按照NEC协议驱动红外线发射管,发送数据了。 **编写发送程序之前,得先明白这个38KHZ的红外光如何产生?** STM32支持硬件PWM功能,可以配置38KHZ方波输出;如果没有硬件PWM功能的单片机,也可以使用延时的方式产生38KHZ方波,差那么一点点问题也不到,解码端适当调整一下时间范围即可。 **采用延时函数实现方法如下:** ```cpp /* 函数功能: 发送38KHZ的载波 函数参数: u32 time_us 持续的时间 u8 flag 1表示发送38KHZ载波,0表示不发送 */ void InfraredSend38KHZ(u32 time_us,u8 flag) { u32 i; if(flag) { //发送38KHZ载波 for(i=0;i13;i++) { INFRARED_OUTPUT=!INFRARED_OUTPUT; DelayUs(13); } } else { INFRARED_OUTPUT=1;//关闭红外线发射管 DelayUs(time_us); } } ``` **为了方便发送指定的用户码和按键码,可以封装成一个函数调用。** ```cpp /* 函数功能: NEC协议编码发送 函数参数: u8 user 用户码 u8 key 按键码 先发低位 按键反码+按键码+用户反码+用户码 */ void InfraredNECSend(u8 user,u8 key) { u32 i; /*1. 组合发送的数据*/ u32 data=((~key&0xFF)24)|((key&0xFF)16)|((~user&0xFF)8)|((user&0xFF)0); /*2. 发送引导码*/ InfraredSend38KHZ(9000,1);//发送38KHZ载波 InfraredSend38KHZ(4500,0);//不发送 /*3. 发送32位数据*/ for(i=0;i32;i++) { InfraredSend38KHZ(560,1); //间隔时间 if(data&0x01)InfraredSend38KHZ(1685,0); //发送1 else InfraredSend38KHZ(560,0); //发送0 data>>=1; } InfraredSend38KHZ(560,1); //间隔时间 } ``` **这是使用逻辑分析仪采集的发送端波形: 和协议对应了一下,没有问题。** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/4/1659578853558645546.png) **对比一下解码端采集的波形图:** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/4/1659578864976399017.png) # 三、核心代码 ## 3.1 main.c ```cpp #include "stm32f10x.h" #include "beep.h" #include "delay.h" #include "led.h" #include "key.h" #include "sys.h" #include "usart.h" #include #include #include "exti.h" #include "timer.h" #include "rtc.h" #include "adc.h" #include "ds18b20.h" #include "ble.h" #include "esp8266.h" #include "wdg.h" #include "oled.h" #include "rfid_rc522.h" #include "infrared.h" int main() { LED_Init(); KEY_Init(); BEEP_Init(); TIM1_Init(72,20000); //辅助串口1接收,超时时间为20ms USART_X_Init(USART1,72,115200); //InfraredDecodeInit(); //红外线解码初始化 InfraredCodingInit(); //红外线编码初始化 printf("UART1 OK.....\n"); while(1) { InfraredNECSend(13,14); //发送红外线数据 DelayMs(500); LED0=!LED0; } } ``` ## 3.2 红外线.c ```cpp #include "infrared.h" /* 函数功能: 红外线编码初始化 硬件连接: PG11 编码思路: 采用延时函数实现38KHZ */ void InfraredCodingInit(void) { RCC->APB2ENR|=18; //PG GPIOG->CRH&=0xFFFF0FFF; GPIOG->CRH|=0x00003000; GPIOG->ODR|=111; } /* 函数功能: 发送38KHZ的载波 函数参数: u32 time_us 持续的时间 u8 flag 1表示发送38KHZ载波,0表示不发送 */ void InfraredSend38KHZ(u32 time_us,u8 flag) { u32 i; if(flag) { //发送38KHZ载波 for(i=0;i13;i++) { INFRARED_OUTPUT=!INFRARED_OUTPUT; DelayUs(13); } } else { INFRARED_OUTPUT=1;//关闭红外线发射管 DelayUs(time_us); } } /* 函数功能: NEC协议编码发送 函数参数: u8 user 用户码 u8 key 按键码 先发低位 按键反码+按键码+用户反码+用户码 */ void InfraredNECSend(u8 user,u8 key) { u32 i; /*1. 组合发送的数据*/ u32 data=((~key&0xFF)24)|((key&0xFF)16)|((~user&0xFF)8)|((user&0xFF)0); /*2. 发送引导码*/ InfraredSend38KHZ(9000,1);//发送38KHZ载波 InfraredSend38KHZ(4500,0);//不发送 /*3. 发送32位数据*/ for(i=0;i32;i++) { InfraredSend38KHZ(560,1); //间隔时间 if(data&0x01)InfraredSend38KHZ(1685,0); //发送1 else InfraredSend38KHZ(560,0); //发送0 data>>=1; } InfraredSend38KHZ(560,1); //间隔时间 } ``` # 四、格力空调遥控协议介绍 ## 4.1 协议解析 报头脉冲:9ms 报头间距:4.5ms 载波频率:37.9KHz(38KHz) 码段1与码段2间距:20ms “1”:脉宽,656us。间距,1640us。 “0”:脉宽,656us。间距,544us。 ## 4.2 编码定义 1-3位:模式 送风:图标:风扇。代码:110。 自动:图标:循环箭头。代码:000。 除湿:图标:水滴。代码:010。 制冷:图标:雪花。代码:100。 制热:图标:太阳。代码:001。 4位(加68位):开机关机 开机:1。 关机:0。第68位取反。 5-6位:风速 一级:10 二级:01 三级:11 自动:00 7、37、41位(加65位):扫风 上下扫风:110。第65位取反 左右扫风:101。 上下左右:111 无扫风:000 8位:睡眠 睡眠:1 不睡眠:0 9-12位与65-68位:温度 制冷模式下: | 温度 | 9-12位 | 65-68位 | | ---- | -------- | -------- | | 30 | **0111** | **1000** | | 29 | **1011** | **0000** | | 28 | **0011** | **1111** | | 27 | **1101** | **0111** | | 26 | **0101** | **1011** | | 25 | **1001** | **0011** | | 24 | **0001** | **1101** | | 23 | **1110** | **0101** | | 22 | **0110** | **1001** | | 21 | **1010** | **0001** | | 20 | **0010** | **1110** | | 19 | **1100** | **0110** | | 18 | **0100** | **1010** | | 17 | **1000** | **0010** | | 16 | **0000** | **1100** | 制热模式: | 温度 | 9-12位 | 65-68位 | | ---- | -------- | -------- | | 30 | **0111** | **0010** | | 29 | **1011** | **1100** | | 28 | **1101** | **0100** | | 27 | **1101** | **1000** | | 26 | **0101** | **0000** | | 25 | **1001** | **1111** | | 24 | **0001** | **0111** | | 23 | **1110** | **1011** | | 22 | **0110** | **0011** | | 21 | **1010** | **1101** | | 20 | **0010** | **0101** | | 19 | **1100** | **1001** | | 18 | **0100** | **0001** | | 17 | **1000** | **1110** | | 16 | **0000** | **0110** | 吸湿模式: | 温度 | 9-12位 | 65-68位 | | ---- | -------- | -------- | | 30 | **0111** | **0100** | | 29 | **1011** | **1000** | | 28 | **0011** | **0000** | | 27 | **1101** | **1111** | | 26 | **0101** | **0111** | | 25 | **1001** | **1011** | | 24 | **0001** | **0011** | | 23 | **1110** | **1101** | | 22 | **0110** | **0101** | | 21 | **1010** | **1001** | | 20 | **0010** | **0001** | | 19 | **1100** | **1110** | | 18 | **0100** | **0110** | | 17 | **1000** | **1010** | | 16 | **0000** | **0010** | 送风模式: | 温度 | 9-12位 | 65-68位 | | ---- | -------- | -------- | | 30 | **0111** | **1100** | | 29 | **1011** | **0100** | | 28 | **0011** | **1000** | | 27 | **1101** | **0000** | | 26 | **0101** | **1111** | | 25 | **1001** | **0111** | | 24 | **0001** | **1011** | | 23 | **1110** | **0011** | | 22 | **0110** | **1101** | | 21 | **1010** | **0101** | | 20 | **0010** | **1001** | | 19 | **1100** | **0001** | | 18 | **0100** | **1110** | | 17 | **1000** | **0110** | | 16 | **0000** | **1010** | 13-20位:睡眠定时 | 时间 | 13-20位 | | ---- | ------------ | | 0.5 | **10010000** | | 1 | **00011000** | | 1.5 | **10011000** | | 2 | **00010100** | | 2.5 | **10010100** | | 3 | **00011100** | | 3.5 | **10011100** | | 4 | **00010010** | | 4.5 | **10010010** | | 5 | **00011010** | | 5.5 | **10011010** | | 6 | **00010110** | | 6.5 | **10010110** | | 7 | **00011110** | | 7.5 | **10011110** | | 8 | **00010001** | | 8.5 | **10010001** | | 9 | **00011001** | | 9.5 | **10011001** | | 10 | **01010000** | | 10.5 | **11010000** | | 11 | **01011000** | | 11.5 | **11011000** | | 12 | **01010100** | | 12.5 | **11010100** | | 13 | **01011100** | | 13.5 | **11011100** | | 14 | **01010010** | | 14.5 | **11010010** | | 15 | **01011010** | | 15.5 | **11011010** | | 16 | **01010110** | | 16.5 | **11010110** | | 17 | **01011110** | | 17.5 | **11011110** | | 18 | **01010001** | | 18.5 | **11010001** | | 19 | **01011001** | | 19.5 | **11011001** | | 20 | **00110000** | | 20.5 | **10110000** | | 21 | **00111000** | | 21.5 | **10111000** | | 22 | **00110100** | | 22.5 | **10110100** | | 23 | **00111100** | | 23.5 | **10111100** | | 24 | **00110010** | | 0 | **00000000** | 21位:超强 超强:1 普通:0 22位:灯光 亮:1 灭:0 23位与25位:健康,换气 健康:10 换气:01 健康+换气:11 普通:00 24位:制冷模式下-干燥;制热模式下-辅热; 干燥:1 普通:0 45-46位:显示温度 不显示:00 显示:10 显示室内温度:01 显示室外温度:11 其他位: 除了29、31、34位为“1”外,均为“0”。其他位功能不详(遥控器无对应项)。 第36位和69位分别是码段1和码段2的最后一位,无所谓“0”“1”。 ## 4.3 其他说明 在自动模式下只可以设置的项目有:风速1、2、3级、自动;上上下左右扫风;显示温度;灯光;睡眠定时(非睡眠)。其他项均不可以设置。此时温度不可设置,温度段的代码为:10011101。 在关机状态下,可以设置定时开机,代码与睡眠定时关机一样。也可以设置灯光。 在制冷模式下,可以设置的项有:温度;扫风;健康换气,节能(仅在此状态下可以设置);风速;定时;超强;睡眠;灯光;温度显示。 在除湿模式下,可以设置的项有:温度;扫风;健康换气;干燥;温度显示;定时;睡眠;灯光。 在送风模式下,可以设置的项有:温度;风速;健康换气;扫风;温度显示;定时;灯光。 在制热模式下,可以设置的项有:温度;风速;扫风;辅热;温度显示;定时;超强;睡眠;灯光。 MGQ 2012-04-141、 格力YB0F2红外信号命令格式 红外信号主要包括CMD1和CMD2两部分,其中CMD1包括35 位的命令 和一位停止位,CMD2包括32位的命令和一位停止位。 # 五、美的空调协议介绍 L为引导码, S为分隔码, A为认别码(A=10110010=B2,预留方案时A=10110111=B7), A'为A的反码, B'为B的反码, C'为C的反码 遥控器发射红外信号之时,通过“560微秒低电平+1680微秒高电平”代表“1”,通过“560微秒低电平+560微秒低电平”代表“0”。 **美的的红外采用NEC格式的R05d** 该协议的红外信号编码格式为:**引导码+客户码+客户反码+数据码+数据反码+结束位**, 其中引导码和结束码都是固定的,数据反码由数据码按位取反得来,真正变化的只有用户码和数据码。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/4/1659578895902229753.png)
  • [技术干货] 基于CC2530_ZigBee+华为云IOT设计的冷链信息采集系统
    ## 1. 前言 近年来,随着人们消费需求的不断提高,连锁超市、便利店、大卖场等商超不但提供了各种各样的新鲜食品,而且采用统一进货和冷链储藏的方式,从而不但使得商品质量有保证,而且购物环境良好,越来越成为人们购物的主要场所。超市作为冷链物流产品的末端,在分销以及零售过程中都对产品质量、运营成本和功耗等方面有着较高的要求,而冷链系统的压力参数作为保证这一品质的重要参数之一,实现智能压力检测和控制对于时刻掌握冷链的工作状态非常重要,可以保证运营的安全性和经济性。因此,如果能够设计一款针对冷链系统数据采集系统,就可以实现对冷链系统进行实时监控,达到经济性运营的目的,对提高企业经济效益具有非常重要的意义。 ## 2. 设计需求 以CC2530单片机为核心器件,设计一个冷链环境信息采集系统,利用传感器技术对冷藏仓内的环境参数进行采集,上传到物联网云平台,然后通过手机端或移动端进行显示,便于分析,观察冷链环境信息。 **硬件选型:** (1)ESP8266-WIFI 用于与上位机进行通信,实现数据传输 (2)CC2530单片机,本身是51内核,与普通的51单片机编程一样,它内部多了一个ZigBee 模块,能实现ZigBee 组网。 (3)DHT11 温湿度传感器。这是一款有已校准数字信号输出的温湿度传感器。 其精度湿度±5%RH, 温度±2℃,量程湿度5~95%RH, 温度-20~+60℃。 (4)蜂鸣器。当设置阀值超出标准时,可以发出警报提醒。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220731/1659279732830585909.png) ## 3. 硬件选型 ### 3.1 CC2530+WIFI模块 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220731/1659279753001899460.png) ### 3.2 DHT11温湿度模块 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220731/1659279760592727149.png) ### 3.3 蜂鸣器 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220731/1659279768676884068.png) ## 4. 物联网云端配置与应用 ### 4.1 华为云IoTDA介绍 当前的设计中,用的物联网平台服务是华为云的设备接入服务(IoTDA),IoTDA提供海量设备连接上云、设备和云端双向消息通信、批量设备管理、远程控制和监控、OTA升级、设备联动规则等能力,并可将设备数据灵活流转到华为云其他服务。 使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。 物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。 设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。 业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220731/1659280917897611126.png) 接下来就详细把整个物联网平台的使用流程进行介绍。 ### 4.1 产品创建 地址: https://www.huaweicloud.com/ ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659318767539998388.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659318802477698241.png) 查看平台接入地址: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659318845861398118.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659318867179517617.png) 点击右上角创建产品: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659318931101336875.png) 根据自己的产品信息填充: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659318973051411162.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659318986372396640.png) 根据产品的传感器属性创建服务器的属性字段: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659319951835193774.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659319979909306876.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659320015048106243.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659320035868499555.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659320092203499175.png) ### 4.2 设备创建 详细创建流程,看下面的截图: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659319015895225162.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659319074944475729.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659319089733402686.png) 保存设备信息,接下来的MQTT登录需要使用。 ```cpp { "device_id": "62e732be3a884835598654f7_dev1", "secret": "12345678" } ``` ### 4.3 MQTT三元组信息生成 在这里可以使用华为云提供的工具快速得到MQTT三元组进行登录。 [https://support.huaweicloud.com/devg-iothub/iot_01_2127.html#ZH-CN_TOPIC_0240834853__zh-cn_topic_0251997880_li365284516112](https://support.huaweicloud.com/devg-iothub/iot_01_2127.html#ZH-CN_TOPIC_0240834853__zh-cn_topic_0251997880_li365284516112) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659320198725137713.png) 工具的页面地址: [https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/](https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/) 根据提示填入信息,然后生成三元组信息即可。 这里填入的信息就是在创建设备的时候生成的信息。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659320259478396144.png) ```cpp ClientId 62e732be3a884835598654f7_dev1_0_0_2022080102 Username 62e732be3a884835598654f7_dev1 Password 13483ebeadd786ea107527a3c92c5463a8f3c71377cd33276143ffe2fb85c1dc ``` ### 4.4 MQTT主题订阅与发布格式 ```cpp //订阅主题: 平台下发消息给设备 $oc/devices/62e732be3a884835598654f7_dev1/sys/messages/down //设备上报数据 $oc/devices/62e732be3a884835598654f7_dev1/sys/properties/report //上报的属性消息 (一次可以上报多个属性,在json里增加就行了) {"services": [{"service_id": "server_id","properties":{"温度":23.4}},{"service_id": "server_id","properties":{"湿度":80.5}}]} ``` ### 4.5 设备模拟登录测试 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659320841996720300.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659320861395753895.png) ### 4.6 应用侧开发接口介绍 在设备上云之后,为了能方便管理设备,方便用户设备入网,都需要开发一款手机APP或者微信小程序、桌面软件等,进行数据交互,设备管理。 华为云IOT提供了应用侧开发的API对接接口,这里就介绍一下使用应用侧开发的流程。这个API接口里常用的接口包括:产品创建、设备创建、设备属性获取、设备删除、查询设备等管理接口,可以通过API主动获取产品下面某个设备的属性,要求设备上报最新的数据过来。整个开发过程,都是基于HTTP协议的API接口进行交互,不依赖开发环境,不依赖开发语言。 不管是桌面软件,还是手机APP、微信小程序、web网页等,核心代码基本都是一样,都是HTTP协议交互。下面的例子里,我是采用C++编写的,采用QT框架库完成整个开发,了解了整个思路,你就可以采用自己熟悉的语言完成相同的功能。 官方帮助文档:[ https://support.huaweicloud.com/usermanual-iothub/iot_01_0045.html]( https://support.huaweicloud.com/usermanual-iothub/iot_01_0045.html) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220801/1659321098553976784.png) ## 5. CC2530程序设计 ### 5.1 IAR环境搭建 完整的安装整个配套环境,需要安装以下的软件,具体的版本型号也介绍了,直接百度搜索就能找到;最简单的办法是,淘宝搜索一下CC2530的开发板,店铺里一般都有配套的资料包下载,里面基本都包含了下面这些软件,直接白嫖就行: 安装集成开发环境: IAR-EW8051-8.10.1。 安装仿真器“SmartRF4EB”的驱动程序。 安装代码烧写工具: Setup_SmartRF_Programmer_1.10.2。 安装 TI 的 Zigbee 协议栈: ZStack-CC2530-2.5.1a。 **安装过程截图请看另外的文档。** ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220731/1659279792871764035.png) ### 5.2 硬件原理图 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220731/1659279805707563798.png) ### 5.3 IAR程序工程图 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220731/1659279814345255814.png) ### 5.4 DHT11.c代码 ```cpp #include "uart.h" /* 函数功能:串口0初始化 */ void Init_Uart0(void) { PERCFG&=~(10); //串口0的引脚映射到位置1,即P0_2和P0_3 P0SEL|=0x32; //将P0_2和P0_3端口设置成外设功能 U0BAUD = 216; //32MHz的系统时钟产生115200BPS的波特率 U0GCR&=~(0x1F0);//清空波特率指数 U0GCR|=110; //32MHz的系统时钟产生115200BPS的波特率 U0UCR |= 0x80; //禁止流控,8位数据,清除缓冲器 U0CSR |= 0x36; //选择UART模式,使能接收器 } /* 函数功能:UART0发送字符串函数 */ void UR0SendString(u8 *str) { while(*str!='\0') { U0DBUF = *str; //将要发送的1字节数据写入U0DBUF while(UTX0IF == 0);//等待数据发送完成 UTX0IF = 0; //清除发送完成标志,准备下一次发送 str++; } } /* 函数功能: 模仿printf风格的格式化打印功能 */ char USART0_PRINT_BUFF[200]; //格式化数据缓存数据 void USART0_Printf(const char *format,...) { char *str=NULL; /*1. 格式化转换*/ va_list ap; // va_list---->char * va_start(ap,format); //初始化参数列表 vsprintf(USART0_PRINT_BUFF, format, ap); //格式化打印 va_end(ap); //结束参数获取 /*2. 串口打印*/ str=USART0_PRINT_BUFF;//指针赋值 while(*str!='\0') { U0DBUF=*str; //发送一个字节的数据 str++; //指针自增,指向下一个数据 while(UTX0IF == 0);//等待数据发送完成 UTX0IF = 0; //清除发送完成标志,准备下一次发送 } } ``` ### 5.5 ESP8266.c代码 ```cpp #include "esp8266.h" uint lenU1 = 0; uchar tempRXU1; uchar RecdataU1[MAXCHAR]; //"AT+CIPSEND=0,10\r\n" //长度10 //返回">" 之后就可以正常发送数据了 //发送成功返回 "SEND OK" //发送数据 void ESP8266_SendData(char *p,int len) { int i=0; char buff[50]; sprintf(buff,"AT+CIPSEND=0,%d\r\n",len); clearBuffU1(); Uart1_Send_String(buff); //发送指令 DelayMs(1000); //等待 for(i=0;i/等待发送完成 DelayMs(1000); RecdataU1[lenU1]='\0'; UR0SendString(RecdataU1); clearBuffU1(); } /**************************************************************************** * 名 称: SetWifi() * 功 能: 设置LED灯相应的IO口 * 入口参数: 无 * 出口参数: 无 ****************************************************************************/ void SetWifi(void) { P0DIR |= 0x40; //P0.6定义为输出 IGT = 0; //高电平复位 DelayMs(500); IGT = 1; //低电平工作 } /* 设置WIFI为AP模式+TCP服务器 */ void SetESP8266_AP_TCP_Server() { clearBuffU1(); Uart1_Send_String("AT\r\n"); DelayMs(2000); RecdataU1[lenU1]='\0'; UR0SendString(RecdataU1); clearBuffU1(); Uart1_Send_String("ATE0\r\n"); DelayMs(2000); RecdataU1[lenU1]='\0'; UR0SendString(RecdataU1); clearBuffU1(); Uart1_Send_String("AT+CWMODE=2\r\n"); DelayMs(2000); RecdataU1[lenU1]='\0'; UR0SendString(RecdataU1); clearBuffU1(); Uart1_Send_String("AT+RST\r\n"); DelayMs(2000); DelayMs(2000); DelayMs(2000); RecdataU1[lenU1]='\0'; UR0SendString(RecdataU1); clearBuffU1(); Uart1_Send_String("ATE0\r\n"); DelayMs(2000); RecdataU1[lenU1]='\0'; UR0SendString(RecdataU1); clearBuffU1(); Uart1_Send_String("AT+CWSAP=\"wifi_cc2530\",\"12345678\",1,4\r\n"); DelayMs(2000); DelayMs(2000); DelayMs(2000); DelayMs(2000); RecdataU1[lenU1]='\0'; UR0SendString(RecdataU1); clearBuffU1(); Uart1_Send_String("AT+CIPMUX=1\r\n"); DelayMs(2000); DelayMs(2000); RecdataU1[lenU1]='\0'; UR0SendString(RecdataU1); clearBuffU1(); Uart1_Send_String("AT+CIPSERVER=1,8089\r\n"); DelayMs(2000); DelayMs(2000); RecdataU1[lenU1]='\0'; UR0SendString(RecdataU1); clearBuffU1(); Uart1_Send_String("AT+CIFSR\r\n"); DelayMs(2000); DelayMs(2000); RecdataU1[lenU1]='\0'; UR0SendString(RecdataU1); } unsigned char dataRecv; unsigned char Flag = 0; /*===================UR1初始化函数====================*/ void Init_Uart1() { PERCFG = 0x00; //位置1 P0.4/P0.5口 P0SEL |= 0x30; //P0.4,P0.5用作串口(外部设备功能) U1CSR |= 0x80; //设置为UART方式 U1GCR |= 11; //BAUD_E U1BAUD |= 216; //BAUD_M 波特率设为115200 UTX1IF = 0; //UART1 TX中断标志初始置位0 U1CSR |= 0X40; //允许接收 IEN0 |= 0x88; // 开总中断,UART1接收中断 } void clearBuffU1(void) { int j; for(j=0;j } lenU1=0; } /******************************************************************************* 串口1发送一个字节函数 *******************************************************************************/ void Uart1_Send_Char(char Data) { U1CSR &= ~0x40; //禁止接收 U1DBUF = Data; while(UTX1IF == 0); UTX1IF = 0; U1CSR |= 0x40; //允许接收 } /******************************************************************************* 串口1发送字符串函数 *******************************************************************************/ void Uart1_Send_String(char *Data) { while(*Data!='\0') { Uart1_Send_Char(*Data); Data++; } } /**************************************************************** 串口接收一个字符: 一旦有数据从串口传至CC2530, 则进入中断,将接收到的数据赋值给变量temp. ****************************************************************/ #pragma vector = URX1_VECTOR __interrupt void UART1_ISR(void) { if(lenU181) { tempRXU1 = U1DBUF; RecdataU1[lenU1]=tempRXU1; URX1IF = 0; // 清中断标志 lenU1++; } } ``` ## 6. 总结 随着业务的发展,越来越多的企业选择结合物联网技术来实现自身效益增长。相比企业自建MQTT集群,使用华为云IoT服务低成本构建物联网解决方案,在能力、成本、运维、安全、生态等诸多方面具有突出优势。 广泛支持IoT主流的接入协议及私有协议,满足各类设备和接入场景要求;与主流模组、芯片预集成,实现多网络、多协议接入,简化设备接入难度,实现小时级设备极简接入。
  • [技术干货] STM32入门开发 介绍IIC总线、读写AT24C02(EEPROM)(采用模拟时序)
    # 一、环境介绍 **编程软件:** keil5 **操作系统:** win10 **MCU型号:** STM32F103ZET6 **STM32编程方式:** 寄存器开发 (方便程序移植到其他单片机) **IIC总线:** STM32本身支持IIC硬件时序的,本文采用的是模拟时序,下篇文章就介绍配置STM32的IIC硬件时序读写AT24C02和AT24C08。 模拟时序更加方便移植到其他单片机,通用性更高,不分MCU;硬件时序效率更高,单每个MCU配置方法不同,依赖硬件本身支持。 **目前器件:** 采用AT24C02 EEPROM存储芯片 # 二、AT24C02存储芯片介绍 ## 2.1 芯片功能特性介绍 AT24C02 是串行CMOS类型的EEPROM存储芯片,AT24C0x这个系列包含了**AT24C01、AT24C02、AT24C04、AT24C08、AT24C16**这些具体的芯片型号。 他们容量分别是:**1K (128 x 8)、2K (256 x 8)、4K (512 x 8)、8K (1024 x 8)、16K (2048 x 8) ,**其中的8表示8位(bit) **它们的管脚功能、封装特点如下:** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/1/1659318065042695792.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/1/1659318086916621006.png) **芯片功能描述:** AT24C02系列支持I2C,总线数据传送协议I2C,总线协议规定任何将数据传送到总线的器件作为发送器。任何从总线接收数据的器件为接收器;数据传送是由产生串行时钟和所有起始停止信号的主器件控制的。主器件和从器件都可以作为发送器或接收器,但由主器件控制传送数据(发送或接收)的模式,由于A0、A1和A2可以组成000~111八种情况,即通过器件地址输入端A0、A1和A2可以实现将最多8个AT24C02器件连接到总线上,通过进行不同的配置进行选择器件。 **芯片特性介绍:** (1). 低压和标准电压运行 –2.7(VCC=2.7伏至5.5伏) –1.8(VCC=1.8伏至5.5伏) (2). 两线串行接口(SDA、SCL) (3). 有用于硬件数据保护的写保护引脚 (4). 自定时写入周期(5毫秒~10毫秒),因为内部有页缓冲区,向AT24C0x写入数据之后,还需要等待AT24C0x将缓冲区数据写入到内部EEPROM区域. (5). 数据保存可达100年 (6). 100万次擦写周期 (7). 高数据传送速率为400KHz、低速100KHZ和IIC总线兼容。 100 kHz(1.8V)和400 kHz(2.7V、5V) (8). 8字节页写缓冲区 这个缓冲区大小与芯片具体型号有关: 8字节页(1K、2K)、16字节页(4K、8K、16K) **2.2 芯片设备地址介绍** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/1/1659318155599884135.png) IIC设备的标准地址位是7位。上面这个图里AT24C02的1010是芯片内部固定值,A2 、A1、 A0是硬件引脚、由硬件决定电平;最后一位是读/写位(1是读,0是写),读写位不算在地址位里,但是根据IIC的时序顺序,在操作设备前,都需要先发送7位地址,再发送1位读写位,才能启动对芯片的操作,我们在写模拟时序为了方便统一写for循环,按字节发送,所以一般都是将7地址位与1位读写位拼在一起,组合成1个字节,方便按字节传输数据。 **我现在使用的开发板上AT24C02的原理图是这样的:** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/1/1659318173282400622.png) 那么这个AT24C02的标准设备地址就是: 0x50(十六进制),对应的二进制就是: 1010000 如果将读写位组合在一起,读权限的设备地址: 0xA1 (10100001) 、写权限的设备地址: 0xA0 (10100000) ## 2.3 对AT24C02 按字节写数据的指令流程(时序) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/1/1659318222536191508.png) **详细解释:** \1. 先发送起始信号 \2. 发送设备地址(写权限) \3. 等待AT24C02应答、低电平有效 \4. 发送存储地址、AT24C02内部一共有256个字节空间,寻址是从0开始的,范围是(0~255);发送这个存储器地址就是告诉AT24C02接下来的数据改存储到哪个地方。 \5. 等待AT24C02应答、低电平有效 \6. 发送一个字节的数据,这个数据就是想存储到AT24C02里保存的数据。 \7. 等待AT24C02应答、低电平有效 \8. 发送停止信号 ## 2.4 对AT24C02 按页写数据的指令流程(时序) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/1/1659318250119895320.png) **详细解释:** \1. 先发送起始信号 \2. 发送设备地址(写权限) \3. 等待AT24C02应答、低电平有效 \4. 发送存储地址、AT24C02内部一共有256个字节空间,寻址是从0开始的,范围是(0~255);发送这个存储器地址就是告诉AT24C02接下来的数据改存储到哪个地方。 \5. 等待AT24C02应答、低电平有效 \6. 可以循环发送8个字节的数据,这些数据就是想存储到AT24C02里保存的数据。 AT24C02的页缓冲区是8个字节,所有这里的循环最多也只能发送8个字节,多发送的字节会将前面的覆盖掉。 需要注意的地方: 这个页缓冲区的寻址也是从0开始,比如: 0~7算第1页,8~15算第2页......依次类推。 如果现在写数据的起始地址是3,那么这一页只剩下5个字节可以写;并不是说从哪里都可以循环写8个字节。 详细流程: 这里程序里一般使用for循环实现 (1). 发送字节1 (2). 等待AT24C02应答,低电平有效 (3). 发送字节2 (4). 等待AT24C02应答,低电平有效 ......... 最多8次. \7. 等待AT24C02应答、低电平有效 \8. 发送停止信号 ## 2.5 从AT24C02任意地址读任意字节数据(时序) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/1/1659318274943930129.png) AT24C02支持当前地址读、任意地址读,最常用的还是任意地址读,因为可以指定读取数据的地址,比较灵活,上面这个指定时序图就是任意地址读。 **详细解释:** \1. 先发送起始信号 \2. 发送设备地址(写权限) \3. 等待AT24C02应答、低电平有效 \4. 发送存储地址、AT24C02内部一共有256个字节空间,寻址是从0开始的,范围是(0~255);发送这个存储器地址就是告诉AT24C02接下来应该返回那个地址的数据给单片机。 \5. 等待AT24C02应答、低电平有效 \6. 重新发送起始信号(切换读写模式) \7. 发送设备地址(读权限) \8. 等待AT24C02应答、低电平有效 \9. 循环读取数据: 接收AT24C02返回的数据. 读数据没有字节限制,可以第1个字节、也可以连续将整个芯片读完。 \10. 发送非应答(高电平有效) \11. 发送停止信号 # 三、IIC总线介绍 ### 3.1 IIC总线简介 I2C(Inter-Integrated Circuit)总线是由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备,是微电子通信控制领域广泛采用的一种总线标准。具有接口线少,控制方式简单,器件封装形式小,通信速率较高等优点。 I2C规程运用主/从双向通讯。器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态。 I2C 总线通过串行数据(SDA)线和串行时钟(SCL)线在连接到总线的器件间传递信息。每个器件都有一个唯一的地址识别,而且都可以作为一个发送器或接收器(由器件的功能决定)。 I2C有四种工作模式: 1.主机发送 2.主机接收 3.从机发送 4.从机接收 I2C总线只用两根线:串行数据SDA(Serial Data)、串行时钟SCL(Serial Clock)。 总线必须由主机(通常为微控制器)控制,主机产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。 SDA线上的数据状态仅在SCL为低电平的期间才能改变。 ### 3.2 IIC总线上的设备连接图 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/1/1659318457495535340.png) I2C 总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。 其中上拉电阻范围是4.7K~100K。 ## 3.3 I2C总线特征 I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个从设备都会对应一个唯一的地址(可以从I2C器件的数据手册得知)。主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。 **1. 总线上能挂接的器件数量** I2C总线上可挂接的设备数量受总线的最大电容400pF 限制,如果所挂接的是相同型号的器件,则还受器件地址的限制。 一般I2C设备地址是7位地址(也有10位),地址分成两部分:芯片固化地址(生产芯片时候哪些接地,哪些接电源,已经固定),可编程地址(引出IO口,由硬件设备决定)。 例如: 某一个器件是7 位地址,其中10101 xxx 高4位出厂时候固定了,低3位可以由设计者决定。 则一条I2C总线上只能挂该种器件最少8个。 如果7位地址都可以编程,那理论上就可以达到128个器件,但实际中不会挂载这么多。 **2. 总线速度传输速度:** I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。一般通过I2C总线接口可编程时钟来实现传输速率的调整。 **3. 总线数据长度** I2C总线上的主设备与从设备之间以字节(8位)为单位进行双向的数据传输。 ## 3.4 I2C总线协议基本时序信号 **空闲状态:** SCL和SDA都保持着高电平。 **起始条件:** 总线在空闲状态时,SCL和SDA都保持着高电平,当SCL为高电平期间而SDA由高到低的跳变,表示产生一个起始条件。在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线。 **停止条件:** 当SCL为高而SDA由低到高的跳变,表示产生一个停止条件。 **答应信号:** 每个字节传输完成后的下一个时钟信号,在SCL高电平期间,SDA为低,则表示一个应答信号。 **非答应信号:** 每个字节传输完成后的下一个时钟信号,在SCL高电平期间,SDA为高,则表示一个应答信号。应答信号或非应答信号是由接收器发出的,发送器则是检测这个信号(发送器,接收器可以从设备也可以主设备)。 **注意:起始和结束信号总是由主设备产生。** ## 3.5 起始信号与停止信号 起始信号就是: 时钟线SCL处于高电平的时候,数据线SDA由高电平变为低电平的过程。SCL=1;SDA=1;SDA=0; 停止信号就是: 时钟线SCL处于低电平的时候, 数据线SDA由低电平变为高电平的过程。SCL=1;SDA=0;SDA=1; ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/1/1659318417074786266.png) ## 3.6 应答信号 数据位的第9位就时应答位。 读取应答位的流程和读取数据位是一样的。示例: SCL=0;SCL=1;ACK=SDA; 这个ACK就是读取的应答状态。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/1/1659318404251520825.png) ## 3.7 数据位传输时序 通过时序图了解到,SCL处于高电平的时候数据稳定,SCL处于低电平的时候数据不稳定。 那么对于写一位数据(STM32---AT24C02): SCL=0;SDA=data; SCL=1; 那么对于读一位数据(STM32----AT24C02): SCL=0;SCL=1;data=SDA; ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/1/1659318392734537285.png) ## 3.8 总线时序 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/1/1659318379971942949.png) # 四、IIC总线时序代码、AT24C02读写代码 在调试IIC模拟时序的时候,可以在淘宝上买一个24M的USB逻辑分析仪,时序出现问题,使用逻辑分析仪一分析就可以快速找到问题。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20228/1/1659318368274806343.png) ## 4.1 iic.c 这是IIC模拟时序完整代码 ```cpp #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; } ``` ## 4.2 AT24C02.c 这是AT24C02完整的读写代码 ```cpp #include "at24c02.h" /* 函数功能:检查AT24C02是否存在 返 回 值:1表示失败,0表示成功 */ u8 At24c02Check(void) { u8 data; At24c02WriteOneByteData(255,0xAA); data=At24c02ReadOneByteData(255); if(data==0xAA)return 0; else return 1; } /* 函数功能:AT24C02随机读数据 函数形参:读取的地址(0~255) 返 回 值:读出一个数据 */ u8 At24c02ReadOneByteData(u32 addr) { u8 data; IIC_Start(); //发送起始信号 IIC_WriteOneByteData(AT24C02_WRITE_ADDR); //设置写模式 IIC_GetACK();//获取应答 IIC_WriteOneByteData(addr); //设置读取数据的位置 IIC_GetACK();//获取应答 IIC_Start(); //发送起始信号 IIC_WriteOneByteData(AT24C02_READ_ADDR); //设置读模式 IIC_GetACK();//获取应答 data=IIC_ReadOneByteData(); //接收数据 IIC_SendACK(1); //发送非应答信号 IIC_Stop(); //停止信号 return data; } /* 函数功能:AT24C02写一个字节的数据 函数形参: addr:写入的地址(0~255) data:写入的数据 */ void At24c02WriteOneByteData(u32 addr,u8 data) { IIC_Start(); //发送起始信号 IIC_WriteOneByteData(AT24C02_WRITE_ADDR); //设置写模式 IIC_GetACK();//获取应答 IIC_WriteOneByteData(addr); //设置写入数据的位置 IIC_GetACK();//获取应答 IIC_WriteOneByteData(data); //设置写入的数据 IIC_GetACK();//获取应答 IIC_Stop(); //停止信号 DelayMs(10); //等待写入完毕 } /* 函数 功 能:AT24C02当前位置读一个字节数据 函数返回值:读出的数据 */ u8 At24c02CurrentAddrReadOneByteData(void) { u8 data; IIC_Start(); //发送起始信号 IIC_WriteOneByteData(AT24C02_READ_ADDR); //设置读模式 IIC_GetACK();//获取应答 data=IIC_ReadOneByteData(); //接收数据 IIC_SendACK(1); //发送非应答信号 IIC_Stop(); //停止信号 return data; } /* 函数功能:AT24C02连续读数据 函数形参: u8 addr //读取的地址(0~255) u8 len //读取的长度 u8 *buff //读出的数据存放缓冲区 */ void At24c02ReadByteData(u32 addr,u8 len,u8 *buff) { u8 i; IIC_Start(); //发送起始信号 IIC_WriteOneByteData(AT24C02_WRITE_ADDR); //设置写模式 IIC_GetACK();//获取应答 IIC_WriteOneByteData(addr); //设置读取数据的位置 IIC_GetACK();//获取应答 IIC_Start(); //发送起始信号 IIC_WriteOneByteData(AT24C02_READ_ADDR); //设置读模式 IIC_GetACK();//获取应答 for(i=0;i<len;i++) { buff<i>=IIC_ReadOneByteData(); //接收数据 IIC_SendACK(0); //发送应答信号 } IIC_SendACK(1); //发送非应答信号 IIC_Stop(); //停止信号 } /* 函数功能:AT24C02页写 函数形参: addr:写入的地址(0~255) *data:写入的数据缓冲区 len :写入的长度 1. 页写的缓冲区大小是8个字节,一次最多写8个字节进去。 2. 页写的地址是固定的。 0~7 是第一页 8~15是第二页 */ void At24c02PageWrite(u32 addr,u8 *data,u8 len) { u8 i; IIC_Start(); //发送起始信号 IIC_WriteOneByteData(AT24C02_WRITE_ADDR); //设置写模式 IIC_GetACK();//获取应答 IIC_WriteOneByteData(addr); //设置写入数据的位置 IIC_GetACK();//获取应答 for(i=0;i<len;i++) { IIC_WriteOneByteData(data<i>); //设置写入的数据 IIC_GetACK();//获取应答 } IIC_Stop(); //停止信号 DelayMs(10); //等待写入完毕 } void AT24C02_WriteData(u32 addr,u8 *data,u8 len) { u32 page_remain=8-addr%8; //一页剩余的字节数量 if(page_remain>=len) { page_remain=len; } while(1) { At24c02PageWrite(addr,data,page_remain); if(page_remain==len) { break; } addr+=page_remain; data+=page_remain; len-=page_remain; if(len>=8)page_remain=8; else page_remain=len; } } ```
  • [技术干货] 智能开关解决方案
    智能开关是利用控制板和电子元器件的组合及编程,实现电路智能控制的单元。也有很多利用单片机控制功能,且智能开关的控制方式简单易于实现,现如今在很多家用电器以及照明灯具的控制中被广泛采用。智能开关与普通开关相比,具有手机远程控制,智能音箱控制,定时开关灯,不用布线的优点,而且由于电流比较微弱,即使是潮湿的手触摸开关也不会有安全隐患,并且可以变单控为多路控制,为人们用电带来了方便。随着城市智能化、工业农业生产自动化提升、安防消防的发展。智能开关的市场会有倍增的发展速度。传统开关走向智能化是必不可挡的趋势。一、智能开关解决方案的工作原理:该智能开关解决方案主要以九齐NY8A系列为核心,通过蓝牙通信技术,把传感器实时采集到的数据反馈给智能手机终端控制平台。智能手机控制平台分析、处理收到的反馈数据,通过蓝牙通信技术返回给单片机相应的指令信息。然后单片机接收和解析指令,利用电路和各元器件(计数器、信号发生器等)发出相应的电平信号。电磁继电器接收给定的电平信号,并做出响应,控制智能家用电器的开启或关闭,从而实现家用电器的自动控制。系统的遥控是利用蓝牙模块,用户可通过手机APP实时查看家用电器的状态,从而实现远程控制。采用单火线电路,无须更改原有的线路,可直接替代传统的机械开关。智能触摸开关由于采用电容触摸屏,触控灵敏,面板图案状态可视。操作简单,实用性强!二、智能开关解决方案功能介绍:该智能开关解决方案,主控芯片采用了九齐NY8A系列触摸开关单片机,通过程序烧录,电路设计后,可以实现如下多种功能:1、触摸控制:单击开启/关闭;2、LED显示:触摸点亮,查看功能和状态;3、状态指示:开启时橙色灯亮起,关闭时蓝色灯亮起;4、定时开关:可以定时开启照明,定时关闭照明,也可以通过远程手动控制。
  • [交流吐槽] 14天鸿蒙设备开发实战学习笔记 第四篇:驱动子系统开发
    华为云14天鸿蒙设备开发实战学习笔记第四篇:驱动子系统开发一、 操作GPIO1. 相关APIwifiot_gpio.h接口中:1) Gpiolnit初始化GPIO2) GpioDeinit取消初始化GPIO3) GpioSetDir设置GPIO引脚方向4) GpioGetDir获取GPIO引脚方向5) GpioSetOutputVal设置GPIO引脚输出电平值6) GpioGetOutputVal获取GPIO引脚输出电平值Wifiiot_gpio_ex.h接口中:1) loSetPull:设置GPIO引脚上拉7) loGetPull:获取GPIO引脚上拉8) loSetFunc:设置GPIO引脚功能9) loGetFunc:获取GPIO引脚功能10) IOSetDriverStrength:设置GPIO驱动能力11) IOGetDriverStrength:获取GPIO驱动能力2. 点亮LED1) 查看原理图由上图可知,LED接于GPIO_02引脚,且高电平触发2) GPIO操作流程初始化GPIO :GpioInit();设置GPIO功能:IoSetFunc(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_IO_FUNC_GPIO_2_GPIO);设置工作模式:GpioSetDir(WIFI_IOT_GPIO_IDX_2, WIFI_IOT_GPIO_DIR_OUT);输出电平:GpioSetOutputVal(WIFI_IOT_GPIO_IDX_2, 1);         GpioSetOutputVal(WIFI_IOT_GPIO_IDX_2, 0);二、 GPIO中断1. 相关API1) 设置GPIO引脚中断功能:GpioRegisterlsrFuncunsigned int GpioRegisterIsrFunc(WifiIotGpioIdx id, WifiIotGpioIntType intType, WifiIotGpioIntPolarity intPolarity, GpioIsrCallbackFunc func, char *arg)WifiIotGpioIdx id:外部中断GPIO引脚WifiIotGpioIntType intType:触发模式(电平/边沿)WifiIotGpioIntPolarity intPolarity:触发状态(高低/上升下降沿)GpioIsrCallbackFunc func:中断回调函数char *arg:扩展参数2) 取消GPIO引脚中断功能:GpioUnregisterlsrFuncGpioUnregisterIsrFunc(WifiIotGpioIdx id)WifiIotGpioIdx id:外部中断GPIO引脚3) 屏蔽GPIO引脚中断功能:GpioSetlsrMaskunsigned int GpioSetIsrMask(WifiIotGpioIdx id, unsigned char mask)WifiIotGpioIdx id:外部中断GPIO引脚unsigned char mask:4) 设置GPIO引脚中断触发模式:GpioSetlsrMode2. 电路由上图可知按键F1,F2接在GPIO11和GPIO12,且默认为高电平    3. 核心代码//初始化F1按键,设置为下降沿触发中断    IoSetFunc(WIFI_IOT_IO_NAME_GPIO_11, WIFI_IOT_IO_FUNC_GPIO_11_GPIO);    GpioSetDir(WIFI_IOT_IO_NAME_GPIO_11, WIFI_IOT_GPIO_DIR_IN);    IoSetPull(WIFI_IOT_IO_NAME_GPIO_11, WIFI_IOT_IO_PULL_UP);GpioRegisterIsrFunc(WIFI_IOT_IO_NAME_GPIO_11, WIFI_IOT_INT_TYPE_EDGE, WIFI_IOT_GPIO_EDGE_FALL_LEVEL_LOW, F1_Pressed, NULL);三、 PWM1. 相关API1) 初始化PWM: PwmInitunsigned int PwmInit(WifiIotPwmPort port)port:PWM端口号2) 取消初始化PWM: PwmDeinitPwmDeinit(WifiIotPwmPort port)port:PWM端口号3) 根据输入参数输出PWM: PwmStartunsigned int PwmStart(WifiIotPwmPort port, unsigned short duty, unsigned short freq)port:PWM端口号duty:占空比freq:频率4) 停止PWM输出: PwmStopPwmStop(WifiIotPwmPort port)port:PWM端口号四、 ADC1. 相关API:AdcReadAdcRead(WifiIotAdcChannelIndex channel, unsigned short *data, WifiIotAdcEquModelSel equModel, WifiIotAdcCurBais curBais, unsigned short rstCnt)wifiot_adc.h中包含声明ADC接口函数:AdcRead:根据输入参数从指定的ADC通道读取一段采样数据参数:channel:通道data:指示用于存放读取数据的地址的指针equModel:表示平均算法的次数curBais:表示模拟功率控制模式rstCnt:指示从重置到转换开始的时间计数2. 案例说明将使用板载用户按键F1来模拟GPIO口电压的变化。GPIO_11对应的是ADC Channel 5 ,所以需要编写软件去读取ADC Channel 5的电压。五、 IIC1. 相关API初始化I2C:l2clnitI2cInit(WifiIotI2cIdx id, unsigned int baudrate)id :WIFI_IOT_I2C_IDX_0或WIFI_IOT_I2C_IDX_1baudrate:频率取消l2C初始化:l2cDeinit            I2cDeinit(WifiIotI2cIdx id)id :WIFI_IOT_I2C_IDX_0或WIFI_IOT_I2C_IDX_1将数据写入到I2C设备:l2cWriteI2cWrite(WifiIotI2cIdx id, unsigned short deviceAddr, const WifiIotI2cData *i2cData)id :WIFI_IOT_I2C_IDX_0或WIFI_IOT_I2C_IDX_1deviceAddr:通信地址,要写的地址i2cData:要写的数据从I2C设备读取数据:I2cReadI2cRead(WifiIotI2cIdx id, unsigned short deviceAddr, const WifiIotI2cData *i2cData)id :WIFI_IOT_I2C_IDX_0或WIFI_IOT_I2C_IDX_1deviceAddr:通信地址,要写的地址i2cData:要写的数据设置频率:I2cSetBaudrateI2cSetBaudrate(WifiIotI2cIdx id, unsigned int baudrate)id :WIFI_IOT_I2C_IDX_0或WIFI_IOT_I2C_IDX_1baudrate:频率2. 电路NFC芯片的I2C对应的GPIO引脚是分别是GPIO0和GPIO1,所以需要编写软件使用GPIO 0和GPIO 1产生I2C信号去控制NFC芯片。打开手机NFC贴近开发板,手机会有相应提示六、 UART1. 相关API1) 初始化UART:UartInit UartInit(WifiIotUartIdx id, const WifiIotUartAttribute *param, const WifiIotUartExtraAttr *extraAttr)id:串口号,WIFI_IOT_UART_IDX_0/ _1/ _2/MAXparam:    设置波特率,停止位、数据位、流控等的结构体extraAttr:设置接收发送数据格式等2) 取消UART初始化:UartDeinitUartDeinit(WifiIotUartIdx id)id:串口号,WIFI_IOT_UART_IDX_0/ _1/ _2/MAX3) 从UART读取数据:UartReadUartRead(WifiIotUartIdx id, unsigned char *data, unsigned int dataLen)id:串口号,WIFI_IOT_UART_IDX_0/ _1/ _2/MAXdata:要发送的数据dataLen:数据长度4) 将数据写入UART:UartWriteUartUartWrite(WifiIotUartIdx id, const unsigned char *data, unsigned int dataLen)id:串口号,WIFI_IOT_UART_IDX_0/ _1/ _2/MAXdata:要发送的数据dataLen:数据长度5) 设置UART流控制:SetFlowCtrlUartSetFlowCtrl(WifiIotUartIdx id, WifiIotFlowCtrl flowCtrl)id:串口号,WIFI_IOT_UART_IDX_0/ _1/ _2/MAX流控方式:WIFI_IOT_FLOW_CTRL_NONE/_ RTS_CTS/ _RTS_ONLY/ CTS_ONLY