
Ohessa PIC-aiheisia kysymyksiä ja vastauksia
#Kysymys: Minulla on ajatus tuotteesta, joka voisi olla halpa PIC:llä tehtynä, mistä aloitan ja paljonko minun tarvitsee sijoittaa ohjelmointivälineisiin?
# Vastaus: Jos et tiedä miten aloitat olet ainakin PIC-ohjelmoinnissa aloittelija, mutta sinulla on kokemusta mahdollisesti muista prosessoreista. Valitettavasti niistä ei ole PIC:n kanssa paljon hyötyä, voi olla jopa helpomnpaa aloittaa aivan alusta. Ainakin PC-ohjelmoijille PIC on shokki. Mitä voi tehdä muutamalla muistitavulla ja yhdellä kilolla koodimuistia? Toisaaalta PC:ssä ei ole yhtään reaaliaikaista 50MHz laskurituloa tai IO-nastaa, jonka ajoitusta ei musitinvirkistys häiritsisi.
Tarvitset aluksi PIC-datakirja-CD:n tai datalehden PIC16F628 ( www.microchip.com), ohjelmointilaitteen esim Probyte PARPIC2, makroassemblerin ( MPLAB)
Muutama PIC16F628A-I/P
on tietysti hyvä olla. Samoin mahdollisesti testipiirilevy
(PICTEST3)
ja ehkä muutama EEPROM 93C56 tai 24C16 . Easy Pic´n
kirja jatkaa
siitä mihin datakirjat pysähtyvät. Jos
haluat tosiaan tehdä
toimivan laitteen nopeasti, PCM- PIC C-
kääntäjä.
Tässä jopa liukuvan pilkun muuttujat.
Näissä on kymmeniä
valmiita esimerkkejä ohjelmista. Muita mukavia
piirejä PIC-ympäristöön
ovat DS1302 kellopiiri AD-muunnin 16 bit RC4151CN
tai AD654J
. DA-muunnin MAX515 (10- 12 bit) maksaa Resonaattori
4/10/12/20MHz
tai vastaavat kiteet. Kiteiden kanssa tarvitset pari 22pF
kondensaattoria
. Lämpötilamittauksiin sopii SMT160-30 tai
LM335z Muita
lämpötila-antureita ovat Dallasin DS1620, 1621 ja
DS1820.
PICTEST3:n mukana tulee ainoa ja ensimmäinen
suomenkielinen
PIC Assembler opas ja esimerkkikoodeja. . Lentokoneservoja
tarvitaan
vähintään kaksi kevyttä robottia
varten.
Mitä et tarvitse : 12-bittisiä, vanhempia ( = halpoja PIC:ejä kuten 16C54 ). Usko pois, ne ovat vanhentuneita! Uudet 16Fxx ja 18F-sarjan PIC:it ovat 14/16-bittisiä ja niissä on paljon hyviä ominaisuuksia. Mm. 8- tasoinen pino, 12-bittisissä on vain 2 tason pino. Isompien ohjelmien teko on helpommpaa jne.
#Kysymys: Mikä on ero PIC16C84 ja PIC16F84 välillä?
#Vastaus: PIC16F84 on uudempi FLASH-nimellä myytävä tuote, jossa on 68-tavua RAM eli puolet enemmän kuin 16C84-versiossa. Suojausbitin oletusarvo on ohjelmoitaessa eri tavalla kuin 16C84:llä. 16F84 on parempi kuin 16C84, ja se korvaa aina 16C84:n. Samoin Power Up Start-delay bitti on käännetty vastamaan muita PIC-piirejä. Hinta on sama. PIC16C84 on suojausongelma, jonka takia Microchip on ilmeisesti halunnut johdattaa käyttäjät kayttämään uudempaa mallia. Ainoa ongelama PIC16F84 on se, että jotkut vanhaemmat ohjelmointiohjelmat eivät tunnista PIC16F84 ja sulakebitit menevät väärinpäin. Mutta tästäkin selviää, laittamalla ne alunperin väärinpäin!
#Kysymys Voinko ohjelmoida uusia PIC16F877-piirejä vanhenmalla ohjelmoitilaitteellani, jossa ei ole tukea PIC16F877:lle?
#Vastaus:
Periaattessa ohjelmointialgoritmi on
sama, mutta käytännössä
ohjemointilaitteiden tekijät
eivät ole huomioineet suurempaa muistikokoa ja uusi
sulakebittejä,
joten käytännössä ohjelminti ei
onnistu. Hanki mieluummin
uusi rinnakkaiporttimallinen ohjelmointilaite (PARPIC2 tai Picstart
Plus),
johon saa lukemattomia ohjelmointiohjelmia. Muita hyviä
ohjlmointilaitteita
ovat Neddham's Electronicsin EMP11, EMP21 ja EMP30
#Kysymys: Voinko ohjelmoida PIC:n C-kielellä, joka on minulle tutumpi kuin PIC-assembleri?
# Vastaus: Kyllä, PIC:lle löytyy joukko C-kääntäjiä. Paras on CCS ( Custom Computer Services) PCM ( 14-bittiset PICit ) ja PCB ( 12-bittiset PICit), PCH on 16-bittisille PIC18-sarjalle. Windows -paketissa ovat molemat kääntäjä yhdessä. Muutamia shareware PIC C-kääntäjiä löytyy myös, mutta ne eivät vielä toimi, kokeie vaikka itse. PCM:ssä on I2C, LCD,EEPROM,ADC ym rutiinit valmiina, joiden tekeminen assemblerilla voi viedä kuukausikaupalla aikaa kokeneelta ohjelmoijalta.
With the window uncovered, a JW part can clear all its File Registers just by being exposed to light. If you're not explicitly initializing RAM, this is kinda handy. If you cover the window, though, the JW part behaves just like the OTP; the File Registers all come up in unknown states and the code doesn't work.
2. Now you change port B0 to an input (TRISB = xxxxxxx1), and read PORTB (with a MOVF PORTB,W or something). Since B0 is pulled up, you'll read a "1" on B0 (PORTB = xxxxxxx1).
Following so far? Good...
3. Now you make port B0 an output again (TRISB = xxxxxxx0), and, since PORTB = xxxxxxx1, the B0 pin will be driven HIGH.
To keep this from happening, you should always load PORTB with the values you expect on the output pins JUST BEFORE you make them outputs. In the example above, a BCF PORTB,0 just before step 3 would have ensured that the pin would drive low after changing the TRISB register.
Of course, you may just be neglecting to set the register-page select bits properly before you write to TRISB, and you might actually be writing to PORTB inadvertently. Since TRISB is on the upper page of registers, you must be sure that RP0 is set before you write to TRISB.
Well, ok... You can, but you don't want to. Use the DT directive instead.
DT "Test",0will assemble to:
RETLW "T"
RETLW "e"
RETLW "s"
RETLW "t"
RETLW 0
A 4 MHz clock speed will give you 32 machine cycles per bit, which should be plenty of time to do what you want. If you need more time, you can run faster.
LIST R=DEC
ASCIIO EQU .... ;A FILE REGISTER
ASCIIT EQU .... ;ANOTHER FILE REGISTER.
ASCIIH EQU .... ;ANOTHER FILE REGISTER.
; ENTER WITH ORIGINAL 8-BIT VALUE IN "ASCIIO". EXITS WITH ASCII
; REPRESENTATION OF ONES' DIGIT IN "ASCIIO", TENS' DIGIT IN "ASCIIT",
; AND HUNDREDS' DIGIT IN "ASCIIH".
CONVRT MOVLW '0'
MOVWF ASCIIH
MOVWF ASCIIT
DO100S MOVLW 100
SUBWF ASCIIO,W
BNC DO10S
MOVWF ASCIIO
INCF ASCIIH
GOTO DO100S
DO10S MOVLW 10
SUBWF ASCIIO,W
BNC ADJUST
MOVWF ASCIIO
INCF ASCIIT
GOTO DO10S
ADJUST MOVLW '0'
ADDWF ASCIIO
a. b. c. d. e. f.At a., the stack is full. After 8 RETURNs, we'll be back at our starting point, 050. At b., we've done one more CALL, pushing the lowest address off the stack. At c., we've done a RETURN... see what happens at the bottom of the stack?
0C0 0D0 0C0 070 060 060
0B0 0C0 0B0 060 060 060
0A0 0B0 0A0 060 060 060
090 0A0 090 060 060 060
080 090 080 060 060 060
070 080 070 060 060 060
060 070 060 060 060 060
050 060 060 060 060 060
The diagram at d. shows what's happened after five more RETURNS. Everything's still going smoothly at e., one RETURN later. After returning to address 060, though, a further RETURN will not send us back to our original starting point (address 050). Instead, we'll just get stuck in a loop between 060 and the first RETURN after 060.
CALL TEST ;Subroutine returns with W=0 for FALSE, W=1 for TRUE.Of course, care must be taken with page-boundary crossings. If you're using the 16Cxx, you'll need to make sure that PCLATH is pointing at the current page before you do the ADDWF. On the 16C5x, this code-fragment will only work if TRUE is on the first half of a page, and (for the 16C56/57/58) you'll need to ensure that the code-page select bits are pointing at the current page before you do the ADDWF.
ADDWF PCL ;For the 16C5x, this should be "ADDWF PC".
GOTO FALSE
TRUE ....
If you're willing to go to a bit of trouble, you can do it
in a two-step
process:
Write a short 16C84 program that just programs your EEPROM to the
desired
values. Burn it into a 16C84, run the program, then burn the 16C84 with
your real code.
If you really get lucky and find yourself trying to determine something
simple, like whether the contents of a register are greater than 127,
you
can, of course, just do a single bit-test.
MOVLW constant ;If register = constant, jump.Note that the ">" and "<=" tests use more space than the others. Rather than testing for "greater than x", therefore, you may wish to test for "greater than or equal to x+1". Likewise, instead of testing for "less than or equal to x", you may wish to test for "less than x+1".
SUBWF register,W
BZ R_EQUALS_C
MOVLW constant ;If register >= constant, jump.
SUBWF register,W
BC R_GT_EQ_C
MOVLW constant ;If register < constant, jump.
SUBWF register,W
BNC R_LT_C
MOVLW constant ;If register > constant, jump.
SUBWF register,W
SKPZ
SKPC
GOTO $+2
GOTO R_GT_C
MOVLW constant ;If register <= constant, jump.
SUBWF register,W
BZ R_LT_EQ_C
BNC R_LT_EQ_C
If you have an external clock (with an oscillator and everything... not just a crystal or ceramic resonator), you can obviously run its signal to the CLKIN (OSC1) pin of each PIC.
If you're using the PIC's built-in oscillator with an external crystal or ceramic resonator, you can simply tie one PIC's CLKOUT (OSC2) pin to the other's CLKIN, then put the crystal/resonator between the two remaining OSC pins. Some experimentation with capacitor values may be necessary.
Unfortunately, the PICs' instruction clocks run at 1/4 the external clock frequency. While it's pretty easy to sync the PICs to within 1 instruction cycle, there's no way to guarantee that each PIC's divided-down instruction clock is exactly in sync with the other's. Because the oscillator startup timer is dependent on an inaccurate internal R/C, the PICs will probably be out of sync by 1 or 2 oscillator periods in either direction.
This means is that you'll still have to allow some time between placing data on the bus and taking it off, even if your software is clever enough to get the PICs in sync to within 1 instruction cycle.
Actually, Martin J. Maney (who's apparently a little more level-headed than I) recently explained the inconsistency thusly:
The first PICs (all the way back to General Instruments' NMOS 1650) didn't include "subtract" instructions. They did have "literal" instructions (ADDLW, XORLW, etc.), but none of those instructions was order-dependent. The only instruction that was order-dependent was MOVLW, so GI/Microchip set the order of all the literal instructions to match it.
By the time that the "Subtract W from L" instruction was added to the newer PICs' instruction sets, it was too late... All the other literal instructions already used the "....LW" ordering, so Microchip applied it to the SUBLW instruction, too.
Personally, this explanation leaves me a little unsatisfied -- foolish consistency is, as they say, the hobgoblin of little minds -- but I guess it'll have to do. At least it takes the edge off my homicidal urge.
#DEFINE LED PORTA,0 ;The LED is on RA0.
....
BSF LED ;Turn the LED on.
INTW EQU [any page-0 register]
INTS EQU [any page-0 register]
....
; INTERRUPT SERVICE ROUTINE:
PUSH MOVWF INTW ;SAVE THE W-REGISTER.
SWAPF STATUS,W ;SAVE THE STATUS REGISTER.
MOVWF INTS ;
BCF STATUS,RP0 ;MAKE SURE WE'RE ON REGISTER PAGE 0 [optional].
....
POP SWAPF INTS,W ;RESTORE THE STATUS REGISTER.
MOVWF STATUS ;
SWAPF INTW ;RESTORE THE W-REGISTER.
SWAPF INTW,W ;
RETFIE ;RETURN AND RE-ENABLE INTERRUPTS.
INTW EQU [any page-0 register]
INTS EQU [any page-0 register]
INTW1 EQU INTW + 080H
...
; INTERRUPT SERVICE ROUTINE:
PUSH MOVWF INTW
MOVF STATUS,W
BCF STATUS,RP0
MOVWF INTS
; save PCLATH and FSR here.
....
POP ; restore PCLATH and FSR here.
MOVF INTS,W
MOVWF STATUS
SWAPF INTW
SWAPF INTW,W
RETFIE
Note that the ADCON1 register defaults to 00 on power-up.
This is also an issue for the 16C71, in which ADCON1 must be set to xxxxxx11 binary.
; NOTE: THIS IS FOR THE CURRENT VERSION OF THE 16C74 ONLY!Note that the __FUSES directive is preceded by two underscores, not one.
CP_ALL EQU 03F8FH ;NOTE: These equates are for the
CP_75 EQU 03F9FH ; current version of the
CP_50 EQU 03FAFH ; PIC16C74 ONLY!
CP_OFF EQU 03FBFH ;
PWRT EQU 03FBFH ; They are NOT for the 16C74A,
NO_PWRT EQU 03FB7H ; nor are they guaranteed to
WDT EQU 03FBFH ; work on any other devices.
NO_WDT EQU 03FBBH ;
LP_OSC EQU 03FBCH ;
XT_OSC EQU 03FBDH ;
HS_OSC EQU 03FBEH ;
RC_OSC EQU 03FBFH ;
__FUSES CP_ALL & PWRT & NO_WDT & XT_OSC ;Enable code
;protection and
;power-up
;timer, disable
;the watchdog
;timer, and
;select XT
;oscillator.
One more thing to note: If you have a GOTO at the interrupt vector (address 0004), remember that it behaves just like any other GOTO; if PCLATH is set when an interrupt occurs, the PIC will jump to an address on page 1. To avoid problems, don't put a GOTO at the interrupt vector. Instead, you can either put your whole interrupt routine at 004 or put just the register-saving code there, followed by a GOTO to the rest of your ISR.
The best way to save registers on the 16C73 is something like this:
INTW EQU any page-0 registerThen, to restore the registers at the end of your ISR, do this:
INTW1 EQU INTW + 080H
INTS EQU any page-0 register
INTP EQU any page-0 register
INTF EQU any page-0 register
ORG 0004h
INTVEC:
MOVWF INTW
MOVF STATUS,W
BCF STATUS,RP0
MOVWF INTS
MOVF PCLATH,W
MOVWF INTP
MOVF FSR,W
MOVWF INTF
;at this point, if you like, you can modify PCLATH and
;jump to the rest of the ISR on another page.
MOVF INTF,W
MOVWF FSR
MOVF INTP,W
MOVWF PCLATH
MOVF INTS,W
MOVWF STATUS
SWAPF INTW
SWAPF INTW,W
RETFIE
The prescaler is not contained in the OPTION register. The OPTION register's PS0-PS2 bits only set the divide-by ratio, or range, of the prescaler, and they are changed only by RESET and explicit writes to the OPTION register. They are not affected by writes to the RTCC.
Put the signal on Port B0, leaving B1-B7 unused. Then:
SETUP MOVLW 00000001B ;MAKE B0 AN INPUT, B1-B7 OUTPUTS.
TRIS PORTB ;
SAMPLE RLF PORTB ;SHIFT 7 CONSECUTIVE SAMPLES OF
RLF PORTB ;B0 INTO B7-B1.
RLF PORTB ;
RLF PORTB ;
RLF PORTB ;
RLF PORTB ;
RLF PORTB ;
MOVF PORTB,W ;W-REG CONTAINS LAST 8 SAMPLES
;(MSB = EARLIEST, LSB = LATEST).
INTW EQU [any page-0 register]Changing the SWAPF STATUS,W to MOVF STATUS,W will work just fine, since the Z-Flag (which can be changed by that MOVF) won't change until after the contents of STATUS are copied to the W-Register.
INTS EQU [any page-0 register]
....
; INTERRUPT SERVICE ROUTINE:
PUSH MOVWF INTW ;SAVE THE W-REGISTER.
SWAPF STATUS,W ;SAVE THE STATUS REGISTER.
MOVWF INTS ;
BCF STATUS,RP0 ;MAKE SURE WE'RE ON REGISTER PAGE 0 [optional].
....
POP SWAPF INTS,W ;RESTORE THE STATUS REGISTER.
MOVWF STATUS ;
SWAPF INTW ;RESTORE THE W-REGISTER.
SWAPF INTW,W ;
RETFIE ;RETURN AND RE-ENABLE INTERRUPTS.
Of course, if you use MOVF STATUS,W in your "save" code, you must use a corresponding MOVF INTS,W in your "restore" code.
If you don't mind reading data books on your PC screen, you can download the data sheets, in Adobe Acrobat format, from either Microchip's BBS or from their Web site (an Acrobat Reader is available from those sites, too)..
;There are a couple of things to note here. First, the range of inputs to the LOOKUP subroutine must be tightly constrained. In our example, W must be in the range [1-10]; calling LOOKUP with W > 10 will cause disastrous results, as the Program Counter will end up pointing God-knows-where in your program. Second, you need to remember that if W = 0 on entry to the subroutine, the ADDWF will point the Program Counter to the instruction after the ADDWF, since the PIC increments its Program Counter before executing the instruction. For this reason, our example (which only deals with inputs > 0) puts a NOP right after the ADDWF.
;RETURN THE X'TH PRIME NUMBER, WHERE X IS IN THE RANGE [1-10].
;ENTER WITH X IN THE W-REGISTER, EXITS WITH THE X'TH PRIME NUMBER
;IN THE W-REGISTER.
;
LOOKUP ADDWF PC ;JUMP TO THE APPROPRIATE RETLW.
NOP ;"ZERO'TH" PRIME NUMBER WOULD GO HERE.
RETLW 2 ; 1ST PRIME NUMBER IS 2.
RETLW 3 ; 2ND PRIME NUMBER IS 3.
RETLW 5 ; 3RD PRIME NUMBER IS 5.
RETLW 7 ; 4TH PRIME NUMBER IS 7.
RETLW 11 ; 5TH PRIME NUMBER IS 11.
RETLW 13 ; 6TH PRIME NUMBER IS 13.
RETLW 17 ; 7TH PRIME NUMBER IS 17.
RETLW 19 ; 8TH PRIME NUMBER IS 19.
RETLW 23 ; 9TH PRIME NUMBER IS 23.
RETLW 29 ;10TH PRIME NUMBER IS 29.
....
MAIN MOVLW 7 ;LOOKUP THE 7TH PRIME NUMBER.
CALL LOOKUP ;
MOVWF RESULT ;STORE IT IN "RESULT".
Ok... Some more things:
On the 16C5x parts, the ADDWF and each of the RETLWs must be located in the first half of the code page in which they're located. That is, the entire subroutine must fit in the [000-0FF], [200-2FF], [400-4FF], or [600-6FF] areas.
For the 16Cxx parts, the subroutine can be anywhere in memory, but the PCLATH register should be loaded with the hi-byte of the table-elements' addresses before calling LOOKUP. That is, the MAIN routine should look like this:
MAIN MOVLW HIGH (LOOKUP+1) ;MAKE SURE THAT THE "ADDWF" IN "LOOKUP"This assumes that the whole lookup-table subroutine fits in one 256-byte page of memory. If the table crosses page-boundaries, the PCLATH must be pre-conditioned to point at the appropriate page for the element being accessed. Since it's usually very easy to keep tables in one page, I won't show the necessary code here; if you need it, it's in Microchip's Application Note #AN556.
MOVWF PCLATH ;JUMPS TO THE CORRECT PAGE.
MOVLW 7 ;LOOKUP THE 7TH PRIME NUMBER.
CALL LOOKUP ;
MOVWF RESULT ;STORE IT IN "RESULT".
MOVLW HIGH ($+2) ;RESTORE THE PCLATH REGISTER.
MOVWF PCLATH ;
MOVF PORTB,W ;READ ALL 8 BITS OF PORTB INTO THE W-REGISTER.
or
MOVLW 00001111B ;PULL RB4-RB7 OUTPUTS LOW.
ANDWF PORTB ;
or
MOVF PORTB,W ;GRAB THE STATE OF ALL 8 PORTB PINS.
MOVWF LAST ;STORE IT.
WAIT_FOR_CHANGE:
MOVF LAST,W ;COMPARE THE STORED STATE TO THE CURRENT STATE
XORWF PORTB,W ;(WITHOUT AFFECTING EITHER "PORTB" OR "LAST").
BNZ WAIT_FOR_CHANGE ;IF ALL 8 PINS ARE STILL THE SAME, LOOP BACK
;AND KEEP WAITING.
For instance, the 16C54A-04 is rated for speeds up to 4 MHz; it can be user-programmed for RC or XT operation. The 16C54A-10 (rated for speeds up to 10 MHz) can be programmed for RC or XT operation up to 4 MHz, or HS operation to 10 MHz. The 16C54A-20 is rated for 20 MHz, and should only be used in HS mode. For LP mode, Microchip recommends the 16LC54A-04, which can be used up to 200 KHz.
The information in the previous paragraph isn't completely accurate. Actually, each of those chips will work in at least three oscillator modes; the -04 will do all 4 modes (but only up to 200 KHz for LP and 4 Mhz for the others), the -10 will do all but LP (but only up to 4 MHz for RC and XT modes), the -20 will also do all but LP (with the same RC and XT maximum-speed restrictions as the -10 part), and the LC54A-04 will do all but HS mode (but only up to 2 MHz in RC or XT mode). However, the parts aren't guaranteed to meet the databook's Min/Max specifications when they're used in these other modes (although they are tested for basic functionality), so it's probably best to stick to the oscillator-type recommendations in the previous paragraph.
ADDWF REG,WFor a source-register destination, the instruction looks like this:
or
ADDWF REG,0
ADDWF REGThese last three are equivalent, and most people just imply the source-register destination by using ADDWF REG.
or
ADDWF REG,F
or
ADDWF REG,1
The folks who man Microchip's Corporate Applications Tech-Support lines recently discovered that there are certain people in the world who are so brain-dead that they can't remember that ADDWF REG puts the result of the addition back in REG. For those people, Microchip has added (over the protests of MPASM's author, Kim Cooper) the "Using default destination...." message.
To keep those messages from appearing, put the following line at the start of your program:
ERRORLEVEL -305
This is fine; RA3 can still be used as a digital output. The problem, though, is that whenever you READ from RA3, you'll see a "0".
The BSF and BCF instructions are read-modify-write instructions. That is, a BSF will read the entire port, change the appropriate bit, then write the entire port back.
When your BSF PORTA,5 instruction executes, it reads all of port A, including RA3 (which reads as "0"). It then sets RA5 and writes the value back, with bit 3 now cleared.
There are a bunch of ways to deal with this situation. The most straightforward is to make changes to a "shadow" of PORTA that you keep in a general-purpose register. After the changes are made, you can copy the contents of this register to PORTA with a MOVWF. If you use this technique, your code would look something like this:
SHADOW EQU [any register]If you're using interrupts that write to or read from PORTA, you'll need to make sure that no interrupts occur while SHADOW and PORTA are out of sync, which is a pain, but other than that (and the speed/space penalty incurred by using a shadow register), the above solution will work fine.
....
CLRF PORTA ;Initialize PORTA and its shadow.
CLRF SHADOW ;
....
BSF RP0 ;Switch to register-page 1.
MOVLW 00000011B ;Set up PORTA2-5 as outputs.
MOVWF TRISA ;
BCF RP0 ;Switch back to register-page 0.
....
MOVF SHADOW,W ;Grab the shadow.
IORLW 00100000B ;Set RA5.
MOVWF PORTA ;
MOVWF SHADOW ;Make sure the shadow is still in
;sync with PORTA.
WAIT_FOR_EE:
CLRWDT
BTFSS EEIF
GOTO WAIT_FOR_EE
old way:
CALL1 CALL SUBNew way:
....
CALL2 CALL SUB
....
SUB NOP
RETLW 0
#DEFINE RETTO1 REG,0 ;If this bit is hi, "return" to CALL1.This uses only part of one register and a few extra instructions, and is much more efficent than simulating a full-blown stack.
;Otherwise, "return" to CALL2.
....
CALL1 BSF RETTO1
GOTO SUB1
....
CALL2 BCF RETTO1
GOTO SUB1
....
SUB1 NOP
BTFSC RETTO1
GOTO CALL1+2
GOTO CALL2+2
CCPxX and CCPxY contain the two lowest-order bits of the 10-bit duty-cycle register. When you use "8-bit" mode, you're just setting those two low-order bits to "00", which effectively sets the PWM duty cycle to four times the number in the duty-cycle register. To use 10-bit mode, just put the low 2 bits of your desired duty-cycle in CCPxX and CCPxY, with the upper eight bits in the duty-cycle register.
Fortunately, Microchip is integrating MPSIM into their existing Windows-based MPLAB integrated development environment, which currently contains an editor, assembler, and emulator. They expect to have completed the integration by Spring, 1996.
"MPSIM-enabled" versions of MPLAB, like all of Microchip's currently-available development tools, will be made available at no charge.
MOVLW 005H ;POINT THE FSR AT REGISTER 5.
MOVWF FSR ;
MOVF INDF,W ;GRAB THE CONTENTS OF THE REGISTER
;POINTED TO BY THE FSR.
INCF FSR ;NOW THE FSR POINTS TO REGISTER 6.
MOVWF INDF ;WRITE THE CONTENTS OF THE W-REGISTER
;TO THE REGISTER POINTED TO BY THE FSR.
Floating-point values are represented by a two-number combination:
a "mantissa" and an "exponent". For a floating-point value x,
x = mantissa * (2 ** exponent),where "**" means "raised to the power".
It's easy to see that there are an infinite number of mantissa/exponent combinations for any x. For example, all the following combinations give a result of 10 (decimal):
10 * (2 ** 0)Binary representation of numbers less than 1 work just like decimal fractions, except that the first digit to the right of the decimal point is the 1/2's digit, the next is the 1/4's, the next is the 1/8's, etc. If we convert the mantissas listed above from decimal to binary, we get:
5 * (2 ** 1)
2.5 * (2 ** 2)
1.25 * (2 ** 3)
0.625 * (2 ** 4)
0.3125 * (2 ** 5)
10 = 1010See a pattern?
5 = 101
2.5 = 10.1
1.25 = 1.01
0.625 = 0.101
0.3125 = 0.0101
Ok... There's a rule for choosing which of the infinite number of mantissa/exponent combinations to use. It's pretty simple: Find the mantissa with all 0's to the left of the decimal point and a "1" immediately to the right of the decimal point. That mantissa, with its corresponding exponent, is the one to use. Converting from one of the other combinations to that one is called "normalization", by the way.
In our case (floating-point representation for x = 10 decimal), we'd use 0.625 * (2 ** 4). Converting our mantissa and exponent to binary,
mantissa = 0.101 exponent = 100Everything clear so far? Good, because it gets a little tricky here.
Since the mantissa is, by definition, always 0.1xxxx..., we don't need to store the "0.1" portion of it. Instead, we'll just imply the "0.1" and make our mantissa contain only the bits which follow the "0.1".
"But wait," you ask, "What about negative numbers?"
Well, ok. Since we've saved a bit or two by not including the leading "0.1", we'll put the sign bit in the mantissa's most-significant (leftmost) bit position. For positive numbers, the sign bit is "1"; for negative numbers, the sign bit is "0".
After stripping off the "0.1", our mantissa is simply "01". Preceding it with the sign bit makes it "101".
Since math guys just can't leave well enough alone, the exponent gets messed with, too. They call it "biasing", and it's real simple: If "m" is the number of bits in the exponent (8 in our case), you just add 2 ** (m-1) to the exponent. After biasing the exponent for our example (and stripping / signing the mantissa), we have:
mantissa = 101 exponent = 10000100Since the PIC math routines use a 16-bit mantissa, the actual values are:
mantissa = 1010000000000000 exponent = 100000100, orFor -10 decimal, the mantissa is 0010000000000000 (note the leading "0" instead of "1"), or 0x2000, and the exponent is unchanged from the exponent for x = 10.
mantissa = 0xA000 exponent = 0x84
That's all there is to it.
For numbers between 0 and 1 (or between 0 and -1), it works exactly the same way. Reach back to your memory of high-school algebra class and remember that if y is a positive integer, then:
x ** -y = 1 / (x ** y)For example, 3 ** 2 = 9; 3 ** -2 = 1/9.
Applying this notion, we can see that the floating-point representation of, say, 1/10 is 0.8 * (2 ** -3). For x = 1/10, then, our mantissa and exponent are:
mantissa = 0.8 (decimal) exponent = -3 (decimal), orNote that the exponent uses two's complement notation for negative numbers (0xFF = -1, 0xFE = -2, ..., 0x81 = -127, 0x80 = -128).
mantissa = 0.1100110011001100.... exponent = 11111101
After stripping/signing the mantissa and biasing the exponent:
mantissa = 1100110011001100 exponent = 01111101, orOh, yeah... One more thing. For x = 0, the mantissa is 0 and the exponent is 0. In fact (because of the biasing), the exponent equals 0 only when x is 0.
mantissa = 0xCCCC exponent = 0x7D
Also, you may care that the PIC representation isn't exactly the same as the IEEE Standard; I don't.
; 8-Bit Divide. Written by Andy Warren.
;
; Permission is hereby granted for any non-commercial use,
; so long as this copyright notice remains intact.
;
; Enter with Dividend in DIVIDEND, divisor in DIVISOR.
;
; Exits with quotient in QUOTIENT and remainder in REMAINDER, DIVIDEND
; scrambled, DIVISOR unchanged, carry-flag set.
DIVIDE MOVLW 1
MOVWF QUOTIENT
CLRF REMAINDER
LOOP RLF DIVIDEND
RLF REMAINDER
MOVF DIVISOR,W
SUBWF REMAINDER,W
SKPNC
MOVWF REMAINDER
RLF QUOTIENT
BNC LOOP
+5V +--------------------------------+--------PIC I/O PINIn your code, make the I/O pin an output and pull it low to discharge the cap. Then make it an input and measure the time it takes for the pin to read "1".
| | |
| 1K V 10K |
+-----/\/\/\/-----/\/\/\/-----/\/\/\/-----+ === 0.1 uF
25K | |
| |
GND GND
The PIC's 0/1 threshold changes with Vdd, and there's a part-to-part variation, but neither of those factors should affect your application much.
If I were you, I'd forget about using a PIC altogether, and just use off-the-shelf sync-separator and sync-generator chips. Everybody makes these things... Try National's LM1881 sync-separator and LM1882 sync-gen (or, if your sync source is less than perfect, you may want to use Elantec's separator).
' PIC viivelaskuri QuickBasic
defdbl a-z
dim q$(40)
for x = 1 to 40
q$(x) = ""
for y = 1 to x: q$(x) = q$(x) + " ": next
next
start:
x = 0
input "Number of cycles to delay:",delay
input "Cycles consumed by user code within the delay loop:",user
delay = int(delay): user = int(user)
u$ = "; Your"+str$(user)+"-cycle code goes here."
if (delay - user) < 32 then print q$(34);u$: delay = delay - user: goto inline
x = delay - 16 - user
d = int(x/(256*(256*(256*(3+user)+2)+2)+2)): if d > 255 then d = 255
x = x - d * (256*(256*(256*(3+user)+2)+2)+2)
c = int (x/(256*(256*(3+user)+2)+2)): if c > 255 then c = 255
x = x - c * (256*(256*(3+user)+2)+2)
b = int (x/(256*(3+user)+2)): if b > 255 then b = 255
x = x - b * (256*(3+user)+2)
a = int (x/(3+user)): if a > 255 then a = 255
x = a * (3+user)
x = x + b * (256*(3+user)+2)
x = x + c * (256*(256*(3+user)+2)+2)
x = x + d * (256*(256*(256*(3+user)+2)+2)+2)
x = x + 16 + user
a = a + 1: b = b + 1: c = c + 1: d = d + 1
if a = 256 then a = 0
if b = 256 then b = 0
if c = 256 then c = 0
if d = 256 then d = 0
a$=right$(q$(3)+str$(a),3)
b$=right$(q$(3)+str$(b),3)
c$=right$(q$(3)+str$(c),3)
d$=right$(q$(3)+str$(d),3)
print q$(7);"list r=dec"
print "r1";q$(5);"equ";q$(5);"[any file register]"
print "r2";q$(5);"equ";q$(5);"r1+1"
print "r3";q$(5);"equ";q$(5);"r2+1"
print "r4";q$(5);"equ";q$(5);"r3+1"
print q$(20);"___________________"
print "delay:";q$(1);"movlw";q$(1);a$;q$(3);"|";q$(19);"|"
print q$(7);"movwf";q$(2);"r1";q$(3);"|";q$(19);"V"
print q$(7);"movlw";q$(1);b$;q$(3);"|";q$(7);"loop:";q$(2);u$
print q$(7);"movwf";q$(2);"r2";q$(3);"|";q$(14);"decfsz";q$(1);"r1"
print q$(7);"movlw";q$(1);c$;q$(3);"|";q$(14);"goto";q$(3);"loop"
print q$(7);"movwf";q$(2);"r3";q$(3);"|";q$(14);"decfsz";q$(1);"r2"
print q$(7);"movlw";q$(1);d$;q$(3);"|";q$(14);"goto";q$(3);"loop"
print q$(7);"movwf";q$(2);"r4";q$(3);"|";q$(14);"decfsz";q$(1);"r3"
print q$(11);"|";q$(7);"|";q$(14);"goto";q$(3);"loop"
print q$(11);"|";Q$(7);"|";q$(14);"decfsz";q$(1);"r4"
print q$(11);"|_______|";q$(14);"goto";q$(3);"loop"
inline:
x = delay - x
g = int(x/2): if g = 0 then goto inline1
for y = 1 to g
print q$(34);"goto";q$(3);"$+1"
next
inline1:
if x/2 = int(x/2) then goto finis
print q$(34);"nop"
finis:
end
ISA-Bus interfacing is a little beyond the scope of this page...
Get a copy of the best book on the subject, ISA &
EISA Theory and
Operation, by Edward Solari. It explains everything
you'll ever
need to know.
If you've found what you think is a bug, you should report it to Microchip; they're very good at following up on these reports.
If you prefer to use the PC-based tools available from Microchip, there are two ways: Buy a used PC or (if you have a suitably-powerful Mac) run Insignia Software's SoftWindows emulator on your Mac.
Jim Oslislo (joslislo@soho.ios.com), a Mac user, recommends the following configuration:
Mac PicstartConnect Mac 6 to Mac 4 to Mac 8 to Picstart 5
6 7 8 1 2 3 4 5
3 4 5 6 7 8 9
1 2
If you have more money to spend and want real-time hardware emulation, I'd recommend the following combination; if you're honest and pay the WinEdit shareware-registration fee, you'll have a decent development system for right around $1,000:
Finally, if cost is no object whatsoever, I'd recommend the following system, which is what we use here:
As dedicated as your third-party vendor may be, it's possible that he will eventually tire of updating his software or hardware, and you'll be left with tools that contain unresolved bugs and/or slowly become obsolete as new microcontrollers are introduced. This seems to be a particular problem with shareware tools written by people who always have a "day job" to go back to.
Many third-party tool developers have introduced PIC development tools with great fanfare, only to drop support for them within a couple of years. Even the largest of the third-party suppliers are not immune.
Microchip, on the other hand, will always support its silicon products with development tools. Those tools may not always be the best -- in fact, they may rarely be, since Microchip's primary focus is VLSI semiconductor manufacture, not tool design -- but I can live with that, because at least they'll be current.
For more information on third-party development tools, see the companies listed in the "Embedded Systems Programming" section of our Links" page.
If these chips are windowed EPROM parts, you should run them through
a UV eraser; Microchip does not guarantee that windowed parts will
arrive
blank. If they're OTP parts, you should return them and/or your
programmer.
LFSR: RLF RANDOM,WHere's another routine, written by Marv Isaacman:
RLF RANDOM,W
BTFSC RANDOM,4
XORLW 1
BTFSC RANDOM,5
XORLW 1
BTFSC RANDOM,3
XORLW 1
MOVWF RANDOM
RETLW 0
MARV: MOVLW 01DH
CLRC
RLF RANDOM
SKPNC
XORWF RANDOM
RETLW 0
CRCHI EQU some registerThis isn't the fastest possible implementation, but it should be quick enough for most purposes. It uses the standard X.25 (and XMODEM) polynomial: x^16+x^12+x^5+1.
CRCLO EQU another register
CLRF CRCHI
CLRF CRCLO
;Append 16 "0" bits to your message here.
LOOP ;If there are no more bits in your message, go to
;"DONE". Otherwise, left-shift the next bit of your
;message into the carry here.
RLF CRCLO
RLF CRCHI
SKPC
GOTO LOOP
MOVLW 00100001B
XORWF CRCLO
MOVLW 00010000B
XORWF CRCHI
GOTO LOOP
DONE ;The CRC is in "CRCHI" and "CRCLO".
You may also want to see the answer to Question #20, in the "Other Processors/Miscellaneous" section, below.
MOVLW [some number] ;THIS COULD ALSO BE A "MOVF [register],W".If you're using the 16C5x family, which doesn't have an ADDLW instruction, the "WAITMS" subroutine looks like this (I'm assuming that you're running at 4 MHz):
CALL WAITMS ;CALL THE "DELAY" SUBROUTINE.
MSTMR EQU [any register]If you're using a PIC whose instruction set includes the ADDLW instruction, you can save a register by rewriting the routine like this:
MSTMR2 EQU [another register]
;
; WAIT SOME NUMBER OF MILLISECONDS. ENTER WITH NUMBER OF
; MILLISECONDS IN W (0 = 256).
;
; AT 4 MHz, 1 MILLISECOND TAKES 1000 CYCLES.
;
WAITMS:
MOVWF MSTMR ;MSTMR = THE NUMBER OF MILLISECONDS.
WAITMS1:
MOVLW 249 ;2-SETUP TO WAIT 1 MILLISECOND.
MOVWF MSTMR2 ;
WAITMS2:
NOP ;249-WASTE A CYCLE. THIS COULD ALSO BE A
;"CLRWDT".
DECFSZ MSTMR2 ;746-
GOTO WAITMS2 ;
DECFSZ MSTMR ;3-HAVE WE WAITED ENOUGH TIME?
GOTO WAITMS1 ; IF NOT, LOOP BACK.
RETLW 0 ;OTHERWISE, RETURN.
MSTMR EQU [any register]Note that neither of these routines will give exact results, since they don't compensate for the CALL/RETURN overhead. They'll be accurate to within a couple of microseconds, though.
;
; WAIT SOME NUMBER OF MILLISECONDS. ENTER WITH NUMBER OF
; MILLISECONDS IN W (0 = 256).
;
; AT 4 MHz, 1 MILLISECOND TAKES 1000 CYCLES.
;
WAITMS:
MOVWF MSTIMR ;STORE THE NUMBER OF MILLISECONDS.
WAITMS1:
MOVLW 249 ;1-SETUP TO WAIT A MILLISECOND.
ADDLW -1 ;995-WASTE 995 MICROSECONDS.
SKPZ ;
GOTO $-2 ;
DECFSZ MSTIMR ;HAVE WE WAITED ENOUGH?
GOTO WAITMS1 ;IF NOT, LOOP BACK.
RETURN ;OTHERWISE, RETURN.
; THIS MACRO JUST WAITS UNTIL PORTA0 GOES HIGH.With TESTA0 defined this way, you can invoke the macro as often as you like, and MPASM will automatically select a unique name for the label in each case.
WAIT4A0 MACRO LOCAL TESTA0
TESTA0 BTFSS PORTA,0
GOTO TESTA0
As far as I can tell, the PIC16C52 die is identical to the 16C54. If Microchip can figure out how to save money by manufacturing a chip with only 384 words of EPROM rather than 512, though, future 16C52s may actually be something other than crippled 16C54s.
More precisely, I think you're misunderstanding the function of the PWM generator. The PIC's PWM output is a continuous stream of pulses, all identical, at a user-specified frequency and duty-cycle. I believe that you've confused this with PWM encoding of data, in which a finite string of "ones" and "zeros" are represented by long and short pulses.
If you're on the up-and-up and/or your PIC isn't code-protected,
you can read the contents of the chip with any PIC programmer, then
load
the resulting ".hex" file into MPLAB and save the disassembled file
that
it creates.
At this instant, MPLAB-C and MPC are nearly identical. As time passes, however, I'd expect the two compilers to evolve in different directions, with MPLAB-C becoming more memory-efficient and MPC growing to include more data types (including floats and 24/32-bit integers), more advanced math functions, etc.
This prediction is my opinion only; please don't misconstrue it as representing the official plans of either Microchip or Bytecraft.
This is why it's called an "interrupt"; it interrupts the normal program execution, jumps to your "Interrupt Service Routine" (ISR) at address 4, executes the ISR, then returns to where it was before the interrupt and continues.
The upshot of all this is that you can't really depend on the Change-On-PortB interrupt to be reliable for anything other than waking-up the processor from sleep mode.
Whenever you start using a new PIC, it'd probably be a good idea to make sure you have the latest assembler; the latest version of MPASM (as well as Microchip's other free development tools) can always be downloaded from the Microchip web page... A link to that page is in the "Embedded Systems Programming" section of our "Links" page.
Sample code for generating DTMF tones is all over the web... I'd
suggest looking at Eric Smith's page; there's a link to it in the
"Embedded
Systems Programming" section of our "Links"
page.
The key is to run your PWM frequency as fast as possible and to
use a low-pass filter with a cutoff frequency as low as possible... LCD
displays are pretty slow, but their internal refresh can "beat" with
your
PWM frequency if it's not filtered very well, leading to really awful
flicker.
The "better" way is to put a low-threshold N-channel FET in the
negative lead (sourced to the negative terminal of the battery) and put
a resistor between the gate and the battery's positive terminal, with a
zener between the gate and source. I believe that this arrangement is
patented
by National Semiconductor.
For the P3, the "normal" vector locations are at $7F8-$7FF, so the
"programming mode" vectors would be at $7F0-$7F7. For the R3/U3, the
"normal"
vectors are at $FF8-$FFF and the "programming mode" vectors are at
$FF0-$FF7.
The "programming mode" RESET vector points to the built-in "bootstrap" code (at $785-$7F7 [P3] or $F80-$FF7 [U3/R3]), which handles all programming functions. Unfortunately, the P3, U3, and R3 parts do not contain bootstrap routines for "verify" or "contents dump".
For more information, see Motorola's application note #857, "MC68705P3/R3/U3 8-Bit EPROM Microcomputer Programming Module".
There are two big differences between the PIC and the 6805:
1996 will be a leap year, since it's divisible by 4 and not
by 100.
2000 will be a leap year, even though it's divisible by 100, because
it's also divisible by 400.
2100 will not be a leap year, because it's divisible by 100 but not
by 400.
To form the Gray code for a sequence of consecutive numbers,
take each
number in the sequence, shift it right one bit, then XOR the result
with
the original number.
For example:
Original Gray Code
Sequence ((x >> 1) ^ x)
(x)
000 000
001 001
010 011
011 010
100 110
101 111
110 101
111 100
However, it's equally well-known that programmers, in general, have zero ability to estimate the time necessary to complete a job.
Potential-Conflict-of-Interest Warning:
I used to work for Linear Corp. While there, I designed their data-encoding and -transmission protocols and wrote the software for nearly all their products, so I may be a little biased here.
You have been warned.
There are a bunch of companies who make RF links, but I'd recommend the products manufactured by Linear Corporation. They sell a whole range of transmitters, from matchbook size on up, and a variety of super-regen and superhet receivers. They have versions for use in both North America and Europe, although they might not be type-accepted in Japan.
As an OEM customer, you can get the transmitters with or without encoders and the receivers with or without decoders. Of course, if you want to be FCC-legal, you'll have to either use their encoders or get yours type-accepted.
The domestic transmitter/receiver pairs are mostly in the 300 MHz range and the European ones are at 433.92 MHz.
Linear Corp. can be reached at 800 421-1587 or 619 438-7000. Ask for OEM Sales.
1. Good programmers can write good code in any language, and mediocre
programmers will write crappy code in all languages. Writing in a
high-level
language makes it easy to avoid some "stupid" mistakes, but so does
experience.
2. The nature of microcontroller programming is such that, often, very little code is reusable between applications. There are so many differences between one microcontroller-based project and another that similar algorithms must often be coded in wildly different ways, which is why the traditional reasons for writing in a high-level language (portability across platforms and reusability from one project to the next) are generally not reasons to use a high-level language to program small microcontrollers. There are reasons -- about 25% of the microcontroller code I write is written in C, for example -- but they have little to do with reuse.
3. Some problems are better expressed in C than assembly, and vice-versa. You've got to pick the right tool for the job.
4. Without a good understanding of the underlying assembly language, you won't be able to write optimal C.
5a. An excellent C compiler can generate better code than an excellent assembly-language programmer, because the compiler can safely generate incomprehensible, non-maintainable, "brittle" code that the programmer wouldn't dare write.
5b. An excellent assembly-language programmer can write better code than an excellent C compiler, because the programmer can see a much larger picture of the problem than the compiler can.
6. C is easier to debug. Compiler code-generation errors are rare, so debugging a C program involves mostly finding and fixing bugs in the program's design, rather than in its implementation. Since C offers a higher level of abstraction than assembly, you don't get bogged down in "can't see the forest for the trees"-type debugging.
7. Writing in C is faster than writing in assembly. I'm about the fastest assembly-language programmer I know, but I can write good C code even faster.
8. Because they're so easy to write and debug, programs written in C often suffer from massive "feature-creep".
9. With intelligent use of a really good macro assembler, you can have your cake and eat it, too. With an assembler that supports macros, multi-module code, long symbol-names, etc., you can write "C-like" (or BASIC-like, or... well, ok, not Lisp-like) macros that make assembly-language programming almost as easy as high-level programming.
10. If you spend your whole life honing your assembly-language skills, you won't be nearly as employable as any of the hundreds of thousands of C programmers out there. While C programs aren't always portable from one processor to another, C programmers are.
First, since they use the AC zero-crossings as a timebase, they
don't work when the power goes out.
Second, they often don't work across circuits.
Third, there are some appliances (Sony TVs are the classic example)
that do such excellent filtering of their incoming AC power that they
suck
the X-10 signal right off the power line. One Sony TV won't usually do
it, but two or three in the same house will generally cause problems.
Fourth, X-10 is very slow... Simple commands can
take a second
or so to be executed, and complicated ones (like the sequence required
to activate BSR's burglar-alarm sirens) can take many seconds. This
isn't
a problem for most typical X-10 applications.
10 parts per million, uncorrected in software, works out to an error
of, at most, 5 minutes per year. If that's too much error for you, you
can find people who make 5 ppm crystals. Instead of simply plating the
crystals with silver, however, the manufacturer has to deposit a small
layer of chrome or gold under the silver base plate, then vapor-deposit
a final plate of solid gold over the whole thing. More manufacturing
steps
+ more labor + higher raw-material cost = a very expensive
crystal.
I'm assuming that your Manchester representation uses a gap/pulse
sequence for "1" and a pulse/gap sequence for "0", and that the message
begins with a couple of "1" start bits:
When I say, "Start the TIMER", etc., in the description above, you can either use a hardware timer or a software loop-counter. I'd prefer the latter, but it'll work either way.
The IF_SHORT and FIRST_HALF flags may require a bit of explanation... FIRST_HALF simply tells us whether we're looking at the first half of a frame or the second. IF_SHORT is a little more complicated; it tells us three things:
Besides, it doesn't pay very well.
I have a feeling that the cost to develop an ASIC would be greater than the cost to simply switch to another processor... Have you considered the Motorola 6805? Its instruction set is nearly the same as the 6303's (with the exception of the double-accumulator instructions, instructions which require a 16-bit index register, and a couple of the more obscure bit-test instructions) and many members of the 6805 family can access external ROM through a 64K address bus.
SAW resonators don't inherently improve a transmitter's range. They do, however, keep its frequency from drifting. Since frequency-drift over time can be a primary cause of decreased-range complaints with tuned-L/C transmitters, this is a good thing.
SAWs aren't quite as accurate as crystals, but they cost a lot less and are certainly good enough to be used with the relatively wide-bandwidth receivers that they're usually mated to in car alarms, automatic garage-door operators, etc.
For all you ever wanted to know about SAWs, talk to RF Monolithics. They don't seem to have a web page, but you can call them at 214 233-2903 (fax: 214 387-8148).
You can find it at ftp://ftp.rocksoft.com/clients/rocksoft/papers/crc_v3.txt.
We use Hamming codes to ensure correct reception of radio data transmissions, to increase the effective endurance of EEPROM cells, etc.
In order to keep from boring you, I'm not going to discuss any of the math behind Hamming codes. If you'd like a rigorous mathematical explanation, there are plenty of discrete-math and error-control coding books available. One of the easiest to follow, by the way, was written by Hamming himself; it's called Coding and Information Theory (ISBN 0-13-139139-9). If you're a real glutton for punishment, you might also want to try to wade through Error Control Coding, by Lin & Costello (ISBN 0-13-283796-X), or An Introduction to Error Correcting Codes with Applications, by Vanstone & van Oorschot (ISBN 0-7923-9017-2).
Let's start with an analogy to binary error-correction. Instead of dealing with binary 1's and 0's, think about the problems of error-correcting English speech. If someone says, "base" and we hear "case", we accept the word, since "case" is in our language. This is the problem we face when we send binary messages without encoding; all combinations of 1's and 0's are allowed, so if a word has errors, we can't tell.
Fortunately, not all combinations of letters form valid English words. If you're reading English text and see "xase", you know there's an error (because "xase" isn't in the language), but you don't know whether the word should be "base", "case", or "vase". This is weakly analogous to the way that checksums and single-bit (odd or even) parity work.
The problem is that the words are too close together; only one letter separates the three words from each other. Even worse, the "b", "c", and "v" keys are adjacent on most typewriter keyboards. In order to correct the error, rather than just detecting it, we need some way to distance the words from each other, so that a large number of errors will have to be made before one word can be confused with another.
The "Alpha", "Bravo", "Charlie"... phonetic alphabet was developed for precisely this reason; none of the 26 words are similar to any of the others. If a pilot hears "Barley", he knows not only that an error occurred (since "Barley" isn't one of the 26 valid words), but he also knows the correct message, since the only word close enough to be incorrectly heard as "Barley" is "Charlie".
We can separate our words by adding a distinctive tag to each of them. Instead of saying "base" and "case", we could say "baseball" and "caseload". If someone hears "caseball", he knows it should be "baseball", since those two words are close (only one letter apart), while "caseball" and "caseload" are distant (four letters apart). He can mentally strip off the tag and safely assume that he should have heard "base".
Ok...
What Hamming did was to apply these ideas to binary messages. Here's how:
The "distance" between one string of binary digits and another is the number of digits in which the two differ. For example, the distance between 1011 and 1010 is 1. The distance between 1011 and 1101 is 2.
In order to correct all 1-bit errors (that is, 1 incorrect bit anywhere in the word), we need to construct a set of code words such that the distance between any two code words in the set is at least 3. Take a minute to understand why this is true.
By the way, a distance of 3 also allows us to detect all 2-bit errors.
Let's say we want to send 4-bit words. Without encoding, the minimum distance between any two of the 16 possible words is only 1. This is no good for us.
To separate the words further, we construct our code words by adding a 3-bit tag to each 4-bit word, like this:
0000 becomes 0000 000 1000 becomes 1000 110If you have nothing better to do, you can examine the above list and see that no two 7-bit code words are closer than three digits.
0001 becomes 0001 111 1001 becomes 1001 001
0010 becomes 0010 011 1010 becomes 1010 101
0011 becomes 0011 100 1011 becomes 1011 010
0100 becomes 0100 101 1100 becomes 1100 011
0101 becomes 0101 010 1101 becomes 1101 100
0110 becomes 0110 110 1110 becomes 1110 000
0111 becomes 0111 001 1111 becomes 1111 111
There are sophisticated ways to decode Hamming codes using syndromes and large matrices. For a microcontroller application using a small Hamming code like this one, though, the following method is probably easier:
Upon receiving a code word, compare it to the code words in the list above. If it matches any of them, accept the first four bits of the code word. If changing the state of any single bit in the received code word produces a word that matches any in the list, change that bit, then accept the first four bits.
Note that proper decoding is not dependent upon error-free reception of the 3 tag bits... That is, there's no chance that an error in the tags will cause your decoding algorithm to erroneously "correct" an otherwise-good message.
Probyte
homepage
Pekka Ritamaki
Copyright ©
1998,1999,2000,2001,2002,2003,2004 by Probyte
.