性无码一区二区三区在线观看,少妇被爽到高潮在线观看,午夜精品一区二区三区,无码中文字幕人妻在线一区二区三区,无码精品国产一区二区三区免费

  • 回復
  • 收藏
  • 點贊
  • 分享
  • 發(fā)新帖

【 DigiKey DIY原創(chuàng)大賽】基于樹莓派的 Modbus網(wǎng)關

引言

在工業(yè)控制中,Modbus 是十分普遍的一個通信協(xié)議,這里借此次比賽的機會,使用樹莓派4開發(fā)板來制作一個Modbus網(wǎng)關

開箱、點亮

活動過程中出了點小插曲,樹莓派的內存不知道怎么的壞了,然后換了一下,立馬給它打印了個外殼穿上防止再次受傷

然后為了方便操作,用HDMI采集器和鍵鼠KVM工具把樹莓派連接到電腦,插上系統(tǒng)卡開機

硬件設計

Modbus協(xié)議一般不是常用的TTL電平標準,一般會使用RS232和RS485電平標準,所以做了個USB轉RS232/RS485的一個模塊,原理圖如下

PCB的3D仿真圖

到手焊好的樣子

下面的撥動開關用來切換RS485還是RS232

軟件開發(fā)

對于Modbus這個已經(jīng)有了幾十年壽命的通信協(xié)議,在各個系統(tǒng)中都有各式各樣的開源軟件庫了,由于樹莓派也是基于Linux系統(tǒng)的,所以這里使用libmodbus來完成Modbus網(wǎng)關的開發(fā)

編譯安裝 libmodbus

使用終端工具和 SSH 連接到樹莓派,我這里使用的是 Tabby,安裝需要的工具包

sudo apt-get install automake autoconf libtool cmake git

進到 libmodbus 的文件夾,然后執(zhí)行自動初始化

./autogen.sh

./configure 完成自動化配置

然后執(zhí)行 make 和 sudo make install 命令

至此 libmodbus 就完成了編譯安裝

測試 libmodbus

硬件連接

硬件使用了兩個 Modbus 溫濕度傳感器,其中一個站號設置為 0x1,另一個設置成了 0x2,將兩個Modbus 溫濕度模塊和制作的 USB 轉 RS485/RS232 模塊連接到一條 Modbus 總線的 A、B上面,樹莓派通過 USB 連接到 USB 轉 RS485/RS232 模塊

測試例程

系統(tǒng)中已經(jīng)安裝好了 libmodbus 的庫,硬件也完成了連接,現(xiàn)在寫一個例程去調用 libmodbus 用 modbus RTU 協(xié)議讀取溫濕度傳感器的數(shù)據(jù),代碼如下,因為是 USB 轉 RS485,所以在系統(tǒng)中是 /dev/ttyUSB0

include <modbus/modbus.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main() {
    // 創(chuàng)建并初始化 Modbus 連接
    modbus_t *ctx = modbus_new_rtu("/dev/ttyUSB0", 9600, 'N', 8, 1);
    if (ctx == NULL) {
        fprintf(stderr, "Unable to create the libmodbus context\n");
        return -1;
    }

    // 設置從站地址(例如,從站地址為 0x01)
    modbus_set_slave(ctx, 0x01);

    // 嘗試打開串口
    if (modbus_connect(ctx) == -1) {
        fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

    // 讀取保持寄存器的數(shù)量和起始地址
    uint16_t reg[6];  // 用于存儲讀取到的6個保持寄存器的值
    int addr = 0;     // 從寄存器地址 0 開始讀取
    int num_regs = 6; // 讀取 6 個寄存器

    int rc = modbus_read_registers(ctx, addr, num_regs, reg);
    if (rc == -1) {
        fprintf(stderr, "Failed to read registers: %s\n", modbus_strerror(errno));
        modbus_close(ctx);
        modbus_free(ctx);
        return -1;
    }

    // 輸出讀取到的寄存器值
    for (int i = 0; i < num_regs; i++) {
        printf("Register %d: %d\n", addr + i, reg[i]);
    }

    // 關閉并釋放 Modbus 連接
    modbus_close(ctx);
    modbus_free(ctx);

    return 0;
}

執(zhí)行命令如下,就可以讀取到站號為1的溫濕度傳感器數(shù)據(jù)了,其中0和1地址的保持寄存器就是溫度和濕度數(shù)據(jù)

gcc test.c -o test -lmodbus
./test

適配 SDL2 和 LVGL

SDL2 是一個跨平臺的開發(fā)庫,用于簡化游戲和多媒體應用程序的開發(fā),LVGL是近些年十分流行的 GUI 開發(fā)框架,在本項目中使用了 SDL2 來作為 HMI 的驅動層,同時使用 LVGL 來作為應用層的框架來使用

首先安裝好 SDL2 的庫

sudo apt install libsdl2-dev

然后 clone 下來 lvgl-sdl 開源代碼

git clone https://github.com/Ryzee119/lvgl-sdl.git

進入倉庫修改 lv_conf.h 文件,使能 LV_USE_DEMO_WIDGETS 這個宏,然后修改 main.c 中的代碼,調用 lv_demo_widgets();

在 example 目錄中使用 cmake 來編譯工程

cmake -Bbuild
cmake --build build

編譯完成的樣子

進入到樹莓派的桌面環(huán)境,然后進入到 build 目錄中執(zhí)行 ./lv_examples,可以看到

LVGL 也適配完成了,然后就可以基于 LVGL 庫繪制屬于自己的 HMI 界面了

搭建網(wǎng)關 HMI

繪制網(wǎng)關 HMI

方才已經(jīng)完成了 libmodbus、SDL2、LVGL 的適配,接下來就是 HMI 界面的開發(fā)了,因為我連接的是兩個溫濕度傳感器,所以這里做了一個對于兩個從站的數(shù)據(jù)采集界面,如果按照商用網(wǎng)關的標準是需要實現(xiàn)增刪改查的功能,提供給用戶自己對從站的數(shù)量、HMI界面的自定義,本項目從簡之后的界面如下

使用的是 GUI Guider 進行的可視化設計,生成代碼后復制到樹莓派中

在 CMakeLists.txt 中添加 GUI Guider 生成的代碼和包含路徑

file(GLOB LVGL_DRIVER_FILES
    "${PROJECT_DIR}/main.c"
    "${PROJECT_DIR}/example.c"
    "${PROJECT_DIR}/assets/*.c"
    "${PROJECT_DIR}/gui/generated/*.c"
    "${PROJECT_DIR}/gui/generated/images/*.c"
    "${PROJECT_DIR}/gui/generated/guider_fonts/*.c"
    "${PROJECT_DIR}/gui/custom/*.c"
)
include_directories(${PROJECT_DIR}/gui/custom)
include_directories(${PROJECT_DIR}/gui/generated)
include_directories(${PROJECT_DIR}/gui/generated/images)
include_directories(${PROJECT_DIR}/gui/generated/guider_fonts)
include_directories(${PROJECT_DIR}/gui/generated/guider_customer_fonts)

然后將 main 函數(shù)中的的 lv_demo_widgets 替換為

    setup_ui(&guider_ui);
    custom_init(&guider_ui);

編譯運行

對接數(shù)據(jù)接口

使用 Linux 的 RT 庫來創(chuàng)建一個定時器如下

    timer_t timer_id;
    struct sigevent sev;
    struct itimerspec ts;

    // 設置定時器的事件通知方式為信號通知,指定回調函數(shù)
    sev.sigev_notify = SIGEV_THREAD;      // 使用線程回調
    sev.sigev_notify_function = timer_handler; // 定時器到期時調用的回調函數(shù)
    sev.sigev_notify_attributes = NULL;   // 默認屬性
    sev.sigev_value.sival_ptr = &timer_id; // 傳遞給回調函數(shù)的參數(shù)

    // 創(chuàng)建定時器
    if (timer_create(CLOCK_REALTIME, &sev, &timer_id) == -1) {
        perror("timer_create");
        exit(EXIT_FAILURE);
    }

    // 設置定時器:首次觸發(fā)延遲 1 秒,周期為 1 秒
    ts.it_value.tv_sec = 1; // 初始延遲 1 秒
    ts.it_value.tv_nsec = 0;
    ts.it_interval.tv_sec = 1; // 周期 1 秒
    ts.it_interval.tv_nsec = 0;

    // 設置定時器
    if (timer_settime(timer_id, 0, &ts, NULL) == -1) {
        perror("timer_settime");
        exit(EXIT_FAILURE);
    }

在定時器的回調函數(shù)中去更新界面的顯示

// 定時器回調函數(shù)
void timer_handler(union sigval arg) {
    // 這里寫定時器每次觸發(fā)時執(zhí)行的代碼
    printf("定時器觸發(fā),執(zhí)行任務...\n");

    {
        // 讀取保持寄存器的數(shù)量和起始地址
        uint16_t reg[2]= {0, 0};  // 用于存儲讀取到的6個保持寄存器的值
        int addr = 0;     // 從寄存器地址 0 開始讀取
        int num_regs = 2; // 讀取 2 個寄存器
        // 設置從站地址(例如,從站地址為 0x01)
        modbus_set_slave(ctx, 0x1);
        modbus_read_registers(ctx, addr, num_regs, reg);

        t1 = reg[0] / 10.0f;
        h1 = reg[1] / 10.0f;
    }
    usleep(5000);
    {
        // 讀取保持寄存器的數(shù)量和起始地址
        uint16_t reg[2] = {0, 0};  // 用于存儲讀取到的6個保持寄存器的值
        int addr = 0;     // 從寄存器地址 0 開始讀取
        int num_regs = 2; // 讀取 2 個寄存器
        // 設置從站地址(例如,從站地址為 0x02)
        modbus_set_slave(ctx, 0x2);
        modbus_read_registers(ctx, addr, num_regs, reg);

        t2 = reg[0] / 10.0f;
        h2 = reg[1] / 10.0f;
    }

    printf("T1: %02.1f H1: %02.1f\n", t1, h1);
    printf("T2: %02.1f H2: %02.1f\n", t2, h2);

    char buff[512];
    lv_ui *ui = &guider_ui;
    sprintf(buff, "從站 1: 溫度: %02.1f℃ 濕度: %02.1f%%", t1, h1);
 lv_label_set_text(ui->screen_label_1, buff);
    sprintf(buff, "從站 2: 溫度: %02.1f℃ 濕度: %02.1f%%", t2, h2);
 lv_label_set_text(ui->screen_label_2, buff);

    static int times = 0;
    if(times++ > 100)
        exit(0);
}

在這個回調函數(shù)中就實現(xiàn)了溫濕度的采集和對于 HMI 界面的數(shù)據(jù)更新任務,編譯運行結果如下

程序源碼

lv_modbus_master.zip

以下為視頻演示:

 

全部回復(0)
正序查看
倒序查看
現(xiàn)在還沒有回復呢,說說你的想法
發(fā)