Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 138 additions & 122 deletions SolarbirdTiny/SolarbirdTiny.ino
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// nick@bitfasching.de, Kazoosh, 2021
// For ATtiny85 @ 8 MHz

//Do you need serial debug messages?
#define DEBUGMODE

// I/O pins //

Expand All @@ -16,6 +18,7 @@
#include "system/leds.h"
#include "system/piezo.h"
#include "system/lowpower.h"
#include "serialout.h"


// Bird voice //
Expand All @@ -28,13 +31,13 @@ Blackbird Bird;

const bool runStartupTest = false;
const bool powerDownAfterStartIfDark = false;
const unsigned long powerDownAfterStartIfDarkTimeoutMillis = 5*1000;
const unsigned long powerDownAfterStartIfDarkTimeoutMillis = 5 * 1000;
const bool lightLEDsWhenPlaying = true;
const int activityMaximum = 8; // should be divisible by the difference of activeSoundInterval*
const int activeSoundIntervalMinimum = 1;
const int activeSoundIntervalMaximum = 5; // should be a power of 2 greater than the minimum
const int idleSoundIntervalMinimum = 60*10; // all intervals must be smaller than 32768
const int idleSoundIntervalMaximum = 60*60;
const int idleSoundIntervalMinimum = 60 * 10; // all intervals must be smaller than 32768
const int idleSoundIntervalMaximum = 60 * 60;
const int nightModeSleepSeconds = 2;
const bool silentAtNight = false;

Expand All @@ -43,127 +46,140 @@ const bool silentAtNight = false;

void setup()
{
// wait to make sure programmer is high-Z
// and be gentle with the power source
delay( 100 );

if ( runStartupTest )
{
// startup test: flash LEDs
LEDs::on();
delay( 50 );
LEDs::off();
delay( 500 );

// startup test: play all melodies once with fixed delay in between
for ( int i = 0; i < Bird.numMelodies; i++ ) {
Bird.playSingleMelody( i, lightLEDsWhenPlaying );
delay( 1000 );
}

delay( 1000 );
}

if ( powerDownAfterStartIfDark )
{
// check if environment is not dark
bool dark = true;
unsigned long startTime = millis();
while ( millis()-startTime <= powerDownAfterStartIfDarkTimeoutMillis ) {
if ( LEDs::senseRawBrightness( false ) > 0 ) {
dark = false;
break;
}
}
if ( dark ) {
// no brightness sensed within defined time window, power down and don't wake up until external reset
Piezo::on();
Piezo::tone( 1E6/220, 1E6 );
Piezo::off();
LowPower::powerDown();
}
}

// initialize random number generator with external source of randomness
pinMode( PIN_LED_1_ANODE, INPUT ); pinMode( PIN_LED_2_ANODE, INPUT );
randomSeed( analogRead( PIN_LED_1_ANODE ) ^ analogRead( PIN_LED_2_ANODE ) );
// wait to make sure programmer is high-Z
// and be gentle with the power source
delay( 100 );
#ifdef DEBUGMODE
SerialOut::printString("\nH E L L O\n");
#endif

if ( runStartupTest )
{
// startup test: flash LEDs
LEDs::on();
delay( 50 );
LEDs::off();
delay( 500 );

// startup test: play all melodies once with fixed delay in between
for ( int i = 0; i < Bird.numMelodies; i++ ) {
Bird.playSingleMelody( i, lightLEDsWhenPlaying );
delay( 1000 );
}

delay( 1000 );
}

if ( powerDownAfterStartIfDark )
{
// check if environment is not dark
bool dark = true;
unsigned long startTime = millis();
while ( millis() - startTime <= powerDownAfterStartIfDarkTimeoutMillis ) {
if ( LEDs::senseRawBrightness( false ) > 0 ) {
dark = false;
break;
}
}
if ( dark ) {
// no brightness sensed within defined time window, power down and don't wake up until external reset
Piezo::on();
Piezo::tone( 1E6 / 220, 1E6 );
Piezo::off();
LowPower::powerDown();
}
}

// initialize random number generator with external source of randomness
pinMode( PIN_LED_1_ANODE, INPUT ); pinMode( PIN_LED_2_ANODE, INPUT );
randomSeed( analogRead( PIN_LED_1_ANODE ) ^ analogRead( PIN_LED_2_ANODE ) );
}

void loop()
{
static int lastBrightness = 0;
static int activity = 0;
static bool active = false;
static int cyclesBeforeNextSound = 0;

// measure current brightness (logarithmic value, proportional to order of magnitude)
int brightness = LEDs::senseBrightness();

// determine if in darkness (i.e., night mode)
bool dark = lastBrightness == 0 && brightness == 0;

// compare with previous brightness (don't care for sign) and save for next cycle
int brightnessActivity = abs( lastBrightness - brightness );
lastBrightness = brightness;

// accumulate activity within limit
activity = activity + brightnessActivity;
activity = min( activityMaximum, activity );

// true if just became active in current cycle
bool justBecameActive = false;

// sensed activity?
if ( activity > 0 )
{
// flash LEDs, the more activity the longer
LEDs::on();
delay( activity * 2 );
LEDs::off();

// just became active?
if ( !active )
{
// set flag and remember active state
justBecameActive = true;
active = true;
}
}
else
{
// clear active state
active = false;
}

// time to make noise?
// either after scheduled interval or on transition from idle to active
if ( cyclesBeforeNextSound == 0 || justBecameActive )
{
// play sound
Bird.play( lightLEDsWhenPlaying );

// determine waiting time before next sound (both for active and idle case)
if ( activity > 0 )
{
// active case = mapping of max activity to min interval plus one cycle of randomness
cyclesBeforeNextSound = (activityMaximum-activity) * (activeSoundIntervalMaximum-activeSoundIntervalMinimum) / activityMaximum + activeSoundIntervalMinimum + random( 0, 1+1 );
}
else
{
cyclesBeforeNextSound = random( idleSoundIntervalMinimum, idleSoundIntervalMaximum+1 );
}
}
else
{
// stop counter when in night mode, i.e., remain silent at night
if ( !dark || !silentAtNight ) {
cyclesBeforeNextSound--;
}
}

// decrease activity level
if ( activity > 0 ) { activity--; }

// wait 1 second by day (2 seconds by night) until next cycle
LowPower::sleepSeconds( dark && !active ? nightModeSleepSeconds : 1 );
static int lastBrightness = 0;
static int activity = 0;
static bool active = false;
static int cyclesBeforeNextSound = 0;

// measure current brightness (logarithmic value, proportional to order of magnitude)
int brightness = LEDs::senseBrightness();
#ifdef DEBUGMODE
SerialOut::printString("\nbrightness = ");
SerialOut::printHex(brightness);
#endif

// determine if in darkness (i.e., night mode)
bool dark = lastBrightness == 0 && brightness == 0;

// compare with previous brightness (don't care for sign) and save for next cycle
int brightnessActivity = abs( lastBrightness - brightness );
lastBrightness = brightness;

// accumulate activity within limit
activity = activity + brightnessActivity;
activity = min( activityMaximum, activity );
#ifdef DEBUGMODE
SerialOut::printString("\nactivity = ");
SerialOut::printHex(activity);
#endif

// true if just became active in current cycle
bool justBecameActive = false;

// sensed activity?
if ( activity > 0 )
{
// flash LEDs, the more activity the longer
LEDs::on();
delay( activity * 2 );
LEDs::off();

// just became active?
if ( !active )
{
// set flag and remember active state
justBecameActive = true;
active = true;
}
}
else
{
// clear active state
active = false;
}

// time to make noise?
// either after scheduled interval or on transition from idle to active
if ( cyclesBeforeNextSound == 0 || justBecameActive )
{
// play sound
Bird.play( lightLEDsWhenPlaying );

// determine waiting time before next sound (both for active and idle case)
if ( activity > 0 )
{
// active case = mapping of max activity to min interval plus one cycle of randomness
cyclesBeforeNextSound = (activityMaximum - activity) * (activeSoundIntervalMaximum - activeSoundIntervalMinimum) / activityMaximum + activeSoundIntervalMinimum + random( 0, 1 + 1 );
}
else
{
cyclesBeforeNextSound = random( idleSoundIntervalMinimum, idleSoundIntervalMaximum + 1 );
}
}
else
{
// stop counter when in night mode, i.e., remain silent at night
if ( !dark || !silentAtNight ) {
cyclesBeforeNextSound--;
}
}

// decrease activity level
if ( activity > 0 ) {
activity--;
}

// wait 1 second by day (2 seconds by night) until next cycle
LowPower::sleepSeconds( dark && !active ? nightModeSleepSeconds : 1 );
}
43 changes: 43 additions & 0 deletions SolarbirdTiny/serialout.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
;25mmHg Kazoosh, 2021
; must be in the same directory with .ino!!!!!!!!!!!!!!!!!!

/* half-duplex 81N serial uart in hand-tuned assembler
* 1%/2% Tx/Rx timing error for 115.2kbps@8Mhz
* 2%/1% Tx/Rx timing error for 230.4kbps@8Mhz
* optimized for no jitter vs AVR305 with 1 cycle/bit jitter
* @author: Ralph Doncaster
* @orginalversion: BasicSerial3.S
* link: https://nerdralph.blogspot.com/2014/01/avr-half-duplex-software-uart.html
* shrink to TX only by 25mmHg
*/

#define delayCount r18
#include <avr/io.h>
; correct for avr/io.h 0x20 port offset for io instructions
#define UART_Port (PORTB-0x20)
#define UART_Tx 4 // DISCONNECT PIEZO !!!!!!!!!!!!!!!!!!!!!!!!

.global TxTimedByte
; transmit byte in r24 with bit delay in r22 - 15 instructions
; calling code must set Tx line to idle state (high) or 1st byte may be lost
; i.e. PORTB |= (1<<UART_Tx)
TxTimedByte:
cli
sbi UART_Port-1, UART_Tx ; set Tx line to output
cbi UART_Port, UART_Tx ; start bit
in r0, UART_Port
ldi r25, 3 ; stop bit + idle state
TxLoop:
; 8 cycle loop + delay - total = 7 + 3*r22
mov delayCount, r22
TxDelay:
; delay (3 cycle * delayCount) -1
dec delayCount
brne TxDelay
bst r24, 0 ; store lsb in T
bld r0, UART_Tx
lsr r25
ror r24
out UART_Port, r0
brne TxLoop
reti ; return and enable interrupts
51 changes: 51 additions & 0 deletions SolarbirdTiny/serialout.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//25mmHg joetronica@gmx.net, Kazoosh, 2021

/* Optimized half-duplex serial uart implementation
* timing within 2% using at 230.4kbps @ 8Mhz
* @author: Ralph Doncaster
* @orginalversion: BasicSerial3.h
* link: https://nerdralph.blogspot.com/2014/01/avr-half-duplex-software-uart.html
* link: https://nerdralph.blogspot.com/2013/12/writing-avr-assembler-code-with-arduino.html
* link to newer version: https://github.com/nerdralph/nerdralph/blob/master/avr/picoUART.h
* shrink to TX only by 25mmHg
* use: printString("\nH E L L O\n");
* use: printHex(10);
*/

#define BAUD_RATE 115200

#ifdef F_CPU
/* account for integer truncation by adding 3/2 = 1.5 */
#define TXDELAY (((F_CPU/BAUD_RATE)-7 +1.5)/3)
#else
#error CPU frequency F_CPU undefined
#endif

extern "C"
{
static void TxTimedByte(char, char);
}

class SerialOut
{
public:

SerialOut() {}

static void printString(const char* str)
{
while (*str) TxTimedByte(*str++, TXDELAY);
}

static void printHex(uint16_t data2hex)
{
//convert unsigned integer numbers 2 HEX and print it
static const uint16_t mask = 0xF;
for(uint8_t i=0; i<4; i++)
{
uint8_t temp = (uint8_t)((data2hex >> (4*(3-i))) & mask);
temp = (temp < 0xA ? temp+'0' : temp-0xA+'A');
TxTimedByte(temp, TXDELAY);
}
}
};
Loading