news 2026/5/18 23:17:46

ART-Pi软件I2C驱动MPU6050:从协议原理到RT-Thread框架集成实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ART-Pi软件I2C驱动MPU6050:从协议原理到RT-Thread框架集成实战

1. 项目概述:从硬件I2C到软件I2C的实战迁移

最近在ART-Pi开发板上折腾MPU6050六轴传感器,遇到了一个挺典型的问题:硬件I2C引脚被其他功能占用了,或者硬件I2C总线上挂了多个设备,时序上出现了冲突。这时候,把通信方式从依赖硬件的I2C控制器切换到完全由软件模拟的“软件I2C”(Software I2C, 或 Bit-Banging I2C)就成了一个非常实用的解决方案。这不仅仅是换几行代码那么简单,它涉及到对I2C协议底层时序的精确掌控,以及对ART-Pi这种基于STM32H750高性能MCU平台GPIO操作特性的深入理解。今天,我就把自己在ART-Pi上实现软件I2C驱动MPU6050的全过程、踩过的坑以及优化心得,毫无保留地分享出来。无论你是刚接触RT-Thread和ART-Pi的新手,还是正在寻找可靠软件I2C方案的开发者,这篇内容都能给你提供一条清晰的路径和可直接复现的代码。

2. 核心思路与方案选型:为什么选择软件I2C?

2.1 硬件I2C的局限性与软件I2C的适用场景

在ART-Pi上,STM32H750自带了强大的硬件I2C外设(I2C1, I2C2, I2C3等),通常通过RT-Thread的I2C设备框架调用,非常方便。但硬件I2C有其固定的引脚映射,例如I2C1的SCL和SDA可能对应着某个特定的GPIO口。当你的项目硬件设计已经将这些引脚用于其他功能(比如UART、SPI或者直接作为普通IO控制LED),或者你需要在同一组I2C总线上连接多个地址冲突的传感器(需要分时复用总线)时,硬件I2C就显得束手束脚了。

软件I2C的核心思想是:不使用MCU内置的专用I2C硬件控制器,而是将任意两个通用GPIO引脚,通过程序代码精确地控制其输出高低电平、读取输入状态,来模拟出I2C协议所要求的起始信号、停止信号、数据发送(ACK/NACK)和时钟同步等全部时序。它的最大优势就是“引脚自由”“时序可控”。你可以把I2C总线“搬”到任何一对空闲的GPIO上,甚至可以动态切换不同的引脚组来模拟多条独立的I2C总线。

对于MPU6050这种最常用的传感器,其通信速率在标准模式下为100kHz,快速模式下为400kHz。在ART-Pi(主频高达480MHz)上,通过精心优化的延时函数,用软件模拟400kHz的时序是完全可以实现的,性能足以满足绝大多数运动传感应用。

2.2 ART-Pi平台与RT-Thread下的实现考量

ART-Pi开发板搭载的STM32H750性能强劲,这为软件I2C提供了坚实的基础。在RT-Thread操作系统下,我们需要考虑几点:

  1. 延时精度:软件I2C的时序依靠rt_thread_delay_us()或循环空指令来实现。H750的高主频意味着单指令周期极短,需要校准出精确的微秒级延时函数。
  2. GPIO操作速度:直接操作寄存器(HAL库或LL库)来翻转GPIO,比通过RT-Thread的PIN设备接口调用要快得多,这对于实现高速率(如400kHz)的软件I2C至关重要。
  3. 框架集成:最佳实践是将软件I2C驱动封装成符合RT-Thread标准的“I2C总线设备”。这样,上层应用(包括MPU6050的传感器驱动包)就可以像使用硬件I2C一样,通过rt_device_find(),rt_i2c_transfer()等标准接口来访问,实现驱动与硬件的解耦,提升代码的可移植性和可维护性。

因此,我们的方案确定为:基于STM32H750的寄存器级GPIO操作,实现一个精准的微秒延时函数,编写完整的软件I2C协议层代码,并将其注册为RT-Thread的一个I2C总线设备,最后挂载MPU6050传感器设备并完成数据读取。

3. 软件I2C驱动层实现详解

3.1 GPIO引脚配置与底层读写函数

首先,你需要选定两个空闲的GPIO作为SCL(时钟线)和SDA(数据线)。例如,我选择PH4作为SCL,PH5作为SDA。在drv_soft_i2c.c文件中,我们需要定义这些引脚并实现最底层的操作。

// 宏定义引脚,方便修改 #define SOFT_I2C1_SCL_PIN GET_PIN(H, 4) #define SOFT_I2C1_SDA_PIN GET_PIN(H, 5) // 引脚方向控制:输出模式 static void sda_out_mode(void) { // 配置PH5为推挽输出,高速模式。这里直接操作H750的GPIOH寄存器 GPIOH->MODER &= ~(GPIO_MODER_MODE5); // 清除模式位 GPIOH->MODER |= (0x01 << GPIO_MODER_MODE5_Pos); // 设置为输出模式(01) GPIOH->OTYPER &= ~(GPIO_OTYPER_OT5); // 设置为推挽输出(0) GPIOH->OSPEEDR |= (0x02 << GPIO_OSPEEDR_OSPEED5_Pos); // 设置为高速模式(10) } // 引脚方向控制:输入模式(带上拉) static void sda_in_mode(void) { // 配置PH5为上拉输入 GPIOH->MODER &= ~(GPIO_MODER_MODE5); // 清除模式位,设置为输入模式(00) GPIOH->PUPDR &= ~(GPIO_PUPDR_PUPD5); // 清除上下拉设置 GPIOH->PUPDR |= (0x01 << GPIO_PUPDR_PUPD5_Pos); // 设置为上拉(01) } // 引脚电平读写 static void scl_high(void) { GPIOH->BSRR = GPIO_BSRR_BS4; } // 置位PH4 static void scl_low(void) { GPIOH->BSRR = GPIO_BSRR_BR4; } // 复位PH4 static void sda_high(void) { GPIOH->BSRR = GPIO_BSRR_BS5; } // 置位PH5 static void sda_low(void) { GPIOH->BSRR = GPIO_BSRR_BR5; } // 复位PH5 static uint8_t sda_read(void) { return ((GPIOH->IDR & GPIO_IDR_ID5) != 0) ? 1 : 0; } // 读取PH5输入状态

注意:直接操作寄存器(GPIOH->BSRR等)是速度最快的方式。务必根据你选择的实际引脚所属的GPIO组(A, B, C...H)来修改寄存器名(如GPIOH改为GPIOA)。GET_PIN宏是RT-Thread的便捷方式,用于获取引脚编号,但底层操作我们绕过了PIN设备接口以求极致速度。

3.2 高精度微秒延时函数实现

软件I2C的时序正确与否,完全依赖于延时函数的精度。我们不能直接用rt_thread_delay_us(),因为它是基于操作系统调度的,存在不确定性。我们需要一个忙等待的精确延时。

// 系统时钟频率,ART-Pi通常为480MHz (HCLK) #define SYS_CLK_FREQ 480000000UL // 基于SysTick或DWT Cycle Counter实现微秒延时 // 这里以简单的循环计数为例,实际项目建议使用DWT(数据观察点跟踪单元)获取CPU周期计数 static void i2c_delay_us(uint32_t us) { uint32_t ticks = us * (SYS_CLK_FREQ / 1000000) / 10; // 粗略估算循环次数,需要校准! for(volatile uint32_t i = 0; i < ticks; i++) { __NOP(); // 执行空操作,消耗时间 } } // 更精确的做法:使用STM32的DWT单元 static void dwt_delay_init(void) { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; // 使能跟踪 DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; // 使能周期计数器 DWT->CYCCNT = 0; // 计数器清零 } static void dwt_delay_us(uint32_t us) { uint32_t start_tick = DWT->CYCCNT; uint32_t delay_ticks = us * (SYS_CLK_FREQ / 1000000); // 需要延时的CPU周期数 while((DWT->CYCCNT - start_tick) < delay_ticks) { // 忙等待 } }

实操心得i2c_delay_us函数中的除法和循环次数需要根据实际编译优化等级和主频进行精确校准。最可靠的方法是使用逻辑分析仪或示波器抓取SCL波形,测量其高电平和低电平时间,反复调整ticks的计算系数,直到时序完全符合I2C规范(标准模式:SCL低电平≥4.7μs,高电平≥4.0μs)。使用DWT是最精准的方法,推荐在要求高的场合使用。

3.3 I2C协议基本信号模拟

有了精准的延时和GPIO控制,我们就可以搭建I2C协议的四大基本信号:起始、停止、应答和非应答。

// 产生I2C起始信号:SCL高电平期间,SDA产生一个下降沿 void i2c_start(void) { sda_out_mode(); sda_high(); scl_high(); i2c_delay_us(5); // 保持时间,大于4.7us即可 sda_low(); i2c_delay_us(5); scl_low(); // 钳住总线,准备发送数据 } // 产生I2C停止信号:SCL高电平期间,SDA产生一个上升沿 void i2c_stop(void) { sda_out_mode(); sda_low(); i2c_delay_us(5); scl_high(); i2c_delay_us(5); sda_high(); // 发送停止信号 i2c_delay_us(5); // 保证总线空闲时间 } // 产生ACK应答信号:在第9个时钟周期,主机将SDA拉低 void i2c_ack(void) { sda_out_mode(); sda_low(); i2c_delay_us(2); scl_high(); i2c_delay_us(5); // SCL高电平期间,SDA保持低电平 scl_low(); i2c_delay_us(2); } // 产生NACK非应答信号:在第9个时钟周期,主机将SDA拉高 void i2c_nack(void) { sda_out_mode(); sda_high(); i2c_delay_us(2); scl_high(); i2c_delay_us(5); // SCL高电平期间,SDA保持高电平 scl_low(); i2c_delay_us(2); }

3.4 字节发送与接收函数

这是软件I2C的核心,负责按位读写数据。

// 发送一个字节(8bit),返回从机应答位 uint8_t i2c_write_byte(uint8_t data) { uint8_t i; uint8_t ack; sda_out_mode(); for(i = 0; i < 8; i++) { if(data & 0x80) { // 先发送最高位(MSB) sda_high(); } else { sda_low(); } i2c_delay_us(2); scl_high(); i2c_delay_us(5); // 保证SCL高电平期间数据稳定 scl_low(); i2c_delay_us(2); data <<= 1; // 左移一位,准备发送下一位 } // 读取ACK位(第9个时钟脉冲) sda_in_mode(); // 切换SDA为输入,准备读取从机应答 scl_high(); i2c_delay_us(2); ack = sda_read(); // 读取SDA电平,0为应答,1为非应答 scl_low(); sda_out_mode(); // 切换回输出模式,为后续操作做准备 return ack; // 通常返回0表示成功(收到ACK) } // 读取一个字节(8bit),并发送应答或非应答信号 uint8_t i2c_read_byte(uint8_t ack_flag) { uint8_t i; uint8_t data = 0; sda_in_mode(); // 确保SDA为输入模式 for(i = 0; i < 8; i++) { data <<= 1; // 先左移,再接收数据 scl_high(); i2c_delay_us(2); if(sda_read()) { data |= 0x01; // 读取到高电平,对应位为1 } scl_low(); i2c_delay_us(5); } // 发送应答/非应答 sda_out_mode(); if(ack_flag == I2C_ACK) { i2c_ack(); // 发送ACK } else { i2c_nack(); // 发送NACK,通常是读取最后一个字节后 } return data; }

4. 集成到RT-Thread I2C设备框架

为了让上层应用无缝使用我们的软件I2C,我们需要实现RT-Thread的rt_i2c_bus_device_ops结构体中的函数,并注册一个I2C总线设备。

4.1 实现总线操作函数

static rt_size_t soft_i2c_xfer(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num) { struct rt_i2c_msg *msg; rt_uint32_t i; rt_uint16_t ignore_nack; // 有些消息可以忽略NACK RT_ASSERT(bus != RT_NULL); RT_ASSERT(msgs != RT_NULL); for (i = 0; i < num; i++) { msg = &msgs[i]; ignore_nack = msg->flags & RT_I2C_IGNORE_NACK; // 发送起始信号 i2c_start(); // 发送设备地址和读写位 uint8_t addr = (msg->addr << 1) | ((msg->flags & RT_I2C_RD) ? 0x01 : 0x00); if (i2c_write_byte(addr) != 0) { // 非0表示收到NACK if (!ignore_nack) { i2c_stop(); return i; // 返回已成功处理的消息数 } } // 处理数据 if (msg->flags & RT_I2C_RD) { // 读操作 for (rt_uint32_t j = 0; j < msg->len; j++) { uint8_t ack_flag = (j == (msg->len - 1)) ? I2C_NACK : I2C_ACK; msg->buf[j] = i2c_read_byte(ack_flag); } } else { // 写操作 for (rt_uint32_t j = 0; j < msg->len; j++) { if (i2c_write_byte(msg->buf[j]) != 0) { if (!ignore_nack) { i2c_stop(); return i; } } } } } // 发送停止信号 i2c_stop(); return num; // 所有消息处理成功 } static const struct rt_i2c_bus_device_ops soft_i2c_ops = { .master_xfer = soft_i2c_xfer, // 软件I2C不支持10位地址和从机模式,这里可以置为RT_NULL .slave_xfer = RT_NULL, .i2c_bus_control = RT_NULL, };

4.2 初始化与注册总线设备

int rt_hw_soft_i2c_init(void) { static struct rt_i2c_bus_device i2c_bus; // 1. 初始化GPIO引脚为默认状态(高电平,上拉输入模拟空闲状态) rt_pin_mode(SOFT_I2C1_SCL_PIN, PIN_MODE_OUTPUT_OD); // 开漏输出,外部上拉 rt_pin_mode(SOFT_I2C1_SDA_PIN, PIN_MODE_OUTPUT_OD); rt_pin_write(SOFT_I2C1_SCL_PIN, PIN_HIGH); rt_pin_write(SOFT_I2C1_SDA_PIN, PIN_HIGH); // 注意:这里用RT-Thread PIN接口初始化,但底层操作函数仍用寄存器直接控制 // 也可以全部用寄存器初始化,保持一致性。 // 2. 初始化DWT高精度延时(如果使用) dwt_delay_init(); // 3. 填充I2C总线设备结构体 i2c_bus.ops = &soft_i2c_ops; i2c_bus.timeout = 100; // 超时时间,单位是RT_TICK_PER_SECOND的倒数 i2c_bus.retries = 2; // 重试次数 // 4. 注册到RT-Thread设备框架,命名为"soft_i2c1" rt_i2c_bus_device_register(&i2c_bus, "soft_i2c1"); return RT_EOK; } // 使用INIT_DEVICE_EXPORT自动初始化 INIT_DEVICE_EXPORT(rt_hw_soft_i2c_init);

完成这一步后,在RT-Thread的MSH命令行中输入list_device,你应该能看到一个名为soft_i2c1的I2C总线设备。

5. 驱动MPU6050传感器并读取数据

现在,我们可以像使用硬件I2C一样,使用这个软件I2C总线来驱动MPU6050了。RT-Thread的packages仓库中通常有MPU6050的软件包(如sensors包中的mpu6xxx系列驱动),我们只需在配置中指定使用soft_i2c1总线即可。

5.1 配置MPU6050软件包

  1. 使用menuconfig工具启用MPU6XXX软件包。

    RT-Thread online packages peripheral libraries and drivers ---> sensors drivers ---> [*] mpu6xxx: Universal 6-axis sensor driver package, support: mpu6000, mpu6050, mpu6500, mpu9250. Version (latest) ---> [*] Enable mpu6xxx sample [*] Enable soft i2c (soft_i2c1) The name of the soft i2c bus used by mpu6xxx

    在“The name of the soft i2c bus used by mpu6xxx”中填入我们注册的总线名soft_i2c1

  2. board.hrtconfig.h中,确保MPU6050的从机地址宏定义正确(通常为0x680x69,取决于AD0引脚电平)。

    #define BSP_USING_MPU6050 #define MPU6050_I2C_BUS_NAME "soft_i2c1" #define MPU6XXX_ADDR_DEFAULT 0x68

5.2 编写应用程序读取数据

软件包初始化后,会创建一个名为mpu的传感器设备。我们可以直接使用RT-Thread的传感器框架API来读取数据。

#include <rtthread.h> #include <rtdevice.h> #include <sensor.h> static void mpu6050_read_thread_entry(void *parameter) { rt_device_t dev = RT_NULL; struct rt_sensor_data sensor_data; rt_size_t res; // 1. 查找传感器设备 dev = rt_device_find("mpu"); if (dev == RT_NULL) { rt_kprintf("mpu6050 device not found!\n"); return; } // 2. 以只读方式打开设备 if (rt_device_open(dev, RT_DEVICE_FLAG_RDONLY) != RT_EOK) { rt_kprintf("open mpu6050 device failed!\n"); return; } while (1) { // 3. 读取传感器数据 res = rt_device_read(dev, 0, &sensor_data, 1); if (res == 1) { // sensor_data.type 指示数据类型(加速度/角速度) // sensor_data.data 是一个联合体,包含accel和gyro等字段 rt_kprintf("Accel: X:%6d, Y:%6d, Z:%6d | ", sensor_data.data.accel.x, sensor_data.data.accel.y, sensor_data.data.accel.z); rt_kprintf("Gyro: X:%6d, Y:%6d, Z:%6d\n", sensor_data.data.gyro.x, sensor_data.data.gyro.y, sensor_data.data.gyro.z); } else { rt_kprintf("read sensor data failed! res=%d\n", res); } rt_thread_mdelay(200); // 每200ms读取一次 } rt_device_close(dev); } int mpu6050_sample(void) { rt_thread_t thread; thread = rt_thread_create("mpu_read", mpu6050_read_thread_entry, RT_NULL, 1024, 25, 10); if (thread != RT_NULL) { rt_thread_startup(thread); } return RT_EOK; } // 导出到MSH命令 MSH_CMD_EXPORT(mpu6050_sample, read mpu6050 sensor data);

在MSH中运行mpu6050_sample命令,如果一切顺利,你将看到不断刷新的加速度计和陀螺仪数据。这证明你的软件I2C驱动和MPU6050传感器都已正常工作。

6. 调试技巧与常见问题排查

软件I2C的调试比硬件I2C更依赖工具和耐心。以下是几个关键点和常见问题的解决方法。

6.1 必备工具:逻辑分析仪

一个几十块钱的USB逻辑分析仪(如Saleae Logic 8克隆版)是调试软件I2C的神器。它能清晰地抓取SCL和SDA线上的每一个波形,让你直观地看到起始信号、停止信号、数据位和ACK/NACK信号是否符合I2C协议规范。没有它,调试就像在黑暗中摸索。

6.2 常见问题速查表

问题现象可能原因排查与解决方法
完全无响应,读取失败1. 引脚配置错误(输入/输出模式弄反)。
2. 上拉电阻未接或阻值过大(I2C总线需要上拉,通常4.7KΩ)。
3. 设备地址错误(MPU6050的AD0引脚电平决定地址是0x68还是0x69)。
4. 延时函数不准确,时序完全不对。
1. 用万用表测量SCL/SDA引脚在空闲时是否为高电平(约3.3V)。
2. 检查硬件上拉电阻(通常接在SCL和SDA到VCC之间)。
3. 用逻辑分析仪抓取起始信号后的第一个字节(设备地址+读写位),核对地址。
4. 用逻辑分析仪测量SCL周期和高低电平时间,与I2C标准(100kHz/400kHz)对比,调整i2c_delay_us的参数。
能收到ACK,但读取的数据全为0或0xFF1. 读取数据时,SDA方向切换(输入/输出)时序错误。
2. 读取字节函数中,数据移位和采样点的顺序有误。
3. MPU6050未正确初始化(如未唤醒、量程设置异常)。
1. 用逻辑分析仪观察读取数据阶段,SDA线是否在SCL高电平期间有变化。检查sda_in_modesda_out_mode的调用时机。
2. 确认i2c_read_byte函数中,是先左移data再采样,还是先采样再左移。标准是在SCL高电平中期采样数据。
3. 确保在读取数据前,通过软件I2C向MPU6050的电源管理寄存器(0x6B)写入0x00以唤醒设备。
通信不稳定,时好时坏1. 延时函数受系统中断影响,精度不够。
2. GPIO翻转速度不够快,无法满足400kHz的速率。
3. 总线被干扰,或上拉电阻阻值不合适导致上升沿太慢。
1. 尝试在操作I2C时序时关闭全局中断(rt_hw_interrupt_disable),操作完再开启。或者使用更精确的DWT延时。
2. 降低I2C速率到100kHz试试。检查GPIO配置是否为“高速”模式。
3. 缩短走线,确保上拉电阻在2.2KΩ~4.7KΩ之间(速率越高,电阻应越小)。用逻辑分析仪观察波形是否干净,上升沿是否陡峭。
注册设备失败1. 总线名称重复。
2.soft_i2c_xfer函数实现有误,导致框架检测失败。
1. 检查是否已有同名I2C设备。修改注册时的名称,如soft_i2c2
2. 简化测试:先不集成框架,写一个简单的测试函数,用i2c_start,i2c_write_byte,i2c_stop等基本函数直接读写MPU6050的WHO_AM_I寄存器(0x75),其返回值应为0x68。确保底层函数正确后再集成到框架。

6.3 性能优化建议

  1. 使用寄存器直接操作:这是最大的性能提升点。避免任何可能产生延迟的函数调用开销。
  2. 精简延时函数:在满足I2C时序最小要求的前提下,尽可能减少延时时间。400kHz模式下,一个时钟周期只有2.5μs,留给软件操作的时间非常紧张。
  3. 关闭中断:在关键的、连续的I2C时序操作(如发送一个完整的数据帧)期间,可以临时关闭全局中断,防止被其他高优先级任务或中断打断,造成时序错乱。但要注意关闭时间不能过长,以免影响系统实时性。
  4. 使用DWT计数器:如前所述,基于CPU周期计数的DWT延时是最高精度的方法,几乎不受编译器优化影响。

7. 总结与扩展思考

通过以上步骤,我们成功在ART-Pi上利用任意GPIO引脚实现了软件I2C,并驱动了MPU6050传感器。整个过程的关键在于对I2C协议底层时序的深刻理解和对MCU GPIO操作的精准控制。将软件I2C封装成RT-Thread的标准总线设备,是让代码变得优雅和可复用的关键。

这个方案的价值不仅在于解决了引脚冲突问题,它更是一种**“降维打击”**的调试思路。当你掌握了用软件“bit-bang”出通信协议的能力,再去理解硬件外设的工作机制就会豁然开朗。你可以用同样的思路去实现软件SPI、软件UART(单线半双工)等。

最后,一个小技巧:如果你的项目对多个传感器有实时性要求,可以考虑为每个传感器单独分配一对GPIO,实现多个独立的软件I2C总线,从而避免总线仲裁和等待,提升整体采样率。当然,这会消耗更多的GPIO资源和CPU时间,需要根据实际需求权衡。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/18 23:14:06

OxyGent入门指南:10分钟快速搭建你的第一个多智能体系统

OxyGent入门指南&#xff1a;10分钟快速搭建你的第一个多智能体系统 【免费下载链接】OxyGent [ACL 2026] OxyGent: Making Multi-Agent Systems Modular, Observable, and Evolvable via Oxy Abstraction 项目地址: https://gitcode.com/gh_mirrors/ox/OxyGent OxyGent…

作者头像 李华
网站建设 2026/5/18 23:13:02

一站式搭建Python GUI开发环境:Anaconda、PyCharm与PyQt5的整合指南

1. 为什么选择AnacondaPyCharmPyQt5组合&#xff1f; 刚开始接触Python GUI开发时&#xff0c;我也被各种工具链搞得眼花缭乱。直到发现AnacondaPyCharmPyQt5这个黄金组合&#xff0c;开发效率直接翻倍。先说Anaconda&#xff0c;它不仅仅是Python发行版&#xff0c;更是一个强…

作者头像 李华
网站建设 2026/5/18 23:10:12

Go-Binance SDK终极指南:一站式解决加密货币交易API集成难题

Go-Binance SDK终极指南&#xff1a;一站式解决加密货币交易API集成难题 【免费下载链接】go-binance A Go SDK for Binance API 项目地址: https://gitcode.com/gh_mirrors/go/go-binance Go-Binance SDK是专为加密货币交易者设计的终极解决方案&#xff0c;这个强大的…

作者头像 李华
网站建设 2026/5/18 23:10:03

gemmlowp输出管道机制揭秘:灵活量化范式的完整教程

gemmlowp输出管道机制揭秘&#xff1a;灵活量化范式的完整教程 【免费下载链接】gemmlowp Low-precision matrix multiplication 项目地址: https://gitcode.com/gh_mirrors/ge/gemmlowp gemmlowp是一个专注于低精度矩阵乘法&#xff08;GEMM&#xff09;的高性能库&…

作者头像 李华
网站建设 2026/5/18 23:05:41

30 分钟 Shell 光速入门教程

30 分钟 Shell 光速入门教程 一、参考资料 【30分钟Shell光速入门教程】 https://www.bilibili.com/video/BV17m411U7cC/?share_sourcecopy_web&vd_source855891859b2dc554eace9de3f28b4528 二、笔记总结 第 1 部分第 2 部分第 3 部分

作者头像 李华