本文章介紹使用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
沒有留言:
張貼留言