一、前言
在之前的文章中介绍了板级调试小助手的结构、DDS外设以及如何使用PYNQ驱动OLED显示视频,在小助手结构的文章中提到,小助手具有自定义脚本功能,这个功能是使用C语言编写的,本质上来说就是一个字典树通过读取脚本的关键词进行识别。在这篇文章中就给大家介绍一下一个简易的自定义脚本解析器吧。
本项目代码完全开源,需要查看源码请移步《板级调试小助手(1)系统结构和原理》文章最后
二、脚本函数定义
在编写之前根据小助手项目自定义四个脚本如下
DO_OUT(A); //数字量输出函数 A 是一个8bit十进制数,它的每一个bit代表一个DO,例如bit0代表DO0
AO_OUT(A,B); //它是模拟量输出函数,A表示通道数,B表示值范围为0~4096
Delay(A);//它是延迟函数,A表示延迟时间,单位是毫秒
SIN_OUT(A,B,C);//它是正弦波输出函数,A表示通道数,B表示频率值,C表示相位值
三、脚本解析流程和代码结构
解析脚本的流程大致为:读取文件->读取一行代码->放入字典树识别关键词->通过关键词读取参数->将识别到的代码解析并放入链表(或二叉树)供其他应用调用。关于字典树和链表的知识这里就不赘述了。解析函数如下
//脚本相关的全局变量
SubLink SubLinkHand; //命令链表表头
mytrie TrieHand; //字典树头
unsigned char g_cTrieDeep; //记录字典树的最大深度
//定义脚本命令
char g_cCommand[10][10] = {"DO_OUT\0","AO_OUT\0","SIN_OUT\0","Delay\0"};
/*
功能:脚本解析初始化
初始化命令链表
*/
void init_SubScript(void)
{
if(SubLinkHand == NULL)
{
SubLinkHand = (SubLink)malloc(sizeof(struct LNode)); //给脚本头申请内存
SubLinkHand->cCommand = 0;
SubLinkHand->Data = 0;
SubLinkHand->Data1 = 0;
SubLinkHand->Data2 = 0;
SubLinkHand->Next = NULL;
}
}
/*
功能,读取脚本文件并解析脚本文件
将解析后的结果放入链表
*/
char Sub_main()
{
FILE *file;
static char cData[50]; //用于存放一行数据
unsigned char cCommand; //用于暂存命令
unsigned int iCmd[3] = {0}; //用于暂存命令数据
DelLink(SubLinkHand); //清空整个脚本链表
file = fopen("/home/xilinx/jupyter_notebooks/Overlay_Assistant/scriptApp/script.s","r"); //读取文件
if (file == NULL) //如果打开失败
{
printf("文件打开失败\r\n");
return -1;
}
while(feof(file)==0) //读取整个脚本文件
{
memset(cData, 0, 50); //清空数组
fgets(cData, sizeof(cData), file); //读取一行数据
cCommand = InquiryCommand(cData, iCmd); //检测命令
if (cCommand != 0xff)
{
WriteLink(SubLinkHand, cCommand, iCmd); //写入链表
}
}
fclose(file); //关闭文件
return 0;
}
解析完毕之后就可以通过读取链表的内容按照顺序执行脚本代码,执行代码如下
/*
功能:循环读执行取链表并下脚本(通过TCP通讯)
*/
void ReadScrip(SubLink Hand)
{
FILE *file;
SubLink q; //定义一个可在链表中移动的指针
char TcpTxBuff[100]; //发送缓存
uint32_t ui32Delay; //延迟时间us
while(1)
{
//脚本文件是否还存在
file = fopen("/home/xilinx/jupyter_notebooks/Overlay_Assistant/scriptApp/script.s","r"); //读取文件
if(file == NULL)
{
break;
}
fclose(file); // 关闭文件
//执行一遍脚本
for(q=Hand;q->Next != NULL;q = q->Next)
{
switch(q->Next->cCommand) //选择指令
{
case COMMAND_DO: //DO指令
memset(TcpTxBuff,0x00,100); //清除缓存
sprintf(TcpTxBuff,"{\"mode\":\"set\",\"DO\":\"%d\"}",q->Next->Data); //转义名称
write(g_iTcpSock, TcpTxBuff, strlen(TcpTxBuff)); //通过网络发送
break;
case COMMAND_AO: //AO指令strlen
memset(TcpTxBuff,0x00,100); //清除缓存
sprintf(TcpTxBuff,"{\"mode\":\"set\",\"AO%d mode\":\"1\",\"AO%d phase\":\"%d\"}",
q->Next->Data,q->Next->Data,q->Next->Data1);
remove_spaces(TcpTxBuff); //删除字符串中的空格
write(g_iTcpSock, TcpTxBuff, strlen(TcpTxBuff)); //通过网络发送
break;
case COMMAND_DDS: //DDS指令
memset(TcpTxBuff,0x00,100); //清除缓存
sprintf(TcpTxBuff,"{\"mode\":\"set\",\"AO%d mode\":\"2\",\"AO%d fre\":\"%d\",\"AO%d phase\":\"%d\"}",
q->Next->Data,q->Next->Data,q->Next->Data1,q->Next->Data,q->Next->Data2);
remove_spaces(TcpTxBuff); //删除字符串中的空格
write(g_iTcpSock, TcpTxBuff, strlen(TcpTxBuff)); //通过网络发送
break;
case COMMAND_Delay: //清屏指令
memset(TcpTxBuff,0x00,100); //清除缓存
ui32Delay = (uint32_t)q->Next->Data * 1000;
usleep(ui32Delay);
break;
}
usleep(100);
}
}
}
该代码是通过读取链表的数据,将指令通过TCP发送给另一个应用(小助手主要逻辑应用)。
四、总结
综上介绍了如何使用字典树的方式简单的搭建了一个识别自定义脚本程序,本人学识有限若有疏漏欢迎指正。