STM32,DCP,i.MXRT1170以及i.MXRT1170技术干货分享

360影视 动漫周边 2025-03-11 09:00 2

摘要:有关显示模块,我们之前分享过了数码管、OLED、LCD12864,最近群里小伙伴有需要STM32驱动LCD1602的源码,咱们今天就分享一下,如何使用STM32驱动LCD1602液晶屏,授人以鱼不如授人以渔,一起来看看怎么驱动LCD1602。

有关显示模块,我们之前分享过了数码管、OLED、LCD12864,最近群里小伙伴有需要STM32驱动LCD1602的源码,咱们今天就分享一下,如何使用STM32驱动LCD1602液晶屏,授人以鱼不如授人以渔,一起来看看怎么驱动LCD1602。

淘宝下单买一个:

LCD1602的意思就是每行可以显示16个字符,一共可以显示2行内容。

LCD1602一共有16个引脚,具体定义如下:

因为STM32为3.3V单片机,所以为了方便,本次测试选用了一个3.3V的LCD1602。

本次实例选用的LCD1602背部如下所示,显示的型号为:1602A

具体连接情况:

注意:程序下载之后,如果屏幕没有任何内容显示,先别急着否定,可以尝试调节一下与VO引脚相连的电位器,有时是因为对比度的原因导致没有任何内容显示。

写操作过程时序分析

根据向LCD1602液晶写入的内容是指令还是数据,将RS引脚置低或置高。写指令,将RS置低;写数据,将RS置高;因为是写操作,所以我们需要将R/W引脚置低;做好写数据之前的准备工作:先将E引脚置低,然后将要写的内容(一个字节数据),发送至DB0~DB7上;

封装的改变DB0~DB7的函数如下:

void WriteData(u8 data){ GPIO_WriteBit(GPIOA, GPIO_Pin_6,(BitAction)((data & 0x80) >> 7 )); //DB7 GPIO_WriteBit(GPIOA, GPIO_Pin_0,(BitAction)((data & 0x40) >> 6 )); GPIO_WriteBit(GPIOC, GPIO_Pin_3,(BitAction)((data & 0x20) >> 5 )); GPIO_WriteBit(GPIOC, GPIO_Pin_2,(BitAction)((data & 0x10) >> 4 )); GPIO_WriteBit(GPIOC, GPIO_Pin_1,(BitAction)((data & 0x08) >> 3 )); GPIO_WriteBit(GPIOC, GPIO_Pin_0,(BitAction)((data & 0x04) >> 2 )); GPIO_WriteBit(GPIOC, GPIO_Pin_13,(BitAction)((data & 0x02) >> 1 )); GPIO_WriteBit(GPIOB, GPIO_Pin_9,(BitAction)(data & 0x01)); //DB0}

4.因为写操作时,下降沿有效,所以我们先将E脚置高,延时一段时间之后,再置低,在此过程中,完成写操作。

综上所述,发送命令和数据的函数封装如下:

/* 向LCD1602液晶写入一字节命令,cmd-待写入命令值 */void LCD1602_Write_Cmd(u8 cmd){ LCD_RS_Clr; LCD_RW_Clr; LCD_EN_Clr; WriteData(cmd); LCD_EN_Set; delay_ms(5); LCD_EN_Clr;}/* 向LCD1602液晶写入一字节数据,dat-待写入数据值 */void LCD1602_Write_Dat(u8 dat){ LCD_RS_Set; LCD_RW_Clr; LCD_EN_Clr; WriteData(dat); LCD_EN_Set; delay_ms(5); LCD_EN_Clr;}

因为要让LCD1602显示内容,只需要往LCD1602发送数据即可,即只需要完成写操作,不需要读操作,所以我们只需要封装上面写指令和写数据两个函数即可,未封装读操作的函数。

LCD1602内部提供了较为丰富的指令设置,比如:清显示(清屏)、光标回原点、显示开/关、光标开/关、显示字符闪烁、光标移位、显示移位等指令。

通过选择相应的指令设置,用户可以实现多种字符显示样式。

查看上面指令表,我们可以知道清屏操作,只需要发送指令0x01即可。

利用上面封装的写指令void LCD1602_Write_Cmd(u8 cmd) 函数,我们封装清屏操作函数如下:

void LCD1602_ClearScreen(void){ LCD1602_Write_Cmd(0x01);}

发送此指令,DDRAM(display data RAM)中的内容全部清除,显示消失;地址计数器AC=0,自动增1模式;显示归位,光标或者闪烁回到原点(显示屏左上角),但并不改变移位设置模式。

要显示字符,首先要设定显示的地址,即告诉LCD1602在哪里显示字符,查找LCD1602数据手册,我们知道内部显示地址(DDRAM)如下:

如果要显示在第一行,第一个字符的位置,那么其地址就是0x00, 要显示在第二行,第一个字符位置,那么其地址就是0x40。

具体对应关系如下:

显示RAM起始地址,即光标位置,封装函数如下:

void LCD1602_Set_Cursor(u8 x, u8 y){ u8 addr; if (y == 0) addr = 0x00 + x; else addr = 0x40 + x; LCD1602_Write_Cmd(addr | 0x80);}

要想显示具体的内容,就要先设置在液晶上显示的位置,即调用上面的函数void LCD1602_Set_Cursor(u8 x, u8 y) ,利用其设定显示的坐标位置,然后调用写数据函数void LCD1602_Write_Dat(u8 dat) ,这样就完成了待显示字符的写入。

/* 在液晶上显示字符串,(x,y)-对应屏幕上的起始坐标,str-字符串指针 */void LCD1602_Show_Str(u8 x, u8 y, u8 *str){ LCD1602_Set_Cursor(x, y); while(*str != '\0') { LCD1602_Write_Dat(*str++); }}

比如我们想要在液晶屏第二行显示小哈哥的个人网站的网址:www.xiaohage.com,需要的代码如下:

LCD1602_Show_Str(0, 1, "www.xiaohage.com");

这里为什么写入字符'1',LCD1602正好可以显示1呢?因为在LCD1602内部有这样一个CGROM和CGRAM中字符代码与字符图形对应关系表:

上图即为CGROM表,表的最左边一列为允许用户自定义字模的CGRAM空间。

表的横行是其高4位地址,表的列是其低4位地址。

如果要显示字符'1',对应的十六进制为0x31,即高四位为0011,低四位为xxxx0001,组合一起即为要显示内容的位置,具体如上表中框选位置。

再对照ASCII码表,发现二者是一一对应的,所以我们直接发送ASCII码,即可显示对应其实际的字符。

/* 初始化1602液晶 */void LCD1602_Init(void){ LCD1602_Write_Cmd(0x38); //16*2显示,5*7点阵,8位数据口 delay_ms(5); LCD1602_Write_Cmd(0x0c); //开显示,光标关闭 delay_ms(5); LCD1602_Write_Cmd(0x06); //文字不动,地址自动+1 delay_ms(5); LCD1602_Write_Cmd(0x01); //清屏 delay_ms(5);}

Copy

总的来说,LCD1602的驱动还是挺简单的,液晶屏的单价也不贵,唯一的缺点就是不能显示中文,至于选取哪种液晶屏,各位可以根据需要自由选取哈。

查看原文:https://www.dianyuan.com/eestar/article-8634.html

大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是i.MXRT系列中数据协处理器DCP使用SNVS Master Key加解密的注意事项

i.MXRT不仅仅是处理性能超强的MCU,也是安全等级极高的MCU。如果大家用过痞子衡开发的一站式安全启动工具 NXP-MCUBootUtility,应该会从其 用户手册 3.3节中了解到i.MXRT支持的几种安全启动等级,其中HAB加密启动方式和BEE/OTFAD加密启动方式中都提及了一种神秘的密钥 - SNVS Master Key,今天痞子衡就跟大家聊聊这个密钥用于DCP模块的注意事项(文中仅以i.MXRT1060为例,其他RT10xx型号或许有微小差别)。

先来给大家科普下DCP模块,DCP是Data Co-Processor的简称,从名字上看是个通用数据协处理器。在i.MXRT1060 Security Reference Manual中有一张系统整体安全架构简图,这个简图中标出了DCP模块的主要功能 :CRC-32算法、AES算法、Hash算法、类DMA数据搬移。

看到DCP支持的功能,你就能明白其模块命名的由来了。本质上它就是一个数据处理加速器,如果说CRC-32/Hash算法只是算出一个结果(下图中Mode3),而AES算法则是明文数据到密文数据的转换(存在数据迁移,下图中Mode2),DMA式数据搬移则更明显了(下图中Mode1),DCP内部集成了memcopy功能,可以实现比普通DMA方式效率更高的内存到内存数据块搬移,memcopy功能还支持blit模式,支持传输矩形数据块到frame buffer用于LCD显示。

我们今天主要是聊DCP的AES加解密功能,其支持AES-128算法,包含Electronic Code Book (ECB)和Cipher Block Chaining (CBC)模式,算法标准符合 NIST US FIPS PUB 197 (2001)规范,AES运算的最小单元是16字节。

对于加解密而言,一个很重要的特性就是密钥管理。DCP的AES密钥(长度均为128bits)来源很丰富,按性质可分成四类:

SRAM-based keys: 用户自定义的存放于SRAM中的密钥,最终会被写入DCP的KEY_DATA寄存器中,最多四组。

Payload key: 用户自定义的跟加解密数据放一起的密钥,操作时DCP直接解析。

eFuse SW_GP2 key: 用户烧录到eFuse SW_GP2区域的密钥,可锁定住让软件无法访问,但DCP可通过内置专用途径获取到。

SNVS Master key: 芯片出厂时预存的唯一密钥,密钥值无法获知,DCP可通过内置专用途径获取到。

选用SRAM-based keys和Payload key仅需要在DCP模块内部配置即可,而选用eFuse SW_GP2 key和SNVS Master key则要在如下IOMUXC_GPR寄存器中额外设置。

IOMUXC_GPR_GPR10寄存器用于选择Key是来自eFuse SW_GP2还是SNVS Master Key:

IOMUXC_GPR_GPR3寄存器用于选择Key是来自SNVS Master Key(总256bits)的低128bit还是高128bit(注意此寄存器对eFuse SW_GP2其实不生效,因为SW_GP2仅128bits):

SNVS全称Secure Non-Volatile Storage,它既是DCP的配套模块,也是芯片系统的安全事务监测中心。它能够提供一个独特的Master Key给DCP模块,这个Master Key可有三种产生方式(在SNVS_LPMKCR中设置):

OTPMK:这种就是直接使用eFuse里出厂预烧录的OTPMK(256bits),这个OTPMK是每个芯片唯一的,并且被锁住了软件不可访问。

ZMK:这种是利用存在SNVS_LP ZMKRx寄存器组中的密钥,该秘钥可由用户写入,此密钥在芯片主电源断掉时会继续保留(因为在LP域可由纽扣电池供电),在芯片受到安全攻击时密钥会被自动擦除。

CMK:前两者组合后的Key,即OTPMK和ZMK的异或结果。

一般来说,使用最多的SNVS Master Key就是默认的OTPMK。

关于DCP模块的驱动,在下载的芯片SDK包里有两种:

ROM版本:\SDK_2.x.x_EVK-MIMXRT1060\devices\MIMXRT1062\drivers\fsl_dcp.c

SDK版本:\SDK_2.x.x_EVK-MIMXRT1060\middleware\mcu-boot\src\drivers\dcp\fsl_dcp.c

middleware里的DCP驱动是ROM team负责的,他们是在芯片Tapeout之前写的,属于早期驱动;device包里的DCP驱动才是SDK team负责的,是芯片Tapeout之后写的,是正式版本。

两版驱动都实现了AES加解密,不过代码风格不同。比如ROM版本驱动的dcp_aes_ecb_crypt函数同时支持加密和解密模式,而在SDK版本驱动里则分成两个函数:DCP_AES_EncryptEcb - 加密 、DCP_AES_DecryptEcb - 解密。

前面铺垫了那么多,终于来到正题了。DCP模块如何拿到正确的SNVS Master Key?让我们以\SDK_2.x.x_EVK-MIMXRT1060\boards\evkmimxrt1060\driver_examples\dcp 例程来做个测试。

这个dcp例程演示了五种DCP工作模式,我们就测试第一种TestAesEcb,将宏DCP_TEST_USE_OTP_KEY改为1,即使用OTPMK低128bits作为DCP的密钥:

#define DCP_TEST_USE_OTP_KEY 1 /* Set to 1 to select OTP key for AES encryption/decryption. */int main(void){ dcp_config_t dcpConfig; // ... /* Initialize DCP */ DCP_GetDefaultConfig(&dcpConfig);#if DCP_TEST_USE_OTP_KEY /* Set OTP key type in IOMUX registers before initializing DCP. */ /* Software reset of DCP must be issued after changing the OTP key type. */ DCP_OTPKeySelect(kDCP_OTPMKKeyLow);#endif /* Reset and initialize DCP */ DCP_Init(DCP, &dcpConfig); /* Call DCP APIs */ TestAesEcb; // ...}

在初始芯片状态(Hab Open)下,使用J-Link下载工程进RAM直接单步调试看一看,在执行完DCP_AES_EncryptEcb函数后查看cipher数组,可以看到其值为0xCF, 0x2E, 0xA3...,好吧我们根本不知道SNVS Master Key到底是多少,所以这个密文是否正确也无从知晓。

既然无法得知SNVS Master Key,那我们做个小实验,使用SRAM-based keys来做一次加密,密钥姑且设个全0吧,再看一下结果,你发现了什么,cipher的值是不是很熟悉?跟之前SNVS Master Key加密的结果一致,难道这颗芯片的SNVS Master Key是全0?想想不可能,肯定是流程哪里出了问题!

现在让我们再回忆 MCUBootUtility 用户手册里关于测试HAB加密以及BEE/OTFAD加密使用SNVS Master Key的前提条件,是的,芯片状态需要先设置为Hab Close,好,让我们现在在eFuse里将SEC_CONFIG[1:0]设为2'b10(Hab Close),然后再次使用J-Link调试进去看一看,怎么回事?cipher值依旧是0xCF, 0x2E, 0xA3...

上面的测试对TestAesEcb函数做了一个简单的修改,将cipher值通过串口打印出来,那我们就将程序通过NXP-MCUBootUtility下载到Flash里由ROM来启动运行吧(退出调试状态),我们再来看串口打印,哈哈,终于值变了,这意味着DCP终于拿到了正确的SNVS Master Key(非0)。

总结一下,SNVS Master Key仅在芯片Hab状态是Close并且非调试状态下才能被DCP正常获取,否则DCP获取到的是全0的假Key。

至此,i.MXRT系列中数据协处理器DCP使用SNVS Master Key加解密的注意事项痞子衡便介绍完毕了,掌声在哪里~~~

查看原文:https://www.dianyuan.com/eestar/article-8644.html

大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是恩智浦i.MXRT1170的eFuse空间访问可靠性保护策略

关于i.MXRT系列的eFuse/OTP,痞子衡之前在介绍Boot时写过两篇,分别是针对RT1050的《eFuse及其烧写方法》 和针对RT600的《OTP及其烧写方法》,今天要介绍的i.MXRT1170 eFuse其实是这两者的融合,在空间组织上(尤其是Shadow Register)更像RT1050,但是在访问可靠性保护策略上又接近RT600。关于访问可靠性保护策略,痞子衡之前没有提及,今天咱们就展开聊一聊。

eFuse是i.MXRT1170内嵌的一块OTP(One Time Programmable) memory,仅可被烧写一次(这里指的是bit位从0到1不可逆),但可以被多次读取。eFuse memory的烧写情况根据可靠性保护策略不同而不同。如果被冗余方法保护,那么eFuse是按bit被烧写的;如果是被ECC方法保护的,那么eFuse是按word被烧写的。

i.MXRT1170的eFuse memory用户地址空间有8Kbit(地址范围为0x900 - 0x18F0,低4bit地址位无效),分为32个BANK,每个BANK含8个word(1word = 4bytes)。下图中0x00 - 0xFF是eFuse的用户bank word索引地址,其与eFuse空间地址对应关系是:

fuse_address = user_fuse_index * 0x10 + 0x900

此外i.MXRT1170的eFuse memory还有额外的0.5Kbit地址空间(范围为0x800 - 0x8F0,低4bit地址位无效),用于存放厂商(NXP)配置以及一些敏感配置,其与eFuse空间地址对应关系是:

fuse_address = supp_fuse_index * 0x10

不管是8Kbit用户空间还是额外的0.5Kbit敏感空间,我们都是可以访问的,其index其实是统一编址的,下面这个index才是真正用于blhost工具或者OCOTP API的地址参数:

fuse_address = fuse_index * 0x10 + 0x800

关于i.MXRT1170的eFuse一般特性(比如Lock属性、OCOTP控制器、Shadow Register)可参考痞子衡在文章开头给出的两篇文章,这里不予赘述。

有三种方法或工具可以帮助烧写eFuse,我们以烧写和回读eFuse地址0xA80(MAC1_ADDR)为例,将0x12345678烧写进MAC1_ADDR并回读。根据上面公式我们可以得出 fuse_index = (fuse_address - 0x800) / 0x10 = 0x28,这个fuse_index便是底下我们传给烧写工具的地址。

2.1 blhost工具

blhost是个上位机命令行工具,其能正常工作的前提是预先加载一个特殊flashloader程序(\SDK_x.x.x_MIMXRT1170-EVK\boards\evkmimxrt1170\bootloader_examples\flashloader)进MCU来实现eFuse烧写,flashloader中集成了OCOTP驱动。关于blhost使用方法,详见痞子衡Boot系列文章,这里仅列出两个命令:

\NXP-MCUBootUtility\tools\blhost2_3\win> .\blhost.exe -u -- efuse-program-once 0x28 12345678\NXP-MCUBootUtility\tools\blhost2_3\win> .\blhost.exe -u -- efuse-read-once 0x28

2.2 OCOTP驱动

如果你觉得blhost工具这一套太复杂,可以直接借助SDK包里的ocotp例程(\SDK_x.x.x_MIMXRT1170-EVK\boards\evkmimxrt1170\driver_examples\ocotp),代码也是非常简单清晰:

#include "fsl_ocotp.h" 2 3int main(void) 4{ 5 status_t status = kStatus_Success; 6 uint32_t fuseData = 0U; 7 8 /* 初始化OCOTP模块 */ 9 OCOTP_Init(OCOTP, 0U);1011 /* 将word数据(0x12345678)写入fuse index为0x28的eFuse memory里 */12 status = OCOTP_WriteFuseShadowRegister(OCOTP, 0x28, 0x12345678);1314 /* 从fuse index为0x28的eFuse memory处读出一个word*/15 status = OCOTP_ReadFuseShadowRegisterExt(OCOTP, 0x28, &fuseData, 1);16}

2.3 MCUBootUtility工具

如果你觉得blhost使用不友好,OCOTP驱动又需要改代码和下载运行,那么还有一个工具可以帮到你,那就是痞子衡开发的MCUBootUtility图形界面工具,小白都能轻松上手烧写eFuse:

eFuse的特性其实主要是OCOTP模块决定的,翻开i.MXRT1170参考手册的OCOTP章节的Features小节,可以看到其比RT1050 OCOTP多了如下这三行:

• Supports ECC mode programming and reading for MTR fuse words by SkyBlue IPS bus• Supports ECC mode programming and reading for all the user fuse words• Supports redundancy mode programming and reading for all the supplementary fuse words

简单地说就是eFuse空间被分成了两类,一类受ECC保护,一类受redundancy(冗余)保护,这是本文要介绍的重点。

3.1 冗余保护

redundancy(冗余)保护是比较简单的访问可靠性保护策略,这个策略基本设计思想就是冗余,将fuse word一分为二,低16bit是用户操作区,高16bit是系统冗余区。烧写时用户只需要管低16bit,高16bit则由系统自动完成复制烧写。回读时得到的结果则是低16bit与高16bit的或(OR)结果。这样的好处就是除非用户操作区(低16bit)和系统冗余区(高16bit)均发生错误才会导致访问不可靠。

redundancy(冗余)保护虽然一定程度上提高了访问可靠性,但代价是牺牲了一半存储空间,所谓鱼和熊掌不可兼得,这个也是可以理解的。下面这些eFuse区域是受redundancy(冗余)保护的,从功能上看这些区域是按bit定义的,功能比较分散,所以存在多次烧写的需求,适用redundancy(冗余)保护。

3.2 ECC保护

ECC保护是相对复杂的访问可靠性保护策略,ECC算法是采用经典的SEC-DED(纠正1bit,检查2bit),每个fuse word算出一个ECC校验值(7bit),这个校验值紧跟着存在efuse word后面(bit31:0是用户区,bit38:32是ECC区),ECC区无法被用户直接访问。如果在回读时发生ECC错误,可在HW_OCOTP_OUT_STATUS0寄存器(这是RT1170 OCOTP模块新增的寄存器)里如下bit找到信息。

ECC保护极大地提高了访问可靠性,但综合eFuse特点其代价就是整个fuse word仅可被烧写一次(即使你一次只改一个bit)。下面这些eFuse区域是受ECC保护的,不过从功能上看这些区域功能比较单一,一般都是一次性烧写,所以也适用ECC保护。

至此,恩智浦i.MXRT1170的eFuse空间访问可靠性保护策略痞子衡便介绍完毕了,掌声在哪里~~~

查看原文:https://www.dianyuan.com/eestar/article-8651.html

大家好,我是程序员小哈。

前一阵参加了涂鸦智能【幻彩灯带】Arduino开发实战训练营,参与完整个过程,收获颇丰,今天对本次制作的幻彩灯带进行一下小结,希望以后可以再次参与其他内容的训练营活动。

概况

本次DIY内容为基于Arduino+tuya SDK实现的幻彩灯带。实现了通过 涂鸦智能 App 下发打开灯带指令控制WS2812全彩灯板的开关,下发音乐指令控制灯带进入音乐律动模式。

创建产品后,添加功能定义。由于要控制灯带的开关,需要添加一个布尔型功能点。需要设置不同的工作模式,所以需要添加一个枚举类型的功能点。

注意记录上图中的PID值,此值在稍后的Arduino UNO代码中会使用。

填写一个较规范的标识符。因为是通过标识符控制功能的实现。

硬件开发标签。

如果要跟MCU配合使用,即CBU模组只负责上网,MCU做主控。那么“已选云端对接方式”要选择涂鸦标准模组MCU SDK开发。

然后选择模组,这里推荐WBR3模组CBU模组。

模组选择完毕,在此页面的下方,下载资料区域,涂鸦IoT平台会根据你选择的模组和定义的功能,提供一份开发资料,下载此资料可以让您快速了解涂鸦物联网平台如何使用,并加速开发过程。

公共资料区域,还可以下载涂鸦智能App

本实例中选用的是CBU模组。

开发资源包模组调试

要想实现MCU+模组组合在一起的开发方式,首先我们要对MCU和模组之间的通讯协议了解清楚,而要了解通讯协议,我们可以借助官方的模组调试助手,利用它可以方便、快速的理解MCU与模组的交互过程。

遇到困难,也方便调试解决问题。

模组调试助手使用教程 https://developer.tuya.com/cn/docs/iot/module-debugging-assistant-instruction?id=K9hs0cj3lf0au

在 MCU 模拟模式中,涂鸦模组调试助手会模拟 MCU 自动回复模组正确的协议数据,用手机给模组配网后可测试 DP 数据的上报下发。

对应上面的信息,模组的串口2的发送引脚,输出的Log信息如下:

好了,有了上面的了解,我们接下来看一下使用Arduino+CBU模组实现的幻彩灯带如何进行控制。

菜单“项目”-->“加载库”

引用tuya库

如果Arduino官方已经收录了涂鸦SDK的话,那么在“管理库”中检索tuya即可;

如果没有被收录,那么使用“添加.ZIP库”文件的方式加载库。

引用Adafruit_NeoPixel库

如果因为网络的原因,管理库中下载Arduino库困难,也可以使用别人下好的库,将其放在Window系统下的:我的电脑>文档>Arduino>libraries 文件夹中。

void setup { pinMode(BUTTON_PIN, INPUT_PULLUP); strip.begin; // Initialize NeoPixel strip object (REQUIRED) strip.show; // Initialize all pixels to 'off' DebugSerial.begin(9600); Serial.begin(9600); //Initialize led port, turn off led. pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); //Initialize networking keys. pinMode(key_pin, INPUT_PULLUP); //incoming all DPs and their types array, DP numbers //Enter the PID and MCU software version my_device.init(pid, mcu_ver); my_device.set_dp_cmd_total(dp_array, 17); //register DP download processing callback function my_device.dp_process_func_register(dp_process); //register upload all DP callback function my_device.dp_update_all_func_register(dp_update_all); last_time = millis; music_last_time = millis;}void loop { //进入配网模式 //Enter the connection network mode when Pin7 is pressed. if (digitalRead(key_pin) == LOW) { delay(80); if (digitalRead(key_pin) == LOW) { my_device.mcu_set_wifi_mode(SMART_CONFIG); } } my_device.uart_service; /* LED blinks when network is being connected */ if ((my_device.mcu_get_wifi_work_state != WIFI_LOW_POWER) && (my_device.mcu_get_wifi_work_state != WIFI_CONN_CLOUD) && (my_device.mcu_get_wifi_work_state != WIFI_SATE_UNKNOW)) { if (millis- last_time >= 500) { last_time = millis; if (led_state == LOW) { led_state = HIGH; } else { led_state = LOW; } digitalWrite(LED_BUILTIN, led_state); } } if(work_mode==3) { if (millis- music_last_time >= 50) { music_last_time = millis; visualize_music; } }}

来源:城市套路深

相关推荐