prettyprint

2025年3月29日 星期六

[Raspberry Pi Pico] A simple environmental monitoring (air dust, temperature and humidity) system

 本文章介紹在Raspberry Pi Pico開發板上連接Sharp GP2Y1014AU0F Dust Sensor與AHT10 溫濕度感應器。即時監控環境中的空氣灰塵濃度與溫濕度。

一、TFT UI:

本文章TFT的UI部份請參閱:

Raspberry Pi Pico (c-sdk)] LVGL Graphics Library & Pico PIO TFT display driver(Serial or Parallel)


二、AHT10 driver library:
根據AHT10 Technical Manual:
  1. device address: 0x38


  2. command set:


  3. Trigger measurement command format:


  4. 溫濕度數據讀取:


  5. 資料轉換:


  6. 相對應的程式碼:


三、SHARP GP2Y1014AU0F PM2.5灰塵粉塵感測器:
  1. Pin 1 ILED vcc的接線:220uF and 150歐姆電阻)


  2. 取樣脈衝:

    每次取樣需要10ms,ILED HIGH為0.32 ms, LOW: 9.68ms。當ILED HIGH時需延遲0.28ms才取樣。


  3. 輸出電壓與灰塵濃度線曲圖,接近一線性函數:



  4. 相對應程式碼:


四、成果影片:


五、程式碼:


  • pico_lvgl library請參閱:

Raspberry Pi Pico (c-sdk)] LVGL Graphics Library & Pico PIO TFT display driver(Serial or Parallel)


AHT10 libarary:

  • CMakeLists.txt
add_library(aht10 INTERFACE)

target_sources(aht10 INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/aht10.c
)

target_include_directories(aht10 INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
)

target_link_libraries(aht10 INTERFACE
        hardware_i2c
)
  • aht10.c
#include "pico/stdio.h"
#include "pico/stdlib.h"
#include "aht10.h"

static i2c_inst_t  *aht10_port = i2c0;
static uint8_t aht10_i2c_sda;
static uint8_t aht10_i2c_scl;

void aht10_init(i2c_inst_t *i2c_port, uint8_t sda, uint8_t scl) {
    aht10_port = i2c_port;
    aht10_i2c_sda = sda;
    aht10_i2c_scl = scl;
    // I2C Initialisation. Using it at 400Khz.
    i2c_init(aht10_port, 400*1000);
    
    gpio_set_function(aht10_i2c_sda, GPIO_FUNC_I2C);
    gpio_set_function(aht10_i2c_scl, GPIO_FUNC_I2C);
    gpio_pull_up(aht10_i2c_sda);
    gpio_pull_up(aht10_i2c_scl);
    uint8_t cmd[3];
    cmd[0] = AHT10_INIT_CMD;
    cmd[1] = 0x08;
    cmd[2] = 0x00;
    i2c_write_blocking(aht10_port, AHT10_I2C_ADDR, cmd, 3, false);
    sleep_ms(300);
}

bool aht10_trigger_measurement(float* humidity, float *temperature) {
    static uint8_t cmd[3] = {AHT10_TRIG_CMD, 0x33, 0x00};
    uint8_t data_buff[10];

    i2c_write_blocking(aht10_port, AHT10_I2C_ADDR, cmd, 3, false);
    sleep_ms(80);
    i2c_read_blocking(aht10_port, AHT10_I2C_ADDR, data_buff, 6, false);

    if (data_buff[0] >> 7) { // status bit[7] --> 1: The device is busy,
        *humidity = 0;
        *temperature = 0;
        return false;
    }

    //Convert the temperature as described in the data book
    *humidity = (data_buff[1] << 12) | (
        data_buff[2] << 4) | ((data_buff[3] & 0xF0) >> 4);
    *humidity = (*humidity/(1 << 20)) * 100.0;
    
    *temperature = ((data_buff[3] & 0xf) << 16) | (
        data_buff[4] << 8) | data_buff[5];
    *temperature = (*temperature * 200.0 / (1 << 20)) - 50;
    return true;
}

void aht10_soft_reset() {
    static uint8_t cmd[1] = {AHT10_RESET_CMD};
    i2c_write_blocking(aht10_port, AHT10_I2C_ADDR, cmd, 1, false);
    sleep_ms(80);
}
  • aht10.h
#ifndef __AHT10_H__
#define __AHT10_H__

#include "hardware/i2c.h"

#define AHT10_INIT_CMD      0b11100001
#define AHT10_TRIG_CMD      0b10101100
#define AHT10_RESET_CMD     0b10111010
#define AHT10_I2C_ADDR      0x38

void aht10_init(i2c_inst_t *i2c_port, uint8_t sda, uint8_t scl);
bool aht10_trigger_measurement(float* humidity, float *temperature);
void aht10_soft_reset();


#endif 

  • CMakeLists.txt(root)
# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)

# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
    set(USERHOME $ENV{USERPROFILE})
else()
    set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.1.0)
set(toolchainVersion 13_2_Rel1)
set(picotoolVersion 2.1.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
    include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD pico CACHE STRING "Board type")

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

project(pico_dust_ht C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Add executable. Default name is the project name, version 0.1

add_executable(pico_dust_ht pico_dust_ht.c )

pico_set_program_name(pico_dust_ht "sharp_gp2y10")
pico_set_program_version(pico_dust_ht "0.1")

# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(pico_dust_ht 0)
pico_enable_stdio_usb(pico_dust_ht 1)

# Add the standard library to the build
target_link_libraries(pico_dust_ht
        pico_stdlib
        hardware_adc
        hardware_pio)

# Add the standard include files to the build
target_include_directories(pico_dust_ht PRIVATE
        ${CMAKE_CURRENT_LIST_DIR}
)
add_subdirectory(aht10)
add_subdirectory(pico_lvgl)
target_link_libraries(pico_dust_ht
            aht10
            pico_lvgl
)

pico_add_extra_outputs(pico_dust_ht)

  • pico_dust_ht.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/adc.h"
#include "hardware/pio.h"
#include "aht10.h"
#include "pico_lvgl.h"

#define GP2Y10_ILED_PIN     18
#define GP2Y10_VOUT_PIN     26
#define AHT10_I2C_PORT      i2c0
#define AHT10_SDA_PIN       16 
#define AHT10_SCL_PIN       17    

PIO TFT_PIO = pio1;
#define TFT_SM 1
#define TFT_SDI_GPIO 9
#define TFT_CSX_DCX_SCK_GPIO 6 // CSX=8, DCX=7, SCK=6, SIDE_SET

// 12-bit conversion, assume max value == ADC_VREF == 3.3 V
const float conversion_factor = 3.3f / (1 << 12);

lv_obj_t *humi_label, *temp_label, *ug_label, *dusk_meter, *avg_1h_label, 
        *avg_6h_label, *max_last_hour_label;
lv_obj_t *humi_bar, *temp_bar;
lv_meter_indicator_t * indic;


void lgvl_ui() {

    dusk_meter = lv_meter_create(lv_scr_act());
    lv_obj_align(dusk_meter, LV_ALIGN_TOP_MID, 0, 10);
    lv_obj_set_size(dusk_meter, 200, 200);

    /*Add a scale first*/
    lv_meter_scale_t * scale = lv_meter_add_scale(dusk_meter);
    lv_meter_set_scale_ticks(dusk_meter, scale, 70, 2, 5, lv_palette_main(LV_PALETTE_GREY));
    lv_meter_set_scale_major_ticks(dusk_meter, scale, 10, 4, 15, lv_color_black(), 10);

    

    /*Add a green arc to the start*/
    indic = lv_meter_add_arc(dusk_meter, scale, 3, lv_palette_main(LV_PALETTE_GREEN), 0);
    lv_meter_set_indicator_start_value(dusk_meter, indic, 0);
    lv_meter_set_indicator_end_value(dusk_meter, indic, 50);

    /*Make the tick lines green at the start of the scale*/
    indic = lv_meter_add_scale_lines(dusk_meter, scale, lv_palette_main(LV_PALETTE_GREEN), lv_palette_main(LV_PALETTE_GREEN),
                                     false, 0);
    lv_meter_set_indicator_start_value(dusk_meter, indic, 0);
    lv_meter_set_indicator_end_value(dusk_meter, indic, 50);
//==
    /*Add a yellow arc to the start*/
    indic = lv_meter_add_arc(dusk_meter, scale, 3, lv_palette_main(LV_PALETTE_YELLOW), 0);
    lv_meter_set_indicator_start_value(dusk_meter, indic, 51);
    lv_meter_set_indicator_end_value(dusk_meter, indic, 100);

    /*Make the tick lines yellow at the start of the scale*/
    indic = lv_meter_add_scale_lines(dusk_meter, scale, lv_palette_main(LV_PALETTE_YELLOW), lv_palette_main(LV_PALETTE_YELLOW),
                                     false, 0);
    lv_meter_set_indicator_start_value(dusk_meter, indic, 51);
    lv_meter_set_indicator_end_value(dusk_meter, indic, 100);

//==
    /*Add a orange arc to the start*/
    indic = lv_meter_add_arc(dusk_meter, scale, 3, lv_palette_main(LV_PALETTE_ORANGE), 0);
    lv_meter_set_indicator_start_value(dusk_meter, indic, 101);
    lv_meter_set_indicator_end_value(dusk_meter, indic, 150);

    /*Make the tick lines yellow at the start of the scale*/
    indic = lv_meter_add_scale_lines(dusk_meter, scale, lv_palette_main(LV_PALETTE_ORANGE), lv_palette_main(LV_PALETTE_ORANGE),
                                     false, 0);
    lv_meter_set_indicator_start_value(dusk_meter, indic, 101);
    lv_meter_set_indicator_end_value(dusk_meter, indic, 150);

 //==   

    /*Add a red arc to the end*/
    indic = lv_meter_add_arc(dusk_meter, scale, 3, lv_palette_main(LV_PALETTE_RED), 0);
    lv_meter_set_indicator_start_value(dusk_meter, indic, 151);
    lv_meter_set_indicator_end_value(dusk_meter, indic, 200);

    /*Make the tick lines red at the end of the scale*/
    indic = lv_meter_add_scale_lines(dusk_meter, scale, lv_palette_main(LV_PALETTE_RED), lv_palette_main(LV_PALETTE_RED), false,
                                     0);
    lv_meter_set_indicator_start_value(dusk_meter, indic, 151);
    lv_meter_set_indicator_end_value(dusk_meter, indic, 200);

    //==
    /*Add a purple arc to the start*/
    indic = lv_meter_add_arc(dusk_meter, scale, 3, lv_palette_main(LV_PALETTE_PURPLE), 0);
    lv_meter_set_indicator_start_value(dusk_meter, indic, 201);
    lv_meter_set_indicator_end_value(dusk_meter, indic, 350);

    /*Make the tick lines yellow at the start of the scale*/
    indic = lv_meter_add_scale_lines(dusk_meter, scale, lv_palette_main(LV_PALETTE_PURPLE), lv_palette_main(LV_PALETTE_PURPLE),
                                     false, 0);
    lv_meter_set_indicator_start_value(dusk_meter, indic, 201);
    lv_meter_set_indicator_end_value(dusk_meter, indic, 350);

    /*Add a needle line indicator*/
    indic = lv_meter_add_needle_line(dusk_meter, scale, 4, lv_palette_main(LV_PALETTE_GREY), -10);
    lv_meter_set_scale_range(dusk_meter, scale, 0, 350, 270, 135);

    //
    avg_1h_label = lv_label_create(lv_scr_act());
    avg_6h_label = lv_label_create(lv_scr_act());
    max_last_hour_label = lv_label_create(lv_scr_act());
    lv_obj_align(avg_1h_label, LV_ALIGN_TOP_LEFT, 5,190);
    lv_obj_align(avg_6h_label, LV_ALIGN_TOP_RIGHT, -5,190);
    lv_obj_align(max_last_hour_label, LV_ALIGN_TOP_LEFT, 5,5);
    lv_obj_set_style_text_color(avg_1h_label, lv_palette_main(LV_PALETTE_BLUE), 0);
    lv_obj_set_style_text_color(avg_6h_label, lv_palette_main(LV_PALETTE_BLUE), 0);
    lv_obj_set_style_text_color(max_last_hour_label, lv_palette_main(LV_PALETTE_RED), 0);
    lv_label_set_text(avg_1h_label, "Avg(1 Hour)\n-.- ug/m3");
    lv_label_set_text(avg_6h_label, "Avg(6 Hours)\n-.- ug/m3");
    lv_label_set_text(max_last_hour_label, "Max(Last Hour)\n-.- ug/m3");

    humi_bar = lv_bar_create(lv_scr_act());
    lv_obj_set_size(humi_bar, 20, 150);
    lv_obj_align(humi_bar, LV_ALIGN_BOTTOM_LEFT, 80, -40);
    lv_bar_set_value(humi_bar, 0, LV_ANIM_ON);
    lv_bar_set_range(humi_bar, 0, 100);

    temp_bar = lv_bar_create(lv_scr_act());
    lv_obj_set_size(temp_bar, 20, 150);
    lv_obj_align(temp_bar, LV_ALIGN_BOTTOM_RIGHT, -80, -40);
    lv_bar_set_value(temp_bar, 0, LV_ANIM_ON);
    lv_bar_set_range(temp_bar, -10, 70);
    lv_obj_t *tl = lv_label_create(lv_scr_act());
    lv_label_set_text(tl, "Humidity");
    lv_obj_align_to(tl, humi_bar, LV_ALIGN_OUT_TOP_MID, 0, -10);
    tl = lv_label_create(lv_scr_act());
    lv_label_set_text(tl, "Temperature");
    lv_obj_align_to(tl, temp_bar, LV_ALIGN_OUT_TOP_MID, 0, -10);

    humi_label = lv_label_create(lv_scr_act());
    temp_label = lv_label_create(lv_scr_act());
    ug_label = lv_label_create(lv_scr_act());
    //lv_obj_set_size(humi_label, 100,30);
    //lv_obj_set_size(humi_label, 50,30);
    //lv_obj_set_size(temp_label, 70,30);
    //lv_obj_set_size(ug_label, 100,30);
    lv_obj_align(ug_label, LV_ALIGN_TOP_MID, 0, 170);
    lv_obj_align_to(humi_label, humi_bar, LV_ALIGN_OUT_BOTTOM_MID, 0, 5);
    lv_obj_align_to(temp_label, temp_bar, LV_ALIGN_OUT_BOTTOM_MID, 0, 5);
    

}

int main()
{
    stdio_init_all();

    pico_lvgl_tft_init(TFT_PIO, TFT_SM, TFT_SDI_GPIO, TFT_CSX_DCX_SCK_GPIO);
    //tft_set_orientation(TFT_ORIENTATION_LANDSCAPE);

    pico_lvgl_display_init(5);
 

    lgvl_ui();
    


    gpio_init(GP2Y10_ILED_PIN);
    gpio_set_dir(GP2Y10_ILED_PIN, true);

    adc_init();
    adc_gpio_init(GP2Y10_VOUT_PIN);
    adc_select_input(0);

    aht10_init(AHT10_I2C_PORT, AHT10_SDA_PIN, AHT10_SCL_PIN);

    uint16_t value, index_1h=0, index_6h=0;
    float ug, humidity, temperature, ug_avg_six_hours=0, ug_avg_one_hour=0;
    uint8_t buff[200];
    static float one_hour_ug[3600];
    static float six_hours_ug[6];
    static float max_last_hour = 0;
    while (true) {
        // == get sharp gp2y10 ug value ==
        gpio_put(GP2Y10_ILED_PIN, false);
        sleep_us(280);
        value = adc_read();
        sleep_us(40);
        gpio_put(GP2Y10_ILED_PIN, true);
        sleep_us(9680);
        ug = (((0.172 * value*conversion_factor) - 0.1) * 1000); //(5v)
        // == get sharp gp2y10 ug value ==

        printf("adcvalue:%d, \tv:%.2f\t ug:.%.2f\n", value, value*conversion_factor,ug);
        
        aht10_trigger_measurement(&humidity, &temperature);
        printf("humidity:%.2f %%, \ttemperature:%.2fC\n\n", humidity, temperature);
        sprintf(buff, "\t%3.0f ug/m3", ug<0?0:ug);
        lv_label_set_text(ug_label, buff);
        sprintf(buff, "%2.1f \xC2\xB0" "C",  temperature);
        lv_label_set_text(temp_label,  buff);

        sprintf(buff, "%2.1f%%",  humidity);
        lv_label_set_text(humi_label,  buff);
        
        lv_meter_set_indicator_value(dusk_meter, indic, (int)(ug));
        lv_bar_set_value(humi_bar, humidity, LV_ANIM_ON);
        lv_bar_set_value(temp_bar, temperature, LV_ANIM_ON);
        one_hour_ug[index_1h] = ug;
        if (++index_1h == 3600) {
            index_1h = 0;
        }
        if ((index_1h) % 60 == 0) {
            for (int i = 0; i < 3600; i++) {
                ug_avg_one_hour += one_hour_ug[i];
            }
            ug_avg_one_hour /= 3600;
            //
            sprintf(buff, "Avg(1 Hour)\n%3.1f ug/m3", ug_avg_one_hour);
            lv_label_set_text(avg_1h_label, buff);
        }
        if(index_1h == 0) {
            six_hours_ug[index_6h++] = ug_avg_one_hour;
            if (index_6h == 6) {
                index_6h = 0;
            }
            for (int i =0; i < 6; i++) {
                ug_avg_six_hours += six_hours_ug[i];
            }
            ug_avg_six_hours = ug_avg_six_hours / 6;
            //
            sprintf(buff, "Avg(6 Hours)\n%3.1f ug/m3", ug_avg_six_hours);
            lv_label_set_text(avg_6h_label, buff);
        }
        max_last_hour = 0;
        for (int i = 0; i < 3600; i++) {
            if (one_hour_ug[i] > max_last_hour) max_last_hour = one_hour_ug[i];
        }
        sprintf(buff, "Max(Last Hour)\n%3.1f ug/m3", max_last_hour);
            lv_label_set_text(max_last_hour_label, buff);

        lv_timer_handler();
        sleep_ms(1000);
        
    }
}


沒有留言:

張貼留言