当前位置: 首页 > news >正文

RT-Thread在STM32硬件I2C的踩坑记录

RT-Thread在STM32硬件I2C的踩坑记录

  • 0.前言
  • 一、软硬件I2C区别
  • 二、RT Thread中的I2C驱动
  • 三、尝试适配硬件I2C
  • 四、i2c-bit-ops操作函数替换
  • 五、Attention Please!
  • 六、总结


参考文章:
1.将硬件I2C巧妙地将“嫁接”到RTT原生的模拟I2C驱动框架
2.基于STM32F4平台的硬件I2C驱动实现笔记
3.《rt-thread驱动框架分析》- i2c驱动

0.前言

  最近打算用RT-Thread做一个小demo玩玩,其中需要用I2C通信驱动一个oled屏幕,但是找了一圈也没找到RTT中对硬件I2C的支持方式以及使用案例,好像大家都心照不宣的用这个好用又不好用的软件I2C。这里还是忍不住吐槽两句,连硬件SPI都已经支持了,甚至支持SPI DMA模式了,硬件I2C这么多年了也没适配。也希望有大佬能贡献一份力量,做出一份能让DIY玩家凑合用的第三方硬件I2C驱动也行。

一、软硬件I2C区别

  有关I2C通信协议的原理部分就不多介绍了,这个算是很常见的通信协议了,CSDN论坛一搜一大把,RT Thread文档中心也有较详细的介绍。
  软件I2C是使用GPIO的电平翻转模拟出I2C信号,它的好处是方便移植,下至51单片机,上至linux平台,只要有GPIO都能适用(当然linux下也不会有人用这个)。缺点则是速率很低,软件操作GPIO电平翻转不可避免的有时延以及毛刺,为了消除这种现象的影响,模拟的I2C信号之间就需要稍微大点的时间间隔。软件I2C的信号频率一般在30KHz ~ 50KHz,即便优化相当好的情况也差不多在这个量级。用来操作128x64的oled屏幕,帧率基本在2帧左右。
  硬件I2C则是通过操作芯片自带的寄存器进行I2C通信,缺点就是不同芯片间驱动不通用,优点则是速度更快,并且可以适配DMA模式,降低CPU负载。笔者使用的STM32RCT6,硬件I2C标准模式信号频率为100KHz,快速模式400KHz,一些性能较好的芯片还有1MHz的极速模式。400kHz情况下操作128x64的oled帧率在25帧左右,可以说是提升巨大了。

二、RT Thread中的I2C驱动

  关于RT Thread中的I2C驱动框架的实现方式,可以参考上述的第三篇参考文章,个人觉得写的很详细也好懂。RT Thread为类Linux的实时操作系统,所以I2C框架的实现方式和linux中的也比较相像:I2C驱动提供一些操作相关的ops函数,并注册到内核中,I2C设备则可以通过probe函数挂载到总线上,通过ops操作函数进行I2C通信。
在这里插入图片描述
并且在该篇文章中,该作者跳过原本的bit_ops,重新设计了一个硬件I2C的实现方式,将驱动直接挂载到内核core中,也实现了作为master设备的硬件I2C驱动。不过笔者认为这种方式对通用结构的兼容性不太好,所以又找了一些其他方式。
在这里插入图片描述

三、尝试适配硬件I2C

参考文章1和2中,通过修改I2C总线的实现函数,“嫁接”一个硬件的I2C驱动实现方式。这里就先放上代码,首先在原drv_soft_i2c.c和drv_soft_i2c.h的同级目录下,分别创建drv_hard_i2c.c和drv_hard_i2c.h:
drv_hard_i2c.h:

/** Copyright (c) 2006-2018, RT-Thread Development Team** SPDX-License-Identifier: Apache-2.0** Change Logs:* Date           Author       Notes* 2018-11-08     balanceTWK   first version*/#ifndef __DRV_I2C__
#define __DRV_I2C__#include <rtthread.h>
#include <rthw.h>
#include <rtdevice.h>#ifdef BSP_USING_HARD_I2C/* stm32 config class */
typedef void (*pI2cConfig)(void);
struct stm32_hard_i2c_config
{rt_uint8_t scl;                     /* scl pin */rt_uint8_t sda;                     /* sda pin */const pI2cConfig pFunc;             /* i2c init function */const char* pName;                  /* i2c bus name */I2C_HandleTypeDef* pHi2c;           /* i2c handle */struct rt_i2c_bus_device i2c_bus;   /* i2c bus device */
};
/* stm32 i2c dirver class */
struct stm32_i2c
{struct rt_i2c_bit_ops ops;struct rt_i2c_bus_device i2c2_bus;
};#define HARD_I2C_CONFIG(x)  \
{.scl        = BSP_I2C##x##_SCL_PIN,    \.sda        = BSP_I2C##x##_SDA_PIN,    \.pFunc      = MX_I2C##x##_Init,         \.pHi2c      = &hi2c##x,                 \.pName      = "i2c"#x,                  \.i2c_bus    = {.ops = &i2c_bus_ops,},
}int rt_hw_i2c_init(void);#endif#endif /* RT_USING_I2C */

其中stm32_hard_i2c_config可以理解为i2c实例对象,属性包括scl和sda引脚、总线名称及初始化函数等。(注:在参考文章2中的总线速度、信号量及互斥锁则不需要,因为使用CubeMx生成的初始化函数中已有总线速度,HAL库中的I2C操作函数内部已有总线锁)
stm32_i2c则封装了设备操作函数及总线,用于与内核对接。
函数宏HARD_I2C_CONFIG(x)则用来后续创建I2C设备对象。

drv_hard_i2c.c:

/** Copyright (c) 2006-2018, RT-Thread Development Team** SPDX-License-Identifier: Apache-2.0** Change Logs:* Date           Author       Notes* 2018-11-08     balanceTWK   first version*/#include <board.h>
#include "drv_hard_i2c.h"
#include "drv_config.h"
#include<rtthread.h>
#include<rtdevice.h>#ifdef BSP_USING_HARD_I2C//#define DRV_DEBUG
#define LOG_TAG              "drv.i2c"
#include <drv_log.h>static const struct stm32_hard_i2c_config hard_i2c_config[] =
{
#ifdef BSP_USING_HARD_I2C1HARD_I2C_CONFIG(1),
#endif
#ifdef BSP_USING_HARD_I2C2HARD_I2C_CONFIG(2),
#endif
#ifdef BSP_USING_HARD_I2C3HARD_I2C_CONFIG(3),
#endif
#ifdef BSP_USING_HARD_I2C4HARD_I2C_CONFIG(4),
#endif
};static struct stm32_i2c i2c_obj[sizeof(hard_i2c_config) / sizeof(hard_i2c_config[0])];/*** This function initializes the i2c pin.** @param Stm32 i2c dirver class.*/
static void stm32_i2c_gpio_init(struct stm32_i2c *i2c)
{struct stm32_soft_i2c_config* cfg = (struct stm32_soft_i2c_config*)i2c->ops.data;rt_pin_mode(cfg->scl, PIN_MODE_OUTPUT_OD);rt_pin_mode(cfg->sda, PIN_MODE_OUTPUT_OD);rt_pin_write(cfg->scl, PIN_HIGH);rt_pin_write(cfg->sda, PIN_HIGH);
}/*** The time delay function.** @param microseconds.*/
static void stm32_udelay(rt_uint32_t us)
{rt_uint32_t ticks;rt_uint32_t told, tnow, tcnt = 0;rt_uint32_t reload = SysTick->LOAD;ticks = us * reload / (1000000 / RT_TICK_PER_SECOND);told = SysTick->VAL;while (1){tnow = SysTick->VAL;if (tnow != told){if (tnow < told){tcnt += told - tnow;}else{tcnt += reload - tnow + told;}told = tnow;if (tcnt >= ticks){break;}}}
}/*** if i2c is locked, this function will unlock it** @param stm32 config class** @return RT_EOK indicates successful unlock.*/
static rt_err_t stm32_i2c_bus_unlock(const struct stm32_soft_i2c_config *cfg)
{rt_int32_t i = 0;if (PIN_LOW == rt_pin_read(cfg->sda)){while (i++ < 9){rt_pin_write(cfg->scl, PIN_HIGH);stm32_udelay(100);rt_pin_write(cfg->scl, PIN_LOW);stm32_udelay(100);}}if (PIN_LOW == rt_pin_read(cfg->sda)){return -RT_ERROR;}return RT_EOK;
}/* I2C initialization function */
int rt_hw_i2c_init(void)
{rt_int8_t ret = RT_ERROR;rt_size_t obj_num = NR(hard_i2c_config);rt_err_t result;for (int i = 0; i < obj_num; i++){//GPIO初始化stm32_i2c_gpio_init(&hard_i2c_config[i]);//检测SDA是否为低电平,低电平则通过管脚模拟9个CLK解锁stm32_i2c_bus_unlock(&hard_i2c_config[i]);//调用Hal库MX_I2Cx_Init(),配置硬件I2Chard_i2c_config[i].pFunc();//向内核注册I2C Bus设备if(rt_i2c_bus_device_register(&(hard_i2c_config[i].i2c_bus), hard_i2c_config[i].pName) != RT_EOK){LOG_E("%s bus init failed!\r\n", hard_i2c_config[i].pName);ret |= RT_ERROR;}else{ret |= RT_EOK;LOG_I("%s bus init success!\r\n", hard_i2c_config[i].pName);}}return ret;
}
//INIT_BOARD_EXPORT(rt_hw_i2c_init);#endif /* BSP_USING_HARD_I2C */

此文件中则主要根据宏定义开关创建I2C实例对象,并对其进行初始化。主要函数为rt_hw_i2c_init(),此函数中所需要的gpio init、delay函数等,则保留软件i2c中的初始化操作。

user_i2c.h:

/** Copyright (c) 2006-2021, RT-Thread Development Team** SPDX-License-Identifier: Apache-2.0** Change Logs:* Date           Author       Notes* 2023-08-27     14187       the first version*/
#ifndef DRIVERS_HARD_I2C_H_
#define DRIVERS_HARD_I2C_H_//硬件i2c宏开关
//#define     BSP_USING_HARD_I2C
#ifdef      BSP_USING_HARD_I2C
//      #define BSP_USING_HARD_I2C1
//      #define BSP_USING_HARD_I2C2
//      #define BSP_USING_HARD_I2C3
//      #define BSP_USING_HARD_I2C4#if defined(BSP_USING_HARD_I2C1 || BSP_USING_HARD_I2C2 || BSP_USING_HARD_I2C3 || BSP_USING_HARD_I2C4)//#define BSP_USING_DMA_I2C_TX//#define BSP_USING_DMA_I2C_RX#endif
#endif#endif /* DRIVERS_HARD_I2C_H_ */

为了不在每次保存RT Thread Settings时,自己的配置被覆盖刷新,所以额外定义了一个头文件,用于保存自定义的I2C宏开关,这样每次刷新后只需要重新在board.h中包含此头文件即可。

至此自定义的硬件I2C宏开关及设备对象创建已完成,剩下的则只需要替换内核中的bit_ops操作函数即可。

四、i2c-bit-ops操作函数替换

在rt thread项目根目录下的 rt-thread/components/drivers/i2c/ 目录下,有一个i2c-bit-ops.c文件,其中则保存了i2c驱动框架中注册的ops操作函数:

static rt_size_t i2c_bit_xfer(struct rt_i2c_bus_device *bus,struct rt_i2c_msg         msgs[],rt_uint32_t               num)
{struct rt_i2c_msg *msg;struct rt_i2c_bit_ops *ops = (struct rt_i2c_bit_ops *)bus->priv;rt_int32_t i, ret;rt_uint16_t ignore_nack;if (num == 0) return 0;for (i = 0; i < num; i++){msg = &msgs[i];ignore_nack = msg->flags & RT_I2C_IGNORE_NACK;if (!(msg->flags & RT_I2C_NO_START)){if (i){i2c_restart(ops);}else{LOG_D("send start condition");i2c_start(ops);}ret = i2c_bit_send_address(bus, msg);if ((ret != RT_EOK) && !ignore_nack){LOG_D("receive NACK from device addr 0x%02x msg %d",msgs[i].addr, i);goto out;}}if (msg->flags & RT_I2C_RD){ret = i2c_recv_bytes(bus, msg);if (ret >= 1){LOG_D("read %d byte%s", ret, ret == 1 ? "" : "s");}if (ret < msg->len){if (ret >= 0)ret = -RT_EIO;goto out;}}else{ret = i2c_send_bytes(bus, msg);if (ret >= 1){LOG_D("write %d byte%s", ret, ret == 1 ? "" : "s");}if (ret < msg->len){if (ret >= 0)ret = -RT_ERROR;goto out;}}}ret = i;out:if (!(msg->flags & RT_I2C_NO_STOP)){LOG_D("send stop condition");i2c_stop(ops);}return ret;
}
...
static const struct rt_i2c_bus_device_ops i2c_bit_bus_ops =
{i2c_bit_xfer,RT_NULL,RT_NULL
};

这段代码中实现了对每个i2c设备发送对应的i2c msg流程,将其修改为硬件i2c的发送方式:

static rt_size_t i2c_xfer(struct rt_i2c_bus_device *bus,struct rt_i2c_msg     msgs[],rt_uint32_t           num)
{rt_uint32_t i;struct rt_i2c_msg *msg;struct stm32_hard_i2c_config *Pconfig = rt_container_of(bus, struct stm32_hard_i2c_config, i2c_bus);fot(i = 0;i < num;i++){msg = &msgs[i];if(msg->flags & RT_I2C_RD){
#if defined(BSP_USING_DMA_I2C_RX)HAL_I2C_Master_Receive_DMA(Pconfig->pHi2c, (msg->addr)<<1, msg->buf, msg->len);rt_hw_us_delay(100);
#elseHAL_I2C_Master_Receive(Pconfig->pHi2c, (msg->addr)<<1, msg->buf, msg->len, 100);
#endif}else{
#if defined(BSP_USING_DMA_I2C_TX)HAL_I2C_Master_Transmit_DMA(Pconfig->pHi2c, (msg->addr)<<1, msg->buf, msg->len);rt_hw_us_delay(100);
#elseHAL_I2C_Master_Transmit(Pconfig->pHi2c, (msg->addr)<<1, msg->buf, msg->len, 100);
#endif}}return i;
}static const struct rt_i2c_bus_device_ops i2c_bit_bus_ops =
{i2c_xfer,RT_NULL,RT_NULL
};

参考软件i2c的发送方式,创建一个新的发送函数rt_size_t i2c_xfer(),并将rt_i2c_bus_device_ops 中对应的发送方式修改为此方式。

至此,硬件I2C的驱动则算是完成了一部分,可以通过与软件i2c一样的声明及挂载方式,将设备挂载到硬件I2C总线上。

五、Attention Please!

问题1:在上述的实现方式中,可以根据宏定义通过HAL_I2C_Master_Transmit()或HAL_I2C_Master_Transmit_DMA()方式发送I2C消息,但并未对是否发送成功做出判断。
问题2:ST官方的HAL库中,I2C发送消息共有三种方式,polling模式(轮询)、中断模式、DMA模式,HAL_I2C_Master_Transmit()则对应轮询模式,此模式相对于软件I2C虽然速率有所提升,但实际的提升效果其实不是特别大。而对于中断模式,则需要移植并实现对应的中断处理函数,可以按照参考文章2进行实现,不过笔者认为该篇需要注意的地方很多,比如在中断处理函数中释放信号量的操作,可能会造成一些隐患(可以直接去除信号量)。对于DMA模式,理论上也需要移植一些中断处理函数,但笔者目前没有用这种方式,所以也没有细究。所以理论上只能停留在polling模式。
问题3:在drv_hard_i2c.c中,INIT_BOARD_EXPORT(rt_hw_i2c_init);这个注册步骤,需要根据实际情况而定,如果想要使用DMA模式,则在此注册步骤之前,需要先注册MX_DMA_Init(),此函数为CubeMX生成,用来初始化DMA功能。中断模式同理。

六、总结

  目前看来,移植ST的硬件I2C驱动还是困难重重,所以笔者选则了更换平台(我逃避。。。)将oled的电路修改成了SPI模式,并更换了芯片平台,手头还有一个LPC54110和一个CH32的开发板,这两个板子的RTT BSP支持包好像有适配硬件I2C驱动,ST再见,希望下次回来有大佬适配了硬件I2C。

相关文章:

RT-Thread在STM32硬件I2C的踩坑记录

RT-Thread在STM32硬件I2C的踩坑记录 0.前言一、软硬件I2C区别二、RT Thread中的I2C驱动三、尝试适配硬件I2C四、i2c-bit-ops操作函数替换五、Attention Please!六、总结 参考文章&#xff1a; 1.将硬件I2C巧妙地将“嫁接”到RTT原生的模拟I2C驱动框架 2.基于STM32F4平台的硬件I…...

小白学Go基础01-Go 语言的介绍

Go 语言对传统的面向对象开发进行了重新思考&#xff0c;并且提供了更高效的复用代码的手段。Go 语言还让用户能更高效地利用昂贵服务器上的所有核心&#xff0c;而且它编译大型项目的速度也很快。 用 Go 解决现代编程难题 Go 语言开发团队花了很长时间来解决当今软件开发人员…...

Spring工具类--Assert的使用

原文网址&#xff1a;Spring工具类--Assert的使用_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍Spring的Assert工具类的用法。 Assert工具类的作用&#xff1a;判断某个字段&#xff0c;比如&#xff1a;断定它不是null&#xff0c;如果是null&#xff0c;则此工具类会报…...

无涯教程-Android - Absolute Layout函数

Absolute Layout 可让您指定其子级的确切位置(x/y坐标)&#xff0c;绝对布局的灵活性较差且难以维护。 Absolute Layout - 属性 以下是AbsoluteLayout特有的重要属性- Sr.NoAttribute & 描述1 android:id 这是唯一标识布局的ID。 2 android:layout_x 这指定视图的x坐标…...

2018ECCV Can 3D Pose be Learned from2D Projections Alone?

摘要 在计算机视觉中&#xff0c;从单个图像的三维姿态估计是一个具有挑战性的任务。我们提出了一种弱监督的方法来估计3D姿态点&#xff0c;仅给出2D姿态地标。我们的方法不需要2D和3D点之间的对应关系来建立明确的3D先验。我们利用一个对抗性的框架&#xff0c;强加在3D结构…...

干旱演变研究:定义及研究方法

在水文系统中,每个组分之间互相关联,包气带水、地下水和河川径流相互响应,水文循环处于动态平衡的状态。 降水作为水文系统的输入量,对水文循环具有重要的影响。降水短缺通过水文循环导致水文系统不同组分(包气带、地下水和地表水)发生干旱,降水不足导致土壤含水量减少,…...

【LeetCode-中等题】114. 二叉树展开为链表

文章目录 题目方法一&#xff1a;前序遍历&#xff08;构造集合&#xff09; 集合&#xff08;构造新树&#xff09;方法二&#xff1a;原地构建方法三&#xff1a;前序遍历--迭代&#xff08;构造集合&#xff09; 集合&#xff08;构造新树&#xff09; 题目 方法一&#x…...

【题解】JZOJ6645 / 洛谷P4090 [USACO17DEC] Greedy Gift Takers P

洛谷 P4090 [USACO17DEC] Greedy Gift Takers P 题意 n n n 头牛排成一列&#xff0c;队头的奶牛 i i i 拿一个礼物并插到从后往前数 c i c_i ci​ 头牛的前面&#xff0c;重复无限次&#xff0c;问多少奶牛没有礼物。 题解 发现若一头牛无法获得礼物&#xff0c;那么它后…...

Vue 项目中的错误如何处理的?

1、 组件中的处理&#xff1a;使用 errorCaptured 钩子 作用&#xff1a;可以捕获来自后代组件的错误 父组件(errorCaptured) -> 子组件 (errorCaptured) -> 当孙子组件出错时&#xff0c;错误会一直向上抛&#xff0c;也就是先触发子组件的 errorCaptured&#xff0c;…...

网络分层的真实含义

复杂的程序都要分层&#xff0c;这是程序设计的要求。比如&#xff0c;复杂的电商还会分数据库层、缓存层、Compose 层、Controller 层和接入层&#xff0c;每一层专注做本层的事情。 当一个网络包从一个网口经过的时候&#xff0c;你看到了&#xff0c;首先先看看要不要请进来…...

RT-Thread 线程间同步

线程间同步 在多线程实时系统中&#xff0c;一项工作的完成往往可以通过多个线程协调的方式共同来完成&#xff0c;那么多个线程之间如何 “默契” 协作才能使这项工作无差错执行&#xff1f;下面举个例子说明。 例如一项工作中的两个线程&#xff1a;一个线程从传感器中接收…...

Python元类再解释

Python元类再解释 元类是什么&#xff1f; 你可以把元类看作是“生产类的工厂”。就像类是用来生产对象的&#xff0c;元类是用来生产类的。 为什么需要元类&#xff1f; 考虑一个场景&#xff1a;假设你正在编写一个框架&#xff0c;你希望框架中的所有类都有某些特定的方…...

常用的Spring Boot 注解及示例代码

简介&#xff1a;Spring Boot 是一个用于快速构建基于 Spring 框架的应用程序的工具&#xff0c;通过提供一系列的注解&#xff0c;它使得开发者可以更加轻松地配置、管理和控制应用程序的各种行为。以下是一些常用的 Spring Boot 注解&#xff0c;以及它们的功能和示例代码&am…...

react app教程

react app教程 环境准备 下载node 下载npx npm install npx创建app npx create-react-app automedia cd automedia npm start构建发布版本 npm run build安装调试工具 # .vscode/launch.json {// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了…...

在vue项目中用vue-watermark快捷开发屏幕水印效果

我们先引入一个第三方依赖 npm install vue-watermark然后 因为这只是个测试工具 我就直接代码写 App.vue里啦 参考代码如下 <template><div><vue-watermark :text"watermarkText"></vue-watermark><!-- 正常的页面内容 --></div…...

无涯教程-Android - Activity

Activity代表具有用户界面的单个屏幕&#xff0c;就像Java的窗口或框架一样。Android Activity 是ContextThemeWrapper类的子类。 如果您使用过C&#xff0c;C或Java编程语言&#xff0c;那么您一定已经看到您的程序从 main()函数开始。与之非常相似&#xff0c;Android系统以 …...

vue项目前端展示数学公式(在表格中渲染)

现有需求为 将实验数据录入表格中,需要表格呈现物理公式,使用Mathjax在vue2中 进行呈现 1.安装 npm i --save mathjax-vue 2.全局注册(main.js中) import MathJax, { initMathJax, renderByMathjax } from mathjax-vuefunction onMathJaxReady() {const el document.getEl…...

java八股文面试[数据库]——MySQL索引的数据结构

知识点&#xff1a; 【2023年面试】mysql索引的基本原理_哔哩哔哩_bilibili 【2023年面试】mysql索引结构有哪些&#xff0c;各自的优劣是什么_哔哩哔哩_bilibili...

python3.11教程2:基础数据类型(数字和字符串)、组合数据类型(集合、元组、列表、字典)

文章目录 五、基本数据类型5.1 整数和浮点数5.1.1 整数和浮点数的类型5.1.2 进制和进制转换5.1.3 round函数 5.2 运算符5.2.1 常用运算符、运算符函数和逻辑运算符5.2.2 位运算符5.2.3 运算符的优先级及其进阶使用 5.3 布尔类型5.4 字符串5.3.1 字符串的基本操作5.3.2 字符串函…...

剑指 Offer 44. 数字序列中某一位的数字(中等)

题目&#xff1a; class Solution { //本题单纯找规律&#xff0c;要注意通过n%digits来判断有几个位数为digits的数 public:int findNthDigit(int n) {long base 9, digits 1; //digits代表位数while(n-base*digits>0){ //该循环是为了确定目标数字所在…...

Omdia:2025年第一季度,东南亚手机市场下滑9%,但厂商利润率正在改善

Omdia最新研究显示&#xff0c;2026年第一季度东南亚智能手机市场出货量同比下降 9%&#xff0c;总量为 2160万部。然而&#xff0c;市场最值得关注的并非出货量下滑&#xff0c;而是平均售价&#xff08;ASP&#xff09;的变化&#xff1a;受存储成本上涨影响&#xff0c;2026…...

为 OpenClaw 配置 Taotoken 作为自定义 OpenAI 兼容供应商

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 为 OpenClaw 配置 Taotoken 作为自定义 OpenAI 兼容供应商 OpenClaw 是一个流行的开源 Agent 框架&#xff0c;它允许开发者通过配…...

昇腾CANN ascend-boost-comm:M×N 算子复用是怎么做到的

CANN 生态里 50 多个仓库&#xff0c;每个仓库有十几到几十个算子。这些算子之间存在大量公共功能&#xff1a;内存搬运算子需要数据切分、通信算子需要拓扑发现、融合算子需要 shape 推导。如果每个仓库各自实现一遍&#xff0c;代码膨胀的同时&#xff0c;任何一个公共功能的…...

不止于仿真:用MATLAB分析OFDM-QPSK系统抗噪声性能,这张误码率曲线图能告诉你什么?

从误码率曲线到系统优化&#xff1a;MATLAB深度解析OFDM-QPSK抗噪性能 在无线通信系统的设计与评估中&#xff0c;仿真分析是不可或缺的一环。当我们完成基础OFDM-QPSK系统的搭建后&#xff0c;如何从仿真结果中提取有价值的信息&#xff0c;进而指导系统优化&#xff1f;本文…...

[STM32U3] 【STM32U385RG 测评】02+调试串口1输出字符串

一&#xff1a;:STM32U385 串口知识分享 通用同步/异步收发器(USART) 这些设备有两个嵌入式通用同步接收器发送器(USART1和USART3)以及两个通用异步接收器发送器(UART4和UART5) 该USART提供了一个灵活的手段来执行全双工数据交换与外部设备需要一个行业标准的NRZ异步串行数据格…...

Wi-Fi/5G信号解码背后的数学:深入浅出图解LLR软解调原理

Wi-Fi/5G信号解码背后的数学&#xff1a;深入浅出图解LLR软解调原理 在数字通信的世界里&#xff0c;信号从发射端到接收端的旅程就像一场充满干扰的马拉松。当你的手机接收Wi-Fi或5G信号时&#xff0c;它获取的并不是完美的0和1序列&#xff0c;而是被噪声扭曲的"模糊版本…...

微生物网络分析终极指南:NetCoMi如何帮你3步构建复杂关联网络

微生物网络分析终极指南&#xff1a;NetCoMi如何帮你3步构建复杂关联网络 【免费下载链接】NetCoMi Network construction, analysis, and comparison for microbial compositional data 项目地址: https://gitcode.com/gh_mirrors/ne/NetCoMi 想揭开微生物群落中隐藏的…...

SOCD Cleaner:游戏按键智能优化工具,告别操作冲突的终极方案

SOCD Cleaner&#xff1a;游戏按键智能优化工具&#xff0c;告别操作冲突的终极方案 【免费下载链接】socd Key remapper for epic gamers 项目地址: https://gitcode.com/gh_mirrors/so/socd 在竞技游戏和动作游戏中&#xff0c;精准的操作响应是胜利的关键。然而&…...

GNSS模块教程:大夏龙雀 DX-GP21,从硬件接线到 NMEA 数据解析

在物联网、无人机、精准农业等场景中&#xff0c;高精度定位是核心需求。深圳大夏龙雀科技的 DX-GP21 作为一款多模多频 GNSS 模块&#xff0c;支持北斗、GPS、Galileo 等多系统联合定位&#xff0c;定位精度&#xff1c;1.0m&#xff0c;兼具低功耗、小尺寸特性&#xff0c;性…...

[具身智能-824]:人的大脑,如何实现高实时、多模态联合、发现表象背后的各种规律和层层叠叠的不同层次的语义的?

人脑实现&#xff1a;高实时响应 多模态融合 深挖底层规律 多层级语义解析 完整原理一、先总述核心机制人脑不是串行流水线&#xff0c;是并行分布式神经集群架构依靠分层神经通路 并行同步处理 经验记忆锚定 潜意识预推理&#xff0c;天然完成&#xff1a;毫秒级高实时、…...