7

I'm trying to squeeze code onto an ATtiny10, but I'm just not getting there. I'm using 1060 bytes and only have space for 1024 bytes.

The code is simple enough; I need to read a button pin. If high it needs to pick a random value and that causes one of two random LEDs on the PCB to turn on for two seconds. Is there a way to optimize this code to work on this IC?

int buttonState = 0;

void setup() {
    pinMode(3, INPUT);
}

void loop() {
    buttonState = digitalRead(3);
    if (buttonState == HIGH) {
        bool ranNum=random(0,1);
        if(ranNum == 0) {
            digitalWrite(0, HIGH);
        }
        else {
            digitalWrite(1, HIGH);
        }
        delay(2000);
        digitalWrite(1, LOW);
        digitalWrite(2, LOW);
    }
}
12
  • 4
    I'm not sure what the compiler would optimize out in this situation, but have you tried using direct port manipulation instead of using digitalWrite(), digitalRead() and pinMode(). When not using this the compiler might optimize these functions out.
    – chrisl
    Commented Feb 3, 2022 at 14:42
  • 2
    Not sure if it helps byte wise but you can change if(ranNum == 0) { digitalWrite(0, HIGH); } else { digitalWrite(1, HIGH); } into digitalWrite(ranNum == 0 ? 0 : 1, HIGH); Commented Feb 3, 2022 at 14:45
  • 2
    also also random() is a wrapper around rand() which probably wastes bytes. Use rand() & 1 to get a random 1 or 0.
    – Majenko
    Commented Feb 3, 2022 at 14:58
  • 6
    On such a small device, I would get rid of the Arduino core and program at the avr-libc level. Commented Feb 3, 2022 at 15:14
  • 3
    @EdgarBonet On such a small device I'd get rid of avr-libc and program it in assembly...
    – Majenko
    Commented Feb 3, 2022 at 15:16

4 Answers 4

12

As I stated in a comment, this device would be too small for me to consider programming it using an Arduino core. I would rather stick with the avr-libc and direct port manipulation:

#include <stdlib.h>
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    DDRB = _BV(PB0)   // PB0 as output
         | _BV(PB1);  // PB1 as output
    for (;;) {
        loop_until_bit_is_set(PINB, PB3);  // wait for PB3 high
        if (rand() & 1)
            PORTB |= _BV(PB1);  // PB1 high
        else
            PORTB |= _BV(PB0);  // PB0 high
        _delay_ms(2000);
        PORTB &= ~_BV(PB0);     // PB0 low
        PORTB &= ~_BV(PB1);     // PB1 low
    }
}

This is plain C, but I expect it to be accepted by the Arduino IDE as a valid .ino file. I tried compiling it like this:

avr-gcc -mmcu=attiny10 -Os -Wall -Wextra -DF_CPU=8000000 prog.c -o prog.elf

And it resulted in a program that uses 660 bytes of flash. Most of that is in the implementation of rand() and its dependencies (multiplication and division routines).

2
  • 2
    If rand() is really taking up that much space, you could probably save a lot of it by using a more compact RNG. Here's one of my favorites, which should run fine on an ATTiny and probably generate better randomness too. Commented Feb 4, 2022 at 18:34
  • 1
    You could even use the LSB noise of the ADC to generate a random number. Works pretty well from my experience.
    – Sim Son
    Commented Feb 5, 2022 at 13:03
7

If the button is pressed by a human and your clock is high enough (MHz range), you can use a trick and get rid of the RNG.

You can replace it with a free running counter like this:

unsigned char count = 0;

void loop() {

    count += 1;

    buttonState = digitalRead(3);

    if (buttonState == HIGH) {
        if( (count & 1) == 0) {
            digitalWrite(0, HIGH);
        }
        else {
            digitalWrite(1, HIGH);
        }
        delay(2000);
        digitalWrite(1, LOW);
        digitalWrite(2, LOW);
    }
}

You have count incremented every ⪝10 us (depending on your clock speed, attiny executes one instruction per cycle), so when the user pushes the button, the counter will be sampled.

Note: I assumed loop() is all what the microcontroller has to do, and it's constantly called.

5
  • 3
    That's better randomness than an RNG. Since delay and its clock is running, you could use its TCNT0 or TCNT0L instead of count and get faster counting at zero cost.
    – Dave X
    Commented Feb 4, 2022 at 18:30
  • 1
    Neither loop() nor digitalWrite() may not even be called. The underlying GCC compiler is pretty good at inlining, optimising them down to single assembly instructions. Commented Feb 5, 2022 at 1:48
  • What does it matter, if the function was inlined or not in this context? At the end the function code gets executed, regardless if the compiler inlined it or not.
    – next-hack
    Commented Feb 5, 2022 at 16:17
  • @next-hack: Function calls are instructions which use up valuable flash space (and need time to execute).
    – Michael
    Commented Feb 6, 2022 at 8:29
  • @Michael, I think I quite know that... Still, what's the connection between Redy000's answer and the fact that loop() or DigitalWrite() might be inlined, or implemented as an actual branch ?
    – next-hack
    Commented Feb 6, 2022 at 8:35
3

Are you sure the pins are correct?

Maybe use a smaller random number generator per https://arduino.stackexchange.com/a/18092/6628 ?

static uint8_t lfsr = 0x01;
const byte LFSR_MASK = 0x8e;

void setup() {
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(3, INPUT);
}
void loop() {
  //      int buttonState = digitalRead(3);
  if (digitalRead(3)) {
    if (generateNoise()) {
      digitalWrite(0, HIGH);
    }
    else {
      digitalWrite(1, HIGH);
    }
    //delay(2000)
    //for (int i = 31; i; i--)_delay_us(64516);
    _delay_ms(2000);
    digitalWrite(0, LOW);
    digitalWrite(1, LOW);
  }
}


uint8_t generateNoise() {
  // Return 1 bit of noise using a Galois Linear Feedback Shift Register
  // See https://en.wikipedia.org/wiki/Linear_feedback_shift_register#Galois_LFSRs

  if (lfsr & 1) {
    lfsr =  (lfsr >> 1) ^ LFSR_MASK ;
    return (1);
  }
  else         {
    lfsr >>= 1;
    return (0);
  }
}

The RNG didn't help as much as changing the much-disparaged delay.

2
  • 1
    Your linear feedback register seems indeed quite efficient space-wise. Plugging it into my implementation of the program reduced its size from 660 to 142 bytes! Commented Feb 3, 2022 at 15:46
  • Great. You could probably afford go to a 16 or 32 bit random state variable for richer randomness.
    – Dave X
    Commented Feb 3, 2022 at 15:48
1

An 8-bit chip requires multiple instructions for processing 16 bit. Changing ints to either uint8_t or int8_t will save code.

Depending on compiler optimization, replacing

if ( (count & 1) == 0) {
    digitalWrite(0, HIGH);
}
else {
    digitalWrite(1, HIGH);
}

with

digitalWrite((count & 1), HIGH);

could save bytes.

1
  • 2
    I tried the optimization you suggest on an Uno. It saved two bytes. Commented Feb 5, 2022 at 22:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.