单片机裸机下写一个自己的shell调试器
该文章是针对于串口通讯过程中快速定义命令而写的,算是我自己的一个通用化的平台,专门用来进行串口调试用,莫要取笑
要处理串口数据首先是要对单片机的串口中断进行处理,我的方法是正确的命令必须要在命令的结尾处同时带有回车和换行,处理过程如下
//串口接收缓冲区
u8 serial_Buffer[SERIAL_MAX_LENGTH] = {};
//串口接收数据长度
u16 serial_Buffer_Length = ; 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 = ;
}
}
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 = ;
}
}
} void USART1_IRQHandler(void)
{
u8 ch = ;
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 = ,j = ;
//数据满
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();//获取长度
if(commandLength1 == )commandLength1 = commandStringLength;//当第二个空格获取返回0的时候,说明没有参数,纯命令,所以没有空格
for(i = ; i < COMMAND_NUM; i++)
{
commandLength2 = StringGetLength(commandStringList[i]);
if(commandLength1 == commandLength2)
{
//长度相同,比对每个字符
for(j = ; 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 = ;
} }
}
先去除发送过来的数据头尾的空格,然后去除中间的空格,这样就能保证一定的数据纠错能力去除空白的代码段如下
//去除命令字符串的前面的空格字符
void Command_Remove_Space_Head(void)
{
u8 index = ;
u8 i = ;
for(index = ; index < commandStringLength; index++)
{
if(commandStringBuffer[index] == ' ')continue;
else break;
}
if(index == )//前面没有空格
{
return;
}
else
{
//删除空格
for(i = ; i < (commandStringLength-index);i++)
{
commandStringBuffer[i] = commandStringBuffer[index+i];
}
commandStringLength -= index;
}
}
//去除命令字符串后面的空格
void Command_Remove_Space_End(void)
{
u8 i = ;
//寻找字符串最尾巴上空格的位置
for(i = commandStringLength; i > ; i--)
{
if(commandStringBuffer[i-] == ' ')continue;//如果这个是空格,继续下一次寻找
else break;//不是空格,到此为止
}
if(i == commandStringLength)//尾上没有空格
{
return;
}
else //尾上有空格
{
commandStringBuffer[i] = '\0';
commandStringLength = i;
return;
} }
//去除命令字符串中间的空格,将连续两个的空格合并成一个
void Command_Remove_Space_Inner(void)
{
u8 spaceCount;
u8 i = ;
u8 j = ;
for(i = ; i < commandStringLength; i++)
{
//此时检测到一个空格
if(commandStringBuffer[i] == ' ')
{
//立刻查看下一个是不是空格
if(commandStringBuffer[i+] == ' ')
{
spaceCount = ;
//下一个也是空格,此时说明已经有了两个连续的空格了必须立刻查找到结束的空格在哪
for(j = i+; j < commandStringLength; j++)
{
//当不是空格的时候跳出来,是空格就一直加
if(commandStringBuffer[j] == ' ')spaceCount++;
else break;
}
//跳出来根据space的值来移动数组,同时减小长度
//i是第一个空格,i+1是第二个空格,最后一个空格是spaceCount-2
for(j = i+;j < commandStringLength-spaceCount+;j++)
{
//要跳过spacecount-1的数量,来拷贝有效字符
commandStringBuffer[j] = commandStringBuffer[j+spaceCount-];
}
//最后修改长度,长度缩减量是空格数-1,因为保留了一个空格
commandStringLength -= (spaceCount-);
}
else
{
//下一个不是空格,说明是只有一个空格的环境,不用操心,进行下一次循环
continue;
}
} }
}
去除空格之后,可能这一次的命令是带参数的,那么我们去除第一个连续的字符串当成命令,所以这个特性就规定了命令本身(不包括参数)必须是连续的字符串,取出命令的函数如下
commandLength1 = Command_Find_Space_Postion();//获取长度
if(commandLength1 == )commandLength1 = commandStringLength;//当第二个空格获取返回0的时候,说明没有参数,纯命令,所以没有空格
获取命令中第一个空格的位置,那就是命令字符串的结尾,接下来需要和命令数组进行比对,命令数组如下
//命令列表,命令最长50字节
u8 commandStringList[][] = \
{
"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[][] ; 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 = ;
u8 i = ;
u32 fac = ;
result = CommandGetParamStr(paramBuffer,PARAM_COVERT_MAX_LENGTH,¶mLength,index);
if(result == )return ;//找不到这么多参数
//找到之后根据长度计算系数
//系数计算
for(i = ; i < paramLength;i++)
{
fac *= ;
}
//校验每个参数字符值是否符合标准,十进制就必须在0-9之间
for(i = ;i<paramLength;i++)
{
if(paramBuffer[i] > '' || paramBuffer[i] < '')
{
return ;//参数错误
}
}
//开始计算
for(i = ; i < paramLength;i++)
{
valueResult += (paramBuffer[i]-'')*fac;
fac/=;
}
//检测最终结果是否大于限制值,如八位那么结果不能大于255
if(valueResult > 0xff)return ;//参数错误
else
{
*value = (u8)valueResult;
return ;
}
} //与上一个参数类似,检测十六进制参数
u8 CommandGetParamToDecU16(u16* value,u8 index)
{
u8 result;
u32 valueResult = ;
u8 i = ;
u32 fac = ;
result = CommandGetParamStr(paramBuffer,PARAM_COVERT_MAX_LENGTH,¶mLength,index);
if(result == )return ;//找不到这么多参数
//找到之后根据长度计算系数
//系数计算
for(i = ; i < paramLength;i++)
{
fac *= ;
}
//校验每个参数字符值是否符合标准,十进制就必须在0-9之间
for(i = ;i<paramLength;i++)
{
if(paramBuffer[i] > '' || paramBuffer[i] < '')
{
return ;//参数错误
}
}
//开始计算
for(i = ; i < paramLength;i++)
{
valueResult += (paramBuffer[i]-'')*fac;
fac/=;
}
//检测最终结果是否大于限制值,如八位那么结果不能大于255
if(valueResult > 0xffff)return ;//参数错误
else
{
*value = (u16)valueResult;
return ;
}
} u8 CommandGetParamToDecU32(u32* value,u8 index)
{
u8 result;
u32 valueResult = ;
u8 i = ;
u32 fac = ;
result = CommandGetParamStr(paramBuffer,PARAM_COVERT_MAX_LENGTH,¶mLength,index);
if(result == )return ;//找不到这么多参数
//找到之后根据长度计算系数
//系数计算
for(i = ; i < paramLength;i++)
{
fac *= ;
}
//校验每个参数字符值是否符合标准,十进制就必须在0-9之间
for(i = ;i<paramLength;i++)
{
if(paramBuffer[i] > '' || paramBuffer[i] < '')
{
return ;//参数错误
}
}
//开始计算
for(i = ; i < paramLength;i++)
{
valueResult += (paramBuffer[i]-'')*fac;
fac/=;
}
//检测最终结果是否大于限制值,如八位那么结果不能大于255
if(valueResult > 0xffffffff)return ;//参数错误
else
{
*value = (u32)valueResult;
return ;
}
} //从命令字符串中获取参数并转换参数,将0x格式的字符串转换为数值 成功返回0 失败返回1
//参数类型必须是0x开头的
u8 CommandGetParamToHexU8(u8* value,u8 index)
{
u8 result;
u32 valueResult = ;
u8 i = ;
u32 fac = ;
result = CommandGetParamStr(paramBuffer,PARAM_COVERT_MAX_LENGTH,¶mLength,index);
if(result == )return ;//找不到这么多参数
//检测参数长度,因为开头必须为0x,所以长度必须为3以上
if(paramLength <= )return ;//失败
//计算系数
for(i = ; i < paramLength; i++)
{
fac *= ; //因为0x占用了两个字节 第一未为1 所以乘法运算从第三个开始
}
//检测开头是否正确 0x
if(paramBuffer[] == '' &&(paramBuffer[] == 'x'||paramBuffer[] == 'X'))
{
//检测每一位数据是否正确并计算最终值
for(i = ; i < paramLength; i++)
{
if(paramBuffer[i] >= '' && paramBuffer[i] <= '')
{
result = paramBuffer[i] -'';
}
else if(paramBuffer[i] >= 'a' && paramBuffer[i] <= 'f')
{
result = paramBuffer[i] -'a'+;
}
else if(paramBuffer[i] >= 'A' && paramBuffer[i] <= 'F')
{
result = paramBuffer[i] -'A'+;
}
else
{
//出现范围之外的数据,返回1
return ;
}
valueResult += (u32)(result*fac);
fac /= ;
}
//计算完成,检测参数是否超过范围
if(valueResult > 0xff)return ;//参数错误
else
{
*value = (u8)valueResult;
return ;
}
}
else
{
//参数开头不对
return ;
}
} //从命令字符串中获取参数并转换参数,将0x格式的字符串转换为数值 成功返回0 失败返回1
u8 CommandGetParamToHexU16(u16* value,u8 index)
{
u8 result;
u32 valueResult = ;
u8 i = ;
u32 fac = ;
result = CommandGetParamStr(paramBuffer,PARAM_COVERT_MAX_LENGTH,¶mLength,index);
if(result == )return ;//找不到这么多参数
//检测参数长度,因为开头必须为0x,所以长度必须为3以上
if(paramLength <= )return ;//失败
//计算系数
for(i = ; i < paramLength; i++)
{
fac *= ;//因为0x占用了两个字节 第一未为1 所以乘法运算从第三个开始
}
//检测开头是否正确 0x
if(paramBuffer[] == '' &&(paramBuffer[] == 'x'||paramBuffer[] == 'X'))
{
//检测每一位数据是否正确并计算最终值
for(i = ; i < paramLength; i++)
{
if(paramBuffer[i] >= '' && paramBuffer[i] <= '')
{
result = paramBuffer[i] -'';
}
else if(paramBuffer[i] >= 'a' && paramBuffer[i] <= 'f')
{
result = paramBuffer[i] -'a'+;
}
else if(paramBuffer[i] >= 'A' && paramBuffer[i] <= 'F')
{
result = paramBuffer[i] -'A'+;
}
else
{
//出现范围之外的数据,返回1
return ;
}
valueResult += (u32)(result*fac);
fac /= ;
}
//计算完成,检测参数是否超过范围
if(valueResult > 0xffff)return ;//参数错误
else
{
*value = (u16)valueResult;
return ;
}
}
else
{
//参数开头不对
return ;
}
} //从命令字符串中获取参数并转换参数,将0x格式的字符串转换为数值 成功返回0 失败返回1
u8 CommandGetParamToHexU32(u32* value,u8 index)
{
u8 result;
u32 valueResult = ;
u8 i = ;
u32 fac = ;
result = CommandGetParamStr(paramBuffer,PARAM_COVERT_MAX_LENGTH,¶mLength,index);
if(result == )return ;//找不到这么多参数
//检测参数长度,因为开头必须为0x,所以长度必须为3以上
if(paramLength <= )return ;//失败
//计算系数
for(i = ; i < paramLength; i++)
{
fac *= ;//因为0x占用了两个字节 第一未为1 所以乘法运算从第三个开始
}
//检测开头是否正确 0x
if(paramBuffer[] == '' &&(paramBuffer[] == 'x'||paramBuffer[] == 'X'))
{
//检测每一位数据是否正确并计算最终值
for(i = ; i < paramLength; i++)
{
if(paramBuffer[i] >= '' && paramBuffer[i] <= '')
{
result = paramBuffer[i] -'';
}
else if(paramBuffer[i] >= 'a' && paramBuffer[i] <= 'f')
{
result = paramBuffer[i] -'a'+;
}
else if(paramBuffer[i] >= 'A' && paramBuffer[i] <= 'F')
{
result = paramBuffer[i] -'A'+;
}
else
{
//出现范围之外的数据,返回1
return ;
}
valueResult += (u32)(result*fac);
fac /= ;
}
//计算完成,检测参数是否超过范围
if(valueResult > 0xffffffff)return ;//参数错误
else
{
*value = (u32)valueResult;
return ;
}
}
else
{
//参数开头不对
return ;
}
}
恩,到这里基本就实现这个shell了,现在增加命令以及增加命令响应函数是不是就简单多了,反正我是觉得简单多了
项目demo位置
http://download.csdn.net/detail/dengrengong/8542891
这个项目里面使用了shell和之前使用的动态内存,可以参考
单片机裸机下写一个自己的shell调试器的更多相关文章
- 单片机裸机下写一个自己的shell调试器(转)
源: 单片机裸机下写一个自己的shell调试器
- 如何写一个简单的shell
如何写一个简单的shell 看完<UNIX环境高级编程>后我就一直想写一个简单的shell来作为练习,因为有事断断续续的写了好几个月,如今写了差不多来总结一下. 源代码放在了Github: ...
- a,b,c为3个整型变量,在不引入第四个变量的前提下写一个算法实现 a=b b=c c=a?(异或解决值互换问题)
package com.Summer_0424.cn; /** * @author Summer * a,b,c为3个整型变量,在不引入第四个变量的前提下写一个算法实现 a=b b=c c=a? */ ...
- a,b为2个整型变量,在不引入第三个变量的前提下写一个算法实现 a与b的值互换
package com.Summer_0424.cn; /** * @author Summer * a,b为2个整型变量,在不引入第三个变量的前提下写一个算法实现 a与b的值互换? */ publi ...
- Ubuntu16.04下写的Qt程序,调试时没问题,运行时偶现崩溃 (需要在运行时生成core dump文件,QMAKE_CC += -g)
记录一下 Ubuntu16.04下写的Qt程序,调试时没问题,运行时偶现崩溃 需要在运行时生成core dump文件 首先在pro结尾里加入 QMAKE_CC += -g QMAKE_CXX += - ...
- 使用CLRMD编写一个自己的C#调试器
博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:使用CLRMD编写一个自己的C#调试器.
- 在Linux下写一个简单的驱动程序
本文首先描述了一个可以实际测试运行的驱动实例,然后由此去讨论Linux下驱动模板的要素,以及Linux上应用程序到驱动的执行过程.相信这样由浅入深.由具体实例到抽象理论的描述更容易初学者入手Linux ...
- 【转】在Linux下写一个简单的驱动程序
转自:https://www.cnblogs.com/amanlikethis/p/4914510.html 本文首先描述了一个可以实际测试运行的驱动实例,然后由此去讨论Linux下驱动模板的要素,以 ...
- UNIX-LINUX编程实践教程->第八章->实例代码注解->写一个简单的shell
一 分析 要实现一个shell,需包含3个步骤 1)读入指令 2)指令解析 3)执行指令 1 从键盘读入指令 从键盘读入指令的几个要点: 1)调用getc函数等待并获取用户键盘输入. 2)每一行命令的 ...
随机推荐
- 静默方式安装window oracle
1. setup.exe -silent -responseFile "C:\app\software\WIN64_11204\RSP\db_install_window.rsp" ...
- JAVA-面向对象2--继承
1. 继承的好处: 1.提高代码复用性. 2.让类与类之间产生关系,为面向对象的第三大特征 多态 提供了前提 2.java中支持单继承,不直接支持多继承,但对c++中多继承进行了改良.java通过多实 ...
- Android &Swift iOS开发:语言与框架对比
转载自:http://www.infoq.com/cn/articles/from-android-to-swift-ios?utm_campaign=rightbar_v2&utm_sour ...
- HTML5新特性总览
html5的革新带来了更多的功能,简单的一个标签遍可以做到很多事情,例如 (1)canvas画图,vedio视屏,geolocation等等新标签. 如何检查浏览器是否支持这些新特性? 这样就足够,改 ...
- Mysql limit性能优化(小offset与大offset)
MySQL的优化是非常重要的.其他最常用也最需要优化的就是limit.MySQL的limit给分页带来了极大的方便,但数据量一大的时候,limit的性能就急剧下降. 同样是取10条数据 selec ...
- Git学习 -- 删除文件
1 从版本库删除文件 git rm <file> git commit -m "xxx" 2 工作区中文件被误删,但版本库中没有删除,可以恢复到工作区 git chec ...
- IDL 遍历 XML文档示例
IDL解析XML文档同样也有2种方法:DOM和SAX方式:两种方法在IDL自带的帮助里面有详细介绍,可以去查看. IDL 源码PRO sample_recurse, oNode, indent COM ...
- Mybatis学习(5)高级映射
需求: 一.一对一查询 查询订单信息,关联查询创建订单的用户信息: orders--->user:一个订单只由一个用户创建,一对一 orders表 和 user表: 1)使用resultType ...
- PAT乙1002
有毒,真的有毒,难题对于简单题影响太大了,想的东西太多,总会在考虑,会不会时间超限,数据量有多大,三个循环就太慢了.... 总之我是真的不愿意看到这样的程序能过的,总觉得有更好的,却总是找不更好的额. ...
- springframwork历史版本下载地址
http://sourceforge.net/projects/springframework/files/springframework-2/