#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; }