0

Hi would like to use an old Diecimila with home-assistant, and that seems to imply installing firmata-standard.

That fails since there is not enough space on the board.

Is there a workaround (I only need inputs, maybe even only digital)?

What would be a good way forward otherwise? Is there an alternative to firmata for homeassistant? Or should I just buy a newer board?

FelixHJ
  • 109
  • 1
  • 1
    You could try ConfigurableFirmata instead. I haven't tried it with such old boards, but when you only need digital I/O, it could even be smaller than StandardFirmata. – PMF Dec 11 '23 at 09:49
  • 1
    The error message was:

    Sketch uses 13172 bytes (91%) of program storage space. Maximum is 14336 bytes. Global variables use 1113 bytes (108%) of dynamic memory, leaving -89 bytes for local variables. Maximum is 1024 bytes.

    – FelixHJ Dec 11 '23 at 21:49
  • Huh. I thought I got it compiling but I'm getting a similar message now, although with a different memory number, still not enough memory though. So, I'm going vote up your comment and delete my own. You should edit that detail into your question because it's important. It may be good to mention which board core and firmata library versions are in use. And even though the error message kind of contains this information, you may want to be explicit about ATmega168. And, I'll see what I can do locally with what I have. – timemage Dec 12 '23 at 00:26

1 Answers1

1

Is there a workaround (I only need inputs, maybe even only digital)?

OldStandardFirmata

I don't know enough about homeassistant to know what you need exactly. But it seems very likely that you can and probably should use OldStandardFirmata. And it is so similar to the modified StandardFirmata (below) that it will probably just work for you. It uses 505 (reported) bytes of RAM.

AllInputsFirmata may also work, but it's RAM usage is only a percent smaller.

Modifying StandardFirmata

That said, it looks like you can get StandardFirmata.ino from current version 2.5.9 to be within limits by removing everything associated with I2C and Servo. It is probably worth trying OldStandardFirmata first, because much of these changes are what makes the difference between the Old and not-Old examples.

Under AVR support version 1.8.7.

Sketch uses 7978 bytes (55%) of program storage space. Maximum is 14336 bytes.
Global variables use 522 bytes (50%) of dynamic memory, leaving 502 bytes for local variables. Maximum is 1024 bytes.

So, slightly larger than OldStandardFirmata.

Action Reported Global variables
Nothing 1079 bytes
Removing only I2C 650 bytes
Removing servo 951 bytes
Removing both I2C and SERVO 522 bytes

Removing servo-related stuff doesn't lower the ram using that much but it does remove some lines from this post. Removing analogWrite, analogRead can remove nontrivial amounts of code, but doesn't affect the memory footprint much. The final size is similar to OldFirmataStandard's size.

The following may work for you. It's the example in entirety after removing I2C and Servo related parts. I would have changed the formatting a bit, but leaving it as is you should be able to diff it against the original to see the changes if you care to.

/*
  Firmata is a generic protocol for communicating with microcontrollers
  from software on a host computer. It is intended to work with
  any host computer software package.

To download a host software package, please click on the following link to open the list of Firmata client libraries in your default browser.

https://github.com/firmata/arduino#firmata-client-libraries

Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. Copyright (C) 2009-2016 Jeff Hoefs. All rights reserved.

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

See file LICENSE.txt for further informations on licensing terms.

Last updated August 17th, 2017 */

#include <Firmata.h>

// the minimum interval for sampling analog input #define MINIMUM_SAMPLING_INTERVAL 1

/*==============================================================================

  • GLOBAL VARIABLES

============================================================================/

#ifdef FIRMATA_SERIAL_FEATURE SerialFirmata serialFeature; #endif

/* analog inputs */ int analogInputsToReport = 0; // bitwise array to store pin reporting

/* digital input ports */ byte reportPINs[TOTAL_PORTS]; // 1 = report this port, 0 = silence byte previousPINs[TOTAL_PORTS]; // previous 8 bits sent

/* pins configuration */ byte portConfigInputs[TOTAL_PORTS]; // each bit: 1 = pin in INPUT, 0 = anything else

/* timer variables */ unsigned long currentMillis; // store the current value from millis() unsigned long previousMillis; // for comparison with currentMillis unsigned int samplingInterval = 19; // how often to run the main loop (in ms)

/* i2c data */ struct i2c_device_info { byte addr; int reg; byte bytes; byte stopTX; };

boolean isResetting = false;

// Forward declare a few functions to avoid compiler errors with older versions // of the Arduino IDE. void setPinModeCallback(byte, int); void reportAnalogCallback(byte analogPin, int value); void sysexCallback(byte, byte, byte*);

/*==============================================================================

  • FUNCTIONS

============================================================================/

void outputPort(byte portNumber, byte portValue, byte forceSend) { // pins not configured as INPUT are cleared to zeros portValue = portValue & portConfigInputs[portNumber]; // only send if the value is different than previously sent if (forceSend || previousPINs[portNumber] != portValue) { Firmata.sendDigitalPort(portNumber, portValue); previousPINs[portNumber] = portValue; } }

/* -----------------------------------------------------------------------------

  • check all the active digital inputs for change of state, then add any events
  • to the Serial output queue using Serial.print() */

void checkDigitalInputs(void) { /* Using non-looping code allows constants to be given to readPort().

  • The compiler will apply substantial optimizations if the inputs
  • to readPort() are compile-time constants. */

if (TOTAL_PORTS > 0 && reportPINs[0]) outputPort(0, readPort(0, portConfigInputs[0]), false); if (TOTAL_PORTS > 1 && reportPINs[1]) outputPort(1, readPort(1, portConfigInputs[1]), false); if (TOTAL_PORTS > 2 && reportPINs[2]) outputPort(2, readPort(2, portConfigInputs[2]), false); if (TOTAL_PORTS > 3 && reportPINs[3]) outputPort(3, readPort(3, portConfigInputs[3]), false); if (TOTAL_PORTS > 4 && reportPINs[4]) outputPort(4, readPort(4, portConfigInputs[4]), false); if (TOTAL_PORTS > 5 && reportPINs[5]) outputPort(5, readPort(5, portConfigInputs[5]), false); if (TOTAL_PORTS > 6 && reportPINs[6]) outputPort(6, readPort(6, portConfigInputs[6]), false); if (TOTAL_PORTS > 7 && reportPINs[7]) outputPort(7, readPort(7, portConfigInputs[7]), false); if (TOTAL_PORTS > 8 && reportPINs[8]) outputPort(8, readPort(8, portConfigInputs[8]), false); if (TOTAL_PORTS > 9 && reportPINs[9]) outputPort(9, readPort(9, portConfigInputs[9]), false); if (TOTAL_PORTS > 10 && reportPINs[10]) outputPort(10, readPort(10, portConfigInputs[10]), false); if (TOTAL_PORTS > 11 && reportPINs[11]) outputPort(11, readPort(11, portConfigInputs[11]), false); if (TOTAL_PORTS > 12 && reportPINs[12]) outputPort(12, readPort(12, portConfigInputs[12]), false); if (TOTAL_PORTS > 13 && reportPINs[13]) outputPort(13, readPort(13, portConfigInputs[13]), false); if (TOTAL_PORTS > 14 && reportPINs[14]) outputPort(14, readPort(14, portConfigInputs[14]), false); if (TOTAL_PORTS > 15 && reportPINs[15]) outputPort(15, readPort(15, portConfigInputs[15]), false); }

// ----------------------------------------------------------------------------- /* sets the pin mode to the correct state and sets the relevant bits in the

  • two bit-arrays that track Digital I/O and PWM status

*/ void setPinModeCallback(byte pin, int mode) { if (Firmata.getPinMode(pin) == PIN_MODE_IGNORE) return;

if (IS_PIN_ANALOG(pin)) { reportAnalogCallback(PIN_TO_ANALOG(pin), mode == PIN_MODE_ANALOG ? 1 : 0); // turn on/off reporting } if (IS_PIN_DIGITAL(pin)) { if (mode == INPUT || mode == PIN_MODE_PULLUP) { portConfigInputs[pin / 8] |= (1 << (pin & 7)); } else { portConfigInputs[pin / 8] &= ~(1 << (pin & 7)); } } Firmata.setPinState(pin, 0); switch (mode) { case PIN_MODE_ANALOG: if (IS_PIN_ANALOG(pin)) { if (IS_PIN_DIGITAL(pin)) { pinMode(PIN_TO_DIGITAL(pin), INPUT); // disable output driver #if ARDUINO <= 100 // deprecated since Arduino 1.0.1 - TODO: drop support in Firmata 2.6 digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups #endif } Firmata.setPinMode(pin, PIN_MODE_ANALOG); } break; case INPUT: if (IS_PIN_DIGITAL(pin)) { pinMode(PIN_TO_DIGITAL(pin), INPUT); // disable output driver #if ARDUINO <= 100 // deprecated since Arduino 1.0.1 - TODO: drop support in Firmata 2.6 digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups #endif Firmata.setPinMode(pin, INPUT); } break; case PIN_MODE_PULLUP: if (IS_PIN_DIGITAL(pin)) { pinMode(PIN_TO_DIGITAL(pin), INPUT_PULLUP); Firmata.setPinMode(pin, PIN_MODE_PULLUP); Firmata.setPinState(pin, 1); } break; case OUTPUT: if (IS_PIN_DIGITAL(pin)) { if (Firmata.getPinMode(pin) == PIN_MODE_PWM) { // Disable PWM if pin mode was previously set to PWM. digitalWrite(PIN_TO_DIGITAL(pin), LOW); } pinMode(PIN_TO_DIGITAL(pin), OUTPUT); Firmata.setPinMode(pin, OUTPUT); } break; case PIN_MODE_PWM: if (IS_PIN_PWM(pin)) { pinMode(PIN_TO_PWM(pin), OUTPUT); analogWrite(PIN_TO_PWM(pin), 0); Firmata.setPinMode(pin, PIN_MODE_PWM); } break; case PIN_MODE_I2C: if (IS_PIN_I2C(pin)) { // mark the pin as i2c // the user must call I2C_CONFIG to enable I2C for a device Firmata.setPinMode(pin, PIN_MODE_I2C); } break; case PIN_MODE_SERIAL: #ifdef FIRMATA_SERIAL_FEATURE serialFeature.handlePinMode(pin, PIN_MODE_SERIAL); #endif break; default: Firmata.sendString("Unknown pin mode"); // TODO: put error msgs in EEPROM } // TODO: save status to EEPROM here, if changed }

/*

  • Sets the value of an individual pin. Useful if you want to set a pin value but
  • are not tracking the digital port state.
  • Can only be used on pins configured as OUTPUT.
  • Cannot be used to enable pull-ups on Digital INPUT pins.

*/ void setPinValueCallback(byte pin, int value) { if (pin < TOTAL_PINS && IS_PIN_DIGITAL(pin)) { if (Firmata.getPinMode(pin) == OUTPUT) { Firmata.setPinState(pin, value); digitalWrite(PIN_TO_DIGITAL(pin), value); } } }

void analogWriteCallback(byte pin, int value) { if (pin < TOTAL_PINS) { switch (Firmata.getPinMode(pin)) { case PIN_MODE_PWM: if (IS_PIN_PWM(pin)) analogWrite(PIN_TO_PWM(pin), value); Firmata.setPinState(pin, value); break; } } }

void digitalWriteCallback(byte port, int value) { byte pin, lastPin, pinValue, mask = 1, pinWriteMask = 0;

if (port < TOTAL_PORTS) { // create a mask of the pins on this port that are writable. lastPin = port * 8 + 8; if (lastPin > TOTAL_PINS) lastPin = TOTAL_PINS; for (pin = port * 8; pin < lastPin; pin++) { // do not disturb non-digital pins (eg, Rx & Tx) if (IS_PIN_DIGITAL(pin)) { // do not touch pins in PWM, ANALOG, SERVO or other modes if (Firmata.getPinMode(pin) == OUTPUT || Firmata.getPinMode(pin) == INPUT) { pinValue = ((byte)value & mask) ? 1 : 0; if (Firmata.getPinMode(pin) == OUTPUT) { pinWriteMask |= mask; } else if (Firmata.getPinMode(pin) == INPUT && pinValue == 1 && Firmata.getPinState(pin) != 1) { // only handle INPUT here for backwards compatibility #if ARDUINO > 100 pinMode(pin, INPUT_PULLUP); #else // only write to the INPUT pin to enable pullups if Arduino v1.0.0 or earlier pinWriteMask |= mask; #endif } Firmata.setPinState(pin, pinValue); } } mask = mask << 1; } writePort(port, (byte)value, pinWriteMask); } }

// ----------------------------------------------------------------------------- /* sets bits in a bit array (int) to toggle the reporting of the analogIns */ //void FirmataClass::setAnalogPinReporting(byte pin, byte state) { //} void reportAnalogCallback(byte analogPin, int value) { if (analogPin < TOTAL_ANALOG_PINS) { if (value == 0) { analogInputsToReport = analogInputsToReport & ~ (1 << analogPin); } else { analogInputsToReport = analogInputsToReport | (1 << analogPin); // prevent during system reset or all analog pin values will be reported // which may report noise for unconnected analog pins if (!isResetting) { // Send pin value immediately. This is helpful when connected via // ethernet, wi-fi or bluetooth so pin states can be known upon // reconnecting. Firmata.sendAnalog(analogPin, analogRead(analogPin)); } } } // TODO: save status to EEPROM here, if changed }

void reportDigitalCallback(byte port, int value) { if (port < TOTAL_PORTS) { reportPINs[port] = (byte)value; // Send port value immediately. This is helpful when connected via // ethernet, wi-fi or bluetooth so pin states can be known upon // reconnecting. if (value) outputPort(port, readPort(port, portConfigInputs[port]), true); } // do not disable analog reporting on these 8 pins, to allow some // pins used for digital, others analog. Instead, allow both types // of reporting to be enabled, but check if the pin is configured // as analog when sampling the analog inputs. Likewise, while // scanning digital pins, portConfigInputs will mask off values from any // pins configured as analog }

/*==============================================================================

  • SYSEX-BASED commands

============================================================================/

void sysexCallback(byte command, byte argc, byte *argv) { byte mode; byte stopTX; byte slaveAddress; byte data; int slaveRegister; unsigned int delayTime;

switch (command) { case SAMPLING_INTERVAL: if (argc > 1) { samplingInterval = argv[0] + (argv[1] << 7); if (samplingInterval < MINIMUM_SAMPLING_INTERVAL) { samplingInterval = MINIMUM_SAMPLING_INTERVAL; } } else { //Firmata.sendString("Not enough data"); } break; case EXTENDED_ANALOG: if (argc > 1) { int val = argv[1]; if (argc > 2) val |= (argv[2] << 7); if (argc > 3) val |= (argv[3] << 14); analogWriteCallback(argv[0], val); } break; case CAPABILITY_QUERY: Firmata.write(START_SYSEX); Firmata.write(CAPABILITY_RESPONSE); for (byte pin = 0; pin < TOTAL_PINS; pin++) { if (IS_PIN_DIGITAL(pin)) { Firmata.write((byte)INPUT); Firmata.write(1); Firmata.write((byte)PIN_MODE_PULLUP); Firmata.write(1); Firmata.write((byte)OUTPUT); Firmata.write(1); } if (IS_PIN_ANALOG(pin)) { Firmata.write(PIN_MODE_ANALOG); Firmata.write(10); // 10 = 10-bit resolution } if (IS_PIN_PWM(pin)) { Firmata.write(PIN_MODE_PWM); Firmata.write(DEFAULT_PWM_RESOLUTION); } if (IS_PIN_DIGITAL(pin)) { Firmata.write(PIN_MODE_SERVO); Firmata.write(14); } if (IS_PIN_I2C(pin)) { Firmata.write(PIN_MODE_I2C); Firmata.write(1); // TODO: could assign a number to map to SCL or SDA } #ifdef FIRMATA_SERIAL_FEATURE serialFeature.handleCapability(pin); #endif Firmata.write(127); } Firmata.write(END_SYSEX); break; case PIN_STATE_QUERY: if (argc > 0) { byte pin = argv[0]; Firmata.write(START_SYSEX); Firmata.write(PIN_STATE_RESPONSE); Firmata.write(pin); if (pin < TOTAL_PINS) { Firmata.write(Firmata.getPinMode(pin)); Firmata.write((byte)Firmata.getPinState(pin) & 0x7F); if (Firmata.getPinState(pin) & 0xFF80) Firmata.write((byte)(Firmata.getPinState(pin) >> 7) & 0x7F); if (Firmata.getPinState(pin) & 0xC000) Firmata.write((byte)(Firmata.getPinState(pin) >> 14) & 0x7F); } Firmata.write(END_SYSEX); } break; case ANALOG_MAPPING_QUERY: Firmata.write(START_SYSEX); Firmata.write(ANALOG_MAPPING_RESPONSE); for (byte pin = 0; pin < TOTAL_PINS; pin++) { Firmata.write(IS_PIN_ANALOG(pin) ? PIN_TO_ANALOG(pin) : 127); } Firmata.write(END_SYSEX); break;

case SERIAL_MESSAGE:

#ifdef FIRMATA_SERIAL_FEATURE serialFeature.handleSysex(command, argc, argv); #endif break; } }

/*==============================================================================

  • SETUP()

============================================================================/

void systemResetCallback() { isResetting = true;

// initialize a defalt state // TODO: option to load config from EEPROM instead of default

#ifdef FIRMATA_SERIAL_FEATURE serialFeature.reset(); #endif

for (byte i = 0; i < TOTAL_PORTS; i++) { reportPINs[i] = false; // by default, reporting off portConfigInputs[i] = 0; // until activated previousPINs[i] = 0; }

for (byte i = 0; i < TOTAL_PINS; i++) { // pins with analog capability default to analog input // otherwise, pins default to digital output if (IS_PIN_ANALOG(i)) { // turns off pullup, configures everything setPinModeCallback(i, PIN_MODE_ANALOG); } else if (IS_PIN_DIGITAL(i)) { // sets the output to 0, configures portConfigInputs setPinModeCallback(i, OUTPUT); } } // by default, do not report any analog inputs analogInputsToReport = 0;

/* send digital inputs to set the initial state on the host computer,

  • since once in the loop(), this firmware will only send on change */

/* TODO: this can never execute, since no pins default to digital input but it will be needed when/if we support EEPROM stored config for (byte i=0; i < TOTAL_PORTS; i++) { outputPort(i, readPort(i, portConfigInputs[i]), true); } */ isResetting = false; }

void setup() { Firmata.setFirmwareVersion(FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION);

Firmata.attach(ANALOG_MESSAGE, analogWriteCallback); Firmata.attach(DIGITAL_MESSAGE, digitalWriteCallback); Firmata.attach(REPORT_ANALOG, reportAnalogCallback); Firmata.attach(REPORT_DIGITAL, reportDigitalCallback); Firmata.attach(SET_PIN_MODE, setPinModeCallback); Firmata.attach(SET_DIGITAL_PIN_VALUE, setPinValueCallback); Firmata.attach(START_SYSEX, sysexCallback); Firmata.attach(SYSTEM_RESET, systemResetCallback);

// to use a port other than Serial, such as Serial1 on an Arduino Leonardo or Mega, // Call begin(baud) on the alternate serial port and pass it to Firmata to begin like this: // Serial1.begin(57600); // Firmata.begin(Serial1); // However do not do this if you are using SERIAL_MESSAGE

Firmata.begin(57600); while (!Serial) { ; // wait for serial port to connect. Needed for ATmega32u4-based boards and Arduino 101 }

systemResetCallback(); // reset to default config }

/*==============================================================================

  • LOOP()

============================================================================/ void loop() { byte pin, analogPin;

/* DIGITALREAD - as fast as possible, check for changes and output them to the

  • FTDI buffer using Serial.print() */

checkDigitalInputs();

/* STREAMREAD - processing incoming messagse as soon as possible, while still

  • checking digital inputs. */

while (Firmata.available()) Firmata.processInput();

// TODO - ensure that Stream buffer doesn't go over 60 bytes

currentMillis = millis(); if (currentMillis - previousMillis > samplingInterval) { previousMillis += samplingInterval; /* ANALOGREAD - do all analogReads() at the configured sampling interval */ for (pin = 0; pin < TOTAL_PINS; pin++) { if (IS_PIN_ANALOG(pin) && Firmata.getPinMode(pin) == PIN_MODE_ANALOG) { analogPin = PIN_TO_ANALOG(pin); if (analogInputsToReport & (1 << analogPin)) { Firmata.sendAnalog(analogPin, analogRead(analogPin)); } } } }

#ifdef FIRMATA_SERIAL_FEATURE serialFeature.update(); #endif }

Upgrading the board

Or should I just buy a newer board?

You probably don't need to because of the above. But you also have the option of upgrading it to an ATmega328P version with just the chip and burning the bootloader or having that done for you. Some hobby places will sell them burned with optiboot.

timemage
  • 5,181
  • 1
  • 13
  • 25