Loading... **STM32单片机使用软件IIC读取AM2320温湿度传感器的数据并显示在0.96寸OLED屏上。** 我用的单片机是STM32F103C8T6,程序用的是ST标准库写的。 STM32使用硬件I2C读取SHTC3温湿度传感器:[https://blog.zeruns.tech/archives/692.html](https://blog.zeruns.tech/archives/692.html) STM32单片机读取AHT10温湿度传感器数据:[https://blog.zeruns.tech/archives/693.html](https://blog.zeruns.tech/archives/693.html) ## 实现效果图   ## I2C协议简介 I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备(那些电平转化芯片),现在被广泛地使用在系统内多个集成电路(IC)间的通讯。 I2C只有一跟数据总线 SDA(Serial Data Line),串行数据总线,只能一位一位的发送数据,属于串行通信,采用半双工通信 半双工通信:可以实现双向的通信,但不能在两个方向上同时进行,必须轮流交替进行,其实也可以理解成一种可以切换方向的单工通信,同一时刻必须只能一个方向传输,只需一根数据线. 对于I2C通讯协议把它分为物理层和协议层物理层规定通讯系统中具有机械、电子功能部分的特性(硬件部分),确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准(软件层面)。 ## I2C物理层 **I2C 通讯设备之间的常用连接方式**  (1) 它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。 (2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线SDA(Serial Data Line ),一条串行时钟线SCL(Serial Data Line )。数据线即用来表示数据,时钟线用于数据收发同步 (3) 总线通过上拉电阻接到电源。**当 I2C 设备空闲时会输出高阻态**,而当所有设备都空闲,都输出高阻态时,**由上拉电阻把总线拉成高电平**。 I2C通信时单片机GPIO口必须设置为开漏输出,否则可能会造成短路。 关于更多STM32的I2C相关信息和使用方法可以看这篇文章:[https://url.zeruns.tech/JC0Ah](https://url.zeruns.tech/JC0Ah) 我这里就不详细讲解了。 ## AM2320温湿度传感器 ### 介绍 AM2320 数字温湿度传感器是一款含有己校准数字信号输出的温湿度复合型传感器。采用专用的温湿度采集技术,确保产品具有极高的可靠性与卓越的长期稳定性。传感器包括一个电容式感湿元件和一个高精度集成测温元件,并与一个高性能微处理器相连接。该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。AM2320 通信方式采用单总线、标准 I2C 两种通信方式。标准单总线接口,使系统集成变得简易快捷。超小的体积、极低的功耗,信号传输距离可达 20 米以上,使其成为各类应用甚至最为苛刻的应用场合的最佳选择。I2C 通信方式采用标准的通信时序,用户可直接挂在 I2C通信总线上,无需额外布线,使用简单。两种通信方式都采用直接输出经温度补偿后的湿度、温度及校验 CRC 等数字信息,用户无需对数字输出进行二次计算,也无需要对湿度进行温度补偿,便可得到准确的温湿度信息。两种通信方式可自由切换,用户可自由选择,使用方便,应该领域广泛。产品为 4 引线,连接方便,特殊封装形式可根据用户需求而提供。 **AM2320数据手册下载地址:[https://url.zeruns.tech/74o6F](https://url.zeruns.tech/74o6F)**    浏览数据手册可以得到一个大概信息: - 温度范围:-40℃\~80℃ - 温度误差:±0.5℃ - 湿度范围:0%\~99.9% - 湿度误差:±3% - 工作电压:3.1v\~5.5v - 通讯方式:I2C或单总线 - 时钟频率:100kHz以内 找到如下几个关键信息 ### 设备地址和读写命令 在实际的使用过程中,AM2320的设备地址需要与读写数据/命令方向位组成一个字节同时发送,字节的最低位为读写数据/命令方向位,高7位是AM2320的设备地址。 如果要通过I2C写数据或命令给AM2320,在I2C起始信号之后,需要发送“1011 1000”,即0xB8给AM2320,除了通过高7位“1011 100”的设备地址寻址还通过最低位“0”通知AM2320接下来是写数据或命令操作。 如果要通过I2C读取AM2320中的数据,在I2C起始信号之后,需要发送“1011 1001”,即0xB9给AM2320,除了通过高7位“1011 100”的设备地址寻址还通过最低位“1”通知AM2320接下来是读取数据的操作。 简单来说就是,0xB8表示写数据,0xB9表示读数据。  ### 读取温湿度数据     从数据手册可知,一个读取周期包概括三个步骤: 1. 唤醒传感器 2. 发送读指令 3. 读返回数据 总结如下: 1. 唤醒传感器:起始信号+发送0xB8+等待(>800us)+停止信号 2. 发送读指令:START+发送0xB8(SLA)+0x03(功能码)+0x00(起始地址)+0x04(寄存器长度)+STOP 3. 接收数据:发送读取指令(0xB9),连续接收8个字节数据。接收到的数据分别为 数据长度+湿度高位+湿度低位+温度高位+温度低位+CRC校验码低字节+CRC校验码高字节 4. 对接收到的数据进行转换处理。 ### 数据的计算 由AM2320数据手册可知   例如:采集到的湿度数值是0x01F4,换算成十进制是500。 则:湿度 = 500 / 10 = 50.0 (单位:%) 采集到的温度数值是0x00FA,换算成十进制是250。 则:温度 = 250 / 10 = 25.0 (单位:℃) ## 需要用的元件 - STM32最小系统板:[https://s.click.taobao.com/M2LgRPu](https://s.click.taobao.com/t?e=m%3D2%26s%3DIlfejTURsNccQipKwQzePOeEDrYVVa64Dne87AjQPk9yINtkUhsv0F7wsYq2ONVgnPBttjfRAZZwhoUbHqff68jZ7535vk8bNdRZtVGuqUX9cmhLhcbMmqktrAX%2B3B0wgvUNyyJS1KL3%2BWMkG3VUs8SSiO22rKBvFlNKklAAlmaWYN2wogyI3kWQvvi6qxFcKVkZ2GYy%2FBbgQFAt6VJWbYfSisBAscogUUAX%2FkQRU7mLMa6PA2rB0ZTJZYSc%2BqAncSpj5qSCmbA%3D&scm=null&pvid=null&app_pvid=59590_33.8.9.90_858_1670489018766&ptl=floorId%3A17741&originalFloorId%3A17741&app_pvid%3A59590_33.8.9.90_858_1670489018766&union_lens=lensId%3APUB%401670489018%402108095a_0b8c_184f0e791ac_cb01%40027kfikYEb1gBhBxZR4uC4j5) - AM2320:[https://s.click.taobao.com/t7jgRPu](https://s.click.taobao.com/t?e=m%3D2%26s%3DR0uzUeJlMVYcQipKwQzePOeEDrYVVa64juWlisr3dOdyINtkUhsv0F7wsYq2ONVgtYjKClaPdgZwhoUbHqff68jZ7535vk8bNdRZtVGuqUX9cmhLhcbMmqktrAX%2B3B0wgvUNyyJS1KL3%2BWMkG3VUswz6PFeWrdXKJ8rlm2%2FM7XT6V%2FWKa%2Fje3OQtuyqg%2FbFF&scm=null&pvid=null&app_pvid=59590_33.5.145.170_876_1670489222279&ptl=floorId%3A17741&originalFloorId%3A17741&app_pvid%3A59590_33.5.145.170_876_1670489222279&union_lens=lensId%3APUB%401670489222%40210591aa_09c4_184f0eaacae_4719%40022TBZLg0pJy4bA6YYXilAWz) - OLED模块:[https://s.click.taobao.com/w67u9Pu](https://s.click.taobao.com/t?e=m%3D2%26s%3DN7VD0p5zDJocQipKwQzePOeEDrYVVa64juWlisr3dOdyINtkUhsv0F7wsYq2ONVg6B%2BcUStZfedwhoUbHqff68jZ7535vk8bNdRZtVGuqUX9cmhLhcbMmqktrAX%2B3B0wgvUNyyJS1KL3%2BWMkG3VUswz6PFeWrdXK50GKAGZv0KRbF9U%2BBkfvvmHpQqR1BtA8&scm=null&pvid=null&app_pvid=59590_33.39.231.240_886_1670489477927&ptl=floorId%3A17741&originalFloorId%3A17741&app_pvid%3A59590_33.39.231.240_886_1670489477927&union_lens=lensId%3APUB%401670489477%402127e7f0_09c3_184f0ee934b_67e5%40026gw1rzHAGrvYStugAJjhcO) - 杜邦线:[https://s.click.taobao.com/anyfRPu](https://s.click.taobao.com/t?e=m%3D2%26s%3D1yHaKWrfDBUcQipKwQzePOeEDrYVVa64Dne87AjQPk9yINtkUhsv0F7wsYq2ONVgx67tMqq9PiZwhoUbHqff68jZ7535vk8bNdRZtVGuqUX9cmhLhcbMmqktrAX%2B3B0wgvUNyyJS1KL3%2BWMkG3VUs7E0cXfLBSzzMocVbYVY6HZppn%2FPki073UtbLf11xMtPxg5p7bh%2BFbQ%3D&scm=null&pvid=null&app_pvid=59590_33.63.244.238_881_1670489525332&ptl=floorId%3A17741&originalFloorId%3A17741&app_pvid%3A59590_33.63.244.238_881_1670489525332&union_lens=lensId%3APUB%401670489525%40213ff4ee_0b76_184f0ef4c75_8620%40024vhxUGCvWlR9FXSQs60GPH) - 面包板:[https://s.click.taobao.com/7Vlu9Pu](https://s.click.taobao.com/t?e=m%3D2%26s%3DHzlOSLGwqEkcQipKwQzePOeEDrYVVa64juWlisr3dOdyINtkUhsv0F7wsYq2ONVgvrXv7W4AVCxwhoUbHqff68jZ7535vk8bNdRZtVGuqUX9cmhLhcbMmqktrAX%2B3B0wgvUNyyJS1KL3%2BWMkG3VUswz6PFeWrdXKlfeHPP4TcME09zdT4ZQOYa7KK8szbHVm&scm=null&pvid=null&app_pvid=59590_33.43.254.46_875_1670489576688&ptl=floorId%3A17741&originalFloorId%3A17741&app_pvid%3A59590_33.43.254.46_875_1670489576688&union_lens=lensId%3APUB%401670489576%40212bfe2e_09eb_184f0f01523_3a5f%400248K3uk1a1TVjOBhVEwY6Iq) - ST-LINK V2:[https://s.click.taobao.com/dtbt9Pu](https://s.click.taobao.com/t?e=m%3D2%26s%3DKuPAjCLq0vQcQipKwQzePOeEDrYVVa64Dne87AjQPk9yINtkUhsv0F7wsYq2ONVg2KO1Zj%2BdyQNwhoUbHqff68jZ7535vk8bNdRZtVGuqUX9cmhLhcbMmqktrAX%2B3B0wgvUNyyJS1KL3%2BWMkG3VUsxxvFEjJs72cXrCNmn8jB%2FDhCUhkMrIaXt%2FAMZDOq1jaxg5p7bh%2BFbQ%3D&scm=null&pvid=null&app_pvid=59590_33.62.127.27_870_1670489613324&ptl=floorId%3A17741&originalFloorId%3A17741&app_pvid%3A59590_33.62.127.27_870_1670489613324&union_lens=lensId%3APUB%401670489613%40213e7f1b_0aff_184f0f0a426_e403%40027h6QCyLb0YpNHX1JUHF1M4) - 电阻:[https://s.click.taobao.com/vbQu9Pu](https://s.click.taobao.com/t?e=m%3D2%26s%3DxaDlNVVExpkcQipKwQzePOeEDrYVVa64juWlisr3dOdyINtkUhsv0F7wsYq2ONVgft33QHpQ%2BRNwhoUbHqff68jZ7535vk8bNdRZtVGuqUX9cmhLhcbMmqktrAX%2B3B0wgvUNyyJS1KL3%2BWMkG3VUs6o%2FQzBeLPrUlVAd73Mv%2FUoTgadFnnOz8NIEqtWdEwbBxiXvDf8DaRs%3D&scm=null&pvid=null&app_pvid=59590_33.49.47.9_887_1670489655536&ptl=floorId%3A17741&originalFloorId%3A17741&app_pvid%3A59590_33.49.47.9_887_1670489655536&union_lens=lensId%3APUB%401670489655%4021312f09_0b5d_184f0f14914_420b%4002hq2D5LVFWVkmCDkifhZTZ) - 逻辑分析仪:[https://s.click.taobao.com/y2br9Pu](https://s.click.taobao.com/t?e=m%3D2%26s%3DrDdmbUnqlF8cQipKwQzePOeEDrYVVa64juWlisr3dOdyINtkUhsv0MGuyjDp%2FGZrc9gbWf8%2BINRwhoUbHqff68jZ7535vk8bNdRZtVGuqUX9cmhLhcbMmqktrAX%2B3B0wgvUNyyJS1KL3%2BWMkG3VUs6o%2FQzBeLPrUW44NknDLN3lZnP9bKb6c6MYMXU3NNCg%2F&scm=null&pvid=null&app_pvid=59590_33.5.20.242_875_1670490218829&ptl=floorId%3A17741&originalFloorId%3A17741&app_pvid%3A59590_33.5.20.242_875_1670490218829&union_lens=lensId%3APUB%401670490218%40210514f2_09bf_184f0f9e16c_dccc%4002rBnRVl9V3fhcBOUvCIxTA) ## 程序 **这里就放出main.c、AM2320.c和OLED.c这三个主要的代码,其他的请下载下面链接的压缩包。** 完整工程文件:[https://url.zeruns.tech/AM2320](https://url.zeruns.tech/AM2320) **AM2320和OLED模块的 SCL接PB12,SDA接PB13。** 如果AM2320单独用别的IO口那要记得接上拉电阻,5KΩ左右就行。 使用VSCode代替Keil实现STM32和51单片机的开发:[https://blog.zeruns.tech/archives/690.html](https://blog.zeruns.tech/archives/690.html) ### main.c ```c #include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "AM2320.h" #include "IWDG.h" int main(void) { IWDG_Configuration(); //初始化看门狗 AM2320_I2C_Init(); OLED_Init(); OLED_ShowString(1, 1, "T:"); OLED_ShowString(2, 1, "H:"); uint16_t i = 0; uint16_t err_count = 0; while (1) { OLED_ShowNum(4, 1, i, 5); float Temp, Hum; //声明变量存放温湿度数据 if (ReadAM2320(&Hum, &Temp)) //读取温湿度数据 { if (Temp >= 0) { char String[10]; sprintf(String, "+%.2fC", Temp); //格式化字符串输出到字符串变量 OLED_ShowString(1, 3, String); //显示温度 sprintf(String, " %.2f%%", Hum); //格式化字符串输出到字符串变量 OLED_ShowString(2, 3, String); //显示湿度 } else { char String[10]; sprintf(String, "-%.2fC", Temp); //格式化字符串输出到字符串变量 OLED_ShowString(1, 3, String); //显示温度 sprintf(String, " %.2f%%", Hum); //格式化字符串输出到字符串变量 OLED_ShowString(2, 3, String); //显示湿度 } } else { err_count++; OLED_ShowNum(3, 1, err_count, 5); //显示错误次数计数 } Delay_ms(100); i++; if (i >= 99999) i = 0; if (err_count >= 99999) err_count = 0; IWDG_FeedDog(); //喂狗(看门狗,超过1秒没有执行喂狗则自动复位) } // blog.zeruns.tech } ``` ### AM2320.c ```c #include "stm32f10x.h" #include "Delay.h" #include "OLED.h" /* 作者博客:https://blog.zeruns.tech 微信公众号:zeruns-gzh B站主页:https://space.bilibili.com/8320520 */ /*AM2320地址*/ #define AM2320_ADDRESS 0xB8 /*引脚配置*/ #define AM2320_SCL GPIO_Pin_12 #define AM2320_SDA GPIO_Pin_13 #define AM2320_W_SCL(x) GPIO_WriteBit(GPIOB, AM2320_SCL, (BitAction)(x)) #define AM2320_W_SDA(x) GPIO_WriteBit(GPIOB, AM2320_SDA, (BitAction)(x)) #define AM2320_R_SDA() GPIO_ReadInputDataBit(GPIOB, AM2320_SDA) #define AM2320_R_SCL() GPIO_ReadInputDataBit(GPIOB, AM2320_SCL) /*当 STM32 的 GPIO 配置成开漏输出模式时,它仍然可以通过读取 GPIO 的输入数据寄存器获取外部对引脚的输入电平,也就是说它同时具有浮空输入模式的功能*/ /** * @brief CRC校验计算 * @param *ptr 要计算的字节数据(以数组变量形式存储) * @param len 要计算的字节个数(数组长度) * @retval CRC校验码 */ unsigned short CRC16(unsigned char *ptr, unsigned char len) { unsigned short crc = 0xFFFF; unsigned char i; while (len--) { crc ^= *ptr++; for (i = 0; i < 8; i++) { if (crc & 0x01) { crc >>= 1; crc ^= 0xA001; } else { crc >>= 1; } } } return crc; } /** * @brief I2C开始 * @param 无 * @retval 无 */ void AM2320_I2C_Start(void) { AM2320_W_SDA(1); Delay_us(2); //延时2微秒 AM2320_W_SCL(1); Delay_us(4); AM2320_W_SDA(0); Delay_us(3); AM2320_W_SCL(0); Delay_us(5); } /** * @brief I2C停止 * @param 无 * @retval 无 */ void AM2320_I2C_Stop(void) { AM2320_W_SDA(0); Delay_us(3); AM2320_W_SCL(1); Delay_us(4); AM2320_W_SDA(1); Delay_us(4); } /** * @brief I2C发送一个字节 * @param Byte 要发送的一个字节 * @retval 无 */ void AM2320_I2C_SendByte(uint8_t Byte) { uint8_t i; for (i = 0; i < 8; i++) { AM2320_W_SDA((Byte << i) & 0x80); AM2320_W_SCL(1); Delay_us(4); AM2320_W_SCL(0); Delay_us(5); } AM2320_W_SDA(1); //释放SDA总线 } /** * @brief 等待应答信号 * @param 无 * @retval 1-非应答信号,0-应答信号 */ uint8_t WaitAck(void) { uint8_t ret; AM2320_W_SCL(1); Delay_us(4); if (AM2320_R_SDA()) { ret = 1; } else { ret = 0; } AM2320_W_SCL(0); Delay_us(5); return ret; } /** * @brief I2C读取一个字节 * @param NACK 1-非应答信号,0-应答信号 * @retval 读取到的字节数据 */ uint8_t AM2320_I2C_ReadByte(uint8_t NACK) { uint8_t i, Byte = 0; AM2320_W_SDA(1); //释放SDA总线 for (i = 0; i < 8; i++) { AM2320_W_SCL(1); Delay_us(4); Byte = Byte | (AM2320_R_SDA() << (7 - i)); AM2320_W_SCL(0); Delay_us(5); } AM2320_W_SDA(NACK); //发送应答/非应答信号 AM2320_W_SCL(1); Delay_us(4); AM2320_W_SCL(0); Delay_us(5); AM2320_W_SDA(1); //释放SDA总线 return Byte; } /*唤醒传感器*/ void AM2320_Wake(void) { AM2320_I2C_Start(); AM2320_I2C_SendByte(AM2320_ADDRESS); WaitAck(); Delay_us(1000); //延时1000微秒 AM2320_I2C_Stop(); } /*引脚初始化*/ void AM2320_I2C_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB时钟 GPIO_InitTypeDef GPIO_InitStructure; //定义结构体配置GPIO GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //开漏输出模式 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = AM2320_SCL; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = AM2320_SDA; GPIO_Init(GPIOB, &GPIO_InitStructure); AM2320_W_SCL(1); AM2320_W_SDA(1); AM2320_Wake(); //唤醒传感器 } /** * @brief 读取AM2320数据 * @param *Hum 湿度 * @param *Temp 温度 * @retval 1 - 读取成功;0 - 读取失败 */ uint8_t ReadAM2320(float *Hum, float *Temp) { uint8_t Data[8]; AM2320_I2C_Start(); //发送起始信号 AM2320_I2C_SendByte(AM2320_ADDRESS); if (WaitAck()) //判断应答信号 { AM2320_I2C_Stop(); //发送停止信号 Delay_us(50); //再尝试读取一次 AM2320_I2C_Start(); //发送起始信号 AM2320_I2C_SendByte(AM2320_ADDRESS); if (WaitAck()) //判断应答信号 { Delay_us(20); AM2320_I2C_Stop(); //发送停止信号 return 0; } else { Delay_us(20);//这里AM2320会莫名其妙地拉低SCL一段时间导致发送的数据出错,所以要延时20微秒等这段时间过去了AM2320释放SCL再继续 AM2320_I2C_SendByte(0x03); //发送功能码 WaitAck(); //等待应答信号 AM2320_I2C_SendByte(0x00); //发送要读取的寄存器起始地址 WaitAck(); //等待应答信号 AM2320_I2C_SendByte(0x04); //发送要读取的寄存器长度 WaitAck(); //等待应答信号 Delay_us(20);//这里AM2320会莫名其妙地拉低SCL一段时间导致停止信号发送失败,所以延时20微秒等这段时间过去了AM2320释放SCL再继续 AM2320_I2C_Stop(); //发送停止信号 } } else { Delay_us(20);//这里AM2320会莫名其妙地拉低SCL一段时间导致发送的数据出错,所以要延时20微秒等这段时间过去了AM2320释放SCL再继续 AM2320_I2C_SendByte(0x03); //发送功能码 WaitAck(); //等待应答信号 AM2320_I2C_SendByte(0x00); //发送要读取的寄存器起始地址 WaitAck(); //等待应答信号 AM2320_I2C_SendByte(0x04); //发送要读取的寄存器长度 WaitAck(); //等待应答信号 Delay_us(20);//这里AM2320会莫名其妙地拉低SCL一段时间导致停止信号发送失败,所以延时20微秒等这段时间过去了AM2320释放SCL再继续 AM2320_I2C_Stop(); //发送停止信号 } Delay_ms(2); //延时2毫秒 AM2320_I2C_Start(); AM2320_I2C_SendByte(AM2320_ADDRESS | 0x01); //发送读取指令 WaitAck(); Delay_us(35); uint8_t i; for (i = 0; i < 8; i++) { if (i != 7) { Data[i] = AM2320_I2C_ReadByte(0); } else { Data[i] = AM2320_I2C_ReadByte(1); //读取最后一个字节时发送非应答信号 } } AM2320_I2C_Stop(); if (CRC16(Data, 6) == (Data[6] | (Data[7] << 8))) //校验数据 { *Hum = ((((uint16_t)Data[2]) << 8) | Data[3]) / 10.0; //计算湿度数据 if (Data[4] >> 7) //判断温度数值是否为负 { *Temp = ((((uint16_t)(Data[4] && 0x7F) << 8)) | Data[5]) / -10.0; //计算负温度 } else { *Temp = ((((uint16_t)Data[4]) << 8) | Data[5]) / 10.0; //计算正温度 } return 1; } return 0; } ``` ### OLED.c ```c #include "stm32f10x.h" #include "OLED_Font.h" /*引脚配置*/ #define OLED_SCL GPIO_Pin_12 #define OLED_SDA GPIO_Pin_13 #define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, OLED_SCL, (BitAction)(x)) #define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, OLED_SDA, (BitAction)(x)) /*引脚初始化*/ void OLED_I2C_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = OLED_SCL; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = OLED_SDA; GPIO_Init(GPIOB, &GPIO_InitStructure); OLED_W_SCL(1); OLED_W_SDA(1); } /** * @brief I2C开始 * @param 无 * @retval 无 */ void OLED_I2C_Start(void) { OLED_W_SDA(1); OLED_W_SCL(1); OLED_W_SDA(0); OLED_W_SCL(0); } /** * @brief I2C停止 * @param 无 * @retval 无 */ void OLED_I2C_Stop(void) { OLED_W_SDA(0); OLED_W_SCL(1); OLED_W_SDA(1); } /** * @brief I2C发送一个字节 * @param Byte 要发送的一个字节 * @retval 无 */ void OLED_I2C_SendByte(uint8_t Byte) { uint8_t i; for (i = 0; i < 8; i++) { OLED_W_SDA(Byte & (0x80 >> i)); OLED_W_SCL(1); OLED_W_SCL(0); } OLED_W_SDA(1); //释放SDA总线 OLED_W_SCL(1); //额外的一个时钟,不处理应答信号 OLED_W_SCL(0); } /** * @brief OLED写命令 * @param Command 要写入的命令 * @retval 无 */ void OLED_WriteCommand(uint8_t Command) { OLED_I2C_Start(); OLED_I2C_SendByte(0x78); //从机地址 OLED_I2C_SendByte(0x00); //写命令 OLED_I2C_SendByte(Command); OLED_I2C_Stop(); } /** * @brief OLED写数据 * @param Data 要写入的数据 * @retval 无 */ void OLED_WriteData(uint8_t Data) { OLED_I2C_Start(); OLED_I2C_SendByte(0x78); //从机地址 OLED_I2C_SendByte(0x40); //写数据 OLED_I2C_SendByte(Data); OLED_I2C_Stop(); } /** * @brief OLED设置光标位置 * @param Y 以左上角为原点,向下方向的坐标,范围:0~7 * @param X 以左上角为原点,向右方向的坐标,范围:0~127 * @retval 无 */ void OLED_SetCursor(uint8_t Y, uint8_t X) { OLED_WriteCommand(0xB0 | Y); //设置Y位置 OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置低4位 OLED_WriteCommand(0x00 | (X & 0x0F)); //设置X位置高4位 } /** * @brief OLED清屏 * @param 无 * @retval 无 */ void OLED_Clear(void) { uint8_t i, j; for (j = 0; j < 8; j++) { OLED_SetCursor(j, 0); for (i = 0; i < 128; i++) { OLED_WriteData(0x00); } } } /** * @brief OLED部分清屏 * @param Line 行位置,范围:1~4 * @param start 列开始位置,范围:1~16 * @param end 列开始位置,范围:1~16 * @retval 无 */ void OLED_Clear_Part(uint8_t Line, uint8_t start, uint8_t end) { uint8_t i, Column; for (Column = start; Column <= end; Column++) { OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分 for (i = 0; i < 8; i++) { OLED_WriteData(0x00); //显示上半部分内容 } OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分 for (i = 0; i < 8; i++) { OLED_WriteData(0x00); //显示下半部分内容 } } } /** * @brief OLED显示一个字符 * @param Line 行位置,范围:1~4 * @param Column 列位置,范围:1~16 * @param Char 要显示的一个字符,范围:ASCII可见字符 * @retval 无 */ void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char) { uint8_t i; OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分 for (i = 0; i < 8; i++) { OLED_WriteData(OLED_F8x16[Char - ' '][i]); //显示上半部分内容 } OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分 for (i = 0; i < 8; i++) { OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //显示下半部分内容 } } /** * @brief OLED显示字符串 * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param String 要显示的字符串,范围:ASCII可见字符 * @retval 无 */ void OLED_ShowString(uint8_t Line, uint8_t Column, char *String) { uint8_t i; for (i = 0; String[i] != '\0'; i++) { OLED_ShowChar(Line, Column + i, String[i]); } } /** * @brief OLED次方函数 * @retval 返回值等于X的Y次方 */ uint32_t OLED_Pow(uint32_t X, uint32_t Y) { uint32_t Result = 1; while (Y--) { Result *= X; } return Result; } /** * @brief OLED显示数字(十进制,正数) * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~4294967295 * @param Length 要显示数字的长度,范围:1~10 * @retval 无 */ void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length) { uint8_t i; for (i = 0; i < Length; i++) { OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0'); } } /** * @brief OLED显示数字(十进制,带符号数) * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:-2147483648~2147483647 * @param Length 要显示数字的长度,范围:1~10 * @retval 无 */ void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length) { uint8_t i; uint32_t Number1; if (Number >= 0) { OLED_ShowChar(Line, Column, '+'); Number1 = Number; } else { OLED_ShowChar(Line, Column, '-'); Number1 = -Number; } for (i = 0; i < Length; i++) { OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0'); } } /** * @brief OLED显示数字(十六进制,正数) * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~0xFFFFFFFF * @param Length 要显示数字的长度,范围:1~8 * @retval 无 */ void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length) { uint8_t i, SingleNumber; for (i = 0; i < Length; i++) { SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16; if (SingleNumber < 10) { OLED_ShowChar(Line, Column + i, SingleNumber + '0'); } else { OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A'); } } } /** * @brief OLED显示数字(二进制,正数) * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~1111 1111 1111 1111 * @param Length 要显示数字的长度,范围:1~16 * @retval 无 */ void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length) { uint8_t i; for (i = 0; i < Length; i++) { OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0'); } } /** * @brief OLED初始化 * @param 无 * @retval 无 */ void OLED_Init(void) { uint32_t i, j; for (i = 0; i < 1000; i++) //上电延时 { for (j = 0; j < 1000; j++) ; } OLED_I2C_Init(); //端口初始化 OLED_WriteCommand(0xAE); //关闭显示 OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率 OLED_WriteCommand(0x80); OLED_WriteCommand(0xA8); //设置多路复用率 OLED_WriteCommand(0x3F); OLED_WriteCommand(0xD3); //设置显示偏移 OLED_WriteCommand(0x00); OLED_WriteCommand(0x40); //设置显示开始行 OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置 OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置 OLED_WriteCommand(0xDA); //设置COM引脚硬件配置 OLED_WriteCommand(0x12); OLED_WriteCommand(0x81); //设置对比度控制 OLED_WriteCommand(0xCF); OLED_WriteCommand(0xD9); //设置预充电周期 OLED_WriteCommand(0xF1); OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别 OLED_WriteCommand(0x30); OLED_WriteCommand(0xA4); //设置整个显示打开/关闭 OLED_WriteCommand(0xA6); //设置正常/倒转显示 OLED_WriteCommand(0x8D); //设置充电泵 OLED_WriteCommand(0x14); OLED_WriteCommand(0xAF); //开启显示 OLED_Clear(); // OLED清屏 } ``` ## 时序图 实际运行的AM2320发送指令和读取数据的时序图。   ## 推荐阅读 - **高性价比和便宜的VPS/云服务器推荐:** [https://blog.vpszj.cn/archives/41.html](https://blog.vpszj.cn/archives/41.html) - 使用NPS搭建内网穿透服务器,带Web面板:[https://blog.vpszj.cn/archives/748.html](https://blog.vpszj.cn/archives/748.html) - Linux搭建网站教程,建站教程:[https://blog.vpszj.cn/archives/1094.html](https://blog.vpszj.cn/archives/1094.html) - 我的世界服务器搭建教程:[https://blog.zeruns.tech/tag/mc/](https://blog.zeruns.tech/tag/mc/) - 基于STM32和HC-SR04模块实现超声波测距功能:[https://blog.zeruns.tech/archives/680.html](https://blog.zeruns.tech/archives/680.html) - ESP8266开发环境搭建及项目演示:[https://blog.zeruns.tech/archives/526.html](https://blog.zeruns.tech/archives/526.html) 最后修改:2022 年 12 月 08 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果您觉得我的文章有帮助,请随意赞赏,赞赏有助于激发博主的热情,感谢!