• [技术干货] 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
  • [技术干货] 第二讲:鲲鹏代码迁移工具基础知识读书笔记
    新增功能&功能变化介绍交付特性   (特性&价值描述)T10语言迁移优化:支持go语言源码分析. Fortran语言迁移增强.C/C++语言源码迁移增强·支持Python/Java/Scala解释型语言的扫描分析内存一致性:扩展语言支持场景,对关键算法进行改进 ·增加自动修复功能 ·支持下载静态检查工具产生的BC文件 ·支持自动生成目标工程的中间文件软件迁移评估:Jar、War包扫描提示优化,识别鲲鹏平台已经支持的依赖,在报告中给出提示;专项软件迁移:·扩充“一键式软件迁移”支持的软件范围:增加HPC场景5款软件的过移文持;支持Top16款操作系统: 新增支持openEuler 20.03(LTS-SP1) 、openEuler 20.03 (LTS-uoS Server 20 Euler (1000).BC- Linux 7.6、BC- Linux 7.7和普华(iSoft)5.1六款操作系统运行环境和目标操作系统;源码迁移识别C/C+/Fortran/汇编源代码,提供修改建议;Make、CMake、Automake编译选项、编译宏的解析及迁移建议支持100%Intrinsic函数转换(6000+个),包括MMX 、SSE及AVX Intrinsic等支持更多的Fortran内联函数和诰法特性以及编译选项的识别支持Go语言迁移,对go程序使用cgo编译部分中的编译选项、宏定义提供兼容性检查,给出修改建议支持python、Java、Scala语言,对程序中的动态链接库提供兼容性检查,给出修改建议新增支持openEuler、BC_Linux等六款主流操作系统C/C++x86Intrinsic函数实现了一键式自动迁移;平台相关宏扫描准确率和覆盖率提升;构建文件编译选项、宏定义识别率提升。FortranFortran内联函数识别(增加xx条),增加Fortran语法特性解析,整体准确率提升70%;构建文件中针对Fortran语言中所使用编译选项的识别率和准确率达到100%。Go对编译选项、宏定义、依赖库提供兼容性检查,一键下载替换。Python/Java/Scala识别源码中的加载动态库的函数;识别依赖库文件,进行兼容性检查,一键下载替换。
  • [技术干货] 基于STM32+华为云IOT设计的智能鱼缸
    ## 1. 前言 为了缓解学习、生活、工作带来的压力,提升生活品质,许多人喜欢在家中、办公室等场所养鱼。为节省鱼友时间、劳力、增加养鱼乐趣;为此,本文基于STM32单片机设计了一款基于物联网的智能鱼缸。该鱼缸可以实现水温检测、水质检测、自动或手动换水、氛围灯灯光变换和自动或手动喂食等功能为一体的控制系统,鱼缸通过ESP8266连接华为云IOT物联网平台,并通过应用侧接口开发了上位机APP实现远程对鱼缸参数检测查看,并能远程控制。 **从功能上分析,需要用到的硬件如下:** (1)STM32系统板 (2)水温温度检测传感器: 测量水温 (3)水质检测传感器: 测量水中的溶解性固体含量,反应水质。 (4)步进电机: 作为鱼饲料投食器 (5)RGB氛围灯: 采用RGB 3色灯,给鱼缸照明。 (6)抽水电动马达: 用来给鱼缸充氧,换水,加水等。 (7)ESP8266 WIFI:设置串口协议的WIFI,内置了TCP/IP协议栈,完善的AT指令,通过简单的指令就可以联网通信,但是当前采用的ESP8266没有烧写第三方固件,采用原本的原滋原味的官方固件,没有内置MQTT协议,代码里连接华为云物联网平台需要使用MQTT协议,所以在STM32代码里通过MQTT协议文档的字段结构自己实现了MQTT协议,在通过ESP8266的TCP相关的AT指令完成数据发送接收,完成与华为云IOT平台交互。 水产养殖水质常规检测的传感器有哪些?水产养殖水质常规检测的传感器有水质ph传感器、溶解氧传感器和温度传感器。 (1)水质ph传感器: ph传感器是高智能化在线连续监测仪,由传感器和二次表两部分组成。可配三复合或两复合电极,以满足各种使用场所。配上纯水和超纯水电极,可适用于电导率小于3μs/cm的水质(如化学补给水、饱和蒸气、凝结水等)的pH值测量。 (2)溶解氧传感器: 氧气的消耗量与存在的氧含量成正比,而氧是通过可透膜扩散进来的。传感器与专门设计的监测溶氧的测量电路或电脑数据采集系统相连。 溶解氧传感器能够空气校准,一般校准所需时间较长,在使用后要注意保养。如果在养殖水中工作时间过长,就必须定期地清洗膜,对其进行额外保养。 在很多水产养殖中,每天测几次溶氧就可以了解溶氧情况。对池塘和许多水槽养殖系统。溶氧水平不会变化很快,池塘一般每天检测2~3次。 对于较高密度养殖系统,增氧泵故障发生可能不到1h就会造成鱼虾等大面积死亡。这些密度高的养殖系统要求有足够多的装备或每小时多次自动测量溶氧。 (3)温度传感器: 温度传感器有多种结构,包括热电偶、电阻温度传感器和热敏电阻。热电偶技术成熟,应用领域广,货源充足。选择热电偶必须满足温度范围要求,且其材料与环境相容。 电阻温度传感器(RTDs)的原理为金属的电阻随温度的改变而改变。大多电阻温度传感器(RTDs)由铂、镍或镍合金制成,其线性度比热电偶好,热切更加稳定,但容易破碎。 热敏电阻是电阻与温度具有负相关关系的半导体。热敏电阻比RTD和热电偶更灵敏,也更容易破碎,不能承受大的温差,但这一点在水产养殖中不成问题。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201716792920061.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201733037441163.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201746248299756.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201759333914668.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657965209488421541.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657965246247913333.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657965261527177785.png) ## 2. 硬件选型 ### 2.1 STM32开发板 主控CPU采用STM32F103RCT6,这颗芯片包括48 KB SRAM、256 KB Flash、2个基本定时器、4个通用定时器、2个高级定时器、51个通用IO口、5个串口、2个DMA控制器、3个SPI、2个I2C、1个USB、1个CAN、3个12位ADC、1个12位DAC、1个SDIO接口,芯片属于大容量类型,配置较高,整体符合硬件选型设计。当前选择的这款开发板自带了一个1.4寸的TFT-LCD彩屏,可以显示当前传感器数据以及一些运行状态信息。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201867380976488.png) ### 2.2 杜邦线 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201877427828564.png) ### 2.3 PCB板 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201887682729967.png) ### 2.4 步进电机 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201904611662942.png) ### 2.5 抽水马达 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201918239686520.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201931290695945.png) ### 2.6 水温检测传感器 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201943249531814.png) 测温采用DS18B20,DS18B20是常用的数字温度传感器,其输出的是数字信号,具有体积小,硬件开销低,抗干扰能力强,精度高的特点。 DS18B20数字温度传感器接线方便,封装成后可应用于多种场合,如管道式,螺纹式,磁铁吸附式,不锈钢封装式,型号多种多样,有LTM8877,LTM8874等等。 主要根据应用场合的不同而改变其外观。封装后的DS18B20可用于电缆沟测温,高炉水循环测温,锅炉测温,机房测温,农业大棚测温,洁净室测温,弹药库测温等各种非极限温度场合。耐磨耐碰,体积小,使用方便,封装形式多样,适用于各种狭小空间设备数字测温和控制领域。 ### 2.7 水质检测传感器 TDS (Total Dissolved Solids)、中文名总溶解固体、又称溶解性固体、又称溶解性固体总量、表明1升水肿容有多少毫克溶解性固体、一般来说、TDS值越高、表示水中含有溶解物越多、水就越不洁净、虽然在特定情况下TDS并不能有效反映水质的情况、但作为一种可快速检测的参数、TDS目前还可以作为有效的在水质情况反映参数来作为参考。常用的TDS检测设备为TDS笔、虽然价格低廉、简单易用、但不能把数据传给控制系统、做长时间的在线监测、并做水质状况分析、使用专门的仪器、虽然能传数据、精度也高、但价格很贵、为此这款TDS传感器模块、即插即用、使用简单方便、测量用的激励源采用交流信号、可有效防止探头极化、延长探头寿命的同时、也增加了输出信号的稳定性、TDS探头为防水探头、可长期侵入水中测量、该产品可以应用于生活用水、水培等领域的水质检测、有了这个传感器、可轻松DIY--套TDS检测仪了、轻松检测水的洁净程度。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201962217614386.png) ### 2.8 ESP8266 ■模块采用串口(LVTTL) 与MCU (或其他串口设备) 通信,内置TCP/IP协议栈,能够实现串口与WIFI之间的转换 ■模块支持LVTTL串口, 兼容3..3V和5V单片机系统 ■模块支持串 口转WIFI STA、串口转AP和WIFI STA+WIFI AP的模式,从而快速构建串口-WIFI数据传输方案 ■模块小巧(19mm*29mm), 通过6个2.54mm间距排针与外部连接 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657202982533373995.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657203009974462868.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657203021370179645.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657203033198961449.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657203069365745560.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657203091092707700.png) ## 3. 华为云IOT产品与设备创建 ### 3.1 创建产品 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657629970229570879.png) 链接:https://www.huaweicloud.com/product/iothub.html 点击右上角窗口创建产品。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630053050713019.png) 填入产品信息。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630110545674495.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630134526799399.png) 接下来创建模型文件: 创建服务。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630672668710473.png) 创建属性。根据鱼缸设备的传感器属性来添加属性。 (1)LED氛围灯 (2)抽水电机 (3)水质传感器 (4)水温温度计 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630781773437369.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630839423167449.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630867501379876.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657631378011982641.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657631432204905242.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657631458486256676.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657631475680495731.png) ### 3.2 创建设备 地址: https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/device/all-device 点击右上角创建设备。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630191959234433.png) 按照设备的情况进行填写信息。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630241007922609.png) 设备创建后保存信息: ```cpp { "device_id": "62cd6da66b9813541d510f64_dev1", "secret": "12345678" } ``` 创建成功。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630344497731008.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630379665587266.png) ### 3.3 设备模拟调试 为了测试设备通信的过程,在设备页面点击调试。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220713/1657719675025872160.png) 选择设备调试: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220713/1657719801527206113.png) ### 3.4 MQTT三元组 为了方便能够以真实的设备登陆服务器进行测试,接下来需要先了解MQTT协议登录需要的参数如何获取,得到这些参数才可以接着进行下一步。 MQTT(Message Queuing Telemetry Transport)是一个基于客户端-服务器的消息发布/订阅传输协议,主要应用于计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备,适合长连接的场景,如智能路灯等。 MQTTS是MQTT使用TLS加密的协议。采用MQTTS协议接入平台的设备,设备与物联网平台之间的通信过程,数据都是加密的,具有一定的安全性。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657962773440848834.png) 采用MQTT协议接入物联网平台的设备,设备与物联网平台之间的通信过程,数据没有加密,如果要保证数据的私密性可以使用MQTTS协议。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657962863655935900.png) 在这里可以使用华为云提供的工具快速得到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/20220716/1657963111182338693.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/20220716/1657963173477286324.png) ```cpp DeviceId 62cd6da66b9813541d510f64_dev1 DeviceSecret 12345678 ClientId 62cd6da66b9813541d510f64_dev1_0_0_2022071609 Username 62cd6da66b9813541d510f64_dev1 Password a23fb6db6b5bc428971d5ccf64cc8f7767d15ca63bd5e6ac137ef75d175c77bf ``` ### 3.5 平台接入地址 华为云的物联网服务器地址在这里可以获取: [https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home](https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657963631268358095.png) ```cpp MQTT (1883) a161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com 对应的IP地址是: 121.36.42.100 ``` ### 3.6 MQTT的主题订阅与发布格式 得到三元组之后,就可以登录MQTT服务器进行下一步的主题发布与订阅。 主题的格式详情: [https://support.huaweicloud.com/api-iothub/iot_06_v5_3004.html](https://support.huaweicloud.com/api-iothub/iot_06_v5_3004.html) 上传的数据格式详情: [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) ```cpp 设备消息上报 $oc/devices/{device_id}/sys/messages/up 平台下发消息给设备 $oc/devices/{device_id}/sys/messages/down 上传的消息格式: { "services": [{ "service_id": "Connectivity", "properties": { "dailyActivityTime": 57 }, "event_time": "20151212T121212Z" }, { "service_id": "Battery", "properties": { "batteryLevel": 80 }, "event_time": "20151212T121212Z" } ] } ``` 根据当前设备的格式总结如下: ```cpp ClientId 62cd6da66b9813541d510f64_dev1_0_0_2022071609 Username 62cd6da66b9813541d510f64_dev1 Password a23fb6db6b5bc428971d5ccf64cc8f7767d15ca63bd5e6ac137ef75d175c77bf //订阅主题: 平台下发消息给设备 $oc/devices/62cd6da66b9813541d510f64_dev1/sys/messages/down //设备上报数据 $oc/devices/62cd6da66b9813541d510f64_dev1/sys/properties/report //上报的属性消息 (一次可以上报多个属性,在json里增加就行了) {"services": [{"service_id": "fish","properties":{"LED":1}},{"service_id": "fish","properties":{"motor":1}},{"service_id": "fish","properties":{"水温":36.2}}]} ``` ### 3.6 MQTT客户端模拟设备调试 得到信息之后,将参赛填入软件进行登录测试。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657964427885652042.png) 数据发送之后,在设备页面上可以看到设备已经在线了,并且收到了上传的数据。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657964467641641236.png) ## 4. STM32程序设计 ### 4.1 硬件连线 ```cpp 硬件连接方式: 1. TFT 1.44 寸彩屏接线 GND 电源地 VCC 接5V或3.3v电源 SCL 接PC8(SCL) SDA 接PC9(SDA) RST 接PC10 DC 接PB7 CS 接PB8 BL 接PB11 2. 板载LED灯接线 LED1---PA8 LED2---PD2 3. 板载按键接线 K0---PA0 K1---PC5 K2---PA15 4. DS18B20温度传感器接线 DQ->PC6 + : 3.3V - : GND 5. 步进电机 ULN2003控制28BYJ-48步进电机接线: ULN2003接线: IN-D: PB15 d IN-C: PB14 c IN-B: PB13 b IN-A: PB12 a + : 5V - : GND 6. 抽水电机 GND---GND VCC---5V AO----PA4 7. 水质检测传感器 AO->PA1 + : 3.3V - : GND 8. RGB灯 PC13--R PC14--G PC15--B 9. ATK-ESP8266 WIFI接线 PA2(TX)--RXD 模块接收脚 PA3(RX)--TXD 模块发送脚 GND---GND 地 VCC---VCC 电源(3.3V~5.0V) ``` ### 4.2 硬件原理图 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657202192754191355.png) ### 4.3 汉字取模 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657202211768465094.png) ### 4.4 程序下载 下载软件在资料包里。点击开始编程之后,点击开发板的复位键即可下载程序进去。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657202235357606354.png) ### 4.5 主要的信息连接代码 ```cpp #include "stm32f10x.h" #include "led.h" #include "delay.h" #include "key.h" #include "usart.h" #include #include "timer.h" #include "esp8266.h" #include "mqtt.h" #include "oled.h" #include "fontdata.h" #include "bh1750.h" #include "iic.h" #include "sht3x.h" #define ESP8266_WIFI_AP_SSID "aaa" //将要连接的路由器名称 --不要出现中文、空格等特殊字符 #define ESP8266_AP_PASSWORD "12345678" //将要连接的路由器密码 //华为云服务器的设备信息 #define MQTT_ClientID "62cd6da66b9813541d510f64_dev1_0_0_2022071609" #define MQTT_UserName "62cd6da66b9813541d510f64_dev1" #define MQTT_PassWord "a23fb6db6b5bc428971d5ccf64cc8f7767d15ca63bd5e6ac137ef75d175c77bf" //订阅与发布的主题 #define SET_TOPIC "$oc/devices/62cd6da66b9813541d510f64_dev1/sys/messages/down" //订阅 #define POST_TOPIC "$oc/devices/62cd6da66b9813541d510f64_dev1/sys/properties/report" //发布 ``` ### 4.6 ESP8266主要代码 ```cpp u8 ESP8266_IP_ADDR[16]; //255.255.255.255 u8 ESP8266_MAC_ADDR[18]; //硬件地址 /* 函数功能: ESP8266命令发送函数 函数返回值:0表示成功 1表示失败 */ u8 ESP8266_SendCmd(char *cmd) { int RX_CNT=0; u8 i,j; for(i=0;i10;i++) //检测的次数--发送指令的次数 { USARTx_StringSend(USART3,cmd); for(j=0;j100;j++) //等待的时间 { delay_ms(50); if(USART3_RX_STA&0X8000) { RX_CNT=USART3_RX_STA&0x7FFF; USART3_RX_BUF[RX_CNT]='\0'; USART3_RX_STA=0; if(strstr((char*)USART3_RX_BUF,"OK")) { return 0; } } } } return 1; } /* 函数功能: ESP8266硬件初始化检测函数 函数返回值:0表示成功 1表示失败 */ u8 ESP8266_Init(void) { //退出透传模式 USARTx_StringSend(USART3,"+++"); delay_ms(100); //退出透传模式 USARTx_StringSend(USART3,"+++"); delay_ms(100); return ESP8266_SendCmd("AT\r\n"); } /* 函数功能: 一键配置WIFI为AP+TCP服务器模式 函数参数: char *ssid 创建的热点名称 char *pass 创建的热点密码 (最少8位) u16 port 创建的服务器端口号 函数返回值: 0表示成功 其他值表示对应错误值 */ u8 ESP8266_AP_TCP_Server_Mode(char *ssid,char *pass,u16 port) { char *p; u8 i; char ESP8266_SendCMD[100]; //组合发送过程中的命令 /*1. 测试硬件*/ if(ESP8266_SendCmd("AT\r\n"))return 1; /*2. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 2; /*3. 设置WIFI模式*/ if(ESP8266_SendCmd("AT+CWMODE=2\r\n"))return 3; /*4. 复位*/ ESP8266_SendCmd("AT+RST\r\n"); delay_ms(1000); delay_ms(1000); delay_ms(1000); /*5. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 5; /*6. 设置WIFI的AP模式参数*/ sprintf(ESP8266_SendCMD,"AT+CWSAP=\"%s\",\"%s\",1,4\r\n",ssid,pass); if(ESP8266_SendCmd(ESP8266_SendCMD))return 6; /*7. 开启多连接*/ if(ESP8266_SendCmd("AT+CIPMUX=1\r\n"))return 7; /*8. 设置服务器端口号*/ sprintf(ESP8266_SendCMD,"AT+CIPSERVER=1,%d\r\n",port); if(ESP8266_SendCmd(ESP8266_SendCMD))return 8; /*9. 查询本地IP地址*/ if(ESP8266_SendCmd("AT+CIFSR\r\n"))return 9; //提取IP地址 p=strstr((char*)USART3_RX_BUF,"APIP"); if(p) { p+=6; for(i=0;*p!='"';i++) { ESP8266_IP_ADDR[i]=*p++; } ESP8266_IP_ADDR[i]='\0'; } //提取MAC地址 p=strstr((char*)USART3_RX_BUF,"APMAC"); if(p) { p+=7; for(i=0;*p!='"';i++) { ESP8266_MAC_ADDR[i]=*p++; } ESP8266_MAC_ADDR[i]='\0'; } //打印总体信息 printf("当前WIFI模式:AP+TCP服务器\r\n"); printf("当前WIFI热点名称:%s\r\n",ssid); printf("当前WIFI热点密码:%s\r\n",pass); printf("当前TCP服务器端口号:%d\r\n",port); printf("当前TCP服务器IP地址:%s\r\n",ESP8266_IP_ADDR); printf("当前TCP服务器MAC地址:%s\r\n",ESP8266_MAC_ADDR); return 0; } /* 函数功能: TCP服务器模式下的发送函数 发送指令: */ u8 ESP8266_ServerSendData(u8 id,u8 *data,u16 len) { int RX_CNT=0; u8 i,j,n; char ESP8266_SendCMD[100]; //组合发送过程中的命令 for(i=0;i10;i++) { sprintf(ESP8266_SendCMD,"AT+CIPSEND=%d,%d\r\n",id,len); USARTx_StringSend(USART3,ESP8266_SendCMD); for(j=0;j10;j++) { delay_ms(50); if(USART3_RX_STA&0X8000) { RX_CNT=USART3_RX_STA&0x7FFF; USART3_RX_BUF[RX_CNT]='\0'; USART3_RX_STA=0; if(strstr((char*)USART3_RX_BUF,">")) { //继续发送数据 USARTx_DataSend(USART3,data,len); //等待数据发送成功 for(n=0;n200;n++) { delay_ms(50); if(USART3_RX_STA&0X8000) { RX_CNT=USART3_RX_STA&0x7FFF; USART3_RX_BUF[RX_CNT]='\0'; USART3_RX_STA=0; if(strstr((char*)USART3_RX_BUF,"SEND OK")) { return 0; } } } } } } } return 1; } /* 函数功能: 配置WIFI为STA模式+TCP客户端模式 函数参数: char *ssid 创建的热点名称 char *pass 创建的热点密码 (最少8位) char *p 将要连接的服务器IP地址 u16 port 将要连接的服务器端口号 u8 flag 1表示开启透传模式 0表示关闭透传模式 函数返回值:0表示成功 其他值表示对应的错误 */ u8 ESP8266_STA_TCP_Client_Mode(char *ssid,char *pass,char *ip,u16 port,u8 flag) { char ESP8266_SendCMD[100]; //组合发送过程中的命令 //退出透传模式 //USARTx_StringSend(USART3,"+++"); //delay_ms(50); /*1. 测试硬件*/ if(ESP8266_SendCmd("AT\r\n"))return 1; /*2. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 2; /*3. 设置WIFI模式*/ if(ESP8266_SendCmd("AT+CWMODE=1\r\n"))return 3; /*4. 复位*/ ESP8266_SendCmd("AT+RST\r\n"); delay_ms(1000); delay_ms(1000); delay_ms(1000); /*5. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 5; /*6. 配置将要连接的WIFI热点信息*/ sprintf(ESP8266_SendCMD,"AT+CWJAP=\"%s\",\"%s\"\r\n",ssid,pass); if(ESP8266_SendCmd(ESP8266_SendCMD))return 6; /*7. 设置单连接*/ if(ESP8266_SendCmd("AT+CIPMUX=0\r\n"))return 7; /*8. 配置要连接的TCP服务器信息*/ sprintf(ESP8266_SendCMD,"AT+CIPSTART=\"TCP\",\"%s\",%d\r\n",ip,port); if(ESP8266_SendCmd(ESP8266_SendCMD))return 8; /*9. 开启透传模式*/ if(flag) { if(ESP8266_SendCmd("AT+CIPMODE=1\r\n"))return 9; //开启 if(ESP8266_SendCmd("AT+CIPSEND\r\n"))return 10; //开始透传 if(!(strstr((char*)USART3_RX_BUF,">"))) { return 11; } //如果想要退出发送: "+++" } printf("WIFI模式:STA+TCP客户端\r\n"); printf("Connect_WIFI热点名称:%s\r\n",ssid); printf("Connect_WIFI热点密码:%s\r\n",pass); printf("TCP服务器端口号:%d\r\n",port); printf("TCP服务器IP地址:%s\r\n",ip); return 0; } /* 函数功能: TCP客户端模式下的发送函数 发送指令: */ u8 ESP8266_ClientSendData(u8 *data,u16 len) { int RX_CNT=0; u8 i,j,n; char ESP8266_SendCMD[100]; //组合发送过程中的命令 for(i=0;i10;i++) { sprintf(ESP8266_SendCMD,"AT+CIPSEND=%d\r\n",len); USARTx_StringSend(USART3,ESP8266_SendCMD); for(j=0;j10;j++) { delay_ms(50); if(USART3_RX_STA&0X8000) { RX_CNT=USART3_RX_STA&0x7FFF; USART3_RX_BUF[RX_CNT]='\0'; USART3_RX_STA=0; if(strstr((char*)USART3_RX_BUF,">")) { //继续发送数据 USARTx_DataSend(USART3,data,len); //等待数据发送成功 for(n=0;n200;n++) { delay_ms(50); if(USART3_RX_STA&0X8000) { RX_CNT=USART3_RX_STA&0x7FFF; USART3_RX_BUF[RX_CNT]='\0'; USART3_RX_STA=0; if(strstr((char*)USART3_RX_BUF,"SEND OK")) { return 0; } } } } } } } return 1; } ```
总条数:248 到第
上滑加载中