要处理串口数据首先是要对单片机的串口中断进行处理,我的方法是正确的命令必须要在命令的结尾处同时带有回车和换行,处理过程如下
//串口接收缓冲区 u8 serial_Buffer[SERIAL_MAX_LENGTH] = {0}; //串口接收数据长度 u16 serial_Buffer_Length = 0; static void SerialRecv(u8 ch) { if((serial_Buffer_Length&0x8000) == 0x8000)//已经接收完成,系统还没处理 { serial_Buffer_Length |= 0x8000;//退出 } else if((serial_Buffer_Length&0x4000) == 0x4000)//接收到回车还没接收到换行 { if(ch == '\n')serial_Buffer_Length |= 0x8000; else { //一帧接受失败 serial_Buffer_Length = 0; } } else { if((serial_Buffer_Length&0xff) < SERIAL_MAX_LENGTH) { if(ch == '\r')serial_Buffer_Length |= 0x4000; else { serial_Buffer[(serial_Buffer_Length&0xff)] = ch; serial_Buffer_Length++; } } else { //一帧接受失败 serial_Buffer_Length = 0; } } } void USART1_IRQHandler(void) { u8 ch = 0; if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)//检查中断发生 { ch = (u8)USART_ReceiveData(USART1); USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除中断 // Debug_Serial_Send_Char(ch); //将收到的数据发送出去 SerialRecv(ch); //处理中断数据 } }
这一帧数据接收到了之后就会阻塞串口,不再接受新数据,这时候我们就要定时的调用命令处理任务,将接收到的数据提取出来,如下
//扫描命令字符串,并调用相应处理函数 void CommandScan(void) { u8 commandLength1; u8 commandLength2; u8 i = 0,j = 0; //数据满 if((serial_Buffer_Length & 0x8000) == 0x8000) { //检测命令不是全为空格 if(Command_Is_Vailed()) { Command_Copy();//copy命令字符串等待处理 //去除命令头上的空白 Command_Remove_Space_Head(); //去除命令尾巴上的空格 Command_Remove_Space_End(); //去除中间的重复空格 Command_Remove_Space_Inner(); commandLength1 = Command_Find_Space_Postion(1);//获取长度 if(commandLength1 == 0)commandLength1 = commandStringLength;//当第二个空格获取返回0的时候,说明没有参数,纯命令,所以没有空格 for(i = 0; i < COMMAND_NUM; i++) { commandLength2 = StringGetLength(commandStringList[i]); if(commandLength1 == commandLength2) { //长度相同,比对每个字符 for(j = 0; j < commandLength1; j++) { if(commandStringBuffer[j] == commandStringList[i][j])continue; else break; } if(j == commandLength1)//比对成功 { //调用函数 Command_Proc_Func_Table[i](); return; } } else { //直接长度不同,不需要比对了 continue; } } if(i == COMMAND_NUM) { //没找到对应命令 printf("not find command\r\n"); } } else { printf("command can't all space\r\n"); serial_Buffer_Length = 0; } } }
先去除发送过来的数据头尾的空格,然后去除中间的空格,这样就能保证一定的数据纠错能力去除空白的代码段如下
//去除命令字符串的前面的空格字符 void Command_Remove_Space_Head(void) { u8 index = 0; u8 i = 0; for(index = 0; index < commandStringLength; index++) { if(commandStringBuffer[index] == ' ')continue; else break; } if(index == 0)//前面没有空格 { return; } else { //删除空格 for(i = 0; i < (commandStringLength-index);i++) { commandStringBuffer[i] = commandStringBuffer[index+i]; } commandStringLength -= index; } }
//去除命令字符串后面的空格 void Command_Remove_Space_End(void) { u8 i = 0; //寻找字符串最尾巴上空格的位置 for(i = commandStringLength; i > 0; i--) { if(commandStringBuffer[i-1] == ' ')continue;//如果这个是空格,继续下一次寻找 else break;//不是空格,到此为止 } if(i == commandStringLength)//尾上没有空格 { return; } else //尾上有空格 { commandStringBuffer[i] = '\0'; commandStringLength = i; return; } }
//去除命令字符串中间的空格,将连续两个的空格合并成一个 void Command_Remove_Space_Inner(void) { u8 spaceCount; u8 i = 0; u8 j = 0; for(i = 0; i < commandStringLength; i++) { //此时检测到一个空格 if(commandStringBuffer[i] == ' ') { //立刻查看下一个是不是空格 if(commandStringBuffer[i+1] == ' ') { spaceCount = 2; //下一个也是空格,此时说明已经有了两个连续的空格了必须立刻查找到结束的空格在哪 for(j = i+2; j < commandStringLength; j++) { //当不是空格的时候跳出来,是空格就一直加 if(commandStringBuffer[j] == ' ')spaceCount++; else break; } //跳出来根据space的值来移动数组,同时减小长度 //i是第一个空格,i+1是第二个空格,最后一个空格是spaceCount-2 for(j = i+1;j < commandStringLength-spaceCount+1;j++) { //要跳过spacecount-1的数量,来拷贝有效字符 commandStringBuffer[j] = commandStringBuffer[j+spaceCount-1]; } //最后修改长度,长度缩减量是空格数-1,因为保留了一个空格 commandStringLength -= (spaceCount-1); } else { //下一个不是空格,说明是只有一个空格的环境,不用操心,进行下一次循环 continue; } } } }
去除空格之后,可能这一次的命令是带参数的,那么我们去除第一个连续的字符串当成命令,所以这个特性就规定了命令本身(不包括参数)必须是连续的字符串,取出命令的函数如下
commandLength1 = Command_Find_Space_Postion(1);//获取长度 if(commandLength1 == 0)commandLength1 = commandStringLength;//当第二个空格获取返回0的时候,说明没有参数,纯命令,所以没有空格
获取命令中第一个空格的位置,那就是命令字符串的结尾,接下来需要和命令数组进行比对,命令数组如下
//命令列表,命令最长50字节 u8 commandStringList[][50] = \ { "help",\ "list",\ "iap_down",\ "iap_jump_app",\ "iap_over",\ "iap_set_flag",\ "iap_clear_flag" };
每个命令最大不超过49个字节,命令个数可以由实际情况编译器自行处理,当比对成功之后,自动的按照命令在命令序列中的序列号调用相应的函数,这里使用指针回调机制,如下
//回调函数数组定义 Command_Proc_Func Command_Proc_Func_Table[] = { Help_Proc_Func, List_Proc_Func, iap_down_s, iap_jump_app_s, iap_over_s, iap_set_flag_s, iap_clear_flag };
typedef void (*Command_Proc_Func)(void); extern u8 commandStringList[][50] ; extern Command_Proc_Func Command_Proc_Func_Table[];
这就要求命令和命令响应函数在数组中的位置必须是对应的,这样就能实现一个简单的shell了,为了解析参数,我做了几个函数可以解析十进制和十六进制的数据,如下
/*******************************字符串参数转换接口***********************/ //将十进制格式的字符串参数转换为数值,返回8位无符号整形 //value 最终转换值指针 //index 指示第几个参数,第一个参数为1 ...... //返回值 转换是否成功,失败返回1 成功返回0 u8 CommandGetParamToDecU8(u8* value,u8 index) { u8 result; u32 valueResult = 0; u8 i = 0; u32 fac = 1; result = CommandGetParamStr(paramBuffer,PARAM_COVERT_MAX_LENGTH,¶mLength,index); if(result == 0)return 1;//找不到这么多参数 //找到之后根据长度计算系数 //系数计算 for(i = 1; i < paramLength;i++) { fac *= 10; } //校验每个参数字符值是否符合标准,十进制就必须在0-9之间 for(i = 0;i<paramLength;i++) { if(paramBuffer[i] > '9' || paramBuffer[i] < '0') { return 1;//参数错误 } } //开始计算 for(i = 0; i < paramLength;i++) { valueResult += (paramBuffer[i]-'0')*fac; fac/=10; } //检测最终结果是否大于限制值,如八位那么结果不能大于255 if(valueResult > 0xff)return 1;//参数错误 else { *value = (u8)valueResult; return 0; } } //与上一个参数类似,检测十六进制参数 u8 CommandGetParamToDecU16(u16* value,u8 index) { u8 result; u32 valueResult = 0; u8 i = 0; u32 fac = 1; result = CommandGetParamStr(paramBuffer,PARAM_COVERT_MAX_LENGTH,¶mLength,index); if(result == 0)return 1;//找不到这么多参数 //找到之后根据长度计算系数 //系数计算 for(i = 1; i < paramLength;i++) { fac *= 10; } //校验每个参数字符值是否符合标准,十进制就必须在0-9之间 for(i = 0;i<paramLength;i++) { if(paramBuffer[i] > '9' || paramBuffer[i] < '0') { return 1;//参数错误 } } //开始计算 for(i = 0; i < paramLength;i++) { valueResult += (paramBuffer[i]-'0')*fac; fac/=10; } //检测最终结果是否大于限制值,如八位那么结果不能大于255 if(valueResult > 0xffff)return 1;//参数错误 else { *value = (u16)valueResult; return 0; } } u8 CommandGetParamToDecU32(u32* value,u8 index) { u8 result; u32 valueResult = 0; u8 i = 0; u32 fac = 1; result = CommandGetParamStr(paramBuffer,PARAM_COVERT_MAX_LENGTH,¶mLength,index); if(result == 0)return 1;//找不到这么多参数 //找到之后根据长度计算系数 //系数计算 for(i = 1; i < paramLength;i++) { fac *= 10; } //校验每个参数字符值是否符合标准,十进制就必须在0-9之间 for(i = 0;i<paramLength;i++) { if(paramBuffer[i] > '9' || paramBuffer[i] < '0') { return 1;//参数错误 } } //开始计算 for(i = 0; i < paramLength;i++) { valueResult += (paramBuffer[i]-'0')*fac; fac/=10; } //检测最终结果是否大于限制值,如八位那么结果不能大于255 if(valueResult > 0xffffffff)return 1;//参数错误 else { *value = (u32)valueResult; return 0; } } //从命令字符串中获取参数并转换参数,将0x格式的字符串转换为数值 成功返回0 失败返回1 //参数类型必须是0x开头的 u8 CommandGetParamToHexU8(u8* value,u8 index) { u8 result; u32 valueResult = 0; u8 i = 0; u32 fac = 1; result = CommandGetParamStr(paramBuffer,PARAM_COVERT_MAX_LENGTH,¶mLength,index); if(result == 0)return 1;//找不到这么多参数 //检测参数长度,因为开头必须为0x,所以长度必须为3以上 if(paramLength <= 2)return 1;//失败 //计算系数 for(i = 3; i < paramLength; i++) { fac *= 16; //因为0x占用了两个字节 第一未为1 所以乘法运算从第三个开始 } //检测开头是否正确 0x if(paramBuffer[0] == '0' &&(paramBuffer[1] == 'x'||paramBuffer[1] == 'X')) { //检测每一位数据是否正确并计算最终值 for(i = 2; i < paramLength; i++) { if(paramBuffer[i] >= '0' && paramBuffer[i] <= '9') { result = paramBuffer[i] -'0'; } else if(paramBuffer[i] >= 'a' && paramBuffer[i] <= 'f') { result = paramBuffer[i] -'a'+10; } else if(paramBuffer[i] >= 'A' && paramBuffer[i] <= 'F') { result = paramBuffer[i] -'A'+10; } else { //出现范围之外的数据,返回1 return 1; } valueResult += (u32)(result*fac); fac /= 16; } //计算完成,检测参数是否超过范围 if(valueResult > 0xff)return 1;//参数错误 else { *value = (u8)valueResult; return 0; } } else { //参数开头不对 return 1; } } //从命令字符串中获取参数并转换参数,将0x格式的字符串转换为数值 成功返回0 失败返回1 u8 CommandGetParamToHexU16(u16* value,u8 index) { u8 result; u32 valueResult = 0; u8 i = 0; u32 fac = 1; result = CommandGetParamStr(paramBuffer,PARAM_COVERT_MAX_LENGTH,¶mLength,index); if(result == 0)return 1;//找不到这么多参数 //检测参数长度,因为开头必须为0x,所以长度必须为3以上 if(paramLength <= 2)return 1;//失败 //计算系数 for(i = 3; i < paramLength; i++) { fac *= 16;//因为0x占用了两个字节 第一未为1 所以乘法运算从第三个开始 } //检测开头是否正确 0x if(paramBuffer[0] == '0' &&(paramBuffer[1] == 'x'||paramBuffer[1] == 'X')) { //检测每一位数据是否正确并计算最终值 for(i = 2; i < paramLength; i++) { if(paramBuffer[i] >= '0' && paramBuffer[i] <= '9') { result = paramBuffer[i] -'0'; } else if(paramBuffer[i] >= 'a' && paramBuffer[i] <= 'f') { result = paramBuffer[i] -'a'+10; } else if(paramBuffer[i] >= 'A' && paramBuffer[i] <= 'F') { result = paramBuffer[i] -'A'+10; } else { //出现范围之外的数据,返回1 return 1; } valueResult += (u32)(result*fac); fac /= 16; } //计算完成,检测参数是否超过范围 if(valueResult > 0xffff)return 1;//参数错误 else { *value = (u16)valueResult; return 0; } } else { //参数开头不对 return 1; } } //从命令字符串中获取参数并转换参数,将0x格式的字符串转换为数值 成功返回0 失败返回1 u8 CommandGetParamToHexU32(u32* value,u8 index) { u8 result; u32 valueResult = 0; u8 i = 0; u32 fac = 1; result = CommandGetParamStr(paramBuffer,PARAM_COVERT_MAX_LENGTH,¶mLength,index); if(result == 0)return 1;//找不到这么多参数 //检测参数长度,因为开头必须为0x,所以长度必须为3以上 if(paramLength <= 2)return 1;//失败 //计算系数 for(i = 3; i < paramLength; i++) { fac *= 16;//因为0x占用了两个字节 第一未为1 所以乘法运算从第三个开始 } //检测开头是否正确 0x if(paramBuffer[0] == '0' &&(paramBuffer[1] == 'x'||paramBuffer[1] == 'X')) { //检测每一位数据是否正确并计算最终值 for(i = 2; i < paramLength; i++) { if(paramBuffer[i] >= '0' && paramBuffer[i] <= '9') { result = paramBuffer[i] -'0'; } else if(paramBuffer[i] >= 'a' && paramBuffer[i] <= 'f') { result = paramBuffer[i] -'a'+10; } else if(paramBuffer[i] >= 'A' && paramBuffer[i] <= 'F') { result = paramBuffer[i] -'A'+10; } else { //出现范围之外的数据,返回1 return 1; } valueResult += (u32)(result*fac); fac /= 16; } //计算完成,检测参数是否超过范围 if(valueResult > 0xffffffff)return 1;//参数错误 else { *value = (u32)valueResult; return 0; } } else { //参数开头不对 return 1; } }
恩,到这里基本就实现这个shell了,现在增加命令以及增加命令响应函数是不是就简单多了,反正我是觉得简单多了。