尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
STM32F103与AT24C02的I2C通信详解:从时序到代码实战
1. 项目来源与硬件背景为什么这组合是入门首选做单片机开发尤其是刚从51、AVR转过来玩STM32的第一次接触I2C总线我强烈建议从AT24C02这块芯片入手。原因很简单它便宜、时序标准、逻辑清晰而且是I2C协议里最典型的一类从机设备——带器件地址、带片内寄存器寻址、支持随机读和顺序读。你把它跑通了市面上90%的I2C传感器比如温湿度SHT30、陀螺仪MPU6050、OLED屏SSD1306的驱动框架你基本就心里有数了。STM32F103的I2C外设是个很有意思的历史遗留问题——它的硬件I2C模块早期版本存在一些bug传闻导致很多老工程师宁可拿GPIO软件模拟I2C也不碰硬件外设。实际上在F103系列里硬件I2C只要配置正确是完全可以稳定工作的只不过它的事件标志管理比较绕不像软件模拟那样每个时序步骤都清清楚楚。这个坑我在后面会专门讲先说结论如果你是为了学协议本身软件模拟I2C能看到每个bit怎么翻如果你是为了工程效率硬件I2C配合中断或DMA省CPU资源。这篇文章两种方式都会覆盖但主线用软件模拟的方式把时序讲透这样你理解协议后再去调硬件I2C就事半功倍了。先看硬件连接。AT24C02是8引脚SOIC或者DIP封装引脚定义如下A0、A1、A2地址选择引脚接地或接VCC决定器件I2C地址的低三位。WP写保护引脚接高电平禁止写入接地或悬空允许写入。SCL时钟线接STM32的PB6硬件I2C1_SCL或任意推挽输出引脚软件模拟。SDA数据线接STM32的PB7硬件I2C1_SDA或任意引脚。VCC、GND电源。这里最容易被新手忽略的是上拉电阻。I2C总线是开漏结构SCL和SDA两根线必须各自接一个上拉电阻到3.3V或5V阻值常见4.7kΩ到10kΩ。如果你用的是STM32F103最小系统板板载的EEPROM芯片有些板子会焊一个AT24C02在背面已经帮你处理好上拉了但如果你是自己飞线搭的电路一定要加上拉电阻否则总线拉不高通信必失败。我实测过不加上拉电阻时SDA线在高电平期间被拉成1.2V左右的浮空电平读回来的数据全是0xFF。供电方面AT24C02工作电压范围是1.8V到5.5VSTM32F103是3.3V逻辑直接共电源3.3V即可不需要电平转换。至于热词里提到的stm32f103 5v转3.3v电路那是另一个话题——如果你用5V供电的模块去接3.3V的MCU注意别让5V直接灌进IO口AT24C02没有这个问题但I2C总线上如果挂了5V的器件就得考虑电平匹配了。通常情况下系统内所有I2C器件统一用3.3V供电是最省心的方案。2. 协议核心从时序图到代码实现的思维转换I2C协议其实特别像两个人对话时的约定说话前先举手示意起始条件说完一句要等对方回应应答位最后说我说完了停止条件。理解了这个思维模型再看时序图就不会觉得抽象了。2.1 起始条件与停止条件的精准卡点I2C的起始条件是SCL为高电平期间SDA从高电平跳变到低电平。停止条件是SCL为高电平期间SDA从低电平跳变到高电平。注意这两个条件都要求SDA的跳变发生在SCL为高的时候这跟数据传输阶段数据只能在SCL低电平期间变化是相反的规则。为什么要这样设计因为如果数据和起始/停止条件不加区分从机就无法判断总线上正在发生的是传输数据还是开始通信。起始和停止条件在物理上就是SDA在SCL高电平期的跳变这个跳变在数据传输中是被禁止的所以从机能明确识别。软件模拟时关键是用延时函数保证时序的占空比。标准I2C速率有100kHz标准模式和400kHz快速模式AT24C02支持400kHz。如果用GPIO模拟通常做到100kHz就足够用了毕竟EEPROM写入本身就要等5ms左右的内部写周期速率再高写操作瓶颈还是在芯片内部闪存。代码实现如下// 微秒级延时函数注意实际延时时间需根据主频调整 void I2C_Delay_us(uint32_t us) { // 72MHz主频下简单循环逼近这里不做精确延时库的引入 uint32_t i; for (i 0; i us * 8; i) { __NOP(); } } void I2C_Start(void) { SDA_H(); // 先拉高数据线 SCL_H(); // 时钟线在高电平期间 I2C_Delay_us(5); SDA_L(); // SDA拉低产生下降沿起始条件建立 I2C_Delay_us(5); SCL_L(); // 拉低时钟线准备发送数据 }起始条件后总线处于忙状态直到停止条件发出。中间任何一个字节传输错误都不能用简单的拉高拉低来复位而应该完整走一遍停止条件时序让从机释放总线。2.2 字节传输与应答位的约定细节数据位的传输规则是SCL高电平期间SDA上的电平必须保持稳定SCL低电平期间SDA允许变化。也就是说发送方在SCL低电平期间准备好数据拉高SCL让从机采样再从高拉低准备下一位。这就是为什么I2C传输每个bit需要一个完整的时钟脉冲。一个字节是8位高位先发MSB First第9个时钟脉冲是应答位。主机发送完8位数据后释放SDA拉高然后给一个时钟脉冲此时从机如果拉低了SDA说明从机收到了数据ACK如果SDA保持高说明从机没收到或者拒绝处理NACK。发送字节的函数核心uint8_t I2C_WriteByte(uint8_t data) { uint8_t i, ack_bit; for (i 0; i 8; i) { if (data 0x80) { SDA_H(); } else { SDA_L(); } data 1; I2C_Delay_us(2); SCL_H(); I2C_Delay_us(5); SCL_L(); I2C_Delay_us(2); } // 第9个时钟读取从机应答 SDA_H(); // 主机释放SDA I2C_Delay_us(2); SCL_H(); I2C_Delay_us(5); ack_bit SDA_READ(); // 读取SDA电平 SCL_L(); I2C_Delay_us(2); return ack_bit; // 0表示应答1表示无应答 }有个细节在SCL拉高之前SDA电平必须稳定。代码里先设置SDA再延时再拉高SCL。这个延时如果太短从机可能来不及采样如果太长会拉低总线速率。实测5微秒的延时在72MHz主频下用软件循环是稳定的如果你用定时器做精确微秒延时效果更好。2.3 器件地址的高三位与引脚电平的硬关联AT24C02的器件地址是8位其中前4位是固定的1010这是AT24C系列的身份标识第5到第7位是A2、A1、A0三个引脚的电平状态第8位是读写标志位1表示读0表示写。举个实际例子如果A0、A1、A2全部接地那么器件地址就是0xA0写操作和0xA1读操作。如果你把A0接到3.3V那么写地址变成0xA2读地址变成0xA3。这个地址计算方法在各种I2C设备里都通用比如有些传感器芯片有多个I2C地址可选就是通过地址引脚高低电平切换来实现的。我见过不少人在这一步翻车原理图上EEPROM的A0引脚画了但是忘记连到MCU的GPIO或者悬空处理。数据手册里明确写了A0/A1/A2内部没有下拉悬空时电平不定可能导致器件地址随机漂移。解决方法是三个地址引脚要么接地要么接VCC根据你的实际需求写死绝对不允许悬空。3. 软件驱动的分层设计基础函数到业务逻辑的过渡写驱动代码不能一上来就写往地址0x00写0xAA那样谁都能写但换个芯片换个场景就废了。好的做法是分三层底层的GPIO/时序层、中间的总线传输层、上层的存储器操作层。底层是跟硬件打交道的比如GPIO模式配置、拉高拉低操作。中间层实现起始、停止、发字节、收字节、应答检查这些通用I2C原语这层不关心你接的是什么从设备。上层才是AT24C02特有的操作——按地址写、按地址读、页写、连续读。这种分层有个直接好处如果你以后要接OLED或者温湿度传感器底层和中间层完全复用只需要新写上层代码。我自己的项目里I2C的中间层代码几乎从STM32F103移植到GD32、CH32V307、ESP32等不同平台时除了延时函数和GPIO操作其他逻辑一行没改。GPIO配置方面用软件模拟I2C时SCL和SDA初始化成推挽输出即可读应答时将SDA切换到输入模式读取读完再切回输出。这里有一个性能细节——频繁切换模式会引入时间开销有一种做法是SDA保持开漏输出模式读的时候写1到输出寄存器让外部上拉把电平拉高然后读输入寄存器。开漏模式下写0可以强制拉低写1相当于释放总线天然就适合I2C的半双工特性。用这种配置就不需要来回切换GPIO模式了。3.1 内存操作的四个基本指令AT24C02的数据手册上定义了几个指令码其实就是控制字节和设备寻址的组合搞懂这些才能正确操作操作类型起始后发送的字节说明写字节0xA0器件地址写标志后面跟1个存储地址和1个数据页写0xA0器件地址写标志后面跟起始存储地址和最多8字节数据当前地址读0xA1器件地址读标志不指定地址读的是内部地址计数器指向的位置随机读0xA0然后0xA1先写器件地址和存储地址再发读地址形成一个假写动作定位地址连续读0xA1随机读之后连续读每收一字节回ACK直到收到NACK停止写字节和页写的区别在于页写能一次写入8个字节AT24C02的页大小是8字节但页写有严格的边界限制——如果写入的地址跨越了页边界数据会回卷到页首覆盖原数据。这是AT24C02最容易踩的坑之一后面单独讲。3.2 写周期的等待策略延时 vs 查询应答AT24C02每次写入包括页写后内部会有一段写周期write cycle典型时间为5ms最大10ms。在这段时间内芯片不响应任何I2C指令你一发送器件地址它不会回复ACK。业界有两种应对方式方式一固定延时。写完数据后delay_ms(10)简单粗暴但会阻塞CPU如果系统里有多个I2C设备效率会比较低。方式二查询应答ACK Polling。不断发送器件地址写地址0xA0直到从机返回ACK为止。因为芯片内部写周期结束后会恢复正常响应而发送地址本身不会触发写入操作所以这是最高效的方法。uint8_t AT24C02_WaitWriteComplete(void) { uint8_t retry 200; // 最多重试200次 while (retry--) { I2C_Start(); // 发送器件地址写标志如果收到ACK说明写周期结束 if (I2C_WriteByte(0xA0) 0) { I2C_Stop(); return 1; // 写完成 } I2C_Stop(); delay_ms(1); } return 0; // 超时失败 }实际工程中我一般写一个函数把写数据等待写周期封装起来上层调用者不需要关心底层是延时还是轮询。如果你要做低功耗或者有实时性要求把这个等待过程放到非阻塞的RTOS任务里也是可以的但那是进阶话题了。4. 实操环节从单字节读写到页写全流程这部分是全文的核心实操环节直接给出可复制的代码和操作步骤配合调试过程讲解。4.1 握手检测确认总线通信正常在真正读写EEPROM之前我习惯先做一个总线扫描向器件地址0xA0发送一个字节注意不是数据而是地址字节看从机是否回复ACK。这等于跟设备打个招呼你在吗uint8_t AT24C02_Check(void) { I2C_Start(); uint8_t ack I2C_WriteByte(0xA0); // 0xA0 1010 0000器件地址写标志 I2C_Stop(); if (ack 0) { return 1; // 收到ACK设备在线 } return 0; // 无应答设备离线或地址不对 }这一步很重要能帮你快速定位问题。如果这里就失败别急着查后面读写的代码先查这几个点供电是否正常、I2C引脚是否接对、上拉电阻有没有、地址引脚电平是否正确。省下的都是调试时间。4.2 单字节写总把五步走记牢往指定地址写一个字节的完整时序如下起始条件发送设备地址写标志0xA0发送存储地址如0x00发送要写入的数据停止条件uint8_t AT24C02_WriteByte(uint16_t addr, uint8_t data) { I2C_Start(); // 发送器件地址写标志检查应答 if (I2C_WriteByte(0xA0) ! 0) { I2C_Stop(); return 0; } // 发送存储地址AT24C02只有256字节地址8位就够了 if (I2C_WriteByte((uint8_t)addr) ! 0) { I2C_Stop(); return 0; } // 发送数据 if (I2C_WriteByte(data) ! 0) { I2C_Stop(); return 0; } I2C_Stop(); // 等待内部写周期完成 return AT24C02_WaitWriteComplete(); }注意一点每次I2C_WriteByte返回非0都要调用I2C_Stop()来释放总线。如果中间某一字节后从机NACK总线上还处于起始后的状态此时不停掉的话后续通信会乱套。这是新手最容易忽略的——任何一个分支都要保证总线状态的完整性。4.3 单字节读一个假写就能定位随机读的步骤比写多一步因为你需要先告诉芯片我要读哪个地址这就要先发起一个写操作来装载地址然后又发起一个读操作来取数据起始条件发送设备地址写标志0xA0——这是假写发送存储地址目标地址再发起始条件即重复起始条件Repeated Start发送设备地址读标志0xA1读取数据字节此时主机发出NACK表示只读一字节就结束停止条件uint8_t AT24C02_ReadByte(uint16_t addr) { uint8_t data 0; I2C_Start(); // 假写 if (I2C_WriteByte(0xA0) ! 0) { I2C_Stop(); return 0; } if (I2C_WriteByte((uint8_t)addr) ! 0) { I2C_Stop(); return 0; } // 重复起始条件 I2C_Start(); // 发送器件地址读标志 if (I2C_WriteByte(0xA1) ! 0) { I2C_Stop(); return 0; } // 读取一个字节主机返回NACK data I2C_ReadByte(0); // 参数0表示主机发NACK I2C_Stop(); return data; }这里的重复起始条件Repeated Start和简单的停止再启动是有区别的。重复起始不释放总线中间不会有Stop条件总线始终被主机占用其他从机插不进来。在随机读的场景里走Stop再Start也能工作但重复起始是更规范的做法尤其当总线上挂了多个设备时能避免地址竞争。I2C_ReadByte的实现大概是主机在每个时钟周期的高电平期读取SDA电平循环8次收齐一个字节。第9个时钟主机根据参数决定发ACK继续读下一字节还是NACK结束本次读。uint8_t I2C_ReadByte(uint8_t ack_mode) { uint8_t i, data 0; for (i 0; i 8; i) { SCL_H(); I2C_Delay_us(5); data 1; if (SDA_READ()) { data | 0x01; } SCL_L(); I2C_Delay_us(5); } // 第9个时钟主机发送应答位 if (ack_mode) { SDA_L(); // 主机拉低表示ACK继续读 } else { SDA_H(); // 主机释放表示NACK结束读 } SCL_H(); I2C_Delay_us(5); SCL_L(); SDA_H(); // 释放SDA return data; }4.4 页写与边界保护8字节一组的坑页写能大幅提升写入效率一次写8个字节比单字节写快8倍虽然内部写周期时间不变但省去了7次地址装载和起始停止开销。但页写有边界限制起始地址的页内偏移加上写入长度不能超过页大小AT24C02的页大小是8字节。举个反面教材如果起始地址是0x06连续写4个字节那么这4个字节的地址范围是0x06、0x07、0x08、0x09。但AT24C02的页0覆盖0x00~0x07页1覆盖0x08~0x0F当写入跨过0x07到0x08时芯片内部的地址计数器会回卷到0x00也就是说你以为写到0x08的数据实际上写到了0x00。这个bug非常隐蔽写进去的数据读出来完全对不上而且不报错。正确的写法是要对页写做边界处理要么把一次跨页的写拆成多次要么强制保证写入长度起始地址偏移不超过8。我做了一个通用的页写函数来处理这个问题uint8_t AT24C02_WritePage(uint8_t addr, uint8_t *data, uint8_t len) { uint8_t i; // 检查是否跨页页内剩余空间是否足够 uint8_t page_remain 8 - (addr % 8); if (len page_remain) { // 简化处理只写页内剩余部分或者拆开写 len page_remain; } I2C_Start(); if (I2C_WriteByte(0xA0) ! 0) { I2C_Stop(); return 0; } if (I2C_WriteByte(addr) ! 0) { I2C_Stop(); return 0; } for (i 0; i len; i) { if (I2C_WriteByte(data[i]) ! 0) { I2C_Stop(); return 0; } } I2C_Stop(); return AT24C02_WaitWriteComplete(); }工程上更稳妥的方案是封装一个任意地址任意长度写的函数内部判断跨页后自动拆分多次写。这种函数在配置存储参数、保存系统状态时特别实用用户上层根本不需要关心页边界的问题。4.5 连续读一个地址顺序读完256字节连续读和随机读的区别在于随机读只读一个字节读完后主机发NACK结束连续读则是一次性把地址之后的内容连续读出每读一字节主机发ACK直到主机想结束才发NACK。uint8_t AT24C02_ReadContinuous(uint8_t addr, uint8_t *buf, uint16_t len) { uint16_t i; I2C_Start(); if (I2C_WriteByte(0xA0) ! 0) { I2C_Stop(); return 0; } if (I2C_WriteByte(addr) ! 0) { I2C_Stop(); return 0; } // 重复起始 I2C_Start(); if (I2C_WriteByte(0xA1) ! 0) { I2C_Stop(); return 0; } for (i 0; i len; i) { // 最后一字节发NACK之前的发ACK if (i len - 1) { buf[i] I2C_ReadByte(0); } else { buf[i] I2C_ReadByte(1); } } I2C_Stop(); return 1; }这个函数可以用来做系统重启后的参数恢复——把关键数据一次性读到内存中避免逐个地址去读产生的总线上多次开销。5. 调试实战示波器、逻辑分析仪与常见坑位盘点这部分分享一下我在实际开发中踩过的坑和排查方法。做I2C调试工具很关键。5.1 调试工具的选择与基本观察方法逻辑分析仪是I2C调试最实用的工具市面上的山寨逻辑分析仪用Saleae逻辑分析仪软件就能解码I2C协议价格几十到几百不等采样率24MHz以上的基本够用。使用要点是把通道0接SCL通道1接SDA设置好触发条件下降沿触发就可以看到完整的波形和解码后的数据。不买工具也不是不能调试一个简单粗暴的方法代码里在关键节点来回翻转一个测试GPIO用示波器或LED闪烁次数来判断程序走到哪一步。早期没有调试工具的时候我甚至用串口打印中间变量的方式排查效率比较低但思路值得借鉴——先确认是没走到这步还是走到了但这步结果不对。有了逻辑分析仪后怎么判断时序正确看这几处起始条件是否是SCL高期间的SDA下降沿。每字节是否8位1个应答位。应答位是不是在第9个时钟沿采样。停止条件后总线释放SDA和SCL都被上拉拉高。5.2 常见问题速查表故障现象可能原因排查/解决发送地址后永远NACK器件地址不对核对A0/A1/A2引脚电平与代码地址是否一致发送地址后永远NACK上拉电阻缺失或阻值过大检查原理图补上拉4.7kΩ发送地址后永远NACK从机正处于内部写周期等待5~10ms再发写数据后读到全0xFF写成功但读地址错误检查随机读的假写地址是否正确能读不能写WP引脚被拉高WP接地或悬空页写后部分数据丢失跨页写导致地址回卷按页边界拆分写入通信不稳定、偶发错误SCL速率过快增加延时降到100kHz通信不稳定、偶发错误电源纹波大或地线长加100nF退耦电容缩短飞线长度5.3 硬件I2C vs 软件模拟 I2C我的项目选择策略STM32F103的硬件I2C确实存在一些资料提到的兼容性问题——不是完全不能用而是需要掌握它的EV事件处理机制尤其要注意**总线忙标志BUSY**的处理。在硬件I2C模式下如果总线被异常拉低LIB线会卡在BUSY状态必须走总线释放流程强制切换GPIO模式产生停止条件才能恢复。软件模拟I2C的优势是时序完全可控、依赖只有一个延时函数、移植方便。缺点是占用CPU时间、无法同时挂多个从机且速率不可调但用SysTick做精确定时的话可以做到时序稳定。我的选择标准很简单学习/验证芯片驱动用软件模拟。先把逻辑搞通可以在线改时序。做产品原型用硬件I2C中断但加上总线异常恢复机制。做低功耗产品软件模拟更可控因为可以完全不依赖外设休眠前把GPIO配置成低功耗模式唤醒后重新初始化。5.4 一个反直觉的坑I2C总线被锁死调试中你可能遇到这种情况程序正常运行突然死机逻辑分析仪显示SDA线一直被拉低。这不是EEPROM的问题而是总线上某个设备把SDA拉住了。常见原因从机处于异常状态等待主机发送时钟信号才能恢复或者主机程序中途崩溃SDA停留在低电平状态没被释放。解决办法在初始化时加一个总线恢复例程——让SCL翻转9个时钟脉冲把SDA释放。这个操作在Linux的i2c子系统里有个专门的术语叫总线恢复bus recovery但在裸机开发中很多人的驱动里没写这个处理。我给自己的I2C中间层加了一个初始化前总线恢复的函数void I2C_BusRecovery(void) { uint8_t i; SDA_H(); SCL_H(); for (i 0; i 9; i) { SCL_L(); I2C_Delay_us(5); SCL_H(); I2C_Delay_us(5); } // 发一次停止条件让总线状态归零 SDA_L(); I2C_Delay_us(5); SCL_H(); I2C_Delay_us(5); SDA_H(); I2C_Delay_us(5); }这个函数在每次系统启动初始化I2C引脚后调用能够大幅降低因为硬件复位造成的总线锁死问题。6. 写在最后我在实际项目中用AT24C02做过系统参数存储、设备校准数据保存、运行日志掉电保护等功能总体来说这枚芯片用起来还是很皮实的只要注意地址、上拉、页写边界这三个核心点基本不会有什么大问题。如果你是想通过它练习I2C协议建议把软件模拟版本的时序自己从头到尾写一遍不要直接抄代码然后把逻辑分析仪的波形图和协议对比着看一遍收获绝对比你跑通一百遍示例代码来得多。一个值得扩展的方向是在Proteus里仿真验证整套逻辑再用真实硬件做交叉验证。仿真环境下的时序参数和真实芯片有差异但用来检查协议状态机的正确性是足够的。另外多说一句如果你调试中遇到的总线问题实在排查不出来先检查是不是杜邦线接触不良——我遇到过一个玄学问题后来发现是面包板上一根SDA线虚接手指轻轻一碰读数就变化这种问题最容易让人怀疑人生。有问题欢迎在评论区交流。
RELATED

相关推荐

PMOS管发热烫手?TP4056锂电池供电切换的五大隐藏原因与解决思路

PMOS管发热烫手?TP4056锂电池供电切换的五大隐藏原因与解决思路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/6 1:04:40
锁相环噪声优化全解析:从传递链到工程实践

锁相环噪声优化全解析:从传递链到工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/6 1:04:40
双目视觉三维人脸重建:从视差图到点云粗配准全流程解析

双目视觉三维人脸重建:从视差图到点云粗配准全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/6 1:04:40
MORE NEWS

更多资讯

📰

HTTP/2与HTTP/3核心机制对比及部署实战指南

HTTP/2 与 HTTP/3 的竞赛,本质上是互联网传输效率的极限追逐。我在实际项目里对比过这两代协议在弱网、移动端和服务端高并发场景下的表现,结论是:HTTP/2 靠“多路复用”解决了 HTTP/1.1 的连接排队问题,HTTP/3 则直接掀翻传输层桌…

📰

二叉树随机漫步:用蒙特卡洛思想探测树结构深度

刚看到一个很有意思的话题,把“二叉树”和“随机漫步”这两个词放在一起的时候,我第一反应是:这到底是在树上做随机游走,还是用二叉树去模拟一个随机过程?后来我仔细想了下,这个组合背后其实藏着一整类非常…

📰

中转API网关与Token机制实战:从JWT签发到限流计费全解析

做后端这些年,我越来越觉得,凡是和 API 打交道的项目,最后都绕不开两样东西:一层中转,一把 Token。上个月帮团队搭了一个内部的中转 API 网关,把好几家模型厂商的接口统一收敛到一个入口后面,用…

📰

轻量级Verilog仿真环境搭建:VSCode + iverilog + GTKWave 指南

你刚改完一行状态机的跳转条件,想在仿真里看下波形对不对,结果Quartus II光启动就要一分钟,编译整个工程又是好几分钟,好不容易调到ModelSim,还时不时弹出一个“failure to obtain a verilog simulation license”之类…

📰

大数运算课程设计:十进制与二进制高精度算法实现与避坑指南

简介:这份数据结构课程设计资源聚焦大数运算的完整实现,面向计算机专业学生及需要处理超长数值的开发者。项目覆盖大数加法、减法、乘法、除法、乘方与取模六类核心操作,并同时支持十进制与二进制大数运算,可应用于密码学、高性能…

📰

用Verilog设计MIPS32单周期CPU:从数据通路到指令执行的完整实战

做过FPGA或数字IC的人应该都有同感:学Verilog写了一堆计数器、状态机、UART收发、FIFO之后,总觉得差点意思,好像每个模块都会写,但脑子里始终没有一张完整的“计算机是怎么跑起来的”地图。直到你动手用Verilog搓出来一个单周期CP…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬