Pic-web linux howto

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

v1.00, 14 April 2007
How to program the Pic-web board under Linux.

1. Introduction

This howto shows how to program the Pic-web board from Olimex under Linux. Several example programs are given that illustrate 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 LED

The following program toggles the LED of the pic-web board every second:
#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 ms
void delay_ms(unsigned int ms) {
  while (ms--) {
    delay_1ms();
  }
}

// initialize board
void init_board(void) {
  TRISDbits.TRISD5=0; // configure PORTD5 for output (LED)
}

void main() {
  init_board();
  LATDbits.LATD5=1; // switch LED on
  while (1) {
    delay_ms(1000);
    LATDbits.LATD5^=1; // toggle LED
  }
}
Compile the program using SDCC:
sdcc -mpic16 -p18f452 led.c
Install the program on your pic-web board using the bootloader python front-end:
pytbl.py -f led.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 should now see the LED blink on your board.

5. Using the button

The following program toggles the LED every time the button is pressed and makes use of interrupts:
#include <pic18f452.h>

// high-priority interrupt service routine
void high_isr(void) interrupt 1 {
  if (INTCONbits.INT0F) { // button pressed
    INTCONbits.INT0F=0;
    LATDbits.LATD5^=1; // toggle LED
  }
}

// initialize board
void init_board(void) {
  TRISBbits.TRISB0=1; // configure PORTB0 for input (button)
  TRISDbits.TRISD5=0; // configure PORTD5 for output (LED)
  RCONbits.IPEN=1; // enable interrupt priority levels
  INTCONbits.GIE=1; // enable high-priority interrupts
  INTCONbits.INT0E=1; // enable INT0 external interrupt (button)
}

void main() {
  init_board();
  LATDbits.LATD5=1; // switch LED on
  while (1) {}; // wait forever
}
The program can be compiled and installed on the board as in the previous example.
sdcc -mpic16 -p18f452 button.c
pytbl.py -f button.hex -p 6

6. Using the network interface

In order to use the network interface of the pic-web board, we use the open-source Microchip TCP/IP stack. The following program responds to ping request sent to 192.168.2.99:
#include <pic18f452.h>
#include "StackTsk.h"
#include "Tick.h"

// initialize board
void init_board(void) {
  TRISD_RD5=0; // configure PORTD5 for output (LED)
}

APP_CONFIG AppConfig;

// initialize TCP/IP stack config
void init_appconfig(void) {
	// init MAC address
  AppConfig.MyMACAddr.v[0]=0x00;
  AppConfig.MyMACAddr.v[1]=0x04;
  AppConfig.MyMACAddr.v[2]=0xa3;
  AppConfig.MyMACAddr.v[3]=0x00;
  AppConfig.MyMACAddr.v[4]=0x00;
  AppConfig.MyMACAddr.v[5]=0x00;
  // init IP address
  AppConfig.MyIPAddr.v[0]=192;
  AppConfig.MyIPAddr.v[1]=168;
  AppConfig.MyIPAddr.v[2]=2;
  AppConfig.MyIPAddr.v[3]=99;
  // init subnet mask
  AppConfig.MyMask.v[0]=255;
  AppConfig.MyMask.v[1]=255;
  AppConfig.MyMask.v[2]=255;
  AppConfig.MyMask.v[3]=0;
  // init router
  AppConfig.MyGateway.v[0]=192;
  AppConfig.MyGateway.v[1]=168;
  AppConfig.MyGateway.v[2]=2;
  AppConfig.MyGateway.v[3]=1;
}

void main(void) {
	init_board();
	LATD5=1; // switch LED on
	init_appconfig();
	// initialize TCP/IP stack components
	TickInit();
	StackInit();
	while (1) {
		TickUpdate(); // update tick count
		StackTask(); // let stack manager perform its task
	}
}
To build this program, we must link with files implementing the TCP/IP stack which are customized for the pic-web board and for SDCC. A link to a tarball containing all the required files is included at the end of this howto.
wget http://datrus.com/picweb-howto.tgz
tar zxvf picweb-howto.tgz
cd picweb-howto/ping
make # builds ping.hex using SDCC
pytbl.py -f ping.hex -p 6

7. Sending/receiving UDP packets

The following program toggles the LED every time an UDP packet is received on a given port. An UDP packet is also sent every time the button is pressed.
#include <pic18f452.h>
#include "StackTsk.h"
#include "Tick.h"
#include "UDP.h"
#include <string.h>

// initialize board
void init_board(void) {
  TRISD_RD5=0; // configure PORTD5 for output (LED)
}

APP_CONFIG AppConfig;

// initialize TCP/IP stack config
void init_appconfig(void) {
  // init MAC address
  AppConfig.MyMACAddr.v[0]=0x00;
  AppConfig.MyMACAddr.v[1]=0x04;
  AppConfig.MyMACAddr.v[2]=0xa3;
  AppConfig.MyMACAddr.v[3]=0x00;
  AppConfig.MyMACAddr.v[4]=0x00;
  AppConfig.MyMACAddr.v[5]=0x00;
  // init IP address
  AppConfig.MyIPAddr.v[0]=192;
  AppConfig.MyIPAddr.v[1]=168;
  AppConfig.MyIPAddr.v[2]=2;
  AppConfig.MyIPAddr.v[3]=99;
  // init subnet mask
  AppConfig.MyMask.v[0]=255;
  AppConfig.MyMask.v[1]=255;
  AppConfig.MyMask.v[2]=255;
  AppConfig.MyMask.v[3]=0;
  // init router
  AppConfig.MyGateway.v[0]=192;
  AppConfig.MyGateway.v[1]=168;
  AppConfig.MyGateway.v[2]=2;
  AppConfig.MyGateway.v[3]=1;
}

#define LPORT_SEND 2000 // local UDP port packets are sent from
#define RPORT_SEND 20000 // remote UDP port packets are sent to
#define LPORT_RECV 3000 // local UDP port on which to listen for packets

void main(void) {
  int send_packet=0;
  NODE_INFO rnode;
  UDP_SOCKET sock1,sock2;
  init_board();
  LATD5=1; // switch LED on
  init_appconfig();
  // initialize TCP/IP stack components
  TickInit();
  StackInit();
  rnode.IPAddr.v[0]=192;
  rnode.IPAddr.v[1]=168;
  rnode.IPAddr.v[2]=2;
  rnode.IPAddr.v[3]=2;
  rnode.MACAddr.v[0]=0x00;
  rnode.MACAddr.v[1]=0x13;
  rnode.MACAddr.v[2]=0xce;
  rnode.MACAddr.v[3]=0x67;
  rnode.MACAddr.v[4]=0x58;
  rnode.MACAddr.v[5]=0xde;
  sock1=UDPOpen(LPORT_SEND,&rnode,RPORT_SEND); // socket to send UDP
  sock2=UDPOpen(LPORT_RECV,NULL,INVALID_UDP_SOCKET); // socket to receive UDP
  while (1) {
    TickUpdate(); // update tick count
    StackTask(); // let stack manager perform its task
    if (INTCONbits.INT0F) { // button pressed
      INTCONbits.INT0F=0;
      send_packet=1;
    }
    if (send_packet && UDPIsPutReady(sock1)) { // send UDP packet
      send_packet=0;
      UDPPut('f');
      UDPPut('o');
      UDPPut('o');
      UDPFlush();
    }
    if (UDPIsGetReady(sock2)) { // UDP packet received
      char c;
      if (UDPGet(&c) && c=='f' &&
          UDPGet(&c) && c=='o' &&
          UDPGet(&c) && c=='o') {
        LATD5^=1; // toggle LED
      }
      UDPDiscard();
    }
  }
}
To compile and install the program:
cd picweb-howto/udp
make
pytbl.py -f udp.hex -p 6
The following script can be used to send UDP packets:
#!/usr/bin/perl
use IO::Socket;
$host=$ARGV[0] or die "missing host name\n";
$port=$ARGV[1] or die "missing port\n";
$data=$ARGV[2] or die "missing packet data\n";
$sock=IO::Socket::INET->new(Proto=>"udp",PeerAddr=>$host,PeerPort=>$port) or die "failed to create socket: $!\n";
$sock->send($data) or die "failed to send data\n";
The following script can be used to receive UDP packets:
#!/usr/bin/perl
use IO::Socket;
$port=$ARGV[0] or die "missing port\n";
$sock=IO::Socket::INET->new(LocalPort=>$port,Proto=>"udp") or die "failed to create socket: $@";
print "listening for packets on port $port...\n";
while ($addr=$sock->recv($data,2048,0)) {
  print "received packet: '$data'\n";
}
The following command should toggle the LED on the pic-web board:
./udp_send 192.168.2.99 3000 foo
By running the following command and then pressing the button on the board you should see received UDP packets:
./udp_recv 20000

8. Establishing TCP connections

The following program toggles the LED every time a command is received on an incoming TCP connection. A TCP connection is made to a given destination every time the button is pressed.
#include <pic18f452.h>
#include "StackTsk.h"
#include "Tick.h"
#include "TCP.h"
#include <string.h>

// initialize board
void init_board(void) {
  TRISD_RD5=0; // configure PORTD5 for output (LED)
}

APP_CONFIG AppConfig;

// initialize TCP/IP stack config
void init_appconfig(void) {
  // init MAC address
  AppConfig.MyMACAddr.v[0]=0x00;
  AppConfig.MyMACAddr.v[1]=0x04;
  AppConfig.MyMACAddr.v[2]=0xa3;
  AppConfig.MyMACAddr.v[3]=0x00;
  AppConfig.MyMACAddr.v[4]=0x00;
  AppConfig.MyMACAddr.v[5]=0x00;
  // init IP address
  AppConfig.MyIPAddr.v[0]=192;
  AppConfig.MyIPAddr.v[1]=168;
  AppConfig.MyIPAddr.v[2]=2;
  AppConfig.MyIPAddr.v[3]=99;
  // init subnet mask
  AppConfig.MyMask.v[0]=255;
  AppConfig.MyMask.v[1]=255;
  AppConfig.MyMask.v[2]=255;
  AppConfig.MyMask.v[3]=0;
  // init router
  AppConfig.MyGateway.v[0]=192;
  AppConfig.MyGateway.v[1]=168;
  AppConfig.MyGateway.v[2]=2;
  AppConfig.MyGateway.v[3]=1;
}

#define RPORT_SEND 20000 // remote TCP port packets are sent to
#define LPORT_RECV 3000 // local TCP port on which to listen for packets

void main(void) {
  int send_packet=0;
  TCP_SOCKET sock1,sock2;
  NODE_INFO rnode;
  init_board();
  LATD5=1; // switch LED on
  init_appconfig();
  // initialize TCP/IP stack components
  TickInit();
  StackInit();
  rnode.IPAddr.v[0]=192;
  rnode.IPAddr.v[1]=168;
  rnode.IPAddr.v[2]=2;
  rnode.IPAddr.v[3]=2;
  rnode.MACAddr.v[0]=0x00;
  rnode.MACAddr.v[1]=0x13;
  rnode.MACAddr.v[2]=0xce;
  rnode.MACAddr.v[3]=0x67;
  rnode.MACAddr.v[4]=0x58;
  rnode.MACAddr.v[5]=0xde;
  sock1=TCPListen(LPORT_RECV);
  while (1) {
    TickUpdate(); // update tick count
    StackTask(); // let stack manager perform its task
    if (INTCONbits.INT0F) { // button pressed
      INTCONbits.INT0F=0;
      send_packet=1;
      sock2=TCPConnect(&rnode,RPORT_SEND);
    }
    if (send_packet && TCPIsPutReady(sock2)) { // send data over TCP
      TCPPut(sock2,'f');
      TCPPut(sock2,'o');
      TCPPut(sock2,'o');
      TCPFlush(sock2);
      TCPDisconnect(sock2);
      send_packet=0;
    }
    if (TCPIsGetReady(sock1)) { // read data over TCP
      char c;
      if (TCPGet(sock1,&c) && c=='f' &&
          TCPGet(sock1,&c) && c=='o' &&
          TCPGet(sock1,&c) && c=='o') {
        LATD5^=1;
      }
      TCPDiscard(sock1);
    }
  }
}
To compile and install the program:
cd picweb-howto/tcp
make
pytbl.py -f tcp.hex -p 6
The following script can be used to make a TCP connection and send data over the connection:
#!/usr/bin/perl
use IO::Socket;
$host=$ARGV[0] or die "missing host name\n";
$port=$ARGV[1] or die "missing port\n";
$data=$ARGV[2] or die "missing packet data\n";
$sock=IO::Socket::INET->new(Proto=>"tcp",PeerAddr=>$host,PeerPort=>$port) or die "failed to create socket: $!\n";
print $sock $data;
close $sock
The following script can be used to accept TCP connections and read data over the connection:
#!/usr/bin/perl
use IO::Socket;
$port=$ARGV[0] or die "missing port\n";
$sock=IO::Socket::INET->new(LocalPort=>$port,Proto=>"tcp",Listen=>5,Reuse=>1) or die "failed to create socket: $@";
print "listening for connections on port $port...\n";
while (1) {
  $sock2=$sock->accept();
  $sock2->read($data,4096);
  print "new connection: '$data'\n";
  close $sock2;
}
The following command should toggle the LED on the pic-web board:
./tcp_send 192.168.2.99 3000 foo
By running the following command and then pressing the button on the board you should see incoming TCP connections:
./tcp_recv 20000

9. Using external Flash memory

The following program counts the number of times the button is pressed and stores the result in flash memory. When the count reaches 10, the LED is toggled and the counter is reset.
#include <pic18f452.h>
#include "StackTsk.h"
#include "XEEPROM.h"

// initialize board
void init_board(void) {
  TRISD_RD5=0; // configure PORTD5 for output (LED)
}

void main(void) {
  XEE_ADDR addr;
  unsigned char count;
  init_board();
  LATD5=1; // switch LED on
  XEEInit(EE_BAUD(CLOCK_FREQ,400000)); // init EEPROM access routines, use ~ 400 KHz
  while (1) {
    if (INTCONbits.INT0F) { // button pressed
      INTCONbits.INT0F=0;
      addr=1234; // address of counter in EEPROM
      XEEBeginRead(EEPROM_CONTROL,addr);
      count=XEERead(); // read old counter value
      XEEEndRead();
      if (count>=9) {
        count=0;
        LATD5^=1;
      } else {
        count++;
      }
      XEEBeginWrite(EEPROM_CONTROL,addr);
      XEEWrite(count); // write new counter value
      XEEEndWrite();
    }
  }
}
To compile and install the program:
cd picweb-howto/flash
make
pytbl.py -f flash.hex -p 6
To test the program, you can for example press the button 7 times, reset the board and then press the button 3 more times to toggle the LED.

10. References