bs_bcu_app/bsp/bsp_modbus.c

614 lines
24 KiB
C
Raw Permalink Normal View History

2024-11-07 17:24:19 +08:00
#include "bsp_modbus.h"
#include "kit_time.h"
#include "kit_data.h"
//#include "kit_debug.h"
/********************************************************************************************************************************************************************************
1.Modbus数据结构
0x01 线 /1B /1B /2B 线/2B /1B /1B /1B 线/NB
0x02 /1B /1B /2B /2B /1B /1B /1B /NB
0x03 /1B /1B /2B /2B /1B /1B /1B /2NB
0x04 /1B /1B /2B /2B /1B /1B /1B /2NB
0x05 线 /1B /1B /2B /2B /1B /1B /2B /2B
0x06 /1B /1B /2B /2B /1B /1B /2B /2B
0x0F 线 /1B /1B /2B /2B /1B /NB /1B /1B /2B /2B
0x10 /1B /1B /2B /2B /1B /2NB /1B /1B /2B /2B
2.Modbus TCP MBAP报文头
CRC校验
0001 0000 0006 01 04 00000005
0001 0000 000D 01 04 0A00000001000200030004
-----------------------------------------------------------------------------------
2 Modbus请求/
2 0=Modbus协议
2
1 线
3.线
0xFF00线 ON0x0000线OFF线
********************************************************************************************************************************************************************************/
#define BRROCAST_ADDR (0u)
#define MB_DEV_ADDR_POS (0u)
#define MB_FUN_CODE_POS (1u)
#define MB_REG_ADDR_POS (2u)
#define MB_DATA_POS (2u)
#define MB_REG_NUM_POS (4u)
#define MB_MIN_LEN (3u)
//TCP比RTU多了事务处理标识符、协议标识符和长度总共6B
#define MB_TCP_OFFSET (6u)
#define MB_TCP_DATA_LEN_POS (4u)
#define MB_TCP_DEV_ADDR_POS (6u)
ModbusCommData modbus_comm_data __attribute__((section (".CCM_RAM")));
const uint8_t modbus_offset_len[kModbusType_End] = {0, MB_TCP_OFFSET};
static void bsp_modbus_task_poll(uint32_t base_time, ModbusItem * const item, ModbusTask *cur_task, GateItem *gate);
void bsp_modbus_poll(ModbusItem * const item, bool is_enable_call)
{
uint8_t fun_code;
uint16_t tmp, pos, addr, reg_num;
uint32_t i, data, tick_diff;
bool cond[kModbusType_End];
GateItem *gate;
ModbusTask *cur_task = NULL;
BspMdExCode err = kBspMdEx_InvalidFunc;
if((item != NULL) && (item->ctrl_rx_int_call != NULL))
{
if(item->task_num > 0)
{
cur_task = &item->modbus_task[item->cur_task_idx];
}
//判断超时时关闭接收中断,防止刚判断完超时后,马上收到数据,导致数据丢失
item->ctrl_rx_int_call(item->comm_dev, false);
tick_diff = kit_time_get_interval_by_now(item->tick);
if(tick_diff >= 5)
{
pos = item->buf_pos;
item->buf_pos = 0;
item->tick = kit_time_get_tick();
item->ctrl_rx_int_call(item->comm_dev, true);
tmp = modbus_offset_len[item->type];
addr = item->buf[MB_DEV_ADDR_POS + tmp];
//当为主设备时接收任何addr设备数据从设备时需要判断地址
if (((item->is_master == true) || ((item->is_master == false) && ((item->addr == addr) || (addr == BRROCAST_ADDR))))
&& (pos > MB_MIN_LEN))
{
if(addr < MODBUS_MAX_DEV)
{
item->time_out[addr] = 0;
}
//modbus RTU校验CRC
cond[kModbusType_RTU] = (kit_check_crc16(item->buf, pos) == 0);
cond[kModbusType_TCP] = (pos == (READ_BT_INT16U_BY_CONST_POS(item->buf, MB_TCP_DATA_LEN_POS) + 6));
if(cond[item->type] == true)
{
//做从机,起始地址直接从数据中获取
//做主机,主动发送数据时会记录起始地址,用于接收从设备数据时使用
addr = (item->is_master == true) ? addr = item->start_addr : READ_BT_INT16U_BY_CONST_POS(item->buf, MB_REG_ADDR_POS + tmp);
fun_code = item->buf[MB_FUN_CODE_POS + tmp];
if((item->fun_call_array[fun_code] != NULL) || (item->task_num != 0))
{
gate = item->gate;
reg_num = READ_BT_INT16U_BY_CONST_POS(item->buf, MB_REG_NUM_POS + tmp);
switch(fun_code)
{
case kModbusFun_0x01:
case kModbusFun_0x02:
case kModbusFun_0x03:
case kModbusFun_0x04:
//对于接收RTC数据长度 = 报文总长度-(地址 功能码 CRC校验TCP数据长度 = 报文总长度-MBAP报文头 功能码)
pos = (item->type == kModbusType_RTU)? (pos - 1 - 1 - 2 - 1) : (pos - 7 - 1);
if(item->task_num == 0)
{
//对于Master addr为请求起始地址 reg_num 无意义 buf为数据 pos为buf长度
//对于Slaver addr为请求起始地址 reg_num 线圈/寄存器数量 buf为要写入回复数据地址 pos无意义更具reg_num填数据
//MB_DATA_POS + tmp + 1+1为字节数
err = item->fun_call_array[fun_code](addr, reg_num, &item->buf[MB_DATA_POS + tmp + 1], &pos);
//数据长度
pos = (fun_code < kModbusFun_0x03) ? ((reg_num + 7) >> 3) : (reg_num << 1);
item->buf[MB_DATA_POS + tmp] = pos;
pos++; //增加字节数
}
//通过建模方式进行modbus通信
else if(cur_task != NULL)
{
cur_task->tick += 900;
//读读数据
if(cur_task->head->fun_code < 3)
{
tmp = (pos == 1) ? ((uint16_t)item->buf[MB_DATA_POS + tmp + 1] << 8) : READ_BT_INT16U_BY_CONST_POS(item->buf, MB_DATA_POS + tmp + 1);
item->gate->read_buf[cur_task->buf_addr] = tmp;
}
else if(cur_task->head->fun_code < 5)
{
item->gate->read_buf[cur_task->buf_addr] = tmp;
bsp_gate_push_read_data(item->gate, cur_task->buf_addr, &item->buf[MB_DATA_POS + tmp + 1], pos);
}
//读写数据
else
{
if(fun_code < kModbusFun_0x03)
{
//一个task最多限制32个线圈每个线圈在写buf中占2字节
data = kit_bt_read_buf(&item->buf[MB_DATA_POS + tmp + 1], 0, pos);
for(i = 0; i < cur_task->head->reg_num; i++)
{
pos = (KIT_GET_BIT(data, i) != 0) << 8;
bsp_gate_push_write_data(gate, cur_task->buf_addr + i, (uint8_t *)&pos, 2);
}
}
else
{
bsp_gate_push_write_data(gate, cur_task->buf_addr, &item->buf[MB_DATA_POS + tmp + 1], pos);
}
}
}
break;
case kModbusFun_0x05:
case kModbusFun_0x06:
if(item->task_num == 0)
{
//对于Master addr为请求起始地址 reg_num 输出值 buf无意义 pos 无意义
//对于Slaver addr为请求起始地址 reg_num 输出值 buf无意义 pos 无意义
err = item->fun_call_array[fun_code](addr, reg_num, &item->buf[MB_REG_NUM_POS + tmp], &pos);
pos = 4;//起始地址+输出数量
}
//通过建模方式进行modbus通信
else if(cur_task != NULL)
{
cur_task->tick += 900;
pos = modbus_offset_len[item->type] + 4;
data = READ_LT_INT16U_BY_CONST_POS(item->buf, pos);
if(item->gate_write_data_absolute_addr == addr)
{
if(fun_code == kModbusFun_0x05)
{
data = (data == 0xFF) ? 0x0100 : 0;
}
if(data == item->gate_write_data_value)
{
gate->write_buf[item->gate_write_data_addr] = data;
item->gate_write_data_absolute_addr = MODBUS_GATE_FREE_ADDR;
}
}
}
break;
case kModbusFun_0x0F:
case kModbusFun_0x10:
//对于Master addr为请求起始地址 reg_num 线圈/寄存器数量 buf无意义 pos无意义
//对于Slaver addr为请求起始地址 reg_num 线圈/寄存器数量 buf寄存器值 pos为buf长度
tmp += 7; //buf 地址7为要写数据
err = item->fun_call_array[fun_code](addr, reg_num, &item->buf[tmp], &pos);
pos = 4;//起始地址+输出数量
break;
default:
break;
}
}
else
{
err = kBspMdEx_InvalidFunc;
}
//做主机时不回复从机报文
if(item->is_master == false)
{
if(err == kBspMdEx_None)
{
bsp_modbus_pos_send(item, pos);
}
else
{
bsp_modbus_neg_send(item, err);
}
}
}
}
if(cur_task != NULL)
{
bsp_modbus_task_poll(tick_diff, item, cur_task, item->gate);
}
if((item->master_call != NULL) && (is_enable_call == true))
{
item->master_call(item, tick_diff);
}
for(i = 0; i < MODBUS_MAX_DEV; i++)
{
if(item->time_out[i] < 60000)
{
item->time_out[i] += tick_diff;
}
}
}
else
{
item->ctrl_rx_int_call(item->comm_dev, true);
}
}
}
uint16_t bsp_modbus_get_time_out(ModbusItem * const item, uint8_t addr)
{
uint16_t tmp = 65000;
if(addr < MODBUS_MAX_DEV)
{
tmp = item->time_out[addr];
}
return tmp;
}
static void bsp_modbus_task_poll(uint32_t base_time, ModbusItem * const item, ModbusTask *cur_task, GateItem *gate)
{
uint8_t fun_code;
uint16_t tmp, data;
int32_t addr;
uint32_t cur_idx;
ModbusHead *head;
cur_task->tick += base_time;
if(cur_task->tick >= cur_task->head->time_out + 1000)
{
cur_task->tick = 0;
if(item->task_cashe != 0xFF)
{
cur_idx = item->task_cashe;
item->task_cashe = 0xFF;
}
else
{
//查找下一个读指令当轮询完一轮后首个即默认为读指令故第一个task必须为读指令
cur_idx = item->cur_task_idx;
if(item->is_read_write_data == true)
{
if(++cur_idx >= item->task_num)
{
cur_idx = 0;
}
}
else
{
while(true)
{
if(++cur_idx >= item->task_num)
{
cur_idx = 0;
}
if(item->modbus_task[cur_idx].head->fun_code < 0x05)
{
break;
}
}
}
}
addr = item->gate_write_data_absolute_addr;
if(addr != MODBUS_GATE_FREE_ADDR)
{
//每一次请求写数据gate 重试次数+1
if(item->gate_write_try_cnt++ >= 3)
{
item->gate_write_data_absolute_addr = MODBUS_GATE_FREE_ADDR;
}
else
{
head = item->gate_write_data_head;
tmp = item->gate_write_data_value;
if(head->fun_code == 0x05)
{
data = (tmp != 0)? 0xFF00 : 0;
}
else
{
data = tmp >> 8;
data |= tmp << 8;
}
bsp_modbus_master_single_write(item, head->slave_addr, head->fun_code, addr, data);
item->task_cashe = cur_idx;
return;
}
}
item->cur_task_idx = cur_idx;
head = item->modbus_task[cur_idx].head;
fun_code = head->fun_code;
if(fun_code > 4)
{
fun_code = head->time_out;
}
//head->time_out 为0时为主动上送
if(head->time_out != 0)
{
bsp_modbus_master_read(item, head->slave_addr, fun_code, head->reg_addr, head->reg_num);
}
}
}
void bsp_modbus_pos_send(ModbusItem * const item, uint16_t len)
{
uint16_t mb_crc;
uint32_t cnt;
if((item != NULL) && (item->send_call != NULL)
&&(item->buf[MB_DEV_ADDR_POS + modbus_offset_len[item->type]] != BRROCAST_ADDR))
{
cnt = len + 1 + 1;//addr and code
if(item->type == kModbusType_RTU)
{
mb_crc = kit_check_crc16(item->buf, cnt);
WRITE_LT_INT16U(item->buf, cnt, mb_crc);
}
else
{
WRITE_BT_INT16U_BY_CONST_POS(item->buf, MB_TCP_DATA_LEN_POS, cnt)
}
item->send_call(item->comm_dev, item->buf, cnt + modbus_offset_len[item->type]);
}
}
void bsp_modbus_neg_send(ModbusItem * const item, BspMdExCode ex_code)
{
uint16_t mb_crc;
uint32_t cnt;
if((item != NULL) && (item->send_call != NULL)
&&item->buf[MB_DEV_ADDR_POS + modbus_offset_len[item->type]] != BRROCAST_ADDR)
{
cnt = 3;
item->buf[1 + modbus_offset_len[item->type]] |= 0x80;
item->buf[2 + modbus_offset_len[item->type]] = ex_code;
if(item->type == kModbusType_RTU)
{
mb_crc = kit_check_crc16(item->buf, cnt);
WRITE_LT_INT16U(item->buf, cnt, mb_crc);
}
else
{
item->buf[5] = 3;
}
item->send_call(item->comm_dev, item->buf, cnt + modbus_offset_len[item->type]);
}
}
void bsp_modbus_push_data(ModbusItem * const item, uint8_t *buf, uint16_t len)
{
uint32_t i;
if((item != NULL) && (item->buf_pos + len < item->buf_size))
{
for (i = 0; i < len; i++)
{
item->buf[item->buf_pos++] = buf[i];
}
if(item->type == kModbusType_RTU)
{
item->tick = kit_time_get_tick();
}
else
{
item->tick = 0;
//tcp使用同步接口时不用poll
//bsp_modbus_poll(item);
}
}
}
void bsp_modbus_set_slaver_addr(ModbusItem * const item, uint8_t addr)
{
if(item != NULL)
{
item->addr = addr;
}
}
uint16_t bsp_modbus_fill_mbap_head(ModbusItem * const item, uint8_t dev_addr, uint16_t len)
{
uint32_t cnt = 0;
if(item->type == kModbusType_TCP)
{
//事务元标识符:服务器从接收的请求中复制 客户端每次++
item->tcp_transaction++;
WRITE_BT_INT16U(item->buf, cnt, item->tcp_transaction);
//协议标识符0=MODBUS 协议
WRITE_BT_INT16U(item->buf, cnt, 0);
//长度单元标识符1B + 功能码1B + 数据4B
WRITE_BT_INT16U(item->buf, cnt, len);
}
//单元标识符:从设备地址 RTU TCP都需要填地址
WRITE_BT_INT8U(item->buf, cnt, dev_addr);
return cnt;
}
//start_addr 用于做主站时记录请求的地址 支持0x01 0x02 0x03 0x04
void bsp_modbus_master_read(ModbusItem * const item, uint8_t dev_addr, uint8_t fun_code, uint16_t start_addr, uint16_t reg_num)
{
uint16_t mb_crc;
uint32_t cnt;
if((item != NULL) && (item->send_call != NULL))
{
item->buf_pos = 0;
item->is_master = true;
item->start_addr = start_addr;
//6 = 单元标识符1Byte + 功能码1Byte + 起始地址2Byte + 寄存器数2Byte
cnt = bsp_modbus_fill_mbap_head(item, dev_addr, 6);
item->buf[cnt++] = fun_code;
WRITE_BT_INT16U(item->buf, cnt, start_addr);
WRITE_BT_INT16U(item->buf, cnt, reg_num);
if(item->type != kModbusType_TCP)
{
mb_crc = kit_check_crc16(item->buf, cnt);
WRITE_LT_INT16U(item->buf, cnt, mb_crc);
}
item->send_call(item->comm_dev, item->buf, cnt);
}
}
//做从站时主动发送数据 支持0x01 0x02 0x03 0x04
void bsp_modbus_slaver_send_read(ModbusItem * const item, uint8_t dev_addr, uint8_t fun_code, uint8_t *buf, uint16_t len)
{
uint16_t mb_crc;
uint32_t cnt;
if((item != NULL) && (item->send_call != NULL))
{
item->buf_pos = 0;
cnt = bsp_modbus_fill_mbap_head(item, dev_addr, 1 + 1 + 1 + len);
item->buf[cnt++] = fun_code;
WRITE_BT_INT8U(item->buf, cnt, len);
kit_copy_buf(&item->buf[cnt], buf, len);
cnt += len;
if(item->type != kModbusType_TCP)
{
mb_crc = kit_check_crc16(item->buf, cnt);
WRITE_LT_INT16U(item->buf, cnt, mb_crc);
}
item->send_call(item->comm_dev, item->buf, cnt);
}
}
//支持 0x05 0x06
void bsp_modbus_master_single_write(ModbusItem * const item, uint8_t dev_addr, uint8_t fun_code, uint16_t start_addr, uint16_t value)
{
bsp_modbus_master_read(item, dev_addr, fun_code, start_addr, value);
}
//支持 0x0F 0x10
void bsp_modbus_master_series_write(ModbusItem * const item, uint8_t dev_addr, uint8_t fun_code, uint16_t start_addr, uint16_t reg_num, uint8_t *buf)
{
uint16_t mb_crc, len;
uint32_t cnt = 0;
if((item != NULL) && (item->send_call != NULL))
{
item->buf_pos = 0;
item->is_master = true;
item->start_addr = start_addr;
//数据计算字节数
if(fun_code == 0x10)
{
len = reg_num << 1;
}
else
{
len = reg_num / 8;
len = ((reg_num % 8) == 0) ? len : (len + 1);
}
cnt = bsp_modbus_fill_mbap_head(item, dev_addr, 2 + 4 + 1 + len);
item->buf[cnt++] = fun_code;
WRITE_BT_INT16U(item->buf, cnt, start_addr);
WRITE_BT_INT16U(item->buf, cnt, reg_num);
WRITE_BT_INT8U(item->buf, cnt, len);
kit_copy_buf(&item->buf[cnt], buf, len);
cnt += len;
if(item->type != kModbusType_TCP)
{
mb_crc = kit_check_crc16(item->buf, cnt);
WRITE_LT_INT16U(item->buf, cnt, mb_crc);
}
item->send_call(item->comm_dev, item->buf, cnt);
}
}
void bsp_modbus_link_list(ModbusItem * const item)
{
ModbusItem ** cur_item = &modbus_comm_data.head_item;
while(*cur_item != NULL)
{
if(*cur_item == item)
{
return;
}
else
{
cur_item = &(*cur_item)->next_item;
}
}
*cur_item = item;
}
void bsp_modbus_register_master_fun(ModbusItem * const item, MasterCall master_call)
{
if((item != NULL) && (master_call != NULL))
{
item->master_call = master_call;
bsp_modbus_link_list(item);
}
}
void bsp_modbus_register_fun(ModbusItem * const item, ModbusFun fun_code, ModbusFunCall register_call)
{
if((item != NULL) && (fun_code < kModbusFun_End))
{
item->fun_call_array[fun_code] = register_call;
bsp_modbus_link_list(item);
}
}
//为了方便统一处理
//05写单个线圈也占用2个字节ON为1 OFF为0发送时将数据转换ON为0xFF00 OFF为0
//
bool bsp_modbus_register_task(ModbusItem * const item, ModbusHead *head)
{
uint16_t len;
bool res = false;
ModbusTask *task;
if((item != NULL) && (item->gate != NULL) && (modbus_comm_data.max_task_num < MODBUS_MAX_TASK))
{
res = true;
item->is_master = true;
if(item->modbus_task == NULL)
{
item->gate_write_data_end_addr = item->gate_write_data_start_addr = modbus_comm_data.gate_write_data_cnt;
item->modbus_task = &modbus_comm_data.modbus_task_buf[modbus_comm_data.max_task_num];
}
modbus_comm_data.max_task_num++;
task = &item->modbus_task[item->task_num++];
len = head->reg_num;
if(head->fun_code < 3)
{
len = (len + 15) / 16;
task->buf_addr = bsp_gate_get_read_buf_addr(item->gate, len);
}
else if(head->fun_code < 5)
{
task->buf_addr = bsp_gate_get_read_buf_addr(item->gate, len);
}
else
{
modbus_comm_data.gate_write_data_cnt += len;
item->gate_write_data_end_addr += len;
task->buf_addr = bsp_gate_get_write_buf_addr(item->gate, len);
}
task->tick = 0;
task->head = head;
bsp_modbus_link_list(item);
}
return res;
}