I'm trying to build a small hygrometer based on the DHT11 and I'm having a bit of an "issue" with the code size. I want to run it on an Attiny45 and it's a wee bit too big (352 bytes too big to be exact). I am aware that I could just use an Attiny85 and have space to spare or don't use a bootloader and barely fit it in (94%) but I kind of want to make my life harder than it needs to be and figure out how to reduce size since it'll probably come in handy in the future. Treat it as a learning experience if you will.
What it's supposed to do:
Side note: 7-segments are adressed via two 74HC595s of which I am using 7 outputs each for the displays and 1 each for a transistor that connects the display in question to GND. There's a schematic at the bottom if you're interested.
As pointed out, my main issue is code size so if anyone has any tips on how to reduce that (or any other tips how to improve the code) please let me know.
I hope I'm asking the question properly, if not please let me know.
Compiler output:
Sketch uses 3872 bytes (110%) of program storage space. Maximum is 3520 bytes.text section exceeds available space in board
Global variables use 107 bytes (41%) of dynamic memory, leaving 149 bytes for local variables. Maximum is 256 bytes.
Sketch too big; see https://support.arduino.cc/hc/en-us/articles/360013825179 for tips on reducing it.
Error compiling for board ATtiny45/85 (Optiboot).
Code:
/*
Humidity/Temperature sensor setup with DHT-11
Two digits for humidity
Two digits for temperature
Button to wake up from sleep
NPNs activated via 8th bit in 74HC595s, always alternating
Author: ElectroBadger
Date: 2021-11-02
Version: 1.0
*/
/*
Reduce power consumption:
- Run at 1 MHz internal clock
- Turn off ADC
- Use SLEEP_MODE_PWR_DOWN
*/
#include "DHT.h" //DHT-11 sensor
#include <avr/sleep.h> // Sleep Modes
#include <avr/power.h> // Power management
#include <avr/wdt.h> //Doggy stuff
//define attiny pins
#define INT_PIN PB4
#define DATA PB1
#define SENSOR PB3
#define LATCH PB2
#define CLK PB0
//define other stuff
#define SENSOR_TYPE DHT11
#define LED_DELAY 50
//changing variables
short ones_data; //16-bits for display of ones
short tens_data; //16-bits for display of tens
byte sevSeg, measurements; //7-segment bit pattern / wait time between LEDs [ms] / # of measurements taken
bool firstPair, btnPress; //tracks which pair of 7-segments is on; tracks button presses
uint32_t oldMillis, sleepTimer; //tracks the last acquisition time and wakeup time
//Initialize sensor
DHT dht(SENSOR, SENSOR_TYPE);
//Shifts 16 bits out MSB first, on the rising edge of the clock.
void shiftOut(int dataPin, int clockPin, short toBeSent){
int i=0;
int pinState = 0;
//Clear everything out just in case
digitalWrite(dataPin, 0);
digitalWrite(clockPin, 0);
//Loop through bits in the data bytes, COUNTING DOWN in the for loop so that
//0b00000000 00000001 or "1" will go through such that it will be pin Q0 that lights.
for(i=0; i<=15; i++){
digitalWrite(clockPin, 0);
//if the value passed to myDataOut AND a bitmask result
//is true then set pinState to 1
if(toBeSent & (1<<i)){
pinState = 1;
}
else{
pinState = 0;
}
digitalWrite(dataPin, pinState); //Sets the pin to HIGH or LOW depending on pinState
digitalWrite(clockPin, 1); //Shifts bits on upstroke of clock pin
digitalWrite(dataPin, 0); //Zero the data pin after shift to prevent bleed through
}
digitalWrite(clockPin, 0); //Stop shifting
}
//Converts an int <10 to a bit pattern for 7-segment displays
short toSegments(int value){
byte pattern = 0b00000000; //create empty pattern
//Using a switch...case (3878 bytes) if...else if...else uses 3946 bytes
switch(value){
case 0:
pattern = 0b01111110;
break;
case 1:
pattern = 0b00110000;
break;
case 2:
pattern = 0b01101101;
break;
case 3:
pattern = 0b01111001;
break;
case 4:
pattern = 0b00110011;
break;
case 5:
pattern = 0b01011011;
break;
case 6:
pattern = 0b01011111;
break;
case 7:
pattern = 0b01110000;
break;
case 8:
pattern = 0b01111111;
break;
case 9:
pattern = 0b01111011;
break;
default:
pattern = 0b00000000;
break;
}
return pattern;
}
void goToSleep(){
//Turn off 7-segments and NPNs
digitalWrite(LATCH, 0);
shiftOut(DATA, CLK, 0b0000000000000000);
digitalWrite(LATCH, 1);
//Set deep sleep mode
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
ADCSRA = 0; // turn off ADC
power_all_disable (); // power off ADC, Timer 0 and 1, serial interface
cli(); // timed sequence coming up, so disable interrupts
btnPress = false;
measurements = 0;
resetWatchdog (); // get watchdog ready
sleep_enable (); // ready to sleep
sei(); // interrupts are required now
sleep_cpu (); // sleep
sleep_disable (); // precaution
power_all_enable (); // power everything back on
}
ISR(PCINT_VECTOR){
btnPress = true;
sleepTimer = millis();
}
// watchdog interrupt
ISR(WDT_vect){
wdt_disable(); //disable watchdog
}
void resetWatchdog(){
MCUSR = 0; //clear various "reset" flags
WDTCR = bit (WDCE) | bit (WDE) | bit (WDIF); //allow changes, disable reset, clear existing interrupt
//set interrupt mode and an interval (WDE must be changed from 1 to 0 here)
WDTCR = bit (WDIE) | bit (WDP3) | bit (WDP0); //set WDIE, and 8 seconds delay
wdt_reset(); //pat the dog
}
void setup(){
resetWatchdog(); // do this first in case WDT fires
cli(); //Disable interrupts during setup
pinMode(INT_PIN, INPUT_PULLUP); //Set interrupt pin as input w/ internal pullup
pinMode(DATA, OUTPUT); //Set serial data as output
pinMode(CLK, OUTPUT); //Set shift register clock as output
pinMode(LATCH, OUTPUT); //Set output register (latch) clock as output
// Interrupts
PCMSK = bit(INT_PIN); //Enable interrupt handler (ISR)
GIFR |= bit(PCIF); // clear any outstanding interrupts
GIMSK |= bit(PCIE); //Enable PCINT interrupt in the general interrupt mask
//default conditions
/* bit 0-6: ones digits
bit 7: NPN for units digits
bit 8-14: ones digits
bit 15: NPN for tens digits
*/
ones_data = 0b0000000000000000;
tens_data = 0b0000000000000000;
measurements = 0;
firstPair = true;
btnPress = false;
oldMillis = 0;
sleepTimer = 0;
//Start sensor
dht.begin();
delay(1000); //wait 1s for sensor to stabilize
sei(); //Enable interrupts after setup
}
void loop(){
if((millis()-oldMillis) > 1000){
//Slow sensor, so readings may be up to 2 seconds old
byte hum = dht.readHumidity(); //Read humidity
byte temp = dht.readTemperature(); //Read temperatuer in °C
//update tens bit string
tens_data = 0b0000000000000000; //reset to all 0s
sevSeg = toSegments(hum/10); //convert tens of humidity to 7-segment logic
tens_data |= sevSeg; // bitwise OR the result with the output short
tens_data = tens_data << 8; //shift by 8 so it's almost in the right place (see below)
sevSeg = toSegments(temp/10); //convert tens of temperature to 7-segment logic
tens_data |= sevSeg; // bitwise OR the result with the output short
tens_data = tens_data << 1; //shift by 1 so everything is in the right place
tens_data |= 0b0000000100000000; //set NPN for tens pair to active and ones NPN to inactive
//update ones bit string
ones_data = 0b0000000000000000; //reset to all 0s
sevSeg = toSegments(hum%10); //convert ones of humidity to 7-segment logic
ones_data |= sevSeg; // bitwise OR the result with the output short
ones_data = ones_data << 8; //shift by 8 so it's almost in the right place (see below)
sevSeg = toSegments(temp%10); //convert ones of temperature to 7-segment logic
ones_data |= sevSeg; // bitwise OR the result with the output short
ones_data = ones_data << 1; //shift by 1 so everything is in the right place
ones_data |= 0b0000000000000001; //set NPN for ones pair to active and tens NPN to inactive
oldMillis = millis(); //I don't much care about the few ms lost
} //during data acquisition
if(btnPress){
//shift out the next batch of data to the display
digitalWrite(LATCH, 0); //Set latch pin LOW so nothing gets shifted out
if(firstPair){
shiftOut(DATA, CLK, tens_data); //Shift out LED states for 7-segments of tens
firstPair = false;
}
else{
shiftOut(DATA, CLK, ones_data); //Shift out LED states for 7-segments of ones
firstPair = true;
}
digitalWrite(LATCH, 1); //sent everything out in parallel
delay(LED_DELAY); //wait for some time until switching to the other displays
if((millis()-sleepTimer) > 6000){ //Sleep after 6s display time
goToSleep();
}
}
else{
if(measurements > 5){
goToSleep();
}
}
}
Okay so thanks to the input of Mat I tried substituting the DHT11 library with something more sleek, which took me a while to get up and running. I ended up using this as a base, edited around a bit and commented heavily for my benefit. I added my updated code below for anyone interested (thanks for pointing out the correct highlighting issue), there's also a github with the rest of the design files.
Seems the library is really heavy, as the compiler output shows:
Compiler output:
Sketch uses 2354 bytes (66%) of program storage space. Maximum is 3520 bytes.
Global variables use 104 bytes (40%) of dynamic memory, leaving 152 bytes for local variables. Maximum is 256 bytes.
Code:
/*
Humidity/Temperature sensor setup with DHT-11
Two digits for humidity
Two digits for temperature
Button to wake up from sleep
NPNs activated via 8th bit in 74HC595s, always alternating
Author: ElectroBadger
Date: 2021-11-09
Version: 2.0
*/
/*
Reduce power consumption:
- Run at 1 MHz internal clock
- Turn off ADC
- Use SLEEP_MODE_PWR_DOWN
*/
//#include "DHT.h" // DHT-11 sensor
#include <avr/sleep.h> // Sleep Modes
#include <avr/power.h> // Power management
#include <avr/wdt.h> // Doggy stuff
// define attiny pins
#define INT_PIN PB4
#define DATA PB1
#define SENSOR PB3
#define LATCH PB2
#define CLK PB0
// define other stuff
//#define SENSOR_TYPE DHT11
#define LED_DELAY 50
//fixed variables
//array lookup for number display; ascending order: 0, 1, 2, ...
const byte numLookup[] = {
0b01111110, //0
0b00110000, //1
0b01101101, //2
0b01111001, //3
0b00110011, //4
0b01011011, //5
0b01011111, //6
0b01110000, //7
0b01111111, //8
0b01111011 //9
};
// changing variables
short ones_data; // 16-bits for display of ones
short tens_data; // 16-bits for display of tens
byte sevSeg, measurements; // 7-segment bit pattern / wait time between LEDs [ms] / # of measurements taken
bool firstPair, btnPress; // tracks which pair of 7-segments is on; tracks button presses
uint32_t oldMillis, sleepTimer; // tracks the last acquisition time and wakeup time
byte humI, humD, tempI, tempD; // values of humidity and temperature (we're only gonna need integral parts but I need all for the checksum)
// Initialize sensor
//DHT dht(SENSOR, SENSOR_TYPE);
// Shifts 16 bits out MSB first, on the rising edge of the clock.
void shiftOut(int dataPin, int clockPin, short toBeSent) {
int i = 0;
int pinState = 0;
// Clear everything out just in case
digitalWrite(dataPin, 0);
digitalWrite(clockPin, 0);
// Loop through bits in the data bytes
for (i = 0; i <= 15; i++) {
digitalWrite(clockPin, 0);
// if the value AND a bitmask result is true then set pinState to 1
if (toBeSent & (1 << i)) {
pinState = 1;
}
else {
pinState = 0;
}
digitalWrite(dataPin, pinState); // sets the pin to HIGH or LOW depending on pinState
digitalWrite(clockPin, 1); // shifts bits on upstroke of clock pin
digitalWrite(dataPin, 0); // zero the data pin after shift to prevent bleed through
}
digitalWrite(clockPin, 0); // Stop shifting
}
void start_signal(byte SENSOR_PIN) {
pinMode(SENSOR_PIN, OUTPUT); // set pin as output
digitalWrite(SENSOR_PIN, LOW); // set pin LOW
delay(18); // wait 18 ms
digitalWrite(SENSOR_PIN, HIGH); // set pin HIGH
pinMode(SENSOR_PIN, INPUT_PULLUP); // set pin as input and pull to VCC (10k)
}
boolean read_dht11(byte SENSOR_PIN) {
uint16_t rawHumidity = 0;
uint16_t rawTemperature = 0;
uint8_t checkSum = 0;
uint16_t data = 0;
unsigned long startTime;
for (int8_t i = -3; i < 80; i++) { // loop 80 iterations, representing 40 bits * 2 (HIGH + LOW)
byte high_time; // stores the HIGH time of the signal
startTime = micros(); // stores the time the data transfer started
// sensor should pull line LOW and keep for 80µs (while SENSOR_PIN == HIGH)
// then pull HIGH and keep for 80µs (while SENSOR_PIN == LOW)
// then pull LOW again, aka send data (while SENSOR_PIN == HIGH)
do { // waits for sensor to respond
high_time = (unsigned long)(micros() - startTime); // update HIGH time
if (high_time > 90) { // times out after 90 microseconds
Serial.println("ERROR_TIMEOUT");
return;
}
}
while (digitalRead(SENSOR_PIN) == (i & 1) ? HIGH : LOW);
// actual data starts at iteration 0
if (i >= 0 && (i & 1)) { // if counter is odd, do this (only counts t_on time and ignores t_off)
data <<= 1; // left shift data stream by 1 since we are at a the next bit
// TON of bit 0 is maximum 30µs and of bit 1 is at least 68µs
if (high_time > 30) {
data |= 1; // we got a one
}
}
switch ( i ) {
case 31: // bit 0-16 is humidity
rawHumidity = data;
break;
case 63: // bit 17-32 is temperature
rawTemperature = data;
case 79: // bit 33-40 is checksum
checkSum = data;
data = 0;
break;
}
}
// Humidity
humI = rawHumidity >> 8;
rawHumidity = rawHumidity << 8;
humD = rawHumidity >> 8;
// Temperature
tempI = rawTemperature >> 8;
rawTemperature = rawTemperature << 8;
tempD = rawTemperature >> 8;
if ((byte)checkSum == (byte)(tempI + tempD + humI + humD)) {
return true;
}
else {
return false;
}
}
void goToSleep() {
// Turn off 7-segments and NPNs
digitalWrite(LATCH, 0);
shiftOut(DATA, CLK, 0b0000000000000000);
digitalWrite(LATCH, 1);
set_sleep_mode (SLEEP_MODE_PWR_DOWN); // Set deep sleep mode
ADCSRA = 0; // turn off ADC
power_all_disable (); // power off ADC, Timer 0 and 1, serial interface
cli(); // timed sequence coming up, so disable interrupts
btnPress = false; // reset button flag
measurements = 0; // reset measurement counter
resetWatchdog (); // get watchdog ready
sleep_enable (); // ready to sleep
sei(); // interrupts are required now
sleep_cpu (); // sleep
sleep_disable (); // precaution
power_all_enable (); // power everything back on
}
ISR(PCINT0_vect) {
btnPress = true;
sleepTimer = millis();
}
// watchdog interrupt
ISR(WDT_vect) {
wdt_disable(); // disable watchdog
}
void resetWatchdog() {
MCUSR = 0; //clear various "reset" flags
WDTCR = bit (WDCE) | bit (WDE) | bit (WDIF); //allow changes, disable reset, clear existing interrupt
WDTCR = bit (WDIE) | bit (WDP3) | bit (WDP0); //set WDIE, and 8 seconds delay
wdt_reset();
}
void setup() {
resetWatchdog(); // do this first in case WDT fires
cli(); // disable interrupts during setup
pinMode(INT_PIN, INPUT_PULLUP); // set interrupt pin as input w/ internal pullup
pinMode(DATA, OUTPUT); //set serial data as output
pinMode(CLK, OUTPUT); //set shift register clock as output
pinMode(LATCH, OUTPUT); //set output register (latch) clock as output
pinMode(SENSOR, INPUT); //set DHT11 pin as input
// Interrupts
PCMSK = bit(INT_PIN); // enable interrupt handler (ISR)
GIFR |= bit(PCIF); // clear any outstanding interrupts
GIMSK |= bit(PCIE); // enable PCINT interrupt in the general interrupt mask
//default conditions
/* bit 0-6: ones digits
bit 7: NPN for units digits
bit 8-14: ones digits
bit 15: NPN for tens digits
*/
ones_data = 0b0000000000000000;
tens_data = 0b0000000000000000;
measurements = 0;
firstPair = true;
btnPress = false;
oldMillis = 0;
sleepTimer = 0;
humI = 0;
humD = 0;
tempI = 0;
tempD = 0;
// Start sensor
//dht.begin();
sei(); // enable interrupts after setup
}
void loop() {
if ((millis() - oldMillis) > 1000) {
// slow sensor, so readings may be up to 2 seconds old
//byte hum = dht.readHumidity(); //Read humidity
//byte temp = dht.readTemperature(); //Read temperatuer in °C
delay(2000); // wait for DHT11 to start up
start_signal(SENSOR); // send start sequence
if(read_dht11(SENSOR)){
// update tens bit string
tens_data = 0b0000000000000000; // reset to all 0s
tens_data |= numLookup[humI / 10]; // bitwise OR the result with the output short
tens_data = tens_data << 8; // shift by 8 so it's almost in the right place (see below)
tens_data |= numLookup[tempI / 10]; // bitwise OR the result with the output short
tens_data = tens_data << 1; // shift by 1 so everything is in the right place
tens_data |= 0b0000000100000000; // set NPN for tens pair to active and ones NPN to inactive
// update ones bit string
ones_data = 0b0000000000000000; // reset to all 0s
ones_data |= numLookup[humI % 10]; // bitwise OR the result with the output short
ones_data = ones_data << 8; // shift by 8 so it's almost in the right place (see below)
ones_data |= numLookup[tempI % 10]; // bitwise OR the result with the output short
ones_data = ones_data << 1; // shift by 1 so everything is in the right place
ones_data |= 0b0000000000000001; // set NPN for ones pair to active and tens NPN to inactive
}
else{
tens_data = 0b1001111110011100;
ones_data = 0b0000101000001011;
}
oldMillis = millis(); // I don't much care about the few ms lost during data acquisition
}
if (btnPress) {
// shift out the next batch of data to the display
digitalWrite(LATCH, 0); // Set latch pin LOW so nothing gets shifted out
if (firstPair) {
shiftOut(DATA, CLK, tens_data); // shift out LED states for 7-segments of tens
firstPair = false; // reset first digit flag
}
else {
shiftOut(DATA, CLK, ones_data); //Shift out LED states for 7-segments of ones
firstPair = true; //set first digit flag
}
digitalWrite(LATCH, 1); //sent everything out in parallel
delay(LED_DELAY); //wait for some time until switching to the other displays
if ((millis() - sleepTimer) > 6000) { //Sleep after 6s display time
goToSleep();
}
}
else {
if (measurements > 3) {
goToSleep();
}
}
}