• 发文章

  • 发资料

  • 发帖

  • 提问

  • 发视频

创作活动
0
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
返回

电子发烧友 电子发烧友

  • 全文搜索
    • 全文搜索
    • 标题搜索
  • 全部时间
    • 全部时间
    • 1小时内
    • 1天内
    • 1周内
    • 1个月内
  • 默认排序
    • 默认排序
    • 按时间排序
  • 全部板块
    • 全部板块
大家还在搜
  • Banana Pi BPI-CM6 计算模块将 8 核 RISC-V 处理器带入 CM4 外形尺寸

    Banana Pi BPI-CM6是一款紧凑型“计算模块”,本质上是一块小型高性能计算机板,它采用的是 RISC-V 处理器,而非更常见的 ARM 芯片。其处理器为 SpacemiT K1:一款八核 64 位芯片。该模块的设计与流行的 Raspberry Pi CM4(和 CM5)系列模块采用相同的尺寸和接口布局,这意味着理论上它可以安装到许多现有的 Raspberry Pi 型载板上(尽管某些功能可能只有在专门设计的载板上才能完全发挥)。 Banana Pi BPI-CM6 它有何特别之处? SpacemiT K1 SoC 采用基于 RISC-V 指令集的“开放式架构”,这使其对开源开发者极具吸引力,并具备长期的灵活性。除了常规的 CPU 功能外,该模块还集成了 Imagine IMG BXE-2-32 GPU(运行频率约为 819 MHz),用于图形处理和通用 GPU 加速任务。AI 加速器/NPU:该 SoC 的架构宣称可提供高达 2.0 TOPS 的 AI 计算能力(专为机器学习/AI 工作负载而设计)。因此,BPI-CM6 能够在一个小型、节能的模块中,不仅支持通用计算,还能支持边缘 AI、机器学习工作负载、计算机视觉(特别是摄像头模块)和机器人技术。 Banana Pi BPI-CM6 正面和背面接口 规格和硬件 这与该公司之前推出的BPI-SM9完全不同。它配备了一颗 8 核 SpacemiT K1 RISC-V 处理器,主频约为 1.6 GHz,以及 8 GB LPDDR4 内存(最大容量可达 16 GB)。此外,它还配备了多种板载 eMMC 存储方案,容量从 8GB 到 128GB 不等,因此可以根据需要定制轻量级和重型存储方案。该设备通过载板连接,并提供千兆以太网接口(采用 Realtek RTL8211F 芯片组)、Wi-Fi 和蓝牙(位于 SDIO 板上)。它还配备了 HDMI 1.4 和 MIPI-DSI 接口,并允许摄像头项目最多使用三个 MIPI-CSI 接口。 该模块提供一个 USB 3.0 端口和两个 USB 2.0 端口,以及一个用于 SSD 或扩展卡的 5 通道 PCIe 2.1 接口。开发人员还可以使用多达 10 个 UART 接口和丰富的 GPIO,适用于机器人、自动化和工业系统。BPI-CM6 尺寸仅为 40 × 55 毫米,采用紧凑的 CM4/CM5 外形尺寸,专为严苛环境而设计,工作温度范围为 -40 °C 至 +85 °C。由于它是一个没有板载连接器的计算模块,因此需要使用载板或 I/O 板才能访问其全部端口和功能。 BPI-CM6 IO载板 BPI-CM6 可以做什么? BPI-CM6 体积小巧,硬件配置强大,适用于各种项目。它内置 AI 加速功能,并提供多种摄像头接口和显示选项,可满足智能摄像头、视觉系统和 AI 机器人等边缘 AI 应用的需求。此外,它还拥有强大的 I/O 接口、多个 UART 接口、千兆以太网接口以及宽广的工作温度范围,使其在工业自动化、SCADA 系统和物联网网关等领域具有显著优势。 该模块通过载板提供 PCIe 和以太网接口,可用于构建 NAS 设备、小型网络设备或轻量级服务器。其显示和 USB 功能也可供开发者用于制作小型 PC、自助服务终端和嵌入式 Linux 系统。对于 RISC-V 爱好者或其他开源硬件开发者而言,该模块提供了一个通用且均衡的平台,便于进行实验和原型设计。 BPI-CM6 IO载板正面和背面接口 为什么这很重要? BPI-CM6 采用 RISC-V 处理器设计,而非封闭的 ARM 架构,是迈向开放式架构计算的真正一步。这为开发者提供了更大的自由度、灵活性和长期发展空间,并让他们能够访问开放的指令集。它将 GPU、AI 加速器和丰富的 I/O 解决方案完美结合,在一个小型封装中提供了以往需要多个开发板才能实现的功能。此外,由于它兼容现有的 CM4 和 CM5 载板,因此对于已经身处 Raspberry Pi 生态系统并希望获得更高性能或 AI 功能的用户来说,BPI-CM6 也非常容易上手。BPI-CM6 拥有 100 kWh 的 RISC-V 六核处理器、200 kWh 的 AI 计算能力以及强大的连接性,所有这些都封装在一个易于识别的计算模块外形尺寸中,适用于机器人、工业控制、AI 边缘设备和嵌入式系统等领域。 BPI-CM6 的软件支持基于 Linux,官方提供了Ubuntu和 Debian 镜像, 内核和工具的源代码也可以在Banana Pi 的GitHub 页面和文档页面上找到。其核心功能稳定,而包括 GPU、NPU 和多媒体在内的其他生态系统组件,则仍在随着 RISC-V 架构的成熟而逐步完善。虽然它足以满足开发和嵌入式项目的需求,但开发者应该意识到这是一个不断发展的平台,而非一个已经完善、功能齐全的软件栈。 价格和供货情况 BPI-CM6 RISC-V 计算模块目前可通过Banana Pi 官方商城和AliExpress购买,单模块售价约为 67 美元,包含 I/O 载板的套装售价约为 84 美元。该模块库存有限,售完即止,并面向全球发货,可供开发者、爱好者以及对 RISC-V 硬件感兴趣的企业使用。

    2025-12-20 09:01

  • 飞凌嵌入式ElfBoard-文件的时间属性

    在前面的struct stat结构体中的参数基本介绍完毕,还有最后一个属性,是文件的时间属性,时间属性都包括如下内容:结构体字段说明struct timespec st_atim;文件最后访问时间struct timespec st_mtim;文件内容最后修改时间struct timespec st_ctim;文件状态最后状态改变时间文件最后访问时间:顾名思义是指最后一次读取文件内容的时间,如使用read函数读取文件的内容之后,此项时间就会发生变化。文件内容最后修改时间:指文件内容发生改变,如使用write函数向文件中写入数据,变会发生变化。文件状态最后被修改时间:状态更改时指文件的inode节点最后一次被修改的时间。那什么情况下inode节点话发生变化呢?下面就简单介绍下。1.文件内容被修改:通过函数对文件读写时,会更新inode节点的修改时间和状态更改时间。如函数fwirte()或者wirte()。2.文件属性修改:修改文件的权限、所有者或者文件名,就会更新inode节点的状态更改时间。如前面提到的chmod()、 chown()、 rename()函数。3.创建或删除文件:创建新的文件或者删除文件也会影响inode节点。如open()、creat()、unlink()、remove()。那么如何修改文件的时间属性呢?下面就来介绍4个修改时间属性的函数:修改时间属性的函数有utime()、utimes()、futimens()、utimensat()等函数,下表中为函数的基本区别:函数功能时间精度utime()设置文件的访问时间和修改时间秒级别utimes()设置文件的访问时间和修改时间微秒级别utimensat()设置文件的访问时间和修改时间纳秒级别futimens()设置已打开文件的访问时间和修改时间纳秒级别上面介绍了修改时间属性函数的基本区别,需要注意的是,不是所有的用户都可以修改文件的时间属性,只有超级用户和有效用户ID和文件用户ID相匹配的进程才会拥有修改权限。

    2025-12-20 08:43

  • RA4L1-SENSOR(01)Keil+RASC环境搭建

    本文重点面向习惯MDK开发环境的开发者及爱好者,文章尽量细致,主打一个小孩子也能学会:从Renesas发布的RASC代码生成工具出发,关注如何使用MDK完成在RA4L1上的第一个点灯及串口收发程序。 一、环境搭建 1.MDK的下载链接: MDK下载 :Keil MDK已经发布了社区版,可以免费非商用使用,不再需要用科技与狠活了。首先点击“Download μVision”,按照提示填写好个人信息并submit一下,在此下载安装即可。 RA4L1要求Keil MDK版本在5.38以上,下载时需要注意版本是否正确~ 具体安装教程可以参考网络资料。 2.RASC和keil固件包的下载链接: 国际用户或者可以科学上网的用户点这里:github-fsp 截图是GitHub上Renesas开发软件的发布页,我这边选取的是V5.9.0版本的,可以看到在“IAR or Keil MDK”段的文字描述后面有蓝色 超链接 ,根据你的系统选择对应超链接下载; 或者拉到V5.9.0词条下的最后面Assets,同时下载RASC软件和 MDK设备固件包 。 国内用户在此链接下载:gitee-开发工具 在此链接下载RA Smart Configurator V5.9.0和MDK Device Packs V5.9.0。 3.RASC的安装 双击下载完成的RASC安装包,待安装程序加载完成后,选择为所有用户安装。 在该界面上可以点击Change,修改文件安装路径;点击 Next ,许可协议也点击Next即可; 等待安装即可。 安装后来到本页面,点击OK完成安装,牢记你选择的安装路径。 同时点击下载的MDK_Device_Pack_vx.x.x.exe文件,按照提示完成安装,若无报错则环境配置没有问题。 4.环境验证及配置 打开 Keil 礦ision5 ,点击魔术棒,点击Device,如果能够搜索到Renesas的芯片,则MDK固件包的安装已经成功。 接着点击tool操作栏,选择 Customize Tools Menu, 设置两个快捷工具: 第一个是RA Smart Configurator,按照图示设置,其中Command中的地址填写你的rasc安装地址中的rasc.exe,我安装在E:\\\\Renesas文件夹中,其他参数照填即可: Initial Folder 填**P**,Argument填写 **--device D --compiler ARMv6 configuration.xml**; 右边勾选项第三项记得勾上; 同样的方式配置RA Device Partition Manager,路径和上述一致,都是填写你的rasc安装地址中的rasc.exe,Initial Folder 填**$P**,不同的是 Argument填写 -application com.renesas.cdt.ddsc.dpm.ui.dpmapplication configuration.xml \"$L%L\"; 配置完成后在Tools→RA Smart Configurator中即可看到RASC工具。 注:在没有RA项目时,点击该项不会有任何响应。后面会经常使用该选项,用于在keil中唤起RASC来修改配置。 二、使用RASC新建第一个RA4L1的MDK工程 首先打开下载的RASC配置工具: 打开应用后,我们点击File→New→FSP Project,新建一个工程。 填入你的工程名,这里填入的是FSP_RA4L1_test,选择你的工程路径,最好不要有中文;完成填写后点击Next; 第二个界面,要求我们选择FSP版本、开发板、器件、IDE等信息, 我们选择Device为** RA4L1BD4CFP ,语言为 C **,IDE Project Type 为 Keil MDK Version 5 。 选择后应该如下图所示: 下一个页面是TrustZone 的选择,我们先选择Non-TrustZone即可。 这个页面可以跳过; RTOS(实时操作系统):我们先选择不用RTOS; 选择Bare Metal - Minimal,点击Finish,生成工程文件。 生成后的窗口如下: 注意到板载晶振为8MHz,首先点击Clock,将外部高速时钟频率更改为8MHz; 接下来我们尝试来驱动这块开发板上的一些设备: 我们不难发现,在板上有三颗用户LED,分别是 LED1、LED2、LED3 ;以及一路的串口转USB芯片。 要确定他们连接哪个管脚,我们必须找到开发板的原理图。原理图下载地址:RA4L1_SENSOR-V1.1 查原理图可得,管脚与其连接的设备名有如下对应关系: LED1 P609 LED2 P610 LED3 P601 TXD9 P109 RXD9 P110 我们首先来配置LED的驱动管脚。 首先切到Pins,在Ports→P6,双击打开 P601、P609、P610 ,分别将Mode切换成**Output mode(输出模式),**可以根据自己的需要,将三个引脚的别名分别改成 LED3,LED1,LED2 。 到此GPIO的输出模式就配置好了。 接下来我们来配置UART接口。 我们切换到Stacks栏,在右上角的 New Stack → Connectivity → UART (r_sci_uart) 载入 UART外设。 载入后的效果如下: 我们点击刚生成的r_sci_uart(),点击左下角的Properties,点击Settings打开UART设置。 首先配置下面的几个参数: Baud Rate(波特率)默认是115200,可以根据自己的需要更改成9600或者其他。 接着点击RXD9或者TXD9的,后面会出现一个小箭头按钮,点击它会自动跳转到Pins的Connectivity→SCI9下,我们将OperationMode更改成 Asynchronous UART (异步UART通信),RASC会自动设置RXD9和TXD9两个引脚。 设置完引脚之后回到BSP→Properties,来更改堆大小:将 RA Common → Heap size 改成0x2000,用于串口通信。 一切完成后点击右上角的Generate Project Content,生成 Keil 工程。 来到我们刚刚选择的项目文件路径,忘记保存在哪里的话可以到Summary窗口查看,可以看到我们的项目文件已经创建成功。 这边点击文件夹下的uvprojx文件,即可用Keil打开。 在Keil中,因为我们需要使用到Printf函数向串口发送数据,所以我们应该打开MicroLIB。点击小魔法棒打开配置页,点击Target→勾选Use MicroLIB即可。 以上操作完成后我们就可以开始写代码了! 三、编写代码 1.编译验证 进入工程后,我们首先点击左上角的一个小箭头向下的图标(编译)进行编译,确保工程文件没有错误。 不出意外还是出意外了,我们收到了一个报错:未定义标识符USER_UART_Callback。 这是因为这个函数在hal_data.h中声明,但是并没有在hal_entry.c等其他文件中被定义,编译器并不能找到这个函数,所以报错。我们需要先定义这个函数: 首先打开hal_entry.c文件,右键代码第一行的#include \"hal_data.h\",打开包含的这个头文件。 我们可以看到有关USER_UART_Callback的定义, 我们复制其中的 void USER_UART_Callback(uart_callback_args_t * p_args); 到hal_entry.c中粘贴。函数的内容我们后面再实现。 进行编译,我们可以看到已经没有Error了,说明工程没有问题。Warning警告是因为USER_UART_Callback串口回调函数的指针参数没有在函数中进行调用,此处可以直接忽略。 2.库函数的用户文档参考 我们目前并不知道GPIO的驱动函数有哪一些,所以需要先找到可靠的途径了解FSP库函数。这里介绍两种方案: 第一种是在Keil下的Functions窗口, 我们在编译后可以看到所有包含在工程内的头文件。点击文件左边的小加号,可以看到文件中定义的所有函数。 这种方法有个缺点,就是对于某个函数的作用或者参数的作用并不明确,可能需要额外时间去查找资料理解。 另一种方法是在RA Smart Configurator 里面的Help→FSP User\'s Manual,打开FSP固件包自带的用户手册。 用户手册的主界面如下: ①GPIO驱动 打开后我们先选择API Reference→BSP→BSP I/O access,可以查看已经封装好的GPIO操作函数:我们可以点击R_BSP_PinWrite跳转到写函数的介绍页上,可以看到这个函数的函数定义为: __STATIC_INLINE void R_BSP_PinWrite ( bsp_io_port_pin_t pin , bsp_io_level_t level ) 它有两个参数,一个是pin,填入我们需要驱动的管脚,一个是level,填入对应的管脚电平。其具体参数同样可以在文档中查询得到: 方法很简单,将鼠标移至参数前的参数类型,可以点击跳转到定义: 可以看到pin和level都是枚举类型, level有两种情况,分别是低电平和 高电平 ,可以用BSP_IO_LEVEL_LOW和BSP_IO_LEVEL_HIGH表示; pin有多种情况,代表不同端口不同管脚,遵循以下规则: BSP_IO_PORT_端口号PIN引脚号 比如P601这一引脚则表示为 BSP_IO_PORT_ 06 PIN 01**。** 同时,如果你给你的GPIO取了别名,比如 P601为LED3 ,此时也可以直接用LED3作为参数。 注意到R_BSP_PinWrite()函数中的说明有提及:GPIO会有PFS引脚功能选择保护,我们需要调用R_BSP_PinAccessEnable()才可以驱动GPIO管脚。 ②软件延时函数 在BSP→MCU Board Support Package→R_BSP_SofewareDelay可以看到软件延时函数的定义和作用: BSP_SECTION_FLASH_GAP void R_BSP_SoftwareDelay ( uint32_t delay , bsp_delay_units_t units ) 其中delay是要延迟的“单位”数量,units是你的单位,有三个参量,分别是 BSP_DELAY_UNITS_SECONDS, BSP_DELAY_UNITS_MILLISECONDS, BSP_DELAY_UNITS_MICROSECONDS 也就是秒,毫秒,微秒。 Note写明了这个函数调用了 R_CGC_SystemClockFreqGet(),以及使用它的注意事项、可能发生的延时不准确等问题,在深入应用时可以自行查看。 3.先来点个灯 使用PinWrite和Delay先点灯 如果你和我一样设置了P601的别名为LED3,那么下面R_BSP_PinWrite内的函数可以直接填写 LED3 ,否则需要写BSP_IO_PORT_06_PIN_01。 如果你的RASC已经关闭,同时你需要修改配置参数,你也可以在Keil中点击Tools→RA Smart Configurator中重新打开,修改你的配置。 在hal_entry(void)中的<span> </span>/* TODO: add your own code here */后面加入以下代码,实现LED灯循环闪烁: void hal_entry(void) { /* TODO: add your own code here */ R_BSP_PinAccessEnable(); while(1){ R_BSP_PinWrite(LED3,BSP_IO_LEVEL_HIGH); //驱动LED3为高电平 R_BSP_SoftwareDelay(500,BSP_DELAY_UNITS_MILLISECONDS); //延时 R_BSP_PinWrite(LED3,BSP_IO_LEVEL_LOW);//驱动LED3为低电平 R_BSP_SoftwareDelay(500,BSP_DELAY_UNITS_MILLISECONDS);//延时 } #if BSP_TZ_SECURE_BUILD /* Enter non-secure code */ R_BSP_NonSecureEnter(); #endif //这一部分是自带的,不用管 } 实验现象: 根据四、程序下载连接和配置,下载程序后,可以看到LED3循环闪烁。此处不赘述下载方法,萌新可以先完成整个程序后再进行下载。 4.启用串口 我们同样根据用户文档,因为我们要使用的是UART通信,先寻找有UART的关键词:依次点开 Modules→Connectivity→UART(r_sci_uart) ,或者在右上角的Search查找一下UART,点击后缀是r_sci_uart的那个选项,即可查看UART的用户文档。 用户文档有给出串口调用例程,我们可以模仿它: 通读一遍,若只需要实现串口发送信息,我们需要用到的函数主要有: R_SCI_UART_Open (uart_ctrl_t *const p_api_ctrl, uart_cfg_t const *const p_cfg); R_SCI_UART_Write (uart_ctrl_t *const p_api_ctrl, uint8_t const *const p_src, uint32_t const bytes); assert(条件表达式); 其工作逻辑是: 如果 “条件表达式” 的结果为 **真(非 0)** :`assert` 不做任何操作,程序继续执行。 如果 “条件表达式” 的结果为 **假(0)** :`assert` 会立即触发错误,打印错误信息(包括文件名、行号、失败的条件),并调用 `abort()` 终止程序运行。 如果串口工作正确,那么函数返回值都是FSP_SUCCESS,这个状态量可以在assert中调用,用于判断串口状态。 用到的状态量主要有: FSP_SUCCESS 通用状态量,判断程序成功执行 UART_EVENT_TX_COMPLETE TX(发送)完成状态量 依此来完善我们的程序: 定义两个全局变量: fsp_err_t err = FSP_SUCCESS; volatile bool uart_send_complete_flag = false; 补充回调函数,在回调函数中我们主要将自己定义的发送完成状态量设定为True: void USER_UART_Callback(uart_callback_args_t * p_args){ if(p_args->event == UART_EVENT_TX_COMPLETE){ uart_send_complete_flag = true; } } 为了能够使用printf函数,在Keil中我们需要对fputc函数进行重定向: 主要完成的是发送一个字节并且等待发送完成的操作。 int fputc(int ch, FILE *f){ err = R_SCI_UART_Write(&g_uart9_ctrl,(uint8_t *)&ch, 1); if(FSP_SUCCESS != err)__BKPT(); while(uart_send_complete_flag == false){}; uart_send_complete_flag = false; return ch; } 在hal_entry函数中主要加入以下代码: 一个是打开UART外设: err = R_SCI_UART_Open(&g_uart9_ctrl, &g_uart9_cfg); assert(FSP_SUCCESS == err); 另外一个是在while循环中加入printf函数循环输出,验证串口开启: while(1){ //其他代码 printf(\"你想打印的文字\"); } 完整的代码如下所示: void hal_entry(void) { /* TODO: add your own code here */ err = R_SCI_UART_Open(&g_uart9_ctrl, &g_uart9_cfg); assert(FSP_SUCCESS == err); R_BSP_PinAccessEnable(); while(1){ R_BSP_PinWrite(LED3,BSP_IO_LEVEL_HIGH); R_BSP_SoftwareDelay(500,BSP_DELAY_UNITS_MILLISECONDS); R_BSP_PinWrite(LED3,BSP_IO_LEVEL_LOW); R_BSP_SoftwareDelay(500,BSP_DELAY_UNITS_MILLISECONDS); printf(\"hello Renesas\"); } #if BSP_TZ_SECURE_BUILD /* Enter non-secure code */ R_BSP_NonSecureEnter(); #endif } 使用J-Link下载程序,将开发板右侧USB接口连接电脑, 打开串口调试工具(这里用SSCOM演示,放在文末,需要可以自行下载),选择CH340串口,打开串口后可以接收到printf的信息。 四、程序下载 首先准备一个J-Link调试器,我这里使用的是群友的J-LinkOB-RA4M2下载器,可以流畅下载和调试; 按照以下方式连接下载器和开发板: 其他版本的J-Link驱动器只需要管脚名称能对上即可,注意官方J-Link没有3V3或者VCC,而是VREF,默认此管脚不参与供电,因此需要用USB线对开发板供电; 下载最新版本的J-Link驱动,如果下载时遇到问题,可能是因为驱动版本太老或者调试器太老,尽量选择最新的: 接下来是配置Debug选项,选择J-LINK→Settings→Flash Download→勾选Reset and Run→点击Add,选择和RA4L1有关的Flash选项,再点击Add。 添加后的界面应该如下: 我们同时把RAM for Algorithm 的参数修改成如下值,然后点击确定: 我们首先点击编译,编译完成后点击LOAD按钮下载程序到开发板上。 等待下载完成,验证开发板现象:一为LED3闪烁,二为电脑串口能接收到开发板传输来的信息。 *附件:RA4L1_Test.zip*附件:RA4L1_SENSOR-V1.1.pdf

    2025-12-20 00:17

  • 急急急!我正在使用vivado2019.2,请帮忙生成一个项目。

    请帮忙生成一个项目:输入一个整数,输出该整数各位数之和,在触摸屏输入整数,在触摸屏输出计算结果。 使用的板子的family是Artix-7,package是fbg676,speed是-2,产生的代码和文件要求在vivado2019.2上严格正确执行,要求无报错。 能解决的大神请加QQ 2257993511,希望这两天解决。

    2025-12-19 23:17

  • 请教技术

    请问PN8145T用于530伏直流电压下其限流电阻为多少阻值为宜?

    2025-12-19 22:09

  • 【阿波罗STM32F767试用体验】+7.使用easybutton库实现按键的多功能操作(单击,双击,三击,长短按,组合按键)

    在嵌入式裸机开发中,经常有按键的管理需求,GitHub上已经有蛮多成熟的按键驱动了,但是这些开源的按键都有很多缺陷,比如常用到了0x1abin的MultiButton,它不支持组合按键,非常可惜。还有FlexibleButton 它也不支持组合按键,以及STM32的大佬写的lwbtn,它也不支持组合按键。因此我移植使用了这个开源的easybutton库,它具备上面3个按键库的所有优点,又具备组合按键,可以说是所有嵌入式按键的集大成者,有了它,所有的按键都能全部搞定,包括矩阵按键,单个按键,多个按键,静态的,动态的,甚至连电脑USB键盘上的所有按键都能搞定,按键个数无限,达到了巅峰!!!!! 可以看出easy_button功能是最全的,并且使用的RAM Size也是最小的,这个在键盘之类有很多按键场景下非常有意义。 本节主要讲解移植实现easybutton库在正点原子阿波罗STM32F767上移植的详细步骤,好了,开始讲解了。 1。从github网站https://github.com/bobwenstudy/easy_button上下载easybutton源代码 2。对于上面的按键库,我已经做好了封装,把按键的核心功能已经封装好了,下面来讲解 #include \"ebtn_APP.h\" #include \"ebtn.h\" #include \"ebtn_APP_Keys.h\" #include \"ebtn_APP_Event.h\" #include \"ebtn_APP_HAL.h\" /** *************************************************************************** @Brief easy_button初始化 @NOTE在主函数中调用 基于单按键和组合键配置列表循环查表自动初始化,通常无需手动配置 */ void ebtn_APP_Keys_Init(void) { // 1. 遍历所有按键枚举值,自动查表初始化 for (key_enum_t key_id = 0; key_id < KEYS_COUNT; key_id++) { // 查找按键配置表中是否有该按键的配置 ebtn_btn_param_t *params = &buttons_parameters; // 默认参数 // 在配置表中查找自定义参数 for (uint8_t i = 0; i < special_key_config_list_size; i++) { if (special_keys_list.key_id == key_id) { params = special_keys_list.params; // 使用配置表中的参数 break; } } // 初始化按键结构体 btn_array[key_id] = (ebtn_btn_t)EBTN_BUTTON_INIT(key_id, params); } // 2. 遍历所有组合键枚举值,自动查表初始化 for (uint8_t i = 0; i < COMBO_KEYS_COUNT; i++) { combo_key_enum_t combo_id = index_to_combo_id(i); // 查找组合键配置表中是否有该组合键的配置 ebtn_btn_param_t *params = &buttons_parameters; // 默认参数 // 在配置表中查找自定义参数 for (uint8_t j = 0; j < special_combo_key_list_size; j++) { if (special_combo_keys_list[j].combo_key_id == combo_id) { params = special_combo_keys_list[j].params; // 使用配置表中的参数 break; } } // 初始化组合键结构体,使用数组索引而非枚举值 btn_combo_array = (ebtn_btn_combo_t)EBTN_BUTTON_COMBO_INIT(combo_id, params); } // 3. 初始化easy_button库,使用实际数组大小 ebtn_init(btn_array, KEYS_COUNT, btn_combo_array, COMBO_KEYS_COUNT, Get_Key_State, ebtn_APP_Event); // 4. 遍历组合键配置表,自动添加组合键 for (uint8_t i = 0; i < combo_config_list_size; i++) { const combo_config_t *config = &combo_keys_config_list; // 转换组合键ID为数组索引 uint8_t index = combo_id_to_index(config->combo_key_id); if (index < COMBO_KEYS_COUNT) { // 向该组合键添加所有配置的按键 for (uint8_t k = 0; k < config->key_count; k++) { ebtn_combo_btn_add_btn(&btn_combo_array[index], config->keys[k]); } } } } /** *************************************************************************** @brief 处理按键事件,需要定期调用,建议以20ms为周期执行一次 @noteTick时基为1ms */ void ebtn_APP_Process(void) { ebtn_process(ebtn_APP_HAL.Get_Tick()); // 获取时间处理按键事件 } #ifndef EBTN_APP_H #define EBTN_APP_H #include <stdint.h> #ifdef __cplusplus extern \"C\" { #endif void ebtn_APP_Keys_Init(void); void ebtn_APP_Process(void); #ifdef __cplusplus } #endif #endif /* EBTN_APP_H */ 添加按键配置 #include \"ebtn_APP_Keys.h\" #include \"ebtn_APP_HAL.h\" /** *************************************************************************** @brief 定义默认按键参数结构体 @ref ebtn_APP_Keys.h */ ebtn_btn_param_t buttons_parameters = EBTN_PARAMS_INIT( DEBOUNCE_TIME,// 按下防抖超时 RELEASE_DEBOUNCE_TIME, // 松开防抖超时 CLICK_AND_PRESS_MIN_TIME, // 按键最短时间 CLICK_AND_PRESS_MAX_TIME, // 按键最长时间 MULTI_CLICK_MAX_TIME,// 连续点击最大间隔(ms) KEEPALIVE_TIME_PERIOD, // 长按报告事件间隔(ms) MAX_CLICK_COUNT// 最大连续点击次数 ); /* -------------------------------- 此处修改按键定义 -------------------------------- */ // 按键ID为 ebtn_APP_Keys.h 中的枚举定义,初始化函数会为所有枚举值初始化 /** *************************************************************************** @brief 按键列表结构体数组,用于将按键ID与GPIO引脚以及触发电平进行绑定, 同时使用查表检测,免去需要为每个按键手动添加检测方式 @note 此处填入所需的按键ID及其GPIO信息和触发时电平 */ key_config_t keys_config_list[] = { // 示例:四个按键 // 按键ID,GPIO端口, GPIO引脚, 触发电平 {KEY_1, GPIOA, GPIO_PIN_0, EBTN_ACTIVE_HIGH}, // B1按键 PA0, 高电平触发 {KEY_2, GPIOH, GPIO_PIN_3, EBTN_ACTIVE_LOW},// B2按键 PH3, 低电平触发 {KEY_3, GPIOH, GPIO_PIN_2, EBTN_ACTIVE_LOW},// B3按键 PH2, 低电平触发 {KEY_4, GPIOC, GPIO_PIN_13,EBTN_ACTIVE_LOW},// B4按键 PC13,低电平触发 }; /* --------------------------------- 此处修改组合键配置 -------------------------------- */ /** *************************************************************************** @brief 组合键配置表,定义每个组合键包含哪些单独按键 @note 支持自适应长度的按键数组,无固定长度限制 */ const combo_config_t combo_keys_config_list[] = { // 示例:四个组合键 //组合键ID, 按键1, 按键2, ..., 按键N COMBO_KEYS(COMBO_KEY_1, KEY_1, KEY_2), COMBO_KEYS(COMBO_KEY_2, KEY_1, KEY_3), COMBO_KEYS(COMBO_KEY_3, KEY_1, KEY_4), COMBO_KEYS(COMBO_KEY_4, KEY_2, KEY_3), COMBO_KEYS(COMBO_KEY_5, KEY_2, KEY_4), COMBO_KEYS(COMBO_KEY_6, KEY_3, KEY_4), }; /* -------------------------------- 此处修改可选参数 -------------------------------- */ /** *************************************************************************** @brief 按键单独参数配置表(可选) @note 只为需要单独参数的按键配置,未配置的按键将自动使用默认的 buttons_parameters @note 如果所有按键都使用默认参数,此表可以为空/ const special_key_list_t special_keys_list[] = { / --------------------------------- 此处配置需要单独参数的按键 -------------------------------- */ // 示例:只配置需要单独参数的按键 // 方式1:使用单独参数配置宏(推荐) // KEY_SPECIAL_CONFIG(KEY_2, fast_response_parameters),// KEY_2使用快速响应参数 // KEY_SPECIAL_CONFIG(KEY_4, slow_response_parameters),// KEY_4使用慢速响应参数 // 方式2:手动配置 // {KEY_3, &special_parameters},// KEY_3使用单独参数 // 如果所有按键都使用默认参数,可以留空 // 初始化函数会为所有按键使用 buttons_parameters }; /** *************************************************************************** @brief 组合键单独参数配置表(可选) @note 只为需要单独参数的组合键配置,未配置的组合键将自动使用默认的 buttons_parameters @note 如果所有组合键都使用默认参数,此表可以为空/ const special_combo_key_list_t special_combo_keys_list[] = { / --------------------------------- 此处配置需要单独参数的组合键 -------------------------------- */ // 示例:只配置需要单独参数的组合键 // 方式1:使用单独参数配置宏(推荐) // COMBO_SPECIAL_CONFIG(COMBO_KEY_1, long_press_parameters),// COMBO_KEY_1使用长按参数 // COMBO_SPECIAL_CONFIG(COMBO_KEY_3, quick_combo_parameters), // COMBO_KEY_3使用快速组合参数 // 方式2:手动配置 // {COMBO_KEY_2, &special_combo_parameters},// COMBO_KEY_2使用单独组合参数 // 如果所有组合键都使用默认参数,可以留空 // 初始化函数会为所有组合键使用 buttons_parameters }; #include \"ebtn.h\" #include \"stdint.h\" #include \"main.h\" /* ------------------------------- 此处修改按键参数定义 ------------------------------- */ // 统一单位: ms;特殊值说明:0=不启用,0xFFFF=不限制上限(按库语义) #define DEBOUNCE_TIME 20 // 防抖:按下防抖超时 #define RELEASE_DEBOUNCE_TIME 20// 防抖:松开防抖超时 #define CLICK_AND_PRESS_MIN_TIME 20// 触发最短时间(小于该值不触发 Click/Press) #define CLICK_AND_PRESS_MAX_TIME 200 // 短按最长时间(超过则触发 Press;0xFFFF=不检查最大值) #define MULTI_CLICK_MAX_TIME 200// 连击间隔超时(两个按键之间的最大间隔) #define KEEPALIVE_TIME_PERIOD 500 // 长按周期(每周期增加 keepalive_cnt) #define MAX_CLICK_COUNT 3// 最大连续短击次数(0=不检查连击) /* -------------------------------- 此处修改按键ID定义 -------------------------------- */ /** *************************************************************************** * @brief 按键ID枚举 */ typedef enum { // 示例:四个按键 KEY_1 = 0, KEY_2, KEY_3, KEY_4, // 更多按键...... KEYS_COUNT // 最大按键,用于提供按键数量 } key_enum_t; #define COMBO_KEY_BASE 100 // 组合键ID基值,根据实际按键数量调整,避免与单键 ID 空间重叠 /** *************************************************************************** * @brief 组合键ID枚举 */ typedef enum { // 示例:四个组合键 COMBO_KEY_1 = COMBO_KEY_BASE, // 组合键基值,避免与单键 ID 空间重叠 COMBO_KEY_2, COMBO_KEY_3, COMBO_KEY_4, COMBO_KEY_5, COMBO_KEY_6, // 更多组合键...... MAX_COMBO_KEY // 最大组合键,用于提供组合键数量 } combo_key_enum_t; /* ---------------------------------- 自定义配置部分结束 ---------------------------------- */ /** *************************************************************************** * @brief 自定义按键配置结构体宏定义 */ typedef struct { key_enum_t key_id; // 按键按键ID GPIO_TypeDef *gpio_port; // GPIO端口 uint16_t gpio_pin; // GPIO引脚 uint8_t active_level; // 有效电平(EBTN_ACTIVE_LOW=低电平有效,EBTN_ACTIVE_HIGH=高电平有效) } key_config_t; /** *************************************************************************** * @brief 组合键配置结构体 */ typedef struct { combo_key_enum_t combo_key_id; // 组合键ID uint8_t key_count; // 组合键包含的按键数量 const uint8_t *keys;// 按键数组指针,支持自适应长度 } combo_config_t; /* -------------------------------- 查表配置结构体 -------------------------------- */ /** *************************************************************************** * @brief 按键配置表项结构体 */ typedef struct { key_enum_t key_id;// 按键ID ebtn_btn_param_t *params; // 按键参数指针 } special_key_list_t; /** *************************************************************************** * @brief 组合键配置表项结构体 */ typedef struct { combo_key_enum_t combo_key_id; // 组合键ID ebtn_btn_param_t *params;// 组合键参数指针 } special_combo_key_list_t; /* -------------------------------- 配置表辅助宏定义 -------------------------------- */ /** *************************************************************************** @brief 按键特殊参数配置宏 @note 用于配置需要特殊参数的按键 */ #define KEY_SPECIAL_CONFIG(key_id, params) {key_id, &params} /** *************************************************************************** @brief 组合键特殊参数配置宏 @note 用于配置需要特殊参数的组合键 */ #define COMBO_SPECIAL_CONFIG(combo_key_id, params) {combo_key_id, &params} /** *************************************************************************** @brief 通用可变参数宏,支持任意数量按键 */ #define COMBO_KEYS(combo_id, ...) { combo_id, sizeof((uint8_t[]){VA_ARGS}) / sizeof(uint8_t), (const uint8_t[]) { VA_ARGS } } /* ------------------------------ 组合键数量计算 ------------------------------ */ #define COMBO_KEYS_COUNT (MAX_COMBO_KEY - COMBO_KEY_BASE) // 实际组合键数量 /*------------------------------ 语义化有效电平定义 ------------------------------ */ #define EBTN_ACTIVE_LOW 0u// 低电平有效 #define EBTN_ACTIVE_HIGH 1u // 高电平有效 extern key_config_t keys_config_list[]; // 按键硬件配置数组 extern ebtn_btn_t btn_array[]; // 按键结构体数组 extern ebtn_btn_combo_t btn_combo_array[]; // 组合键结构体数组 extern const combo_config_t combo_keys_config_list[];// 组合键配置表 extern const special_key_list_t special_keys_list[]; // 按键配置表 extern const special_combo_key_list_t special_combo_keys_list[]; // 组合键配置表 extern const uint8_t btn_array_size;// 按键结构体数组大小 extern const uint8_t btn_combo_array_size;// 组合键结构体数组大小 extern const uint8_t keys_list_size;// 按键配置数组大小 extern const uint8_t combo_config_list_size; // 组合键配置表大小 extern const uint8_t special_key_config_list_size; // 按键特殊参数配置表大小 extern const uint8_t special_combo_key_list_size;// 组合键特殊参数配置表大小 // 添加组合键索引转换函数声明 static inline uint8_t combo_id_to_index(combo_key_enum_t combo_id); static inline combo_key_enum_t index_to_combo_id(uint8_t index); extern ebtn_btn_param_t buttons_parameters; // 默认按键参数 下面是添加按键的触发功能 #include \"ebtn_APP_Event.h\" #include \"ebtn_APP_Keys.h\" #include \"ebtn_APP_HAL.h\" /* ---------------------------- 此函数中可自定义按键状态检测方式 ---------------------------- */ /** *************************************************************************** @brief获取按键状态回调函数,每次执行ebtn_process都会调用此函数 此函数默认采用了查表检测,免去需要手动为每个按键添加检测方式 也可为特殊按键自定义检测方法 @note查表检测需要配合ebtn_custom_config.c中的按键配置结构体数组使用 @parambtn: easy_button按键结构体指针 @Return 按键状态,0表示未按下,1表示按下 */ uint8_t Get_Key_State(struct ebtn_btn *btn) { // 查表法检测 for (int i = 0; i < keys_list_size; i++) { if (keys_config_list.key_id == btn->key_id) { uint8_t pin_state = ebtn_APP_HAL.Read_Pin(keys_config_list.gpio_port, keys_config_list.gpio_pin); // 根据有效电平转换 if (keys_config_list.active_level == pin_state) { pin_state = 1; } else { pin_state = 0; } return pin_state; } } /* ------------------------------- 此处自定义特殊按键检测 ------------------------------ */ // 可自定义特殊按键的检测方式,如矩阵按键的检测 return 0; // 未找到按键ID,返回0 } /* ------------------------------- 此处自定义按键触发事件 ------------------------------ */ /** *************************************************************************** @brief按键事件处理回调函数,在此定义按键触发事件 @parambtn: easy_button按键结构体指针 @paramevt: 事件类型 */ void ebtn_APP_Event(struct ebtn_btn btn, ebtn_evt_t evt) { switch (btn->key_id) // 按键ID { / ---------------------------------- KEY1 ---------------------------------- / case KEY_1: / ---------------------------------- 按下按键时 --------------------------------- / if (evt == EBTN_EVT_ONPRESS) { } / ---------------------------------- 松开按键时 --------------------------------- / else if (evt == EBTN_EVT_ONRELEASE) { } / ----------------------------- 短按按键时(可获取连击次数) ----------------------------- / else if (evt == EBTN_EVT_ONCLICK) { / ----------------------------------- 单击时 ---------------------------------- / if (btn->click_cnt == 1) { printf(\"按键1单击\\\\r\\\\n\"); } / ----------------------------------- 双击时 ---------------------------------- / else if (btn->click_cnt == 2) { printf(\"按键1双击\\\\r\\\\n\"); } / ----------------------------------- 三击时 ---------------------------------- / else if (btn->click_cnt == 3) { printf(\"按键1三击\\\\r\\\\n\"); } } / ------------------------- 长按达到最短时间(配置默认500ms),触发长按计数时 ------------------------ / else if (evt == EBTN_EVT_KEEPALIVE) { / ------------------------------- 长按计数到达指定值时 ------------------------------- / if (btn->keepalive_cnt == 1) { printf(\"按键1长按\\\\r\\\\n\"); } } break; / ----------------------------------- KEY2 ---------------------------------- / case KEY_2: / ---------------------------------- 按下按键时 --------------------------------- */ if (evt == EBTN_EVT_ONPRESS) { } /* ---------------------------------- 松开按键时 --------------------------------- */ else if (evt == EBTN_EVT_ONRELEASE) { } /* ----------------------------- 短按按键时(可获取连击次数) ----------------------------- */ else if (evt == EBTN_EVT_ONCLICK) { /* ----------------------------------- 单击时 ---------------------------------- */ if (btn->click_cnt == 1) { printf(\"按键2单击\\\\r\\\\n\"); } /* ----------------------------------- 双击时 ---------------------------------- */ else if (btn->click_cnt == 2) { printf(\"按键2双击\\\\r\\\\n\"); } /* ----------------------------------- 三击时 ---------------------------------- */ else if (btn->click_cnt == 3) { printf(\"按键2三击\\\\r\\\\n\"); } } /* ------------------------- 长按到达最最短时间(默认500ms),触发长按计数时 ------------------------ */ else if (evt == EBTN_EVT_KEEPALIVE) { /* ------------------------------- 长按计数到达指定值时 ------------------------------- */ if (btn->keepalive_cnt == 1) { printf(\"按键2长按\\\\r\\\\n\"); } } break; /* ----------------------------------- KEY3 ---------------------------------- */ case KEY_3: /* ---------------------------------- 按下按键时 --------------------------------- / if (evt == EBTN_EVT_ONPRESS) { } / ---------------------------------- 松开按键时 --------------------------------- / else if (evt == EBTN_EVT_ONRELEASE) { } / ----------------------------- 短按按键时(可获取连击次数) ----------------------------- / else if (evt == EBTN_EVT_ONCLICK) { / ----------------------------------- 单击时 ---------------------------------- / if (btn->click_cnt == 1) { printf(\"按键3单击\\\\r\\\\n\"); } / ----------------------------------- 双击时 ---------------------------------- / else if (btn->click_cnt == 2) { printf(\"按键3双击\\\\r\\\\n\"); } / ----------------------------------- 三击时 ---------------------------------- */ else if (btn->click_cnt == 3) { printf(\"按键3三击\\\\r\\\\n\"); } } /* ------------------------- 长按到达最最短时间(默认500ms),触发长按计数时 ------------------------ */ else if (evt == EBTN_EVT_KEEPALIVE) { /* ------------------------------- 长按计数到达指定值时 ------------------------------- */ if (btn->keepalive_cnt == 1) { printf(\"按键3长按\\\\r\\\\n\"); } } break; /* ----------------------------------- KEY4 ---------------------------------- */ case KEY_4: /* ---------------------------------- 按下按键时 --------------------------------- / if (evt == EBTN_EVT_ONPRESS) { } / ---------------------------------- 松开按键时 --------------------------------- / else if (evt == EBTN_EVT_ONRELEASE) { } / ----------------------------- 短按按键时(可获取连击次数) ----------------------------- / else if (evt == EBTN_EVT_ONCLICK) { / ----------------------------------- 单击时 ---------------------------------- / if (btn->click_cnt == 1) { printf(\"按键4单击\\\\r\\\\n\"); } / ----------------------------------- 双击时 ---------------------------------- / else if (btn->click_cnt == 2) { printf(\"按键4双击\\\\r\\\\n\"); } / ----------------------------------- 三击时 ---------------------------------- */ else if (btn->click_cnt == 3) { printf(\"按键4三击\\\\r\\\\n\"); } } /* ------------------------- 长按到达最最短时间(默认500ms),触发长按计数时 ------------------------ */ else if (evt == EBTN_EVT_KEEPALIVE) { /* ------------------------------- 长按计数到达指定值时 ------------------------------- */ if (btn->keepalive_cnt == 1) { printf(\"按键4长按\\\\r\\\\n\"); } } break; /* ----------------------------------- 组合键1 ---------------------------------- */ case COMBO_KEY_1: /* ---------------------------------- 按下组合按键1时 --------------------------------- / if (evt == EBTN_EVT_ONPRESS) { // 组合键1的处理逻辑 printf(\"按下组合按键1\\\\r\\\\n\"); } / ---------------------------------- 松开按键时 --------------------------------- */ else if (evt == EBTN_EVT_ONRELEASE) { } break; /* ---------------------------------- 组合键2 ---------------------------------- */ case COMBO_KEY_2: /* ---------------------------------- 按下按键时 --------------------------------- / if (evt == EBTN_EVT_ONPRESS) { // 组合键2的处理逻辑 printf(\"按下组合按键2\\\\r\\\\n\"); } / ---------------------------------- 松开按键时 --------------------------------- */ else if (evt == EBTN_EVT_ONRELEASE) { } break; /* ---------------------------------- 组合键3 ---------------------------------- */ case COMBO_KEY_3: /* ---------------------------------- 按下按键时 --------------------------------- / if (evt == EBTN_EVT_ONPRESS) { // 组合键3的处理逻辑 printf(\"按下组合按键3\\\\r\\\\n\"); } / ---------------------------------- 松开按键时 --------------------------------- */ else if (evt == EBTN_EVT_ONRELEASE) { } break; /* ---------------------------------- 组合键4 ---------------------------------- */ case COMBO_KEY_4: /* ---------------------------------- 按下按键时 --------------------------------- / if (evt == EBTN_EVT_ONPRESS) { // 组合键4的处理逻辑 printf(\"按下组合按键4\\\\r\\\\n\"); } / ---------------------------------- 松开按键时 --------------------------------- */ else if (evt == EBTN_EVT_ONRELEASE) { } break; /* ---------------------------------- 组合键5 ---------------------------------- */ case COMBO_KEY_5: /* ---------------------------------- 按下按键时 --------------------------------- / if (evt == EBTN_EVT_ONPRESS) { // 组合键5的处理逻辑 printf(\"按下组合按键5\\\\r\\\\n\"); } / ---------------------------------- 松开按键时 --------------------------------- */ else if (evt == EBTN_EVT_ONRELEASE) { } break; /* ---------------------------------- 组合键6 ---------------------------------- */ case COMBO_KEY_6: /* ---------------------------------- 按下按键时 --------------------------------- / if (evt == EBTN_EVT_ONPRESS) { // 组合键6的处理逻辑 printf(\"按下组合按键6\\\\r\\\\n\"); } / ---------------------------------- 松开按键时 --------------------------------- */ else if (evt == EBTN_EVT_ONRELEASE) { } break; // 更多按键/组合键...... } } 在定时器中断里,每隔20ms调用按键处理函数 /* USER CODE BEGIN Header / /* @file: main.c @brief : Main program body @attention Copyright (c) 2025 STMicroelectronics. All rights reserved. This software is licensed under terms that can be found in the LICENSE file in the root directory of this software component. If no LICENSE file comes with this software, it is provided AS-IS. / / USER CODE END Header / / Includes ------------------------------------------------------------------*/ #include \"main.h\" #include \"adc.h\" #include \"usart.h\" #include \"gpio.h\" /* Private includes ----------------------------------------------------------/ / USER CODE BEGIN Includes */ #include <string.h> #include <stdio.h> #include \"TK8620.h\" #include \"ebtn_APP.h\" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------/ / USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------/ / USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* Private function prototypes -----------------------------------------------/ void SystemClock_Config(void); static void MPU_Config(void); / USER CODE BEGIN PFP */ union { float fdata; uint8_tdata[4]; }temp; /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------/ / USER CODE BEGIN 0 */ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance==USART3) { //UART_RX_TK8620(); } } /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------/ void SystemClock_Config(void); static void MPU_Config(void); / USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------/ / USER CODE BEGIN 0 */ #define V25 0.76 #define AVARAGE_SLOPE 0.0025 #define VSTEP 3.3/4096 uint32_t adcValue = 0; float temperature = 0; float readTemperature(uint32_t *adcValue) { float temperature; HAL_ADC_Start(&hadc1); if (HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY) == HAL_OK) { *adcValue = HAL_ADC_GetValue(&hadc1); } HAL_ADC_Stop(&hadc1); float vSense = (float) *adcValue * VSTEP; temperature = (vSense - V25) / (AVARAGE_SLOPE) + 25; return temperature; } /* USER CODE END 0 */ /** @briefThe application entry point. @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MPU Configuration--------------------------------------------------------*/ MPU_Config(); /* Enable the CPU Cache */ /* Enable I-Cache---------------------------------------------------------*/ SCB_EnableICache(); /* Enable D-Cache---------------------------------------------------------*/ SCB_EnableDCache(); /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals / MX_GPIO_Init(); MX_USART1_UART_Init(); MX_ADC1_Init(); MX_USART3_UART_Init(); / USER CODE BEGIN 2 */ ebtn_APP_Keys_Init(); //TK8620_Init(); //TK8620_Tx(); printf(\"easybutton 按键库测试\\\\r\\\\n\"); //uint32_t print_tick = HAL_GetTick(); /* USER CODE END 2 */ /* Infinite loop / / USER CODE BEGIN WHILE */ while (1) { // if (HAL_GetTick() - print_tick >= 300) // { //temp.fdata =readTemperature(&adcValue); //Send_AT_Data(temp.data, sizeof(temp.data)); //printf(\"STM32F767发送板子温度值给TKB8620: %f 度\\\\r\\\\n\",temp.fdata); // print_tick = HAL_GetTick(); // } //ebtn_APP_Process(); //HAL_Delay(10); /* if (HAL_GetTick() - print_tick >= 20) { ebtn_APP_Process(); print_tick = HAL_GetTick(); } */ /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** @brief System Clock Configuration @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Configure the main internal regulator output voltage */ __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); /** Initializes the RCC Oscillators according to the specified parameters in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLM = 25; RCC_OscInitStruct.PLL.PLLN = 432; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLQ = 2; RCC_OscInitStruct.PLL.PLLR = 2; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Activate the Over-Drive mode */ if (HAL_PWREx_EnableOverDrive() != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_7) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /* MPU Configuration */ void MPU_Config(void) { MPU_Region_InitTypeDef MPU_InitStruct = {0}; /* Disables the MPU */ HAL_MPU_Disable(); /** Initializes and configures the Region and the memory to be protected */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.BaseAddress = 0x0; MPU_InitStruct.Size = MPU_REGION_SIZE_4GB; MPU_InitStruct.SubRegionDisable = 0x87; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.AccessPermission = MPU_REGION_NO_ACCESS; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* Enables the MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } /** @briefThis function is executed in case of error occurrence. @retval None/ void Error_Handler(void) { / USER CODE BEGIN Error_Handler_Debug / / User can add his own implementation to report the HAL error return state / __disable_irq(); while (1) { } / USER CODE END Error_Handler_Debug / } #ifdef USE_FULL_ASSERT /* @briefReports the name of the source file and the source line number where the assert_param error has occurred. @paramfile: pointer to the source file name @paramline: assert_param error line source number @retval None */ void assert_failed(uint8_t file, uint32_t line) { / USER CODE BEGIN 6 / / User can add his own implementation to report the file name and line number, ex: printf(\"Wrong parameters value: file %s on line %d\\\\r\\\\n\", file, line) / / USER CODE END 6 / } #endif / USE_FULL_ASSERT */ 添加按键初始化 至此,easybutton库就移植完成了 打开串口助手 可以看到按键完成了单击,双击,三击,长短按,组合按键等功能! 详情见视频

    2025-12-19 20:56

  • 米尔RK3506核心板SDK重磅升级,解锁三核A7实时控制新架构

    在工业控制与边缘智能领域,开发者的核心需求始终明确:在可控的成本内,实现可靠的实时响应、稳定的通信与高效的开发部署。米尔电子基于RK3506处理器打造的MYC-YR3506核心板平台,近期完成了一次以“实时性”和“可用性”为核心的SDK战略升级,致力于将多核架构的潜力转化为工程师可快速落地的产品力。本次升级围绕两大主线展开:系统生态的多样化与实时能力的深度释放。我们不仅提供了从轻量到丰富的操作系统选择,更关键的是,通过软件架构优化,全面激活了芯片的异构实时控制潜能,帮助您在工业通信、运动控制与边缘计算场景中,构建性能、成本与可靠性平衡的解决方案。 一、按需选择:三重系统生态我们构建了覆盖从轻量定制到丰富生态的完整系统支持矩阵,让您的开发工作始于最合适的平台: Buildroot:轻量化定制之选,启动快、资源占用低,适合深度裁剪与批量部署。 Ubuntu 22.04 LTS:开发者可无缝接入Ubuntu庞大的软件仓库与成熟的开发运维生态,显著简化高级应用与服务的部署流程,加速产品迭代。 Yocto Project:提供深度适配的Layer,支持构建自主可控的企业级发行版,满足高安全与合规性要求。 二、AMP异构实时方案:低成本实现硬实时控制RK3506三核Cortex-A7架构是实时能力的核心。我们实现了非对称多处理(AMP)方案,允许将其中一颗Cortex-A7核隔离出来,独立运行实时操作系统RT-Thread。此架构实现了完美的任务隔离:两颗A7核运行通用Linux,处理网络、存储等复杂业务;被隔离的A7核则专司硬实时任务,确保微秒级响应。这为传统PLC、远程I/O、高速数据采集等场景提供了极具性价比的实时升级路径。 三、高端工业实时方案:RT-Linux与核隔离的强强联合为满足对系统级确定性和标准工业协议的顶级要求,我们提供了基于Linux内核增强的专业解决方案。 RT-Linux+ IGH EtherCAT:通过对标准Linux内核打入实时补丁(Preempt-RT),并集成开源IGH EtherCAT主站协议栈,使整个Linux系统具备微秒级的任务调度确定性,并能直接控制EtherCAT从站设备。此方案非常适合需要复杂运算且要求精确同步的多轴运动控制、高端PLC及机器人控制器。 CPU核隔离方案(与RT-Linux协同):此方案将实时性推向极致。通过配置,可将另一颗Cortex-A7物理核完全隔离,专用于运行最苛刻的裸机实时任务或安全监控程序,实现接近硬件极限的零干扰、零延迟性能。 RT实时性数据如下: 四、极速启动体验:2秒直达工作状态针对快速响应需求,我们实现了快速启动(Thunder Boot)模式。通过跳过uboot运行代码来缩短启动时间。系统从上电到文件系统可操作仅需约2秒,这一特性为自助终端、应急设备、快速部署的现场仪表带来显著价值提升。结语 本次MYD-YR3506 SDK升级,标志着该平台正式成为面向工业控制与确定性边缘计算的成熟之选。我们不仅提供了多样化的系统入口,更重要的是,通过AMP与RT-Linux两套实时方案,为从基础控制到高端运动控制的全场景需求,提供了清晰、可靠且高性价比的实现路径。

    2025-12-19 20:35

  • 发一款使用的国产Cameralink采集卡转USB3.0,便携式Cameralink采集卡,功能齐全才1千多

    鹰速光电公司的ES-CV-CLF-U3 项目 说明 产品型号 ES-CV-CLF-U3 输入接口 Cameralink Base/Medium/Full/Full 80;小型Mini SDR X2; 输入时钟 Cameralink并行端时钟支持:20-85Mhz 输出接口 UBS3.0 TYPE A母口 输出速率 MAX 330Mbytes/s 采集支持格式 黑白:MONO8/10/12/14/16; 彩色:BAYER8/YUV411/YUV422/RGB888/RGB30/RGB36等; 线阵/面阵图像类型可配置。 支持tap数 1/2/3/4/6/8/10 支持图像大小 各种任意分辨率,(图像宽度设置需要是4的整倍数) 单帧图像MAX = 500MBytes(例如:mono8分辨率为50000*10000) 分析功能 Cameralink接口FVAL、LVAL、DVAL、CLK信号时序分析统计 外触发 支持CC1控制相机触发功能 通讯 支持Cameralink UART与相机通讯功能 采集软件 基于WINDOWS的专用采集软件ESpeedGrab 驱动安装 专用USB3.0驱动安装,基于windows64位操作系统 二次软件开发 支持windows和linux下的二次开发,提供SDK和例程Demo; 供电 USB3.0直接供电5V,<900mA(无需额外电源) 体积 104X28.5X55mm 重量 约120g 工作温度 -40~75℃ 存储温度 -55~90℃ 采集分辨率支持任意分辨率,常见的分辨率如: 320X240,640X480,640X512,1024X768,1024X1024,1280X960,1920X1080,2048X1536,2048X2048,2560X1280,3840X2160,5120X5120等等。 Tap格式包括了所有AIA协会定义的Cameralink Base/Medium/Full/Full80标准格式,在此基础上,还包括了额外的特殊Cameralink格式。具体支持采集格式如下: 像素格式(PIXEL FORMAT) TAP****数(OF SEGMENT PER LINE) MONO8 / BAYER8 1/2/3/4/6/8/【FULL 80】10 MONO10 1/2/4/【FULL 80】8 MONO12 1/2/4 MONO14 1/2/4 MONO16 1/2/4 RGB888/BGR888 1/2 YUV444-8bit 1/2 YUV422/UVY422/YVU422/VUY422-8bit 1/2/4 YUV411/YVU411-8bit 2/4/【FULL 80】6 YUV422/UVY422-10bit 1/2 RGB30 1 BGR30 1 RGB36 1 BGR36 1 注:Cameralink协议里面定义的Tap具体含义,指的是一个时钟传输对应几个像素的含义。 l例如:RGB888,1tap格式下,1个Tap对应1个像素,占用的Cameralink通道里面的A、B、C这3个通道。 l例如:MONO14,1tap格式下,1个Tap对应1个像素,占用的Cameralink通道里面的A、B这2个通道。 基于WINDOWS的专用上位机软件:ESpeedGrab 。主体采集和显示界面如下: 提供相关软件功能如下。 图像实时播放显示; 图像原始像素数值查看; 输入时序检测,输入时序稳定性记录; 图像保存,支持BMP,JPEG,RAW,AVI视频; 采集格式设定,目前标准Cameralink支持的所有格式; 图像直方图实时显示; 图像白平衡、伪彩、拉伸、自动对比度拉伸等多种图像处理功能; 图像灰度均值、彩色均值、信噪比、动态范围、MTF等多项参数实时计算; 多窗口显示,同时看一张图像的不同部位细节、全景、颜色分量等; Cameralink****虚拟串口通讯功能; Cameralink CC1 PWM****触发参数设定; 两个SDR 26脚Mini插座,针脚顺序如下:

    2025-12-19 20:31

  • 【瑞萨RA6E2地奇星开发板试用】DHT11 测量温湿度

    使用瑞萨 RA6E2 微控制器,实现 DHT11 温湿度传感器的数据采集,并通过 I2C 接口的 OLED12864 显示屏实时显示数据。 硬件准备​ 核心板:瑞萨 RA6E2 地奇星开发板 传感器:DHT11 温湿度传感器 显示屏:I2C 接口 OLED12864(128×64 分辨率,SSD1306 驱动)​ 辅助工具:杜邦线、5V 电源(或开发板供电)、万用表(可选)​ 硬件接线表:​ RA6E2 引脚 DHT11 引脚 OLED12864 引脚 说明 ​ P403 DATA - 温湿度数据传输引脚 ​ P101 - SDA I2C 数据引脚 ​ P100 - SCL I2C 时钟引脚 ​ 注意:DHT11 的 DATA 引脚需串联 10K 上拉电阻。​​ 核心代码实现​ DHT11 数据读取函数​ DHT11 采用单总线通信协议,需严格遵循时序要求 DHT11 驱动代码如下: sret DHT11_Read(u8* dat) { u8 t, i, j, d, sum; if (dat == NULL) return SR_ERR_PARAM; DHT11_DAT_L; DHT11_DELAY_MS(19); // DHT11: Min: 18ms, type: 20ms, Max: 30ms; DHT22: Min: 0.8ms, type: 1ms, Max: 20ms DHT11_DAT_H; DHT11_DELAY(5); DHT11_WAIT(DHT11_DAT_R, 90); // DHT11: Min: 10us, type: 13us, Max: 35us; DHT22: Min: 25us, type: 30us, Max: 45us DHT11_WAIT(!DHT11_DAT_R, 180);// DHT11: Min: 78us, type: 83us, Max: 88us; DHT22: Min: 75us, type: 80us, Max: 85us DHT11_WAIT(DHT11_DAT_R, 190);// DHT11: Min: 80us, type: 87us, Max: 92us; DHT22: Min: 75us, type: 80us, Max: 85us sum = 0; d = 0; for (j=0; j<5; j++) { sum += d; d = 0; for (i=0; i<8; i++) { DHT11_WAIT(!DHT11_DAT_R, 120);// DHT11: Min: 50us, type: 54us, Max: 58us; DHT22: Min: 48us, type: 50us, Max: 55us DHT11_WAIT(DHT11_DAT_R, 150); // DHT11: 0: Min: 23us, type: 24us, Max: 27us; 1: Min: 68us, type: 71us, Max: 74us; DHT22: 0: Min: 22us, type: 26us, Max: 30us; 1: Min: 68us, type: 70us, Max: 75us d <<= 1; if (t > 45) d |= 0x01; } *dat++ = d; } if (sum != d) { return SR_ERR_CHECK; } DHT11_WAIT(!DHT11_DAT_R, 110);// DHT11: Min: 52us, type: 54us, Max: 56us; DHT22: Min: 45us, type: 50us, Max: 55us return SR_OK; } 总结​ 本文通过瑞萨 RA6E2 的 GPIO 和 I2C 外设,实现了 DHT11 温湿度采集与 OLED 显示功能。核心在于掌握 DHT11 的单总线时序和 OLED 的 I2C 通信协议,同时合理配置 FSP 驱动。

    2025-12-19 18:50

  • 高性能网络存储设计:NVMe-oF IP的实现探讨

    什么是 NVMe-oF? NVMe-oF全称:NVMe over Fabrics。 它允许主机通过网络访问远端。 NVMe SSD与本地 NVMe差异主要体现在: NVMe:基于 PCIe,Memory semantics NVMe-oF:基于 TCP/RDMA,Message semantics 我们根据以往NVMe和RDMA 开发经验,设计NVMe-oF IP。目标是将NVMe的低延迟特性延伸到网络中。 该IP系统架构如下: 它具有如下特点: ① 动态队列绑定(DynamicQueue Binding)机制 系统针对 NVMe SSD 的多队列并行特性,设计了 负载感知的动态队列绑定策略。 该机制能够根据 IO 类型、SSD 当前队列深度、任务并行度动态选择最优NVMe传输队列,避免队列热点(Queue Hotspot)与长尾延迟,有效提升NVMe层吞吐能力与指令并行度。在多流场景下仍可保持稳定低延迟表现。 ② 基于 UID 的 Capsule–NVMe解耦映射机制 NVMeoF层的 Capsule CID 与底层 NVMe HC 的物理 CID 完全解耦,通过唯一UID 建立中间映射,该机制实现了NVMeoF与NVMe之间的软硬件解耦、队列虚拟化、并行事务动态调度,显著提升系统可扩展性与调度灵活度。 ③ RAID0 横向扩展的多通道NVMe聚合架构 系统提供 面向高吞吐场景的 NVMeRAID0 横向扩展能力,通过多通道NVMe控制器并行访问多个SSD,实现以下技术特性: •多 SSD 带宽聚合,读写性能接近线性提升 •跨盘条带(Striping)调度优化,减少跨盘等待 •与 UID/动态队列绑定机制协同,避免单一SSD 成为瓶颈 整体架构支持跨 SSD 的 可扩展 IO 并行流水线,非常适合大带宽、多任务、NVMeoFTarget 场景。 ④ 多 Initiator 并发访问支持(Multi-Initiator Sharing) 系统从协议栈到调度机制均支持多个 Initiator(多个客户端)同时访问单个Target: •每个 Initiator拥有独立的RDMAQP 与 NVMeoF会话 •系统级别的UID 映射机制保证多Initiator隔离性 •动态队列绑定确保多Initiator的并发 IO 不发生队列竞争 •RAID0带宽聚合增强多Initiator的整体吞吐能力 适合云存储、虚拟化、多主机训练集群等多租户环境。 我们尝试fio测试,得到结果是: •Read:6472 MB/s •Write:6394MB/s 对这感兴趣的,可以看B站视频,给出如何测试,以及结果。 https://www.bilibili.com/video/BV1f6mbBeEiH/?spm_id_from=333.337.search-card.all.click&vd_source=c355545d27a44fe96188b7caefeda6e7

    2025-12-19 18:45