[ Homepage ]
[ What's new ] [ Contact ] [ Sitemap ] [ Forums ] [ Top Menu ]
[ Products ]
[ Documentation ]
[ Downloads ]
[ Sales ]
[ JK Alliance ]
[ FAQ ]
[ Proven Solutions ]
           

Programming in C

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,&regs, &regs);    /* 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,&regs, &regs); /* do the software int */
         regs.h.ah=1;
         regs.h.al=2;           /* set the range  (0-5V) */
         regs.x.bx=0;
         int86 (IO_INT,&regs, &regs); /* do the software int */
         regs.h.ah=1;
         regs.h.al=3;           /* get the data */
         int86 (IO_INT,&regs, &regs); /* 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;
}

Home | Products | Company | Order
Copyright © 1998-2003 JKmicrosystems, Inc.