尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
CH32F103C8T6+BMP280+OLED:I2C驱动气压传感器与显示实战
简介这份资源面向使用CH32F103C8T6RISC-V内核做嵌入式入门与传感器采集练习的开发者核心解决BMP280气压、海拔与芯片温度数据的读取及0.96寸OLED本地显示问题。硬件连接已明确BMP280走PB5/PB6的I2COLED走PB10/PB11便于快速对照接线复现。压缩包共168个文件、约4.63MB以31个c源文件与32个h头文件为主体配合31个o、31个crf等编译中间文件以及uvprojx、uvoptx工程配置、hex与axf固件、sct分散加载和启动汇编构成可直接打开的Keil完整工程。已有167人学习下载。读者可从中获得CH32外设驱动组织方式、I2C时序调试与传感器数据换算的参考实现并借助现成工程结构排查编译与下载环节的问题适合作为课程设计或自学练手的基础模板。1. 从一颗气压传感器说起这套 CH32 组合能干什么手里有一块 CH32F103C8T6 最小系统板一颗 BMP280 气压传感器再加一块 0.96 寸 OLED这三样凑一起能做出什么答案是一个能实时读出大气压强、温度、估算海拔的桌面小仪表。整套方案成本低、接线少、代码量可控适合刚接触国产 MCU 的开发者拿来练手也适合需要做气压监测原型的从业者快速验证。CH32F103C8T6 是青稞架构的 Cortex-M3 兼容芯片引脚和 STM32F103C8T6 基本对齐BMP280 走 I2C 或 SPI 都能读OLED 用 SSD1306 驱动I2C 四线接法最省事。这套组合的核心价值在于用一颗国产芯片把传感器采集、数据处理、屏幕显示串成完整链路代码跑通之后可以直接迁移到气象站、室内环境监测、无人机高度辅助等场景。下面从硬件接线、驱动移植、数据校准到显示刷新一步步拆开讲。2. 硬件接线与 I2C 地址分配先把物理层钉死2.1 三块板子的引脚对应关系CH32F103C8T6 的 I2C1 默认引脚是 PB6SCL和 PB7SDA这两个脚在最小系统板上通常已经引出。BMP280 模块和 OLED 模块都支持 I2C可以挂在同一条总线上前提是地址不冲突。BMP280 的 I2C 地址由 SDO 引脚决定SDO 接地时地址为 0x76接 VCC 时为 0x77。SSD1306 OLED 的地址通常是 0x3C 或 0x3D由模块背面的电阻选择。常见做法是把 BMP280 的 SDO 接地OLED 保持默认 0x3C这样一条总线上两个设备互不干扰。接线时注意电平匹配。CH32F103C8T6 的 IO 是 3.3V 电平BMP280 模块和 OLED 模块一般也支持 3.3V 供电直接连即可。如果手头的 OLED 模块只标了 5V 供电需要确认板上有没有稳压芯片没有的话不要直接接 3.3V 逻辑否则 I2C 通信会不稳定。上拉电阻方面BMP280 模块和 OLED 模块通常自带上拉总线并联后阻值会变小一般不影响通信但如果发现波形上升沿变缓可以在软件里把 I2C 速率降到 100kHz 以下。信号CH32 引脚BMP280OLEDSCLPB6SCLSCLSDAPB7SDASDAVCC3.3VVCCVCCGNDGNDGNDGND2.2 I2C 初始化的关键参数CH32F103C8T6 的 I2C 外设和 STM32 的寄存器布局高度相似标准库或 HAL 库都能用。初始化时重点盯三个参数时钟速率、应答使能、地址模式。下面是一段基于标准库风格的初始化代码实际工程里根据所用库调整函数名即可。/* I2C1 初始化100kHz7 位地址模式 */ void I2C1_Init(void) { GPIO_InitTypeDef gpio; I2C_InitTypeDef i2c; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); /* PB6/PB7 开漏复用输出外部上拉 */ gpio.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; gpio.GPIO_Mode GPIO_Mode_AF_OD; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, gpio); i2c.I2C_Mode I2C_Mode_I2C; i2c.I2C_DutyCycle I2C_DutyCycle_2; i2c.I2C_OwnAddress1 0x00; /* 主机模式本机地址随意 */ i2c.I2C_Ack I2C_Ack_Enable; i2c.I2C_AcknowledgedAddress I2C_AcknowledgedAddress_7bit; i2c.I2C_ClockSpeed 100000; /* 100kHz先求稳再提速 */ I2C_Init(I2C1, i2c); I2C_Cmd(I2C1, ENABLE); }这段代码里GPIO_Mode_AF_OD是关键I2C 必须用开漏复用模式配成推挽输出会导致总线锁死。I2C_ClockSpeed设 100kHz 是保守值BMP280 和 SSD1306 都支持 400kHz但长杜邦线或劣质模块在 400kHz 下容易出误码调试阶段先用 100kHz 把链路跑通稳定后再往上调。I2C_OwnAddress1在主机模式下不参与寻址填 0 即可但有些库要求填一个非零值按库的约定来。2.3 上电自检先扫地址再读寄存器接线完成后不要急着写业务代码先做一次总线扫描确认两个设备都能被寻到。BMP280 的芯片 ID 寄存器地址是 0xD0读出来应该是 0x58SSD1306 没有统一的 ID 寄存器但向 0x3C 发一个空写操作能收到 ACK 就说明在线。/* 简易 I2C 地址探测返回 0 表示收到 ACK */ uint8_t I2C_Probe(uint8_t addr) { uint32_t timeout 10000; I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)) { if (--timeout 0) return 1; } I2C_Send7bitAddress(I2C1, addr 1, I2C_Direction_Transmitter); timeout 10000; while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { if (--timeout 0) { I2C_GenerateSTOP(I2C1, ENABLE); return 1; } } I2C_GenerateSTOP(I2C1, ENABLE); return 0; }探测顺序建议先 0x76 再 0x3C两个都返回 0 才继续往下走。如果 0x76 不通优先查 SDO 引脚有没有虚焊如果 0x3C 不通查 OLED 模块背面的地址电阻。这一步花五分钟能省掉后面半小时的瞎猜。3. BMP280 驱动移植从原始寄存器到可用气压值3.1 补偿算法不能省原始值直接读出来是废的BMP280 输出的是未补偿的 ADC 原始值温度、气压都需要用芯片内部固化的校准系数做多项式补偿直接拿原始值换算出来的数字毫无意义。校准系数存在 0x88 到 0xA1 这一段寄存器里上电后一次性读出来存到结构体之后每次采集都用这组系数参与运算。常见做法是移植 Bosch 官方提供的补偿函数逻辑固定不需要自己推导。typedef struct { uint16_t dig_T1; int16_t dig_T2, dig_T3; uint16_t dig_P1; int16_t dig_P2, dig_P3, dig_P4, dig_P5; int16_t dig_P6, dig_P7, dig_P8, dig_P9; } BMP280_Calib; BMP280_Calib calib; void BMP280_ReadCalib(void) { uint8_t buf[24]; BMP280_ReadRegs(0x88, buf, 24); /* 一次性读完 0x88~0x9F */ calib.dig_T1 (buf[1] 8) | buf[0]; calib.dig_T2 (buf[3] 8) | buf[2]; calib.dig_T3 (buf[5] 8) | buf[4]; calib.dig_P1 (buf[7] 8) | buf[6]; calib.dig_P2 (buf[9] 8) | buf[8]; calib.dig_P3 (buf[11] 8) | buf[10]; calib.dig_P4 (buf[13] 8) | buf[12]; calib.dig_P5 (buf[15] 8) | buf[14]; calib.dig_P6 (buf[17] 8) | buf[16]; calib.dig_P7 (buf[19] 8) | buf[18]; calib.dig_P8 (buf[21] 8) | buf[20]; calib.dig_P9 (buf[23] 8) | buf[22]; }注意dig_T1和dig_P1是无符号的其余都是有符号拼字节时类型转换别搞反否则补偿结果会偏出几百帕。读校准系数之前要先让芯片进入正常模式也就是往 0xF4 寄存器写配置值否则读出来全是 0。3.2 采集配置过采样与滤波的取舍BMP280 的 0xF4 寄存器控制过采样和功耗模式0xF5 控制 IIR 滤波系数和待机时间。过采样越高数据越稳但转换越慢滤波系数越大输出越平滑但响应越迟钝。做桌面气压显示建议温度过采样 ×2、气压过采样 ×16、滤波系数 4这样每秒出一次数读数不跳。/* osrs_tx2, osrs_px16, normal mode */ BMP280_WriteReg(0xF4, (0x02 5) | (0x05 2) | 0x03); /* standby0.5ms, filter4 */ BMP280_WriteReg(0xF5, (0x00 5) | (0x02 2));0xF4的低两位是模式位0x03表示 normal 模式芯片会按待机周期自动循环采集不需要主机反复触发。0xF5的滤波系数 4 对应0x02如果发现气压值在空调出风口附近跳得厉害可以升到 8 或 16代价是数值变化滞后一两秒。海拔换算用国际气压公式以海平面 1013.25 hPa 为基准算出来的相对高度对室内场景够用绝对海拔需要当地基准气压修正。3.3 读取时序别在转换中途抢数据normal 模式下芯片自己循环转换主机读 0xF7 到 0xF9 三个字节拿气压0xFA 到 0xFC 拿温度。读取时连续读六个字节保证气压和温度来自同一次转换周期分开读会导致补偿时温度与气压不同步算出来的值有细微偏差。I2C 读多字节时注意在最后一个字节前发 NACK否则从机会一直占着 SDA 不放。void BMP280_ReadRaw(int32_t *raw_p, int32_t *raw_t) { uint8_t buf[6]; BMP280_ReadRegs(0xF7, buf, 6); *raw_p ((int32_t)buf[0] 12) | ((int32_t)buf[1] 4) | (buf[2] 4); *raw_t ((int32_t)buf[3] 12) | ((int32_t)buf[4] 4) | (buf[5] 4); }20 位原始值左移拼接时buf[2] 4取高四位低四位是无效的。这段代码跑通后把原始值丢进补偿函数温度精度能到 0.01℃气压精度在 ±1 hPa 以内对于桌面仪表完全够用。4. SSD1306 显示驱动把气压值刷到 0.96 寸屏上4.1 显存组织与页地址模式SSD1306 的 128×64 显存按页组织八页每页八行像素一列一个字节。写显存前要先设页地址和列地址再连续写数据。I2C 写 OLED 时每个字节前要加控制字节0x00 表示后面是命令0x40 表示后面是显存数据。很多新手在这里翻车把命令和数据混在一个传输里屏幕要么全亮要么全黑。void OLED_WriteCmd(uint8_t cmd) { I2C_Start(); I2C_WriteByte(0x3C 1); I2C_WriteByte(0x00); /* 控制字节命令 */ I2C_WriteByte(cmd); I2C_Stop(); } void OLED_WriteData(uint8_t *data, uint16_t len) { I2C_Start(); I2C_WriteByte(0x3C 1); I2C_WriteByte(0x40); /* 控制字节数据 */ while (len--) I2C_WriteByte(*data); I2C_Stop(); }初始化序列按 SSD1306 手册的推荐值走关显示、设时钟分频、设多路复用率 0x3F、开电荷泵、设寻址模式为页模式最后开显示。这一串命令顺序不能乱尤其是电荷泵使能0x8D 后跟 0x14漏了这一步屏幕不亮。4.2 字模与刷新策略局部刷新省总线0.96 寸屏刷一整屏 1024 字节100kHz I2C 下大约 100ms每秒刷一次可以接受但没必要。气压值变化慢只刷新数值所在的那几页就行。常见做法是把屏幕分成固定区域第一页显示标题第三页显示气压第五页显示温度第七页显示海拔。每次采集后只重写对应区域的显存其余页不动。/* 在指定页、指定列写一个 6x8 字符 */ void OLED_PutChar(uint8_t page, uint8_t col, char ch) { uint8_t buf[6]; memcpy(buf, font6x8[(ch - ) * 6], 6); OLED_SetPos(page, col); OLED_WriteData(buf, 6); }OLED_SetPos里发三条命令设页起始地址、设列低四位、设列高四位。列地址是 0 到 127页地址是 0 到 7。写数值时先把数字转成字符串再逐字符写注意留够位数气压值 5 位加小数点温度 4 位加小数点位数不够会残留上一次的旧字符看起来像乱码。4.3 显示内容组织单位与小数位气压显示建议用 hPa保留两位小数比如1013.25 hPa温度保留一位小数25.3 C海拔用米取整。单位符号在 6x8 字库里不一定有可以自己补几个自定义字符或者用字母代替比如hPa直接写三个字母。刷新频率控制在 2Hz 左右太快人眼读不清太慢又显得卡顿。如果发现屏幕有轻微闪烁检查是不是每次都在全屏清显存改成局部刷新就能消掉。5. 避坑与排查五个真实翻车点5.1 总线挂死程序卡在等待 ACK现象是程序烧进去之后停在while(!I2C_CheckEvent(...))里出不来。原因多半是某个设备没接好或者地址配错主机发地址后收不到 ACK状态机卡死。解决方法是给每个等待循环加超时计数超时后发 STOP 复位总线再重新初始化 I2C。另外检查 SDA/SCL 有没有接反杜邦线接触不良也会导致偶发挂死。5.2 气压值恒定不变或跳变剧烈恒定不变通常是模式寄存器没写对芯片还在 sleep 模式读出来的是上次残留值。跳变剧烈则是滤波系数太小或者过采样太低空调风一吹数值就飘。先把 0xF4 读回来确认低两位是 0x03再把 0xF5 的滤波系数调到 4 以上基本能压住。5.3 OLED 花屏或只亮一半花屏一般是显存写入时列地址越界或者控制字节用错。检查OLED_SetPos里列地址有没有超过 127页地址有没有超过 7。只亮一半常见于初始化序列里多路复用率设成了 0x1F 而不是 0x3F导致只驱动了前 32 行。把初始化命令逐条对照手册核一遍别凭记忆抄。5.4 温度正常但气压偏差几十帕补偿算法里dig_P1被当成有符号数处理或者拼字节时高低位颠倒都会导致气压系统性偏移。用一组已知的原始值和校准系数手算一遍补偿结果和芯片输出对比能快速定位是系数读取错误还是公式移植错误。另外确认海拔换算用的基准气压是不是 1013.25用错基准会整体偏移。5.5 上电偶尔不启动复位才好电源上升沿太慢或者去耦电容不足CH32 和传感器上电时序不一致I2C 初始化时从机还没准备好。在初始化前加 100ms 延时或者在main开头先拉低两个设备的电源脚再释放能明显改善。OLED 模块内部有电荷泵上电瞬间电流冲击较大电源走线细的话也会拖慢上升沿。6. 进阶技巧把气压值变成可用的高度与趋势跑通显示只是起点真正让这套组合有价值的是数据后处理。BMP280 的气压分辨率足够高短时间内的气压变化可以反映高度变化按每上升 8.5 米气压下降约 1 hPa 估算室内上下楼能看出明显差异。我一般会在代码里维护一个滑动窗口存最近 16 次气压采样算平均值和标准差标准差超过阈值就判定为环境扰动暂时冻结高度显示避免数值乱跳。#define WIN_SIZE 16 static float press_win[WIN_SIZE]; static uint8_t win_idx 0; float press_filter(float new_val) { press_win[win_idx % WIN_SIZE] new_val; float sum 0; for (int i 0; i WIN_SIZE; i) sum press_win[i]; return sum / WIN_SIZE; }这个滑动平均比芯片内部 IIR 滤波更灵活窗口大小可以按场景调做趋势判断时还能拿窗口首尾差值算变化率。另一个实用技巧是校准基准气压把设备放在已知海拔的位置反算出当地海平面气压存到 Flash之后每次上电直接用这个基准绝对高度就准了。校准值不要写死在代码里留一个按键或者串口命令触发校准流程换地方之后重新标一次。还有一点I2C 总线在长线场景下容易受干扰如果发现偶发读失败除了降速可以在软件层加一次重试读失败后发 STOP、延时 1ms、重新发起传输连续三次失败才报错。这个重试逻辑我一般封装在BMP280_ReadRegs内部上层业务代码不用关心。从那以后我每次做 I2C 多设备项目都强制先跑一遍地址扫描和单寄存器读写测试确认物理层没问题再往上堆业务逻辑省下来的调试时间远比这几行测试代码多。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

ROS2自定义消息实战:从编译到通信的完整指南

ROS2自定义消息实战:从编译到通信的完整指南

简介:面向ROS2初学者与机器人开发者的消息通信实践资料,聚焦节点间数据共享这一核心机制,帮助读者跨越从内置消息到自定义消息的门槛。内容围绕创建消息包、编写.msg定义、生成C与Python接口、安装消息包,以及发布者与订阅者节点的…

📅 2026/10/26 2:34:09
ROS2自定义消息实战:从建包到发布订阅的完整指南

ROS2自定义消息实战:从建包到发布订阅的完整指南

简介:这份资源面向ROS2初学者与机器人开发入门者,围绕消息自定义与节点通信这一核心机制展开,帮助读者理解如何从零搭建自定义消息包并完成发布订阅的完整链路。压缩包共645个文件,约3.43MB,以cmake、make构建脚本和h、…

📅 2026/10/26 2:34:09
大模型Agent生产级落地:架构设计、记忆体系与工作流编排

大模型Agent生产级落地:架构设计、记忆体系与工作流编排

简介:《美团大模型Agent实践手册》是一份面向技术开发者、业务应用者与决策者的技术指南,系统讲解大模型Agent的定义、核心能力与定位,并覆盖龙猫大模型(LongCat-Flash-Chat)核心架构、模型训练流程及能力评估矩阵等关…

📅 2026/10/26 2:34:09
MORE NEWS

更多资讯

📰

多模态大模型中的System 2认知架构与工程实践

1. 多模态大模型中的System 2认知架构解析当GPT-4V能够看着一张破损自行车图片说"需要更换链条"时,它真的理解机械原理吗?这触及了当前多模态大模型最关键的认知鸿沟——感知能力(System 1)与真实推理(Syste…

📰

AI驱动学术写作:智能文献管理6大核心技术解析

1. 学术写作的智能化转型契机去年帮一位博士生修改论文时,我发现他花在文献整理上的时间竟占总工时的40%。这绝非个例——Nature最新调研显示,科研人员平均每周要耗费7.2小时处理文献引用。当传统EndNote遇上GPT-4时代,我们终于迎来了革命性的…

📰

C语言插入排序算法:从考古到现代化修复

1. 项目概述:41号代码的考古与新生在某个尘封的代码仓库角落,编号41的C语言文件静静躺了十余年。当我用现代编译器打开这份实现插入排序的代码时,满屏的warning如同考古现场的警示带——这不是一份能直接运行的代码,而是一个时代的…

📰

从90年代C语言插入排序代码看算法优化与编程规范演进

1. 项目背景与核心价值这个41号代码案例来自某高校计算机系90年代的算法实验课存档,是一份典型的早期C语言插入排序实现。我在整理旧资料时发现这份代码虽然能编译通过,但存在几处隐蔽的逻辑错误和性能缺陷。作为早期C语言教学的活化石,修复并…

📰

多模态大模型中的System 2推理机制与实现

1. 认知架构的本质与System 2推理的定位认知架构在多模态大模型中扮演着"操作系统"的角色,它决定了模型如何处理和整合来自不同模态的信息。System 2推理作为认知架构中的高级处理模块,与人类大脑的慢思考系统相对应,负责需要集中注…

📰

2026年easyread阅读工具深度解析:正文提取与本地排版的生存逻辑

2026年还在折腾easyread这类阅读辅助工具的人,多少有点“非主流”了。浏览器自带的阅读模式越来越普及,内容平台也都在拼命优化自己的沉浸式排版,按理说这玩意儿早该被淘汰了。但事实是,在技术社区、独立博客、还有不少研究型用户…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬