Pic Tricks

The Warp-13 PIC programmer

My programmer is the Warp-13 from NewFound Electronics. It's quite small and handles a wide range of pics. I got it for my birthday (actually, I ordered it and bought it myself, using my girlfriend's credit card. Am I sad or what?). It's very simple to set up, requiring a standard rs232 serial port and about 15 volts(although looking at the circuit it appears to be regulated down to 5 and 8 volts). I'm using a 12V wall-wart without problems(they produce around 15V when unloaded).

Because my powerbook doesn't have any external serial ports I'm using a keyspan USB->RS232 adapter I won at linux.conf.au 2000.

warp13 pic programmer

downloader software: tm4

For reasons best known to the designer, it took me considerable effort to determine if there was linux support for the Warp-13. Eventually I got hold of Jim Robertson of Newfound electronics and twisted his arm. He seems to have something against free software, although exactly what I couldn't determine (Oh well, I bought his card anyway). He spent a lot of time telling me that the driver is in a poor state etc. etc. Anyway, it turns out there is a Linux driver. I've cleaned it up a tad (the original had a few oddities and random \r chars spattered through. Nothing significant):

The driver worked first go - I burnt in a .hex file provided by Ken Sheridan and his interrupt driven blinking lights program worked. I can't see much needs to be added to the downloader, although I may have to modify it to tie it into sdcc neatly. (later:) Well sdcc's output .hex files seem to work fine. There really isn't much to do here.

C compiler: sdcc

I'm using a recent (20020820) version of sdcc . It compiled without hassle and installed cleanly. I then ran it through its paces to see if I could generate .hex files. Nope, the files appear to be ihx files. Hmmmm...

Ok, read the manual. Ok, I needed to use the PIC target (I knew that, but some how forgot in the excitement of testing...) -mpic14 . This sets the target to 14bit instruction pic chips, of which my 16f876 is one. The compiler has a few uglies, such as requiring the user to run a perl script to generate the profile header. This wasn't helped by the fact that it needed gpasm, and I had gputils... (Here's a patch)

A bit more fiddling and I have my very first .hex file! Unfortunately the hexfile only executes inside the CPU - I haven't worked out i/o, so it's not terribly useful yet.

I've joined the mailing list and now to try something interesting...

First program

Here is my first attempt at a program:

#define __16F876
#include "pic16f876.h"

unsigned char count;

void main() {
   TRISB = 0;
   count = 0;
   while(1) {
     PORTB = count;
     count ++;
   }
}

Sadly, it don't work. not sure why - looking at the asm:

;--------------------------------------------------------
; code
;--------------------------------------------------------
;	.area CSEG    (CODE)
;***
;  pBlock Stats: dbName = M
;***
;entry:  _main	;Function start
; 2 exit points
;has an exit
;; Starting pCode block
_main	;Function start
; 2 exit points
	BSF	_STATUS,5    # Switch to buffer 2 in memory
;#CSRC	simple.c 7
;  TRISB = 0;
	CLRF	_TRISB
	BCF	_STATUS,5    # Switch back again :)
;#CSRC	simple.c 8
;  count = 0;
	CLRF	_count       # Start with 0 count (not really needed - it should wrap pretty darned quick
_00106_DS_
;#CSRC	simple.c 10
;  PORTB = count;
	MOVF	_count,W     # No idea what this W does
	MOVWF	_PORTB       # some kind of accumulator?
;#CSRC	simple.c 11
;  count ++;
	INCF	_count,F     # next!
	GOTO	_00106_DS_   # go back to the port write
	RETURN	             # never get here.
; exit point of _main
	end

The symptoms are oscilations being picked up by my buffer driving the leds. The actual port pins are so weakly held that the oscilloscope probe pulls them down (10* probes have a resistor in them, I think). This implies that the pins are in high impedance state - thus TRISB looks like it isn't being set. It's nice that the simulator matches reality.

(2 hours later): Ok, I worked out what the problem is.. I needed to set my __CONFIG word correctly for the oscilator. The simulpic version still doesn't show the output changing... but...

Here is the code to make that work:

// How do I set the __CONFIG word?  Bingo, M.Fragni, libero.it gives it in one of his questions:
typedef unsigned int word;
word at 0x2007  __CONFIG = _CP_OFF & _WDT_OFF & _BODEN_ON & \
         _PWRTE_ON & _HS_OSC & _WRT_ENABLE_ON & \
         _LVP_OFF & _DEBUG_OFF & _CPD_OFF;
//Should look like this: 0x3f72;

And of course the warp13 donwloader says:

TM4 programmer ROM Version 13.11897.255255
Reading object file simple.hex ...done
Highest address used is 0009
Configuration word is 3f72 <---- Yay!

My first successful program

picture of successful counting output

This is two led's (0 and 4) pins on the digiscope. You can see that led 0 is blinking like nobody's business, whereas led 4 is much more sedate. (We don't need no stinking storage scope)

The leds are going very fast because my timing is all done by the cpu busy waitings (for 0 cycles :). I can slow the blinking down by using a busy loop, but that means I'm executing say 10000 instructions without doing anything useful. Now, ken's cute little program used the timer to organise the outputs. That's what we'll be looking at in the next program.

Slower blinking lights

I haven't made much progress here yet - here is my program to date:

#define __16F876
#include "pic16f876.h"

typedef unsigned int word;
word at 0x2007  __CONFIG = _CP_OFF & _WDT_OFF & _BODEN_ON & \
         _PWRTE_ON & _HS_OSC & _WRT_ENABLE_ON & \
         _LVP_OFF & _DEBUG_OFF & _CPD_OFF;

unsigned char count, x;
unsigned char ms_delay;

void timer_intr() interrupt 0 {
  ms_delay++;
  PIR1 = 0;
}

void main() {
  NOT_RBPU=0;
  T2CON=0x7f;
  INTCON=0xc0;
  PIR1 = 0;
  PIE1 = 2;
  PR2 = 200;
  
  TRISB = 0;
  count = 0;
  while(1) {
    count = TMR2;
    x = PIR1; // XXX
    PORTB = (count & 0xf0) | (x & 0xf);
  }
}

To debug what is going on here I have resorted to a very primitive form of printf debugging. I am outputing a nibbles worth of the timer 2 counter register to port b, and I use the other nibble to output the part of a register I'm interested in.

In this example I convinced myself that PIR1's second bit was being set by reading the value at the line marked with XXX. This read occured each time around the loop (which I know is executing because I can see the funny square wave coming out in the high nibble).

Thus, the timer2 interrupt is being generated, and should be moving to the next stage, namely, the PEIE bit. Checking this should be a cinch, using the same technique. However, for some reason using INTCON instead of PIR1 causes the assembler to barf with this:

 *** Saved 1 registers ***
No registers saved on this pass
simple.asm:244:Error [116] Value of symbol "_INTCON" differs on second pass
 pass 1=11,  pass 2=38
simple.asm:273:Error [116] Value of symbol "_INTCON" differs on second pass
 pass 1=11,  pass 2=38

Looking through the asm I can see:

@237
  cblock  0X0020	; Bank 0
	SSAVE
	WSAVE
	r0x53
	_ms_delay
	_count
	_x
	_INTCON
  endc
@273
_INTCON	EQU	0x026
@280
INTCON	EQU	0x00b

No idea what the cause of this is, but it is again bed time. It looks like _INTCON is actually somekind of local variable. It should be a static register.

Ok found a work around - I can read and write (at least, I think I can) the bits GIE and PEIE directly.

void main() {
  NOT_RBPU=0;
  T2CON=0x7f;
  GIE = 1;
  PEIE = 1;
  //INTCON=0xc0;
  PIR1 = 0;
  PIE1 = 2;
  PR2 = 200;
  
  TRISB = 0;
  count = 0;
  while(1) {
    count = TMR2;
    x = PEIE;
    x = x << 1;
    x |= GIE;
    PORTB = (count & 0xf0) | (x & 0xf);
  }
}

Even forcing these two bits high each time through doesn't seem to work... Now it really is time for bed.

Bug in sdcc #1

It looks like INTCON is not considered the special register is meant to be, If I hack the asm to only talk about INTCON @ 0xb, then I can read the value quite successfully.

Interrupts are go!

Ok, knowing that it was INTCON that was broken, rather than interrupts made things a lot easier! The working code demonstrates a timer that counts steadily and writes the value to the top nibble of port b. The lower nibble is currently set to the enable register - I just wanted to see if that was changing.

Serial fun

My first serial programming challenge is to get a form of printf working. This will be extremely useful for debugging (ok, it's not quite gdb, but being able to dump state is probably the most useful thing to get working).

Out

The first stage is to get a single character out. Not much to it - simply follow the instructions in the docs. We set up the appropriate bits first:

// Configure UART serial transmit

SPBRG = 25; // 16MHz => 9600 baud
BRGH = 0;
SYNC = 0;
SPEN = 1;
TXIE = 1;

// enable transmission
TXEN = 1;
TXREG = 'n'; // Actually emit our char.

In this case we don't actually need to catch the interrupts because we're only emitting one char. However, to catch interrupts we just modify the previous interrupt handler:

void timer_intr() interrupt 0 {
  if(TXIF) { // serial interrupt
    // Most interrupts must be cleared in software:
    // TXIF = 0; // not doable :)
    TXREG = 'n';
    TXIE = 0;
  } 
}

The next step is to write a print string command. The idea is to have a small, circular, buffer of say 16 bytes which you can add chars to be output. To start with, I'm treating the output buffer as a once off buffer.

char ring[16];
unsigned char ring_pointer = 0;

void print(char* p) {
  unsigned char i = 0;
  unsigned char c;
  //while(TXIE); // wait for interrupts to finish last message.
  // May as well start transmitting first char.
  while(c = *p) {
    ring[i] = c;
    i++;
    p++;
  }
  ring[i] = 0; // terminate
  ring_pointer = 0;
  c = ring[0];
  if(c) {
    ring_pointer++; // Only move forward if there is a char there
    TXIE = 1;
    TXREG = c;
  }
}

Doesn't work - I seem to just get a high byte (0xcf). Will leave for now.

incoming serial

Ok, here's a simple toy: read a char from the serial port and write it to port B. All the work is done in the interrupt handler:

void receive_intr() interrupt 0 {
  unsigned char rst;
  if(RCIF) { // serial received
    rst = RCSTA;
    // check for errors here?
    PORTB = RCREG;
    // if we got an error we should clear CREN
    CREN = 0;
    // re-enable receiveing
    CREN = 1;
  }
}

The main program just initialises the serial port for the appropriate speed and waits. (I should try using a sleep command here - Ok, looked it up, SLEEP turns the UART off (not in synchronous mode though, but I don't have a synchronous USB serial port...).)

void main() {
  NOT_RBPU=0;
  
  // Configure UART serial transmit
  
  SPBRG = 25; // 16MHz => 9600 baud
  BRGH = 0;
  SYNC = 0;
  SPEN = 1;
  RCIE = 1;
  CREN = 1;
  
  GIE = 1;
  PEIE = 1;
  
  TRISB = 0;
  while(1);
}

New Makefile

Martin Dubuc (m.dubuc rogers (fullstop) com) sent me this makefile:

INCLUDE=-I/usr/local/share/sdcc/include/pic
PROCESSOR_FLAG=-mpic14
CC=sdcc
AS=gpasm
CFLAGS=-S

HEX_FILES=simple.hex

all: $(HEX_FILES)

%.hex: %.c
	$(CC) $(INCLUDE) $(PROCESSOR_FLAG) $(CFLAGS) $<
	$(AS) $*.asm

clean:
	$(RM) *.asm *.cod *.lst *.hex *.p *.d *.lnk *~

It handles .rel automagically(although there are other errors still generated by the assembler. I am just compiling simple.c here, you just need to change the HEX_FILES define for more files. If you don't understand Makefiles, here is a lecture I gave to our first years.

A simple servo controller

What got me started on PIC programming was the need for a servo controller for my DIY zoning project. I figured that $200 for an 8 port servo controller driven by RS232 was a tad excessive and that I could build one much cheaper if I used a PIC chip. Of course, my programmer cost $200, but at least I can make more than one servo controller....

What is a servo controller? Some quick googling(funny how a rarely used word for an even more rarely occuring number(10^100) became a verb so quickly!) finds that a servo is mostly likely to be controlled with a repeated pulse control.

The pulse is required to occur at least once every 20ms, but more often is fine. The pulse's width is somewhere between 1ms and 2ms, 1.5ms being neutral, or the position the servo moves to when it is powered but uncontrolled.

My servo controller will be a simple affair, it will cycle through 8 port pins, turning each one on for its appropriate time, then off, moving to the next pin. I'm planning to use timer2 to provide the timebase, avoiding trickier isochronous code.

To change values some simple serial protocol will be needed. Somthing like a digit for which device to change, followed by the new value as a pair of hex digits. I'll worry about that after I get my simple version working.

The simple version

The simple version is a single pin driven contrinuously up and down at the frequency that corresponds to the appropriate timebase. I can't find anything that says this won't work, and it is significantly simpler than the final project.

The code to make it work is quite simple:

#define __16F876
#include "pic16f876.h"

typedef unsigned int word;
word at 0x2007  __CONFIG = _CP_OFF & _WDT_OFF & _BODEN_ON & \
         _PWRTE_ON & _HS_OSC & _WRT_ENABLE_ON & \
         _LVP_OFF & _DEBUG_OFF & _CPD_OFF;

unsigned char angle;
unsigned char pin = 1;

void general_intr() interrupt 0 {
  if(TMR2IF) {
    PORTB = pin;
    //pin = pin << 1;
    if(pin == 0) // eventually the program will pad with extra time here
      pin = 1;
    else
      pin = 0;
    TMR2IF = 0;
    PIR1 = 0;
    PEIE = 1;
  }
  if(RCIF) { // serial received
    //rst = RCSTA;
    // check for errors here
    if(OERR) {
      // if we got an error we should clear CREN
      CREN = 0;
      // re-enable receiveing
      CREN = 1;
    } else {
      angle = RCREG;
    }
  } 
}

void main() {
  NOT_RBPU=0;
  
  // Configure UART serial receive
  
  SPBRG = 25; // 16MHz => 9600 baud
  BRGH = 0;
  SYNC = 0;
  SPEN = 1;
  RCIE = 1;
  CREN = 1;
  
  GIE = 1;
  PEIE = 1;
  
  //Configure timer 2
  
  T2CON=0x03;
  TMR2ON = 1;
  GIE = 1;
  PEIE = 1;
  PIR1 = 0;
  PIE1 = 2;
  PR2 = 255; // 1ms = 0 degrees.
  
  TRISB = 0;
  while(1) {
  }
}

You can see where I'm headed from the commented out lines. The pulse generated by this code is almost exactly 1ms! Thus if I connect this to the control pin on my servo it should swivel around to the 0 degree mark.

That's exactly what it did. However, the servo makes an annoying buzzing noise and refuses to shut up. What's wrong with it? Well my first thought was jitter in the interrupts. This seems odd as the pin toggling has a fixed length code path from the start of the interrupt. I asked Ken if he knew of anything causing jitter, he didn't know of anything. I asked Mark if he knew. He didn't either.

I performed some simple tests (if you have the equipment anyway :). I set the oscope to trigger on my output pulses. It triggers on the rising pulse. I then move the trigger back 1ms (On the TDS 220 you just move the horizontal position knob). Then I zoom in on the downward edge:

The pic's output pin, driven by timer2 interrupts.  click to zoom in on falling edge

As you can see, no jitter at all!

the signal on the control line of the servo (superimposed on the control pin signal)

Looking at the servo control pin on the other hand and I see lots of noise and ringing! That's probably the culprit. My drive circuit, a 74HCT541 and a 2K2 resistor, aren't pumping enough juice out. Add to that the fact that my servo motor supply is sagging. To fix it is a matter of putting enough capacitance onto the supply line, directly driving the control line, and probably making better connections everywhere(resistor leads jammed into the servo plug isn't exactly a good electrical connection :).

I'm giving up on this one for a while, I'll find out what the right way to drive the control line is: it may even be a crap servo to blame. One idea might be to set the servo's position, then turn the power off — that will leave the servo in the right position. If I did that I could use a simple hardware multiplexer to select the servo and power lines. Hmm, I have a tube of PCF8574s...

Back to printf

I'm begining to realise that problems with my code aren't always my fault :) I've found a few cases where SDCC generates incorrect code. Well, in the spirit of free software I shall attempt to track them down and fix them myself, failing that, I've submitted a bug reports.

Anyway, I really want a printf function for debugging, and for telling me how things are going. I'm going to implement a printf to PORTB function. That way I will have an example of serial output on one hand, and an example of buffered io on the other. Then it's a SMOP to make them work together, and numerous hours of debugging :)

blocking IO

Ok, it looks like string handling is currently broken. putc-blocking.c gives an example of writing single chars to the serial port. The putc function takes a single char and returns as soon as the hardware accepts the char. I call this blocking mode because the program has to wait until the serial haardware is ready to emit the byte. This is non-optimal in many cases because there are useful things that the program could be doing while waiting for the relatively slow serial hardware to click through.

To make non blocking io there needs to be a buffer of chars ready to send. Obviously if this buffer is full then the program still needs to wait, or give up, but the idea is to make the buffer large enough that the majority of prints can return immediately.

In the code you will also see two calls to print(char* p). That's me trying to provide a simpler interface which you could use standard C strings. Neither of those work, which makes me suspect the indirect referencing code. I'll look at these later.

Blocking mode isn't totally useless - a lot of code has the meaty bits implemented in interrupt handlers, the mainloop just busy waits, so replacing those busy waits with a continuous serial dump is quite useful!

Master mode, polled, i2c

This one has been a real struggle. The documentation isn't as helpful as the UART section, mainly because the MSSP is able to do lots of other things, and i2c mastering wasn't considered the most useful of them.

My biggest trouble has been with sdcc's handling of while(FLAG); constructs. It seems to get them wrong about half the time. Insert an extra write operation and the code suddenly becomes wrong. I think this, and the fact that the errors are local, implies it is the peephole optimiser. Bug report once I can simplify the trigger.

For example, compare the following two pieces of code:

void i2c_write(unsigned char x) {
  SSPBUF = x;
  SSPIF = 0;
  while(!SSPIF);
}
;***
;  pBlock Stats: dbName = C
;***
;entry:  _i2c_write	;Function start
; 2 exit points
;has an exit
;; Starting pCode block
_i2c_write	;Function start
; 2 exit points
;#CSRC	simple.c 74
;  void i2c_write(unsigned char x) {
	MOVWF	_SSPBUF
;#CSRC	simple.c 76
;  SSPIF = 0;
	BCF	(_SSPIF >> 3), (_SSPIF & 7)
_00144_DS_
;#CSRC	simple.c 77
;  while(!SSPIF);
	GOTO	_00144_DS_
	RETURN	
; exit point of _i2c_write
void i2c_write(unsigned char x) {
  SSPBUF = x;
  SSPIF = 0;
  while(!(PIR1&8));
}
;***
;  pBlock Stats: dbName = C
;***
;entry:  _i2c_write	;Function start
; 2 exit points
;has an exit
;; Starting pCode block
_i2c_write	;Function start
; 2 exit points
;#CSRC	simple.c 74
;  void i2c_write(unsigned char x) {
	MOVWF	_SSPBUF
;#CSRC	simple.c 76
;  SSPIF = 0;
	BCF	(_SSPIF >> 3), (_SSPIF & 7)
_00144_DS_
;#CSRC	simple.c 77
;  while(!(PIR1&8));//SSPSTAT&4); // R/W bit = bit 2
	BTFSS	_PIR1,3
	GOTO	_00144_DS_
	RETURN	
; exit point of _i2c_write

The second one generates working code. The first one doesn't. Even more weird is that this version:

void i2c_write(unsigned char x) {
  SSPBUF = x;
  while(!SSPIF);
}
;***
;  pBlock Stats: dbName = C
;***
;entry:  _i2c_write	;Function start
; 2 exit points
;has an exit
;; Starting pCode block
_i2c_write	;Function start
; 2 exit points
;#CSRC	simple.c 74
;  void i2c_write(unsigned char x) {
	MOVWF	_SSPBUF
_00144_DS_
;#CSRC	simple.c 77
;  while(!SSPIF);
	BTFSS	(_SSPIF >> 3), (_SSPIF & 7)
	GOTO	_00144_DS_
	RETURN	
; exit point of _i2c_write

Also generates correct code. (well, it doesn't work, but the while loop is generated correctly).

Next day

Ok, I think I know what's going wrong here - the compiler is removing redundant loads :). I need to find out which register bits have side-effects and/or are volatile.

simple compiler for printf

One easy way to make a useful printf for the PIC is to compile printf statements into the minimal code to make the printf useful. I've written such a beast. It converts printf("Hello there: %d %x", alpha, beta); into a slab of code that calls putc in the appropriate places.

Eventually I'll try and move this functionality into the compiler, but this version is here and now and works :)

Servo revisited

I got a separate buffer chip today from ken (on a nice little board and everything :). I plug it in and bingo! the old program worked out of the box.... All I need to do now is provide 8 output ports and use timer1, with its 16bit resolution, and finally, add a simple serial interface to the whole thing.

With the new buffer chip the output is nice and solid. I had to add an extra cap across the power supply of the servo to stop the servo from reseting all the time from power supply droop, but otherwise it seems that it was just a matter of pump enough juice in after all.

Martins 16bit port of SDCC

Martin has put up a nice page of his work on the 16 bit port. I like the looks of his progress. As soon as I get time and an 18f I'll give it a go! (The 18f is essentially the same as the 16f, but has extended operands and more memory and stuff. Martin gives a good summary)

Servo with a vengence

Ok, I spent a lot of time today working out how to make my keyspan 28x work with my pic serial port. The first problem was to use mknod /dev/ttyUSB2 c 2 188 to generate the device file. This created a device with major number 2 and minor number 188, rather than 188 2. Thankfully, the two devices in question - a non-existant kernel memory interface and the pseudo-tty master /dev/ptyac didn't mind me writing random bytes to them.

The second problem was using a cross over cable. The 28x is a dual ported usb to serial converter. I heard Hugh Bleming give an interesting talk on the keyspan serial converters, so I was fairly sure that the driver worked. However, when I used it it seemed to not be transmitting. I eventually put a standard apple serial to serial link cable in and bingo! I could talk to myself (it's not as rare an occurence as you might think). Turned out that the apple din 8 round plug to 25 pin DB25 plug cable (which then plugged into a palm pilot DB25->DB9 converter) had transmit and receive swaped over :) That would explain the lack of signal on Rx1.

Now that I have another serial working separately from my burner I can develop my serial interfaces better. I am using a very similar program for my latest servo controller. It currently only controls a single servo, although it actually puts the same signal out on all of PORTB.

The pic listens on the serial port and sets its output on-time to match the value sent via serial. I've written a little python gtk script to allow me to interact with the servo:

It pretty neat being able to have a little servo motor track the mouse on screen.

Generalised quadrature

Ken asked me about generalising quadrature for higher precission input devices.