|
|
Now my Dad drummed into me from a very early age not to use a broken tool, nor, for that matter, one which was blunt. He also showed me that if I didn’t have the proper tool for a job, then that job was compromised in some small way, and that methods should be sought to work around the problem. Finally, (this was 50’s Britain) he also showed me how to make a new tool from scratch, or modify an existing one to complete the job.
His (and mine at the time) was a largely practical and tangible world, with workpieces and tools made of metal, wood and leather. But I never forgot the lessons.
I recently purchased some 18F6620 PIC microcontrollers at the give-away price of £2.00 per pop. Given that these are currently being sold at around £8.00 by Farnell et al, I thought that this was a bargain. I still do, even after discovering that these 64-pin TQFP animals have a hardware bug where a GOTO located at the RESET vector is either ignored or spuriously interpreted, resulting in a jump into La-La-Land.
After experiencing the problem, and installing test-code, I read the device’s revision ID (it’s 3) and consulted the Microchip documentation. In: DS80129J (PIC18F6620/8620/6720/8720 Rev. A3 Silicon/Data Sheet Errata 2005) page 4, the terrible truth was revealed, and I quote: It has been observed that in certain Reset conditions, including power-up, the first GOTO instruction at address 0×0000 may not be executed. This occurrence is rare and affects very few applications.
Despite taking issue at the last statement above, I read on and discovered that the suggested workaround is very simple - place a NOP before the GOTO at 0×000000.
Fair enough, and indeed taking a local copy of the ‘C’ startup code and carrying out the small hack of inserting a NOP before the 1st GOTO, cures the problem. Job done? Well, not quite.
If you are using an ICSP programmer for your iterative PIC development, then the workaround above is fine. I dislike having this dongle attached to my hardware, and usually program the PIC with a bootloader - my first choice TinyBld, written by Claudiu Chiculita. And yes, you’ve already guessed, TinyBld will not work if it dosn’t find a valid GOTO at the 1st location in memory of your application.
Neither does the command-line clone TinyBld2, nor for that matter the latest bootloader offering from Microchip themselves, the AN1310 Serial Bootloader.
Well, I hear you saying, fix the bootloader!
In the case of the original TinyBld, the PC Application source is not available, however the TInyBld2 command-line clone is, and I downloaded and installed the Watcom-C compiler, created a project in the IDE, dumped in the source-files and it compiled 1st-time. I hacked a work-around and sure-enough, got myself back in business. I then spent some time removing all the scat that the Watcom compiler had decorated my environment variables with, because it’s installation had broken my Qt compiler. Still, job done, but I really prefer a GUI, rather than the command-line, so I turned next to the Microchip offering.
I notice that in the PIC firmware assembly-source, provision is made to specify a NOP for certain other PICs with the same hardware bug, and modifying the assembler-code similarly for the 18F6620, I burned-in the produced HEX and tried the bootloader. Nada, Nothing, Zilch. In fact, if the firmware source was written by the same guy who wrote the PC application, he suffers badly from some sort of cognitive dissonance problem, because the PC application blithely undoes any corrective measures you have put in place to ensure the bootloader firmware boots correctly, by scribbling the same unworthy nonsense at address 0×000000 whatever you do. Moving then to the source of the PC app, I hacked a workaround, but although the NOP and GOTO at vector 0×000000 were being retained correctly the bootloader still refused to work. Close inspection of the produced disassembly showed me why - it’s quite simply rubbish - produced from source that is a mass of assumptions and mis-perceptions, all of which are obfuscated by a sea of conditional-assembly directives. I re-wrote the code and now my 18F6620 bootloader works fine. At reset it jumps, as it should, straight into my application if present, otherwise it executes the bootloader code where it waits for a command on the serial port. When an application is already loaded at reset, invoking the bootloader is the simple invocation of the serial break, followed by a reset of the hardware and pressing the bootloader button - all as documented.
A carpenter has to accept, make provision for, and work around the knots in his timber - when are some tool programmers going to accept that not all PICs do ‘what it says on the tin’?
I’m not detailing the hacks I’ve done to TinyBld2, the PC and firmware sources for AN1310, but I’ve included links to zips of the hacked source-code for all three below. Those interested can do a ‘diff’ with the original sources.
One final point worth mentioning in respect to use of the 18f6620 with the TinyBld firmware and my serial setup (FTDI-USB TTL lead). I had to re-instate the commented-out lines shown below, otherwise TinyBld only booted a ‘tiny’ bit of my apps into the PIC!
writebyte
movf POSTINC0,w ; put 1 byte
movwf TABLAT
tblwt+*
decfsz counter_lo
bra writebyte
movlw b'10000100' ; Setup writes
rcall Write
decfsz counter_hi
bra writesloop
waitwre
; !!! See my note in header - JWB 16th July 2010
; re-instate the following 2 lines of code
btfsc EECON1,WR ;for eeprom writes (wait to finish write)
bra waitwre ;no need: round trip time with PC bigger than 4ms
bcf EECON1,WREN ;disable writes
bra MainLoop
ziieroare ;CRC failed
The hacked PC Source for Microchip AN1310 Serial Bootloader is here: http://picprojects.info/Bootloaders/AN1310_SerialBoot.zip
The hacked firmware source for Microchip AN1310 Serial Bootloader is here: http://picprojects.info/Bootloaders/PIC18Bldr.zip
The hacked PC Source for TinyBld2 command-line Bootloader is here: http://picprojects.info/Bootloaders/Tinybld2_18f6620_hack.zip
The modified TinyBld source (as per modification above) for 18F6620 is here: http://picprojects.info/Bootloaders/Tinybld18f6620.zip
This article is part of a series describing communicating with the Sparkfun VS1103b BOB (Break-out board)
Please note: The project from which this article is taken is still ‘under development’. A full library of routines to drive the VS1103b will be made available when this is completed.
A link to a zip of a working set of the current software is included at the foot of the page.
The code below shows a re-written clone of Microchips WriteSWSPI routine, but now supporting 16-bit word transmit/receive and correcting the sloppy original which demonstrated glitches on the data line.
Using a PIC18F4620 with a 10Mhz crystal and the HSPLL operating, the pulse period is ~2.67us or a frequency of 374.5 Khz.
I’ve included commented-out sections showing calls to the Delay10TCYx delay routine (in delays.h) to indicate where these should be inserted if a lower frequency is required.
/********************************************************************
* Function Name: WriteIntSWSPI *
* Return Value: int: received data (throw away) *
* Parameters: int output : data to transmit *
* Description: Write/Read 16 bit to/from custom SPI port *
* Re-written from Microchip original to *
* support 16 bits *
* JWB 14th June 2010 *
********************************************************************/
//
// JWB 12th May 2010 - NOTE!! Have to add 40 ck cycles delay to each part of waveform
// if using CMOS level-shift as CD40109 not responding to fast waveforms.
//
unsigned int WriteIntSWSPI(unsigned int command, unsigned int output)
{
static unsigned long spi_val;
char BitCount; // Bit counter
spi_val = (long)command << 16; // catenate command, reg addr and data to write into 32-bit value
spi_val |= (unsigned long)output;
BitCount = 32; // ! Important! VS1103b transaction must be an atomic 32-bits
// Mode 0: This MODE is suitable for VS1103b
// SCK idles low
// Data output after falling edge of SCK
// Data sampled before rising edge of SCK
// ====================================================================
// JWB : rather than making the port pin 0, THEN 1 if the data bit is 1
// as in the Microchip-supplied code, test the putative outgoing bit
// properly before writing it to the pin
// ====================================================================
// Set Dout to MSB of data output (confusingly called input!)
if (spi_val & 0x80000000)
SW_DOUT_PIN = 1;
else
SW_DOUT_PIN = 0;
Nop(); // Adjust for jitter
Nop();
do // Loop for bit count
{
// ========================================================================
// JWB Again, rather than writing to the STATUS register TWICE in one case
// and ONCE in the other - test the bit BEFORE writing the STATUS carry bit
// ========================================================================
// Set the carry bit according to the Din pin
if (SW_DIN_PIN)
STATUSbits.C = 1;
else
STATUSbits.C = 0;
SW_SCK_PIN = 1; // Set the SCK pin high
_asm
rlcf spi_val,1,1 // rotate carry into least-sig bit of 'input'
rlcf spi_val+1,1,1
rlcf spi_val+2,1,1
rlcf spi_val+3,1,1
_endasm
Nop(); // Produces a 50% duty cycle clock
Nop();
Nop();
Nop();
Nop();
Nop();
Nop();
Nop();
Nop();
Nop();
Nop();
// Delay10TCYx(4); // set to 40 (4 * 10) ck delays - CMOS 40109 does not responding to higher freq
SW_SCK_PIN = 0; // Set the SCK pin low
// Set Dout to MSB of data output
if (spi_val & 0x80000000)
SW_DOUT_PIN = 1;
else
SW_DOUT_PIN = 0;
BitCount--; // Count iterations through loop for full bitcount
// Delay10TCYx(4); // set to 40 (4 * 10) ck delays - CMOS 40109 does not responding to higher freq
} while (BitCount);
return(spi_val & 0x0000ffff); // Return the received data
}
To control specific hardware, the above code should be suitably wrapped. A wrapper supporting writes to registers on the VLSI VS1103b MIDI synth DSP is shown below. This is taken from the routines supplied in the zip:
#include "sw_spi.h"
#include "VS1103b.h"
////////////////////////////////////////
//
// WriteSCI - Wait until the VS1103b
// signals it is read to receive, then
// Write a 16-bit word to its command
// interface, and/or read a 16-bit word back.
//
// Leaves VS1103b XCS & XDCS lines high
// (inactive)
// JWB 08 May 2010
////////////////////////////////////////
unsigned int WriteSCI (unsigned int command, unsigned int param)
{
unsigned int result;
while (SW_DREQ_PIN == 0); // wait 'til DREQ goes high
SW_XDCS_PIN = 1;
SW_XCS_PIN = 0; // select SCI
result = WriteIntSWSPI(command, param);
SW_XCS_PIN = 1; // de-select SCI
return result;
}
The above is further wrapped, together with a read routine in a procedure called: SCI_Command, provided in the source-code.
An example of using the SCI_Command routine to read a register is given below (my calls to the serial port are for debug purposes only):
#include "sw_spi.h"
#include "VS1103b.h" // software SPI for VS1103b interface
.
.
.
.
union SCI_REG_VAL dummy;
result = SCI_Command(READ_OP, SCI_VOL, dummy);
// output to serial port to observe result
TxSerial ( ( result >> 8 ) & 0x00ff);
TxSerial (result & 0x00ff);
Another example, this time writing LH and RH volume levels into the SCI_VOL register (at address 0xb)
#include "sw_spi.h"
#include "VS1103b.h" // software SPI for VS1103b interface
.
.
.
.
union SCI_REG_VAL sound_val;
sound_val.word_part = 0x407;
result = SCI_Command(WRITE_OP, SCI_VOL, sound_val);
The current source for the project is here. You should add these files to your project, and put the two includes shown above in your client code, as in my two examples above.
If you haven’t got at least an Acrobat reader on your machine, then you won’t be able to see what is embedded below.
This site is under construction - please be patient
Inside the PIC microcontroller, it’s a very ordered world. Almost everthing that happens is regulated by the internal clock. (usually crystal controlled) In our world unfortunately, things tend to happen without any sort of order or regularity. In fact when two events that concern us happen together, the reaction to some of these coincidences is to regard these as ’spooky’. For myself, I’ve never understood why this should so regarded, as there are six billion of us on this planet and therefore phenomena are occurring in a massively-parallel way - but I digress.
In our microcontroller these asynchronous events have to be handled in some way, and in the case of the PIC it is usual for dedicated hardware to handle the event and then inform us that it has occured, in other words generate an interrupt. A lot of beginners are afraid of interrupts, which is a shame because non-use will limit the functionality of your firmware very drastically, and anyway interrupts make handling asynchronicity very easy.
In the following example, use is made of a timer interrupt simply to demonstrate handling an asynchronous event. Purists may argue that this is not an asynchronous event, because it happens at strictly timed intervals. Yes & No. Although the timer events themselves are synchronous, the handling of these may be delayed or postponed altogether by the foreground program, so I believe it is a valid example.
Once again, I am dipping into an example supplied by Microchip for the 44-pin demo board, as it illustrates some of the points above. You will also discover that, as an exemplar, the example is deeply flawed. Because the program is a little bigger than the Hello World example I’ve split it up into sections, each of which I will comment on.
; *******************************************************************
; PICkit 2 Lesson 10 - Interrupts
;
; This shows how to configure and use the Timer 0 interrupt to
; trigger reading the A2D every time Timer 0 overflows.
;
; *******************************************************************
; * See 44-pin Demo Board User's Guide for Lesson Information *
; *******************************************************************
#include <p16F887.inc>
__CONFIG _CONFIG1, _LVP_OFF & _FCMEN_OFF & _IESO_OFF & _BOR_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _INTRC_OSC_NOCLKOUT
__CONFIG _CONFIG2, _WRT_OFF & _BOR21V
cblock 0x20
Delay1 ; Assign an address to label Delay1
Delay2
Display ; define a variable to hold the diplay
Direction
LookingFor
T0Semaphore
endc
; Flag Definitions
cblock 0x70 ; put these up in unbanked RAM
W_Save
STATUS_Save
endc
There are two interesting and important points to note from the above allocation of program variables. In the code that will handle an interrupt, we can’t assume which ram bank is currently in use and we want to keep executable statements to an absolute minimum. Using ram addresses of 0×70 and above for saving the controller STATUS and W registers is a good plan, because irrespective of the contents of the bank registers these addresses are always available. The bald comment ‘put these up in unbanked ram’ dosn’t really cut it as an explanation for a beginner. Although most of the variable names are self-explanatory ‘LookingFor’ begs the question looking for what? The comment ‘Assign an address to label Delay1′ is just plain unnecessary, and brings nothing to the feast. As an example for beginners, the two comments and variable naming noted both fail my ‘just good enough’ test.
org 0
goto Start
org 4
ISR:
movwf W_Save ; Save context
movf STATUS,w
movwf STATUS_Save
; btfsc PIR1,T1IF ; Uncomment to check Timer 1 (routine needed)
; goto ServiceTimer1
btfsc INTCON,T0IF
goto ServiceTimer0
; btfsc PIC1,ADIF ; Uncomment to check the ADC (routine needed)
; goto ServiceADC
goto ExitISR
ServiceTimer0:
bcf STATUS,RP0 ; Ensure ISR executes in Register Bank 0
bcf STATUS,RP1
bcf INTCON,T0IF ; clear the interrupt flag. (must be done in software)
bsf T0Semaphore,0 ; signal the main routine that the Timer has expired
bsf ADCON0,GO_DONE ; start conversion
btfss ADCON0,GO_DONE ; this bit will change to zero when the conversion is complete
goto $-1
comf ADRESH,w ; Form the 1's complement of ADresult
movwf TMR0 ; Also clears the prescaler
goto ExitISR
ExitISR:
movf STATUS_Save,w ; Restore context
movwf STATUS
swapf W_Save,f ; swapf doesn't affect Status bits, but MOVF would
swapf W_Save,w
retfie
The above represents one way to handle an interrupt:
-
The context (STATUS & W registers) is saved.
- Test the interrupt - if it is one we are interested in then service it.
- Restore the context and exit.
I have a few minor and one major criticism here, the first minor one is with the: goto $-1. In the days of programming on a teletype machine, being terse was a virtue, here it serves only to confuse a beginner. Adding a label and referring to that makes the code far more readable:
bsf ADCON0,GO_DONE ; start conversion
testadcdone:
btfss ADCON0,GO_DONE ; this bit will change to zero when the conversion is complete
goto testadcdone
Secondly the code sequence:
comf ADRESH,w ; Form the 1's complement of ADresult
movwf TMR0 ; Also clears the prescaler
goto ExitISR
ExitISR:
movf STATUS_Save,w ; Restore context
only makes sense if another (missing) service routine is interposed after the: goto ExitISR instruction - another possible cause for puzzlement by a beginner. A check of the disassembly will show that the operation has been left in by the assembler, and represents both a waste of time and space. However, it dosn’t stop the code working, and the author obviously felt it was just good enough! The final minor criticism is with the comments the statement: ; Form the 1’s complement of ADresult merely states the obvious. What should be said here is that the analogue voltage just converted, will affect the time until the next interrupt, also When assigned to the Timer0 module, all instructions writing to the TMR0 register will clear the prescaler.
I could halt our discussion here, as the code above satisfies the 1st rule - i.e. it works, but I won’t. My major criticism is both with the concept and implementation of the example. The author apparently sets out to do two things in the above routine:
- Demonstrate handling a timer interrupt.
- Invoking analog conversion then await it’s completion.
In an earlier paragraph I stated that in an interrupt routine, we should keep the instruction sequence as short as possible. In fact this service routine contains a loop - the goto just discussed. I’m going to be bold here - never ever use polling of this type inside an interrupt routine. A quick look at the datasheet for the PIC16F887 will show you that there is the facility to generate an interrupt when the ADC conversion completes, and use should have been made of this. (RTFM) In his effort to make the timer interrupt routine appear to be a little more useful, the author has shown you a really bad way to carry out an ADC conversion. Unfortunately, if you are a beginner, you may have regarded the above as a nifty way of reading the ADC at regular intervals - sadly it is not. An important lesson can be learned here - leaving out the label and using the sequence: goto $-1 has obfuscated the fact that the routine contains a loop. Such code, if offered up for peer review in a commercial software outfit, would be rejected out of hand.
The remainder of the code is relatively trivial and any other important features are discussed elsewhere. A corrected version of the above is given here:
This site is under construction - please be patient
Sanity Testing Your First Prototype
Your test program to perform the above task can be either in assembly-language or ‘C’. It does two things - make a port pin an output, and set that output to a logical ‘1′. (usually +5volts) On some of my projects, this piece of code is actually left in and a LED/resistor combination installed permanently. In these cases I usually switch on the LED and then switch it off after a short delay. I call this LED a ‘reassurance’ or ‘comfort’ LED for obvious reasons.
The following assembler code example has been shamelessly lifted from Microchips PICKit 2 Debug Express software, and is set up to run on a 16F887 on the 44-pin Demo board supplied with the PICKit 2 Debug Express kit:
;*******************************************************************
; PICkit 2 Lesson 1 - "Hello World"
;
; This turns on LED 0 on the 44-Pin Demo Board.
;
; *******************************************************************
; * See 44-pin Demo Board User's Guide for Lesson Information *
; *******************************************************************
#include <p16F887.inc>
__CONFIG _CONFIG1, _LVP_OFF & _FCMEN_OFF & _IESO_OFF & _BOR_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _INTRC_OSC_NOCLKOUT
__CONFIG _CONFIG2, _WRT_OFF & _BOR21V
org 0
Start:
bsf STATUS,RP0 ; select Register Bank 1
bcf TRISD,0 ; make IO Pin RD0 an output
bcf STATUS,RP0 ; back to Register Bank 0
bsf PORTD,0 ; turn on LED RD0 (DS0)
goto $ ; wait here
end
This piece of code demonstrates a few simple rules, that if remembered, will stand you in good stead with your future projects.
- The first ‘rule’ of a program - it should work.
- Keep what is necessary, throw the rest away.
- Comment your code properly - make each comment add to understanding the code. If you don’t comment properly, your code (especially assembler) will become ‘write only’ with time. In other words at a later date you may have to unpick it because you’ve forgotten what it does.
- Set up the configuration correctly. This is essential.
- Initialise the resources you are going to use on the PIC.
- Perform operation(s) needed before execution of the program loop - in this case simply light up an LED.
- Loop forever - performing more operations if necessary (in this case none)
If you build the above program in MPLAB, then take a look at the disassembly listing, you won’t be surprised to see that there is a one-to-one correspondence between your source-code and the machine-code produced.
0000 1683 BSF 0x3, 0x5 34: bsf STATUS,RP0 ; select Register Bank 1
0001 1008 BCF 0x8, 0 35: bcf TRISD,0 ; make IO Pin RD0 an output
0002 1283 BCF 0x3, 0x5 36: bcf STATUS,RP0 ; back to Register Bank 0
0003 1408 BSF 0x8, 0 37: bsf PORTD,0 ; turn on LED RD0 (DS0)
0004 2804 GOTO 0x4 38: goto $ ; wait here
Now take a look at a program that does exactly the same job in ‘C’. I wrote this using the free SourceBoost ‘C’ compiler:
//
// HiWorld.c
// JWB 2009
//
#include <system.h>
// Target PIC16F887 configuration words
#pragma DATA _CONFIG1, _LVP_OFF & _FCMEN_OFF & _IESO_OFF & _BOR_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _INTRC_OSC_NOCLKOUT
#pragma DATA _CONFIG2, _WRT_OFF & _BOR21V
void main( void )
{
// note that I've copied the assembler statements exactly
set_bit (status, RP0); // select register bank 1
clear_bit (trisd, 0); // make IO pin RD0 an output
clear_bit (status, RP0); // back to register bank 0
set_bit (portd, 0); // turn on LED on RD0
while( 1 ); // wait here forever
}
Now take a look at the machine-code produced by the ‘C’ compiler:
ORG 0x00000000
0000 280B GOTO _startup
ORG 0x00000003
0003 main
0003 ; { main ; function begin
0003 1683 BSF gbl_status,5
0004 1683 BSF STATUS, RP0
0005 1303 BCF STATUS, RP1
0006 1008 BCF gbl_trisd,0
0007 1283 BCF gbl_status,5
0008 1283 BCF STATUS, RP0
0009 1408 BSF gbl_portd,0
000A label1
000A 280A GOTO label1
000B ; } main function end
ORG 0x0000000B
000B _startup
000B 118A BCF PCLATH,3
000C 120A BCF PCLATH,4
000D 2803 GOTO main
ORG 0x00002007
2007 20C4 DW 0x20C4
2008 3EFF DW 0x3EFF
Note the duplication of the machine-code for the setting and clearing of the STATUS register, bit 5 (RP0)
SourceBoost ‘C’ makes an assumption that you will ‘forget’ to switch banks if you are going to clear a bit in TRISD, so it does the switching for you, either side of your: clear_bit(trisd,0) statement. In other words, you don’t need to concern yourself about bank-switching - not in this case anyway, so try building the program again with the two bank-switch statements commented, and this is what you will get:
ORG 0x00000000
0000 2809 GOTO _startup
ORG 0x00000003
0003 main
0003 ; { main ; function begin
0003 1683 BSF STATUS, RP0
0004 1303 BCF STATUS, RP1
0005 1008 BCF gbl_trisd,0
0006 1283 BCF STATUS, RP0
0007 1408 BSF gbl_portd,0
0008 label1
0008 2808 GOTO label1
0009 ; } main function end
ORG 0x00000009
0009 _startup
0009 118A BCF PCLATH,3
000A 120A BCF PCLATH,4
000B 2803 GOTO main
ORG 0x00002007
2007 20C4 DW 0x20C4
2008 3EFF DW 0x3EFF
Hmm, better I hope you will agree, but still a little verbose compared with the assembler version. I would judge this as ‘just good enough’, the trade-off in bigger programs will be that they are easier to write in ‘C’, but will almost always be larger in terms of the memory they occupy.
So with the unwanted lines removed, the final ‘C’ program looks like this:
//
// HiWorld.c
// JWB 2009
#include <system.h>
// Target PIC16F877 configuration words
#pragma DATA _CONFIG1, _LVP_OFF & _FCMEN_OFF & _IESO_OFF & _BOR_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _INTRC_OSC_NOCLKOUT
#pragma DATA _CONFIG2, _WRT_OFF & _BOR21V
void main( void )
{
clear_bit (trisd, 0); // make IO pin RD0 an output
set_bit (portd, 0); // turn on LED on RD0
while( 1 ); // wait here forever
}
blah blah
|
PIC Assembler Guide
C18 'C' Compiler Guides (PDF)
PIC32 'C' Compiler Guides (PDF)
PIC Project Development
|