V25 I/O Ports | 386Ex I/O Ports |
LCD Driver and File Streams | Using DOS and BIOS Interrupts |
Interrupt Service Routines for IRQs on the 386Ex | |
C Programming Environments |
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.
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; }
The following sample program configures all pins of PORT A as inputs then
continually reads and displays the status of these pins. 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. 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. 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.
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
Flashlite386 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. 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 V25 uses a superset of the 8086
instruction set and any of the 286/386/etc. instructions will not work. The
386Ex uses the 386 instruction set. Floating point math also is an issue. The
Flashlites 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. It is best to force the
compiler to generate code using floating point emulation.
See our pdf document Getting Started with the
Borland IDE for a step by step guide to starting projects using the
Borland 4.52 IDE.
386Ex Ports and Port I/O
The I/O ports on the Flashlite 386Ex
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.
/* 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.
#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 usefull */
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.
/* 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.
// 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;
}
C Programming Environments
You do not need
any special compilers or linkers to generate executables for the Flashlite
computers. We have compiled many C programs using Turbo C 2.0 and Borland
C/C++ 4.52.