手搓温湿度仪(单片机普冉PY32F002AF15P6TU + 温湿度传感器 SHT40-AD1B-R2 + 0.96寸TFT IPS 显示屏)软件实现

书接上文,使用普冉单片机在ST7735显示屏上显示"Hello World!",现在实现使用单片机通过软件i2c读取温湿度传感器SHT40-AD1B-R2的温湿度数据,显示在0.96寸TFT IPS 显示屏上。

硬件原理图

引脚分配

硬件 功能 引脚 说明
显示屏 LCD SCK PA1 软件SPI时钟
显示屏 LCD MOSI PA7 软件SPI数据
显示屏 LCD DC PA4 命令/数据选择
显示屏 LCD CS PB0 片选
显示屏 LCD BLK PB1 背光
温湿度传感器 SHT40 SCL PF1 软件 I2C 时钟
温湿度传感器 SHT40 SDA PF0 软件 I2C 数据

LCD 显示布局(横屏模式)

复制代码
┌─────────────────────────────┐
│  Temp: 25.3 C               │  ← "Temp:" 红色(0xF800) 在 (5,24)
│  Humi: 60.5 %               │  ← "Humi:" 绿色(0x07E0) 在 (5,48)
└─────────────────────────────┘

每 2 秒自动刷新一次数值。如果传感器通信失败,会显示 "I2C Err""CRC Err"

实现内容

新增文件

文件 说明
Core/soft_i2c.h / soft_i2c.c 软件 I2C 驱动(Bit-bang),支持 Start/Stop/ACK/发送/接收
Core/sht40.h / sht40.c SHT40 传感器驱动,含 CRC8 校验、温湿度换算

修改文件

文件 修改内容
Core/main.c 集成 SHT40 初始化与读取循环,每 2 秒刷新 LCD 显示
PY32F002_SHT40_ST7735.uvprojx 添加 soft_i2c.c/h 到 Keil 工程的 Core 组

源文件

soft_i2c.c

复制代码
#include "soft_i2c.h"

/* ========== 简易微秒延时 ========== */
static void I2C_Delay_us(uint32_t us)
{
    volatile uint32_t count = us * 2U;
    while(count--) { __NOP(); }
}

/* ========== SCL/SDA 电平控制 ========== */
static inline void SCL_H(void)
{
    HAL_GPIO_WritePin(SOFT_I2C_SCL_PORT, SOFT_I2C_SCL_PIN, GPIO_PIN_SET);
}

static inline void SCL_L(void)
{
    HAL_GPIO_WritePin(SOFT_I2C_SCL_PORT, SOFT_I2C_SCL_PIN, GPIO_PIN_RESET);
}

static inline void SDA_H(void)
{
    HAL_GPIO_WritePin(SOFT_I2C_SDA_PORT, SOFT_I2C_SDA_PIN, GPIO_PIN_SET);
}

static inline void SDA_L(void)
{
    HAL_GPIO_WritePin(SOFT_I2C_SDA_PORT, SOFT_I2C_SDA_PIN, GPIO_PIN_RESET);
}

static inline uint8_t SDA_Read(void)
{
    return HAL_GPIO_ReadPin(SOFT_I2C_SDA_PORT, SOFT_I2C_SDA_PIN);
}

/* ========== 初始化 ========== */
void SoftI2C_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* 使能GPIO时钟 (SCL=PF1, SDA=PF0, 都在GPIOF上) */
    __HAL_RCC_GPIOF_CLK_ENABLE();

    /* SCL -> 推挽输出 */
    GPIO_InitStruct.Pin = SOFT_I2C_SCL_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(SOFT_I2C_SCL_PORT, &GPIO_InitStruct);

    /* SDA -> 开漏输出 (支持双向) */
    GPIO_InitStruct.Pin = SOFT_I2C_SDA_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;  /* 开漏,外接上拉 */
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(SOFT_I2C_SDA_PORT, &GPIO_InitStruct);

    /* 初始状态: SCL=1, SDA=1 (空闲) */
    SCL_H();
    SDA_H();
    I2C_Delay_us(SOFT_I2C_DELAY_US);
}

/* ========== 起始信号 S ========== */
void SoftI2C_Start(void)
{
    /* 确保SDA为输出高 */
    SDA_H();
    I2C_Delay_us(SOFT_I2C_DELAY_US);
    SCL_H();
    I2C_Delay_us(SOFT_I2C_DELAY_US);
    /* SDA下降沿,SCL高 */
    SDA_L();
    I2C_Delay_us(SOFT_I2C_DELAY_US);
    SCL_L();
    I2C_Delay_us(SOFT_I2C_DELAY_US);
}

/* ========== 停止信号 P ========== */
void SoftI2C_Stop(void)
{
    SDA_L();
    I2C_Delay_us(SOFT_I2C_DELAY_US);
    SCL_H();
    I2C_Delay_us(SOFT_I2C_DELAY_US);
    /* SDA上升沿,SCL高 */
    SDA_H();
    I2C_Delay_us(SOFT_I2C_DELAY_US);
}

/* ========== 等待ACK ========== */
uint8_t SoftI2C_WaitAck(void)
{
    uint8_t ack;

    /* SDA设为输入 (释放SDA线) */
    SDA_H();
    I2C_Delay_us(SOFT_I2C_DELAY_US);
    SCL_H();
    I2C_Delay_us(SOFT_I2C_DELAY_US);

    /* 读取ACK */
    ack = SDA_Read();

    SCL_L();
    I2C_Delay_us(SOFT_I2C_DELAY_US);

    return ack;  /* 0=ACK, 1=NACK */
}

/* ========== 发送ACK ========== */
void SoftI2C_SendAck(void)
{
    SDA_L();
    I2C_Delay_us(SOFT_I2C_DELAY_US);
    SCL_H();
    I2C_Delay_us(SOFT_I2C_DELAY_US);
    SCL_L();
    I2C_Delay_us(SOFT_I2C_DELAY_US);
}

/* ========== 发送NACK ========== */
void SoftI2C_SendNack(void)
{
    SDA_H();
    I2C_Delay_us(SOFT_I2C_DELAY_US);
    SCL_H();
    I2C_Delay_us(SOFT_I2C_DELAY_US);
    SCL_L();
    I2C_Delay_us(SOFT_I2C_DELAY_US);
}

/* ========== 发送1字节 ========== */
void SoftI2C_SendByte(uint8_t byte)
{
    uint8_t i;
    for (i = 0; i < 8; i++)
    {
        if (byte & 0x80)
            SDA_H();
        else
            SDA_L();
        byte <<= 1;
        I2C_Delay_us(SOFT_I2C_DELAY_US);
        SCL_H();
        I2C_Delay_us(SOFT_I2C_DELAY_US);
        SCL_L();
        I2C_Delay_us(SOFT_I2C_DELAY_US);
    }
}

/* ========== 读取1字节 ========== */
uint8_t SoftI2C_ReadByte(void)
{
    uint8_t i, byte = 0;

    /* SDA设为输入 (释放SDA线) */
    SDA_H();
    I2C_Delay_us(SOFT_I2C_DELAY_US);

    for (i = 0; i < 8; i++)
    {
        byte <<= 1;
        SCL_H();
        I2C_Delay_us(SOFT_I2C_DELAY_US);
        if (SDA_Read())
            byte |= 0x01;
        SCL_L();
        I2C_Delay_us(SOFT_I2C_DELAY_US);
    }
    return byte;
}

/* ========== 写数据 (设备地址 + 数据) ==========
 * addr: 7位设备地址,已左移1位 (如 0x44 << 1 = 0x88)
 * 返回: 0=成功, 1=无ACK
 */
uint8_t SoftI2C_Write(uint8_t addr, uint8_t *data, uint8_t len)
{
    uint8_t i;

    SoftI2C_Start();
    SoftI2C_SendByte(addr & 0xFE);  /* 写模式: bit0=0 */
    if (SoftI2C_WaitAck() != 0) {
        SoftI2C_Stop();
        return 1;
    }

    for (i = 0; i < len; i++)
    {
        SoftI2C_SendByte(data[i]);
        if (SoftI2C_WaitAck() != 0) {
            SoftI2C_Stop();
            return 1;
        }
    }

    SoftI2C_Stop();
    return 0;
}

/* ========== 读数据 (设备地址 + 读取) ==========
 * addr: 7位设备地址,已左移1位
 * 返回: 0=成功, 1=无ACK
 */
uint8_t SoftI2C_Read(uint8_t addr, uint8_t *data, uint8_t len)
{
    uint8_t i;

    SoftI2C_Start();
    SoftI2C_SendByte(addr | 0x01);  /* 读模式: bit0=1 */
    if (SoftI2C_WaitAck() != 0) {
        SoftI2C_Stop();
        return 1;
    }

    for (i = 0; i < len; i++)
    {
        data[i] = SoftI2C_ReadByte();
        if (i < len - 1)
            SoftI2C_SendAck();   /* 中间字节发ACK */
        else
            SoftI2C_SendNack();  /* 最后一个字节发NACK */
    }

    SoftI2C_Stop();
    return 0;
}

soft_i2c.h

复制代码
#ifndef __SOFT_I2C_H
#define __SOFT_I2C_H

#include "../Driver/py32f0xx_hal.h"
#include <stdint.h>

/* ========== 软件I2C引脚配置 ==========
 * 根据原理图: I2C_SCL -> PF1, I2C_SDA -> PF0
 */
#define SOFT_I2C_SCL_PORT   GPIOF
#define SOFT_I2C_SCL_PIN    GPIO_PIN_1
#define SOFT_I2C_SDA_PORT   GPIOF
#define SOFT_I2C_SDA_PIN    GPIO_PIN_0

/* I2C时序延时 (HSI 8MHz, 约100kHz I2C速率) */
#define SOFT_I2C_DELAY_US   5

/* 函数声明 */
void SoftI2C_Init(void);
void SoftI2C_Start(void);
void SoftI2C_Stop(void);
uint8_t SoftI2C_WaitAck(void);
void SoftI2C_SendAck(void);
void SoftI2C_SendNack(void);
void SoftI2C_SendByte(uint8_t byte);
uint8_t SoftI2C_ReadByte(void);

/* 高层封装 */
uint8_t SoftI2C_Write(uint8_t addr, uint8_t *data, uint8_t len);
uint8_t SoftI2C_Read(uint8_t addr, uint8_t *data, uint8_t len);

#endif

sht40.c

复制代码
#include "sht40.h"
#include "soft_i2c.h"
#include <string.h>

/* ========== SHT40 CRC8校验 ==========
 * 多项式: x^8 + x^5 + x^4 + 1 (0x31)
 * 初始值: 0xFF
 */
static uint8_t SHT40_CRC8(uint8_t *data, uint8_t len)
{
    const uint8_t poly = 0x31U;
    uint8_t crc = 0xFFU;
    uint8_t i, j;

    for (i = 0; i < len; i++)
    {
        crc ^= data[i];
        for (j = 0; j < 8; j++)
        {
            crc = (crc & 0x80U) ? ((crc << 1U) ^ poly) : (crc << 1U);
        }
    }
    return crc;
}

/* ========== 传感器初始化检测 ==========
 * 发送软复位指令检测传感器是否在线
 * 返回: 0=成功, 1=失败
 */
uint8_t SHT40_Init(void)
{
    uint8_t reset_cmd = SHT40_CMD_SOFT_RESET;

    /* 发送软复位指令 */
    if (SoftI2C_Write(SHT40_I2C_ADDR, &reset_cmd, 1) != 0)
        return 1;

    /* 复位等待 1ms */
    HAL_Delay(1);

    return 0;
}

/* ========== 读取温湿度数据 ==========
 * 使用高精度测量模式 (0xFD)
 * 测量时间: 约8.2ms (典型值)
 *
 * 返回数据格式 (6字节):
 *   [0] T_MSB, [1] T_LSB, [2] T_CRC
 *   [3] H_MSB, [4] H_LSB, [5] H_CRC
 *
 * 返回: 0=成功, 1=I2C通信失败, 2=CRC校验失败
 */
uint8_t SHT40_ReadData(SHT40_Data_t *data)
{
    uint8_t buf[6] = {0};
    uint16_t temp_raw, humi_raw;
    uint8_t meas_cmd = SHT40_MEAS_HIGH_PREC;

    /* 发送测量指令 */
    if (SoftI2C_Write(SHT40_I2C_ADDR, &meas_cmd, 1) != 0)
        return 1;

    /* 等待转换完成 (高精度模式典型8.2ms,留足余量) */
    HAL_Delay(10);

    /* 读取6字节数据 */
    if (SoftI2C_Read(SHT40_I2C_ADDR, buf, 6) != 0)
        return 1;

    /* CRC校验 */
    if ((SHT40_CRC8(&buf[0], 2) != buf[2]) || (SHT40_CRC8(&buf[3], 2) != buf[5]))
        return 2;

    /* 原始数据拼接 */
    temp_raw = ((uint16_t)buf[0] << 8) | buf[1];
    humi_raw = ((uint16_t)buf[3] << 8) | buf[4];

    /* 官方公式换算 (datasheet公式):
     * Temperature = -45 + 175 * (raw / 65535)
     * Humidity    = -6  + 125 * (raw / 65535)
     */
    data->Temperature = -45.0f + 175.0f * ((float)temp_raw / 65535.0f);
    data->Humidity    = -6.0f  + 125.0f * ((float)humi_raw / 65535.0f);

    /* 湿度限幅 0~100% */
    if (data->Humidity < 0.0f)   data->Humidity = 0.0f;
    if (data->Humidity > 100.0f) data->Humidity = 100.0f;

    return 0;
}

/* ========== 读取序列号 (可选,用于验证传感器) ==========
 * 返回: 0=成功, 1=失败
 */
uint8_t SHT40_ReadSerial(uint32_t *serial)
{
    uint8_t buf[6] = {0};
    uint8_t cmd = SHT40_CMD_READ_SERIAL;

    if (SoftI2C_Write(SHT40_I2C_ADDR, &cmd, 1) != 0)
        return 1;

    HAL_Delay(1);

    if (SoftI2C_Read(SHT40_I2C_ADDR, buf, 6) != 0)
        return 1;

    if ((SHT40_CRC8(&buf[0], 2) != buf[2]) || (SHT40_CRC8(&buf[3], 2) != buf[5]))
        return 2;

    *serial = ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) |
              ((uint32_t)buf[3] << 8)  | buf[4];

    return 0;
}

sht40.h

复制代码
#ifndef __SHT40_H
#define __SHT40_H

#include "../Driver/py32f0xx_hal.h"
#include <stdint.h>

/* ========== SHT40 硬件配置 ========== */
#define SHT40_I2C_ADDR        (0x44U << 1U)   /* 7位地址0x44,左移1位 */

/* ========== SHT40 命令定义 ========== */
#define SHT40_MEAS_HIGH_PREC  0xFDU   /* 高精度测量 (重复性高) */
#define SHT40_MEAS_MED_PREC   0xF6U   /* 中精度测量 */
#define SHT40_MEAS_LOW_PREC   0xE0U   /* 低精度测量 (最快) */
#define SHT40_CMD_SOFT_RESET  0x94U   /* 软复位 */
#define SHT40_CMD_READ_SERIAL 0x89U   /* 读取序列号 */

/* ========== 数据结构体 ========== */
typedef struct {
    float Temperature;  /* 单位:摄氏度 (C) */
    float Humidity;     /* 单位:相对湿度 %RH */
} SHT40_Data_t;

/* ========== 函数声明 ========== */
uint8_t SHT40_Init(void);
uint8_t SHT40_ReadData(SHT40_Data_t *data);
uint8_t SHT40_ReadSerial(uint32_t *serial);

#endif

main.c

复制代码
#include "st7735.h"
#include "main.h"
#include "fonts.h"
#include "soft_i2c.h"
#include "sht40.h"
#include <stdio.h>
#include <string.h>

#define AUTO_BAUD_MODE0    /*自动波特率检测模式选择,从start位开始测量;若屏蔽选择下降沿到下降沿测量*/

// 外设句柄
I2C_HandleTypeDef hi2c1;
SPI_HandleTypeDef hspi1;
UART_HandleTypeDef UartHandle;  // 全局变量,名称必须和py32f0xx_it.c中一致


// UART1初始化函数
void MX_USART1_UART_Init(void)
{
  /* USART1初始化 */
  __HAL_RCC_USART1_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  UartHandle.Instance          = USART1;
  UartHandle.Init.BaudRate     = 115200;
  UartHandle.Init.WordLength   = UART_WORDLENGTH_8B;
  UartHandle.Init.StopBits     = UART_STOPBITS_1;
  UartHandle.Init.Parity       = UART_PARITY_NONE;
  UartHandle.Init.HwFlowCtl    = UART_HWCONTROL_NONE;
  UartHandle.Init.Mode         = UART_MODE_TX_RX;

  UartHandle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_AUTOBAUDRATE_INIT;
  UartHandle.AdvancedInit.AutoBaudRateEnable = UART_ADVFEATURE_AUTOBAUDRATE_ENABLE; /* 自动波特率使能 */
#ifdef AUTO_BAUD_MODE0
  UartHandle.AdvancedInit.AutoBaudRateMode = UART_ADVFEATURE_AUTOBAUDRATE_ONSTARTBIT; /* 自动波特率检测模式从start位开始测量波特率,上位机发送0x7f即可 */                             
#else
  UartHandle.AdvancedInit.AutoBaudRateMode = UART_ADVFEATURE_AUTOBAUDRATE_ONFALLINGEDGE; /* 自动波特率检测模式下降沿到下降沿测量,上位机发送0x55即可 */
#endif
  if (HAL_UART_DeInit(&UartHandle) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UART_Init(&UartHandle) != HAL_OK)
  {
    Error_Handler();
  }
  
  /* 使能USART1中断 */
  HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(USART1_IRQn);
}

/*
 * 纯软件SPI测试版本
 * 不初始化SPI/UART/I2C,只用GPIO模拟SPI时序驱动ST7735
 * 目的:排除硬件SPI外设问题,验证屏幕是否正常工作
 *
 * 接线:
 *   PA1 = SCK  (软件模拟时钟)
 *   PA7 = MOSI (软件模拟数据)
 *   PA4 = DC   (命令/数据选择)
 *   PB0 = CS   (片选,低电平有效)
 *   PB1 = BLK  (背光,高电平点亮)
 */

/* ========== 简易延时 ========== */
static void Delay_us(uint32_t us)
{
    /* HSI 8MHz, 约125ns per loop */
    volatile uint32_t count = us * 2U;
    while(count--) { __NOP(); }
}

static void Delay_ms(uint32_t ms)
{
    while(ms--) { Delay_us(1000); }
}

/* ========== 软件SPI底层 ========== */

/* 软件SPI发送1字节 (MSB first, SPI Mode 0: CPOL=0, CPHA=0) */
static void SoftSPI_WriteByte(uint8_t data)
{
    uint8_t i;
    for (i = 0; i < 8; i++)
    {
        /* 设置数据位 (MSB first) */
        if (data & 0x80)
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);
        else
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);

        /* SCK上升沿,ST7735采样数据 */
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);

        /* SCK下降沿,准备下一位 */
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);

        data <<= 1;
    }
}

/* 发送命令 (DC=0) */
static void LCD_WriteCmd(uint8_t cmd)
{
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);  /* DC = 0 (命令) */
    SoftSPI_WriteByte(cmd);
}

/* 发送数据 (DC=1) */
static void LCD_WriteData8(uint8_t data)
{
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);    /* DC = 1 (数据) */
    SoftSPI_WriteByte(data);
}

/* 发送16位数据 */
static void LCD_WriteData16(uint16_t data)
{
    LCD_WriteData8((uint8_t)(data >> 8));
    LCD_WriteData8((uint8_t)(data & 0xFF));
}

/* ========== ST7735初始化命令 ========== */
static void LCD_Init(void)
{
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);  /* CS = 0 (选中) */

    /* Software Reset */
    LCD_WriteCmd(0x01);
    Delay_ms(150);

    /* Out of Sleep */
    LCD_WriteCmd(0x11);
    Delay_ms(255);

    /* Frame Rate Control (Normal Mode) */
    LCD_WriteCmd(0xB1);
    LCD_WriteData8(0x01);
    LCD_WriteData8(0x2C);
    LCD_WriteData8(0x2D);

    /* Frame Rate Control (Idle Mode) */
    LCD_WriteCmd(0xB2);
    LCD_WriteData8(0x01);
    LCD_WriteData8(0x2C);
    LCD_WriteData8(0x2D);

    /* Frame Rate Control (Partial Mode) */
    LCD_WriteCmd(0xB3);
    LCD_WriteData8(0x01);
    LCD_WriteData8(0x2C);
    LCD_WriteData8(0x2D);
    LCD_WriteData8(0x01);
    LCD_WriteData8(0x2C);
    LCD_WriteData8(0x2D);

    /* Display Inversion Control */
    LCD_WriteCmd(0xB4);
    LCD_WriteData8(0x07);

    /* Power Control 1 */
    LCD_WriteCmd(0xC0);
    LCD_WriteData8(0xA2);
    LCD_WriteData8(0x02);
    LCD_WriteData8(0x84);

    /* Power Control 2 */
    LCD_WriteCmd(0xC1);
    LCD_WriteData8(0xC5);

    /* Power Control 3 */
    LCD_WriteCmd(0xC2);
    LCD_WriteData8(0x0A);
    LCD_WriteData8(0x00);

    /* Power Control 4 */
    LCD_WriteCmd(0xC3);
    LCD_WriteData8(0x8A);
    LCD_WriteData8(0x2A);

    /* Power Control 5 */
    LCD_WriteCmd(0xC4);
    LCD_WriteData8(0x8A);
    LCD_WriteData8(0xEE);

    /* VCOM Control */
    LCD_WriteCmd(0xC5);
    LCD_WriteData8(0x0E);

    /* Inversion Off - 先不反色,测试基础颜色 */
    LCD_WriteCmd(0x20);  /* Inversion Off */

    /* Memory Access Control - 横屏模式, 交换行列 */
    LCD_WriteCmd(0x36);
    LCD_WriteData8(0x68);  /* MV=1(交换行列), MY=0, MX=1, ML=0, BGR */
                           /* 0x68 = 0110 1000: MY=0, MX=1, MV=1, ML=0, BGR(bit3=1) */

    /* Color Mode: 16-bit */
    LCD_WriteCmd(0x3A);
    LCD_WriteData8(0x05);

    /* 先不设CASET/RASET范围,让填充函数自己设 */

    /* Gamma Correction (Positive) */
    LCD_WriteCmd(0xE0);
    LCD_WriteData8(0x02); LCD_WriteData8(0x1C);
    LCD_WriteData8(0x07); LCD_WriteData8(0x12);
    LCD_WriteData8(0x37); LCD_WriteData8(0x32);
    LCD_WriteData8(0x29); LCD_WriteData8(0x2D);
    LCD_WriteData8(0x29); LCD_WriteData8(0x25);
    LCD_WriteData8(0x2B); LCD_WriteData8(0x39);
    LCD_WriteData8(0x00); LCD_WriteData8(0x01);
    LCD_WriteData8(0x03); LCD_WriteData8(0x10);

    /* Gamma Correction (Negative) */
    LCD_WriteCmd(0xE1);
    LCD_WriteData8(0x03); LCD_WriteData8(0x1D);
    LCD_WriteData8(0x07); LCD_WriteData8(0x06);
    LCD_WriteData8(0x2E); LCD_WriteData8(0x2C);
    LCD_WriteData8(0x29); LCD_WriteData8(0x2D);
    LCD_WriteData8(0x2E); LCD_WriteData8(0x2E);
    LCD_WriteData8(0x37); LCD_WriteData8(0x3F);
    LCD_WriteData8(0x00); LCD_WriteData8(0x00);
    LCD_WriteData8(0x02); LCD_WriteData8(0x10);

    /* Normal Display On */
    LCD_WriteCmd(0x13);
    Delay_ms(10);

    /* Display On */
    LCD_WriteCmd(0x29);
    Delay_ms(100);

    /* 关闭睡眠模式,防止自动息屏 */
    LCD_WriteCmd(0x38);  /* Idle Mode Off */
    Delay_ms(10);
}

/* ========== 全屏填充颜色 ========== */
static void LCD_FillColor(uint16_t color)
{
    uint16_t i;
    uint32_t total = 162U * 132U;  /* 写满整个内部显存,避免偏移计算误差 */

    /* 设置写RAM窗口 (内部显存全范围 132x162) */
    /* 横屏模式下 MV=1, 行列已交换, 但这里写的是内部显存地址, 不变 */
    LCD_WriteCmd(0x2A);  /* Column Address Set */
    LCD_WriteData8(0x00); LCD_WriteData8(0x00);  /* XS = 0 */
    LCD_WriteData8(0x00); LCD_WriteData8(0xA1);  /* XE = 161 */

    LCD_WriteCmd(0x2B);  /* Row Address Set */
    LCD_WriteData8(0x00); LCD_WriteData8(0x00);  /* YS = 0 */
    LCD_WriteData8(0x00); LCD_WriteData8(0x83);  /* YE = 131 */

    LCD_WriteCmd(0x2C);  /* Memory Write */

    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);  /* DC = 1 */

    for (i = 0; i < total; i++)
    {
        SoftSPI_WriteByte((uint8_t)(color >> 8));
        SoftSPI_WriteByte((uint8_t)(color & 0xFF));
    }
}

/* ========== 显示单个字符 (窗口写入模式,高效) ========== */
static void LCD_DrawChar(uint16_t x, uint16_t y, char ch, FontDef *font, uint16_t color, uint16_t bgColor)
{
    uint32_t i, j, b;
    uint16_t tmp;

    /* 设置写RAM窗口: 列从 x 到 x+width-1, 行从 y 到 y+height-1 */
    LCD_WriteCmd(0x2A);  /* Column Address Set */
    LCD_WriteData8((uint8_t)(x >> 8));
    LCD_WriteData8((uint8_t)(x & 0xFF));
    LCD_WriteData8((uint8_t)((x + font->width - 1) >> 8));
    LCD_WriteData8((uint8_t)((x + font->width - 1) & 0xFF));

    LCD_WriteCmd(0x2B);  /* Row Address Set */
    LCD_WriteData8((uint8_t)(y >> 8));
    LCD_WriteData8((uint8_t)(y & 0xFF));
    LCD_WriteData8((uint8_t)((y + font->height - 1) >> 8));
    LCD_WriteData8((uint8_t)((y + font->height - 1) & 0xFF));

    LCD_WriteCmd(0x2C);  /* Memory Write */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);  /* DC = 1 */

    /* 字体数据: 行优先存储, 每行一个uint16_t, font->height个数据/字符 */
    /* 有效位在高位: bit(16-width) ~ bit15 */
    for (j = 0; j < font->height; j++)
    {
        tmp = font->data[(ch - 32) * font->height + j];
        for (i = 0; i < font->width; i++)
        {
            b = tmp & (1 << (15 - i));
            if (b)
                LCD_WriteData16(color);
            else
                LCD_WriteData16(bgColor);
        }
    }
}

/* ========== 显示字符串 ========== */
static void LCD_DrawString(uint16_t x, uint16_t y, const char *str, FontDef *font, uint16_t color, uint16_t bgColor)
{
    uint16_t curX = x;
    while (*str != '\0')
    {
        LCD_DrawChar(curX, y, *str, font, color, bgColor);
        curX += font->width;
        str++;
    }
}

/* ========== 画测试像素块 (用于验证显示是否正常) ========== */
static void LCD_DrawBlock(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color)
{
    uint32_t i, total = (uint32_t)w * h;

    LCD_WriteCmd(0x2A);
    LCD_WriteData8((uint8_t)(x >> 8));
    LCD_WriteData8((uint8_t)(x & 0xFF));
    LCD_WriteData8((uint8_t)((x + w - 1) >> 8));
    LCD_WriteData8((uint8_t)((x + w - 1) & 0xFF));

    LCD_WriteCmd(0x2B);
    LCD_WriteData8((uint8_t)(y >> 8));
    LCD_WriteData8((uint8_t)(y & 0xFF));
    LCD_WriteData8((uint8_t)((y + h - 1) >> 8));
    LCD_WriteData8((uint8_t)((y + h - 1) & 0xFF));

    LCD_WriteCmd(0x2C);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);  /* DC = 1 */

    for (i = 0; i < total; i++)
    {
        LCD_WriteData16(color);
    }
}

/* ========== 浮点数转字符串 (简单实现,无sprintf依赖) ========== */
static void FloatToString(float value, char *buf, uint8_t decimal_places)
{
    int32_t int_part;
    int32_t frac_part;
    uint8_t is_negative = 0;
    uint8_t i = 0;
    uint8_t j;
    char temp[16];
    int32_t multiplier = 1;
    uint8_t k;
    uint8_t frac_digits;

    if (value < 0) {
        is_negative = 1;
        value = -value;
    }

    /* 整数部分 */
    int_part = (int32_t)value;

    /* 小数部分 */
    {
        float frac = value - (float)int_part;
        for (k = 0; k < decimal_places; k++) multiplier *= 10;
        frac_part = (int32_t)(frac * multiplier + 0.5f);
    }

    /* 处理进位 (如 99.99 -> 100.00) */
    if (frac_part >= multiplier) {
        frac_part = 0;
        int_part++;
    }

    /* 转换整数部分到字符串 (倒序) */
    if (int_part == 0) {
        temp[i++] = '0';
    } else {
        while (int_part > 0) {
            temp[i++] = '0' + (int_part % 10);
            int_part /= 10;
        }
    }

    /* 添加负号 */
    if (is_negative) {
        buf[0] = '-';
        j = 1;
    } else {
        j = 0;
    }

    /* 倒序写入整数部分 */
    while (i > 0) {
        buf[j++] = temp[--i];
    }

    /* 小数点 */
    if (decimal_places > 0) {
        buf[j++] = '.';

        /* 转换小数部分 */
        i = 0;
        if (frac_part == 0) {
            for (k = 0; k < decimal_places; k++) {
                buf[j++] = '0';
            }
        } else {
            /* 倒序转换小数部分 */
            frac_digits = 0;
            while (frac_part > 0) {
                temp[i++] = '0' + (frac_part % 10);
                frac_part /= 10;
                frac_digits++;
            }
            /* 补前导零 */
            for (k = frac_digits; k < decimal_places; k++) {
                buf[j++] = '0';
            }
            /* 倒序写入 */
            while (i > 0) {
                buf[j++] = temp[--i];
            }
        }
    }

    buf[j] = '\0';
}

/* ========== 系统时钟 ========== */
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    RCC_OscInitStruct.OscillatorType = 0x01U;
    RCC_OscInitStruct.HSIState = 0x01U;
    RCC_OscInitStruct.HSIDiv = 0x00U;
    RCC_OscInitStruct.HSICalibrationValue = 0x00U;
    RCC_OscInitStruct.LSIState = 0x00U;
    if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { while(1); }

    RCC_ClkInitStruct.ClockType = 0x07U;
    RCC_ClkInitStruct.SYSCLKSource = 0x00U;
    RCC_ClkInitStruct.AHBCLKDivider = 0x00U;
    RCC_ClkInitStruct.APB1CLKDivider = 0x00U;
    if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, 0x00U) != HAL_OK) { while(1); }
}

/* ========== 主函数 ========== */
int main(void)
{
    SHT40_Data_t sht40_data;
    uint8_t sht40_status;
    char temp_str[16];
    char humi_str[16];
    uint32_t loop_count = 0;

    HAL_Init();
    SystemClock_Config();

    /* 使能GPIO时钟 */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_GPIOF_CLK_ENABLE();  /* PF0(SDA), PF1(SCL) for SHT40 I2C */

    MX_USART1_UART_Init();
    LOG_INFO("System Init Done!!!");

    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull  = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;

    /* PA1(SCK)、PA4(DC)、PA7(MOSI) → 推挽输出 */
    GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_7;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* PB0(CS)、PB1(BLK) → 推挽输出 */
    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* 初始电平 */
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);    /* BLK = 1 (点亮背光) */
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);    /* CS = 1 (未选中) */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);    /* DC = 1 */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);  /* SCK = 0 (空闲低) */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);  /* MOSI = 0 */

    LOG_INFO("GPIO Init Done!!!");

    /* 初始化软件I2C (用于SHT40) */
    SoftI2C_Init();
    LOG_INFO("SoftI2C Init Done!!!");

    /* 初始化SHT40传感器 */
    sht40_status = SHT40_Init();
    if (sht40_status == 0) {
        LOG_INFO("SHT40 Init OK!!!");
    } else {
        LOG_INFO("SHT40 Init FAILED!!!");
    }

    /* 初始化屏幕 */
    LCD_Init();

    /* 全屏白色背景 */
    __disable_irq();
    LCD_FillColor(0xFFFF);  /* 全白 */
    __enable_irq();

    /* 显示标题 */
    __disable_irq();
    LCD_DrawString(5, 24, "Temp:", &Font_11x18, 0xF800, 0xFFFF);
    LCD_DrawString(5, 48, "Humi:", &Font_11x18, 0x0000, 0xFFFF);
    __enable_irq();

    /* 主循环: 定时读取并刷新显示 */
    while (1)
    {
        /* 读取温湿度 */
        sht40_status = SHT40_ReadData(&sht40_data);

        if (sht40_status == 0)
        {
            /* 转换浮点数为字符串 */
            FloatToString(sht40_data.Temperature, temp_str, 1);
            FloatToString(sht40_data.Humidity, humi_str, 1);

            /* 拼接单位 */
            strcat(temp_str, " C");
            strcat(humi_str, " %");

            LOG_INFO("Temp:");
            LOG_INFO(temp_str);
            LOG_INFO("Humi:");
            LOG_INFO(humi_str);
        }
        else if (sht40_status == 2)
        {
            strcpy(temp_str, "CRC Err");
            strcpy(humi_str, "CRC Err");
            LOG_INFO("SHT40 CRC Error!!!");
        }
        else
        {
            strcpy(temp_str, "I2C Err");
            strcpy(humi_str, "I2C Err");
            LOG_INFO("SHT40 I2C Error!!!");
        }

        /* 刷新显示数值 (覆盖旧值区域) */
        __disable_irq();
        /* 温度值区域: x=55~155, y=24~42, 用背景色覆盖 */
        LCD_DrawBlock(55, 24, 100, 18, 0xFFFF);
        LCD_DrawString(55, 24, temp_str, &Font_11x18, 0xF800, 0xFFFF);

        /* 湿度值区域: x=55~155, y=48~66, 用背景色覆盖 */
        LCD_DrawBlock(55, 48, 100, 18, 0xFFFF);
        LCD_DrawString(55, 48, humi_str, &Font_11x18, 0x0000, 0xFFFF);
        __enable_irq();

        /* 每2秒刷新一次 */
        HAL_Delay(2000);
        loop_count++;
    }
}

// 错误处理
void Error_Handler(void)
{
    while(1)
    {
        HAL_Delay(100U);
    }
}

#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif

最终效果

相关推荐
黑白园1 小时前
STM32F103ZET6移植-电机2804-驱动板SimpleFOC Mini实现速度开环_位置开环控制(二、代码移植及功能实现)
stm32·单片机·嵌入式硬件
深圳市晨芯阳科技有限公司1 小时前
HC9623晨芯阳400mA带载、18V耐压、低压差快速响应LDO
单片机·嵌入式硬件·ldo线性稳压ic·深圳市晨芯阳科技有限公司
chengpei1472 小时前
电信创维E900盒子固件备份刷机
嵌入式硬件
星夜夏空9911 小时前
STM32单片机学习(10)——GPIO输入
stm32·单片机·学习
secondyoung13 小时前
Arm架构解析:Cortex-R系列架构概览
arm开发·单片机·嵌入式硬件·mcu·arm
FreakStudio15 小时前
开源分享|用MicroPython 做了个 AI 小鸡,它会长大,还记得我所有的情绪
python·单片机·嵌入式·面向对象·并行计算·电子diy·电子计算机
黑白园17 小时前
I2C_GPIO模拟 读取AS5600编码器数据
stm32·单片机·嵌入式硬件
羽获飞17 小时前
从零开始学嵌入式之STM32——34.ADC-模数转换
stm32·单片机·嵌入式硬件
csg110717 小时前
智慧养殖篇(四):猪场自动化饲喂与疫病预警
运维·单片机·嵌入式硬件·物联网·自动化