Minimizing Flash/RAM use for just hwSerial RX on ATtiny

Hmm normally i manage the whole explanation in the topic name, anyway:
I have an ATtiny2313a that i want to use as part of an alarm clock. If i would have had a 4313 or some of the newer series versions the issue i am facing might not have come up, but when i bought the 2313 i had no idea what i wanted to use it for.
So just to make it clear; I want to use the UART to receive data from an ESP-01 with updates on the correct time. It is going to be a one way transmission.
Just to explain why not to use an ESP-01 (or a nodeMCU) for that matter to do the whole thing. The clock should have a backup battery that powers the clock and makes the alarm go off regardless of mains power, and should be fitted with a rechargeable battery (preferably LIPO)
ATtiny chips or low on power use and run on a wide voltage range (If i want to use a LIPO an ATtiny is the obvious choice)
Anyway, the disadvantage of an ATtiny (and this one in particular) is the small Flash and RAM size, and though the requirements are small, it is going to be a challenge.
Now the issue gets aggravated by the use of hwSerial

#define PIN 4
#define DEL 500

void setup() {
  //Serial.begin(9600);
  pinMode(PIN, OUTPUT);
}

void loop() {
  digitalWrite(PIN, HIGH);
  delay(DEL);
  digitalWrite(PIN, LOW);
  delay(DEL);
}

If i compile the using Spence Konde 's Library i get

Sketch uses 450 bytes (21%) of program storage space. Maximum is 2048 bytes.
Global variables use 9 bytes (7%) of dynamic memory, leaving 119 bytes for local variables. Maximum is 128 bytes.

but if i un-comment the Serial.begin()

Sketch uses 1256 bytes (61%) of program storage space. Maximum is 2048 bytes.
Global variables use 92 bytes (71%) of dynamic memory, leaving 36 bytes for local variables. Maximum is 128 bytes.

Now i am only planning to do RX, and normally speaking i just want to set the BAUD-rate once, and do a check on available() and read().
I really don't need all of the rest, i don't need the write, the TX buffer, peek etc. and looking through the core files related to it, i realized that since i am planning to receive messages only 4 bytes long, i might not need the RX buffer to be any longer than that. And rather than discarding the byte in case of a full buffer i would want to just overwrite the oldest byte. Anyway, rather than modifying the library i figure it might be better to just make the reading of the UART a part of the main sketch. But now i am not really sure what i do and what i don't need, and i am talking about the parts that i find in the HardwareSerial.h .cpp & private.h
For instance :
Do i need all these ?

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <util/atomic.h>
#include "Arduino.h"

I figure i'll need these :

int HardwareSerial::available(void)
{
  return ((unsigned int)(SERIAL_RX_BUFFER_SIZE + _rx_buffer_head - _rx_buffer_tail)) % SERIAL_RX_BUFFER_SIZE;
}

int HardwareSerial::read(void)
{
  // if the head isn't ahead of the tail, we don't have any characters
  if (_rx_buffer_head == _rx_buffer_tail) {
    return -1;
  } else {
    unsigned char c = _rx_buffer[_rx_buffer_tail];
    _rx_buffer_tail = (rx_buffer_index_t)(_rx_buffer_tail + 1) % SERIAL_RX_BUFFER_SIZE;
    return c;
  }
}

and i need to set up the UART at a fixed BAUD-rate manually and i've never done that before.
Maybe i am overcomplicating things by using the UART, it's 4 bytes once a minute or so (once an hour would also be ok) But it reliability is a focus point in this project.

Hmm always a little disappointing when nobody responds to your post. Anyway i made good progress this morning. Backing up part of the core and modifying it, i managed to compile :

#define PIN 4
#define DEL 500

void setup() {
  Serial.begin(9600);
  pinMode(PIN, OUTPUT);
}

void loop() {
  if (Serial.available()) {
    char c = Serial.read();
    if (c == 'a') digitalWrite(PIN, LOW);
    if (c == 'b') digitalWrite(PIN, HIGH);
  }
}

and use only :

Sketch uses 746 bytes (36%) of program storage space. Maximum is 2048 bytes.
Global variables use 45 bytes (35%) of dynamic memory, leaving 83 bytes for local variables. Maximum is 128 bytes.

That will suffice as a solution for now, though i would rather drop the whole class-system for it, since i think i can probably reduce it even more. Let me mention @DrAzzy , you are after all the author of the core, and you may have missed the post.

This is what I used on some of my Attiny2313 projects. Modify as needed. This is TX only but you can set the received enable bit to enable receiving via UART

	/***********************************************
	Internal Oscillator Configuration
	Runs at 8MHz
	***********************************************/
	// change system clock pre-scaler to 1, run at 8 MHz from the internal oscillator
	CLKPR = 1<<CLKPCE;
	CLKPR = 0<<CLKPCE | 0<<CLKPS3 | 0<<CLKPS2 | 0<<CLKPS1 | 0<<CLKPS0;


	/***********************************************
	UART driver initialization
	PORTD0 [ RXD ]
	PORTD1 [ TXD ]
	***********************************************/
	// UART TX and RX pins
	DDRD |=  (1<<PORTD1);
	DDRD &= ~(1<<PORTD0);

	//UART initialization, 9600-8-n-1, 8MHz system clock
	UCSRA = 1<<U2X | 0<<MPCM;
	UCSRB = 0<<RXCIE | 0<<TXCIE | 0<<UDRIE | 0<<RXEN | 1<<TXEN | 0<<UCSZ2;
	UCSRC = 0<<UMSEL0 | 0<<UPM1 | 0<<UPM0 | 0<<USBS | 1<<UCSZ1 | 1<<UCSZ0 | 0<<UCPOL;
	UBRRH = 0;
	UBRRL = 103;


	// clear terminal screen (ESC 'c' sequence)
	static char tmpCharArray[3] = {27, 99, 0};
	uart_write_string(tmpCharArray);


void uart_write(char data)
{
	UDR = data;
	while((UCSRA & 1<<TXC)==0);
	UCSRA |= 1<<TXC;
	
	return;
}

void uart_write_string(char * data)
{
	for(uint8_t count = 0; data[count] != 0; count++)
	{
		UDR = data[count];
		while((UCSRA & 1<<TXC) == 0);
		UCSRA |= 1<<TXC;
	}
	return;
}

I don't have map generation set up on for ATTinyCore currently, so we can[t find out the easy way what parts of it take up the most flash, but I can say - print and write are shockingly bloated. Everyone always acts luke receiving is hard - and it's more technically demanding, but in terms of flash usage, you can build up bloat very rapidly with that ring buffer management the ISR to service DRE, and the print routines itself (the number printing stuff puills in division for example). I just took a quick look at the listing for a 2313 with just serial.begin() (it can;t not pull in methods that don't get used because they're virtual and always get included and... thereis also something unholy going with begin..... Looks like it's in my ATTinyCore too :frowning: that needs to be fixed, though the dev, version doesn't compile anything right now and I've got some really hot issues that I need to attend to with the other cores right now, but that's awful. They're using sbi/cbi macros on *pointers, . and calling them for each bit they're setting or clearing as if it was real sbi/cbi, when it's like 24 bytes each of mess generated for each one, and it;'s for initial setup so they can just assign absolute values instead. Ugh. Well, I didn't know that the code these people had written was crap when I started maintaining the cores!

Yeah i mean on a machine that small, i was also a bit shocked, but i commented most of it out, but due to the inheritance from stream which inherits from print, it became a bit of a stage thing. I've learned programming the way i learn most things, as an auto-didact, but that sometimes leaves gaps in my training.

Saw that too, i know that doing a calculation for setting the registers for the BAUD-rate is going to be a waste of RAM and flash but it is only a few bytes. I don't even want to be bothered about having a function for reading from the buffer which returns a byte, i want my parsing incorporated, i'll handle the buffer. Anyway, i think that for this project i've made sufficient progress, but there is a lot that can still be optimized.

Keep up the good work.

Oh thanks, I have to have a look in the morning, look back at the begin method, check the datasheet.
It looks like very clean and efficient code.

For Completions sake a basic code to do hwSerial RX & TX.
Whatever byte is received is echoed back, and the state of pin 4 is toggled.

#define BAUD 9600UL
#define UB ((F_CPU / (8 * BAUD)) - 1)

void setup() {
  // UART TX and RX pins
  DDRD |=  (1<<PORTD1) | (1<<PORTD2) ;  // set TX pin and pin 4 to output
  DDRD &= ~(1<<PORTD0);  // set RX pin to input 

  //UART initialization, 9600-8-n-1, 8MHz system clock
  UCSRA = 1<<U2X | 0<<MPCM;
  UCSRB = 1<<RXCIE | 0<<TXCIE | 0<<UDRIE | 1<<RXEN | 1<<TXEN | 0<<UCSZ2;  // enable TX & RX
  
  UCSRC = 0<<UMSEL0 | 0<<UPM1 | 0<<UPM0 | 0<<USBS | 1<<UCSZ1 | 1<<UCSZ0 | 0<<UCPOL;  // 8N1
  UBRRH = (UB >> 8);  // set baud rate
  UBRRL = (UB & 0xFF); // HL register
}

void loop() {
}

void uart_write(char data) {
  UDR = data;
  while((UCSRA & 1<<TXC)==0);
  UCSRA |= 1<<TXC;  
}

void uart_write_string(char * data) {
  for(uint8_t count = 0; data[count] != 0; count++) {
    UDR = data[count];
    while((UCSRA & 1<<TXC) == 0);
    UCSRA |= 1<<TXC;
  }
}

ISR(USART_RX_vect) {
    unsigned char c  =  UDR;
    uart_write(c);
    PORTD ^= (1<<PORTD2);
}

now that uses

Sketch uses 312 bytes (15%) of program storage space. Maximum is 2048 bytes.
Global variables use 9 bytes (7%) of dynamic memory, leaving 119 bytes for local variables. Maximum is 128 bytes

Thanks again !

If single characters have a defined meaning, and there are no multi-character commands, it can be done with no library super efficiently. Just remember that you can't do anything slow in the ISR.
I think you'd want

UCSRA=0x02;
UCSRB=0x90;
UCSRC=0x06;
UBRR=207; //9600 baud at 16 MHz. 
ISR(USART_RX_vect) {
unsigned char c = UDR;
if (c =='a') digitalWrite(PIN_LOW);
//and so on
}

Yeah as you saw i sort of got to that. Minor differences, id set the 'UCSRB' to 0x98 to also enable the TX (it's not that i am short of pins and during development it may come in handy)
And i did the BAUD calculation in a #define within braces, so that the pre-compiler would do the calculation.
Also with supply voltages possibly running below 4v i decided on an 8Mhz Crystal.
eh

UBRR=207;

does not compile, you have to set the H & L registers separately.

Yeah i know, some basic operations and compares are possible.
I've use Max Piersons DMX reception and found the limits of what can be done within the reception ISR (at 250kbps that is) If the ISR takes to long to complete, an incoming byte may get lost.
For the Alarm Clock i now have :

ISR(USART_RX_vect) {
  unsigned char c  =  UDR;
  if (in == 1) {
    if ((c >= 32) && (c < 56)) input.hr = c - 32;
    else in = -1;
  }
  else if (in == 2) {
    if ((c >= 32) && (c < 92)) input.mn = c - 32;
    else in = -1;
  }
  else if (in == 3) {
    if (c == 't') {
      UCSRB &=~ (1 << RXCIE); // disable interrupt, complete message
      return;  // in == 3 is complete message flag
    }
    else  in = -1;
  }
  else if (c == 'n') {
  }
  else if (c == 'a') {
    setalarm = true;
  }
  else in--;
  in++;
}

Where effectively i am receiving 3 bytes + terminator. I think the error checking should suffice.
Once the data is processed in the main loop i re-enable the RXCIE

  if (in == 3) {
    if (setalarm) {
      alarm.hr = input.hr;
      alarm.mn = input.mn;
      setalarm = false;
    }
    else {
      current.hr = input.hr;
      current.mn = input.mn;
    }
    in = 0;
    UCSRB |= (1 << RXCIE); // re-enable RX-interrupt
  }

Speaks for itself that 'in' & 'input' are declared 'volatile'
I haven't fully tested it yet, but i am not expecting any issues. (just found a typo though, so let's say 'real issues' ) I should probably mark this as solved, but now i run into the limitations of the forum, it was not just 1 response that 'is' the solution. Thanks again.

Ah yeah, apparently on the 2313, the high and low baud registers aren't even next to each other! This is the sort of crap I don't miss one bit about the classic AVRs. like really guys, it's practically the first tiny you're releasing - blank slate! You can arrange the registers all perfectly. Nope. blindfold and dart board....

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.