Pic-web linux howto
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