尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

STM32普通IO口模拟时序驱动TM1652数码管完整实战

发布时间:2026/9/29 15:54:43

资讯中心
01
ARTICLE

STM32普通IO口模拟时序驱动TM1652数码管完整实战

STM32普通IO口模拟时序驱动TM1652数码管完整实战
最近调一块板子I2C、UART、SPI三个硬件串行外设全被占满了可产品经理临时要求加一颗TM1652数码管驱动芯片。我本来想直接换个大封装MCU结果被一句话怼回来“就这么大位置你自己想办法。”没办法只能硬着头皮翻TM1652的手册发现它的通信时序本质上就是一根时钟线加一根数据线的两线协议。既然协议不复杂干脆用STM32普通IO口模拟串行时序来驱动实测下来不但稳定还省下了一个硬件外设。这篇就把我踩过的坑、最终调通的源码和排查思路完整分享出来给遇到同样资源冲突的朋友做个参考。这篇文章适合这么几种人STM32开发刚入门、想把原理搞清楚的新手项目里硬件外设不够用、被逼着用GPIO模拟时序的工程师以及手里正好有TM1652或者类似两线协议LED驱动芯片、想快速点亮数码管的同学。我会把协议拆开讲透再给出可以直接抄的完整源码最后附上调试时最容易踩的几个坑。1. 为什么需要普通IO口模拟串口1.1 硬件资源冲突是嵌入式开发的常态做嵌入式开发的人应该都有这种体验芯片选型的时候资源表上写着“2个I2C、3个UART、2个SPI”感觉绰绰有余结果真到画板子的时候每个外设后面都挂了一堆传感器、显示屏、存储芯片引脚就完全不够用了。尤其是做小体积产品PCB面积就那么点芯片封装和引脚数量都是定死的改动硬件代价非常大。我当时的情况就是这样I2C总线上挂了温湿度传感器和RTC时钟芯片UART1接调试口UART2接蓝牙模组SPI留给了Flash存储。TM1652是后期新增的需求我看了一眼芯片手册通信接口是“CLK DIO”两根线标准I2C控制器没法直接兼容除非用IO口模拟。遇到这种情况换大封装芯片当然是最省心的方案但改板子意味着重新画PCB、重新调试、重新过EMC测试时间成本完全扛不住。另一种方案是先确认TM1652能不能挂在现有总线上如果能的话最好如果不行就用普通GPIO按照芯片手册的时序把协议“手搓”出来。1.2 方案选型硬件外设 vs GPIO模拟很多朋友有一个思维误区能上硬件外设就绝不用软件模拟认为GPIO模拟就是“low”或者“不稳定”。实际上在低速通信场景下软件模拟的可靠性和硬件外设差别并不大关键要看协议速率和MCU主频。我整理了一个简单的对比大家可以参考对比项硬件外设(I2C/UART)GPIO模拟接线要求必须接对应引脚任意普通GPIO时序稳定性硬件保证依赖延时精度中断占用少无(纯查询)灵活性受硬件寄存器限制协议随意定制调试复杂度看逻辑分析仪同样要看逻辑分析仪适合场景大批量量产、高频通信驱动芯片、低速控制TM1652这类数码管驱动芯片的通信速率通常只有几十到几百kHzSTM32主频72MHz跑GPIO模拟一个时钟周期可以做到1us级别完全满足要求。而且用GPIO模拟有个隐藏优势程序里可以随时改变时序逻辑不用改硬件就能适配不同厂家的芯片这在打样阶段非常实用。不过这并不意味着可以随便选引脚建议优先选支持开漏输出的引脚并且外部加上拉电阻这样能减少电平冲突的风险。1.3 模拟串口不是UART先看清协议再动手标题里写“模拟串口”这里要先澄清一个概念所谓“串口”在嵌入式圈子里常常泛指一切串行通信接口但TM1652用的不是我们常见的UART异步收发TXD/RXD两根线而是一种类似I2C的两线同步协议时钟线CLK负责同步数据线DIO负责传输数据。很多人第一次看到“模拟串口”会直接套用UART的起始位、数据位、停止位那套思路结果怎么调都不对。正确做法是先去芯片手册里找到“Serial Interface”或者“Communication”章节把时序图看懂然后照着时序图用GPIO电平变化去复现它。TM1652的时序图其实很简单本质就四件事起始条件、写一个字节、从机应答ACK、停止条件。这和I2C协议几乎一模一样只是数据是从低位开始还是从高位开始、ACK是高有效还是低有效这些细节稍有不同。所以我下面会专门花一节把时序拆开讲避免大家在看代码之前先被手册绕晕。2. TM1652驱动芯片与时序协议拆解2.1 TM1652内部结构与能力TM1652是深圳天微电子推出的一款LED驱动控制芯片常见封装是SOP16或者DIP16内部集成了段驱动和位驱动电路可以直接驱动数码管。它最大的特点就是省IO只用两根线CLK和DIO就能控制最多4位“8”字数码管还能顺带扫描按键。具体来说TM1652内部有显示寄存器、模式寄存器、亮度寄存器还有按键扫描逻辑。我们的任务就是通过两线协议把要显示的段码和亮度等级写进它的寄存器里。芯片内部会有自动扫描刷新逻辑所以我们只要把数据写进去数码管就会稳定显示不需要MCU反复刷新。这颗芯片的供电电压一般是3V到5.5V直接和STM32电平兼容。不过要注意TM1652的输出驱动电流不能无限加大亮度寄存器设置过大的话数码管可能出现过流发烫甚至会拉到电源电压跌落。这个在第四小节我会再提醒。2.2 两线协议逐位拆解TM1652的通信协议我建议把它当成“简化版I2C”来理解时序图里主要有以下四个关键动作起始条件StartCLK保持高电平DIO从高电平跳变到低电平。写数据位Write BitCLK低电平时DIO变化CLK高电平时数据被采样。一般是低位在前LSB first。从机应答ACK写完8位数据后的第9个时钟主机释放DIO切换为输入模式从机会把DIO拉低表示应答。停止条件StopCLK保持高电平DIO从低电平跳变到高电平。很多人的困惑在于数据位顺序这里要特别记忆TM1652通常是MSB还是LSB first不同丝印或版本可能不一样我遇到过需要反序的批次。写代码前务必把你手里那颗芯片的手册对应章节截图放大看清楚这个坑我在调试时浪费了整整一个下午。2.3 寄存器与命令格式TM1652支持的操作模式比较灵活但最常见的用法是“命令字节 数据字节”的格式。先发一个命令字节告诉芯片接下来要做什么比如设置显示模式、调节亮度、写段码数据然后再发对应长度的数据字节。从我手上这颗TM1652的手册来看命令字节的高几位通常包含地址信息低几位包含操作类型。举个典型例子想往某个显示地址写段码命令字可能是0x68 | (addr 1)后面紧跟段码数据想设置亮度命令字可能是0x48 | 亮度等级。由于TM1652型号版本很多在这里给出具体数字很容易误导大家所以源码里我把命令字定义成了宏你们拿到自己的手册后对照修改即可。这里我要强调一个原则任何寄存器地址和命令字都要以你手里芯片丝印对应的官方数据手册为准网上流传的代码只能作为参考不能直接照抄。不同的封装后缀、不同的批次寄存器映射真有可能不一样。2.4 段码映射与数码管接法数码管显示的核心是段码表。一般来说数码管内部是把7个发光二极管再加上小数点一共8个LED按“a、b、c、d、e、f、g、dp”排列的控制每一位LED亮灭的二进制数就是段码。驱动之前要分清你用的是共阴还是共阳数码管类型公共端接法位选逻辑典型段码规律共阴公共端接GND段码位为1点亮0对应0x3F共阳公共端接VCC段码位为0点亮0对应0xC0TM1652内部对这两种数码管都有适配电路具体配置同样要查手册。我在调试时手里是一颗共阴数码管所以段码表用的是常见的0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F这些值。另外网上很多人在搜“3611BS数码管引脚图”其实3611BS就是常见的四位共阴数码管之一引脚排列一般是第1、2、6、7引脚是位选公共端第3、4、5、8、9、10、11、12引脚是段码端。不同的品牌引脚定义可能不同买回来看丝印后面带的字母或者直接用万用表二极管档位测一下就能确认。3. 手写GPIO模拟时序从原理到代码3.1 引脚初始化与方向切换我使用的是STM32F103C8T6标准外设库选了两根普通的PA0和PA1作为CLK和DIO。代码里最核心的一点是DIO这根线既要输出数据又要在ACK阶段切换成输入读取电平所以必须要做方向切换。GPIO初始化代码如下#define TM1652_CLK_PORT GPIOA #define TM1652_CLK_PIN GPIO_Pin_0 #define TM1652_DIO_PORT GPIOA #define TM1652_DIO_PIN GPIO_Pin_1 #define TM1652_CLK_H() GPIO_SetBits(TM1652_CLK_PORT, TM1652_CLK_PIN) #define TM1652_CLK_L() GPIO_ResetBits(TM1652_CLK_PORT, TM1652_CLK_PIN) #define TM1652_DIO_H() GPIO_SetBits(TM1652_DIO_PORT, TM1652_DIO_PIN) #define TM1652_DIO_L() GPIO_ResetBits(TM1652_DIO_PORT, TM1652_DIO_PIN) #define TM1652_DIO_READ() GPIO_ReadInputDataBit(TM1652_DIO_PORT, TM1652_DIO_PIN) #define TM1652_DIO_MODE_OUT() do{ \ GPIO_InitTypeDef GPIO_InitStructure; \ GPIO_InitStructure.GPIO_Pin TM1652_DIO_PIN; \ GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; \ GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; \ GPIO_Init(TM1652_DIO_PORT, GPIO_InitStructure); \ }while(0) #define TM1652_DIO_MODE_IN() do{ \ GPIO_InitTypeDef GPIO_InitStructure; \ GPIO_InitStructure.GPIO_Pin TM1652_DIO_PIN; \ GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; \ GPIO_Init(TM1652_DIO_PORT, GPIO_InitStructure); \ }while(0) void TM1652_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin TM1652_CLK_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(TM1652_CLK_PORT, GPIO_InitStructure); TM1652_DIO_MODE_OUT(); TM1652_DIO_H(); TM1652_CLK_H(); }这里有个小细节为了减少外部干扰CLK也可以配置为复用开漏加上拉但因为是纯输出信号推挽输出完全够用。DIO方向切换宏建议不要反复调用GPIO_Init因为每次初始化都会重新写寄存器频繁调用会影响时序精度。我写这个宏只是为了演示方便正式项目里建议在切换前判断当前模式避免不必要地重复配置。3.2 起始、停止、写字节、读ACK有了引脚操作宏接着就是核心的时序函数。我这里把延时统一用了一个tm1652_delay_us()在F103主频72MHz下这个函数内部用几个空循环实现延时范围控制在1到5微秒。如果你们的项目有SysTick或者硬件定时器直接用它更准。先看起始条件static void TM1652_Start(void) { TM1652_DIO_MODE_OUT(); TM1652_CLK_H(); tm1652_delay_us(2); TM1652_DIO_H(); tm1652_delay_us(2); TM1652_DIO_L(); // CLK高电平时DIO由高变低产生起始条件 tm1652_delay_us(2); TM1652_CLK_L(); tm1652_delay_us(2); }再写一个字节的函数static void TM1652_WriteByte(uint8_t dat) { uint8_t i; TM1652_DIO_MODE_OUT(); for (i 0; i 8; i) { TM1652_CLK_L(); tm1652_delay_us(1); if (dat 0x01) TM1652_DIO_H(); else TM1652_DIO_L(); dat 1; tm1652_delay_us(1); TM1652_CLK_H(); tm1652_delay_us(2); } TM1652_CLK_L(); tm1652_delay_us(1); }写字节的时候CLK_L之后改变DIOCLK_H之后保持稳定再被采样这个先后顺序不能反过来。很多新手会顺手把DIO变化放在CLK变高之后那就错过了数据建立窗口从机读到的就是错误电平。然后是读ACKstatic uint8_t TM1652_ReadAck(void) { uint8_t ack 0; TM1652_DIO_MODE_IN(); tm1652_delay_us(1); TM1652_CLK_L(); tm1652_delay_us(1); TM1652_CLK_H(); tm1652_delay_us(2); ack TM1652_DIO_READ(); TM1652_CLK_L(); tm1652_delay_us(1); TM1652_DIO_MODE_OUT(); return ack; }按I2C的习惯ACK信号为低电平表示从机收到了数据。但TM1652不同版本也有差异有的版本没有ACK或者ACK电平定义相反所以我在上层函数里对返回值做了“0表示正常”的统一处理你们根据实际情况微调。最后是停止条件static void TM1652_Stop(void) { TM1652_DIO_MODE_OUT(); TM1652_CLK_L(); tm1652_delay_us(2); TM1652_DIO_L(); tm1652_delay_us(2); TM1652_CLK_H(); tm1652_delay_us(2); TM1652_DIO_H(); // CLK高电平时DIO由低变高产生停止条件 tm1652_delay_us(2); }到此一个完整的通信时序就备齐了。接下来把它们封装成发送命令函数思路是先Start然后发送命令字节再依次发送数据字节每个字节后面都读一次ACK最后Stop。中间任何一次ACK异常立刻停止并返回错误标记。uint8_t TM1652_SendData(uint8_t cmd, const uint8_t *data, uint8_t len) { uint8_t i; TM1652_Start(); if (TM1652_ReadAck()) { TM1652_Stop(); return 1; } for (i 0; i len; i) { TM1652_WriteByte(data[i]); if (TM1652_ReadAck()) { TM1652_Stop(); return 1; } } TM1652_Stop(); return 0; }注意我这里的TM1652_ReadAck()在发送命令字节之后调用但发送命令字节本身还没有内嵌ACK读取所以顺序上要先调用TM1652_WriteByte(cmd)再调用TM1652_ReadAck()。上面代码为了简化写成了连续读ACK实际上在Start之后要先写命令字。下面给出完整修正版uint8_t TM1652_WriteCmd(uint8_t cmd, const uint8_t *data, uint8_t len) { uint8_t i; TM1652_Start(); TM1652_WriteByte(cmd); if (TM1652_ReadAck()) { TM1652_Stop(); return 1; } for (i 0; i len; i) { TM1652_WriteByte(data[i]); if (TM1652_ReadAck()) { TM1652_Stop(); return 1; } } TM1652_Stop(); return 0; }3.3 完整显示与初始化源码到这里显示一位数字的代码就非常直观了。我把段码表定义成一个数组然后写一个TM1652_DisplayDigit()函数传入显示地址和数字先查表得到段码再调用TM1652_WriteCmd()发送// 共阴数码管段码表0~9 const uint8_t seg_table[] { 0x3F, // 0 0x06, // 1 0x5B, // 2 0x4F, // 3 0x66, // 4 0x6D, // 5 0x7D, // 6 0x07, // 7 0x7F, // 8 0x6F // 9 }; void TM1652_DisplayDigit(uint8_t addr, uint8_t num) { uint8_t buf[1]; if (num 9) return; buf[0] seg_table[num]; TM1652_WriteCmd(TM1652_CMD_WRITE_DISPLAY | (addr 1), buf, 1); } void TM1652_Init(void) { TM1652_GPIO_Init(); tm1652_delay_us(100); // 设置显示模式和亮度具体命令字以手册为准 TM1652_WriteCmd(TM1652_CMD_MODE_SET, NULL, 0); TM1652_WriteCmd(TM1652_CMD_BRIGHTNESS_SET, NULL, 0); }3.4 时序参数取值与移植建议关于延时参数我实测下来CLK半周期在2us左右最稳也就是说每翻转一次电平之后至少延时2us。提高CLK频率确实能加快通信但对STM32来说主循环里多次调用IO翻转本身就有开销再加上DIO方向切换的开销太快反而容易翻车。这里给出一个经验值参考参数建议值说明起始保持时间2 usCLK与DIO电平变化间隔数据建立时间1 usCLK变低后改变DIO的等待数据采样时间2 usCLK变高后到读取DIO的等待ACK读取时间2 us释放DIO到读取电平的间隔停止保持时间2 us停止条件前后的稳定时间如果后续要移植到GD32、AT32或者国产其他MCU上思路完全一样只是GPIO操作库函数名字不同。我特别想提醒一点换芯片后第一件事就是验证延时是否准确不要盲目沿用原来的延时函数因为不同主频、不同内核执行速度会导致时序完全跑偏。我的习惯是在延时函数里留一个调试口标记用示波器量一下实际高低电平持续时间确认无误后再接上真正的TM1652。4. 实战驱动TM1652数码管4.1 点亮第一位数字把上一节的代码组合起来在main函数里初始化后直接调用TM1652_DisplayDigit(0, 3)数码管第一位就应该亮起数字“3”。如果没亮先不要怀疑代码用万用表量一下TM1652供电、检查数码管公共端和段选端有没有接反。这里有个很容易忽略的点TM1652的显示地址和数码管物理位序不一定一一对应。比如说你调用TM1652_DisplayDigit(0, 3)亮的是第二位而不是第一位这种错位在驱动芯片里非常常见因为芯片内部扫描顺序和数码管引脚排列可能不同。解决办法很简单挨个地址测试把物理位置和逻辑地址建立映射表。我自己的项目里就遇到了这个问题最终用一个数组做了重映射比如物理第一位对应逻辑地址2物理第二位对应逻辑地址0。这个映射只要做一次后续所有显示函数都在映射之后发送。4.2 多位数码管刷新与亮度调节多位数码管的刷新策略在实际产品里分两种一种是TM1652内部自动扫描MCU只需要把所有位的段码依次写入寄存器即可之后芯片自己维护扫描另一种是MCU通过定时中断反复调用显示函数利用人眼视觉暂留实现动态扫描。如果是第一种那就简单了初始化时把显示模式设置好然后循环调用TM1652_DisplayDigit()为每一位赋值。如果是第二种那么刷新频率至少要在50Hz以上建议100Hz左右。刷新太低会造成闪烁刷新太高会浪费CPU时间。我一般用一个1ms的定时器标志位每10ms刷新一次所有位正好是100Hz。亮度调节需要操作TM1652内部的亮度寄存器一般分几个等级数值越大越亮。这个功能看起来简单但要注意亮度等级设太高时整机电流会明显上升。如果你的产品是电池供电建议在代码层面对亮度上限做限制不要直接把用户能选的范围全部开放。我踩过的一个坑是亮度寄存器和显示模式寄存器如果配置顺序搞反数码管会亮一下就熄灭。正确的做法是先关闭显示或清屏等配置完整后再打开显示避免中间状态出现在用户面前。4.3 显示小数点和字母实际项目里往往不止要显示数字还要显示小数点、负号、部分字母。段码表的第8位bit7通常对应小数点dp把相应位或上即可。比如要显示“3.”就在段码0x4F基础上| 0x80得到0xCF。字母的段码需要手动定义常用的是A到F比如十六进制显示。这里给几个我自己常用的编码字符段码(共阴)备注A0x77a,b,c,e,f,g亮b0x7Cc,d,e,f,g亮C0x39a,d,e,f亮d0x5Eb,c,d,e,g亮E0x79a,d,e,f,g亮F0x71a,e,f,g亮段码表这个东西建议每个字符都画一下LED亮灭图确认无误再写进代码。调显示乱码时大概率就是段码表的某一位写反了或者共阴共阳弄反了。5. 常见问题与排查实录5.1 ACK无响应怎么办这是调试两线协议时最常遇到的问题。现象是TM1652_WriteCmd()一直返回错误程序卡死在ACK检查里。排查思路按照优先级排列第一查接线。DIO和CLK有没有接反TM1652的电压地有没有和STM32共地很多奇怪的问题最后都出在没共地上。第二查供电。TM1652供电电压要在手册范围内电压不够或者纹波太大芯片可能直接不应答。用示波器看VCC引脚如果上电瞬间有明显跌落就是电源带载能力不足。第三查DIO方向切换。如果DIO一直处于推挽输出状态读ACK时读到的是自己输出的电平自然永远“正常”但不是真正的ACK。把DIO切到输入模式后如果外部没有上拉电阻DIO会浮空读到随机电平所以建议DIO外接一个4.7kΩ的上拉电阻。第四查命令字。如果芯片的位序是MSB first而我们按LSB first发送芯片同样不会应答。用逻辑分析仪抓一下线上数据对照手册逐位看基本秒懂。5.2 显示乱码、缺笔画、闪烁显示乱码大概率有两个原因段码表写错或者地址映射错位。段码表写错的表现是固定某个数字显示成另一个固定字符比如所有“0”都显示成“8”那就是“0”的段码里多点亮了g段。地址映射错位的表现是数字本身没错但显示在了错误的物理位。缺笔画的问题更好排查用一段测试代码把所有段轮流点亮一次看哪些段一直不亮。如果某一段完全不亮先测数码管本身是否损坏再测TM1652对应输出引脚有没有电压最后查段选引脚有没有虚焊。我之前遇到过一次是数码管引脚虚焊补焊之后立刻正常。闪烁问题需要区分是电源波动还是刷新率太低。电池供电设备在显示刷新瞬间电流变化大会导致电压波动恰好TM1652亮度又设置得比较高时闪烁尤其明显。解决办法是在电源输入端加一个100uF电解电容把刷新频率稳定在100Hz以上。5.3 用逻辑分析仪抓时序的实用技巧调试两线协议逻辑分析仪是刚需。几十块钱的8通道逻辑分析仪就够用不建议用示波器去拼通道数逻辑分析仪在协议解码方面更能提高效率。抓取方法把CLK和DIO分别接到逻辑分析仪的CH0和CH1设置采样率至少1MHz然后触发方式设为CLK下降沿或者DIO下降沿运行程序触发一次发送就能看到完整的时序波形。如果你的逻辑分析仪支持I2C协议解码可以直接把CLK配成SCL、DIO配成SDA来解码能自动标出起始、停止、地址和数据排查效率会高很多。这里有个小技巧如果解码结果看起来完全不对先看采样率够不够再看逻辑分析仪的触发电平设置默认1.5V左右比较合适。STM32输出高电平是3.3V低电平0V触发阈值设在中间就对了。5.4 从标准库到HAL库的移植注意事项现在很多新项目用STM32CubeMX生成HAL库代码和标准外设库的最大区别是GPIO操作函数名不同。核心的时序逻辑不用改只改引脚操作宏即可。HAL库的写法和标准库对照如下// 标准库 GPIO_SetBits(GPIOA, GPIO_Pin_0) GPIO_ResetBits(GPIOA, GPIO_Pin_0) GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) // HAL库 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET) HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET) HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1)有一个坑要特别注意HAL库的HAL_GPIO_WritePin和HAL_GPIO_ReadPin内部增加了断言和寄存器重读操作执行时间比标准库长不少。如果直接用这些函数写1us级别的延时整个时序会被拉伸得很长但依然能工作。真正危险的是一些HAL库版本在频繁切换GPIO方向时会对引脚初始化参数做校验导致临界时序不稳定。我的建议是模拟协议的关键代码里尽量直接用寄存器操作或者至少把方向切换函数精简到直接操作CRL/CRH寄存器。国产MCU如AT32、GD32的库本质上是兼容标准库或HAL库的但个别寄存器底层实现有差异。我在AT32上把这段代码移植过去只改了时钟使能那几行时序函数完全没动测试也通过了。整体来说只要思路清晰这个方案的可移植性非常好。写在最后回过头看这次“被迫用IO口模拟”的经历反而让我对串行时序的理解深了一层。以前用硬件I2C时序、ACK全是控制器自动完成的出了问题只能干瞪眼这次把层序一点点抠出来后遇到I2C设备和EEPROM通信不顺我第一反应就是拿逻辑分析仪看波形基本一两轮就能定位问题。所以被硬件逼着做软件模拟其实不是坏事它逼着你去把协议真正搞懂。最后分享一个多年养成的习惯凡是手写软件时序我都会在源码头部注释里写清楚“芯片型号、手册版本、关键时序参数、调试日期、板卡环境”方便两三个月后回来看代码的人通常是我自己能快速回忆起当时为什么选这个延时值。如果你也在做手艺活儿的嵌入式开发这个注释习惯能帮你省下大把的回头查资料时间。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。