电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
B类软件保护措施
分 享
扫描二维码分享
B类软件保护措施
B类软件
保护
单片机
流着汗的鱼
关注
发布时间: 2019-10-24
丨
阅读: 1064
家用电器的控制器按软件分类分为3类:A类、B类和C类。 A类的软件控制器不涉及到安全功能方面的控制,如湿度控制器、照明控制器、定时和计时开关;B类的软件控制器可防止所控设备的不安全操作,如洗衣机的热断路器和门锁、电磁炉的超温保护;C类是面对更加危险的功能控制,如自动燃烧器控制器和水加热系统用的热断路器(不通风)。 电磁炉控制器软件等级为B类,因为它只要保证不超温失火即可,当然也可以不做B类软件保护认证,可以用热熔断体来代替软件认证,当锅具温度干烧后,通过微晶面板传热给下面的热熔断体,达到一定温度后,断掉整个220VAC。这样的缺点就是硬件成本增加,由于传热差异保护温度不稳定。目前市场上大部分电磁炉都是进行软件保护认证,代替热熔断体。 电磁炉认证时,引用国标家用和类似用途电自动控制器GB14536.1-2008,在标准中定义了B类软件控制器的要求。以及软件自检要求,同时给出了具体的一些测试方法。可以查看标准中表 H.11.12.7,下面我给出一个简化的选择方案如下图: ![](https://cf03.ickimg.com/bbsimages/201910/15e488835b506f4583690d652078c9f5.jpg) 还是用STC单片机为例 1.针对寄存器的错误的措施,用的是“棋盘算法”进行周期性检测,当然部分寄存器在运行中是不可以随意更改数据的,所以上电时我们做了全检,正常运行时只对部分进行周期检测。根据寄存器的类型,向寄存器中交替写入特定的测试数据(多个),再用相同的数据与寄存器异或,再读取确认是否为零。下面程序用的0xAA和0x55对寄存器进行测试。 ```c CLASSBRESULT PowerOnTest(void) { ACC = 0x55; B = 0xaa; while((ACC!=0x55)|| (B!=0xaa)) return CLASSB_TEST_FAIL; ACC = 0xaa; B = 0x55; while((ACC!=0xaa)|| (B!=0x55))return CLASSB_TEST_FAIL; ACC = 0x55; B = 0xAA; while((ACC!=0x55)|| (B!=0xaa))return CLASSB_TEST_FAIL; DPL = 0x55; DPH = 0xaa; while((DPL!=0x55)|| (DPH!=0xaa))return CLASSB_TEST_FAIL; DPL = 0xaa; DPH = 0x55; while((DPL!=0xaa)|| (DPH!=0x55))return CLASSB_TEST_FAIL; DPL = 0x55; DPH = 0xAA; while((DPL!=0x55)|| (DPH!=0xaa))return CLASSB_TEST_FAIL; B = PSW; PSW = 0x54; while((PSW&0xfe)!=0x54)return CLASSB_TEST_FAIL; PSW = 0xaa; while((PSW&0xfe)!=0xaa)return CLASSB_TEST_FAIL; PSW = 0x54; while((PSW&0xfe)!=0x54)return CLASSB_TEST_FAIL; PSW = B; B = SP; SP = 0x55; while((SP!=0x55))return CLASSB_TEST_FAIL; SP = 0xaa; while((SP!=0xaa))return CLASSB_TEST_FAIL; SP = 0x55; while((SP!=0x55))return CLASSB_TEST_FAIL; SP = B; return CLASSB_TEST_PASS; } ``` 下面为正常运行时,进行的特殊寄存器检测,DPL和DPH数据指针寄存器,不能随意改变,所以正常运行时不作检测。一般先把寄存器数据存到B寄存器,待校验完成后再由B寄存器写回去。 ```c CLASSBRESULT Special_RAM_TEST(void) using 1 { EA = 0;ACC = 0x55;B = 0xaa; while((ACC!=0x55)|| (B!=0xaa))return CLASSB_TEST_FAIL; ACC = 0xaa;B = 0x55; while((ACC!=0xaa)|| (B!=0x55))return CLASSB_TEST_FAIL; ACC = 0x55;B = 0xaa; while((ACC!=0x55)|| (B!=0xaa))return CLASSB_TEST_FAIL; B = PSW;PSW = 0x54; while((PSW&0xfe)!=0x54)return CLASSB_TEST_FAIL; PSW = 0xaa; while((PSW&0xfe)!=0xaa)return CLASSB_TEST_FAIL; PSW = 0x54; while((PSW&0xfe)!=0x54)return CLASSB_TEST_FAIL; PSW = B;B = SP;SP = 0x55; while((SP!=0x55))return CLASSB_TEST_FAIL; SP = 0xaa; while((SP!=0xaa))return CLASSB_TEST_FAIL; SP = 0x55; while((SP!=0x55))return CLASSB_TEST_FAIL; SP = B; B = R00;R00 = 0x55; while((R00!=0x55))return CLASSB_TEST_FAIL; R00 = 0xaa; while((R00!=0xaa))return CLASSB_TEST_FAIL; R00 = 0x55; while((R00!=0x55))return CLASSB_TEST_FAIL; R00 = B; B = R01;R01 = 0x55; while((R01!=0x55))return CLASSB_TEST_FAIL; R01 = 0xaa; while((R01!=0xaa))return CLASSB_TEST_FAIL; R01 = 0x55; while((R01!=0x55))return CLASSB_TEST_FAIL; R01 = B;B = R02;R02 = 0x55; while((R02!=0x55))return CLASSB_TEST_FAIL; R02 = 0xaa; while((R02!=0xaa))return CLASSB_TEST_FAIL; R02 = 0x55; while((R02!=0x55))return CLASSB_TEST_FAIL; R02 = B;B = R03;R03 = 0x55; while((R03!=0x55))return CLASSB_TEST_FAIL; R03 = 0xaa; while((R03!=0xaa))return CLASSB_TEST_FAIL; R03 = 0x55; while((R03!=0x55))return CLASSB_TEST_FAIL; R03 = B;B = R04;R04 = 0x55; while((R04!=0x55))return CLASSB_TEST_FAIL; R04 = 0xaa; while((R04!=0xaa))return CLASSB_TEST_FAIL; R04 = 0x55; while((R04!=0x55))return CLASSB_TEST_FAIL; R04 = B;B = R05;R05 = 0x55; while((R05!=0x55))return CLASSB_TEST_FAIL; R05 = 0xaa; while((R05!=0xaa))return CLASSB_TEST_FAIL; R05 = 0x55; while((R05!=0x55))return CLASSB_TEST_FAIL; R05 = B;B = R05;R06 = 0x55; while((R06!=0x55))return CLASSB_TEST_FAIL; R06 = 0xaa; while((R06!=0xaa))return CLASSB_TEST_FAIL; R06 = 0x55; while((R06!=0x55))return CLASSB_TEST_FAIL; R06 = B;B = R07;R07 = 0x55; while((R07!=0x55))return CLASSB_TEST_FAIL; R07 = 0xaa; while((R07!=0xaa))return CLASSB_TEST_FAIL; R07 = 0x55; while((R07!=0x55))return CLASSB_TEST_FAIL; R07 = B;PSW = 0;R00 = 0;B = R10;R10 = 0x55; while((R10!=0x55))return CLASSB_TEST_FAIL; R10 = 0xaa; while((R10!=0xaa))return CLASSB_TEST_FAIL; R10 = 0x55; while((R10!=0x55))return CLASSB_TEST_FAIL; R10 = B;B = R11;R11 = 0x55; while((R11!=0x55))return CLASSB_TEST_FAIL; R11 = 0xaa; while((R11!=0xaa))return CLASSB_TEST_FAIL; R11 = 0x55; while((R11!=0x55))return CLASSB_TEST_FAIL; R11 = B;B = R12;R12 = 0x55; while((R12!=0x55))return CLASSB_TEST_FAIL; R12 = 0xaa; while((R12!=0xaa))return CLASSB_TEST_FAIL; R12 = 0x55; while((R12!=0x55))return CLASSB_TEST_FAIL; R12 = B;B = R13;R13 = 0x55; while((R13!=0x55))return CLASSB_TEST_FAIL; R13 = 0xaa; while((R13!=0xaa))return CLASSB_TEST_FAIL; R13 = 0x55; while((R13!=0x55))return CLASSB_TEST_FAIL; R13 = B;B = R14;R14 = 0x55; while((R14!=0x55))return CLASSB_TEST_FAIL; R14 = 0xaa; while((R14!=0xaa))return CLASSB_TEST_FAIL; R14 = 0x55; while((R14!=0x55))return CLASSB_TEST_FAIL; R14 = B;B = R15;R15 = 0x55; while((R15!=0x55))return CLASSB_TEST_FAIL; R15 = 0xaa; while((R15!=0xaa))return CLASSB_TEST_FAIL; R15 = 0x55; while((R15!=0x55))return CLASSB_TEST_FAIL; R15 = B;B = R15;R16 = 0x55; while((R16!=0x55))return CLASSB_TEST_FAIL; R16 = 0xaa; while((R16!=0xaa))return CLASSB_TEST_FAIL; R16 = 0x55; while((R16!=0x55))return CLASSB_TEST_FAIL; R16 = B;B = R17;R17 = 0x55; while((R17!=0x55))return CLASSB_TEST_FAIL; R17 = 0xaa; while((R17!=0xaa))return CLASSB_TEST_FAIL; R17 = 0x55; while((R17!=0x55))return CLASSB_TEST_FAIL; R17 = B;B = R10;R20 = 0x55; while((R20!=0x55))return CLASSB_TEST_FAIL; R20 = 0xaa; while((R20!=0xaa))return CLASSB_TEST_FAIL; R20 = 0x55; while((R20!=0x55))return CLASSB_TEST_FAIL; R20 = B;B = R11;R21 = 0x55; while((R21!=0x55))return CLASSB_TEST_FAIL; R21 = 0xaa; while((R21!=0xaa))return CLASSB_TEST_FAIL; R21 = 0x55; while((R21!=0x55))return CLASSB_TEST_FAIL; R21 = B;B = R22;R22 = 0x55; while((R22!=0x55))return CLASSB_TEST_FAIL; R22 = 0xaa; while((R22!=0xaa))return CLASSB_TEST_FAIL; R22 = 0x55; while((R22!=0x55))return CLASSB_TEST_FAIL; R22 = B;B = R23;R23 = 0x55; while((R23!=0x55))return CLASSB_TEST_FAIL; R23 = 0xaa; while((R23!=0xaa))return CLASSB_TEST_FAIL; R23 = 0x55; while((R23!=0x55))return CLASSB_TEST_FAIL; R23 = B;B = R24;R24 = 0x55; while((R24!=0x55))return CLASSB_TEST_FAIL; R24 = 0xaa; while((R24!=0xaa))return CLASSB_TEST_FAIL; R24 = 0x55; while((R24!=0x55))return CLASSB_TEST_FAIL; R24 = B;B = R15;R25 = 0x55; while((R25!=0x55))return CLASSB_TEST_FAIL; R25 = 0xaa; while((R25!=0xaa))return CLASSB_TEST_FAIL; R25 = 0x55; while((R25!=0x55))return CLASSB_TEST_FAIL; R25 = B;B = R25;R26 = 0x55; while((R26!=0x55))return CLASSB_TEST_FAIL; R26 = 0xaa; while((R26!=0xaa))return CLASSB_TEST_FAIL; R26 = 0x55; while((R26!=0x55))return CLASSB_TEST_FAIL; R26 = B;B = R27;R27 = 0x55; while((R27!=0x55))return CLASSB_TEST_FAIL; R27 = 0xaa; while((R27!=0xaa))return CLASSB_TEST_FAIL; R27 = 0x55; while((R27!=0x55))return CLASSB_TEST_FAIL; R27 = B;B = R30;R30 = 0x55; while((R30!=0x55))return CLASSB_TEST_FAIL; R30 = 0xaa; while((R30!=0xaa))return CLASSB_TEST_FAIL; R30 = 0x55; while((R30!=0x55))return CLASSB_TEST_FAIL; R30 = B;B = R31;R31 = 0x55; while((R31!=0x55))return CLASSB_TEST_FAIL; R31 = 0xaa; while((R31!=0xaa))return CLASSB_TEST_FAIL; R31 = 0x55; while((R31!=0x55))return CLASSB_TEST_FAIL; R31 = B;B = R32;R32 = 0x55; while((R32!=0x55))return CLASSB_TEST_FAIL; R32 = 0xaa; while((R32!=0xaa))return CLASSB_TEST_FAIL; R32 = 0x55; while((R32!=0x55))return CLASSB_TEST_FAIL; R32 = B;B = R33;R33 = 0x55; while((R33!=0x55))return CLASSB_TEST_FAIL; R33 = 0xaa; while((R33!=0xaa))return CLASSB_TEST_FAIL; R33 = 0x55; while((R33!=0x55))return CLASSB_TEST_FAIL; R33 = B;B = R34;R34 = 0x55; while((R34!=0x55))return CLASSB_TEST_FAIL; R34 = 0xaa; while((R34!=0xaa))return CLASSB_TEST_FAIL; R34 = 0x55; while((R34!=0x55))return CLASSB_TEST_FAIL; R34 = B;B = R35;R35 = 0x55; while((R35!=0x55))return CLASSB_TEST_FAIL; R35 = 0xaa; while((R35!=0xaa))return CLASSB_TEST_FAIL; R35 = 0x55; while((R35!=0x55))return CLASSB_TEST_FAIL; R35 = B;B = R35;R36 = 0x55; while((R36!=0x55))return CLASSB_TEST_FAIL; R36 = 0xaa; while((R36!=0xaa))return CLASSB_TEST_FAIL; R36 = 0x55; while((R36!=0x55))return CLASSB_TEST_FAIL; R36 = B;B = R37;R37 = 0x55; while((R37!=0x55))return CLASSB_TEST_FAIL; R37 = 0xaa; while((R37!=0xaa))return CLASSB_TEST_FAIL; R37 = 0x55; while((R37!=0x55))return CLASSB_TEST_FAIL; R37 = B;EA = 1; return CLASSB_TEST_PASS; } ``` 2.针对程序计数器的故障使用独立间隙检测-也就是看门狗; WDT_CONTR = 0x04; //看门狗定时器溢出时间计算公式: (12 * 32768 * PS) / FOSC (秒) //设置看门狗定时器分频数为32,溢出时间如下: //11.0592M : 1.14s //18.432M : 0.68s //20M : 0.63s WDT_CONTR |= 0x20; //启动看门狗 3.针对中断处理和执行的故障-还是用看门狗; 4.针对时钟的故障-依然还是用看门狗; 5.针对不可变存储器( ROM)的故障,使用多重校验和,也就是CRC检测。下面程序使用CRC16进行检查,这段代码是CRC16_ MODBUS模式的子函数,多项式为8005,CRC初始值0xFFFF代入。单片机上电时,把整个单片机代码区(也可以只针对程序编译后的ROM大小)进行CRC检测。 ```c //120us @18.432MHz /@@******************************************************************** * Description: * This function computes the periodic checksum using the Cyclic Redundancy * Check (CRC). It detects the single bit Faults in the invariable memory. * This function returns the final CRC Value. * CRC-16 is used as a divisor. * * CRC-16 = x16+x15+x2+1 = 1 1000 0000 0000 0101= 8005(hex) IBM SDLC * ********************************************************************/ static unsigned int CRC_Check_A001(unsigned char *p,unsigned int crcSeed) { unsigned char i,j; unsigned int CRC_Value; CRC_Value = crcSeed;//初始值 for(i = 0; i < CRCDATALENGTH; i++) { CRC_Value ^= (unsigned int)(*p); p++; for(j = 0; j < 8; j++) { if((CRC_Value&0x0001)==1) { CRC_Value >>= 1; CRC_Value ^= 0xa001;//CRC校验码的生成多项式为X16+X15+X2+1//注意进行校验码生成的时候是从数据流的第1个字节开始的 } else { CRC_Value >>= 1; } } } return CRC_Value; } ``` CLASSB_ROMCRCTest函数周期性调用,CurrentAddress记录当前检测地址,每次调用函数只检测8个字节,CRCENDADDRESS是被检测地址的最后一位,可以宏定义成0x2800,也就是10k,可以根据实际代码量进行调整。当CRC计算没有到最后一位时,则返回CLASSB_TEST_INPROGRESS;如果是最后一位时,则与第一次CRC校验结果进行比较,相同返回CLASSB_TEST_PASS,不同返回CLASSB_TEST_FAIL。 ```c CLASSBRESULT CLASSB_ROMCRCTest(void) { if(CurrentAddress < CRCENDADDRESS) { if(CurrentAddress == 0) CRCResult = 0xffff; CRCResult = CRC_Check_A001((unsigned char code *)CurrentAddress,CRCResult); CurrentAddress += 8; return CLASSB_TEST_INPROGRESS; } else { CurrentAddress = 0; if(CRCResult == FirstCRCResult) return CLASSB_TEST_PASS; else return CLASSB_TEST_FAIL; } } ``` 下面这一段代码为上电初始化时,CRC调用代码。等待ROM全部检测完毕,然后记录第一次ROM的CRC校验结果。 ```c void CLASSB_PowerOnROMCRCTest(void) { CurrentAddress = 0; while(CLASSB_ROMCRCTest() == CLASSB_TEST_INPROGRESS); FirstCRCResult = CRCResult; } ``` 6.针对可变存储器(RAM)的故障,再次使用棋盘算法,下面截了一个图说明棋盘算法如何操作的。 ![](https://cf03.ickimg.com/bbsimages/201910/85c63db8d8d460cf71ed60c4d6460e68.jpg) 在实际使用时,先将RAM中划分一块区域出来,这部分区域作中间存储用。比如此区域为A,现要检测区域B,那么现将区域B的数据复制到A中,待B区域检测完毕后,再将A区域数据复制到B区域,这样保证原来的数据不丢失。那么A区域可以自行运用棋盘算法自检。 STC内部RAM如下,工作寄存器组区已经在上面特殊寄存器中做检测了,特殊功能寄存器是没有办法检测的,否则直接影响单片机运行。所以只需检测0x20到0x7F检测的内部RAM,以及外部扩展的XRAM。 ![](https://cf03.ickimg.com/bbsimages/201910/bdd0cb8ffe2e5f6487fb88b2cd863280.jpg) 那么A区域我定义是从0x24到0x2B,还需定义三个变量,定义在了0x20-0x23;从0x2c到0x7f和XRAM都是B区域了。 /#define BUFFERADDRESS 0x23 unsigned char data *CLASSB_Checkerboardaddress _at_ 0x20; unsigned char xdata *CLASSB_CheckerboardaddressX _at_ 0x21; unsigned char data CLASSB_Checkerboardbuffer[CLASSB_CheckerboardLength] _at_ BUFFERADDRESS; CLASSB_RAMCheckerboardTest函数是进行周期RAM检测。此函数分为4大类,通过下面中的if else就可以看出。RAM地址为A区域时,进行A区域自检;第2部分为0x2B到0x7b的8字节连续的RAM;第3部分,是0x78到0x7f是内部RAM最后8个字节;第四部分是XRAM从0到XDATALENGTH字节检测,同样根据实际使用大小调整XDATALENGTH的值。 每部分检测基本都是这样:1.保存数据到缓冲区域A;2.写入0x55 0xaa轮流写入;3.读并确认刚才的数据正确后再写入0xaa 0x55;3.读取并确认是否正确,正确恢复数据。(下面程序中是写0,应该恢复数据) ```c CLASSBRESULT CLASSB_RAMCheckerboardTest(void) { unsigned char i; //idata test if(CLASSB_Checkerboardaddress < (BUFFERADDRESS+8)) return CheckerGlobalTest(); else if(CLASSB_Checkerboardaddress < (BUFFERADDRESS+0x50)) { //save data to buffer for(i = 0; i < 8; i++) { CLASSB_Checkerboardbuffer[i] = *CLASSB_Checkerboardaddress++; } // Write 0x55, 0xAA for(i = 0; i < 4; i++) { *--CLASSB_Checkerboardaddress = 0x55; *--CLASSB_Checkerboardaddress = 0xaa; } // Read back, check and write inverse checkerboard for(i = 0; i < 4; i++) { if(*CLASSB_Checkerboardaddress^0xaa) return CLASSB_TEST_FAIL; else *CLASSB_Checkerboardaddress++ = 0x55; if(*CLASSB_Checkerboardaddress^0x55) return CLASSB_TEST_FAIL; else *CLASSB_Checkerboardaddress++ = 0xaa; } // Read back, check and wirte zero for(i = 0; i < 4; i++) { if(*--CLASSB_Checkerboardaddress^0xaa) return CLASSB_TEST_FAIL; else *CLASSB_Checkerboardaddress = 0; if(*--CLASSB_Checkerboardaddress^0x55) return CLASSB_TEST_FAIL; else *CLASSB_Checkerboardaddress = 0; } CLASSB_Checkerboardaddress += 8; } else if(CLASSB_Checkerboardaddress < 0x80) { CLASSB_Checkerboardaddress = 0x78; //save data to buffer for(i = 0; i < 8; i++) { CLASSB_Checkerboardbuffer[i] = *CLASSB_Checkerboardaddress++; } // Write 0x55, 0xAA for(i = 0; i < 4; i++) { *--CLASSB_Checkerboardaddress = 0x55; *--CLASSB_Checkerboardaddress = 0xaa; } // Read back, check and write inverse checkerboard for(i = 0; i < 4; i++) { if(*CLASSB_Checkerboardaddress^0xaa) return CLASSB_TEST_FAIL; else *CLASSB_Checkerboardaddress++ = 0x55; if(*CLASSB_Checkerboardaddress^0x55) return CLASSB_TEST_FAIL; else *CLASSB_Checkerboardaddress++ = 0xaa; } // Read back, check and wirte zero for(i = 0; i < 4; i++) { if(*--CLASSB_Checkerboardaddress^0xaa) return CLASSB_TEST_FAIL; else *CLASSB_Checkerboardaddress = 0; if(*--CLASSB_Checkerboardaddress^0x55) return CLASSB_TEST_FAIL; else *CLASSB_Checkerboardaddress = 0; } CLASSB_Checkerboardaddress += 8; } else { //xdata test //save data to buffer if(CLASSB_CheckerboardaddressX < XDATALENGTH) { for(i = 0; i < 8; i++) { CLASSB_Checkerboardbuffer[i] = *CLASSB_CheckerboardaddressX++; } // Write 0x55, 0xAA for(i = 0; i < 4; i++) { *--CLASSB_CheckerboardaddressX = 0x55; *--CLASSB_CheckerboardaddressX = 0xaa; } // Read back, check and write inverse checkerboard for(i = 0; i < 4; i++) { if(*CLASSB_CheckerboardaddressX^0xaa) return CLASSB_TEST_FAIL; else *CLASSB_CheckerboardaddressX++ = 0x55; if(*CLASSB_CheckerboardaddressX^0x55) return CLASSB_TEST_FAIL; else *CLASSB_CheckerboardaddressX++ = 0xaa; } // Read back, check and wirte zero for(i = 0; i < 4; i++) { if(*--CLASSB_CheckerboardaddressX^0xaa) return CLASSB_TEST_FAIL; else *CLASSB_CheckerboardaddressX = 0; if(*--CLASSB_CheckerboardaddressX^0x55) return CLASSB_TEST_FAIL; else *CLASSB_CheckerboardaddressX = 0; } CLASSB_CheckerboardaddressX += 8; } else { CLASSB_CheckerboardaddressX = 0; CLASSB_Checkerboardaddress = BUFFERADDRESS; return CLASSB_TEST_PASS; } } return CLASSB_TEST_INPROGRESS; } ``` 7.内部数据路径的故障与内部数据路径的寻址的故障,由RAM和ROM自检一并覆盖,无需其他检测。 8.针对外部通信的故障:如果不涉及单片机与外部其他芯片的通讯,这个部分可以不考虑。如果涉及单片机与外部其他芯片的通讯,可以使用CRC校验。如果安全控制部分不涉及通讯,也可以不需要考虑。如电磁炉的超温控制安全回路中没有通讯部分,所以不考虑。 9.针对输入/输出外围电路故障:可以使用多路并行输出。电磁炉中,有3路IO口同时控制电磁炉工作,所以不用做自检。 10.针对模拟 I/O 中的 A‐D 和 D‐A 转换器故障:似真性检查。可以测试外部的一个固定电压,来判断AD是否正确,如外部电路提供2.5V固定电压,测试单片机本身AD情况,有很多单片机内部有AD通道进行自检,外部电路即可省去。DA可以结合AD一起自检。 A类控制器如需提升可靠性,也可以加入B类的自检部分。
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
2
)
流着汗的鱼
关注
评论
(0)
登录后可评论,请
登录
或
注册
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交