软件设计
keilkill.bat,是一个批处理文件,双击,可以删除 MDK 编译过程中产生的中间文件, 从而大大减少整个工程所占用的空间,节省硬盘空间,方便传输。
由于 USMART 默认提供了 STM32 的 TIM4 中断初始化设置代码,我们只需要在 usmart.h 里面设置 USMART_ENTIMX_SCAN 为 1,即可完成 TIM4 的设置,通过 TIM4 的中断服务函 数,调用 usmart_dev.scan()(就是 usmart_scan 函数),实现 usmart 的扫描。此部分代码我们就 不列出来了,请参考 usmart.c。
此时,我们就可以使用 USMART 了,不过在主程序里面还得执行 usmart 的初始化,另外 还需要针对你自己想要被 USMART 调用的函数在 usmart_config.c 里面进行添加。下面先介绍 如何添加自己想要被 USMART 调用的函数,打开 usmart_config.c
这里的添加函数很简单,只要把函数所在头文件添加进来,并把函数名按上图所示的方式增加即可,默认我们添加了两个函数:delay_ms 和 delay_us。另外,read_addr 和 write_addr 属 于 usmart 自带的函数,用于读写指定地址的数据,通过配置 USMART_USE_WRFUNS,可以 使能或者禁止这两个函数
#include "usmart.h"
#include "usmart_str.h"
用户配置区///
//这下面要包含所用到的函数所申明的头文件(用户自己添加)
#include "delay.h"
#include "sys.h"
#include "lcd.h"
extern void led_set(u8 sta);
extern void test_fun(void(*ledset)(u8),u8 sta);
//函数名列表初始化(用户自己添加)
//用户直接在这里输入要执行的函数名及其查找串
struct _m_usmart_nametab usmart_nametab[]=
{
#if USMART_USE_WRFUNS==1 //如果使能了读写操作
(void*)read_addr,"u32 read_addr(u32 addr)",
(void*)write_addr,"void write_addr(u32 addr,u32 val)",
#endif
(void*)delay_ms,"void delay_ms(u16 nms)",
(void*)delay_us,"void delay_us(u32 nus)",
(void*)LCD_Clear,"void LCD_Clear(u16 Color)",
(void*)LCD_Fill,"void LCD_Fill(u16 xsta,u16 ysta,u16 xend,u16 yend,u16 color)",
(void*)LCD_DrawLine,"void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2)",
(void*)LCD_DrawRectangle,"void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2)",
(void*)LCD_Draw_Circle,"void Draw_Circle(u16 x0,u16 y0,u8 r)",
(void*)LCD_ShowNum,"void LCD_ShowNum(u16 x,u16 y,u32 num,u8 len,u8 size)",
(void*)LCD_ShowString,"void LCD_ShowString(u16 x,u16 y,u16 width,u16 height,u8 size,u8 *p)",
(void*)led_set,"void led_set(u8 sta)",
(void*)test_fun,"void test_fun(void(*ledset)(u8),u8 sta)",
(void*)LCD_ReadPoint,"u16 LCD_ReadPoint(u16 x,u16 y)",
};
///END///
/
//函数控制管理器初始化
//得到各个受控函数的名字
//得到函数总数量
struct _m_usmart_dev usmart_dev=
{
usmart_nametab,
usmart_init,
usmart_cmd_rec,
usmart_exe,
usmart_scan,
sizeof(usmart_nametab)/sizeof(struct _m_usmart_nametab),//函数数量
0, //参数数量
0, //函数ID
1, //参数显示类型,0,10进制;1,16进制
0, //参数类型.bitx:,0,数字;1,字符串
0, //每个参数的长度暂存表,需要MAX_PARM个0初始化
0, //函数的参数,需要PARM_LEN个0初始化
};
#include "usmart_str.h"
#include "usmart.h"
//对比字符串str1和str2
//*str1:字符串1指针
//*str2:字符串2指针
//返回值:0,相等;1,不相等;
u8 usmart_strcmp(u8 *str1,u8 *str2)
{
while(1)
{
if(*str1!=*str2)return 1;//不相等
if(*str1=='\0')break;//对比完成了.
str1++;
str2++;
}
return 0;//两个字符串相等
}
//把str1的内容copy到str2
//*str1:字符串1指针
//*str2:字符串2指针
void usmart_strcopy(u8*str1,u8 *str2)
{
while(1)
{
*str2=*str1; //拷贝
if(*str1=='\0')break;//拷贝完成了.
str1++;
str2++;
}
}
//得到字符串的长度(字节)
//*str:字符串指针
//返回值:字符串的长度
u8 usmart_strlen(u8*str)
{
u8 len=0;
while(1)
{
if(*str=='\0')break;//拷贝完成了.
len++;
str++;
}
return len;
}
//m^n函数
//返回值:m^n次方
u32 usmart_pow(u8 m,u8 n)
{
u32 result=1;
while(n--)result*=m;
return result;
}
u8 usmart_str2num(u8*str,u32 *res)
{
u32 t;
u8 bnum=0; //数字的位数
u8 *p;
u8 hexdec=10;//默认为十进制数据
p=str;
*res=0;//清零.
while(1)
{
if((*p<='9'&&*p>='0')||(*p<='F'&&*p>='A')||(*p=='X'&&bnum==1))//参数合法
{
if(*p>='A')hexdec=16; //字符串中存在字母,为16进制格式.
bnum++; //位数增加.
}else if(*p=='\0')break; //碰到结束符,退出.
else return 1; //不全是十进制或者16进制数据.
p++;
}
p=str; //重新定位到字符串开始的地址.
if(hexdec==16) //16进制数据
{
if(bnum<3)return 2; //位数小于3,直接退出.因为0X就占了2个,如果0X后面不跟数据,则该数据非法.
if(*p=='0' && (*(p+1)=='X'))//必须以'0X'开头.
{
p+=2; //偏移到数据起始地址.
bnum-=2;//减去偏移量
}else return 3;//起始头的格式不对
}else if(bnum==0)return 4;//位数为0,直接退出.
while(1)
{
if(bnum)bnum--;
if(*p<='9'&&*p>='0')t=*p-'0'; //得到数字的值
else t=*p-'A'+10; //得到A~F对应的值
*res+=t*usmart_pow(hexdec,bnum);
p++;
if(*p=='\0')break;//数据都查完了.
}
return 0;//成功转换
}
u8 usmart_get_cmdname(u8*str,u8*cmdname,u8 *nlen,u8 maxlen)
{
*nlen=0;
while(*str!=' '&&*str!='\0') //找到空格或者结束符则认为结束了
{
*cmdname=*str;
str++;
cmdname++;
(*nlen)++;//统计命令长度
if(*nlen>=maxlen)return 1;//错误的指令
}
*cmdname='\0';//加入结束符
return 0;//正常返回
}
//获取下一个字符(当中间有很多空格的时候,此函数直接忽略空格,找到空格之后的第一个字符)
//str:字符串指针
//返回值:下一个字符
u8 usmart_search_nextc(u8* str)
{
str++;
while(*str==' '&&str!='\0')str++;
return *str;
}
//从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;
//判断函数是否有返回值
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;
}
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++; //防止重复增加
parmnum++; //参数增加一个
}
}
strtemp++;
}
if(parmnum==1)//只有1个参数.
{
fpname[fplcnt]='\0';//加入结束符
if(usmart_strcmp(fpname,"void")==0)parmnum=0;//参数为void,表示没有参数.
}
*pnum=parmnum; //记录参数个数
*fname='\0'; //加入结束符
return res; //返回执行结果
}
//从str中得到一个函数的参数
//*str:源字符串指针
//*fparm:参数字符串指针
//*ptype:参数类型 0,数字;1,字符串;0XFF,参数错误
//返回值:0,已经无参数了;其他,下一个参数的偏移量.
u8 usmart_get_aparm(u8 *str,u8 *fparm,u8 *ptype)
{
u8 i=0;
u8 enout=0;
u8 type=0;//默认是数字
u8 string=0; //标记str是否正在读
while(1)
{
if(*str==','&& string==0)enout=1; //暂缓立即退出,目的是寻找下一个参数的起始地址
if((*str==')'||*str=='\0')&&string==0)break;//立即退出标识符
if(type==0)//默认是数字的
{
if((*str>='0' && *str<='9')||(*str>='a' && *str<='f')||(*str>='A' && *str<='F')||*str=='X'||*str=='x')//数字串检测
{
if(enout)break; //找到了下一个参数,直接退出.
if(*str>='a')*fparm=*str-0X20; //小写转换为大写
else *fparm=*str; //小写或者数字保持不变
fparm++;
}else if(*str=='"')//找到字符串的开始标志
{
if(enout)break;//找到,后才找到",认为结束了.
type=1;
string=1;//登记STRING 正在读了
}else if(*str!=' '&&*str!=',')//发现非法字符,参数错误
{
type=0XFF;
break;
}
}else//string类
{
if(*str=='"')string=0;
if(enout)break; //找到了下一个参数,直接退出.
if(string) //字符串正在读
{
if(*str=='\\') //遇到转义符(不复制转义符)
{
str++; //偏移到转义符后面的字符,不管什么字符,直接COPY
i++;
}
*fparm=*str; //小写或者数字保持不变
fparm++;
}
}
i++;//偏移量增加
str++;
}
*fparm='\0'; //加入结束符
*ptype=type; //返回参数类型
return i; //返回参数长度
}
//得到指定参数的起始地址
//num:第num个参数,范围0~9.
//返回值:该参数的起始地址
u8 usmart_get_parmpos(u8 num)
{
u8 temp=0;
u8 i;
for(i=0;i<num;i++)temp+=usmart_dev.plentbl[i];
return temp;
}
//从str中得到函数参数
//str:源字符串;
//parn:参数的多少.0表示无参数 void类型
//返回值:0,成功;其他,错误代码.
u8 usmart_get_fparam(u8*str,u8 *parn)
{
u8 i,type;
u32 res;
u8 n=0;
u8 len;
u8 tstr[PARM_LEN+1];//字节长度的缓存,最多可以存放PARM_LEN个字符的字符串
for(i=0;i<MAX_PARM;i++)usmart_dev.plentbl[i]=0;//清空参数长度表
while(*str!='(')//偏移到参数开始的地方
{
str++;
if(*str=='\0')return USMART_FUNCERR;//遇到结束符了
}
str++;//偏移到"("之后的第一个字节
while(1)
{
i=usmart_get_aparm(str,tstr,&type); //得到第一个参数
str+=i; //偏移
switch(type)
{
case 0: //数字
if(tstr[0]!='\0') //接收到的参数有效
{
i=usmart_str2num(tstr,&res); //记录该参数
if(i)return USMART_PARMERR; //参数错误.
*(u32*)(usmart_dev.parm+usmart_get_parmpos(n))=res;//记录转换成功的结果.
usmart_dev.parmtype&=~(1<<n); //标记数字
usmart_dev.plentbl[n]=4; //该参数的长度为4
n++; //参数增加
if(n>MAX_PARM)return USMART_PARMOVER;//参数太多
}
break;
case 1://字符串
len=usmart_strlen(tstr)+1; //包含了结束符'\0'
usmart_strcopy(tstr,&usmart_dev.parm[usmart_get_parmpos(n)]);//拷贝tstr数据到usmart_dev.parm[n]
usmart_dev.parmtype|=1<<n; //标记字符串
usmart_dev.plentbl[n]=len; //该参数的长度为len
n++;
if(n>MAX_PARM)return USMART_PARMOVER;//参数太多
break;
case 0XFF://错误
return USMART_PARMERR;//参数错误
}
if(*str==')'||*str=='\0')break;//查到结束标志了.
}
*parn=n; //记录参数的个数
return USMART_OK;//正确得到了参数
}
main.c
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "usmart.h"
//LED状态设置函数
void led_set(u8 sta)
{
LED1=sta;
}
//函数参数调用测试函数
void test_fun(void(*ledset)(u8),u8 sta)
{
ledset(sta);
}
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
LCD_Init();
usmart_dev.init(SystemCoreClock/1000000); //初始化USMART
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"WarShip STM32 ^_^");
LCD_ShowString(30,70,200,16,16,"USMART TEST");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2015/1/14");
while(1)
{
LED0=!LED0;
delay_ms(500);
}
}
list,该命令用于打印所有 usmart 可调用函数。发送该命令后,串口将受到所有能被 usmart 调用得到函数。
id,该指令用于获取各个函数的入口地址。比如前面写的 test_fun 函数,就有一个函数参数, 我们需要先通过 id 指令,获取 ledset 函数的 id(即入口地址),然后将这个 id 作为函数参数, 传递给 test_fun。
help(或者‘ ?’也可以),发送该指令后,串口将打印 usmart 使用的帮助信息。
hex 和 dec,这两个指令可以带参数,也可以不带参数。当不带参数的时候,hex 和 dec 分 别用于设置串口显示数据格式为 16 进制/10 进制。当带参数的时候,hex 和 dec 就执行进制转 换,比如输入:hex 1234,串口将打印:HEX:0X4D2,也就是将 1234 转换为 16 进制打印出来。 又比如输入:dec 0X1234,串口将打印:DEC:4660,就是将 0X1234 转换为 10 进制打印出来。
runtime 指令,用于函数执行时间统计功能的开启和关闭,发送:runtime 1,可以开启函数 执行时间统计功能;发送:runtime 0,可以关闭函数执行时间统计功能。函数执行时间统计功 能,默认是关闭的。
我们要调用带有函数参数的函数,就必须先得到函数参 数的入口地址(id),通过输入 id 指令,我们可以得到 led_set 的函数入口地址是:0X0800022D, 所以,我们在串口输入:test_fun(0X0800022D,0),就可以控制 DS1 亮了。