prettyprint

2025年1月13日 星期一

[Raspberry Pi Pico W] MODBUS TCP

本影片以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: 
內容與長度依Function code而有不同內容。


詳細內容請參閱:https://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf

成果影片:

程式碼:

nanoMODBUS library 配合本專案以queue儲存tcp/ip packet, nanomodbus.c程式碼作如下的修改:

modbus slave(modbus Server, tcp server)端程式碼:


  • 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_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)端程式碼:


# 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
  









2024年10月10日 星期四

[Raspberry Pi Pico SDK] Stepper motor driver and its rotation angle, speed and direction controlled by LVGL UI

 本文章介紹利用Raspberry Pi Pico 微處理器,以GPIO 控制步進馬達(28BYJ-48 與ULN2003馬達驅動器),以LVGL 使用者介面控制馬達的選轉速度、方向與角度。

一、28BYJ-48步進順序

  • full step:

相對應的程式碼:


  • half step:



相對應的程式碼:



二、LVGL使用者介面:


  • 設定旋轉速度RPM:1~12
  • 設定步進馬達角度0度位置(set 0 pos button)
  • 旋轉任意角度。
  • 順時針或反時針旋轉360度。
詳細操作過程請參閱成果展示,文末附有詳細程式碼。

三、成果展示



四、程式碼



  • picow_lvgl_step_motor.c(主程式碼)
#include <stdio.h>
#include "pico/stdlib.h"
#include "steppermotor.h"
#include "pico_lvgl.h"

// TFT PIO setting
PIO TFT_PIO = pio0;
#define TFT_SM 0
#define TFT_SDI_GPIO 9
#define TFT_CSX_DCX_SCK_GPIO 6 // CSX=8, DCX=7, SCK=6, SIDE_SET

uint16_t motor_pos=0;  
static uint16_t new_motor_pos = 0;
static uint8_t rpm_value=10;
static lv_obj_t* bar_label;
static lv_obj_t *motor_pos_arc;
static lv_obj_t * arc_label;

void inc_button_event_cb(lv_event_t *e) {
    lv_obj_t * arc = lv_event_get_target(e);
    lv_obj_t * bar = lv_event_get_user_data(e);
    int32_t v = lv_bar_get_value(bar);
    rpm_value=v+1;
    if (rpm_value > 12) rpm_value=12;
    lv_bar_set_value(bar, rpm_value, LV_ANIM_ON);
    lv_label_set_text_fmt(bar_label, "%d", rpm_value);
    stepperMotor_set_speed(rpm_value);
}

void dec_button_event_cb(lv_event_t *e) {
    lv_obj_t * arc = lv_event_get_target(e);
    lv_obj_t * bar = lv_event_get_user_data(e);
    int32_t v = lv_bar_get_value(bar);
    rpm_value=v-1;
    if (rpm_value <=0) rpm_value=1;
    lv_bar_set_value(bar, rpm_value, LV_ANIM_ON);
    lv_label_set_text_fmt(bar_label, "%d", rpm_value);
    stepperMotor_set_speed(rpm_value);
}

void onc_revo_c_event_cb(lv_event_t *e) {
    stepperMotor_rotate_angle(360, true);
}

void onc_revo_cc_event_cb(lv_event_t *e) {
    stepperMotor_rotate_angle(360, false);
}

void set_pos_zero_event_cb(lv_event_t *e) {
    new_motor_pos=motor_pos=0;
    lv_arc_set_value(motor_pos_arc, 0);
    lv_label_set_text_fmt(arc_label,"%d  \xC2\xB0",0);
}

static void motor_pos_changed_event_cb(lv_event_t * e)
{  
    lv_obj_t * arc = lv_event_get_target(e);
    lv_obj_t * label = lv_event_get_user_data(e);
    new_motor_pos=lv_arc_get_value(arc);
    lv_label_set_text_fmt(label, "%d \xC2\xB0", new_motor_pos);    
}

static void bar_value_event_cb(lv_event_t * e)
{
    lv_obj_t * bar = lv_event_get_target(e);
    lv_obj_t * label = lv_event_get_user_data(e);
    lv_label_set_text_fmt(label, "%d", lv_bar_get_value(bar));
}

void setp_motor_control_pad() {
    motor_pos_arc = lv_arc_create(lv_scr_act());
    arc_label = lv_label_create(motor_pos_arc);
    lv_obj_set_style_text_font(arc_label, &lv_font_montserrat_18, 0);
    lv_obj_center(arc_label);
    lv_obj_set_style_bg_color(motor_pos_arc, lv_color_hex(0xff0000), LV_PART_KNOB);
    lv_obj_set_size(motor_pos_arc, 200,200);
    lv_obj_center(motor_pos_arc);
    lv_arc_set_bg_angles(motor_pos_arc, 0, 360);
    lv_arc_set_range(motor_pos_arc, 0, 360);
    lv_arc_set_rotation(motor_pos_arc, 270);
    lv_arc_set_value(motor_pos_arc, 0);
    lv_obj_add_event_cb(motor_pos_arc, motor_pos_changed_event_cb, LV_EVENT_VALUE_CHANGED, arc_label);

    lv_obj_t *rpm = lv_obj_create(lv_scr_act());
    lv_obj_set_style_pad_all(rpm, 1, 0);
    lv_obj_set_size(rpm, 100, 300);
    lv_obj_align(rpm, LV_ALIGN_TOP_LEFT, 10,10);
    lv_obj_t *rpm_bar = lv_bar_create(rpm);
    bar_label = lv_label_create(rpm_bar);
    lv_obj_center(bar_label);
    lv_obj_set_style_text_color(bar_label, lv_color_white(), 0);
    lv_obj_add_event_cb(rpm_bar, bar_value_event_cb, LV_EVENT_VALUE_CHANGED, bar_label);
    lv_obj_set_size(rpm_bar, 30, 180);
    lv_obj_align(rpm_bar, LV_ALIGN_TOP_MID, 0, 30);
    lv_bar_set_range(rpm_bar, 0, 12);
    lv_bar_set_value(rpm_bar, rpm_value, LV_ANIM_ON);
    lv_obj_t* rpm_label = lv_label_create(rpm);
    
    lv_obj_set_size(rpm_label, 40,40);
    lv_obj_align(rpm_label, LV_ALIGN_TOP_MID, 0,0);
    lv_label_set_text(rpm_label, "RPM");
    lv_obj_t *inc_button = lv_btn_create(rpm);
    lv_obj_t *dec_button = lv_btn_create(rpm);
    lv_obj_align(inc_button, LV_ALIGN_BOTTOM_MID, +20, -5);
    lv_obj_align(dec_button, LV_ALIGN_BOTTOM_MID, -20, -5);
    lv_obj_set_size(inc_button, 36,36);
    lv_obj_set_size(dec_button, 36,36);
    lv_obj_t *btn_label = lv_label_create(inc_button);
    lv_obj_center(btn_label);
    lv_label_set_text(btn_label, LV_SYMBOL_PLUS);
    btn_label = lv_label_create(dec_button);
    lv_obj_center(btn_label);
    lv_label_set_text(btn_label, LV_SYMBOL_MINUS);

    lv_obj_add_event_cb(inc_button, inc_button_event_cb, LV_EVENT_CLICKED, rpm_bar);
    lv_obj_add_event_cb(dec_button, dec_button_event_cb, LV_EVENT_CLICKED, rpm_bar);

    lv_event_send(motor_pos_arc, LV_EVENT_VALUE_CHANGED, NULL);
    lv_event_send(rpm_bar, LV_EVENT_VALUE_CHANGED, NULL);

    lv_obj_t *one_revo_c = lv_btn_create(lv_scr_act());
    lv_obj_t *one_revo_cc = lv_btn_create(lv_scr_act());
    lv_obj_t *set_pos_zero = lv_btn_create(lv_scr_act());
    lv_obj_set_size(one_revo_c, 100, 50);
    lv_obj_set_size(one_revo_cc, 100, 50);
    lv_obj_set_size(set_pos_zero, 100, 50);
    lv_obj_align(one_revo_c, LV_ALIGN_TOP_RIGHT, -10, 10);
    lv_obj_align(one_revo_cc, LV_ALIGN_TOP_RIGHT, -10, 100);
    lv_obj_align(set_pos_zero, LV_ALIGN_BOTTOM_RIGHT, -10, -10);
    btn_label = lv_label_create(one_revo_c);
    lv_label_set_text(btn_label, "Clockwise");
    lv_obj_center(btn_label);
    btn_label = lv_label_create(one_revo_cc);
    lv_label_set_text(btn_label, "CounterClock");
    lv_obj_center(btn_label);
    btn_label = lv_label_create(set_pos_zero);
    lv_label_set_text(btn_label, "set 0 pos");
    lv_obj_center(btn_label);
    lv_obj_add_event_cb(one_revo_c, onc_revo_c_event_cb, LV_EVENT_CLICKED, rpm_bar);
    lv_obj_add_event_cb(one_revo_cc, onc_revo_cc_event_cb, LV_EVENT_CLICKED, rpm_bar);
    lv_obj_add_event_cb(set_pos_zero, set_pos_zero_event_cb, LV_EVENT_CLICKED, rpm_bar);
}


int main()
{
    stdio_init_all();
    stepperMotor_init(18,19,20,21); //No need to use contiguous GPIO pins
    stepperMotor_set_speed(rpm_value);
    stepperMotor_rotate_angle(360, true);  

    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();
    setp_motor_control_pad();

    while (1) {
        lv_timer_handler();
        sleep_ms(5);

        if (new_motor_pos > motor_pos) {
            stepperMotor_rotate_angle(new_motor_pos - motor_pos, true);
        } else {
            stepperMotor_rotate_angle(motor_pos-new_motor_pos, false);
        }
        if (new_motor_pos != motor_pos)
            motor_pos = new_motor_pos; 
  
    }    
}

  

  • 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 NEVER EDIT THE NEXT LINES for Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
    set(USERHOME $ENV{USERPROFILE})
else()
    set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.0.0)
set(toolchainVersion 13_2_Rel1)
set(picotoolVersion 2.0.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_lvgl_step_motor 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_lvgl_step_motor picow_lvgl_step_motor.c )

pico_set_program_name(picow_lvgl_step_motor "picow_lvgl_step_motor")
pico_set_program_version(picow_lvgl_step_motor "0.1")

# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(picow_lvgl_step_motor 1)
pico_enable_stdio_usb(picow_lvgl_step_motor 0)

# Add the standard library to the build
target_link_libraries(picow_lvgl_step_motor
        pico_stdlib)

# Add the standard include files to the build
target_include_directories(picow_lvgl_step_motor PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)

# Add any user requested libraries
add_subdirectory(GPIO_StepperMotor)
target_link_libraries(picow_lvgl_step_motor
    gpio_stepper_motor
)
add_subdirectory(pico_lvgl)
target_link_libraries(picow_lvgl_step_motor
    pico_lvgl
)

pico_add_extra_outputs(picow_lvgl_step_motor)


  

stepper motor gpio driver code:

  • steppermotor.c

#include <stdio.h>
#include "pico/stdlib.h"
#include "steppermotor.h"

#define MAX_SPEED_RPM               12
#define MIN_SPEED_RPM               0.1 

#define STEPS_PER_REVOLUTION_FULL     2048  // 28BYJ-48 STEPPER MOTOR
#define STEPS_PER_REVOLUTION_HALF     4096
#define SETP_MOTOR_MIN_SPEED_DELAY_US 2500
#define STEP_MOTOR_TYPE               3        // half or full step
static uint32_t step_seq[8]= {0,0,0,0,0,0,0,0};  // full step :4, half step :8
static int8_t step_pos=0;
static uint32_t stepMotor_step_delay=SETP_MOTOR_MIN_SPEED_DELAY_US;
static uint32_t stepMotor_pins;
static uint16_t stepsPerRevolution;
static uint8_t totalSteps=4;

void stepperMotor_init(uint8_t in1,uint8_t in2,uint8_t in3,uint8_t in4) {
    stepMotor_pins = 1<<in1 | 1<<in2 | 1 << in3 | 1<< in4;
    gpio_init_mask(stepMotor_pins);
    gpio_set_dir_masked(stepMotor_pins, stepMotor_pins);
    #if STEP_MOTOR_TYPE == 1
        step_seq[0] = 1 << in1;
        step_seq[1] = 1 << in2;
        step_seq[2] = 1 << in3;
        step_seq[3] = 1 << in4;
        totalSteps=4;
        stepsPerRevolution=STEPS_PER_REVOLUTION_FULL;
    #elif STEP_MOTOR_TYPE == 2
        step_seq[0] = 1 << in1 | 1 << in2;
        step_seq[1] = 1 << in2 | 1 << in3;
        step_seq[2] = 1 << in3 | 1 << in4;
        step_seq[3] = 1 << in4 | 1 << in1;
        totalSteps=4;
        stepsPerRevolution=STEPS_PER_REVOLUTION_FULL;
    #elif STEP_MOTOR_TYPE == 3
        step_seq[0] = 1 << in1;
        step_seq[1] = 1 << in1 | 1 << in2;
        step_seq[2] = 1 << in2;
        step_seq[3] = 1 << in2 | 1 << in3;
        step_seq[4] = 1 << in3;
        step_seq[5] = 1 << in3 | 1 << in4;
        step_seq[6] = 1 << in4;
        step_seq[7] = 1 << in4 | 1 << in1;
        totalSteps=8;
        stepsPerRevolution=STEPS_PER_REVOLUTION_HALF;
    #else 
        #error STEP_MOTOR_TORGUE value only 1,2 or 3.
    #endif 
    gpio_put_masked(stepMotor_pins, step_seq[step_pos]);
    

}

/* speed range: 0.1~12 rpm*/
void stepperMotor_set_speed(float rpm) {
    if (rpm < MIN_SPEED_RPM) rpm = MIN_SPEED_RPM;
    if (rpm > MAX_SPEED_RPM) rpm = MAX_SPEED_RPM;
    stepMotor_step_delay = 60*1000*1000/(stepsPerRevolution*rpm);
}

void stepperMotor_rotate_angle(uint16_t angle, bool clockwise) {
    uint16_t steps = angle*stepsPerRevolution/360;  // angle to steps
    if (clockwise) {
        for (int i=0; i < steps; i++) {
            step_pos = (++step_pos)%totalSteps;
            gpio_put_masked(stepMotor_pins, step_seq[step_pos]);
            sleep_us(stepMotor_step_delay);
        }
    } else {
        for (int i=0; i < steps; i++) {
            step_pos = (--step_pos+totalSteps)%totalSteps;
            gpio_put_masked(stepMotor_pins, step_seq[step_pos]);
            sleep_us(stepMotor_step_delay);
   
        }
        
    } 
    //gpio_put_masked(stepMotor_pins, 0);
}


  
  • stepermotor.h
#ifndef _STEP_MOTOR_
#define _STEP_MOTOR_
void stepperMotor_init(uint8_t in1,uint8_t in2,uint8_t in3,uint8_t in4);
void stepperMotor_rotate_angle(uint16_t angle, bool clockwise);
void stepperMotor_set_speed(float rpm);

#endif //_STEP_MOTOR
  
  • CMakeLists.txt
add_library(gpio_stepper_motor INTERFACE)

target_sources(gpio_stepper_motor INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/steppermotor.c
)

target_include_directories(gpio_stepper_motor INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
)

target_link_libraries(gpio_stepper_motor INTERFACE
        pico_stdlib
)

  





2024年9月21日 星期六

STM32 HAL || Large 7-Segment LED with WS2812 || Rotary Encoder || STM32F103C8T6

 本文章介紹利用WS2812燈條來製作一個大型的七段式顯示器,用來顯示SHT40的溫濕度。利用Rotary encoder來設定七段式顯示器的顏色。MCU 使用STM32F103C8T6。


WS2812每個0或1的編碼方式與時序。

STM32F103C8T6使用SPI介面來輸出波形。
RCC Clock 設定40MHz

SPI baud rate:

SPI GPIO:



Rotary Encoder Timer設定:

成果展示:



程式碼:

main.c
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ws2812.h"
#include "sht40.h"
#include "math.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
I2C_HandleTypeDef hi2c1;

SPI_HandleTypeDef hspi1;
DMA_HandleTypeDef hdma_spi1_tx;

TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim3;

/* USER CODE BEGIN PV */
// 7-segment digit
uint8_t digit[13][7] = {
    {1,1,1,1,1,1,0}, //0
    {1,0,0,0,0,1,0}, //1
    {0,1,1,0,1,1,1}, //2
    {1,1,0,0,1,1,1}, //3
    {1,0,0,1,0,1,1}, //4
    {1,1,0,1,1,0,1}, //5
    {1,1,1,1,1,0,1}, //6
    {1,0,0,0,1,1,0}, //7
    {1,1,1,1,1,1,1}, //8
    {1,0,0,1,1,1,1}, //9
	{0,0,0,1,1,1,1}, // degree
	{0,1,1,1,1,0,0}, // C
	{1,1,1,0,0,0,1}, // low%
};
uint8_t rgb_color[3] = {128,128,128};
enum {
	SET_RED=0,
	SET_GREEN=1,
	SET_BLUE=2,
	SET_COUNT=3,
};
uint8_t rgb_set_index=SET_RED;
int16_t rgb_set_start_counter=0;

uint8_t sw_state=1;
uint8_t set_color_mode=0;
uint8_t long_press_count=0;
int16_t counter=0;

ws2812_pixel_color_t pixels_color;

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_SPI1_Init(void);
static void MX_I2C1_Init(void);
static void MX_TIM2_Init(void);
static void MX_TIM3_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void display_digit(uint8_t number) {

    for (int i=0; i < 7; i++) {
        if (digit[number][i] == 1) {
        	for (int k=0; k < 3;k++) ws2812_display_pixel(&pixels_color,1);
        }
        else {
        	for (int k=0; k < 3;k++) ws2812_display_pixel(&pixels_color,0);
        }
    }
}

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef* htim) {
	if (set_color_mode) {
		counter = (int16_t)__HAL_TIM_GET_COUNTER(htim);
		rgb_color[rgb_set_index] += (counter - rgb_set_start_counter);
		rgb_set_start_counter = counter;
		ws2812_set_pixel_color(&pixels_color,0,0,rgb_color[SET_BLUE]);
		display_digit(1);
		ws2812_set_pixel_color(&pixels_color,0,rgb_color[SET_GREEN],0);
		display_digit(1);
		ws2812_set_pixel_color(&pixels_color,rgb_color[SET_RED],0,0);
		display_digit(1);
		ws2812_set_pixel_color(&pixels_color,rgb_color[SET_RED],rgb_color[SET_GREEN],rgb_color[SET_BLUE]);
		display_digit(1);
	}
}


void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
	if (HAL_GPIO_ReadPin(RT_SW_GPIO_Port, RT_SW_Pin) == GPIO_PIN_SET) {
		HAL_TIM_Base_Stop_IT(&htim2);
		if (long_press_count > 20) {
			set_color_mode = !set_color_mode;
		}
		long_press_count=0;
		sw_state = 1;

		rgb_set_index = (rgb_set_index+1) % SET_COUNT;
		rgb_set_start_counter = (int16_t)__HAL_TIM_GET_COUNTER(&htim3);

		if (set_color_mode){
			ws2812_set_pixel_color(&pixels_color,0,0,rgb_color[SET_BLUE]);
			display_digit(1);
			ws2812_set_pixel_color(&pixels_color,0,rgb_color[SET_GREEN],0);
			display_digit(1);
			ws2812_set_pixel_color(&pixels_color,rgb_color[SET_RED],0,0);
			display_digit(1);
			ws2812_set_pixel_color(&pixels_color,rgb_color[SET_RED],rgb_color[SET_GREEN],rgb_color[SET_BLUE]);
			display_digit(1);
		}
	} else {
		long_press_count++;
	}

}


void HAL_GPIO_EXTI_Callback(uint16_t gpio) {
	if (gpio == RT_SW_Pin && sw_state) {
		sw_state=0;
		HAL_TIM_Base_Start_IT(&htim2);
	}
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_SPI1_Init();
  MX_I2C1_Init();
  MX_TIM2_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */

  HAL_TIM_Encoder_Start_IT(&htim3, TIM_CHANNEL_ALL);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  ws2812_set_pixel_color(&pixels_color,rgb_color[SET_RED],rgb_color[SET_GREEN],rgb_color[SET_BLUE]);


 uint8_t temp, digit0, digit1;
 float sht40_temp, sht40_humi;
 while (1)
 {
	sht40_get_th_data(&sht40_temp, &sht40_humi);
	temp = (uint8_t) round(sht40_temp);
	digit0 = temp%10;
	digit1 = temp/10;
	if(!set_color_mode) {
	  display_digit(11);
	  display_digit(10);
	  display_digit(digit0);
	  display_digit(digit1);
	  HAL_Delay(2000);
	}

	temp = (uint8_t) round(sht40_humi);
	digit0 = temp%10;
	digit1 = temp/10;
	if(!set_color_mode) {
	  display_digit(12);
	  display_digit(10);
	  display_digit(digit0);
	  display_digit(digit1);
	  HAL_Delay(2000);
	}

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL5;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief I2C1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2C1_Init(void)
{

  /* USER CODE BEGIN I2C1_Init 0 */

  /* USER CODE END I2C1_Init 0 */

  /* USER CODE BEGIN I2C1_Init 1 */

  /* USER CODE END I2C1_Init 1 */
  hi2c1.Instance = I2C1;
  hi2c1.Init.ClockSpeed = 100000;
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c1.Init.OwnAddress1 = 0;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2C1_Init 2 */

  /* USER CODE END I2C1_Init 2 */

}

/**
  * @brief SPI1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_SPI1_Init(void)
{

  /* USER CODE BEGIN SPI1_Init 0 */

  /* USER CODE END SPI1_Init 0 */

  /* USER CODE BEGIN SPI1_Init 1 */

  /* USER CODE END SPI1_Init 1 */
  /* SPI1 parameter configuration*/
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_1LINE;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */

  /* USER CODE END SPI1_Init 2 */

}

/**
  * @brief TIM2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM2_Init(void)
{

  /* USER CODE BEGIN TIM2_Init 0 */

  /* USER CODE END TIM2_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM2_Init 1 */

  /* USER CODE END TIM2_Init 1 */
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 2000-1;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 1000-1;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM2_Init 2 */

  /* USER CODE END TIM2_Init 2 */

}

/**
  * @brief TIM3 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM3_Init(void)
{

  /* USER CODE BEGIN TIM3_Init 0 */

  /* USER CODE END TIM3_Init 0 */

  TIM_Encoder_InitTypeDef sConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM3_Init 1 */

  /* USER CODE END TIM3_Init 1 */
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 0;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 65535;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
  sConfig.IC1Polarity = TIM_ICPOLARITY_FALLING;
  sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC1Filter = 0;
  sConfig.IC2Polarity = TIM_ICPOLARITY_FALLING;
  sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC2Filter = 0;
  if (HAL_TIM_Encoder_Init(&htim3, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM3_Init 2 */

  /* USER CODE END TIM3_Init 2 */

}

/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Channel3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel3_IRQn);

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin : RT_SW_Pin */
  GPIO_InitStruct.Pin = RT_SW_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(RT_SW_GPIO_Port, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(EXTI3_IRQn);

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

ws2812.c

#include "main.h"
#include "ws2812.h"
#include "string.h"

extern SPI_HandleTypeDef hspi1;
extern DMA_HandleTypeDef hdma_spi1_tx;

void ws2812_set_pixel_color(ws2812_pixel_color_t *ws2812_pixels_color, uint8_t red, uint8_t green, uint8_t blue) {
	ws2812_pixels_color->green.bit0 = green >> 0 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->green.bit1 = green >> 1 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->green.bit2 = green >> 2 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->green.bit3 = green >> 3 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->green.bit4 = green >> 4 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->green.bit5 = green >> 5 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->green.bit6 = green >> 6 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->green.bit7 = green >> 7 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;

	ws2812_pixels_color->red.bit0 = red >> 0 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->red.bit1 = red >> 1 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->red.bit2 = red >> 2 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->red.bit3 = red >> 3 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->red.bit4 = red >> 4 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->red.bit5 = red >> 5 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->red.bit6 = red >> 6 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->red.bit7 = red >> 7 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;

	ws2812_pixels_color->blue.bit0 = blue >> 0 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->blue.bit1 = blue >> 1 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->blue.bit2 = blue >> 2 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->blue.bit3 = blue >> 3 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->blue.bit4 = blue >> 4 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->blue.bit5 = blue >> 5 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->blue.bit6 = blue >> 6 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
	ws2812_pixels_color->blue.bit7 = blue >> 7 & 0x01 ? WS2812_BIT_1:WS2812_BIT_0;
}

void ws2812_display_pixel(ws2812_pixel_color_t *ws2812_pixels_color,uint8_t show) {
	if (show) {
		HAL_SPI_Transmit(&hspi1, (uint8_t*)ws2812_pixels_color, WS2812_PIXEL_BYTES, 1000);
	} else {
		ws2812_pixel_color_t black;
		ws2812_set_pixel_color(&black, 0, 0, 0);
		HAL_SPI_Transmit(&hspi1, (uint8_t*)&black  , WS2812_PIXEL_BYTES, 1000);
	}
}



ws2812.h

#ifndef INC_WS2812_H_
#define INC_WS2812_H_

#define WS2812_BIT_1 0b110
#define WS2812_BIT_0 0b100

#define WS2812_PIXEL_BYTES	12 // 3*4, 4 bytes for red, green or blue

typedef	struct {
	uint8_t bit6:4;  //low nibble
	uint8_t bit7:4;  //high nibble for byte one
	uint8_t bit4:4;
	uint8_t bit5:4;
	uint8_t bit2:4;
	uint8_t bit3:4;
	uint8_t bit0:4;
	uint8_t bit1:4;
} ws2812_color_bits_t;

typedef struct {
	ws2812_color_bits_t green;
	ws2812_color_bits_t red;
	ws2812_color_bits_t blue;
}ws2812_pixel_color_t;


void ws2812_display_pixel(ws2812_pixel_color_t *ws2812_pixels_color,uint8_t show);
void ws2812_set_pixel_color(ws2812_pixel_color_t *ws2812_pixels_color, uint8_t red, uint8_t green, uint8_t blue);


#endif /* INC_WS2812_H_ */

sht40.c

#include "sht40.h"


void sht40_get_th_data(float *temp, float *humi) {
        uint8_t buff[8];

        float t_ticks, rh_ticks, t_degC, rh_pRH;
        buff[0] = 0xFD;
        //ret = i2c_write_blocking(I2C_SHT40_PORT, 0x44, buff,1,false);
        HAL_I2C_Master_Transmit(&hi2c1, 0x44<<1, buff, 1, 1000);
        HAL_Delay(10);
        //ret = i2c_read_blocking(I2C_SHT40_PORT, 0x44, buff, 6, false);
        HAL_I2C_Master_Receive(&hi2c1, 0x44<<1, buff, 6,1000);
        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);
}



sht40.h

#ifndef __SHT40_H__
#define __SHT40_H__
#include "main.h"
extern I2C_HandleTypeDef hi2c1;


void sht40_get_th_data(float* temp, float* humi);

#endif

WS2812_F103.ioc

#MicroXplorer Configuration settings - do not modify
CAD.formats=
CAD.pinconfig=
CAD.provider=
Dma.Request0=SPI1_TX
Dma.RequestsNb=1
Dma.SPI1_TX.0.Direction=DMA_MEMORY_TO_PERIPH
Dma.SPI1_TX.0.Instance=DMA1_Channel3
Dma.SPI1_TX.0.MemDataAlignment=DMA_MDATAALIGN_BYTE
Dma.SPI1_TX.0.MemInc=DMA_MINC_ENABLE
Dma.SPI1_TX.0.Mode=DMA_NORMAL
Dma.SPI1_TX.0.PeriphDataAlignment=DMA_PDATAALIGN_BYTE
Dma.SPI1_TX.0.PeriphInc=DMA_PINC_DISABLE
Dma.SPI1_TX.0.Priority=DMA_PRIORITY_LOW
Dma.SPI1_TX.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority
File.Version=6
GPIO.groupedBy=Group By Peripherals
KeepUserPlacement=false
Mcu.CPN=STM32F103C8T6
Mcu.Family=STM32F1
Mcu.IP0=DMA
Mcu.IP1=I2C1
Mcu.IP2=NVIC
Mcu.IP3=RCC
Mcu.IP4=SPI1
Mcu.IP5=SYS
Mcu.IP6=TIM2
Mcu.IP7=TIM3
Mcu.IPNb=8
Mcu.Name=STM32F103C(8-B)Tx
Mcu.Package=LQFP48
Mcu.Pin0=PD0-OSC_IN
Mcu.Pin1=PD1-OSC_OUT
Mcu.Pin10=PB7
Mcu.Pin11=VP_SYS_VS_Systick
Mcu.Pin12=VP_TIM2_VS_ClockSourceINT
Mcu.Pin2=PA5
Mcu.Pin3=PA7
Mcu.Pin4=PA13
Mcu.Pin5=PA14
Mcu.Pin6=PB3
Mcu.Pin7=PB4
Mcu.Pin8=PB5
Mcu.Pin9=PB6
Mcu.PinsNb=13
Mcu.ThirdPartyNb=0
Mcu.UserConstants=
Mcu.UserName=STM32F103C8Tx
MxCube.Version=6.12.0
MxDb.Version=DB.6.0.120
NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.DMA1_Channel3_IRQn=true\:0\:0\:false\:false\:true\:false\:true\:true
NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.EXTI3_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true
NVIC.ForceEnableDMAVector=true
NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4
NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:false
NVIC.TIM2_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true
NVIC.TIM3_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true
NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
PA13.Mode=Serial_Wire
PA13.Signal=SYS_JTMS-SWDIO
PA14.Mode=Serial_Wire
PA14.Signal=SYS_JTCK-SWCLK
PA5.Mode=Simplex_Bidirectional_Master
PA5.Signal=SPI1_SCK
PA7.Mode=Simplex_Bidirectional_Master
PA7.Signal=SPI1_MOSI
PB3.GPIOParameters=GPIO_PuPd,GPIO_Label,GPIO_ModeDefaultEXTI
PB3.GPIO_Label=RT_SW
PB3.GPIO_ModeDefaultEXTI=GPIO_MODE_IT_FALLING
PB3.GPIO_PuPd=GPIO_PULLUP
PB3.Locked=true
PB3.Signal=GPXTI3
PB4.Signal=S_TIM3_CH1
PB5.Signal=S_TIM3_CH2
PB6.Mode=I2C
PB6.Signal=I2C1_SCL
PB7.Mode=I2C
PB7.Signal=I2C1_SDA
PD0-OSC_IN.Mode=HSE-External-Oscillator
PD0-OSC_IN.Signal=RCC_OSC_IN
PD1-OSC_OUT.Mode=HSE-External-Oscillator
PD1-OSC_OUT.Signal=RCC_OSC_OUT
PinOutPanel.RotationAngle=0
ProjectManager.AskForMigrate=true
ProjectManager.BackupPrevious=false
ProjectManager.CompilerOptimize=6
ProjectManager.ComputerToolchain=false
ProjectManager.CoupleFile=false
ProjectManager.CustomerFirmwarePackage=
ProjectManager.DefaultFWLocation=true
ProjectManager.DeletePrevious=true
ProjectManager.DeviceId=STM32F103C8Tx
ProjectManager.FirmwarePackage=STM32Cube FW_F1 V1.8.6
ProjectManager.FreePins=false
ProjectManager.HalAssertFull=false
ProjectManager.HeapSize=0x200
ProjectManager.KeepUserCode=true
ProjectManager.LastFirmware=true
ProjectManager.LibraryCopy=1
ProjectManager.MainLocation=Core/Src
ProjectManager.NoMain=false
ProjectManager.PreviousToolchain=
ProjectManager.ProjectBuild=false
ProjectManager.ProjectFileName=WS2812_F103.ioc
ProjectManager.ProjectName=WS2812_F103
ProjectManager.ProjectStructure=
ProjectManager.RegisterCallBack=
ProjectManager.StackSize=0x400
ProjectManager.TargetToolchain=STM32CubeIDE
ProjectManager.ToolChainLocation=
ProjectManager.UAScriptAfterPath=
ProjectManager.UAScriptBeforePath=
ProjectManager.UnderRoot=true
ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_DMA_Init-DMA-false-HAL-true,4-MX_SPI1_Init-SPI1-false-HAL-true,5-MX_I2C1_Init-I2C1-false-HAL-true,6-MX_TIM2_Init-TIM2-false-HAL-true,7-MX_TIM3_Init-TIM3-false-HAL-true
RCC.ADCFreqValue=20000000
RCC.AHBFreq_Value=40000000
RCC.APB1CLKDivider=RCC_HCLK_DIV2
RCC.APB1Freq_Value=20000000
RCC.APB1TimFreq_Value=40000000
RCC.APB2Freq_Value=40000000
RCC.APB2TimFreq_Value=40000000
RCC.FCLKCortexFreq_Value=40000000
RCC.FamilyName=M
RCC.HCLKFreq_Value=40000000
RCC.IPParameters=ADCFreqValue,AHBFreq_Value,APB1CLKDivider,APB1Freq_Value,APB1TimFreq_Value,APB2Freq_Value,APB2TimFreq_Value,FCLKCortexFreq_Value,FamilyName,HCLKFreq_Value,MCOFreq_Value,PLLCLKFreq_Value,PLLMCOFreq_Value,PLLMUL,PLLSourceVirtual,SYSCLKFreq_VALUE,SYSCLKSource,TimSysFreq_Value,USBFreq_Value,VCOOutput2Freq_Value
RCC.MCOFreq_Value=40000000
RCC.PLLCLKFreq_Value=40000000
RCC.PLLMCOFreq_Value=20000000
RCC.PLLMUL=RCC_PLL_MUL5
RCC.PLLSourceVirtual=RCC_PLLSOURCE_HSE
RCC.SYSCLKFreq_VALUE=40000000
RCC.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK
RCC.TimSysFreq_Value=40000000
RCC.USBFreq_Value=40000000
RCC.VCOOutput2Freq_Value=8000000
SH.GPXTI3.0=GPIO_EXTI3
SH.GPXTI3.ConfNb=1
SH.S_TIM3_CH1.0=TIM3_CH1,Encoder_Interface
SH.S_TIM3_CH1.ConfNb=1
SH.S_TIM3_CH2.0=TIM3_CH2,Encoder_Interface
SH.S_TIM3_CH2.ConfNb=1
SPI1.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_16
SPI1.CalculateBaudRate=2.5 MBits/s
SPI1.Direction=SPI_DIRECTION_1LINE
SPI1.IPParameters=VirtualType,Mode,Direction,BaudRatePrescaler,CalculateBaudRate
SPI1.Mode=SPI_MODE_MASTER
SPI1.VirtualType=VM_MASTER
TIM2.IPParameters=Prescaler,Period
TIM2.Period=1000-1
TIM2.Prescaler=2000-1
TIM3.EncoderMode=TIM_ENCODERMODE_TI12
TIM3.IC1Polarity=TIM_ICPOLARITY_FALLING
TIM3.IC2Polarity=TIM_ICPOLARITY_FALLING
TIM3.IPParameters=EncoderMode,IC1Polarity,IC2Polarity
VP_SYS_VS_Systick.Mode=SysTick
VP_SYS_VS_Systick.Signal=SYS_VS_Systick
VP_TIM2_VS_ClockSourceINT.Mode=Internal
VP_TIM2_VS_ClockSourceINT.Signal=TIM2_VS_ClockSourceINT
board=custom
isbadioc=false