发作品签到
专业版

#第九届立创电赛#[训练营]传感器

工程标签

141
0
0
0

简介

基于SHT40的桌面温湿度计,同时,使用esp32作为主控可以实现wifi无线发送功能。硬件支持制作小电视

简介:基于SHT40的桌面温湿度计,同时,使用esp32作为主控可以实现wifi无线发送功能。硬件支持制作小电视

开源协议

Public Domain

(未经作者授权,禁止转载)
创建时间:2024-07-05 12:39:51更新时间:2025-02-18 06:19:37

描述

(啊啊啊!来不及啦,时间不够啦,赶紧做完温湿度交上)

* 1、项目功能介绍


实现SHT桌面温湿度计功能

esp32无线发送/接收数据

使用LCD作为显示屏

mpu6050作为交互

可选按键作为交互

(后续我有时间的话会学一下LVGL、SD卡,将此项目开发成小电视)

(远程传感器控制中心,有时间的话实现。暂时想的是esp32做远程传感器模块)

*2、项目属性


原创

 

* 3、开源协议


Public Domain

*4、硬件部分


主控板:

主控部分:esp32模组

                 主控部分使用esp32,这样我可以直接使用wifi功能无线发送/接收数据。虽然我之前并没有学习过如何使用esp32,但是咱就是不怕困难,这次就边学边做。

显示部分:2.8寸LCD显示屏(插接)

                 

交互:MPU6050、按键(可选)

                 可以不焊接连接按键板的fpc座子,使用mpu6050作为交互。也可以使用fpc连线连接到其他设计好的板子拓展功能。

电源:聚合物锂电池;tp4056充电。

                 想着是想画一块和lcd差不多大小的板子,但画出来发现空了一块,干脆把供电换成锂电池,并且加上充放电模块。

按键板:fpc接线,随手一画

                 按照随手画的面板的鼓包按键的位置布局了一下按键,最后设计外壳的时候可能会做一个带按键的。

面板:随手一画(可能有点丑)

远程传感器板(没时间了,先不做了):

                 使用esp32芯片+陶瓷天线+传感器制作远程传感器模块,这样就可以在家里各个角落捕获一些信息,然后在主控板的lcd上显示。当前想到的一些场景是温度传感器模块安装在易起火的地方作火灾报警;燃气传感器安装在燃气灶检测燃气泄漏。

 

*5、软件部分


由于sht40被我烧掉,淘宝商家发错货等各种原因,加上我开始工作时间不够,这次就只做了测温湿度和显示。(先交上作业,后面再慢慢完善功能吧)

正文:首先,我第一次使用esp32,而且没有使用Arduino编程而是使用esp-idf库。程序写的不好,还请各位大佬多指教

一、 i2c:这一部分网上基本没有最新版esp-idf库的资料,我是跟着官方文档写的程序

文档:快速入门 - ESP32 - — ESP-IDF 编程指南 v5.3 文档 (espressif.com)

这里我不知道怎么弄代码好,就直接粘贴了,有时间研究一下markdown看看

1. i2c初始化:因为我用到了mpu6050和sht40两种i2c设备,所以i2c总线初始化和设备初始化分开写了

i2c_master_bus_handle_t i2c_handle;     //i2c总线的句柄后面可能会用到,所以我直接定义成了全局变量
i2c_master_dev_handle_t sht40_handle;//sht40句柄同理
void i2c_init()
{
    i2c_master_bus_config_ti2c_cfg=
    {
        .clk_source = I2C_CLK_SRC_DEFAULT,
        .i2c_port = I2C_NUM_0,
        .scl_io_num = GPIO_NUM_36,
        .sda_io_num = GPIO_NUM_37,
        .flags.enable_internal_pullup = true,
        .glitch_ignore_cnt = 7,
    };
    ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_cfg, &i2c_handle));
}

2. i2c设备穷举:初始化以后可以穷举一下看看i2c设备(sht40)连接是否正常

void i2c_probe()
{
    printf("i2c device list:\r\n");
    for (uint16_t i = 0; i <= 0x77; i++)
    {
        if (i2c_master_probe(i2c_handle, i, 100) == ESP_OK)
        {
            printf("%d  ", i);
        }
    }
    printf("\r\n");
}
正常的话会有68输出,68转16进制0x44刚好是sht40的地址
3. sht40初始化:实际上就是创建并注册i2c设备句柄
void sht40_init()
{
    i2c_device_config_t sht40_cfg = {
        .dev_addr_length = I2C_ADDR_BIT_LEN_7,
        .device_address = 0x44,
        .scl_speed_hz = 100000,
    };
    ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_handle, &sht40_cfg, &sht40_handle));
}
4. 读温湿度:初始化完i2c以后就可以愉快的读取温湿度啦
//sht40有三种测温命令,这里command_measure就是传入测温命令,常用的应该是0xfd吧
void sht40_read_temp(uint8_t command_measure, float *data)
{
    uint8_t buf[6];
    uint16_t t_data, rh_data;
    ESP_ERROR_CHECK(i2c_master_transmit(sht40_handle, &command_measure, 1, 100));
    vTaskDelay(10/portTICK_PERIOD_MS);
    ESP_ERROR_CHECK(i2c_master_receive(sht40_handle, buf, 6, 100));
    t_data = ((uint16_t)buf[0] << 8) + buf[1];
    rh_data = ((uint16_t)buf[3] << 8) + buf[4];
    data[0] = ((float)t_data / 65535) * 175 - 45;
    data[1] = ((float)rh_data / 65535) * 125 - 6;
}
二、lcd:这部分挺复杂的,有一些代码我是从官方示例中抄下来的
1. 全局变量
spi_device_handle_t lcd_handle;//同理,我把spi总线句柄设置成了全局变量
uint16_t LCD_height = 240;
uint16_t LCD_width = 320;
//下面抄的官方示例(显示方向有改动)
DRAM_ATTR static const lcd_init_cmd_t st_init_cmds[] = {
    /* Memory Data Access Control, MX=MV=1, MY=ML=MH=0, RGB=0 */
    {0x36, {0xa8}, 1},
    /* Interface Pixel Format, 16bits/pixel for RGB/MCU interface */
    {0x3A, {0x55}, 1},
    /* Porch Setting */
    {0xB2, {0x0c, 0x0c, 0x00, 0x33, 0x33}, 5},
    /* Gate Control, Vgh=13.65V, Vgl=-10.43V */
    {0xB7, {0x45}, 1},
    /* VCOM Setting, VCOM=1.175V */
    {0xBB, {0x2B}, 1},
    /* LCM Control, XOR: BGR, MX, MH */
    {0xC0, {0x2C}, 1},
    /* VDV and VRH Command Enable, enable=1 */
    {0xC2, {0x01, 0xff}, 2},
    /* VRH Set, Vap=4.4+... */
    {0xC3, {0x11}, 1},
    /* VDV Set, VDV=0 */
    {0xC4, {0x20}, 1},
    /* Frame Rate Control, 60Hz, inversion=0 */
    {0xC6, {0x0f}, 1},
    /* Power Control 1, AVDD=6.8V, AVCL=-4.8V, VDDS=2.3V */
    {0xD0, {0xA4, 0xA1}, 1},
    /* Positive Voltage Gamma Control */
    {0xE0, {0xD0, 0x00, 0x05, 0x0E, 0x15, 0x0D, 0x37, 0x43, 0x47, 0x09, 0x15, 0x12, 0x16, 0x19}, 14},
    /* Negative Voltage Gamma Control */
    {0xE1, {0xD0, 0x00, 0x05, 0x0D, 0x0C, 0x06, 0x2D, 0x44, 0x40, 0x0E, 0x1C, 0x18, 0x16, 0x19}, 14},
    /* Sleep Out */
    {0x11, {0}, 0x80},
    /* Display On */
    {0x29, {0}, 0x80},
    {0, {0}, 0xff}
};
2. 初始化:这里只有lcd挂载到了spi总线上,sd卡单独挂载到了其他spi总线,所以lcd初始化和spi初始化我写到了一块
//这里我理解的是发送前会调用这个函数
void lcd_pre_callback(spi_transaction_t *t)
{
    int dc = (int)t->user;
    gpio_set_level(LCD_DC_IO_NUM, dc);
}
void lcd_init()
{
    /*spi初始化 */
    //spi总线配置结构体
    spi_bus_config_t spi_cfg = {
        .sclk_io_num = GPIO_NUM_12,
        .mosi_io_num = GPIO_NUM_11,
        .flags = SPICOMMON_BUSFLAG_MASTER,
        .miso_io_num = -1,
        .quadhd_io_num = -1,
        .quadwp_io_num = -1,
    };
    //lcd设备配置结构体
    spi_device_interface_config_t lcd_cfg = {
        .mode = 0,
        .clock_speed_hz = 10000000,
        .queue_size = 8,
        .spics_io_num = GPIO_NUM_10,
        .pre_cb = lcd_pre_callback,
    };
    ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &spi_cfg, SPI_DMA_CH_AUTO));
    ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &lcd_cfg, &lcd_handle));
    //rst dc bl 引脚初始化
    gpio_config_t lcd_io_cfg = {
        .mode = GPIO_MODE_OUTPUT,
        .pull_up_en = GPIO_PULLUP_ENABLE,
        .pin_bit_mask = (1ull << LCD_DC_IO_NUM) | (1ull << LCD_BL_IO_NUM) | (1ull << LCD_RST_IO_NUM),
    };
    gpio_config(&lcd_io_cfg);
    //硬件复位
    gpio_set_level(LCD_RST_IO_NUM, 0);
    vTaskDelay(100 / portTICK_PERIOD_MS);
    gpio_set_level(LCD_RST_IO_NUM, 1);
    vTaskDelay(100 / portTICK_PERIOD_MS);
    //初始化
    int cmd = 0;
    while (st_init_cmds[cmd].databytes != 0xff) {
        lcd_cmd(st_init_cmds[cmd].cmd, false);
        lcd_data(st_init_cmds[cmd].data, st_init_cmds[cmd].databytes & 0x1F);
        if (st_init_cmds[cmd].databytes & 0x80) {
            vTaskDelay(100 / portTICK_PERIOD_MS);
        }
        cmd++;
    }

    ///Enable backlight
    gpio_set_level(LCD_BL_IO_NUM, 1);
}
 3. 发送命令和数据的函数:抄的官方示例
void lcd_cmd(const uint8_t cmd, bool keep_cs_active)
{
    esp_err_t ret;
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));       //Zero out the transaction
    t.length = 8;                   //Command is 8 bits
    t.tx_buffer = &cmd;             //The data is the cmd itself
    t.user = LCD_CMD_MODE;              //D/C needs to be set to 0
    if (keep_cs_active) {
        t.flags = SPI_TRANS_CS_KEEP_ACTIVE;   //Keep CS active after data transfer
    }
    ret = spi_device_polling_transmit(lcd_handle, &t); //Transmit!
    assert(ret == ESP_OK);          //Should have had no issues.
}
void lcd_data(const uint8_t *data, int len)
{
    spi_transaction_t t;
    if (len == 0) {
        return;    //no need to send anything
    }
    memset(&t, 0, sizeof(t));       //Zero out the transaction
    t.length = len * 8;             //Len is in bytes, transaction length is in bits.
    t.tx_buffer = data;             //Data
    t.user = LCD_DATA_MODE;              //D/C needs to be set to 1
    ESP_ERROR_CHECK(spi_device_polling_transmit(lcd_handle, &t));
}
4. 设置显示区域:借鉴了官方示例,没有使用lcd_cmd和lcd_data函数,那样太慢了
void lcd_set_window(uint16_t startX, uint16_t endX, uint16_t startY, uint16_t endY)
{
    spi_transaction_t trans[4];
    for (uint8_t i = 0; i < 4; i++)
    {
        memset(&trans[i], 0, sizeof(spi_transaction_t));
        if ((i & 1) == 0)
        {
            trans[i].length = 8;
            trans[i].user = LCD_CMD_MODE;
        }
        else
        {
            trans[i].length = 8 * 4;
            trans[i].user = LCD_DATA_MODE;
        }
        trans[i].flags = SPI_TRANS_USE_TXDATA;
    }
    trans[0].tx_data[0] = 0x2a;
    trans[1].tx_data[0] = startX >> 8;
    trans[1].tx_data[1] = startX& 0xff;
    trans[1].tx_data[2] = endX >> 8;
    trans[1].tx_data[3] = endX & 0xff;
    trans[2].tx_data[0] = 0x2b;
    trans[3].tx_data[0] = startY >> 8;
    trans[3].tx_data[1] = startY & 0xff;
    trans[3].tx_data[2] = endY >> 8;
    trans[3].tx_data[3] = endY& 0xff;
    for (uint8_t i = 0; i < 4; i++)
    {
        ESP_ERROR_CHECK(spi_device_queue_trans(lcd_handle, &trans[i], portMAX_DELAY));
    }
}
5. 画点,没什么好说的:
void lcd_draw_point(uint16_t x, uint16_t y, uint16_t color)
{
    uint8_t buf[2];
    buf[0] = color >> 8;
    buf[1] = color;
    lcd_set_window(x, x, y, y);
    lcd_cmd(0x2c, false);
    lcd_data(buf, 2);
}
6. 清屏:扫描线的方式填充,使用画点函数太慢
void lcd_clear(uint16_t color)
{
    uint8_t *buf;
    buf = heap_caps_malloc(320 * sizeof(uint16_t), MALLOC_CAP_DMA);
    memset(buf, color, 320 * sizeof(uint16_t));

    lcd_set_window(0, LCD_width-1, 0, LCD_height-1);
    lcd_cmd(0x2c, false);
    for (uint16_t i = 0; i < LCD_height; i++)
    {
        lcd_data(buf, 320 * 2);
    }
    heap_caps_free(buf);
}
7. 显示字符:ascii16是字模,这是从我很久以前用stm32写lcd的时候写的函数,字模忘记怎么取得了。。。。。这部分挺好改的。
void LCD_ShowChar(uint16_t x, uint16_t y, uint8_t ch, uint8_t size,
                    uint16_t color, uint16_t bg_color, uint8_t mode)
{
    uint8_t temp;
    uint16_t y0 = y, x0 = x;
    uint8_t idx = 0;
    for (uint8_t i = 0; i < size/2; i++)
    {
        for (uint8_t t = 0; t < size/8; t++)
        {
            switch (size)
            {
            case 16:
                temp = ascii16[ch-32][idx];
                break;
            case 24:
                temp = ascii24[ch-32][idx];
                break;
            case 32:
                temp = ascii32[ch-32][idx];
                break;
            default:
                break;
            }
            idx++;
            for (uint8_t j = 0; j < 8; j++)
            {
                if (temp & 0x80)
                    lcd_draw_point(x0, y0, color);
                else
                    if (mode == 1)
                    {
                        lcd_draw_point(x0, y0, bg_color);
                    }
                   
                y0++;
                temp <<= 1;
            }
        }
        y0 = y;
        x0++;
    }
}
8. 显示字符串:循环调用显示字符函数就完了
void LCD_ShowString(uint16_t x, uint16_t y, char *str, uint8_t size,
                        uint16_t color, uint16_t bg_color, uint8_t mode)
{
    uint8_t idx = 0;
    while (str[idx] != '\0')
    {
        LCD_ShowChar(x, y, str[idx], size, color, bg_color, mode);
        x+=size/2;
        idx++;
    }    
}
 
三、主函数
void app_main(void)
{
    i2c_init();
    sht40_init();
    lcd_init();
    float t_rh[2];
    char t_rh_str[22];
    vTaskDelay(100/portTICK_PERIOD_MS);
    lcd_clear(LCD_COLOR_BLACK);
    vTaskDelay(100/portTICK_PERIOD_MS);
    while (1)
    {
        sht40_read_temp(0xfd, t_rh);
        printf("temp:%.2f  RH:%.2f\r\n", t_rh[0], t_rh[1]);
        sprintf(t_rh_str, "T: %.2foC, RH: %.2f%c", t_rh[0], t_rh[1], '%');
        LCD_ShowString(0, 0, t_rh_str, 32, LCD_COLOR_RED, LCD_COLOR_BLACK, 1);
        vTaskDelay(100);
    }
}
    只显示温湿度的话这些函数就够了,但很明显,这就是垃圾程序。我先写成这样完成作业再说吧,以后我会慢慢完善的。而且这次硬件设计上完全可以做成小电视的,后续我会再学一下esp32的联网怎么搞和lvgl尝试把小电视整出来。还有就是完成工程源码先不放出来了,写的太烂了,做完整理完再上传吧。
    (打工真的没时间啊!!!)

*7、大赛LOGO验证


 

* 8、演示您的项目并录制成视频上传


 

 

 

设计图

未生成预览图,请在编辑器重新保存一次

BOM

暂无BOM

附件

序号文件名称下载次数
1
7d58616c2927e686087bc40b6d14ae2b.mp4
0
克隆工程
添加到专辑
0
0
分享
侵权投诉

工程成员

评论

全部评论(1)
按时间排序|按热度排序
粉丝0|获赞0
相关工程
暂无相关工程

底部导航