本影片以Raspberry Pi Pico W使用nanoMODBUS與LWIP libraries. 實做MODBUS TCP, 以一個Master與兩個Slaves進行測試.
架構如下圖:
Slave 1(MODBUS Server 1)提供溫度與濕度,Slave 2(MODBUS server 2)提供8 個Coils分別連接8的switches(本測試專案連接8個Leds)。
網路層TCP/IP使用Pico LWIP library, MODBUS protocol層使用nanoMODBUS library(https://github.com/debevv/nanoMODBUS),如下圖所示:
在本專案中,兩個MODBUS servers分別使用mDNS命名為picow_mb_ht.local與picow_mb_leds.local。
modbus master(MODBUS Client)在lwipopts.h設定
以便透過mDNS查詢modbus server 1 與server 2的IP address。MODBUS TCP/IP ADU各欄位分如下:
- MBAP Header:
- PDU function Code:
- PDU Data:
詳細內容請參閱:https://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf
成果影片:
程式碼:nanoMODBUS library 配合本專案以queue儲存tcp/ip packet, nanomodbus.c程式碼作如下的修改:
modbus slave(modbus Server, tcp server)端程式碼:
# 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_server_tcp 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_server_tcp picow_modbus_server_tcp.c nanomodbus/nanomodbus.c picow_tcp_server/picow_tcp_server.c sht40/sht40.c) pico_set_program_name(picow_modbus_server_tcp "picow_modbus_server_tcp") pico_set_program_version(picow_modbus_server_tcp "0.1") # Modify the below lines to enable/disable output over UART/USB pico_enable_stdio_uart(picow_modbus_server_tcp 1) pico_enable_stdio_usb(picow_modbus_server_tcp 0) # Add the standard library to the build target_link_libraries(picow_modbus_server_tcp pico_stdlib hardware_i2c) # Add the standard include files to the build target_include_directories(picow_modbus_server_tcp PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/nanomodbus ${CMAKE_CURRENT_LIST_DIR}/pciow_tcp_server ${CMAKE_CURRENT_LIST_DIR}/sht40 ) # Add any user requested libraries target_link_libraries(picow_modbus_server_tcp pico_cyw43_arch_lwip_threadsafe_background pico_lwip_mdns ) pico_add_extra_outputs(picow_modbus_server_tcp)
- picow_modbus_server_tcp.c
#include <stdio.h> #include "pico/stdlib.h" #include "pico/cyw43_arch.h" #include "nanomodbus.h" #include "lwip/pbuf.h" #include "lwip/tcp.h" #include "lwip/apps/mdns.h" #include "picow_tcp_server/picow_tcp_server.h" #include "sht40/sht40.h" nmbs_t nmbs; // 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 // A single nmbs_bitfield variable can keep 2000 coils bool terminate = false; nmbs_bitfield server_coils = {0}; uint16_t server_registers[REGS_ADDR_MAX] = {0}; uint16_t server_file[FILE_SIZE_MAX]; #define UNUSED_PARAM(x) ((x) = (x)) void sighandler(int s) { UNUSED_PARAM(s); terminate = true; } nmbs_error handle_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 > COILS_ADDR_MAX ) return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; // Write coils values to our server_coils nmbs_bitfield_write(server_coils, address, value); gpio_put(address+2, value); // address 0 map to gpio 2 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)); } uint32_t gpio_value = server_coils[0] << 2; gpio_put_masked(0x3fc, gpio_value); 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; // Read our registers values into registers_out float temp, humi; sht40_get_th_data(&temp, &humi); printf("t:%f, h:%f\n", temp, humi); server_registers[0]=(int)temp; server_registers[1]=(int)((temp-(int)temp)*100); server_registers[2]=(int)humi; server_registers[3]=(int)((humi-(int)humi)*100); for (int i = 0; i < quantity; i++) registers_out[i] = server_registers[address + i]; for(int i=0; i < quantity; i++) printf("%d,", registers_out[i]); printf("--\n"); 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 server_registers[address] = value; 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_transport_read(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; static recv_queue_t temp_recv; static uint16_t queue_left = 0; static uint16_t recv_count=0; static uint16_t start_index=0; if (queue_left == 0) { if (!queue_try_remove(&(state->recv_queue), &temp_recv)) return 0; queue_left = temp_recv.buffer_len; } recv_count = queue_left <= count ? queue_left : count; start_index = temp_recv.buffer_len-queue_left; for (int i=0; i < recv_count; i++) buf[i] = temp_recv.buffer[start_index+i]; queue_left = queue_left-recv_count; /* //Debug printf("\n=====transport read:%d:%d:%d\n", count, temp_recv.buffer_len, queue_left); for (int i=0; i < recv_count; i++) { printf("%02x ", buf[i]); } printf("\n==\n"); */ return recv_count; } int32_t nmbs_transport_write(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms,void* arg){ TCP_SERVER_T *state = (TCP_SERVER_T*)arg; tcp_write(state->client_pcb, buf, count, TCP_WRITE_FLAG_COPY); //Debug printf("======transport write:\n"); for (int i=0; i < count; i++) { printf("%02x ", buf[i]); } printf("\n"); return count; } /*!< Bytes write transport function pointer */ int main() { stdio_init_all(); // 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); gpio_init_mask(0x3FC); gpio_set_dir_out_masked(0x3fc); sht40_init(); // 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_ht"; //uint8_t host_name[] = "picow_mb_leds"; 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(); // nmbs_transport_read() and nmbs_transport_write() are implemented by the user 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 TCP_SERVER_T *modbus_tcp_server; platform_conf.arg = modbus_tcp_server; nmbs_callbacks callbacks; nmbs_callbacks_create(&callbacks); callbacks.read_coils = handle_read_coils; callbacks.read_discrete_inputs = handle_read_discrete_inputs; callbacks.write_single_coil = handle_write_single_coil; callbacks.write_multiple_coils = handle_write_multiple_coils; callbacks.read_holding_registers = handler_read_holding_registers; callbacks.write_multiple_registers = handle_write_multiple_registers; callbacks.write_single_register = handle_write_single_register; nmbs_error err = nmbs_server_create(&nmbs, 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, 2000); printf("Modbus TCP server started\n"); cyw43_gpio_set(&cyw43_state, CYW43_WL_GPIO_LED_PIN,true); while (true) { nmbs_server_poll(&nmbs); #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.c
#include <stdio.h> #include "pico/stdlib.h" #include "pico/cyw43_arch.h" #include "nanomodbus.h" #include "lwip/pbuf.h" #include "lwip/tcp.h" #include "picow_tcp_server.h" TCP_SERVER_T *modbus_tcp_server; extern nmbs_t nmbs; 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) { TCP_SERVER_T *state = (TCP_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) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; return ERR_OK; } err_t tcp_server_send_data(void *arg, struct tcp_pcb *tpcb) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; return ERR_OK; } err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; state->client_pcb = tpcb; static recv_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); // 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) { TCP_SERVER_T *state = (TCP_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(client_pcb, state); tcp_sent(client_pcb, tcp_server_sent); tcp_recv(client_pcb, tcp_server_recv); tcp_poll(client_pcb, tcp_server_poll, POLL_TIME_S * 2); tcp_err(client_pcb, tcp_server_err); return ERR_OK; } static bool tcp_server_open(void *arg) { TCP_SERVER_T *state = (TCP_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); 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(TCP_SERVER_T)); if (!modbus_tcp_server) { return; } queue_init(&modbus_tcp_server->recv_queue, sizeof(recv_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 __recv_queue_t{ uint8_t buffer[260]; uint16_t buffer_len; } recv_queue_t; typedef struct TCP_SERVER_T_ { struct tcp_pcb *server_pcb; struct tcp_pcb *client_pcb; queue_t recv_queue; } TCP_SERVER_T; void picow_tcp_server_init(void);
- sht40.c
#include "stdio.h" #include "pico/stdlib.h" #include "sht40.h" static float sht40_temp=0; static float sht40_humidity=0; void sht40_init() { i2c_init(I2C_SHT40_PORT, 400*1000); gpio_set_function(I2C_SHT40_SDA, GPIO_FUNC_I2C); gpio_set_function(I2C_SHT40_SCL, GPIO_FUNC_I2C); gpio_pull_up(I2C_SHT40_SDA); gpio_pull_up(I2C_SHT40_SCL); } bool sht40_read(float *temp, float *humi) { char buff[8]; int ret; float t_ticks, rh_ticks, t_degC, rh_pRH; buff[0] = 0xFD; ret = i2c_write_blocking(I2C_SHT40_PORT, 0x44, buff,1,false); busy_wait_ms(10); ret = i2c_read_blocking(I2C_SHT40_PORT, 0x44, buff, 6, false); t_ticks = buff[0] * 256 + buff[1]; //checksum_t = rx_bytes[2] rh_ticks = buff[3] * 256 + buff[4]; //checksum_rh = rx_bytes[5] t_degC = -45 + 175 * t_ticks/65535; rh_pRH = -6 + 125 * rh_ticks/65535; if (rh_pRH > 100) rh_pRH = 100; if (rh_pRH < 0) rh_pRH = 0; *temp = t_degC; *humi = rh_pRH; //printf("temp:%f, himidity:%f\n", t_degC, rh_pRH); } void sht40_get_th_data(float* temp, float* humi) { sht40_read(temp, humi); //*temp = sht40_temp; //*humi = sht40_humidity; }
- sht40.h
#ifndef __SHT40_H__ #define __SHT40_H__ #include "hardware/i2c.h" #define I2C_SHT40_PORT i2c0 #define I2C_SHT40_SDA 16 #define I2C_SHT40_SCL 17 void sht40_init(); void sht40_get_th_data(float* temp, float* humi); #endif
modbus MASTER(modbus client, tcp client)端程式碼:
pico_lvgl程式碼請參閱:
- 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_client_tcp 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_client_tcp picow_modbus_client_tcp.c picow_tcp_client/picow_tcp_client.c nanomodbus/nanomodbus.c lvgl_modbus_ui.c) pico_set_program_name(picow_modbus_client_tcp "picow_modbus_client_tcp") pico_set_program_version(picow_modbus_client_tcp "0.1") # Modify the below lines to enable/disable output over UART/USB pico_enable_stdio_uart(picow_modbus_client_tcp 1) pico_enable_stdio_usb(picow_modbus_client_tcp 0) # Add the standard library to the build target_link_libraries(picow_modbus_client_tcp pico_stdlib pico_multicore) # Add the standard include files to the build target_include_directories(picow_modbus_client_tcp PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/picow_tcp_client ${CMAKE_CURRENT_LIST_DIR}/nanomodbus ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required ) # Add any user requested libraries target_link_libraries(picow_modbus_client_tcp pico_cyw43_arch_lwip_threadsafe_background pico_lwip_mdns ) add_subdirectory(pico_lvgl) target_link_libraries(picow_modbus_client_tcp pico_lvgl ) pico_add_extra_outputs(picow_modbus_client_tcp)
- picow_modbus_client_tcp.c
#include <stdio.h> #include "pico/stdlib.h" #include "pico/cyw43_arch.h" #include "hardware/pio.h" #include "pico_lvgl.h" #include "picow_tcp_client.h" #include "nanomodbus.h" #include "pico/multicore.h" #include "pico/mutex.h" //#define _DEBUG #ifdef _DEBUG #define DEBUG_PRINT(...) printf(__VA_ARGS__) #else #define DEBUG_PRINT(...) (void) (0) #endif 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 extern MB_SERVER_T *modbus_ht_server, *modbus_sw_server; nmbs_t nmbs; mutex_t sent_request; void draw_master_ui(); extern lv_obj_t *temp_label, *humi_label, *msg_label; 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 DEBUG_PRINT("\n=====transport read:%d:%d\n", count, state->temp_recv.buffer_len); for (int i=0; i < state->recv_count; i++) { DEBUG_PRINT("%02x ", buf[i]); } DEBUG_PRINT("\n==\n"); return state->recv_count; } int32_t nmbs_transport_write(const uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) { MB_SERVER_T *state = (MB_SERVER_T*)arg; // Debug DEBUG_PRINT("====\nnmbs_transport_write count:%d\n", count); for (int i=0; i < count;i++) { DEBUG_PRINT("%02x ", buf[i]); } DEBUG_PRINT("\n====\n"); err_t err = tcp_write(state->tcp_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->tcp_pcb)); return 0; } err = tcp_output(state->tcp_pcb); if (err != ERR_OK) { DEBUG_PRINT("%s:tcp_output:%d\n", state->server_host_name, err); return 0; } return count; } void core1_thread(void) { uint8_t label_buff[30]; uint16_t holding_registers[4]; uint16_t trans_timeout_count=0; while(1) { if (!modbus_ht_server->connected) { sleep_ms(5000); if (picow_tcp_client_open(modbus_ht_server)) trans_timeout_count = 0; continue; } lv_label_set_text(msg_label, "Receiving HT data..."); lv_timer_handler(); if (!mutex_enter_timeout_ms(&sent_request, nmbs.read_timeout_ms*2)) continue; nmbs_set_platform_arg(&nmbs, modbus_ht_server); nmbs_error nmbserr= nmbs_read_holding_registers(&nmbs, 0, 4, holding_registers); if (nmbserr == NMBS_ERROR_NONE) { sprintf(label_buff,"Temp: %d.%d \xC2\xB0""C", holding_registers[0], holding_registers[1]); lv_label_set_text(temp_label, label_buff); sprintf(label_buff, "Humi: %d.%d%%", holding_registers[2], holding_registers[3]); lv_label_set_text(humi_label, label_buff); lv_label_set_text(msg_label, ""); }else { tcp_abort(modbus_ht_server->tcp_pcb); DEBUG_PRINT("read holding registers error:%d\n", nmbserr); trans_timeout_count++; lv_label_set_text(msg_label, "HT Server error!"); lv_timer_handler(); if (nmbserr < 0 && trans_timeout_count > 5) { nmbs_set_platform_arg(&nmbs, modbus_ht_server); picow_tcp_client_close(modbus_ht_server); } } mutex_exit(&sent_request); lv_timer_handler(); sleep_ms(3000); } } int main() { stdio_init_all(); mutex_init(&sent_request); 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(); // Initialise the Wi-Fi chip if (cyw43_arch_init()) { DEBUG_PRINT("Wi-Fi init failed\n"); lv_label_set_text(msg_label, "Wi-Fi init failed"); lv_timer_handler(); return -1; } if (!picow_tcp_client_init()) return 0; nmbs_platform_conf platform_conf; nmbs_platform_conf_create(&platform_conf); platform_conf.transport = NMBS_TRANSPORT_TCP; platform_conf.read = nmbs_transport_read; platform_conf.write = nmbs_transport_write; // Create the modbus client nmbs_error err = nmbs_client_create(&nmbs, &platform_conf); if (err != NMBS_ERROR_NONE) { lv_label_set_text(msg_label, "Error creating modbus client"); lv_timer_handler(); fprintf(stderr, "Error creating modbus client\n"); if (!nmbs_error_is_exception(err)) return 1; } // Set only the response timeout. Byte timeout will be handled by the TCP connection nmbs_set_read_timeout(&nmbs, 1000); nmbs_set_byte_timeout(&nmbs, 1000); multicore_launch_core1(core1_thread); while (true) { lv_timer_handler(); sleep_ms(50); //tight_loop_contents(); } }
- lvgl_modbus_ui.c
#include "lvgl.h" #include "nanomodbus.h" #include "stdio.h" #include "pico/stdlib.h" #include "picow_tcp_client.h" #include "pico/mutex.h" lv_obj_t *temp_label, *humi_label, *msg_label, *slave1_label, *slave2_label; lv_obj_t **chk; lv_obj_t **sw; extern nmbs_t nmbs; extern MB_SERVER_T *modbus_ht_server, *modbus_sw_server; extern mutex_t sent_request; static void btn_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); uint8_t address; nmbs_bitfield coils; int i; if(code == LV_EVENT_CLICKED) { if (!modbus_sw_server->connected) return; for ( i=0; i < 8; i++) { if (lv_obj_get_state(*(chk+i)) & LV_STATE_CHECKED) { nmbs_bitfield_set(coils, i); } else { nmbs_bitfield_unset(coils, i); } } lv_timer_handler(); if (!mutex_enter_timeout_ms(&sent_request, nmbs.read_timeout_ms*2)) { lv_label_set_text(msg_label, "Network busy! Try again"); lv_timer_handler(); return; } nmbs_set_platform_arg(&nmbs, modbus_sw_server); nmbs_error nmbserr=nmbs_write_multiple_coils(&nmbs, 0, 8, coils); if (nmbserr == NMBS_ERROR_NONE) { for (int i=0; i < 8; i++) { if (nmbs_bitfield_read(coils, i)) { lv_obj_add_state(*(sw+i), LV_STATE_CHECKED); } else { lv_obj_clear_state(*(sw+i), LV_STATE_CHECKED); } } } else { printf("0:write_multiple_coils error:%d\n", nmbserr); } mutex_exit(&sent_request); lv_timer_handler(); } } static void switch_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); uint8_t address; int ud = *(int*)lv_event_get_user_data(e); int i; if(code == LV_EVENT_VALUE_CHANGED && ud == 1) { if (!modbus_sw_server->connected) return; for ( i=0; i < 8; i++) { if (obj == *(sw+i)){ address = i; break; } } lv_timer_handler(); uint8_t coil = lv_obj_has_state(obj, LV_STATE_CHECKED) ? 1 : 0; if (!mutex_enter_timeout_ms(&sent_request, nmbs.read_timeout_ms*2)) { lv_label_set_text(msg_label, "Network busy! Try again"); lv_timer_handler(); return; } nmbs_set_platform_arg(&nmbs, modbus_sw_server); nmbs_error nmbserr=nmbs_write_single_coil(&nmbs, address, coil); if (coil) lv_obj_add_state(*(chk+i), LV_STATE_CHECKED); else lv_obj_clear_state(*(chk+i), LV_STATE_CHECKED); if (nmbserr != NMBS_ERROR_NONE) printf("0:write_single_coils error:%d\n", nmbserr); mutex_exit(&sent_request); lv_timer_handler(); } } void draw_master_ui() { slave1_label = lv_label_create(lv_scr_act()); slave2_label = lv_label_create(lv_scr_act()); lv_obj_align(slave1_label, LV_ALIGN_TOP_LEFT, 10, 10); lv_obj_set_style_text_font(slave1_label, &lv_font_montserrat_30, 0); lv_obj_set_style_text_color(slave1_label, lv_palette_main(LV_PALETTE_BLUE),0); lv_label_set_text(slave1_label, "Slave 1:" LV_SYMBOL_CLOSE); msg_label = lv_label_create(lv_scr_act()); lv_obj_align(msg_label, LV_ALIGN_TOP_MID, 0, 50); lv_obj_set_style_text_font(msg_label, &lv_font_montserrat_18, 0); lv_obj_set_style_text_color(msg_label, lv_palette_main(LV_PALETTE_RED),0); lv_label_set_text(msg_label, ""); temp_label = lv_label_create(lv_scr_act()); lv_obj_align_to(temp_label, slave1_label, LV_ALIGN_OUT_RIGHT_MID, 60, 0); lv_obj_set_style_text_font(temp_label, &lv_font_montserrat_18, 0); lv_label_set_text(temp_label, ""); humi_label = lv_label_create(lv_scr_act()); lv_obj_align_to(humi_label, temp_label, LV_ALIGN_OUT_LEFT_MID, 180, 0); lv_obj_set_style_text_font(humi_label, &lv_font_montserrat_18, 0); lv_label_set_text(humi_label, ""); static int ud=1; sw = (lv_obj_t**)malloc(sizeof(lv_obj_t*)*8); for (int i=0; i < 8; i++) { *(sw+i) = lv_switch_create(lv_scr_act()); lv_obj_align(*(sw+i), LV_ALIGN_TOP_LEFT, i*60+5, 250); lv_obj_add_event_cb(*(sw+i), switch_event_handler, LV_EVENT_ALL, (void*)&ud); //lv_obj_add_event_cb(*(sw+i), switch_event_handler, LV_EVENT_ALL, NULL); } lv_obj_t* label = lv_label_create(lv_scr_act()); for (int i = 0; i < 8; i++) { label = lv_label_create(lv_scr_act()); lv_obj_align(label, LV_ALIGN_TOP_LEFT, 60*i+5, 180); lv_label_set_text_fmt(label,"SW%d",i); } chk = (lv_obj_t**)malloc(sizeof(lv_obj_t*)*8); for (int i=0; i < 8; i++) { *(chk+i) = lv_checkbox_create(lv_scr_act()); lv_obj_align(*(chk+i), LV_ALIGN_TOP_LEFT, i*60+5, 200); lv_checkbox_set_text(*(chk+i), ""); //lv_obj_add_event_cb(*(chk+i), checkbox_event_handler, LV_EVENT_ALL, NULL); } lv_obj_t* btn = lv_btn_create(lv_scr_act()); lv_obj_align(btn, LV_ALIGN_TOP_RIGHT, -50, 130); label = lv_label_create(btn); lv_label_set_text(label, "Multi"); lv_obj_add_event_cb(btn, btn_event_handler, LV_EVENT_ALL, NULL); lv_obj_align(slave2_label, LV_ALIGN_TOP_LEFT, 10, 130); lv_obj_set_style_text_font(slave2_label, &lv_font_montserrat_30, 0); lv_obj_set_style_text_color(slave2_label, lv_palette_main(LV_PALETTE_BLUE),0); lv_label_set_text(slave2_label, "Slave 2:"LV_SYMBOL_CLOSE); 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, 110); }
- picow_tcp_client.c
#include "pico/stdio.h" #include "pico/stdlib.h" #include "picow_tcp_client.h" #include "pico/cyw43_arch.h" #include "lwip/tcp.h" #include "lwip/pbuf.h" #include "lwip/dns.h" #include "lwip/apps/mdns.h" #include "lvgl.h" #define WIFI_SSID "your-SSID" #define WIFI_PASSWD "your-password" #define MODBUS_TCP_PORT 502 #define POLL_TIME_S 1 #define DEBUG_printf printf MB_SERVER_T *modbus_ht_server, *modbus_sw_server; extern lv_obj_t *msg_label, *slave1_label, *slave2_label; // Call back with a DNS result static void local_server_dns_found(const char *hostname, const ip_addr_t *ipaddr, void *arg) { MB_SERVER_T *state = (MB_SERVER_T*)arg; if (ipaddr) { state->remote_addr = *ipaddr; printf("modbus server address %s\n", ipaddr_ntoa(ipaddr)); state->dns_found = true; } else { printf("modbus server dns request failed\n"); } } static err_t tcp_client_close(void *arg) { MB_SERVER_T *state = (MB_SERVER_T*)arg; err_t err = ERR_OK; if (strcmp(state->server_host_name, "picow_mb_leds.local") == 0) { lv_label_set_text(slave2_label, "Slave2:" LV_SYMBOL_CLOSE); state->connected = false; } if (strcmp(state->server_host_name, "picow_mb_ht.local") == 0) { lv_label_set_text(slave1_label, "Slave1:" LV_SYMBOL_CLOSE); state->connected = false; } lv_timer_handler(); if (state->tcp_pcb != NULL) { tcp_arg(state->tcp_pcb, NULL); tcp_poll(state->tcp_pcb, NULL, 0); tcp_sent(state->tcp_pcb, NULL); tcp_recv(state->tcp_pcb, NULL); tcp_err(state->tcp_pcb, NULL); err = tcp_close(state->tcp_pcb); if (err != ERR_OK) { DEBUG_printf("close failed %d, calling abort\n", err); tcp_abort(state->tcp_pcb); err = ERR_ABRT; } state->tcp_pcb = NULL; } return err; } static err_t tcp_client_sent(void *arg, struct tcp_pcb *tpcb, u16_t len) { MB_SERVER_T *state = (MB_SERVER_T*)arg; // DEBUG_printf("tcp_client_sent %u\n", len); return ERR_OK; } static err_t tcp_client_connected(void *arg, struct tcp_pcb *tpcb, err_t err) { MB_SERVER_T *state = (MB_SERVER_T*)arg; if (err != ERR_OK) { printf("connect failed %d\n", err); if (strcmp(state->server_host_name, "picow_mb_leds.local") == 0) { lv_label_set_text(slave2_label, "Slave2:" LV_SYMBOL_CLOSE); } else { lv_label_set_text(slave1_label, "Slave1:" LV_SYMBOL_CLOSE); } state->connected = false; return err; } if (strcmp(state->server_host_name, "picow_mb_leds.local") == 0) { lv_label_set_text(slave2_label, "Slave2:" LV_SYMBOL_OK); } else { lv_label_set_text(slave1_label, "Slave1:" LV_SYMBOL_OK); } lv_timer_handler(); state->connected = true; DEBUG_printf("Waiting for buffer from server\n"); return ERR_OK; } static err_t tcp_client_poll(void *arg, struct tcp_pcb *tpcb) { MB_SERVER_T *state = (MB_SERVER_T*)arg; //DEBUG_printf("tcp_client_poll:%s\n", state->server_host_name); return ERR_OK; } static void tcp_client_err(void *arg, err_t err) { MB_SERVER_T *state = (MB_SERVER_T*)arg; if (err != ERR_ABRT) { DEBUG_printf("tcp_client_err %d\n", err); } DEBUG_printf("%s:tcp_client_err %d\n", state->server_host_name, err); } err_t tcp_client_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { MB_SERVER_T *state = (MB_SERVER_T*)arg; recv_queue_t *temp_recv; if (!p) { printf("connect close---------\n"); tcp_client_close(arg); return ERR_CLSD; } if (p->tot_len > 0) { temp_recv = (recv_queue_t*)malloc(sizeof(recv_queue_t)); //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); 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"); */ } else printf("total len =0"); pbuf_free(p); return ERR_OK; } static bool tcp_client_open(void *arg) { MB_SERVER_T *state = (MB_SERVER_T*)arg; state->connected = false; state->dns_found = false; dns_init(); cyw43_arch_lwip_begin(); err_t err = dns_gethostbyname(state->server_host_name, &(state->remote_addr), local_server_dns_found, state); cyw43_arch_lwip_end(); absolute_time_t timeout = get_absolute_time(); while (!state->dns_found && absolute_time_diff_us(timeout, get_absolute_time()) < 2000000) { //cyw43_arch_poll(); sleep_ms(100); } if (!state->dns_found) return false; DEBUG_printf("Connecting to %s port %u\n", ip4addr_ntoa(&state->remote_addr), MODBUS_TCP_PORT); lv_label_set_text_fmt(msg_label, "Connecting to %s port %u\n", ip4addr_ntoa(&state->remote_addr), MODBUS_TCP_PORT); lv_timer_handler(); state->tcp_pcb = tcp_new_ip_type(IP_GET_TYPE(&state->remote_addr)); if (!state->tcp_pcb) { DEBUG_printf("failed to create pcb\n"); lv_label_set_text(msg_label,"Failed to create pcb"); lv_timer_handler(); return false; } tcp_arg(state->tcp_pcb, state); tcp_poll(state->tcp_pcb, tcp_client_poll, POLL_TIME_S * 2); tcp_sent(state->tcp_pcb, tcp_client_sent); tcp_recv(state->tcp_pcb, tcp_client_recv); tcp_err(state->tcp_pcb, tcp_client_err); cyw43_arch_lwip_begin(); err = tcp_connect(state->tcp_pcb, &state->remote_addr, MODBUS_TCP_PORT, tcp_client_connected); cyw43_arch_lwip_end(); lv_label_set_text(msg_label, ""); lv_timer_handler(); return err == err; } bool modbus_tcp_client_init(MB_SERVER_T* mb_server) { queue_init(&mb_server->recv_queue, sizeof(recv_queue_t), 4); if (!tcp_client_open(mb_server)) { //return false; } return true; } // Perform initialisation bool picow_tcp_client_init() { cyw43_arch_enable_sta_mode(); printf("Connecting to Wi-Fi...\n"); lv_label_set_text(msg_label,"Connecting to Wi-Fi..."); lv_timer_handler(); if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { printf("failed to connect.\n"); return false; } else { printf("Connected.\n"); } //============ modbus_sw_server = calloc(1, sizeof(MB_SERVER_T)); if (!modbus_sw_server) { lv_label_set_text(msg_label,"failed to allocate state"); lv_timer_handler(); DEBUG_printf("failed to allocate state\n"); return false; } modbus_sw_server->queue_left=0; modbus_sw_server->start_index=0; modbus_sw_server->recv_count=0; strcpy(modbus_sw_server->server_host_name, "picow_mb_leds.local"); if (!modbus_tcp_client_init(modbus_sw_server)) { printf("modbus Switches server init failuer!\n"); lv_label_set_text(msg_label,"modbus Switches server init failuer!"); return false; } //=========== modbus_ht_server = calloc(1, sizeof(MB_SERVER_T)); if (!modbus_ht_server) { lv_label_set_text(msg_label,"failed to allocate state"); lv_timer_handler(); DEBUG_printf("failed to allocate state\n"); return false; } modbus_ht_server->queue_left=0; modbus_ht_server->start_index=0; modbus_ht_server->recv_count=0; strcpy(modbus_ht_server->server_host_name, "picow_mb_ht.local"); if (!modbus_tcp_client_init(modbus_ht_server)) { printf("modbus Humi-Temp server init failuer!\n"); lv_label_set_text(msg_label,"modbus Humi-Temp server init failuer!"); return false; } lv_label_set_text(msg_label,""); lv_timer_handler(); return true; } err_t picow_tcp_client_close(void *arg) { return tcp_client_close(arg); } bool picow_tcp_client_open(void *arg) { return tcp_client_open(arg); }
- picow_tcp_client.h
#ifndef __PICOW_TCP_CLIENT__ #define __PCIOW_TCP_CLIENT__ #include "lwip/tcp.h" #include "pico/util/queue.h" typedef struct __recv_queue_t{ uint8_t buffer[260]; uint16_t buffer_len; } recv_queue_t; typedef struct TCP_CLIENT_T_ { struct tcp_pcb *tcp_pcb; ip_addr_t remote_addr; 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; recv_queue_t temp_recv; } MB_SERVER_T; bool picow_tcp_client_init(); err_t picow_tcp_client_close(void *arg); bool picow_tcp_client_open(void *arg); #endif
沒有留言:
張貼留言