#include "modbus_master.h" #include "modbus_encoder.h" #include "st_sys.h" #include //周期通讯指令缓冲池 static modbus_master_period_cmd_t period_cmd_pool[MODBUS_MASTER_PERIOD_POOL_SIZE]; //单次命令缓冲池 static modbus_master_once_cmd_t once_cmd_pool[MODBUS_MASTER_ONCE_POOL_SIZE]; //周期命令池已用计数 static int period_cmd_count = 0; void modbus_master_init(void) { memset(period_cmd_pool, 0, MODBUS_MASTER_PERIOD_POOL_SIZE); memset(once_cmd_pool, 0, MODBUS_MASTER_PERIOD_POOL_SIZE); } /** * 创建一条周期任务 * * @author lxz (2019/8/25/周日) * * @param group modbus 运行组 * @param id id * @param cmd 命令 * @param address 地址 * @param number 长度 * @param value * 要读写的数值,对于写指令,它是数值 * @param respone_timeout 该指令的等待响应时间 * @param period * 该指令的采集周期,协议会尽可能满足该周期 * @param on_respone 命令响应时的回调函数 * * @return modbus_master_cmd_t 命令的指针 */ modbus_master_period_cmd_t* modbus_master_add_period_cmd( modbus_master_t *master, unsigned char cmdcode, unsigned char id, unsigned char cmd, unsigned short address, unsigned short number, unsigned char *value, int respone_timeout, void (*on_respone)(modbus_master_cmd_t *, unsigned char *, int), int period ) { if (period_cmd_count < MODBUS_MASTER_PERIOD_POOL_SIZE) { modbus_master_period_cmd_t *next = 0; //获取一个没用过的包 modbus_master_period_cmd_t *period_cmd = &period_cmd_pool[period_cmd_count]; period_cmd_count++; //初始化命令 period_cmd->cmd.cmdcode=cmdcode; period_cmd->cmd.id = id; period_cmd->cmd.cmd = cmd; period_cmd->cmd.address = address; period_cmd->cmd.number = number; period_cmd->cmd.value = value; period_cmd->cmd.respone_timeout = respone_timeout; period_cmd->cmd.on_respone = on_respone; period_cmd->cmd.dir = 0; period_cmd->period = period; period_cmd->next = 0; //将命令挂靠到主站对象到 if (master->period_cmd_list == 0) { master->period_cmd_list = period_cmd; } else { next = master->period_cmd_list; while (next->next != 0) { next = (modbus_master_period_cmd_t *)next->next; } next->next = (void *)period_cmd; } return period_cmd; } return 0; } /** * 创建一条单次命令 * * @author lxz (2019/8/25/周日) * * @param group modbus 运行组 * @param id id * @param cmd 命令 * @param address 地址 * @param number 长度 * @param value * 要读写的数值,对于写指令,它是数值 * @param respone_timeout 该指令的等待响应时间 * @param period * 该指令的采集周期,协议会尽可能满足该周期 * @param on_respone 命令响应时的回调函数 * @param dir 方向,0表示读,1表示写 * * @return modbus_master_cmd_t 命令的指针 */ modbus_master_once_cmd_t* modbus_master_add_once_cmd( modbus_master_t *master, unsigned char cmdcode, unsigned char id, unsigned char cmd, unsigned short address, unsigned short number, unsigned char *value, int respone_timeout, void (*on_respone)(modbus_master_cmd_t *, unsigned char *, int) ) { int index = 0; while (index < MODBUS_MASTER_ONCE_POOL_SIZE) { if (once_cmd_pool[index].token == 0) { break; } index++; } if (index < MODBUS_MASTER_ONCE_POOL_SIZE) { modbus_master_once_cmd_t *next = 0; //获取一个没用过的包 once_cmd_pool[index].token=1; modbus_master_once_cmd_t *once_cmd = &once_cmd_pool[index]; index++;//period_cmd_count++;(修改2024-0703) //初始化命令 once_cmd->cmd.cmdcode=cmdcode; once_cmd->cmd.id = id; once_cmd->cmd.cmd = cmd; once_cmd->cmd.address = address; once_cmd->cmd.number = number; once_cmd->cmd.value = value; once_cmd->cmd.respone_timeout = respone_timeout; once_cmd->cmd.on_respone = on_respone; once_cmd->next = 0; //将命令挂靠到主站对象到 if (master->once_cmd_list == 0) { master->once_cmd_list = once_cmd; } else { next = master->once_cmd_list; while (next->next != 0) { next = (modbus_master_once_cmd_t *)next->next; } next->next = (void *)once_cmd; } return once_cmd; } return 0; } /** * 获取一条指令, * * @author lxz (2019/8/25/周日) * * @param master modbus master对象,里面有命令缓冲 * * @return int * 返回1表示有需要处理的立即指令,返回2表示有需要处理的周期指令 */ static int modbus_master_get_cmd(modbus_master_t *master) { //优先读取单次指令发送 if (master->once_cmd_list != 0) { //复制命令 master->current_cmd = master->once_cmd_list->cmd; //命令归还缓冲 master->once_cmd_list->token = 0; //保存下一条命令 //if (master->once_cmd_list->next != 0) { master->once_cmd_list = (modbus_master_once_cmd_t *)master->once_cmd_list->next; } return 1; } //选取周期指令 else if (master->period_cmd_list != 0) { modbus_master_period_cmd_t *next = master->period_cmd_list; modbus_master_period_cmd_t *pre = master->period_cmd_list; while (next != 0) { //周期时间到 if (sw_timer_expire(&next->timer)) { //周期时间到了, 需要进行处理 master->current_cmd = next->cmd; //将该命令时间重置,并放入尾部,从而保证每个命令有平等进行通讯的机会 sw_timer_start(&next->timer, 0, next->period * 1000); //如果是第一个命令 if (next == master->period_cmd_list) { //当不止一个命令的时候 if (next->next != 0) { //对象保存的是下一个指令 master->period_cmd_list = (modbus_master_period_cmd_t *)next->next; //查找最后一个指令,并将它置于链表尾部 pre = next; while (next->next != 0) { next = next->next; } if (next != pre) { next->next = pre; } pre->next = 0; } } else { //如果不是最后一个指令 if (next->next != 0) { //对象保存的是下一个指令 pre->next = (modbus_master_period_cmd_t *)next->next; //查找最后一个指令,并将它置于链表尾部 pre = next; while (next->next != 0) { next = (modbus_master_period_cmd_t *)next->next; } if (next != pre) { next->next = pre; } pre->next = 0; } } return 2; } //因为是单向列表,必须知道自己上一个指令对象; pre = next; next = (modbus_master_period_cmd_t *)next->next; } } return 0; } /** * 创建命令 * * @author lxz (2019/8/25/周日) * * @param master * @param request * * @return int */ static int modbus_master_create_request(modbus_master_t *master, unsigned char *request) { int length = 0; int request_length = 0; int number = 0; modbus_master_cmd_t *cmd = &master->current_cmd; //写指令 master->send_count++; switch (cmd->cmd) { case 0x01: case 0x02: case 0x03: case 0x04: //读指令进行统一,不负责特殊指令 request[length++] = cmd->id; request[length++] = cmd->cmd; request[length++] = (unsigned char)(cmd->address >> 8); request[length++] = (unsigned char)(cmd->address); request[length++] = (unsigned char)(cmd->number >> 8); request[length++] = (unsigned char)(cmd->number); if (cmd->cmd == 0x02 || cmd->cmd == 0x01) { request_length = 3 + (cmd->number + 7) >> 3; } else { request_length = 3 + cmd->number * 2; } break; case 0x05: if (cmd->number == 1) { //写单个线圈 request[length++] = cmd->id; request[length++] = 0x05; request[length++] = (unsigned char)(cmd->address >> 8); request[length++] = (unsigned char)(cmd->address); if (cmd->value[0]) { request[length++] = 0xff; request[length++] = 0; } else { request[length++] = 0; request[length++] = 0; } } else { //写多个线圈 request[length++] = cmd->id; request[length++] = 0x0F; cmd->cmd=0x0F;//命令已经改变 20240513 laz request[length++] = (unsigned char)(cmd->address >> 8); request[length++] = (unsigned char)(cmd->address); request[length++] = (unsigned char)(cmd->number >> 8); request[length++] = (unsigned char)(cmd->number); request[length++] = (unsigned char)((cmd->number + 7) >> 3); memcpy(&request[length], cmd->value, request[length - 1]); length += request[length - 1]; } request_length = 6; break; case 0x0F: //写多个线圈 request[length++] = cmd->id; request[length++] = 0x0F; request[length++] = (unsigned char)(cmd->address >> 8); request[length++] = (unsigned char)(cmd->address); request[length++] = (unsigned char)(cmd->number >> 8); request[length++] = (unsigned char)(cmd->number); request[length++] = (unsigned char)((cmd->number + 7) >> 1); memcpy(&request[length], cmd->value, request[length - 1]); length += request[length - 1]; request_length = 6; break; case 0x06: if (cmd->number == 1) { //写单个数值 request[length++] = cmd->id; request[length++] = 0x06; request[length++] = (unsigned char)(cmd->address >> 8); request[length++] = (unsigned char)(cmd->address); request[length++] = (unsigned char)(cmd->value[1]); request[length++] = (unsigned char)(cmd->value[0]); } else { //写多个数值 request[length++] = cmd->id; request[length++] = 0x10; cmd->cmd=0x10;//命令已经改变 20240513 laz request[length++] = (unsigned char)(cmd->address >> 8); request[length++] = (unsigned char)(cmd->address); request[length++] = (unsigned char)(cmd->number >> 8); request[length++] = (unsigned char)(cmd->number); request[length++] = (unsigned char)(cmd->number * 2); while (number < cmd->number) { request[length++] = cmd->value[number * 2 + 1]; request[length++] = cmd->value[number * 2]; number++; } } request_length = 6; break; case 0x10: //写多个数值 request[length++] = cmd->id; request[length++] = 0x10; request[length++] = (unsigned char)(cmd->address >> 8); request[length++] = (unsigned char)(cmd->address); request[length++] = (unsigned char)(cmd->number >> 8); request[length++] = (unsigned char)(cmd->number); request[length++] = (unsigned char)(cmd->number * 2); while (number < cmd->number) { request[length++] = cmd->value[number * 2 + 1]; request[length++] = cmd->value[number * 2]; number++; } request_length = 6; break; default: //默认特殊指令,会忽略地址,将number当做value里面的指令长度 request[length++] = cmd->id; request[length++] = cmd->cmd; memcpy(&request[length], cmd->value, cmd->number); length += cmd->number; request_length = 6; break; } //具有有效命令的时候,进行编码并返回编码后的命令长度 if (length > 0) { modbus_encoder_t *encoder = modbus_encoder_get(master->encoder_type); length = encoder->encode(request, request, length); //计算命令需要等待的时候,单位是us request_length = encoder->length_calc(1,request_length); master->wait_respone_time = request_length * master->one_byte_time + cmd->respone_timeout * 1000; return length; } return 0; } /** * 处理接收命令 * * @author lxz (2019/8/26/周一) * * @param master * @param respone * @param length */ static char modbus_master_deal_respone(modbus_master_t *master, unsigned char *respone, int length) { modbus_master_cmd_t *cmd = &master->current_cmd; int number = 0; if (respone[0] != cmd->id) { //ID不正确 return 1; } else if (respone[1] & 0x80) { //命令报错 //如果设置了回调函数,调用回调函数 if (cmd->on_respone != 0) { cmd->on_respone(&master->current_cmd, respone, length); } if ((respone[1] & 0x7F) == cmd->cmd) { return 0; } else { return 2; } } else if (respone[1] != cmd->cmd) { //错误命令 return 3; } master->receive_count++; //只对通用的几个读值指令进行处理 switch (cmd->cmd) { case 0x01: case 0x02: memcpy(&cmd->value[number], &respone[3], respone[2]); break; case 0x03: case 0x04: while (number + 1 < respone[2]) { cmd->value[number] = respone[3 + number + 1]; cmd->value[number + 1] = respone[3 + number]; number += 2; } break; } //如果设置了回调函数,回调函数 if (cmd->on_respone != 0) { cmd->on_respone(&master->current_cmd, respone, length); } return 0; } /** * 主站运行函数 * * @author lxz (2019/8/25/周日) * * @param request 请求缓冲 * @param respone 响应的数据缓冲 * @param length 响应数据长度 * * @return int * 生成的请求命令长度,当大于0时,说明request里面有需要进行发送的命令 */ int modbus_master_run(modbus_master_t *master, unsigned char *request, unsigned char *respone, int length) { switch (master->step) { case 0: //获取一条指令,优先指令 if (modbus_master_get_cmd(master)) { master->step++; master->current_try_time = master->retry + 1; } break; case 1: //可以重次几次 if (master->current_try_time) { master->current_try_time--; master->step++; return modbus_master_create_request(master, request); } else { master->step = 0; } break; case 2: //等待响应时间 master->step++; sw_timer_start(&master->timer, 0, master->wait_respone_time); break; case 3: if (length > 0) { //执行校验,如果校验失败,那就重试,校验成功,处理完等待间隔 modbus_encoder_t *encoder = modbus_encoder_get(master->encoder_type); length = encoder->decode(respone, respone, length); if (length > 0) { //处理协议,如果返回一些错误,需要重试 if (modbus_master_deal_respone(master,respone,length)) { master->step++; } } sw_timer_start(&master->timer, 0, master->interval); master->step++; } else if (sw_timer_expire(&master->timer)) { //超时没收到命令,重发 master->step = 1; } break; case 4: //上次通讯完成,准备下一条指令 if (sw_timer_expire(&master->timer)) { master->step = 0; } break; case 5: //上次通讯是错误帧,需要重试 if (sw_timer_expire(&master->timer)) { master->step = 1; } break; } return 0; }