This repository has been archived on 2025-04-02. You can view files and clone it, but cannot push or open issues or pull requests.
controller-hart/User/hart/readme.md

547 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 介绍
HART 协议模块是 C 语言编写的,使用者无需对 HART 协议了解,通过接口文档可以快速完成应用层协议开发。
模块可以分别被主机和从机使用,调用少量接口完成开发,模块接口测试覆盖率 100%。
# 架构
<img src="./public/img/架构.png">
## 架构说明
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
## 组织结构
<img src="./public/img/组织结构.png">
## 模块说明
| 文件 | 路径 | <div style="width:300px">说明</div> |
| :-------------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------- |
| 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为响应数据分配内存。
- 将响应数据结构体添加到缓存中。
- 返回处理成功。
3. hart_slave_init 函数,用于初始化从机。
- 检查输入参数是否有效。
- 初始化从机的请求处理结构体。
- 返回处理成功
4. hart_slave_device_send 函数,用于发送响应帧给一个 HART 从设备
- 定义一些变量,如响应数据长度、异或校验、数据长度指针、响应码指针等。
- 填充响应帧的数据,包括前导码、地址、命令、负载等。
- 计算响应帧的数据长度,并将结果存储在 data_len_p 中。
- 计算响应帧的校验码,并将结果存储在 response_code_p 中。
- 更新响应帧的长度字段和响应码字段。
- 如果响应帧的数据长度大于 0调用 handle->response_call 函数将响应帧发送给指定的 UART 索引。同时,增加设备属性的消息计数器。
5. 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 也一并加入到工程中。
<img src="./public/img/HART加入.png">
2. 添加宏定义和头文件
宏定义STM32,SLAVE
<img src="./public/img/宏定义.png">
头文件路径
<img src="./public/img/头文件.png">
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);
}
```