1.3寸单色OLED屏驱动移植与温湿度显示实战基于CW32F030C8T6与DHT11最近在做一个环境监测的小项目需要把温湿度数据直观地显示出来。我选择了1.3寸的单色OLED屏搭配DHT11温湿度传感器主控用的是立创的CW32F030C8T6开发板。这套组合成本低、效果好非常适合初学者学习嵌入式外设驱动和显示技术。今天我就来手把手教你如何把OLED屏的驱动移植到CW32F030C8T6上并读取DHT11的数据最终在屏幕上实时显示温湿度。整个过程我会详细拆解从硬件连接到软件驱动再到数据整合显示保证你跟着做就能成功。1. 项目硬件准备与连接在开始写代码之前咱们先把硬件搞清楚、连对线。这是后续所有工作的基础线接错了代码写得再好也没用。1.1 认识我们的“主角”这次项目用到三个核心部件主控MCU立创CW32F030C8T6开发板。这是一款基于ARM Cortex-M0内核的微控制器性能足够应对我们这个项目而且资料开源对初学者友好。传感器DHT11温湿度传感器。它是一个数字传感器只用一根数据线就能同时读取温度和湿度非常方便。它的精度对于日常环境监测完全够用。显示屏1.3寸单色OLED屏。这种屏幕是自发光的不需要背光显示对比度高功耗低而且接口简单通常使用I2C或SPI通信。1.2 硬件连接图思路由于原始资料主要提供了DHT11的驱动我们这里重点讲解DHT11的连接。OLED屏的连接需要根据你手头屏幕的具体型号是I2C接口还是SPI接口来确定但原理是相通的。DHT11模块连接DHT11模块通常有3个引脚VCC, GND, DATA。我们需要将它连接到CW32开发板的GPIO上。VCC- 开发板的3.3V引脚GND- 开发板的GND引脚DATA- 开发板的GPIO引脚例如 PB0注意DHT11的数据线DATA在空闲时必须保持高电平。好在模块板上通常已经集成了上拉电阻所以我们直接连接GPIO即可。如果没有就需要在DATA线和VCC之间接一个4.7K-10K的上拉电阻。OLED屏连接以常见的I2C接口为例I2C接口的OLED通常有4个引脚VCC, GND, SCL, SDA。VCC- 开发板的3.3V引脚GND- 开发板的GND引脚SCL- 开发板的I2C时钟引脚例如 PB6SDA- 开发板的I2C数据引脚例如 PB7请务必查阅你OLED屏的资料确认其通信接口和引脚定义。2. DHT11温湿度传感器驱动详解DHT11的驱动是整个数据采集的关键。它的通信协议是“单总线”听起来简单但时序要求很严格写代码时得特别小心。2.1 单总线通信时序剖析你可以把单总线想象成一条独木桥主机MCU和从机DHT11都要按严格的顺序和时间通过不能抢行。一次完整的DHT11数据读取分为四个阶段起始信号MCU发起聊天。MCU把数据线配置为输出模式拉低至少18毫秒。然后MCU释放总线拉高等待20-40微秒。代码体现就是输出低电平 - 延时19ms - 输出高电平 - 延时20us。响应信号DHT11回应“我在请讲”。MCU将数据线切换为输入模式。DHT11会先把总线拉低约80微秒然后再拉高80微秒告诉MCU它准备好了。代码里我们需要等待这个低电平的到来如果等不到超时说明通信失败。数据传输DHT11发送数据。DHT11发送40位数据。每一位都以一个54微秒的低电平开始。紧接着是一个高电平它的持续时间决定了这一位是0还是126-28微秒的高电平表示位‘0’。70微秒的高电平表示位‘1’。我们的策略是等待每个位开始的低电平过去后延时28微秒一个介于0和1之间的时间点再去读取引脚电平。如果此时还是高电平那肯定是‘1’如果是低电平那就是‘0’。结束信号DHT11发送完数据后会拉低总线54微秒然后释放。MCU需要重新将总线设置为输出模式并拉高结束本次通信。2.2 数据格式与校验DHT11一次发送40位5个字节数据。这5个字节的含义和校验方法至关重要数据段字节1字节2字节3字节4字节5含义湿度整数湿度小数温度整数温度小数校验和说明例如35固定为0例如24例如4前4字节相加的低8位举个例子如果收到数据0x23, 0x00, 0x18, 0x04, 0x3F。湿度 0x23 35 %RH温度 0x18 0x04 * 0.1 24 0.4 24.4 ℃校验和 0x23 0x00 0x18 0x04 0x3F与收到的第5字节(0x3F)一致数据有效。如果校验和不一致说明传输过程中可能受到了干扰这次数据应该丢弃重新读取。2.3 驱动代码移植与解析原始资料提供了完整的DHT11驱动代码我们把它移植到自己的工程里就行。这里我挑核心函数DHT11_Read_Data讲讲关键点。首先在你的工程里新建dht11.c和dht11.h两个文件。dht11.h 头文件关键配置这里主要是引脚定义你需要根据自己实际的连接来修改。比如我的DATA线接在PB0上。// dht11.h #ifndef _BSP_DHT11_H_ #define _BSP_DHT11_H_ #include board.h /**************引脚修改此处****************/ #define RCC_DHT11_GPIO_ENABLE() __RCC_GPIOB_CLK_ENABLE() #define PORT_DHT11 CW_GPIOB #define GPIO_DHT11 GPIO_PIN_0 // 根据你的连接修改引脚号 //设置DHT11输出高或低电平 #define DATA_GPIO_OUT(x) GPIO_WritePin(PORT_DHT11, GPIO_DHT11, x ? GPIO_Pin_SET : GPIO_Pin_RESET) //获取DHT11数据引脚高低电平状态 #define DATA_GPIO_IN GPIO_ReadPin(PORT_DHT11, GPIO_DHT11) extern float temperature; extern float humidity; void DHT11_GPIO_Init(void); unsigned int DHT11_Read_Data(void); float Get_temperature(void); float Get_humidity(void); #endifdht11.c 核心读取函数解析这个函数严格遵循了我们上面分析的时序。// dht11.c 部分代码 unsigned int DHT11_Read_Data(void) { int i; long long val0; // 用于存储40位原始数据 int timeout0; float small_point0; unsigned char verify_num 0; // 1. 发送起始信号 DATA_GPIO_OUT(0); // 主机拉低数据线 delay_ms(19); // 保持低电平至少18ms这里给19ms留点余量 DATA_GPIO_OUT(1); // 主机释放总线拉高 delay_us( 20 ); // 拉高后等待20us // 2. 切换为输入模式等待DHT11响应 DHT11_GPIO_Mode_IN(); // 等待DHT11拉低响应信号开始 timeout 5000; while( (! DATA_GPIO_IN ) ( timeout 0 ) )timeout--; if(timeout 0) return 0; // 超时响应失败 // 等待DHT11拉高响应信号结束准备发数据 timeout 5000; while( DATA_GPIO_IN ( timeout 0 ) )timeout-- ; if(timeout 0) return 0; // 超时 // 3. 接收40位数据 #define CHECK_TIME 28 // 关键延时用于区分0和1 for(i0;i40;i) { timeout 5000; // 等待每位开始的低电平过去约54us while( ( !DATA_GPIO_IN ) (timeout 0) ) timeout--; if(timeout 0) break; // 超时退出 delay_us(CHECK_TIME); // 延时28us后采样 // 判断当前电平决定是0还是1 if ( DATA_GPIO_IN ) // 还是高电平说明是1 { val(val1)1; // 左移一位末尾加1 } else // 是低电平说明是0 { val1; // 左移一位末尾加0 } // 等待当前位的高电平结束如果是1则等待剩余的高电平时间 timeout 5000; while( DATA_GPIO_IN (timeout 0) ) timeout-- ; } // 4. 通信结束主机重新控制总线 DHT11_GPIO_Mode_OUT(); DATA_GPIO_OUT(1); // 5. 校验数据 // 计算前4个字节的和 verify_num (val32) (val24) (val16) (val8); // 与接收到的校验和第5字节比较 verify_num verify_num - (val0xff); if( verify_num ) // 校验和不等于0说明错误 { return 0; } else // 校验成功解析数据 { // 解析湿度整数部分小数部分 humidity (val32)0xff; small_point (val24)0x00ff; small_point small_point * 0.1; humidity humidity small_point; // 解析温度整数部分小数部分 temperature (val16)0x0000ff; small_point (val8)0x000000ff; small_point small_point * 0.1; temperature temperature small_point; return val8; // 可以返回原始数据供其他用途 } }踩坑提示CHECK_TIME这个延时值是区分数据0和1的关键。28us是一个经验值因为‘0’的高电平是26-28us‘1’的高电平是70us。在28us时采样能可靠地区分两者。如果发现数据偶尔错乱可以微调这个值并用逻辑分析仪抓取时序波形进行对比。2.4 在主函数中测试将驱动文件添加到工程后在主函数里初始化并循环读取可以通过串口打印数据来验证。// main.c #include board.h #include stdio.h #include bsp_uart.h #include dht11.h int32_t main(void) { board_init(); // 开发板初始化 uart1_init(115200); // 初始化串口用于打印数据 DHT11_GPIO_Init(); // DHT11引脚初始化 delay_ms(1000); // 上电后等待传感器稳定 printf(DHT11 Test Start\r\n); while(1) { if(DHT11_Read_Data()) // 如果读取成功 { printf(Temp: %.1f C, Humi: %.1f %%RH\r\n, Get_temperature(), Get_humidity()); } else { printf(DHT11 Read Error!\r\n); } delay_ms(2000); // 每隔2秒读取一次DHT11两次读取间隔需大于1秒 } }编译下载到开发板打开串口助手你应该能看到每隔2秒打印出的温湿度数据。如果显示“Read Error”请检查硬件连接、引脚配置以及CHECK_TIME延时参数。3. 1.3寸OLED屏驱动移植OLED屏的驱动移植相对独立。你需要找到对应你屏幕型号的驱动代码通常是oled.c和oled.h。市面上常见的OLED驱动芯片是SSD1306驱动代码很多。3.1 驱动适配关键点无论你拿到的是I2C还是SPI的驱动代码移植到CW32F030上主要修改以下几个地方硬件接口函数重写驱动代码底层一定有OLED_WR_Byte写字节、OLED_Init初始化这样的函数里面包含了具体的GPIO操作或I2C/SPI发送函数。你需要将这些函数内部对硬件的操作替换成CW32 HAL库的函数。如果是I2C使用CW32的CW_I2C库函数进行读写。如果是SPI使用CW32的CW_SPI库函数进行读写。如果是模拟GPIO软件I2C/SPI修改里面的OLED_SCL、OLED_SDA等宏定义对应的CW32 GPIO操作函数。延时函数替换驱动代码里可能有delay_ms或delay_us确保它们指向你工程里可用的延时函数。屏幕尺寸配置在oled.h中确认OLED_WIDTH和OLED_HEIGHT被正确设置为你的屏幕分辨率例如128x64。3.2 显示温湿度数据驱动移植成功后OLED屏应该能显示测试图形或文字了。接下来我们把DHT11读取的数据显示上去。通常OLED驱动会提供字符和字符串显示函数比如OLED_ShowString、OLED_ShowNum。我们需要做的是在main.c中包含OLED头文件。在main函数中在初始化DHT11之后初始化OLED屏。在while(1)循环中读取DHT11数据后调用OLED显示函数来刷新屏幕。// 在main.c中 #include oled.h int32_t main(void) { char str_buf[20]; // 用于格式化字符串的缓冲区 board_init(); uart1_init(115200); DHT11_GPIO_Init(); OLED_Init(); // 初始化OLED OLED_Clear(); // 清屏 delay_ms(1000); while(1) { if(DHT11_Read_Data()) // 读取数据成功 { float temp Get_temperature(); float humi Get_humidity(); // 通过串口打印 printf(Temp: %.1f C, Humi: %.1f %%RH\r\n, temp, humi); // 在OLED上显示 OLED_Clear(); // 清屏准备刷新也可局部刷新优化 OLED_ShowString(0, 0, Env Monitor, 16); // 第0行显示标题 OLED_ShowString(0, 2, Temp:, 16); // 第2行 sprintf(str_buf, %.1f C, temp); // 将浮点数格式化为字符串 OLED_ShowString(40, 2, (uint8_t*)str_buf, 16); OLED_ShowString(0, 4, Humi:, 16); // 第4行 sprintf(str_buf, %.1f %%, humi); OLED_ShowString(40, 4, (uint8_t*)str_buf, 16); OLED_Refresh(); // 更新显示到屏幕 } else { OLED_ShowString(0, 6, Sensor Error!, 16); OLED_Refresh(); } delay_ms(2000); // 2秒更新一次 } }3.3 优化与调试建议避免频繁清屏OLED_Clear()会清除整个屏幕缓冲区再全部重画可能导致闪烁。可以优化为只更新数字变化的区域。处理读取失败DHT11容易受干扰一次读取失败是正常的。可以增加重试机制比如连续读取3次取其中校验成功的两次结果。检查电源确保给DHT11和OLED的电源3.3V稳定。不稳定的电源是通信失败的常见原因。使用逻辑分析仪如果时序怎么调都不对逻辑分析仪是终极武器。用它抓取DATA线上的实际波形和DHT11数据手册的时序图对比一目了然。按照以上步骤你应该就能在CW32F030C8T6开发板上成功驱动OLED屏并显示来自DHT11的实时温湿度数据了。这个项目虽然小但涵盖了GPIO控制、精确延时、单总线协议、数据校验、外设驱动移植和显示输出等多个嵌入式开发基础知识点非常适合用来练手和巩固基础。动手试试吧遇到问题多在论坛里交流讨论。