Citadel Forums
Home
Company Information
Information Request
Linux How-to Guides
ADSP 21xx Digital Signal
Processing Tutorials
SW Utilities
On-line Order Form
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;
|
|
|