This repository has been archived on 2025-02-28. You can view files and clone it, but cannot push or open issues or pull requests.
controller-hd/User/hart
许晟昊 5a1078b82c 更新晶振 2024-11-27 10:12:52 +08:00
..
inc 更新晶振 2024-11-27 10:12:52 +08:00
lib 合并代码(有错) 2024-11-26 14:16:38 +08:00
master 合并代码(有错) 2024-11-26 14:16:38 +08:00
public/img 合并代码(有错) 2024-11-26 14:16:38 +08:00
slave 更新晶振 2024-11-27 10:12:52 +08:00
test 合并代码(有错) 2024-11-26 14:16:38 +08:00
Makefile 合并代码(有错) 2024-11-26 14:16:38 +08:00
hart.c 更新晶振 2024-11-27 10:12:52 +08:00
hart_cache.c 合并代码(有错) 2024-11-26 14:16:38 +08:00
hart_frame.c 更新晶振 2024-11-27 10:12:52 +08:00
readme.md 合并代码(有错) 2024-11-26 14:16:38 +08:00

readme.md

介绍

HART 协议模块是 C 语言编写的,使用者无需对 HART 协议了解,通过接口文档可以快速完成应用层协议开发。 模块可以分别被主机和从机使用,调用少量接口完成开发,模块接口测试覆盖率 100%。

架构

架构说明

HART 协议模块是设备与主机之间的通信协议,主机和从机之间通过串口通信,主机通过串口发送命令,从机接收命令并返回响应数据。因此,主机和从机都需要使用 HART 协议模块,主机和从机的接口不同,主机接口主要是发送命令,从机接口主要是接收命令并返回响应数据。

考虑到移植性在设计阶段模块因避免使用操作系统相关的接口,模块接口使用回调函数的方式实现,主机和从机需要实现不同的回调函数,主机需要实现发送数据的回调函数,从机需要实现接收数据的回调函数。

内容说明

  1. 7.9 版本认证支持命令 78、79、534
  2. safeHART 数字化安全连锁,一种通讯协议,主要是对数据部分增加了 CRC 校验和序列号,打开 Burst-Mode 后发送的命令发送的是 safeHART 命令。 https://library.fieldcommgroup.org/20085/TS20085/4.0/#page=36
  3. 2024 年 3 月 8 日后提交测试必须是 3.8 版本测试
  4. 新增 548-553 指令

软件设计

结构图

│ hart.c │ hart_cache.c │ hart_frame.c │ readme.md ├─inc │ hart.h │ hart_common_tables_specification.h │ hart_frame.h │ hart_frame_user.h │ ├─lib │ ├─flow │ │ example.c │ │ flow.h │ │ flow_core.c │ │ flow_core.h │ │ flow_def.h │ │ flow_sem.h │ │ README.md │ │ │ ├─inc │ │ data_type_def.h │ │ debug.h │ │ lib.h │ │ log.h │ │ malloc.h │ │ osel_arch.h │ │ sqqueue.h │ │ │ └─src │ debug.c │ lib.c │ malloc.c │ sqqueue.c │ ├─master │ ├─inc │ │ hart_master.h │ │ hart_master_frame.h │ │ hart_master_req.h │ │ hart_master_rsp.h │ │ │ └─src │ hart_master.c │ hart_master_frame.c │ hart_master_req.c │ hart_master_req_user.c │ hart_master_rsp.c │ ├─public │ └─slave ├─inc │ hart_slave.h │ hart_slave_frame.h │ hart_slave_req.h │ └─src hart_slave.c hart_slave_frame.c hart_slave_req.c hart_slave_req_user.c

组织结构

模块说明

文件 路径
说明
hart.c ./ 用于处理 HART高阶实时通信技术模块的接口。这个文件包含了模块的初始化、数据处理、Flash 读写等函数,以及一个用于处理用户事件的回调函数
hart_cache.c ./ 一个缓存消息模块,用于处理设备操作需要时间暂时不回复消息的情况
hart_frame.c ./ 包含了用于处理帧数据结构、响应码、RTC 时间转换、日期检测等功能
hart_slave.c ./slave Hart 从机处理函数,用于处理主机的请求并返回相应的响应
hart_slave_req.c ./slave 从机命令响应的处理函数
hart_slave_req_user.c ./slave 从机命令响应用户自定义指令的处理函数

主机模块说明暂时省略

hart.c

  1. hart_init 函数:用于初始化 Hart 模块,根据传入的参数配置模块的工作模式(主模块或从模块)和接口。
  2. hart_handle 函数:用于处理接收到的数据,根据模块的工作模式将数据分发给对应的主机或从机处理。
  3. hart_storage_write 和 hart_storage_read 函数:用于实现 Flash 的读写操作。
  4. perform_self_test 和 perform_self_test_finish 函数:用于自检设备。
  5. device_reset 函数:用于设备复位。
  6. squawk_control 函数:用于呼叫,根据传入的参数打开或关闭呼叫功能。
  7. armed 函数:用于判断是否有技术人员按下一个特殊的按钮或按钮组合。
  8. common_event 函数:用于处理用户自定义的事件。

hart_cache.c

  1. 全局变量sqqueue_ctrl_t cache这是一个队列控制结构体用于控制缓存队列的属性。
  2. 缓存消息结构体hart_cache_t这个结构体包含了一个 uuid 字段,用于存储消息的唯一标识。还有其他一些与具体设备相关的字段,例如设备状态、操作结果等。
  3. hart_cache_init(),用于初始化缓存队列。
  4. hart_cache_add(),用于向缓存中添加一条消息。首先检查缓存是否已满,如果满则返回。然后分配内存,拷贝数据,并设置 uuid。最后将数据入队。
  5. hart_cache_free(),用于释放缓存中一条消息的内存。
  6. hart_cache_get(),用于根据 uuid 获取缓存中一条消息。首先检查缓存是否为空,如果为空则返回 NULL。然后遍历缓存中的消息如果找到匹配的 uuid则返回该消息。否则将当前消息出队然后重新入队。

hart_frame.c

  1. hart_frame_data_length_start 和 hart_frame_data_length_end 函数用于在处理请求开始和结束时记录帧数据长度位置。
  2. hart_frame_response_code_start 和 hart_frame_response_code_end 函数用于在处理请求开始和结束时记录帧响应码起始位置。
  3. hart_frame_response_communication_code 和 hart_frame_master_response_operate_code 函数用于设置和获取响应码第一个字节和第二个字节。
  4. hart_is_write_command 函数用于判断指令是否是写。
  5. hart_is_support_command 函数用于判断版本是否支持当前指令。
  6. rtc_to_timestamp 函数用于将 RTC 时间转时间戳。
  7. get_rtc_date 函数用于获取 RTC 的日期。
  8. check_date 函数用于检测日期是否有效。
  9. is_broadcast_address 函数用于判断地址是否为广播地址。
  10. timestamp_to_hmsms 函数用于将时间戳转换为小时:分钟:秒:毫秒的格式。

hart_slave.c

  1. hart_slave_state_check 来检查命令是否合法。如果命令合法,它将调用相应的处理函数 hart_command_ptr_arr[command]来处理命令。如果处理成功,它将返回 True否则返回 False。 在处理完命令后,函数将响应数据的长度存储在 handle->response->cache_data.payload_length 中。如果响应数据长度不为 0它将分配内存来存储响应数据并将数据复制到分配的内存中。最后它将响应数据结构 handle->response->cache_data 添加到缓存中,并发送响应数据。

  2. hart_slave_handle 函数,该函数用于处理从机的请求。

  • 首先,检查输入参数是否有效。
  • 获取从机的地址。
  • 比较主机请求的地址和从机地址是否一致。如果不一致,返回错误。
  • 获取命令码。
  • 根据命令码查找对应的处理函数。如果找不到对应的处理函数,返回错误。
  • 执行处理函数,并将结果存储在 handle->response 结构体中。
  • 计算响应数据的长度。
  • 如果响应数据长度不为 0为响应数据分配内存。
  • 将响应数据结构体添加到缓存中。
  • 返回处理成功。
  1. hart_slave_init 函数,用于初始化从机。
  • 检查输入参数是否有效。
  • 初始化从机的请求处理结构体。
  • 返回处理成功
  1. hart_slave_device_send 函数,用于发送响应帧给一个 HART 从设备
  • 定义一些变量,如响应数据长度、异或校验、数据长度指针、响应码指针等。
  • 填充响应帧的数据,包括前导码、地址、命令、负载等。
  • 计算响应帧的数据长度,并将结果存储在 data_len_p 中。
  • 计算响应帧的校验码,并将结果存储在 response_code_p 中。
  • 更新响应帧的长度字段和响应码字段。
  • 如果响应帧的数据长度大于 0调用 handle->response_call 函数将响应帧发送给指定的 UART 索引。同时,增加设备属性的消息计数器。
  1. hart_slave_device_attribute_init 函数,用于从机设备属性初始化

hart_slave_req.c

  1. hart_slave_req_init 函数,用于初始化哈希表,用于注册从端发送给主端的命令的处理函数。
  • hart_command_ptr_arr 数组,用于存储命令码和处理函数的映射关系。因为命令码的使用不是连续的,因此占用了大量的空间,但是提高了查找效率。

hart_slave_req_user.c

实现方式上同 hart_slave_req.c和通用命令做区分

移植步骤

这里以 STM32 为例,介绍如何移植 HART 协议模块。

  1. 将 HART 协议模块的源码添加到工程中。 HART 协议模块中用到的 lib 也一并加入到工程中。

  2. 添加宏定义和头文件 宏定义STM32,SLAVE 头文件路径

  3. HART 协议和应用层的中间层 该文件主要实现 HART 模块需要的和 MCU 相关的接口例如串口发送、Flash 读写等。

hart_user_data_refush(): 刷新用户实时数据,包括模拟量和数字量。 response(uint8_t uart_index, uint8_t *data, uint16_t len): 发送数据(hart 协议数据帧)。 flash_read(uint32_t addr, uint8_t *data, uint16_t len): flash 读取接口。 flash_write(uint32_t addr, uint8_t *data, uint16_t len): flash 写入接口。 perform_self_test(void): 执行自检。 device_reset(void): 设备复位。 squawk_control(BOOL open, uint8_t second): 设备呼叫,0 一直呼叫1-255 秒。 armed(void): 技术人员按下一个特殊的按钮或按钮组合,指示从机应响应 command74。 set_dynamics(device_variable_dynamics_t *const dynamics): 设置动态变量。 hart_rx_cb(uint8_t uart_index, uint8_t *data, uint16_t len): 串口 1、5 接收中断回调函数。 hart_tx_complete_cb(void): 串口 1 发送完成回调函数。 hart_uart_init(void): 串口 1 初始化。 hart_uart_dinit(void): 串口 1 去初始化。 hart_ble_init(void): BLE 初始化。 hart_ble_dinit(void): BLE 去初始化。 hart_init(void): HART 初始化。 hart_dinit(void): HART 去初始化。 hart_task(void): HART 任务。

源码实现:

/*
 * @Author: xxx
 * @Date: 2023-08-02 08:28:36
 * @LastEditors: xxx
 * @LastEditTime: 2023-08-23 14:15:44
 * @Description: 此文件主要实现板卡的HART功能
 * Copyright (c) 2023 by xxx, All Rights Reserved.
 */

#include "app.h"
#include "hart.h"
#include "hart_frame.h"
#include "uarts.h"
#include "test_bsp.h"

#define HART_UART1 USART1
#define HART_UART2 USART5

#define UART_RXSIZE (240u)
#define UART_TXSIZE (240u)
uart_t *uarts[APP_UART_MAX];

app_dynamics_t app_dynamics;
static __IO BOOL hart_idle = TRUE;

static void hart_user_data_refush(void)
{
    app_analog_quantity_t *analog_quantity = &app_dynamics.analog_quantity;
    app_digital_quantity_t *digital_quantity = &app_dynamics.digital_quantity;

    // 模拟量
    analog_quantity->input_current = adc_raw[ADC_LOOP_CHANNEL];             // 输入电流
    analog_quantity->valve_feedback = adc_raw[ADC_PSB_CHANNEL];             // 阀门反馈
    analog_quantity->atmospheric_pressure_source = adc_raw[ADC_BP_CHANNEL]; // 气压源压力
    analog_quantity->pressure_at_port_a = 0;                                // A口压力
    analog_quantity->pressure_at_port_b = 0;                                // B口压力
    analog_quantity->amplifier_circuit = adc_raw[ADC_IPSB_CHANNEL];         // 放大器回路
    analog_quantity->built_in_temperature = 0;                              // 内置温度
    analog_quantity->ip_output_detection = rt_data.ip_output;                         // IP输出检测
    analog_quantity->valve_percentage = actual_travel;                      // 阀位百分比

    // 数字量
    digital_quantity->input_current = loop_current;                          // 输入电流
    digital_quantity->valve_feedback = adc_raw[ADC_PSB_CHANNEL];             // 阀门反馈
    digital_quantity->atmospheric_pressure_source = adc_raw[ADC_BP_CHANNEL]; // 气压源压力
    digital_quantity->pressure_at_port_a = 0;                                // A口压力
    digital_quantity->pressure_at_port_b = 0;                                // B口压力
    digital_quantity->amplifier_circuit = adc_raw[ADC_IPSB_CHANNEL];         // 放大器回路
    digital_quantity->built_in_temperature = 0;                              // 内置温度
    digital_quantity->ip_output_detection = rt_data.ip_output;                         // IP输出检测

    digital_quantity->target_row = target_travel;  // 目标行程
    digital_quantity->current_row = actual_travel; // 实际行程
    digital_quantity->friction = 0;                // 摩擦力
    digital_quantity->spring_force = 0;            // 弹簧力
}

/**
 * @brief 发送数据(hart协议数据帧)
 * @param {uint8_t} *txBuf
 * @param {uint16_t} len
 * @return {*}
 */
static void response(uint8_t uart_index, uint8_t *data, uint16_t len)
{
#ifdef STM32
    if (uart_index == APP_UART_1)
    {
        HART_CD_OFF(); // 因为DMA发送调用uart_send_data接口后会立即退出不需要等待发送是否成功因此HART_CD_ON需要在DMA发送完成中断中调用
    }

    uart_send_data(uarts[uart_index], data, len);
#else
    LOG_HEX(data, len);
#endif
}

/**
 * @brief flash读取接口
 * @param {uint32_t} addr
 * @param {uint8_t} *data
 * @param {uint16_t} len
 * @return {*}
 */
static BOOL flash_read(uint32_t addr, uint8_t *data, uint16_t len)
{
    m95_1_normal_read(addr, data, len);
    return TRUE;
}

/**
 * @brief flash写入接口
 * @param {uint32_t} addr
 * @param {uint8_t} *data
 * @param {uint16_t} len
 * @return {*}
 */
static BOOL flash_write(uint32_t addr, uint8_t *data, uint16_t len)
{
    m95_1_normal_write(addr, data, len);
    return TRUE;
}

/**
 * @brief 执行自检
 * @param {*}
 * @return {*}
 */
static void perform_self_test(void)
{
}

/**
 * @brief 设备复位
 * @param {*}
 * @return {*}
 */
static void device_reset(void)
{
}

/**
 * @brief 设备呼叫,0一直呼叫1-255秒
 * @param {*}
 * @return {*}
 */
static void squawk_control(BOOL open, uint8_t second)
{
}

/**
 * @brief 技术人员按下一个特殊的按钮或按钮组合指示从机应响应command74
 * @param {*}
 * @return {*}
 */
static BOOL armed(void)
{
    return TRUE;
}

/**
 * @brief 设置动态变量
 * @return {*}
 */
static BOOL set_dynamics(device_variable_dynamics_t *const dynamics)
{
    app_analog_quantity_t *analog_quantity = &app_dynamics.analog_quantity;
    app_digital_quantity_t *digital_quantity = &app_dynamics.digital_quantity;

    // 模拟量
    dynamics->dynamics_user.analog_quantity.input_current = &analog_quantity->input_current;                             // 输入电流
    dynamics->dynamics_user.analog_quantity.valve_feedback = &analog_quantity->valve_feedback;                           // 阀门反馈
    dynamics->dynamics_user.analog_quantity.atmospheric_pressure_source = &analog_quantity->atmospheric_pressure_source; // 大气压力源
    dynamics->dynamics_user.analog_quantity.pressure_at_port_a = &analog_quantity->pressure_at_port_a;                   // A口压力
    dynamics->dynamics_user.analog_quantity.pressure_at_port_b = &analog_quantity->pressure_at_port_b;                   // B口压力
    dynamics->dynamics_user.analog_quantity.amplifier_circuit = &analog_quantity->amplifier_circuit;                     // 放大器电路
    dynamics->dynamics_user.analog_quantity.built_in_temperature = &analog_quantity->built_in_temperature;               // 内置温度
    dynamics->dynamics_user.analog_quantity.ip_output_detection = &analog_quantity->ip_output_detection;                 // IP输出检测
    dynamics->dynamics_user.analog_quantity.valve_percentage = &analog_quantity->valve_percentage;                       // 阀位百分比

    // 数字量
    dynamics->dynamics_user.digital_quantity.input_current = &digital_quantity->input_current;                             // 输入电流
    dynamics->dynamics_user.digital_quantity.valve_feedback = &digital_quantity->valve_feedback;                           // 阀门反馈
    dynamics->dynamics_user.digital_quantity.atmospheric_pressure_source = &digital_quantity->atmospheric_pressure_source; // 大气压力源
    dynamics->dynamics_user.digital_quantity.pressure_at_port_a = &digital_quantity->pressure_at_port_a;                   // A口压力
    dynamics->dynamics_user.digital_quantity.pressure_at_port_b = &digital_quantity->pressure_at_port_b;                   // B口压力
    dynamics->dynamics_user.digital_quantity.amplifier_circuit = &digital_quantity->amplifier_circuit;                     // 放大器电路
    dynamics->dynamics_user.digital_quantity.built_in_temperature = &digital_quantity->built_in_temperature;               // 内置温度
    dynamics->dynamics_user.digital_quantity.ip_output_detection = &digital_quantity->ip_output_detection;                 // IP输出检测
    dynamics->dynamics_user.digital_quantity.target_row = &digital_quantity->target_row;                                   // 目标行程
    dynamics->dynamics_user.digital_quantity.current_row = &digital_quantity->current_row;                                 // 当前行程
    dynamics->dynamics_user.digital_quantity.friction = &digital_quantity->friction;                                       // 摩擦力
    dynamics->dynamics_user.digital_quantity.spring_force = &digital_quantity->spring_force;                               // 弹簧力

    return TRUE;
}

static BOOL common_event(hart_interface_user_event_e event, const void *const data)
{
    switch (event)
    {
    case HART_COMMAND_257_EVENT:
        // 寻找、计算P / I / D
        break;
    case HART_COMMAND_258_EVENT:
        // 计算 摩擦力
        break;
    case HART_COMMAND_259_EVENT:
        // 计算 弹簧力
        break;
    case HART_COMMAND_UPDATE_EVENT:
        // 刷新用户实时数据
        hart_user_data_refush();
        break;
    case HART_COMMAND_500_EVENT:
        // 测试命令
        return hart_user_test((hart_user_req_t *)data);
    default:
        return FALSE;
    }
    return TRUE;
}

// 串口1、5接收中断回调函数
static void hart_rx_cb(uint8_t uart_index, uint8_t *data, uint16_t len)
{
    DBG_ASSERT(uart_index < APP_UART_MAX __DBG_LINE);
    hart_idle = FALSE;
    // 串口1接收HART数据因为是空闲中断处理这里只过滤掉前导码
    hart_handle(uart_index, data, len);
    hart_idle = TRUE;
}

static void hart_tx_complete_cb(void)
{
    // 串口1发送完成回调函数
    HART_CD_ON();
}

void hart_uart_init(void)
{
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_USART1);
    GPIO_SET_OUTPUT(HART_EN_GPIO_Port, HART_EN_Pin);
    GPIO_SET_OUTPUT(HART_RST_GPIO_Port, HART_RST_Pin);
    GPIO_SET_OUTPUT(HART_CD_GPIO_Port, HART_CD_Pin);
    GPIO_SET_ALTERNATE(HART_TX_GPIO_Port, HART_TX_Pin);
    GPIO_SET_ALTERNATE(HART_RX_GPIO_Port, HART_RX_Pin);
    delay_ms(10);
    // 串口1初始化开始
    HART_CD_ON();
    HART_RST_OFF();
    delay_ms(20);
    HART_RST_ON();
    HART_EN_DISABLE();
    HART_EN_ENABLE();
    HART_CD_ON();

    if (uarts[APP_UART_1] == NULL)
    {
        uarts[APP_UART_1] = uart_create(HART_UART1, TRUE, UART_RXSIZE, hart_rx_cb, TRUE, UART_TXSIZE, hart_tx_complete_cb);
        uarts[APP_UART_1]->uart_index = APP_UART_1;
        uarts[APP_UART_1]->dma = DMA1;
        uarts[APP_UART_1]->dma_rx_channel = LL_DMA_CHANNEL_3;
        uarts[APP_UART_1]->dma_tx_channel = LL_DMA_CHANNEL_2;
        uart_recv_en(uarts[APP_UART_1]);
    }
    // 串口1初始化结束
}

void hart_uart_dinit(void)
{
    HART_EN_DISABLE();
    LL_APB2_GRP1_DisableClock(LL_APB2_GRP1_PERIPH_USART1);
    LL_USART_Disable(USART1);
    GPIO_SET_ANALOG(HART_EN_GPIO_Port, HART_EN_Pin);
    GPIO_SET_ANALOG(HART_RST_GPIO_Port, HART_RST_Pin);
    GPIO_SET_ANALOG(HART_CD_GPIO_Port, HART_CD_Pin);
    GPIO_SET_ANALOG(HART_TX_GPIO_Port, HART_TX_Pin);
    GPIO_SET_ANALOG(HART_RX_GPIO_Port, HART_RX_Pin);
}

void hart_ble_init(void)
{
    LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USART5);
    GPIO_SET_OUTPUT(BLE_PWR_GPIO_Port, BLE_PWR_Pin);
    GPIO_SET_INPUT(BLE_STATE_GPIO_Port, BLE_STATE_Pin);
    GPIO_SET_ALTERNATE(BLE_TX_GPIO_Port, BLE_TX_Pin);
    GPIO_SET_ALTERNATE(BLE_RX_GPIO_Port, BLE_RX_Pin);
    BLE_EN_ENABLE();
    delay_ms(100);
    if (uarts[APP_UART_2] == NULL)
    {
        uarts[APP_UART_2] = uart_create(HART_UART2, TRUE, UART_RXSIZE, hart_rx_cb, TRUE, UART_TXSIZE, NULL);
        uarts[APP_UART_2]->uart_index = APP_UART_2;
        uarts[APP_UART_2]->dma = DMA1;
        uarts[APP_UART_2]->dma_rx_channel = LL_DMA_CHANNEL_6;
        uarts[APP_UART_2]->dma_tx_channel = LL_DMA_CHANNEL_7;
        uart_recv_en(uarts[APP_UART_2]);
    }
}

void hart_ble_dinit(void)
{
    BLE_EN_DISABLE();
    LL_APB1_GRP1_DisableClock(LL_APB1_GRP1_PERIPH_USART5);
    LL_USART_Disable(USART5);
    GPIO_SET_ANALOG(BLE_PWR_GPIO_Port, BLE_PWR_Pin);
    GPIO_SET_ANALOG(BLE_STATE_GPIO_Port, BLE_STATE_Pin);
    GPIO_SET_ANALOG(BLE_TX_GPIO_Port, BLE_TX_Pin);
    GPIO_SET_ANALOG(BLE_RX_GPIO_Port, BLE_RX_Pin);
}

BOOL app_hart_is_idle(void)
{
    return hart_idle;
}

BOOL app_hart_init(void)
{
    hart_init_t init;
    init.hart_protocol_version = HART_PROTOCOL_VERSION_7;
    init.dir = MODULE_SLAVE;
    init.interface.response = response;
    init.interface.flash_read = flash_read;
    init.interface.flash_write = flash_write;
    init.interface.perform_self_test = perform_self_test;
    init.interface.device_reset = device_reset;
    init.interface.squawk_control = squawk_control;
    init.interface.armed = armed;
    init.interface.set_dynamics = set_dynamics;
    init.interface.common_event = common_event;

    uarts[APP_UART_1] = NULL;
    uarts[APP_UART_2] = NULL;
    // 判断当前电流  >=8mA启动蓝牙  >=3.8mA启动uart
    // 注串口初始化移动到all_flow
    // hart_uart_init();
    // hart_ble_init();
    return hart_init(&init);
}