Help shrinking code

Hi All
I am trying to use an ATtiny84A as a Transceiver using the 433mhZ modules and RH_ASK library.
I managed to get my program to eventually compile but when it came to uploading to the micro I get the following error in AVRDUDESS: firmware.hex: 8,333 / 8,192 Bytes (101.78%) [!].
I don’t know how to trim the code ant further. Perhaps there are other ways to achieve this???
Code is:

/*Header file E:\Atmel\Atmel Easy Headers\ATmega328P 16MHz.txt
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Program   : GPOTx V3.cpp
// File Path : E:\Mike's-Projects\Remote Controlled GPO\GPO Transmitter\
//			     : \PIO Software\GPO Transmitter V3
// Author    : Mike
// Date      : 16 Sep 2026
// Notes     : Make as a Transceiver to check signals.
//           : 17 Sep Added Transmit as a Function.
//					 : 20 Sep. Working OK Transmitting. 
//           : Now add the Receiver and Buzzer.
//					 : 20 Sep. If a good signal is rec'd Receiver sends an "Ack"
//					 : 24 Sep. V3. Changed Micro to an ATtiny84A.
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//     ATtiny84A:                  +----v----+
//                             VCC-[1      14]-GND
//                   XTAL1/D10/PB0-[2      13]-PA0/(D0)
//                  XTAL2/(D9)/PB1-[3      12]-PA1/(D1)
//                       R̅E̅S̅E̅T̅/PB3-[4      11]-PA2/(D2)
//                  CKOUT/(D8)/PB2-[5      10]-PA3/(D3)
//                        (D7)/PA7-[6       9]-PA4/(D4)/SCK
//                   MOSI/(D6)/PA7-[7       8]-PA5/(D5)/MISO
//                                 +---------+
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Atmel-ICE to ATtiny84A connections:
// |J5 |        |JST|
// |Pin|Name|Col|Pin|84A|
// | 1 |MISO|BRN| 5 | 8 |
// | 2 |VCC |RED| 3 | 1 |
// | 3 |SCK |ORN| 4 | 9 |
// | 4 |MOSI|YEL| 6 | 7 |
// | 5 |RST |GRN| 2 | 4 |
// | 6 |GND |BLU| 1 | 14|

//  Set-up for Internal 8MHz clock:
//  Fuses for 84A without Crystal:
//      LOW   0xE2; Binary 11100010
//      HIGH  0xD5; Binary 11010101
//  EXTENDED  0xFF; Binary 11111111

//  0=Set, 1=Clear.
// Fuse Names:
// |Bit|  Low   |Low|   High    |Hgh| Extended  |Ext|
// | 7 | CKDIV8 | 1 | RSTDISBL  | 1 |           | 1 |
// | 6 | CKOUT  | 1 | DWEN      | 1 |           | 1 |
// | 5 | SUT1   | 1 | SPIEN     | 0 |           | 1 |
// | 4 | SUT0   | 0 | WDTON     | 1 |           | 1 |
// | 3 | CKSEL3 | 0 | EESAVE    | 0 |           | 1 |
// | 3 | CKSEL3 | 0 | BODLEVEL2 | 1 |           | 1 |
// | 2 | CKSEL2 | 1 | BODLEVEL1 | 0 |           | 1 |
// | 1 | CKSEL1 | 0 | BODLEVEL0 | 1 | SELFPRGEN | 1 |
// |   |        | E2|           | D5|           | FF|

// LOW Byte=$E2
// DO NOT Divide clock by 8;-------------------------------[CKDIV8=1]
// NO Clock Output;----------------------------------------[CKOUT=1]
// Int. RC Osc. 8MHz; Start-up time PWRDWN/RESET: 6 CK/14 CK + 64ms;
// --------------------------------------------------------[SUT=10:CKSEL=0010]
 
// HIGH Byte=$D5
// External Reset ENABLED;---------------------------------[RSTDISBL=1]
// debugWIRE DISABLED;-------------------------------------[DWEN=1]
// Enable Serial Programming & Data Downloading;-----------[SPIEN=0]
// Watchdog Timer NOT Always On;---------------------------[WDTON=1]
// EEPROM Memory Reserved Through Chip Erase;--------------[EESAVE=0]
// Brown-out detection level at VCC=2.7V;------------------[BODLEVEL=101]

// Extended Byte=$FF
// Self programming disabled-------------------------------[SELFPRGEN=1]

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Copy and Paste everything into the PlatformIO IDE.
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

  #include <Arduino.h>
// Other Includes
  #include <RH_ASK.h>   //RadioHead library.

// Constants
//RH_ASK rf_driver;  //Default Use Pin 12 for Tx, Pin 11 for Rx.

  RH_ASK driver(2000, 9, 10, 0); 
//84A, RX on D9 Pin3 (DIn), Tx on D10 Pin2 (DOut).

  const int SW1_On  = 0;	//D0 pin 13 I/P
  const int LedG    = 1;  //D1 pin 12 O/P
//	const int LedR    = 2;  //D2 pin 11 O/P
  const int SW2_Off = 3;  //D3 pin 10 I/P
  const int Buzzer  = 7; 	//D7 pin 06 O/P
  const int Latch   = 8;  //D8 pin 05 O/P

	String Holdstring;
	
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Put Functions here.
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void receiveMessage(String &stringIn)
{//Receive the message.
  uint8_t buf[RH_ASK_MAX_MESSAGE_LEN];
  uint8_t buflen = sizeof(buf);
  if (driver.recv(buf, &buflen)) 
  {
    buf[buflen] = 0;                   // Null-terminate the string
//    stringIn = ((char*)buf);  //Put the message into stringIn for comparison.
		Holdstring = ((char*)buf);  //Put the message into stringIn for comparison.
  }
}  
//+++++++++++++++++++++++++++++++++++++++++++++++
void transmitMessage(const char *msg)
{
  driver.send((uint8_t *)msg, strlen(msg));
  driver.waitPacketSent();
    delay(200);
}
//+++++++++++++++++++++++++++++++++++++++++++++++
void soundBuzzer()
{
  //Sound the Buzzer.
  tone(Buzzer, 900, 500);			//Play 900Hz for 50ms.
  delay(700);
	noTone(Buzzer);							//Stops the Tone.
	delay(100);
}
//+++++++++++++++++++++++++++++++++++++++++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++	

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// put your setup code here, to run once.
void setup()
{
  pinMode(SW1_On, INPUT);   //Make D0 pin 13 an Input.
  pinMode(LedG, OUTPUT);    //Make D1 pin 12 an Output.
//  pinMode(LedR, OUTPUT);    //Make D2 pin 11 an Output.
  pinMode(SW2_Off, INPUT);  //Make D3 pin 10 an Input.
  pinMode(Buzzer, OUTPUT);  //Make D7 pin 06 an Output.
  pinMode(Latch, OUTPUT);   //Make D8 pin 05 an Output.
}
// end of setup.
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// put your main code here, to run repeatedly.
void loop()
{
//+++++Read SW1(On switch)+++++++
//+++++++++++++++++++++++++++++++
//+++++Turn the GPO ON+++++++++++
//+++++++++++++++++++++++++++++++

  int OnSwitch = digitalRead(SW1_On);  //Read the Switch state.
  if (OnSwitch == HIGH)        //The switch is On.
  {
    digitalWrite(Latch, HIGH);		//Turn ON the Latch.
		//+++++Transmit message+++++	
//	  String stringOut = "SW1_is_On";
		String Holdstring = "SW1_is_On";
    //This will send Hex S(53), W(57), 1(31), 
    //_(5F), i(69), s(73), _(5F), O(4F), n(6E).

    //+++++Go to the Transmit Function+++++
    //Use .c_str() to pass it as a const char*
	  transmitMessage(Holdstring.c_str());
		Holdstring = String("");      //Clear Holdstring
  }
		
//+++++Check for Ack message+++++
//		receiveMessage(stringIn);		//Go to Function.
		receiveMessage(Holdstring);		//Go to Function.

//+++++Check the contents of "stringIn" and go to relevant Function.+++++
//+++++If the following is True then Transmit messsage+++++
//	if (stringIn == "Ack_SW1_is_On")//Then the On message was received.
	if (Holdstring == "Ack_SW1_is_On")//Then the On message was received.
	{
    digitalWrite(LedG, HIGH);   //Turn the GRN Led On.
    digitalWrite(Latch, HIGH);  //Turn the Latch On.

    soundBuzzer();

		Holdstring = String("");      //Clear Holdstring

    delay(500);                 //Keep the Led On.
    digitalWrite(LedG, LOW);    //Turn the GRN Led Off.
  }
//+++++End of SW1(On switch)+++++
//+++++++++++++++++++++++++++++++

//+++++Read SW2(Off switch)++++++
//+++++++++++++++++++++++++++++++
//+++++Turn the GPO OFF++++++++++
//+++++++++++++++++++++++++++++++

  int OffSwitch = digitalRead(SW2_Off);  //Read the Switch state.
  if (OffSwitch == HIGH)        //The switch is On.
  {
		//+++++Transmit message+++++	
		String Holdstring = "Now_SW2_is_On";
    //This will send Hex N(4E), o(6F), w(77),_(5F), S(53),
    //W(57), 2(32), _(5F), i(69), s(73), _(5F), O(4F), n(6E).
		//+++++Go to the Transmit Function+++++++++++++++
    //Use .c_str() to pass it as a const char*
	  transmitMessage(Holdstring.c_str());
		Holdstring = String("");      //Clear Holdstring
  }
//+++++Check for Ack message+++++
//		receiveMessage(stringIn);		//Go to Function.
		receiveMessage(Holdstring);		//Go to Function.

//+++++Check the contents of "stringIn" and go to relevant Function.+++++
//+++++If the following is True then Transmit messsage+++++
//	if (stringIn == "Ack_Now_SW2_is_On")//Then the On message was received.
	if (Holdstring == "Ack_Now_SW2_is_On")//Then the On message was received.
	{
    digitalWrite(LedG, HIGH);   //Turn the RED Led On.

    soundBuzzer();

    Holdstring = String("");      //Clear Holdstring
   
    delay(500);                 //Keep the Led On.
    digitalWrite(LedG, LOW);    //Turn the RED Led Off.
    digitalWrite(Latch, LOW);   //Turn the Latch Off.
//This will shut down the Transmitter.    
  }
//+++++End of SW1(On switch)+++++
}
//+++++End of Loop+++++++++++++++++++++++++++++++

//+++++End of Program++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Thanks for any help.
Mike

With what platformio.ini are you compiling this exactly?

I got some problems compiling the RH_Serial file inside the RadioHead library so I just wrote a script to not compile the file (it’s not needed for this sketch anyways). With your source code unchanged and a platformio.ini of

[env:attiny84]
platform = atmelavr@5.3.0
board = attiny84
framework = arduino
lib_deps =
    https://github.com/epsilonrt/RadioHead/archive/refs/tags/v1.143.zip
extra_scripts = pre:filter_radiohead.py

with filter_radiohead.py at the same level as the platformio.ini with content

Import("env")

def skip_rh_serial(node):
    if "RH_Serial.cpp" in node.name:
        return None
    return node

env.AddBuildMiddleware(skip_rh_serial)

I get

Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [========  ]  82.0% (used 420 bytes from 512 bytes)
Flash: [========  ]  81.5% (used 6678 bytes from 8192 bytes)
Building .pio\build\attiny84\firmware.hex
========== [SUCCESS] Took 6.84 seconds ==========

Personally I’d rewrite it to not use the String class at all (heap-allocated data with such small RAM devices is very dangerous), just use regular uint8_t and char buffers.

Additionally, use Arduino F (Flash) strings (https://docs.arduino.cc/language-reference/en/variables/utilities/PROGMEM/) to place constant strings in FLASH instead of in both RAM and Flash if you declare them regularly. You also then need special functions to compare a RAM stored string and a FLASH stored string with the _P suffix, e.g., strcmp_P.

Further, I’ve noticed you’re not waiting at all correctly for a message to be received with some timeout after transmitting a message. I’ve added a timeout now.

The receive buffer was also pulled out of the stack of receiveMessage() function into the global scope so that its RAM usage is more easily visible. Otherwise you can get an unnoticed stack overflow. The previous logic in the receiveMessage was wrong anyways, it tried to assign the string a raw char pointer..

#include <Arduino.h>
#include <RH_ASK.h> //RadioHead library.
#include <avr/pgmspace.h>

RH_ASK driver(2000, 9, 10, 0);
// 84A, RX on D9 Pin3 (DIn), Tx on D10 Pin2 (DOut).

const int SW1_On = 0; // D0 pin 13 I/P
const int LedG = 1;   // D1 pin 12 O/P
//	const int LedR    = 2;  //D2 pin 11 O/P
const int SW2_Off = 3; // D3 pin 10 I/P
const int Buzzer = 7;  // D7 pin 06 O/P
const int Latch = 8;   // D8 pin 05 O/P
const uint16_t AckTimeoutMs = 1000;

/* can be RH_ASK_MAX_MESSAGE_LEN + 1 at maximum but let's only support smaller messages 32 characters for now with NUL terminator */
uint8_t rxBuffer[32+1];
uint8_t rxLength;

bool receiveMessage() {
    rxLength = sizeof(rxBuffer) - 1;
    if (driver.recv(rxBuffer, &rxLength)) {
        rxBuffer[rxLength] = 0; // Null-terminate the message.
        return true;
    }
    // nothing was received, set length to 0 and clear the buffer
    rxLength = 0;
    rxBuffer[0] = 0;
    return false;
}
bool waitForExpectedMessage(PGM_P expectedMessage, uint16_t timeoutMs) {
    const uint32_t startTime = millis();
    while ((uint32_t)(millis() - startTime) < timeoutMs) {
        if (receiveMessage() && strcmp_P((const char *)rxBuffer, expectedMessage) == 0) {
            return true;
        }
        delay(1);
    }
    return false;
}

void transmitMessage(const char *msg) {
    const size_t messageLength = strlen(msg);
    if (messageLength > sizeof(rxBuffer) - 1) {
        return;
    }

    driver.send((uint8_t *)msg, messageLength);
    driver.waitPacketSent();
    delay(200);
}

void transmitMessage(const __FlashStringHelper *msg) {
    const size_t messageLength = strlen_P((const char *)msg);
    if (messageLength > sizeof(rxBuffer) - 1) {
        return;
    }

    memcpy_P(rxBuffer, (const void *)msg, messageLength);
    driver.send(rxBuffer, messageLength);
    driver.waitPacketSent();
    delay(200);
}

void soundBuzzer() {
    // Sound the Buzzer.
    tone(Buzzer, 900, 500); // Play 900Hz for 50ms.
    delay(700);
    noTone(Buzzer); // Stops the Tone.
    delay(100);
}

void setup() {
    pinMode(SW1_On, INPUT); // Make D0 pin 13 an Input.
    pinMode(LedG, OUTPUT);  // Make D1 pin 12 an Output.
    //  pinMode(LedR, OUTPUT);    //Make D2 pin 11 an Output.
    pinMode(SW2_Off, INPUT); // Make D3 pin 10 an Input.
    pinMode(Buzzer, OUTPUT); // Make D7 pin 06 an Output.
    pinMode(Latch, OUTPUT);  // Make D8 pin 05 an Output.
}

void loop() {
    // Read the On switch and turn the GPO on.
    int OnSwitch = digitalRead(SW1_On); // Read the Switch state.
    if (OnSwitch == HIGH) {               // The switch is On.
        digitalWrite(Latch, HIGH); // Turn ON the Latch.
        // This will send Hex S(53), W(57), 1(31),
        //_(5F), i(69), s(73), _(5F), O(4F), n(6E).

        transmitMessage(F("SW1_is_On"));
        if (waitForExpectedMessage(PSTR("Ack_SW1_is_On"), AckTimeoutMs)) {
            digitalWrite(LedG, HIGH);  // Turn the GRN Led On.
            digitalWrite(Latch, HIGH); // Turn the Latch On.

            soundBuzzer();

            delay(500);              // Keep the Led On.
            digitalWrite(LedG, LOW); // Turn the Led Off.
        }
    }
    // Read the Off switch and turn the GPO off.
    int OffSwitch = digitalRead(SW2_Off); // Read the Switch state.
    if (OffSwitch == HIGH) {                // The switch is On.
        // This will send Hex N(4E), o(6F), w(77),_(5F), S(53),
        // W(57), 2(32), _(5F), i(69), s(73), _(5F), O(4F), n(6E).
        transmitMessage(F("Now_SW2_is_On"));
        if (waitForExpectedMessage(PSTR("Ack_Now_SW2_is_On"), AckTimeoutMs)) {
            digitalWrite(LedG, HIGH); // Turn the RED Led On.

            soundBuzzer();

            delay(500);               // Keep the Led On.
            digitalWrite(LedG, LOW);  // Turn the RED Led Off.
            digitalWrite(Latch, LOW); // Turn the Latch Off.
            // This will shut down the Transmitter.
        }
    }
}

this compiles to

RAM:   [========  ]  81.2% (used 416 bytes from 512 bytes)
Flash: [=======   ]  69.4% (used 5686 bytes from 8192 bytes)
========[SUCCESS] Took 4.20 seconds ========

Wow Max that’s amazing thanks. I will have to have a good look at what you have done and try to understand it.
Here is the platform.ini file:

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:uno]
platform = atmelavr
board = uno
framework = arduino
monitor_port = COM5
monitor_speed = 9600
lib_deps = mikem/RadioHead@^1.120

Thank you so much for the help.
Mike

Huh? But you’ve selected the Arduino Uno (ATMega328P) there as your board. That’s probably why it compiled but produced a binary that couldn’t be flashed onto a ATTiny84A.