本文章介紹使用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:
[Raspberry Pi Pico (c-sdk)] LVGL Graphics Library & Pico PIO TFT display driver(Serial or Parallel)
- CMakeLists.txt:
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.1.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
沒有留言:
張貼留言