Blinking LEDs without suspending main function in PIC

Discussion in 'Embedded Systems and Microcontrollers' started by jwilk13, Jun 26, 2012.

  1. jwilk13

    Thread Starter Member

    Jun 15, 2011
    228
    12
    Hi all,

    My self-taught knowledge of PICs has left me wondering about something I've just encountered. I have a PIC reading an analog voltage from 0-5V. This voltage determines the output duty cycle of a PWM waveform, but it also determines the flash rate of LEDs connected through a resistor to the MCU.

    What I'm wondering is this: How do I do both "at the same time"? I know that's not entirely possible, but let's consider an example.

    Say I wanted to flash one LED once per second if the voltage at the ADC goes above 1 VDC. As the voltage increases, I want to increase the PWM duty cycle, but if I'm using delays to flash the LED, the sampling rate of my ADC goes way down, right? How would I go about accomplishing both objectives?

    As usual, I'm not looking for code, I can do that myself. I just need a friendly push in the right direction :)
     
  2. takao21203

    Distinguished Member

    Apr 28, 2012
    3,577
    463
    timer interrupt
     
  3. jwilk13

    Thread Starter Member

    Jun 15, 2011
    228
    12
    That's what I was thinking. I'm going to try sampling the ADC at 20 Hz or so using a timer interrupt and then do the same with the LED flashes.
     
  4. Eric007

    Senior Member

    Aug 5, 2011
    1,044
    33
    I like that!
     
  5. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,388
    1,605
    The rate you sample the A2D at and the flash rate are not related unless you make it that way.

    How I would set this up would be governed by how fast I need the circuit to update. How fast should the PWM vary with the voltage? The last time I did this the lowest I could get my PWM to run was some 200Hz, which works fine with the LED and helps with the "flicker" issue. Sampling the A2D any faster then that would be to no use, you would not see it. 1Hz would appear to be "laggy" (the pot turns faster then the output changes), 10 or 20 Hz may work fine, 30Hz should appear instantaneous and isn't a huge computational burden.

    That sets the sample rate, and it would probably be simplest if you have a timer interrupt running to set some base time to either trigger an A2D reading or start or stop a blink.

    And of course to blink a PWM controlled LED you just set the PWM period to the intensity period for ON, or to zero for OFF.
     
  6. takao21203

    Distinguished Member

    Apr 28, 2012
    3,577
    463
    It's a lot of effort to employ PWM just to blink a LED.

    You can easily use a counter for LEDs,
    and for instance program a on/off ratio.

    For instance your main timer interrupt would be every 1ms,
    and then you increment a counter, switch on LED,
    increment another counter when the LED is on, and switch off again.

    Usually I do things like this in the main function, using flags, not directly in the interrupt handler.
     
  7. jwilk13

    Thread Starter Member

    Jun 15, 2011
    228
    12
    I think we're on the same page here for the A2D sampling.

    I think takao and I are on the same page for the LED blinking. Thanks for the suggestions!
     
  8. John P

    AAC Fanatic!

    Oct 14, 2008
    1,634
    224
    And if the object is to drive an LED that a user can see, it's impossible. The frequency for the hardware PWM won't go down anywhere near what you need to make the flashes visible.
     
  9. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,388
    1,605
    Your missing the OP's point: the PWN is for brightness, and a zero period is a blink off.

    And a hardware PWM always has a small software footprint then a software PWM. It's a set-it and forget-it kind of deal.
     
  10. ArmbrusterJo

    New Member

    Jun 27, 2012
    5
    0
    How I would set this up would be governed by how fast I need the circuit to update.
    [​IMG]
     
  11. jwilk13

    Thread Starter Member

    Jun 15, 2011
    228
    12
    I'm not sure my explanation was good enough for anyone to understand what I'm doing :p

    The goal was to read an ADC value between 0 and 5V. There are two outcomes based on this voltage: 1) If the ADC value was above a specified value, the LED flashes at a certain rate (let's say once per second, and this is a digital output). 2) The ADC value determines the PWM duty cycle of a DIFFERENT output (higher value, higher duty cycle).

    My solution for the first part was to use timed interrupts to set a specified sampling rate for the ADC (I chose 50 Hz). I then counted the number of times the program entered the ISR and used that value to blink my LED once per second. My ISR is shown below, the main loop just controls the LED flashing. And I'm using a PIC18F45K20 right now set at 8MHz.

    Code ( (Unknown Language)):
    1. void interrupt()
    2. {
    3.    
    4.     if (intcon.2)
    5.     {
    6.         clear_bit(t0con,7);     // stop timer
    7.         interruptCount++;
    8.         if (interruptCount > 50)
    9.         {
    10.             interruptCount = 0;
    11.         }
    12.  
    13.         adcon0 = 0b00110001;    // set AN12 (RB0) as A/D input
    14.         set_bit(adcon0,1);      // Activate GO/DONE bit
    15.         clear_bit(intcon,2);    // reset interrupt flag
    16.         tmr0l = 100;            // set timer value to 100
    17.         set_bit(t0con,7);       // restart timer
    18.     }
    19.    
    20.     if (pir1.6)
    21.     {
    22.         adcValue = adresl;
    23.         adcValue += (adresh << 8);
    24.         clear_bit(pir1,6);
    25.        
    26.     }  
    27. }
    Basically, the first "if" statement checks to see if the interrupt flag set was for the timer overflow, and the second checks to see if the interrupt was from an A/D conversion completing. I did it this way so that the program never has to stop and wait for A/D conversions or for delays between LED blinks.
     
    Last edited: Jun 28, 2012
  12. Markd77

    Senior Member

    Sep 7, 2009
    2,803
    594
    Another option is to start the AD conversion at the end of the interrupt and then assume it is ready the next time the timer interrupt happens. It slightly increases the time available in the main function at the expense of the AD result being slightly late.
    I'd recommend using the flag names instead of bit numbers, it makes things easier to read / check. Eg. intcon, T0IF instead of intcon, 2
     
    jwilk13 likes this.
  13. jwilk13

    Thread Starter Member

    Jun 15, 2011
    228
    12
    That's a good suggestion. I didn't know until now that my compiler supports that. The only references I found in their documentation up to this point used the bit number, not the name.

    I just tried it though and the compiler supports it in their set_bit and clear_bit macros as well as the IF statements. I changed some of them accordingly...

    Code ( (Unknown Language)):
    1. void interrupt()
    2. {
    3.    
    4.     if (intcon.T0IF)
    5.     {
    6.         clear_bit(t0con,TMR0ON);            // stop timer
    7.         interruptCount++;
    8.        
    9.         // Check for 50 iterations.  This corresponds to one second.
    10.        
    11.         if (interruptCount > 50)
    12.         {
    13.             interruptCount = 0;
    14.         }
    15.        
    16.         adcon0 = 0b00110001;        // set AN12 (RB0) as A/D input
    17.         set_bit(adcon0,1);      // Activate GO/DONE bit
    18.         clear_bit(intcon,T0IF);     // reset interrupt flag
    19.         tmr0l = 100;            // set timer value to 100
    20.         set_bit(t0con,TMR0ON);  // restart timer
    21.     }
    22.    
    23.     if (pir1.ADIF)
    24.     {
    25.         adcValue = adresl;
    26.         adcValue += (adresh << 8);  // Read value in ADC registers
    27.         clear_bit(pir1,ADIF);       // Reset interrupt flag
    28.     }  
    29. }
    The only one I wasn't sure about was the GO/DONE bit in ADCON0. You're right though, seeing something like TMR0ON as opposed to just a number is a lot easier to read through. Thanks for the advice!

    EDIT: I looked through my compiler's documentation, and the GO/DONE bit can be referenced using GO, DONE, GO_DONE, NOT_DONE, or GO_NOT_DONE.
     
    Last edited: Jun 28, 2012
  14. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,388
    1,605
    Here's a style inquiry. I wonder why you would use:

    clear_bit(pir1,ADIF);

    when you also use:

    if (pir1.ADIF)

    Sure, the clear_bit() and set_bit() functions (or macros, I haven't looked them up) work just fine, but my preference is to use:

    pir1.ADIF = 0;
     
  15. jwilk13

    Thread Starter Member

    Jun 15, 2011
    228
    12
    I guess I've just gotten so used to using set_bit and clear_bit that I automatically use them. Either way works, but it does make more sense to do it the way you described, even if it's just to use periods across the board and avoid commas. I've gotten so used to it I guess I just don't notice it. It's kinda like driving a stickshift I suppose...I still reach down for the shift knob even when I'm driving an automatic :p

    EDIT: My code must be pretty slick if you're picking out stuff like that :D I'll take it as a compliment!
     
  16. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,388
    1,605
    By all means take it as such. You have a clear indentation scheme marking off each block, the blocks themselves are commented, plus a nice comment on each line.

    The best code makes you want to read it.
     
  17. takao21203

    Distinguished Member

    Apr 28, 2012
    3,577
    463
    using XC8, it's sufficient actually only to write "T0IF", means

    Code ( (Unknown Language)):
    1.  if(T0IF)T0IF=0;
    what I recently figured out, things like that will work and produce nearly 1:1 assembler equivalent:

    Code ( (Unknown Language)):
    1. v_data<<=1;if(CARRY)LB5=1;
    where LB is the latch register for port B

    quite interesting because until recently I used binary tables for that, requiring
    a reference to constant array each time.

    This is by no means documentated somewhere in form of a how-to instruction sheet. I figured it out by random, and a lot of guesswork.
     
Loading...