Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

PIC16F628A PWM for LED Chaser

Status
Not open for further replies.
Would you be interested in a Charlieplexed display with a hi-speed hi-rez BAM driver for 12F1840 or 16F18313?

Charlie Flasher.png
 
So Charlie-Plexing is the only way to add more LEDs? So, if I do that, I need to change the pins to in & out, which will add to the complexity of the programming. I told myself that I needed to study and read more...lol I need to quite goofing around and get to reading.
 
So Charlie-Plexing is the only way to add more LEDs? So, if I do that, I need to change the pins to in & out, which will add to the complexity of the programming. I told myself that I needed to study and read more...lol I need to quite goofing around and get to reading.

Yes, the limitations of only 8 pins limits what you can do - why not go to a slightly larger device, 14 or even 18 pin from the same datasheet.

BTW, following my last post I've ordered some :D
 
To use RA7 instead of RA3.
It's an 8 pin PIC, it only has RA0 - RA5.

You are correct, RA3 is input only, having just tried it..
The datasheet starts out saying "6 I/O" - but it also refers to a footnote saying one is input only; I missed that.
I thought the config bit to disable external reset allowed full function - but I never tested it.

With debug turned off, all the other pins work fine as outputs - though the programmer / debugger may need disconnecting, if it's loading some pins.

rescue161 -
With any of the larger devices you bought, you should easily be able to add more LEDs.

Edit - missed some posts..

Charlieplexing means the program has to work differently as the PWM is presently applied directly to all outputs.
Adding multiplexing of any type prevents that, as outputs have to be switched in different combinations to enable different LEDs.
The program would need rewriting for the different output system.
 
Last edited:
You are correct, RA3 is input only, having just tried it..
The datasheet starts out saying "6 I/O" - but it also refers to a footnote saying one is input only; I missed that.
I thought the config bit to disable external reset allowed full function - but I never tested it.

I've just recently done the same with a 12F1840, I'd got a spare pin, thought I'd stick an LED on it - oops! :D

However, with the PIC16F18313 I could 'perhaps' have overcome it, as you might be able to reallocate ADC to the pin, and I'm using two ADC inputs so could switch one of those to RA3 (assuming you can), and reuse the other pin. I might give it ago when the PIC16F18313's arrive.
 
Yep, that is what I thought with Charlie-plexing.

Success!

I changed it to the following:

I added "#define LED_P4 64" and then changed the hex values around. Now I have a 5-LED sequencer.
C:
        ledout = calc_pwm(LED_P0);
      
        if(ledout == 1) {
            outimg = outimg | 0x01;
        }
        else {
            outimg = outimg & ~0x01;
        }
  
        // The same sequence for the next:
        ledout = calc_pwm(LED_P1);
      
        if(ledout == 1) {
            outimg = outimg | 0x02;
        }
        else {
            outimg = outimg & ~0x02;
        }
      
        // And again for each pin, passing the phase offset
        // then setting the appropriate pin
        ledout = calc_pwm(LED_P2);
      
        if(ledout == 1) {
            outimg = outimg | 0x04;
        }
        else {
            outimg = outimg & ~0x04;
        }


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

      

        ledout = calc_pwm(LED_P4);
      
        if(ledout == 1) {
            outimg = outimg | 0x20;
        }
        else {
            outimg = outimg & ~0x20;
        }
 
Last edited:
So Charlie-Plexing is the only way to add more LEDs? So, if I do that, I need to change the pins to in & out, which will add to the complexity of the programming. I told myself that I needed to study and read more...lol I need to quite goofing around and get to reading.
The interrupt driven BAM (Bit Angle Modulation) driver takes care of that for you. A small program reads records from an array (table) that contains brightness levels for each LED (0..31) plus a duration value. The BAM driver provides 32 linear brightness levels spanning a 256 step (250-uS steps) period for each LED and the entire 12 LED display is refreshed every 768-usecs (1302-Hz refresh rate)... This is a high performance method that's probably over-kill for your application...
 
Yes. Most definitely overkill, plus it is way over my head. I'm barely able to understand a fraction of what I'm looking at. Thank you though.
 
No problem.

If anyone else is interested, I'd be happy to convert my old 20-LED 12F683 assembly language demo' program to 12 LEDs on the 16F18313...

Cheerful regards, Mike
 
Last edited:
Well, it looks as if my edit of the code didn't do what I thought it was doing. Although 5 LEDs are working (RA0, RA1, RA2, RA4 & RA5), RA0 & RA5 are lit at the same time, i.e., they are showing the same PWM cycle. I suppose I need to add LED_P4 somewhere else in the code besides where I added it. So for now, I removed LED_P4 and am just using 4 LEDs. Been playing with the timing. Pretty cool stuff.
 
Using 64 makes the timing the same as 0, as it's a 64 stage step table; 64 is not divisible by five so not suitable for anything other than 2/4/8

I've done the mods I suggested plus some others in this version (120 step table & brightness vs angle from real world data), plus adding more detailed info & comments.
The outputs are now low for on by default, see the PORTA = line and comments near that to invert the outputs for active high.

C:
/*
* Rotating strobe simulator
*
* (C) 2020 Robert Jenkins
* Released under GNU GPL licence.
*
* This version made to be buildable
* using MPLAB X and the free XC8 compiler
*
*/

// PIC16F18313 Configuration Bit Settings

// 'C' source line config statements

// CONFIG1
#pragma config FEXTOSC = OFF    // FEXTOSC External Oscillator mode Selection bits (Oscillator not enabled)
#pragma config RSTOSC = HFINT32 // Power-up default value for COSC bits (HFINTOSC with 2x PLL (32MHz))
#pragma config CLKOUTEN = OFF   // Clock Out Enable bit (CLKOUT function is disabled; I/O or oscillator function on OSC2)
#pragma config CSWEN = ON       // Clock Switch Enable bit (Writing to NOSC and NDIV is allowed)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
#pragma config MCLRE = OFF      // Master Clear Enable bit (MCLR/VPP pin function is digital input; MCLR internally disabled; Weak pull-up under control of port pin's WPU control bit.)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config WDTE = OFF       // Watchdog Timer Enable bits (WDT enabled, SWDTEN is ignored)
#pragma config LPBOREN = OFF    // Low-power BOR enable bit (ULPBOR disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable bits (Brown-out Reset enabled, SBOREN bit ignored)
#pragma config BORV = LOW       // Brown-out Reset Voltage selection bit (Brown-out voltage (Vbor) set to 2.45V)
#pragma config PPS1WAY = ON     // PPSLOCK bit One-Way Set Enable bit (The PPSLOCK bit can be cleared and set only once; PPS registers remain locked after one clear/set cycle)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will cause a Reset)
#pragma config DEBUG = OFF       // Debugger enable bit (Background debugger enabled)

// CONFIG3
#pragma config WRT = OFF        // User NVM self-write protection bits (Write protection off)
#pragma config LVP = OFF        // Low Voltage Programming Enable bit (HV on MCLR/VPP must be used for programming.)

// CONFIG4
#pragma config CP = OFF         // User NVM Program Memory Code Protection bit (User NVM code protection disabled)
#pragma config CPD = OFF        // Data NVM Memory Code Protection bit (Data NVM code protection disabled)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#include <xc.h>

#include <stdint.h>


// Pin configuration used with 16F18313
//
// 01 = VDD                 +5V
// 02 = RA5                 LED 4
// 03 = RA4                    LED 3
// 04 = RA3 / MCLR          VPP for pprogramming
// 05 = RA2                    LED 2
// 06 = RA1                    LED 1
// 07 = RA0                    LED 0
// 08 = VSS                 0V


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

uint8_t outimg;


// Master, coarse speed setting value; larger is slower.
#define SUBSTEP_MAX 9


// 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 or 8 times the number of different-brightness lights, -1

// With a 32MHz clock, 120 steps can still run ludicrously fast.
#define STEP_MAX    120

// Output phase, equal offsets through the "step" cycle.
// eg. With 120 step table, 0-40-80 for three outputs,
// 0-30-60-90 for four, 0-24-48-72-96 for five,
// 0-20-40-60-80-100 for six and so on.
//

#define LED_P0      0
#define LED_P1      24
#define LED_P2      48
#define LED_P3      72
#define LED_P4      96
//#define LED_P5


//
// The association between LED numbers and output
// pins is created in each pin routine, further down.
//

// Look-up table for brightness sequence for one light
// Brightness range is from 0 (completely off) to PWM_MAX (always on)

uint8_t  ltable1[STEP_MAX] =
// 120 Steps, 3' per step.
      
// This one is for a very narrow beam angle lens front, around 10'     

// 180' to -138'
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// -135' to -93'
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// -90' to -48'
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// -45' to -3'
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 4, 10, 24,
// Zero degrees to +42'
32, 24, 10, 4, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
// +45' to +87'
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// +90' to +132'
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// +135' to +177'
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};



uint8_t  ltable2[STEP_MAX] =
// 120 Steps, 3' per step.

// This one is for a wide beam & open reflector, around 30'

// 180' to -138'
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// -135' to -93'
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// -90' to -48'
0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6,
// -45' to -3'
6, 7, 8, 9, 10, 11, 12, 15, 24, 26, 28, 29, 30, 31, 31,
// Zero degrees to +42'
32, 31, 31, 30, 29, 28, 26, 24, 15, 12, 11, 10, 9, 8, 7,
// +45' to +87'
6, 6, 5, 5, 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1,
// +90' to +132'
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// +135' to +177'
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};



// Function definition
uint8_t calc_pwm(uint8_t);



/*
*
*/
int main(int argc, char** argv) {
  
    int8_t ledout;
  
    outimg = 0;
    pwmreg = 0;
    stepcount = 0;
    substep = 0;
  
  
    // I/O Pin directions:
    // All out: TRIS = 0x0000 0000
    TRISA = 0x00;
  
    // OSC = 32 MHz.
    // Timer source = osc / 4; 8 MHz.
  
  
    for(;;)
    {
        CLRWDT();
      
        pwmreg = pwmreg + 1;
        pwmreg = pwmreg & PWM_MAX;
      
      
        // Do one LED > device pin bit at a time.
      
        // Get the on/off state and set the appropriate output pin
        // Outputs high for on; LED & resistor to 0V.
      
        // The | / ~ number used is the hex value that puts a single bit on the appropriate output pin.
      
        ledout = calc_pwm(LED_P0);
      
        if(ledout == 1) {
            outimg = outimg & ~0x01;
        }
        else {
            outimg = outimg | 0x01;
        }
  
        // The same sequence for the next:
        ledout = calc_pwm(LED_P1);
      
        if(ledout == 1) {
            outimg = outimg & ~0x02;
        }
        else {
            outimg = outimg | 0x02;
        }
      
        // And again for each pin, passing the phase offset
        // then setting the appropriate pin image in outimg
        ledout = calc_pwm(LED_P2);
      
        if(ledout == 1) {
            outimg = outimg & ~0x04;
        }
        else {
            outimg = outimg | 0x04;
        }


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

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

      
        /*
        ledout = calc_pwm(LED_P5);
      
        if(ledout == 1) {
            outimg = outimg & ~0x20;
        }
        else {
            outimg = outimg | 0x20;
        }
        */
      
              
        //
        // Set all the pins in the port simultaneously
        // by copying outimg to the port register.
        //
        PORTA = outimg;
      
        //
        // Or use PORTA = ~outimg;
        // and comment out the above line,
        // if you need pins high for light on
        //     
        // PORTA = ~outimg;
      
        //
        // For larger devices with two or more ports, use two or
        // more outimg - eg. outimga & outimgb
        // processing each bit as abovem then copying both to the
        // respective ports.
        //
      
        // Add a few microseconds delay here to give a sine speed adjustment.
        // Change the
      

        // 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 > SUBSTEP_MAX) {
          
                substep = 0;
              
                stepcount = stepcount + 1;
                                  
                if(stepcount >= STEP_MAX)
                {
                    stepcount = 0;
                }
            }
        }
              
    }

    return (1);
}


uint8_t calc_pwm (uint8_t lp) {
    uint8_t 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
    // Use ltable1 or ltable2 as appropriate.
  
    y = ltable2[x];
  
    // Compare to present PWM value to determine on or off
  
    if(y > pwmreg) {
        return 1;
    }
    return 0;
}
 
Robert. Have you heard of a "toggle table" PWM method? It's faster and much simpler but it uses more RAM memory. Here's an example 6-bit (64 step) interrupt driver where the eight byte led[] array would contain values of 0..63;

Food for thought... Regards...

Code:
void interrupt ISR()                // Michael McLaren, K8LH
{ static unsigned char step = 0;
  static unsigned char toggle[64] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
                                      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
                                      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
                                      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
  PORTB ^= toggle[step];            // update channel outputs
  toggle[step] = 0;                 // clear element for next period
  step++;                           //
  if(step == 64)                    // if end-of-period
  { step = 0;                       //
    toggle[led[0]] |= 1;            // insert LED 0 toggle bit
    toggle[led[1]] |= 2;            // insert LED 1 toggle bit
    toggle[led[2]] |= 4;            // insert LED 2 toggle bit
    toggle[led[3]] |= 8;            // insert LED 3 toggle bit
    toggle[led[4]] |= 16;           // insert LED 4 toggle bit
    toggle[led[5]] |= 32;           // insert LED 5 toggle bit
    toggle[led[6]] |= 64;           // insert LED 6 toggle bit
    toggle[led[7]] |=128;           // insert LED 7 toggle bit
    toggle[0] ^= ~PORTB;            // initialize first toggle element
  }                                 //
  TMR0 = 210;                       // adjusts step size & frame rate
  TMR0IF = 0;                       //
}                                   //
 
Last edited:
Hi Mike,
I normally use interrupts and as many of the device hardware facilities as possible when working on our products, but as an example for someone just learning C, I tried to keep my example program as straightforward as possible; no interrupts, no special hardware features & no C shortcuts or compound operations.

I know it can be made rather faster and far more code efficient, but at the cost of functional clarity & I thought that was most important.

(The gamma correction in your other article is something I thought it needed, though. It needs more PWM levels ideally to work well with that).
 
I didn't realize that you guys posted, but yes, I figured out that if I put 64, it would represent zero. I was editing the table, but all that did was add more states, so I just divided 64 by 5 and rounded up to 13. Now it looks good for a 5-LED light. It is slowly sinking in.

Looking at the new code, I understand what to modify to make everything equal out. I was afraid to change things as I wasn't sure what would "break" the code. Thank you guys for baby-stepping this through for me. It really means a lot.

Edit:
Looking a little deeper, I see that there are two ltables. So, I can leave them commented the way they are as long as I select which one to use (ltable1 or ltable2) for "y" at the bottom? I added another table to see the differences.
 
Last edited:
So, I can leave them commented the way they are as long as I select which one to use (ltable1 or ltable2) for "y" at the bottom? I added another table to see the differences.

Yes, just use whichever you want in the PWM lookup routine, any others are ignored.
 
Nice. So if I wanted to change the speed, do I change a combination or just one specific area. I tried changing the substep, but it didn't seem to have any effect.
 
So if I wanted to change the speed, do I change a combination or just one specific area.

The SUBSTEP_MAX value defined near the top of the program is the coarse speed setting, changing that should change the overall speed proportionally; eg. half it for double speed, increase by 50% for 2/3 speed.
 
Status
Not open for further replies.

New Articles From Microcontroller Tips

Back
Top