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


2025年3月19日 星期三

[Raspberry Pi Pico W] MODBUS Ep 4. MODBUS/TCP Security || LWIP || MbedTLS || Node-RED

 本文章介紹MODBUS/TCP Security, 網路library使用LWIP altcp(application layer TCP),此library將SSL/TLS(MbedTLS)結合原來的TCP library。

Modbus server使用Raspberry Pi Pico W,Clinet端分使用Node-RED與Raspberry Pi Pico W來進行測試,在本專案中利用Wireshark擷取封包來比較未加密與透過Transport layer security加密的ADU封包。

MODBUS/TCP security在原來MODBUS TCP加上TLS Transport Layer Security, 如下圖比較:

LWIP網路API使用altcp,此組函式庫擴張原來TCP library,在原來使用tcp_*的呼叫流程中為呼叫altcp_*即可。另外須先初始或tls_config將Root CA, server primary key與server certification加入呼叫

server:  altcp_tls_create_config_server_privkey_cert(...)
client:  altcp_tls_create_config_client(...)
中。

若須使用self-signed certification可使用下列步驟建立:
  1. openssl genrsa -des3 -out ca.key 2048:
    建立Root CA key。
  2. openssl req -new -x509 -days 3650 -key ca.key -out ca.cert:
    建立self-signed root Certificate。
  3. openssl genrsa -out server.key 2048:
    建立server side private key。
  4. openssl req -new -out server.csr -key server.key
    使用server key製作server憑證需求(certificate request)
  5. openssl x509 -req -in server.csr -CA ca.cert -CAkey ca.key -CAcreateserial -out server.cert -days 365
    使用root CA ca.cert簽署server side certificate。
完整程式碼附於文章末尾。

成果影片:
程式碼:
Server:
  • CMakeLists.txt
# 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_3_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_w CACHE STRING "Board type")

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

project(picow_modbus_tcp_security_server 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(picow_modbus_tcp_security_server 
                picow_modbus_tcp_security_server.c 
                nanomodbus/nanomodbus.c
                picow_tls_server/picow_tls_server.c 
                modbus_tcp_server/modbus_tcp_server.c)

pico_set_program_name(picow_modbus_tcp_security_server "picow_modbus_tcp_security_server")
pico_set_program_version(picow_modbus_tcp_security_server "0.1")

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

# Add the standard library to the build
target_link_libraries(picow_modbus_tcp_security_server
        pico_stdlib)

# Add the standard include files to the build
target_include_directories(picow_modbus_tcp_security_server PRIVATE
        ${CMAKE_CURRENT_LIST_DIR}
)

add_subdirectory(GPIO_StepperMotor)
# Add any user requested libraries
target_link_libraries(picow_modbus_tcp_security_server 
        pico_cyw43_arch_lwip_threadsafe_background
        pico_lwip_mbedtls
        pico_mbedtls
        pico_lwip_mdns
        gpio_stepper_motor
        )

pico_add_extra_outputs(picow_modbus_tcp_security_server)

# Ignore warnings from lwip code
set_source_files_properties(
        ${PICO_LWIP_PATH}/src/apps/altcp_tls/altcp_tls_mbedtls.c
        PROPERTIES
        COMPILE_OPTIONS "-Wno-unused-result"
        )

  • picow_modbus_tcp_security_server.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "lwip/apps/mdns.h"

#include "nanomodbus/nanomodbus.h"
#include "picow_tls_server/picow_tls_server.h"
#include "modbus_tcp_server/modbus_tcp_server.h"

#include "lwip/pbuf.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp_tls.h"
#include "lwip/dns.h"

#include "GPIO_StepperMotor/steppermotor.h"

#define MODBUS_SERVER_HOST_NAME "modbus_tls_tcp"


int main()
{
    stdio_init_all();
    gpio_init(LED_PIN);
    gpio_set_dir(LED_PIN,true);

    stepperMotor_init(MOTOR_PIN1, MOTOR_PIN2, MOTOR_PIN3, MOTOR_PIN4);
    stepperMotor_set_speed(5);

// ====== Initialise the Wi-Fi chip =====
    if (cyw43_arch_init()) {
        printf("Wi-Fi init failed\n");
        return -1;
    }
    cyw43_gpio_set(&cyw43_state, CYW43_WL_GPIO_LED_PIN,false);
    
    // Enable wifi station
    cyw43_arch_enable_sta_mode();

    DEBUG_print("Connecting to Wi-Fi...\n");
    if (cyw43_arch_wifi_connect_timeout_ms(SSID, PWD, CYW43_AUTH_WPA2_AES_PSK, 30000)) {
        DEBUG_print("failed to connect.\n");
        return 1;
    } else {
        DEBUG_print("Connected.\n");
        // Read the ip address in a human readable way
        uint8_t *ip_address = (uint8_t*)&(cyw43_state.netif[0].ip_addr.addr);
        DEBUG_print("IP address %d.%d.%d.%d\n", ip_address[0], ip_address[1], ip_address[2], ip_address[3]);
    }
#if LWIP_MDNS_RESPONDER
    mdns_resp_init();
    uint8_t host_name[] = MODBUS_SERVER_HOST_NAME;
    if (mdns_resp_add_netif(netif_default, host_name)== ERR_OK)
    {
        printf("mDNS add successfully\n");
    } else {
        printf("mDNS failure\n");
    }
#endif
    picow_tls_server_init();

    
//======= modbus tcp server(modbus server) init ======
    nmbs_platform_conf platform_conf;
    nmbs_platform_conf_create(&platform_conf);
    platform_conf.transport = NMBS_TRANSPORT_TCP;
    platform_conf.read = nmbs_transport_read;
    platform_conf.write = nmbs_transport_write;

    extern ALTCP_SERVER_T *altcp_tcp_server;
    platform_conf.arg = altcp_tcp_server;
    
    nmbs_callbacks callbacks;
    nmbs_callbacks_create(&callbacks);
    callbacks.read_coils = handle_altcp_read_coils;
    callbacks.read_discrete_inputs = handle_altcp_read_discrete_inputs;
    callbacks.write_single_coil = handle_altcp_write_single_coil;
    callbacks.write_multiple_coils = handle_altcp_write_multiple_coils;
    callbacks.read_holding_registers = handler_tcp_read_holding_registers;
    callbacks.write_multiple_registers = handle_altcp_write_multiple_registers;
    callbacks.write_single_register = handle_altcp_write_single_register;
    callbacks.read_file_record = handle_altcp_read_file_record;
    callbacks.write_file_record = handle_altcp_write_file_record;
    callbacks.arg = altcp_tcp_server;
    
    nmbs_error err = nmbs_server_create(&nmbs_tls, 0, &platform_conf, &callbacks);
    if (err != NMBS_ERROR_NONE) {
        printf("Error creating modbus server\n");
        return 1;
    }

    // Set only the polling timeout. Byte timeout will be handled by the TCP connection
    nmbs_set_read_timeout(&nmbs_tls, 2000);
//======= modbus tcp server(modbus server) init ======
    
    printf("Modbus TCP server started\n");
    cyw43_gpio_set(&cyw43_state, CYW43_WL_GPIO_LED_PIN,true);
//========================

    while (true) {
        nmbs_server_poll(&nmbs_tls);
        
#if PICO_CYW43_ARCH_POLL
        cyw43_arch_poll();
         cyw43_arch_wait_for_work_until(make_timeout_time_ms(1000));
#else
        tight_loop_contents();
#endif
    }
}
  • picow_tls_server.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"

#include "nanomodbus/nanomodbus.h"

#include "lwip/pbuf.h"
#include "lwip/altcp_tls.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp.h"
#include "lwip/def.h"

#include "picow_tls_server.h"
#include "modbus_tcp_server/modbus_tcp_server.h"
#include "modbus_server_cert.h"

static int8_t conn_count=0;

ALTCP_SERVER_T *altcp_tcp_server;
static struct altcp_tls_config* tls_config;

static err_t altcp_client_close(struct altcp_pcb *client_pcb) {
    err_t err = ERR_OK;
    if (client_pcb != NULL) {
        altcp_arg(client_pcb, NULL);
        altcp_poll(client_pcb, NULL, 0);
        altcp_sent(client_pcb, NULL);
        altcp_recv(client_pcb, NULL);
        altcp_err(client_pcb, NULL);
        err = altcp_close(client_pcb);
        if (err != ERR_OK) {
            DEBUG_print("close failed %d, calling abort\n", err);
            altcp_abort(client_pcb);
            err = ERR_ABRT;
        }
        client_pcb = NULL;
        altcp_tcp_server->connected = false;
    }
    DEBUG_print("connections after client_closed:%d\n", --conn_count);
    return err;
}

static err_t altcp_server_close(void *arg) {
    ALTCP_SERVER_T *state = (ALTCP_SERVER_T*)arg;
    err_t err = ERR_OK;

    if (state->client_pcb != NULL) {
        altcp_arg(state->client_pcb, NULL);
        altcp_poll(state->client_pcb, NULL, 0);
        altcp_sent(state->client_pcb, NULL);
        altcp_recv(state->client_pcb, NULL);
        altcp_err(state->client_pcb, NULL);
        err = altcp_close(state->client_pcb);
        if (err != ERR_OK) {
            DEBUG_print("close failed %d, calling abort\n", err);
            altcp_abort(state->client_pcb);
            err = ERR_ABRT;
        }
        state->client_pcb = NULL;
    }
    if (state->server_pcb) {
        altcp_arg(state->server_pcb, NULL);
        altcp_close(state->server_pcb);
        state->server_pcb = NULL;
    }
    DEBUG_print("altcp server closed\n");
    return err;
}

static err_t altcp_server_sent(void *arg, struct altcp_pcb *tpcb, u16_t len) {
    ALTCP_SERVER_T *state = (ALTCP_SERVER_T*)arg;
    return ERR_OK;
}

static err_t altcp_server_send_data(void *arg, struct altcp_pcb *tpcb)
{
    ALTCP_SERVER_T *state = (ALTCP_SERVER_T*)arg;
    return ERR_OK;
}

err_t altcp_server_recv(void *arg, struct altcp_pcb *tpcb, struct pbuf *p, err_t err) {
    ALTCP_SERVER_T *state = (ALTCP_SERVER_T*)arg;
    //state->client_pcb = tpcb;
    static altcp_queue_t temp_recv;
    if (!p) {
        DEBUG_print("client connect close---------\n");
        return altcp_client_close(tpcb);
    }

    if (p->tot_len > 0) {
        // Receive the buffer 
        uint16_t recv_len = pbuf_copy_partial(p, temp_recv.buffer, p->tot_len, 0); 
        temp_recv.buffer_len = recv_len;
        //queue_add_blocking(&state->recv_queue, &temp_recv);
    if (!queue_try_add(&state->recv_queue, &temp_recv)) {
        DEBUG_print("\n=======\n\nbusy\n-------\n");
        return ERR_INPROGRESS;
    }

        altcp_recved(tpcb, recv_len);
#ifdef __DEBUG__
        DEBUG_print("---------recv---------\n");
        for (int i =0; i < recv_len; i++) {
            DEBUG_print("%02x ",temp_recv.buffer[i]);
        }
        DEBUG_print("\n-------------\n");
#endif
        
    }
    pbuf_free(p);
    return ERR_OK;
}

static err_t altcp_server_poll(void *arg, struct altcp_pcb *tpcb) {

    return ERR_OK;
}

static void altcp_server_err(void *arg, err_t err) {
    if (err != ERR_ABRT) {
        DEBUG_print("tcp_client_err_fn %d\n", err);
    }
}

static err_t altcp_server_accept(void *arg, struct altcp_pcb *client_pcb, err_t err) {
    ALTCP_SERVER_T *state = (ALTCP_SERVER_T*)arg;
    if (err != ERR_OK || client_pcb == NULL) {
        DEBUG_print("Failure in accept\n");
        return err;
    }
    
    //only allow one client(modbus master) to connect
    if (state->connected) {
        DEBUG_print("More than one connection!\n");
        return ERR_CONN;
    }
        
    state->client_pcb = client_pcb;
    
    altcp_arg(state->client_pcb, state);
    altcp_sent(state->client_pcb, altcp_server_sent);
    altcp_recv(state->client_pcb, altcp_server_recv);
    altcp_poll(state->client_pcb, altcp_server_poll, POLL_TIME_S * 2);
    altcp_err(state->client_pcb, altcp_server_err);
    state->connected = true;
    strcpy(state->client_ip_addr, ip4addr_ntoa(altcp_get_ip(client_pcb, false)));
    DEBUG_print("Client connected:%d:%s\n", ++conn_count, state->client_ip_addr);
    return ERR_OK;
}

static bool altcp_server_open(void *arg) {
    ALTCP_SERVER_T *state = (ALTCP_SERVER_T*)arg;
    DEBUG_print("Starting server at %s on port %u\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), MODBUS_ALTCP_PORT);

    // set TLS connection
    tls_config = altcp_tls_create_config_server_privkey_cert(MODBUS_SERVER_KEY, sizeof(MODBUS_SERVER_KEY), NULL, 0, MODBUS_SERVER_CERT, sizeof(MODBUS_SERVER_CERT));
    //mbedtls_x509_crt_parse(tls_config->cert, CA_CERT, sizeof(CA_CERT));
    
    struct altcp_pcb *alpcb = altcp_tls_new(tls_config, IPADDR_TYPE_ANY);
    if (!alpcb) {
        DEBUG_print("failed to create pcb\n");
        return false;
    }
    
    err_t err = altcp_bind(alpcb, NULL, MODBUS_ALTCP_PORT);
    if (err) {
        DEBUG_print("failed to bind to port %u\n", MODBUS_ALTCP_PORT);
        return false;
    }

    
    state->server_pcb = altcp_listen_with_backlog(alpcb,1);
    if (!state->server_pcb) {
        DEBUG_print("failed to listen\n");
        if (alpcb) {
            altcp_close(alpcb);
        }
        return false;
    }

    altcp_arg(state->server_pcb, state);
    altcp_accept(state->server_pcb, altcp_server_accept);

    return true;
}

bool picow_tls_server_init(void) {
    /* modbus TCP server  init*/
    altcp_tcp_server = calloc(1, sizeof(ALTCP_SERVER_T));
    if (!altcp_tcp_server) {
        return false;
    }

    //altcp_tcp_server initial values;
    queue_init(&altcp_tcp_server->recv_queue, sizeof(altcp_queue_t), 4);
    altcp_tcp_server->connected = false;
    altcp_tcp_server->queue_left=0;
    altcp_tcp_server->recv_count=0;
    altcp_tcp_server->start_index=0;

    if (!altcp_server_open(altcp_tcp_server)) {
        DEBUG_print("tcp server open error\n");
        return false;
    }

    return true;
}

  • picow_tls_server.h

#define MODBUS_TCP_PORT 502            //MODBUS default port
#define MODBUS_ALTCP_PORT 802            //MODBUS default port

#define POLL_TIME_S 1

#define __DEBUG__
#ifdef __DEBUG__
#define DEBUG_print(...) printf(__VA_ARGS__)
#else
#define DEBUG_print(...) (void) (0)
#endif


#define SSID "your-SSID"
#define PWD "your-PASSWD"

#define LED_PIN 15
#define MOTOR_PIN1  21
#define MOTOR_PIN2  20
#define MOTOR_PIN3  19
#define MOTOR_PIN4  18

#include "pico/util/queue.h"
#include "lwip/altcp.h"

typedef struct __altcp_queue_t{
    uint8_t buffer[260];
    uint16_t buffer_len;
} altcp_queue_t;

typedef struct ALTCP_SERVER_T_ {
    struct altcp_pcb *server_pcb;
    struct altcp_pcb *client_pcb;
    unsigned char client_ip_addr[20];
    bool connected;
    queue_t recv_queue;
    uint8_t server_host_name[126];
    uint16_t recv_count;
    uint16_t start_index;
    uint16_t queue_left;
    altcp_queue_t temp_recv;
} ALTCP_SERVER_T;

bool picow_tls_server_init(void);
  • modbus_tcp_server.c
#include "stdio.h"
#include "pico/stdlib.h"
#include "modbus_tcp_server.h"


#include "lwip/pbuf.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp_tls.h"
#include "picow_tls_server/picow_tls_server.h"
#include "GPIO_StepperMotor/steppermotor.h"


nmbs_t nmbs_tls;

#define COILS_ADDR_MAX      100
#define REGS_ADDR_MAX       32
#define FILE_SIZE_MAX       32

// A single nmbs_bitfield variable can keep 2000 coils
bool terminate = false;
nmbs_bitfield server_coils = {0};
uint16_t server_registers[REGS_ADDR_MAX] = {0};
uint16_t server_file[FILE_SIZE_MAX];


#define UNUSED_PARAM(x) ((x) = (x))

void sighandler(int s) {
    UNUSED_PARAM(s);
    terminate = true;
}

nmbs_error handle_altcp_read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg) {
    UNUSED_PARAM(arg);
    UNUSED_PARAM(unit_id);
    ALTCP_SERVER_T * mb = (ALTCP_SERVER_T*) arg;


    if (address + quantity > COILS_ADDR_MAX + 1)
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;

   
     
    for (int i = 0; i < quantity; i++) {
        nmbs_bitfield_write(server_coils, address + i, nmbs_bitfield_read(coils_out, i));
    }
  
    return NMBS_ERROR_NONE;
}

nmbs_error handle_altcp_read_discrete_inputs(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg) {
    UNUSED_PARAM(arg);
    UNUSED_PARAM(unit_id);

    if (address + quantity > COILS_ADDR_MAX + 1)
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;

     
    for (int i = 0; i < quantity; i++) {
        nmbs_bitfield_write(server_coils, address + i, nmbs_bitfield_read(coils_out, i));
    }

    return NMBS_ERROR_NONE;
}

nmbs_error handle_altcp_write_single_coil(uint16_t address, bool value, uint8_t unit_id, void* arg) {
    UNUSED_PARAM(arg);
    UNUSED_PARAM(unit_id);

    //if (address > COILS_ADDR_MAX )
    //    return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
    
    if (address > 2 )
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;

    
    // Write coils values to our server_coils
    nmbs_bitfield_write(server_coils, address, value);
    if (address == 1) {
        gpio_put(LED_PIN, value);
    }
    if (address == 0) {
        stepperMotor_rotate_angle(90, value);
    }

    return NMBS_ERROR_NONE;
}

nmbs_error handle_altcp_write_multiple_coils(uint16_t address, uint16_t quantity, const nmbs_bitfield coils, uint8_t unit_id,
                                       void* arg) {
    UNUSED_PARAM(arg);
    UNUSED_PARAM(unit_id);

    if (address + quantity > COILS_ADDR_MAX + 1)
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;

    

    // Write coils values to our server_coils
    for (int i = 0; i < quantity; i++) {
        nmbs_bitfield_write(server_coils, address + i, nmbs_bitfield_read(coils, i));
    }

    return NMBS_ERROR_NONE;
}


nmbs_error handler_tcp_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out, uint8_t unit_id,
                                          void* arg) {
    UNUSED_PARAM(arg);
    UNUSED_PARAM(unit_id);

    if (address + quantity > REGS_ADDR_MAX + 1)
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;

    
    for (int i = 0; i < quantity; i++)
        server_registers[address + i] = registers_out[i];

    return NMBS_ERROR_NONE;
}

nmbs_error handle_altcp_write_single_register(uint16_t address, uint16_t value, uint8_t unit_id, void* arg) {
    UNUSED_PARAM(arg);
    UNUSED_PARAM(unit_id);

    if (address > REGS_ADDR_MAX)
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
    
    
    // Write registers values to our server_registers

        server_registers[address] = value;

    return NMBS_ERROR_NONE;
}

nmbs_error handle_altcp_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* registers,
                                           uint8_t unit_id, void* arg) {
    UNUSED_PARAM(arg);
    UNUSED_PARAM(unit_id);

    if (address + quantity > REGS_ADDR_MAX + 1)
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
    
    

    // Write registers values to our server_registers
    for (int i = 0; i < quantity; i++)
        server_registers[address + i] = registers[i];

    return NMBS_ERROR_NONE;
}

nmbs_error handle_altcp_read_file_record(uint16_t file_number, uint16_t record_number, uint16_t* registers, uint16_t count,
                                   uint8_t unit_id, void* arg) {
    UNUSED_PARAM(arg);
    UNUSED_PARAM(unit_id);

    if (file_number != 1)
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;

    if ((record_number + count) > FILE_SIZE_MAX)
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
    


    memcpy(registers, server_file + record_number, count * sizeof(uint16_t));

    return NMBS_ERROR_NONE;
}


nmbs_error handle_altcp_write_file_record(uint16_t file_number, uint16_t record_number, const uint16_t* registers,
                                    uint16_t count, uint8_t unit_id, void* arg) {
    UNUSED_PARAM(arg);
    UNUSED_PARAM(unit_id);

    if (file_number != 1)
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;

    if ((record_number + count) > FILE_SIZE_MAX)
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;

    
    memcpy(server_file + record_number, registers, count * sizeof(uint16_t));

    return NMBS_ERROR_NONE;
}

int32_t nmbs_transport_read(uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
    ALTCP_SERVER_T *state=(ALTCP_SERVER_T*) arg;
 
    uint16_t loop_count = 0;

    if (state->queue_left == 0) {
        while (queue_is_empty(&state->recv_queue) && loop_count < 20) {           
            loop_count++;
            busy_wait_ms(50);
        }

        if (!queue_try_remove(&(state->recv_queue), &state->temp_recv))  {
                return 0;
        } else {
            DEBUG_print("\t\t\t\t---loop count:%d\n", loop_count);
        }
        state->queue_left = state->temp_recv.buffer_len;
    }
  
    state->recv_count = state->queue_left <= count ? state->queue_left : count;
    state->start_index = state->temp_recv.buffer_len-state->queue_left;
    for (int i=0; i < state->recv_count; i++) buf[i] = state->temp_recv.buffer[state->start_index+i];

    state->queue_left = state->queue_left-state->recv_count;

    //Debug
#ifdef __DEBUG__
    DEBUG_print("\n=====transport read:%d:%d\n", count, state->temp_recv.buffer_len);
    for (int i=0; i < state->recv_count; i++) {
        DEBUG_print("%02x ", buf[i]);
    }
    DEBUG_print("\n==\n");
#endif
    return state->recv_count;
}

int32_t nmbs_transport_write(const uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
    ALTCP_SERVER_T *state = (ALTCP_SERVER_T*)arg;

    // Debug
#ifdef __DEBUG__
    DEBUG_print("====\nnmbs_transport_write count:%d\n", count);
    for (int i=0; i < count;i++) {
        DEBUG_print("%02x ", buf[i]);
    }
    DEBUG_print("\n====\n");
#endif
    err_t err = altcp_write(state->client_pcb, buf, count, TCP_WRITE_FLAG_COPY);
    if (err != ERR_OK) 
    {
        //tcp_abort(state->tcp_pcb);
        DEBUG_print("tcp_write error:%d:%d\n", err, altcp_sndbuf(state->client_pcb)); 
        return 0;
    }

    err = altcp_output(state->client_pcb);
    if (err != ERR_OK) 
    {
        DEBUG_print("%s:tcp_output:%d\n", state->server_host_name, err); 
        return 0;
    }

    return count;
}
  • modbus_tcp_server.h
#ifndef __MODBUS_GATEWAY_TCP_SERVER__
#define __MODBUS_GATEWAY_TCP_SERVER__

#include "nanomodbus/nanomodbus.h"

extern nmbs_t nmbs_tls;

nmbs_error handle_altcp_read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg);
nmbs_error handle_altcp_read_discrete_inputs(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg);
nmbs_error handle_altcp_write_single_coil(uint16_t address, bool value, uint8_t unit_id, void* arg);
nmbs_error handle_altcp_write_multiple_coils(uint16_t address, uint16_t quantity, const nmbs_bitfield coils, uint8_t unit_id, void* arg);
nmbs_error handler_tcp_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out, uint8_t unit_id, void* arg);
nmbs_error handle_altcp_write_single_register(uint16_t address, uint16_t value, uint8_t unit_id, void* arg);
nmbs_error handle_altcp_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* registers,
                                           uint8_t unit_id, void* arg);
nmbs_error handle_altcp_read_file_record(uint16_t file_number, uint16_t record_number, uint16_t* registers, uint16_t count,
                                   uint8_t unit_id, void* arg);
nmbs_error handle_altcp_write_file_record(uint16_t file_number, uint16_t record_number, const uint16_t* registers,
                                    uint16_t count, uint8_t unit_id, void* arg);                                  
int32_t nmbs_transport_read(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg);
int32_t nmbs_transport_write(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms,void* arg);


#endif 

client:

  • CMakeLists.txt
# 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_3_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_w CACHE STRING "Board type")

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

project(picow_modbus_tcp_security_client 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(picow_modbus_tcp_security_client 
            picow_modbus_tcp_security_client.c 
            picow_altcp_client/picow_altcp_client.c
            nanomodbus/nanomodbus.c)

pico_set_program_name(picow_modbus_tcp_security_client "picow_modbus_tcp_security_client")
pico_set_program_version(picow_modbus_tcp_security_client "0.1")

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

# Add the standard library to the build
target_link_libraries(picow_modbus_tcp_security_client
        pico_stdlib)

# Add the standard include files to the build
target_include_directories(picow_modbus_tcp_security_client PRIVATE
        ${CMAKE_CURRENT_LIST_DIR}
)

# Add any user requested libraries
target_link_libraries(picow_modbus_tcp_security_client 
        pico_cyw43_arch_lwip_threadsafe_background
        pico_lwip_mbedtls
        pico_mbedtls
        pico_lwip_mdns
        )

pico_add_extra_outputs(picow_modbus_tcp_security_client)

# Ignore warnings from lwip code
set_source_files_properties(
        ${PICO_LWIP_PATH}/src/apps/altcp_tls/altcp_tls_mbedtls.c
        PROPERTIES
        COMPILE_OPTIONS "-Wno-unused-result"
        )
  • picow_modbus_tcp_security_client.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "lwip/altcp.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp_tls.h"
#include "lwip/pbuf.h"

#include "picow_altcp_client/picow_altcp_client.h"
#include "nanomodbus/nanomodbus.h"
#include "modbus_server_cert.h"

#define MODBUS_SERVER_HOST_NAME "modbus_server.local"
#define GATE_BUTTON_PIN 16  
#define LIGHT_BUTTON_PIN 17

nmbs_t nmbs;
ALTCP_CLIENT_T *modbus_client_addr1;

int32_t nmbs_transport_read(uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
    ALTCP_CLIENT_T *state=(ALTCP_CLIENT_T*) arg;
 
    uint16_t loop_count = 0;

    if (state->queue_left == 0) {
        while (queue_is_empty(&state->recv_queue) && loop_count < 20) {           
            loop_count++;
            busy_wait_ms(50);
        }
        if (!queue_try_remove(&(state->recv_queue), &state->temp_recv))  {
                return 0;
        } else {
            DEBUG_print("\t\t\t\t---loop count:%d\n", loop_count);
        }
        state->queue_left = state->temp_recv.buffer_len;
    }
   
    state->recv_count = state->queue_left <= count ? state->queue_left : count;
    state->start_index = state->temp_recv.buffer_len-state->queue_left;
    for (int i=0; i < state->recv_count; i++) buf[i] = state->temp_recv.buffer[state->start_index+i];

    state->queue_left = state->queue_left-state->recv_count;

    //Debug
    DEBUG_print("\n=====transport read:%d:%d\n", count, state->temp_recv.buffer_len);
    for (int i=0; i < state->recv_count; i++) {
        DEBUG_print("%02x ", buf[i]);
    }
    DEBUG_print("\n==\n");
 
    return state->recv_count;
}

int32_t nmbs_transport_write(const uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
    ALTCP_CLIENT_T *state = (ALTCP_CLIENT_T*)arg;

    // Debug
    DEBUG_print("====\nnmbs_transport_write count:%d\n", count);
    for (int i=0; i < count;i++) {
        DEBUG_print("%02x ", buf[i]);
    }
    DEBUG_print("\n====\n");

    err_t err = altcp_write(state->tcp_pcb, buf, count, TCP_WRITE_FLAG_COPY);
    if (err != ERR_OK) 
    {
        //tcp_abort(state->tcp_pcb);
        DEBUG_print("tcp_write error:%d:%d\n", err, altcp_sndbuf(state->tcp_pcb)); 
        return 0;
    }
    err = altcp_output(state->tcp_pcb);
    if (err != ERR_OK) 
    {
        DEBUG_print("%s:tcp_output:%d\n", state->server_host_name, err); 
        return 0;
    }
    return count;
}

void gpio_button_irq_cb(uint gpio, uint32_t event_mask){
    static bool addr0_value=false, addr1_value=false;
    if ((gpio == GATE_BUTTON_PIN || gpio == LIGHT_BUTTON_PIN)&&event_mask == GPIO_IRQ_EDGE_FALL)
    {
        gpio_acknowledge_irq(gpio, GPIO_IRQ_EDGE_FALL);
        if (modbus_client_addr1->conn_state == TCP_CONNECTED) {
            if (gpio == GATE_BUTTON_PIN) {
                addr0_value = !addr0_value;
                nmbs_write_single_coil(&nmbs,0, addr0_value);
                
            }
            if (gpio == LIGHT_BUTTON_PIN) {
                addr1_value = !addr1_value;
                nmbs_write_single_coil(&nmbs,1, addr1_value);
                
            }
        }
        
    }

}
int main()
{
    stdio_init_all();
    gpio_init(GATE_BUTTON_PIN);
    gpio_init(LIGHT_BUTTON_PIN);

    gpio_set_irq_enabled_with_callback(GATE_BUTTON_PIN, GPIO_IRQ_EDGE_FALL, true, gpio_button_irq_cb);
    gpio_set_irq_enabled_with_callback(LIGHT_BUTTON_PIN, GPIO_IRQ_EDGE_FALL, true, gpio_button_irq_cb);
    // Initialise the Wi-Fi chip
    if (cyw43_arch_init()) {
        DEBUG_print("Wi-Fi init failed\n");
        return -1;
    }

    if (!picow_sta_connect(WIFI_SSID, WIFI_PASSWD)) {
        return 0;
    }
    if (!picow_altcp_client_init(&modbus_client_addr1, MODBUS_SERVER_HOST_NAME, MODBUS_ALTCP_PORT, MODBUS_SERVER_CERT)) return 0; 
      

    nmbs_platform_conf platform_conf;
    nmbs_platform_conf_create(&platform_conf);
    platform_conf.transport = NMBS_TRANSPORT_TCP;
    platform_conf.read = nmbs_transport_read;
    platform_conf.write = nmbs_transport_write;
    
    
    // Create the modbus client
    nmbs_error err = nmbs_client_create(&nmbs, &platform_conf);
    if (err != NMBS_ERROR_NONE) {
        fprintf(stderr, "Error creating modbus client\n");
        if (!nmbs_error_is_exception(err))
            return 1;
    }

    // Set only the response timeout. Byte timeout will be handled by the TCP connection
    nmbs_set_read_timeout(&nmbs, 1000);
    nmbs_set_platform_arg(&nmbs, modbus_client_addr1);
    bool value = true;
    uint count=0;
    while (true) {
    
        tight_loop_contents();
    }
}
  • picow_altcp_client.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "lwip/altcp.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp_tls.h"
#include "lwip/pbuf.h"

#include "picow_altcp_client/picow_altcp_client.h"
#include "nanomodbus/nanomodbus.h"
#include "modbus_server_cert.h"

#define MODBUS_SERVER_HOST_NAME "modbus_server.local"
#define GATE_BUTTON_PIN 16  
#define LIGHT_BUTTON_PIN 17

nmbs_t nmbs;
ALTCP_CLIENT_T *modbus_client_addr1;

int32_t nmbs_transport_read(uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
    ALTCP_CLIENT_T *state=(ALTCP_CLIENT_T*) arg;
 
    uint16_t loop_count = 0;

    if (state->queue_left == 0) {
        while (queue_is_empty(&state->recv_queue) && loop_count < 20) {           
            loop_count++;
            busy_wait_ms(50);
        }
        if (!queue_try_remove(&(state->recv_queue), &state->temp_recv))  {
                return 0;
        } else {
            DEBUG_print("\t\t\t\t---loop count:%d\n", loop_count);
        }
        state->queue_left = state->temp_recv.buffer_len;
    }
   
    state->recv_count = state->queue_left <= count ? state->queue_left : count;
    state->start_index = state->temp_recv.buffer_len-state->queue_left;
    for (int i=0; i < state->recv_count; i++) buf[i] = state->temp_recv.buffer[state->start_index+i];

    state->queue_left = state->queue_left-state->recv_count;

    //Debug
    DEBUG_print("\n=====transport read:%d:%d\n", count, state->temp_recv.buffer_len);
    for (int i=0; i < state->recv_count; i++) {
        DEBUG_print("%02x ", buf[i]);
    }
    DEBUG_print("\n==\n");
 
    return state->recv_count;
}

int32_t nmbs_transport_write(const uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
    ALTCP_CLIENT_T *state = (ALTCP_CLIENT_T*)arg;

    // Debug
    DEBUG_print("====\nnmbs_transport_write count:%d\n", count);
    for (int i=0; i < count;i++) {
        DEBUG_print("%02x ", buf[i]);
    }
    DEBUG_print("\n====\n");

    err_t err = altcp_write(state->tcp_pcb, buf, count, TCP_WRITE_FLAG_COPY);
    if (err != ERR_OK) 
    {
        //tcp_abort(state->tcp_pcb);
        DEBUG_print("tcp_write error:%d:%d\n", err, altcp_sndbuf(state->tcp_pcb)); 
        return 0;
    }
    err = altcp_output(state->tcp_pcb);
    if (err != ERR_OK) 
    {
        DEBUG_print("%s:tcp_output:%d\n", state->server_host_name, err); 
        return 0;
    }
    return count;
}

void gpio_button_irq_cb(uint gpio, uint32_t event_mask){
    static bool addr0_value=false, addr1_value=false;
    if ((gpio == GATE_BUTTON_PIN || gpio == LIGHT_BUTTON_PIN)&&event_mask == GPIO_IRQ_EDGE_FALL)
    {
        gpio_acknowledge_irq(gpio, GPIO_IRQ_EDGE_FALL);
        if (modbus_client_addr1->conn_state == TCP_CONNECTED) {
            if (gpio == GATE_BUTTON_PIN) {
                addr0_value = !addr0_value;
                nmbs_write_single_coil(&nmbs,0, addr0_value);
                
            }
            if (gpio == LIGHT_BUTTON_PIN) {
                addr1_value = !addr1_value;
                nmbs_write_single_coil(&nmbs,1, addr1_value);
                
            }
        }
        
    }

}
int main()
{
    stdio_init_all();
    gpio_init(GATE_BUTTON_PIN);
    gpio_init(LIGHT_BUTTON_PIN);

    gpio_set_irq_enabled_with_callback(GATE_BUTTON_PIN, GPIO_IRQ_EDGE_FALL, true, gpio_button_irq_cb);
    gpio_set_irq_enabled_with_callback(LIGHT_BUTTON_PIN, GPIO_IRQ_EDGE_FALL, true, gpio_button_irq_cb);
    // Initialise the Wi-Fi chip
    if (cyw43_arch_init()) {
        DEBUG_print("Wi-Fi init failed\n");
        return -1;
    }

    if (!picow_sta_connect(WIFI_SSID, WIFI_PASSWD)) {
        return 0;
    }
    if (!picow_altcp_client_init(&modbus_client_addr1, MODBUS_SERVER_HOST_NAME, MODBUS_ALTCP_PORT, MODBUS_SERVER_CERT)) return 0; 
      

    nmbs_platform_conf platform_conf;
    nmbs_platform_conf_create(&platform_conf);
    platform_conf.transport = NMBS_TRANSPORT_TCP;
    platform_conf.read = nmbs_transport_read;
    platform_conf.write = nmbs_transport_write;
    
    
    // Create the modbus client
    nmbs_error err = nmbs_client_create(&nmbs, &platform_conf);
    if (err != NMBS_ERROR_NONE) {
        fprintf(stderr, "Error creating modbus client\n");
        if (!nmbs_error_is_exception(err))
            return 1;
    }

    // Set only the response timeout. Byte timeout will be handled by the TCP connection
    nmbs_set_read_timeout(&nmbs, 1000);
    nmbs_set_platform_arg(&nmbs, modbus_client_addr1);
    bool value = true;
    uint count=0;
    while (true) {
    
        tight_loop_contents();
    }
}
  • picow_altcp_client.h
#ifndef __PICOW_ALTCP_CLIENT__
#define __PCIOW_ALTCP_CLIENT__

#include "pico/util/queue.h"

#include "lwip/altcp.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp_tls.h"


#define WIFI_SSID "your-SSID"
#define WIFI_PASSWD "your-PASSWD"

#define MODBUS_TCP_PORT 502
#define MODBUS_ALTCP_PORT 802
#define POLL_TIME_S 1

#define __DEBUG__
#ifdef __DEBUG__
#define DEBUG_print(...) printf(__VA_ARGS__)
#else
#define DEBUG_print(...) (void) (0)
#endif

enum _TCP_STATE{
    TCP_CONNECTING  = 1,
    TCP_CONNECTED   = 2,
    TCP_CLOSING     = 3,
    TCP_CLOSED      = 4,
};

typedef struct __recv_queue_t{
    uint8_t buffer[260];
    uint16_t buffer_len;
} recv_queue_t;

typedef struct ALTCP_CLIENT_T_ {
    struct altcp_pcb *tcp_pcb;
    struct altcp_tls_config *tls_config;
    ip_addr_t remote_addr;
    uint      remote_port;
    uint8_t conn_state;
    bool dns_found;
    queue_t recv_queue;
    uint8_t server_host_name[256];
    uint16_t recv_count;
    uint16_t start_index;
    uint16_t queue_left;
    recv_queue_t temp_recv;
} ALTCP_CLIENT_T;

bool picow_altcp_client_init(ALTCP_CLIENT_T* *modbus_altcp_client, const char* hostname, uint port, const u8_t *cert);
err_t picow_altcp_client_close(void *arg);
err_t picow_altcp_client_conn(void *arg);
err_t picow_altcp_client_disconn(void *arg);
bool picow_altcp_client_open(ALTCP_CLIENT_T* state, const u8_t *cert);
bool picow_sta_connect(uint8_t* ssid, uint8_t* pwd);

#endif

  • nanoMODBUS library:






[Raspberry Pi Pico] PIO programming: 7-segment display and 4x4 keypad as examples

  本文章介紹使用Raspberry Pi Pico的PIO功能製作一個8位數的七段式顯示器的驅動程式,另外結合以前使用PIO製作的4x4 KEYPAD作為輸入,建構一個簡單的計算器。

8位數七段顯示除使用8個共陰極的顯示器外,另外使用2個74HC595 shift register與8個NPN電晶體,第一個shift register控制七段顯示器A~G與dp的顯示,第二個shift register Q0~Q7連接個別7段顯示器的pin 3/8控制顯示器顯示(low)或關閉(high)。

線路圖下所示:


顯示方式以快速一次顯示一個7段顯示器,因此每顯示一個位數須輸出2 bytes,第一個byte為7段顯示器內容,第二個byte為第幾個顯示器開啟(其他關閉)。

PIO程式部份使用side-set 控制shift register 的SH_CP與ST_CP clock,程式碼如文末所示。

4x4 keypad詳細內容請參閱:

Raspberry Pi Pico PIO(Programmable IO) Episode 1: 4x4 matrix keypad


成果影片



程式碼:

CMakeLists.txt(main)
# 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_3_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_pio_7seg 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_pio_7seg pico_pio_7seg.c)

pico_set_program_name(pico_pio_7seg "pico_pio_7seg")
pico_set_program_version(pico_pio_7seg "0.1")

target_compile_definitions(pico_pio_7seg PRIVATE
        WIFI_SSID="$ENV{WIFI_SSID}"
        )


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

# Add the standard library to the build
target_link_libraries(pico_pio_7seg
        pico_stdlib
        )

# Add the standard include files to the build
target_include_directories(pico_pio_7seg PRIVATE
        ${CMAKE_CURRENT_LIST_DIR}
)

# Add any user requested libraries
target_link_libraries(pico_pio_7seg 
        hardware_pio
        )
add_subdirectory(disp_7segment)
add_subdirectory(pico_keypad)
target_link_libraries(pico_pio_7seg
        disp_7segment
        pico_keypad)

pico_add_extra_outputs(pico_pio_7seg)


 
pico_pio_7seg.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "pico/multicore.h"

#include "disp_7segment.pio.h"
#include "disp_7segment.h"
#include "keypad.h"

#include "stdlib.h"
#include "string.h"


#define DS_PIN          16
#define SH_ST_CLK_PIN   17


uint8_t* disp_expression_value(double value) {
    static uint8_t value_str[20];
    sprintf(value_str, "%f", value);
    if (strchr(value_str, '.')) {
        for (int i = strlen(value_str)-1; i>=0; i--) {
            if (value_str[i] == '0' ) {
                value_str[i]=0; 
            } else { 
                if (value_str[i] == '.') value_str[i]=0;  
                break;
            }
        }
    }
    if (value_str[0]==0) {
        value_str[0]='0';
    }
    disp_7segment_write_str_digits(value_str);
    return value_str;
}

void demo();
int main()
{
    stdio_init_all();

   disp_7segment_init(pio0, 1, DS_PIN,  SH_ST_CLK_PIN, 8, 10000000);
   keypad_default_init();
  
//demo();

   uint8_t exp[256]={0};
   uint8_t input[20]={0};
   int pos_index=0;
   uint8_t key;
   uint8_t ks[2]={0,0};
   double pre_result=0;
   uint8_t pre_op=0;
   disp_7segment_write_str_digits("0");
   while (true) {
        key = get_new_keypad_value();
        switch (key) {
            case '0': case '1': case '2' :case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.':
                if (pos_index > 8) continue;
                input[pos_index++] = key;
                disp_7segment_write_str_digits(input);
                break;
            case 'b':
                --pos_index;
                if (pos_index < 0) { memset(input, 0, sizeof(input));pos_index = 0; }
                input[pos_index] = 0; 
                disp_7segment_write_str_digits(input);
                break;
            case '+': case '-': case '*':  
                // update expression
                ks[0] = key;
                strcat(exp, input);
                strcat(exp, ks);
                
                // calculate previous sub-expression
                if (pre_op) {
                    if (pre_op == '+') {
                        pre_result += atof(input);
                    }
                    if (pre_op == '-') {
                        pre_result -= atof(input);
                    }
                    if (pre_op == '*') {
                        pre_result *= atof(input);
                    }
                    disp_expression_value(pre_result);
                }  
                else 
                    pre_result=atof(input);

                pre_op = key;

                //clean input buffer
                memset(input, 0, 20);
                pos_index = 0;

                break;
            case '=':    //calculate total expression
                
                strcat(exp, input);
                
                   
                if (pre_op) {
                    if (pre_op == '+') {
                        pre_result += atof(input);
                    }
                    if (pre_op == '-') {
                        pre_result -= atof(input);
                    }
                    if (pre_op == '*') {
                        pre_result *= atof(input);
                    }
                } 
                printf("%s=%f\n", exp, pre_result);
                disp_expression_value(pre_result);

                memset(input, 0, 20); 
                pos_index = 0;

                pre_op = 0;
                pre_result = 0;
                memset(exp, 0, sizeof(exp));

                break;
        }
        //disp_7segment_write_str_digits(input);

        tight_loop_contents();
   }
  
       
}

void demo() {
    //sleep_ms(5000);
    uint8_t buf[20];
    for (int i =0; i < 16;i++) {
        disp_7segment_write_digits(i);
        sleep_ms(500);
    }
    sleep_ms(500);
    disp_7segment_write_str_digits("a");
    sleep_ms(500);
    disp_7segment_write_str_digits("ab");
    sleep_ms(500);
    disp_7segment_write_str_digits("abc");
    sleep_ms(500);
    disp_7segment_write_str_digits("abcd");
    sleep_ms(500);
    disp_7segment_write_str_digits("abced");
    sleep_ms(500);
    disp_7segment_write_str_digits("abcdef");
    sleep_ms(1000);
    for (int i =-1; i >= -15;i--) {
        disp_7segment_write_digits(i);
        sleep_ms(500);
    }
    sleep_ms(500);
    disp_7segment_write_str_digits("1");
    sleep_ms(500);
    disp_7segment_write_str_digits("12");
    sleep_ms(500);
    disp_7segment_write_str_digits("123");
    sleep_ms(500);
    disp_7segment_write_str_digits("1234");
    sleep_ms(500);
    disp_7segment_write_str_digits("12345");
    sleep_ms(500);
    disp_7segment_write_str_digits("123456");
    sleep_ms(500);
    disp_7segment_write_str_digits("1234567");
    sleep_ms(500);
    disp_7segment_write_str_digits("12345678");
    sleep_ms(1000);
    for (double i=0.67; i < 100; i+=5.71) {
        sprintf(buf, "%f", i);
        disp_expression_value(i);
        sleep_ms(500);
    }
    sleep_ms(2000);


}
 

disp_7segment library:
  • disp_7segment.c
#include "pico/stdlib.h"
#include "stdio.h"
#include "stdlib.h"
#include "disp_7segment.h"
#include "disp_7segment.pio.h"
#include "string.h"
#include "pico/multicore.h"
uint8_t led_digit[] = {
    0b11111100,     //0
    0b01100000,     //1
    0b11011010,     //2
    0b11110010,     //3
    0b01100110,     //4
    0b10110110,     //5
    0b10111110,     //6
    0b11100000,     //7
    0b11111110,     //8
    0b11100110,     //9
    0b11101110,     //A
    0b00111110,     //b
    0b10011100,     //C
    0b01111010,     //d
    0b10011110,     //E
    0b10001110,     //F
    0b00000010,     //-
    0b00000000,     // default no display
};
static uint8_t total_digits;
static PIO disp_7segment_pio;
static uint disp_7segment_sm;
static void display_digits(uint8_t *value_str, uint8_t value_len);

void core1_display_digits(void) {
    uint32_t popup_data;
    uint8_t display_data_len=0;
    uint8_t display_data_str[20]={0};
    while(1) {
        if (multicore_fifo_pop_timeout_us(100,&popup_data)) {
            strcpy(display_data_str, (uint8_t*)popup_data);
            display_data_len = strlen(display_data_str);
        }
        display_digits(display_data_str, display_data_len);
    }
}

void disp_7segment_init(PIO pio, uint sm, uint ds, uint sh_st_clk, uint8_t digits, uint freq) {
    total_digits = digits;
    uint offset = pio_add_program(pio, &disp_7segment_program);
    pio_sm_config c = disp_7segment_program_get_default_config(offset);
    pio_gpio_init(pio, ds);
    for (int i = 0; i < 2; i++) pio_gpio_init(pio, sh_st_clk+i);
     
    disp_7segment_pio = pio;
    disp_7segment_sm = sm;

    pio_sm_set_consecutive_pindirs(pio, sm, ds, 1, true);
    pio_sm_set_consecutive_pindirs(pio, sm, sh_st_clk, 2, true);

    sm_config_set_sideset_pin_base(&c, sh_st_clk);
    sm_config_set_out_pins(&c, ds,1);
    sm_config_set_out_shift(&c, true, true, 8);
    if (freq > 100000) freq = 100000;
    sm_config_set_clkdiv(&c, SYS_CLK_HZ/(freq*2));
    //sm_config_set_clkdiv(&c, 62.5);

    pio_sm_init(pio, sm, offset, &c);
    pio_sm_exec(pio, sm, pio_encode_set(pio_y, digits%8 ? digits/8+1 : digits/8));

    pio_sm_set_enabled(pio, sm, true);

    // launch core1 to continuely display ditits
    multicore_launch_core1(core1_display_digits);
}

static uint8_t char_to_index(uint8_t c) {
    //if (c >='0' && c <='9') return c-'0';
    switch (c) {
        case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
            return c-'0';
            break; 
        case 'A':  case 'a':
            return  10;
            break;
        case 'B': case 'b':
            return 11;
            break;
        case 'C': case 'c':
            return 12;
            break;
        case 'D': case 'd':
            return 13;
            break;
        case 'E': case 'e':
            return 14;
            break;
        case 'F': case 'f':
            return 15;
            break;
        case '-': 
            return 16;
            break;
        default:
            return 17;
            break;
    }
}
static void display_digits(uint8_t *value_str, uint8_t value_len) {
    uint32_t digit_pos=0;
    uint8_t temp_total_digitals = (total_digits %8) ? (total_digits/8+1)*8 : total_digits;
    bool dp = false;
    uint8_t real_digits=total_digits;
    for (int i=0, pos=0; i < real_digits; i++) {
        if (i > value_len-1) pio_sm_put_blocking(disp_7segment_pio, disp_7segment_sm, 0);
        else {  
            if (value_str[value_len-i-1] == '.') 
            {
                dp=true;
                real_digits++;
                continue;
            } 
            if (dp) {
                dp = false;
                pio_sm_put_blocking(disp_7segment_pio, disp_7segment_sm, led_digit[char_to_index(value_str[value_len-i-1])]+1);
            } else {      
                pio_sm_put_blocking(disp_7segment_pio, disp_7segment_sm, led_digit[char_to_index(value_str[value_len-i-1])]);
            }
        }
        digit_pos=0;
        digit_pos = 1 << (temp_total_digitals-pos-1);
        pio_sm_put_blocking(disp_7segment_pio, disp_7segment_sm, digit_pos);
        pos++;
         //sleep_us(100);
    }
}

void disp_7segment_write_digits(int32_t value) {
    static uint8_t value_str[20];
    sprintf(value_str, "%ld", value);
    multicore_fifo_push_blocking((uint32_t)value_str);
}

void disp_7segment_write_str_digits(uint8_t* value) {
    static uint8_t value_str[20];
    if (value[0] == 0) 
        strcpy(value_str,"0");
    else 
        strcpy(value_str, value);

    multicore_fifo_push_blocking((uint32_t)value_str);
}
 
  • disp_7segment.h
#ifndef __DISP_7_SEGMENT__
#define __DISP_7_SEGMENT__

#include "hardware/pio.h"

extern uint8_t led_digit[];

void disp_7segment_init(PIO pio, uint sm, uint ds, uint sh_st_clk, uint8_t digits, uint freq);
void disp_7segment_write_digits(int32_t value);
void disp_7segment_write_str_digits(uint8_t* value);
#endif
 
  • disp_7segment.pio
.program disp_7segment
.side_set 2                         ;SH_CP(bit0), ST_CP(bit1)

mov isr, y              side 0b00   ;total digits preload to y
                                    ;isr used to store y temporarily 
.wrap_target
digits_loop:
set x,7                 side 0b00   ;a~g and dp 
seg_loop:
out pins, 1             side 0b00   
jmp x--, seg_loop       side 0b01   ;shift out one shift register data 
jmp y--, digits_loop    side 0b00   ;
mov y, isr              side 0b10   ;restore y
.wrap

  • CMakeLists.txt
add_library(disp_7segment INTERFACE)
pico_generate_pio_header(disp_7segment ${CMAKE_CURRENT_LIST_DIR}/disp_7segment.pio)

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

target_include_directories(disp_7segment INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
)

target_link_libraries(disp_7segment INTERFACE
        hardware_pio 
        pico_multicore
)


 

pico_keypad libarary:
  • keypad.c
#include "keypad.pio.h"
#include "keypad.h"
#include "hardware/clocks.h"
#include "stdio.h"
#include "pico/stdlib.h"


static uint8_t key_value=0;

static  PIO keypad_pio=pio1;
static  uint keypad_sm=0;
static const uint8_t keys[4][4]={ // define user key
    {'1','2','3','+'},
    {'4','5','6','-'},
    {'7','8','9','*'},
    {'b','0','.','='}
    };

static void keypad_handle() {
    if (pio_interrupt_get(keypad_pio, KEYPAD_PIO_INT_NUM)) {
        key_value=0;
        pio_interrupt_clear(keypad_pio, KEYPAD_PIO_INT_NUM);
        uint32_t x, y;
        y=pio_sm_get_blocking(keypad_pio, keypad_sm);
        x=pio_sm_get_blocking(keypad_pio, keypad_sm);

        for(uint8_t i = 0 ; i < 4; i++){
            if ((x >> i)==1) {x=i;break;}
        }
        for(uint8_t j = 0 ; j < 4; j++){
            if ((y >> j)==1) {y=j;break;}
        }
        key_value = keys[x][y];
    }
}

void keypad_pio_init(PIO pio, uint sm, uint set_base, uint in_base, uint freq) {
    uint offset=0;
    pio_sm_config c;
    offset = pio_add_program(pio, &keypad_program);
    c = keypad_program_get_default_config(offset);
    
    for (int i=0; i < 4; i++) pio_gpio_init(pio, in_base+i);
    for (int i=0; i < 4; i++) pio_gpio_init(pio, set_base+i);

    pio_sm_set_consecutive_pindirs(pio, sm, in_base, 4, false);
    pio_sm_set_consecutive_pindirs(pio, sm, set_base, 4, true);

    sm_config_set_in_pins(&c, in_base);
    sm_config_set_set_pins(&c, set_base, 4);
   
    sm_config_set_in_shift(&c, false, false, 32);

    float div = clock_get_hz(clk_sys)/freq;
    sm_config_set_clkdiv(&c, div);

    uint pio_irq = pio_get_index(pio)? PIO1_IRQ_0:PIO0_IRQ_0;
    pio_interrupt_source_t pis_int_num;
    switch (KEYPAD_PIO_INT_NUM) {
        case 0: pis_int_num = pis_interrupt0; break;
        case 1: pis_int_num = pis_interrupt1; break;
        case 2: pis_int_num = pis_interrupt2; break;
        case 3: pis_int_num = pis_interrupt3; break;
    }
    pio_set_irq0_source_enabled(pio, pis_int_num, true);
    irq_add_shared_handler(pio_irq, keypad_handle, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    irq_set_enabled(pio_irq, true);

    pio_sm_init(pio, sm, offset, &c);

    pio_sm_set_enabled(pio, sm, true);
}
uint8_t get_new_keypad_value() {
    uint8_t ret_vale = key_value;
    key_value=0;
    return ret_vale;
}

/* default value:
    pio: pio1
    sm:  0
    set pins:gpio 8-11
    in pins: 12-15
    */
void keypad_default_init() {
    keypad_pio_init(keypad_pio, keypad_sm,  8, 12, 100000);
}
 
  • keypad.h
#ifndef __KEYPAD_H
#define __KEYPAD_H


void keypad_default_init();
uint8_t get_new_keypad_value();
void keypad_pio_init(PIO pio, uint sm, uint set_base, uint in_base, uint freq);

#endif
 
  • keypad.pio
.define PUBLIC KEYPAD_PIO_INT_NUM 0   // only 0-3, PIO version 0

.program keypad
.wrap_target
set_row_1:
    set pins, 0b0001            // pull up line 1(pin 1) 
    set x,0b0001                // store value in register X 
    in pins,4                   // get input value of line 5~8 (pin 5~8)
    mov y, isr                  // and store in Y
    jmp !y set_row_2            // If no button is pressed (0b0000), continue checking line 2 
    jmp rx_fifo         [10]    // if any button is pressed, jump to push x, y 
    
set_row_2:
    set pins, 0b0010  
    set x,0b0010 
    in pins,4  
    mov y, isr  
    jmp !y set_row_3  
    jmp rx_fifo         [10]   // waiting for button bounce

set_row_3: 
    set pins, 0b0100   
    set x,0b0100  
    in pins,4  
    mov y, isr  
    jmp !y  set_row_4  
    jmp rx_fifo         [10]

set_row_4:
    set pins, 0b1000   
    set x,0b1000  
    in pins,4  
    mov y, isr  
    jmp !y  set_row_1
    nop                 [10]
rx_fifo:  
    push                        // push y col value   
    in x,4                      
    push                        // and then x row value  
    irq  KEYPAD_PIO_INT_NUM     // raising interrupt

wait 0 pin 0                    // check whether key is released
wait 0 pin 1  
wait 0 pin 2  
wait 0 pin 3  
.wrap      

 
  • CMakeLists.txt
add_library(pico_keypad INTERFACE)
pico_generate_pio_header(pico_keypad ${CMAKE_CURRENT_LIST_DIR}/keypad.pio)
target_sources(pico_keypad INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/keypad.c
)

target_include_directories(pico_keypad INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
)

target_link_libraries(pico_keypad INTERFACE
        hardware_pio
)
 



2025年3月18日 星期二

[Raspberry Pi Pico C-SDK] Simple electronic compass using HMC5883L 3-axis digital compass

 介紹使用HMC5883L三軸數位羅盤,詳細內容如下列影片所示。

有關4-line serial TFT libarary請參閱:

[Raspberry Pi Pico (c-sdk)] Display: Ep 5 :TFT LCD 4-lines Serial(SPI) Driver -- A single Frame(128x160) takes only  11~12ms

成果影片


程式碼:


  • CMakeLists.txt

# == 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.1)
set(toolchainVersion 14_2_Rel1)
set(picotoolVersion 2.1.1)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
    include(${picoVscode})
endif()
# ====================================================================================
# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "/home/duser/pico/pico-sdk")

set(PICO_BOARD pico CACHE STRING "Board type")

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

if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0")
  message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()

project(compass 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(compass compass.c )

pico_set_program_name(compass "compass")
pico_set_program_version(compass "0.1")

pico_enable_stdio_uart(compass 0)
pico_enable_stdio_usb(compass 1)

# Add the standard library to the build
target_link_libraries(compass
        pico_stdlib)

# Add the standard include files to the build
target_include_directories(compass PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)

# Add any user requested libraries
target_link_libraries(compass 
        hardware_i2c
        hardware_dma
        )
add_subdirectory(pico_tft)
target_link_libraries( compass 
      pico_tft
      )

pico_add_extra_outputs(compass)


  • compass.c

#include <stdio.h>
#include "stdlib.h"
#include "pico/stdlib.h"
#include "hardware/i2c.h"

#include "hardware/dma.h"
#include "hardware/pio.h"

#include "pico_tft.h"
#include "tft_string.h"
#include "fonts/font_fixedsys_mono_16.h"
#include "compass_img.h"
#include "string.h"
#include "registers.h"

#include "math.h"

#define I2C_PORT i2c0
#define I2C_SDA 16
#define I2C_SCL 17

#define DEVICE_ADDR   0x1E 

typedef struct _rotate_obj_t {
    uint8_t *srcimg;
    uint8_t *destimg;
    uint16_t width;
    uint16_t height;
    uint16_t angle;
} rotate_obj_t;


int16_t x_offset=-64;
int16_t z_offset= 76;

int main()
{
    uint8_t cmd_buf[4];
    uint8_t read_data[7];
    stdio_init_all();

    rotate_obj_t *rotate_img = (rotate_obj_t*) calloc(1, sizeof(rotate_obj_t));
    uint8_t *read_buff = (uint8_t*) calloc(TFT_WIDTH*TFT_HEIGHT*2, sizeof(uint8_t));  //frame buffer
    if (!read_buff) {
        printf("alloc memory error\n");
        return 0;
    }
     uint8_t *write_buff = (uint8_t*) calloc(TFT_WIDTH*TFT_HEIGHT*2, sizeof(uint8_t));  //frame buffer
    if (!write_buff) {
        printf("alloc memory error\n");
        return 0;
    }
    tft_init();
    tft_fill_rect(0,0, TFT_WIDTH, TFT_HEIGHT, 0xffff);

    // I2C Initialisation. Using it at 400Khz.
    i2c_init(I2C_PORT, 400*1000);
    
    gpio_set_function(I2C_SDA, GPIO_FUNC_I2C);
    gpio_set_function(I2C_SCL, GPIO_FUNC_I2C);
    gpio_pull_up(I2C_SDA);
    gpio_pull_up(I2C_SCL);


    // init 8-average, 15 Hz default, normal measurement
    cmd_buf[0] = 0x00;
    cmd_buf[1] = 0x70;
    i2c_write_blocking(i2c0, DEVICE_ADDR,cmd_buf, 2,true);
    cmd_buf[0] = 0x01;
    cmd_buf[1] = 0xa0; //0xa0
    i2c_write_blocking(i2c0, DEVICE_ADDR,cmd_buf, 2,true);
    
    int16_t x,y,z;
    double heading;

    memcpy(read_buff, image_data_compass, TFT_WIDTH*TFT_WIDTH*2);
    tft_rotate_image((uint16_t*)read_buff, (uint16_t*)write_buff, TFT_WIDTH, TFT_WIDTH, 90);
    tft_set_address_window (0, 0, TFT_WIDTH, TFT_WIDTH);
    tft_cmd(TFT_MEMORYWRITE, TFT_WIDTH*TFT_WIDTH*2,  (uint8_t*)write_buff);

    char buf_heading[10];
    while(1) {
        //Single-Measurement Mode (Default).
        cmd_buf[0] = 0x02;
        cmd_buf[1] = 0x01; //0x00
        i2c_write_blocking(i2c0, DEVICE_ADDR,cmd_buf, 2,false);
        sleep_ms(10);

        cmd_buf[0] = 0x03;
        i2c_write_blocking(i2c0, DEVICE_ADDR,cmd_buf, 1,false);    
        i2c_read_blocking(i2c0, DEVICE_ADDR, read_data, 6, false);

       
        x = ((uint16_t)read_data[0]) << 8 | read_data[1];
        z = ((uint16_t)read_data[2]) << 8 | read_data[3];
        y = ((uint16_t)read_data[4]) << 8 | read_data[5];

        
        printf("%d, %d, %d \n", x,y,z );  // print out for calibration           

        heading = atan2(x-x_offset,z-z_offset)*57.3; //180/PI

        if(heading < 0) heading+=360;
        
        //heading=360-heading; // N=0/360, E=90, S=180, W=270
        tft_rotate_image((uint16_t*)read_buff, (uint16_t*)write_buff, TFT_WIDTH, TFT_WIDTH, 360-heading);
        tft_set_address_window (0, 0, TFT_WIDTH, TFT_WIDTH);
        tft_cmd(TFT_MEMORYWRITE, TFT_WIDTH*TFT_WIDTH*2,  (uint8_t*)write_buff);
        sprintf(buf_heading, "%03.02f   ", heading);
        tft_draw_string_withbg(10, TFT_WIDTH+4, buf_heading, 0xf800, 0xffff, &font_fixedsys_mono_16);

        
    }

    return 0;
}

  • compass_img.h:
    使用lcd-image-converter軟體產生。