QwikFlash linux howto

David Janssens, dja@info.ucl.ac.be

v1.00, 24 April 2007
How to program the QwikFlash board under Linux.

1. Introduction

This howto shows how to program the QwikFlash board under Linux. A few example programs are given to show how the board can be used.

2. Fetching development tools

Install GPUTILS, which contains the PIC assembler:
wget http://ovh.dl.sourceforge.net/sourceforge/gputils/gputils-0.13.4.tar.gz
tar zxvf gputils-0.13.4.tar.gz
cd gputils-0.13.4
./configure
make
make install
Install SDCC, which contains the PIC C compiler:
wget http://ovh.dl.sourceforge.net/sourceforge/sdcc/sdcc-src-2.6.0.tar.gz
tar zxvf sdcc-src-2.6.0.tar.gz
cd sdcc
./configure
make
make install
Get the Tiny PIC bootloader, which is used to program the PIC via serial port:
wget http://www.etc.ugal.ro/cchiculita/software/tinybld193.zip
unzip tinybld193.zip -d tinybld
Install Python Tiny PIC bootloader, which is a python interface to communicate with the bootloader from Linux:
wget http://www.cihologramas.com/contrib/pytbl.py

3. Installing the bootloader

In order to be able to program the pic-web board via the serial port, we must first install the bootloader.
Cutomize the bootloader for the pic-web board (clock freq, config bits, delay):
cd tinybld/picsource/18f
ed tinybld18F.asm
,s/xtal EQU 20000000/xtal EQU 40000000/
,s/_HS_OSC_1H/_HSPLL_OSC_1H/
,s/movlw xtal\/2000000/movlw xtal\/400000/
wq
The first change modifies the clock frequency from 20Mhz to 40Mhz. The second change sets the config bit so that the internal PLL is used; it transforms the 10Mhz quartz frequency to an internal 40Mhz clock. The third change increases the delay after reset of the bootloader from 1s to 5s. This makes it more convenient to install new programs on the PIC.
Now compile the bootloader:
gpasm tinybld18F.asm
This produces a file named tinybld18F.hex which contains the compiled bootloader.
Now we can install the bootloader on the PIC using an external Microchip ICD2 programmer device for example. This needs to be done only once and can be done using the Microchip MPLAB development environment.

4. Using the LEDs and pushbutton

The following program toggles the LEDs every time the button is pressed:
#include <pic18f452.h>

// initialize board
void init_board(void) {
  ADCON1=0x7; // PORTA used for digital I/O
  TRISAbits.TRISA1=0; // configure PORTA1 for output (right LED)
  TRISAbits.TRISA2=0; // configure PORTA2 for output (center LED)
  TRISAbits.TRISA3=0; // configure PORTA3 for output (left LED)
  TRISDbits.TRISD3=1; // configure PORTD3 for input (pushbutton)
}

void main() {
  int button_down=0;
  init_board();
  // switch LEDs on
  LATAbits.LATA1=1;
  LATAbits.LATA2=1;
  LATAbits.LATA3=1;
  while (1) {
    if (!PORTDbits.AD3) { // is button pressed?
      if (!button_down) {
        // toggle LEDs 
        LATAbits.LATA1^=1;
        LATAbits.LATA2^=1;
        LATAbits.LATA3^=1;
      }
      button_down=1;
    } else {
      button_down=0;
    }
  }
}
Compile the program using SDCC:
sdcc -mpic16 -p18f452 button.c
Install the program on your pic-web board using the bootloader python front-end:
pytbl.py -f button.hex -p 6
Use the appropriate serial port number for the -p option. Just after issuing the previous command, reset the pic-web board (the bootloader waits for commands a couple of seconds after reset and then launches the installed program).
You can test the program by pressing the pushbutton, which will toggle the LEDs.

5. Using timer interrupts

The following program toggles the LEDs every second and makes use of interrupts:
#include <pic18f452.h>

#define CLOCK_FREQ 40000000 // 40 Mhz
#define EXEC_FREQ CLOCK_FREQ/4 // 4 clock cycles to execute an instruction

// initialize board
void init_board(void) {
  ADCON1=0x7; // PORTA used for digital I/O
  TRISAbits.TRISA1=0; // configure PORTA1 for output (right LED)
  TRISAbits.TRISA2=0; // configure PORTA2 for output (center LED)
  TRISAbits.TRISA3=0; // configure PORTA3 for output (left LED)
  TRISAbits.TRISA4=0; // configure PORTA4 for output (alive LED)

  T0CONbits.T08BIT=0; // use timer0 16-bit counter
  T0CONbits.T0CS=0; // use timer0 instruction cycle clock
  T0CONbits.PSA=1; // disable timer0 prescaler
  T0CONbits.TMR0ON=1; // enable timer0

  RCONbits.IPEN=1; // enable interrupt priority levels
  INTCONbits.GIE=1; // enable high-priority interrupts
  INTCONbits.T0IE=1; // enable TMR0 interrupt
  INTCONbits.T0IF=0; // clear timer0 overflow bit
  INTCON2bits.T0IP=1; // set TMR0 interrupt priority to high
}

// set timer counter so that it will overflow in 1 millisecond, which will result in an interrupt being raised
void set_timer_counter(void) {
  TMR0H=(0x10000-EXEC_FREQ/1000)>>8;
  TMR0L=(0x10000-EXEC_FREQ/1000)&0xff;
}

long time=0;

// high-priority interrupt service routine
void high_isr(void) interrupt 1 {
  if (INTCONbits.T0IF) { // timer interrupt was raised
    time++;
    if (time==1000) {
      time=0;
      // toggle LEDs
      LATAbits.LATA1^=1;
      LATAbits.LATA2^=1;
      LATAbits.LATA3^=1;
      LATAbits.LATA4^=1;
    }
    INTCONbits.T0IF=0;
    set_timer_counter();
  }
}
void main() {
  init_board();
  // switch LEDs on
  LATAbits.LATA1=1;
  LATAbits.LATA2=1;
  LATAbits.LATA3=1;
  LATAbits.LATA4=0;
  set_timer_counter();
  while (1) {} // wait forever
}
The program can be compiled and installed on the board as in the previous example.
sdcc -mpic16 -p18f452 led.c
pytbl.py -f led.hex -p 6

6. Using the LCD

The following program displays "Hello world!" on the LCD:
#include <pic18f452.h>

#define CLOCK_FREQ 40000000 // 40 Mhz
#define EXEC_FREQ CLOCK_FREQ/4 // 4 clock cycles to execute an instruction

// wait for approx 1ms
void delay_1ms(void) {
  TMR0H=(0x10000-EXEC_FREQ/1000)>>8;
  TMR0L=(0x10000-EXEC_FREQ/1000)&0xff;
  T0CONbits.TMR0ON=0; // disable timer0
  T0CONbits.T08BIT=0; // use timer0 16-bit counter
  T0CONbits.T0CS=0; // use timer0 instruction cycle clock
  T0CONbits.PSA=1; // disable timer0 prescaler
  INTCONbits.T0IF=0; // clear timer0 overflow bit
  T0CONbits.TMR0ON=1; // enable timer0
  while (!INTCONbits.T0IF) {} // wait for timer0 overflow
  INTCONbits.T0IF=0; // clear timer0 overflow bit
  T0CONbits.TMR0ON=0; // disable timer0
}

// wait for some milliseconds
void delay_ms(unsigned int ms) {
  while (ms--) {
    delay_1ms();
  }
}

// send byte to lcd
void send_lcd_byte(char c) {
  // send upper 4 bits of char
  LATEbits.LATE1=1; // set clock to 1
  PORTD=c;
  delay_ms(1);
  LATEbits.LATE1=0; // set clock to 0
  delay_ms(1);
  // send lower 4 bits of char
  LATEbits.LATE1=1; // set clock to 1
  PORTD=c<<4;
  delay_ms(1);
  LATEbits.LATE1=0; // set clock to 0
  delay_ms(1);
}

// print string on lcd
// pos_code is a position code specific to the HD44780
//   line1: 0x80-0x87 (left to right)
//   line2: 0xc0-0xc7 (left to right)
void print_lcd(char pos_code, char *str) {
  char *c;
  LATEbits.LATE0=0; // set register select to 0 (control)
  send_lcd_byte(pos_code);
  LATEbits.LATE0=1; // set register select to 0 (data)
  for (c=str; *c; c++) {
    send_lcd_byte(*c);
  }
}

const char lcd_str[]={0x33,0x32,0x28,0x01,0x0c,0x06,0x00}; // lcd initialization string

// setup lcd by sending initialization string
void init_lcd() {
  char *c;
  LATEbits.LATE0=0; // set register select to 0 (control)
  for (c=lcd_str; *c; c++) {
    send_lcd_byte(*c);
  }
}

void init_board(void) {
  ADCON1=0x7; // PORTE used for digital I/O
  TRISEbits.TRISE0=0; // configure PORTE0 for output (lcd register select)
  TRISEbits.TRISE1=0; // configure PORTE1 for output (lcd clock)
  TRISDbits.TRISD4=0; // configure PORTD4 for output (lcd nibble interface)
  TRISDbits.TRISD5=0; // configure PORTD5 for output (lcd nibble interface)
  TRISDbits.TRISD6=0; // configure PORTD6 for output (lcd nibble interface)
  TRISDbits.TRISD7=0; // configure PORTD7 for output (lcd nibble interface)

  delay_ms(100); // wait for 0.1s to get past lcd's power-on reset time
  init_lcd();
}

void main() {
  init_board();
  print_lcd(0x80,"Hello");
  print_lcd(0xc0,"World!");
  while (1) {} // wait forever
}
To compile and install the program:
sdcc -mpic16 -p18f452 lcd.c
pytbl.py -f lcd.hex -p 6
The source code of all these examples is contained in the tarball linked at the end of this howto.

7. References