PROGMEM with cli()/sei()

First post here and I've looked through a lot of forum stuff and not found this one (yet).

I have an Uno and I've been doing some high speed bit bashing with some code off Adafruit. For performance reasons the code disables interrupts using cli()/sei() to wrap some digitalWrite() and delayMicroseconds() calls.

All has so far been well. I have been writing out to a pin using a table of on/off (write/delay) values. But this table has been stored in RAM and I have lots of them.

So I moved the tables to flash using the PROGMEM macro. I select a table from those stored and copy it to RAM using pg._read_word() calls. I checked the values and they look ok.

However, after this I then call my bit bashing function which seems to run much slower now. I'm not sure why. I'm done with the flash by this point. Indeed there after Serial.print seems very slow.

Any thoughts? All the data does indeed appear to be copied into RAM but still things seem slow. If I comment out the flash=>RAM copy function the code is fast as before.

Any help appreciated :slight_smile:

Reading from PROGMEM is twice as slow as reading from RAM.

If you have a string in pgmspace you want to print with serial, you can avoid copying the string to ram first (then printing RAM).

const char str[] PROGMEM = "test";

void func() {
  //Print global
  Serial.print( (__FlashStringHelper*) str );

  //Print string from PROGMEM inline.
  Serial.print( F("test") );
}

But unless you are maxing out your CPU, PROGMEM is probably not your speed issue. Post your code and someone may notice a logic mistake.

Thanks for that.

So I have an array of iR LED on/off intervals thus:

static const uint16_t onOff[MAX_PULSES] PROGMEM = {
// ON, OFF (in 10's of microseconds)
439, 216,
31, 23,
31, 24,
30, 24,
31, 24,
30, 24,
30, 79,
30, 24,
31, 23,
31, 78,
31, 78,
31, 78,
30, 78,
31, 78,
31, 24,
30, 79,
30, 79,
30, 24,
31, 23,
31, 23,
31, 24,
31, 23,
31, 24,
30, 24,
31, 23,
31, 78,
31, 78,
31, 78,
31, 78,
30, 78,
31, 78,
31, 78,
31, 78,
31, 0
};

I then load them into a RAM array

#define MAX_PULSES 100
uint16_t global_pulses[MAX_PULSES][2]; // pair is high and low pulse

uint16_t loadKey(void)
{
Serial.println("start");
uint16_t i, j;
j = 0;
for(i = 0;i < 200;i++) {
uint16_t on = (uint16_t)pgm_read_word(onOff + i++);
uint16_t off = (uint16_t)pgm_read_word(onOff + i);
global_pulses[j][0] = on * RESOLUTION;
global_pulses[j][1] = off * RESOLUTION;
j++;
if(on == 0 || off == 0)
{
break;
}
}

return j;
}

I then have a function to pump these out to my LED pin

void Sendv7monitor(uint16_t (*pulses)[2], uint16_t count)
{
uint16_t j ;

//debug logging normally removed
for(j = 0; j < count; j++) {
long on = global_pulses[j][0];
long off = global_pulses[j][1];

Serial.print("ON OFF ");
Serial.print(on, DEC);
Serial.print(", ");
Serial.println(off, DEC);
}

for(j = 0; j < count; j++) {
long on = global_pulses[j][0];
long off = global_pulses[j][1];
pulseIR(on);
if (off < 3000)
{
delayMicroseconds(off);
}else {
delay((off+500)/1000);
}
}
delay(10);
}

Now if I Sendv7monitor() without calling loadKey () first the debug logging spews out quickly but if I DO call loadKey () first (even with a delay after) it spits out initially quickly but slows down after a few lines like some FIFO is filling up somewhere.

I suspect there is some additional interrupt running under the hood I'm not seeing.
I'm not using any strings or chars but unit16_t values. I'm copying them to ram initially for speed.

All help GRATEFULLY received.

Mike

Are you sure your PC isn't the one delaying.

You could simplify your code by reading the data directly from PROGMEM, rather than buffering it then using a copy.

Also using a struct/class makes this far simpler to read (in my opinion).

#define MAX_PULSES 100
#define RESOLUTION 100

struct Pulse{
  uint8_t on;
  uint8_t off;
};

const Pulse onOff[MAX_PULSES] PROGMEM = {
  { 10, 10 },
  { 20, 20 },
  { 30, 30 },
  //...
};

void Sendv7monitor(uint16_t count) 
{
  for( uint16_t j = 0; j < count; j++){
    
    uint16_t on = (uint16_t) pgm_read_byte( &onOff[ j ].on ) * RESOLUTION;
    uint16_t off = (uint16_t) pgm_read_byte( &onOff[ j ].off ) * RESOLUTION;
    
    pulseIR(on);
    
    if(off < 3000){
      delayMicroseconds(off);
    }else{
      delay((off+500)/1000);    
    }
  }
  delay(10);    
}

void setup(){}
void loop(){}

Well as mentioned above, reading from PROGMEM should incur a speed hit. The PC serial port works ok because if I turn off the copying from PROGMEM (so I'm reading effectively an uninitialised array but that doesn't matter) then the PC printing is very quick. Also with PROGMEM copy in place and the Serial print commented out the iRRC codes don't work (I presume due to the slow timing) but do if the data is originally in RAM (i.e. no PROGMEM at all).

It is as if pgm_read_word() initiates an interrupt and fails to cancel it.

A speed hit only because it takes two cycles compared to one. However this is still exceptionally fast.

Once you fill the Serial buffer, it becomes a blocking method until the there is enough room for more data. Do your test with less ouput (just the significant bits, not the whole array dump).

Hi, could you post your complete program? Use code tags (the </> button on the toolbar above the rely window) or, if it is too long, attach it as a file.

#include "avr/pgmspace.h" 


// We need to use the 'raw' pin reading methods
// because timing is very important here and the digitalRead()
// procedure is slower!
//uint8_t IRpin = 2;
// Digital pin #2 is the same as Pin D2 see
// http://arduino.cc/en/Hacking/PinMapping168 for the 'raw' pin mapping
#define IRpin_PIN PIND
#define IRpin 2
// for MEGA use these!
//#define IRpin_PIN PINE
//#define IRpin 4
 
const int IRledPin =  12;    // LED connected to digital pin 13
const int buttonPin = 4;     // the number of the pushbutton pin
const int ledPin =  13;      // the number of the LED pin

// the maximum pulse we'll listen for - 65 milliseconds is a long time
//#define MAXPULSE 65000
#define MAXPULSE 1000

// what our timing resolution should be, larger is better
// as its more 'precise' - but too large and you wont get
// accurate timing
#define RESOLUTION 20

// we will store up to 100 pulse pairs (this is -a lot-)
#define MAX_PULSES 100
uint16_t global_pulses[MAX_PULSES][2]; // pair is high and low pulse


void Sendv7monitor(uint16_t (*pulses)[2], uint16_t count);

static const uint16_t onOff[MAX_PULSES] PROGMEM = {
// ON, OFF (in 10's of microseconds)
439, 216,
  31, 23,
  31, 24,
  30, 24,
  31, 24,
  30, 24,
  30, 79,
  30, 24,
  31, 23,
  31, 78,
  31, 78,
  31, 78,
  30, 78,
  31, 78,
  31, 24,
  30, 79,
  30, 79,
  30, 24,
  31, 23,
  31, 23,
  31, 24,
  31, 23,
  31, 24,
  30, 24,
  31, 23,
  31, 78,
  31, 78,
  31, 78,
  31, 78,
  30, 78,
  31, 78,
  31, 78,
  31, 78,
  31, 0
};



static uint16_t g_count = 50;

// The setup() method runs once, when the sketch starts 
void setup()   {                
 
  Serial.begin(9600);

  // initialize the IR digital pin as an output:
  pinMode(IRledPin, OUTPUT);      
  // initialize the LED pin as an output:
  pinMode(ledPin, OUTPUT);      
  // initialize the pushbutton pin as an input:
  pinMode(buttonPin, INPUT); 

  g_count = loadKey(0);
}



void loop()                     
{
      Sendv7monitor(global_pulses, g_count);  
}



// This procedure sends a 38KHz pulse to the IRledPin 
// for a certain # of microseconds. We'll use this whenever we need to send codes
void pulseIR(uint16_t microsecs) {
  // we'll count down from the number of microseconds we are told to wait
 
  cli();  // this turns off any background interrupts
 
  while (microsecs > 0) {
    // 38 kHz is about 13 microseconds high and 13 microseconds low
   digitalWrite(IRledPin, HIGH);  // this takes about 3 microseconds to happen
   delayMicroseconds(10);         // hang out for 10 microseconds, you can also change this to 9 if its not working
   digitalWrite(IRledPin, LOW);   // this also takes about 3 microseconds
   delayMicroseconds(10);         // hang out for 10 microseconds, you can also change this to 9 if its not working
 
   // so 26 microseconds altogether
   microsecs -= 26;
  }
 
  sei();  // this turns them back on
}

uint16_t loadKey(int keyCode)
{
  Serial.println("start");
  uint16_t i, j;
  j = 0;
  for(i = 0;i < 200;i++) {
    uint16_t on = (uint16_t)pgm_read_word(onOff + i++);
    uint16_t off = (uint16_t)pgm_read_word(onOff + i);
    global_pulses[j][0] = on * RESOLUTION;
    global_pulses[j][1] = off * RESOLUTION;
    j++;
    if(on == 0 || off == 0)
    {
      break;
    }
  }
  printpulses(global_pulses,j);

  return j;
}

void Sendv7monitor(uint16_t (*pulses)[2], uint16_t count) 
{
  uint16_t j ;

  for(j = 0; j < count; j++) {
    long on = global_pulses[j][0];
    long off = global_pulses[j][1];

    Serial.print("ON OFF ");
    Serial.print(on, DEC);
    Serial.print(", ");
    Serial.println(off, DEC);
  }

 
  for(j = 0; j < count; j++) {
    long on = global_pulses[j][0];
    long off = global_pulses[j][1];
    pulseIR(on);
    if (off < 3000)
    {
      delayMicroseconds(off);
    }else {
      delay((off+500)/1000);    
    }
  }
  delay(10);    
}

(i've edited out unused code to fit in the 9000 character forum limit).

I know the code is very rough but I've been hacking it a lot. (Sorry)

void pulseIR(uint16_t microsecs) {
...
  while (microsecs > 0) {
...   
    microsecs -= 26;
  }
 ...
}

Is microsecs always a multiple of 26? If not, the -= 26 will overflow from a small value > 0 to a large value a bit less than 65535 and the while loop will repeat.

(SLAPS OWN FACE)

YES! YES! Thank you. I don't know why this worked before but changing it fixes everything. Thank you.

That bit code code was crippled of Adafruit - not that that is an excuse :wink:

Crippled => cribbed. Damn spell checker ;-p