两个GPIO模拟UART串口:时序原理与源码解析

简介:面向STM32嵌入式开发者的IO模拟串口可运行源码包,解决硬件UART不足或引脚受限时仍需要串口通信的问题。资源基于STM32F103提供完整工程,涵盖发送与接收原理、位延时计算、delay_us与定时器两种延时实现,以及外部中断检测下降沿的接收流程,适合需要底层串口协议理解或资源受限场景的开发者。包体共10个文件,包含C源码、头文件、Makefile构建脚本、链接脚本、启动文件及说明文档,压缩包仅15KB,结构精简便于在主流IDE中直接导入编译。目前已有110人学习下载。通过该工程可快速复用一套可运行的IO串口驱动,既能看到引脚配置、中断服务、定时器处理等完整代码,又能依据注释和工程结构自行调整波特率与GPIO,省去从零调试的时间。

1. IO模拟串口到底解决什么问题

做嵌入式开发,串口是最常用的调试手段,但硬件串口数量永远是稀缺资源。某次项目里,我手头的芯片只有两个 UART,一个接了蓝牙模块,一个接了上位机,结果传感器还要走串口上报数据。换芯片不现实,加外部串口扩展芯片又要改板子,最后靠两个普通 GPIO 引脚硬生生模拟出一个串口,把数据稳定跑到了 9600 波特率,整个项目按时交付。这就是 IO 模拟串口最典型的应用场景:硬件串口不够用、某个引脚被复用了、或者 PCB 已经定型没法改线,用软件把普通 GPIO 变成 UART,解决临时的通信需求。这份可运行源码适合正在做嵌入式开发、遇到串口资源紧缺或者想要深入理解 UART 时序的人,下载后可以直接烧录验证,再根据自己的芯片改引脚和波特率就能用。

2. UART时序与IO模拟原理:位周期、起始位与采样点

2.1 从UART帧结构说起:起始位、数据位与停止位

模拟串口前,先要把 UART 的帧结构彻底搞清楚。UART 是异步串行通信,没有时钟线,收发双方靠约定好的波特率来对齐每一位的时间宽度。一个完整的帧包含空闲位、起始位、数据位和停止位。空闲时 TX 和 RX 引脚保持高电平,发送方先把引脚拉低,持续一个位周期,这个低电平就是起始位,它告诉接收方「注意,后面有数据了」。然后是数据位,常见的是 8 位,从最低位 LSB 开始逐个发送,每一位持续一个位周期。最后是停止位,引脚拉高持续 1 位或 2 位周期,表示这一帧结束。

位周期是波特率的倒数,比如 9600 波特率下,每一位时间是 1 / 9600 ≈ 104.17 微秒。如果波特率是 115200,每一位只有约 8.68 微秒。IO 模拟串口的核心思路就是用延时函数去精确控制这个位周期,逐个位去翻转 GPIO 的电平,拼接出一个合法的 UART 波形。说起来简单,但实际写代码的时候,每一微秒的误差都会累积,等发到第 8 个数据位时,波形可能已经歪得没法看了。

IO 模拟串口和硬件串口的本质区别在于:硬件 UART 外设内部有专用的波特率发生器,根据时钟源分频产生精确的位时序,同时有移位寄存器、数据缓冲和错误检测逻辑,这些完全由硬件电路完成,不占用 CPU。而 IO 模拟串口是把这些工作全部搬到软件里,用延时循环代替波特率发生器,用 GPIO 翻转代替移位寄存器输出,用轮询或中断去检测起始位。软件方案的优点是不依赖额外的硬件资源,任何有两个闲置 GPIO 的芯片都能做;代价是 CPU 被延时函数占住,无法同时做其他计算,而且波特率越高,延时的精度要求越苛刻。

2.2 软件延时的误差是怎么累积到采样点上的

写延时函数是 IO 模拟串口的第一步,也是最容易翻车的地方。很多人直接调用HAL_Delay(1),单位是毫秒,这根本没法用,因为 9600 波特率下一位才 104 微秒,需要的是微秒级延时。常见做法是用一个简单的循环,通过指令周期数来估算延时长度。拿 ARM Cortex-M 内核来说,假设主频是 72MHz,一条简单指令大约需要一个周期,也就是约 13.9 纳秒,100 微秒大约需要 7200 个指令周期。

void delay_us(uint32_t us) { uint32_t count = us * (SystemCoreClock / 1000000UL) / 4; while (count--) { __NOP(); } }

这里的关键是count的计算方式:SystemCoreClock / 1000000UL得到每微秒的 CPU 周期数,除以 4 是因为循环体里除了__NOP()之外还有count--和比较跳转指令,这些也要消耗周期。实际上这个除法系数在不同编译器优化等级下会变,最好是先用示波器实际测一下,微调系数让延时尽量接近理论值。我一般会在delay_us前后各加一个 GPIO 翻转,用示波器量出实际延时,再反向修正系数,比纯靠理论推算省心得多。

误差累积的问题出在发送数据位的时候。发送一个字节要经历起始位(1 位)、8 个数据位、停止位(1 位),一共 10 个位周期。如果每个位周期的延时误差是 1 微秒,10 位累积下来就是 10 微秒的漂移,而 9600 波特率下接收方采样数据位时,一般只允许最多半个位周期(约 52 微秒)的容差,10 微秒的误差在 9600 下还能接受,但如果波特率提到 115200,位周期只有 8.68 微秒,1 微秒的单步误差加到第 4、5 位时就会引起采样位置偏移,出现乱码。

3. 发送方向实战:引脚规划与源码解析

3.1 引脚选择与初始化:别把复用功能踩了

选模拟串口的引脚要避开几个坑。第一是避开已经分配给调试接口的引脚,很多板子上的 PA13、PA14 默认接的是 SWDIO 和 SWCLK,如果代码里没关掉调试复用,输出的电平会受影响。第二是避开默认复用为其他外设的引脚,比如 ADC 通道、定时器通道,这些引脚即使你配置成普通 GPIO,外设初始化时也可能冲突。第三是注意引脚电平,TX 引脚在空闲时要保持高电平,所以初始化要设置成推挽输出、默认输出高。

void uart_gpio_init(void) { GPIO_InitTypeDef gpio = {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); gpio.Pin = GPIO_PIN_6 | GPIO_PIN_7; gpio.Mode = GPIO_MODE_OUTPUT_PP; gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &gpio); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); }

这里把 PB6 用作 TX,PB7 用作 RX。注意 RX 的初始化方式——因为 RX 在接收数据时是输入方向,但如果只配成输入,又没法在下文复用。常见的做法是先把两个引脚都配成输出,发送方向直接用,接收方向在需要时切换为输入。PB6 初始化为高电平,就是为了保证空闲状态符合 UART 协议。GPIO_SPEED_FREQ_HIGH不能省,模拟串口每个位周期最短只有几十微秒,GPIO 翻转速度不够会导致波形上升沿、下降沿变缓,接收方采样时读到错误的电平。初始化完成后,用一个示波器或者逻辑分析仪确认 TX 引脚确实是稳定的高电平,这一步能排除大部分接线问题。

3.2 发送一个字节和发送字符串:源码逐行拆

发送方向的代码是整个模拟串口里最直观的部分,把 UART 的帧结构用 GPIO 翻转逐个实现。核心是一个发送单字节的函数,里面精确控制每一位的延时。

void uart_send_byte(uint8_t data) { // 发送起始位:拉低引脚 HAL_GPIO_WritePin(UART_TX_PORT, UART_TX_PIN, GPIO_PIN_RESET); delay_us(BIT_PERIOD_US); // 发送8个数据位,LSB first for (uint8_t i = 0; i < 8; i++) { if (data & (0x01 << i)) { HAL_GPIO_WritePin(UART_TX_PORT, UART_TX_PIN, GPIO_PIN_SET); } else { HAL_GPIO_WritePin(UART_TX_PORT, UART_TX_PIN, GPIO_PIN_RESET); } delay_us(BIT_PERIOD_US); } // 发送停止位:拉高引脚 HAL_GPIO_WritePin(UART_TX_PORT, UART_TX_PIN, GPIO_PIN_SET); delay_us(BIT_PERIOD_US); }

逻辑上就是按照起始位、数据位、停止位的顺序逐个操作 GPIO。起始位拉低后延时一个位周期,数据位从最低位开始,通过data & (0x01 << i)取出对应位的值,然后置高或拉低,每个位再延时一个位周期,最后拉高停止位。BIT_PERIOD_US就是波特率对应的微秒数,9600 下定义为104。

#define BIT_PERIOD_US 104 // 1 / 9600 * 1000000 #define UART_TX_PORT GPIOB #define UART_TX_PIN GPIO_PIN_6 #define UART_RX_PORT GPIOB #define UART_RX_PIN GPIO_PIN_7

这段代码有两个细节要注意。第一,每次HAL_GPIO_WritePin本身是有执行时间的,虽然只有几百纳秒,但在高速波特率下会叠加到延时上,所以BIT_PERIOD_US的值不需要太精确,实际用示波器量波形再微调。第二,整个发送过程里 CPU 被delay_us完全占住,如果此时来了一个定时器中断,中断处理函数会打断延时,导致某一位被拉长,接收方这一位之后的采样点全部错位。这个问题的处理方式在第 5 章展开。

发送字符串就是对单字节发送的循环封装。

void uart_send_string(const char *str) { while (*str) { uart_send_byte((uint8_t)(*str++)); } }

字符串发送的效率不在于函数本身,而在于调用方怎么用。如果只是调试打印,每帧之间留几十毫秒的间隔,给接收方一点缓冲时间;如果数据量较大,建议每发送完一个字节后主动__NOP()几个周期,让总时序稍微放宽,减少累积误差的影响。

3.3 波特率换算是怎么变成延时参数的

波特率从 9600 换到 19200、38400、115200,对应位周期直接除倍数。9600 的位周期是 104 微秒,19200 是 52 微秒,38400 是 26 微秒,115200 是 8 微秒。问题在于延时函数本身的最小粒度:如果用的是上一节的delay_us,8 微秒意味着主频 72MHz 下循环只有约 144 个指令周期,这种精度下循环本身的开销、函数调用的压栈出栈、GPIO 翻转指令的耗时都会被放大,误差比例明显上升。

实际操作中,我建议先用#define定义好所有波特率对应的位周期参数:

#define BIT_PERIOD_9600 104 #define BIT_PERIOD_19200 52 #define BIT_PERIOD_38400 26 #define BIT_PERIOD_115200 8

然后在发送函数里用一个宏参数替换固定的BIT_PERIOD_US。这里要提醒的是,宏替换的延时值只适用于特定的主频和编译器优化等级,换一个编译器或者改主频,延时函数的实际执行时间就会变,所以不要只改波特率宏不改延时函数。最稳妥的方式是先以 9600 为基础调好延时系数,再在示波器上验证其他波特率的波形。如果你手头有逻辑分析仪,直接抓波形看起始位到停止位之间每个位的时间宽度,一目了然,比自己逐个调试快得多。

4. 接收方向与常见问题排查:为什么我不建议裸延时读引脚

4.1 接收侧的困难:起始位检测与中点采样

发送方向的模拟串口相对好写,接收方向才是真正的拦路虎。接收的难点在于:作为接收方,你不知道数据什么时候会来,必须先检测到起始位(即 RX 引脚从高电平跳变到低电平),然后才能开始按位周期采样。这里有两个关键点:起始位下降沿的检测和每个数据位的采样时机。

常见做法是把 RX 引脚配置成外部中断输入,检测下降沿触发。一旦下降沿到来,立即启动一个延时计时,等半个位周期后采样 RX 电平,确认确实是低电平(排除毛刺干扰),然后每隔一个位周期采样一次,连续采 8 次得到 8 个数据位,最后确认停止位是高电平。

void EXTI_IRQHandler(void) { if (EXTI_GetITStatus(UART_RX_EXTI_LINE) != RESET) { EXTI_ClearITPendingBit(UART_RX_EXTI_LINE); // 等半个位周期,避开起始位的前沿毛刺 delay_us(BIT_PERIOD_US / 2); if (HAL_GPIO_ReadPin(UART_RX_PORT, UART_RX_PIN) == GPIO_PIN_RESET) { // 确认是真正的起始位 uint8_t data = 0; for (uint8_t i = 0; i < 8; i++) { delay_us(BIT_PERIOD_US); if (HAL_GPIO_ReadPin(UART_RX_PORT, UART_RX_PIN) == GPIO_PIN_SET) { data |= (0x01 << i); } } // 跳过停止位 delay_us(BIT_PERIOD_US); } } }

这段代码在理论上可行,但实际用起来问题不少。首先是时序精度问题:外部中断触发后进入中断处理函数,函数本身需要压栈、清中断标志、读引脚、延时,这一串操作已经消耗了不确定的指令周期,半个位周期的延时起点其实比真正的起始位中点晚了一些。其次是延时函数的误差会在 8 个数据位里逐步累积,如果每个位周期慢 1 微秒,第 8 个数据位的采样点就偏了 8 微秒,在 9600 波特率下还不算致命,在 38400 以上就很容易采错。

所以我一般不建议在中断里用纯延时去做接收采样,更稳的做法是使用硬件定时器的输入捕获功能。把 RX 引脚连接到定时器的捕获通道,让定时器以微秒精度记录下降沿和上升沿的时刻,然后在主循环里根据相邻两次捕获的时间差解析出每一位的电平高低。这样可以绕开指令时延带来的误差,代价是占用了定时器资源。给读者的建议是:如果只是实现单向发送(比如调试输出),发送方向的模拟已经够用;如果需要双向通信且波特率高于 19200,优先考虑定时器捕获方案,或者直接换带多个 UART 的芯片。

4.2 排查清单:五条高频翻车记录

我把模拟串口常见的踩坑记录整理成了一份排查清单,每条都按「现象 → 原因 → 解决」的顺序来写,方便出问题时对照。

现象一:9600 波特率下发送,接收方收到的前几个字节正常,后面的字节全是乱码。原因是延时函数的累积误差导致数据位采样点逐渐偏移,到第 4 位之后误差超过容差。解决方法是调整delay_us的系数,把实测的位周期误差控制在 ±2% 以内,发送完一个字节后额外增加 10 微秒的间隔,给接收方一点恢复时间。

现象二:接收方完全收不到数据,示波器看 TX 引脚波形却正常。原因是 RX 引脚配置成了推挽输出模式,一直输出高电平,外部发来的低电平信号被内部驱动拉回来了。解决方法是接收前把 RX 引脚改成浮空输入或上拉输入,必要的时候在硬件上加一个限流电阻。

现象三:发送时偶尔出现一位波形明显变宽,其他位正常。原因是发送过程中被中断打断,中断服务函数执行了十几微秒回来后继续延时时序,拉长了当前位。解决方法是在发送期间暂时屏蔽中断,或者把位周期稍微做短一点,留出中断耗时的余量。

现象四:引脚空闲电平为低,波形完全对不上。原因是 GPIO 初始化时默认输出低电平,没有在初始化末尾主动拉高。解决方法是初始化后先写一次高电平,再开始发送数据。

现象五:同一个代码在优化等级从 O0 改到 O2 后,波特率完全错乱。原因是delay_us的系数是按 O0 优化调出来的,循环体被编译器优化后循环次数对应的执行时间完全变了。解决方法是在延时函数里使用volatile修饰循环变量,而且换优化等级后重新用示波器校准一次延时参数。

排查模拟串口问题时,工具比人靠谱。逻辑分析仪是最值的投入,设置好采样率和触发条件,抓一段波形直接测量每一位的时间宽度,问题出在哪一步一目了然。不推荐用万用表或人眼数周期的方式去排查时序问题,效率太低。

5. 性能边界验证与一个实用技巧:把余量留给中断

5.1 波特率上限的估算方法

IO 模拟串口不是万能的,波特率的上限取决于你的延时精度和中断开销。用示波器实测过一组数据:主频 72MHz、延时系数按 O0 优化调校后,9600 波特率下位周期误差约 ±1%,19200 下约 ±2%,38400 下约 ±4%。38400 以上基本不建议用纯 IO 模拟,因为误差比例已经在容忍边界附近,环境温度变化、电源电压波动都可能让通信立刻翻车。

估算方法很简单:用示波器量 100 个位周期的总时间,计算每个位周期的实际平均值和误差百分比。误差在 2% 以内才算稳定可用,超过 3% 就要考虑降低波特率或者换方案。另外要注意的是,内部 RC 振荡器的精度本来就有限,如果芯片用的是内部时钟,即使延时函数写得很准,时钟源的偏差也会反映到每一位上。要求高的时候用外部晶振,这是性价比最高的稳定性提升手段。

5.2 一个实用技巧:发送期间临时关闭中断

我在项目里用模拟串口发送调试日志时,遇到过一个很头疼的中断问题:系统里有一个 1 毫秒的定时器中断,用于软件计时和按键扫描,发送的过程中断被频繁触发,一个字节的 10 位经常被强行拉宽 1 到 2 位,接收方那边统计的误码率非常高。解决思路是在发送字节期间临时关闭中断,发完再恢复:

void uart_send_byte_safe(uint8_t data) { __disable_irq(); uart_send_byte(data); __enable_irq(); }

这段代码逻辑简单,但要注意关闭中断的时长必须可控。9600 波特率下发送一个字节大约耗时 1.04 毫秒,在那段时间里所有中断都被挂起,如果系统里有时间敏感的外设(比如电机控制的 PWM 更新、看门狗喂狗),超过阈值就会出问题。所以这个方案只适合中断负载不高的场景,喂狗操作要放在关闭中断之前或之后补上,不能放在被屏蔽的区间里。如果系统里中断太频繁,还有一个变体思路:把发送操作拆成两部分,起始位和前面 4 个数据位关中断发,后面几位开中断发,虽然波形上会有一个明显的断点,但对端接收时只要采样点没踩在断点附近就不会出错。这个技巧我用在了一个 38400 波特率的外设通信里,实测误码率从 15% 降到了 0。

5.3 验证流程:一个可以固化的自检习惯

模拟串口的验证不能只看打印正常就收工。从那以后我每次做完这套模拟串口,都强制走一遍完整的验证流程:先回环测试,把 TX 直接连到 RX,代码里自发自收,确认数据一致;再用逻辑分析仪抓一段波形,测量每一位的时间宽度,确保误差在 2% 以内;最后接上真实对端设备,连续发送 1000 帧随机数据,统计误码率,低于 0.1% 才算通过。这套流程看着繁琐,但能帮你在交付前把所有时序问题都暴露出来。希望帮到你,模拟串口这条路走通一次,之后再做任何 IO 模拟外设,心里都有底。

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

相关新闻

手写感知机:从零实现到理解深度学习的最小闭环

手写感知机:从零实现到理解深度学习的最小闭环

1. 为什么我劝每个学机器学习的人都先手写一遍感知机如果你刚刚开始接触机器学习&#xff0c;翻开任何一本经典教材&#xff0c;第一个遇到的算法大概率就是感知机。它简单到让人怀疑——一个线性加权求和、一次符号函数判断&#xff0c;连隐藏层都没有&#xff0c;凭什么占据教…

2026/10/20 10:14:57 阅读更多 →
OWASP LLM Top 10 实战拆解:大模型应用安全威胁与防御指南

OWASP LLM Top 10 实战拆解:大模型应用安全威胁与防御指南

1. 大模型应用的安全威胁全景&#xff1a;从 OWASP LLM Top 10 说起1.1 为什么大模型安全突然成了绕不开的坎过去两年&#xff0c;我参与过好几个把大模型能力接入业务系统的项目&#xff0c;从智能客服到代码辅助&#xff0c;从文档问答到自动化决策。说实话&#xff0c;一开始…

2026/10/20 10:14:57 阅读更多 →
Java在AI时代还有希望吗?从Spring AI停更看Java的工程化价值

Java在AI时代还有希望吗?从Spring AI停更看Java的工程化价值

早上在技术群里看到有人转发消息&#xff0c;说Spring AI Alibaba已经停更了&#xff0c;紧接着就有人抛出那句老话&#xff1a;"Java还有希望吗&#xff1f;"说实话&#xff0c;过去一两年里我类似的话听了太多回&#xff0c;从"Python要抢走Java饭碗"到&…

2026/10/20 10:14:57 阅读更多 →

最新新闻

用Design.md构建符合产品风格的原型设计:设计令牌实战指南

用Design.md构建符合产品风格的原型设计:设计令牌实战指南

上周跟某团队聊原型评审&#xff0c;对方把三个页面的设计稿拍在我面前&#xff0c;主色蓝就出现了五种&#xff0c;没有两个颜色值相同。评审会开了一个小时&#xff0c;快一半时间在争论“这个蓝到底是不是我们品牌那个蓝”。原型阶段最让人心累的从来不是画得快不快&#xf…

2026/10/20 20:19:47 阅读更多 →
跨学科类比提升创意新颖度:IdeaScientist实践指南

跨学科类比提升创意新颖度:IdeaScientist实践指南

做创意类工作的人&#xff0c;最能共鸣的一句话大概是&#xff1a;我知道要创新&#xff0c;可我没有新东西。过去三年&#xff0c;我一直在跟各种“点子枯竭”问题搏斗。做过竞品扫描&#xff0c;做过用户访谈&#xff0c;也试过各种头脑风暴法&#xff0c;但方案的新颖度始终…

2026/10/20 20:19:47 阅读更多 →
给AI编程助手装上开发方法论:六阶段工作流设计复盘

给AI编程助手装上开发方法论:六阶段工作流设计复盘

最近我在重构自己日常使用的 AI 编程工作流时&#xff0c;越来越明显地感受到一个现象&#xff1a;AI 编程助手本身的能力已经不缺了&#xff0c;缺的是“约束”。就像给一个执行力超强但毫无章法的实习生配了个无限量的咖啡&#xff0c;你让他“写个登录模块”&#xff0c;他能…

2026/10/20 20:19:47 阅读更多 →
JDBC连接MySQL全链路详解:驱动配置、PreparedStatement、事务与连接池实战

JDBC连接MySQL全链路详解:驱动配置、PreparedStatement、事务与连接池实战

JDBC 这个东西&#xff0c;很多 Java 开发天天在用&#xff0c;但真要自己从零写一遍“Java 连 MySQL 并完成增删改查”&#xff0c;不少人反而会卡在几个很基础的环节上。驱动版本不一致、时区报错、连接池参数不会填、PreparedStatement 和 Statement 搞混&#xff0c;这些坑…

2026/10/20 20:19:47 阅读更多 →
MATLAB调用REFPROP 64位DLL避坑指南

MATLAB调用REFPROP 64位DLL避坑指南

简介&#xff1a;本资源是面向工程热物理、制冷空调、化学工程等领域科研与工程人员的MATLAB-REFPROP高效调用方案&#xff0c;专为64位Windows平台设计&#xff0c;解决MATLAB环境下调用NIST官方REFPROP热力学数据库时常见的环境配置复杂、接口编译困难、DLL加载失败等痛点。压…

2026/10/20 20:19:47 阅读更多 →
技能拆解与能力建模:从抽象描述到可执行单元的系统方法

技能拆解与能力建模:从抽象描述到可执行单元的系统方法

1. 从“skills”这个词说起&#xff1a;它到底指什么“skills”这个词看起来简单&#xff0c;但放在不同语境里&#xff0c;含义差别很大。在职场语境下&#xff0c;它指的是一个人完成某项任务所具备的能力单元&#xff1b;在技术社区里&#xff0c;它常常指代一组可复用的技能…

2026/10/20 20:18:46 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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/20 10:02:08 阅读更多 →
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/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练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/20 9:50:45 阅读更多 →