本帖最后由 我爱下载 于 2022-12-22 08:39 编辑

【HPM6750EVKMINI开发板】+通讯管理项目测试(二)
        本测试为评测项目——通讯管理系统项目的第二部分,在第一部份的基础上,主要完成基本通讯接口、协议及信息交互系统构建,为了具有普遍性,通讯接口选择串口通讯,CAN通讯接口通讯和以太网(无线wifi)通讯,协议选择Modbus-RTU,自定义CAN总线协议和无线MQTT。系统主要通过串口的Modbus-RTU主机系统获取辅机设备数据;通过CAN标准2.0,执行自定义协议,获取CAN总线辅机数据。将获取的数据整合后,通过mqtt物联网协议将数据共享到数据服务器,从而完成数据共享。
1、串口modbus-RTU通讯
1.1 物理接口
forum.jpg
系统中提供的P1连接器包含两组对外引出的串口,这里选择UART14作为我们测试串口,我们需要在系统中使能它。
1.2 使能UART14串口
        在RT-Thread Settings->硬件->Enable UART14 , 如下图所示。
forum.jpg
保存后,系统将增加UART14的相应串口驱动的支持。
1.3 增加Modbus-RTU协议主站支持包
有了硬件的底层支持,我们就可以增加高层协议的支持了, 使能RT-Thread Settings ->软件包->IOT 物联网-> agile_modbus Lightweight modbus protocol stack,增加agile_modbus协议包的支持。
forum.jpg
1.4软件设计
        按照RT-Thread设备使用方式,首先创建一个初始化函数,完成硬件端口初始化和任务创建。
int rt_modbus_master_app(int argc, char *argv[])
  • {
  • rt_err_t ret = RT_EOK;
  • struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 初始化配置参数 */
  • /* 查找系统中的串口设备 */
  • serial = rt_device_find(MODBUS_UART_NAME);
  • if (!serial)
  • {
  • rt_kprintf("find %s failed!\n", MODBUS_UART_NAME);
  • return RT_ERROR;
  • }
  • /* step2:修改串口配置参数 */
  • config.baud_rate = BAUD_RATE_9600; //修改波特率为 9600
  • config.data_bits = DATA_BITS_8; //数据位 8
  • config.stop_bits = STOP_BITS_1; //停止位 1
  • config.rx_bufsz = 128; //修改缓冲区 buff size 为 128
  • config.parity = PARITY_NONE; //无奇偶校验位
  • /* step3:控制串口设备。通过控制接口传入命令控制字,与控制参数 */
  • rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
  • /* 初始化信号量 */
  • rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
  • /* 以中断接收及轮询发送模式打开串口设备 */
  • rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
  • /* 设置接收回调函数 */
  • rt_device_set_rx_indicate(serial, uart_input);
  • /* 创建定时器1 周期定时器 */
  • timer1 = rt_timer_create("timer1", timeout1,
  • RT_NULL, 20,
  • RT_TIMER_FLAG_PERIODIC);
  • /* 启动定时器1 */
  • // if (timer1 != RT_NULL) rt_timer_start(timer1);
  • /* 创建 modbus master 处理线程 */
  • rt_thread_t thread = rt_thread_create("modbus master app", mm_app_thread_entry, RT_NULL, 4096, 25, 10);
  • /* 创建成功则启动线程 */
  • if (thread != RT_NULL)
  • {
  • rt_thread_startup(thread);
  • }
  • else
  • {
  • ret = RT_ERROR;
  • }
  • return ret;
  • }
  • /* 导出到 msh 命令列表中 */
  • MSH_CMD_EXPORT(rt_modbus_master_app, rt_modbus_master_app);
  • 复制代码
    按照主机协议获取辅机数据。完成主机协议初始化,读取数据和数据按协议解析。
    static void mm_app_thread_entry(void *parameter)
  • {
  • char ch;
  • 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);
  • printf("Running.\n\r");
  • while (1)
  • {
  • rt_thread_delay(5000);
  • int send_len = agile_modbus_serialize_read_registers(ctx, 0, 10);
  • for(int i=0;i<send_len;i++)
  • {
  • ch = ctx->send_buf[i];
  • rt_device_write(serial, 0, &ch, 1);
  • }
  • int read_len = serial_read(5000);
  • if (read_len < 0) {
  • printf("Receive error, now exit.\n\r");
  • break;
  • }
  • if (read_len == 0) {
  • continue;
  • }
  • int rc = agile_modbus_deserialize_read_registers(ctx, read_len, hold_register);
  • if (rc < 0) {
  • printf("Receive failed.");
  • if (rc != -1)
  • printf("Error code:%d \n\r", -128 - rc);
  • continue;
  • }
  • mqtt_publish_yc(hold_register, 10);
  • printf("Hold Registers:");
  • for (int i = 0; i < 10; i++)
  •      printf("Register [%d]: 0x%04X\n\r", i, hold_register[i]);
  • printf("\r\n\r\n\r\n");
  • }
  • }
  • 复制代码
    2、CAN总线自定义通讯
    2.1 物理接口
    forum.jpg
    系统中提供两组CAN接口,这里选择P2连接器上的CAN1接口,我们需要在系统中使能它。
    1.2 使能CAN1接口
            在RT-Thread Settings->硬件->Enable CAN -〉Enable CAN1 , 如下图所示。
    forum.jpg
    保存后,系统将增加CAN1相应驱动的支持。
    2.3 软件设计
    由于采用自定义协议设计,所以我们直接在程序中直接实现。
    首先,增加CAN总线的初始化和任务初始化。
    #define CAN_DEV_NAME "can1" /* CAN 设备名称 */
  • int can_sample(int argc, char *argv[])
  • {
  • struct rt_can_msg msg = {0};
  • rt_err_t res;
  • rt_size_t size;
  • rt_thread_t thread;
  • /* 查找 CAN 设备 */
  • can_dev = rt_device_find(CAN_DEV_NAME);
  • if (!can_dev)
  • {
  • rt_kprintf("find %s failed!\n", CAN_DEV_NAME);
  • return RT_ERROR;
  • }
  • if((can_dev->open_flag & RT_DEVICE_OFLAG_OPEN) == 0)
  • {
  • /* 初始化 CAN 接收信号量 */
  • rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
  • /* 设置 CAN 通信的波特率为 50kbit/s*/
  • res = rt_device_control(can_dev, RT_CAN_CMD_SET_BAUD, (void *)CAN50kBaud);
  • /* 以中断接收及发送方式打开 CAN 设备 */
  • res = rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX); //
  • RT_ASSERT(res == RT_EOK);
  • /* 创建数据接收线程 */
  • thread = rt_thread_create("can_rx", can_rx_thread, RT_NULL, 2048, 15, 10);
  • if (thread != RT_NULL)
  • {
  • rt_thread_startup(thread);
  • }
  • else
  • {
  • rt_kprintf("create can_rx thread failed!\n");
  • }
  • /* 创建数据发送线程 */
  • thread = rt_thread_create("can_tx", can_tx_thread, RT_NULL, 1024, 15, 10);
  • if (thread != RT_NULL)
  • {
  • rt_thread_startup(thread);
  • }
  • else
  • {
  • rt_kprintf("create can_rx thread failed!\n");
  • }
  • }
  • return res;
  • }
  • /* 导出到 msh 命令列表中 */
  • MSH_CMD_EXPORT(can_sample, can device sample);
  • 复制代码
    辅助数据巡检任务。这里选择的协议比较简单,使用标准帧,id定义为辅机的地址,第一个字节定义为命令。
    static void can_tx_thread(void *parameter)
  • {
  • int i;
  • rt_err_t res;
  • rt_size_t size;
  • struct rt_can_msg msg = {0};
  • while (1)
  • {
  • msg.id = CAN_SLAVE_ID; /* ID 为 0x02 */
  • msg.ide = RT_CAN_STDID; /* 标准格式 */
  • msg.rtr = RT_CAN_DTR; /* 数据帧 */
  • msg.len = 1; /* 数据长度为 8 */
  • /* 待发送的 1 字节数据 */
  • msg.data[0] = 0x01;
  • /* 发送一帧 CAN 数据 */
  • if(can_dev)
  • {
  • size = rt_device_write(can_dev, 0, &msg, sizeof(msg));
  • if (size == 0)
  • {
  • rt_kprintf("can dev write data failed!\n");
  • }
  • }
  • rt_thread_mdelay(1000);
  • }
  • }
  • 复制代码
    辅机数据响应处理。在接收到CAN总线的数据后,首先判断是否为辅机的响应数据,如果是就对数据帧进行处理,否则将放弃数据。
    static void can_rx_thread(void *parameter)
  • {
  • int i;
  • rt_err_t res;
  • struct rt_can_msg rxmsg = {0};
  • /* 设置接收回调函数 */
  • rt_device_set_rx_indicate(can_dev, can_rx_call);
  • while (1)
  • {
  • /* hdr 值为 - 1,表示直接从 uselist 链表读取数据 */
  • rxmsg.hdr = -1;
  • /* 阻塞等待接收信号量 */
  • rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
  • /* 从 CAN 读取一帧数据 */
  • rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg));
  • printf("can frame: id=%d %02X %02X %02X %02X %02X %02X %02X %02X \n",rxmsg.id, rxmsg.data[0],rxmsg.data[1],rxmsg.data[2],rxmsg.data[3],
  • rxmsg.data[4],rxmsg.data[5],rxmsg.data[6],rxmsg.data[7]);
  • if(rxmsg.id == CAN_SLAVE_ID) //受到了正确的响应
  • {
  • mqtt_publish_can(rxmsg.data, 8);
  • }
  • }
  • }
  • 复制代码
    3、无线MQTT通讯数据整合和共享
    3.1 MQTT协议的支持
    由于wifi网络在测试一中我们已经完成,因此,我们需要构建MQTT的客户端协议支持。使能RT-Thread Settings->软件包->IOT 物联网 -〉MY MQTT 。增加Eclipse Paho MQTT C/C++ 客户端协议栈的支持。
    forum.jpg
    定义服务器连接相关参数
    #define MQTT_URI "tcp://192.168.2.112:1883"
  • #define MQTT_SUBTOPIC "test"
  • #define MQTT_PUBTOPIC "test"
  • #define MQTT_WILLMSG "Goodbye!"
  • 复制代码
    启动MQTT客户端
    static int mqtt_start(int argc, char **argv)
  • {
  • /* init condata param by using MQTTPacket_connectData_initializer */
  • MQTTPacket_connectData condata = MQTTPacket_connectData_initializer;
  • static char cid[20] = { 0 };
  • if (argc != 1)
  • {
  • rt_kprintf("mqtt_start --start a mqtt worker thread.\n");
  • return -1;
  • }
  • if (is_started)
  • {
  • LOG_E("mqtt client is already connected.");
  • return -1;
  • }
  • /* config MQTT context param */
  • {
  • client.isconnected = 0;
  • client.uri = MQTT_URI;
  • /* generate the random client ID */
  • rt_snprintf(cid, sizeof(cid), "rtthread%d", rt_tick_get());
  • /* config connect param */
  • memcpy(&client.condata, &condata, sizeof(condata));
  • client.condata.clientID.cstring = cid;
  • client.condata.keepAliveInterval = 30;
  • client.condata.cleansession = 1;
  • /* config MQTT will param. */
  • client.condata.willFlag = 1;
  • client.condata.will.qos = 1;
  • client.condata.will.retained = 0;
  • client.condata.will.topicName.cstring = MQTT_PUBTOPIC;
  • client.condata.will.message.cstring = MQTT_WILLMSG;
  • /* malloc buffer. */
  • client.buf_size = client.readbuf_size = 1024;
  • client.buf = rt_calloc(1, client.buf_size);
  • client.readbuf = rt_calloc(1, client.readbuf_size);
  • if (!(client.buf && client.readbuf))
  • {
  • LOG_E("no memory for MQTT client buffer!");
  • return -1;
  • }
  • /* set event callback function */
  • client.connect_callback = mqtt_connect_callback;
  • client.online_callback = mqtt_online_callback;
  • client.offline_callback = mqtt_offline_callback;
  • /* set subscribe table and event callback */
  • client.message_handlers[0].topicFilter = rt_strdup(MQTT_SUBTOPIC);
  • client.message_handlers[0].callback = mqtt_sub_callback;
  • client.message_handlers[0].qos = QOS1;
  • /* set default subscribe event callback */
  • client.default_message_handlers = mqtt_sub_default_callback;
  • }
  • {
  • int value;
  • uint16_t u16Value;
  • value = 5;
  • paho_mqtt_control(&client, MQTT_CTRL_SET_CONN_TIMEO, &value);
  • value = 5;
  • paho_mqtt_control(&client, MQTT_CTRL_SET_MSG_TIMEO, &value);
  • value = 5;
  • paho_mqtt_control(&client, MQTT_CTRL_SET_RECONN_INTERVAL, &value);
  • value = 30;
  • paho_mqtt_control(&client, MQTT_CTRL_SET_KEEPALIVE_INTERVAL, &value);
  • u16Value = 3;
  • paho_mqtt_control(&client, MQTT_CTRL_SET_KEEPALIVE_COUNT, &u16Value);
  • }
  • /* run mqtt client */
  • paho_mqtt_start(&client, 8196, 20);
  • is_started = 1;
  • return 0;
  • }
  • 复制代码
    信息发布函数。一个是串口modbus-RTU收集的辅机遥测量信息发布,一个是CAN总线收集辅机信息发布。
    int mqtt_publish_yc(uint16_t *yc, uint32_t len)
  • {
  • if (is_started == 0)
  • {
  • LOG_E("mqtt client is not connected.");
  • return -1;
  • }
  • for(int i=0; i < len; i++)
  •       sprintf(mqtt_yc_buf+i*9,"h%02d=%04d;",i,yc[i]);
  • paho_mqtt_publish(&client, QOS1, "yc", mqtt_yc_buf, strlen(mqtt_yc_buf));
  • return 0;
  • }
  • 复制代码
    int mqtt_publish_can(uint8_t *dat, uint32_t len)
  • {
  • if (is_started == 0)
  • {
  • LOG_E("mqtt client is not connected.");
  • return -1;
  • }
  • sprintf(mqtt_yc_buf,"candata:");
  • for(int i=0; i < len; i++)
  •     sprintf(mqtt_yc_buf+8+i*3,"%02X ",dat[i]);
  • paho_mqtt_publish(&client, QOS1, "can", mqtt_yc_buf, strlen(mqtt_yc_buf));
  • return 0;
  • }
  • 复制代码
    3.2 MQTT协议服务器的建立
            MQTT服务器的建立这里不详细介绍,我采用树梅派,自己编译Mosquitto的linux下的服务器。
    4、实测运行
    整个测试系统的照片,包括HPM6750EVKMINI,NXP Mxdock,usb转串口,usb转can口。
    forum.jpg
    4.1 串口modbus-RTU的实际测试
    利用modbusslave软件在windows上虚拟了一个modbus辅机。主机运行于HPM6750EVKMINI,每间隔5s巡检一个辅机数据,辅机在接到巡检后响应相应的寄存器信息。
    forum.jpg
    4.2 CAN总线的实测
    HPM6750EVKMINI上运行CAN总线主机,通过Mxdock上的CAN总线接口和自制的支持SLCAN 协议的python-can硬件设备通讯,完成数据的交换。
    forum.jpg

    4.3 MQTT数据交换测试
    wifi建立连接,启动mqtt的客户端
    forum.jpg
    通过Eclipse paho MQTT Utility工具连接到服务器,检查HPM6750EVKMINI上运行的MQTT客户端。
    在终端执行mqtt_publish hello , 这条执行默认发布topic = test的一个信息,因此我们可以通过在mqtt工具上订阅test来监视。
    forum.jpg
    forum.jpg