How to write an effective switch () in assembly 8051?

If I ever want to create a state machine in assembly 8051, I will need an effective equivalent of the C switch() expression.

[for this issue, we neglect passive behavior, both conservation and decline are acceptable).

There are several ways to achieve this in assembly 8051, but each of them has its drawbacks. The ones for short switches in 5-10 cases are quite simple and clear enough, but if I need a switch of> 128 or even> 256 cases, things get more complicated.

The first is simple, a CJNE chain comparing an operand with values ​​and jumping to the next case, if not equal; equivalent to if(){...}else if(){....} else if(){...} . The growth potential is simplicity, the disadvantage is obvious: in the case of a long switch, this will create a very long selection line. This can be reduced by building a binary tree using JB for consecutive bits of the control variable. It is still not very effective, very difficult to maintain and makes it difficult to execute sparse keys ("case 1: ..., case 5: ..., case 23: ...; case 118: ...)

The next approach is to multiply, then shift the jump. Multiply the control variable by 2, load the result into DPTR, load the battery with an offset, then execute JMP @A+DPTR in the area preloaded with multiple AJMP . (or multiply by 3 and go to the zone preloaded with lots of LJMP .).

I did it once. Setting the location of the byte conversion command was a puzzle that I really don't want to repeat, plus the conversion table is unnecessarily large (repeating each other's AJMP bytes). Maybe I don’t know some trick that will make it easier for you ...

And there is an approach there to pull the address from the selected table, preload the stack and perform RET . This sounds very neat, except that you need to pull out the address from the selected table with the terrifying MOVC A, @A+DPTR or MOV A, @A+PC - these addressing modes make me MOVC A, @A+DPTR so much that I never tried to implement it . If you know a neat way to do this, send this as an answer.

In general, I would like to know if there is a more elegant and efficient way to perform a switch () style transition - one without creating too much overhead, without spending too much memory and giving freedom to jump at least AJMP distance, with the number of case going in hundreds.

+6
source share
2 answers

My second answer is another that you might not like.

Do not write such things in assembler! What do you hope to achieve? Old age?

A state machine (perhaps lexical analysis ?) Is exactly what the generated code is for. The generated code is virtually error-free and much easier to maintain. There are good free tools available . The output of the code generator is usually nice, convenient C. You give C to the compiler. And guess what, the compiler knows the answer to your question. A good compiler will know how to make the most efficient switch statement, and it will just continue working and continue working without wasting weeks of your life debugging them.

Another thing is that once, when you decide that 8051 is not enough for you, you can trivially switch to a more powerful architecture without having to completely rewrite and debug the entire state machine from scratch! In addition, you will not be dependent on working with the 8051s for the rest of your life.

Added:

Since this is not a lexical analysis, I suggest you use a machine state compiler like Ragel . You just pass him the description of your state machine, and it generates your C code of the state machine.

Alternatively, try a logic diagram .

+6
source

I am usually not a fan of this type of answer, but I find it appropriate.

Do not do this! A very large switch statement is the smell of code. This indicates that some bad planning has occurred in your code, or some initially good design has grown out of control as the project volume has grown.

You should use the switch statement if you have a choice of several really different options. Like this:

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

Does your switch statement really translate a program stream into hundreds of really different options? Or is it more like this?

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

Be that as it may, you can do it better with some sort of array. To save the answer in assembler, I will write it in C, but the concept is the same.

If each case of the switch includes a call to another function, then:

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

You could argue that an array of function pointers takes up a lot of memory. If it is const, then it should only accept FLASH, or RAM, and should occupy less FLASH than the equivalent switch statement;

Or, something like this might be more relevant. If each switch case involves the assignment of a different value, then:

 char validResponses[256] = {INVALID, OK, OK, OK, PENDING, OK, INVALID, .... }; void HandleCharacter(unsigned char c) { sendResponse(validResponses[c]); } 

In general, try to find the pattern in the switch statement; what is changing and what remains the same? Put what changes into an array and what remains unchanged in the code.

+4
source

Source: https://habr.com/ru/post/969796/


All Articles