功能简介:
有点类似shell指令一样,Linux下的shell是通过输入指令,然后shell脚本去按path路径去寻找相应指令,运行最先找到的那个指令文件。这里实现的机制则是通过串口输入指令,单片串口接受并分析指令,然后匹配对应的指令然后运行并反馈。到这里,这个程序倒是开拓了我的思路,以前没想过使用串口来控制单片机的,最多就是收发一些字符串。由于原子哥的视频长度有限,也可能是他们不想在语法上花费过多时间,所以讲解的就比较的简略,下面我将根据其运行的过程以及一些语法上做出一些解释。
流程分析
1.定时器中断的扫描函数
void TIM4_IRQHandler(void)
{
if(TIM_GetITStatus(TIM4,TIM_IT_Update)==SET)//溢出中断
{
usmart_dev.scan(); //执行usmart扫描
TIM_SetCounter(TIM4,0); //清空定时器的CNT
TIM_SetAutoreload(TIM4,100);//恢复原来的设置
}
TIM_ClearITPendingBit(TIM4,TIM_IT_Update); //清除中断标志位
}
原子哥在视频中说过这个的实现过程是通过定时器中断执行这个usmart的扫描函数,下面再看看扫描函数中的具体逻辑,如果在一个工程里面难以寻找到一个中断函数,可以在启动文件中寻找。
2.扫描函数
void usmart_scan(void)
{
u8 sta,len;
if(USART_RX_STA&0x8000)//串口接收完成?
{
len=USART_RX_STA&0x3fff; //得到此次接收到的数据长度
USART_RX_BUF[len]='\0'; //在末尾加入结束符.
sta=usmart_dev.cmd_rec(USART_RX_BUF);//得到函数各个信息
if(sta==0)usmart_dev.exe(); //执行函数
else
{
len=usmart_sys_cmd_exe(USART_RX_BUF);
if(len!=USMART_FUNCERR)sta=len;
if(sta)
{
switch(sta)
{
case USMART_FUNCERR:
printf("函数错误!\r\n");
break;
case USMART_PARMERR:
printf("参数错误!\r\n");
break;
case USMART_PARMOVER:
printf("参数太多!\r\n");
break;
case USMART_NOFUNCFIND:
printf("未找到匹配的函数!\r\n");
break;
}
}
}
USART_RX_STA=0;//状态寄存器清空
}
}
USART_RX_STA是设定一个标志字节,bit7,6是判断是否接受完成并且接受数据是否正确,bit0~5则是接受的字符串长度,可以看出扫描函数的逻辑很简单就是接受数据然后判断数据,然后执行,接下来就是如何对命令进行处理以及运行命令
2.1 usmart处理串口数据
u8 usmart_cmd_rec(u8*str)
{
u8 sta,i,rval;//状态
u8 rpnum,spnum;
u8 rfname[MAX_FNAME_LEN];//暂存空间,用于存放接收到的函数名 ,
//#define MAX_FNAME_LEN 30
u8 sfname[MAX_FNAME_LEN];//存放本地函数名
sta=usmart_get_fname(str,rfname,&rpnum,&rval);//得到接收到的数据的函数名及参数个数
if(sta)return sta;//错误
for(i=0;i<usmart_dev.fnum;i++)
{
sta=usmart_get_fname((u8*)usmart_dev.funs[i].name,sfname,&spnum,&rval);//得到本地函数名及参数个数
if(sta)return sta;//本地解析有误
if(usmart_strcmp(sfname,rfname)==0)//相等
{
if(spnum>rpnum)return USMART_PARMERR;//参数错误(输入参数比源函数参数少)
usmart_dev.id=i;//记录函数ID.
break;//跳出.
}
}
if(i==usmart_dev.fnum)return USMART_NOFUNCFIND; //未找到匹配的函数
sta=usmart_get_fparam(str,&i); //得到函数参数个数
if(sta)return sta; //返回错误
usmart_dev.pnum=i; //参数个数记录
return USMART_OK;
}
这里有函数usmart_get_fname(str,rfname,&rpnum,&rval);//得到接收到的数据的函数名及参数个数,其原形是
u8 usmart_get_fname(u8str,u8fname,u8 *pnum,u8 *rval)
这里可以是对指针的一种经典用法,传入两个字符串的地址,然后进行操作,常见一些字符串操作函数,比如大家常用strpcy()之类的,传入参数,修改参数的用法还有更多更复杂的用法,但是其核心原理就是通过指针去修改指针所指向的内存区域,指针指向谁就修改谁,n级指针修改n-1级指针的内存区域,所以遇到指针多画内存的四区模型以后可能会更新,这里挖一个小坑。下面我们先介绍一下这个函数的具体逻辑,
//从str中得到函数名 串口接受到的数据,也就是指令名
//*str:源字符串指针
//*fname:获取到的函数名字指针
//*pnum:函数的参数个数
//*rval:是否需要显示返回值(0,不需要;1,需要)
//返回值:0,成功;其他,错误代码.
u8 usmart_get_fname(u8*str,u8*fname,u8 *pnum,u8 *rval)
{
u8 res;
u8 fover=0; //括号深度
u8 *strtemp;
u8 offset=0;
u8 parmnum=0;
u8 temp=1;
u8 fpname[6];//void+X+'/0'
u8 fplcnt=0; //第一个参数的长度计数器
u8 pcnt=0; //参数计数器
u8 nchar;
//if(((pcnt&0x7f)==4)&&(*strtemp!='*'))break;//最后一个字符,必须是*
//个人感觉这句话应该使用 ||
//if(((pcnt&0x7f)==4)||(*strtemp ='*'))break;
//这样当读完五个字符或者最后一个读是‘*’ 表示指针才算是把参数类型读完整了
/*****************************/
//判断函数是否有返回值
strtemp=str;
while(*strtemp!='\0')//没有结束
{
if(*strtemp!=' '&&(pcnt&0X7F)<5)//最多记录5个字符
{
if(pcnt==0)pcnt|=0X80;//置位最高位,标记开始接收返回值类型
if(((pcnt&0x7f)==4)&&(*strtemp!='*'))break;//最后一个字符,必须是*
fpname[pcnt&0x7f]=*strtemp;//记录函数的返回值类型
pcnt++;
}else if(pcnt==0X85)break;
strtemp++;
}
if(pcnt)//接收完了
{
fpname[pcnt&0x7f]='\0';//加入结束符
if(usmart_strcmp(fpname,"void")==0)*rval=0;//不需要返回值
else *rval=1; //需要返回值
pcnt=0;
}
/*****************************/
//这一块就很简单了,就是跳过空格,不要被循环条件所迷惑了,主要就是跳过所有空格和'*' 主要看offset的值,他会是函数名的前一个位置
res=0;
strtemp=str;
while(*strtemp!='('&&*strtemp!='\0') //此代码找到函数名的真正起始位置
{
strtemp++;
res++;
if(*strtemp==' '||*strtemp=='*')
{
nchar=usmart_search_nextc(strtemp); //获取下一个字符
if(nchar!='('&&nchar!='*')offset=res; //跳过空格和*号
}
}
/*****************************/
strtemp=str;
if(offset)strtemp+=offset+1;//跳到函数名开始的地方
res=0;
nchar=0;//是否正在字符串里面的标志,0,不在字符串;1,在字符串;
while(1)
{
if(*strtemp==0)
{
res=USMART_FUNCERR;//函数错误
break;
}
else if(*strtemp=='('&&nchar==0)fover++;//括号深度增加一级
else if(*strtemp==')'&&nchar==0)
{
if(fover)fover--;
else res=USMART_FUNCERR;//错误结束,没收到'('
if(fover==0)break;//到末尾了,退出
}else if(*strtemp=='"')nchar=!nchar;
//根据括号判断函数名是否接受完,一个字符一个字符的传递
if(fover==0)//函数名还没接收完
{
if(*strtemp!=' ')//空格不属于函数名
{
*fname=*strtemp;//得到函数名
fname++;
}
}else //已经接受完了函数名了.
{
if(*strtemp==',')
{
temp=1; //使能增加一个参数
pcnt++;
}else if(*strtemp!=' '&&*strtemp!='(')
{
if(pcnt==0&&fplcnt<5) //当第一个参数来时,为了避免统计void类型的参数,必须做判断.
{
fpname[fplcnt]=*strtemp;//记录参数特征.
fplcnt++;
}
temp++; //得到有效参数(非空格)
}
if(fover==1&&temp==2)//temp
{
temp++; //防止重复增加
parmnum++; //参数增加一个
}
}
strtemp++;
}
if(parmnum==1)//只有1个参数.
{
fpname[fplcnt]='\0';//加入结束符
if(usmart_strcmp(fpname,"void")==0)parmnum=0;//参数为void,表示没有参数.
}
*pnum=parmnum; //记录参数个数
*fname='\0'; //加入结束符
return res; //返回执行结果
}
重点先记住几个参数的意义,