prettyprint

2025年1月22日 星期三

[Raspberry Pi Pico] MODBUS EP. 2. Modbus RTU

 本文章介紹使用nanoMODBUS library 在Raspberry Pi Pico上實做MODBUS RTU。以一組MODBUS Master使用LVGL介面,控制兩個Modbus Slaves: 對Slave 1 以write single coil 遙控門的開/關與上鎖/解鎖,以Read Coils 讀取磁簧開關狀態,判斷門的開啟狀態。對Slave 2以write multiple registers 控制WS2812燈條的顏色與點亮LED的個數。

實驗架構如下圖:


MODBUS RTU APU 如下圖所示:


其他Function code 與data內容請參閱以下modbus文件連結:https://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf


成果影片:



程式碼:

本實驗專案使用nanoMODBUS(modbus protocol) library網址如下:

https://github.com/debevv/nanoMODBUS

Slave 1: 

  • 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 CACHE STRING "Board type")

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

project(pico_modbus_rtu 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_modbus_rtu pico_modbus_rtu.c 
                                nanomodbus/nanomodbus.c
                                )

pico_set_program_name(pico_modbus_rtu "pico_modbus_rtu")
pico_set_program_version(pico_modbus_rtu "0.1")

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

# Add the standard library to the build
target_link_libraries(pico_modbus_rtu
        pico_stdlib
        hardware_pwm)

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

# Add any user requested libraries
target_link_libraries(pico_modbus_rtu 
        hardware_i2c
        )

pico_add_extra_outputs(pico_modbus_rtu)


  
  • pico_modbus_rtu.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "hardware/uart.h"
#include "nanomodbus/nanomodbus.h"
#include "hardware/pwm.h"

#define PWM_DOOR_PIN            14
#define PWM_LOCK_PIN            15
#define REED_SWITCH_PIN         16

#define BAUD_RATE               115200
#define DATA_BITS               8
#define STOP_BITS               1
#define PARITY                  UART_PARITY_EVEN

#define DOOR_OPEN_CLOSE_ADDRESS    1
#define DOOR_LOCK_ADDRESS          2   
#define REED_SWITCH_ADDRESS        3

#define UART_ID uart1
// Use pins 4 and 5 for UART1
#define UART_TX_PIN             4
#define UART_RX_PIN             5

// The data model of this sever will support coils addresses 0 to 3
#define COILS_ADDR_MAX      4
#define RTU_SERVER_ADDRESS  1

uint pwm_ch_door, pwm_slice_door;
uint pwm_ch_locker, pwm_slice_locker;
int32_t servo_door_ang, servo_locker_ang;

void setServoAngle(int ang, uint pwm_ch, uint slice);


nmbs_t nmbs;
// A single nmbs_bitfield variable can keep 2000 coils
bool terminate = false;
nmbs_bitfield server_coils = {0};

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

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

nmbs_error handle_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);
    if (address + quantity > COILS_ADDR_MAX + 1)
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;

    // Read our coils values into coils_out
    for (int i = 0; i < quantity; i++) {
        bool value = nmbs_bitfield_read(server_coils, address + i);
        nmbs_bitfield_write(coils_out, i, value);
    }

    return NMBS_ERROR_NONE;
}


nmbs_error handle_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 )
    if (address != DOOR_OPEN_CLOSE_ADDRESS &&  address != DOOR_LOCK_ADDRESS)
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;

    // Write coils values to our server_coils
 
        nmbs_bitfield_write(server_coils, address, value);
     // set servo motor
    if (address == DOOR_OPEN_CLOSE_ADDRESS) {
        setServoAngle(-1*value*90, pwm_ch_door, pwm_slice_door);
    }
    if (address == DOOR_LOCK_ADDRESS) {
        setServoAngle(value*90, pwm_ch_locker, pwm_slice_locker);
    }
    
    //gpio_put(address+2, value);  // address 0 map to gpio 2
    return NMBS_ERROR_NONE;
}


int32_t nmbs_uart_read(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg){
    uint64_t start_time = time_us_64();
    int32_t bytes_read = 0;
    uint64_t timeout_us = (uint64_t) byte_timeout_ms * 1000;
    while (time_us_64() - start_time < timeout_us && bytes_read < count) {
        if (uart_is_readable(UART_ID)) {
            buf[bytes_read++] = uart_getc(UART_ID);
            start_time = time_us_64();    // Reset start time after a successful read
        }
    }

    return bytes_read;    
}
int32_t nmbs_uart_write(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
    uart_write_blocking(UART_ID, buf, count);

    return count;
}

//servo motor library
void setServoAngle(int ang, uint pwm_ch, uint slice) {
    uint16_t top_count = (uint16_t)(ang/0.09 + 1500);  // angle 0: 1.5 ms duty
    pwm_set_chan_level(slice, pwm_ch, top_count);
}

void servo_init(uint pin, uint *pwm_ch, uint* slice) {
    // set Servo pwm
    gpio_set_function(pin, GPIO_FUNC_PWM);
    *slice = pwm_gpio_to_slice_num(pin);
    *pwm_ch = pwm_gpio_to_channel(pin);
    pwm_config c = pwm_get_default_config();
    pwm_config_set_clkdiv(&c, 125);  // 20ms period, steps 20000, clkdiv = 125 
    pwm_config_set_wrap(&c, 20000);
    pwm_config_set_phase_correct(&c, false);
    pwm_init(*slice, &c, true);    
}

void reed_switch_sensor_callback(uint gpio, uint32_t events) {
    if (gpio == REED_SWITCH_PIN) {
        if (events == GPIO_IRQ_EDGE_RISE) {
            nmbs_bitfield_write(server_coils, REED_SWITCH_ADDRESS, 1);
        }
        if (events == GPIO_IRQ_EDGE_FALL) {
            nmbs_bitfield_write(server_coils, REED_SWITCH_ADDRESS, 0);
        }
    }
}

int main()
{
    stdio_init_all();

    gpio_set_irq_enabled_with_callback(REED_SWITCH_PIN, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, 
            true, &reed_switch_sensor_callback);

    //servo motor init 
    servo_locker_ang = 0;
    servo_door_ang = 0;
    servo_init(PWM_DOOR_PIN, &pwm_ch_door, &pwm_slice_door);
    servo_init(PWM_LOCK_PIN, &pwm_ch_locker, &pwm_slice_locker);

    setServoAngle(servo_door_ang, pwm_ch_door, pwm_slice_door);
    setServoAngle(servo_locker_ang, pwm_ch_locker, pwm_slice_locker);


    // Set up our UART
    uart_init(UART_ID, BAUD_RATE);
    gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
    gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);
    int __unused actual = uart_set_baudrate(UART_ID, BAUD_RATE);
    uart_set_hw_flow(UART_ID, false, false);
    uart_set_format(UART_ID, DATA_BITS, STOP_BITS, PARITY);
    uart_set_fifo_enabled(UART_ID, false);
  
    
    // nmbs_uart_read() and nmbs_uart_write() are implemented by the user 
    nmbs_platform_conf platform_conf;
    nmbs_platform_conf_create(&platform_conf);
    platform_conf.transport = NMBS_TRANSPORT_RTU;
    platform_conf.read = nmbs_uart_read;
    platform_conf.write = nmbs_uart_write;
    
    nmbs_callbacks callbacks;
    nmbs_callbacks_create(&callbacks);
    callbacks.read_coils = handle_read_coils;
    callbacks.write_single_coil = handle_write_single_coil;
    
    nmbs_error err = nmbs_server_create(&nmbs, RTU_SERVER_ADDRESS, &platform_conf, &callbacks);
    if (err != NMBS_ERROR_NONE) {
        fprintf(stderr, "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, 1000);

    while (true) {

        nmbs_server_poll(&nmbs);

        tight_loop_contents();
    }
}

  
Slave 2: 
  • CMakeLists.txt(main)
# == 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()
# ====================================================================================
# == 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()
# ====================================================================================
# 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_modbus_rtu_slave2 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_modbus_rtu_slave2 pico_modbus_rtu_slave2.c 
                                nanomodbus/nanomodbus.c
                                )

pico_set_program_name(pico_modbus_rtu_slave2 "pico_modbus_rtu_slave2")
pico_set_program_version(pico_modbus_rtu_slave2 "0.1")

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

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

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

# Add any user requested libraries
target_link_libraries(pico_modbus_rtu_slave2 
        hardware_i2c
        )
add_subdirectory (ws2812)
target_link_libraries(pico_modbus_rtu_slave2
        ws2812
        )
pico_add_extra_outputs(pico_modbus_rtu_slave2)


 
  • pico_modbus_rtu_slave2.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "hardware/uart.h"
#include "nanomodbus/nanomodbus.h"
#include "hardware/pio.h"
#include "ws2812.h"

#define UART_ID uart1
#define UART_TX_PIN 4
#define UART_RX_PIN 5

#define BAUD_RATE 115200
#define DATA_BITS 8
#define STOP_BITS 1
#define PARITY    UART_PARITY_EVEN

// The data model of this sever will support registers addresses from 0 to 1
#define REGS_ADDR_MAX 2  // register 0 for color, register 1 for the number of leds

#define RTU_SERVER_ADDRESS 2

#define WS2812_PIO          pio0  //WS2812 LED PIO parameters
#define WS2812_SM           0
#define WS2812_PIN          16
#define TOTAL_LEDS_NUM      10

nmbs_t nmbs;
bool terminate = false;
uint16_t server_registers[REGS_ADDR_MAX] = {0};


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

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

nmbs_error handler_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;


    return NMBS_ERROR_NONE;
}

nmbs_error handle_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];
  
    // ========== ws2812, color, led nums
    int pixels;
    uint16_t le_color = server_registers[0] >> 8 | (server_registers[0] & 0x00ff) <<8;
    uint32_t color = urgb_u32(le_color >> 11, (le_color&0x3e0) >> 5,le_color & 0x1f);
    for (pixels =0; pixels < server_registers[1] && pixels < TOTAL_LEDS_NUM; pixels++) {
        ws2812_put_grb_pixel(WS2812_PIO, WS2812_SM,  color);
    }
    for (int i = pixels; i < TOTAL_LEDS_NUM; i++) {
        ws2812_put_grb_pixel(WS2812_PIO, WS2812_SM,  0);
    }

    return NMBS_ERROR_NONE;
}

int32_t nmbs_uart_read(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg){
    uint64_t start_time = time_us_64();
    int32_t bytes_read = 0;
    uint64_t timeout_us = (uint64_t) byte_timeout_ms * 1000;
    while (time_us_64() - start_time < timeout_us && bytes_read < count) {
        if (uart_is_readable(UART_ID)) {
            buf[bytes_read++] = uart_getc(UART_ID);
            start_time = time_us_64();    // Reset start time after a successful read
        }
    }

    return bytes_read;    
}
int32_t nmbs_uart_write(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
    uart_write_blocking(UART_ID, buf, count);

    return count;
}


int main()
{
    stdio_init_all();
    
    ws2812_init(WS2812_PIO, WS2812_SM, WS2812_PIN);

    // Set up our UART
    uart_init(UART_ID, BAUD_RATE);
    gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
    gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);
    int __unused actual = uart_set_baudrate(UART_ID, BAUD_RATE);
    uart_set_hw_flow(UART_ID, false, false);
    uart_set_format(UART_ID, DATA_BITS, STOP_BITS, PARITY);
    uart_set_fifo_enabled(UART_ID, false);
  
    // nmbs_uart_read() and nmbs_uart_write() are implemented by the user 
    nmbs_platform_conf platform_conf;
    nmbs_platform_conf_create(&platform_conf);
    platform_conf.transport = NMBS_TRANSPORT_RTU;
    platform_conf.read = nmbs_uart_read;
    platform_conf.write = nmbs_uart_write;
    
    nmbs_callbacks callbacks;
    nmbs_callbacks_create(&callbacks);

    callbacks.read_holding_registers = handler_read_holding_registers;
    callbacks.write_multiple_registers = handle_write_multiple_registers;
    
    nmbs_error err = nmbs_server_create(&nmbs, RTU_SERVER_ADDRESS, &platform_conf, &callbacks);
    if (err != NMBS_ERROR_NONE) {
        fprintf(stderr, "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, 1000);

    while (true) {
        nmbs_server_poll(&nmbs);

        tight_loop_contents();
    }
}

 
WS2812 Library:
  • CMakeLists.txt
add_library(ws2812 INTERFACE)
pico_generate_pio_header(ws2812 ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio)
target_sources(ws2812 INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/ws2812.c
)

target_include_directories(ws2812 INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
)

target_link_libraries(ws2812 INTERFACE
        hardware_pio
)

 
  • ws2812.c
#include "ws2812.h"

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


#define green 0x1f0000
#define red 0x001f00
#define blue 0x00001f

static void ws2812_program_init(PIO pio, uint sm, uint pin, float freq) {
    
    uint offset = pio_add_program(pio, &ws2812_program);
    pio_sm_config c = ws2812_program_get_default_config(offset);

    pio_gpio_init(pio, pin);
    pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);

    sm_config_set_sideset_pins(&c, pin);
    sm_config_set_out_shift(&c, false, true, 24);
    sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);

    int cycles_per_bit = 6;
    float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
    sm_config_set_clkdiv(&c, div);

    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

void ws2812_put_grb_pixel(PIO pio, uint sm, uint32_t pixel_grb) {
    pio_sm_put_blocking(pio, sm, pixel_grb << 8u);
}

uint32_t urgb_u32(uint8_t r, uint8_t g, uint8_t b) {
    return
            ((uint32_t) (r) << 8) |
            ((uint32_t) (g) << 16) |
            (uint32_t) (b);
}

void ws2812_init(PIO pio, uint sm, uint ws2812_pin) {

    ws2812_program_init(pio, sm,  ws2812_pin, 800000);
}



 
  • ws1812.h

#ifndef _WS2812_H_
#define _WS2812_H_
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "ws2812.pio.h"
#include "hardware/clocks.h"



void ws2812_init(PIO pio, uint sm, uint ws2812_pin);
void ws2812_put_grb_pixel(PIO pio, uint sm, uint32_t pixel_grb);
uint32_t urgb_u32(uint8_t r, uint8_t g, uint8_t b);

#endif 
 
  • ws2812.pio
.program ws2812
.side_set 1

.wrap_target
bitloop:
    out x, 1       side 0 [1]
    jmp !x do_zero side 1 [1]
do_one:
    jmp  bitloop   side 1 [1]
do_zero:
    nop            side 0 [1]
.wrap


 

modbus master:
pico_lvgl library 請參閱以前文章:
# == 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()
# ====================================================================================
# == 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()
# ====================================================================================
# 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_modbus_rtu_master 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_modbus_rtu_master pico_modbus_rtu_master.c 
                                nanomodbus/nanomodbus.c
                                modbus_master_ui.c
                                )

pico_set_program_name(pico_modbus_rtu_master "pico_modbus_rtu")
pico_set_program_version(pico_modbus_rtu_master "0.1")

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

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

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

# Add any user requested libraries
target_link_libraries(pico_modbus_rtu_master 
        hardware_i2c
        hardware_pio
        )
add_subdirectory(pico_lvgl)
target_link_libraries(pico_modbus_rtu_master
        pico_lvgl
)

pico_add_extra_outputs(pico_modbus_rtu_master)


 
  • modbus_master_ui.c
#include "stdio.h"
#include "pico/stdlib.h"
#include "lvgl/lvgl.h"
#include "pico_modbus_rtu_master.h"
#include "nanomodbus/nanomodbus.h"
#include "images/door_open.h"
#include "images/door_close.h"
#include "images/lock.h"
#include "images/unlock.h"
#include "images/open.h"
#include "images/close.h"

#define TOTAL_LED_NUM   10

extern nmbs_t nmbs;
LV_IMG_DECLARE(door_open);
LV_IMG_DECLARE(door_close);
LV_IMG_DECLARE(door_lock);
LV_IMG_DECLARE(door_unlock);
LV_IMG_DECLARE(open);
LV_IMG_DECLARE(close);

lv_obj_t *door, *lock_obj;
lv_obj_t* slider_label;

lv_color_t led_color;
int32_t led_num;

static void slider_event_cb(lv_event_t *e) {
    lv_obj_t *obj = lv_event_get_target(e);
    lv_event_code_t code = lv_event_get_code(e);
    uint16_t registers[2];

    if (code == LV_EVENT_VALUE_CHANGED) {
        led_num = lv_slider_get_value(obj)/TOTAL_LED_NUM;
        lv_label_set_text_fmt(slider_label, "%d", led_num);
        // send to modbus server
        registers[0] = led_color.full;
        registers[1] = led_num;
        nmbs_set_destination_rtu_address(&nmbs, RTU_WS2812_SERVER_ADDRESS);
        nmbs_error err = nmbs_write_multiple_registers(&nmbs, 0, 2, registers);
    }
}

static void color_picker_cb(lv_event_t *e) {
    lv_obj_t *color_picker = lv_event_get_target(e);
    lv_event_code_t code = lv_event_get_code(e);
    if (code == LV_EVENT_VALUE_CHANGED) {
        uint16_t registers[2];
        led_color = lv_colorwheel_get_rgb(color_picker);
        // send to modbus server
        registers[0] = led_color.full;
        registers[1] = led_num;
        nmbs_set_destination_rtu_address(&nmbs, RTU_WS2812_SERVER_ADDRESS);
        nmbs_error err = nmbs_write_multiple_registers(&nmbs, 0, 2, registers);
    }
}

static void btn_lock_event_handler(lv_event_t * e)
{ 
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * obj = lv_event_get_target(e);
    lv_obj_t *label = lv_obj_get_child(obj, 0);
    lv_obj_t* img = lv_obj_get_child(obj, 1);
    uint8_t address;
    nmbs_bitfield coils;
   int i;
    if(code == LV_EVENT_CLICKED) {
        nmbs_set_destination_rtu_address(&nmbs, RTU_DOOR_SERVER_ADDRESS);
        nmbs_error err = nmbs_read_coils(&nmbs, 0, 4, coils);
        if (nmbs_bitfield_read(coils,DOOR_LOCK_ADDRESS) == 1) {
            err = nmbs_write_single_coil(&nmbs, DOOR_LOCK_ADDRESS, 0);
            lv_label_set_text(label, "Lock");
            lv_img_set_src(img, &lock);
        } else {
            err = nmbs_write_single_coil(&nmbs, DOOR_LOCK_ADDRESS, 1);
            lv_label_set_text(label, "Unlock");
            lv_img_set_src(img, &unlock);
        }
           //lv_timer_handler();
    }
}

static void btn_door_event_handler(lv_event_t * e)
{ 
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * obj = lv_event_get_target(e);
    lv_obj_t *label = lv_obj_get_child(obj, 0);
    lv_obj_t *img = lv_obj_get_child(obj, 1);
    uint8_t address;
    nmbs_bitfield coils;
   int i;
    if(code == LV_EVENT_CLICKED) {
        nmbs_set_destination_rtu_address(&nmbs, RTU_DOOR_SERVER_ADDRESS);
        nmbs_error err = nmbs_read_coils(&nmbs, 0, 4, coils);
        if (nmbs_bitfield_read(coils,REED_SWITCH_ADDRESS) == 1) {
            err = nmbs_write_single_coil(&nmbs, DOOR_OPEN_CLOSE_ADDRESS, 0);
            lv_img_set_src(door, &door_close);
            lv_label_set_text(label, "Open");
            lv_img_set_src(img, &open);
        } else {
            err = nmbs_write_single_coil(&nmbs, DOOR_OPEN_CLOSE_ADDRESS, 1);
            lv_img_set_src(door, &door_open);
            lv_label_set_text(label, "Close");
            lv_img_set_src(img, &close);
        }
        //lv_timer_handler();
    }
}

void draw_master_ui() {
    
    lv_obj_t*  label = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(label, "Slave\nAddr %d", RTU_DOOR_SERVER_ADDRESS);
    lv_obj_align(label, LV_ALIGN_TOP_LEFT, 5,5);

    lv_obj_t* btn = lv_btn_create(lv_scr_act());
    lv_obj_align(btn, LV_ALIGN_TOP_MID, 70, 20);
    label = lv_label_create(btn);
    lv_obj_t *img = lv_img_create(btn);
    lv_label_set_text(label, "Open");
    lv_img_set_src(img, &open);
    lv_obj_align(img, LV_ALIGN_LEFT_MID, 0,0);
    lv_obj_align_to(label, img, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
    lv_obj_set_size(btn, 120, 60);
    lv_obj_set_style_bg_color(btn, lv_color_hex(0x00cf00), 0);
    lv_obj_add_event_cb(btn, btn_door_event_handler, LV_EVENT_ALL, NULL);
    

    btn = lv_btn_create(lv_scr_act());
    lv_obj_align(btn, LV_ALIGN_TOP_MID, 70, 90);
    label = lv_label_create(btn);
    img = lv_img_create(btn);
    lv_label_set_text(label, "Lock");
    lv_img_set_src(img, &lock);
    lv_obj_align(img, LV_ALIGN_LEFT_MID, 0,0);
    lv_obj_align_to(label, img, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
    lv_obj_set_size(btn, 120, 60);
    lv_obj_set_style_bg_color(btn, lv_color_hex(0x00cf00), 0);
    lv_obj_add_event_cb(btn, btn_lock_event_handler, LV_EVENT_ALL, NULL);

    door = lv_img_create(lv_scr_act());
    lv_img_set_src(door, &door_close);
    lv_obj_align(door, LV_ALIGN_TOP_MID, -90, 10);
    lv_obj_set_size(door, 160, 160);

// ============== line 
    static lv_point_t line_points[] = { {5, 0}, {470,0} };
    /*Create style*/
    static lv_style_t style_line;
    lv_style_init(&style_line);
    lv_style_set_line_width(&style_line, 8);
    lv_style_set_line_color(&style_line, lv_palette_main(LV_PALETTE_BLUE));
    lv_style_set_line_rounded(&style_line, true);
    lv_style_set_shadow_width(&style_line,1);

    /*Create a line and apply the new style*/
    lv_obj_t * line1;
    line1 = lv_line_create(lv_scr_act());
    lv_line_set_points(line1, line_points, 2);     /*Set the points*/
    lv_obj_add_style(line1, &style_line, 0);
    lv_obj_align(line1, LV_ALIGN_TOP_LEFT, 0, 170);
    // ===========
    label = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(label, "Slave\nAddr %d", RTU_WS2812_SERVER_ADDRESS);
    lv_obj_align_to(label, line1, LV_ALIGN_OUT_BOTTOM_LEFT, 5,5);

    lv_obj_t *color_wheel = lv_colorwheel_create(lv_scr_act(), true);
    lv_obj_set_size(color_wheel, 120, 120);
    lv_obj_align(color_wheel, LV_ALIGN_BOTTOM_MID, -90, -20);
    led_color = lv_color_make(0xff, 0,0);
    lv_colorwheel_set_rgb(color_wheel, led_color);
    //lv_colorwheel_set_mode(color_wheel, LV_COLORWHEEL_MODE_VALUE);
    lv_colorwheel_set_mode_fixed(color_wheel, true);
    lv_obj_add_event_cb(color_wheel, color_picker_cb, LV_EVENT_VALUE_CHANGED, NULL);

    lv_obj_t * slider = lv_slider_create(lv_scr_act());
    lv_obj_align(slider, LV_ALIGN_BOTTOM_MID, 100, -60);
    lv_obj_set_size(slider, 180, 20);
    lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL);

    /*Create a label below the slider*/
    slider_label = lv_label_create(lv_scr_act());
    lv_label_set_text(slider_label, "0");
    lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
    


    
}
 
  • pico_modbus_rtu_master.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "hardware/uart.h"
#include "nanomodbus/nanomodbus.h"
#include "hardware/pio.h"
#include "pico_lvgl.h"
#include "pico_modbus_rtu_master.h"

nmbs_t nmbs;
bool terminate = false;
nmbs_bitfield server_coils = {0};
uint16_t server_registers[REGS_ADDR_MAX] = {0};

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

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

void draw_master_ui();

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

nmbs_error handle_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);
    if (address + quantity > COILS_ADDR_MAX + 1)
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;

    // Read our coils values into coils_out
    for (int i = 0; i < quantity; i++) {
        bool value = nmbs_bitfield_read(server_coils, address + i);
        nmbs_bitfield_write(coils_out, i, value);
    }

    return NMBS_ERROR_NONE;
}

nmbs_error handle_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;

    // Read our coils values into coils_out
    for (int i = 0; i < quantity; i++) {
        bool value = nmbs_bitfield_read(server_coils, address + i);
        nmbs_bitfield_write(coils_out, i, value);
    }

    return NMBS_ERROR_NONE;
}

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

    if (address != DOOR_OPEN_CLOSE_ADDRESS &&  address != DOOR_OPEN_CLOSE_ADDRESS)
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;

    // Write coils values to our server_coils
 
        nmbs_bitfield_write(server_coils, address, value);

    return NMBS_ERROR_NONE;
}

nmbs_error handle_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_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;

    return NMBS_ERROR_NONE;
}

nmbs_error handle_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

    return NMBS_ERROR_NONE;
}

nmbs_error handle_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;
}

int32_t nmbs_uart_read(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg){
    uint64_t start_time = time_us_64();
    int32_t bytes_read = 0;
    uint64_t timeout_us = (uint64_t) byte_timeout_ms * 1000;
    while (time_us_64() - start_time < timeout_us && bytes_read < count) {
        if (uart_is_readable(UART_ID)) {
            buf[bytes_read++] = uart_getc(UART_ID);
            start_time = time_us_64();    // Reset start time after a successful read
        }
    }

    return bytes_read;    
}
int32_t nmbs_uart_write(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
    uart_write_blocking(UART_ID, buf, count);

    return count;
}

nmbs_error err;
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);
    pico_lvgl_xpt2046_init();

    draw_master_ui();
    lv_timer_handler();


    // Set up our UART
    uart_init(UART_ID, BAUD_RATE);

    gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
    gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);

    int __unused actual = uart_set_baudrate(UART_ID, BAUD_RATE);
    uart_set_hw_flow(UART_ID, false, false);
    uart_set_format(UART_ID, DATA_BITS, STOP_BITS, PARITY);
    uart_set_fifo_enabled(UART_ID, false);

    // nmbs_uart_read() and nmbs_uart_write() are implemented by the user 
    nmbs_platform_conf platform_conf;
    nmbs_platform_conf_create(&platform_conf);
    platform_conf.transport = NMBS_TRANSPORT_RTU;
    platform_conf.read = nmbs_uart_read;
    platform_conf.write = nmbs_uart_write;
 
    err = nmbs_client_create(&nmbs, &platform_conf);
    if (err != NMBS_ERROR_NONE) {
        fprintf(stderr, "Error creating modbus client\n");
        return 1;
    }

    // Set only the polling timeout. Byte timeout will be handled by the TCP connection
    nmbs_set_read_timeout(&nmbs, 1000);

    uint16_t registers[2] = {0,10};
    nmbs_set_destination_rtu_address(&nmbs, RTU_WS2812_SERVER_ADDRESS);
    nmbs_error err = nmbs_write_multiple_registers(&nmbs, 0, 2, registers);

    nmbs_set_destination_rtu_address(&nmbs, RTU_DOOR_SERVER_ADDRESS);
    err = nmbs_write_single_coil(&nmbs, DOOR_LOCK_ADDRESS, false);
    err = nmbs_write_single_coil(&nmbs, DOOR_OPEN_CLOSE_ADDRESS, false);
    nmbs_bitfield coils;

    while (true) {
        lv_timer_handler();

        sleep_ms(5);
    }
}

 
  • pico_modbus_rtu_master.h
#ifndef __PICO_MODBUS_RTU_MASETER_
#define __PICO_MODBUS_RTU_MASETER_

// UART defines
#define UART_ID uart0
#define UART_TX_PIN 0
#define UART_RX_PIN 1

#define BAUD_RATE       115200
#define DATA_BITS       8
#define STOP_BITS       1
#define PARITY          UART_PARITY_EVEN

#define DOOR_OPEN_CLOSE_ADDRESS     1
#define DOOR_LOCK_ADDRESS           2
#define REED_SWITCH_ADDRESS         3

// The data model of this sever will support coils addresses 0 to 100 and registers addresses from 0 to 32
#define COILS_ADDR_MAX 4
#define REGS_ADDR_MAX 2

#define RTU_DOOR_SERVER_ADDRESS 1
#define RTU_WS2812_SERVER_ADDRESS 2

#endif  











沒有留言:

張貼留言