/*
* thermidity.c
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2.
*
* AVR MCU based thermometer and hygrometer with e-ink display.
*
* Created on: 01.05.2023
* Author: torsten.roemer@luniks.net
*
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include <util/atomic.h>
#include "pins.h"
#include "meter.h"
#include "spi.h"
#include "sram.h"
#include "eink.h"
#include "display.h"
#include "utils.h"
#include "usart.h"
/* Measure and average temperature and relative humidity every ~32 seconds */
#define MEASURE_INTS 4 // should be a power of 2 to avoid division
/* Display should not be updated more frequently than once every 180 seconds */
#define DISP_UPD_INTS 36
/* Number of fast updates until a full update is done to avoid ghosting */
#define DISP_MAX_FAST 9
/* 1 int = 8 seconds */
static volatile uint8_t ints = DISP_UPD_INTS;
static uint8_t updates = DISP_MAX_FAST + 1;
ISR(WDT_vect) {
ints++;
}
EMPTY_INTERRUPT(ADC_vect);
/**
* Sets up the pins.
*/
static void initPins(void) {
// set sensor power pin as output pin
DDR_SENS |= (1 << PIN_PWR);
// drive sensor power pin low
PORT_SENS &= ~(1 << PIN_PWR);
// set MOSI and SCK as output pin
DDR_SPI |= (1 << PIN_MOSI);
DDR_SPI |= (1 << PIN_SCK);
// pull SS (ensure master) and MISO high
PORT_SPI |= (1 << PIN_SS);
PORT_SPI |= (1 << PIN_MISO);
// set SRAM CS pin as output pin
DDR_SSPI |= (1 << PIN_SRCS);
// set display CS, D/C and RST pin as output pin
DDR_DSPI |= (1 << PIN_ECS);
DDR_DSPI |= (1 << PIN_DC);
DDR_DISP |= (1 << PIN_RST);
// drive SPI and display output pins high
PORT_SSPI |= (1 << PIN_SRCS);
PORT_DSPI |= (1 << PIN_ECS);
PORT_DSPI |= (1 << PIN_DC);
PORT_DISP |= (1 << PIN_RST);
// set display BUSY pin as input pin (default)
DDR_DISP &= ~(1 << PIN_BUSY);
// pull all unused pins high/set to defined level to reduce current
// consumption when not in sleep mode
PORTB |= (1 << PB6);
PORTB |= (1 << PB7);
PORTC |= (1 << PC4);
PORTC |= (1 << PC5);
PORTD |= (1 << PD0);
PORTD |= (1 << PD1);
PORTD |= (1 << PD2);
PORTD |= (1 << PD3);
PORTD |= (1 << PD4);
}
/**
* Enables SPI master mode.
*/
static void initSPI(void) {
SPCR |= (1 << SPR0);
SPCR |= (1 << MSTR);
}
/**
* Sets up the watchdog.
*/
static void initWatchdog(void) {
cli();
wdt_reset();
// watchdog change enable
WDTCSR |= (1 << WDCE) | (1 << WDE);
// enable interrupt, disable system reset, bark every 8 seconds
WDTCSR = (1 << WDIE) | (0 << WDE) | (1 << WDP3) | (1 << WDP0);
}
/**
* Sets up the ADC.
*/
static void initADC(void) {
// disable digital input on the ADC inputs to reduce digital noise
DIDR0 = 0b00111111;
// ADC clock prescaler/64 ~ 125kHz @ 8MHz
ADCSRA |= (1 << ADPS2) | (1 << ADPS1);
// enable ADC interrupt
ADCSRA |= (1 << ADIE);
}
/**
* Powers on the sensors.
*/
static void powerOnSensors(void) {
PORT_SENS |= (1 << PIN_PWR);
}
/**
* Powers off the sensors.
*/
static void powerOffSensors(void) {
PORT_SENS &= ~(1 << PIN_PWR);
}
/**
* Enables the ADC.
*/
static void enableADC(void) {
ADCSRA |= (1 << ADEN);
}
/**
* Disables the ADC.
*/
static void disableADC(void) {
ADCSRA &= ~(1 << ADEN);
}
/**
* Enables SPI.
*/
static void enableSPI(void) {
SPCR |= (1 << SPE);
}
/**
* Disables SPI.
*/
static void disableSPI(void) {
SPCR &= ~(1 << SPE);
PORT_SPI &= ~(1 << PIN_SCK);
}
/**
* Stops the clock of unused modules to reduce power consumption.
*/
static void reducePower(void) {
// stop TWI, Timer0, Timer1, Timer2 and USART
PRR |= (1 << PRTWI) | (1 << PRTIM0) | (1 << PRTIM1) | (1 << PRTIM2) | (1 << PRUSART0);
}
/**
* Disables global interrupts and the watchdog to stop measuring
* and updating the display when batteries are too weak, to limit
* discharging below cutoff voltage.
*/
static void powerDown(void) {
cli();
wdt_disable();
}
int main(void) {
reducePower();
initPins();
initSPI();
initWatchdog();
initADC();
// initUSART();
// enable global interrupts
sei();
// delay initial display update after power on
_delay_ms(1000);
while (true) {
if (ints % MEASURE_INTS == 0) {
powerOnSensors();
// give the humidity sensor time to settle
_delay_ms(100);
enableADC();
measureValues();
disableADC();
powerOffSensors();
if (ints >= DISP_UPD_INTS) {
ints = 0;
// measured battery voltage is /5 by voltage divider
if (getMVBat() < BAT_LOW / 5) {
powerDown();
} else {
enableSPI();
if (updates > DISP_MAX_FAST) {
// make a full update after a certain number of fast
// updates to avoid ghosting
displayValues(false);
updates = 0;
} else {
if (displayValues(true)) {
updates++;
}
}
disableSPI();
}
}
}
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_mode();
}
return 0;
}