How to deal with multi-letter ASCII commands? (through UART)

Go To Last Post
10 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi - I'm working on writing a program that will communicate with a computer through the serial port and the UART. I'm trying to figure out what the best way to write it would be. Commands will be multiple letters, some different numbers of letters than others, and some will have multiple digit ascii coded decimal numbers that will need to be converted to binary. All in all - quite the headache!

So what I'm thinking is I'll have three primary subroutines. So the first subroutine would essentially contain the other three, and some other code as well. Let's just call it "primary". The first thing it'd do would be to call subroutine "receive". Receive would first pop the return address into two registers, then it would watch the UART for commands received and keep on pushing every byte received through the uart onto the stack after converting all ascii capitol letters to lowercase, or the other way around. Then, once it received a enter keystroke it would push the registers loaded with the return address onto the stack, and return.

The next part of the "primary" subroutine would interpret the code. The nasty part of this is that the first letter popped from the stack would be the last digit in the command. So it could be a number or letter - essentially the command would have to be interpreted backwards. So what I'm thinking is first the code would recognize if the first digit after the enter was a number or a letter, and make a jump based on that.

If it were a number, the program would push that last number back onto the stack and jump to a bit of code that would convert the ascii decimal to binary - which shouldn't be too hard (subtract ascii "0" from each number to get them to BCD, then multiply the digits by their respective powers of 10 and add them together.) Once it shifted in a space, it would load this number into a set of registers (it would be 2 bytes long) and then jump to the same place that the program jumps to if there are no numbers in the command.

This bit of code would just go letter by letter, backwards like everything else. So this is where I just can't figure out a way to make the coding really pretty, and pretty much the primary reason for my post. My thoughts were that it would essentially check to see if the first letter was an A, then it would make a jump if it was, othewise it would check to see if it was a B, a C, etc. Over and over. Then the placed where it jumped to would do the exact same thing, over and over. Naturally - It wouldn't have to check for every letter, only the letters which would be possible at each point (so if the first letter was an A there may be 5 different letters possible for the second letter, if the first letter was B there may only be 3 possible letters, etc.), and since there will only be 10 or so commands there won't be too many, but still this seems clunky to me and I must admit it bothers me. Anyways the code would then jump to the location in the code necessary to complete the command received. That code would then use the 2 byte number if that command needed to do so.

Lastly - upon completion of the received command, the program would push something like ".eonD" ("Done." backwards) into the stack, then call the last major subroutine, "transmit". This would pop the return address into some temporary registers, then pop and send each byte in the stack, then push the return address back onto the stack, and return. The program would then jump back up to primary - and start all over.

Sorry for writing such a book - I tried to keep it as short as possible, honest! So - how does this look? Could anybody suggest a better way to do any of this? Thanks!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

well, if you do it that way it'll at least match your user name :D But why not proecess it as it comes in? Or keep it all in a buffer and process it from start to finish as a queue instead of a stack. Using it as a stack basically means that you can't receive more data anyway, so the fact that you'd have empty spaces at the start of the queu if you make it out of an array doesn't mean much and when you're done processing it, just set the queue size to 0.

I'm not certain that I understand though.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

NogginGTS wrote:
well, if you do it that way it'll at least match your user name :D But why not proecess it as it comes in? Or keep it all in a buffer and process it from start to finish as a queue instead of a stack. Using it as a stack basically means that you can't receive more data anyway, so the fact that you'd have empty spaces at the start of the queu if you make it out of an array doesn't mean much and when you're done processing it, just set the queue size to 0.

I'm not certain that I understand though.


lol yup I didn't even realize it would be backwards till I typed that all out and came to that conclusion. It doesn't matter too much either way, backwards or forwards, though. It's the same process. It's just a little less intuitive. My attraction to loading the entire command and then processing it is that there'd be less code between each character received, thus making the chance of missing a character even at a high baud rate very very low. I suppose though that that probabaly wouldn't be an issue either way though. What do you mean by buffer it?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

well you're storing it somewhere, I'm just calling that the buffer.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Maybe instead of using the stack it would make more sense to just write the bytes received to the beginning of the ram with the ST X+ command? Now that I'm thinking about it - that seems to make much more sense. What do you think?

edit: and if I did it that way the first byte received would be written at the lowest address in the ram. I would also include the enter byte, as that would be used to determine the end of the command.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

oh, you were going to use "the" stack. I don't write much assembly, but that definately doesn't seem like a good (or at least not the best) idea to me especially if you're going to be waiting for all of the bytes to come in first. I thought you were going to implement your own stack.

And using ST X+ (I'm taking for granted that this is the proper command) would be just as fast as pushing and popping from the stack since it auto-increments. When you're 0x13 comes in then reset X to address of your string/array/queue/buffer whatever you want to call it, then read them back out until you reach the 0x10 (assuming your enter key sends 0x10 0x13)

I take it that you're going to be controlling this using a terminal program. What I did for something similar in C was along the lines of a state machine.

if current state is X {
	if character is 'a' then do this and next state = Y
	if character is 'b' then do this and next state = Z
	if character is.... }
if current state is Y {
	if character is 'a' then do this and next state = W
	if character is.....}
if current state is ..... {
	if character is....}

after doing the tasks for the current state and the received character, then go to the next state
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am in the midst of writing a command processor & have recieved a lot of great help here.
My technique is to define a library of commands. A uart buffer holds the incoming stream & waits for a carriage return. This then triggers a parser that searches the dictionary for a match.
To define the commands, I use lowercase letters to add control features
x indicates an optional number to be provided (each x indicates a digit)
nxx indicates the next state (xx) to goto or activate upon recption of the command
= indicates the end of each command definiton
spaces are remove & ignored both in the received data & command definitions
Examples:
Address_tbl .dw cmd1,cmd2,cmd3,........

cmd1: .db "VOLUMExxx n11="
cmd2: .db "n44 SPKROFF="
cmd3: .db "SETVOLT xxxxx n66="
cmd4: .db "hour n33 xx ="

the parser returns telling 1) if a vaild command was received
2) what next state is to be executed 3)a 16 bit numeric value (converted from 1 to 5 digit ascii number)

So for example, receiving SETVOLT 10000 would provide a valid command (3), a value of $2710 (10000 base 10) & a nextstate value of 66 So "routine 66" would set the Power DAC to $2710, for example.

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@ OP

You may look at the code for my AVR J1850 Interface (at my homepage or in User Projects). This code receive and parses AT commands with one or two command identifier and optional parameters as well as none AT commands. None AT commands are hex bytes but send with ASCII chars. ie: One byte hex is made from two byte ASCII.
The code explains how to use a receive buffer, check for valid alphanumeric and hex only chars, and how to parse a string from receive buffer.
The code includes an echo function, optional line feed output, a function wich makes one byte hex from two byte ASCII and some output routines for error message.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Forget the stack, use a simple linear buffer, which was automatically filled inside the UART receive interrupt.

And whenever a 0x0D or 0x0A was received the buffer was detected as filled with a command.

Then call a command interpreter which compare the buffer with a list of valid commands and execute the routine which was defined behind the command text.

Then after the command was executed copy all received characters after this old command to the buffer start and check, if already a next command was received or wait until a next command was received.

Following my bootloader example which works in this way:

http://www.mikrocontroller.net/a...

The command interpreter read also a hex-value after the command and check, if a question mark was also received.

Peter

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

download this (400k zip):
http://www.vfx.hu/proj/avrpac/re...
and check terminal.asm. This using tokensand parameters in UART commands.

VFX.
http://www.vfx.hu