prettyprint

2025年4月26日 星期六

[STM32] STM32F407 using LVGL and XPT2046 touch screen and rotary encoder input device library

 本文章介紹STM32F407VET6 使用LVGL library與結合XPT2046 touch screen 與Rotary Encoder 等input devices。

有關移植LVGL到STM32F407的步驟請參閱前篇文章

STM32 HAL|| 16 bit parallel LCD-TFT driver using FSMC interface for LVGL || DMA


詳細步驟請參閱「成果影片」

成果影片:


程式碼:

  • rotary_encoder.c
/*
 * rotary_encoder.c

 */
#include <main.h>
#include "lvgl.h"

lv_indev_t *encoder_indev;
lv_group_t *encoder_group;

static TIM_HandleTypeDef *encoder_htim;

static uint32_t old_counter;

uint8_t re_key_press() {
    if (HAL_GPIO_ReadPin(Encoder_SW_GPIO_Port, Encoder_SW_Pin) == GPIO_PIN_RESET) {
        return 1;
    }
    return 0;
}

int16_t re_get_new_moves(void) {
    int16_t new_moves;
    new_moves = 0;
    uint32_t counter = __HAL_TIM_GET_COUNTER(encoder_htim);
    if (counter < old_counter) {
    	 new_moves = 1; // LV_KEY_RIGHT
    }
    if (counter > old_counter) {
    	new_moves = -1; // LV_KEY_LEFT
    }
    old_counter = counter;

    return new_moves;

}

void lvgl_encoder_read_cb(struct _lv_indev_drv_t * indev, lv_indev_data_t* data) {
  //data->key = get_key();;
	data->enc_diff = re_get_new_moves();    //rotation
	if (re_key_press()) {					// switch
		data->state = LV_INDEV_STATE_PRESSED;
	} else {
		data->state = LV_INDEV_STATE_RELEASED;
		//data->enc_diff = re_get_new_moves();
	}

 }





void lvgl_rotary_encoder_init(TIM_HandleTypeDef *htim){
	encoder_htim = htim;
	HAL_TIM_Encoder_Start(htim,  TIM_CHANNEL_1);
	__HAL_TIM_SET_COUNTER(encoder_htim,32768);
	old_counter = 32768;

	// setup rotary encoder input device
	static lv_indev_drv_t indev_drv;           /*Descriptor of a input device driver*/
	lv_indev_drv_init(&indev_drv);             /*Basic initialization*/
	indev_drv.type = LV_INDEV_TYPE_ENCODER;    /*Rotary Encoder device*/

	indev_drv.read_cb = lvgl_encoder_read_cb;      /*Set your driver function*/

	indev_drv.long_press_time=1000;
	indev_drv.long_press_repeat_time=100;
	encoder_indev = lv_indev_drv_register(&indev_drv);         /*Finally register the driver*/
	encoder_group = lv_group_create();

	//lv_group_set_default(kg);
	lv_indev_set_group(encoder_indev, encoder_group);
}


 
  • rotary_encoder.h
/*
 * rotary_incoder.h

 */

#ifndef ROTARY_ENCODER_ROTARY_ENCODER_H_
#define ROTARY_ENCODER_ROTARY_ENCODER_H_

extern lv_indev_t *encoder_indev;
extern lv_group_t *encoder_group;

void lvgl_rotary_encoder_init(TIM_HandleTypeDef *htim);

void lvgl_encoder_read_cb(struct _lv_indev_drv_t * indev, lv_indev_data_t* data);




#endif /* ROTARY_ENCODER_ROTARY_ENCODER_H_ */

 
  • STM32_LVGL_LIB_DEMO.ioc
#MicroXplorer Configuration settings - do not modify
CAD.formats=
CAD.pinconfig=
CAD.provider=
Dma.MEMTOMEM.0.Direction=DMA_MEMORY_TO_MEMORY
Dma.MEMTOMEM.0.FIFOMode=DMA_FIFOMODE_ENABLE
Dma.MEMTOMEM.0.FIFOThreshold=DMA_FIFO_THRESHOLD_FULL
Dma.MEMTOMEM.0.Instance=DMA2_Stream0
Dma.MEMTOMEM.0.MemBurst=DMA_MBURST_SINGLE
Dma.MEMTOMEM.0.MemDataAlignment=DMA_MDATAALIGN_HALFWORD
Dma.MEMTOMEM.0.MemInc=DMA_MINC_DISABLE
Dma.MEMTOMEM.0.Mode=DMA_NORMAL
Dma.MEMTOMEM.0.PeriphBurst=DMA_PBURST_SINGLE
Dma.MEMTOMEM.0.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD
Dma.MEMTOMEM.0.PeriphInc=DMA_PINC_ENABLE
Dma.MEMTOMEM.0.Priority=DMA_PRIORITY_LOW
Dma.MEMTOMEM.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode,FIFOThreshold,MemBurst,PeriphBurst
Dma.Request0=MEMTOMEM
Dma.RequestsNb=1
FSMC.AddressSetupTime1=7
FSMC.BusTurnAroundDuration1=7
FSMC.DataSetupTime1=10
FSMC.IPParameters=DataSetupTime1,BusTurnAroundDuration1,AddressSetupTime1
File.Version=6
KeepUserPlacement=false
Mcu.CPN=STM32F407VET6
Mcu.Family=STM32F4
Mcu.IP0=DMA
Mcu.IP1=FSMC
Mcu.IP2=NVIC
Mcu.IP3=RCC
Mcu.IP4=SPI2
Mcu.IP5=SYS
Mcu.IP6=TIM2
Mcu.IP7=TIM3
Mcu.IPNb=8
Mcu.Name=STM32F407V(E-G)Tx
Mcu.Package=LQFP100
Mcu.Pin0=PC14-OSC32_IN
Mcu.Pin1=PC15-OSC32_OUT
Mcu.Pin10=PE7
Mcu.Pin11=PE8
Mcu.Pin12=PE9
Mcu.Pin13=PE10
Mcu.Pin14=PE11
Mcu.Pin15=PE12
Mcu.Pin16=PE13
Mcu.Pin17=PE14
Mcu.Pin18=PE15
Mcu.Pin19=PB12
Mcu.Pin2=PH0-OSC_IN
Mcu.Pin20=PB13
Mcu.Pin21=PB14
Mcu.Pin22=PB15
Mcu.Pin23=PD8
Mcu.Pin24=PD9
Mcu.Pin25=PD10
Mcu.Pin26=PD13
Mcu.Pin27=PD14
Mcu.Pin28=PD15
Mcu.Pin29=PA13
Mcu.Pin3=PH1-OSC_OUT
Mcu.Pin30=PA14
Mcu.Pin31=PD0
Mcu.Pin32=PD1
Mcu.Pin33=PD4
Mcu.Pin34=PD5
Mcu.Pin35=PD7
Mcu.Pin36=VP_SYS_VS_Systick
Mcu.Pin37=VP_TIM3_VS_ClockSourceINT
Mcu.Pin4=PA0-WKUP
Mcu.Pin5=PA1
Mcu.Pin6=PA5
Mcu.Pin7=PC4
Mcu.Pin8=PC5
Mcu.Pin9=PB1
Mcu.PinsNb=38
Mcu.ThirdPartyNb=0
Mcu.UserConstants=
Mcu.UserName=STM32F407VETx
MxCube.Version=6.12.0
MxDb.Version=DB.6.0.120
NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.ForceEnableDMAVector=true
NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4
NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:false
NVIC.TIM3_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true
NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
PA0-WKUP.Signal=S_TIM2_CH1_ETR
PA1.Signal=S_TIM2_CH2
PA13.Mode=Serial_Wire
PA13.Signal=SYS_JTMS-SWDIO
PA14.Mode=Serial_Wire
PA14.Signal=SYS_JTCK-SWCLK
PA5.GPIOParameters=GPIO_PuPd,GPIO_Label
PA5.GPIO_Label=Encoder_SW
PA5.GPIO_PuPd=GPIO_PULLUP
PA5.Locked=true
PA5.Signal=GPIO_Input
PB1.GPIOParameters=GPIO_Label
PB1.GPIO_Label=LCD_BL
PB1.Locked=true
PB1.Signal=GPIO_Output
PB12.GPIOParameters=GPIO_Label
PB12.GPIO_Label=T_CS
PB12.Locked=true
PB12.Signal=GPIO_Output
PB13.Locked=true
PB13.Mode=Full_Duplex_Master
PB13.Signal=SPI2_SCK
PB14.Locked=true
PB14.Mode=Full_Duplex_Master
PB14.Signal=SPI2_MISO
PB15.Locked=true
PB15.Mode=Full_Duplex_Master
PB15.Signal=SPI2_MOSI
PC14-OSC32_IN.Mode=LSE-External-Oscillator
PC14-OSC32_IN.Signal=RCC_OSC32_IN
PC15-OSC32_OUT.Mode=LSE-External-Oscillator
PC15-OSC32_OUT.Signal=RCC_OSC32_OUT
PC4.Locked=true
PC4.Signal=GPIO_Input
PC5.GPIOParameters=GPIO_Label
PC5.GPIO_Label=T_IRQ
PC5.Locked=true
PC5.Signal=GPIO_Input
PD0.Mode=16b-d1
PD0.Signal=FSMC_D2
PD1.Mode=16b-d1
PD1.Signal=FSMC_D3
PD10.Mode=16b-d1
PD10.Signal=FSMC_D15
PD13.Mode=A18_1
PD13.Signal=FSMC_A18
PD14.Mode=16b-d1
PD14.Signal=FSMC_D0
PD15.Mode=16b-d1
PD15.Signal=FSMC_D1
PD4.Mode=Lcd1
PD4.Signal=FSMC_NOE
PD5.Mode=Lcd1
PD5.Signal=FSMC_NWE
PD7.Mode=NorPsramChipSelect1_1
PD7.Signal=FSMC_NE1
PD8.Mode=16b-d1
PD8.Signal=FSMC_D13
PD9.Mode=16b-d1
PD9.Signal=FSMC_D14
PE10.Mode=16b-d1
PE10.Signal=FSMC_D7
PE11.Mode=16b-d1
PE11.Signal=FSMC_D8
PE12.Mode=16b-d1
PE12.Signal=FSMC_D9
PE13.Mode=16b-d1
PE13.Signal=FSMC_D10
PE14.Mode=16b-d1
PE14.Signal=FSMC_D11
PE15.Mode=16b-d1
PE15.Signal=FSMC_D12
PE7.Mode=16b-d1
PE7.Signal=FSMC_D4
PE8.Mode=16b-d1
PE8.Signal=FSMC_D5
PE9.Mode=16b-d1
PE9.Signal=FSMC_D6
PH0-OSC_IN.Mode=HSE-External-Oscillator
PH0-OSC_IN.Signal=RCC_OSC_IN
PH1-OSC_OUT.Mode=HSE-External-Oscillator
PH1-OSC_OUT.Signal=RCC_OSC_OUT
PinOutPanel.RotationAngle=0
ProjectManager.AskForMigrate=true
ProjectManager.BackupPrevious=false
ProjectManager.CompilerOptimize=6
ProjectManager.ComputerToolchain=false
ProjectManager.CoupleFile=false
ProjectManager.CustomerFirmwarePackage=
ProjectManager.DefaultFWLocation=true
ProjectManager.DeletePrevious=true
ProjectManager.DeviceId=STM32F407VETx
ProjectManager.FirmwarePackage=STM32Cube FW_F4 V1.28.0
ProjectManager.FreePins=false
ProjectManager.HalAssertFull=false
ProjectManager.HeapSize=0x200
ProjectManager.KeepUserCode=true
ProjectManager.LastFirmware=false
ProjectManager.LibraryCopy=1
ProjectManager.MainLocation=Core/Src
ProjectManager.NoMain=false
ProjectManager.PreviousToolchain=
ProjectManager.ProjectBuild=false
ProjectManager.ProjectFileName=STM32_LVGL_LIB_DEMO.ioc
ProjectManager.ProjectName=STM32_LVGL_LIB_DEMO
ProjectManager.ProjectStructure=
ProjectManager.RegisterCallBack=
ProjectManager.StackSize=0x400
ProjectManager.TargetToolchain=STM32CubeIDE
ProjectManager.ToolChainLocation=
ProjectManager.UAScriptAfterPath=
ProjectManager.UAScriptBeforePath=
ProjectManager.UnderRoot=true
ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_DMA_Init-DMA-false-HAL-true,4-MX_FSMC_Init-FSMC-false-HAL-true,5-MX_SPI2_Init-SPI2-false-HAL-true,6-MX_TIM2_Init-TIM2-false-HAL-true,7-MX_TIM3_Init-TIM3-false-HAL-true
RCC.48MHZClocksFreq_Value=48000000
RCC.AHBFreq_Value=168000000
RCC.APB1CLKDivider=RCC_HCLK_DIV4
RCC.APB1Freq_Value=42000000
RCC.APB1TimFreq_Value=84000000
RCC.APB2CLKDivider=RCC_HCLK_DIV2
RCC.APB2Freq_Value=84000000
RCC.APB2TimFreq_Value=168000000
RCC.CortexFreq_Value=168000000
RCC.EthernetFreq_Value=168000000
RCC.FCLKCortexFreq_Value=168000000
RCC.FamilyName=M
RCC.HCLKFreq_Value=168000000
RCC.HSE_VALUE=8000000
RCC.HSI_VALUE=16000000
RCC.I2SClocksFreq_Value=192000000
RCC.IPParameters=48MHZClocksFreq_Value,AHBFreq_Value,APB1CLKDivider,APB1Freq_Value,APB1TimFreq_Value,APB2CLKDivider,APB2Freq_Value,APB2TimFreq_Value,CortexFreq_Value,EthernetFreq_Value,FCLKCortexFreq_Value,FamilyName,HCLKFreq_Value,HSE_VALUE,HSI_VALUE,I2SClocksFreq_Value,LSI_VALUE,MCO2PinFreq_Value,PLLCLKFreq_Value,PLLM,PLLN,PLLQ,PLLQCLKFreq_Value,PLLSourceVirtual,RTCFreq_Value,RTCHSEDivFreq_Value,SYSCLKFreq_VALUE,SYSCLKSource,VCOI2SOutputFreq_Value,VCOInputFreq_Value,VCOOutputFreq_Value,VcooutputI2S
RCC.LSI_VALUE=32000
RCC.MCO2PinFreq_Value=168000000
RCC.PLLCLKFreq_Value=168000000
RCC.PLLM=4
RCC.PLLN=168
RCC.PLLQ=7
RCC.PLLQCLKFreq_Value=48000000
RCC.PLLSourceVirtual=RCC_PLLSOURCE_HSE
RCC.RTCFreq_Value=32000
RCC.RTCHSEDivFreq_Value=4000000
RCC.SYSCLKFreq_VALUE=168000000
RCC.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK
RCC.VCOI2SOutputFreq_Value=384000000
RCC.VCOInputFreq_Value=2000000
RCC.VCOOutputFreq_Value=336000000
RCC.VcooutputI2S=192000000
SH.S_TIM2_CH1_ETR.0=TIM2_CH1,Encoder_Interface
SH.S_TIM2_CH1_ETR.ConfNb=1
SH.S_TIM2_CH2.0=TIM2_CH2,Encoder_Interface
SH.S_TIM2_CH2.ConfNb=1
SPI2.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_32
SPI2.CalculateBaudRate=1.3125 MBits/s
SPI2.Direction=SPI_DIRECTION_2LINES
SPI2.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,BaudRatePrescaler
SPI2.Mode=SPI_MODE_MASTER
SPI2.VirtualType=VM_MASTER
TIM2.EncoderMode=TIM_ENCODERMODE_TI1
TIM2.IC1Filter=0
TIM2.IC1Polarity=TIM_ICPOLARITY_FALLING
TIM2.IC2Filter=0
TIM2.IC2Polarity=TIM_ICPOLARITY_FALLING
TIM2.IPParameters=Period,IC2Polarity,IC1Polarity,EncoderMode,Prescaler,IC1Filter,IC2Filter
TIM2.Period=65535
TIM2.Prescaler=0
TIM3.IPParameters=Prescaler,Period
TIM3.Period=10-1
TIM3.Prescaler=42000-1
VP_SYS_VS_Systick.Mode=SysTick
VP_SYS_VS_Systick.Signal=SYS_VS_Systick
VP_TIM3_VS_ClockSourceINT.Mode=Internal
VP_TIM3_VS_ClockSourceINT.Signal=TIM3_VS_ClockSourceINT
board=custom
isbadioc=false

 
其餘程式碼請參閱前篇文章:




2025年4月14日 星期一

[Raspberry Pi Pico2 W] SNTP & RTC || RP2350

 本影片介紹了Raspberry Pi Pico 2 W對SNTP和RTC功能的實作。相同的程式碼可以在 Pico W 上運行。

專案的UI使用LVGL函式庫(TFT display & Rotary Encoder)

LwIP SNTP application API在專案中包裝程一組Libarary提供三個 functions:

bool pico_sntp_enable(int8_t tz);
bool pico_sntp_get_system_time_timeout_ms(uint32_t ms);
bool pico_sntp_restart_timeout_ms(int8_t tz, uint32_t ms);

Pico 2與Pico 使用RTC的Hardware 不一樣,新的aon_timer_xxx這組的api適用於RP2040與RP2350,詳細說明可參閱RP2040與RP2350 datasheet。

其他詳細內容與成果展示影片,附於文末。


成果影片:



程式碼:

sntp_lib:

  • CMakeLists.txt

add_library(sntp_lib INTERFACE)
target_sources(sntp_lib INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/sntp_lib.c
)

target_include_directories(sntp_lib INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
)

target_link_libraries(sntp_lib INTERFACE
        pico_aon_timer
        pico_lwip_sntp
)

  • sntp_lib.c

#include "stdio.h"
#include "pico/stdlib.h"
#include "sntp_lib.h"
#include "lwip/apps/sntp.h"
#include "pico/aon_timer.h"

//SNTP
static bool ntp_datetime_ok=false;
static int8_t sntp_timezone;
void sntp_set_system_time(u32_t sec)
{
    char buf[32];
    struct tm current_time_val;

    time_t current_time = (sec+sntp_timezone*60*60);
    struct tm* p= gmtime(&current_time);

    aon_timer_set_time_calendar(p);

    ntp_datetime_ok=true;
}

bool pico_sntp_enable(int8_t tz) {
    sntp_timezone = tz;
    sntp_setoperatingmode(SNTP_OPMODE_POLL);
    sntp_setservername(0, "pool.ntp.org");      //SNTP_SERVER_DNS (lwipop.h)
    //ip_addr_t ntpserver;
    //ipaddr_aton("118.163.81.63", &ntpserver);
    //sntp_setserver(0, &ntpserver);
    sntp_init();
    if (!sntp_enabled()) {
        printf("sntp not enable\n");
        return false;
    }
    return true;
}

bool pico_sntp_get_system_time_timeout_ms(uint32_t ms) {
    absolute_time_t curTime = get_absolute_time();
    bool ret = false;
    while (absolute_time_diff_us(curTime, get_absolute_time()) < ms*1000) {
        if (ntp_datetime_ok) {
            ret = true;
            break;
        } else {
            sleep_ms(100);
        }
    }
    return ret;
}

bool pico_sntp_restart_timeout_ms(int8_t tz, uint32_t ms) {
    sntp_stop();
    if (pico_sntp_enable(tz)) {
        return pico_sntp_get_system_time_timeout_ms(ms);
    } else {
        return false;
    }
}

  • sntp_lib.h

#ifndef __SNTP_LIB__
#define __SNTP_LIB__

bool pico_sntp_enable(int8_t tz);
bool pico_sntp_get_system_time_timeout_ms(uint32_t ms);
bool pico_sntp_restart_timeout_ms(int8_t tz, uint32_t ms);

#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.1)
set(toolchainVersion 13_3_Rel1)
set(picotoolVersion 2.1.1)
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(pico2w_clock_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(pico2w_clock_ht pico2w_clock_ht.c display.c)

pico_set_program_name(pico2w_clock_ht "pico2w_clock_ht")
pico_set_program_version(pico2w_clock_ht "0.1")

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

# Add the standard library to the build
target_link_libraries(pico2w_clock_ht
        pico_stdlib
        pico_aon_timer
        hardware_pio
        hardware_timer
        )

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

# Add any user requested libraries
target_link_libraries(pico2w_clock_ht 
        pico_cyw43_arch_lwip_threadsafe_background
        )



target_compile_definitions(pico2w_clock_ht PRIVATE
        WIFI_SSID="$ENV{WIFI_SSID}"
        WIFI_PASSWD="$ENV{WIFI_PASSWD}"
)


add_subdirectory(pico_lvgl)
add_subdirectory(sntp_lib)
add_subdirectory(aht10_lib)
target_link_libraries(pico2w_clock_ht
        pico_lvgl
        sntp_lib
        aht10_lib
        )

pico_add_extra_outputs(pico2w_clock_ht)

  • pico2w_clock_ht.c

#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "hardware/pio.h"
#include "pico/aon_timer.h"

#include "pico_lvgl.h"
#include "sntp_lib.h"
#include "display.h"
#include "aht10.h"

#define TFT_PIO             pio0
#define TFT_SM              2
#define TFT_SDI_GPIO        9
#define TFT_CSX_DCX_SCK_GPIO 6 // CSX=8, DCX(A0)=7, SCK=6, SIDE_SET

#define TIME_ZONE           8

#define AHT10_I2C_PORT      i2c0
#define AHT10_SDA           16
#define AHT10_SCL           17


bool timer_callback(repeating_timer_t* rt) { 
    display_clock_meter();
    return true;
}
bool aht10_callback(repeating_timer_t* rt) {
    display_ht();
    return true;
}

int main()
{
    stdio_init_all();

    gpio_init(ALARM_BUZZER_PIN);
    gpio_set_dir(ALARM_BUZZER_PIN, true);
    gpio_put(ALARM_BUZZER_PIN, false);

    pico_lvgl_tft_init(TFT_PIO, TFT_SM, TFT_SDI_GPIO, TFT_CSX_DCX_SCK_GPIO);
    tft_set_orientation(TFT_ORIENTATION_LANDSCAPE_MIRROR);
    pico_lvgl_display_init(5);
    pico_lvgl_encoder_init(true);
    
    struct tm dt;
    dt.tm_year = 2025-1900;
    dt.tm_mon = 3;
    dt.tm_mday = 1;
    dt.tm_hour = 0;
    dt.tm_min = 0;
    dt.tm_sec = 0;
    if (!aon_timer_start_calendar(&dt)) {
        show_msgbox("Error", "Start RTC error");
        return 1;
    }
    char msgbuff[100];
    // Initialise the Wi-Fi chip
    if (cyw43_arch_init()) {
        printf("Wi-Fi init failed\n");
        show_msgbox( "Error", "Wi-Fi init failed");
        return -1;
    }

    // Enable wifi station
    cyw43_arch_enable_sta_mode();

    printf("Connecting to Wi-Fi...\n");
    show_msgbox("Message", "Connecting to Wi-Fi...");
    if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWD, CYW43_AUTH_WPA2_AES_PSK, 30000)) {
        printf("failed to connect.\n");
        close_msgbox();
        show_msgbox("Error", "failed to connect.");
        return 1;
    } else {
        printf("Connected.\n");       
        // Read the ip address in a human readable way
        uint8_t *ip_address = (uint8_t*)&(cyw43_state.netif[0].ip_addr.addr);
        
        sprintf(msgbuff, "IP address %d.%d.%d.%d", ip_address[0], ip_address[1], ip_address[2], ip_address[3]);
        printf("%s\n",msgbuff);
        close_msgbox();
        show_msgbox( "Message", msgbuff);
        sleep_ms(2000);
        close_msgbox();        
    }
    // enable SNTP
    if (!pico_sntp_enable(TIME_ZONE)) {
        show_msgbox("Error", "Enable SNTP Error");
        return -1;
    }
    show_msgbox("Message", "Getting NTP Time...");
    if (pico_sntp_get_system_time_timeout_ms(20000)) {
        struct tm dt;
        aon_timer_get_time_calendar(&dt);
        sprintf(msgbuff, "Date:%04d-%02d-%02d\nTime:%02d:%02d:%02d\n", dt.tm_year+1900, dt.tm_mon+1, dt.tm_mday,
                    dt.tm_hour, dt.tm_min, dt.tm_sec);
        close_msgbox();
        show_msgbox("Info", msgbuff);
    } else {
        printf("sntp time out\n");
        close_msgbox();
        show_msgbox("Info", "Restarting SNTP");
        if (!pico_sntp_restart_timeout_ms(TIME_ZONE, 20000)) {
            close_msgbox();
            show_msgbox("Info", "Get NTP Time timeout");
            return -1;
        } else {
            close_msgbox();
            struct tm dt;
            aon_timer_get_time_calendar(&dt);
            sprintf(msgbuff, "Date:%04d-%02d-%02d\nTime:%02d:%02d:%02d\n", dt.tm_year+1900, dt.tm_mon+1, dt.tm_mday,
                        dt.tm_hour, dt.tm_min, dt.tm_sec);
            show_msgbox("Info", msgbuff);
        } 
    }
    close_msgbox();

    draw_screens();  // draw analog clock screen and Alarm setting screen
    lv_disp_load_scr(clock_scr); // load clock screen as default
    repeating_timer_t rt;
    add_repeating_timer_ms(-1000, timer_callback, NULL, &rt);

    //init AHT10 sensor and read Temperature/Humidity every 5 seconds
    aht10_init(AHT10_I2C_PORT, AHT10_SDA,AHT10_SCL);
    repeating_timer_t rt_ht;
    add_repeating_timer_ms(-5000, aht10_callback, NULL, &rt_ht);

    while (true) {
        lv_timer_handler();
        sleep_ms(10);
    }
}


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
)