STM32F103内部Flash读写:免外部EEPROM,实现掉电数据保存

简介:面向STM32F103嵌入式开发者的内部Flash读写资料包,覆盖Flash存储器原理、读取操作、扇区擦除、写入时序与EEPROM模拟方法,帮助解决程序代码存储、配置数据保存及非易失性数据读写等实用问题。资源包共156个文件,以C源码和头文件为主体,辅以HEX固件、Keil工程文件、脚本及配置文件,压缩后仅544KB,便于快速导入工程学习与二次开发。已有4153人学习下载,适合嵌入式入门及进阶开发者掌握Flash操作要点。内容针对整扇区擦除、页擦除、字节/半字写入均给出例程,并通过串口指令封装实现远程控制Flash读写,方便调试验证;同时利用软件模拟EEPROM,在有限擦写寿命下完成可靠的掉电保存,整体代码结构清晰,可直接迁移到实际项目中。

1. STM32F103内部Flash读写:不挂外部EEPROM,掉电数据照样存

很多基于STM32F103的项目,参数想掉电保存,第一反应是外挂一颗EEPROM。但实际上F103的内部Flash读写并不复杂,把片内Flash当成存储介质完全可行,还能省掉一路I2C/SPI和一颗芯片。我在一个数据采集设备里就是这么干的:上电时从固定Flash地址读回校准系数,运行中修改参数,先整页擦除再按半字写回,全程没加任何外部存储。这套方案适合两类人:一类是产品数据量只有几个KB以内、想省成本省面积的开发者;另一类是正在做BootLoader、需要在启动跳转前后保存标志位和回滚版本号的。难点不在调用库函数,而在页划分、解锁时序、地址重叠这几件小事。把这三处理顺,内部Flash读写比想象中顺手得多。

2. 存储架构与工程准备:先搞懂F103的Flash分成几块再动手

2.1 主存储区、系统存储区与选项字节

F103的Flash不是一个能随意读写的平坦大数组,它物理上被分为几个独立区域,先搞清楚区域划分再动寄存器,能省掉后面大量排查时间。

第一块是主存储区,起始地址0x08000000,固件和参数都存在这里。主存储区按页组织,最小擦除单位就是页。中容量型号每页1KB,大容量型号每页2KB,以常见的64KB容量芯片为例,就是64页、每页1KB。做参数存储时,我习惯把最后一页或者最后两页划出来当参数区,代码区域不去碰,地址也方便算。

第二块是系统存储区,地址从0x1FFFF000附近开始,出厂固化了BootLoader程序,应用层基本不需要主动访问它。第三块是选项字节,在0x1FFFF800,用来配置读保护、写保护以及BOOT选项。选项字节属于高风险区域,配置错误可能导致调试器连不上芯片,所以项目初期不要乱动它。

Flash操作相关的寄存器集中在FLASH外设里,我平时主要用这几个:

寄存器作用常用位
FLASH->ACR等待周期与预取缓存配置LATENCY[2:0]、PRFTBE
FLASH->KEYR解锁Flash写保护写入两个密钥后才会解锁
FLASH->SR状态寄存器BSY忙标志、PGERR编程错误、WRPRTERR写保护错误
FLASH->CR控制寄存器PG编程、PER页擦除、STRT启动、LOCK锁定位

写Flash之前必须先解锁,解锁的本质就是向KEYR依次写入两个固定数值,让CR获得写权限。擦除和写入过程中要持续观察SR寄存器里的BSY位,一切操作都以BSY清零为前提。

2.2 工程搭建与固件库依赖

我采用标准外设库的方式开发,也可以用寄存器直接操作,两者的差别只在封装层,底层触达的硬件是一样的。工程里需要包含启动文件、时钟初始化、串口调试代码。

时钟初始化有一处容易被忽略:系统时钟跑到72MHz时,必须把FLASH->ACR的等待周期设置为两个周期,同时打开预取缓存。顺序搞反的话,CPU从Flash取指令会错位,表现出来就是程序随机跑飞、变量读出来的值是乱的。

void SystemClock_Init(void) { // 先把验收周期和预取缓存配好,再去切系统时钟 FLASH->ACR |= FLASH_ACR_LATENCY_2; // 72MHz主频对应两个等待周期 FLASH->ACR |= FLASH_ACR_PRFTBE; // 打开预取缓冲,减少CPU取指等待 // 然后做时钟树配置:内部8MHz经过PLL倍频到72MHz }

这段代码强调顺序:ACR配置必须出现在PLL启动之前,不能等倍频生效后才想起设置等待周期。这里失败时看到的往往不是明确报错,而是程序无规律跳变,最容易让人误判成硬件问题。

标准外设库里其实带了一组现成FLASH函数,但我不会直接拿它们当业务API。库函数粒度太细,擦完不校验、写坏不重试,不二次封装的话,上层代码会被寄存器细节淹没。

2.3 顶层接口设计:读写擦三个函数先定下

写代码前先定三个函数,上层只依赖这三个接口,不关心寄存器怎么操作:

uint8_t Flash_ErasePage(uint32_t page_addr); uint8_t Flash_WriteData(uint32_t addr, const uint16_t *buf, uint32_t len); uint8_t Flash_ReadData(uint32_t addr, uint16_t *buf, uint32_t len);

Flash_ErasePage负责把指定地址所在的页整页擦成0xFF;Flash_WriteData把len个16位数据写到addr起始的位置;Flash_ReadData从addr把数据读回。三个函数的语义合在一起,覆盖了参数存储的完整链路。

这里有个设计取舍:addr要求按16位对齐,len单位是半字数量。F103的Flash只能按半字编程,写32位整数时上层需要自己拆成两个16位数据,接口层不做隐式宽窄转换。这看起来增加了一点调用成本,却避免了隐含的数据错位问题,二次开发时排查起来会轻松很多。

2.4 参数区地址规划:给参数区一个固定落脚点

地址不能随手写死,要结合芯片容量和固件大小来定。先确认芯片容量,再确认固件实际占用,最后把参数区放在存储区末尾。

// 以256KB容量F103为例,页大小为2KB,最后一页从0x0803F800开始 #define PARAM_BASE_ADDR 0x0803F800 #define PARAM_PAGE_SIZE 2048 #define PARAM_WORD_COUNT (PARAM_PAGE_SIZE / 2) // 一页可容纳1024个半字

选择末页作为参数区的原因是:固件从0x08000000开始往后占,正常增长时不会突然追到末尾。如果把参数区放在代码区中间,每次升级固件体积变大,就可能撞进参数区,擦除时把正在运行的程序也擦掉了。

如果芯片只有64KB容量,末页起始地址就变成0x0800FC00,页大小也变成1KB。这块没有统一写法,最靠谱的是按具体型号用条件编译区分。我因为一次把256KB芯片的地址用在小容量芯片上,写参数时直接触发硬件错误,从那以后地址宏都会写明对应型号。

3. 核心代码实现:一次完整Flash读写背后的时序和状态检查

3.1 解锁流程:为什么Flash默认写保护

F103的Flash上电后处于锁定状态。这种锁不是说不能读,而是禁止擦除和编程。目的是防止程序里某个野指针误写,把固件区覆盖掉,导致设备变砖。

解锁步骤是依次向KEYR寄存器写入两个固定密钥:

void Flash_Unlock(void) { // 检查LOCK位,若CR的LOCK位为1则说明还没解锁 if (FLASH->CR & FLASH_CR_LOCK) { FLASH->KEYR = FLASH_KEY1; // 第一个密钥 0x45670123 FLASH->KEYR = FLASH_KEY2; // 第二个密钥 0xCDEF89AB } }

代码看起来简单,但有一个时序要求:两个密钥必须连续写入,中间不能插入其他对KEYR的写操作,否则解锁序列不合法,LOCK位不会清除。另一个细节是解锁针对整个Flash外设,没有只解锁某一页的概念。芯片虽然支持按页加写保护,但那是选项字节的活,一般产品用不到。

解锁状态在软件复位后会重新回到锁定,所以每次上电执行一次解锁是正常流程。我在调试时发现,如果把这段函数放到判断条件里,偶尔会出现写入失败,原因就是调试器复位后KEYR已清空,程序却以为解锁成功了。

3.2 页擦除:整页变0xFF的关键步骤

擦除是Flash操作里最需要耐心的环节。它一次只清除一页,清完之后整页内容全部变成0xFF。擦除时序分四步:置PER位、写入地址、置STRT位、等BSY清零。

uint8_t Flash_ErasePage(uint32_t page_addr) { // 等待上一次Flash操作彻底结束 while (FLASH->SR & FLASH_SR_BSY); // 置页擦除模式位 FLASH->CR |= FLASH_CR_PER; // 把目标页地址写到AR寄存器 FLASH->AR = page_addr; // 触发擦除启动 FLASH->CR |= FLASH_CR_STRT; // 等待擦除完成,典型耗时几十毫秒 while (FLASH->SR & FLASH_SR_BSY); // 退出页擦除模式 FLASH->CR &= ~FLASH_CR_PER; return 0; }

这段代码里的两个等待循环必不可少。F103擦除一页虽然只要几十毫秒,但在BSY置位期间,Flash外设对新的操作要求是拒绝的,后一个操作必须排队。

page_addr参数只要按半字对齐即可,硬件会自动把地址落到页边界。我调试时会额外加一段校验:擦除后把这个页从首地址开始读一遍,确认全是0xFF。如果读出来不是全1,说明擦除根本没执行成功,原因多半是解锁失败或者写保护打开,这时候再检查锁定位比盯住数据更有效。

3.3 半字写入:触发编程的是CPU那一次写操作

Flash写入的逻辑和RAM不一样。RAM是CPU往地址上写数据,数据立刻生效;Flash则是CPU写一次地址后,Flash控制器开始执行烧录流程,数据在随后若干微秒内逐步写进去。

uint8_t Flash_WriteHalfWord(uint32_t addr, uint16_t data) { // 等待上一次Flash操作结束 while (FLASH->SR & FLASH_SR_BSY); // 置编程模式位PG FLASH->CR |= FLASH_CR_PG; // 对目标地址做半字写入,这一步会触发硬件编程 *(volatile uint16_t *)addr = data; // 等待编程完成 while (FLASH->SR & FLASH_SR_BSY); // 退出编程模式 FLASH->CR &= ~FLASH_CR_PG; return 0; }

*(volatile uint16_t *)addr = data;这一句是核心。Flash控制器检测到CPU对Flash映射地址发起写操作后,自动进入烧录状态。所以必须先置PG位再写数据,顺序反了编程不会生效。

F103只支持半字编程,不支持按32位字编程,也不支持按字节编程。写32位整数时如果不拆成两个16位,直接用一个32位指针赋值,结果不是写入失败,而是触发硬件错误,程序当场跑飞。这个限制在F103系列上是硬性的,不要拿F4系列的字节编程习惯往F103上套。

3.4 读取:不需要解锁的直接访问

读取Flash比擦写简单得多,不需要解锁,也不需要等BSY,直接构造指针访问就行。CPU本身就在从Flash取指令,读Flash操作没有任何额外门槛。

uint8_t Flash_ReadData(uint32_t addr, uint16_t *buf, uint32_t len) { uint32_t i; // 本函数不校验地址合法性,上层需要保证在有效Flash范围内 for (i = 0; i < len; i++) { buf[i] = *(volatile uint16_t *)(addr + i * 2); } return 0; }

addr按半字偏移推进,每读一个半字地址前进2字节。buf是上层提供的缓冲区,长度由len决定。读取性能和RAM访问差不多,没有等待周期。

这里要提醒的是读写对称性:写入时按半字推进,读取也一定要按半字推进。如果写入用半字步进,读取却按字节一个一个拿,读出来的数据会整体错乱。很多乱码问题不是Flash坏了,就是读写两边步长不一致导致的。

3.5 完整封装:把擦读写串成一套带状态码的API

三个底层函数分开看都很简单,但真正工程里需要把它们串起来,还要加上错误处理。常见的失败包括擦除未执行、写保护生效、编程错误位被置起。

uint8_t Flash_WriteData(uint32_t addr, const uint16_t *buf, uint32_t len) { uint32_t i; // 写入前检查状态寄存器里的错误标志 if (FLASH->SR & FLASH_SR_WRPRTERR) { return 1; // 写保护错误,先检查解锁和选项字节 } if (FLASH->SR & FLASH_SR_PGERR) { return 2; // 编程错误,常见原因是写入前未擦除 } for (i = 0; i < len; i++) { Flash_WriteHalfWord(addr + i * 2, buf[i]); // 每个半字写完后做一次读回对比,发现不等立即停止 if (*(volatile uint16_t *)(addr + i * 2) != buf[i]) { return 3; // 校验失败 } } return 0; }

这套封装增加了两个价值:第一,每个半字写完后立刻读回比对,数据不对当场报错,而不是等业务层用的时候才发现;第二,错误码区分写保护错误、编程错误和校验失败,排查问题时能直接定位方向。生产环境中,闪存写失败不是零概率事件,加上这层检查才能尽早暴露问题。

4. 把工程跑起来:串口验证一次“擦写再读”闭环

4.1 测试场景:末尾页当参数区,先擦再写最后读

搭建最小验证工程时,我直接用末尾页做参数区,测完再擦掉,不影响固件正常运行。测试流程是:解锁、擦除、写入一组已知数据、读回、串口打印比对。

int main(void) { uint16_t wbuf[8] = {0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888}; uint16_t rbuf[8] = {0}; uint8_t i; SystemClock_Init(); UART_Init(); Flash_Unlock(); // 先整页擦除,让参数区回到全0xFF状态 Flash_ErasePage(PARAM_BASE_ADDR); // 再写入8个半字数据 Flash_WriteData(PARAM_BASE_ADDR, wbuf, 8); // 最后读回来做比对 Flash_ReadData(PARAM_BASE_ADDR, rbuf, 8); for (i = 0; i < 8; i++) { UART_SendHex(rbuf[i]); // 用串口打印读回值 UART_SendByte(' '); } while (1) ; }

这段测试代码的顺序不能乱。如果不先擦除就直接写,Flash里原本可能是旧的随机数据,新数据写入时只能把某些位从1变成0,不能把0变成1,读回来的数据会变成旧值和新值混在一起。只要看到这种怪异组合,第一反应就应该检查是不是漏了擦除。

4.2 串口输出与结果判断

串口打印函数这里不展开实现,只要能把uint16_t按十六进制输出就行。预期读回来的是0x1111到0x8888八个数,发送顺序和写入顺序一致。

如果串口打印出八个0xFFFF,说明整页没有被正确擦除,或者擦除根本没执行。这时优先检查两处:FLASH->CR的LOCK位是否为0,以及FLASH->SR里是否有写保护错误位。0xFFFF有一个特殊情况:如果参数区地址越界,指向了不存在Flash空间,读回来的内容通常也是0xFFFF或0x0000,这类问题在后面避坑章节细说。

我一直习惯先跑这个小闭环再往项目里集成,速度快且能确认底层三个函数可用。确认之后再加入具体业务逻辑,比如“开机读配置、运行中改配置、掉电前保存”,排查范围会缩小很多。

4.3 验证并测试重复写入:擦写次数要心里有数

测试时顺手验证一下重复写。F103内部Flash的擦写寿命一般在1万次量级,做参数频繁保存的场景要评估一下写入频率。比如每分钟写一次参数,一天1440次,一周就超过1万次,这种频率必须考虑磨损均衡或换外部EEPROM。

// 连续擦写循环测试,观察是否出现写入失败 while (1) { Flash_ErasePage(PARAM_BASE_ADDR); Flash_WriteData(PARAM_BASE_ADDR, wbuf, 8); Flash_ReadData(PARAM_BASE_ADDR, rbuf, 8); // 一旦校验失败,通过串口打印出错地址 for (i = 0; i < 8; i++) { if (rbuf[i] != wbuf[i]) { UART_SendHex(i); // 打印哪个半字出错 } } Delay_ms(50); }

4.4 一个容易翻车的测试陷阱:擦除覆盖了正在跑的代码

跑测试时不要顺手把参数区地址改成代码区附近。比如有人在0x08005000测试,擦除后程序立即崩溃,因为那个地址所在的页可能正承载着正在执行的函数。

如果非要在非末尾页测试,至少保证待擦除页里没有任何代码。链接脚本生成的.map文件里能看到每个函数的地址,先用它确认目标页不在代码段内。我的习惯是永远保留末页做测试,这不用查map文件,默认就是安全的。

5. Flash读写避坑:解锁失败、全0xFF、跑飞都在这排查

5.1 现象:写入后读回全是0xFF

这是最常见的故障。代码看着没有遗漏,擦除也执行了,但读回来整片0xFFFF。原因通常是两条:一是擦除根本没有成功,Flash里本来就是旧数据且未被擦掉;二是地址指向了不存在的Flash区域,读了个寂寞。

排查时先读FLASH->SR寄存器,看WRPRTERR写保护错误位有没有被置起。如果置起了,说明当前Flash还处在锁定状态,需要重新解锁。还要确认地址是否合法,用参数区末页地址减去起始地址,算一下是不是超出容量范围。

解决的办法是对擦除函数增加返回值检查,同时对SR寄存器做一次状态打印。很多玄学问题其实都是SR寄存器里躺着一个错误位,却被业务代码忽略了。

5.2 现象:写32位整数后程序直接跑飞

直接定义一个uint32_t value然后用*(uint32_t *)addr = value写入,程序大概率会跑飞到HardFault。这是F103 Flash只支持半字编程的硬限制,32位和8位写入都会触发硬件错误。

原因是Flash编程接口一次只接收16位数据,控制器以半字为单位进行电荷注入,宽度不同的写操作没有对应的时序支持。解决方法是把32位数据拆成两个16位,先写低16位,再写高16位。

void Flash_WriteU32(uint32_t addr, uint32_t value) { uint16_t lo = (uint16_t)(value & 0xFFFF); uint16_t hi = (uint16_t)(value >> 16); Flash_WriteHalfWord(addr, lo); Flash_WriteHalfWord(addr + 2, hi); }

与此对称,32位整数的读取也要两个半字拼起来,不然高低字节顺序很容易搞混。我在这上面浪费过不少时间,后来就把32位读写做成固定函数,不再让业务层自己拆。

5.3 现象:擦除后程序跑飞,复位也不稳定

擦除的页里包含当前正在执行的代码,这是最危险的错误。擦除过程中CPU还要从这一页取指令,取着取着地址空间内容全部变0xFF,CPU取到无效指令,程序跳进HardFault甚至直接跑飞。

原因是程序代码和参数区重叠了。比如把参数区设在0x08008000附近,而固件已经占到了0x08009000,一擦就把代码擦掉了。解决方法是把参数区固定在存储区末尾,确保任何固件版本增长都不会追到那一页。

排查技巧是看崩溃地址。连接调试器后停在HardFault,查看栈里的PC值,如果PC落在参数区范围,基本就是页重叠。

5.4 现象:擦写过程中串口中断打断,导致数据错乱

Flash擦写期间如果来了串口中断,中断服务程序要读取代码区,这本身不影响Flash烧录,但F103在Flash忙时,CPU访问Flash取指会进入等待。等待期间中断现场的保存和恢复容易出问题,极端情况下会卡死。

原因是Flash外设忙时,访问Flash地址会插入等待周期,中断嵌套越深,问题越明显。解决方法是擦写期间关闭中断,或者用临界区保护。标准外设库里也提供了类似处理,我通常在Flash_ErasePage开头加一段临界区保护:

__asm volatile ("cpsid i"); // 关闭全局中断 // Flash擦写代码... __asm volatile ("cpsie i"); // 恢复中断

如果用的是带操作系统环境,还需要在擦写前挂起调度器。因为擦写周期几十毫秒,这个时间对实时任务来说不算短,应该尽量避免在此阶段切走任务。

5.5 现象:明明是128KB芯片,却把参数区写到了128KB之外

这种错误在代码上不会立即暴露,因为地址越界后读写可能返回错误,也可能只是读到无效数据。原因是对芯片容量估算错误,比如把128KB当成256KB用,参数区落在了不存在的内存地址上。

排查方法是先查芯片型号对应的Flash容量,再看参数区末地址有没有跨过边界。128KB芯片的Flash地址范围是从0x08000000到0x0801FFFF,末端最后一项的数据,末页起始地址应该小于或等于0x0801F800。这里算数是减法,不是加法,容量边界减1才是最后一个有效地址。

我在项目里用条件编译把地址宏和芯片型号绑定,这样换芯片时编译器会直接报错提示,不会让错误悄悄带到现场。

6. 二次开发改造:用Flash模拟EEPROM做参数掉电保存

6.1 改造思路:半页写满再擦,降低擦写频率

直接按“擦一页写一份数据”的用法,参数区寿命会很短。一个简单有效的改造是循环槽位:把一页分成多个槽,第一次数据写到槽0,第二次写到槽1,槽写满后才擦除整页。这样擦除次数被分散开。

#define SLOT_COUNT 8 #define SLOT_SIZE (PARAM_PAGE_SIZE / SLOT_COUNT) uint8_t slot_write_index = 0; // 记录当前写入第几个槽 void Param_Save(uint16_t *data) { uint32_t slot_addr = PARAM_BASE_ADDR + slot_write_index * SLOT_SIZE; Flash_WriteData(slot_addr, data, 8); slot_write_index++; if (slot_write_index >= SLOT_COUNT) { // 所有槽都用完了,擦掉整页重新从槽0开始 Flash_ErasePage(PARAM_BASE_ADDR); slot_write_index = 0; } }

6.2 加一层调度策略

这样改造后写参数和擦除的比例从1比1变成8比1,同样1万次擦写寿命下,实际可写次数接近8万次。对于数据保存频率较高的场景,还可以把槽数量再加大。

6.3 验证方法与调试习惯

改造完的验证方法很简单:连续写几百次参数,断电再上电,看参数是否能正确恢复。关键是每次断电前把当前写入槽号存到参数区头两个字节,上电时先读槽号再定位最新数据。

我从最初做这个模块时踩过整页擦除的坑,到现在每次调试前都会先打开串口打印Flash状态,确认解锁、BSY、错误标志都正常再继续。从那以后凡是改动Flash相关代码,我都会强制走一遍“擦写读打印”的闭环,确认底层三个函数没有回归问题。希望这次讲清楚的东西,也能让你少踩几个我当年踩过的坑。

本文还有配套的精品资源,点击获取

相关新闻

杰理AC696N开发板Windows开发环境搭建完整指南

杰理AC696N开发板Windows开发环境搭建完整指南

很多人拿到一块新开发板&#xff0c;第一反应都是先插USB、找资料、装IDE。我拿到AC6966B开发板的时候也差不多&#xff0c;但实际走了一遍才发现&#xff0c;这套AC696N开发环境并不是一个安装包能搞定的&#xff0c;它由固件工程、编译工具链、下载工具和调试串口四部分拼装而…

2026/10/21 7:38:02 阅读更多 →
私域流量自动化运营系统架构与实操:事件驱动规则引擎

私域流量自动化运营系统架构与实操:事件驱动规则引擎

1. 私域自动化运营的底层逻辑与方案选型1.1 为什么私域运营必须走自动化路线做私域的人都有一个共同的痛&#xff1a;手动运营的天花板来得特别快。一个人管三五个群还能应付&#xff0c;一旦群数量上到二十个、三十个&#xff0c;每天光是欢迎新成员、发公告、回答重复问题就能…

2026/10/22 12:29:56 阅读更多 →
MySQL生产环境压测实战:用sysbench提前暴露上线隐患

MySQL生产环境压测实战:用sysbench提前暴露上线隐患

接手过几套生产 MySQL 之后&#xff0c;我越发觉得&#xff1a;sysbench 压测这件事&#xff0c;不是跑个命令把 TPS 截图发群里就算干完了。它最大的价值&#xff0c;是在新库上线前、或者一次大的参数调整之后&#xff0c;用一套标准化的负载提前暴露那些“平时看着没事、一上…

2026/10/22 14:02:00 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年&#xff0c;我大部分时间都在跟浏览器标签页打交道&#xff0c;直到第一次把桌面应用跑起来&#xff0c;才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词&#xff0c;最开始是在某段时间频繁出现在同事的讨论里&#xff0c;一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档&#xff0c;主要面向智能锁安装人员、终端用户及售后维护者&#xff0c;帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件&#xff0c;大小4.37MB&#xff0c;内容结构清晰&#xff…

2026/10/22 11:01:53 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统&#xff0c;整车要求空载跑到8m/s&#xff0c;电机转速已经拉到额定点&#xff0c;驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令&#xff0c;转速纹丝不动&#xff0c;三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年&#xff0c;用它写过嵌入式驱动、做过游戏服务端&#xff0c;也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词&#xff0c;我其实挺感慨的——这门语言的难点从来不是语法多&#xff0c;而是知识点之间的耦合太深…

2026/10/22 10:42:00 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书以PDF格式发布&#xff0c;适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开&#xff0c;先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能&#xff0c;再给出完整安装步骤&…

2026/10/22 10:40:08 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历&#xff1a;翻开源码注释、论文摘要或者一份专业报告&#xff0c;每个字都认识&#xff0c;连在一起却像天书。想找人聊聊&#xff0c;身边要么没人懂&#xff0c;要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型&#xff0c;几秒钟之后&a…

2026/10/22 10:56:37 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/21 21:32:28 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/21 21:50:56 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/21 21:51:55 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/21 16:24:48 阅读更多 →