• [技术干货] 基于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
  • [交流吐槽] 鸿蒙培训第四章笔记
    GPIOGPIO API介绍wifiiot_gpio.h接口中包含声明GPIO接口函数,这些功能用于初始化GPIO。通过控制GPIO输出的高低电平信号来实现LED灯的闪烁。代码首先要使用GPIO的功能,则要引入两个头文件#include "wifiiot_gpio.h"#include "wifiiot_gpio_ex.h"初始化GPIO设置GPIO为复用,例子中设置为普通GPIO在一个参数填的是引脚号(下面红字)IoSetFunc(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_IO_FUNC_GPIO_2_GPIO);设置引脚为输出模式例子中要输出高低电平控制LED灯闪烁,因此用“OUT”依靠“while”循环重复引脚的高低电平变换,实现LED的闪烁拓展实验实现GPIO通过打印将引脚方向的读取,以及高低电平的输出显示出来GPIO中断GPIO中断API介绍wifiiot_gpio.h中包含声明GPIO中断相关函数通过检测引脚中传出的中断信号来判断按键的状态代码与上讲相同,初始化LED灯设置输出方向为OUT与上讲不同的是,接下来初始两个按键的代码同样赋值引脚为GPIO由于是检测输出的高低电平来判断按键状态,所以按键设置为输入状态设置按键在不按下的状态时,LED灯一直处于高电平的状态中因此当按键按下时LED灯为低电平状态GPIO引脚的电平发生跳变,即认定为一次按下按键的动作通过设置GPIO中断API,设置功能拓展实验PWMPWM API介绍wifiiot_pwm.h中包含声明PWM接口函数通过引脚输出的PWM波控制LED灯闪烁的亮度代码初始化GPIO将引脚复用功能设置为PWM模式因为PWM波要为输出状态,所以设置引脚为输出初始化PWM波利用while循环控制LED灯的亮度变化拓展实验ADCADC API介绍wifiiot_adc.h中包含声明ADC接口函数利用F1按键模拟电压的变化即读取ADC引脚接口的电压值并输出显示代码首先上拉了ADC对应GPIO引脚,使引脚一直处于高电平的状态再利用whlie循环读取电压值并显示打印读取电压值用的是AdcRead()接口函数可以修改取得次数获得平均值,例子中获取了8次求平均值I2CI2C API介绍wifiiot _i2c.h中包含声明I2C接口函数wifiiot_i2c_ex.h中包含声明扩展I2C接口函数NFC芯片接在两个GPIO引脚上因此利用编写程序让GPIO引脚产生I2C信号控制NFC芯片代码改编写程序实现了NFC芯片的写数据的操作,即将数据写入初始化GPIO将两个GPIO接口复用功能设置为I2C初始化I2C接口设置I2C频率用于中途修改频率,防止再次进行初始化操作调用I2CD 驱动,来实现NFC的写操作其实就是调用了读写的操作,以下为三数据的编写UARTUART API介绍wifiiot_uart.h中包含声明UART接口函数利用UART对应的两个引脚(GPIO5,GPIO6)对UART数据进行收代码调用UartInit进行初始化编写程序中配置的波特率要和接入的UART上的波特率相同初始化之后进行数据的收发UartWrite进行发数据UartRead进行收数据实现自发自收的功能通过打印将收发的数据显示出来
  • [交流吐槽] 鸿蒙培训第四章笔记
    GPIOGPIO API介绍wifiiot_gpio.h接口中包含声明GPIO接口函数,这些功能用于初始化GPIO。通过控制GPIO输出的高低电平信号来实现LED灯的闪烁。代码首先要使用GPIO的功能,则要引入两个头文件#include "wifiiot_gpio.h"#include "wifiiot_gpio_ex.h"初始化GPIO设置GPIO为复用,例子中设置为普通GPIO在一个参数填的是引脚号(下面红字)IoSetFunc(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_IO_FUNC_GPIO_2_GPIO);设置引脚为输出模式例子中要输出高低电平控制LED灯闪烁,因此用“OUT”依靠“while”循环重复引脚的高低电平变换,实现LED的闪烁拓展实验实现GPIO通过打印将引脚方向的读取,以及高低电平的输出显示出来GPIO中断GPIO中断API介绍wifiiot_gpio.h中包含声明GPIO中断相关函数通过检测引脚中传出的中断信号来判断按键的状态代码与上讲相同,初始化LED灯设置输出方向为OUT与上讲不同的是,接下来初始两个按键的代码同样赋值引脚为GPIO由于是检测输出的高低电平来判断按键状态,所以按键设置为输入状态设置按键在不按下的状态时,LED灯一直处于高电平的状态中因此当按键按下时LED灯为低电平状态GPIO引脚的电平发生跳变,即认定为一次按下按键的动作通过设置GPIO中断API,设置功能拓展实验PWMPWM API介绍wifiiot_pwm.h中包含声明PWM接口函数通过引脚输出的PWM波控制LED灯闪烁的亮度代码初始化GPIO将引脚复用功能设置为PWM模式因为PWM波要为输出状态,所以设置引脚为输出初始化PWM波利用while循环控制LED灯的亮度变化拓展实验ADCADC API介绍wifiiot_adc.h中包含声明ADC接口函数利用F1按键模拟电压的变化即读取ADC引脚接口的电压值并输出显示代码首先上拉了ADC对应GPIO引脚,使引脚一直处于高电平的状态再利用whlie循环读取电压值并显示打印读取电压值用的是AdcRead()接口函数可以修改取得次数获得平均值,例子中获取了8次求平均值I2CI2C API介绍wifiiot _i2c.h中包含声明I2C接口函数wifiiot_i2c_ex.h中包含声明扩展I2C接口函数NFC芯片接在两个GPIO引脚上因此利用编写程序让GPIO引脚产生I2C信号控制NFC芯片代码改编写程序实现了NFC芯片的写数据的操作,即将数据写入初始化GPIO将两个GPIO接口复用功能设置为I2C初始化I2C接口设置I2C频率用于中途修改频率,防止再次进行初始化操作调用I2CD 驱动,来实现NFC的写操作其实就是调用了读写的操作,以下为三数据的编写UARTUART API介绍wifiiot_uart.h中包含声明UART接口函数利用UART对应的两个引脚(GPIO5,GPIO6)对UART数据进行收代码调用UartInit进行初始化编写程序中配置的波特率要和接入的UART上的波特率相同初始化之后进行数据的收发UartWrite进行发数据UartRead进行收数据实现自发自收的功能通过打印将收发的数据显示出来 
  • [技术干货] 2021年全国大学生电子设计大赛F题——智能送药小车,全方位解决方案+程序代码(详细注释)山东赛区国奖-转载
    1.赛题及硬件方案分析:        刚得到消息,我们队被推荐为国奖,国一国二还没定,大概率是国二,因为测评时发挥部分的小车二在转弯时因为电池原因急转弯失败,发挥部分就没有成功验收。刚收到消息就更新了一下,本方案的成本可以说是此赛题方案中成本最低的。        通过题目可以很容易的看出来,这道题有最难的两个点:巡线+数字识别。        此题的巡线又不同于以往电赛题目的黑白巡线,此题是巡红白线。这样就会带来一个问题,以往巡黑白线我们最常用的是红外传感器,但是此题中的红线和白色背景两者间的吸光能力差别很小,一般的红外传感器根本难以分辨,这就需要用到较为不常用的灰度传感器或者是颜色传感器。据我所知,开赛前就已经准备好这两种传感器的小组还是较少的,包括我们组就没有准备,只能用摄像头代替,用对应的算法进行巡线。        数字识别用到的硬件就没什么好说的了,肯定用到摄像头,我们组用的是赛前已经准备好的OpenMV,OpenMV上手还是很快的,但是需要有一定的Python基础 。在比赛过程中也有很多大佬认为OpenMV同时进行巡线和数字识别的话,其帧率会很低,我们组当然也遇到了以上问题,具体的解决方法在后面会讲到。        另外,题目中要求检测到200g药品的装载和卸载,一般的话可以用压力传感器,但为了方便我们直接用的红外对管,以检测障碍物的方式判断药品是否装载。2.用到的主要器件清单:        STM32F407ZGT6单片机        OpenMV4智能处理摄像头        舵机转向四驱车        锂电池、5V、12V降压模块        L298N电机驱动模块        红外传感器        蓝牙模块HC-05        LED指示灯  以上器件都是一辆小车用到的,如果要做发挥部分的话就需要两辆车了,不过小车2用到的器件基本不变。我们采取的方案是一辆小车用一个摄像头,我看网上有的大佬一辆小车左右两边各有一个摄像头,这样的话写代码就会较为简便,不过一般的OpenMV价格在300到500之间,除非你的经费充足且赛前已经准备好,否则还是老老实实用一个摄像头肝吧.....就像我们组,当时只有一个OpenMV,第二个还是临时找关系借的。3.各部分思路及代码实现        (1).小车舵机、马达驱动                这一部分的代码就不贴上了,都是基本的PWM输出,可以参照正点原子的相关例程,并且两辆小车的代码都一样,只不过舵机让车轮保持直行时的PWM脉宽不同,到时候自己找到并在代码中改数字即可。        (2).蓝牙通信                蓝牙通信的代码说白了就是串口通信,代码如下: #include "stm32f4xx.h"#include "bluetooth.h"/*蓝牙通信:USART3,所用引脚为PC10,PC11*/void bluetooth_U3_Init(){GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);  //使能GPIOA时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); //使能USART2时钟//串口1对应引脚复用映射GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_USART3); //GPIOA9复用为USART1GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_USART3); //GPIOA10复用为USART1//USART1端口配置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; //GPIOA9与GPIOA10GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHzGPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; //上拉GPIO_Init(GPIOC, &GPIO_InitStructure); //初始化PA9,PA10//USART1 初始化设置USART_InitStructure.USART_BaudRate = 9600;//波特率设置USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式USART_Init(USART3, &USART_InitStructure);//初始化串口1USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); //开启相关中断//Usart1 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;  //串口1中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;  //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  //IRQ通道使能NVIC_Init(&NVIC_InitStructure);  //根据指定的参数初始化VIC寄存器、USART_Cmd(USART3, ENABLE); //使能串口1}/* 蓝牙使能引脚:PA9 */void bluetooth_IO_Init(){GPIO_InitTypeDef GPIO_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); //??GPIOA??GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //??50MHzGPIO_InitStructure.GPIO_OType = GPIO_OType_OD; //??????GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //??GPIO_Init(GPIOC, &GPIO_InitStructure); //???PA9,PA10}注意:以上代码中蓝牙使能引脚PA9可以不进行初始化,这样就会一直使能,如果要进行初始化的话需要设置为开漏输出 。        (3).单片机与OpenMV的串口通信                此代码与蓝牙通信代码一样,都是串口通信,只不过我用的是USART5,所用引脚为PC12,PD2        (4).单片机与OpenMV的通信协议                因为OpenMV只能通过串口传输字符(除非使用OpenMV的数据流打包,但是我们传输的一般一次只有一个字符,所以不用数据流打包),比如,你要把 52 这个数传给单片机,OpenMV会自动把 52 这个数转换为两个字符'5'和‘2’传输,所以我们要在单片机和OpenMV之间定义一个统一的通信协议,代码如下(此代码中OpenMV传输的整数的范围在-99到99之间):/* openmv发送过来的字符串转换为数据:帧头为' ',帧尾为'.',转换后只保留整数,中间用小数点判断,函数返回值为0则数据格式出错*/signed char CharCon(signed char c[],signed char x){int i = 0;int j = 0;while(c[i] != '.') {c[i] = c[i] - '0';i++;}j = i;if(c[0] == ('-'-'0')){i = 4;c[0] = 0;}switch(i){case 1:x = c[i-1];break;case 2:x = c[i-1] + 10*c[i-2];break;case 3:x = -(c[i-1] + 10*c[i-2]);break;case 4:x = -(c[j-1] + 10*c[j-2]);break;default:return 0;break;}    return x;}                OpenMV发送的数据格式:uart.write(" ")uart.write(str(int(deflection_angle)))uart.write(".")        (5).单片机main文件中的函数:                main函数中需要用到状态机的编程逻辑,在单片机收到OpenMV发送的数据时需要判断接收到的是数据帧,还是命令,比如收到的是'f',则接收到的是笔直向前的命令。// PB10是红外对管的输入引脚#include "stm32f4xx.h"#include "sys.h"#include "delay.h"#include "duoji.h"#include "dianji.h"#include "bluetooth.h"#include "to_openmv.h"#include "hongwai.h"#include "usart.h"#define Center 88           //小车前轮保持居中不偏时单片机输出的PWN脉宽#define Hongwai_IO PBin(10) //装载完药品,hongwai_IO = 0#define Red PAout(14)#define Green PEout(5)#define White PAout(13)//以下定义的是状态机的各种状态,在最终的代码中有些并没有用到#define Start 0 //初始的等待状态#define Turn_left 1#define Turn_right 2#define Turn_no 3#define Stop 4#define Wait 5#define Slower 6#define Faster 7#define Forward 8#define Backward 9#define Back 10#define Change 11#define Stoping 12#define Changezuo 13#define Changeyou 14#define Fuwei 15#define Finish 16#define Nfuwei 17#define Zanwait 18int t = 0;int speed = 200;               //马达速度signed char angle = 0;         //巡线时舵机转的角度signed char change_angle = 30; //路口停下来识别数字时微调的角度uint16_t bluetooth = 0;        //接收到的蓝牙消息uint16_t temp = 0;char state = 0;                  //状态机中的当前状态char flag = 0;                   //是否已经识别到要去的病房号signed char rec_flag = 0;        //单片机与OpenMV通信中的命令标志位signed char openmv_rec[4] = {0}; //OpenMV发送的数据signed char i = 0;signed char task = 0; //当前执行的是任务几/* 指示灯引脚初始化 */void LED_Init(){    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);    GPIO_InitTypeDef GPIO_Instruct;    GPIO_Instruct.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14;    GPIO_Instruct.GPIO_Mode = GPIO_Mode_OUT;    GPIO_Instruct.GPIO_Speed = GPIO_Speed_100MHz;    GPIO_Instruct.GPIO_OType = GPIO_OType_PP;    GPIO_Instruct.GPIO_PuPd = GPIO_PuPd_UP;    GPIO_Init(GPIOA, &GPIO_Instruct);    GPIO_Instruct.GPIO_Pin = GPIO_Pin_5;    GPIO_Init(GPIOE, &GPIO_Instruct);}void All_Init(){    LED_Init();    hongwai_IO_Init();    dianji_IO_Init();    dianji_PWM_Init(1000 - 1, 84 - 1);    duoji_PWM_Init(2000 - 1, 840 - 1); //0    bluetooth_U3_Init();    //bluetooth_IO_Init();    openmv_U5_Init();    // computer_U1_Init();    uart_init(9600);    delay_init(168);}void turn_r(int t){    TIM_SetCompare1(TIM3, 88);    PFout(5) = 0;    PFout(7) = 1;    PCout(4) = 1;    PCout(5) = 0;    TIM_SetCompare1(TIM5, t);    TIM_SetCompare1(TIM13, t);    delay_ms(3000);}int main(void){    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    All_Init();    while (1)    {        switch (state)        {        case Start:            turn(0);            while (flag == 0); //已经识别到病房号            Green = 0;            White = 1;            delay_ms(1000);            White = 0;            while (flag == 1)            {                if (Hongwai_IO == 0)                {                    delay_ms(1000);                    if (Hongwai_IO == 0)                    {                        state = Forward;                        break;                    }                }            }            break;        case Forward:            turn(angle);            zhengzhuan(speed);            break;        case Turn_right:            zhengzhuan(speed);            turn_right();            delay_ms(400);            break; //假设急转弯1.5S已经转过去,具体参数需要调试        case Turn_left:            turn_left();            zhengzhuan(200);            delay_ms(400);            break; //假设急转弯1.5S已经转过去,具体参数需要调试        case Stop:            stop();            delay_ms(20);            fanzhuan(speed);            delay_ms(300);            stop();            turn(0);            state = Stoping;            break;        case Wait:            stop();            turn(0);            Red = 1;            if (Hongwai_IO == 1)            {                delay_ms(500);                if (Hongwai_IO == 1)                {                    USART_SendData(UART5, 's');                    Red = 0;                    state = Back;                    flag = 0;                }            }            break;        case Zanwait:            stop();            turn(0);            Red = 1;            if (Hongwai_IO == 1)            {                delay_ms(500);                if (Hongwai_IO == 1)                {                    USART_SendData(USART3, 'o');                    delay_ms(10);                    USART_SendData(UART5, 's');                    Red = 0;                    state = Back;                    flag = 0;                }            }            break;        case Back:            fanzhuan(speed - 50);            // turn(-angle);            delay_ms(400);            state = Backward;            USART_SendData(UART5, 's');            break;        case Backward:            turn(3 - angle);            fanzhuan(speed - 50);            break;        case Change:            turn(angle);            zhengzhuan(speed);            state = Stop;            delay_ms(500);            break;        case Changezuo:            fanzhuan(speed);            delay_ms(400);            stop();            turn(30);            zhengzhuan(speed);            change_angle = 30;            delay_ms(500);            state = Stoping;            USART_SendData(UART5, 's');            break;        case Changeyou:            fanzhuan(speed);            delay_ms(400);            stop();            turn(-30);            zhengzhuan(speed);            change_angle = -30;            delay_ms(500);            state = Stoping;            USART_SendData(UART5, 's');            break;        case Fuwei:            turn(change_angle);            fanzhuan(speed);            delay_ms(500);            stop();            turn(0);            zhengzhuan(speed);            delay_ms(400);            stop();            break;        case Nfuwei:            turn(change_angle);            fanzhuan(speed);            delay_ms(500);            stop();            turn(0);            break;        case Stoping:            stop();            turn(0);            break;        case Finish:            stop();            turn(0);            Green = 1;            flag = 0;            state = Start;            break;        default:            break;        }    }}/* 蓝牙接收 */void USART3_IRQHandler(){    if (USART_GetITStatus(USART3, USART_IT_RXNE))    {        bluetooth = USART_ReceiveData(USART3);    }    USART_ClearITPendingBit(USART3, USART_IT_RXNE);}/* openmv接收 */void UART5_IRQHandler(){    if (USART_GetITStatus(UART5, USART_IT_RXNE))    {        char j = 0;        signed char temp = USART_ReceiveData(UART5);        if (temp == ' ')        {            rec_flag = 1;            i = 0;        }        else if (rec_flag == 1)        {            if (temp == '.')            {                openmv_rec[i] = '.';                angle = CharCon(openmv_rec, angle);                rec_flag = 0;                for (j = 0; j < 4; j++)                    openmv_rec[j] = 0;            }            else            {                openmv_rec[i] = temp;                i++;            }        }        else //rec_flag == 0,接收的为指令        {            switch (temp)            {            case 's':                state = Stop;                break;            case 'l':                state = Turn_left;                break;            case 'r':                state = Turn_right;                break;            case 'f':                state = Forward;                break;            case 'z':                state = Wait;                break;            case 'b':                state = Backward;                break;            case 'd':                state = Finish;                break;            case 'e':                state = Zanwait;                break;            case 'o':                flag = 1;                break;            case 'c':                state = Change;                break;            case 'x':                state = Changezuo;                break;            case 'y':                state = Changeyou;                break;            case 'w':                state = Fuwei;                break;            case 'n':                state = Nfuwei;                break;            case 't':                state = Stoping;                break;            default:                break;            }        }    }    USART_ClearITPendingBit(UART5, USART_IT_RXNE);}        一下部分是OpenMV部分的代码,包括巡线和数字识别的算法,以及赛题各任务的判断逻辑        (6).巡线                思路:将摄像头一帧图片的上半部分划分为三个平行的感兴趣区,在三个感兴趣区中分别寻找最大的红色色块,获得三个中心坐标,然后给予其不同的权重后计算平均质心坐标,用此质心坐标计算得到巡线时的偏转角度。                为什么只将一帧图片的上半部分?因为下半部分容易受到小车的阻挡或者阴影干扰   RED_THRESHOLD = [(200,10,10),(255,50,50)]#色块阈值BLACK_THRESHOLD = [(0, 48, -58, 49, -36, -1)]ROIS = [(0, 15, 160, 25, 0.9),        (0, 40, 160, 25, 0.6),        (0, 65, 160, 30, 0.6)]#--------------------------------------- 判断直线角度 ------------------------------------------------    centroid_sum = 0    dx = 0    for r in ROIS:        blobs = img.find_blobs(RED_THRESHOLD, roi=r[0:4], merge=True) # r[0:4] is roi tuple.        if blobs:            # Find the blob with the most pixels.            largest_blob = max(blobs, key=lambda b: b.pixels()) # 返回最大值像素点            #img.draw_rectangle(largest_blob.rect())            # 将此区域的像素数最大的颜色块画矩形和十字形标记出来            #img.draw_cross(largest_blob.cx(),largest_blob.cy())            #print(largest_blob.cx())            centroid_sum += (80-largest_blob.cx())*r[4] # r[4] is the roi weight.            weight_sum = weight_sum + r[4]            #计算centroid_sum,centroid_sum等于每个区域的最大颜色块的中心点的x坐标值乘本区域的权值    if weight_sum:        center_pos = (centroid_sum / weight_sum)        weight_sum = 0#--------------------------------------- 什么也识别不到则认为是到达了虚线 --------------------------------#----------------------------------------- 求解直线角度-----------------------------------------------    deflection_angle = 0    ns = center_pos/60  # QQVGA 160x120.    deflection_angle = -math.atan(ns)    deflection_angle = math.degrees(deflection_angle)    deflection_angle = 0 - int(deflection_angle)    #print(str(deflection_angle))#------------------------------------------ 直行 ----------------------------------------------------    uart.write(" ")    uart.write(str(int(deflection_angle)))    uart.write(".")        (7).识别十字路口                我们需要识别十字路口,然后停车进行数字识别或者进行其他操作。思路:感兴趣区域取左上角和右上角,当两个区域同时检测到红色色块时,就表示识别到了十字路口。ROI = [(10,0,40,50),(110,0,50,50)]    blobs1 = img.find_blobs(RED_THRESHOLD, roi=ROI[0],area_threshold=150, merge=True)    if blobs1:        #img.draw_rectangle(blobs1.arect())        blobs2 = img.find_blobs(RED_THRESHOLD, roi=ROI[1], area_threshold=150,merge=True)        if blobs2:#表示已经识别到十字路口            #img.draw_rectangle(blobs2.rect())            print("+")        (8).数字识别                一开始我们组用的是特征点检测的方法,写好代码以后发现效果并不好,不仅速度慢而且识别准确率很低,后来就改成了模板匹配的方法,这种方法唯一的缺点就是要想识别准确率高就需要截取较多的模板,不仅有多种大小,还要有多种角度,我们小组最终截取了将近200张模板,如果逐一进行匹配的话速度会非常的慢,但是根据任务要求我们可以灵活匹配,比如基础任务一我们只需要匹配数字一和二的模板,其他任务只有在一开始识别病房号时需要从3到8逐一匹配,但识别到病房号以后,在路口进行数字识别时只需要对病房号的模板进行匹配并判断其在小车的左侧还是右侧,以此来判断应该做转还是右转。核心代码如下:#------------------------------------ 初始时刻识别病房号 ----------------------------------------------    while(wait == 0):        if(task == 0):            img = sensor.snapshot().lens_corr(strength = 1.6, zoom = 1.0)            img=img.to_grayscale()            #histogram = img.get_histogram()            #Thresholds = histogram.get_threshold()            #v = Thresholds.value()            img.binary([(0,v)])            for t in templates12:                template = image.Image(t)                #对每个模板遍历进行模板匹配                r = img.find_template(template, 0.65,step=4, search=SEARCH_EX)                if r:                    #img.draw_rectangle(r, color=255)                    print(t[10]) #打印模板名字                    number=int(t[10])                    uart.write("o")                    wait = 1                    break        else:#初始时刻任务二三识别病房号            img = sensor.snapshot().lens_corr(strength = 1.6, zoom = 1.0)            img=img.to_grayscale()            img.binary([(0,v)])            for t in templates3:                template = image.Image(t)                r = img.find_template(template, 0.65,step=4, search=SEARCH_EX)                if r:                    shuzi=int(t[10])                    array1[shuzi-1] =array1[shuzi-1]+1            for t in templates4:                template = image.Image(t)                r = img.find_template(template, 0.55,step=4, search=SEARCH_EX)                if r:                    shuzi=int(t[10])                    array1[shuzi-1] =array1[shuzi-1]+1            for t in templates5:                template = image.Image(t)                r = img.find_template(template, 0.60,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60))                if r:                    shuzi=int(t[10])                    array1[shuzi-1] =array1[shuzi-1]+1            for t in templates6:                template = image.Image(t)                r = img.find_template(template, 0.61,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60))                if r:                    shuzi=int(t[10])                    array1[shuzi-1] =array1[shuzi-1]+1            for t in templates7:                template = image.Image(t)                r = img.find_template(template, 0.65,step=4, search=SEARCH_EX)                if r:                    shuzi=int(t[10])                    array1[shuzi-1] =array1[shuzi-1]+1            for t in templates8:                template = image.Image(t)                r = img.find_template(template, 0.65,step=4, search=SEARCH_EX)                if r:                    shuzi=int(t[10])                    array1[shuzi-1] =array1[shuzi-1]+1            if (max(array1) > 1):                m = array1[0]                for index in range(0,8):                    if array1[index] > m:                        m = array1[index]                        number = index + 1                print(str(number))                uart.write("o")                wait = 1                num = task                if(num >2):                    num = num - 2                continue            else:#没识别出来则重新循环                print("no")                continue#--------------------------------------- 十字路口前识别数字--------------------------------------------    while(wait == 2):        if((task == 2)and(last != 3)):            if(n1<2):                for loop in range(0,3):                    img = sensor.snapshot()                    img.lens_corr(1.6)                    img=img.to_grayscale()                    img.binary([(0,v)])                    if number==3:                        for t in template3:                            template = image.Image(t)                            r = img.find_template(template, 0.60,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60))                            if r:                                #img.draw_rectangle(r,color = 255)                                print(str(number))                                n1=n1+1                                if r[0]<60:                                    zuo=zuo+1                                else:                                    you=you+1                    if number==5:                        for t in template5:                            template = image.Image(t)                            r = img.find_template(template, 0.60,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60))                            if r:                                #img.draw_rectangle(r,color = 255)                                print(str(number))                                n1=n1+1                                if r[0]<60:                                    zuo=zuo+1                                else:                                    you=you+1                       原文链接:https://blog.csdn.net/qq_45204725/article/details/121375031
  • [交流吐槽] 第五次笔记
    GPIO操作GPIO api介绍GPIO引脚查看ADC采样开发ADC的API介绍wifiiot_adc.h接口简介ADC接口函数参数查看ADC对应的GPIO引脚使用用户按键1来模拟GPIO电压变化,GPIO_11对应的是ADC Channel 5,需要编写软件去读取ADC Channel 5的电压按键按下时是低电压,大约在0V左右ADC读取GPIO的电压值打开B4工程的adc_example.c文件,即可查看读取ADC通道5的电压值的代码*读取电压值用的是AdcRead,第一个参数要用adc的第五个通道I2C总线开发I2C的API介绍wifiiot_i2c.h声明接口函数wiffiot_i2c_ex.h包含扩展I2C接口函数查看NFC的I2C对应的GPIO引脚NFC芯片的I2C对应的GPIO引脚是,GPIO0和GPIO1,需要编写软件使用GPIO_0和GPIO_1产生I2C信号去控制NFC芯片I2C读写NFC芯片打开B5工程的i2c_example.c文件,即可查看代码可以实现NFC芯片的写操作*I2C协议的写操作UART读写开发API介绍查看对应GPIO引脚UART1对应的GPIO引脚为GPIO5和GPIO6
  • [交流吐槽] 第四章(4-6)
    ## ADC采样开发 ### ADC的API介绍 wifiiot_adc.h接口简介 ADC接口函数 ![屏幕截图 2022-07-22 154224.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658479663040215506.png) 参数 ![屏幕截图 2022-07-22 154532.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658479912204896606.png) ### 查看ADC对应的GPIO引脚 ![屏幕截图 2022-07-22 154630.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480064236968018.png) 使用用户按键1来模拟GPIO电压变化,GPIO_11对应的是ADC Channel 5,需要编写软件去读取ADC Channel 5的电压 按键按下时是低电压,大约在0V左右 ### ADC读取GPIO的电压值 打开B4工程的adc_example.c文件,即可查看读取ADC通道5的电压值的代码 *读取电压值用的是AdcRead,第一个参数要用adc的第五个通道 ## I2C总线开发 ### I2C的API介绍 wifiiot_i2c.h声明接口函数 ![屏幕截图 2022-07-22 160248.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480087428143307.png) wiffiot_i2c_ex.h包含扩展I2C接口函数 ![屏幕截图 2022-07-22 160403.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480101622340307.png) ### 查看NFC的I2C对应的GPIO引脚 ![屏幕截图 2022-07-22 160639.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480114101284406.png) NFC芯片的I2C对应的GPIO引脚是,GPIO0和GPIO1,需要编写软件使用GPIO_0和GPIO_1产生I2C信号去控制NFC芯片 ### I2C读写NFC芯片 打开B5工程的i2c_example.c文件,即可查看代码 ![屏幕截图 2022-07-22 161128.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480129803296005.png) 可以实现NFC芯片的写操作 *I2C协议的写操作 ![屏幕截图 2022-07-22 161717.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480140977450636.png) ## UART读写开发 ### UART的API介绍 wifiiot_uart.h包含UART接口函数 ![屏幕截图 2022-07-22 162723.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480153520548667.png) 查看UART1对应的GPIO引脚 ![屏幕截图 2022-07-22 162746.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480163555947784.png) UART1对应的GPIO引脚是GPIO5和GPIO6 ### UART读写数据 打开B6工程下的uart_example.c文件,即可查看代码 *将RXD和TXD短接在一起,实现自接自收 ![屏幕截图 2022-07-22 163930.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480176334897918.png)
  • [技术干货] 第四章笔记总结
    # 驱动子系统开发 ## 1.驱动GPIO (1)具体内容 ・GPIO相关API ・如何操作GPIO点亮LED灯 ・如何读取GPIO电平状态 (2)GPIO API介绍 wifiot_gpio.h接口简介: 注意: 这个.h中包含声明GPIO接口函数,这些功能用于初始化GPIO。 常用接口及其常用功能 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658399591179340484.png) (3)与LED对应的GPIO引脚 LED对应的GPIO引脚是GPIO2通过控制GPIO2输出的的电平信号来实现LED灯的闪烁。 ·高电平时点亮LED灯。 ·低电平时熄灭LED灯。 (4)操作GPIO点亮LED 相关代码:![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658399875307339272.png) (5)本节收获: 学习了GPIO的主要引脚 学习了操作GPIO来点亮LED灯 ## 2.GPIO的中断 (1)主要内容 ·GPIO中断相关API ·如何使用GPIO中断相关API ·如何通过GPIO中断判断按键状态 (2)GPIO中断API介绍 其实就是wifiot_gpio.h中包含声明GPIO中断相关函数。 主要接口及其功能描述: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658400329778899183.png) (3)如何查看按键对应的GPIO引脚 F1和F2按键对应的GPIO引脚是分别是GPIO11和GPIO12,通过检测GPIO的电平信号来判断按键的状态。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658400428313648516.png)
  • [交流吐槽] 第4次
    ## 软件定时器 ------ ### 软件定时器基本概念 ------ 软件定时器:基于系统Tick时钟中断由软件模拟的定时器,构架于硬件定时器之上。相比硬件定时器提供了更多的定时器数量。 软件定时器功能上支持: ●静态裁剪:能通过宏关闭软件定时器功能。 ●软件定时器创建。 ●软件定时器启动。 ●软件定时器停止。 ●软件定时器删除。 ●软件定时器剩余Tick数获取。 ### 软件定时器运作机制 ------ **软件定时器使用了系统的一个队列和一个任务资源,软件定时器的触发遵循队列规则,先进先出,定时时间短的定时器总是比定时时间长的靠近队列头,满足优先被触发的准则。** 软件定时器以Tick为基本计时单位,当用户创建并启动一个软件定时器时,LiteOS会根据当前系统Tick时间及用户设置的定时间隔确定该定时器的到期Tick时间,并将该定时器控制结构挂入计时*全局链表*。. 当Tick中断到来时,在Tick中断处理函数中**扫描**软件定时器的*计时全局链表*,看是否有定时器超时,若有则将超时的定时器记录下来。Tick中断处理函数结束后,软件定时器任务(优先级为最高)被唤醒,在该任务中调用之前记录下来的定时器的超时回调函数。 ### 软件定时器创建 ------ ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394466050944442.png) **vs code内路径如下** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394481139827804.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394482481129656.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394487295251843.png) 执行一系列操作后,编译。 ## 信号量 ### 信号量的基本概念 ------ 1.信号量是实现任务间通信的机制,达到任务间同步或临界资源的互斥访问。 2.提供同步或互斥实现临界资源的保护 3.信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。含义分两种: ​ 1)0,表示没有积累下来的Post信号量操作,且有可能再次信号量上 阻塞的任务 ​ 2)正值,表示有一个或多个Post信号量操作。 4、以同步为目的的信号量和以互斥为目的的信号量在使用有如下不同: **1)用作互斥时**,信号量创建后记数是满的,在需要使用临界资源时,先取信号量,使其变 空,这样其他任务需要使用临界资源时就会因为无法取到信号量而阻塞,从而保证了临界资 源的安全。 **2)用作同步时**,信号量在创建后被置为空,任务1取信号量而阻塞,任务2在某种条件发生后, 释放信号量,于是任务1得以进入READY或RUNNING态,从而达到了两个任务间的同步。 ### 信号量运作机制 ------ #### 运作原理 1、信号量初始化,为配置的N个信号量申请内存(N值可以由用户自行配置,受内存限制), 并把 所有的信号量初始化成未使用,并加入到未使用链表中供系统使用。 2、信号量创建,从未使用的信号量链表中获取-个信号量资源,并设定初值。 3、信号量申请,若其计数器值大于0,则直接减1返回成功。否则任务阻塞,等待其它任务释 放该信号量,等待的超时时间可设定。当任务被一个信号量阻塞时,将该任务挂到信号量等待 任务队列的队尾。 4、信号量释放,若没有任务等待该信号量,则直接将计数器加1返回。否则唤醒该信号量等待 任务队列上的第一个任务。 5、信号量删除,将正在使用的信号量置为未使用信号量,并挂回到未使用链表。 6、信号量允许多个任务在同- -时刻访问同- -资源,但会限制同一时刻访问此资源的最大任务数目。访问同一资源的任务数达到该资源的最大数量时,会阻塞其他试图获取该资源的任务,直到有任务释放该信号量。 #### 实现信号量功能 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394505266232641.png) ## 事件管理 ### 实现事件功能 ------ ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394521597802008.png) ## 互斥锁 独占式访问,同一时间只能一个进入公共资源 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394779095181242.png) ## 消息队列 1.用于任务通信,接受任务或中断的不固定长度信息,并根据接口选择是否存放。 2.任务可从队列读取信息,信息为空,挂起读取任务;有新消息,唤醒任务 3.提供异步处理,允许蒋雄安熙放入队列,但并不立即处理,同时对列起缓冲消息作用 **LiteOS用队列实现异步通信特性如下:** ●消息以先进先出方式排队,支持异步读写工作方式。 ●读队列和写队列都支持超时机制。 ●发送消息类型由通信双方约定,可以允许不同长度(不超过队列节点最大值)消息。 ●一个任务能够从任意一 个消息队列接收和发送消息。。多个任务能够从同一个消息队列接收 和发送消息。 ●当队列使用结束后,如果是动态申请的内存,需要通过释放内存函数回收。 #### 运作原理: 创建队列时,根据用户传入队列长度和消息节点大小来开辟相应 的内存空间以供该队列使用,返回队列ID。 在队列控制块中维护一个消息头节点位置Head和一个消息尾节点 位置Tail来表示当前队列中消息存储情况。Head表示队列中被占 用消息的起始位置。Tail表示队列中被空闲消息的起始位置。刚创 建时Head和Tail均指向队列起始位置。 写队列时,根据Tail找到被占用消息节点末尾的空闲节点作为数据 写入对象。 读队列时,根据Head找到最先写入队列中的消息节点进行读取。 删除队列时,根据传入的队列ID寻找到对应的队列,把队列状态 置为未使用,释放原队列所占的空间,对应的队列控制头置为初 始状态。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394804745794960.png) #### 实现消息队列功能 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394817815916576.png) ## 操作GPIO ### 接口简介 ------ ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394833464102972.png) ### 查看LED对应的GPIO引脚 ------ LED对应的GPIO引脚是GPIO2通过控制GPIO2输出的的电平信号来实 现LED灯的闪烁(高亮低灭)。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394848591357658.png) 路径:sample/B1_basic_led_blink/led_example(.c) ## GPIO中断 ### 中断API介绍 ------ **wifilot_gpio.h接口简介:** wifiliot gpio.h中包含声明GPIO中断相关函数。 | 接口名 | 功能描述 | | :-------------------- | ------------------------- | | GpioRegisterlsrFunc | 设置GPIO引脚中断功能| | GpioUnregisterlsrFunc | 取消GPIO引脚中断功能| | GpioSetlsrMask | 屏蔽GPIO引脚中断功能| | GpioSetlsrMode | 设置GPIO引脚中断触发模式 | ### 按键对应的GPIO ------ **F1**和**F2**按键对应的GPIO引脚是分别是**GPIO11**和**GPIO12**,通过检测GPIO的电平信号来判断按键的状态。 SPI和按键用用一个接口通过电阻连接,当使用SPI时不可操作按键。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394905461509991.png) ### 驱动子系统开发之PWM ------ ### API介绍 ------ ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394928054343244.png) ### LED对应的GPIO引脚 ------ LED对应的GPIO引脚是**GPIO2**通过控制GPIO2输出的的电平信号来实现LED灯的闪烁(高亮低灭)。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394942397480815.png) ### 输出PWM改变LED亮度 ------ 打开"B3_ basic pwm_ led" 工程的pwm_ example.c文件,可在代码中查看实现输出不同占空比的PWM来改变LED的亮度代码。 通过以下路径查看pwm接口,并在component内修改接口: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394957322310965.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394968818285715.png) ## 驱动子系统开发之ADC(模拟数字转换器,将模拟信号转化为数字信号的电子元件) ### 相关API ------ ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394997230329169.png) ### 查看ADC对应的GPIO引脚 ------ 本案例将使用板载用户按键F1来模拟GPIO口电压的变化。GPIO_ 11对应的是ADC Channel 5 ,所以需要编写软件去读取ADC Channel 5的电压。 ## I2C总线 ------ ### API介绍: ------ ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658395024504158465.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658395031291370644.png) ### I2C读写NFC芯片 ------ NFC芯片的I2C对应的GPIO引脚是分别是GPIO0和GPIO1,所以需要编写软件使用GPIO_ 0和GPIO 1产生I2C信号去控制NFC芯片。 ## UART ------ ### API介绍 ------ ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658395047034634500.png) #### UART对应的接口 ------ UART1对应的GPIO引脚是分别是**GPIO5和GPIO6**,将使用GPIO5和GPIO6进行UART数据的收发。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658395057851790183.png)
  • [交流吐槽] 第三章扩展实例总结
    GPIO:扩展实例代码(B1):#include <stdio.h>#include <unistd.h>#include "ohos_init.h"#include "cmsis_os2.h"#include "wifiiot_gpio.h"#include "wifiiot_gpio_ex.h"static void LedTask(void){//初始化GPIOGpioInit();//设置GPIO_2的复用功能为普通GPIOIoSetFunc(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_IO_FUNC_GPIO_2_GPIO);//设置GPIO_2为输出模式GpioSetDir(WIFI_IOT_GPIO_IDX_2, WIFI_IOT_GPIO_DIR_OUT);WifiIotGpioDir val = {0};//定义读取变量valGpioGetDir(WIFI_IOT_GPIO_IDX_2, &val);//获取WIFI_IOT_GPIO_IDX_2的引脚方向,并赋值给变量valprintf("GPIO_2 Dir is %d\r\n", val);WifiIotGpioValue OutputVal = {0};//定义读取变量OutputValwhile (1)    {//设置GPIO_2输出高电平点亮LED灯GpioSetOutputVal(WIFI_IOT_GPIO_IDX_2, 1);GpioGetOutputVal(WIFI_IOT_GPIO_IDX_2, &OutputVal);//读取WIFI_IOT_GPIO_IDX_2的高电平,并赋值给变量OutputValprintf("GPIO_2 OutputVal is %d\r\n", OutputVal);//延时1susleep(1000000);//设置GPIO_2输出低电平熄灭LED灯GpioSetOutputVal(WIFI_IOT_GPIO_IDX_2, 0);GpioGetOutputVal(WIFI_IOT_GPIO_IDX_2, &OutputVal);//读取WIFI_IOT_GPIO_IDX_2的低电平,并赋值给OutputValprintf("GPIO_2 OutputVal is %d\r\n", OutputVal);//延时1susleep(1000000);    }}static void LedExampleEntry(void){osThreadAttr_t attr;attr.name = "LedTask";attr.attr_bits = 0U;attr.cb_mem = NULL;attr.cb_size = 0U;attr.stack_mem = NULL;attr.stack_size = 1024*2;//要打印字符,需要增大任务的空间attr.priority = 25;if (osThreadNew((osThreadFunc_t)LedTask, NULL, &attr) == NULL)    {printf("Falied to create LedTask!\n");    }}APP_FEATURE_INIT(LedExampleEntry);GPIO中断:扩展实例代码(B2):#include <stdio.h>#include <unistd.h>#include "ohos_init.h"#include "cmsis_os2.h"#include "wifiiot_gpio.h"#include "wifiiot_gpio_ex.h"static void F1_Pressed(char *arg){    (void)arg;GpioSetOutputVal(WIFI_IOT_IO_NAME_GPIO_2, 1);//点亮LEDprintf("This is F1_Pressed\r\n");GpioSetIsrMode(WIFI_IOT_IO_NAME_GPIO_11, WIFI_IOT_IO_NAME_GPIO_11, WIFI_IOT_GPIO_EDGE_RISE_LEVEL_HIGH);//将WIFI_IOT_IO_NAME_GPIO_11的模式修改为电平由低到高才触发中断}static void F2_Pressed(char *arg){    (void)arg;GpioSetOutputVal(WIFI_IOT_IO_NAME_GPIO_2, 0);//熄灭LEDprintf("This is F1_Pressed\r\n");GpioSetIsrMask(WIFI_IOT_IO_NAME_GPIO_12, 1);//屏蔽WIFI_IOT_IO_NAME_GPIO_12的中断功能}static void ButtonExampleEntry(void){GpioInit();//初始化LED灯IoSetFunc(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_IO_FUNC_GPIO_2_GPIO);GpioSetDir(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_GPIO_DIR_OUT);//初始化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_IO_NAME_GPIO_11, WIFI_IOT_GPIO_EDGE_FALL_LEVEL_LOW, F1_Pressed, NULL);//初始化F2按键,设置为下降沿触发中断(即电平由高到低触发中断)IoSetFunc(WIFI_IOT_IO_NAME_GPIO_12, WIFI_IOT_IO_FUNC_GPIO_12_GPIO);GpioSetDir(WIFI_IOT_IO_NAME_GPIO_12, WIFI_IOT_GPIO_DIR_IN);IoSetPull(WIFI_IOT_IO_NAME_GPIO_12, WIFI_IOT_IO_PULL_UP);GpioRegisterIsrFunc(WIFI_IOT_IO_NAME_GPIO_12, WIFI_IOT_INT_TYPE_EDGE, WIFI_IOT_GPIO_EDGE_FALL_LEVEL_LOW, F2_Pressed, NULL);}APP_FEATURE_INIT(ButtonExampleEntry);PWM:扩展实例代码(B3):#include <stdio.h>#include <unistd.h>#include "ohos_init.h"#include "cmsis_os2.h"#include "wifiiot_pwm.h"#include "wifiiot_gpio.h"#include "wifiiot_gpio_ex.h"#define PWM_TASK_STACK_SIZE 512#define PWM_TASK_PRIO 25static void PWMTask(void){unsigned int i;//初始化GPIOGpioInit();//设置GPIO_2引脚复用功能为PWMIoSetFunc(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_IO_FUNC_GPIO_2_PWM2_OUT);//设置GPIO_2引脚为输出模式GpioSetDir(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_GPIO_DIR_OUT);//初始化PWM2端口PwmInit(WIFI_IOT_PWM_PORT_PWM2);uint8_t j = 0;while (1)    {for (i = 0; i < 40000; i += 100)        {//输出不同占空比的PWM波PwmStart(WIFI_IOT_PWM_PORT_PWM2, i, 40000);//PwmStop(WIFI_IOT_PWM_PORT_PWM2);//在PWM刚输出时即停止,灯仍然闪烁,但非常暗usleep(10);        }i = 0;j++;if(j == 5){PwmDeinit(WIFI_IOT_PWM_PORT_PWM2);//当LED灯闪烁5次后,取消初始化PWM,LED灯始终保持最亮        }    }}static void PWMExampleEntry(void){osThreadAttr_t attr;attr.name = "PWMTask";attr.attr_bits = 0U;attr.cb_mem = NULL;attr.cb_size = 0U;attr.stack_mem = NULL;attr.stack_size = 512;attr.priority = 25;if (osThreadNew((osThreadFunc_t)PWMTask, NULL, &attr) == NULL)    {printf("Falied to create PWMTask!\n");    }}APP_FEATURE_INIT(PWMExampleEntry);ADC:BearPi_HM Nano芯片手册位置:./my_bearpi_hm_nano/applocations/BearPi/BearPi-HM_Nano/I2C:如果想让手机触碰NFC设备自动打开网页,要将网址写入位置设置为NDEFFirstPos
总条数:235 到第
上滑加载中