本帖主要帮助用户快速熟悉新发布的柿饼M3 SDK 如何通过 JS 端与 C 端进行数据交互的使用。下面通过利用 DCM 模块实现 UI界面通过串口UART0和PC电脑进行 Modbus 通信的示例。
示例开始前:先来动图看看效果
UI 界面控制改变变量值
![](//file.elecfans.com/web2/M00/53/A4/pYYBAGLPh8yATYiPAAFFdiwYLRY856.jpg)
PC 界面控制修改变量值
![](//file.elecfans.com/web2/M00/53/A4/pYYBAGLPh8yAXXWhAAFMWWwfb3s812.jpg)
SDK 固件修改
一.安装Modbus软件包
在 software\firmware 中打开 ENV 软件,使用 menuconfig 命令。依次进入 -> RT-Thread online packages -> IoT - internet of things 选中 agile_modbus 按 Y 键进行 Modbus 软件包的添加。
![](//file.elecfans.com/web2/M00/53/08/poYBAGLPh8yALTGQAAE7vAQ0-mE418.jpg)
在 ENV 中执行 pkgs --update 命令,将软件包下载到本地,也可以在云端将软件包下载到路径 software\firmware\packages 中。完成后如下图所示。
在 ENV 中执行 scons --target=vsc 命令,可以生成 vscode 工程,这样可以更方便的使用 vscode 进行开发
![](//file.elecfans.com/web2/M00/53/A4/pYYBAGLPh8yADPUHAABqIsHtKiM716.jpg)
二、对Modbus软件包进行移植配置
在路径 software\firmware 中创建目录 dcm_modbus ,并创建 SConscript 文件。
SConscript
from building import *
import rtconfig
cwd = GetCurrentDir()
src = Glob('.c') + Glob('.cpp')
CPPPATH = [cwd]
group = DefineGroup('dcm_modbus_demo', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
在 software\drivers\drv_uart.c 中打开 uart0 的初始化函数:
![](//file.elecfans.com/web2/M00/53/08/poYBAGLPh8yAO6xrAACQHHz3fIA820.jpg)
移植 agile_modbus 的串口接口:
serial_port.c
#include "serial_port.h"
#include <board.h>
#define DBG_TAG "serialP"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
static struct rt_serial_device *serial = RT_NULL;
static rt_sem_t _rx_sem = RT_NULL;
static rt_err_t serial_ind_cb(rt_device_t dev, rt_size_t size)
{
rt_sem_release(_rx_sem);
return RT_EOK;
}
int serial_send(uint8_t *buf, int len)
{
rt_device_write(&serial->parent, 0, buf, len);
return len;
}
int serial_receive(uint8_t *buf, int bufsz, int timeout, int bytes_timeout)
{
int len = 0;
while (1)
{
rt_sem_control(_rx_sem, RT_IPC_CMD_RESET, RT_NULL);
int rc = rt_device_read(&serial->parent, 0, buf + len, bufsz);
if (rc > 0)
{
timeout = bytes_timeout;
len += rc;
bufsz -= rc;
if (bufsz == 0)
break;
continue;
}
if (rt_sem_take(_rx_sem, rt_tick_from_millisecond(timeout)) != RT_EOK)
break;
timeout = bytes_timeout;
}
return len;
}
int serial_init(void)
{
_rx_sem = rt_sem_create("serial", 0, RT_IPC_FLAG_FIFO);
if (_rx_sem == RT_NULL)
{
LOG_E("create rx_sem failed.");
return -RT_ERROR;
}
rt_device_t dev = RT_NULL;
dev = rt_device_find("uart0");
if (dev == RT_NULL)
{
LOG_E("can't find device uart0.");
rt_sem_delete(_rx_sem);
return -RT_ERROR;
}
serial = (struct rt_serial_device *)dev;
serial->config.data_bits = DATA_BITS_8;
serial->config.parity = PARITY_NONE;
serial->config.baud_rate = 9600;
serial->config.stop_bits = STOP_BITS_1;
serial->ops->configure(serial, &(serial->config));
if (!rt_device_open(&serial->parent, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX))
{
rt_device_set_rx_indicate(&serial->parent, serial_ind_cb);
}
else
{
return -RT_ERROR;
}
return RT_EOK;
}
serial_port.h
#ifndef SERIAL_PORT_H
#define SERIAL_PORT_H
#include <rtthread.h>
#include <rtdevice.h>
#include <stdint.h>
int serial_send(uint8_t *buf, int len);
int serial_receive(uint8_t *buf, int bufsz, int timeout, int bytes_timeout);
int serial_init(void);
#endif
三、创建DCM数据池
创建dcm相关文件
dcm.c
/*
- Copyright (c) 2006-2020, RT-Thread Development Team
- SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <finsh.h>
#include <rtthread.h>
#include "dcm_inc.h"
#define DBG_TAG "dcm.demo"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
struct dcm_pool *g_dcm_pool = RT_NULL;
#define DCM_POOL_NAME "sensor"
#define DCM_TEST_CACHE_NUM_MAX 100
rt_int16_t data5 = 0;
rt_int16_t data6 = 0;
rt_int16_t data7 = 0;
rt_int16_t data8 = 0;
rt_bool_t is_need_set = RT_FALSE;
/**
- [url=home.php?mod=space&uid=2666770]@Brief[/url] 设置int16型数据到DCM,并通知JS层
- [url=home.php?mod=space&uid=3142012]@param[/url] name 变量名
- @param value 值
*/
void dcm_tool_set_int16_value(const char *name, rt_int16_t value)
{
if (g_dcm_pool == RT_NULL)
return
struct dcm_pool *dcm_pool = g_dcm_pool
dt_value_t *setptr = RT_NULL
dt_value_t dt_value
setptr = dt_value_init(&dt_value)
dt_pack_int32(setptr, value)
dcm_value_set(dcm_pool, name, setptr)
}
static void dcm_data5_change_task(void *user_data)
{
data5 = (rt_int16_t)dt_unpack_int32(dcm_value_get(g_dcm_pool, "data5"))
is_need_set = RT_TRUE
LOG_D("data5: %d", data5)
}
static void dcm_data6_change_task(void *user_data)
{
data6 = (rt_int16_t)dt_unpack_int32(dcm_value_get(g_dcm_pool, "data6"))
is_need_set = RT_TRUE
LOG_D("data6: %d", data6)
}
static void dcm_data7_change_task(void *user_data)
{
data7 = (rt_int16_t)dt_unpack_int32(dcm_value_get(g_dcm_pool, "data7"))
is_need_set = RT_TRUE
LOG_D("data7: %d", data7)
}
static void dcm_data8_change_task(void *user_data)
{
data8 = (rt_int16_t)dt_unpack_int32(dcm_value_get(g_dcm_pool, "data8"))
is_need_set = RT_TRUE
LOG_D("data8: %d", data8)
}
int dcm_sensor_start(void)
{
if (g_dcm_pool == RT_NULL)
{
g_dcm_pool = rt_calloc(1, sizeof(struct dcm_pool));
if (g_dcm_pool == RT_NULL)
{
LOG_E("dcm pool(%s) malloc failed.", DCM_POOL_NAME);
return -RT_ENOMEM;
}
g_dcm_pool->name = DCM_POOL_NAME;
g_dcm_pool->max_num = DCM_TEST_CACHE_NUM_MAX;
if (dcm_pool_init(g_dcm_pool, RT_NULL, RT_NULL) < 0)
{
LOG_E("dcm pool initialized failed.");
return -RT_ERROR;
}
LOG_D("dcm pool(%s) initialized success.\n", DCM_POOL_NAME);
}
dcm_cache_bind(g_dcm_pool, "data5", DCM_CB_TYPE_CHANGE, dcm_data5_change_task, RT_NULL);
dcm_cache_bind(g_dcm_pool, "data6", DCM_CB_TYPE_CHANGE, dcm_data6_change_task, RT_NULL);
dcm_cache_bind(g_dcm_pool, "data7", DCM_CB_TYPE_CHANGE, dcm_data7_change_task, RT_NULL);
dcm_cache_bind(g_dcm_pool, "data8", DCM_CB_TYPE_CHANGE, dcm_data8_change_task, RT_NULL);
return 0;
}
INIT_APP_EXPORT(dcm_sensor_start);
dcm_inc.h
#ifndef DCM_INC_H
#define DCM_INC_H
#include <dcmtype.h>
#include <ubj.h>
extern rt_int16_t data5;
extern rt_int16_t data6;
extern rt_int16_t data7;
extern rt_int16_t data8;
extern rt_bool_t is_need_set;
extern void dcm_tool_set_int16_value(const char *name, rt_int16_t value);
#endif
四、创建Modbus线程
创建modbus.c
#include "agile_modbus.h"
#include "dcm_inc.h"
#include "serial_port.h"
#include <dcmtype.h>
#include <rtthread.h>
#define DBG_TAG "mb_master"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
static uint8_t ctx_send_buf[AGILE_MODBUS_MAX_ADU_LENGTH];
static uint8_t ctx_read_buf[AGILE_MODBUS_MAX_ADU_LENGTH];
static uint16_t hold_register[100];
static void mb_master_poll(void *parameter)
{
agile_modbus_rtu_t ctx_rtu;
agile_modbus_t *ctx = &ctx_rtu._ctx;
agile_modbus_rtu_init(&ctx_rtu, ctx_send_buf, sizeof(ctx_send_buf), ctx_read_buf, sizeof(ctx_read_buf));
agile_modbus_set_slave(ctx, 1);
LOG_I("Running.");
while (1)
{
rt_thread_mdelay(100);
int send_len = 0, read_len = 0, rc = 0;
if (!is_need_set)
{
send_len = agile_modbus_serialize_read_registers(ctx, 0, 4);
serial_send(ctx->send_buf, send_len);
read_len = serial_receive(ctx->read_buf, ctx->read_bufsz, 1000, 20);
if (read_len == 0)
{
continue;
}
rc = agile_modbus_deserialize_read_registers(ctx, read_len,
hold_register);
if (rc < 0)
{
continue;
}
dcm_tool_set_int16_value("data1", hold_register[0]);
dcm_tool_set_int16_value("data2", hold_register[1]);
dcm_tool_set_int16_value("data3", hold_register[2]);
dcm_tool_set_int16_value("data4", hold_register[3]);
}
else
{
is_need_set = RT_FALSE;
rt_int16_t *buf = rt_malloc(4);
buf[0] = data5;
buf[1] = data6;
buf[2] = data7;
buf[3] = data8;
send_len = agile_modbus_serialize_write_registers(ctx, 4, 4, (const rt_uint16_t *)buf);
rt_free(buf);
serial_send(ctx->send_buf, send_len);
read_len = serial_receive(ctx->read_buf, ctx->read_bufsz, 1000, 20);
if (read_len == 0)
{
continue;
}
LOG_I("write cnt success!");
}
}
}
int mb_master_start()
{
rt_thread_t tid1 = RT_NULL;
serial_init();
tid1 = rt_thread_create("md_m_poll", mb_master_poll, RT_NULL, 4096, 10, 10);
if (tid1 != RT_NULL)
{
rt_thread_startup(tid1);
}
else
{
goto __exit;
}
return RT_EOK;
__exit:
if (tid1)
rt_thread_delete(tid1);
return -RT_ERROR;
}
MSH_CMD_EXPORT(mb_master_start, mb_master_start);
五、编译
最后整个 dcm_modbus 目录的结构如下
![](//file.elecfans.com/web2/M00/53/08/poYBAGLPh8yAcOsyAAC9jVMp3go838.jpg)
在 ENV 中执行 scons -j8 命令,对项目进行编译。
烧录
在 ENV 中执行 udb.exe upgrade .\fw_compress.rbl download 0 命令,对项目进行烧录。
UI界面JS端
一、前台界面与后台逻辑设计
界面设计如下:
![](//file.elecfans.com/web2/M00/53/08/poYBAGLPh8yAMppVAACY5LXFuwU774.jpg)
其中,data1~4
为读取值,data5~8
为写入值。在从机的数据发生改变时,data1~4
也会进行相对应的更新。在拖动滑条对 data5~8
的值进行重新设定后,点击发送键,data5~8
的值也会被同步到从机中。
JS代码如下
/*
- File : app.js
- COPYRIGHT (C) 2006-2021, Shanghai Real-Thread Technology Co., Ltd
- Change Logs:
- Date Author Notes
- 2020-08-20 realthread the first version
/
Page({
data5: 0,
data6: 0,
data7: 0,
data8: 0,
/ 页面加载时触发该函数 /
onLoad: function (event) {
this.dcm = require("dcm");
this.dbpool = this.dcm.Open('sensor');
var that = this;
/ 对data1的改变事件进行绑定,当Data1的值改变时,程序将其读取出来并对控件进行赋值 /
that.dbpool.onChange("data1", this.onData1Change = function (event) {
var data = that.dbpool.getItem("data1");
that.setData({ label1: { value: data } });
});
that.dbpool.onChange("data2", this.onData2Change = function (event) {
var data = that.dbpool.getItem("data2");
that.setData({ label2: { value: data } });
});
that.dbpool.onChange("data3", this.onData3Change = function (event) {
var data = that.dbpool.getItem("data3");
that.setData({ label3: { value: data } });
});
that.dbpool.onChange("data4", this.onData4Change = function (event) {
var data = that.dbpool.getItem("data4");
that.setData({ label4: { value: data } });
});
},
/ 此方法展示窗体后发生 /
onResume: function (event) {
},
/ 页面显示时触发该函数 /
onShow: function (event) { },
/ 页面隐藏时触发该函数 /
onHide: function (event) { },
/ 页面退出时触发该函数 */
onExit: function (event) {
this.dbpool.offChange("data1", this.onData1Change);
this.dbpool.offChange("data2", this.onData2Change);
this.dbpool.offChange("data3", this.onData3Change);
this.dbpool.offChange("data4", this.onData4Change);
},
on_button: function (event) {
this.dbpool.setItem("data5", this.data5);
this.dbpool.setItem("data6", this.data6);
this.dbpool.setItem("data7", this.data7);
this.dbpool.setItem("data8", this.data8);
},
on_slider: function (event) {
var that = this;
switch (event.target.id) {
case 'slider1':
that.data5 = event.detail.value;
this.setData({
label5: {
value: this.data5
}
});
break;
case 'slider2':
that.data6 = event.detail.value;
this.setData({
label6: {
value: this.data6
}
});
break;
case 'slider3':
that.data7 = event.detail.value;
this.setData({
label7: {
value: this.data7
}
});
break;
case 'slider4':
that.data8 = event.detail.value;
this.setData({
label8: {
value: this.data8
}
});
break;
}
}
});
二、下载UI工程
在 UI 工程的根目录下打开 ENV ,在 ENV 中执行 udb.exe push .\dist\output\original\ /gui 命令即可下载 UI 文件
结果演示
在 MSH 中执行命令 mb_master_start 打开 modbus 主机程序,为了方便演示,这里使用 Modbus Slave 当作从机,结果如下
![](//file.elecfans.com/web2/M00/53/A4/pYYBAGLPh8yAcILLAAK8kqiRQsc388.jpg)
![](//file.elecfans.com/web2/M00/53/A4/pYYBAGLPh8yAfS0aAADZCm2YxaA835.jpg)
![](//file.elecfans.com/web2/M00/53/08/poYBAGLPh8yAYJ8bAADxojrvudg215.jpg)
原作者:xiaorui