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.
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.
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...
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!
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.
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.
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.
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.
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).
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.
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);
}
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.
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 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:
As you can see, no jitter at all!
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...
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 :)
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!
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).
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.
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 :)
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.
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)
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.
Ken asked me about generalising quadrature for higher precission input devices.