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軟體產生。