PIC16F628A PWM for LED Chaser

Status
Not open for further replies.

rescue161

Member
I am brand new to PIC coding and programming, so don't beat me up too bad. I have a background in electronics, but the assembly language is throwing me off a bit. I'm more of a see one, do one, teach one kind of guy, so I've been looking at other folks source codes to try to decipher and mimic what they are doing. My end goal is to make a model of a rotating sealed-beam light to put on a 10th scale RC truck. Most of the commercially available lights are for 1/14th scale and smaller, and the operation isn't quite what I'm after. I successfully made a discrete component mock-up on a breadboard and it worked okay, but I just could not get the timing slow enough while keeping the fade rate the way I wanted. The rotation effect was either too fast with good fade, or the speed was right and it was too dim.

Here is a short video of the original discrete component circuit using a 555 and a couple of 4017s. There are some components that aren't connected. I was testing different things and just left them on the board.


That's when I started looking at PICs for my solution. I am trying to learn about changing PWM code in assembly language. The code I'm currently using is from http://picprojects.org.uk/projects/433chaser/index.htm and I modified it to suit my needs, but it only has four PWM states, off, dim, bright and very bright. This works pretty good using 6 LEDs and only lighting one LED to have it rotate around. I tried to use 8 LEDs, arranged in a circle and I lit two LEDs at a time (opposite of each other) to mimic a sealed-beam rotating PAR36 light that I'm trying to emulate. I can time the PIC to flash the "rotator" at 90 flashes per minute, but due to the PWM only having four brightness's, it looks jittery. I asked the writer of the original code to point me in the right direction, so I could add a few more states, but he said that it would take too much to modify the code and that if he had to do that, he would start over and write a new program in C. That is way over my head. So, I ask the experts here, what do I need to do to add a few more PWM states to the source code in the above link?

Here is the area of the code that pertains to the PWM operation. The writer pointed me here, but I have absolutely no clue where to start. The area where I played around with is in the SeqData.inc file. That is where I changed which LED is lit, how bright and when, as well as the hold time between cycles. It works, it is just a little too jittery.

Code:
; PWM Function


_pwm          movfw         vc0           ; AND all 5 bits of vertical counter
              andwf         vc1,W
              andwf         vc2,W
              andwf         vc3,W
              andwf         vc4,W
              iorwf         pwmOutput,F   ; then OR bits with pwmOutput working variable
              movfw         pwmOutput     ; load in W

                                          ; when 5 bits in vertical count reach 11111 
                                          ; corresponding Port bit is turned on and then
                                          ; remains on until counter is reset
    
    movwf    PORTA    ; write to PORTA
              
    andlw    0xF0    ; force W to xxxx0000
    iorwf    copyPORTB,W    ; copy the software oscillator output bit 
              movwf         PORTB         ; and write to physical PORTB

 

; ---------------------------------------
; 2^5 bit x 8 vertical counter
; generates the pwm for the 8 channel LED output
;  http://everything2.com/e2node/vertical counter

_vc32         movf          vc3,W
              andwf         vc2,W
              andwf         vc1,W
              andwf         vc0,W
              xorwf         vc4,F

              movf          vc2,W
              andwf         vc1,W
              andwf         vc0,W
              xorwf         vc3,F

              movf          vc1,W
              andwf         vc0,W
              xorwf         vc2,F

              movf          vc0,W
              xorwf         vc1,F

              comf          vc0,F

; ---------------------------------------

              decfsz        pwm,F         ; decrement PWM counter
              return                      ; return if count != 0

              ; reset and reload PWM output / counter
              movlw         .31           ; reload PWM counter
              movwf         pwm
                  
    clrf    pwmOutput     ; reset output port working variable
              ; vertical counter is 8 channels by 5 bits
              ; rvc1 rvc0  vc4-0  PWM ratio
              ;    0 0     00000  0/31   off
              ;    0 1     00001  1/31   dim
              ;    1 0     00100  8/31   bright
              ;    1 1     11111  31/31  very bright
              ;
              movfw         loReload      ; reload the  vertical counter
              movwf         vc0           
              movfw         hiReload
              movwf         vc3
              andwf         loReload,W
              movwf         vc1
              movwf         vc2
              movwf         vc4
              return

I read quite a bit on how the PIC gets its instructions and how the code works, but I'm just a simpleton, so any help is greatly appreciated.
Thank you in advance.
 
Just 8 for each rotator, but if I could get a 12-LED program, I could omit the ones I don't need by leaving zeros in the sequence code. I want to build a replica of a Twin Sonic that initially started me off on this project. If I could figure out how to edit the code without breaking it, I'd be fine. I just don't grasp things very well until I see it. I have different breadboards set up currently using modified SeqData.inc for 6, 8 and 12-LED lights, but it is jittery due to only having a few LEDs between the four full brightness ones and pretty steep jumps in brightness values. Eventually, I want to make a 12-LED model of a Dietz 7-11 4-sealed-beam beacon to put on a wrecker. I have a couple here that I've made (LEDs only) and they look okay, but only if the speed is fast enough to not notice the jumpiness. They had a very slow flash rate and I'd like to mimic that effect.

The way I did the 8-LED light looks like this:

Code:
              control 1,31
              hold 19
              sdat 3,0,1,2,3,0,1,2
              hold 19
              sdat 2,3,0,1,2,3,0,1
              hold 19
              sdat 1,2,3,0,1,2,3,0
              hold 19
              sdat 0,1,2,3,0,1,2,3
              seqend

I want the 8-LEDs set up with opposing LEDs to be lit, so the above code accomplishes that. The hold time of 19 gives me 79 flashes per minute, which is as close as I could get to 80 FPM on the original Twin Sonic. If the code could be modified to add one more PWM state, I would just change the SeqData.inc to look like this:

Code:
              control 1,31
              hold 1
              sdat 4,0,0,0,4,0,0,0
              hold 1
              sdat 4,1,0,0,4,1,0,0
              hold 1
              sdat 4,2,0,0,4,2,0,0
              hold 1
              sdat 4,3,0,0,4,3,0,0
              hold 1
              sdat 3,4,0,0,3,4,0,0
              hold 1
              sdat 2,4,0,0,2,4,0,0
              hold 1
              sdat 1,4,0,0,1,4,0,0
              hold 1
              sdat 0,4,0,0,0,4,0,0
              hold 1
              sdat 0,4,1,0,0,4,1,0
              hold 1
              sdat 0,4,1,0,0,4,1,0
              hold 1
              sdat 0,4,2,0,0,4,2,0
              hold 1
              sdat 0,4,3,0,0,4,3,0
              hold 1
              sdat 0,3,4,0,0,3,4,0
              hold 1
              sdat 0,2,4,0,0,2,4,0
              hold 1
              sdat 0,1,4,0,0,1,4,0
              hold 1
              sdat 0,0,4,0,0,0,4,0
              hold 1
              sdat 0,0,4,1,0,0,4,1
              hold 1
              sdat 0,0,4,2,0,0,4,1
              hold 1
              sdat 0,0,4,3,0,0,4,3
              hold 1
              sdat 0,0,3,4,0,0,3,4
              hold 1
              sdat 0,0,2,4,0,0,2,4
              hold 1
              sdat 0,0,1,4,0,0,1,4
              hold 1
              sdat 0,0,0,4,0,0,0,4
              hold 1
              sdat 1,0,0,4,1,0,0,4
              hold 1
              sdat 2,0,0,4,2,0,0,4
              hold 1
              sdat 3,0,0,4,3,0,0,4
              hold 1
              sdat 4,0,0,3,4,0,0,3
              hold 1
              sdat 4,0,0,2,4,0,0,2
              hold 1
              sdat 4,0,0,1,4,0,0,1
              seqend

That ends up being 29 mS long with putting a "1" in place of the "19" on the hold time, so the FPM would be a good bit less. Now that I've typed it out, it may not work the way I'd like it to. I guess that's why I'm asking you guys for help. What do you think would be the best approach?
 
Last edited:
Here is a video of the 12-LED with 4 LEDs lit at once. This is as smooth as I can get it with only 3 lit states.

 
I'd like to recommend a Bit Angle Modulation (BAM) driver to provide more PWM 'steps' but the 4-MHz INTOSC on the '628A is too slow.

Which PIC programmer do you have? Is the PIC16F628A the only PIC you can use?
 
I'm just using a MiniPro to program. I have some PIC16F84A and PIC16F877A available as well. The ones I have the most are the 628A, hence why I went that route. I wanted to also make everything as small as possible so I could put everything on the same board as the LEDs. I just jumped right in to making PCBs at home and PIC programming all in one step and figured I could use what I had at home for programming. I had the MiniPro for programming ROMs for my old Pac-Man machine and for burning firmware for my repeaters, so I bought the PICs based off of what most people were using for their LED chaser circuits.
 

All those PIC's are seriously old, the datasheet I've got for the 628 is dated 2003, and the others date from the previous century.

I used the 628 for my PIC tutorials, as at the time it fitted my basic idea nicely, having lot's of I/O and an internal oscillator.

However, those days are LONG!! gone, and more modern devices have much better facilities, more memory, and run lot's faster. I'm currently doing a project using the little 8 pin 12F1840, and that runs at 32MHz using it's internal oscillator, 8 times as fast as the 628. If you want something of a similar size to the 628, I would suggest the 16F1847 (or the lesser memory 16F1827) which are essentially the same core as the little 8 pin one, and I use lot's of 1847/27's in products we make and sell.

Following on from that, there are even more enhanced versions, with lot's of extra cool peripherals, such as the 16F18426 (14 pin) or 16F18446 (20 pin), another two devices that we use in products we manufacture. These could let you use their extra hardware to make your project easier.

I would also suggest you get a PICKIT4, or at least a PICKIT3 - they make life a lot easier, and connect directly to MPLABX.
 
Cool. So I should just scrap trying to edit this code and start over? I have been reading a lot about how to program the PIC and I understand the theory, but like I said, I don't know where to start. I didn't buy the PICKIT because there were too many arguments for which one was the best and from my very short research, it looked like the best one for me was no longer made? So is the 4 better than the 3? If I remember correctly, they omitted some of the earlier versions features on the newer PICKITs, but I am probably wrong.

That was another thing. I was trying to use MPLAB X IDE, but reverted to using the last version of MPLAB IDE, because the newer X would not compile the code correctly. I got it working great on the old version, but again, I was probably doing something wrong on the X version. Thank you guys for the suggestions.
 

The 4 is 'best' in that it programs more new devices, it's faster, and can provide more power to the target circuit. The 3 often doesn't cover some modern devices, but does have some extra facilities (such as a simple logic analyser) - but as a programmer the 4 is superior.

I've got both (and also a 2), and use them pretty interchangeably, but some boards won't program with the 3 as it can't supply enough power, and I than have to use the 4 instead. The 3's do seem very lacking on power capability, and it;'s often easier to program the chips out of the board and use an IC socket.


I was loath to move to X, but had to eventually, and now I wouldn't want to go back.
 
Good deal. Looks like I'll just buy a PICKIT 4 then. Got any suggestions that may be better than Amazon or Ebay? I've heard that there are counterfeit clones out there that I should avoid. I hate that I wasted the money on out-dated chips, but I suppose it is par for the course.
 

I've still got 16C84's here - which was replaced by the 16F84 - which was replaced by the 16F84A - which was (essentially) replaced by the 16F628 etc. etc. etc.

It's quite amazing that they still make the 16F84A, bu they certainly charge a premium for it, as it's MUCH more expensive than more modern much better devices. From RS it's £4.39 inc VAT, the 16F1827 is only £1.44 inc vat. and has 4 times the program memory.

If you want to ensure you get a 'real' PICKIT4, then get it direct from MicroChip, or from RS Components, Farnell, Digikey etc. Mine came from RS Components.
 
Thank you again. As far as the code, should I still write it in assembly, or is there something better (easier). It takes me a long time to pick up new things. My memory is not as good as it used to be.
 
Thank you again. As far as the code, should I still write it in assembly, or is there something better (easier). It takes me a long time to pick up new things. My memory is not as good as it used to be.

Well it's always been a controversial issue

Historically I've always been a HUGE supported of assembler, and I still think everyone should start by learning the rudiments of assembler, as it forces you to understand the hardware. However, many of the more modern datasheets and application notes now give examples in C rather than assembler, and as XC8 is a free download (for the un-optimised version), it makes sense to use XC8 instead of Assembler - and all my PIC programming in now done using XC8.

I still don't like C, and I don't claim to be good at it - I spent ages the other day because I typed '==' instead of '=' (usually I do the opposite), and it's unhelpful with things like that - personally I'd much prefer a good Pascal version However, as application notes aren't likely to be coming in Pascal, I'm sticking with C.
 
Have you considered using neopixel ws2812 LED's - you can easily fade them and change colours as much as you want, and I have code adapted for the 16F18446 from a MicroChip Application Note and Arduino examples - it uses the CLC hardware, as ws2812 LED IC's require extremely fast control signals.
 
This is all new to me. And whatever makes it easy for me to make. I do like to play around with the code to see how everything works and to change things up if I decide to add another light to the mix. The original circuit was a single 555 controlling two 4017s. I had them timed together by tying pin 15 of each 4017 together. Even if they started out of sync, they were immediately re-sync'd after the first cycle. It worked good, but there were just way too many parts with all of the caps and transistors.

All of this code tweaking reminds me of when I put my MAME cabinet together and I edited the MAME and front-end files.
 
These are the LED IC's


You simply wire them in series, DOUT to DIN and connect ground and power together - each colour, red, blue and green can be set to anyone of 256 levels, providing a wide range of options, and RAPID changes. The controller simply feeds the first DIN pin and the control signal is passed along all the LED's.

This should give you plenty of scope for dimming and spinning.

 
I've just written you a more versatile example multi-PWM setup.

It's in C i'm afraid, as I am many years out of date with small PIC assembly & I can't remember all the page select stuff.

I have tried to make it understandable & avoided using C shortcut functions for clarity.

It uses a single PWM counter, incremented every loop of the program.
Plus, a "step table" that defines the brightness for a single light at equal intervals through a cycle.

The individual LEDs uses offset constants arranged equally through that table, so eg. when one
LED is at step 0, another is at step 16, another at step 32 and so on.

The step counter is added to the constant offset, so the LED brightnesses "rotate" through the step buffer.

After every PWM count increment, a function (subroutine) is called for each LED offset, to get the step and from that the PWM on/off decision.

That is used to set or clear the appropriate output pin for that LED (or two LEDs if you are doubling up 180' apart).

You can have as many separate outputs as you wish, using a suitabel size step table; eg. for six out, make it 60 long with offset constants 0, 10, 20, 30, 40 & 50..



C:
#include <16F18313.h>
#zero_ram

#DEVICE PIC16F18313 ICD=1

#USE DELAY(internal=4MHz)

#USE FAST_IO (A)


// Do all pin selects
// 01 = VDD                 +5V
// 02 = RA5
// 03 = RA4
// 04 = RA3 / MCLR          Debug VPP
// 05 = RA2
// 06 = RA1 / ICSP CLK
// 07 = RA0 / ICSP DAT
// 08 = VSS                 0V


//#include <stdio.h>
//#include <stdlib.h>

int8 pwmreg;        //address configuration
int8 stepcount;     //
int8 substep;       // PWM cycles per step increment; speed setting

int8 outimg;


// PWM range 0-x; a (power of 2) - 1 value
// Using 0 - 31
#define PWM_MAX     0x1f

// Brightness steps for a full rotation sequence of one light;
// eg. 4, 8 or 16 times the number of different-brightness lights, -1
#define STEP_MAX    64

// Output phase, equal offsets through the "step" cycle.
#define LED_P0      0
#define LED_P1      16
#define LED_P2      32
#define LED_P3      48

// Look-up table for brightness sequence for one light
// Brightness range 0 = PWM_MAX

unsigned int8  ltable[STEP_MAX] =
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 5, 7, 8, 9, 10, 12, 15, 17, 20, 22, 25, 28, 32,
 32, 28, 25, 22, 20, 17, 15, 12, 10, 9, 8, 7, 5, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};


// Function definition
int8 calc_pwm (unsigned int8);

/*
 * Main program
 */
int main(int argc, char** argv) {
    
    int8 ledout;
    
    outimg = 0;
    pwmreg = 0;
    stepcount = 0;
    substep = 0;
    
    
    // I/O Pin directions:
    // All out: TRIS = 0x0000 0000
    set_tris_a(0x00);
    
    // OSC = 32 MHz.
    // Timer source = osc / 4; 8 MHz.
    
    
    for(;;)
    {
        restart_wdt();
        pwmreg = pwmreg + 1;
        pwmreg = pwmreg & PWM_MAX;
        
        
        // Do one LED
        // Get the on/off state and set the appropriate output pin
        // Outputs high for on; LED & resistor to 0V.
        
        ledout = calc_pwm(LED_P0);
        
        if(ledout == 1) {
            outimg = outimg | 0x04;
        }
        else {
            outimg = outimg & ~0x04;
        }
    
        // The same sequence for the next:
        ledout = calc_pwm(LED_P1);
        
        if(ledout == 1) {
            outimg = outimg | 0x08;
        }
        else {
            outimg = outimg & ~0x08;
        }
        
        // And again for each pin, passing the phase offset
        // then setting the appropriate pin
        ledout = calc_pwm(LED_P2);
        
        if(ledout == 1) {
            outimg = outimg | 0x10;
        }
        else {
            outimg = outimg & ~0x10;
        }


        ledout = calc_pwm(LED_P3);
        
        if(ledout == 1) {
            outimg = outimg | 0x20;
        }
        else {
            outimg = outimg & ~0x20;
        }

        
        output_a(outimg);

        // At each 0 of the PWM reg, count the substep delay
        // and if that overflows, move to the next sequence step.

        if(pwmreg == 0) {
            
            //
            // Substep limit sets the overall cycle speed
            substep = substep + 1;
            if(substep > 4) {
            
                substep = 0;
                
                stepcount = stepcount + 1;
                                    
                if(stepcount >= STEP_MAX)
                {
                    stepcount = 0;
                }
            }
        }
        
    }

    return (1);
}


calc_pwm (unsigned int8 lp) {
    unsigned int8 x, y;
    
    // Work out the cycle stage for the lamp, then get the table brightness
    // and compare to the PWM count to determine on/off
    
    x = stepcount + lp;
    
    // Wrap the result back to the start of the table;
    // if it is beyond the end.
    
    if(x >= STEP_MAX) {
        x = x - STEP_MAX;
    }
    
    // Get the brightness value from the table
    y = ltable[x];
    
    // Compare to present PWM value to determine on or off
    
    if(y > pwmreg) {
        return 1;
    }
    return 0;
}
 
Video of that running; the board is not ideal, it's a test rig for another project and just an 8 pin PIC; plus three of the six I/O pins are used for the ICD3 connection - but I think it demonstrates the principle.

(Without the debug connections, you could use one of those for up to six LED outputs though).

 
Last edited:
Oh man, thank you very much! I'm going to put in an order tonight for a PICKIT 4 and some of the mentioned PICs. Is there anything else you guys think I should get on this order? Is there anything else required to use the PICKIT-4? It looks like it only comes with a USB cable.

I have in my cart the following:

1 - PICKIT-4
10 - PIC16F18313-E/P
4 - PIC16F18446-E/P
4 - PIC12F1840-E/P
4 - PIC16F1847-E/P
4 - PIC16F18426-E/P

I always buy extra just in case things go well and then I have more for other projects, or if I fail and the magic smoke gets let out.
 
Last edited:
Status
Not open for further replies.
Cookies are required to use this site. You must accept them to continue using the site. Learn more…