Hey Guys,
sometimes I'm a bit of a fizz-buzzkill. so how about we play the opposite of this game and see how hard we can optimize a piece of code i'm guilty of?
things that still need to be done:
- somehow stop the actual work and go on if it takes longer than a cycle should take
- measure the time for the cycle calculation stuff and give it less time to sleep
(3. is the bit poking in readData right?)
and maybe some rules:
No Quake-Hacks, only things you can explain at the time of writing. If you explain it hacks and hardware abuse is fine.
Comment your code, give future generations a chance to learn. And no, "self-explanatory code" isn't a thing
and here the code i have so far comes:
///
/// Tool for performing tasks at a given interval
///
/// (C) 2021/2/16 by NO ONE! Public domain. Everyone did it. I do not take ANY
/// responsibilities. Use at your own risk. Better don't use it in production
/// at all! You have been warned!
///
#define MDEBUG 1 // send some useful data to Serial
#define PIN_BUTTON 7 // pin on which the button to pause is plugged in
#define PIN_DATA A0 // pin from which to read sensor data, leave unplugged for random data
// delayMicroseconds longer than this would become inaccurate, use delay instead, https://www.arduino.cc/reference/en/language/functions/time/delaymicroseconds/
#define MAX_DELAY_MICROS 16383 // don't we all love magic numbers!
const uint32_t cyclesPerUpdate = 42; // do stuff every cyclesPerUpdate'th cycles
const uint32_t cyclesPerSecond = 30; // aim for this many cycles every second
void readData();
void computeData();
void printData();
struct Timings {
uint64_t start = 0; // start of a cycle
uint64_t end = 0; // end of a cycle
uint64_t cycleTime = 0; // time the cycle needed
uint64_t sleep = 0; // maximum time a cycle may take
uint64_t delay = 0; // how long to delay between cycles
uint64_t cycleStart = 0; // time the cycles-per-seconds counter was started
uint32_t cyclesPerSecond = 0; // number of cycles per million microseconds (=one second)
uint64_t cyclesMicros = 0; // how long the second actually was
uint64_t cycleCount = 0; // number of cycles we survived so far. will overflow quickly.
float workload = 0.0f; // how much of the total time available (sleep) we use for a cycle
} timings;
bool bPaused = false;
struct Button {
uint64_t change = 0; // millis() when the change happened
uint64_t cooldown = 250; // how long to wait before accepting the next event
bool lastState = false; // what it changed from
bool state = false; // what it changed to
} button;
// just some data for benchmarking
#define DATA_SIZE 8
struct Data {
uint8_t d1[DATA_SIZE];
uint16_t d2;
float d3;
} data;
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(PIN_BUTTON, INPUT);
pinMode(PIN_DATA, INPUT);
timings.sleep = 1000000 / cyclesPerSecond; // 1 second divided by how often we want to run per second gives the total time we have per cycle
timings.cycleStart = micros();
}
void loop() {
timings.start = micros();
timings.cyclesPerSecond++;
timings.cycleCount++;
button.lastState = digitalRead(PIN_BUTTON);
if ((button.state != button.lastState) && (timings.start - button.change > button.cooldown)) {
button.state = !button.state;
button.change = timings.start;
#ifdef MDEBUG
Serial.print("Button changed: ");
Serial.print(button.state);
Serial.print(", Paused: ");
Serial.println(bPaused);
#endif
// only change if the button was pressed, not when it was released
if (button.state == HIGH) {
bPaused = !bPaused;
}
}
// if we are paused take a break and see in the next cycle if the user wants to go on...
if (bPaused == true) {
delay(250);
timings.end = micros(); // useless at the moment
return;
}
// if the boss is looking pretend to do some useful work
if (timings.cycleCount % cyclesPerUpdate == 0) {
digitalWrite(LED_BUILTIN, HIGH);
readData();
digitalWrite(LED_BUILTIN, LOW);
computeData();
digitalWrite(LED_BUILTIN, HIGH);
printData();
digitalWrite(LED_BUILTIN, LOW);
}
timings.end = micros(); // yay! we are done with the work for this cycle! calculating cycle stuff now...
timings.cyclesMicros = timings.end - timings.cycleStart; // we will need this to know if the second to measure the number of cycles in a second is over
timings.cycleTime = timings.end - timings.start; // get an idea about how long this one cycle took...
// see how much time we have left so we can rest a bit maybe
if (timings.sleep > timings.cycleTime) {
timings.delay = timings.sleep - timings.cycleTime;
} else {
timings.delay = 0;
}
timings.workload = 100.0 - ((100.0 / timings.sleep) * timings.delay); // how much of the total time per cycle we used in percent, = approx. cpu workload
#ifdef MDEBUG
Serial.print("Timings: Time spent: ");
Serial.print((uint32_t) timings.cycleTime);
Serial.print(" um, sleeping: "); // the micro-m isn't ascii, is it?
Serial.print((uint32_t) timings.delay);
Serial.print(" um ( of ");
Serial.print((uint32_t) timings.sleep);
Serial.print(" um, workload: ");
Serial.print(timings.workload);
Serial.println(" %)");
#endif
// if we ran for a whole second (or more) let us know how many cycles we actually had ./. the number of cycles we want
// keep in mind there is a 0th cycle, so it's cyclesPerSecond-1
if (timings.cyclesMicros >= 1000000) {
#ifdef MDEBUG
Serial.print("Cycles per second (");
Serial.print((uint32_t) (timings.cyclesMicros / 1000));
Serial.print(" ms): ");
Serial.print(timings.cyclesPerSecond);
Serial.print("(target = ");
Serial.print(cyclesPerSecond);
Serial.println(")");
#endif
// reset the counter for how many cycles we've run in a second
timings.cyclesPerSecond = 0;
timings.cycleStart = micros();
}
// if we can sleep...
if (timings.delay > 0) {
if (timings.delay < MAX_DELAY_MICROS) { // ...see if the delayMicroseconds is precise enough...
delayMicroseconds(timings.delay);
} else { // ...or if we should rather use the delay(millis)
delay((uint64_t) ((double) timings.delay / 1000.0));
}
}
}
void readData() {
for (int i = 0;i < DATA_SIZE;i++) {
data.d1[i] = analogRead(PIN_DATA);
}
data.d2 = analogRead(PIN_DATA);
data.d2 <<= 8;
data.d2 |= analogRead(PIN_DATA);
}
void computeData() {
for (int i = 0;i < DATA_SIZE;i++) {
data.d3 += data.d1[i] * i;
}
data.d3 = (float) data.d3 / (float) data.d2;
}
void printData() {
Serial.println("Data: ");
Serial.print("d1:");
for (int i = 0;i < DATA_SIZE;i++) {
Serial.print(" ");
Serial.print(data.d1[i], HEX);
}
Serial.println("");
Serial.print("d2: ");
Serial.println(data.d2, HEX);
Serial.print("d3: ");
Serial.println(data.d3);
}