Maker Pro
Maker Pro

Benchtop PWM Generator

Fish4Fun

So long, and Thanks for all the Fish!
Aug 27, 2013
481
Joined
Aug 27, 2013
Messages
481
I have bins full of projects in various states of completion because some number of hours/days/weeks/months into the project I "discovered" there was a better solution ready-made for some relatively trivial price or some Open Source project easily modified to suit my needs..... Yesterday I wanted to test a bread-boarded output stage and simply needed a PWM pulse train ~50kHz with fairly flexible duty cycles.....Not a mind boggling task on an Arduino, but it is a recurring obstacle that consumes several hours each time I encounter it; so, because the project on my desk is NOT time sensitive I thought to myself: "I am going to write some code and remove this obstacle once and for all" and down the rabbit hole I went.....

Of course removing the a fore mentioned obstacle on a permanent basis requires a priori knowledge about future requirements and some type of user interface.....Rutabagas! The more you chew them, the bigger they get, and pretty soon the project was spiraling out of control. I started by working with the built-in PWM features in the AVR ATMega2560, but the flexibility I want eluded me.... I can Brute-Force a 50kHz Pulse Train with ~300 duty cycle increments, but that doesn't leave much processor time for anything else, and certainly doesn't require an ATMEGA2560.....So then I thought about porting the code to ATTINY2313A's, but this solution has its own problems ... Next I considered a Spartan-6 FPGA prototype board collecting dust on my shelf (collecting dust because I haven't dedicated time to lean an HDL.....while the project on my desk isn't time sensitive, learning an HDL in an effort to build a general solution to flexible Pulse trains for testing a prototype of one segment of an output stage is pretty far off the beaten path).....But I turned right at the Y between a firmware solution and a hardware solution, tripped over a root and tumbled further down the rabbit-hole....

What is my question? Is there a readily available addressable/OR/programmable IC designed to output a wide range of PWM signals? Ideally, I would like to control Base Frequency, On-Time and Off-Time in ~100nS intervals with 8-bit accuracy in "On Time" / "Off Time" and a 4-bit selection in Time-Base (Ultimately Frequency) . For example, if the IC had an input clock of 10Mhz the interval selection would be: 100nS, 200nS, 500nS, 1uS, 2uS, 5uS, 10uS, 20uS, 50uS, 100uS, 500uS, 1mS 2mS, 5mS, 10mS and 50mS.....Again, Ideally, "On Time" would be 8-bits (0 to 255 intervals) and "Off Time" would be 8-Bits (0 to 255 intervals); however, 4-bit On/Off intervals would likely be sufficient for most applications, and a simple clock divider on the input side of the device could handle the interval period selection.....

Code:
Example:
Input Frequency = 10Mhz
PWM Target Frequency = 50kHz
Duty = 10% - 90%
Select Interval Period = 100nS (100nS * 200 = 20uS ---> 1/20uS = 50Khz)
Intervals Per Cycle = 200
10% Duty Cycle = 20 "On" Intervals & 180 "Off" Intervals
20% Duty Cycle = 40 "On" Intervals & 160 "Off" Intervals
30% Duty Cycle = 60 "On" Intervals & 140 "Off" Intervals
40% Duty Cycle = 80 "On" Intervals & 120 "Off" Intervals
50% Duty Cycle = 100 "On" Intervals & 100 "Off" Intervals
60% Duty Cycle = 120 "On" Intervals & 80 "Off" Intervals
.
.
.
90% Duty Cycle = 180 "On" Intervals & 20 "Off" Intervals

Building the described device from discreet logic ICs is certainly possible.....Programming a PAL/CPLD/FPGA is also certainly possible, (but for me would still require learning some type of HDL), A combination of Logic and a uC might be easier to design, but would also include some limitations imposed by the uC to Logic interface......so if there is already an IC designed for the task, and it's cost is reasonable (What is "Reasonable"? .... hard to say, building a "one-off" from discreet logic might take 20 to 200 hours by the time Design/Prototyping/Debugging/PCB Layout/Fabrication/Population is complete, writing the code for a PAL/CPLD/FPGA might take days/weeks/months of learning before a working version emerged, but would be cheap/easy to integrate into projects.....So "Reasonable" is a moving target; certainly anything < $100 would be a "deal" for a one-off.....but if such an IC exists I would hope it would be in the $1-10 price range so I could afford to keep a stock-pile on hand ;-) )

Thanks!

Fish
 

Alec_t

Jul 7, 2015
3,591
Joined
Jul 7, 2015
Messages
3,591
Lots of bells and whistles there. A simple analogue PWM generator, with frequency and duty cycle separately controllable with respective potentiometers, could be built cheaply using a TL494 ic.
 

Fish4Fun

So long, and Thanks for all the Fish!
Aug 27, 2013
481
Joined
Aug 27, 2013
Messages
481
@Alec_t --> Not exactly what I was thinking, BUT absolutely perfect! and @ $0.58/ea delivered, I went ahead and ordered 20 to play with :) I figured I couldn't be the first person to need a variable frequency/duty PWM IC. @$0.58ea they are certainly cheap enough to embed in any future project, and building a multiple output PWM Bench-Top tool with analog/digital control should be relatively easy.....

Since my OP, I did some searching/reading on modern CPLDs & HDLs.....It APPEARS the Altera family of CPLDs have a schematic capture IDE that (at first glance) seems easy enough to grasp.....( http://www.hackshed.co.uk/getting-started-with-cplds-index/ ).... Not quite as cheap as the TL494, and likely difficult to embed in a DIY PCB project, but with demo board + programmer ~$12 delivered, I went ahead and ordered a companion for my Sparta-6 FPGA board....(It was tired of collecting dust all by itself...)

Thanks!

Fish
 

Fish4Fun

So long, and Thanks for all the Fish!
Aug 27, 2013
481
Joined
Aug 27, 2013
Messages
481
@adam.... Thanks for the link! Interesting chip, I <think> that I would like to play with one....They are ~$10 from DigiKey (reasonable enough) .... ebay-China has the ICs listed for ~$4 each or several permutations of Demo Boards ranging from ~$6 to ~$40 ...any thoughts or suggestions?

The TL494s I ordered were delivered @ work Saturday....(I will pick them up Monday).... I also ordered one of the Altera EPM240 Dev Boards + Programmer (likely be here by the Ides of March...slow boat from China ;-) ), and I ordered a Cyclone IV Development Board with an EP4CE6E2217N FPGA on it ..... should arrive tomorrow (Monday) ... I am very excited about Altera's Quartus IDE which includes a "Schematic Capture" type interface that **theoretically** allows the HDL ignorant to "visually configure" CPLDs and FPGAs .... I have played with the Xilinx IDE (Vivado) ... the literature brags about "Schematic Capture" features, but it seems very convoluted to me .... best I can figure it requires the user to "create logic functions" (like AND NAND, OR, NOR etc, etc) using an HDL, then "Draw" the Schematic Symbol for it, and at the end of the goat roping (if it all compiles properly and you manage to wrap it all up properly) you can then place the Schematic Symbol in a special window and start writing HDL code to bind the new symbol to other Schematic Symbols you have created, ( or Inputs, Outputs etc.....) I have read a few tutorials on Quartus .... (plus watched a few you-tube tutorials), and it **seems** like the Schematic Capture portion of the IDE includes libraries of common logic functions and allows the user to proceed from start to finish w/o having to write any HDL code ....

We will see....LoL. I have downloaded and installed the Quartus IDE, but I am waiting until I have the demo board on my desk to start playing with it .... The tutorial I am starting with configures the Device at each step and verifies success .... Step one is powering an LED ... In theory it takes about 5 minutes LoL.

Fish
 

Arouse1973

Adam
Dec 18, 2013
5,178
Joined
Dec 18, 2013
Messages
5,178
Let us know how you get on with it. I would go for the DEV board that does what you want it to do, having an area to add other components is always handy if they have this.
Thanks
Adam
 

Fish4Fun

So long, and Thanks for all the Fish!
Aug 27, 2013
481
Joined
Aug 27, 2013
Messages
481
@adam.... Kinda what I was thinking ... Thanks!


@Anyone....

At the risk of Hi-Jacking my own thread.....(But in my defense: Same Project, Same Lead-In .....)

Is there an up/down Gray-Code Counter IC? Or an IC that translates a two-channel encoder input into "pulses" with a "Direction Bit".....

(Again, obviously not difficult to do on-board with a uController, or from discrete logic) but with high-pulse-count encoders I am concerned about ISR timing and uController resources..... A 2000ppr encoder @ a modest 100 rpm only leaves 80 clocks between ISRs in an Arduino Mega2560 running @ 16Mhz (and so far I haven't found a way to shrink the ISRs below ~35 clocks ... not leaving a lot of clocks for other tasks...). LoL! Perhaps: Back to the CPLD // FPGA!


FWIW:

The "Larger" project involves converting an existing medium-format CNC router from stepper drive to servo drive.... Building CNC machines (like electronics) is a Hobby, and I have been anxious to give Servos a try, but servos in general aren't really hobby-budget friendly and making the transition from "open-loop-control" to "closed-loop-control" is proving to be a challenge.... I picked up some surplus servos on ebay a while back for cheap-cheap, (well, cheap-cheap compared to new servos, roughly the same price as good-quality steppers), but so far all I have done is make them spin and watch the encoder outputs on a scope .... making them "useful" will involve servo drivers, and I haven't located any of those with-in a reasonable hobby budget, LoL .... I have 10 of the servos, so I am going to dedicate a fair amount of time/effort to designing//building suitable drivers.... (unless, of course, I happen to run across some surplus drivers ;-) ).

Thanks!

Fish
 

Fish4Fun

So long, and Thanks for all the Fish!
Aug 27, 2013
481
Joined
Aug 27, 2013
Messages
481
Progress Report......

I have drawn up a schematic to test the TL494's .... but I haven't had a chance to cut a PCB yet .... Maybe next week.

I did "play" a bit with a Gray Code translator..... Looked at various solutions posted online ... considered a couple, but @ the end of the day I was going to need to order one logic IC or another to test it....meanwhile I have a couple hundred ATTINY2313A's collecting dust, so I figured "What the heck an ATTINY2313A is just about as cheap as any given logic IC .. and cheaper than a handful of logic ICs .... Why Not?"

Did a quick Google search and I was off to the races .....after a bit of frustration I realized I had put the cart ahead of the mule .... I scrolled down a bit and noticed all the comments were pointing out the code didn't work as advertised, LoL! Rather than attempting to make said code work, I did what I should have done to start with, I started from scratch...... A couple of hours later I had a working solution .... The code is in AVR Assembler, and absolutely NOT optimized to do anything except translate Gray Code to Pulse and direction .... (4 pulses per 2-bit cycle). I have tested it using a 2000 ppr encoder (using the 2313's internal clock source @ 8Mhz, and my fingers to turn the encoder shaft .. ) Everything looks as it should on my DSO, EXCEPT the Dir pin seems to always end on Low ..... Stays High as long as the shaft is rotating "up", but for some reason when I stop turning the shaft it always seems to end up Low. Might just be my fingers .... a 2000ppr encoder is VERY sensitive.... Next step is to do some testing with a motor and an ATMEGA2560 to "count" the pulses .... if there is some error somewhere it will certainly show up then....

Here is the Code as I am currently using it:

Code:
;
; ATTiny2313A_GrayCode.asm
;
; Created: 3/18/2017 9:59:35 PM
; Author : Fish
;


;Genral Purpose Registers
.def    Temp      = r16
.def    Temp1     = r17
.def    Temp2     = r18
.def    Cntr      = r20

;Constants
.equ    InptA       = 3         ;Set InputA = PORTD.3
.equ    InptB       = 4         ;Set InputB = PORTD.4
.equ    Pulse       = 0         ;Set PulsePin to PORTB.0
.equ    Dir         = 1         ;Set DirPin to PORTB.1
.equ    Pulse_Time  = 8         ;Initially Set to 16 --> Assuming AVR Clock = 8Mhz, This Will Kill ~ xxx Clocks
                                ;Lowered to 8


;Unused Interrupt Vectors
rjmp   Reset
rjmp   INT0_V
rjmp   Int1_V
rjmp   TC1CAP_V
rjmp   TC1MA_V      ;
rjmp   TC1OVF_V
rjmp   TC0OVF_V
rjmp   RXC_V
rjmp   DRE_V
rjmp   TXC_V
rjmp   A_Comp_V
rjmp   PCINT0_V
rjmp   TC1MB_V      ;
rjmp   TC0MA_V
rjmp   TC0MB_V
rjmp   USI_Start_V
rjmp   USI_Ovf_V
rjmp   EERdy_V
rjmp   WDTovf_V
rjmp   PCINT1_V
rjmp   PCINT2_V


;********************************************************************************************
;********************************************************************************************
;
;  Following Interrupt Vectors Go To ReSet
;
;********************************************************************************************
;********************************************************************************************
    INT0_V:
    Int1_V:
    TC1CAP_V:
    TC1OVF_V:
    TC0OVF_V:
    DRE_V:
    TXC_V:
    A_Comp_V:
    PCINT0_V:
    USI_Start_V:
    USI_Ovf_V:
    EERdy_V:   
    WDTovf_V:
    PCINT1_V:
    PCINT2_V:
    TC0MA_V:                           
    TC0MB_V:                           
    RXC_V:
    TC1MB_V:
    TC1MA_V:
               rjmp    ReSet           ; All Unhandled Interrupts Should Vector to ReSet
                nop                     ; Just Cause
ReSet:
            cli                         ; Turn Global IRQ OFF
            ldi     Temp, RAMEND        ;
            out     SPL, Temp           ; Set Stack Pointer
            ;Set Up Input
            cbi     DDRD, InptA         ; Set PORTD.InptA = Input
            cbi     DDRD, InptB         ; Set PORTD.InptB = Input
            ;Set Up Outputs
            sbi     DDRB, Pulse         ; Set PORTB.Pulse to Output
            cbi     PORTB, Pulse        ; Make Sure PORTB.Pluse = Low
            sbi     DDRB, Dir           ; Set PORTB.Pluse to Output
                                        ;
                                                             ;
; Truth Table for Gray Code:
;
;     -----------------------
;     |        |  New State |
;     | Initial|    Dir     |
;     |  State | Up  | Down |
;     |   AB   | AB     AB  |
;     |---------------------|  
;     |   00   | 10  |  01  |
;     |   10   | 11  |  00  |
;     |   11   | 01  |  10  |
;     |   01   | 00  |  11  |
;     |---------------------|
;     
GetState_AB:
            sbic    PIND, InptA        ; 1 or 2 If InputA is Clear Then Skip Jump
            rjmp    Inpt_1x             ; InputA = High, Jump to Check InputB
            sbic    PIND, InptB        ; If InputB is Clear then Skip Jump
            rjmp    Inpt_01             ;
                                        ; AB = 00 (Initial State)

    Inpt_00:    ; A=0 & B=0              Two Possible Outcomes: 10 --> Dir = Up
                ;                                               01 --> Dir = Down
            sbic    PIND, InptA         ; Skip Jump if InputA = 0
            rjmp    Dir_Up_10           ; If InputA = 1, Then Dir = 1 & Send Pulse
            sbic    PIND, InptB         ; Skip Jump if InputB = 0
            rjmp    Dir_Down_01         ; If B = 1 Then State has Changed --> Dir = Down & Send Pulse to Host
            rjmp    Inpt_00             ; Both Jumps Skipped ---> Nothing Changed, Check Again
    Inpt_1x:
            sbic    PIND, InptB       ; If InputB is Clear then Skip Jump to Inpt_11
            rjmp    Inpt_11             ; If InputB is Set Then Jump to Inpt_11
                                        ; Else Inpt_10  
    Inpt_01:    ; A=0 & B=1              Two Possible Outcomes: 00 --> Dir = Up
                ;                                               11 --> Dir = Down
            sbic    PIND, InptA        ; Skip Jump if InputA = 0 --> **No Change**
            rjmp    Dir_Down_11           ; If InputA = 1, Then State Has Changed --> Dir = Up & Send Pulse to Host
            sbis    PIND, InptB        ; Skip Jump if InputB = 1
            rjmp    Dir_Up_00         ; AB = 00 --> Dir = Down
            rjmp    Inpt_01             ; Nothing Changed, Check Again
    Inpt_10:    ; A=1 & B=0              Two Possible Outcomes: 11 --> Dir = Up
                ;                                               00 --> Dir = Down
            sbis    PIND, InptA       ; Skip Jump if InputA = 1 --> **No Change**
            rjmp    Dir_Down_00         ; AB = 00 .... State Has Changed --> Dir = Down & Send Pulse to Host
            sbic    PIND, InptB       ; Skip Jump if InputB = 0
            rjmp    Dir_Up_11           ; AB = 11 .... State Has Changed --> Dir = Up & Send Pulse to Host
            rjmp    Inpt_10             ; Nothing Changed, Check Again
    Inpt_11:    ; A=1 & B=1              Two Possible Outcomes: 01 --> Dir = Up
                ;                                               10 --> Dir = Down
            sbis    PIND, InptA       ; Skip Jump if InputA = 1 --> **No Change**
            rjmp    Dir_Up_01           ; AB = 01 .... State Has Changed --> Dir = Up & Send Pulse to Host
            sbis    PIND, InptB       ; Skip Jump if InputB = 1 --> **No Change**
            rjmp    Dir_Down_10         ; AB = 10 .... State Has Changed --> Dir = Down  & Send Pulse to Host
            rjmp    Inpt_11             ; Nothing Changed, Check Again

Dir_Down_00:                            ;ie: Sets Dir = Down ... Sends Pulse .... Vectors to Inpt_01:
            rcall   Dir_PauseDown       ;
            rjmp    Inpt_00             ;
Dir_Up_00:
            rcall   Dir_PauseUp
            rjmp    Inpt_00             ;
Dir_Down_01:                            ;ie: Sets Dir = Down ... Sends Pulse .... Vectors to Inpt_01:
            rcall   Dir_PauseDown       ;
            rjmp    Inpt_01             ;
Dir_Up_01:
            rcall   Dir_PauseUp
            rjmp    Inpt_01             ;
Dir_Down_10:                            ;ie: Sets Dir = Down ... Sends Pulse .... Vectors to Inpt_01:
            rcall   Dir_PauseDown       ;
            rjmp    Inpt_10             ;
Dir_Up_10:                              ;
            rcall   Dir_PauseUp         ;
            rjmp    Inpt_10             ;
Dir_Down_11:                            ;ie: Sets Dir = Down ... Sends Pulse .... Vectors to Inpt_01:
            rcall   Dir_PauseDown       ;
            rjmp    Inpt_11             ;
Dir_Up_11:                        ;
            rcall   Dir_PauseUp         ;
            rjmp    Inpt_11             ;

;If Dir Changes Let Dir Change Settle
;If NO Dir Change then Just Send Pulse
Dir_PauseDown:
            sbis    PORTB, Dir          ; Skip rjmp if Dir is Set
            rjmp    PulseOut            ; If PORTB.Dir is ALready Clear then no need to Clear it and Pause
            cbi     PORTB, Dir          ;
            ldi     Cntr, 2             ; 1
            rjmp    Dir_Pause_Wait      ; Give Dir Change a little settling time

Dir_PauseUp:                            ; 3 for rcall
            sbic    PORTB, Dir          ; 1 or 2 Skip rjmp if Dir is Clear
            rjmp    PulseOut            ; 2 If PORTB.Dir is ALready Set then no need to Set it and Pause
            sbi     PORTB, Dir          ; 1
            ldi     Cntr, 2             ; 1  Give Dir Change a little Settling Time
    Dir_Pause_Wait:                     ;
            nop                         ; 1
            dec     Cntr                ; 1
            brne    Dir_Pause_Wait      ; 2 or 1  
                         ;             -----------
    PulseOut:           
            ldi     Cntr, Pulse_Time    ; 1
            sbi     PORTB, Pulse        ; Start Pulse .....
            rcall   Pulse_Duration      ;
            cbi     PORTB, Pulse        ;
            ret                         ; 4 for ret
                         ;             ------------
;Kill Some Clocks to allow the Host time to catch the pulse
Pulse_Duration:                         ; 3 For rCall
            tst     Cntr                ; 1      Just Killing Clocks, No Need to put breq after dec and then Add Nops to the Loop or Increase Cnt       
            breq    Pulse_Done          ; 1 or 2
            dec     Cntr                ; 1
            rjmp    Pulse_Duration      ; 2
;           -------------------------------------
;                                         5 Clocks Per Iteration
;                                      x  8 Iterations
;                                    ------------------
;                                        40
;                                       + 3 for rcall                                                       
    Pulse_Done:          ;              + 1 for breq
            ret          ;              + 4 for ret                       
                         ;           ------------------
                         ;               48 Clocks   
                         ;                @4Mhz = 12uS   (~80Khz Max)
                         ;                @8Mgz = 6uS   (~160Khz Max)
                         ;               @16Mhz = 3uS  (~300Khz Max)  <<<<---Likey Too Fast >>>>>>

I Originally used PD.2 & PD.3 as Inputs A/B, but PD.2 had a considerable amount of 8Mhz "noise" on it, so I moved the inputs to PD.3 & PD.4 and the noise problem vanished.....I have run into this in the Past on ATTINY2313A's ... PD.2 is the "Clock Out" pin when Bit.6 of the Low Fuse Byte is programmed.... I checked, and the low byte read $84 just like it was suppose to....but there is 8Mhz noise there none-the-less....Might be troubling if I needed more pins, but I didn't.

I will update the code if I make any tweaks after testing with a motor & counter .... And, of course, as soon as I get the TL494 circuit built/tested I will post my findings :)

FIsh
 

Fish4Fun

So long, and Thanks for all the Fish!
Aug 27, 2013
481
Joined
Aug 27, 2013
Messages
481
;Found a Dyslexia Moment ...... Can't edit the above post ..... I removed the Direction settling time, and decreased the pulse width to 16 system clocks ... using the 8Mhz internal clock, this ***Should*** allow operation up to at least 200kHz ... Now I just need to make sure an ATMEGA2560 running @ 16Mhz can handle the counting chores....

Why such a fast pulse count?

A 2000ppr Encoder even @ a modest 30rps (1800RPM) translates to 60k counts/second .... Assuming the sampling rate needs to be at least two times as fast as the signal .... the A/B inputs need to be sampled in ~8uS intervals .... For An ATMEGA operating @ 16Mhz, attempting to use ISR handlers would require an ISR every 128 system clocks .... With a 2313 sorting out direction, the DIR output can be routed to one of the ATMEGA's External Interrupts, and the T5 external clock input can be used to keep a 16-Bit count until an OVF or Dir Change .... In the first case, (assuming a worst-case 250kHz count frequency) the OVFs would be ~250mS Apart .... (ie the upper 16-Bits of a 32-Bit counter would only require an Increment/decrement every 250ms on a long run in one direction) .... In the second case (a direction change) there is a great deal more latitude introduced by Physics .... That is, Reversing Direction requires Acceleration - STOP - Acceleration, so long before the Direction change the Pulse stream will be in a reasonable time domain, and the Mega will have time to Re-Set the counter and update the 32-Bit SRAM counter by adding/subtracting the 16-Bit word from the TCNT5 register to/from the 32-bit Value in SRAM.


Code:
; ATTiny2313A_GrayCode.asm
;
; Created: 3/18/2017 9:59:35 PM
; Author : Fish
;


;Genral Purpose Registers
.def    Temp      = r16
.def    Temp1     = r17
.def    Temp2     = r18
.def    Cntr      = r20

;Constants
.equ    InptA       = 3         ;Set InputA = PORTD.3
.equ    InptB       = 4         ;Set InputB = PORTD.4
.equ    Pulse       = 0         ;Set PulsePin to PORTB.0
.equ    Dir         = 1         ;Set DirPin to PORTB.1
.equ    Pulse_Time  = 8         ;Initially Set to 16 --> Assuming AVR Clock = 8Mhz, This Will Kill ~ xxx Clocks
                                ;Lowered to 8


;Unused Interrupt Vectors
rjmp   Reset
rjmp   INT0_V
rjmp   Int1_V
rjmp   TC1CAP_V
rjmp   TC1MA_V      ;
rjmp   TC1OVF_V
rjmp   TC0OVF_V
rjmp   RXC_V
rjmp   DRE_V
rjmp   TXC_V
rjmp   A_Comp_V
rjmp   PCINT0_V
rjmp   TC1MB_V      ;
rjmp   TC0MA_V
rjmp   TC0MB_V
rjmp   USI_Start_V
rjmp   USI_Ovf_V
rjmp   EERdy_V
rjmp   WDTovf_V
rjmp   PCINT1_V
rjmp   PCINT2_V


;********************************************************************************************
;********************************************************************************************
;
;  Following Interrupt Vectors Go To ReSet
;
;********************************************************************************************
;********************************************************************************************
    INT0_V:
    Int1_V:
    TC1CAP_V:
    TC1OVF_V:
    TC0OVF_V:
    DRE_V:
    TXC_V:
    A_Comp_V:
    PCINT0_V:
    USI_Start_V:
    USI_Ovf_V:
    EERdy_V:   
    WDTovf_V:
    PCINT1_V:
    PCINT2_V:
    TC0MA_V:                           
    TC0MB_V:                           
    RXC_V:
    TC1MB_V:
    TC1MA_V:
           rjmp    ReSet           ; All Interrupts Should Vector to ReSet
            nop                     ; Just Cause
ReSet:
            cli                         ; Turn Global IRQ OFF
            ldi     Temp, RAMEND        ;
            out     SPL, Temp           ; Set Stack Pointer
            ;Set Up Inputs
            cbi     DDRD, InptA         ; Set PORTD.InptA = Input
            cbi     DDRD, InptB         ; Set PORTD.InptB = Input
            sbi     PORTD, InptA        ; Enable Input Pullup
            sbi     PORTD, InptB        ; Enable Input Pullup
            ;Set Up Outputs
            sbi     DDRB, Pulse         ; Set PORTB.Pulse to Output
            cbi     PORTB, Pulse        ; Make Sure PORTB.Pulse = Low
            sbi     DDRB, Dir           ; Set PORTB.Pluse to Output
                                        ;
                                         
                                                             ;
; Truth Table for Gray Code:
;
;     -----------------------
;     |        |  New State |
;     | Initial|    Dir     |
;     |  State | Up  | Down |
;     |   AB   | AB     AB  |
;     |---------------------|  
;     |   00   | 10  |  01  |
;     |   10   | 11  |  00  |
;     |   11   | 01  |  10  |
;     |   01   | 00  |  11  |
;     |---------------------|
;     
GetState_AB:
            sbic    PIND, InptA         ; 1 or 2 If InputA is Clear Then Skip Jump
            rjmp    Inpt_1x             ; InputA = High, Jump to Check InputB
            sbic    PIND, InptB         ; If InputB is Clear then Skip Jump
            rjmp    Inpt_01             ;
                                        ; AB = 00 (Initial State)
    Inpt_00:   
            cbi     PORTB, Pulse        ; End Pulse .....
    Inpt_00_Lp: ; A=0 & B=0              Two Possible Outcomes: 10 --> Dir = Up
                ;                                               01 --> Dir = Down   
            sbic    PIND, InptA         ; Skip Jump if InputA = 0
            rjmp    Dir_Up_10           ; If InputA = 1, Then Dir = 1 & Send Pulse
            sbic    PIND, InptB         ; Skip Jump if InputB = 0
            rjmp    Dir_Down_01         ; If B = 1 Then State has Changed --> Dir = Down & Send Pulse to Host
            rjmp    Inpt_00_Lp          ; Both Jumps Skipped ---> Nothing Changed, Check Again

    Inpt_1x:
            sbic    PIND, InptB         ; If InputB is Clear then Skip Jump to Inpt_11
            rjmp    Inpt_11             ; If InputB is Set Then Jump to Inpt_11
                                        ; Else Inpt_10  
    Inpt_10:   
            cbi     PORTB, Pulse        ; End Pulse .....
    Inpt_10_Lp: ; A=1 & B=0              Two Possible Outcomes: 11 --> Dir = Up
                ;                                               00 --> Dir = Down           
            sbis    PIND, InptA         ; Skip Jump if InputA = 1 --> **No Change**
            rjmp    Dir_Down_00         ; AB = 00 .... State Has Changed --> Dir = Down & Send Pulse to Host
            sbic    PIND, InptB         ; Skip Jump if InputB = 0
            rjmp    Dir_Up_11           ; AB = 11 .... State Has Changed --> Dir = Up & Send Pulse to Host
            rjmp    Inpt_10_Lp          ; Nothing Changed, Check Again

    Inpt_01:
            cbi     PORTB, Pulse        ; End Pulse .....
    Inpt_01_Lp: ; A=0 & B=1              Two Possible Outcomes: 00 --> Dir = Up
                ;                                               11 --> Dir = Down           
            sbic    PIND, InptA         ; Skip Jump if InputA = 0 --> **No Change**
            rjmp    Dir_Down_11         ; If InputA = 1, Then State Has Changed --> Dir = Up & Send Pulse to Host
            sbis    PIND, InptB         ; Skip Jump if InputB = 1
            rjmp    Dir_Up_00           ; AB = 00 --> Dir = Down
            rjmp    Inpt_01_Lp          ; Nothing Changed, Check Again
    Inpt_11:
            cbi     PORTB, Pulse        ; End Pulse .....
    Inpt_11_Lp:    ; A=1 & B=1              Two Possible Outcomes: 01 --> Dir = Up
                   ;                                               10 --> Dir = Down
            sbis    PIND, InptA         ; Skip Jump if InputA = 1 --> **No Change**
            rjmp    Dir_Up_01           ; AB = 01 .... State Has Changed --> Dir = Up & Send Pulse to Host
            sbis    PIND, InptB         ; Skip Jump if InputB = 1 --> **No Change**
            rjmp    Dir_Down_10         ; AB = 10 .... State Has Changed --> Dir = Down  & Send Pulse to Host
            rjmp    Inpt_11_Lp          ; Nothing Changed, Check Again

    Dir_Down_00:                            ;ie: Sets Dir = Down ... Sends Pulse .... Vectors to Inpt_01:
            rcall   Dir_PauseDown       ;
            rjmp    Inpt_00             ;
    Dir_Up_00:
            rcall   Dir_PauseUp
            rjmp    Inpt_00             ;
    Dir_Down_01:                            ;ie: Sets Dir = Down ... Sends Pulse .... Vectors to Inpt_01:
            rcall   Dir_PauseDown       ;
            rjmp    Inpt_01             ;
    Dir_Up_01:
            rcall   Dir_PauseUp
            rjmp    Inpt_01             ;
    Dir_Down_10:                            ;ie: Sets Dir = Down ... Sends Pulse .... Vectors to Inpt_01:
            rcall   Dir_PauseDown       ;
            rjmp    Inpt_10             ;
    Dir_Up_10:                              ;
            rcall   Dir_PauseUp         ;
            rjmp    Inpt_10             ;
    Dir_Down_11:                            ;ie: Sets Dir = Down ... Sends Pulse .... Vectors to Inpt_01:
            rcall   Dir_PauseDown       ;
            rjmp    Inpt_11             ;
    Dir_Up_11:                        ;
            rcall   Dir_PauseUp         ;
            rjmp    Inpt_11             ;

;If Dir Changes Let Dir Change Settle
;If NO Dir Change then Just Send Pulse
Dir_PauseDown:                          ; 3 for rcall
            sbic    PORTB, Dir          ; 1 or 2 Skip rjmp if Dir is Clear
            cbi     PORTB, Dir          ; 1
            sbi     PORTB, Pulse        ; Start Pulse .....
            rcall   Pulse_Duration      ; 8
            ret                         ; 4 for ret
Dir_PauseUp:                            ; 3 for rcall
            sbis    PORTB, Dir          ; 1 or 2 Skip rjmp if Dir is Clear
            sbi     PORTB, Dir          ; 1
            sbi     PORTB, Pulse        ; Start Pulse .....
            rcall   Pulse_Duration      ; 8
            ret                         ; 4 for ret
Pulse_Duration:                         ; 3 For rCall
            nop                         ; 1
            ret                         ; 4

Fish
 
Top