The Gnu C Compiler (gcc) is very popular with many microcontrollers. After all, it is a world-class compiler with great debugging support. In fact, many gcc libraries for microcontrollers will allow you to write floating point code directly. Since that's true, why would you want to connect a math coprocessor PAK to a processor where you were using gcc? Actually, there are several reasons:
Code space. Since math coprocessor PAKs require very little code on the host, you can save a lot of space by using a PAK instead of large native libraries.Some of our PAKs use RS232 to communicate. But the several (including the floating point math units) use a special protocol that makes it simple for hosts to communicate with them. In this article, I'll show you a gcc library that works with our PAK-II. This math coprocessor can do transcendental functions, logarithms, and even evaluate polynomials. There is even a version (the PAK-IX) that includes an A/D converter.
My gcc library is aimed at the Atmel AVR target (specifically, I wrote it to accommodate the ATMega16. However, only a small bit of code is specific to the AVR. It should be easy to port the code over to any gcc-supported architecture. What's more is the low-level routines are reusable with any of the PAKs that use this protocol (currently the PAKs I-IV, VII, VIII, and IX).
By the way, if you want to use a PAK from AVR assembly, you'll find code for that elsewhere on our site.
All of the PAKs that don't use RS232 use the same basic protocol. So I wrote a file, pakbase.c that encodes all of the core protocol. This is the file (along with pakbase.h) you'll need to modify if you want to port my code to a different setup. There are just a few simple functions you need to change, and I'll cover them later.
The pak2.c file contains all the high-level functions that make the processor work with the PAK-II. Since gcc uses IEEE-format numbers, it is very easy to use the functions in pak2.c. Here is a simple example:
pak2reset(); // reset PAK before using it for the first time pak2floadx(2.0); // X=2.0 pak2floady(3.3); // Y=3.3 pak2add(); // X=X+Y pak2floady(9); // Y=9 (automatically casts to float) pak2mult(); // X=X*Y
As you can probably guess, the PAK has an X and Y register that you can use to perform math operations. In addition, there are several "storage registers" where you can save or restore X to deal with intermediate computations.
By default, the library waits for each operation to complete. However, if you set the global variable pak2WaitMode to zero, the library will return from lengthy operations (like pak2mult, for example) before the operation completes. If you make another coprocessor call, of course, then the code has to wait for the operation to complete. However, you can use this time to do other processing.
The figure below shows a program calling square root. With the input provided, the coprocessor takes about 2100uS to compute the square root. If pak2WaitMode is set, the processor doesn't do any useful work while this processing occurs. However, the top trace shows the same code with wait mode disabled. Now the processor is busy for 400uS. The processor can then proceed with other tasks until the computation is complete (of course, it can also wait longer if it wishes). A call to pakready() allows the program to test the state of the computation. If the program calls any library function, it will automatically block until the pending computation is complete.
You can find all of the PAK-II commands in the manual. Each command has a gcc wrapper which also handles the IEEE format conversion (this conversion is done on the chip).
Here's some code to compute a table of square roots and cube roots:
for (n=0;n<=100;n++) { pak2loadxint(n); pak2sqrt(); printf("%d - ",n); // use the computing time to print round3(); printx(); pak2loadxint(n); pak2loadyint(3); pak2root(); putchar('\t'); // use computing time to print round3(); printx(); putchar('\r'); putchar('\n'); }
Loading an integer is more efficient than allowing the compiler to promote the integer to a float. Note that the program starts computations and then executes printf's so that it makes use of some of the computation time for other purposes.
The round3 function is quite simple:
void round3(void) { // round to 3 digits pak2floady(.0005); pak2add(); // printx will chop off the other digits }
The printx routine uses a special PAK function to pick the number apart digit by digit:
#define LDIGITS 3 // digits before decimal point #define RDIGITS 3 // digits after decimal point void printx(void) { int i; putchar(pak2digit(0)); for (i=LDIGITS;i>=1;i--) putchar(pak2digit(i)); putchar('.'); for (i=0x81;i<=0x80+RDIGITS;i++) putchar(pak2digit(i)); }
This simply prints 3 digits to the left of the decimal point and 3 digits to the right. Using this instead of the AVR's full-blown printf (the one that supports %f) saved about 3K of program space! You can still use the limited printf that doesn't know about floating point for everything else. Of course, you can link with the larger printf and use it, if you wish. Simply uncomment the line in the makefile which reads:
LDFLAGS += -Wl,-u,vfprintf -lprintf_flt -lm
Speaking of printf, I used Peter Fleury's excellent UART code (with some tweaks to make it work on the processors I use; I understand Peter has updated the code, but I'm still using an older version). A call to fdevopen allows me to associate his UART code with my stdio streams.
If you want to port this code to a different setup, there are only a few things that need to change. First, pakbase.h has several constants that allow you to set the I/O pins for the PAK. This is used by the custom code in pakbase.c, so if you are porting to something other than an AVR, you might change this completely. Here are the constants:
#define DBIT 0x8 #define CBIT 0x10 #define PPORT PORTB #define PPIN PINB #define PDIR DDRB
These define the data bit, the clock bit, and the port registers (write, read, and direction). So in this case, the PAK is wired with its data pin to PORTB.3 and its clock to PORTB.4. The hookup, by the way, is exactly as shown in the manual (with a .1uF decoupling capacitor across Vdd and Vss, of course).
There are two other defines in pakbase.h, BASEDELAY and DELAYMULT. The default pakdelay routine uses these to adjust the clock timing.
The only functions that use these constants are the functions at the top of pakbase.c. You'll need to change or rewrite them to suit your situation:
pakdelay - Causes the processor to delay for a half cycle. To meet the PAK specification, the complete cycle must be less than 100kHz in frequency. The supplied code is for an AVR with a 7.3728MHz clock. The timing is not critical so long as it does not exceed the maximum speed. When developing, you should try a slower speed and then work up after everything is working. Note, however, that holding the clock line high for several seconds (when debugging, for example) may cause the PAK to reset.If you port these functions, the rest of the library should work. Note these are all inline and typically quite simple. For example, read_port on the AVR is just one single line:
static inline int read_port(void){ return PPIN;}
This archive contains the files you'll need. They also include a demo program (pak.c) and the patched version of Peter Fleury's UART code that I'm using. This is not required for the library, but the demo program uses it.
Here's the demo program running:
The second column shows the square root and the last column shows the cube root. After the program is done calculating, it blinks any LEDs on the PAK's B port and also the PAK's A port, bit 3.
Site contents © 1997-2018 by AWC, Houston TX (281) 334-4341