工具准备:
STM32F407VGT-DISC开发板,AHT20温湿度传感器模块
CubeIDE,CubeMX,vscode
首先从GitHub上拉取驱动代码
libdriver/aht20: AHT20 full-featured driver library for general-purpose MCU and Linux.
然后在CubeMX中创建工程,首先将debug模式改为serial wire模式
打开外部时钟源
按如图所示设置时钟,模块挂载在APB1上
打开I2C1保持默认设置
打开串口2打印数据
勾选为每个外设分别生成.c.h文件
保存并生成代码
在usart.h中引入stdio.h,在usart.c文件中编写printf重定向函数,方便串口打印
/* USER CODE BEGIN 1 */ int __io_putchar(int ch) { HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; } /* USER CODE END 1 */用IDE打开项目在Core文件夹下新建CustomDrivers文件存放之前从GitHub上拉取的代码
右键工程点击properties
按下图步骤添加工作区,否则识别不到驱动文件
在i2c.h文件中引入driver_aht20.h文件,在i2c.c文件中编写获取温度,湿度的代码。
读取并编写获取温湿度代码的工作此处交由了vscode中配置的claude完成得到以下代码并添加到i2c.c中:
/* USER CODE BEGIN 1 */ /* ================== AHT20 接口层:把 LibDriver 驱动链接到 STM32 HAL I2C ================== */ /* IIC 传输超时时间,单位 ms */ #define AHT20_IIC_TIMEOUT 1000 /* AHT20 句柄(LibDriver 驱动要求,需链接 HAL 接口函数后使用) */ aht20_handle_t aht20_handle; /** * @brief IIC 初始化:I2C1 外设已由 MX_I2C1_Init() 完成初始化,此处直接返回成功 * @retval 0 成功 */ static uint8_t aht20_iic_init(void) { return 0; } /** * @brief IIC 反初始化:无特殊操作,返回成功 * @retval 0 成功 */ static uint8_t aht20_iic_deinit(void) { return 0; } /** * @brief 从 AHT20 读取 len 字节数据 * @note LibDriver 传入的 AHT20_ADDRESS(0x70) 已经是 STM32 HAL 所需的 * 移位后地址(7 位地址 0x38 << 1),因此直接透传,不要再移位。 * @param[in] addr 器件地址 * @param[out] buf 数据缓冲区 * @param[in] len 读取字节数 * @retval 0 成功,1 失败 */ static uint8_t aht20_iic_read_cmd(uint8_t addr, uint8_t *buf, uint16_t len) { if (HAL_I2C_Master_Receive(&hi2c1, addr, buf, len, AHT20_IIC_TIMEOUT) != HAL_OK) { return 1; } return 0; } /** * @brief 向 AHT20 写入 len 字节数据 * @param[in] addr 器件地址 * @param[in] buf 数据缓冲区 * @param[in] len 写入字节数 * @retval 0 成功,1 失败 */ static uint8_t aht20_iic_write_cmd(uint8_t addr, uint8_t *buf, uint16_t len) { if (HAL_I2C_Master_Transmit(&hi2c1, addr, buf, len, AHT20_IIC_TIMEOUT) != HAL_OK) { return 1; } return 0; } /** * @brief 毫秒延时 * @param[in] ms 延时毫秒数 */ static void aht20_delay_ms(uint32_t ms) { HAL_Delay(ms); } /** * @brief 调试打印:通过 printf 重定向输出到串口(usart.c 中已实现 __io_putchar) * @param[in] fmt 格式字符串 */ static void aht20_debug_print(const char *const fmt, ...) { va_list args; va_start(args, fmt); vprintf(fmt, args); va_end(args); } /** * @brief AHT20 初始化:将接口函数链接到驱动句柄并初始化芯片 * @note 必须在 MX_I2C1_Init() 之后调用(在主循环前调用一次即可) * @retval 0 成功;非 0 失败(返回码含义见 driver_aht20.h 中 aht20_init 注释) */ uint8_t aht20_initialize(void) { DRIVER_AHT20_LINK_INIT(&aht20_handle, aht20_handle_t); DRIVER_AHT20_LINK_IIC_INIT(&aht20_handle, aht20_iic_init); DRIVER_AHT20_LINK_IIC_DEINIT(&aht20_handle, aht20_iic_deinit); DRIVER_AHT20_LINK_IIC_READ_CMD(&aht20_handle, aht20_iic_read_cmd); DRIVER_AHT20_LINK_IIC_WRITE_CMD(&aht20_handle, aht20_iic_write_cmd); DRIVER_AHT20_LINK_DELAY_MS(&aht20_handle, aht20_delay_ms); DRIVER_AHT20_LINK_DEBUG_PRINT(&aht20_handle, aht20_debug_print); return aht20_init(&aht20_handle); } /** * @brief 温度获取函数 * @retval 温度值(摄氏度),读取失败返回 -999.0f */ float aht20_get_temperature(void) { uint32_t temperature_raw; float temperature_s = -999.0f; if (aht20_read_temperature(&aht20_handle, &temperature_raw, &temperature_s) != 0) { return -999.0f; /* 读取失败 */ } return temperature_s; } /** * @brief 湿度获取函数 * @retval 相对湿度(%RH,保留小数),读取失败返回 -999.0f * @note 驱动返回的 humidity_s 是整数百分比,这里用原始值重新换算以保留小数位 */ float aht20_get_humidity(void) { uint32_t humidity_raw; uint8_t humidity_s; if (aht20_read_humidity(&aht20_handle, &humidity_raw, &humidity_s) != 0) { return -999.0f; /* 读取失败 */ } return (float)humidity_raw / 1048576.0f * 100.0f; } /* USER CODE END 1 */在i2c.h中添加以下代码
/* USER CODE BEGIN Includes */ #include "driver_aht20.h" /* USER CODE END Includes */ extern I2C_HandleTypeDef hi2c1; /* USER CODE BEGIN Private defines */ /* USER CODE END Private defines */ void MX_I2C1_Init(void); /* USER CODE BEGIN Prototypes */ uint8_t aht20_initialize(void); /* AHT20 初始化,须在 MX_I2C1_Init() 之后调用 */ float aht20_get_temperature(void); /* 获取温度,摄氏度,失败返回 -999.0f */ float aht20_get_humidity(void); /* 获取湿度,%RH,失败返回 -999.0f */ /* USER CODE END Prototypes */接下来就可以正式在main文件中编写代码了
在main.c文件中写入以下代码:
/* USER CODE BEGIN 2 */ uint8_t ret = aht20_initialize(); if (ret != 0) { printf("AHT20 初始化失败,错误码:%d\r\n", ret); } else { printf("AHT20 初始化成功\r\n"); } float temperature = 0; float humidity = 0; /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { temperature = aht20_get_temperature(); humidity = aht20_get_humidity(); if (temperature == -999.0f) { printf("温度读取失败\r\n"); } else { printf("温度:%.2f ℃\r\n", temperature); } if (humidity == -999.0f) { printf("湿度读取失败\r\n"); } else { printf("湿度:%.2f %%\r\n", humidity); } HAL_Delay(1000); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }在CubeIDE中点击编译,无报错无警告,但是可以看到代码中有提示,浮点型变量不支持,这里需要修改设置
右键工程点击properties,按如图步骤勾选为printf支持浮点型变量,点击应用并关闭
硬件按MX配置接好线,串口如有问题可2查看我的另一篇文章嵌入式开发学习记录:USART串口通信,printf重定向到串口打印-CSDN博客
本实验中AHT20模块SDA接开发板PB7,SCL接PB6,VCC接3v3,GND接GND,具体情况看自己配置的MX,为开发板烧录代码,打开波特律动串口助手选择串口并连接,上电后发现温湿度都读取失败了,而且初始化也失败了
经过探测I2C总线上是否有设备应答发现并没有设备应答,据此可知是模块问题,目前模块是通过杜邦线和面包板连接到开发板上的,先检查能初步检查的事,如接线不良,换过面包板孔位,防止是面包板内部线路问题
在换过如下图线路接线后正常接收了一会信息,可是拔掉重新插回的时候又失效了,这说明元器件没问题,软件配置也没问题,问题一定是在接线或者面包板上,一般需要在SDA和SCL与接线柱间串个5k欧至10k欧的电阻方能得到较为稳定的数据,大概率是这一问题