Serial Comms against a greedy interrupt

Hey guys,

New post from a new Arduino user. Used to earn my living writing C++ many years ago, so hopefully not entirely a lost cause :wink:

I'm trying to implement an AC power control (think 'dimmer') where the duty cycle for the AC load is configured by messages received through an RF module.

I've got the AC power control circuit working, thanks to the very helpful videos by Lewis Loflin and Backyard Amusement.

Here's the kick: The interrupt service routine driving the triac can occupy as much as 4,333 of every 8,333 microseconds - i.e. it will delay as much as 4,333 us before activating the triac and then exiting. It is fired each time the AC sine wave crosses zero.

My RF source will broadcast messages at a rate of about 4 Hz, and with the ISR active, I'm missing about 50% of my serial messages, with either timeouts or checksum failures. I hadn't realized the UART and the external interrupt would compete for interrupt time, but I suppose it makes sense :frowning:

I've trimmed the content of serialEvent() down about as much as I can. Any ideas? Would SoftwareSerial be any better/worse? Is there a shield perhaps that would let me off-load the serial comms to separate processor?

smithron99:
Here's the kick: The interrupt service routine driving the triac can occupy as much as 4,333 of every 8,333 microseconds - i.e. it will delay as much as 4,333 us before activating the triac and then exiting. It is fired each time the AC sine wave crosses zero.

That sounds like very poor program design. How could it possibly take more than 10 or 100 µsecs?

Post your program.

...R

Robin2:
That sounds like very poor program design. How could it possibly take more than 10 or 100 µsecs?

It sounds like he is just waiting (possibly delay) from the zero crossing to the trigger point.

Smithron99, there is no need to have the code block while it's waiting to trigger the triac. Just let the main loop keep ticking over and keep checking micros() until it's time to trigger.

If you need greater timing accuracy you can set a timer (timer2 for example) to trigger an interrupt when it's time to fire the triac. Though polling micros in the main loop is simpler and possible good enough.

stuart0:
If you need greater timing accuracy you can set a timer (timer2 for example) to trigger an interrupt when it's time to fire the triac. Though polling micros in the main loop is simpler and possible good enough.

Perfect! I'll give that a shot.

OK. Tried triggering off of micros() in loop(). That fixed the serial problems but got a lot of flicker in the current AC load I'm testing with (light bulb). So I'm trying to use timer 1 (need 16 bits since period can be as high as 4,000 microseconds). Rather than grok the Mega timer intricacies, I'm trying to use Paul Stoffregen's TimerOne library. Seemingly, no matter how I configure the timer, the triac seems to fire every 444 us (give or take 4) after the zero cross.

I'd post a question to the author of the lib, but I can't see any avenue for doing that, so thought I'd post here in case anyone else had experience. Here are relevant snippets of code:

const int CYCLE_MICROS = 8333; // number of microseconds between zero cross events, for 60Hz power.
const int HIGH_BUFFER = 200; // each wave will get at most CYCLE_MICROS - HIGH_BUFFER microseconds. Maybe this could go to zero...
const int LOW_BUFFER = 4333; // each wave will get at least LOW_BUFFER microseconds on-time before zero cross (minimum duty cycle)

const int ZERO_CROSS = 2; // wire zero cross interrupt to this pin
const int PULSE_WIDTH = 40; // Pulse to activate triac (in microseconds).

void startWork(unsigned short baselineTime, unsigned short baselineRevs)
{
//set baseline duty cycle at lower threshold
gCycleDelay = CYCLE_MICROS - LOW_BUFFER;
gIsInterruptHooked = true;
gPrevTime = baselineTime;
gPrevRevolutions = baselineRevs;
lcd.display();
//set up zero cross interrupt routine. Fan should start at min speed
attachInterrupt(digitalPinToInterrupt(ZERO_CROSS), zeroCross, FALLING);
Timer1.attachInterrupt(fireTriac);
}

void zeroCross(void)
{
#if defined (DEBUG) && (DEBUG > 1)
Serial.print(" ZC: ");
Serial.println(micros());
#endif
Timer1.setPeriod(gCycleDelay); //unsigned long with value in the range 200->4000
Timer1.restart();
}

void fireTriac( void )
{
#if defined (DEBUG) && (DEBUG > 1)
Serial.print("Fire: ");
Serial.println(micros());
#endif
Timer1.stop();
digitalWrite( TRIAC, HIGH );
delayMicroseconds( PULSE_WIDTH );
digitalWrite( TRIAC, LOW );
}

void stopWork(unsigned long timestamp)
{
Timer1.stop();
Timer1.detachInterrupt();
if (gIsInterruptHooked)
{
detachInterrupt(digitalPinToInterrupt(ZERO_CROSS));
gIsInterruptHooked = false;
}

lcd.noDisplay();
}

Don't put any Serial call into an Interrupt Service Routine. Don't include any delay() or delayMicroseconds either. Just switch the thing on and leave (saving the value of micros() for use elsewhere would also be fine). Use another ISR or just regular code to turn it off.

You could use the "ON" ISR to start a hardware timer that causes an interrupt when the OFF is due.

...R

OK, I've stripped this down to the bare bones and still no DICE on the timer. Unless anyone is familiar with TimerOne, I guess I'll have to code the timer stuff from scratch :confused: . With the attached code, this is what I get in the serial monitor. Doesn't seem to matter what I initialize the period to.

Start: 752
Stop: 764
Gap: 12
Start: 9056
Stop: 9064
Gap: 8
Start: 17416
Stop: 17428
Gap: 12
Start: 25720
Stop: 25728
Gap: 8
Start: 34076
Stop: 34088
Gap: 12
Start: 42380
Stop: 42388
Gap: 8
Start: 50740
Stop: 50748
Gap: 8
Start: 59044
Stop: 59052
Gap: 8
Start: 67404
Stop: 67412
Gap: 8
Start: 75708
Stop: 75716
Gap: 8
Start: 84064
Stop: 84076
Gap: 12
Start: 92368
Stop: 92380
Gap: 12
Start: 100732
Stop: 100740
Gap: 8
Start: 109032
Stop: 109044
Gap: 12
Start: 117392
Stop: 117404
Gap: 12
Start: 125696
Stop: 125708
Gap: 12
Start: 134056
Stop: 134064
Gap: 8
Start: 142360
Stop: 142368
Gap: 8
Start: 150720
Stop: 150728
Gap: 8
Start: 159024
Stop: 159032
Gap: 8

TimerTest.ino (2.13 KB)

It looks like you have interrupts disabled the whole time - how can you expect anything to work?

I am not familiar with the Timer library but I suspect it will be much easier to do what you want without it.

Can you explain in English (not code) what you want to happen

  • what causes an interrupt?
  • what should happen when the interrupt occurs?
  • what should happen after that?
  • and anything else that will help us understand what you require.

...R

Sure. The purpose of the code is to control the power going to an AC device by supressing current for a portion of each half-cycle of the AC sine wave. The duration to suppress is determined by data coming in over the UART . My test device is a light bulb. The target device is a fan.

An opto-isolator generates a pulse to pin 2 each time the sine wave crosses the zero line, and I have an interrupt (zeroCross) hanging off the falling edge of that pulse. The intent of the zeroCross ISR is to start a timer for a pre-determined number of micro-seconds (range 200-4000), with the fireTriac ISR being called when the timer period is reached. fireTriac closes the circuit and current flows until the next zero cross event.

With 60 Hz AC power, the sine wave runs about 8333 microseconds positive, the 8333 microseconds negative. So, if I delay the triac firing by 4167, for example, that's about 50% power going to the load. There's a good summary of the circuit attached. Unfortunately I've lost the reference to the source.

Interestingly, in looking for the source of that diag I came across this, which is exactly what I'm trying to do, but using the base timer primitives. Not sure how I missed that before. I'll go there next if I can't get the TimerOne lib to work.

Image from Reply #8 so we don't have to download it. See this Image Guide

...R

Thanks for the explanation.

I have not studied it carefully but the code in the link you posted seems like what I had in mind.

...R

Ok, I must be misunderstanding something fundamental about timer1. From what I've read, here's what would expect: If the CPU clock is 16 MHz and I select a pre-scale factor of 8, then I would expect to see 2,000,000 timer ticks per second, or one every 0.5 microseconds.

If that's correct, then I would expect to see the (16-bit) timer overflow every 32,768 microseconds. When i set up a test and measured, the period was much shorter, something like 256 micros (don't have access to the test at the moment). Any idea what I'm missing? I can post the test sketch tonight.

Your logic sounds correct but I always work my way very very slowly through the Atmega datasheet when setting up code for a Timer.

...R

Any idea what I'm missing?

Most likely the mode setting of the timer. 256us at presecaler 8 is 512 counts, so you are probably in the default mode for the timer which is up and down to 255.

Robin2:
Your logic sounds correct but I always work my way very very slowly through the Atmega datasheet when setting up code for a Timer.

...R

Is there any other way to read that sucker? I haven't thought about registers since I was writing Motorola 68k assembler 30 years ago :confused:

Well, in preparing to post my test sketch, to eliminate any noise I unrolled my home-rolled timer class and re-coded the register primitives inline and the damn thing worked.

Pencil sharpening begins...

cattledog:
Most likely the mode setting of the timer. 256us at presecaler 8 is 512 counts, so you are probably in the default mode for the timer which is up and down to 255.

Gold star for cattledog. Somehow (haven't figured out how yet) WGM10 in TCCR1A is getting set. For now I'm forcing TCCR1A back to zero when I set either OCR1A or OCR1B.

smithron99:
Somehow (haven't figured out how yet) WGM10 in TCCR1A is getting set.

Aren't you setting it in your code?

...R

Robin2:
Aren't you setting it in your code?

...R

I had initialized all the registers in the timer class constructor, and declared a global instance of the timer. On the theory that maybe the timers were being used somehow in initial setup happening between initializing the timer class and calling setup(), I moved register initialization into a begin() method and called begin() within setup(). This seems to have solved it.

The timer registers have default settings from the arduino ide, and those defaults determine the standard pwm frequencies with analogWrite().

If you write an empty sketch which simply prints out the value of the timer registers in startup() you can see all the preset vaues.

For Timer1, WGM10 is set for the standard pwm. The presets from the ide are the reason why you should start all custom timer set ups by zeroing the registers, (or defining your set of values with =) and not or-ing your values with the existing ones.