Home and Links
 Your PC and Security
 Server NAS
 DVD making
 Raspberry Pi
 PIC projects
 Other projects
 Next >>

Using the PIC - a basic overview

PIC basics

PIC differences

There are 4 slightly different 8bit CPU's, starting with the 'base-line' 12bit instruction set (12F and 165x devices), the mid-range 14bit instruction set (, the enhanced mid-range (14bit) set and the 'high end' 18F device with a 16 bit instruction set. Then there are the 16bit CPU (24F and dsPIC) and finally the PIC32 32bit CPU devices. For more on this, see wikipedia PIC instruction sets

Normally, you would only expect to program the 8bit devices - especially the base-line 12bit set - in assembler - and the 18F devices (and above) in C.
The mid-tange and enhanced mid-range devices can be programmed in either.

The 12F range consists of the 8 pin devices with 4 or 6 i/o pins. The 16F devices just have a lot more i/o lines (at least 12). The 18F have a much more advanced CPU, more built-in circuits and cost a lot more :-)

The 12F and 16F PIC's have similar internal "extra's" = almost all have counter/timers, many have internal OSC circuits and analogue comparators, some have A-D converters and serial data support circuits (I2C etc).
Due to the relatively low program space count (a minimum of 512 and a maximum of only a few thousand) these devices would almost always be programmed in 'assembler'.
However all the PIC 12F and 16F5x series PIC's have the same 'base-line' CPU 12bit instruction set, which makes it easy to 'migrate' code from one device to another (although the 'mnemonics' (instruction 'names') are rather hard to remember as they make so little sense)
Other 16F devices have the mid-range CPU with an enhanced 14bit instruction set.

The 18F devices have a lot more program space and would normally be programmed in C.

Microchip make their basic assembler / C compiler software available for free. This gives them a considerable advantage in the hobbyist market
Their assembler has good macro support, so it's quite possible to replace the Microchip 'mnemonics' with something rather more logical.

Using the older low-end PIC16Fxx (PIC16F54, PIC16F57, PIC16F59) series

Whilst these chips may be still available today (2016), they are essentially obsolete and should not really be considered for new projects. I have stock left over from the 'good old days' - which is why I continue to use them - however new users should go straight to the 16F16xx 'enhanced' series or (better) the 18F series and skip the 16F5x series completely.

In case the 16F5x becomes 'too old to support', you can download the full data sheet (pdf) from my page here

The PIC16F54 (in an 18 pin PDIP package) really is about the most basic usable 'entry level' compute device you can buy. For 44p each (Qty 10 off price, CPC) you get a 5v 'TTL' device capable of running from 2v to 5.5v and from 4MHz (<3v) up to the official maximum of 20MHz (at 4.5v+). Unofficially they can be overclocked up to 32MHz at 5.5v (and will act like a fuse above 6v), and all this for less than the cost of most single function 74xx 'support' IC's !

If you need more i/o pins, instruction space or registers (RAM), the 16F57 (28pin, 20 i/o, 2k instructions, 72 bytes of RAM) comes in at 69p and the 16FG59 (40pin, 32 i/o, 2k instructions, 134 bytes RAM) at 84p, however you should note that the more 'advanced' 16F16xx starts to come in at these prices (eg the 16F1615 is only 86p (10off, Farnell)) !

Note that in many cases it will be cheaper to use a second PIC than to purchase a 'dedicated' counter, shift register, UART, I2C comms etc. chip

The 16F54's 18 pins consist of :-

Power (Vdd) & GND, the 2x OSC pins (supporting various clock generation methods), the Reset pin (MCLR), the 'clock' (T0CLKI) for the single** 8bit counter (TMR0) and 12 'general purpose' i/o pins (one 8 bit set, one 4 bit set)
**There are actually two embedded counters - the second is the Watch Dog Timer (WDT). The WDT runs from it's own free-running internal clock, and, if enabled, will 'time-out' (if not 'restarted' every 18mS, or at some multiple of 2x 18mS if using the 'pre-scaler') causing the chip to 'Reset'. Note that a Power-On-Reset (detected by monitoring the Vdd supply voltage) clears 'everything' (and imposes a power-on delay of approx 18mS (1 WDT time)), whilst a 'normal' WDT Reset or MCLR (pin) Reset does not.

The 'pre-scaler' is a binary clock divider with 8 modes (1/2 to 1/256) which can only be used with either the WDT or TMR0. TMR0 is clocked from the 'instruction' clock (OSC/4) or, optionally, from an external dedicated pin (T0CLKI)

One thing the 16F5x lacks is any sort of 'interrupt' facility (other than WDT/Reset) - something that almost every other PIC has. However it does have a 2 level 'subroutine call/return ' stack

Power on, WDT time-out and /MCLR (pulling the 'Reset' input pin Lo) all 'set' the program counter (PC) to "all 1's" and the CPU executes the instruction at the top of the program Flash memory (address 0x01FF in the '54). If this is a 'NOP', the PC 'rolls over' to address 0x0000 and continues execution from that point. Two 'status' bits can be used to determine the 'cause' of the Reset (WDT or MCLR/WDT after Sleep) so (with clever programming) a 'single level' Interrupt is possible (although the state of most registers is lost)

The 16F54 8bit CPU supports 33** instructions (each 12bits wide) held in a 512 instruction word Flash memory. It has 25 general purpose (byte) registers (plus 7 special function registers). The 8bit ALU performs "2's complement" addition and subtraction on single byte values. The CPU takes 4 OSC cycles to execute 1 instruction, so, when driven at it's official maximum 20MHz, this 44p chip is running at 5 MIPS ! (it achieves 8MIPS at 32MHz overclock)

** The '33' instructions include both 'compute' instructions and 'control' instructions, which makes things a bit confusing. At least one instruction is 'redundant' (the 'Clear Accumulator' instruction does exactly the same as 'AND Accumulator with 0x00', even to the detail of only setting the Z status bit). All instructions execute in 1 CPU CLK, except the 7 'jump' type instructions which take 2 CPU CLK's.
Confusion is caused by the .PDF data-sheet 'PIC-speak', the misleading usage of a 'general' name for a specific function.
So, for example, 'PIC-speak' typically refers to function control Latches, the Accumulator and the Indirect Addressing Pointer as 'registers' whilst the RAM locations are called 'register file' addresses.
The fact that the control Latches are mapped into the Register address space then adds to the overall confusion (the Indirect Addressing pointer ('File Select Register' or FSR) is 'register 4' (and to invoke it, you address regsiter 0). The (bottom 8 bits of the) Program Counter is 'register 2', the i/o pin Data Latches (Port A and B) are 'register 5' and 'register 6'). The Timer/counter is register 1 and the ALU Status bits can be found in register 3.
Of course not everything is 'mapped' - the Accumulator (which in PIC-speak is the 'W register') has no register address and other notable exceptions are the subroutine return address 'stack' and the i/o ports direction control latch ('tri-state latch' or TRIS in PIC-speak)

One thing to note is that CALLing a subroutine clears the 9th bit (for the 16F54, that's the top bit) of the program counter !

In other words, all subroutines must start within the first 256 memory locations (otherwise they are not 'reachable' via a CALL). The return stack holds all bits of the 'from' address (so you can CALL from a higher address). The direct GOTO instruction supports 9 addresses bits, so subroutines above the 256 address 'boundary' can be reached via an 'indirect' call (i.e. you CALL to a location within the first 265 that then performs a GOTO the actual subroutine above 256)

Note that the 16F57 and 16F59 uses 'paging' to address locations above 512 and the '256 address Subroutine boundary' then applies to each page

Each instruction executes in 4 OSC cycles. A simple R/C circuit connected to one OSC pin allows operation up to 4MHz, in which case OSC/4 (the instruction cycle clock) is output on the other OSC pin. Faster speeds (up to 20MHz) require the use of a 'resonator' or crystal oscillator (and uses both pins - OSC2 is the 'driver' and can be used to 'pick off' the OSC clock for an external circuit).

The max. frequency achieved depends on the supply voltage (Vdd). At 3.3v Vdd, OSC is limited to 4MHz. At 4.5v it's 10MHz, and only above 4.5v will it achieve 20MHz. These speeds are all with a Crystal - if you use the R-C circuit (to keep costs down) the max speed (at 5v) is 4MHz (1 MIPS)

The data sheet specifies 20MHz all the way up to the standard operating max. Vdd 5.5v, however users have reported overclocking to 32MHz at 5.5v and above (Vdd must be kept under 6.5v, the device data sheet 'absolute max rating' = and the data-sheet also warns that "exposure to max rating for extended periods may effect device reliability")

The wide voltage range makes these devices well suited to battery operation (4x '1.2v' NiMH rechargeable will give at least 4.8v (or up to 5.6v when fully charged). Power dissipation is OSC and Vdd dependent. Max Vdd, max OSC means max. Pdisp so perhaps not the best approach for battery operation :-)

The 16F684 is the 'top end' of the 'bottom' range PIC's

This fantastic little PIC's comes in a 14 pin DIL/SOIC package with 2k program instruction space and 128bytes RAM (and 256 EEPROM). However, it's key advantage is it's 10 bit ADC and the price = 48p each

The 10 off / SOIC-14 package price is £4.74, eBay (Post free from China) as of Nov 2016, so 48p each. Adding another 10p (or less) for the SOIC PCB (which gets us wiring holes) takes this to 69.5p. The DIL version best price I could find is 78.5p (10off price £7.69, eBay China).
Only the simpler (2k program, 128 RAM, 8bit DAC, no anlaogue but with serial SPI/I2C slave capability) 28pin PIC16F72 (in TSOP) can beat this price (10pcs can be had for £2.88).
The best price I could find for a DIP package PIC was for the 16F630 (5pcs for £2) a basic DIP14 device with no DAC (but 1k program, 64 RAM and 128 eeprom)
NB. DO NOT be tempted to buy from the 'UK' sellers who simply buy in lots of 10pcs from China and then resell them one or two at a time to mugs for 3 to 5 times the price

The 16F684 requires minimal use of external components, so a single SOIC 20/24/28 'adapter' PCB may be all you need to mount everything (I purchase SOIC-28 adapters in lots of 20pcs for £2.19 (so about 10p each) from 'ynaan' on eBay = these typically arrive in less than a week via airmail)

The 16F684 offers the choice of an internal 8MHz OSC divided by /1 to /64 (so you can get a max. of 2 MIPS without using any external components at all). The 31kHz OSC (used for the WD counter) can also be used (for ultra low power consumption.
An external R-C OSC capability exists that requires the use of a single pin. The formula for OSC period is Time = R(k) * C(pf) ln [(Vdd/(Vdd-Vih)] (where Vih = OSC pin input Vhi = 0.9*Vdd). Using max. recommended values of R=100k and C=22pf at Vdd=5v gets OSC=20MHz. A crystal controlled OSC requires the use of 2 pins.
The maximum OSC is specified as 20MHz (Vdd 4.5v and higher), however, as with other PICs, at 5.5v you can expect to overclock to at least 24MHz. From 3v ro 4.5v the max. OSC is 10MHz (and below 3v the max. is 4MHz, all the way down to Vdd min. which is 2v !!).
As well as a 10bit DAC, this PIC includes 2 analogue comparators, 3 timers, a 10bit PWM and an 8 level circular stack - but has no serial communications circuits (so you will need to read all about 'bit banging' :-) )
The 16F684 i/o pins (organised as 2 PORTs, A and C, each of 6 bits), when used as inputs, can even have internal 'weak pull ups' (which can further save on the sue of external components).
Note that the RAM is organised into 2 Banks of 128. In addition to various control latches ('special purpose registers'), the first Bank addresses 96 'general purpose' registers, the second 32. Normal instructions use 7 bit addressing + a 'Bank select' bit (from the Status Register), however the Indirect Address pointer is 8 bits and overrides (but does not 'overwrite') the Bank Select bit.

It's worth noting that i/o pins with an 'analogue' capability will power-on with the 'digital' control circuits (including the input biffer) 'disabled'. So trying to 'read' the pin (without setting 'digital' mode) will always get you '0' :-)

(-) I2C bit banging

PIC "Bit Banging", I2C serial comms

What is “Bit Banging” ?

'Bit Banging' is PIC code written to send data one bit at a time using a minimal number of i/o pin for serial transmission. The code is typically highly optimised in order to maintain the maximum possible speed. This means the code is written in 'assembler', sometimes 'byte in line' (i.e. as a 'stack' of 8 'send bit' fragments, rather than 'looping' 8 times sending 1 bit at a time).

Plainly, such code is required only for PIC devices that do not have any sort of built-in serial transmission hardware - i.e. the low end PIC16 C/F 5x devices. The drawback of these devices is their rather limited instruction set.

Coding the 16C/F5x devices
Some of the instruction limitations are just 'annoyances' (such as the fact that a subroutine CALL destination is limited to the first 256 bytes of each memory page), which can be 'worked around' (eg by using 'indirect Call' tables - at the cost of at least 2 extra instruction cycles per 'Call'). Others - such as the fact that there is no 'increment Accumulator' (aka W "register") and the 'Inc/Dec register skip on Zero' instructions does not set the Z flag - enforce fundamental restrictions on your coding approach (whilst waiting to 'trip you up' should you forget which instructions effect the Status bits (and which do not)).

If you do ANY assembler coding, I suggest you do it with a print-out of 'Table 9-2: Instruction Set Summary' from the PIC16F5X pdf manual in front of you !

Using the Microchip MPLAB IDE
If you think web page CMS ('Content Management Systems') are a real pain, you should try Microchip's IDE ('Integrated Development Environment'). Written for DOS users, it can be almost impenetrable !

HOWEVER it does has one VITAL advantage - it offers full MACRO support, which allows you to write your own Macro 'library' and thus 'replace' Microchip's total confusing and non-intuitive 'PIC-speak' instruction 'names' with your own !

'Blink that LED' - the PIC in action.
Most PIC programs are written to 'blink a LED' when they start running in order to provide some 'instant feedback' to the user (and show that the code is, in fact, 'working'). Since most PIC projects quickly find a use for every i/o pins you will soon run out and start looking to 'double up' the function of some pins - and the 'serial output' pin is the obvious one to 'double up' as the 'blinking LED' drive - with the advantage that (if you run the 'bit banging' code slow enough) it's even possible to send visual messages :-)

PIC power limits
When driving multiple LED's you need to watch out for the PIC current limits = you may have 20mA 'per pin' to play with, however you are limited to 50mA 'per PORT' and 100mA for the whole device. In effect this means you can only use cheap 15-20mA LED's if you only require one or two - if you need to drive half a dozen or more LED's you will have to stick to the more expensive high efficiency 3-5mA LED's.

Note that you can 'pulse drive' a LED with higher 'instantaneous' currents so long as the 'average' is within both the LED and PIC power limits (a LED driven with double the current for half the time appears a lot brighter to the human eye than one driven with the normal current all the time).

NB. When choosing current limiting resistors, don't forget that the PIC16F5x only runs at 20MHz if you supply it with '5v' (the more LED friendly '3.3v' supply limits you to 10MHz).

Why serial ?

If you want to communicate with a PC, using it's Serial Port is a lot easier than using the Parallel (Printer) Port, especially as so few modern PC's actually have Serial or Parallel Ports (so, in practice, your PIC serial data will actually be wired to a Serial <> USB converter)

Standard (UART implemented) serial comms is 'asynchronous' in that that both the transmitter and receiver have to be aware of (and maintain) the same fixed data rate. SPI and I2C are synchronous protocols - they have separate data and clock lines - which allows the transmitter to vary the bit rate 'as much as it likes'.

For 'bit banging' this means the 'bit send' program code can be written as 'tight' as possible i.e. there is no need to 'pad out' a 'fast bit' to match the same CPU cycle count as a 'slow bit'. The disadvantage is that it takes twice as long to send 1 bit using a synchronous protocol (since you have to 'toggle' the clock before changing the data bit)

I2C is similar to SPI, it that it is a two wire (data and clock) communication system, but differs in that it is 2 wire only (i.e. drops the two (or more) 'master select' / 'slave select' wires needed to implement SPI). More to the point, the industry standard 'I2C' serial comms protocol is directly supported by many peripheral chips such as RAM / EEPROM (storage) and analogue processing chips (amplifiers, DAC's etc) which are likely to find their way into your projects.

Since I2C uses only 2 wires, it is about the 'least expensive' (in terms of hardware) choice, since the absolute minimum '1-wire' protocol is designed for low speed and is rather more complex to implement (it uses 64 bit 'ID codes' to identify devices).

The '1-wire' protocol is used by Dell laptops to identify Dell Power Supplies (via the 'center' pin of the power plug), so may be of interest to those with Dell laptops that refuse to charge due to failure of the '1-wire' hardware (the pin is extremely fragile, as is the laptop socket)

How does I2C work ?

I2C is a 2 wire 'bus' (SCL = clock, SDA = data) that multiple devices can attach to using a 'Master / Slave' control scheme. Initially specified at Vdd=5v for data rates up to 100kHz, modern implementations typically support 3v3 and data rates in 'Fast mode' (400kHz) and 'Fast mode+' (1Mbps). These days, many devices also support 'High-speed' (3.4 Mbit/s) and 'Ultra Fast-mode' at up to 5 Mbit/s (see, for example, the UM10204 specification).

Both wires are fitted with 'passive pull up' resistors (and must never be driven with an 'active' Hi). This causes particular problems for PIC 'bit banging' because you are forced to use the single 'TRIS (Acc to Port Tri-state Latch)' command to switch SCL/SDA i/o pins from 'active Lo' (0v) to 'tri-state' (off) and back again. This means using 'clever tricks' to manipulate bits in the Accumulator (W 'register').

Most systems define 0.3Vdd as 'Lo' and 0.7Vdd as Hi. On a 3v3 I2C bus, 'Lo' will be 0.99v, on a 5v system Lo is 1.5v (or less). Since only 'Lo' drive is wanted (or permissible) it's possible to 'cheat' by using in-line low forward voltage (Schottky) diodes to 'eliminate' the Hi drive. This would add about 0.3v to the PIC i/o pin 'Lo' of 0.6v = 0.9v. This is well within the 1.5v limit for a 5v I2C system but rather near the max. for a 3v3 system

Sticking with 'tri-state' approach (see below) requires about 7 CPU instructions to send each bit. On a 20MHz PIC, that's 28 OSC cycles and means a potential data rate of just over 710 kbps (i.e. requires a receiver that supports Fast mode+ (1 mbps)).

Since the PIC is the 'Master', it can control data receive rate, so should have no problems clicking data out of a Fast mode+ (1 mbps) chip.

Data has to be 'set' before it is 'clocked'. You are not allowed to change the data whilst the clock is Hi (except during the start sequence). Data Transmission starts with the MSBit and is a 3 step process :-
Set the next data bit
Set the clock Hi
Set the clock Lo
(.. repeat)

Since multiple 'Master' devices can exist, the first step is to 'take control' of the bus with a 'start sequence'. Once that has been achieved, the 'Master' then has to 'select' a 'Slave' before it can actually send data. Having selected the Slave, the PIC then sends the 'destination register/address' (within the Slave) and finally the data i.e. :-

1. Send a start sequence
2. Send the I2C address of the slave with the R/W flag (bit0 = low is Master sends data, Hi is Slave sends data)
3. Send the Slave register number / address you want to write to / read from
4. Send (or receive) the actual data byte(s) (if the destination (address) is a 'buffer', you can send multiple data bytes to 'fill the buffer' **)
5. Send the stop sequence.

**Note that, when sending to the Slave, the Slave must 'respond' with an 'ACK' after each byte before you can send another one.

Gaining control of the I2C bus
In a multi-Master system, 'Bus Contention' can occur when two Masters try to gain control of the bus at the same time. The I2C bus is 'idle' when both lines SCL & SDA are 'Hi'. Further, during the transmission sequence, SCL (clock) is 'normally Lo' (i.e. it is 'pulsed' hi to transmit a bit). So, if a Master 'spots' both lines are Hi and notes SCL is Hi for 'a significant' period, it can assume the Bus is 'free' and take control.

To 'take control' (start a transfer) a Master pulls SDA Lo BEFORE pulling SCL Lo. To 'give up' (end a transfer) the bus, the Master ensures that SDA is Lo, then lets SCL go Hi BEFORE allowing SDA to go Hi again. Other Masters that are waiting for control of the Bus can look for the 'end transfer' sequence and 'jump in'.

It is, of course, possible for a 'race condition' to occur when two Masters both 'spot' the Bus is idle and both issue a 'start' at the same time. This will lead to garbled transmission - however by 'checking' the state of the SCL and SDA lines during transmission a Master can quickly detect when their 'Hi' is being corrupted by another Masters 'Lo'

Optimised 'bit banging' code Many 'tutorials' offer you a 'C code' approach, which, of course, is never going to be 'as fast as possible' (and is a big joke on a 16F54 PIC that has space for only 512 instructions :-) ). Another common mistake is to write 'pseudo code' that is only really suitable for PIC 18Fxxx devices with larger instruction sets (and many kb's of instruction space) - however such devices typically have built in I2C hardware, so have no need for 'big banging' code :-). Finally, some authors go out of their way to create code capable of dealing with 'any' bit counts, which (of course) leads to 'count and test' loops sending single bits which adds multiple additional instructions to every bit transmitted. By assuming all I2C transmission is 'byte based', the 'loop' can be eliminated (if your project calls for something else, feel free to modify my code :-) ) The code I present here is aimed at the 16F54, and fits well within the 256 subroutine space (so no nonsense with 'paging', and no need for 'Call tables') NOTE. The TRIS latch controls all bits of a PORT and is not 'readable' = so there is no way to 'preserve' the existing i/o pin mode HOWEVER I will assume a 'copy' will be held in a Register (lets call it 'PortA-trisReg') PORT bit0 must be used as SDA (serial data). SDC could be any bit, however I'm going to use choose b1. NOTE that on entry to the I2C code it is assumed that b0,b1 are already 'tri-state', however the state of PORT A o/p data latch is not assumed. The big problem is the need to manipulate the TRIS bits in the Acc before using the 'Copy Acc to TRIS' command (this being the only possible way to change the TRIS latch, although at least the 'polarity' is correct (1 on TRIS = tri-state, 0 = enable o/p), so if the PORT Data latch contains a '0', the data 'bit' only has to be copied to ACC (and thence to TRIS) To extract the next data bit (without using Bit TEST instructions) we can ROTL the dataReg - this puts bit7 into Cy (and the Rotated Reg back to itself). The quickest way to get that bit into the ACC is to perform a ROTL with a 'dummy' Reg as the 'source' and Acc as the 'destination'. Whilst 'dummy' is not changed, the ACC (and thus TRIS) bits 1-7 will be taken from 'dummy' 0-6 and ACC (TRIS) b0 from Cy. So 'dummy' is, in fact, a copy of 'PortA-trisReg' that has been pre-shifted right 1 bit. Since I2C CLK has be be kept 'Lo' whilst data is being changed, b1 of the ACC must also be kept Lo. This means 'dummyReg' b0 must be a 0 (since, after ROTL, ACC b1=dummy b1, ACC b0=Cy). NB. Bit Set uses 'OR Immediate with ACC' and BClear uses AND Bit output is thus :- ROTL dataReg     ;rotate the dataReg with top bit to Cy ROTL dmyReg,Acc  ;ACC b1=Lo (from dummy b0), ACC b0 comes from Cy TRIS ;SDC stays Lo, enable b0 (if data was Lo) - data changes now BSET b1,Acc      ;prepare SDC bit Hi TRIS             ; o/p CLK Hi BCLR b1,Acc ;prepare SDC bit Lo TRIS             ; o/p CLK Lo ... and loop for the next bit (there's no need to reset b0 data bit, it will be overwritten on the next ROTL from Cy) In the above 7 instruction sequence, the data bit is valid from 2 CPU CLK before CLK Hi and remains valid until changed. However the I2C CLK is 'Hi' for only 2 CPU Clk periods (which is only a problem if we are 'talking' to another PIC = see Data receive below) Code
Clear PORT A o/p latch
Read PORT A, b0 (SDA) - if Lo, jump to BusBusy:
Read PORT A, b1 (SDC) - if Hi jump to StartSeq:

BusBsy:	;here another Master has control of the Bus, so we have to wait for an 'end sequence'
(loop waiting)

StartSeq:  ;OK, Bus idle, lets go with the start sequence
Send 0x0E to TRIS PORTA		; enable b0 (only), = set SDA Lo (SCL is Hi)
Send 0x0C to TRIS PORTA		; enable b0 and b1, = set both SDA and SCL Lo
; OK, we have control ... set up dummyReg
ROTR PortA-trisReg,Acc      ;get current TRIS, shifted Right
; OK, now we send the Slave address (note, b0 indicates if we are reading (hi) or writing (lo) the Slave)
Copy SlaveID to Acc
Call SendByte:
; We have the Slave, now send the Control code (Slave register address)
Copy SlaveReg to Acc
Call SendByte:
; OK, now read or write a byte
Test SlaveID, b0 - if Hi jump to I2CRead:
; it's write, so do it
Copy SlaveData to Acc
Call SendByte:
; OK all done, let the I2C bus go
Send 0x0C to TRIS PORTA		; enable b0 and b1, = set both SDA and SCL Lo
Send 0x0E to TRIS PORTA		; enable b0 (only), = keep DA Lo whilst allowing SCL Hi
Send 0x0F to TRIS PORTA		; disable both bits, SDA and SCL both Hi
; and that's it !

SendByte: ; 7 Instructions per bit (plus some start and end overhead) ; On entry both Data and Clock pins are Lo, so Acc is 0x00 ; The byte to be sent is in dataReg (b7 is sent first) ; PORT A (b0-3) is used for o/p, b0 = data, b1 = clk
; Set the data pin, set Clk Hi, Clk Lo, then set next data bit
ROTL dataReg     ;rotate the dataReg with top bit to Cy
ROTL dmyReg,Acc  ;ACC b1=Lo (from dummy b0), ACC b0 comes from Cy = data top
TRIS			 ;SDC stays Lo, enable b0 (if data was Lo) - data changes now
BSET b1,Acc      ;prepare SDC bit Hi
TRIS             ; o/p CLK Hi
BCLR b1,Acc		 ;prepare SDC bit Lo
TRIS             ; o/p CLK Lo
ROTL dataReg     ;next data bit data to CY
ROTL dmyReg,Acc  ;ACC b1=Lo (from dummy b0), ACC b0 comes from Cy = data top
TRIS			 ;SDC stays Lo, enable b0 (if data was Lo) - data changes now
BSET b1,Acc      ;prepare SDC bit Hi
TRIS             ; o/p CLK Hi
BCLR b1,Acc		 ;prepare SDC bit Lo
TRIS             ; o/p CLK Lo
ROTL dataReg     ;next data bit data to CY
ROTL dmyReg,Acc  ;ACC b1=Lo (from dummy b0), ACC b0 comes from Cy = data top
TRIS			 ;SDC stays Lo, enable b0 (if data was Lo) - data changes now
BSET b1,Acc      ;prepare SDC bit Hi
TRIS             ; o/p CLK Hi
BCLR b1,Acc		 ;prepare SDC bit Lo
TRIS             ; o/p CLK Lo
ROTL dataReg     ;next data bit data to CY
ROTL dmyReg,Acc  ;ACC b1=Lo (from dummy b0), ACC b0 comes from Cy = data top
TRIS			 ;SDC stays Lo, enable b0 (if data was Lo) - data changes now
BSET b1,Acc      ;prepare SDC bit Hi
TRIS             ; o/p CLK Hi
BCLR b1,Acc		 ;prepare SDC bit Lo
TRIS             ; o/p CLK Lo
ROTL dataReg     ;next data bit data to CY
ROTL dmyReg,Acc  ;ACC b1=Lo (from dummy b0), ACC b0 comes from Cy = data top
TRIS			 ;SDC stays Lo, enable b0 (if data was Lo) - data changes now
BSET b1,Acc      ;prepare SDC bit Hi
TRIS             ; o/p CLK Hi
BCLR b1,Acc		 ;prepare SDC bit Lo
TRIS             ; o/p CLK Lo
;ROTL dataReg     ;next data bit data to CY
ROTL dmyReg,Acc  ;ACC b1=Lo (from dummy b0), ACC b0 comes from Cy = data top
TRIS			 ;SDC stays Lo, enable b0 (if data was Lo) - data changes now
BSET b1,Acc      ;prepare SDC bit Hi
TRIS             ; o/p CLK Hi
BCLR b1,Acc		 ;prepare SDC bit Lo
TRIS             ; o/p CLK Lo
ROTL dataReg     ;next data bit data to CY
ROTL dmyReg,Acc  ;ACC b1=Lo (from dummy b0), ACC b0 comes from Cy = data top
TRIS			 ;SDC stays Lo, enable b0 (if data was Lo) - data changes now
BSET b1,Acc      ;prepare SDC bit Hi
TRIS             ; o/p CLK Hi
BCLR b1,Acc		 ;prepare SDC bit Lo
TRIS             ; o/p CLK Lo
ROTL dataReg     ;next data bit data to CY
ROTL dmyReg,Acc  ;ACC b1=Lo (from dummy b0), ACC b0 comes from Cy = data top
TRIS			 ;SDC stays Lo, enable b0 (if data was Lo) - data changes now
BSET b1,Acc      ;prepare SDC bit Hi
TRIS             ; o/p CLK Hi
BCLR b1,Acc		 ;prepare SDC bit Lo
TRIS             ; o/p CLK Lo
; all 8 bits done, now need to clock in the Slave 'ACK'
BSET b0,Acc		;tri state the data pin
BSET b1,Acc		; clk Hi
BCLR b1,Acc		; clk Lo
RETURN			; exit (Slave ACK is ignored)

7 CPU instructions per bit, with a 20MHz OSC thats 28 cycles or 714 kbps.

I2C data receive

The PIC has to be the 'Master', the device a 'slave' (typical device would be a I2C RAM chip). The goal is to collect data from the slave at 1mbps (why else pay for a 'Fast mode+' (1Mbps) capable device ?). With OSC 20MHz max. CPU CLK is OSC/4 and 1 Mbps means we have 5 CPU CLK's to get each bit.

To get the data bit we send the SDC (click) Hi, sample the data then pull the Clk Lo again (the slave is only permitted to change the data when Clk is Lo)

The data must be PORT b0 (since we use ROTR to get it into Cy). PORT b1 will be used as the Clk. Don't forget that MSB is sent first (so has to be ROTR into the dataReg)

NOTE. The TRIS latch controls all bits of a PORT and is not 'readable' = so there is no way to 'preserve' the existing i/o pin mode HOWEVER I will assume a 'copy' will be held in a Register (let's call it 'PortA-trisReg').

; prepare ACC for TRIS control
COPY PortA-trisReg,ACC
BSET b0,Acc              ;set data pin = input only (should already be the case)
BCLR b1,PortA            ;set o/p i/o b0 value 0
BCLR b1,Acc              ;set TRIS b0 to o/p mode = clk Lo
COPY ACC,dummyReg        ;dummy contains TRIS settings for Clk Lo
; OK here Clk is Lo, ready to go with first bit (MSB)
BSET b1,Acc              ;prepare clk hi
TRIS                     ;do it
ROTR PortA,ACC           ;get data b0 to Cy (without effecting other bits on the PORT o/p latch)
ROTL dataReg             ;load data bit into LSB to reg
COPY dummyReg,ACC        ;reset Acc to clk Lo
TRIS                     ;and Lo

If all 8 bits are coded 'in-line', this is 6 CPU instructions per bit, so 833 kbps (not bad).

To speed this up we have to eliminate one of the instructions and the obvious one is 'COPY dummyReg,ACC'.

That means the previous instruction, ROTR PortA,ACC has to leave the ACC in the 'correct' state for a TRIS to send clk Lo (and leave data b0 =1 i.e. as an input).

This is possible if b2 is used as an input and is wired Lo (b1, Clk will be Hi at this time, so '1' will be rotated into b0). Since b3 will be rotated into b2 (and b2 is wired Lo), b3 has to be Hi (otherwise a TRIS would enable b2 as output !).

This means all 4 bits of PORT A have to be dedicated to I2C use

This note last modified: 22nd Nov 2016 17:40.


Using a typical mid-range PIC (PIC18F2455, 2550, 4455, 4550)

The PIC18F2455 is an 8bit device with 24kb of flash program space (i.e. 12k x 16bit instructions), 2kb of RAM and 256 bytes of EEPROM. The most significant feature, however, is the built in USB 2.0 interface (the other chips in the range, 18F2550, 4455 & 4550 offer differing pin counts and flash program space). These devices are quite complex and whilst it is still possible (just) to program them in assembler, it is simpler to program them in the high level 'C' language.

The PIC programming tools allow assembler code to be combined with C, so you can always code key parts of your program in assembler.

To support USB 2.0, these devices must run at 48MHz internally (they have an internal 4x PLL so you only need a 12MHz external OSC circuit). The CPU clk is OSC(internal)/4, giving a max CPU speed of 12 MIPS

Overclocking is also possible on these devices, however 'your mileage will vary' from 50MHz-60MHz

The 'top of the 16Fxxx' range (the 'K' devices) have internal OSC. circuits (and a 4x PLL) that can run the CPU at 64MHz, so the CPU, at OSC/4, is delivering 16 MIPS (most of these devices have two OSC circuits (or a dual-use main OSC) that supports a 32,768Hz (i.e. supports a RTC - Real Time Clock - function)

Note, however, that these devices typically cost more than £5 each = so you should ONLY consider using them in applications where it's totally impossible to use the Pi Zero

Using the high-end PIC (PIC24FJ64 and above)

The PIC24FJ64 contains a 16bit CPU, 64k of flash and 8k of RAM. Whilst it would be possible to program these devices in 'assembler' the complexity of the tasks you are likely to use it for makes using the 'C' programming language almost mandatory. The 24F series CPU is clocked at OSC/2 (so a '32MHz' 24F has the same CPU clock as a 64MHz 18F part). Note that the high end devices are 3v3 (although some have i/o pins that are 5v 'tolerant')

The free version of the Microchip C compiler is 'non-optimised', however it does include a link to 60 day 'trial' license of the Pro (optimised) function (after which it reverts to non-optimised).
Users report little difference between 'optimised' and 'non-optimised' code.

Whilst they can be be had for as little as £3 each (PIC24FJ644, 5off price, eBay China), these devices have been effectively wiped out by the £4 Pi Zero.

Any project that needs the 'power' of a 'high end' PIC (such as the PIC24FJ64) can be done 10x simpler and faster using the Pi Zero (and at a considerably lower total cost)

Next page :- PIC16F5x tips and tricks