【CubeMX+STM32】SD卡 SDIO通信 DMA读写

本篇,将使用CubeMX+Keil,创建一个SD卡的DMA读写工程。

目录

一、简述

二、CubeMX 配置 SDIO + DMA

三、Keil 编辑代码

四、实验效果


实现效果,如下图:


一、简述

上篇已简单介绍了SD、SDIO,本篇不再啰嗦,有兴趣的可翻看上篇:

STM32_SD卡的SDIO通信_基础读写-CSDN博客

使用SD卡存储数据,常用的两种方式:

  • DMA读写(不带文件系统):逻辑简单、代码体积小,适合只做数据存储的应用。
  • 模拟U盘方式(FATFS+USB):方便电脑端文件存储,但代码体积大、文件解释麻烦。

两种方式各有优劣,得按硬件限制、方案需求而进行选择。

本篇示范的是:不带FATFS的DMA读写。


二、CubeMX 配置 SDIO + DMA

新建工程部分,略过。

1、使能SDIO

  • Mode:选择SD的四线模式,即SD 4 bits Wide bus.
  • F407系列:不用修改配置,默认即可。
  • F103系列,需把时钟分频系数修改为6 或 8,即SDIOCLK Clock divide factor这一项,由默认0改为6, 不然会通信失败。

2、启用DMA

  • F407系列:如下图,DMA Settings /添加SDIO_RX、SDIO_TX这两项; 其它参数默认;
  • F103系列:点击ADD后,添加SDIO。它只有SDIO一项,因为F103的SDIO是读写共用同一DMA通道。其它参数默认。

3、开启SDIO全局中断,修改中断优先级

  • F407 系列:如下图三项,打勾启用,并修改优先级。
  • F103 系列:只有SDIO global interupt 这一项。打勾启用,并修改优先级。

4、时钟设置

进入时钟树配置页面。

如果之前没配置过SDIO、USB,这时就会弹窗:是否自动配置所需时钟?

选择:NO ,手动修改即可。

不推荐 Yes,因为它将针对已使能的SDIO、USB进行必须值的配置,而已设置好的系统时钟,将会被修改成其它值。

F407 系列芯片:

如果板子用25M晶振,使用下图配置即可;如果是8M晶振,修改晶振、分频两处为8即可。

重点:箭头所指的Q值,它用于控制USB 、SDIO和随机数生成器的时钟,这个时钟必须是 48M ! 因此,这里设置为 7;

F103 系列芯片:

使用新建工程时的默认参数,即可,无需额外修改。红圈处已是SDIO的默认时钟配置。

好了,已完成配置。

重新生成工程,即可!


三、Keil 编辑代码

1、打开keil 工程,先重新编译一次。

  • 正常情况,编译是0 Error的。
  • 如果有Error, 应该是新建工程时,路径、名称有中文了,重新开建工程,改为英文即可。

2、重要修改:SD卡的初始化,使用 1-bit 模式

CubeMX生成的SDIO初始化代码,有一个bug,需要手动修改,操作如下:

  • 右击 main.c 文件中函数 MX_SDIO_SD_Init(),
  • 在弹出菜单中:Go To Ddfinition Of ...; 将跳转到SD卡初始化函数内部;

跳转到 sdio.c文件的 MX_SDIO_SD_Init()函数内部后,

把下图位置中的 4B,改为 1B ;

它下面还有一个4B,不用修改,只修改刚才那个即可。不要改错位置了!

重要!CubeMX每次重新生成后,都要手动修改一次。如果不修改,初始化过程会导致程序卡死。

3、DMA 读写函数 介绍

我们在上篇介绍SD卡的基础读写时,使用的4个函数,如下:

这4个函数,在本工程中,还是可用的。

1、获取SD卡信息 HAL_SD_CardInfoTypeDef pCardInfo = {0}; // SD卡信息结构体 HAL_SD_GetCardInfo(&hsd, &pCardInfo); // 获取 SD 卡的信息 2、读数据 HAL_SD_ReadBlocks(&hsd, aOldData, 7, 2, 3000); // SD卡的句柄、数据、块地址、块数量、超时ms 3、写数据 HAL_SD_WriteBlocks(&hsd, aTestData, 7, 2, 3000) // SD卡的句柄、数据、块地址、块数量、超时ms 4、擦除数据 HAL_SD_Erase(&hsd, 7, 8) // SD卡的句柄、块起始地址、块结束地址

本篇通过DMA读写,将使用以下两个读写函数:

(和基础读写的函数名称近似,只是多了后辍"_DMA")

4、读取数据_通过DMA HAL_SD_ReadBlocks_DMA(&hsd, aOldData, 7, 2); // 读取SD卡指定块的数据; 参数:SD句柄、数据地址、块起始地址、需要读取的块数量; 5、写入数据_通过DMA HAL_SD_WriteBlocks_DMA(&hsd, aTestData, 7, 2); // 向指定块写入数据; 参数:SD句柄、数据地址、块起始地址、需要写入的块数量;

注意机制的不同:

  • 基础读、写函数: HAL_SD_ReadBlocks() 和 HAL_SD_WriteBlocks() ,它俩是”死等式“执行的,即,读写完成、操作超时,才能结束函数、并返回执行状态,执行下一行代码。如果数据量大,会"卡"程序。
  • DMA读、写函数:HAL_SD_ReadBlocks_DMA() 和 HAL_SD_WriteBlocks_DMA() ,将按函数的参数配置、启动DMA后,就会退出函数,执行下一行代码。

4、中断回调函数

通过DMA读、写后,有两种方式可以知道读写操作是否完成:

  1. 通过HAL_SD_GetState(&hsd)获取状态;
  2. 通过它俩的中断回调函数。本篇将使用中断回调函数的方式,这样操作性更好;

中断回调函数需要自行手写,可以写在 it.c 或 main.c底部 ,它俩分别是:

1、DMA TX 传输完成 中断回调函数 void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd) { printf("写入完成\r\n"); } 2、DMA RX 传输完成 中断回调函数 void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd) { printf("读取完成\r\n"); }

5、具体读写操作示例

首先说明一点,本示例示范如何通过DMA读写,而示范代码中的状态判断,只是提供一个思路,这种写法是不高效的,需按自身方案作出适合的修改。

第一步:新建两个变量,作为DMA读写完成的标志。

在main()的上方,新建两个变量,作为读写结束的标志:

volatile uint8_t myFlagSDReadReady = 0; // 读取传输完成标志; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。 volatile uint8_t myFlagSDWriteReady = 0; // 写入传输完成标志; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。

技巧:

这里用了volatile关键字修饰变量。

因为示范代码中有几段 while (myFlagSDReadReady == 0) 这样的空循环判断变量状态变化。

而CubeMX生成的工程,默认编译优化等级是3。编译时会把这一段中的变量误判为是不变的,编译运行后,就会导致循环体“卡死”。

使用 volatile 修饰,是告诉编译器不要优化它,每次循环时都必须从内存中读一次这变量的真实值。

编写完成后,位置如下图:

第二步:编写需要的两个中断回调函数

F407 系列:

在main() 下方,即/* USER CODE BEGIN 4 */下面,添加这两个中断回调函数。

/*DMA Tx传输完成中断回调函数*/ void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd) { myFlagSDWriteReady = 1; // 写入传输完成标志; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。 } /*DMA Rx传输完成中断回调函数*/ void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd) { myFlagSDReadReady = 1; // 读取传输完成标志; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。 }

F103 系列:

上面在配置CubeMX时已提及:F1的SDIO读写共用同一DMA通道,因此,操作与F4的稍有不同。

  • 首先,在main.c的 /* USER CODE BEGIN 0 */里面,添加一个引用:
extern DMA_HandleTypeDef hdma_sdio; // SDIO的DMA通道(单通道读写共用)
  • 然后,在main() 下方,即/* USER CODE BEGIN 4 */下面,添加这两个中断回调函数。
/*DMA Tx传输完成中断回调函数*/ void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd) { myFlagSDWriteReady = 1; __HAL_DMA_DISABLE(&hdma_sdio); // F103关键修复:单DMA通道读写共用,完成后强制关通道,否则下次换向不生效 } /*DMA Rx传输完成中断回调函数*/ void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd) { myFlagSDReadReady = 1; __HAL_DMA_DISABLE(&hdma_sdio); // F103关键修复:单DMA通道读写共用,完成后强制关通道,否则下次换向不生效 }

编写完成后,位置如下图:

第三步:在 main()函数内,/* USER CODE BEGIN 2 */ 注释下方,编写读写代码:

有点点长,建议直接复制。

其实,有效的读写只是简单的几行而已,这里写了一堆,只是为了展示。

代码里已附详细注释,整体流程是:

  • 获取SD卡信息
  • 读取测试块的原数据
  • 写入测试
  • 擦除测试
  • 写回原数据
/***************** SD卡读写通信测试 *****************/ /* 1、获取卡信息,打印到串口助手 */ /* 2、读测试:读出测试位置原数据,保存在 aOldData[] */ /* 3、写测试:在测试的块上,写入指定数据 */ /* 读出刚才写入的块数据,打印到串口助手观察 */ /* 4、擦除测试:擦除指定块上的数据 */ /* 读出刚才擦除块的数据,打印到串口助手观察 */ /* 5、写回原数据到指定位置 */ /* 读出刚才写入的块数据,打印到串口助手观察 */ #define SD_TEST_SIZE 1024 // 测试数据的字节数,刚好是2个块大小:2x512 static uint8_t aOldData[SD_TEST_SIZE] = {0}; // 用于存放旧数据,先读出来,测试完了,再把旧数据写回去 static uint8_t aTestData[SD_TEST_SIZE] = {0}; // 临时缓存,用来存放测试数据 HAL_SD_CardInfoTypeDef pCardInfo = {0}; // SD卡信息结构体 uint8_t status = HAL_SD_GetCardState(&hsd); // SD卡状态标志值 if (status == HAL_SD_CARD_TRANSFER) { /* 1、获取卡信息,打印到串口助手 */ HAL_SD_GetCardInfo(&hsd, &pCardInfo); // 获取 SD 卡的信息 printf("\r1、获取SD卡信息 ... \r\n"); printf("卡类型:%d \r\n", pCardInfo.CardType); // 类型返回:0-SDSC、1-SDHC/SDXC、3-SECURED printf("卡版本:%d \r\n", pCardInfo.CardVersion); // 版本返回:0-CARD_V1、1-CARD_V2 printf("块数量:%d \r\n", pCardInfo.BlockNbr); // 可用的块数量 printf("块大小:%d \r\n", pCardInfo.BlockSize); // 每个块的大小; 单位:字节 printf("卡容量:%lluG \r\n", ((unsigned long long)pCardInfo.BlockSize * pCardInfo.BlockNbr) / 1024 / 1024 / 1024); // 计算卡的容量 HAL_Delay(1000); // 重要:稍作延时再开始读写测试; 避免有些仿真器烧录期间的多次复位,短暂运行了程序,导致下列读写数据不完整。 /* 2、读测试:读出测试位置原数据,保存在 aOldData[] */ printf("\r2、读取测试块的原数据 ... \r\n"); memset(aOldData, 0, SD_TEST_SIZE); // 清0数组的数据 myFlagSDReadReady = 0; // 读取传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1. HAL_SD_ReadBlocks_DMA(&hsd, aOldData, 7, 2); // 读取SD卡指定块的数据; 参数:SD句柄、数据地址、块起始地址、需要读取的块数量; while (myFlagSDReadReady == 0); // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。 for (uint32_t i = 0; i < SD_TEST_SIZE; i++) // 打印 原始数据 printf("%X ", aOldData[i]); printf("\r\n"); /* 3-1、写测试:在测试的块上写入数据 */ printf("\r3、SD卡 写入测试 ...\r\n"); memset(aTestData, 0x8, SD_TEST_SIZE); // 整个数组填充指定值,作为测试写入的数据 myFlagSDWriteReady = 0; // 写入传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1. HAL_SD_WriteBlocks_DMA(&hsd, aTestData, 7, 2); // 向指定块写入数据; 参数:SD句柄、数据地址、块起始地址、需要写入的块数量; while (myFlagSDWriteReady == 0); // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。 printf("对指定块写入结束! \r写入的数据是:\n"); for (uint32_t i = 0; i < SD_TEST_SIZE; i++) // 打印 写入的数据 printf("%X ", aTestData[i]); printf("\r\n"); /* 3-2、读出现在块内的数据 */ printf("\r现在块内的数据是:\r\n"); memset(aTestData, 0, SD_TEST_SIZE); // 清0数组的数据 myFlagSDReadReady = 0; // 读取传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1. HAL_SD_ReadBlocks_DMA(&hsd, aTestData, 7, 2); // 读SD卡数据块; 参数:SD句柄、数据地址、块起始地址、读的块数量; while (myFlagSDReadReady == 0); // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。 for (uint32_t i = 0; i < SD_TEST_SIZE; i++) // 打印 写入后块内现在数据 printf("%X ", aTestData[i]); printf("\r\n"); /* 4-1、擦除测试:擦除指定块上的数据 */ printf("\r4、擦除块测试 ...\r\n"); if (HAL_SD_Erase(&hsd, 7, 8) == HAL_OK) // 擦除SD卡上的数据; 参数:SD结构体、块的起始地址、块的结束地址 { while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束 printf("擦除 成功! \r\n"); } else { printf("擦除 失败! \r\n"); } /* 4-2、读取,擦除后指定块上的数据 */ printf("擦除后,现在块内的数据是:\r\n"); memset(aTestData, 0, SD_TEST_SIZE); // 清0数组的数据 myFlagSDReadReady = 0; // 读取传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个值赋值1. HAL_SD_ReadBlocks_DMA(&hsd, aTestData, 7, 2); // 读SD卡数据块; 参数:SD句柄、数据地址、块起始地址、读的块数量; while (myFlagSDReadReady == 0); // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。 for (uint32_t i = 0; i < SD_TEST_SIZE; i++) // 打印 块内现在的数据 printf("%X ", aTestData[i]); printf("\r\n"); /* 5-1、写回测试块上的原始数据 */ printf("\r5、写回原数据 ...\r\n"); //memset(aOldData, 0, SD_TEST_SIZE); // 这行是备用的,为了测试后写入特定数据 myFlagSDWriteReady = 0; // 写入传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1. HAL_SD_WriteBlocks_DMA(&hsd, aOldData, 7, 2); // 向指定块写入数据; 参数:SD句柄、数据地址、块起始地址、需要写入的块数量; while (myFlagSDWriteReady == 0); // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。 printf("写入结束! \n"); /* 5-2、读取现在块内的数据 */ printf("现在块内的数据是: \r\n"); memset(aTestData, 0, SD_TEST_SIZE); // 清0数组的数据 myFlagSDReadReady = 0; // 读取传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个值赋值1. HAL_SD_ReadBlocks_DMA(&hsd, aTestData, 7, 2); // 读SD卡数据块; 参数:SD句柄、数据地址、块起始地址、读的块数量; while (myFlagSDReadReady == 0); // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。 for (uint32_t i = 0; i < SD_TEST_SIZE; i++) // 打印 块内现在的数据 printf("%X ", aTestData[i]); printf("\r\n\r\n"); printf("SD卡 读写测试结束!\r\n"); }

编写完成后,位置如下图:

至此,代码编写完成,可以编译、烧录了。


四、实验效果

程序运行后,串口助手输出如下:

如有错漏 ,望指正~~~!

相关新闻

StartOS集群部署:如何构建高可用性服务器集群

StartOS集群部署:如何构建高可用性服务器集群

StartOS集群部署&#xff1a;如何构建高可用性服务器集群 StartOS是一款专为自托管优化的图形化服务器操作系统&#xff0c;通过集群部署可以显著提升服务的可用性和可靠性。本文将详细介绍如何使用StartOS构建一个高可用性的服务器集群&#xff0c;即使在单节点故障的情况下也…

2026/10/20 1:15:49 阅读更多 →
WHAT - 滚动条 css scrollbar-* 属性

WHAT - 滚动条 css scrollbar-* 属性

/* 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:15:49 阅读更多 →
渲染流水线简单梳理

渲染流水线简单梳理

/* 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:15:49 阅读更多 →

最新新闻

调教Stable Diffusion注意力机制,让出图更清晰

调教Stable Diffusion注意力机制,让出图更清晰

1. 先别急着加高清修复&#xff0c;问题多半出在注意力分配上先说个不少人踩过的坑&#xff1a;用Stable Diffusion出图&#xff0c;总感觉边缘发虚、细节糊成一团&#xff0c;第一反应往往是把分辨率从512拉高到1024&#xff0c;或者直接开Hires. fix&#xff0c;结果要么显存…

2026/10/20 3:27:05 阅读更多 →
加密狗复制风险与工控软件授权管理:从停产事故看正确应对方式

加密狗复制风险与工控软件授权管理:从停产事故看正确应对方式

上个月我去一家做汽车零部件加工的朋友那边聊设备改造&#xff0c;正好撞上他们车间的一件急事&#xff1a;产线上一套工艺管理软件的加密狗突然失效&#xff0c;操作员报错、工艺配方调不出来&#xff0c;整个班组停了半个多小时。朋友的第一反应不是联系软件供应商&#xff0…

2026/10/20 3:27:05 阅读更多 →
Unity Scroll View Content配置全解:滚动失效排查与动态列表实战

Unity Scroll View Content配置全解:滚动失效排查与动态列表实战

聊起Unity里的Scroll View&#xff0c;十个新手有九个都栽在Content设置上。上周交流群里又有人发截图问&#xff1a;东西全拖进Scroll View了&#xff0c;为什么滚不动、显示不全、滚动条乱蹦&#xff1f;问题基本都出在Content这个子节点上。这篇专门讲Scroll View下Content的…

2026/10/20 3:27:05 阅读更多 →
AI Agent核心能力拆解与工程落地实战指南

AI Agent核心能力拆解与工程落地实战指南

各位做技术、做产品的朋友&#xff0c;最近是不是隔三差五就被"AI Agent"这个词刷屏&#xff1f;团队研讨它在讲&#xff0c;朋友圈技术博主在聊&#xff0c;产品评审会也在问"为什么不做成Agent"。但如果真让你一句话讲清楚它到底是什么&#xff0c;很多人…

2026/10/20 3:27:05 阅读更多 →
OpenAI兼容格式接入中文大模型:从密钥配置到流式输出实战

OpenAI兼容格式接入中文大模型:从密钥配置到流式输出实战

我先说个结论&#xff1a;如果你现在手里攒着一堆“用某个平台接大模型”的活儿&#xff0c;最省心的路线&#xff0c;往往不是直接用厂商自己那套SDK&#xff0c;而是找一个已经帮你把接口层做成了OpenAI兼容格式的数据中间件。这篇内容就围绕一个具体方案展开——用某数据云平…

2026/10/20 3:27:05 阅读更多 →
DeepSeek降AI指令5步流程:从AI味到自然学术写作

DeepSeek降AI指令5步流程:从AI味到自然学术写作

最近太多人在问“DeepSeek降AI指令怎么写”&#xff0c;尤其是论文写了一半&#xff0c;拿AI润色完&#xff0c;自己一读都觉得“太工整了”&#xff0c;然后被各种AI痕迹检测提示搞得心里发毛。先说结论&#xff1a;所谓“降AI”&#xff0c;并不是玄学&#xff0c;本质是让大…

2026/10/20 3:26:04 阅读更多 →

日新闻

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/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出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/19 2:54:50 阅读更多 →
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/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →