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);
}
}
4 Answers 4
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).
-
2If
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.Ilmari Karonen– Ilmari Karonen2022年02月04日 18:34:20 +00:00Commented Feb 4, 2022 at 18:34 -
1You could even use the LSB noise of the ADC to generate a random number. Works pretty well from my experience.Sim Son– Sim Son2022年02月05日 13:03:51 +00:00Commented Feb 5, 2022 at 13:03
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.
-
3That'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– Dave X2022年02月04日 18:30:52 +00:00Commented Feb 4, 2022 at 18:30 -
1Neither loop() nor digitalWrite() may not even be called. The underlying GCC compiler is pretty good at inlining, optimising them down to single assembly instructions.Peter Mortensen– Peter Mortensen2022年02月05日 01:48:36 +00:00Commented 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– next-hack2022年02月05日 16:17:19 +00:00Commented Feb 5, 2022 at 16:17
-
@next-hack: Function calls are instructions which use up valuable flash space (and need time to execute).Michael– Michael2022年02月06日 08:29:56 +00:00Commented 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– next-hack2022年02月06日 08:35:16 +00:00Commented Feb 6, 2022 at 8:35
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.
-
1Your 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!Edgar Bonet– Edgar Bonet2022年02月03日 15:46:04 +00:00Commented 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– Dave X2022年02月03日 15:48:17 +00:00Commented Feb 3, 2022 at 15:48
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.
-
2I tried the optimization you suggest on an Uno. It saved two bytes.Edgar Bonet– Edgar Bonet2022年02月05日 22:25:01 +00:00Commented Feb 5, 2022 at 22:25
Explore related questions
See similar questions with these tags.
digitalWrite()
,digitalRead()
andpinMode()
. When not using this the compiler might optimize these functions out.if(ranNum == 0) { digitalWrite(0, HIGH); } else { digitalWrite(1, HIGH); }
intodigitalWrite(ranNum == 0 ? 0 : 1, HIGH);
random()
is a wrapper aroundrand()
which probably wastes bytes. Userand() & 1
to get a random 1 or 0.