bs_bcu_app/bsp/bsp_modbus.c

614 lines
24 KiB
C
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.

#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请求线圈为 ON。十六进制值0x0000请求线圈为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;
}