本影片以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(arg);
return ERR_CLSD;
}
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