Aerospace



Citadel Forums

Home

Company Information

Information Request

Linux How-to Guides

ADSP 21xx
Digital Signal Processing
Tutorials

SW Utilities

On-line Order Form


bonk

Have you found this site useful? Did we save you time? Did we cure your head-ache? Is your hair growing back now?

Please make a donation to help with maintenance.


Objective Real-Time Software on the ADSP21XX

Strings

General

Some people consider strings to be the bane of DSP programming. Creating a module to handle user I/O via a terminal screen can be awfully time consuming, due to a number of problems:

  • A DSP is much faster than any user terminal and serial port, causing buffer overflows when you want to update a whole screen.
  • A DSP works with 16 or 24 bit values, while ASCII text is only 8 bits wide.
  • Storing ASCII strings in memory is very wasteful, unless the string is packed into 24 bit words.
  • Packed strings cannot be readily compared with unpacked strings, making any kind of menu driven actions awkward to create.
  • Data frequently need to be presented in various formats: text, decimal, hexadecimal, binary etc.

This chapter presents a collection of string manipulation routines to take the strain out of string handling. These string routines are intended for use together with the serial UART routines presented in the previous chapter and the full source code is on the CD in directory \work\string.

The ASCII Table

Handling ASCII characters in assembler is a painful experience, since one cannot simply use an instruction such as

   ar = "A";

instead, one has to write

   ar = 0x41;

This is OK if you happen to know the whole ASCII table, but if you are like most of us, then using direct hexadecimal values is not recommended. Our good friend H.Acker also had a problem to remember the ASCII table and in order to alleviate this problem, he defined the whole ASCII table in the file work\string\inc\string.h, so he could write:

   ar = ASC_A;

While the ASCII definitions were certainly helpful, creating a user menu still caused a huge problem. The previous chapter introduced the function ut_char_out which can be used to output single characters to a terminal screen and H.Acker put it to heavy use like this:

   ar = ASC_M;
   call ut_char_out;
   ar = ASC_e;
   call ut_char_out;
   ar = ASC_n;
   call ut_char_out;
   ar = ASC_u;
   call ut_char_out;

A simple block copy operation down the screen, then go back up quickly and type in the characters. Quite neat, but extremely wasteful in terms of memory use and a terrible thing to modify, as H.Acker soon discovered when the guys in the Human Factors Engineering Ddepartment didn't like his screen displays.

Clearly, a string output function would be prefered. However, that raises the problem of defining and initializing the strings. Since ASCII characters are only 8 bits wide, while program memory is 24 bits wide, it would be very wasteful to save a constant string in PM, unless the characters are packed three to a word.

2ASCII

It would be handy to have a little utility to enable us to type a string and get the string in hexadecimal form, to use as an initializer for the constant string definition.

Aerospace Software comes to the rescue again, with a little DOS utility to do just that. The program 2ASCII.EXE takes as input a string typed on the command line and outputs it to the console.

For example:
D:\>2ascii "Have a nice day!"
0x486176, 0x652061, 0x206E69, 0x636520, 0x646179, 0x210000;

You can redirect the output to file and then cut and paste it into your source file:

D:\>2ascii "Have a nice day!">string.txt

The initialized string definitions would look like this:

/* "Have a nice day!" */

.VAR/PM/RAM/seg=INT_PM 
   NICE_DAY_MSG[6];
.INIT NICE_DAY_MSG:
   0x486176, 0x652061, 0x206E69, 0x636520, 0x646179, 0x210000;

Note that for NULL terminated strings to work correctly, it is essential that every string is terminated with at least one zero byte!

String I/O

We have created a number of routines for string I/O. The most used one is to output a string to a terminal, but if the terminal screen has to be interactive, we also need a string input routine and we need to be able to compare the input string with a constant string in memory in order to determine what action the user wants to perform.

It is usually a good idea to convert user input to all upper case before comparing it with a constant string. This should make the use of a menu system more user friendly. The constant string then also need to be stored in all upper case of course!

The string input function saves the user string as an unpacked byte string in DM. The compare function compares this byte string with a packed string in PM and returns 1 or 0 in the accumulator register ar - yea or nay.

Table 1 below, shows a set of string I/O and comparison routines, for use with the UART functions in the previous chapter.

Table 1. String I/O
/*********************************************************************
* Name         st_string_out
* Description: output a zero terminated packed string from PM
* Constraints: ar = address of string in PM
*********************************************************************/
st_string_out:
   MAC_ENTER

st_string_enter:
   m4 = 1;
   l4 = 0;
   i4 = ar;
   ay1 = 0x00FF;

st_string_loop:
   MAC_DISABLE(mr0)
   ax0 = pm(i4, m4);
   ax1 = px;
   MAC_ENABLE(mr0)

   ar = ax0;
   sr = LSHIFT ar by -8 (lo);
   ar = sr0 AND ay1;
   ar = pass ar;
   if eq jump st_string_exit;
   call ut_char_out;
   
   ar = ax0 AND ay1;
   ar = pass ar;
   if eq jump st_string_exit;
   call ut_char_out;
   
   ar = ax1 AND ay1;
   ar = pass ar;
   if eq jump st_string_exit;
   call ut_char_out;
   jump st_string_loop;
   
st_string_exit:
   MAC_EXIT
   rts;

/*********************************************************************
* Name         st_string_in
* Description: Get an unpacked string from the terminal and save it in DM
* Constraints: ar = address of string in DM
*              CR terminates string input
*              string saved as NULL terminated unpacked string.
*              string length is limited to 32 bytes
*********************************************************************/
st_string_in:
   MAC_ENTER

   ay1 = ST_STRING_END;
   l4 = 0;
   m4 = 1;
   i4 = ar;                            /* save the buffer address */
   
st_string_in_loop:                     /* read a char */
   call st_char_in;
   af = ar + 1;
   if eq jump st_string_in_loop;

   ay0 = ASC_CR;
   af = ar - ay0;                      /* terminate on CR */
   if eq jump st_string_in_done;

   call ut_char_out;
   call st_to_upper;                   /* convert to upcase */
   dm(i4, m4) = ar;                    /* save string */ 
   
   ar = ay1 - 1;                       /* terminate if string is too long */
   ay1 = ar;
   if eq jump st_string_in_done;
   jump st_string_in_loop;

st_string_in_done:                     /* NULL terminate the string */
   ar = 0;  
   dm(i4, m4) = ar;
    
st_string_in_exit: 
   MAC_EXIT
   rts;
   

/*********************************************************************
* Name         st_bytestr_out
* Description: output a zero terminated unpacked byte string from DM
* Constraints: ar = address of string in DM
*********************************************************************/
st_bytestr_out:
   MAC_ENTER

st_bytestr_enter:
   l4 = 0;
   m4 = 1;
   i4 = ar;
   ay1 = 0x00FF;

st_bytestr_loop:
   ar = dm(i4, m4);
   ar = ar and ay1;
   ar = pass ar;
   if eq jump st_bytestr_exit;
   
   MAC_DISABLE(mr0)
   call ut_char_out;
   MAC_ENABLE(mr0)
   jump st_bytestr_loop;
   
st_bytestr_exit:
   MAC_EXIT
   rts;
/*********************************************************************
* Name:        st_strcmp
* Description: Compares 2 strings
* Constraints: ar = Unpacked data string address in DM
*              ay0 = Packed constant string address in PM
*              returns ar = 0 if equal
*              strings must be NULL terminated
*********************************************************************/
st_strcmp:
   MAC_ENTER
   
   /*
    * set array indices to point to 2 strings
    */
   l5 = 0;
   l4 = 0;
   m5 = 1;
   m4 = 1;
   i5 = ar;                      /* data string */
   i4 = ay0;                     /* constant string */
   ay1 = 0x00FF;                 /* byte mask */
   ax0 = 0;                      /* assume equal to begin with */

   /* 
    * compare 2 strings
    */   
st_strcmp_loop:
   MAC_DISABLE(mr0)
   ar = pm(i4, m4);              /* read a PM word */
   ax1 = px;                     /* ax1 = LSB */
   MAC_ENABLE(mr0)
   
   sr = LSHIFT ar by -8 (lo);    /* sr0 = MSB */
   ar = ar and ay1;              /* ay0 = MID */
   ay0 = ar;

st_strcmp_msb:
   ar = dm(i5, m5);              /* MSB: read a DM word */
   af = ar and ay1;
   if eq jump st_strcmp_exit;    /* NULL terminated string end reached */
   ar = pass sr0;
   if eq jump st_strcmp_exit;    /* NULL terminated string end reached */
   ar = ar - af;
   if ne jump st_strcmp_diff;
   
st_strcmp_mid:
   ar = dm(i5, m5);              /* MID: read a DM word */
   af = ar and ay1;
   if eq jump st_strcmp_exit;    /* NULL terminated string end reached */
   ar = pass ay0;
   if eq jump st_strcmp_exit;    /* NULL terminated string end reached */
   ar = ar - af;
   if ne jump st_strcmp_diff;
   
st_strcmp_lsb:
   ar = dm(i5, m5);              /* LSB: read a DM word */
   af = ar and ay1;
   if eq jump st_strcmp_exit;    /* NULL terminated string end reached */
   ar = pass ax1;
   if eq jump st_strcmp_exit;    /* NULL terminated string end reached */
   ar = ar - af;
   if ne jump st_strcmp_diff;
   
   jump st_strcmp_loop;

st_strcmp_diff:   
   ax0 = 1;                      /* different */
   
st_strcmp_exit:   
   ar = ax0;
   MAC_EXIT
   rts;

/*********************************************************************
* Name:        st_to_upper
* Description: char to_upper(ascii)
* input:       ar
* output:      ar = upper case if lower case 'a' - 'z'
*********************************************************************/
st_to_upper:
   MAC_ENTER
        
        ay0 = 0x7A;
        af = ar - ay0;
        if gt jump st_to_upper_exit;

        ay0 = 0x61;
        af = ar - ay0;
        if lt jump st_to_upper_exit;

        ay0 = 0x20;
        ar = ar - ay0;

st_to_upper_exit:
   MAC_EXIT
        rts;

Numeric I/O

String I/O is only one part of the problem. The next horror is handling different numeric formats. Outputting a hexadecimal number to a terminal is already quite problematic, but outputting a decimal number is even worse, since it involves division by 10 and calculation of remainders - ugh.

Table 2 shows a set of routines for numeric I/O. It can handle hexadecimal numbers from 1 to 4 digits in width and decimal numbers 1 to 2 digits in width. The decimal routines include a fixed point division routine to handle the conversion problem. These routines can be used as a basis for any numeric format desired.

Table 2. Numeric I/O
/*********************************************************************
* Name:        st_hex_out
* Description: Output a single hex digit
*              pass the digit in ar
* Constraints: none
*********************************************************************/
st_hex_out:
   MAC_ENTER
   MAC_PUSH(ar)
   
   ay1 = 0x0A;             /* 10 = AH */
   af = ar - ay1;
   if ge jump st_out_a;
   ay1 = 0x30;             /* make numeric */
   ar = ar + ay1;
   jump st_out_exit;

st_out_a:
   ay1 = 0x37;             /* make alpha */
   ar = ar + ay1;

st_out_exit:
   call ut_char_out;       /* output the hex digit */

   MAC_POP(ar)
   MAC_EXIT
   rts;

/*********************************************************************
* Name:        st_hex_out4
* Description: Output a 4 digit hex address
*              pass the address in ar
* Constraints: none
*********************************************************************/
st_hex_out4:
   MAC_ENTER
   MAC_PUSH(ar)

   sr0 = ar;                        /* load ar */
   ay1 = 0x000F;                    /* nibble mask */

   cntr = 4;
   do st_out_h4 until ce;
      sr = LSHIFT sr0 by 4 (LO);    /* output MSB first */
      ar = sr1 AND ay1;
      call st_hex_out;              /* output a nibble */
      nop;
ut_out_h4: nop;

   MAC_POP(ar)
   MAC_EXIT
   rts;

/*********************************************************************
* Name:        st_hex_out2
* Description: Output a 2 digit hex byte
*              pass the byte in ar
* Constraints: none
*********************************************************************/
st_hex_out2:
   MAC_ENTER
   MAC_PUSH(ar)

   ax0 = ar;
   ay1 = 0x000F;                    /* nibble mask */

   sr = LSHIFT ar by -4 (LO);       /* output MSB first */
   ar = sr0 AND ay1;
   call st_hex_out;                 /* output high nibble */
   ar = ax0 AND ay1;
   call st_hex_out;                 /* output low nibble */

   MAC_POP(ar)
   MAC_EXIT
   rts;
/*********************************************************************
* Name:        st_hex_get
* Description: Get a single hex digit
*              return the digit in ar
* Constraints: none
*********************************************************************/
st_hex_get:
   MAC_ENTER

st_get:
   call st_char_in;
   af = ar + 1;
   if eq jump st_get;
   call ut_char_out;

   ay1 = 0x61;             /* a */
   af = ar - ay1;
   if lt jump st_get_upper;
   ay1 = 0x20;             /* convert to upper case */
   ar = ar - ay1;

st_get_upper:
   ay1 = 0x41;             /* A */
   af = ar - ay1;
   if lt jump st_hex_get_num;
   ay1 = 0x37;             /* A+10 */
   ar = ar - ay1;
   jump st_get_exit;

st_hex_get_num:
   ay1 = 0x30;             /* 0 */
   ar = ar - ay1;

st_get_exit:
   MAC_EXIT
   rts;


/*********************************************************************
* Name:        st_hex_get4
* Description: Get a 4 digit hex address
*              return the address in ar
* Constraints: none
*********************************************************************/
st_hex_get4:
   MAC_ENTER

   call st_hex_get;
   sr = LSHIFT ar by 12 (LO);

   call st_hex_get;
   sr = sr OR LSHIFT ar by 8 (LO);

   call st_hex_get;
   sr = sr OR LSHIFT ar by 4 (LO);

   call st_hex_get;
   sr = sr OR LSHIFT ar by 0 (LO);

   ar = sr0;

   MAC_EXIT
   rts;

/*********************************************************************
* Name:        st_get_dec
* Description: Get a single decimal digit
*              return the digit in ar
* Constraints: returns -1 when non-numeric character received
*********************************************************************/
st_get_dec:
   MAC_ENTER

st_get_dec_enter:
   call st_char_in;
   af = ar + 1;
   if eq jump st_get_dec_enter;

   ay1 = ASC_0;                     /* range check */
   af = ar - ay1;
   if lt jump st_get_dec_invalid;
   
   ay1 = ASC_9;
   af = ar - ay1;
   if gt jump st_get_dec_invalid;
   
st_get_dec_valid:   
   call ut_char_out;

   ay1 = ASC_0;                     /* Convert ASCII to number */
   ar = ar - ay1;
   jump st_get_dec_exit;
   
st_get_dec_invalid:
   ar = 0xFFFF;

st_get_dec_exit:
   MAC_EXIT
   rts;

/*********************************************************************
* Name:        st_get_num
* Description: Get a decimal number from the terminal
*              returns the number in ar
* Constraints: returns -1 when only invalid digits received
*              assume system is in integer multiply mode
*********************************************************************/
st_get_num:
   MAC_ENTER

   my0 = 10;                     /* base 10 */
   mr0 = 0xFFFF;                 /* preset to -1 = no valid number */
   mr1 = 0;
   mr2 = 0;
   mx0 = 0;                      /* start value set to zero */
   
st_get_num_enter:
   call st_get_dec;
   af = ar + 1;
   if eq jump st_get_num_done;
   
   mr0 = ar;                     /* multiply number by 10 and add new digit */
   mr = mr + mx0 * my0 (uu);
   mx0 = mr0;
   jump st_get_num_enter;
   
st_get_num_done:
   ar = mr0;

st_get_num_exit:
   MAC_EXIT
   rts;
   
/*********************************************************************
* Name:        st_str_num
* Description: Get a decimal number from an unpacked string in DM
* Constraints: ar = pointer to string
*              returns -1 when only invalid digits received
*              returns the number in ar
*              assume system is in integer multiply mode
*********************************************************************/
st_str_num:
   MAC_ENTER

   l4 = 0;                       /* make a data pointer */
   m4 = 1;
   i4 = ar;
   
   my0 = 10;                     /* base 10 */
   mr0 = 0xFFFF;                 /* preset to -1 = no valid number */
   mr1 = 0;
   mr2 = 0;
   mx0 = 0;                      /* start value set to zero */
   
st_str_num_enter:
   ar = dm(i4, m4);              /* read a character from the string */

   ay1 = ASC_0;                  /* range check */
   af = ar - ay1;
   if lt jump st_str_num_done;
   
   ay1 = ASC_9;
   af = ar - ay1;
   if gt jump st_str_num_done;
   
st_str_num_valid:   
   ay1 = ASC_0;                  /* Convert ASCII to number */
   ar = ar - ay1;
   
   mr0 = ar;                     /* multiply number by 10 and add new digit */
   mr = mr + mx0 * my0 (uu);
   mx0 = mr0;
   jump st_str_num_enter;
   
st_str_num_done:
   ar = mr0;

st_str_num_exit:
   MAC_EXIT
   rts;

/*********************************************************************
* Name:        st_num_out
* Description: display a 2 digit decimal number, with leading zero
* Constraints: ar = number
*********************************************************************/
st_num_out:
   MAC_ENTER
   
   ay0 = ar;            /* save the number */
   ax0 = 10;
   call st_divide;
   
   ay1 = ASC_0;         /* 10s */
   ar = ar + ay1;
   call ut_char_out;
   
   ar = mr0;            /* 1s */
   ar = ar + ay1;
   call ut_char_out;
  
   MAC_EXIT
   rts;

/*********************************************************************
* Name:        st_divide
* Description: 15 bit unsigned integer divide ay0 by ax0
*              Used for decimal number string conversions
* Constraints: ay0 = dividend
*              ax0 = divisor
*              returns ar = quotient
*                      mr = remainder
* Almost all computational registers are destroyed by this operation!
* Registers left over: ax1, mx0, mx1, my1
*********************************************************************/
st_divide:
   mr0 = ay0;

   sr0 = ay0;
   sr1 = 0;
   sr = LSHIFT sr0 by 1 (lo);
   ay0 = sr0;
   
   ar = 0;
   af = pass ar;
   ay1 = ar;

   astat = 0;
   divq ax0; divq ax0; divq ax0; divq ax0;
   divq ax0; divq ax0; divq ax0; divq ax0;
   divq ax0; divq ax0; divq ax0; divq ax0;
   divq ax0; divq ax0; divq ax0; divq ax0;

   mr1 = 0;
   mr2 = 0;
   my0 = ax0, ar = pass ay0;
   mr = mr - ar * my0 (uu);
   rts;



Copyright © 1995-2010, Aerospace Software Ltd., GPL.