Interval Timer, Function Pointers, and Variables.

Status
Not open for further replies.

halfordC

Member
Hey all,
I'm currently working on porting a pretty sizable Atmel AVR project over to Teensy 4.1.
The First main issue I've run into, is the OLED Screen I'm working with. The AVR Chip I was working with was only running at 16Mhz, so The timing requirements for this screen weren't really that big of an issue.
With the 4.1's significantly higher clock speed, I really have to pay more attention to those timing requirements.
My board layout uses this display in 8bit parallel mode, So I've got 8 data pins, a Data/Command pin, and an enable pin going to the display. Since I can't really write to ports anymore, My plan was to write to a buffer of 16bit Ints, where the bottom 8 bits are the Data pins, and the 9th bit is the Data/command pin(bits 10-16 are unused). Using an interval timer, I can write what gets stored in the buffer to the screen, and the rest of the program can write to the buffer without any serious clashes.
Here is the code for how I expect that to work:


OLED Library (separate File):
Code:
#include "OLEDLib.h"
#include "globalVariables.h"

uint8_t new_line[4] = { 0x80, 0xA0, 0xC0, 0xE0 };
uint8_t OLEDPinArray[9] = {OLEDData0,OLEDData1,OLEDData2,OLEDData3,OLEDData4,OLEDData5, OLEDData6, OLEDData7, OLEDDataCommand};

void enableCycle(volatile Globals *OLEDGlobals) //called on by interval timer.
{
	if (OLEDGlobals->OLEDBuffer[OLEDGlobals->oledReadIndex]) //if this is a non-0 value, continue with interupt. 
	{
		//we need to bring the enable pin high, then wait one micro second, then low. 
		//this interupt will be happenign every 5 to 10 microseconds. We'll keep the delay in for now, and if it negativley impacts performance, we can get rid of it. 
		uint16_t toParse = OLEDGlobals->OLEDBuffer[OLEDGlobals->oledReadIndex]; //create a parse variable, so we don't destroy the variable in the buffer. Might be unnecessary. 
		for (int i= 0; i<9;i++ ) 
		{
			digitalWriteFast(OLEDPinArray[i],(toParse&1)); //mask toParse with 1, so we just get the first bit. 
			toParse = toParse >> 1; //shift toParse down one, so we can get the next bit to write.
		}
		OLEDGlobals->OLEDBuffer[OLEDGlobals->oledReadIndex] = 0; //reset buffer to 0, now that we have shifted out the data.
		OLEDGlobals->oledReadIndex = OLEDGlobals->oledReadIndex + 1; //increment read Index

		digitalWriteFast(OLEDEnable, HIGH);
		delayMicroseconds(1);
		digitalWriteFast(OLEDEnable, LOW); //This pulse sends our data to the screen
	}

}

void command(uint8_t c, Globals *OLEDGlobals)
{
	uint16_t toBuffer; //this number will be inserted into the buffer, at current buffer index. 
	toBuffer = c; // since D/C pin is 0, we don't need to shift anything in. bit #8 is just a 0.
	OLEDGlobals->OLEDBuffer[OLEDGlobals->oledWriteIndex] = toBuffer;
	OLEDGlobals->oledWriteIndex = OLEDGlobals->oledWriteIndex + 1; //incriment write index. 
	//no need to worry about overflows, we overflow to the next part of the buffer.

}

void data(uint8_t d, Globals *OLEDGlobals)
{
	uint16_t toBuffer; //this number will be inserted into the buffer, at current buffer index. 
	toBuffer = d;
	toBuffer = toBuffer | (1 << 8); //this will be our "HIGH" message to the D/C pin
	OLEDGlobals->OLEDBuffer[OLEDGlobals->oledWriteIndex] = toBuffer;
	OLEDGlobals->oledWriteIndex = OLEDGlobals->oledWriteIndex + 1; //incriment write index. 
}
Globals struct from GlobalVariables Library(separate File):
Code:
typedef struct Globals
{
	//New Globals for Teensy port.
	uint16_t OLEDBuffer[256];
	uint8_t oledReadIndex;
	uint8_t oledWriteIndex;

}Globals;

Main Code(main.ino):
Code:
#include "OLEDLib.h"
#include "globalVariables.h"
//initialize our global structs. 
volatile Pattern currentPattern;
volatile Globals currentGlobals;
volatile Screen screenBank;
IntervalTimer OLEDIntervalTimer; //we may be able to save these in our global struct.
void (*enableCyclePointer)(volatile Globals *OLEDGlobals) = &enableCycle;


void setup() {
	initBank(&currentPattern); //set bank to factory defaults
	initGlobals(&currentGlobals, 0); //set globals to factory defaults. both of these will change once the eeprom is implemented. 
	///for current testing, these will stay in here. 
	initPins();
	OLEDIntervalTimer.begin((enableCyclePointer(&currentGlobals)), 6); //This is the Line in Question. 
	initScreen(&currentGlobals);
	

}

// the loop function runs over and over again until power down or reset
void loop() {

}

When Compiling, I get this error:
Code:
Teensy4.1Port.ino: 25:66: error: invalid use of void expression
   OLEDIntervalTimer.begin((enableCyclePointer(&currentGlobals)), 6);

SO.
The question is, What's the right way to pass a variable to into the function pointer that intervalTimer.begin() expects?
In my operating systems class, we never used variables inside of function pointers, but this article seems to insinuate that you can pass a variable to a function pointer.
I tried setting things up in those code examples this way, but I can't get this to compile.


I do understand that I can just make the function being called by interval timer use a global variable, but sharing global variables across files means using "extern", which feels like it will get very messy with all the global variables that will be flying around in this project.

I could also use the OLED screen in question in SPI or TWI modes, but parallel is significantly faster, and I've already ordered the boards for the parallel layout.

This is kindof different coming from AVR, since you could pass variables to functions called inside of ISR functions, no problem.

I thought of maybe busting out the datasheet, and digging through the interval timer library to see If I could just roll my own interrupt timer, but seeing tables of other function pointers made me think I just needed to figure out how to get this working. Or maybe overload the .begin method in the Interval Timer library? That also sounds messy for distributing this codebase. (I foresee library conflicts If I don't do things exactly the right way.)

Using Visual Micro on Windows.
Here is the repository for the project being ported:
https://github.com/BrutalistInstruments/Tsunami-CS-1

Thanks for reading a long explanation for what is essentially just a syntax question.
 
Looking here : pjrc.com/teensy/td_timing_IntervalTimer.html

The called function takes no parameters: myTimer.begin(blinkLED, 150000); // blinkLED to run every 0.15 seconds

to call :: void blinkLED() {

That is the expected usage.

@luni has shown versions that take a parameter with "classy" stuff - maybe that is in his version of timers on github : github.com/luni64/TeensyTimerTool

Though would need to refer to this to see if the version with param is actually suitable for use : github.com/luni64/TeensyTimerTool/wiki
 
You could use a lambda function:
Code:
OLEDIntervalTimer.begin(+[]{ enableCyclePointer(&currentGlobals); }, 6);

Pieter
 
You can't use a function that takes inputs with IntervalTimer.


this article seems to insinuate that you can pass a variable to a function pointer.

Well, yes, you can if you use that approach to rewrite IntervalTimer. It is indeed possible to create something like IntervalTimer, but more complex to handle passing data around. But that is not the way IntervalTimer was written. Wishing for a thing to be different than it is doesn't make it so.

To use IntervalTimer as it currently exists, the function it calls must have no inputs and no outputs.
 
If you know the variable at compile time you can simply do something like this:

Code:
void myFunc(int i)
{
    Serial.println(i);
}

IntervalTimer t1, t2;

void setup()
{
    t1.begin([] { myFunc(42); }, 10'000);
    t2.begin([] { myFunc(17); }, 30'000);
}
void loop()
{
}
which prints 42 every 10ms and 17 every 30ms.

In case you need a real variable or something more complex, you can simply extend the IntervalTimer to use std::function or, more traditional, enabling it to accept a void* parameter to pass state. See here https://github.com/luni64/TeensyHelpers#intervaltimerex for more details and working code. Here an example what can be done:

Code:
#include "IntervalTimerEx.h"

// implement a one shot timer by using the timer address
// passed to the callback:

void myCallback_1(void* state){
    IntervalTimer* timer = (IntervalTimer*)state;
    Serial.printf("Called @\t%d ms\n", millis());
    timer->end();
}
//------------------

IntervalTimerEx t1;

void setup(){
    while(!Serial);
    Serial.printf("Triggered @\t%d ms\n", millis());
    t1.begin(myCallback_1, &t1, 200'000); // pass the address of the timer to the callback
}
void loop(){
}

Output:
Code:
Triggered @	388 ms
Called @	588 ms
 
Thanks everyone! Looks Like I have a good chunk of reading to do.
@luni's libraries look super useful, I'll have to give them a spin.
Thanks again!
 
Status
Not open for further replies.
Back
Top