STM32移植U8g2库驱动OLED:源码精简与硬件适配实战

news2026/5/16 21:58:07
1. 项目概述与核心思路之前玩ESP8266的时候在Arduino环境下用U8g2库驱动OLED画点线面、显示文字确实方便。但很多实际项目尤其是对成本、功耗有要求的还是绕不开STM32这类更纯粹的MCU。最近有个小项目需要在STM32F103C8T6这块“蓝色小药丸”上驱动一块0.96寸的OLED屏显示复杂的菜单和图表自然就想到了功能强大的U8g2库。然而直接从GitHub上把U8g2的源码拖下来一股脑儿塞进Keil MDK工程里编译十有八九会报错不是内存爆了就是一堆未定义的符号。这很正常U8g2为了通用性源码包罗万象支持上百种驱动芯片和屏幕。我们的目标很明确让它在STM32上驱动我们手头这块SSD1306芯片的128x64 OLED跑起来无论是IIC还是SPI接口。所以这次移植的核心思路就两个字精简。我们要像外科手术一样从庞大的U8g2源码库中精准地剥离出我们需要的部分然后为STM32的硬件环境“量身定制”几个关键函数。整个过程我会把每一步的原理、为什么这么做、以及我踩过的坑都详细拆解出来。只要你手头有STM32的开发环境我用的是Keil MDK和一块OLED屏跟着做一定能点亮。2. U8g2库源码结构与精简策略U8g2的源码结构非常清晰但初次接触可能会觉得文件繁多。理解它的组织方式是成功移植的第一步。2.1 源码目录解析从GitHub克隆或下载的U8g2库我们主要关注csrc目录这里是纯C语言的实现也是STM32移植的基础。u8g2.h/u8x8.h: 核心头文件定义了所有的数据结构、函数原型和宏。u8g2_*.c: 一系列文件实现了U8g2层的高级API比如画图、字体渲染等。这部分我们通常全部保留因为它们是图形功能的核心。u8x8_*.c: 实现了U8x8层底层驱动抽象层的各类函数。我们需要重点关注的是u8x8_d_*.c显示驱动和u8x8_cad_*.c通信抽象。u8g2_d_setup.c: 这个文件包含了所有屏幕的初始化模板函数。比如u8g2_Setup_ssd1306_i2c_128x64_noname_f就是为我们这种屏幕准备的。这是第一个需要动刀子的地方。u8g2_d_memory.c: 这个文件管理显示缓冲区buffer的内存分配。这是第二个也是最重要的精简点处理不当直接导致编译失败。2.2 驱动文件的选择与删除我们的屏幕是SSD1306驱动128x64分辨率IIC接口。那么在csrc目录下找到所有u8x8_d_*.c文件。除了u8x8_d_ssd1306_128x64_noname.c或类似名称具体看你的屏幕驱动芯片和分辨率其他的都可以安全删除。例如u8x8_d_ssd1316.c,u8x8_d_sh1106.c等等都是给其他屏幕用的留着只会增加代码体积。同理u8x8_cad_*.c文件我们只需要保留与IIC通信相关的。对于软件IIC我们即将采用的方式保留u8x8_cad.c即可它内部通过回调调用我们的GPIO函数。对于硬件IIC或SPI则需要保留对应的u8x8_cad_ssd13xx_fast_i2c.c或u8x8_cad_001.c等。为了简单起见本次移植统一使用软件模拟所以只留u8x8_cad.c。注意这里“删除”指的是从你的STM32工程中移除这些源文件的引用或者将它们移到工程目录外。直接在源码包里删除原文件不是好习惯建议拷贝一份干净的U8g2csrc目录到你的项目里在里面进行操作。2.3 核心配置文件的精准裁剪这是减少代码体积和避免内存问题的关键。2.3.1 精简u8g2_d_setup.c打开这个文件你会看到几十个甚至上百个u8g2_Setup_xxx函数。我们的目标只有一个找到并保留u8g2_Setup_ssd1306_i2c_128x64_noname_f。其他所有函数都可以用预编译指令注释掉或者直接删除。为什么是_f后缀U8g2为不同内存需求的场景提供了不同缓冲区大小的初始化函数_1: 缓冲区128字节。适用于极低内存环境但需要配合分页绘制API使用复杂。_2: 缓冲区256字节。折中方案。_f: 缓冲区1024字节128*64/8。全帧缓冲区可以一次性在内存中构建完整画面然后一次性发送到屏幕编程最简单直观也是我们最常用的方式。对于STM32F103C8T620K RAM来说1KB的缓冲区完全能接受。所以保留u8g2_Setup_ssd1306_i2c_128x64_noname_f其他全部注释掉。最终这个文件应该看起来非常简洁#include “u8g2.h” /* ssd1306 f */ void u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb) { uint8_t tile_buf_height; uint8_t *buf; u8g2_SetupDisplay(u8g2, u8x8_d_ssd1306_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, byte_cb, gpio_and_delay_cb); buf u8g2_m_16_8_f(tile_buf_height); u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_vertical_top_lsb, rotation); }2.3.2 精简u8g2_d_memory.c—— 避免内存不足的关键这是最容易出错的一步。打开这个文件里面有很多u8g2_m_xx_xx_x函数。每个u8g2_Setup_xxx函数都会调用其中一个来分配缓冲区。我们的u8g2_Setup_ssd1306_i2c_128x64_noname_f调用了u8g2_m_16_8_f。因此我们必须只保留这一个函数其他所有函数都必须注释或删除。为什么链接器在链接时会把所有未被调用的函数也打包进最终的可执行文件除非开启了严格的函数级优化。如果保留了几十个内存分配函数它们每个都可能定义了一个静态缓冲区就像u8g2_m_16_8_f里的static uint8_t buf[1024]即使这些缓冲区没有被实际使用编译器也会为它们分配空间。这会在编译阶段就耗尽STM32F103C8T6那宝贵的20K RAM编译报错通常指向.bss或.data段溢出或者在链接阶段报错。精简后的u8g2_d_memory.c应该像这样#include “u8g2.h” uint8_t *u8g2_m_16_8_f(uint8_t *page_cnt) { #ifdef U8G2_USE_DYNAMIC_ALLOC *page_cnt 8; return 0; #else static uint8_t buf[1024]; // 这就是那个1KB的全帧缓冲区 *page_cnt 8; return buf; #endif } // 其他函数全部删除或注释掉 // uint8_t *u8g2_m_16_4_1(uint8_t *page_cnt) { … } // uint8_t *u8g2_m_16_4_2(uint8_t *page_cnt) { … } // …实操心得我第一次移植时就在这里栽了跟头编译顺利通过但链接时疯狂报错“.bss” segment overflow。排查了半天才发现是u8g2_d_memory.c里几十个缓冲区函数没删它们定义的静态数组加起来远远超过了20KB。所以这一步务必干净利落。3. 硬件接口的适配与驱动编写精简完源码接下来就要告诉U8g2如何与我们STM32的GPIO“对话”了。U8g2通过一个叫做u8x8_gpio_and_delay的回调函数来抽象所有硬件操作我们需要实现它。3.1 GPIO初始化首先根据你的硬件连接初始化IIC对应的两个GPIO引脚。这里以常见的PB6(SCL), PB7(SDA)为例使用推挽输出模式。// 根据自己的硬件连接修改引脚和端口 #define OLED_IIC_SCL_PIN GPIO_Pin_6 #define OLED_IIC_SDA_PIN GPIO_Pin_7 #define OLED_IIC_GPIO_PORT GPIOB #define OLED_IIC_GPIO_CLK RCC_APB2Periph_GPIOB void OLED_IIC_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(OLED_IIC_GPIO_CLK, ENABLE); GPIO_InitStructure.GPIO_Pin OLED_IIC_SCL_PIN | OLED_IIC_SDA_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(OLED_IIC_GPIO_PORT, GPIO_InitStructure); // 初始置高总线空闲 GPIO_SetBits(OLED_IIC_GPIO_PORT, OLED_IIC_SCL_PIN | OLED_IIC_SDA_PIN); }如果你用的是SPI接口那么就需要初始化SCK、MOSI、CS、DC命令/数据和RESET这几个引脚模式同样是推挽输出。3.2 实现u8x8_gpio_and_delay回调函数这是移植的核心。U8g2底层驱动u8x8会调用这个函数来实现所有延迟和GPIO控制。// 你需要实现的延时函数1微秒和1毫秒级别基于SysTick或定时器 extern void delay_us(uint32_t us); extern void delay_ms(uint32_t ms); uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { switch(msg) { // 延时消息 case U8X8_MSG_DELAY_NANO: // 延时 arg_int 纳秒通常用空指令实现 // 对于STM32通常不实现纳秒级延时留空或简单处理 break; case U8X8_MSG_DELAY_100NANO: // 延时 arg_int * 100 纳秒 // 同样简单处理或留空对IIC时序影响不大 break; case U8X8_MSG_DELAY_10MICRO: // 延时 arg_int * 10 微秒 delay_us(arg_int * 10); break; case U8X8_MSG_DELAY_MILLI: // 延时 arg_int 毫秒用于初始化等 delay_ms(arg_int); break; case U8X8_MSG_DELAY_I2C: // I2C速度延时arg_int单位是100kHz。例如400kHz时arg_int4 // 软件IIC下用于控制SCL高低电平的保持时间实现时序 // 这里我们用一个简单的微秒延时具体值需要根据IIC速度和CPU频率调整 delay_us(2); // 假设400kHz IIC半周期约1.25us这里给2us留有余量 break; // GPIO控制消息 (软件IIC) case U8X8_MSG_GPIO_I2C_CLOCK: // 控制SCL引脚, arg_int0输出低arg_int1输出高 if(arg_int) GPIO_SetBits(OLED_IIC_GPIO_PORT, OLED_IIC_SCL_PIN); else GPIO_ResetBits(OLED_IIC_GPIO_PORT, OLED_IIC_SCL_PIN); break; case U8X8_MSG_GPIO_I2C_DATA: // 控制SDA引脚, arg_int0输出低arg_int1输出高 if(arg_int) GPIO_SetBits(OLED_IIC_GPIO_PORT, OLED_IIC_SDA_PIN); else GPIO_ResetBits(OLED_IIC_GPIO_PORT, OLED_IIC_SDA_PIN); break; // 以下是一些菜单功能相关的GPIO如果不用可以返回0或1 case U8X8_MSG_GPIO_MENU_SELECT: case U8X8_MSG_GPIO_MENU_NEXT: case U8X8_MSG_GPIO_MENU_PREV: case U8X8_MSG_GPIO_MENU_HOME: u8x8_SetGPIOResult(u8x8, 0); // 假设没有连接这些按钮 break; default: u8x8_SetGPIOResult(u8x8, 1); // 默认返回1 break; } return 1; // 总是返回1表示成功 }关键点解析U8X8_MSG_DELAY_I2C: 这个延时决定了软件IIC的时钟速度。arg_int参数传递的是以100kHz为单位的IIC速度值。例如标准模式100kHz对应arg_int1快速模式400kHz对应arg_int4。我们实现的延时如delay_us(2)需要满足这个速度下高低电平的最小保持时间。如果屏幕初始化或通信不稳定可以尝试增大这个延时。U8X8_MSG_GPIO_I2C_CLOCK/DATA: 这就是软件模拟IIC的底层操作。U8g2的u8x8_byte_sw_i2c函数会通过调用这些消息配合延时一步步“拼”出完整的IIC起始、停止、发送字节、接收应答等时序。我们不需要自己写IIC协议只需要提供最基本的引脚高低电平控制。对于SPI接口的实现如果你的OLED是SPI接口那么u8x8_gpio_and_delay函数的内容会完全不同你需要处理U8X8_MSG_GPIO_SPI_CLOCK,U8X8_MSG_GPIO_SPI_DATA,U8X8_MSG_GPIO_CS,U8X8_MSG_GPIO_DC等消息并调用你写好的SPI底层发送函数。同时初始化函数也要换成u8g2_Setup_ssd1306_128x64_noname_f注意没有i2c字样。3.3 U8g2初始化封装函数编写一个初始化函数将前面精简好的设置组合起来。#include “u8g2.h” // 声明我们写好的回调函数 extern uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr); void u8g2_ssd1306_128x64_i2c_init(u8g2_t *u8g2) { // 关键初始化函数参数依次为 // u8g2: 上下文结构体 // U8G2_R0: 旋转方向0度不旋转 // u8x8_byte_sw_i2c: U8g2提供的软件IIC字节传输函数 // u8x8_gpio_and_delay: 我们刚实现的硬件抽象函数 u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_sw_i2c, u8x8_gpio_and_delay); u8g2_InitDisplay(u8g2); // 向OLED发送初始化序列硬件复位后必须调用 u8g2_SetPowerSave(u8g2, 0); // 唤醒OLED0打开显示1进入省电模式 u8g2_ClearBuffer(u8g2); // 清空内部显示缓冲区 }4. 工程集成与显示测试4.1 在Keil MDK中添加源码和头文件路径添加源文件在你的STM32工程中新建一个分组例如命名为U8g2。将我们精简后的csrc目录下所有.c文件除了那些明确删除的驱动文件添加到这个分组中。务必确认u8g2_d_setup.c和u8g2_d_memory.c是我们修改后的版本。添加头文件路径在项目的“Options for Target” - “C/C” - “Include Paths”中添加U8g2csrc目录的路径。这样编译器才能找到u8g2.h。4.2 主程序编写与测试在主函数中按顺序进行初始化然后就可以使用U8g2丰富的API进行绘制了。#include “stm32f10x.h” #include “delay.h” // 你的延时函数头文件 #include “u8g2.h” #include “oled_i2c.h” // 包含了你写的 GPIO_Init 和 u8x8_gpio_and_delay 等函数 u8g2_t u8g2; // 定义一个全局的U8g2上下文结构体 int main(void) { SystemInit(); // 系统初始化 delay_init(); // 延时初始化 OLED_IIC_GPIO_Init(); // 初始化IIC GPIO u8g2_ssd1306_128x64_i2c_init(u8g2); // 初始化U8g2和OLED // 测试绘制 u8g2_SetFont(u8g2, u8g2_font_ncenB08_tr); // 设置字体 u8g2_DrawStr(u8g2, 0, 12, “Hello U8g2!”); // 在坐标(0,12)绘制字符串 u8g2_DrawFrame(u8g2, 5, 20, 118, 40); // 画一个矩形框 u8g2_DrawCircle(u8g2, 64, 40, 15, U8G2_DRAW_ALL); // 画一个实心圆 // 将缓冲区内容发送到OLED显示 u8g2_SendBuffer(u8g2); while(1) { // 可以在这里实现动态刷新 // 例如使用 u8g2_FirstPage / u8g2_NextPage 循环进行动态绘图 // u8g2_FirstPage(u8g2); // do { // // 绘制代码 // } while ( u8g2_NextPage(u8g2) ); } }4.3 使用页面缓冲模式进行动态显示上面的例子用的是u8g2_SendBuffer它一次性发送整个1KB的缓冲区。对于动态画面更高效的方式是使用页面缓冲模式。void dynamic_draw_test(void) { char count_str[10]; static uint8_t count 0; u8g2_FirstPage(u8g2); // 开始页面循环 do { // 在循环内绘制每一帧的内容 u8g2_SetFont(u8g2, u8g2_font_logisoso16_tf); sprintf(count_str, “%03d”, count); u8g2_DrawStr(u8g2, 40, 30, count_str); u8g2_DrawRFrame(u8g2, 10, 10, 108, 44, 5); // 圆角框 } while ( u8g2_NextPage(u8g2) ); // 自动发送当前页并判断是否还有下一页 count; delay_ms(200); }在main的while(1)中调用dynamic_draw_test()你会看到一个不断递增的数字在屏幕上刷新。u8g2_FirstPage/u8g2_NextPage机制会自动管理缓冲区的分页发送比手动调用SendBuffer更适用于动画或频繁更新的界面。5. 常见问题排查与深度优化移植过程很少一帆风顺这里总结几个典型问题及解决方法。5.1 编译链接错误问题编译通过但链接时报错“.bss” segment overflow或“.data” overflow。原因几乎可以肯定是u8g2_d_memory.c文件没有精简干净里面多个静态缓冲区定义占用了大量RAM。解决再次彻底检查u8g2_d_memory.c确保只保留了u8g2_m_16_8_f这一个函数。问题提示undefined symbol u8x8_d_ssd1306_128x64_noname等未定义错误。原因对应的驱动源文件如u8x8_d_ssd1306_128x64_noname.c没有添加到工程中或者头文件路径不正确。解决检查工程文件列表和头文件包含路径。5.2 屏幕无显示或显示乱码问题屏幕完全不亮或者亮但显示雪花点、乱码。排查步骤硬件检查确认OLED的VCC、GND连接正确IIC的上拉电阻通常4.7K或10K是否接好。用逻辑分析仪或示波器抓一下SCL和SDA的波形是最直接的。初始化顺序确保先执行了u8g2_InitDisplay再执行u8g2_SetPowerSave(u8g2, 0)。顺序反了屏幕不会开启。IIC地址大部分0.96寸OLED的IIC地址是0x78写地址或0x7A。但有些可能是0x3C。U8g2的u8x8_d_ssd1306_128x64_noname驱动默认使用0x3C。如果不对需要修改驱动文件。在u8x8_d_ssd1306_128x64_noname.c文件中找到u8x8_d_ssd1306_128x64_noname这个结构体里面有一个i2c_address成员将其改为0x78。延时问题u8x8_gpio_and_delay中的U8X8_MSG_DELAY_I2C延时太短可能导致IIC时序不满足屏幕要求。尝试将delay_us(2)增大到delay_us(5)或delay_us(10)。缓冲区未发送绘制完成后忘记调用u8g2_SendBuffer或使用页面循环。绘图操作只是在内存缓冲区中进行必须发送才能显示。5.3 显示内容错位或镜像问题文字或图形显示的位置不对或者上下/左右镜像了。原因屏幕的扫描方向或COM引脚配置与驱动默认值不符。解决在初始化后调用U8g2的屏幕旋转或重映射命令。例如u8g2_SetDisplayRotation(u8g2, U8G2_R2); // 旋转180度 // 或者使用更底层的重映射命令 u8g2_SendF(u8g2, “c”, 0xA0); // 列地址重映射 u8g2_SendF(u8g2, “c”, 0xC0); // COM扫描方向重映射具体命令需要查阅SSD1306的数据手册。U8g2的u8g2_SendF函数可以直接发送原始命令到屏幕。5.4 性能优化与字体管理问题绘制复杂界面或大量文字时感觉慢。优化建议字体选择U8g2内置了大量字体但有些字体文件很大。使用u8g2_font_开头的字体是完整字体而u8g2_font_xxx_mr或u8g2_font_xxx_tr是经过裁剪的字体只包含ASCII字符集体积小很多。在u8g2.h附近有一个u8g2_fonts.c文件你可以只把你需要的字体文件加入工程而不是全部。减少全局刷新对于局部更新可以只清除和重绘发生变化的部分区域而不是每次都u8g2_ClearBuffer全屏重绘。提升IIC速度在保证稳定的前提下减小U8X8_MSG_DELAY_I2C的延时值。如果使用硬件IIC速度会有显著提升需要实现对应的u8x8_byte_hw_i2c回调。5.5 移植到其他STM32型号或开发环境从F103到其他系列如F4, H7:整个过程完全通用。只需要注意你的delay_us和delay_ms函数需要针对新的时钟频率进行调整。GPIO初始化部分的寄存器操作可能不同如果使用HAL库则调用HAL库函数。对于RAM更大的型号如F407你甚至可以不用精简u8g2_d_memory.c但为了代码整洁建议还是精简。在STM32CubeIDE或PlatformIO中移植步骤完全一致。添加文件到项目设置头文件路径。重点依然是那两个.c文件的精简和u8x8_gpio_and_delay函数的实现。这些IDE的构建系统可能对未使用的函数有更好的优化但手动精简是最保险的做法。移植U8g2到STM32本质上是一个“对接”工作将通用的图形库与特定的硬件平台连接起来。一旦打通了这个环节U8g2强大的图形和字体功能就能为你所用在小小的OLED上实现复杂的UI就不再是难题。整个过程最需要耐心和细心的地方就是源码的精简和硬件回调函数的调试。希望这份详细的记录能帮你少走弯路。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2619524.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…