| |

NOTE: The following information is based on our work with Borland Turbo C v2.0
and Borland C++ v4.52. The concepts discussed are not unique to Borland products, but
code compatibility with other C compilers has not been verified.
C Programming Environments
You do not need any special compilers or linkers to generate executables for the
JK microsystems single board computers. We have compiled many C programs using
Turbo C 2.0 and Borland C/C++ 4.52.
When using the Windows based development packages, you must be sure that you
can create a DOS executable file. The default is usually for Win95, WinNT or Win3x
executables. These will not work on the Flashlite. When starting a
project be sure to specify DOS as the target operating system.
It is also good check the settings for target processor and math instructions
used by the compiler. The Flashlite-V25 uses a superset of the 8086 instruction
set and any of the 286/386/etc. instructions will not work. The Flashlite 386Ex
uses the 386 instruction set. Floating point math also is an issue. The JK microsystems
single-board controllers do not support floating point math and therefore, the
compiler needs to include the software emulation libraries. In some cases, the
compiler will generate code with both emulation and direct 387 instructions and
attempt to determine if a 387 co-processor is present at runtime. This test has
been known to cause problems with the Flashlite-V25 SBC. It is best to force the
compiler to generate code using floating point emulation.
See our pdf document
Getting Started with the Borland IDE (158K)
for a step by step guide to starting projects using the Borland 4.52 IDE.
Flashlite-V25 Ports and Memory I/O
The I/O ports on the Flashlite-V25 embedded controller are mapped into high memory.
The ports can be used in at least 2 different ways. Some C compilers have peek and
poke instructions that allow direct access to memory space. Functions such as
peek(seg,off) and poke(seg,off,value) that are 16 bit (word)
instructions and peekb(seg,off) and pokeb(seg,off,value) that
are 8 bit (byte) instructions allow direct modification of memory. These functions
are unique to the 80x86 architecture and are part of the DOS library.
Alternatively, the ports can be accessed by defining a pointer
to the port address and reading or writing to that location. This method
does not require any extra include files, although it will require
compiling you program in a memory model that can handle data in far memory.
The following sample program configures all pins of PORT 2 as inputs
then continually reads and displays the status of these pins.
/* Example program to read and print the state of Port 2 on */
/* the Flashlite-V25. */
#include <stdio.h>
/*define a macro to create a far pointer out of a segment
and offset*/
/*macro could cast result as (unsigned char far *) but the
(void far*)*/
/*is more generic and allows the result to be used with
variables other*/
/*than unsigned characters */
#define MK_FP(seg,off) ((void far *) \
(((unsigned long)(seg) << 16) | (unsigned)(off)))
#define PORT_SEG 0xF000 /* segment for port registers */
#define PORT_2 0xFF10 /* offset of Port 2 */
#define PORT_MODE_2 0xFF11 /* offset of port 2 mode reg */
#define PORT_MODE_CTL_2 0xFF12 /* offset of port 2 mode ctl reg */
main()
{
unsigned char far *port2, *mode2, *ctl2;
port2=(unsigned char far*)MK_FP(PORT_SEG,PORT_2);
/* port 2 location */
mode2=(unsigned char far*)MK_FP(PORT_SEG,PORT_MODE_2);
/* port 2 mode location */
ctl2=(unsigned char far*)MK_FP(PORT_SEG,PORT_MODE_CTL_2);
/* port 2 mode ctl location */
*ctl2=0; /* mode control -> i/o */
*mode2=0xFF; /* mode -> all bits input */
while ( 1 ) /* do it forever */
printf("PORT2: %X\n",(int)*port2);
/*read and print port 2 value*/
return 0;
}
Flashlite 386Ex Ports and Port I/O
The I/O ports on JK microsystems Intel 386Ex based products are mapped into port space.
Using the ports from C requires the use of in and out functions
unique to the 80x86 family of processors. Borland C, for example, supports an
inport(port) and outport(port,value) that are 16 bit (word)
instructions and inportb(port) and outportb(port,value) that
are 8 bit (byte) instructions. These functions are part of the dos.h (or similar)
header file.
The following sample program configures all pins of PORT A as inputs
then continually reads and displays the status of these pins.
/* Example program to read and print the state of Port A on */
/* the Flashlite 386Ex. */
#include <stdio.h>
#include <dos.h>
#define PORT_A 0x60
/* address of Port A */
#define PORT_DIR 0x63
/* address of port direction register */
#define PORT_A_DIR_MASK 0x10
/* dir bit is bit 4 (00010000) = 0x10 */
main()
{
unsigned char portA;
portA = inportb(PORT_DIR);
/* get current value of direction reg */
portA |= PORT_A_DIR_MASK;
/* set direction bit for input /*
outportb(PORT_DIR,portA);
/* write value to direction reg */
while ( 1 ) /* do it forever */
printf("PORT A: %X\n",(int)inportb(PORT_A));
/*read and print port A value*/
return 0;
}
LCD Driver and Streams
Here are is an example program and a few pointers when C programs
write to the Flashlite LCD driver.
First, do not send end-of-line characters (\n) to the LCD. These characters
will look like black boxes or strange characters and may prevent control
sequences from working properly.
Second, C may buffer output to streams.
This can cause erratic behavior or make the program seem not to work. The
answer is to disable the buffering. In Borland C, the setbuf(*stream, *buffer)
command can be used.
The following example prints the date and time in the center of the 4x20 LCD
until a key is pressed.
Note: With some compilers, opening LPT1 as a file will fail. In this
event, try using stdprn (the standard print device). It will
still be necessary to disable buffering on the device.
#include <stdio.h>
#include <time.h>
#include <conio.h>
#define LCD_CMD 160 /* command for LCD driver */
#define LCD_CMD1 40 /* i/o format setup command */
#define LCD_CMD2 6 /* cursor setup command */
#define CLR_HOME 1 /* clear and home command */
void main()
{
time_t sec_now, sec_prev=0; /* time in seconds, 2 copies */
struct tm *tm_now; /* time/date structure */
FILE *lcd; /* stream for LCD data */
unsigned char lcd_pos=0xC6; /* demo position w/ variable */
/* **when using Turbo C, use the following line: */
/* lcd=fopen("LPT1","w"); /* open LPT1 for output */
/* **when using Borland 4.52, use the following line: */
lcd=stdprn;
setbuf(lcd, NULL); /* disable buffering */
/* send init commands to LCD */
fprintf(lcd,"%c%c",LCD_CMD,LCD_CMD1);
/* with defined commands */
fprintf(lcd,"%c%c",LCD_CMD,40); /* with decimal commands */
fprintf(lcd,"%c%c",LCD_CMD,0xC); /* with hex commands */
fprintf(lcd,"%c%c",LCD_CMD,LCD_CMD2);
fprintf(lcd,"%c%c",LCD_CMD,CLR_HOME);
while ( !kbhit() ) { /* repeat until key hit */
time(&sec_now); /* get the time (sec from whenever) */
if (sec_now != sec_prev) { /* see if new time */
sec_prev=sec_now;
tm_now=localtime(&sec_now);
/* convert seconds into something useful */
fprintf(lcd,"%c%c",LCD_CMD,lcd_pos);
/* set position to output on LCD */
fprintf(lcd,"%02d-%02d-%02d", /* output date */
tm_now->tm_mon,tm_now->tm_mday,tm_now->tm_year);
fprintf(lcd,"%c%c",LCD_CMD,0x9A); /* reposition cursor */
fprintf(lcd,"%02d:%02d:%02d", /* output time */
tm_now->tm_hour,tm_now->tm_min,tm_now->tm_sec);
}
}
fclose(lcd); /* close the stream */
return 0; /* done */
}
Using DOS and BIOS Interrupts
It frequently becomes necessary to invoke software interrupts
to perform specific functions not supported directly by C. One
example is interfacing with TSR (Terminate and Stay Resident) type
drivers.
Using software interrupts requires use of the x86 registers to
pass information between the interrupt service routine and its caller,
in this case, a C program. High level languages do not allow the
programmer direct access to the registers (that's the compilers territory),
so the answer is to have a function that saves all the registers, puts
in the user values, does the software interrupt, saves the return
registers, restores the original register status, and returns control to the
C program.
Fortunately, most x86 C compilers have done this for us. Functions
like int86(int_no,*inregs,*outregs) make use of a structure holding
the register values to call software interrupts. Look for similar functions in the
DOS library. A dos.h (or similar) header should define the register structure.
Be ware, the interrupt functions do not always save/restore all of the registers.
int86( ) for example does not modify ds and es (segment registers).
The following code is a short example that uses a TSR that traps int 0x30. The
TSR is written to configure an A/D converter and get data from it.
/* A/D interface example */
/* uses ad_int.com software interrupt 0x30 */
/* ah=1, ad interface functions */
/* al=0, get version, bl=ver, cx=55AA */
/* al=1, set channel w/ bx=channel (0..7) */
/* al=2, set range, w/ bx: 0=(0-5V), 1=(+/- 5V) */
/* 2=(0-10V), 3=(+/- 10V) */
/* al=3, get result, value returned in bx */
/* displays data until key pressed */
#include <stdio.h>
#include <conio.h>
#include <dos.h>
#define IO_INT 0x30
#define GAIN 0.00122 /* 5.00 Vref / 4096 counts -> 1.22 mV/count */
main()
{
int i;
union REGS regs; /* x86 registers */
regs.h.ah = 1; /* id A/D the driver */
regs.h.al = 0;
int86 (IO_INT,®s, ®s); /* do the software int */
if (regs.x.cx != 0x55AA) {
printf("\nDevice interrupt not found\n");
/*wont work w/o the TSR*/
exit(-1);
/* that's all, folks */
}
else
printf("Driver Version: %X\n",(int)regs.h.bl );
while (!kbhit() ) { /* repeat until key pressed */
for (i=0; i<4; i++) { /* loop for 4 channels */
regs.h.ah=1;
regs.h.al=1; /* set the channel */
regs.x.bx=i;
int86 (IO_INT,®s, ®s); /* do the software int */
regs.h.ah=1;
regs.h.al=2; /* set the range (0-5V) */
regs.x.bx=0;
int86 (IO_INT,®s, ®s); /* do the software int */
regs.h.ah=1;
regs.h.al=3; /* get the data */
int86 (IO_INT,®s, ®s); /* do the software int */
printf("%1.3f (%4X) ",
i+1, regs.x.bx * GAIN, regs.x.bx );
}
printf("\n"); /* start a new line */
}
return 0;
}
Interrupt Service Routines for IRQs on the 386Ex
Writing Interrupt Service Routines (ISRs) in C can be a trying task.
The following information should help to ease the process. There are
several common reasons to write ISRs including serial receive routines,
counters, and other requests from external hardware. The ISR should
be a short as possible, as it is usually required to complete before
another event occurs.
Interrupts are processed by the 8259 Programmable Interrupt Controller (PIC).
The 386Ex contains two cascaded PICs. This is the same configuration as
in common PCs. The master PIC is located at port address 0x20 and the
slave PIC is at 0xA0. IRQ2 is the cascade interrupt that allows the slave PIC to
communicate to the master PIC. IRQ9 is on the second PIC and will require
a bit more effort to use.
When changing the PIC configuration or when processing interrupts it is usually
desirable to disable (using disable() ) all interrupts before making the changes and
then re-enable (using enable() )them after the changes have been made.
At the end of the ISR it is necessary to reset the PIC or chain to another ISR.
To reset the PIC, write a 0x20 to the proper port. When working with the slave
PIC, remember to reset both PICs before returning from the interrupt. To pass
processing of the interrupt along to another function, simply call that function.
When using IRQ3 or 4 with external interrupt signals, it is necessary reprogram the 386Ex to
connect these signals to the external processor pins rather than the internal UARTs.
The commonly available IRQ inputs on the Flashlite 386Ex are IRQ3, IRQ4, IRQ5, IRQ6
and IRQ9. NOTE: there was a typo on our documentation that erroneously indicated IRQ8
was available on pin2 of J13 (extended bus).
The following example (compiled with Borland C++ 4.52) counts the
interrupts on IRQ3, IRQ4 and IRQ9.
// Interrupt counter example program
// JK microsystems
// EW June 1998
#include <stdio.h>
#include <dos.h>
#include <conio.h>
#include <stdlib.h>
#define FALSE 0
#define TRUE (!FALSE)
#define P3CFG 0xF824 // port 3 config register (386Ex)
#define INTCFG 0xF832 // interrupt config register (386Ex)
#define MCR0 0x3FC // modem control reg, uart 0
#define MCR1 0x2FC // modem control reg, uart 1
long count3=0, count4=0, count9=0;
char newcount=FALSE;
void interrupt (*oldirq3)(...);
void interrupt (*oldirq4)(...);
void interrupt (*oldirq9)(...);
// Interrupt Service Routine, IRQ3
void interrupt countint3(...) {
disable(); // disable ints
count3++; // increment count
newcount=TRUE; // flag to indicate new count ready
outportb(0x20,0x20); // reset PIC
enable(); // ints ok now
}
// Interrupt Service Routine, IRQ4
void interrupt countint4(...) {
disable(); // disable ints
count4++; // increment count
newcount=TRUE; // flag to indicate new count ready
outportb(0x20,0x20); // reset PIC
enable(); // ints ok now
}
// Interrupt Service Routine, IRQ9
void interrupt countint9(...) {
disable(); // disable ints
count9++; // increment count
newcount=TRUE; // flag to indicate new count ready
outportb(0xA0,0x20); // send EOI to PIC2
outportb(0x20,0x20); // send EOI to PIC1
enable(); // ints ok now
}
main ( void ) {
int PIC1mask=0,PIC2mask=0; // define vars and set default values
int tmp;
printf("\nInterrupt Counter\n\n");
// generate IRQ mask for PIC
PIC1mask = 0x1C; // bits set for IRQ3, IRQ4 and IRQ2(cascade)
PIC2mask = 0x02; // bit set for IRQ9
disable(); // disable interrupts
// save old vectors and set new ones
outportb( INTCFG, inportb(INTCFG) | 0x60 );
// port pin, not internal UARTs
outportb( P3CFG, inportb(P3CFG) | 0x03 );
// connect IRQ 3 and 4 to
outportb( MCR0, inportb(MCR0) & ~0x08 );
// more 386Ex stuff to get
outportb( MCR1, inportb(MCR1) & ~0x08 );
// signals connected to pins
oldirq3=getvect(0xB); // IRQ 3
setvect(0xB,countint3);
oldirq4=getvect(0xC); // IRQ 4
setvect(0xC,countint4);
oldirq9=getvect(0x71); // IRQ 9
setvect(0x71,countint9);
printf("Old vectors: %Fp, %Fp, %Fp\n", oldirq3,oldirq4,oldirq9 );
printf("New vectors: %Fp, %Fp, %Fp\n",
getvect(0xB), getvect(0xC), getvect(0x71) );
printf("ISRs: %Fp, %Fp, %Fp\n", countint3, countint4, countint9 );
tmp=inportb(0x21)&~PIC1mask;
outportb(0x21,tmp); // clear bits for IRQ2,3,4 in PIC1
tmp=inportb(0xA1)&~PIC2mask;
outportb(0xA1,tmp); // clear bit for IRQ9 in PIC2
enable(); // re-enable interrupts
// end interrupt enable
printf("\nPress any key to exit.\n\n");
while( !kbhit() ) {
if ( newcount ) {
printf( "IRQ3=%ld IRQ4=%ld IRQ9=%ld\r",
count3, count4, count9 );
newcount=FALSE;
}
}
getch(); // eat keypress
// turn off our interrupts and reset vectors
disable();
// restore old vectors
setvect(0x0B,oldirq3);
setvect(0x0C,oldirq4);
setvect(0x71,oldirq9);
outportb(0xA1,inportb(0xA1) | PIC2mask );
outportb(0x21,inportb(0x21) | PIC1mask );
printf("\nInterrupt vectors reset to: %Fp %Fp, %Fp\n",
getvect(0xB), getvect(0xC), getvect(0x71) );
enable();
return 0;
}
Copyright © 1998-2003 JKmicrosystems, Inc.
|