本文章介紹如何使用 nanoMODBUS 函式庫在 Raspberry Pi Pico W上實作 MODBUS TCP/RTU Gateway。使用 MODBUS TCP Master透過 Node-RED UI 控制兩個 Modbus RTU Slaves。
架構如下圖所示:
MODBUS TCP/RTU Gateway收到modbus tcp master(client) request 根據UintID轉送到相對應的MODUBS RTU Slave(msater),Response依相反順序進行。
對應的code如下:
成果展示:
程式碼:
- CMakeLists.txt
# Generated Cmake Pico project file cmake_minimum_required(VERSION 3.13) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Initialise pico_sdk from installed location # (note this can come from environment, CMake cache etc) # == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == if(WIN32) set(USERHOME $ENV{USERPROFILE}) else() set(USERHOME $ENV{HOME}) endif() set(sdkVersion 2.1.0) set(toolchainVersion 13_3_Rel1) set(picotoolVersion 2.1.0) set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) if (EXISTS ${picoVscode}) include(${picoVscode}) endif() # ==================================================================================== set(PICO_BOARD pico_w CACHE STRING "Board type") # Pull in Raspberry Pi Pico SDK (must be before project) include(pico_sdk_import.cmake) project(picow_modbus_gateway C CXX ASM) # Initialise the Raspberry Pi Pico SDK pico_sdk_init() # Add executable. Default name is the project name, version 0.1 add_executable(picow_modbus_gateway picow_modbus_gateway.c nanomodbus/nanomodbus.c picow_tcp_server/picow_tcp_server.c modbus_rtu_master/modbus_rtu_master.c modbus_tcp_client/modbus_tcp_client.c) pico_set_program_name(picow_modbus_gateway "picow_modbus_gateway") pico_set_program_version(picow_modbus_gateway "0.1") # Modify the below lines to enable/disable output over UART/USB pico_enable_stdio_uart(picow_modbus_gateway 0) pico_enable_stdio_usb(picow_modbus_gateway 1) # Add the standard library to the build target_link_libraries(picow_modbus_gateway pico_stdlib) # Add the standard include files to the build target_include_directories(picow_modbus_gateway PRIVATE ${CMAKE_CURRENT_LIST_DIR} ) # Add any user requested libraries target_link_libraries(picow_modbus_gateway pico_cyw43_arch_lwip_threadsafe_background pico_lwip_mdns ) pico_add_extra_outputs(picow_modbus_gateway)
- picow_modbus_gateway.c
#include <stdio.h> #include "pico/stdlib.h" #include "pico/cyw43_arch.h" #include "lwip/apps/mdns.h" #include "picow_tcp_server/picow_tcp_server.h" #include "nanomodbus/nanomodbus.h" #include "lwip/pbuf.h" #include "lwip/tcp.h" #include "modbus_tcp_client/modbus_tcp_client.h" #include "modbus_rtu_master/modbus_rtu_master.h" /* MODBUS GATEWAY modbus tcp client <--> modbus tcp server ^ | v modbus rtu client <--> modbus rtu server */ int main() { stdio_init_all(); uart_setup(UART_ID); //========== modbus rtu master(client) init ========== // nmbs_uart_read() and nmbs_uart_write() are implemented by the user nmbs_platform_conf rtu_platform_conf; nmbs_platform_conf_create(&rtu_platform_conf); rtu_platform_conf.transport = NMBS_TRANSPORT_RTU; rtu_platform_conf.read = nmbs_uart_read; rtu_platform_conf.write = nmbs_uart_write; nmbs_error err = nmbs_client_create(&nmbs_rtu_master, &rtu_platform_conf); if (err != NMBS_ERROR_NONE) { fprintf(stderr, "Error creating modbus client\n"); return 1; } // RTU: set read timeout and byte timeout nmbs_set_read_timeout(&nmbs_rtu_master, 2000); nmbs_set_byte_timeout(&nmbs_rtu_master, 2000); // ============ modbus rtu master(client) init ============= // ====== Initialise the Wi-Fi chip ===== if (cyw43_arch_init()) { printf("Wi-Fi init failed\n"); return -1; } cyw43_gpio_set(&cyw43_state, CYW43_WL_GPIO_LED_PIN,false); // Enable wifi station cyw43_arch_enable_sta_mode(); printf("Connecting to Wi-Fi...\n"); if (cyw43_arch_wifi_connect_timeout_ms(SSID, PWD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { printf("failed to connect.\n"); return 1; } else { printf("Connected.\n"); // Read the ip address in a human readable way uint8_t *ip_address = (uint8_t*)&(cyw43_state.netif[0].ip_addr.addr); printf("IP address %d.%d.%d.%d\n", ip_address[0], ip_address[1], ip_address[2], ip_address[3]); } #if LWIP_MDNS_RESPONDER mdns_resp_init(); uint8_t host_name[] = "picow_mb_gateway"; if (mdns_resp_add_netif(netif_default, host_name)== ERR_OK) { printf("mDNS add successfully\n"); } else { printf("mDNS failure\n"); } #endif picow_tcp_server_init(); //======= modbus tcp server(modbus server) init ====== nmbs_platform_conf platform_conf; nmbs_platform_conf_create(&platform_conf); platform_conf.transport = NMBS_TRANSPORT_TCP; platform_conf.read = nmbs_transport_read; platform_conf.write = nmbs_transport_write; extern MB_SERVER_T *modbus_tcp_server; platform_conf.arg = modbus_tcp_server; nmbs_callbacks callbacks; nmbs_callbacks_create(&callbacks); callbacks.read_coils = handle_tcp_read_coils; callbacks.read_discrete_inputs = handle_tcp_read_discrete_inputs; callbacks.write_single_coil = handle_tcp_write_single_coil; callbacks.write_multiple_coils = handle_tcp_write_multiple_coils; callbacks.read_holding_registers = handler_tcp_read_holding_registers; callbacks.write_multiple_registers = handle_tcp_write_multiple_registers; callbacks.write_single_register = handle_tcp_write_single_register; callbacks.read_file_record = handle_tcp_read_file_record; callbacks.write_file_record = handle_tcp_write_file_record; callbacks.arg = modbus_tcp_server; err = nmbs_server_create(&nmbs_gateway_tcp, 0, &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_gateway_tcp, 2000); //======= modbus tcp server(modbus server) init ====== printf("Modbus TCP server started\n"); cyw43_gpio_set(&cyw43_state, CYW43_WL_GPIO_LED_PIN,true); //======================== while (true) { nmbs_server_poll(&nmbs_gateway_tcp); #if PICO_CYW43_ARCH_POLL cyw43_arch_poll(); cyw43_arch_wait_for_work_until(make_timeout_time_ms(1000)); #else tight_loop_contents(); #endif } }
picow_tcp_server:
- picow_tcp_server.c
#include <stdio.h> #include "pico/stdlib.h" #include "pico/cyw43_arch.h" #include "nanomodbus/nanomodbus.h" #include "lwip/pbuf.h" #include "lwip/tcp.h" #include "picow_tcp_server.h" #include "modbus_tcp_client/modbus_tcp_client.h" MB_SERVER_T *modbus_tcp_server; static err_t tcp_client_close(struct tcp_pcb *client_pcb) { err_t err = ERR_OK; if (client_pcb != NULL) { tcp_arg(client_pcb, NULL); tcp_poll(client_pcb, NULL, 0); tcp_sent(client_pcb, NULL); tcp_recv(client_pcb, NULL); tcp_err(client_pcb, NULL); err = tcp_close(client_pcb); if (err != ERR_OK) { DEBUG_printf("close failed %d, calling abort\n", err); tcp_abort(client_pcb); err = ERR_ABRT; } client_pcb = NULL; } return err; } static err_t tcp_server_close(void *arg) { MB_SERVER_T *state = (MB_SERVER_T*)arg; err_t err = ERR_OK; if (state->client_pcb != NULL) { tcp_arg(state->client_pcb, NULL); tcp_poll(state->client_pcb, NULL, 0); tcp_sent(state->client_pcb, NULL); tcp_recv(state->client_pcb, NULL); tcp_err(state->client_pcb, NULL); err = tcp_close(state->client_pcb); if (err != ERR_OK) { DEBUG_printf("close failed %d, calling abort\n", err); tcp_abort(state->client_pcb); err = ERR_ABRT; } state->client_pcb = NULL; } if (state->server_pcb) { tcp_arg(state->server_pcb, NULL); tcp_close(state->server_pcb); state->server_pcb = NULL; } return err; } static err_t tcp_server_sent(void *arg, struct tcp_pcb *tpcb, u16_t len) { MB_SERVER_T *state = (MB_SERVER_T*)arg; return ERR_OK; } err_t tcp_server_send_data(void *arg, struct tcp_pcb *tpcb) { MB_SERVER_T *state = (MB_SERVER_T*)arg; return ERR_OK; } err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { MB_SERVER_T *state = (MB_SERVER_T*)arg; state->client_pcb = tpcb; static modbus_queue_t temp_recv; if (!p) { tcp_client_close(tpcb); tcp_abort(tpcb); return ERR_ABRT; } if (p->tot_len > 0) { //DEBUG_printf("tcp_client_recv %d err %d\n", p->tot_len, err); // Receive the buffer uint16_t recv_len = pbuf_copy_partial(p, temp_recv.buffer, p->tot_len, 0); temp_recv.buffer_len = recv_len; //queue_add_blocking(&state->recv_queue, &temp_recv); if (!queue_try_add(&state->recv_queue, &temp_recv)) { printf("\n=======\n\nbusy\n-------\n"); return ERR_INPROGRESS; } tcp_recved(tpcb, recv_len); //Debug printf("---------recv---------\n"); for (int i =0; i < recv_len; i++) { printf("%02x ",temp_recv.buffer[i]); } printf("\n-------------\n"); } pbuf_free(p); nmbs_server_poll(&nmbs_gateway_tcp); // process modbus request return ERR_OK; } static err_t tcp_server_poll(void *arg, struct tcp_pcb *tpcb) { //nmbs_error err = nmbs_server_poll(&nmbs); // process modbus request while tpc idle //if (err != NMBS_ERROR_NONE) { // printf("Error on modbus connection - %s\n", nmbs_strerror(err)); // In a more complete example, we would handle this error by checking its nmbs_error value //} return ERR_OK; } static void tcp_server_err(void *arg, err_t err) { if (err != ERR_ABRT) { DEBUG_printf("tcp_client_err_fn %d\n", err); } } static err_t tcp_server_accept(void *arg, struct tcp_pcb *client_pcb, err_t err) { MB_SERVER_T *state = (MB_SERVER_T*)arg; if (err != ERR_OK || client_pcb == NULL) { DEBUG_printf("Failure in accept\n"); return err; } DEBUG_printf("Client connected\n"); state->client_pcb = client_pcb; tcp_arg(state->client_pcb, state); tcp_sent(state->client_pcb, tcp_server_sent); tcp_recv(state->client_pcb, tcp_server_recv); tcp_poll(state->client_pcb, tcp_server_poll, POLL_TIME_S * 2); tcp_err(state->client_pcb, tcp_server_err); return ERR_OK; } static bool tcp_server_open(void *arg) { MB_SERVER_T *state = (MB_SERVER_T*)arg; DEBUG_printf("Starting server at %s on port %u\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), MODBUS_TCP_PORT); struct tcp_pcb *pcb = tcp_new_ip_type(IPADDR_TYPE_ANY); if (!pcb) { DEBUG_printf("failed to create pcb\n"); return false; } err_t err = tcp_bind(pcb, NULL, MODBUS_TCP_PORT); //err_t err = tcp_bind(pcb, netif_ip4_addr(netif_list), MODBUS_TCP_PORT); if (err) { DEBUG_printf("failed to bind to port %u\n", MODBUS_TCP_PORT); return false; } state->server_pcb = tcp_listen_with_backlog(pcb, 1); if (!state->server_pcb) { DEBUG_printf("failed to listen\n"); if (pcb) { tcp_close(pcb); } return false; } tcp_arg(state->server_pcb, state); tcp_accept(state->server_pcb, tcp_server_accept); return true; } void picow_tcp_server_init(void) { /* modbus TCP server init*/ modbus_tcp_server = calloc(1, sizeof(MB_SERVER_T)); if (!modbus_tcp_server) { return; } queue_init(&modbus_tcp_server->recv_queue, sizeof(modbus_queue_t), 4); if (!tcp_server_open(modbus_tcp_server)) { DEBUG_printf("tcp server open error\n"); return; } return; }
- picow_tcp_server.h
#define MODBUS_TCP_PORT 502 //MODBUS default port #define DEBUG_printf printf #define POLL_TIME_S 1 #define SSID "your-SSID" #define PWD "your-PASSWORD" #include "pico/util/queue.h" typedef struct __modbus_queue_t{ uint8_t buffer[260]; uint16_t buffer_len; } modbus_queue_t; typedef struct TCP_CLIENT_T_ { struct tcp_pcb *server_pcb; struct tcp_pcb *client_pcb; bool connected; bool dns_found; queue_t recv_queue; uint8_t server_host_name[256]; uint16_t recv_count; uint16_t start_index; uint16_t queue_left; modbus_queue_t temp_recv; } MB_SERVER_T; void picow_tcp_server_init(void);
modbus_tcp_client:
- modbus_tcp_client.c
#include "stdio.h" #include "pico/stdlib.h" #include "modbus_tcp_client.h" #include "lwip/pbuf.h" #include "lwip/tcp.h" #include "picow_tcp_server/picow_tcp_server.h" #include "modbus_rtu_master/modbus_rtu_master.h" //#define _DEBUG #ifdef _DEBUG #define DEBUG_PRINT(...) printf(__VA_ARGS__) #else #define DEBUG_PRINT(...) (void) (0) #endif nmbs_t nmbs_gateway_tcp; // A single nmbs_bitfield variable can keep 2000 coils bool terminate = false; nmbs_bitfield server_coils[MAX_CLIENT_DEVICES] = {0}; uint16_t server_registers[MAX_CLIENT_DEVICES][REGS_ADDR_MAX] = {0}; uint16_t server_file[MAX_CLIENT_DEVICES][FILE_SIZE_MAX]; #define UNUSED_PARAM(x) ((x) = (x)) void sighandler(int s) { UNUSED_PARAM(s); terminate = true; } nmbs_error handle_tcp_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); MB_SERVER_T * mb = (MB_SERVER_T*) arg; if (address + quantity > COILS_ADDR_MAX + 1) return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id); nmbs_error err = nmbs_read_coils(&nmbs_rtu_master, address, quantity, coils_out); if (err != NMBS_ERROR_NONE) { DEBUG_printf("Error info(read coils): unit_id:%d, %s\n", unit_id, nmbs_strerror(err)); return err; } for (int i = 0; i < quantity; i++) { nmbs_bitfield_write(server_coils[unit_id], address + i, nmbs_bitfield_read(coils_out, i)); } return NMBS_ERROR_NONE; } nmbs_error handle_tcp_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; nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id); nmbs_error err = nmbs_read_discrete_inputs(&nmbs_rtu_master, address, quantity, coils_out); if (err != NMBS_ERROR_NONE) { DEBUG_printf("Error info(read discrete coils): unit_id:%d, %s\n", unit_id, nmbs_strerror(err)); return err; } for (int i = 0; i < quantity; i++) { nmbs_bitfield_write(server_coils[unit_id], address + i, nmbs_bitfield_read(coils_out, i)); } return NMBS_ERROR_NONE; } nmbs_error handle_tcp_write_single_coil(uint16_t address, bool value, uint8_t unit_id, void* arg) { UNUSED_PARAM(arg); UNUSED_PARAM(unit_id); if (address > COILS_ADDR_MAX ) return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id); nmbs_error err = nmbs_write_single_coil(&nmbs_rtu_master, address, value); if (err != NMBS_ERROR_NONE) { DEBUG_printf("Error info(write single coil): unit_id:%d, %s\n", unit_id, nmbs_strerror(err)); return err; } // Write coils values to our server_coils nmbs_bitfield_write(server_coils[unit_id], address, value); return NMBS_ERROR_NONE; } nmbs_error handle_tcp_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; nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id); nmbs_error err = nmbs_write_multiple_coils(&nmbs_rtu_master, address, quantity, coils); if (err != NMBS_ERROR_NONE) { DEBUG_printf("Error info(write mulitple coils): unit_id:%d, %s\n", unit_id, nmbs_strerror(err)); uart_reset(UART_ID); return err; } // Write coils values to our server_coils for (int i = 0; i < quantity; i++) { nmbs_bitfield_write(server_coils[unit_id], address + i, nmbs_bitfield_read(coils, i)); } return NMBS_ERROR_NONE; } nmbs_error handler_tcp_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out, uint8_t unit_id, void* arg) { UNUSED_PARAM(arg); UNUSED_PARAM(unit_id); if (address + quantity > REGS_ADDR_MAX + 1) return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id); nmbs_error err = nmbs_read_holding_registers(&nmbs_rtu_master, address, quantity, registers_out); // Read our registers values into registers_out if (err != NMBS_ERROR_NONE) { DEBUG_printf("Error info(read holding regiseters): unit_id:%d, %s\n", unit_id, nmbs_strerror(err)); return err; } for (int i = 0; i < quantity; i++) server_registers[unit_id][address + i] = registers_out[i]; return NMBS_ERROR_NONE; } nmbs_error handle_tcp_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; nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id); nmbs_error err = nmbs_write_single_register(&nmbs_rtu_master, address, value); if (err != NMBS_ERROR_NONE) { DEBUG_printf("Error info(write single register): unit_id:%d, %s\n", unit_id, nmbs_strerror(err)); return err; } // Write registers values to our server_registers server_registers[unit_id][address] = value; return NMBS_ERROR_NONE; } nmbs_error handle_tcp_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; nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id); nmbs_error err = nmbs_write_multiple_registers(&nmbs_rtu_master, address, quantity, registers); if (err != NMBS_ERROR_NONE) { DEBUG_printf("Error info(write multiple registers): unit_id:%d, %s\n", unit_id, nmbs_strerror(err)); return err; } // Write registers values to our server_registers for (int i = 0; i < quantity; i++) server_registers[unit_id][address + i] = registers[i]; return NMBS_ERROR_NONE; } nmbs_error handle_tcp_read_file_record(uint16_t file_number, uint16_t record_number, uint16_t* registers, uint16_t count, uint8_t unit_id, void* arg) { UNUSED_PARAM(arg); UNUSED_PARAM(unit_id); if (file_number != 1) return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; if ((record_number + count) > FILE_SIZE_MAX) return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id); nmbs_error err = nmbs_read_file_record(&nmbs_rtu_master, file_number, record_number, registers, count); if (err != NMBS_ERROR_NONE) { DEBUG_printf("Error info: unit_id:%d, %s\n", unit_id, nmbs_strerror(err)); return err; } memcpy(registers, server_file[unit_id] + record_number, count * sizeof(uint16_t)); return NMBS_ERROR_NONE; } nmbs_error handle_tcp_write_file_record(uint16_t file_number, uint16_t record_number, const uint16_t* registers, uint16_t count, uint8_t unit_id, void* arg) { UNUSED_PARAM(arg); UNUSED_PARAM(unit_id); if (file_number != 1) return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; if ((record_number + count) > FILE_SIZE_MAX) return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id); nmbs_error err = nmbs_write_file_record(&nmbs_rtu_master, file_number, record_number, registers, count); if (err != NMBS_ERROR_NONE) { DEBUG_printf("Error info: unit_id:%d, %s\n", unit_id, nmbs_strerror(err)); return err; } memcpy(server_file[unit_id] + record_number, registers, count * sizeof(uint16_t)); return NMBS_ERROR_NONE; } int32_t nmbs_transport_read(uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) { MB_SERVER_T *state=(MB_SERVER_T*) arg; uint16_t loop_count = 0; if (state->queue_left == 0) { while (queue_is_empty(&state->recv_queue) && loop_count < 20) { loop_count++; busy_wait_ms(50); } if (!queue_try_remove(&(state->recv_queue), &state->temp_recv)) { return 0; } else { DEBUG_PRINT("\t\t\t\t---loop count:%d\n", loop_count); } state->queue_left = state->temp_recv.buffer_len; } state->recv_count = state->queue_left <= count ? state->queue_left : count; state->start_index = state->temp_recv.buffer_len-state->queue_left; for (int i=0; i < state->recv_count; i++) buf[i] = state->temp_recv.buffer[state->start_index+i]; state->queue_left = state->queue_left-state->recv_count; //Debug #ifdef _DEBUG DEBUG_PRINT("\n=====transport read:%d:%d\n", count, state->temp_recv.buffer_len); for (int i=0; i < state->recv_count; i++) { DEBUG_PRINT("%02x ", buf[i]); } DEBUG_PRINT("\n==\n"); #endif return state->recv_count; } int32_t nmbs_transport_write(const uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) { MB_SERVER_T *state = (MB_SERVER_T*)arg; // Debug #ifdef _DEBUG DEBUG_PRINT("====\nnmbs_transport_write count:%d\n", count); for (int i=0; i < count;i++) { DEBUG_PRINT("%02x ", buf[i]); } DEBUG_PRINT("\n====\n"); #endif err_t err = tcp_write(state->client_pcb, buf, count, 0 /*TCP_WRITE_FLAG_COPY*/); if (err != ERR_OK) { //tcp_abort(state->tcp_pcb); DEBUG_PRINT("tcp_write error:%d:%d\n", err, tcp_sndbuf(state->client_pcb)); return 0; } err = tcp_output(state->client_pcb); if (err != ERR_OK) { DEBUG_PRINT("%s:tcp_output:%d\n", state->server_host_name, err); return 0; } return count; }
- modbus_tcp_client.h
#ifndef __MODBUS_GATEWAY_TCP_CLIENT__ #define __MODBUS_GATEWAY_TCP_CLIENT__ #include "nanomodbus/nanomodbus.h" extern nmbs_t nmbs_gateway_tcp; nmbs_error handle_tcp_read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg); nmbs_error handle_tcp_read_discrete_inputs(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg); nmbs_error handle_tcp_write_single_coil(uint16_t address, bool value, uint8_t unit_id, void* arg); nmbs_error handle_tcp_write_multiple_coils(uint16_t address, uint16_t quantity, const nmbs_bitfield coils, uint8_t unit_id, void* arg); nmbs_error handler_tcp_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out, uint8_t unit_id, void* arg); nmbs_error handle_tcp_write_single_register(uint16_t address, uint16_t value, uint8_t unit_id, void* arg); nmbs_error handle_tcp_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* registers, uint8_t unit_id, void* arg); nmbs_error handle_tcp_read_file_record(uint16_t file_number, uint16_t record_number, uint16_t* registers, uint16_t count, uint8_t unit_id, void* arg); nmbs_error handle_tcp_write_file_record(uint16_t file_number, uint16_t record_number, const uint16_t* registers, uint16_t count, uint8_t unit_id, void* arg); int32_t nmbs_transport_read(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg); int32_t nmbs_transport_write(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms,void* arg); #endif
modbus_rtu_master:
- modbus_rtu_master.c
#include "stdio.h" #include "pico/stdlib.h" #include "modbus_rtu_master.h" nmbs_t nmbs_rtu_master; 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; } void uart_reset(uart_inst_t* uart) { uart_deinit(uart); uart_setup(uart); } void uart_setup(uart_inst_t* uart) { // Set up our UART uart_init(uart, 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); }
- modbus_rtu_master.h
#ifndef __MODBUS_GATEWAY_RTU_MASTER__ #define __MODBUS_GATEWAY_RTU_MASTER__ #include "nanomodbus/nanomodbus.h" // UART defines #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 coils addresses 0 to 100 and registers addresses from 0 to 32 #define COILS_ADDR_MAX 100 #define REGS_ADDR_MAX 32 #define FILE_SIZE_MAX 32 #define MAX_CLIENT_DEVICES 64 extern nmbs_t nmbs_rtu_master; int32_t nmbs_uart_read(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg); int32_t nmbs_uart_write(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg); void uart_reset(uart_inst_t* uart); void uart_setup(uart_inst_t* uart); #endif
nanoMODBUS:
原始library起參閱https://github.com/debevv/nanoMODBUS
在本文章專案中作以下修改部份程式碼:
Node-RED Modbus client:
[ { "id": "48fad0c0f57cea35", "type": "tab", "label": "Flow 1", "disabled": false, "info": "", "env": [] }, { "id": "67287decfe7536cf", "type": "ui_colour_picker", "z": "48fad0c0f57cea35", "name": "", "label": "Color of LED", "group": "fc04be05304743a6", "format": "rgb", "outformat": "object", "showSwatch": true, "showPicker": false, "showValue": false, "showHue": true, "showAlpha": false, "showLightness": false, "square": "true", "dynOutput": "false", "order": 2, "width": 0, "height": 0, "passthru": false, "topic": "color", "topicType": "str", "className": "", "x": 510, "y": 40, "wires": [ [ "e50739a98c836bc2" ] ] }, { "id": "730e5141782e4353", "type": "ui_slider", "z": "48fad0c0f57cea35", "name": "", "label": "num 0f Leds", "tooltip": "", "group": "fc04be05304743a6", "order": 3, "width": 0, "height": 0, "passthru": false, "outs": "all", "topic": "leds", "topicType": "str", "min": 0, "max": 10, "step": 1, "className": "", "x": 530, "y": 140, "wires": [ [ "945a43328cae473b" ] ] }, { "id": "850f63e35b7c0dd6", "type": "function", "z": "48fad0c0f57cea35", "name": "Modbus Uint 2", "func": "var regs = [4];\nregs[0] = flow.get('color_red');\nregs[1] = flow.get('color_green');\nregs[2] = flow.get('color_blue');\nregs[3] = flow.get('num_leds');\nmsg.payload = {value: regs, 'fc': 16, 'unitid': 2, 'address': 0, 'quantity': 4 };\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 900, "y": 80, "wires": [ [ "0b897cabd78f207b" ] ] }, { "id": "e50739a98c836bc2", "type": "change", "z": "48fad0c0f57cea35", "name": "set flow color", "rules": [ { "t": "set", "p": "color_red", "pt": "flow", "to": "payload.r", "tot": "msg" }, { "t": "set", "p": "color_green", "pt": "flow", "to": "payload.g", "tot": "msg" }, { "t": "set", "p": "color_blue", "pt": "flow", "to": "payload.b", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 710, "y": 40, "wires": [ [ "850f63e35b7c0dd6" ] ] }, { "id": "945a43328cae473b", "type": "change", "z": "48fad0c0f57cea35", "name": "", "rules": [ { "t": "set", "p": "num_leds", "pt": "flow", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 710, "y": 140, "wires": [ [ "850f63e35b7c0dd6" ] ] }, { "id": "0b897cabd78f207b", "type": "modbus-flex-write", "z": "48fad0c0f57cea35", "name": "", "showStatusActivities": false, "showErrors": false, "showWarnings": true, "server": "54922a4357ba259d", "emptyMsgOnFail": false, "keepMsgProperties": false, "delayOnStart": false, "startDelayTime": "", "x": 1090, "y": 260, "wires": [ [ "24a8a58f01fcf5cc", "00a3c1cedc235bc8" ], [] ] }, { "id": "24a8a58f01fcf5cc", "type": "modbus-response", "z": "48fad0c0f57cea35", "name": "", "registerShowMax": 20, "x": 1130, "y": 160, "wires": [] }, { "id": "40e24a450a247c58", "type": "ui_button", "z": "48fad0c0f57cea35", "name": "Gate", "group": "fc04be05304743a6", "order": 8, "width": 2, "height": 1, "passthru": false, "label": "{{lbl}}", "tooltip": "", "color": "", "bgcolor": "{{background}}", "className": "", "icon": "{{myicon}}", "payload": "u1_coil0", "payloadType": "flow", "topic": "addr0", "topicType": "str", "x": 570, "y": 240, "wires": [ [ "c24f886f7213b046" ] ] }, { "id": "5654c6c9dbd8c887", "type": "function", "z": "48fad0c0f57cea35", "name": "Modbus Uint 1", "func": "if (msg.address == 0)\n msg.payload = {value: msg.payload, 'fc': 15, 'unitid': 1, 'address': msg.address, 'quantity': msg.quantity };\nif (msg.address == 1)\n msg.payload = {value: msg.payload, 'fc': 5, 'unitid': 1, 'address': msg.address, 'quantity': msg.quantity };\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 900, "y": 320, "wires": [ [ "0b897cabd78f207b" ] ] }, { "id": "c24f886f7213b046", "type": "function", "z": "48fad0c0f57cea35", "name": "Uint 1", "func": "var newmsg={};\n\nif (msg.payload == true) {\n if (msg.topic == 'addr0') {\n newmsg.lbl = 'CLOSE';\n newmsg.background=\"magenta\";\n }\n else {\n newmsg.lbl = \"OFF\";\n newmsg.background=\"red\";\n }\n newmsg.myicon = 'fa-toggle-off';\n\n \n} else { \n if (msg.topic == 'addr0') {\n newmsg.lbl='OPEN';\n newmsg.background='lightblue';\n }\n else {\n newmsg.lbl = 'ON';\n newmsg.background='blue';\n }\n newmsg.myicon = 'fa-toggle-on';\n \n}\nnewmsg.topic = msg.topic;\nlet address =2;\nif (newmsg.topic == 'addr0') {\n address =0;\n //newmsg.payload={};\n flow.set('u1_coil0', !msg.payload);\n flow.set('u1_coil1', !msg.payload);\n newmsg.payload = [msg.payload, msg.payload];\n newmsg.quantity = 2;\n}\nif (newmsg.topic == 'addr1') {\n address =1;\n flow.set('u1_coil1', !msg.payload);\n newmsg.payload=msg.payload;\n newmsg.quantity = 1;\n}\nnewmsg.address=address;\n\nreturn newmsg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 710, "y": 320, "wires": [ [ "5654c6c9dbd8c887", "b090ab4f179755b5" ] ] }, { "id": "3009c8f5180b8b48", "type": "ui_button", "z": "48fad0c0f57cea35", "name": "Light", "group": "fc04be05304743a6", "order": 12, "width": 2, "height": 1, "passthru": false, "label": "{{lbl}}", "tooltip": "", "color": "", "bgcolor": "{{background}}", "className": "", "icon": "{{myicon}}", "payload": "u1_coil1", "payloadType": "flow", "topic": "addr1", "topicType": "str", "x": 570, "y": 420, "wires": [ [ "c24f886f7213b046" ] ] }, { "id": "b090ab4f179755b5", "type": "switch", "z": "48fad0c0f57cea35", "name": "toggle", "property": "topic", "propertyType": "msg", "rules": [ { "t": "eq", "v": "addr0", "vt": "str" }, { "t": "eq", "v": "addr1", "vt": "str" } ], "checkall": "false", "repair": false, "outputs": 2, "x": 430, "y": 360, "wires": [ [ "deb6e9852981cbe7" ], [ "ea4d168a958d4731" ] ] }, { "id": "7fcff809d9501142", "type": "modbus-getter", "z": "48fad0c0f57cea35", "name": "Uint1 FC:1", "showStatusActivities": false, "showErrors": false, "showWarnings": true, "logIOActivities": false, "unitid": "1", "dataType": "Coil", "adr": "0", "quantity": "2", "server": "54922a4357ba259d", "useIOFile": false, "ioFile": "", "useIOForPayload": false, "emptyMsgOnFail": false, "keepMsgProperties": false, "delayOnStart": false, "startDelayTime": "", "x": 170, "y": 340, "wires": [ [ "78865a9a77ef7a00", "cf34a46dd051bf77" ], [] ] }, { "id": "78865a9a77ef7a00", "type": "function", "z": "48fad0c0f57cea35", "name": "get coil 0 init value", "func": "var newmsg={};\nflow.set('u1_coil0',!msg.payload[0]);\n\nnewmsg.payload=msg.payload[0];\n\nif (msg.payload[0] == false) {\n newmsg.lbl = \"OPEN\";\n newmsg.myicon='fa-toggle-on';\n newmsg.background='lightblue';\n} else {\n newmsg.lbl = 'CLOSE';\n newmsg.myicon='fa-toggle-off';\n newmsg.background='magenta';\n}\nreturn newmsg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 290, "y": 240, "wires": [ [ "deb6e9852981cbe7" ] ] }, { "id": "cf34a46dd051bf77", "type": "function", "z": "48fad0c0f57cea35", "name": "get coil 1 init value", "func": "var newmsg={};\nflow.set('u1_coil1',!msg.payload[1]);\n\nnewmsg.payload=msg.payload[1];\n\nif (msg.payload[1] == false) {\n newmsg.lbl = \"ON\";\n newmsg.background='blue';\n newmsg.myicon='fa-toggle-on';\n} else {\n newmsg.lbl = 'OFF';\n newmsg.myicon='fa-toggle-off';\n newmsg.background='red';\n}\n//newmsg.label = newmsg.label_v;\nreturn newmsg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 290, "y": 460, "wires": [ [ "ea4d168a958d4731" ] ] }, { "id": "714e1f0e2afa0885", "type": "function", "z": "48fad0c0f57cea35", "name": "color", "func": "var newmsg={};\nnewmsg.payload = {r:msg.payload[0],g:msg.payload[1],b:msg.payload[2], a:1};\nreturn newmsg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 330, "y": 40, "wires": [ [ "67287decfe7536cf" ] ] }, { "id": "c1fc304462222840", "type": "function", "z": "48fad0c0f57cea35", "name": "num_of_leds", "func": "var newmsg = {};\nnewmsg.payload = msg.payload[3];\nreturn newmsg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 350, "y": 140, "wires": [ [ "730e5141782e4353" ] ] }, { "id": "22c0fbed37498038", "type": "ui_text", "z": "48fad0c0f57cea35", "group": "fc04be05304743a6", "order": 1, "width": 0, "height": 0, "name": "", "label": "Unit 1", "format": "{{msg.payload}}", "layout": "row-left", "className": "", "style": true, "font": "Times New Roman,Times,serif", "fontSize": "22", "color": "#0000ff", "x": 890, "y": 280, "wires": [] }, { "id": "b3dc9565e22052fe", "type": "ui_text", "z": "48fad0c0f57cea35", "group": "fc04be05304743a6", "order": 5, "width": 0, "height": 0, "name": "", "label": "Unit 2", "format": "{{msg.payload}}", "layout": "row-left", "className": "", "style": true, "font": "Times New Roman,Times,serif", "fontSize": "22", "color": "#0000ff", "x": 890, "y": 40, "wires": [] }, { "id": "40896a542df60d8a", "type": "ui_text", "z": "48fad0c0f57cea35", "group": "fc04be05304743a6", "order": 6, "width": 1, "height": 2, "name": "", "label": "Gate", "format": "{{msg.payload}}", "layout": "col-center", "className": "", "style": true, "font": "", "fontSize": "15", "color": "#8f0a0a", "x": 710, "y": 240, "wires": [] }, { "id": "4828163558cac23f", "type": "ui_text", "z": "48fad0c0f57cea35", "group": "fc04be05304743a6", "order": 10, "width": 1, "height": 2, "name": "", "label": "Light:", "format": "{{msg.payload}}", "layout": "col-center", "className": "", "style": true, "font": "Verdana,Verdana,Geneva,sans-serif", "fontSize": "15", "color": "#133305", "x": 710, "y": 420, "wires": [] }, { "id": "7710bdc96bee5a64", "type": "link in", "z": "48fad0c0f57cea35", "name": "Unit 1:Read Coil 1", "links": [ "336bf1d73e3c16f0" ], "x": 35, "y": 340, "wires": [ [ "7fcff809d9501142" ] ] }, { "id": "336bf1d73e3c16f0", "type": "link out", "z": "48fad0c0f57cea35", "name": "Unit 1: Read Coil 1", "mode": "link", "links": [ "7710bdc96bee5a64" ], "x": 1235, "y": 300, "wires": [] }, { "id": "fe478f2f1bf74258", "type": "ui_ui_control", "z": "48fad0c0f57cea35", "name": "", "events": "all", "x": 80, "y": 200, "wires": [ [ "4855c1d9cb3c5de3", "7fcff809d9501142" ] ] }, { "id": "4855c1d9cb3c5de3", "type": "modbus-getter", "z": "48fad0c0f57cea35", "name": "Unit2 FC:3", "showStatusActivities": false, "showErrors": false, "showWarnings": true, "logIOActivities": false, "unitid": "2", "dataType": "HoldingRegister", "adr": "0", "quantity": "4", "server": "54922a4357ba259d", "useIOFile": false, "ioFile": "", "useIOForPayload": false, "emptyMsgOnFail": false, "keepMsgProperties": false, "delayOnStart": false, "startDelayTime": "", "x": 170, "y": 100, "wires": [ [ "714e1f0e2afa0885", "c1fc304462222840" ], [] ] }, { "id": "ea4d168a958d4731", "type": "ui_template", "z": "48fad0c0f57cea35", "group": "fc04be05304743a6", "name": "Light Status", "order": 11, "width": 2, "height": 2, "format": "<script>\n(function(scope) {\n scope.$watch('msg', function(msg) {\n if (msg) {\n if (msg.payload) {\n scope.light_icon=\"light_on.png\";\n } else {\n scope.light_icon=\"light_off.png\";\n }\n }\n\n\n });\n\n})(scope);\n</script>\n\n\n<div>\n <img src = {{light_icon}} width=60 height=60>\n \n</div>\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, "templateScope": "local", "className": "", "x": 510, "y": 480, "wires": [ [ "3009c8f5180b8b48" ] ] }, { "id": "deb6e9852981cbe7", "type": "ui_template", "z": "48fad0c0f57cea35", "group": "fc04be05304743a6", "name": "Gate Status", "order": 7, "width": 2, "height": 2, "format": "<script>\n(function(scope) {\n scope.$watch('msg', function(msg) {\n if (msg) {\n if (msg.payload) {\n scope.light_icon=\"gate_open.png\";\n } else {\n scope.light_icon=\"gate_close.png\";\n }\n }\n\n\n });\n\n})(scope);\n</script>\n\n\n<div>\n <img src = {{light_icon}} width=60 height=60>\n \n</div>\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, "templateScope": "local", "className": "", "x": 490, "y": 300, "wires": [ [ "40e24a450a247c58" ] ] }, { "id": "1f2e434da8912dc8", "type": "ui_template", "z": "48fad0c0f57cea35", "group": "fc04be05304743a6", "name": "seprate line", "order": 4, "width": 0, "height": 0, "format": "<div>\n <img src='line.png' width=250>\n</div>", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, "templateScope": "local", "className": "", "x": 630, "y": 180, "wires": [ [] ] }, { "id": "d11918fa96d8a971", "type": "link in", "z": "48fad0c0f57cea35", "name": "Unit 2: read registers", "links": [ "41175f479b349001" ], "x": 45, "y": 100, "wires": [ [ "4855c1d9cb3c5de3" ] ] }, { "id": "41175f479b349001", "type": "link out", "z": "48fad0c0f57cea35", "name": "Unit 2: read registers", "mode": "link", "links": [ "d11918fa96d8a971" ], "x": 1235, "y": 340, "wires": [] }, { "id": "00a3c1cedc235bc8", "type": "switch", "z": "48fad0c0f57cea35", "name": "link out", "property": "payload.unitid", "propertyType": "msg", "rules": [ { "t": "eq", "v": "1", "vt": "num" }, { "t": "eq", "v": "2", "vt": "num" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 1120, "y": 320, "wires": [ [ "336bf1d73e3c16f0" ], [ "41175f479b349001" ] ] }, { "id": "60796979c4948a75", "type": "ui_spacer", "z": "48fad0c0f57cea35", "name": "spacer", "group": "fc04be05304743a6", "order": 9, "width": 2, "height": 1 }, { "id": "136b2d0e7ac84aae", "type": "ui_spacer", "z": "48fad0c0f57cea35", "name": "spacer", "group": "fc04be05304743a6", "order": 13, "width": 2, "height": 1 }, { "id": "fc04be05304743a6", "type": "ui_group", "name": "Gateway", "tab": "cb1cfb3cbe2a4244", "order": 1, "disp": true, "width": 5, "collapse": false, "className": "" }, { "id": "54922a4357ba259d", "type": "modbus-client", "name": "picow_mb_gateway", "clienttype": "tcp", "bufferCommands": true, "stateLogEnabled": false, "queueLogEnabled": false, "failureLogEnabled": true, "tcpHost": "picow_mb_gateway.local", "tcpPort": 502, "tcpType": "DEFAULT", "serialPort": "/dev/ttyUSB", "serialType": "RTU-BUFFERD", "serialBaudrate": 9600, "serialDatabits": 8, "serialStopbits": 1, "serialParity": "none", "serialConnectionDelay": 100, "serialAsciiResponseStartDelimiter": "0x3A", "unit_id": 1, "commandDelay": 1, "clientTimeout": 2000, "reconnectOnTimeout": false, "reconnectTimeout": 2000, "parallelUnitIdsAllowed": true, "showErrors": false, "showWarnings": true, "showLogs": true }, { "id": "cb1cfb3cbe2a4244", "type": "ui_tab", "name": "MODBUS", "icon": "dashboard", "disabled": false, "hidden": false } ]