0

Parsing serial text based commands

Posted by Carol on Oct 27, 2009 in Uncategorized

One of the fun tasks I have at work is creating fun lab projects for teaching classes.  In this case, the class was on basic embedded programming and the object was to show how serial input command strings are parsed into their basic components.  My pseudo project was a coffee maker, and the command set was relatively simple;

 

T 12:00                        sets the real time clock to 12:00

A 1:00                         sets the start time for the coffee maker to 1:00

M                                             turns on the timer

m                                             turns off the timer

?                                                          returns the time, start time, and current temperature of the coffee

 

Now, while the command set looks relatively simple, I also had to make it reasonably fool proof to use as well.  So;

1.                  I had to be able to accept both upper and lower case for the time and alarm commands.

2.                  I had to accept both single and double digit input for the hours.

3.                  I had to accept both a ‘ ‘ or a ‘:’ as a data delimiter.

4.                  And, I had to accept a cr as a delimiter and the termination of the command string.

 

So, I started out by creating a set a of variables to hold the parsed data;

 

A CHAR to hold the command character.  Note: mt_char is a default character.

      cmd_char   = mt_char;

 

And a 4 data variables for the individual nibbles of the hours and minutes

      data_var1a = 0;

      data_var2a = 0;

      data_var1b = 0;

      data_var2b = 0;

 

I then created an init routine to preset the variables to their default values.  Note, I use this both at startup, and after I decode the commands, to preset the variables for the next command string.

 

/******************************************************************************

* Function: void pars_init(void)

*

* Overview: This function configures the parser for operation

*

* Input:    none

*

* Output:   none

*

******************************************************************************/

void pars_init(void)

{

       cmd_char   = mt_char;

       data_var1a = 0;

       data_var2a = 0;

       data_var1b = 0;

       data_var2b = 0;

       mode       = cmd;

       pars_done  = 0;

       pars_falt  = 0;

}     

 

Note: resetting the variables to zero also provides any leading zeros required for single digit entries.

 

Next, I created my parser routine in two halves; the first which accepts the individual characters from the serial input and parses the data into blocks, and a second which executes the actual command.

 

The first section is implemented as a statemachine with 4 states, CMD for command, VAR1 for the first data variables, VAR2 for the second data variable, and DEFAULT which is a general error handler. 

 

In the CMD state, any character that is not a number or a delimiter is stored in the cmd_char variable.  If the character is a ‘ ‘, then the statemachine is advanced to the VAR1 state.  If the character is a cr, then the parser is done and the command can be decoded by the second half of the routine.  If any other character is detected than a pars_falt is set indicating an invalid command.

 

In the VAR1 state, any number is stored in the data_var1a, after the contents of data_var1a has been shifted into data_var1b.  This shift register arrangement allows us to either enter 1 or 2 values because both variables were cleared prior to the start of the command, leaving a leading zero for any single value entry.  If the character is a ‘:’, then the statemachine advances to the state VAR2 for a second value entry.  If the character is a cr, then the parser is done and the command can be decoded by the second half of the routine.  If any other character is detected than a pars_falt is set indicating an invalid command.

 

The VAR2 state operates nearly identically to VAR1, with the exception that the only delimiter accepted is the cr to terminate the command.

 

The only function of the default state is to set the fault flag so the second half of the routine can reset the statemachine to its default condition.

 

When the first half of the function is complete, and the  pars_done variable is set, then the 5 data variables (            cmd_char, data_var1a, data_var2a, data_var1b, & data_var2b) should have legal values in them, either from data received from the serial port, or default data loaded during the initialization function.

 

/******************************************************************************

* Function: void parser(void)

*

* Overview: This function disassembles serial command strings

*

* Input:    none

*

* Output:   values

*

******************************************************************************/

void parser(void)

{

       switch(mode)

       {

              Case cmd:     if (((hold >= ‘a’)&(hold <= ‘z’)) | ((hold >= ‘A’)&(hold <= ‘Z’)) | (hold == ‘?’))

                                         {

                                                cmd_char = hold;                         // a-z or A-Z or ? detected store as command

                                          }     

                     else if (hold == ‘ ‘)

                     {

                           mode = var1;                             // ‘ ‘ delimiter detected goto next field of data

                     }

                     else if (hold == CR)

                     {

                           pars_done  = 1;                          // CR detected terminate parser and decode command

                     }

                     else

                     {     

                           pars_falt  = 1;                          // no clue send a ? and restart

                     }

                     break;

              case var1:    if ((hold >= ‘0′) && (hold <= ‘9′))

                                         {

                                                data_var1b = data_var1a;   // 0-9 detected store as first data

                                                data_var1a = hold – ‘0′;

                                         }     

                                         else if (hold == ‘:’)

                                          {

                                                 mode = var2;                             // ‘:’ delimiter detected goto next field of data

                                          }

                                          else if (hold == CR)

                                          {

                                                pars_done  = 1;                                 // CR detected terminate parser and decode command

                                          }

                                         else

                                          {     

                                                pars_falt  = 1;                                 // no clue send a ? and restart

                                         }     

                                         break;

              case var2:    if ((hold >= ‘0′) && (hold <= ‘9′))

                                         {     

                                                data_var2b = data_var2a;          // 0-9 detected store as second data

                                                data_var2a = hold – ‘0′;

                                         }

                                         else if (hold == CR)

                                         {

                                                pars_done  = 1;                                 // CR detected terminate parser and decode command

                                         }

                                         else

                                          {     

                                                pars_falt  = 1;                                 // no clue send a ? and restart

                                          }     

                                          break;

              default:             pars_falt = 1;                                                // definitely lost, send a ?

                                         break;

       }                         

 

Once the data is parsed into its separate elements, it is a simple matter to decode the command and routine the new data into the appropriate variables.  In this case; rtc_hours, rtc_mins, alrm_hours, alrm_min, & alrm_on.  Based on the value in cmd_char, a simple switch statement is all that is needed to routine the incoming data to the appropriate variables.  Once the command decode is complete, then the routine is reset by calling the initialization routine to clear the variables and set them to their default values.  In the event that their was a communications fault during the parse or decode operation, then the data is discarded and the system is reset using the initialization routine.

 

       if (pars_done == 1)                                                                // if CR detected, then decode the command

       {

              switch(cmd_char)

              {

                     case ‘T’:                                                                          // time command uses both data blocks

                     case ‘t’:     rtc_hours = (data_var1b * 10) + data_var1a;

                                                rtc_mins  = (data_var2b * 10) + data_var2a;

                                                break;

                     case ‘A’:                                                                          // alarm set command uses both data blocks

                     case ‘a’:     alrm_hours = (data_var1b * 10) + data_var1a;

                                                alrm_mins  = (data_var2b * 10) + data_var2a;

                                                break;

                     case ‘M’:     alarm_on = 1;

                                                break;

                     case ‘m’:     alarm_on = 0;

                                                break;

                     case ‘?’:     send_all_data();                                // ? sends the time, alarm, and temperature

                                                break;

              }     

              pars_init();

       }     

       if (pars_falt == 1)

       {

              Tx_send(’?');                                                                     // send ‘huh?’ message to user

              Tx_save(CR);

              Tx_save(LF);

              pars_init();

       }     

}

Tags: ,

 
0

Converting between binary and decimal in assembly

Posted by Keith on Oct 22, 2009 in Uncategorized

One of the interesting challenges in working in assembly, versus C, is converting between BCD and binary. We all know that processors work in binary and users work in C, right? The problem is, even thought some processors include BCD math capabilities, things like multiplication, division, and all the system timers, work in binary.

So, one of the long standing problems, is how do we efficiently convert between base 2 and base 10. Well, a couple of years ago, I was faced with this problem, and I came up with a fairly efficient conversion algorithm that works well in both directions.

The routines are based on the same basic principal as a multiply routine. For example, let’s convert an 8-bit binary number to BCD.

8-bit binary value 10110101

First, we build up a constant table for each bit location;

0 0 1

0 0 2

0 0 4

0 0 8

0 1 6

0 3 2

0 6 4

1 2 8

Then we create a set of BCD output variables and clear them

BCD100 BCD10 BCD1

Next, we shift through the binary value, and for every 1, we add the corresponding value from the table, using BCD addition;

8-bit binary value 10110101

0 0 1

0 0 4

0 1 6

0 3 2

+ 1 2 8

—————————————

1 8 1

To go in the other direction, we just use a binary array of constants, and add using binary addition

BCD value 181

Binary constants

00000001 1

00000010 2

00000100 4

00001000 8

00001010 10

00010100 20

00101000 40

01010000 80

01100100 100

11001000 200

And the conversion looks like this;

BCD value 181

01100100 100

01010000 80

+ 00000001 1

———————————-

10110101

Of course, this technique can be expanded to any number of bits; all you need to do is expand the constant table to include all the bit values. Attached to the end of this blog are listings for both binary to decimal and decimal to binary for the PIC16FXXX family of parts;

;************************************************************************

; Converts a 5 digit BCD to 16-bit number in H_byte:L_byte

; Limitation: 5-digit bcd must be less than 2^17 (= 65536) in order

; to fit into the 16-bit result. Else result is remainder.

;

; R2H R2L R1H R1L R0H R0L

; - dig5 dig4 dig3 dig2 dig1

BCD5BIN16:

clrf H_byte

movf R2, W

andlw 0×0F

movwf L_byte

call mpy10a ; result = 10a+b

swapf R1, W

call mpy10b ; result = 10[10a+b]

movf R1, W

call mpy10b ; result = 10[10[10a+b]+c]

swapf R0, W

call mpy10b ; result = 10[10[10[10a+b]+c]+d]

movf R0, W

andlw 0×0F

addwf L_byte, F

btfsc STATUS, C

incf H_byte, F ; result = 10[10[10[10a+b]+c]+d]+e

return ; BCD to binary conversion done

mpy10b:

andlw 0×0F

addwf L_byte, F

btfsc STATUS, C

incf H_byte, F

mpy10a:

bcf STATUS, C ; multiply by 2

rlf L_byte, W

movwf L_temp

rlf H_byte, W ; (H_temp,L_H_temp) = 2*N

movwf H_temp

bcf STATUS, C ; multiply by 2

rlf L_byte, F

rlf H_byte, F

bcf STATUS, C ; multiply by 2

rlf L_byte, F

rlf H_byte, F

bcf STATUS, C ; multiply by 2

rlf L_byte, F

rlf H_byte, F ; (H_byte,L_byte) = 8*N

movf L_temp, W

addwf L_byte, F

btfsc STATUS, C

incf H_byte, F

movf H_temp, W

addwf H_byte, F

return ; (H_byte,L_byte) = 10*N

;*********************************************************************

;———————————————————————

; Bin16BCD

; Converts 16-bit binary number (H_byte:L_byte) into a 5 digit

; BCD number in 5 nibbles of registers R2, R1, R0 as shown.

;

; R2H R2L R1H R1L R0H R0L

; - dig5 dig4 dig3 dig2 dig1

;———————————————————————

Bin16BCD:

bcf STATUS,C ; clear the carry bit in STATUS

movlw .16

movwf temp+1

clrf R0

clrf R1

clrf R2

b16b_loop16:

rlf L_byte, F

rlf H_byte, F

rlf R0, F

rlf R1, F

rlf R2, F

decfsz temp+1, F

goto adjDEC

return

adjDEC:

movlw R0

movwf FSR

call adjBCD

movlw R1

movwf FSR

call adjBCD

movlw R2

movwf FSR

call adjBCD

goto b16b_loop16

adjBCD:

movlw 0×03

addwf INDF,W

movwf temp

btfsc temp,3 ; test if result > 7

movwf INDF

movlw 0×30

addwf INDF,W

movwf temp

btfsc temp,7 ; test if result > 7

movwf INDF ; save as MSD

return

Tags: ,

 
0

Capacitive Touch Algorithm Tuning for Thick Overlays

Posted by Padmaraja on Oct 20, 2009 in Capacitive Touch

Often the capacitive touch applications need thick overlay to be used over the capacitive touch pads to make the system mechanically robust. In addition to that, for aesthetic reasons, the overlay may be curved. This poses a serious issue of reducing the touch sensitivity. The curvature on the overlay distorts the sample reading from the buttons. Also it gives a variation in reading from button to button. To overcome these issues, the capacitive touch algorithm needs some tuning.      

 

When thick overlay of plastic or glass is used, the sensitivity goes down significantly. As you know, the human touch produces a basic capacitor, with finger and the touch pad being the electrodes and the overlay material is the dielectric material separating the plates. Because the thickness of the dielectric material increased(the overlay), the capacitance shift produced by touch reduces, thus reducing the sensitivity. Because of this, the signal to noise ratio reduces further down.

To overcome this, a slow moving average algorithm is used. First, the capacitive sensing module, such as Charge Time Measurement unit(CTMU) or Capacitive Sensing Module(CSM), is sampled 16 to 64 times(N Samples) every fixed interval. These N samples are accumulated and used as the current running sample. The averaging algorithm takes the current sample and takes a fraction of it and adds to the running average. The running average is not updated until 20 or more samples of slow moving average. By doing this the average changes very slow and the slight shift of the current sample allows to detect the touch.

Because of the low sensitivity, the difference between the current sample and the average data could be less than 1%. Each channel needs tuning on the number of samples for slow moving average to suit the touch key size and key characteristics.

 
0

24-hr Microchip Technology giveaway for Halloween

Posted by Carol on Oct 8, 2009 in Uncategorized

Makezine

 
0

Interesting Webcast about microcontroller in Human Interface

Posted by Yann on Oct 5, 2009 in Capacitive Touch, Displays, General

Technical Editor Robert Cravotta explores processor and software-processing architectures and the impact they have on system and software development. Relevant architectures include microprocessors, microcontrollers, digital signal processors (DSPs), multiprocessor architectures, processor fabrics, coprocessors, and accelerators, plus embedded cores in FPGAs, SOCs, and ASICs.

EDN Webcast

Copyright © 2010 Notes from the Lab All rights reserved. Theme by Laptop Geek.