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);
}
}


沒有留言:

張貼留言