**WARNING: This code is just for W's not HW's. My code might screw up your HW if you don't make adjustments.
Uff, it took vastly longer than I thought it would, but I've finally stripped out all references to the Arduino libraries as well as taken just a teeny subset of Felix's library and rewritten it to make use of auto mode as well as sped that up as much as I could. The only outside reference is to LowPower for sleep duties.
Regarding code size, I'm down to 1,418 bytes with the fastest version of the code which uses always inline on some RFM69CW routines. With that macro commented out, the compiler will optimize for smaller code which adds 8 microseconds to the loop but drops the hex file down to 1,084 bytes. The difference between fast and "slow" is 312 microseconds vs. 304 microseconds of awake time to get a Temp, RH, Vin measurement and send it all to the radio. Almost all of that is the brutally slow I2C conversation.
Main program
include "HTU21D.h"
#include "LowPower.h"
#include "ADC.h"
#include "RFM69CW.h"
#define DEBUG 0
int main(void) {
uint8_t data[6]; //16-bit temp, 16-bit RH, 16-bit Vcc
#if DEBUG
uint16_t startT, elapsed;
startT = micros(); // ==================== START THE CLOCK ====================
#endif
Fast_ADC_Init(); // Initialize the AVR registers needed
RadioInit(); // Configure the radio while it is asleep
initTWI(); // Initialize the I2C bus at 400kHz
#if DEBUG
elapsed = micros()-startT; // ==================== STOP THE CLOCK ====================
Serial.begin(115200);
Serial.print("\nInitialization took ");
Serial.print(elapsed);
Serial.print(" us\n");
_delay_ms(2);
#endif
for(;;) { // Loop forever
#if DEBUG
startT = micros(); // ==================== START THE CLOCK ====================
#endif
ENABLE_ADC; // Turn on the ADC just prior to needing it
START_ADC_CONVERSION; // start a conversion
issueCommand(WRITE_USER_REGISTER, ELEVEN_BIT_TEMP); // Change the measurement resolution to 11 bits
issueCommand(TRIGGER_TEMP_MEASURE_NOHOLD,0); // Begin a temperature measurement
data[5] = ADCL; // Avoid 16-bit math in this loop since it adds 40us
data[6] = ADCH; // Read back the results of the ADC
DISABLE_ADC; // Turn off the ADC to save power
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF); // Sleep while the sensor measures...
readRaw(&data[0]); // Retreive the measurement results
issueCommand(WRITE_USER_REGISTER, EIGHT_BIT_RH); // Change the measurement resolution to 8 bits
issueCommand(TRIGGER_HUMD_MEASURE_NOHOLD,0); // Begin a RH measurement
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF); // Sleep while the sensor measures...
readRaw(&data[2]); // Retreive the measurement results
TWCR = (1<<TWINT)|(1<<TWEN)| (1<<TWSTO); // stop the TWI
SendFrame(RECEIVER, data, 6); // Send the data
#if DEBUG
elapsed = micros()-startT; // ==================== STOP THE CLOCK ====================
float Tamb = ((uint16_t) (data[0]<<8 | data[1])) * (316.296 / 65535.0) - 52.33;
float RHamb = ((uint16_t) (data[2]<<8 | data[3])) * (125.0 / 65535.0) - 6.0;
Serial.print("\nTemperature: ");
Serial.print(Tamb);
Serial.print("\tHumidity: ");
Serial.print(RHamb);
Serial.print("\t\tVcc: ");
Serial.print(112296/(data[6]<<8 | data[5]));
Serial.print("\t\tLoop took: ");
Serial.print(elapsed);
Serial.print(" us");
_delay_ms(4); // Give the UART time to get our output across
Serial.flush();
#endif
for ( uint8_t sleep_count=0; sleep_count<1; sleep_count++) LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF); // Sleep for 32 seconds
} // end for
} // end main
ADC.h
#include "Arduino.h"
// Define various ADC prescaler
#define ADC_PS_16 (1 << ADPS2)
#define ADC_PS_32 (1 << ADPS2) | (1 << ADPS0)
#define ADC_PS_64 (1 << ADPS2) | (1 << ADPS1)
#define ADC_PS_128 (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0)
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define START_ADC_CONVERSION sbi(ADCSRA, ADSC); // start a conversion
#define ENABLE_ADC cbi(PRR, PRADC); ADCSRA |= bit(ADEN); // Enable the ADC
#define DISABLE_ADC cbi(ADCSRA, ADEN); sbi(PRR, PRADC); // Disable the ADC to save power
static inline void Fast_ADC_Init(void) {
// Timer 0 initialization from wiring.c for a ATmega 328P (Arduino Uno rev 3) + 12 bytes to sketch size
TCCR0A = _BV(WGM01) | _BV(WGM00); // set timer 0 prescale factor to 64
TCCR0B = _BV(CS01) | _BV(CS00); // set timer 0 prescale factor to 64
TIMSK0 = _BV(TOIE0); // enable timer 0 overflow interrupt
// Timer 2 initialization from wiring.c for an ATmega 328P (Arduino Uno rev 3) + 20 bytes to sketch size
TCCR2A |= _BV(COM2A1) | _BV(WGM20); // Enable timer 2 to _delay_ms() works properly
TCCR2B |= CS22; // set clkT2S/64 (From prescaler)
// ADC Housekeeping
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); // Set the multiplexer to read the internal bandgap voltage
ADCSRA |= ADC_PS_32; // set our own prescaler to 32
}
HTU21D.h
#include "Arduino.h"
#define BAUD_RATE 8000000ul
#define TRIGGER_TEMP_MEASURE_NOHOLD 0xF3
#define TRIGGER_HUMD_MEASURE_NOHOLD 0xF5
#define WRITE_USER_REGISTER 0xE6
#define ELEVEN_BIT_TEMP B10000011
#define EIGHT_BIT_RH B00000011
#define SLA_W TWDR = (0x40 << 1)
#define SLA_R TWDR = ((0x40 << 1) + 0x01)
#define START_TWI TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN); WAIT_FOR_TWI_INT
#define RESTART_TWI TWCR = (1<<TWINT) | (1<<TWEN); WAIT_FOR_TWI_INT
#define RESTART_TWI_ACK TWCR = (1<<TWINT) | (1<<TWEA) | (1<<TWEN); WAIT_FOR_TWI_INT
#define WAIT_FOR_TWI_INT while (!(TWCR & (1<<TWINT)) && ++counter)
#define STOP_TWI TWCR = (1<<TWINT)|(1<<TWEN)| (1<<TWSTO)
#define NOT_READY (TWSR & 0xF8) == 0x48 && ++counter
static inline void initTWI() {
DDRC |= (1<<PC3) | (1<<PC2);
PORTC |= (1<<PC2) | (1<<PC4) | (1<<PC5);
TWBR=1; //TWBR = ((F_CPU / BAUD_RATE) - 16) / 2;
}
static inline void issueCommand(uint8_t comm, uint8_t res) {
uint16_t counter;
START_TWI;
SLA_W;
RESTART_TWI;
TWDR = comm; // Send the command
RESTART_TWI;
if (comm == WRITE_USER_REGISTER) { // Send the new resolution
TWDR = res;
RESTART_TWI;
} else { // Issue a stop on the I2C bus so we can enter sleep
STOP_TWI;
}
}
static inline void readRaw(uint8_t *ptr) {
uint16_t counter;
do { // Start + SLA(R) until we get an ACK
START_TWI;
SLA_R;
RESTART_TWI;
} while (NOT_READY);
// Measurement is ready, read back 2 bytes an store them at the address of ptr
RESTART_TWI_ACK; // Set the ACK bit to let the transmitter know we need another byte
*ptr++ = TWDR; // Write the MSB
RESTART_TWI; // Set the NACK bit to let the transmitter know we are done
*ptr = TWDR & 0xFC; // Write the LSB with the status bits masked off
}
RFM69CW.h
#include "Arduino.h"
#define OPT_FLAG //__attribute__((always_inline)) // Comment this definition out to optimize for size
// ============================== User Definitions ==============================
#define NETWORKID 100 //the same on all nodes that talk to each other - 170 is 10101010 DC free value
#define RECEIVER 1 // ID of the gateway that all nodes report back to
#define NODEID 2 // ID of this Node
#define SS_PIN PB2 // Slave select pin
// ============================== SPI Definitions ==============================
#define SELECT noInterrupts(); PORTB &= ~(1<<SS_PIN)
#define UNSELECT SS_WRITE_HIGH; interrupts()
#define SS_WRITE_HIGH PORTB |= 1<<SS_PIN
#define WAIT_WHILE_SPI_BUSY asm volatile("nop"); while (!(SPSR & 1<<SPIF))
// ============================== RFM69CW Definitions ==============================
#define SLEEP_MODE B00000000
#define AUTO_TRANSMITTER B01011011 // Enter = FIFO level; Exit = Packet Sent; Intermediate Mode = TX
#define CHANGE_OP_MODE(mode) writeReg(REG_OPMODE, mode)
#define SET_POWER_LEVEL(level) writeReg(REG_PALEVEL, 0x80 | (level & 0x0F)); // Only works for the RFM69CW not the RFM69HCW
#define REG_FIFO 0x00
#define REG_OPMODE 0x01
#define REG_BITRATEMSB 0x03
#define REG_BITRATELSB 0x04
#define REG_FDEVMSB 0x05
#define REG_FDEVLSB 0x06
#define REG_PALEVEL 0x11
#define REG_RXBW 0x19
#define REG_RSSITHRESH 0x29
#define REG_SYNCCONFIG 0x2E
#define REG_SYNCVALUE1 0x2F
#define REG_SYNCVALUE2 0x30
#define REG_PACKETCONFIG1 0x37
#define REG_AUTOMODES 0x3B
#define REG_PACKETCONFIG2 0x3D
#define RF_BITRATEMSB_300000 0x00 // Begin 300 kbps auto Tx settings
#define RF_BITRATELSB_300000 0x6B
#define RF_FDEVMSB_300000 0x13
#define RF_FDEVLSB_300000 0x33
#define RF_RXBW_DCCFREQ_111 0xE0
#define RF_RXBW_MANT_16 0x00
#define RF_RXBW_EXP_0 0x00
#define RF_SYNC_ON 0x80
#define RF_SYNC_FIFOFILL_AUTO 0x00
#define RF_SYNC_SIZE_2 0x08
#define RF_SYNC_TOL_0 0x00
#define RF_PACKET1_FORMAT_VARIABLE 0x80
#define RF_PACKET1_DCFREE_OFF 0x00
#define RF_PACKET1_CRC_OFF 0x00
#define RF_PACKET1_CRCAUTOCLEAR_OFF 0x08
#define RF_PACKET1_ADRSFILTERING_OFF 0x00
#define RF_PACKET2_RXRESTARTDELAY_2BITS 0x10
#define RF_PACKET2_AUTORXRESTART_ON 0x02
#define RF_PACKET2_AES_OFF 0x00 // End 300 kbps auto Tx settings
static inline void SPI_INIT(void) { // Level 0 code - initialize the SPI bus at fosc/2 (8MHz)
PORTB |= 1<<SS_PIN;
DDRB |= _BV(SS_PIN);
SPCR |= _BV(MSTR) | _BV(SPE);
SPSR |= (1<<SPI2X); // Set the SPI bus speed to Fosc/2 = 8MHz at full speed
// No clue why this is neccessary as opposed to DDRB |= 1<<SCK | 1<<MOSI
volatile uint8_t *reg;
reg = &DDRB;
*reg |= 0x28; // Bit mask of SCK and MOSI
}
OPT_FLAG uint8_t SPI_XFER(uint8_t data) { // Level 0 code - move data over the SPI bus
SPDR = data;
WAIT_WHILE_SPI_BUSY;
return SPDR;
}
OPT_FLAG uint8_t readReg(uint8_t addr) { // Level 1 code - interact witht the radio's registers
SELECT;
SPDR = ( addr & 0x7F );
WAIT_WHILE_SPI_BUSY;
SPDR = ( 0 );
WAIT_WHILE_SPI_BUSY;
UNSELECT;
return SPDR;
}
OPT_FLAG void writeReg(uint8_t addr, uint8_t value) { // Level 1 code - interact witht the radio's registers
SELECT;
SPDR = ( addr | 0x80 );
WAIT_WHILE_SPI_BUSY;
SPDR = ( value );
WAIT_WHILE_SPI_BUSY;
UNSELECT;
}
static inline void SendFrame(uint8_t toAddress, const void* buffer, uint8_t bufferSize) { // Level 2 code - do useful work
SELECT;
SPI_XFER(REG_FIFO | 0x80); // write to FIFO using SPI burst mode
SPI_XFER(bufferSize + 3); // LEN byte
SPI_XFER(toAddress); // 1st byte
SPI_XFER(NODEID); // 2nd byte
SPI_XFER(0x00); // 3rd byte
for (uint8_t i = 0; i < bufferSize; i++) SPI_XFER(((uint8_t*) buffer)[i]); // Write 6 more bytes to the FIFO
UNSELECT;
}
static inline void RadioInit(void) {
SPI_INIT();
CHANGE_OP_MODE(SLEEP_MODE); // Put the radio to sleep ASAP to save power
writeReg( REG_BITRATEMSB, RF_BITRATEMSB_300000 ); // 0x03
writeReg( REG_BITRATELSB, RF_BITRATELSB_300000 ); // 0x04
writeReg( REG_FDEVMSB, RF_FDEVMSB_300000 ); // 0x05
writeReg( REG_FDEVLSB, RF_FDEVLSB_300000 ); // 0x06
writeReg( REG_RXBW, RF_RXBW_DCCFREQ_111 | RF_RXBW_MANT_16 | RF_RXBW_EXP_0 ); // 0x19
writeReg( REG_RSSITHRESH, 220 ); // 0x29
writeReg( REG_SYNCCONFIG, RF_SYNC_ON | RF_SYNC_FIFOFILL_AUTO | RF_SYNC_SIZE_2 | RF_SYNC_TOL_0 ); // 0x2E - 2 sync bytes
writeReg( REG_SYNCVALUE1, 0xAA ); // 0x2F
writeReg( REG_SYNCVALUE2, NETWORKID ); // 0x30
writeReg( REG_PACKETCONFIG1, RF_PACKET1_FORMAT_VARIABLE | RF_PACKET1_DCFREE_OFF | RF_PACKET1_CRC_OFF | RF_PACKET1_CRCAUTOCLEAR_OFF | RF_PACKET1_ADRSFILTERING_OFF ); // 0x37 // 0x37
writeReg( REG_AUTOMODES, AUTO_TRANSMITTER ); // 0x3B - Put the radio in automatic mode
writeReg( REG_PACKETCONFIG2, RF_PACKET2_RXRESTARTDELAY_2BITS | RF_PACKET2_AUTORXRESTART_ON | RF_PACKET2_AES_OFF ); // 0x3D
SET_POWER_LEVEL(0);
}
I now plan to look into
adding back ACKs (done added 4 bytes) and generalizing back to HWs as well. Once that is done I'll need to experiment with auto receivers. Other pending issues are CRC-4 or some sort of encryption.