|
Daycounter,
Inc. Engineering Services |
||||||||
|
> Lab Book |
Home | Company | Services | Products | Partners | Clients | Site Map | Contact Us | |
|
|
DSP563CC
Compiler Tricks Freescale's DSP563xx 24 bit processor is a great digital signal processor, and best yet their development tools are free. Their C compiler is gnu based, and isn't supported by them anymore, and so it can be a challenge to use. This web page describes some of the pit falls we encountered when using the compiler, and hard learned lessons, that we believe will be useful to other developers who are targeting this processor. COMPILER
For our project we used the DSP563CC compiler from Motorola.
Initially we ran into a few problems with this compiler.
First, the DSP56303 has three memory spaces: P, X, and Y.
The P memory is used for program memory.
The X and Y are used for data.
The problem is that the compiler only allows you to use one memory
space or the other (X or Y). It
does not allow you to use both X and Y at the same time unless you combine
them to use one big 48 bit memory space (Referred to in the documentation
as L memory). This presented a problem since we were designing our board to
access both X and Y memory for data storage.
To get around this problem we created a function using inline
assembly to manually write to the memory space.
This can be tricky since whichever memory space you specify to the
compiler it uses for the stack and variable initialization.
Make sure that you specify in the crt0.asm file the maximum memory
address that the compiler can use (the crt0.asm will be explained in more
depth later). That way you
won’t be writing to any address that the compiler is using.
Here is an example read and write function to the Y memory space: void YMemWrite(unsigned int Address, unsigned int Value) { __asm("move %0,y:(%1)"::"R"(Value),"A"(Address)); } unsigned int YMemRead(unsigned int Address) { unsigned int ReturnValue; __asm("move y:(%1),%0":"=D"(ReturnValue):"A"(Address)); return ReturnValue; } The
__asm function takes in a string parameter representing the assembly code
and then a list of variables. The
%#s in the string represent the variables in the list. The list is separated into 2 parts by colons.
The first part contains the variables to be written to, and the
second contains the variables to be read.
That is why there are no values in the first part of the Write
function. There is a third
part but we won’t discuss it here.
The variable modifiers “R”, “A”, and “=D” tell the
compiler what type of variable it is.
See the compiler manual for more information.
Be careful using modifiers! The
compiler manual has a whole list of various modifiers for both the
variable lists and for use inside the assembly.
Using some of them can make the register usage of your inline
assembly incompatible with the compiled C code.
Also, using the wrong modifier or using the one in the wrong place
can be very tedious to debug. We
found that the simpler… the better. So
now you have written your code and modified the crt0.asm file and now you
are ready to compile. Here is
a few problems that we had when it was time to compile. First lets explain how the compiler works. Using
the g563c command line compiler can implement both the compiler and linker
in one step. It also adds in
some library files that are needed by the compiler for certain function
calls, debugging, etc. (Note:
If you try and compile and link separate using the command line linker it
does not automatically link the library file in.).
You
will almost always have to modify the crt0.asm file for your project.
This files sets up the stack, interrupt vector, and other
variables. It also specifies
the top memory location of the stack. The compiler manual specifies that this file can then be
linked in by using the “–crt” command after the asm file is
compiled. Example: :>
g563c –c crt0.asm :>
g563c –crt crt0.cln foo.c The
problem is this doesn’t work. The
reason is is that crt0.cln is already in the library that the linker tries
to add so it will give you duplication errors.
The way to fix this is find the library file that you are using.
They are in the “\LIB” folder in the DSP directory.
It will have three library files.
One for X, Y, and L memory space options (if you did not specify a
memory space it defaults to Y). Run
dsplib.exe from the command prompt and it will give you the dsplib prompt
(make sure your in the LIB folder). Type
in “list <library filename>”.
This will list all the modules compiled into the library.
Find the name of the crt module (usually crt0.cln).
Then type “delete <library name> <crt module name>”
to delete the module from the library (you may want to back it up first
using extract). Now the –crt
option should work fine. BOOTING FROM EXTERNAL DEVICE (EEPROM,
SCI, ETC) Thanks
to Motorola this is not as easy as it looks.
There are three problems that occur when booting from an external
device. All of them have to
do with memory initialization. When
the code is compiled and loaded into the DSP from the ADS debugger than
the data memory automatically gets initialized.
When you load the memory from an eeprom or SCI only the program
memory gets restored. Fortunately
there is a solution to all three of the problems. Problem 1: System
Variable Initialization crt0.asm
sig.asm
exit.c F__stack_safety
F__sig_drop_count
F__functions_registered F__mem_limit
F__c_sig_handlers
F__exit_function_registry F__break F__y_size Ferrno F__max_signal F__time We initialized the
variables with the following code (Remember this is for Y memory space
only) placed in the crt0.asm file at the start of the F__start section: ; Added by Daycounter Engineering to enable booting from an
external source ; (Eeprom, SCI, etc.) ;
Initialize Y - variables on startup ; F__stack_safety
move
#1024,x0
move
x0,y:F__stack_safety ; F__mem_limit
move
#TOP_OF_MEMORY,x0
move
x0,y:F__mem_limit ; F__break
move
#TOP_OF_MEMORY,x0
move
x0,y:F__break ; F__y_size
move
#DSIZE,x0
move
x0,y:F__y_size ; Ferrno
move
#0,x0
move
x0,y:Ferrno ; F__max_signal
move
#$fe,x0
move
x0,y:F__max_signal ; F__time
move
#0,x0
move
x0,y:F__time ; sig.asm ; F__sig_drop_count
move
#0,x0
move
x0,y:F__sig_drop_count ; F__c_sig_handlers
move
#F__c_sig_handlers,r0
do
#$3e,F__c_sig_handlers_end
move
#F__sig_dfl,x0
move
x0,y:(r0)+
move
#0,x0
move
x0,y:(r0)+ F__c_sig_handlers_end ; exit.c ; F__functions_registered
move
#0,x0
move
x0,y:F__functions_registered ; F__exit_function_registry
move
#F__exit_function_registry,r0
move
#0,x0
do
#33,F__exit_function_registry_end
move
x0,y:(r0)+ F__exit_function_registry_end Problem 2: Switch Statement Addresses
There is another
problem with variable initialization.
The compiler also places memory addresses for switch statements in
the data memory section. The
memory addresses are used in the assembly jump statements.
In other words it does a compare and then jumps to the appropriate
address. Fortunately the
compiler has a command line parameter to move these addresses into the
program memory (you would think it would do this by default).
The parameter is “-mp-mem-switchable”.
Add this to your compile command and your newly compiled program
should have no problems booting from an external device. Problem 3: Program Variable
Initialization
This problem occurs when you initialize a variable inside your
code. Here are some examples
contained in a function or globally: static
unsigned int foo1 = 0; unsigned
int foo2 = 0; The previous code is
initialized through the debugger so when you boot from a eeprom or SCI the
variables initially have garbage values.
The solution to this is easy always manually assign the variables
in your code. For example: static
unsigned int foo1; unsigned
int foo2; if
first time running (psuedo code) foo1
= 0; foo2
= 0; You have to be
careful with this that you global or static variables only get initialized
when they are supposed to. It
is a good idea to always initialize your variables when working with the
DSP even if it may matter or not. Double Check
Ok,
now we have done the previous solutions but we want to ensure that we
didn’t forget anything. We
can check to see what variables are being initialized in the data memory
space by having the compiler output a memory map file.
This is done by including the “-M <filename>” parameter
in the linker command; however, this gets tricky since we are compiling
and linking in the same step. Including
this in your g563c command line won’t work.
We need to only send it to the linker.
This is done by using the –j parameter.
The following line of code is an example: g563c
-j "-M Mapfile.txt" foo.c Whatever is in the
string following the –j gets sent directly to the linker. This then creates a file called Mapfile.txt that contains all
the memory mapping information generated by the linker. You can open this file and see all the variables that are
mapped into the data memory space. Double
check that all the bytes are accounted for and you should have no problems
booting. BOOTING FROM SCI USING HYPERTERMINAL We
wanted a quick and easy way to download the code to our DSP device.
So we decided to use Hyperterminal.
Which works great you just have to have the data in the right
format before sending the file. You
need a binary file with the words arranged least significant byte first,
with the first 3 bytes containing the number of program words, and the
next 3 bytes containing the start address.
To do this we wrote a command line program to convert an srec file
to a binary file in this format. Now
after compiling your program into a cld file you convert it to an srec
file by using the srec.exe program included with the compiler.
This creates separate files for each memory space.
You then take the .p file and convert it using our PackDSP56.exe
program. The following is an
example: g563c
–g –o foo.cld foo.c srec
foo.cld PackDSP56
foo.p foo.bin This packages our
program memory into a file that is ready for Hyperterminal. Now open up Hyperterminal (Note directions are given using
Windows XP, may be different on other operating systems).
Type in a name for the new connection.
Hit ok and select the com port that your device is connected to.
Then set the bits per second to the value to match your device (The
frequency going into the SCI’s SCLK pin divided by 16).
Select 8 Data bits, None Parity, 1 Stop bit, and no flow control.
Click ok and the terminal should be up and connected.
Reset your DSP with the proper mod inputs set to boot off the SCI
and then click on Transfer->Send Text File… from the top menu.
Select your binary file and click open.
The hyperterminal should dump numerous characters and make weird
beeps as the file is transferred. The
reason for this is the DSP repeats back to hyperterminal the information
it receives. Since the
information the hyperterminal is receiving is not really text it displays
weird characters. Don’t
worry it does work it just isn’t very pretty.
When your finished your code should be downloaded and running (It
jumps to the start of your code and begins execution after downloading it
from the SCI). Note: programming
your device from the SCI only copies the information received into the
program memory at the address specified.
If you want to program an external eeprom you will have to write or
use a bootloader program to download to the program memory.
When the bootloader is run you can then download your program
through the bootloader program into the eeprom or external memory.
Since each bootloader is different we enabled our PackDSP56.exe
program with various options to aid in this process.
See the PackDSP56 documentation for more information.
For our project our program memory was small enough to fit entirely
on the onboard program memory space so when we want to program our eeprom
we download the code to the device using hyperterminal and then when our
code runs it checks to see what boot mode we are in if it is in “boot
from SCI” mode we copy the entire program memory to the eeprom.
If it isn’t it runs the program like normal.
That way everytime it is programmed from the SCI it flashes the
program to the eeprom and if it boots in any other mode it will not. PACKDSP56 DOCUMENTATION PackDSP56 is a program we developed to create a binary file from and srec file. An srec file is the file created by the srec.exe utility included with the Motorola’s DSP563ccc compiler (pretty much the same as an s19 file). Click here to download: PackDsp56.exe PackDSP56 is a
command line program with the following usage: PackDSP56
[options] <inputfile> <outputfile> The <inputfile>
is the srec file and the output file is the binary file you want to output
to (Caution: it will overwrite the specified file). The following are the optional command line parameters
(options): -NoHeader
This option leaves out the first 6 bytes from the output file
that contain the 3 bytes of the number of program words and the 3 bytes of
the starting address. -MemSize:<value> This option changes the default target memory size.
The default value is 4k. The
value is the max size that the binary file can be programmed too.
If the program memory that you are programming on your DSP is
larger than 4k you will have to specify it here for the program to work
properly. -StartAdd:<value> This option changes the default target starting address on
the DSP. The default value is
0x000000. The input value can
be up to address 0xffffff. Does
not work if the –NoHeader option is used. -BytesPerAdd:<value>
This option changes the default number of bytes in each program
word. The default value is 3
for the DSP563xx family since they have 24bit memory.
We put this option in just incase the user would like to use the
utility to program other devices using different memory sizes and would
like to specify how it is packed. You
can bring up the option’s descriptions anytime by typing PackDSP56
without any options or files specified. USING % OR / For
our project timing was essential so I was disappointed to find out that
the % and / routines that the compiler uses are very slow and tedious.
I ended up removing all of the % and / from my code except for a
few on initialization when the timing for the ISRs didn’t matter.
Here are some examples of how to get the same computations without
using % or /. % This gets used a lot
especially if you are using circular buffers or have numbers that
increment and loop back to zero. If
the number you are moding by is a power of 2 this becomes really easy.
You can simply mask over the carry bit.
For example say I want to do the following: x = x%8; This can be written
by simple masking off all the bits 8 or greater or in other words: x
&= 0x000007; This only allows the
x variable to increment from 0 to 7 then it repeats again.
This is quick an easy if you what you are moding by is a power of
2. Else the quickest way I
have come up with is to check if the value is greater than the max and if
it is then subtract the max. For
example: x = x%35; Would be: if
(x>=35) x -= 35; / Once again our divide
is very simple if we are dividing by a power of 2. Now we simply have to shift the bits to divide.
For example: x = x/8; is more easily computed as x <<= 3;
Lets see if this works. If
x = 16 (binary: 00010000) then x <<= 3 = 00000010 or 2.
This even works if x is not a power of 8 it just removes the
remainder. If we divide by a number that is not a power of 2 things get a lot more complicated. We wont go into optimization routines for this type of computations. See a Digital Signal Processing book for help. My advice is to not do this computation if you have to. Another way is to use the compiler to do the computation but do it when the timing isn’t critical like at the first of the program during Initialization or during the main loop (not in an ISR).
Daycounter is a
contract engineering company specializing in a variety of electronics and
software design. Please contact
us if you would like to outsource your next product design. |