[/x00-xff]都包括哪些字符?

找了好多资料,说"[/x00-xff]"是表示单字节字符,"[^/x00-xff]"是表示双字节字符,是这样的吗?

#include "main.h" /*----------------------------------------------------------------------------------------------------------*/ // 添加全局变量 ConfigData g_config; uint8_t x_left_running = 0; //手动控制变量 uint8_t x_right_running = 0; //手动控制变量 uint8_t z_up_running = 0; //手动控制变量 uint8_t z_down_running = 0; //手动控制变量 uint8_t levelState; // 液面传感器状态(0:正常 1:异常) // 静态变量保存历史值,用于判断是否变化(仅初始化一次) static int32_t last_temp = 0; // 初始化为极小值,确保首次能触发更新 static int32_t last_temp2 = 0; // 初始化为极小值,确保首次能触发更新 static uint8_t last_level = 0; // 初始化为极大值,确保首次能触发更新 /*----------------------------------------------------------------------------------------------------------*/ // 初始化SD卡文件 FIL step_file; FRESULT fr; FIL log_file; FIL config_file; /*----------------------------------------------------------------------------------------------------------*/ extern volatile uint8_t x_right_limit_triggered; extern volatile uint8_t z_up_limit_triggered; /*----------------------------------------------------------------------------------------------------------*/ // 全局变量:记录当前状态、延时开始时间、目标延时 StainState g_stain_state = STAIN_IDLE; // 当前染色状态 uint32_t g_delay_start_tick = 0; // 延时开始的时间戳(毫秒) uint32_t g_current_delay_s = 0; // 当前步骤的目标延时时长(毫秒) /*----------------------------------------------------------------------------------------------------------*/ // 从SD卡读取配置参数(扩展支持新参数) uint8_t read_config_from_sd(ConfigData *config) { FIL config_file; FRESULT fr; char line[4096]; fr = f_open(&config_file, "0:config.txt", FA_READ); delay_ms(5); if (fr != FR_OK) { printf("config2.t0.txt=\"配置文件打开失败\"\xff\xff\xff"); config->x_pos1 = 160000; config->x_pos2 = 510000; config->x_pos3 = 960000; config->x_pos4 = 1370000; config->x_pos5 = 1750000; config->x_pos6 = 2080000; config->x_pos7 = 2400000; config->x_pos8 = 2710000; config->x_pos9 = 3020000; config->x_pos10 = 80000; config->x_offset = 0; config->z_soak = 230000; config->z_lift = 480000; config->z_lift1 = 0; config->t_fix = 60; // 30min config->t_rinse = 10; // 30s config->t_acid = 60; // 30min config->t_stain = 60; // 45min config->t_dehy1 = 60; // 10min config->t_dehy2 = 60; config->t_dehy3 = 60; config->t_dehy4 = 60; config->t_dry = 10; // 20min write_config_to_sd(config); return 1; } delay_ms(5); while (f_gets(line, sizeof(line), &config_file)) { if (strstr(line, "X_POS1:")) sscanf(line, "X_POS1:%ld", &config->x_pos1); else if (strstr(line, "X_POS2:")) sscanf(line, "X_POS2:%ld", &config->x_pos2); else if (strstr(line, "X_POS3:")) sscanf(line, "X_POS3:%ld", &config->x_pos3); else if (strstr(line, "X_POS4:")) sscanf(line, "X_POS4:%ld", &config->x_pos4); else if (strstr(line, "X_POS5:")) sscanf(line, "X_POS5:%ld", &config->x_pos5); else if (strstr(line, "X_POS6:")) sscanf(line, "X_POS6:%ld", &config->x_pos6); else if (strstr(line, "X_POS7:")) sscanf(line, "X_POS7:%ld", &config->x_pos7); else if (strstr(line, "X_POS8:")) sscanf(line, "X_POS8:%ld", &config->x_pos8); else if (strstr(line, "X_POS9:")) sscanf(line, "X_POS9:%ld", &config->x_pos9); else if (strstr(line, "X_POS10:")) sscanf(line, "X_POS10:%ld", &config->x_pos10); else if (strstr(line, "X_OFFSET:")) sscanf(line, "X_OFFSET:%ld", &config->x_offset); else if (strstr(line, "Z_SOAK:")) sscanf(line, "Z_SOAK:%ld", &config->z_soak); else if (strstr(line, "Z_LIFT:")) sscanf(line, "Z_LIFT:%ld", &config->z_lift); else if (strstr(line, "Z_LIFT1:")) sscanf(line, "Z_LIFT1:%ld", &config->z_lift1); else if (strstr(line, "T_FIX:")) sscanf(line, "T_FIX:%lu", &config->t_fix); else if (strstr(line, "T_RINSE:")) sscanf(line, "T_RINSE:%lu", &config->t_rinse); else if (strstr(line, "T_ACID:")) sscanf(line, "T_ACID:%lu", &config->t_acid); else if (strstr(line, "T_STAIN:")) sscanf(line, "T_STAIN:%lu", &config->t_stain); else if (strstr(line, "T_DEHY1:")) sscanf(line, "T_DEHY1:%lu", &config->t_dehy1); else if (strstr(line, "T_DEHY2:")) sscanf(line, "T_DEHY2:%lu", &config->t_dehy2); else if (strstr(line, "T_DEHY3:")) sscanf(line, "T_DEHY3:%lu", &config->t_dehy3); else if (strstr(line, "T_DEHY4:")) sscanf(line, "T_DEHY4:%lu", &config->t_dehy4); else if (strstr(line, "T_DRY:")) sscanf(line, "T_DRY:%lu", &config->t_dry); } f_close(&config_file); return 0; } /*----------------------------------------------------------------------------------------------------------*/ // 写入配置参数到SD卡 void write_config_to_sd(ConfigData *config) { FIL config_file; FRESULT fr; UINT bw; char line[2048]; fr = f_open(&config_file, "0:config.txt", FA_WRITE | FA_CREATE_ALWAYS); if (fr != FR_OK) { printf("config2.b3.txt=\"配置文件打开失败\"\xff\xff\xff"); return; }else if(fr == FR_OK) { f_lseek(&config_file, 0); // 移至文件开头,准备覆盖写入 } sprintf(line, "X_POS1:%ld\r\n", config->x_pos1); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "X_POS2:%ld\r\n", config->x_pos2); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "X_POS3:%ld\r\n", config->x_pos3); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "X_POS4:%ld\r\n", config->x_pos4); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "X_POS5:%ld\r\n", config->x_pos5); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "X_POS6:%ld\r\n", config->x_pos6); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "X_POS7:%ld\r\n", config->x_pos7); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "X_POS8:%ld\r\n", config->x_pos8); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "X_POS9:%ld\r\n", config->x_pos9); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "X_POS10:%ld\r\n", config->x_pos10); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "X_OFFSET:%ld\r\n", config->x_offset); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "Z_SOAK:%ld\r\n", config->z_soak); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "Z_LIFT:%ld\r\n", config->z_lift); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "Z_LIFT1:%ld\r\n", config->z_lift1); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "T_FIX:%lu\r\n", config->t_fix); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "T_RINSE:%lu\r\n", config->t_rinse); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "T_ACID:%lu\r\n", config->t_acid); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "T_STAIN:%lu\r\n", config->t_stain); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "T_DEHY1:%lu\r\n", config->t_dehy1); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "T_DEHY2:%lu\r\n", config->t_dehy2); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "T_DEHY3:%lu\r\n", config->t_dehy3); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "T_DEHY4:%lu\r\n", config->t_dehy4); f_write(&config_file, line, strlen(line), &bw); sprintf(line, "T_DRY:%lu\r\n", config->t_dry); f_write(&config_file, line, strlen(line), &bw); f_sync(&config_file); f_close(&config_file); delay_ms(10); printf("config2.b3.txt=\"配置成功\"\xff\xff\xff"); printf("config2.tm1.en=1\xff\xff\xff"); USART_ClearBuffer(); } void write_log(int32_t x, int32_t z) { FRESULT fr; UINT bw; char log_line[512]; char time_str[64]; // 存储时间字符串 "YYYY-MM-DD HH:MM:SS" // 1. 通过RTC获取当前时间 RTC_Get(); // 2. 格式化时间字符串 sprintf(time_str, "%04d-%02d-%02d %02d:%02d:%02d", calendar.w_year, calendar.w_month, calendar.w_date, calendar.hour, calendar.min, calendar.sec); // 3. 打开日志文件(追加模式) fr = f_open(&log_file, "0:log.txt", FA_WRITE | FA_OPEN_ALWAYS); if (fr != FR_OK) { OLED_ShowString(1, 1, "Log Open Err"); printf("config3.t0.txt=\"追踪文件打开失败\"\xff\xff\xff"); printf("config3.tm3.en=1\xff\xff\xff"); return; } // 4. 移动到文件末尾(准备追加内容) f_lseek(&log_file, f_size(&log_file)); // 5. 格式化日志内容(末尾仅添加一次 \r\n 换行) sprintf(log_line, "[%s] X_位置:%ld\r, Z_位置:%ld\r\n", time_str,x,z); // 关键修改 // 6. 写入文件并同步 f_write(&log_file, log_line, strlen(log_line), &bw); f_sync(&log_file); // 确保数据写入SD卡 f_close(&log_file); } /*----------------------------------------------------------------------------------------------------------*/ //查看配置参数 void usmart_view_config(void) { FIL file; FRESULT fr; char buffer[2048]; // 缓冲区,根据配置文件可能的大小调整 UINT br; DWORD pos = 0; // 打开文件 fr = f_open(&file, "0:config.txt", FA_READ); if (fr != FR_OK) { printf("config2.va1.txt=\"Open config.txt failed! Error: %d\r\n\"\xff\xff\xff", fr); USART_ClearBuffer(); return; } // 确定读取位置(配置文件通常需全量显示,从开头读取) pos = 0; f_lseek(&file, pos); // 读取文件内容到缓冲区(最多读取缓冲区大小) f_read(&file, buffer, sizeof(buffer) - 1, &br); // 留1字节给结束符 buffer[br] = '\0'; // 添加字符串结束符 // 打印配置文件内容 printf("config2.va1.txt=\"设备配置参数:\r\n%s\r\n\"\xff\xff\xff", buffer); delay_ms(5); // 关闭文件 f_close(&file); USART_ClearBuffer(); } // 参数: 配置项名称(字符串)、新值(整数) void setParam(const char* param_name, int32_t value) { if (strcmp(param_name, "T_FIX") == 0) g_config.t_fix = value; else if (strcmp(param_name, "T_RINSE") == 0) g_config.t_rinse = value; else if (strcmp(param_name, "T_ACID") == 0) g_config.t_acid = value; else if (strcmp(param_name, "T_STAIN") == 0) g_config.t_stain = value; else if (strcmp(param_name, "T_DEHY1") == 0) g_config.t_dehy1 = value; else if (strcmp(param_name, "T_DEHY2") == 0) g_config.t_dehy2 = value; else if (strcmp(param_name, "T_DEHY3") == 0) g_config.t_dehy3 = value; else if (strcmp(param_name, "T_DEHY4") == 0) g_config.t_dehy4 = value; else if (strcmp(param_name, "T_DRY") == 0) g_config.t_dry = value; else if (strcmp(param_name, "X_POS1") == 0) g_config.x_pos1 = value; else if (strcmp(param_name, "X_POS2") == 0) g_config.x_pos2 = value; else if (strcmp(param_name, "X_POS3") == 0) g_config.x_pos3 = value; else if (strcmp(param_name, "X_POS4") == 0) g_config.x_pos4 = value; else if (strcmp(param_name, "X_POS5") == 0) g_config.x_pos5 = value; else if (strcmp(param_name, "X_POS6") == 0) g_config.x_pos6 = value; else if (strcmp(param_name, "X_POS7") == 0) g_config.x_pos7 = value; else if (strcmp(param_name, "X_POS8") == 0) g_config.x_pos8 = value; else if (strcmp(param_name, "X_POS9") == 0) g_config.x_pos9 = value; else if (strcmp(param_name, "X_POS10") == 0) g_config.x_pos10 = value; else if (strcmp(param_name, "Z_LIFT") == 0) g_config.z_lift = value; else if (strcmp(param_name, "Z_LIFT1") == 0) g_config.z_lift1 = value; else if (strcmp(param_name, "Z_SOAK") == 0) g_config.z_soak = value; else if (strcmp(param_name, "X_OFFSET") == 0) { delay_ms(10); g_config.x_offset = value; g_config.x_pos2 = g_config.x_pos2+value; g_config.x_pos3 = g_config.x_pos3+value; g_config.x_pos4 = g_config.x_pos4+value; g_config.x_pos5 = g_config.x_pos5+value; g_config.x_pos6 = g_config.x_pos6+value; g_config.x_pos7 = g_config.x_pos7+value; g_config.x_pos8 = g_config.x_pos8+value; g_config.x_pos9 = g_config.x_pos9+value; g_config.x_pos10 = g_config.x_pos10+value; delay_ms(10); } // 其他参数... else { printf("config2.t0.txt=\"请检查参数,输入无效\"\xff\xff\xff"); USART_ClearBuffer(); return; } write_config_to_sd(&g_config); } //////查看log文件后10行 void usmart_view_log(void) { FIL file; FRESULT fr; char buffer[2048]; UINT br; DWORD file_size, pos; int line_count = 0; fr = f_open(&file, "0:log.txt", FA_READ); if (fr != FR_OK) { printf("config3.va0.txt=\"Open log.txt failed! Error: %d\r\n\"\xff\xff\xff", fr); USART_ClearBuffer(); return; } file_size = f_size(&file); // 从文件末尾向前查找10行 pos = file_size < sizeof(buffer) ? 0 : file_size - sizeof(buffer); f_lseek(&file, pos); f_read(&file, buffer, sizeof(buffer), &br); buffer[br] = '\0'; // 从后往前统计行数 for (int i = br - 1; i >= 0; i--) { if (buffer[i] == '\n') { line_count++; if (line_count >= 30) { pos = i + 1; break; } } } // 打印找到的内容 if (line_count < 20) pos = 0; // 如果不足10行则从头开始 printf("config3.va0.txt=\"\r\n%s\r\n\"\xff\xff\xff",buffer + pos); USART_ClearBuffer(); f_close(&file); } //删除log文件 void delete_log(void) { delay_ms(50); u8 ret = mf_unlink("log.txt"); if (ret == FR_OK) { printf("config3.t0.txt=\"日志删除成功\r\n日志删除成功\r\n\"\xff\xff\xff"); } else { printf("config3.t0.txt=\"日志删除失败\r\n日志删除失败\r\n\"\xff\xff\xff"); } } /*----------------------------------------------------------------------------------------------------------*/ void moveX(int32_t steps) { moveAxisCommon(AXIS_X,steps); } void moveZ(int32_t steps) { moveAxisCommon(AXIS_Z,steps); } /*----------------------------------------------------------------------------------------------------------*/ // 移动到指定染缸(1-9) void moveToTank(uint8_t tank_num) { if (tank_num < 1 || tank_num > 9) { OLED_ShowString(4,1,"Invalid Tank"); return; } // 获取目标缸X坐标 int32_t target_x = 0; switch(tank_num) { case 1: target_x = g_config.x_pos1; break; case 2: target_x = g_config.x_pos2; break; case 3: target_x = g_config.x_pos3; break; case 4: target_x = g_config.x_pos4; break; case 5: target_x = g_config.x_pos5; break; case 6: target_x = g_config.x_pos6; break; case 7: target_x = g_config.x_pos7; break; case 8: target_x = g_config.x_pos8; break; case 9: target_x = g_config.x_pos9; break; } // X轴移动到目标位置 int32_t x_steps = target_x - x_current_position; delay_ms(10); moveAxisCommon(AXIS_X, x_steps);//1 // Z轴下降() moveAxisCommon(AXIS_Z, g_config.z_lift);//2 } /*----------------------------------------------------------------------------------------------------------*/ // 染色流程状态机处理函数(在主循环中调用,非阻塞) void process_auto_stain(void) { if (g_stain_state == STAIN_IDLE) return; // 空闲状态不处理 get_sys_time_s(); uint32_t elapsed_ms = sys_time_s - g_delay_start_tick; // 已过去的时间 switch (g_stain_state) { // -------------------------- 步骤1:等待放入染架 -------------------------- case STAIN_STEP1_WAIT_LOAD_INIT: printf("move.t0.txt=\"请放入染架到固定缸\"\xff\xff\xff"); g_delay_start_tick = sys_time_s; // 记录开始时间 g_current_delay_s = 3; // 等待 g_stain_state = STAIN_STEP1_WAIT_LOAD_DELAY; // 进入延时状态 break; case STAIN_STEP1_WAIT_LOAD_DELAY: if (elapsed_ms >= g_current_delay_s) { // 等待结束,进入固定缸初始化 g_stain_state = STAIN_STEP1_FIX_INIT; } break; // -------------------------- 步骤1:固定缸(1#) -------------------------- case STAIN_STEP1_FIX_INIT: printf("move.t2.bco=1024\xff\xff\xff"); g_delay_start_tick = sys_time_s; // 记录开始时间 g_current_delay_s = g_config.t_fix; // 目标延时(用户配置) g_stain_state = STAIN_STEP1_FIX_DELAY; // 进入延时状态 printf("move.t0.txt=\"固定剩余时间:\"\xff\xff\xff"); printf("move.tm1.en=1\xff\xff\xff"); printf("move.t25.aph=127\xff\xff\xff"); printf("move.t26.aph=127\xff\xff\xff"); break; case STAIN_STEP1_FIX_DELAY: if (elapsed_ms >= g_current_delay_s) { printf("move.t25.txt=\"0\"\xff\xff\xff"); printf("move.tm1.en=0\xff\xff\xff"); printf("move.t25.aph=0\xff\xff\xff"); printf("move.t26.aph=0\xff\xff\xff"); g_stain_state = STAIN_STEP1_FIX_DONE; } break; case STAIN_STEP1_FIX_DONE: // 执行移动操作(非阻塞,假设moveAxisCommon内部已处理等待) printf("move.t0.txt=\"移动到冲洗缸\"\xff\xff\xff"); moveAxisCommon(AXIS_X, g_config.x_pos1); moveAxisCommon(AXIS_Z, g_config.z_lift); Xmove_right2(); //右移小距离,抓住染架 Zmove_up(); g_stain_state = STAIN_STEP2_RINSE_INIT; // 进入下一步 break; // -------------------------- 步骤2:冲洗缸(4#) -------------------------- case STAIN_STEP2_RINSE_INIT: printf("move.t8.bco=1024\xff\xff\xff"); moveToTank(4); // 移动到冲洗缸 up_down(); // 执行上下动作 g_delay_start_tick = sys_time_s; //这里不是current_tick,是因为运动会阻塞函数,无法运行 uint32_t current_tick = sys_time_s;获取当前时间 g_current_delay_s = g_config.t_rinse; // 冲洗延时 g_stain_state = STAIN_STEP2_RINSE_DELAY; printf("move.t0.txt=\"冲洗剩余时间:\"\xff\xff\xff"); printf("move.t25.aph=127\xff\xff\xff"); printf("move.t26.aph=127\xff\xff\xff"); printf("move.tm2.en=1\xff\xff\xff"); break; case STAIN_STEP2_RINSE_DELAY: if (elapsed_ms >= g_current_delay_s) { printf("move.t25.txt=\"0\"\xff\xff\xff"); printf("move.tm2.en=0\xff\xff\xff"); printf("move.t25.aph=0\xff\xff\xff"); printf("move.t26.aph=0\xff\xff\xff"); g_stain_state = STAIN_STEP2_RINSE_DONE; } break; case STAIN_STEP2_RINSE_DONE: printf("move.t0.txt=\"移动到酸化缸\"\xff\xff\xff"); Zmove_up(); g_stain_state = STAIN_STEP3_ACID_INIT; // 进入酸化缸 break; // -------------------------- 步骤3:酸化缸(2#) -------------------------- case STAIN_STEP3_ACID_INIT: printf("move.t4.bco=1024\xff\xff\xff"); moveToTank(2); // 移动到酸化缸 g_delay_start_tick = sys_time_s; g_current_delay_s = g_config.t_acid; // 酸化延时 g_stain_state = STAIN_STEP3_ACID_DELAY; printf("move.t0.txt=\"酸化剩余时间:\"\xff\xff\xff"); printf("move.t25.aph=127\xff\xff\xff"); printf("move.t26.aph=127\xff\xff\xff"); printf("move.tm3.en=1\xff\xff\xff"); break; case STAIN_STEP3_ACID_DELAY: if (elapsed_ms >= g_current_delay_s) { printf("move.t25.txt=\"0\"\xff\xff\xff"); printf("move.t25.aph=0\xff\xff\xff"); printf("move.t26.aph=0\xff\xff\xff"); printf("move.tm3.en=0\xff\xff\xff"); g_stain_state = STAIN_STEP3_ACID_DONE; } break; case STAIN_STEP3_ACID_DONE: printf("move.t0.txt=\"移动到冲洗缸\"\xff\xff\xff"); Zmove_up(); g_stain_state = STAIN_STEP4_RINSE_INIT; // 进入冲洗缸 break; // -------------------------- 步骤4:冲洗缸(4#) -------------------------- case STAIN_STEP4_RINSE_INIT: printf("move.t8.bco=1024\xff\xff\xff"); moveToTank(4); // 移动到冲洗缸 up_down(); g_delay_start_tick = sys_time_s; g_current_delay_s = g_config.t_rinse; // 冲洗延时 g_stain_state = STAIN_STEP4_RINSE_DELAY; printf("move.t0.txt=\"冲洗剩余时间:\"\xff\xff\xff"); printf("move.t25.aph=127\xff\xff\xff"); printf("move.t26.aph=127\xff\xff\xff"); printf("move.va3.val=%d\xff\xff\xff",g_config.t_rinse); printf("move.tm2.en=1\xff\xff\xff"); break; case STAIN_STEP4_RINSE_DELAY: if (elapsed_ms >= g_current_delay_s) { printf("move.t25.txt=\"0\"\xff\xff\xff"); printf("move.tm2.en=0\xff\xff\xff"); printf("move.t25.aph=0\xff\xff\xff"); printf("move.t26.aph=0\xff\xff\xff"); g_stain_state = STAIN_STEP4_RINSE_DONE; } break; case STAIN_STEP4_RINSE_DONE: printf("move.t0.txt=\"移动到染色缸\"\xff\xff\xff"); Zmove_up(); g_stain_state = STAIN_STEP5_STAIN_INIT; // 进入染色缸 break; // -------------------------- 步骤5:染色缸(3#) -------------------------- case STAIN_STEP5_STAIN_INIT: printf("move.t6.bco=1024\xff\xff\xff"); moveToTank(3); // 移动到染色缸 g_delay_start_tick = sys_time_s; g_current_delay_s = g_config.t_stain; // 染色延时 g_stain_state = STAIN_STEP5_STAIN_DELAY; printf("move.t0.txt=\"染色剩余时间:\"\xff\xff\xff"); printf("move.t25.aph=127\xff\xff\xff"); printf("move.t26.aph=127\xff\xff\xff"); printf("move.tm4.en=1\xff\xff\xff"); break; case STAIN_STEP5_STAIN_DELAY: if (elapsed_ms >= g_current_delay_s) { printf("move.t25.txt=\"0\"\xff\xff\xff"); printf("move.tm4.en=0\xff\xff\xff"); printf("move.t25.aph=0\xff\xff\xff"); printf("move.t26.aph=0\xff\xff\xff"); g_stain_state = STAIN_STEP5_STAIN_DONE; } break; case STAIN_STEP5_STAIN_DONE: printf("move.t0.txt=\"移动到冲洗缸\"\xff\xff\xff"); Zmove_up(); g_stain_state = STAIN_STEP6_RINSE_INIT; // 进入冲洗缸 break; // -------------------------- 步骤6:冲洗缸(4#) -------------------------- case STAIN_STEP6_RINSE_INIT: printf("move.t8.bco=1024\xff\xff\xff"); moveToTank(4); // 移动到冲洗缸 up_down(); g_delay_start_tick = sys_time_s; g_current_delay_s = g_config.t_rinse; // 冲洗延时 g_stain_state = STAIN_STEP6_RINSE_DELAY; printf("move.va3.val=%d\xff\xff\xff",g_config.t_rinse); printf("move.t0.txt=\"冲洗剩余时间:\"\xff\xff\xff"); printf("move.t25.aph=127\xff\xff\xff"); printf("move.t26.aph=127\xff\xff\xff"); printf("move.tm2.en=1\xff\xff\xff"); break; case STAIN_STEP6_RINSE_DELAY: if (elapsed_ms >= g_current_delay_s) { printf("move.t25.txt=\"0\"\xff\xff\xff"); printf("move.tm2.en=0\xff\xff\xff"); printf("move.t25.aph=0\xff\xff\xff"); printf("move.t26.aph=0\xff\xff\xff"); g_stain_state = STAIN_STEP6_RINSE_DONE; } break; case STAIN_STEP6_RINSE_DONE: printf("move.t0.txt=\"移动到梯度脱水1缸\"\xff\xff\xff"); Zmove_up(); g_stain_state = STAIN_STEP7_DEHY1_INIT; // 进入脱水1 break; // -------------------------- 步骤7:梯度脱水1(5#) -------------------------- case STAIN_STEP7_DEHY1_INIT: printf("move.t10.bco=1024\xff\xff\xff"); moveToTank(5); // 移动到脱水1缸 g_delay_start_tick = sys_time_s; g_current_delay_s = g_config.t_dehy1; // 脱水1延时 g_stain_state = STAIN_STEP7_DEHY1_DELAY; printf("move.t0.txt=\"梯度脱水1剩余时间:\"\xff\xff\xff"); printf("move.t25.aph=127\xff\xff\xff"); printf("move.t26.aph=127\xff\xff\xff"); printf("move.tm5.en=1\xff\xff\xff"); break; case STAIN_STEP7_DEHY1_DELAY: if (elapsed_ms >= g_current_delay_s) { printf("move.t25.txt=\"0\"\xff\xff\xff"); printf("move.tm5.en=0\xff\xff\xff"); printf("move.t25.aph=0\xff\xff\xff"); printf("move.t26.aph=0\xff\xff\xff"); g_stain_state = STAIN_STEP7_DEHY1_DONE; } break; case STAIN_STEP7_DEHY1_DONE: printf("move.t0.txt=\"移动到梯度脱水2缸\"\xff\xff\xff"); Zmove_up(); g_stain_state = STAIN_STEP8_DEHY2_INIT; // 进入脱水2 break; // -------------------------- 步骤8:梯度脱水2(6#) -------------------------- case STAIN_STEP8_DEHY2_INIT: printf("move.t12.bco=1024\xff\xff\xff"); moveToTank(6); // 移动到脱水2缸 g_delay_start_tick = sys_time_s; g_current_delay_s = g_config.t_dehy2; // 脱水2延时 g_stain_state = STAIN_STEP8_DEHY2_DELAY; printf("move.t0.txt=\"梯度脱水2剩余时间:\"\xff\xff\xff"); printf("move.t25.aph=127\xff\xff\xff"); printf("move.t26.aph=127\xff\xff\xff"); printf("move.tm6.en=1\xff\xff\xff"); break; case STAIN_STEP8_DEHY2_DELAY: if (elapsed_ms >= g_current_delay_s) { printf("move.t25.txt=\"0\"\xff\xff\xff"); printf("move.tm6.en=0\xff\xff\xff"); printf("move.t25.aph=0\xff\xff\xff"); printf("move.t26.aph=0\xff\xff\xff"); g_stain_state = STAIN_STEP8_DEHY2_DONE; } break; case STAIN_STEP8_DEHY2_DONE: printf("move.t0.txt=\"移动到梯度脱水3缸\"\xff\xff\xff"); Zmove_up(); g_stain_state = STAIN_STEP9_DEHY3_INIT; // 进入脱水3 break; // -------------------------- 步骤9:梯度脱水3(7#) -------------------------- case STAIN_STEP9_DEHY3_INIT: printf("move.t14.bco=1024\xff\xff\xff"); moveToTank(7); // 移动到脱水3缸 g_delay_start_tick = sys_time_s; g_current_delay_s = g_config.t_dehy3; // 脱水3延时 g_stain_state = STAIN_STEP9_DEHY3_DELAY; printf("move.t0.txt=\"梯度脱水3剩余时间:\"\xff\xff\xff"); printf("move.t25.aph=127\xff\xff\xff"); printf("move.t26.aph=127\xff\xff\xff"); printf("move.tm7.en=1\xff\xff\xff"); break; case STAIN_STEP9_DEHY3_DELAY: if (elapsed_ms >= g_current_delay_s) { printf("move.t25.txt=\"0\"\xff\xff\xff"); printf("move.tm7.en=0\xff\xff\xff"); printf("move.t25.aph=0\xff\xff\xff"); printf("move.t26.aph=0\xff\xff\xff"); g_stain_state = STAIN_STEP9_DEHY3_DONE; } break; case STAIN_STEP9_DEHY3_DONE: printf("move.t0.txt=\"移动到梯度脱水4缸\"\xff\xff\xff"); Zmove_up(); g_stain_state = STAIN_STEP10_DEHY4_INIT; // 进入脱水4 break; // -------------------------- 步骤10:梯度脱水4(8#) -------------------------- case STAIN_STEP10_DEHY4_INIT: printf("move.t16.bco=1024\xff\xff\xff"); moveToTank(8); // 移动到脱水4缸 g_delay_start_tick = sys_time_s; g_current_delay_s = g_config.t_dehy4; // 脱水4延时 g_stain_state = STAIN_STEP10_DEHY4_DELAY; printf("move.t0.txt=\"梯度脱水4剩余时间:\"\xff\xff\xff"); printf("move.t25.aph=127\xff\xff\xff"); printf("move.t26.aph=127\xff\xff\xff"); printf("move.tm8.en=1\xff\xff\xff"); break; case STAIN_STEP10_DEHY4_DELAY: if (elapsed_ms >= g_current_delay_s) { printf("move.t25.txt=\"0\"\xff\xff\xff"); printf("move.tm8.en=0\xff\xff\xff"); printf("move.t25.aph=0\xff\xff\xff"); printf("move.t26.aph=0\xff\xff\xff"); g_stain_state = STAIN_STEP10_DEHY4_DONE; } break; case STAIN_STEP10_DEHY4_DONE: printf("move.t0.txt=\"移动到梯度晾片缸\"\xff\xff\xff"); Zmove_up(); g_stain_state = STAIN_STEP11_DRY_INIT; // 进入晾片缸 break; // -------------------------- 步骤11:晾片缸(9#) -------------------------- case STAIN_STEP11_DRY_INIT: printf("move.t18.bco=1024\xff\xff\xff"); printf("move.t0.txt=\"晾片\"\xff\xff\xff"); moveToTank(9); // 移动到晾片缸 printf("move.tm11.en=1\xff\xff\xff"); g_delay_start_tick = sys_time_s; g_current_delay_s = g_config.t_dry; // 晾片延时 g_stain_state = STAIN_STEP11_DRY_DELAY; break; case STAIN_STEP11_DRY_DELAY: if (elapsed_ms >= g_current_delay_s) { printf("move.t25.txt=\"0\"\xff\xff\xff"); printf("move.tm11.en=0\xff\xff\xff"); printf("move.t25.aph=0\xff\xff\xff"); printf("move.t26.aph=0\xff\xff\xff"); g_stain_state = STAIN_STEP11_DRY_DONE; } break; case STAIN_STEP11_DRY_DONE: Xmove_left(); // 脱离晾片缸 g_stain_state = STAIN_FINISH; // 流程结束 break; // -------------------------- 流程结束 -------------------------- case STAIN_FINISH: printf("move.t0.txt=\"染色完成,请移走染架\"\xff\xff\xff"); g_stain_state = STAIN_IDLE; // 重置为空闲状态 break; default: g_stain_state = STAIN_IDLE; // 异常状态重置 break; } } /*----------------------------------------------------------------------------------------------------------*/ // 优化后的X轴移动函数 void Xmove_left(void) { delay_ms(10); moveAxisCommon(AXIS_X,g_config.x_pos1);//最后一次移动距离,脱离晾片缸 } void Xmove_right(void) { delay_ms(10); moveAxisCommon(AXIS_X,-g_config.x_pos1); } void Xmove_right2(void) { delay_ms(10); moveAxisCommon(AXIS_X,-g_config.x_pos10);//距离是需要右移或者左移抓染架的距离 } void Xmove_left2(void) { delay_ms(10); moveAxisCommon(AXIS_X,g_config.x_pos10);//距离是需要右移或者左移抓染架的距离 } // 优化后的Z轴移动函数 void Zmove_down(void) { delay_ms(10); moveAxisCommon(AXIS_Z,g_config.z_lift); } void Zmove_down_2(void) { delay_ms(10); moveAxisCommon(AXIS_Z,g_config.z_soak); } void Zmove_up(void) { delay_ms(10); moveAxisCommon(AXIS_Z,-g_config.z_lift); } void Zmove_up_2(void) { delay_ms(10); moveAxisCommon(AXIS_Z,-g_config.z_soak); } void up_down(void) { delay_ms(10); Zmove_up_2(); delay_ms(100); Zmove_down_2(); Zmove_up_2(); delay_ms(100); Zmove_down_2(); } /*----------------------------------------------------------------------------------------------------------*/ //一直移动函数GUI界面函数 // 处理触摸屏指令 void touch_command_process(uint8_t cmd) { switch(cmd) { case TOUCH_X_LEFT_PRESS: x_left_running = 1; x_right_running = 0; break; case TOUCH_X_LEFT_RELEASE: if(x_left_running) { motor_stop(AXIS_X); x_left_running = 0; } break; case TOUCH_X_RIGHT_PRESS: x_right_running = 1; x_left_running = 0; break; case TOUCH_X_RIGHT_RELEASE: if(x_right_running) { motor_stop(AXIS_X); x_right_running = 0; } break; case TOUCH_Z_UP_PRESS: z_up_running = 1; z_down_running = 0; break; case TOUCH_Z_UP_RELEASE: if(z_up_running) { motor_stop(AXIS_Z); z_up_running = 0; } break; case TOUCH_Z_DOWN_PRESS: z_down_running = 1; z_up_running = 0; break; case TOUCH_Z_DOWN_RELEASE: if(z_down_running) { motor_stop(AXIS_Z); z_down_running = 0; } break; case STAIN_STEP1_WAIT_LOAD_INIT_USART: if (g_stain_state == STAIN_IDLE) { g_stain_state = STAIN_STEP1_WAIT_LOAD_INIT; // 从第一步开始 } break; case STAIN_IDLE_USART: GPIO_SetBits(X_ENA_PORT, X_ENA_PIN); // 高电平禁用 GPIO_SetBits(Z_ENA_PORT, Z_ENA_PIN); reinitialize_system(); break; } } // 电机连续移动函数 void motor_continuous_move(AxisType axis, int32_t direction) { if(axis == AXIS_X) { if(!x_motor_running) { // 设置一个较大的目标步数实现连续移动 moveAxisCommon(AXIS_X, direction * 10000000); } } else if(axis == AXIS_Z) { if(!z_motor_running) { moveAxisCommon(AXIS_Z, direction * 10000000); } } } // 电机停止函数 void motor_stop(AxisType axis) { if(axis == AXIS_X) { x_motor_running = 0; disableMotor(AXIS_X); } else if(axis == AXIS_Z) { z_motor_running = 0; disableMotor(AXIS_Z); } } /*----------------------------------------------------------------------------------------------------------*/ void X_home(void) { Home_Motor(AXIS_X); } void Z_home(void) { Home_Motor(AXIS_Z); } /*----------------------------------------------------------------------------------------------------------*/ //初始化SD配置文件 void SD_config_init(void) { fr = f_open(&config_file, "0:config.txt", FA_READ); if (fr == FR_OK) { read_config_from_sd(&g_config); delay_ms(10); f_close(&config_file); } else { printf("not find config"); delay_ms(10); } fr = f_open(&log_file, "0:log.txt", FA_READ | FA_OPEN_ALWAYS); f_close(&log_file); } /*----------------------------------------------------------------------------------------------------------*/ /** * 读取温度状态并更新(仅在状态变化时执行操作) */ void prin_temp(void) { // 读取温度(已转为整数) int32_t current_temp = (int32_t)(SMBus_ReadTemp()); // 仅当当前温度与历史值不同时,才执行更新(避免无效操作) if (current_temp != last_temp) { if(current_temp < 150 && current_temp > -10) { // 调用公共函数更新显示和发送数据 printf("home.va0.val=%d\xff\xff\xff", current_temp); OLED_ShowNum(3,4, current_temp, 2); // 显示温度(占3位,适应更大数值) OLED_ShowString(3,6,"C"); OLED_ShowString(3,1,"T1:"); last_temp = current_temp; // 更新历史值 USART_ClearBuffer(); } } } /** * 读取温度状态并更新(仅在状态变化时执行操作) */ void prin_temp2(void) { // 读取温度(已转为整数) int32_t current_temp2 = (int32_t)DS18B20_Get_Temp()/ 10.0f; // 仅当当前温度与历史值不同时,才执行更新(避免无效操作) if (current_temp2 != last_temp2) { if(current_temp2 < 150 && last_temp2 > -10){ // 调用公共函数更新显示和发送数据 printf("home.va1.val=%d\xff\xff\xff", current_temp2); OLED_ShowNum(3,12, current_temp2, 2); // 显示温度(占3位,适应更大数值) OLED_ShowString(3,14,"C"); OLED_ShowString(3,9,"T2:"); last_temp2 = current_temp2; // 更新历史值 USART_ClearBuffer(); } } } /** * 读取液位状态并更新(仅在状态变化时执行操作) */ void print_level(void) { // 读取液位状态 uint8_t current_level = LevelSensor_GetState(); if(current_level==1)//出水,模式 { close_rinse_input(); open_rinse_output(); } if(current_level==0)//进水,判断,水满信号位置1 { open_rinse_input(); close_rinse_output(); } // 仅当当前状态与历史值不同时,才执行更新 if (current_level != last_level) { // 调用公共函数更新显示和发送数据 printf("move.va1.val=%d\xff\xff\xff", current_level); OLED_ShowString(1,1,"water_leve:"); OLED_ShowNum(1,14,current_level, 2); // 显示液位状态 last_level = current_level; // 更新历史值 USART_ClearBuffer(); } } /*----------------------------------------------------------------------------------------------------------*/ void moveAxisCommon(AxisType axis, int32_t target_steps) { delay_ms(10); // 启动带加速度的运动(假设此函数仅启动运动,不阻塞) moveStepsWithAccel(axis, target_steps); // 立即更新OLED显示(运动开始时的位置) OLED_ShowNum(2, 9, x_current_position, 7); OLED_ShowNum(4, 9, z_current_position, 7); while ((axis == AXIS_X && x_motor_running) || (axis == AXIS_Z && z_motor_running)) { delay_ms(1); // 短延迟,降低CPU占用 } delay_ms(10); // 稳定延迟 // 5. 写日志与显示 write_log(x_current_position, z_current_position); } /*--------------------------------------------------------------------------------------------------------------------------------*/ // 安全清理函数:释放资源,停止外设 void safe_cleanup(void) { // 1. 停止电机运动 motor_stop(AXIS_X); motor_stop(AXIS_Z); x_left_running = 0; x_right_running = 0; z_up_running = 0; z_down_running = 0; // 2. 关闭文件系统相关资源 f_close(&step_file); f_close(&log_file); f_close(&config_file); f_mount(NULL, "0:", 1); // 卸载SD卡 f_mount(NULL, "1:", 1); // 卸载FLASH // 3. 重置状态机和全局变量 g_stain_state = STAIN_IDLE; g_delay_start_tick = 0; g_current_delay_s = 0; // 4. 清空OLED显示 OLED_Clear(); delay_ms(500); } // 重新初始化函数(复用main中的初始化逻辑) void reinitialize_system(void) { safe_cleanup(); // 先清理资源 // 重新执行初始化(完全复用main中的初始化步骤) delay_init(72); delay_ms(10); OLED_Init(); OLED_Clear(); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); StepMotor_GPIO_Init(AXIS_X); StepMotor_GPIO_Init(AXIS_Z); StepMotor_TIM_Init(AXIS_X); StepMotor_TIM_Init(AXIS_Z); delay_ms(10); my_mem_init(SRAMIN); delay_ms(10); SMBus_Init(); delay_ms(10); LevelSensor_Init(); delay_ms(10); DS18B20_Init(); delay_ms(10); // 重新初始化SD卡(带重试) while(SD_Init()) { OLED_ShowString(2, 1, "SD Error, Retry..."); delay_ms(1000); } delay_ms(10); // 重新初始化文件系统 while(exfuns_init()) { OLED_ShowString(2, 1, "FS Error, Retry..."); delay_ms(1000); } f_mount(fs[0], "0:", 1); f_mount(fs[1], "1:", 1); delay_ms(10); // 重新初始化串口和USMART USART1_Init(115200); delay_ms(10); usmart_dev.init(72); delay_ms(10); // 重新初始化RTC RTC_Init(); delay_ms(10); // 重新加载配置 SD_config_init(); delay_ms(10); // 重新初始化限位开关 LimitSwitch_Init(); CheckLimitSwitches(); delay_ms(10); /*************************开机初始化将配置参数传到move页面***************************************/ printf("move.va2.val=%d\xff\xff\xff",g_config.t_fix); printf("move.va3.val=%d\xff\xff\xff",g_config.t_rinse); printf("move.va4.val=%d\xff\xff\xff",g_config.t_acid); printf("move.va5.val=%d\xff\xff\xff",g_config.t_stain); printf("move.va6.val=%d\xff\xff\xff",g_config.t_dehy1); printf("move.va7.val=%d\xff\xff\xff",g_config.t_dehy2); printf("move.va8.val=%d\xff\xff\xff",g_config.t_dehy3); printf("move.va9.val=%d\xff\xff\xff",g_config.t_dehy4); printf("move.va10.val=%d\xff\xff\xff",g_config.t_dry); /***********************************************************************************************/ OLED_Clear(); printf("系统重新初始化完成\xFF\xFF\xFF"); delay_ms(1000); OLED_Clear(); } /*--------------------------------------------------------------------------------------------------------------------------------*/ int main(void) { int a=0; //温度循环值初始,1秒检测一次 g_stain_state = STAIN_IDLE; //初始化为空闲状态,状态机 delay_init(72); delay_ms(1000); OLED_Init(); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组0 StepMotor_GPIO_Init(AXIS_X); StepMotor_GPIO_Init(AXIS_Z); StepMotor_TIM_Init(AXIS_X); StepMotor_TIM_Init(AXIS_Z); delay_ms(10); my_mem_init(SRAMIN); //初始化内部内存池 delay_ms(10); SMBus_Init(); // 初始化SMBus总线(用于MLX90614通信) delay_ms(10); LevelSensor_Init(); // 初始化液面传感器 delay_ms(10); DS18B20_Init(); // 初始化DS18B20温度传感器 delay_ms(10); while(SD_Init()) //检测不到SD卡,SDIO初始化 { OLED_ShowString(1,1,"SD Card Error!"); printf("SD Card Error!"); delay_ms(100); OLED_ShowString(1,1,"Please Check! "); delay_ms(100); } delay_ms(10); while(exfuns_init()) //FAFTS文件系统是否挂载失败 { OLED_ShowString(1,1," exfuns_init Error!"); printf("exfuns_init Error!"); delay_ms(1000); } f_mount(fs[0],"0:",1); //挂载SD卡 f_mount(fs[1],"1:",1); //挂载FLASH. /*************************初始化USMART通信********************************************************/ delay_ms(10); USART1_Init(115200); //串口初始化为115200 delay_ms(10); usmart_dev.init(72); //串口通信初始化 delay_ms(10); /*************************初始化RTC初始化********************************************************/ RTC_Init(); //RTC初始化 delay_ms(10); SD_config_init(); //初始化,将config文件的参数一次性更新到程序里 delay_ms(10); LimitSwitch_Init(); //运动限位最后初始化。 CheckLimitSwitches(); //检查限位,复位限位 OLED_Clear(); //oled显示屏幕清空 // Home_Motor(AXIS_Z); //初始化Z轴,Z轴回零 // Home_Motor(AXIS_X); //初始化X轴,X轴回零 OLED_Clear(); //清空屏幕 /*************************开机初始化将配置参数传到move页面***************************************/ printf("move.va2.val=%d\xff\xff\xff",g_config.t_fix); printf("move.va3.val=%d\xff\xff\xff",g_config.t_rinse); printf("move.va4.val=%d\xff\xff\xff",g_config.t_acid); printf("move.va5.val=%d\xff\xff\xff",g_config.t_stain); printf("move.va6.val=%d\xff\xff\xff",g_config.t_dehy1); printf("move.va7.val=%d\xff\xff\xff",g_config.t_dehy2); printf("move.va8.val=%d\xff\xff\xff",g_config.t_dehy3); printf("move.va9.val=%d\xff\xff\xff",g_config.t_dehy4); printf("move.va10.val=%d\xff\xff\xff",g_config.t_dry); /***********************************************************************************************/ while (1) { process_auto_stain(); if(x_left_running) { motor_continuous_move(AXIS_X, 1); // X轴左移 } if(x_right_running) { motor_continuous_move(AXIS_X, -1); // X轴右移 } if(z_up_running) { motor_continuous_move(AXIS_Z, -1); // Z轴上移 } if(z_down_running) { motor_continuous_move(AXIS_Z, 1); // Z轴下移 } a++; //累加计数 if(a==800){ prin_temp(); prin_temp2(); a=0; } print_level(); delay_ms(2); // 主循环基础延时(保持不变) } } 为什么初始化SD卡时容易失败
最新发布
10-31
#region MC协议 /// <summary> /// MC协议 /// </summary> public class MCClient { #region 对象 /*定义连接的Socket*/ Socket ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //---通讯的socket /*定义socket线程*/ Thread receiveTh; //-----------------------接收socket线程 #endregion #region 事件 public del LogInfoDisplay; //-------------------委托变量-信息显示 public Action<long[]> returnReceiveEvent; //------接收返回数据事件 #endregion #region 变量 /*运行中的状态*/ string nameClient; //--------------------------客户端名称 public bool connecte; //-----------------------连接状态 public InfoMsg info = new InfoMsg("", ""); //---显示信息 public bool isWriteSuccess; //-----------------写入成功 public bool isReadSuccess; //------------------读取成功 public bool debugDisplay; //-------------------调试信息显示:true.显示;false.不显示; public bool isWriteHandshake; //---------------写入握手信号:true.写入有握手信号;false.写入没有握手信号; /*数据*/ int readType; //-------读取类型:1.按8位读取;2.按16位读取;4按32位读取; string[] readStr; //---读取数据string类型 long[] readData; //----读取数据long类型 /*时间*/ DateTime startTime, stopTime; DateTime stopTime1, stopTime2; #endregion #region 客户端初始化 /// <summary> /// 客户端初始化 /// </summary> /// <param name="name">客户端名称</param> /// <param name="receiveData">接收数据事件</param> /// <param name="info">消息显示事件</param> public void Initial(string name = "客户端", Action<long[]> receiveData = null, del info = null) { nameClient = name; //-------------------客户端名称 returnReceiveEvent += receiveData; //---接收数据事件 LogInfoDisplay += info; //--------------消息事件 this.info.msgType = nameClient; //---log消息类型 } #endregion #region 断开连接 /*关闭客户端*/ /// <summary> /// 断开连接 /// </summary> public void Disconnect() { try { if (ClientSocket != null) { if (ClientSocket.Connected == true) { ClientSocket.Close(); ClientSocket.Dispose(); connecte = false; //---连接成功 info.msg = "Socket连接关闭成功!"; if (LogInfoDisplay != null) //------判断委托不为空时执行委托 LogInfoDisplay(info); } else { info.msg = "Socket连接不存在!"; if (LogInfoDisplay != null) //------判断委托不为空时执行委托 LogInfoDisplay(info); } } } catch { } try { if (receiveTh != null) { if (receiveTh.IsAlive == true) { ClientSocket.Close(); receiveTh.Abort(); //-----------------------接收socket线程关闭 receiveTh.DisableComObjectEagerCleanup(); connecte = false; //---连接成功 info.msg = "客户端关闭成功"; if (LogInfoDisplay != null) //------判断委托不为空时执行委托 LogInfoDisplay(info); } else { info.msg = "客户端不存在!"; if (LogInfoDisplay != null) //------判断委托不为空时执行委托 LogInfoDisplay(info); } } } catch { } Thread.Sleep(100); } #endregion #region 连接的Socket /*连接的Socket*/ /// <summary> /// 连接的Socket /// </summary> /// <param name="IP">IP地址</param> /// <param name="Port">端口号</param> public void Connection(string IP, string Port) { try { IPAddress ip = IPAddress.Parse(IP.Trim()); IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(Port.Trim())); if (ClientSocket != null) { ClientSocket.Dispose(); } Disconnect(); ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); ClientSocket.Connect(point); connecte = true; //---连接成功 info.msgType = nameClient; info.msg = "连接成功"; if (LogInfoDisplay != null) //------判断委托不为空时执行委托 LogInfoDisplay(info); /*开启一个新线程负责不停的接收服务器发过来的数据*/ if (receiveTh == null) { receiveTh = new Thread(Receive); //-----------------------接收socket线程 receiveTh.IsBackground = true; receiveTh.Start(); } else { if (receiveTh.IsAlive != true) { receiveTh = new Thread(Receive); //-----------------------接收socket线程 receiveTh.IsBackground = true; receiveTh.Start(); } } } catch { } } #endregion #region MC读取命令固定格式 /// <summary> /// MC读取命令固定格式 /// </summary> /// <param name="addr">地址</param> /// <param name="length">读取数据长度</param> /// <param name="type">读取类型:1.按8位读取;2.按16位读取;4按32位读取;</param> public void CommandRead(string addr, int length, int type = 2) { #region 读取命令说明 /* * buffer[0] = 0x50; 网络副标题L--固定值 * buffer[1] = 0x00; 网络副标题H--固定值 * buffer[2] = 0x00; 网络编号--固定值 * buffer[3] = 0xFF; PLC编号--固定值 * buffer[4] = 0xFF; 目标模块IO编号L--固定值 * buffer[5] = 0x03; 目标模块IO编号H--固定值 * buffer[6] = 0x00; 模块站编号--固定值 * buffer[7] = 0x0C; 请求数据长度L * buffer[8] = 0x00; 请求数据长度H * buffer[9] = 0x01; 时钟L //---(cpu监视定时器) : 表示等待PLC响应的timeout时间;这里 值是0010,十进制是16 ;相当与最大等待时间250ms*16=4秒;实际上PLC一般2,3个毫秒内就响应了; * buffer[10] = 0x00; 时钟H * buffer[11] = 0x01; 命令L * buffer[12] = 0x04; 命令H * buffer[13] = 0x00; 子命令L //---00 00 (子命令) : 值是0表示按字读取(1个字=16位),如果值是1就按位读取; * buffer[14] = 0x64; 子命令H * buffer[15] = 0x00; 首地址 * buffer[16] = 0x00; 首地址 * buffer[17] = 0x00; 首地址 * buffer[18] = 0xA8; 软元件 0xA8-D;0x90-M;0x9C-X;0x9D-Y * buffer[19] = 0x01; 读取数据长度L * buffer[20] = 0x00; 读取数据长度H */ #endregion startTime = DateTime.Now; //---读取开始时间 byte[] addrBuffer = MCReadDecode(addr); readType = type; //------读取类型:1.按8位读取;2.按16位读取;4按32位读取; /*BIN指令*/ byte[] buffer = new byte[256]; buffer[0] = 0x50; /*网络副标题L--固定值*/ buffer[1] = 0x00; /*网络副标题H--固定值*/ buffer[2] = 0x00; /*网络编号--固定值*/ buffer[3] = 0xFF; /*PLC编号--固定值*/ buffer[4] = 0xFF; /*目标模块IO编号L--固定值*/ buffer[5] = 0x03; /*目标模块IO编号H--固定值*/ buffer[6] = 0x00; /*模块站编号--固定值*/ buffer[7] = 0x0C; /*请求数据长度L*/ buffer[8] = 0x00; /*请求数据长度H*/ buffer[9] = 0x01; /*时钟L*/ //---(cpu监视定时器) : 表示等待PLC响应的timeout时间;这里 值是0010,十进制是16 ;相当与最大等待时间250ms*16=4秒;实际上PLC一般2,3个毫秒内就响应了; buffer[10] = 0x00; /*时钟H*/ buffer[11] = 0x01; /*命令L*/ buffer[12] = 0x04; /*命令H*/ buffer[13] = addrBuffer[4]; /*子命令L*/ //---00 00 (子命令) : 值是0表示按字读取(1个字=16位),如果值是1就按位读取; buffer[14] = 0x00; /*子命令H*/ buffer[15] = addrBuffer[0]; /*首地址*/ buffer[16] = addrBuffer[1]; /*首地址*/ buffer[17] = addrBuffer[2]; /*首地址*/ buffer[18] = addrBuffer[3]; /*软元件 0xA8-D;0x90-M;0x9C-X;0x9D-Y*/ buffer[19] = (byte)length; /*读取数据长度L*/ buffer[20] = 0x00; /*读取数据长度H*/ try { int r = ClientSocket.Send(buffer, buffer.Length, 0); isReadSuccess = false; //---接收完成 } catch { isReadSuccess = false; } //---发送完成 #region 调试信息显示 if (debugDisplay) //---调试信息显示 { stopTime1 = DateTime.Now; //------读取发送指令时间 info.msg = "发送指令时间(毫秒):" + stopTime1.Subtract(startTime).TotalMilliseconds; if (LogInfoDisplay != null) //------判断委托不为空时执行委托 LogInfoDisplay(info); } #endregion } #endregion #region MC写入命令固定格式 /// <summary> /// MC写入命令固定格式 /// </summary> /// <param name="addr">地址</param> /// <param name="data">写入数据数组</param> /// <param name="length">写入数据长度</param> /// <param name="type">写入类型:0.按字节写入(8位写入);1.按字写入(16位写入);2.按双字写入(32位写入);</param> public void CommandWrite(string addr, string[] data, int length, ushort type = 1) { #region BIN指令屏蔽 /*BIN指令*/ //byte[] buffer = new byte[255]; //buffer[0] = 0x50; /*网络副标题L--固定值*/ //buffer[1] = 0x00; /*网络副标题H--固定值*/ //buffer[2] = 0x00; /*网络编号--固定值*/ //buffer[3] = 0xFF; /*PLC编号--固定值*/ //buffer[4] = 0xFF; /*目标模块IO编号--固定值L*/ //buffer[5] = 0x03; /*目标模块IO编号H--固定值*/ //buffer[6] = 0x00; /*模块站编号--固定值*/ //buffer[7] = 0x0E; /*请求数据长度L*/ //buffer[8] = 0x00; /*请求数据长度H*/ //buffer[9] = 0x01; /*时钟L*/ //buffer[10] = 0x00; /*时钟H*/ //buffer[11] = 0x01; /*命令L*/ //buffer[12] = 0x14; /*命令H*/ ////buffer[13] = addrBuffer[4]; /*子命令L*/ //buffer[13] = 0x00; /*子命令L*/ //buffer[14] = 0x00; /*子命令H*/ //buffer[15] = addrBuffer[0]; /*首地址LL*/ //buffer[16] = addrBuffer[1]; /*首地址L*/ //buffer[17] = addrBuffer[2]; /*首地址H*/ //buffer[18] = addrBuffer[3]; /*软元件 0xA8-D;0x90-M;0x0C-X;0x9D-Y*/ //buffer[19] = 0x01; /*数据长度L*/ //buffer[20] = 0x00; /*数据长度H*/ //buffer[21] = dataBuffer[0]; /*写入第一个数据L*/ //buffer[22] = dataBuffer[1]; /*写入第一个数据H*/ ////buffer[21] = 0x7B; /*写入第二个数据L*/ ////buffer[22] = 0x01; /*写入第二个数据H*/ #endregion isWriteSuccess = false; //---发送完成 byte[] buffer = MCWriteDecode(addr, data, (short)length, type); int r = ClientSocket.Send(buffer, buffer.Length, 0); if (!isWriteHandshake) //---写入握手信号 isWriteSuccess = true; //---写入成功标志 } #endregion #region MC读取命令解析 /// <summary> /// MC读取命令解析 /// </summary> /// <param name="addr">读取数据长度</param> /// <returns>指令数组</returns> private byte[] MCReadDecode(string addr) { /*MC读取命令固定格式 addrBuffer[0]-----地址HH addrBuffer[1]-----地址H addrBuffer[2]-----地址L addrBuffer[3]-----软件件 addrBuffer[4]-----子指令L */ char[] addrChar = addr.ToArray(); string addrStr = ""; byte[] addrBuffer = new byte[5]; try { for (int i = 1; i < addrChar.Length; i++) { addrStr += addrChar[i]; } //int addrInt = Convert.ToInt32(addrStr); int addrInt = int.Parse(addrStr); string addrStr2 = addrInt.ToString("x"); string addrStr3 = ""; switch (addrStr2.Length) { case 1: addrStr3 = "0" + "0" + "0" + "0" + "0" + addrStr2; break; case 2: addrStr3 = "0" + "0" + "0" + "0" + addrStr2; break; case 3: addrStr3 = "0" + "0" + "0" + addrStr2; break; case 4: addrStr3 = "0" + "0" + addrStr2; break; case 5: addrStr3 = "0" + addrStr2; break; } char[] addrChar2 = addrStr3.ToArray(); addrBuffer[0] = byte.Parse(addrChar2[4].ToString() + addrChar2[5].ToString(), System.Globalization.NumberStyles.HexNumber); addrBuffer[1] = byte.Parse(addrChar2[2].ToString() + addrChar2[3].ToString(), System.Globalization.NumberStyles.HexNumber); addrBuffer[2] = byte.Parse(addrChar2[0].ToString() + addrChar2[1].ToString(), System.Globalization.NumberStyles.HexNumber); if (addrChar[0] == 'D' || addrChar[0] == 'd') { addrBuffer[3] = 0xA8; addrBuffer[4] = 0x00; } if (addrChar[0] == 'M' || addrChar[0] == 'm') { addrBuffer[3] = 0x90; addrBuffer[4] = 0x01; } if (addrChar[0] == 'X' || addrChar[0] == 'x') { addrBuffer[3] = 0x9C; addrBuffer[4] = 0x01; } if (addrChar[0] == 'Y' || addrChar[0] == 'y') { addrBuffer[3] = 0x9D; addrBuffer[4] = 0x01; } } catch { } return addrBuffer; } #endregion #region MC写入命令解析 /// <summary> /// MC写入命令解析 /// </summary> /// <param name="addr">地址</param> /// <param name="data">写入数据数组</param> /// <param name="length">写入数据长度</param> /// <param name="type">写入类型:0.按字节写入(8位写入);1.按字写入(16位写入);2.按双字写入(32位写入);</param> /// <returns></returns> private byte[] MCWriteDecode(string addr, string[] data, short length, ushort type) { #region 读取命令固定格式 /*读取命令固定格式 * 形参 addr-------地址 * 形参 data-------数据 * 形参 number-----个数 * addrBuffer[0]-----地址HH * addrBuffer[1]-----地址H * addrBuffer[2]-----地址L * addrBuffer[3]-----软元件 * addrBuffer[4]-----子指令L */ #endregion #region MC写入命令固定格式 /*MC写入命令固定格式 * buffer[0] = 0x50 网络副标题L--固定值 * buffer[1] = 0x00; 网络副标题H--固定值 * buffer[2] = 0x00; 网络编号--固定值 * buffer[3] = 0xFF; PLC编号--固定值 * buffer[4] = 0xFF; 目标模块IO编号--固定值L * buffer[6] = 0x00; 模块站编号--固定值 * buffer[7] = 0x0E; 请求数据长度L * buffer[8] = 0x00; 请求通讯数据长度H * buffer[9] = 0x01; 时钟L * buffer[10] = 0x00; 时钟H * buffer[11] = 0x01; 命令L * buffer[12] = 0x14; 命令H * buffer[13] = 0x00; 子命令L * buffer[14] = 0x00; 子命令H * buffer[15] = addrBuffer[0]; 地址LL * buffer[16] = addrBuffer[1]; 地址L * buffer[17] = addrBuffer[2]; 地址H * buffer[18] = 0xA8; 软元件 0xA8-D;0x90-M;0x0C-X;0x9D-Y * buffer[19] = 0x01; 写入数据长度L * buffer[20] = 0x00; 写入数据长度H * buffer[21] = 0x12; 写入第一个数据L * buffer[22] = 0x11; 写入第一个数据H * buffer[23] = 0x3; 写入第二个数据L * buffer[24] = 0x4; 写入第二个数据H */ #endregion #region MC协议数据地址解析 char[] addrChar = addr.ToArray(); string addrStr = ""; byte[] addrBuffer = new byte[5]; for (int i = 1; i < addrChar.Length; i++) { addrStr += addrChar[i]; } int addrInt = int.Parse(addrStr); string addrStr2 = addrInt.ToString("x"); string addrStr3 = ""; switch (addrStr2.Length) { case 1: addrStr3 = "0" + "0" + "0" + "0" + "0" + addrStr2; break; case 2: addrStr3 = "0" + "0" + "0" + "0" + addrStr2; break; case 3: addrStr3 = "0" + "0" + "0" + addrStr2; break; case 4: addrStr3 = "0" + "0" + addrStr2; break; case 5: addrStr3 = "0" + addrStr2; break; } char[] addrChar2 = addrStr3.ToArray(); addrBuffer[0] = byte.Parse(addrChar2[4].ToString() + addrChar2[5].ToString(), System.Globalization.NumberStyles.HexNumber); addrBuffer[1] = byte.Parse(addrChar2[2].ToString() + addrChar2[3].ToString(), System.Globalization.NumberStyles.HexNumber); addrBuffer[2] = byte.Parse(addrChar2[0].ToString() + addrChar2[1].ToString(), System.Globalization.NumberStyles.HexNumber); #endregion #region 地址类型解析 if (addrChar[0] == 'D' || addrChar[0] == 'd') { addrBuffer[3] = 0xA8; addrBuffer[4] = 0x00; } if (addrChar[0] == 'M' || addrChar[0] == 'm') { addrBuffer[3] = 0x90; addrBuffer[4] = 0x01; } if (addrChar[0] == 'X' || addrChar[0] == 'x') { addrBuffer[3] = 0x9C; addrBuffer[4] = 0x01; } if (addrChar[0] == 'Y' || addrChar[0] == 'y') { addrBuffer[3] = 0x9D; addrBuffer[4] = 0x01; } #endregion #region MC协议固定值 byte[] buffer = new byte[256]; buffer[0] = 0x50; /*网络副标题L--固定值*/ buffer[1] = 0x00; /*网络副标题H--固定值*/ buffer[2] = 0x00; /*网络编号--固定值*/ buffer[3] = 0xFF; /*PLC编号--固定值*/ buffer[4] = 0xFF; /*目标模块IO编号--固定值L*/ buffer[5] = 0x03; /*目标模块IO编号H--固定值*/ buffer[6] = 0x00; /*模块站编号--固定值*/ #endregion #region MC协议变动值 if (addrChar[0] == 'D' || addrChar[0] == 'd') { byte[] dataBuffer = DataDecode(data, length, type); /*data数据解析*/ /*BIN指令*/ buffer[7] = (byte)(0x0C + 2 * (dataBuffer.Length / 2)); /*数据长度L*/ //buffer[7] = 0x0E; /*应答数据长度L*/ buffer[8] = 0x00; /*应答数据长度H*/ buffer[9] = 0x01; /*时钟L*/ buffer[10] = 0x00; /*时钟H*/ buffer[11] = 0x01; /*命令L*/ buffer[12] = 0x14; /*命令H*/ buffer[13] = 0x00; /*子命令L*/ buffer[14] = 0x00; /*子命令H*/ buffer[15] = addrBuffer[0]; /*起始软元件地址LL*/ buffer[16] = addrBuffer[1]; /*起始软元件地址L*/ buffer[17] = addrBuffer[2]; /*起始软元件地址H*/ buffer[18] = addrBuffer[3]; /*软元件 0xA8-D;0x90-M;0x0C-X;0x9D-Y*/ buffer[19] = (byte)(dataBuffer.Length / 2); /*写入数据长度L*/ buffer[20] = 0x00; /*写入数据长度H*/ //buffer[21] = dataBuffer[0]; /*写入第一个数据L*/ //buffer[22] = dataBuffer[1]; /*写入第一个数据H*/ //buffer[23] = dataBuffer[2]; /*写入第二个数据L*/ //buffer[24] = dataBuffer[3]; /*写入第二个数据H*/ for (int i = 0; i < dataBuffer.Length; i++) { buffer[21 + i] = dataBuffer[i]; /*循环写入数据*/ } } else { byte newData = byte.Parse(data[0]); buffer[7] = 0x0C; /*数据长度L*/ buffer[8] = 0x00; /*数据长度H*/ buffer[9] = 0x01; /*时钟L*/ buffer[10] = 0x00; /*时钟H*/ buffer[11] = 0x02; /*命令L*/ buffer[12] = 0x14; /*命令H*/ buffer[13] = addrBuffer[4]; /*子命令L*/ buffer[14] = 0x00; /*子命令H*/ buffer[15] = 0x01; /*位访问点数*/ buffer[16] = addrBuffer[0]; /*地址*/ buffer[17] = addrBuffer[1]; /*地址*/ buffer[18] = addrBuffer[2]; /*地址*/ buffer[19] = addrBuffer[3]; /*软元件 0xA8-D;0x90-M;0x0C-X;0x9D-Y*/ buffer[20] = newData; /*数据设置&复位L*/ } #endregion return buffer; } #endregion #region Data数据解析 #region Data数据解析 /// <summary> /// Data数据解析 /// </summary> /// <param name="data">写入数据数组</param> /// <param name="length">写入数据长度</param> /// <param name="type">写入类型:0.按字节写入(8位写入);1.按字写入(16位写入);2.按双字写入(32位写入);</param> /// <returns></returns> private byte[] DataDecode(string[] data, short length, ushort type) { byte[] dataBuffer = null; //---------返回写入指令数组定义 char[] dataChar = null; //-----------写入数据_字符数组 char[] dataChar2 = null; //----------写入数据_字符数组2 char[] dataChar3 = null; //----------写入数据_字符数组3 string dataStr = ""; //--------------写入数据_字符串 string dataStr2 = ""; //-------------写入数据_字符串2 string dataStr3 = ""; //-------------写入数据_字符串3 string dataStr4 = ""; //-------------写入数据_字符串4 Int32 dataInt32; //------------------写入数据_整数 Int64 dataInt64; //------------------写入数据_整数 switch (type) { case 0: //---------------按字节写入(8位写入) #region 按字节写入(8位写入) dataBuffer = new byte[length]; //--------返回写入指令数组实例化 for (int i = 0; i < length; i++) { dataBuffer[i] = byte.Parse(GetComplementer(data[i], 1), System.Globalization.NumberStyles.HexNumber); } #endregion break; case 1: //---------------按字写入(16位写入) #region 按字写入(16位写入) dataBuffer = new byte[length * 2]; //--------返回写入指令数组实例化 for (int i = 0; i < length; i++) { dataChar = GetComplementer(data[i], 2).ToArray(); //-------------写入数据_转化为字符数组 #region 10进制数据转换为字符串 for (int j = 0; j < dataChar.Length; j++) { dataStr += dataChar[j]; //---------------写入数据_转化为字符串 } #endregion #region 字符串数据转换为16进制 dataInt32 = Convert.ToInt32(dataStr); //-------写入数据_转化为长整形 dataStr2 = dataInt32.ToString("x"); //---------写入数据_转化为字符串2 #endregion #region 字符长度补齐 dataChar2 = dataStr2.ToArray(); //-----------写入数据_转化为字符数组2 if (dataChar2.Length % 2 == 1) { dataStr3 = "0"; for (int j = 0; j < dataChar2.Length; j++) { dataStr3 += dataChar2[j]; //---------写入数据_转化为字符串3 } } else { for (int j = 0; j < dataChar2.Length; j++) { dataStr3 += dataChar2[j]; //---------写入数据_转化为字符串3 } } #endregion #region 16进制数据长度补齐 if (dataStr3.Length < 4) { for (int j = 0; j < 4 - dataStr3.Length; j++) { dataStr4 += "0"; } } dataStr4 += dataStr3; #endregion #region 16进制数据按MC协议格式排列 /*16进制数据按MC协议格式排列*/ dataChar3 = dataStr4.ToArray(); for (int j = 0; j < dataStr4.Length / 2; j++) { dataBuffer[2 * i + j] = byte.Parse(dataChar3[dataStr4.Length - 2 - 2 * j].ToString() + dataChar3[dataStr4.Length - 1 - 2 * j].ToString(), System.Globalization.NumberStyles.HexNumber); } #endregion #region 数据清除 dataStr = ""; //--------------写入数据_字符串 dataStr2 = ""; //-------------写入数据_字符串2 dataStr3 = ""; //-------------写入数据_字符串3 dataStr4 = ""; //-------------写入数据_字符串4 #endregion } #endregion break; case 2: //---------------按双字写入(32位写入) #region 按双字写入(32位写入) dataBuffer = new byte[length * 4]; //--------返回写入指令数组实例化 for (int i = 0; i < length; i++) { dataChar = GetComplementer(data[i], 4).ToArray(); //-------------写入数据_转化为字符数组 #region 10进制数据转换为字符串 for (int j = 0; j < dataChar.Length; j++) { dataStr += dataChar[j]; //---------------写入数据_转化为字符串 } #endregion #region 字符串数据转换为16进制 dataInt64 = Convert.ToInt64(dataStr); //-------写入数据_转化为长整形 dataStr2 = dataInt64.ToString("x"); //---------写入数据_转化为字符串2 #endregion #region 字符长度补齐 dataChar2 = dataStr2.ToArray(); //-----------写入数据_转化为字符数组2 if (dataChar2.Length % 2 == 1) { dataStr3 = "0"; for (int j = 0; j < dataChar2.Length; j++) { dataStr3 += dataChar2[j]; //---------写入数据_转化为字符串3 } } else { for (int j = 0; j < dataChar2.Length; j++) { dataStr3 += dataChar2[j]; //---------写入数据_转化为字符串3 } } #endregion #region 16进制数据长度补齐 if (dataStr3.Length < 8) { for (int j = 0; j < 8 - dataStr3.Length; j++) { dataStr4 += "0"; } } dataStr4 += dataStr3; #endregion #region 16进制数据按MC协议格式排列 /*16进制数据按MC协议格式排列*/ dataChar3 = dataStr4.ToArray(); for (int j = 0; j < dataStr4.Length / 2; j++) { dataBuffer[4 * i + j] = byte.Parse(dataChar3[dataStr4.Length - 2 - 2 * j].ToString() + dataChar3[dataStr4.Length - 1 - 2 * j].ToString(), System.Globalization.NumberStyles.HexNumber); } #endregion #region 数据清除 dataStr = ""; //--------------写入数据_字符串 dataStr2 = ""; //-------------写入数据_字符串2 dataStr3 = ""; //-------------写入数据_字符串3 dataStr4 = ""; //-------------写入数据_字符串4 #endregion } #endregion break; } return dataBuffer; } #endregion #region data数据解析_屏蔽 /// <summary> /// data数据解析 /// </summary> /// <param name="data">写入数据数组</param> /// <param name="length">写入数据长度</param> /// <param name="type">写入类型:0.按字节写入(8位写入);1.按字写入(16位写入);2.按双字写入(32位写入);</param> /// <returns></returns> //private byte[] DataDecode(string[] data, short length, ushort type) //{ // #region 10进制数据转换为字符串 // /*10进制数据转换为字符串*/ // char[] dataChar = data[0].ToArray(); // string dataStr = ""; // byte[] dataBuffer = new byte[length * 2]; // for (int i = 0; i < dataChar.Length; i++) // { // dataStr += dataChar[i]; // } // #endregion // #region 字符串数据转换为16进制 // /*字符串数据转换为16进制*/ // long dataInt = Convert.ToInt64(dataStr); // string dataStr2 = dataInt.ToString("x"); // #endregion // #region 字符长度补齐 // /*字符长度补齐*/ // string dataStr3 = ""; // char[] dataChar2 = dataStr2.ToArray(); // if (dataChar2.Length % 2 == 1) // { // dataStr3 = "0"; // for (int i = 0; i < dataChar2.Length; i++) // { // dataStr3 += dataChar2[i]; // } // } // else // { // for (int i = 0; i < dataChar2.Length; i++) // { // dataStr3 += dataChar2[i]; // } // } // #endregion // #region 16进制数据长度补齐 // /*16进制数据长度补齐*/ // string dataStr4 = ""; // for (int i = 0; i < length * 4 - dataStr3.Length; i++) // { // dataStr4 += "0"; // } // #endregion // #region 16进制数据按MC协议格式排列 // /*16进制数据按MC协议格式排列*/ // dataStr4 += dataStr3; // char[] dataChar3 = dataStr4.ToArray(); // for (int i = 0; i < dataStr4.Length / 2; i++) // { // dataBuffer[i] = byte.Parse(dataChar3[dataStr4.Length - 2 - 2 * i].ToString() + // dataChar3[dataStr4.Length - 1 - 2 * i].ToString(), // System.Globalization.NumberStyles.HexNumber); // } // #endregion // return dataBuffer; //} #endregion #region data数据解析备份屏蔽 /*data数据解析备份*/ //private byte[] DataDecode2(string data) //{ // char[] dataChar = data.ToArray(); // string dataStr = ""; // byte[] dataBuffer = new byte[2]; // for (int i = 0; i < dataChar.Length; i++) // { // dataStr += dataChar[i]; // } // int dataInt = Convert.ToInt32(dataStr); // string dataStr2 = dataInt.ToString("x"); // string dataStr3 = ""; // switch (dataStr2.Length) // { // case 1: // dataStr3 = "0" + "0" + "0" + dataStr2; // break; // case 2: // dataStr3 = "0" + "0" + dataStr2; // break; // case 3: // dataStr3 = "0" + dataStr2; // break; // } // char[] dataChar2 = dataStr3.ToArray(); // dataBuffer[0] = byte.Parse(dataChar2[2].ToString() + dataChar2[3].ToString(), System.Globalization.NumberStyles.HexNumber); // dataBuffer[1] = byte.Parse(dataChar2[0].ToString() + dataChar2[1].ToString(), System.Globalization.NumberStyles.HexNumber); // return dataBuffer; //} #endregion #endregion #region 负数求补码 /// <summary> /// 负数求补码string类型 /// </summary> /// <param name="data">数据</param> /// <param name="length">长度</param> /// <returns></returns> private string GetComplementer(string data, int length) { long iData = int.Parse(data); if (iData < 0) { iData = Math.Abs(iData); switch (length) { case 1: //------字节 iData = iData ^ 0xFF; iData++; break; case 2: //------字 iData = iData ^ 0xFFFF; iData++; break; case 4: //------双字 iData = iData ^ 0xFFFFFFFF; iData++; break; } } return iData.ToString(); } /// <summary> /// 负数求补码long类型 /// </summary> /// <param name="data">数据</param> /// <param name="length">长度</param> /// <returns></returns> private long GetComplementer(long data, int length) { long lData = data; long mark = 0; switch (length) { case 1: mark = 0x80; break; case 2: mark = 0x8000; break; case 4: mark = 0x80000000; break; } if (lData > mark) { lData = Math.Abs(lData); switch (length) { case 1: //------字节 lData = data ^ 0xFF; lData++; break; case 2: //------字 lData = data ^ 0xFFFF; lData++; break; case 4: //------双字 lData = data ^ 0xFFFFFFFF; lData++; break; } lData = -lData; } return lData; } #endregion #region MC协议数据接收----------开启一个新线程负责不停的接收服务器发过来的数据 /// <summary> /// MC协议数据接收 /// </summary> private void Receive() { #region 读取返回命令说明 /* * buffer[0] = D0 网络副标题L--固定值 * buffer[1] = 00 网络副标题H--固定值 * buffer[2] = 00 网络编号--固定值 * buffer[3] = FF PLC编号--固定值 * buffer[4] = FF 目标模块IO编号L--固定值 * buffer[5] = 03 目标模块IO编号H--固定值 * buffer[6] = 00 模块站编号--固定值 * buffer[7] = 06 应答数据长度L * buffer[8] = 00 应答数据长度H * buffer[9] = 00 结束代码L //---(结束代码) :可以理解成异常代码,如果正常的话,就是0000 * buffer[10] = 00 结束代码H * buffer[11] = 00 读取第一个数据L * buffer[12] = 00 读取第一个数据H * buffer[13] = 00 读取第二个数据L * buffer[14] = 00 读取第二个数据H */ #endregion #region 写入返回命令说明 /* * buffer[0] = D0 网络副标题L--固定值 * buffer[1] = 00 网络副标题H--固定值 * buffer[2] = 00 网络编号--固定值 * buffer[3] = FF PLC编号--固定值 * buffer[4] = FF 目标模块IO编号L--固定值 * buffer[5] = 03 目标模块IO编号H--固定值 * buffer[6] = 00 模块站编号--固定值 * buffer[7] = 02 应答数据长度L * buffer[8] = 00 应答数据长度H * buffer[9] = 00 结束代码L * buffer[10] = 00 结束代码H */ #endregion while (true) { try { byte[] buffer = new byte[1024]; int r = ClientSocket.Receive(buffer); long[] dataBuffer = new long[256]; int dataLength = buffer[7] - 2; //---数据长度(buffer[7]:数据长度) #region 调试信息显示 if (debugDisplay) //---调试信息显示 { stopTime2 = DateTime.Now; info.msg = "接收指令时间(毫秒):" + stopTime2.Subtract(stopTime1).TotalMilliseconds; if (LogInfoDisplay != null) //------判断委托不为空时执行委托 LogInfoDisplay(info); } #endregion /*判断结束代码是:0x0000,正常结束;不是0x0000*/ if (buffer[9] == 0x00 && buffer[10] == 0x00) /*判断结束代码是:0x0000;正常结束*/ { if (dataLength > 0) /*读取原始数据--(buffer[7]:读取数据长度)*/ { #region 读取原始数据 /*读取数据命令*/ string[] dataBufferStr = DataBufferDecode(buffer, r); string dataStr = ""; for (int i = dataLength; i > 0; i--) { dataStr += dataBufferStr[11 + i - 1]; /*数据结构:低位在前,高位在后;*/ } if (debugDisplay) //---调试信息显示 { info.msg = "读取数据(16进制):" + dataStr + ""; if (LogInfoDisplay != null) //------判断委托不为空时执行委托 LogInfoDisplay(info); } readStr = new string[dataLength / readType]; //---读取数据string类型 string[] readStr2 = new string[dataLength / readType]; //--读取数据string类型2 readData = new long[dataLength / readType]; //------读取数据long类型 for (int i = 0; i < dataLength / readType; i++) { for (int j = readType; j > 0; j--) { readStr[i] += dataBufferStr[11 + readType * i + j - 1]; } } #endregion #region 10进制数据按MC协议格式排列 #region for循环解码---屏蔽 ///*16进制数据按MC协议格式排列*/ //string infoStr = null; //long infoLong = 0; //int m = 0; //foreach (var item in readStr) //{ // char[] dataChar3 = item.ToArray(); // foreach (var cItem in dataChar3) // { // readStr2[m] += cItem.ToString(); // } // readData[m] = int.Parse(readStr2[m], // System.Globalization.NumberStyles.HexNumber); // infoStr = readData[m].ToString(); // infoLong = GetComplementer(long.Parse(infoStr), readType); // state = "读取数据(10进制" + readType + "位):" + infoStr + "; 补码:" + infoLong; // Infomation(state); /*回调方法---状态显示*/ // m++; //} #endregion #region for循环解码 /*16进制数据按MC协议格式排列*/ string data_decimalStr = null; long data_decimalLong = 0; for (int i = 0; i < readStr.Length; i++) { char[] dataChar3 = readStr[i].ToArray(); for (int j = 0; j < 2 * readType; j++) { readStr2[i] += dataChar3[j].ToString(); } readData[i] = int.Parse(readStr2[i], System.Globalization.NumberStyles.HexNumber); data_decimalStr = readData[i].ToString(); data_decimalLong = GetComplementer(long.Parse(data_decimalStr), readType); if (debugDisplay) //---调试信息显示 { info.msg = "读取数据(10进制" + readType + "位):" + data_decimalStr + "; 补码:" + data_decimalLong; if (LogInfoDisplay != null) //------判断委托不为空时执行委托 LogInfoDisplay(info); } } #endregion #endregion #region 接收数据赋值 long[] readData_long = new long[readData.Length + 1]; for (int i = 0; i < readData.Length; i++) { readData_long[i] = readData[i]; } readData_long[readData.Length] = readType; readData = readData_long; #endregion if (returnReceiveEvent != null) returnReceiveEvent(readData_long); //------接收返回数据事件 //isReceiveSuccess = true; //---接收完成 #region 调试信息显示 if (debugDisplay) //---调试信息显示 { stopTime = DateTime.Now; info.msg = "数据解析时间(毫秒):" + stopTime.Subtract(stopTime2).TotalMilliseconds; if (LogInfoDisplay != null) //------判断委托不为空时执行委托 LogInfoDisplay(info); info.msg = "读取总时间(毫秒):" + stopTime.Subtract(startTime).TotalMilliseconds; if (LogInfoDisplay != null) //------判断委托不为空时执行委托 LogInfoDisplay(info); } #endregion } else /*写入反馈解析*/ { if (isWriteHandshake) //---写入握手信号 isWriteSuccess = true; //---写入成功标志 #region 写入反馈 if (debugDisplay) //---调试信息显示 { /*写入反馈*/ string dataStr = buffer[10].ToString("x") + buffer[9].ToString("x"); info.msg = "写入反馈结束代码(16进制):" + dataStr + ""; if (LogInfoDisplay != null) //------判断委托不为空时执行委托 LogInfoDisplay(info); int dataInt = int.Parse(dataStr, System.Globalization.NumberStyles.HexNumber); info.msg = "写入反馈结束代码(10进制):" + dataInt + ""; if (LogInfoDisplay != null) //------判断委托不为空时执行委托 LogInfoDisplay(info); //status = "写入反馈命令:" + newBuffer + ""; //Infomation(status); /*回调方法---状态显示*/ } #endregion } } else /*判断结束代码不是:0x0000;异常结束*/ { #region 异常结束 if (debugDisplay) //---调试信息显示 { string dataStr = buffer[10].ToString("x") + buffer[9].ToString("x"); info.msg = "异常结束(16进制):" + dataStr + ""; if (LogInfoDisplay != null) //------判断委托不为空时执行委托 LogInfoDisplay(info); int dataInt = int.Parse(dataStr, System.Globalization.NumberStyles.HexNumber); info.msg = "异常结束(10进制):" + dataInt + ""; if (LogInfoDisplay != null) //------判断委托不为空时执行委托 LogInfoDisplay(info); } #endregion } } catch { } } } #endregion #region 读出数据解码 /// <summary> /// 读出数据解码 /// </summary> /// <param name="buffer">反馈命令--type[]数组类型</param> /// <param name="length">长度</param> /// <returns>返回命令--16进制string[]数组类型;每位2个数据</returns> private string[] DataBufferDecode(byte[] buffer, int length = 0) { if (length <= 0) length = buffer.Length; string[] dataBuffer = new string[length]; for (int i = 0; i < length; i++) { if (buffer[i].ToString("x").Length == 1) { dataBuffer[i] = "0" + buffer[i].ToString("x"); } else { dataBuffer[i] = buffer[i].ToString("x"); } } return dataBuffer; } #endregion #region 获取读取数据 /// <summary> /// 获取读取数据 /// </summary> /// <returns>long类型数据</returns> public long[] GetReceiveData_long() { return readData; } /// <summary> /// 获取读取数据 /// </summary> /// <returns>string类型数据</returns> public string[] GetReceiveData_string() { return readStr; } #endregion } #endregion
10-16
你提供的代码是一个用于与 **三菱PLC(Mitsubishi PLC)** 通过 **MC协议(MELSEC Communication Protocol)** 进行通信的 C# 客户端类 `MCClient`,主要使用 TCP Socket 实现数据读写。该协议常用于工业自动化中对 PLC 的 D、M、X、Y 等寄存器进行读写操作。 下面我将从以下几个方面详细解析这段代码: --- ## ✅ 功能概述 该 `MCClient` 类实现了以下功能: - 建立与三菱PLC的TCP连接 - 发送 MC 协议格式的读/写命令 - 接收并解析返回的数据 - 支持多种数据类型(8位、16位、32位) - 支持负数补码处理 - 提供事件回调机制(日志、接收数据) --- ## ✅ 关键结构分析 ### 1. **Socket 通信基础** ```csharp Socket ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); ``` 使用标准 TCP 协议连接到 PLC,默认端口通常是 `5001` 或 `5002`。 --- ### 2. **MC 协议帧结构** #### 🟩 读命令请求帧(QnA 3E 帧兼容) | 字节偏移 | 内容 | |---------|------| | 0~1 | 固定值 `0x5000`(副标题) | | 2 | 网络号(通常为 0) | | 3 | PLC编号(通常为 0xFF) | | 4~5 | IO站号(H:0x03, L:0xFF → 模块IO编号) | | 6 | 站号(通常为 0) | | 7~8 | 请求数据长度(L/H),固定为 `0x0C00` | | 9~10 | 监视定时器(等待响应时间,单位:250ms,如 `0x0001`=4秒) | | 11~12 | 命令码:读=`0x0104`,写=`0x0114` | | 13~14 | 子命令:按字=0x0000,按位=0x0001 | | 15~17 | 起始地址(低位在前) | | 18 | 元件类型(D=0xA8, M=0x90, X=0x9C, Y=0x9D) | | 19~20 | 数据点数(L/H) | > 示例:读取 D100 地址,长度为 2 个字(即 2×16bit) > > ```hex > 50 00 00 FF FF 03 00 0C 00 01 00 01 04 00 00 64 00 00 A8 02 00 > ``` --- #### 🟥 写命令请求帧 - 类似读命令,但命令码是 `0x0114` - 数据部分附加实际要写入的数值(小端序) --- #### 🔵 返回响应帧 | 字节偏移 | 含义 | |--------|------| | 0~6 | 头部信息(同上) | | 7~8 | 应答数据长度(读时为 `0x06 + 数据长度×2`,写时为 `0x02`) | | 9~10 | 结束码:`0x0000`=成功,非零表示错误(见下表) | | 11+ | 实际读取的数据(每两个字节一个 WORD,小端序) | 常见结束码: - `0x0000`: 正常完成 - `0x0401`: PLC STOP 状态 - `0x0402`: 命令错误 - `0x1301`: 数据长度异常 - `0x1401`: 寄存器地址越界 --- ## ✅ 核心方法详解 ### 🔹 `CommandRead(addr, length, type)` 构造读取指令,并发送。 ```csharp public void CommandRead(string addr, int length, int type = 2) { byte[] addrBuffer = MCReadDecode(addr); // 解析地址和元件类型 ... buffer[15] = addrBuffer[0]; // 地址LL buffer[16] = addrBuffer[1]; // 地址L buffer[17] = addrBuffer[2]; // 地址H buffer[18] = addrBuffer[3]; // 元件代码 buffer[19] = (byte)length; // 数据长度L buffer[20] = 0x00; // 数据长度H ... ClientSocket.Send(buffer); } ``` ✅ 注意:这里 `type` 参数并未真正影响读取方式(只记录未用),因为 MC 协议读取默认是以“字”(WORD=16bit)为单位。 --- ### 🔹 `MCReadDecode(string addr)` 解析地址字符串(如 `"D100"`)成 MC 协议所需的三部分:地址高位、中位、低位 + 元件代码。 例如:`"D100"` → - 地址十进制:100 → 十六进制:`0x0064` - 分解为: - LL: `0x64`, L: `0x00`, H: `0x00` - 元件代码:D → `0xA8` 输出数组: ```csharp addrBuffer[0] = 0x64; addrBuffer[1] = 0x00; addrBuffer[2] = 0x00; addrBuffer[3] = 0xA8; ``` ⚠️ 缺陷:对于超过 65535 的地址可能有问题(需支持双字节扩展) --- ### 🔹 `CommandWrite(addr, data[], length, type)` 构建写入命令帧。 重点在于 `DataDecode()` 方法将字符串数组转换为字节数组。 支持三种模式: - type=0: byte 写入 - type=1: word 写入(16位) - type=2: dword 写入(32位) 但是注意:当前逻辑中只有 D 寄存器支持多字节写入,M/X/Y 是单 bit 操作。 --- ### 🔹 `DataDecode(data[], length, type)` 将输入的字符串数组(如 `{"100", "-5"}`)转为符合 MC 协议的小端序字节数组。 #### 示例:写入 100(16位) - 十进制 100 → 十六进制 `0x0064` - 小端序存储:低字节在前 → `[0x64, 0x00]` #### 负数处理:调用 `GetComplementer()` ```csharp private string GetComplementer(string data, int length) { long iData = int.Parse(data); if (iData < 0) { iData = Math.Abs(iData); switch (length) { case 1: iData = iData ^ 0xFF; break; case 2: iData = iData ^ 0xFFFF; break; case 4: iData = iData ^ 0xFFFFFFFF; break; } iData++; } return iData.ToString(); } ``` 这正确计算了负数的补码(二进制反码+1),然后以正整数形式传给 PLC。 --- ### 🔹 `Receive()` —— 异步接收线程 持续监听来自 PLC 的回复包。 关键步骤: 1. 接收字节流 2. 判断结束码是否为 `0x0000` 3. 若是读操作,则解析数据段 4. 调用 `returnReceiveEvent(readData_long)` 返回结果 ⚠️ 潜在问题: - 使用 `while(true)` 循环没有退出条件控制 - 异常捕获太宽泛(`catch { }`),不利于调试 - 未做粘包/分包处理(虽然MC协议一般一来一回,但建议加判断) --- ### 🔹 `DataBufferDecode(byte[])` 把每个字节转为两位十六进制字符串(带补零),便于后续拼接。 例如:`buffer[11]=0x64` → `"64"` --- ### 🔹 `GetReceiveData_long()` / `string[]` 提供对外接口获取最新读取的结果。 --- ## ✅ 使用示例(补充) ```csharp var client = new MCClient(); // 初始化(可选回调) client.Initial("MyPLC", receiveData: (data) => { Console.WriteLine($"收到数据: [{string.Join(",", data)}]"); }, info: (msg) => { Console.WriteLine($"[LOG] {msg.msgType}: {msg.msg}"); }); client.debugDisplay = true; // 连接 PLC client.Connection("192.168.0.10", "5001"); // 读取 D100 开始的 2 个字 client.CommandRead("D100", 2); // 写入 D200,值为 123 和 -45 client.CommandWrite("D200", new[] { "123", "-45" }, 2, type: 1); // 1=16位写入 ``` --- ## ✅ 改进建议 | 问题 | 建议 | |------|------| | 所有异常都被吞掉(空 catch) | 添加日志或抛出异常 | | `receiveTh.Abort()` 已废弃 | 改用 `CancellationToken` 控制循环退出 | | `Thread.Sleep(100)` 不精确 | 可接受,但可用更优雅方式 | | 不支持异步 async/await | 可升级为 `Task` + `SocketAsyncEventArgs` | | 地址解析仅支持简单 D/M/X/Y | 可增加对 W、R、SD 等地址的支持 | | 无超时重试机制 | 建议添加超时检测(如 `Stopwatch`) | | `GetComplementer(long, int)` 中存在 bug | `lData > mark` 条件不合理,应改为 `(ulong)lData >= (1UL << (length*8 - 1))` | --- ## ✅ 修复建议:负数判断逻辑优化 当前 `GetComplementer(long data, int length)` 有问题: ```csharp if (lData > mark) // ❌ 错误!正数也可能大于 mark ``` 应改为判断符号位: ```csharp private long GetComplementer(long value, int bytes) { ulong mask = bytes == 1 ? 0x80UL : bytes == 2 ? 0x8000UL : bytes == 4 ? 0x80000000UL : 0; if ((ulong)value >= (1UL << (bytes * 8 - 1))) { // 视为负数补码 value = -(long)((~value + 1) & ((1UL << (bytes * 8)) - 1)); } return value; } ``` 这样可以从原始 `uint16` 补码还原出正确的 `int16`。 --- ## ✅ 总结 这是一个实用的 **MC协议客户端实现**,适用于大多数三菱FX/Q系列PLC的基础通信需求。尽管存在一些细节上的不足(如异常处理、线程安全、地址范围限制),但整体架构清晰,适合学习和二次开发。 --- ##
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值