如何在8051汇编中编写高效的开关()?

时间:2014-05-24 11:48:55

标签: assembly embedded 8051

如果我想在8051程序集中创建一个有限状态机,我需要一个有效的C switch()表达式。

[对于这个问题,让我们忽略掉落行为,保留和放弃它都是可以接受的]。

在8051程序集中有几种方法可以实现这一点,但每种方法都有它们的缺点。用于5-10个案例的短开关的那些很简单,并且足够清晰,但如果我想要一个> 128或甚至> 256个案例的开关,事情会变得复杂。

第一个是简单的,CJNE链将操作数与值进行比较,如果不相等则跳转到下一个案例;相当于if(){...}else if(){....} else if(){...}。优点是简单,缺点很明显:如果是长开关,这将产生很长的选择。 这可以通过构建二叉树来减少,使用JB来控制变量的连续位。这仍然不是很有效,很难维护并且难以实现稀疏键设置(案例1:......;案例5:......;案例23:......;案例118:......)< / p>

接下来的方法是乘法,然后跳跃偏移。将控制变量乘以2,将结果加载到DPTR中,使用偏移量加载累加器,然后在预先加载了多个JMP @A+DPTR的区域中执行AJMP。 (或乘以3并跳转到预先加载了多个LJMP的区域。)。

我做过一次。调整跳转指令到字节的位置是一个我不想重复的难题,加上跳转表不必要地大(每隔一个字节重复ajmp)。也许我不知道一些让它变得容易的技巧......

还有从专用表中提取地址,预加载堆栈并执行RET的方法。它听起来非常整洁,除了从专用表中拉出一个地址需要用可怕的MOVC A, @A+DPTRMOV A, @A+PC来完成 - 这些寻址模式让我如此努力,我从未尝试过实施它。如果您知道这样做的简洁方法,请将其作为答案发布。

一般来说,我想知道是否有更优雅,更有效的方式来执行switch()样式跳转 - 一个不会产生太多开销,不会浪费太多内存并且至少可以自由跳跃{{ 1}}距离,AJMP的数量为数百。

2 个答案:

答案 0 :(得分:6)

我的第二个答案是另一个你可能不喜欢的答案。

不要在汇编程序中写这种东西!你有什么希望实现的?晚年?

状态机(可能是lexical analysis?)正是generated code的用途。生成的代码实际上没有bug,并且很多更容易维护。有很好的免费tools available。代码生成器的输出通常很好,方便C.您将C提供给编译器。猜猜看,编译器知道你问题的答案。一个优秀的编译器将知道如何制作最有效的switch语句,它将继续使用它,而不会浪费你几周的生命调试。

另一件好事是,有一天,当你认为8051对你来说不够强大时,你可以轻而易举地转向更强大的架构而无需完全重写和调试整个状态机从头开始!另外,你终其一生都不会stuck working with 8051s

添加了:

由于这不是词法分析,我建议您使用状态机编译器,如Ragel。您只需向其提供状态机的描述,它就会生成您的状态机C代码。

或者,尝试logic grid方法。

答案 1 :(得分:4)

我通常不喜欢这种答案,但我认为这是合适的。

不要这样做!一个非常大的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;
    }

你的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;
    }
}

无论你正在做什么,它都可以用某种数组来做得更好。为了节省在汇编程序中编写答案,我将用C语言编写,但概念是相同的。

如果交换机的每个案例都涉及调用不同的函数,那么:

const void (*p[256]) (int x, int y) = {myFunction0, myFunction1, ... myFunction255};

void HandleCharacter(unsigned char c)
{
    (*p[c])();
}

你可能会争辩说函数指针数组会占用大量内存。如果它是常量,那么它应该只占用FLASH,而不是RAM,并且应该占用比等效switch语句更少的FLASH;

或者,这样的事情可能更相关。如果交换机的每个案例涉及分配不同的值,则:

char validResponses[256] = {INVALID, OK, OK, OK, PENDING, OK, INVALID, .... };

void HandleCharacter(unsigned char c)
{
    sendResponse(validResponses[c]);
}

总之,尝试在switch语句中找到一个模式;什么变化,什么保持不变?将变化的数据放入数组中,并将相同的内容保存到代码中。