单⽚机裸机下写⼀个⾃⼰的shell调试器
该⽂章是针对于串⼝通讯过程中快速定义命令⽽写的,算是我⾃⼰的⼀个通⽤化的平台,专门⽤来进⾏串⼝调试⽤,莫要取笑
要处理串⼝数据⾸先是要对单⽚机的串⼝中断进⾏处理,我的⽅法是正确的命令必须要在命令的结尾处同时带有回车和换⾏,处理过程如下
//串⼝接收缓冲区
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)return1;//不到这么多参数
//到之后根据长度计算系数
/
/系数计算
for(i = 1; i < paramLength;i++)
{
fac *= 10;
}
//校验每个参数字符值是否符合标准,⼗进制就必须在0-9之间
for(i = 0;i<paramLength;i++)
{
if(paramBuffer[i] > '9' || paramBuffer[i] < '0')
{
return1;//参数错误
}
}
//开始计算
for(i = 0; i < paramLength;i++)
{
valueResult += (paramBuffer[i]-'0')*fac;
fac/=10;
}
//检测最终结果是否⼤于限制值,如⼋位那么结果不能⼤于255
if(valueResult > 0xff)return1;//参数错误
else
单片机printf函数
{
*value = (u8)valueResult;
return0;
}
}
//与上⼀个参数类似,检测⼗六进制参数
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)return1;//不到这么多参数
//到之后根据长度计算系数
//系数计算
for(i = 1; i < paramLength;i++)
{
fac *= 10;
}
//校验每个参数字符值是否符合标准,⼗进制就必须在0-9之间
for(i = 0;i<paramLength;i++)
{
if(paramBuffer[i] > '9' || paramBuffer[i] < '0')
{
return1;//参数错误
}
}
//开始计算
for(i = 0; i < paramLength;i++)
{
valueResult += (paramBuffer[i]-'0')*fac;
fac/=10;
}
//检测最终结果是否⼤于限制值,如⼋位那么结果不能⼤于255
if(valueResult > 0xffff)return1;//参数错误
else
{
*value = (u16)valueResult;
return0;
}
}
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)return1;//不到这么多参数
//到之后根据长度计算系数
//系数计算
for(i = 1; i < paramLength;i++)
{
fac *= 10;
}
//校验每个参数字符值是否符合标准,⼗进制就必须在0-9之间
for(i = 0;i<paramLength;i++)
{
if(paramBuffer[i] > '9' || paramBuffer[i] < '0')
{
return1;//参数错误
}
}
//开始计算
for(i = 0; i < paramLength;i++)
{
valueResult += (paramBuffer[i]-'0')*fac;
fac/=10;
}
//检测最终结果是否⼤于限制值,如⼋位那么结果不能⼤于255
if(valueResult > 0xffffffff)return1;//参数错误
else
{
*value = (u32)valueResult;
return0;
}
}
/
/从命令字符串中获取参数并转换参数,将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)return1;//不到这么多参数
//检测参数长度,因为开头必须为0x,所以长度必须为3以上
if(paramLength <= 2)return1;//失败
//计算系数
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
{