SF.*_*SF. 7 embedded assembly 8051
如果我想在8051汇编中创建一个有限状态机,我需要一个有效的C switch()表达式.
[对于这个问题,让我们忽视掉落行为,保留和放弃都是可以接受的].
在8051程序集中有几种方法可以实现这一点,但每种方法都有其缺点.用于5-10个案例的短开关的那些很简单,并且足够清晰,但如果我想要一个> 128,甚至> 256个案例的开关,事情会变得复杂.
第一个是简单的,一个CJNE比较操作数和值的链,如果不相等则跳转到下一个案例; 相当于if(){...}else if(){....} else if(){...}.优点是简单,缺点很明显:如果是长开关,这将产生很长的选择.这可以通过构建二叉树来减少,使用JB控制变量的连续位.这仍然不是很有效,很难维护并且难以实现稀疏键设置(案例1:......;案例5:......;案例23:......;案例118:......)
接下来的方法是乘以,然后跳跃偏移.将控制变量乘以2,将结果加载到DPTR中,使用偏移量加载累加器,然后执行JMP @A+DPTR预加载多个的区域AJMP.(或乘以3并跳入预先加载了多个区域LJMP.).
我做过一次.调整跳转指令到字节的位置是一个我真的不想重复的难题,加上跳转表不必要地大(重复ajmp每个其他字节).也许我不知道一些让它变得容易的技巧......
还有从专用表中提取地址,预加载堆栈并执行的方法RET.听起来非常整洁,除了从专用表中拉出一个地址需要用可怕的MOVC A, @A+DPTR或者 MOV A, @A+PC- 这些寻址模式让我畏缩,我从未尝试过实施它.如果您知道这样做的简洁方法,请将其作为答案发布.
一般来说,我想知道是否有更优雅,更有效的方式来执行switch()样式跳转 - 一个不会产生太多开销,不会浪费太多内存并且可以自由跳跃至少AJMP距离,数量为case进入数百人.
我的第二个答案是你可能不喜欢的另一个答案.
不要在汇编程序中写这种东西!你有什么希望实现的?老年?
状态机(可能用于词法分析?)正是生成代码的那种东西.生成的代码实际上没有错误,并且更容易维护.有很好的免费工具.代码生成器的输出通常很好,方便C.您将C提供给编译器.猜猜看,编译器知道你问题的答案.一个好的编译器将知道如何制作最有效的switch语句,并且它将继续使用它,而不会浪费你几周的生命调试它.
另一件好事是,有一天,当你决定8051不够强大时,你可以轻松地转向更强大的架构,而无需从头开始重新编写和调试整个状态机!此外,你将不会被困在8051s的余生中.
添加:
由于这不是词法分析,我建议您使用状态机编译器,如Ragel.您只需向其提供状态机的描述,它就会生成您的状态机C代码.
或者,尝试逻辑网格方法.
我通常不喜欢这种类型的答案,但我觉得它在这里是合适的。
不要这样做!非常大的 switch 语句是一种代码味道。它表明您的代码中的某个地方发生了一些糟糕的规划,或者随着项目范围的扩大,一些原本良好的设计已经失去了控制。
当您可以选择几个真正不同的选项时,您应该使用 switch 语句。像这样:
void HandleCommand(unsigned char commadndByte)
{
switch (commandByte)
{
case COMMAND_RESET:
Reset();
break;
case COMMAND_SET_PARAMETERS:
SetPID(commandValue[0], commandValue[1], commandValue[2]);
ResetController();
SendReply(PID_PARAMETERS_SET);
break;
default:
SendReply(COMMAND_HAD_ERROR);
break;
}
Run Code Online (Sandbox Code Playgroud)
您的 switch 语句是否真的将程序流转移到数百个真正不同的选项?还是更像这样?
void HandleCharacter(unsigned char c)
{
switch (c)
{
case 'a': c='A'; break;
case 'b': c='B'; break;
case 'c': c='C'; break;
case 'd': c='D'; break;
...
case 'w': c='W'; break;
case 'x': c='X'; break;
case 'y': c='Y'; break;
case 'z': c='Z'; break;
}
}
Run Code Online (Sandbox Code Playgroud)
无论你在做什么,使用某种数组可能会做得更好。为了节省用汇编程序编写答案的时间,我将用 C 语言编写它,但概念是相同的。
如果切换的每种情况都涉及调用不同的函数,则:
const void (*p[256]) (int x, int y) = {myFunction0, myFunction1, ... myFunction255};
void HandleCharacter(unsigned char c)
{
(*p[c])();
}
Run Code Online (Sandbox Code Playgroud)
您可能会说函数指针数组占用了大量内存。如果它是const,那么它应该只占用FLASH,而不占用RAM,并且应该比等效的switch语句占用更少的FLASH;
或者,类似的事情可能更相关。如果开关的每种情况都涉及分配不同的值,则:
char validResponses[256] = {INVALID, OK, OK, OK, PENDING, OK, INVALID, .... };
void HandleCharacter(unsigned char c)
{
sendResponse(validResponses[c]);
}
Run Code Online (Sandbox Code Playgroud)
总之,尝试在 switch 语句中找到一种模式;什么变化了,什么保持不变?将变化的内容放入数组中,将保持不变的内容放入代码中。