logo
background
 Home and Links
 Your PC and Security
 Server NAS
 Wargames
 Astronomy
 PhotoStory
 DVD making
 Raspberry Pi
 PIC projects
 Other projects
 Next >>
[The simple R-2R binary DAC] [Ternary  logic] [The Hybrid approach] [PWM]
The Binary (R/2R), Ternary (3R/4R) and Hybrid DAC fo a low end PIC

The simple R-2R binary DAC

One deficiency of the low-end PIC's is the lack of any Digital to Analogue (DAC) output (or Pulse Width Modulation - PWM) allowing simple servo motor control) circuit. Fortunately, a handful of resistors can convert 5 or 6 'binary' bits into a stepped control voltage forming a simple DAC

4bit R-2R chain DAC for low end PIC
The 'easy' way to build a DAC is to use the classic R-2R resistor 'chain'.

The chain consists of a series of resistors 'R', with each bit is connected to the chain via double that resistance (2R).

So long as the drive pins output 'the same' voltages (and are capable of delivering 'reasonable' current') this will yield a binary weighted output voltage at the 'top' of the chain (assuming zero current is drawn from the top of the chain, i.e. the top is fed to an 'infinite impedance', like the input to an Op Amp)

For more on designing your own DAC, expand the note below

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

[top]

Ternary (or 'trinary') logic

Using 'n' pins, binary gets you 2^n states, ternary 3^n. This makes a real difference to the number of pins needed very quickly = 3 binary bits (2^3) is only 8 states, whist 3 ternary (3^3) is 27 ! To get a 64 state DAC we would need 6 binary bits, whilst 4 ternary bits (or 'tets') gives us 81 states and 5^3 = 243 ! All PIC's have i/o pins that can generate 3 states = these are '1' (typically 4.3v), '0' (typically 0v6) and 'off' (input mode = 'tri-state' i.e. 'no voltage')

3bit ternary 3R-4R chain DAC for low end PIC
When the PIC pin is used as an output, the divider resistors will dominate the current draw. If we want to keep within 20mA**, Rdivider = 5/.02 = 250 ohms, or nearest E24 (5%) = 270 ohms (although we are using 5% resistors that's the 'absolute value' accuracy - in any 'random' batch you should be able to find 2 resistors making a 'matched pair' (i.e. correct ratio) to within the accuracy of your multimeter (typ 4 digits i.e. .1%) and that's all we need).

**The PIC has both a pin limit (20/25mA) and a 'total PORT' limit (typically 100mA). So you can build a 5 pin DAC at 20mA per pin, but a 6 pin DAC means reducing the current from each to 100/6 = 16mA .. of course when driving (or sinking) 20mA the pin output voltage will won't be 4.3v (or .6v) !

Next we consider the resistor 'ladder'. For ternary, the ladder ratios need to be 3R/4R (rather than R/2R). Since the 'chain' resistors must be high enough to avoid impacting the 'divider' resistor effect, the 'obvious' choice is 75k and 100k.

For more on designing your own Ternary DAC, expand the note below

(+) Ternary DAC - (R3R)

(+) Hybrid ternary bit conversion - (code)

[top]

The Hybrid approach

From LTspice we discover that a 3 pin 'ternary' DAC (so 3x3x3 = 27 levels) 'works' well, however above this the 'half voltage' divider of a 4th ternary bit pin overwhelms the voltages of the 3 lower bits to such a degree that the final ouptut voltage 'steps' are 'reversed' when the 4th bit is switched 'off'.

However we still want to get as many 'steps' as possible from the available i/o pins. So, how can we get over 50 steps with only 4 bits (and over 100 steps with only 5 pins) ?

3bit ternary 3R-4R chain DAC for low end PIC
The 'trick' is to run the 4th (and 5th) bit in 'binary' mode. 4 bits gets us 3x3x3x2 = 54 states whilst 4 binary bits is only 16 states (and 5 pins can deliver 3x3x3x2x2 = 108 states) ! To get anywhere near this, we would need 7 binary bits (which delivers 127 levels), whilst a 'pure binary' 5 bits would only deliver 32 states !

Using 180R for the 'half step' (2v5) divider resisters means each PIC pin sources or sinks 10mA (at 0v6/4v3). Using 3 bits from an 8 bit PIC i/o port uses 30mA, but the 4th bit consumes less than 1mA. This leaves some 70mA (of the 100mA PORT total) to be divided amongst the remaining 4 bits (about 17mA for each). This will need to be taken into account when deciding what to use the other bits for


[top]

PWM

Modern servo motor control circuits are all driven by Pulse Width Modulation. However it's difficult to achieve both a high enough PWM frequency (20kHz is about the minimium) and a decent resolution using a low-end PIC with it's internal (or simple external R-C) 4MHz OSC.

Typically you will want better than 1% speed control.
 
This implies at least 100 'step' PWM mark-space ratio - and assuming you can achieve 1 CPU CLK control this means a 100 CLK cycle time = whihc inturn is only 10kHz PWM frequency (4MHz OSC = 1MHz CLK, 1Mz/100 = 10kHz PWM)
 
The 'trick' is use a 50 step PWM mark-space and obtain a 'half step' resolution by switch from one PWM to the next each alternative cycle (so, for example, switching from PWM step 24 to step 25 (in a 50 step system) gives you '24.5' (== step 49 in a 100 step system)


(-) Pulse Width Modulation - (PWM)


Pulse Width Modulation (using a basic PIC)

The baseline PIC devices - such as the 10F2xx and 12F675, 12F519, 12F683, 16F628 and the ancient 16F5x (54,57,59) - are so cheap (I paid between 20p and 50p) that it's more 'cost effective' to implement even quite difficult functions (such as PWM) 'in software' rather than to use a 'dedicated' external PWM controller or a mid-range or more modern PIC with PWM built-in.

Indeeed, a software based PWM implementation allows all sorts of 'clever functions' - such as a non-linear PWM response, hi/lo drive limits, power-on 'over-rides' and 'defaults' and gradual or 'ramped' output changes. Using software even allows you to incorporate all sorts of clever 'feed-back' speed monitoring.

Of course, many of the mid-range PIC chips (such as the 16F628a) have a built-in 'CCP' module that can be set to 'PWM mode' and runs at OSC frequencies (so a 20MHz OSC means you get 10bit resolution with a 19.5kHz PWM duty cycle). A software based PWM only runs at CPU CLK frequencies - so (at best) a 20MHz OSC (= 5MHz Clk, 5 MIPS CPU) means you can only achieve 8 bit resolution (at 19.5kHz PWM duty cycle)

The PIC16F54 has 12 i/o pins, so if the 8 bits of PORTB are used to input a 'PWM demand' value, then the whole of PORTA (4 bits) could be dedicated to PWM 'drive'. That means (in theory) we have 100mA of PWM motor drive current available = enough to drive a small motor (although the danger of overheating or blowing the PIC pin i/o 'driver' circuit with a short etc. suggests you should always use an 'isolation' transistor)

Some of the 8 pin PIC only have 4 i/o pins, so controlling the PWM ratio may only be possible by using serial command input.

However the PWM is controlled, it's a 'good idea' to use an i/o pin as a 'feedback' input to monitor actual the motor speed (or some geared down ratio of the motor speed) and the PIC programmed to vary the PWM mark-space to achieve the required speed.

Traditional feedback systems often 'count' grating pulses, looking for 'N counts per rev per second'. With a 1-5 MIPS PIC, the actual time of each grating pulse can be measured to high accuracy and feedback applied much faster

How it's done

Obtaining both a reasonable PWM duty cycle as well as reasonable resolution is not easy, especially as the 'obvious' approach (using the Timer/Counter with some sort of 'interrupt' or 'wake up') won't work with the low end baseline PIC's (such as the 16F54) as they have no 'interrupt' circuits.

The advantage of using the TMR to count CLK cycles is that the PIC can be programmed to 'go off' and perform other functions of 'variable length' CPU cycles - eg. CALLs to one or more subroutines to calculate the 'next' set of PWM parameters - so long as the TMR count is checked at frequent intervals (and control returned to the PWM code before the TMR expires). So finding some way to do this would be quire an advantage.

All low-end PIC's (such as the 16F54) have a Timer/Counter (TMR) but no interrupt. However, there is also a 'Watch Dog Timer' WDT, which generates a 'reset' - so is there some way to use this during 'normal' operation ?

The TMR (or WDT) can be sued with a 'pre-scaler', which divides the count clock by one of 8 binary steps (/2 to /256). With no Pre-scale set - so running at max. speed - the WDT will 'time out' in about 18mS (about 55Hz), irrespective of the PIC OSC frequency, which is way too slow for a PWM motor drive (to avoid audible 'whine', most PWM systems use a duty cycle of 20kHz or better).

The 8bit 'counter-timer' (TMR) can be driven (optionally via the Pre-scaler) by the CPU CLK or from an external input. Unfortunately, the absolute maximum external clock rate is the same as the CPU CLK rate (the external clock input is 'synchronised' to the CPU CLK which means it cannot increment the TMR faster than once per CPU CLK). So using an external Clk is no faster than using the internal CPU CLK.

The problem is that when the TMR is run at CPU CLK rate, detecting a specific count (such as 0) is extremely difficult (the CTR is 'free running' i.e. will just clock on from 0xFF to 0x00 without stopping).

It's easy enough to code up a 'test' for a specific count, however whilst the CPU is 'jumping back to test again' the TMR could be stepping past the count !

The minimium 'Test and wait' loop is 3 CLK's ("Test Timer bit 7 skip if zero (1 CLK), jump $-1 (2 CLK)"), which can be used to detect TMR 'overflow' from 128+ to 0??.

Waiting for 'count = Zero' takes longer (TEST TMR, Skip if Z, JMP $-2 = 4 CLK cycles) and there is a 3 in 4 chance that 'count 0' will be 'missed' whilst skipping or jumping.

So any TMR 'test' thus has to be synchronised with the count, such that the test is made on the CLK cycle directly after that count is reached (see PIC data sheet DS41213D page 33). In the case of the 'TEST for Zero', the count is tested once per 4 CLK's, so a 'start delay' of 1,2 or 3 CLK's have to be added to 'align' the TEST loop.

Whilst it's easy enough to work out the start delay (by reading the current TMR count) thi si just one omore thing that can 'go wrong'.



Note that 'test for a bit (state)' is 'safer' than 'TEST for count N' (if you 'slip up' on testing for a bit change, the 'wait' still terminates, even if the bit is seen one or more CLK's 'late' - however if you slip up on a 'TEST for Zero' the loop will run for ever :-0 )


PIC OSC v's PWM duty cycle

At the max. rated OSC of 20MHz (Xtal, or ExtClk), the CPU CLK (OSC/4) will be 5MHz i.e. 5 MIPS, whilst the best R-C frequency is 4MHz, giving a CPU clk of 1MHz.

At 5 MIPS, with 8 bit resolution (256 CPU CLK's) we can achieve a PWM duty cycle of 19.5 kHz (which should be fine), however if a 30kHz cycle is required, a 5MHz CPU will have time for only 166 instructions (which means a PWM resolution of about 7.3 bits (157 states)).

Of course Xtals cost money, so, unless a 20MHz CLK is available from some other part of the design, you will likely want to stick to R-C operation to keep costs down. This delivers a 4MHz OSC = 1MHz CPU CLK and for 8bit resolution the PWM duty cycle would be a bit less than 4kHz !

Whilst the 'target' PWM frequency is 'above audible', in 'real life' few, if any, 'low end' motors are able to respond to a 4kHz drive. So, depending on the motor, we might 'get away with' using the cheap R-C OSC configuration, especially if 7bit accuracy is acceptable (in which case the PWM frequency is doubled to almost 8kHz)

Some of the low-end 10F IC's can be run from an internal 8MHz OSC allowing 8 bit PWM accuracy at 8kHz (or 7 bits at 16kHz)

Feedback counts

An 8 pin PIC can be used to 'match' it's PWM motor drive to some pre-set 'feedback count' - and the 'obvious' way to 'count' the feedback is to use the Timer/Counter !

Ideally the 'target' feedback pulse rate would reach 'count 127' (on the 8 bit CTR) by the time a PWM cycle completes. This whould allow a decision to be made for the mark-space ratio on a per cycle basis = however it's rather more likley that 'count 127' will only be reached after some multiple of the PWM cycle count.

The required motor speed will depend on the application, however in almost all cases a servo motor (whihc typically runs at 10 - 15,000 rpm) will be drastically geared down - and the feedback signel taken after the gearing (so that the motor speed can be adjusted to compensate for gearing errors).

One example might be the 'EQ drive' for a telescope mount. Here the post-gearing feedback pulse rate would be 'N per second' at best. Depending on the grating, 'N' would typically be in the 90-144 pps range, so the time between each feedback 'pulse' is quite long. For example, with N=100 (1/100th second beyween each 'pulse') and a PWM frequency of 20kHz, there would be 20k/100 = 200 PWM cycles between each feedback pulse

In a typical application (such as SQ drive), the PIC would thus measure the time of each feedback pulse and only recalcuate the PWM mark-space ratio once every 200 cycles.


Generating the PWM frequency

A software 'delay loop' can be coded to an exact number of CPU CLKs, and this is actually simpler than using the timer-counter.

If we want to support an 8bit 'demand' count, and assuming we can achieve a minimum 'mark' or 'space' of 1 CLK, then the PWM instruction 'loop' cycle must be 256 CPU clocks. This corresponds to a PWM cycle frequency of 5MHz/256 = 19.5kHz (for a 20MHz OSC = 5MIPS CPU) which is perfectly acceptable

20kHz is right at the top of the 'audio' range and way beyond any likely motor response. If this does cause your motor/drive to 'squeal', then you will have to 'up' the PWM cycle to 40kHz (which means a 127 CPU clk PWM cycle and thus either 7 bits accuracy or the addition of a 'half step' circuit = see later)

Generating the mark-space ratio

The main thing to note is the need to avoid 'glitches' in the output 'drive' - i.e. when the PWM is 00, there should be no spurious 'Hi' pulses, and when 0xFF no spurious 'Lo' pulses.

Further, when the PWM 'demand' count changes, the output should change 'quickly' BUT the 'transition' from one mark-space to the next should be 'clean' i.e. not 'glitch' to a lower (or higher) mark-space than that demanded (this means, in effect, that each PWM mark-space cycle should be 'completed' before 'switching' to the next)

Depending on the application, in some cases the mark-space ratio would be 'stepped' towards the 'target' rate over a number of cycles.

For example, in the 'EQ drive' considered above, the mark-space might only be changed by 1 step every 200 cycles. This means that, to go from 'stopped' (0/100) to 'half speed' (50/50 = count 127), would take 200x127 = 25,400 cycles. Whilst this sounds a long time, at a 20kHz PWM cycle rate this is only just over 1 second !


Basic approach (for 8bit demand Count, 256 PWM states)

The basic approach is a 'delay loop' that 'counts down' to each transition, except (of course) for the '0' state ('off') and 255 state (= full 'on') when there will be no transition.

The problem is that near the PWM limits (1,2,3 .. and .. 252,253,254) the number of CPU clk cycles before the next transition is just too few to allow any actual 'counting' ..

For example, at a mark/space of 1/245, the i/o pin has to remain 'on' for only one CPU clk, whilst at 254/1 it has to remain 'off' for only one clk only

Of course, when the 'on' time is only a few clocks, there are plenty of CPU clks to count the 'off' time (and vise versa) - so the 'trick' is to handle the 'short' side (fast transitions) as 'special cases'.

To avoid having to code twice (once for 'short on' then again for 'short off') we 'INVert' the PORT bits (using the 'COMF' instruction) rather than using Bit set ('Bset') or Bit clear ('Bclr').

Note we can't use COMF if the TRIS (Tri-state) control is used to turn the i/o pin on and off = instead the new TRIS 'state' has to be pre-loaded into the Accumulator 

A 'GOTO $+1' instruction 'costs' 2 clks and can be used instead of a pair of NOP's to minimise the instruction 'count' (low-end (= cheap) PIC chips have very little program soace - for example the 16F54 only has 512 instructions !).

The short 'pulses' can thus be generated by :-
0    n/a (no change)
1	INV, INV
2	INV, NOP, INV
3	INV, GOTO $+1, INV
4	INV, GOTO $+1, NOP, INV

The use of 'INVert' means the 'short pulse' code applies to both short Hi and short Lo cases. It should thus be possible to code the entire PWM cycle 'once' (i.e. start with a 'bit set' or 'bit clear' and then 'run' with INVert).

It will be noted that the 'short pulse' always starts with an INV and always ends with an INV

Actual code

The 'short' delay must be 'called' from the 'long' part of the PWM cycle (there's no time to do it the other way around :-) ). This means :-

The 'long count' waits for most of it's count, and then CALL's the short cycle code
Some fixed length code (part of the long count) works out which short cycle is needed (might be 0), then Jumps to that one
The short cycle starts with an INV, then, after the short count, executes the end INV (for '1 CLK', that's back-to-back INV, INV) before RETurning to the long count = so the long count never has to INV anything (instead, the long count has to decide what the next mark-space ratio will be)

As with the long count, the short delay loop is 4 CLK's long, so 0,1,2 or 3 'adjust' CLK's are performed before the x4 loop is entered. This allows the 'ultra-short' counts (0,1,2,3) to be 'spotted' at the 'same time'. 

DoShort:			;long count CALL's here
; check the short count bits 0 and 1
CLR Cy				;make sure Cy is clear (don't want it setting b7 of the count :-) )
ROTR ShortReg
Skip NCy
JMP adjust1or3		;Cy set, so adjust is 1 or 3 (or count is 1 or 3)
;
ROTR ShortReg		;Cy was not set, safe to rotate again
Skip NCy
JMP adjust2			;Cy set, adjust be 2 (or count is 2)
; adjust is 0, or count is 0 .. need to check now
TEST ShortReg		;(need to Test, as ROTR didn't set Z)
Skip NZ
JMP	count0
; here count is at least 4 and no adjust needed, so we can go for it
INV					;0 adjust, start the short pulse
; 					; if ShortReg=1, then 4 CLK's to second INV
NOP						; 4x loops back here
Do4xLoop:				;enter here with 4x count in ShortReg
DEC ShortReg,skip Z		;dec the count, drop through in nonZ (1), else skip [2]
JMP $-2					;keep looping
INV					;counts as 1
;
RETURN					;exit back to long count
;
;
adjust1or3:
; Cy set on first rotate, so adjust is 1 or 3 (or count is 1 or 3)
CLR Cy				;clear Cy (don't want it setting b7 of the count :-) )
ROTR ShortReg
Skip NCy
JMP adjust3			;Cy set, so adjust is 3 (or count is 3)
; here adjust 1 or count 1
TEST ShortReg		;(need to Test, as ROTR didn't set Z)
Skip NZ
JMP	count1
; here count is at least 4 and 1 CLK adjust needed ...
INV
JMP Do4xLoop:		;JMP takes 2, + (DEC the count for 2 then INv for 1, total 5) 
; above adds 1 CLK (if count is 1 x4, then JMP takes 2, DEC the count & skip on Z for +2, then INV for +1 = total 5) 
;
adjust3:
; here adjust 3 or count 3
TEST ShortReg		;(need to Test, as ROTR didn't set Z)
Skip NZ
JMP	count3
; here count is at least 4 and 3 CLK adjust needed ...
INV
JMP $+2				;2 delay
JMP Do4xLoop:		;plus 1 CLK (if count is 1 x4, then 2 delay, +2 JMP, DEC the count & skip on Z for +2, then INV for +1 = total 7) 
;
;
;All adjusts down, now just have to do the 'specials'
count0:				;no short INV, equalise the CLK's then Return
..
RETurn
;
count1:				;1CLK, equalise the CLK's, do the INV's, then Return
..
INV
INV
RETurn
;
count2:				;2CLK, equalise the CLK's, do the INV's, then Return
..
INV
NOP
INV
RETurn
;
count3:				;3CLK, equalise the CLK's, do the INV's, then Return
..
INV
JMP $+1
INV
RETurn
;



Next lets consider the 'long half' cycle. This consists of a delay of (almost) half the count (almost because 'worst case' means switching immediately after the 'half point' i.e. a mark:space of 50:50 (PWM 128+128)

So we build a 'delay subroutine' that can be 'called' with a value in 'delayReg' to generate a specific CLK count delay ..

When the main-line code executs a 'CALL delay' instruction, this will generate an exact 'delayReg' CLK delay i.e the Call (2 CLK) and the Return (2 CLK) is included in the count.

DoDelay: ; arrive here on a CALL for (2) CLK's ; adjust the count for the 'cost' of this code, 19 CLK's PLUS 8 CLK's for the calling code LOAD Acc,27 ; (3) SUB Acc,delayReg ; (4) ; CLR CY ; (5) ROTR delayReg ; div by 2, b0 to CY (6) ; if CY is set, need to add 1 CLK Skip NCY ; [ +1 if CY, +2 if nCY ] JMP $+1 ; [ +2 if CY, +0 if nCy ] ; b0 done for (8), count is now a multiple of 2, we need multiple of 4 ; handle b1 (new b0) next CLR CY ; (9) ROTR delayReg ; div by 2, b1 to CY (10) ; if Cy is set, need to add 2 CLK's overall Skip NCY ; [ +1 if CY, +2 if nCY ] JMP $+1 ; [ +2 if CY, +0 if nCy ] Skip NCY ; [ +1 if CY, +2 if nCY ] JMP $+1 ; [ +2 if CY, +0 if nCy ] ; arrive here after ... +6 if Cy, +4 if nCy ; Overall count (after b0 and b1 done) is now (14), count is multiple of 4 ; ; this is the main 4x CLK delay loop ; if we 'fall into' the loop with count =1, we exit after +5 i.e. total subroutine 'cost' is (19) ; (loops back here) NOP ; +1 DEC delayReg,skipNZ ; +1 JMP $-2 ; +2 if NZ (+1 if Z) RETURN ; exit, +2
NOTE minimium count AFTER subtracting 19+8 is 1 loop of 4, so minimuium delayReg is 23 !!


Defining the cycle

Now we have the 'short' cases and main 'delay' routines, the complete PWN cycle 'main line' code can be written. This is where the 'limit' cases (0x00 'all space' and 0xFF 'all mark') are handled.

Input PORTB 8bit demand count, 0 = 0%'on' : 100%'off'. PWM 127 = 50/50, PWM 255 = 100%'on' : 0%'off'.
Output PORTA, pin Hi (1) = on, Lo (0) = off (note whilst INV PORTA (CONF PORTA) flips the state of all 4 PORTA bits, 'XOR Acc,PORTA' can be used to flup specific bits (eg if Acc = 0x01, this flips bit 0 only) ).

Since the 'demand count' at (PORT B) is just as fast to 'read' as any other Register, it can be accessed 'in the loop' for 'instant' response to a change in demand.

If we take PORTB as a starting count, the 'on' time = count down to zero (off time = count up to FF). The 'trick' is to 'spot' a short 'on' (or short 'off') - i.e. when we are within 16 steps of 'overflow' or 'underflow' - and 'special case' it.

Detect can be done using the AND instruction (which only sets the Z flag) or ADD/SUB (which sets all flags) but is harder to interpret.

Note 1, using INV means there is (some) danger of the output mark:space getting 'inverted' .. to avoid this we CLR PORTA when 'starting' a cycle with PWM b7=Lo, and set it when starting with b7=Hi (in each case that means 'for the long half cycle')

Note 2, the 'cost' of every 'decision' instruction (1 or 2 clks), has to be 'counted' to maintain the over-all mark-space cycle frequency time (exactly 256 clk's), so all paths through a 'conditional jump tree' have to be (very carefully) balanced.

We starts (of course) with power-on ...

Power-on

The PIC starts generating PWM output immediately from power-on (there is no point in waiting for anything, although defining one of the PORTA pins as an input for a 'PWM off' override may be useful)

Typically, we want to start (power-on) with motor = off. Assuming the PIC o/p pins are tri-state at that time, some sort of pull-up/pull-down resistors would be a 'good idea'

Power-on-reset: ; assume PORTs are all TRIS'd (1) as inputs ... CLR PORTA ; set off (don't assume PORT latch values) CLR Acc ; clr b0-3 TRIS PORTA ; set b0-3 as outputs ; jump to one or the other side of the PWM loop, depending on b7 of PORTB (the demand count)
The PWM cycle (main loop)

All the 'decisions', including which delay to Call and calling the 'special case' code, have to be made in the 'long half' fo the PWM cycle (i.e. when we have plenty of time). That includes deciding which will be the 'long half' of the cycle 'next time' (i.e. for the next 256 CLK's) and detecting a special case for next time. 

The overall cycle should always start with the Hi (mark) 'half', so the sequenc is always on/off ( and never 'off/on off/on ...', since this would lead to a 'double off' cycle sequence 'on/off off/on' (or double on 'off/on on/off') as the code 'switches' at the 50:50 demand value, which would generate half the required frequency).

Instead we always have to 'start' with PWM set and perfrorm a complete cycle of 256 CLK's. This means we need 2 code lopps - one where the decisions are made in the 'mark' (= long half) and the second where decisions are made in the 'space' (= long half).

NOTE that when mark is the long half, it can 'switch' to space as the next long half, but not to a 'special case' mark (and same for space long half). This means there will be a 1 cycle delay when reponding to a chnage in demand in excess of 128-(special case max) i.e. about 100 steps.

HiLongHalf: ;Bset long half cycle (before arriving here, demand count has been copied to HiReg) Bset PORTA,pwm ;Force PWM = set (1 CLK) COPY HiReg,Acc ;get the Hi demand count (1) COPY Acc,delayReg ; save to the delay counter (1) ; Now count down most of the 'mark' time, leaving just enough CLK's to detect a special case Lo CALL DoDelay ;delay INCLUDES an extra 8 CLK's (used 3 above already) ; ; now need to detect the 'special case' Lo COPY HiReg,Acc ;get the Hi demand count (1) COPY Acc,delayReg ; save to the delay counter (1) INV Acc ;Lo count = inverse of Hi (1) COPY Acc,LoReg ; keep the Lo count (1) AND Acc,0xE0 ;less than 32 ?(1) Skip Z ; (2), if Z JMP NoLoSpecialCase ; ; work out where to go next .. using a fixed CLK count :-) ; NoLoSpecialCase: ; No Lo special case, we can just use simple delays (min delay is 31 CLK) ;  total so far 8 CLK .. CALL DoDelay ;delay INCLUDES the 8 CLK's above ; Hi done, switch to Lo INV PORTA ; switch is at end of CPU CLK, so this counts as 1 (24) ; ; we are only here for non-special case Lo, so we can just do the Lo count as a delay ;  HOWEVER we need to detect if the next Hi is a special case ! COPY LoReg,Acc ; (1) COPY Acc,delayReg ; save to the delay counter (1) CALL DoDelay ;delay INCLUDES an extra 8 CLK's ; now have 6 CLK's before end of cycle to detect (and act on) a special case Hi LOAD Acc,0xFE ; 1 AND Acc,PORTB ; 1 Skip NZ ; 2 if not special, 1 if is JMP HiSpecialCase ; 0 not special, 2 if is ; 2 more b4 end of Lo, COPY PORTB,Acc ;get the (new) demand count (1) INV PORTA ; .... correct Lo, but cycle time is out .... ; COPY Acc,HiReg ;save (1) SkipZ HiReg,7 ;skip if b7 is Lo (1 if Hi) JMP HiLongHalf ; 2 for jump, but PWM set hi takes one more JMP LoLongHalf
;
BTST, Skip On Clear, PORTB,b7	; check PWM demand count, if b7 =0, count is < 127 = mainly space
JMP Mark:						; b7 was hi, exit to mainly mark
Space:
; OK, here mainly 'space' .. we are going to count 
CLR PORTA		; ensure space
; detect special case (mark < 16, PWM count < 0F)
LOAD Acc,0xF0			;
AND PORTA,Acc,PORTA		; PORTA = input, just set Z
SKIP nZ					; skip if nZ
JMP shortMark:			;
; here not short mark .. can count up with space then down for mark

COPY PORTB,Acc	; Copy reg to Acc will set Z bit



Pseudo Code

Start:
1) IF PORTB = 00, CLR PORTA, jump to start:
2) IF PORTB = FF, Set PORTA=xF, jump to start:
3) Test PORTB bit 7
b7 clear, PWM count is 0x7x- (0x00-7F), mainly 'space', mark is special case
b7 set, PWM count is 0x8x+ (0x80-FE), mainly 'mark', space is special case


offWait
Wait for most of the off time
If it's a short (? 2,1,0) on :-
	Do the short-on/off, loop back to offWait
Set on
onWait
Wait for most of the on time
If it's a short (? 2,1,0) off :-
	Do the short off/on, loop back to onWait
Set off
Jump to offWait

We can 'count down' the 'raw' demand Count as 'on' cycles. The 'off' count = INV(demandCount).



offTop: BClr PORTA,PWMbit ;make sure it's Lo JMP $+1 ; 2 CLK delay offWait: ; arrive here 3CLK's 'off' INV PORTB,Acc ;off count = INV demandCount COPY Acc,countReg LOAD Acc, ;adjustment value (loop+3) SUB Acc,countReg ; reduce count SKIP nBy ; skip if no borrow, i.e. off time > X JMP onTop ;count underflow, must be mostly on Bclr Cy ; make sure no Cy ROTR countReg ; divide count by 4 ROTR countReg :mOffwait (4 clk loop) DEC countReg ; (note = cy not effected) 1 clk SKIP Z ; skip Z, 1 clk if no skip (2 tot) JMP mOffwait ; back to loop, 2 clk (4 tot) ; COPY PORTB,Acc ; get the 'on' time SKIP nZ JMP offTop ;'on' time is zero, back to 'off' AND 0xF0 ; check if 'on' count > 15 (mask bottom bits) SKIP z ;if Z, count is < 16 JMP onTop ; 'on' is >15, go do it ; short (<16) on time INC PORTB,Acc ; get inverse AND 0x0F ; remove top bits ; short 'on' time stack (jump to time) ADD Acc,$ ;add Acc to PCL, 0x0F =15 = On count 1, 0x00 =0 = On count 15 Bset PORTA,PWMbit ;jump here if Acc 0x00 = On count 15 ... Bset PORTA,PWMbit ;jump here if Acc 0x0F = On count 1 Bclr PORTA,PWMbit ;set PWM back to 'off' JUMP offWait ; loop for off count ; ; onTop; Bset PORTA,PWMBbit ; JMP $+1 ; 2 CLK delay onWait; COPY PORTB,Acc ;on count COPY Acc,countReg ;save LOAD Acc, SUB Acc,countReg ; reduce count SKIP nBy ; skip if no borrow, i.e. on time > X JMP offTop ;count underflow, must be mostly off :mOnwait DEC countReg ; (note = cy not effected) SKIP Z ; skip Z JMP mOnwait ;wait for most of the on time INV PORTB,Acc ; get the 'off' time SKIP nZ JMP onTop ;'off' time is zero, back to 'on' AND 0xF0 ; check if 'off' count > 15 (mask bottom bits) SKIP z ;if Z, count is < 16 JMP onTop ; 'on' is >15, go do it ; short (<16) 'off' time COPY PORTB,Acc ; get on time = inverse of 'off' time AND 0x0F ; remove top bits ; short 'off' time stack (jump to time) ADD Acc,$ ;add Acc to PCL, 0x0F =15 = On count 1, 0x00 =0 = On count 15 Bclr PORTA,PWMbit ;jump here if Acc 0x00 = On count 15 ... Bclr PORTA,PWMbit ;jump here if Acc 0x0F = On count 1 Bset PORTA,PWMbit ;set PWM back to 'on' JUMP onWait ; loop for on count
Half-step circuit

On a 20MHz driven part (5 MIPS), a 19.5kHz duty cycle means 256 CPU CLK's and thus 8 bit resolution.

However the R-C OSC tops out at 4MHz (1 MIPS) = the same as many PIC's with an internal OSC = so a 19.5kHz duty cycle would only allow 64 CPU CLK's and thus only 6 bit resolution. Reducing the duty cycle to 9.75kHz means we get 7 bits, however to achieve 8 bits we really have to add a 'half CPU CLK delay' circuit.

This means adding a 7474 D flip-flop and that means we can't drive the motor 'direct' from the PIC PORT (even if we wanted to). So a transistor driver (+ diode and resistor to ensure TTL 'Lo' = transistor 'off') has to be added as well. All these extra components will likely costs more than a 20MHz Xtal, so what follows is really just an 'exercise' 

Photo: ../PIC_projects/photos/3000_LSB-half-CLK-delay.gifHalf CLK delay circuit

When the RC OSC option is used, the CPU CLK is available on 'OSC2'.

To delay the output, a 7474 D flip-flop is used and the PIC outputs on one of two pins, /PWM 'direct' or on 'PWM-.5' for the 'half CLK delay'. /PWM drives the /Pre pin (whilst PWM-.5 is held Lo). To use the delay, /PWM is held Hi whilst PWM-.5 drives both the /Rst and D input (the i/o port pin changes state on the Lo-going edge of the CPU CLK2, so the next high going edge of OSC2 is 'half a CPU CLK' later which is what 'moves' the Hi at D by 1/2 CPU CLK)

Note this circuit uses the fact that when both /Preset and /Reset are Lo at the same time, the 7474 Q (and /Q) output will be forced Hi.

The PWM software code above only needs a few slight changes to 'switch' between two output pins (/PWM and PWM-.6) and ensure the 'right' bit is INVerted (i.e. set up the Acc for 'XOR Acc,PORTA' so bit to be INVerted = 1, other = 0).

With some carefull coding, the PWM will operate correctly (but only at 7 bit resolution) without the prescence of the 7474 'half CLK delay' circuit.


Half-step in software

Depending on the appllication, to avoid the need for additional nardware, we can program in a 'half step' by using software to alternate between two 'full step' states every PWM cycle.

Thus, for example, if the 'full step' PWM resolution is 7 bits (so 0/127 to 127/0 with 'half speed' at 64/63) to generate an 8 bit PWN 'half speed' (127/128), each 7 bit PWM cycle we switch from 63/64 to 64/63 and then back again.


1/N step in software

The 'half-step in software' can be expanded to '1/N step in software' by varying the ammount of time spent in each 'major step'.

If we use a 1 MIPS PIC (eg. using the R-C OSC at 4MHz or one of the 8pin 10F / 12F PIC's with internal 4MHz OSC) to get 19.2kHz PWM cycle that means 64 CPU CLK's per PWM cycle and only 6 bit resolution.

However 2 extra bits of resolution can be obtained by switching the '64 step' major state in sets of 4.

So (for example) to get a 1/254 mark/space from 64 steps, we run a set of 4 PWM cycles as '0/63 0/63 0/63 1/62'.






This note last modified: 13th Aug 2017 05:32.

[top]

Next page :- PIC low voltage ac motor control

[top]