logo
background
 Home and Links
 Your PC and Security
 Server NAS
 Wargames
 Astronomy
 PhotoStory
 DVD making
 Raspberry Pi
 PIC projects
 Other projects
 Next >>

Using the PIC for everything

Links to all my PIC tips, tricks and 'mini-project' notes

Whilst the mid-range PIC's can tackle many complex and otherwise almost impossible applications with ease, the challenge is to minimise cost by using the cheapest baseline PIC 'whenever possible'. Baseline PIC's can be had for less than 50p each = I purchased many 16F5x chips for between 40 and 50p each (mainly from CPC as 'remaindered' stock in their 'Bargain bin' section).

The even cheaper to use 12F675 (it has an internal OSC) can be found for as little as 20p (in Qty 10pcs, eBay), as can many other PIC's for less than £1 each. These PIC's are so cheap that you will soon start using them 'for everything' (especially as the PIC can often be used in place of a higher cost 'single function' digital chip - such as divider, ADC, PWM generator etc.) !

Buying the PIC in a 'TSOP' package is (sometimes) cheaper than the DIL/DIP package version = and whilst this costs you 10-20p extra for a mini-PCB TSOP-DIP 'converter', if you use a 'bigger' PCB than the PIC TSOP really needs you can mount other devices (resistors, caps, even osc. crystals) on the same board - and make use of the extra 'pin holes' to wire this up to the rest of your circuit

Below is a mix of programming tips and tricks, common circuit tricks and all the 'mini-projects' I've used the PIC for

I hope these details proves as useful to you as it does to me !

Below, click on the '+' to expand 'in place' (includes diagrams/images) or click the title URL (to view/download the text only version).

(+) 0004 Multi byte ADD - (24bit)

(+) 0005 new PIC 33 instruction set - (macros)

(+) 0006 Binary multiply methods

(+) 0007 8x8 - (multiply)

(+) 0008 8x16 - (multiply)

(+) 0011 Bi color LED driving

(+) 0012 One pin dual LED and button detect

(+) 0013 Input only multi button detect

(+) 001a One pin controls motor Fwd off Reverse

(+) 001c One pin controls 3 relays



(-) 0020 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
BCLR b0,ACC
COPY ACC,dmyReg
; 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 !
RETURN


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
TRIS
BSET b1,Acc		; clk Hi
TRIS
BCLR b1,Acc		; clk Lo
TRIS
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)
TRIS
BCLR b1,PortA            ;set o/p i/o b0 value 0
BCLR b1,Acc              ;set TRIS b0 to o/p mode = clk Lo
TRIS
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.

[top]

(+) 0021 I2C code

(+) 0021 Serial link - (9600 baud)

(+) 0028 RS422 RS485 drive with one PIC pin

(+) 0030 D to A conversion - (R2R DAC)

(+) 0031 Ternary DAC - (R3R)

(+) 0032 Hybrid ternary bit conversion - (code)

(+) 0035 Pulse Width Modulation - (PWM)

(+) 0040 Gearing axis sensor

(+) 005a TYC50 AC motor details

(+) 0061 16F54 2char month map - (VTI)

(+) 0062 DDmmmYYYY character map - (VTI)

(+) 1000 PIC16F684 tips and tricks

(+) 2000 18Fx tips and tricks

(+) 6500 18Fxx data Table output - (max rate)

(+) 6501 18Fxx Return with value LUT - (max rate)

(+) 6502 18Fxx extended instruction data output - (max rate)

(+) 6530 simple data transmission

(+) 6540 Using RS485

Next subject :- index

[top]