Pseudo-Multithreading with Arduino

smoore:
The most crucial item is a I2C communication. This requires Wire.h, which uses interrupts. So I had to nest Wire() inside of my ISR, having to use sei() before the Wire call. Yuck! Nesting interrupts! But being my most critical requirement, there it is. I'm guaranteed to be flamed for this. To make myself feel better, I put a cli() after the Wire call.

Perfectly normal and acceptable. You may consider making your ISR non-reentrant and just enabling interrupts at the top. Note that sei() is not available on other Arduino architectures but interrupts() is.

the only technique to increase a few more clock copying is to use a partial unrolling manual .
Considering the average size of strings, I think the portable version and " simple " of cstrcpy() should be more than good

You could possibly use the standard function stpcpy which is already in some versions of avr-gcc and avr-g++. It does the same thing as cstrcpy and has the same function prototype.

You may consider making your ISR non-reentrant and just enabling interrupts at the top. Note that sei() is not available on other Arduino architectures but interrupts() is.

How to make the ISR non-reentrant while also enabling interrupts for Wire()? Seems contradictory to disable interrupts (to prevent reentry) while enabling interrupts (to use Wire). I haven't found where Sketch offers this granularity.

I changed sei() to interrupts().

I also noticed that the code that copies global variables into local buffered space is vulnerable to getting interrupted. This would garble the copy if interrupted mid-byte. Theoretically possible. So I save the interrupt flag, stop the interrupts, copy the globals into local variables, and then restart. Code below:

uint8_t SaveSREG = SREG;                // save interrupt flag
nointerrupts();                         // stop interrupts when grabbing the globals
Variable_1_Local = Variable_1_Global;   // copy the global variables into local copies
Variable_2_Local = Variable_2_Global;
Variable_3_Local = Variable_3_Global;
interrupts();
SREG = SaveSREG;                        // restore the interrupt flag

Whole code below, original code, not using vbextreme's optimizations. There's more optimizations but that's outside of the OP scope of handling tasks.

#include <math.h>
#include <Wire.h>

volatile float Variable_1_Global;
volatile float Variable_2_Global;
volatile float Variable_3_Global;
volatile int dataready;

void setup() {
  Serial.begin(115200);
  delay(100);
  Serial.println(" Starting I2C Init");
  Wire.begin();
  delay(100);
  // this is where I setup my I2C device and init variables
  Serial.println("Finished I2C Init");
}

void loop() { 
  char tmp1[10];
  char tmp2[10];
  char tmp3[10];
  char outputstring[60];
  int serialbuffer;
  
  float Variable_1_Local;
  float Variable_2_Local;
  float Variable_3_Local;

  Serial.println("Configure Timer Interrupt");
  noInterrupts()
  TCCR1A = 0;
  TCCR1B = 0;
  OCR1A = 45;               // set interrupt at 3000us (using 1024 prescalar)
  TCCR1B |= (1 << WGM12);   // turn on CTC mode:
  TCCR1B |= (1 << CS10);    // prescalar
  TCCR1B |= (1 << CS12);    // prescalar 1024
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt:
  interrupts();
  Serial.println("Timer Interrupt Enabled");
  while(1 > 0){                                 // forever loop
    serialbuffer = Serial.availableForWrite();  // check serial TX buffer
    if(serialbuffer > 60){                      // if serial TX buffer is (nearly) empty
      if(dataready > 0) {                       // if data is ready
        uint8_t SaveSREG = SREG;                // save interrupt flag
        nointerrupts();                         // stop interrupts when grabbing the globals
        Variable_1_Local = Variable_1_Global;   // copy the global variables into local copies
        Variable_2_Local = Variable_2_Global;
        Variable_3_Local = Variable_3_Global;
        interrupts();
        SREG = SaveSREG;                        // restore the interrupt flag
        dtostrf(Variable_1_Local,6,4,tmp1);     // Convert variables into char[] strings
        dtostrf(Variable_2_Local,6,4,tmp2);
        dtostrf(Variable_3_Local,6,4,tmp3);

        outputstring[0] = (char)0;              // reset the output char[] string
        strncat(outputstring,tmp1,5);           // Assemble output char[] strings
        strncat(outputstring,",",1);
        strncat(outputstring,tmp2,5);
        strncat(outputstring,",",1);
        strncat(outputstring,tmp3,5);
        Serial.println(outputstring);           // Print the output char[] string
        dataready = 0;                          // clear the flag and wait for new data
      }
    }
  }
}

ISR(TIMER1_COMPA_vect) {
  interrupts();              // restart interrupts to use Wire() for I2C comms
  Wire.read();               // I2C comms
  Wire.write();              // I2C comms
  nointerrupts();            // stop interrupts to prevent reentrant 
  Variable_1_Global = 1;     // save parameters as global variables
  Variable_2_Global = 2;
  Variable_3_Global = 3;
  dataready = 1;             // set global flag that data is ready to be handled
}

@christop stpcpy() it is more slow of cstrcpy()
becouse the stpcpy use a strlen and memcpy.
Strlen require One cicle and memcpy another cicle, two cicles is more slow of One.
AND stpcpy() it not more portable

vbextreme:
@christop stpcpy() it is more slow of cstrcpy()
becouse the stpcpy use a strlen and memcpy.

Sources, please.

When I tested stpcpy() in a short program, the compiler generated an inline loop to copy bytes until it reached the null character. It can't get any faster than that on AVR.

AND stpcpy() it not more portable

It's standardized in POSIX.1-2008. avr-gcc implements much of POSIX where it makes sense (it doesn't have system calls like open()/close(), but it has functions like stpcpy()).

But not all compilers support POSIX, so your cstrcpy is a decent alternative to stpcpy.

But not all compilers support POSIX, so your cstrcpy is a decent alternative to stpcpy.

i.e. <stdlib.h> doesn't work with the Arduino zero, so things like stpcpy need to be coded manually.

smoore:
i.e. <stdlib.h> doesn't work with the Arduino zero, so things like stpcpy need to be coded manually.

That link only suggests that dtostrf doesn't work on the Zero. That's a non-standard avr-libc function, so it shouldn't be too surprising that it's not supported on another platform.

smoore:
How to make the ISR non-reentrant while also enabling interrupts for Wire()? Seems contradictory to disable interrupts (to prevent reentry) while enabling interrupts (to use Wire). I haven't found where Sketch offers this granularity.

Your ISR should be non re-entrant. You can give it the ability to detect if another copy of itself is running, so it knows that it interrupted itself and it can exit and leave the first copy to handle the interrupt.

Basically you need to set a boolean variable and always check that first in your ISR. If it is set to true, then exit. If it's not, then set it to true, re-enable interrupts and keep on working.

I also noticed that the code that copies global variables into local buffered space is vulnerable to getting interrupted. This would garble the copy if interrupted mid-byte. Theoretically possible. So I save the interrupt flag, stop the interrupts, copy the globals into local variables, and then restart. [/b]

You can't be interrupted in the middle of a byte. Accessing a byte in memory is an "atomic" operation that cannot be interrupted. Now if your variables are multi-byte such as int then yes you do need to disable interrupts for the copy operation. Single-byte variables (such as booleans) can be copied without worrying about being interrupted in the middle.

       interrupts();
       SREG = SaveSREG;                        // restore the interrupt flag

Do you have any doubt as to the state of the interrupt flag here? If so please state why.

nointerrupts();

I'm having a little trouble believing that compiles (capitalization).


noInterrupts()

It doesn't. No semicolon.


volatile float Variable_1_Global;
volatile float Variable_2_Global;
volatile float Variable_3_Global;

...

ISR(TIMER1_COMPA_vect) {
  interrupts();              // restart interrupts to use Wire() for I2C comms
  Wire.read();               // I2C comms
  Wire.write();              // I2C comms
  nointerrupts();            // stop interrupts to prevent reentrant
  Variable_1_Global = 1;     // save parameters as global variables
  Variable_2_Global = 2;
  Variable_3_Global = 3;
  dataready = 1;             // set global flag that data is ready to be handled
}

In timing critical code, why are you using floats to store 1, 2 and 3?


I'm guaranteed to be flamed for this.

Well, I won't flame you. However there is a reason why you don't turn interrupts on in an ISR, and that reason is reliability.

Posting on the forum, in effect, "I know I'm doing things in a not-recommended way, but, hell, I'm going to do it anyway" is not actually going to make the microprocessor work better. This thread might go on for quite a while, and then eventually you will remove this stuff which you have added "despite being flamed" and then it will start working more reliably.

ISR(TIMER1_COMPA_vect) {
  interrupts();              // restart interrupts to use Wire() for I2C comms
  Wire.read();               // I2C comms
  Wire.write();              // I2C comms

This nonsense anyway. Sorry.

  Wire.read();               // I2C comms

Why? Why read it and not store the results?

  Wire.write();              // I2C comms

Why? Write what?

No Wire.beginTransmission(), no Wire.endTransmission ().

There is no way that code does anything useful at all.

@christop
the code A:

char* cstrcpy(char* d, const char* s)
{
    while ( (*d++ = *s++) );
    return d-1;
}

and the code B (from gcc 4.8.1):

/* Implement the stpcpy function.
   Copyright (C) 2003 Free Software Foundation, Inc.
   Written by Kaveh R. Ghazi <ghazi@caip.rutgers.edu>.

This file is part of the libiberty library.
Libiberty is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

Libiberty is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with libiberty; see the file COPYING.LIB.  If
not, write to the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
Boston, MA 02110-1301, USA.  */

/*

@deftypefn Supplemental char* stpcpy (char *@var{dst}, const char *@var{src})

Copies the string @var{src} into @var{dst}.  Returns a pointer to
@var{dst} + strlen(@var{src}).

@end deftypefn

*/

#include <ansidecl.h>
#include <stddef.h>

extern size_t strlen (const char *);
extern PTR memcpy (PTR, const PTR, size_t);

char *
stpcpy (char *dst, const char *src)
{
  const size_t len = strlen (src);
  return (char *) memcpy (dst, src, len + 1) + len;
}

are two possible solutions.
The second solution is faster in 32-bit systems but much more slowly in those 8bit and most likely will be used the code A than B.
Of course on Arduino including only string.h returns error function stpcpy() .

and call up the ISR as non-blocking ?

I love this.

...R