// $Id: c.c,v 1.5 2006/09/23 20:16:12 michai Exp $
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include "cr.h"
#define EE_SIZE 64
#define EE_ADDR( x ) ( ( const uint8_t * )( uint16_t )( x ) )
#define DDR_LEDS DDRB
#define OUTP_LEDS PORTB
#define OUTM_LED_GRE _BV( 0 )
#define OUTM_LED_RED _BV( 1 )
#define OUTM_LED_YEL _BV( 2 )
#define INM_VBATT _BV( 3 );
#define DDR_LDR_CAP DDRB
#define OUTP_LDR_CAP PORTB
#define OUTM_LDR_CAP _BV( 4 );
static void sleep_ms( uint8_t ms ) { while ( ms-- ) _delay_ms( 1 ); }
static void blink_green( void )
{
OUTP_LEDS &= ~OUTM_LED_GRE;
sleep_ms( 5 );
OUTP_LEDS |= OUTM_LED_GRE;
}
static void blink_red( uint8_t N, uint8_t off_ms )
{
bool forever = !N;
while ( forever || N-- ) {
OUTP_LEDS &= ~OUTM_LED_RED;
_delay_ms( 5 );
OUTP_LEDS |= OUTM_LED_RED;
sleep_ms( off_ms );
}
}
static void blink_for_counting( uint8_t N ) { blink_red( N, 250 ); }
#define PWM_PERIOD 22
static void set_led( uint8_t mask, uint8_t dc )
{
uint8_t N;
if ( dc & _BV( 4 ) ) N = 1;
else if ( dc & _BV( 3 ) ) N = 2;
else if ( dc & _BV( 2 ) ) N = 3;
else if ( dc & _BV( 1 ) ) N = 5;
else N = 7;
while ( N-- ) {
if ( dc ) OUTP_LEDS &= ~mask;
sleep_ms( dc );
OUTP_LEDS |= mask;
sleep_ms( ( PWM_PERIOD - 1 ) - dc );
}
}
static void pwm_led( uint8_t mask )
{
uint8_t dc;
for ( dc = 0; dc < PWM_PERIOD; dc++ ) set_led( mask, dc );
while ( --dc ) set_led( mask, dc );
}
// (Only) assumes ADMUX has been set up correctly
static uint16_t get_adc_val( void )
{
uint16_t sample;
ADCSRA |= _BV( ADEN ); // enable ADC
// Enable ADC
ADCSRA |= _BV( ADEN );
// Get sample
ADCSRA |= _BV( ADSC ); // start conversion
while ( ADCSRA & _BV( ADSC ) ) ;
sample = ADCL;
sample |= ( ( uint16_t )ADCH << 8 );
// Disable ADC
ADCSRA &= ~_BV( ADEN );
return sample;
}
static uint16_t get_ldr_val( void )
{
ADMUX = _BV( MUX1 ); // select ADC2 (PB4), Vcc as ref
return get_adc_val();
}
static uint16_t get_batt_val( void )
{
ADMUX = ( _BV( MUX1 ) | _BV( MUX0 ) ); // select ADC3 (PB3), Vcc as ref
return get_adc_val();
}
static void init_leds( void )
{
// LEDs are output
DDR_LEDS = ( OUTM_LED_GRE | OUTM_LED_RED | OUTM_LED_YEL );
// Turn off LEDs
OUTP_LEDS = ( OUTM_LED_GRE | OUTM_LED_RED | OUTM_LED_YEL );
}
static uint8_t batt_is_ok( void ) { return ( get_batt_val() > ( ( 1024 * 9 ) / 33 ) ); }
static uint8_t get_ldr_darkness_perc( void )
{
// Drain, then start recharging cap
DDR_LDR_CAP |= OUTM_LDR_CAP;
_delay_ms( 1 );
DDR_LDR_CAP &= ~OUTM_LDR_CAP;
// Measure ADC until it hits 66% of Vcc in 1ms intervals
// (ADC-conversion itself + code takes about 600us, but
// resulting percentage is just that, a percentage of something.
uint8_t num_ok = 0;
uint8_t perc;
for ( perc = 0; perc < 100; perc++ ) {
uint16_t sample = get_ldr_val();
if ( sample > ( ( 2 * 1024 ) / 3 ) ) { num_ok++; }
else { num_ok = 0; }
if ( num_ok == 3 ) break;
_delay_ms( 1 );
}
// how to interpret 'perc'?
//
// TL-lit hobbyroom, uncovered : <5
// TL-lit hobbyroom, hand 10cm over LDR : 13
// TL-lit hobbyroom, hand 1cm over LDR : 75
// 2nd light hobbyroom, uncovered : 18
// 2nd light hobbyroom, hand 10cm over LDR : 80
// 2nd light hobbyroom, hand 1cm over LDR : >100
//
return perc;
}
static void init_wdt()
{
// Clear possible WD-reset flag
MCUSR &= ~_BV( WDRF );
// Start timed sequence to change prescaler
WDTCR |= ( _BV( WDTIE ) | _BV( WDCE ) );
// Set prescaler to 1s
WDTCR |= ( _BV( WDTIE ) | _BV( WDP2 ) | _BV( WDP1 ) );
sei();
}
ISR( SIG_WATCHDOG_TIMEOUT ) { /* just continue where we left off */ }
//ISR( SIG_WATCHDOG_TIMEOUT )
//{
// OUTP_LEDS ^= OUTM_LED_YEL;
// _delay_ms( 5 );
// OUTP_LEDS ^= OUTM_LED_YEL;
//}
static void power_down_1s( void )
{
wdt_reset();
// Enter power-down sleep, to be woken up by WDT
set_sleep_mode( SLEEP_MODE_PWR_DOWN );
sleep_enable();
sleep_cpu();
sleep_disable();
}
static void TASK_monitor_darkness( bool *enable )
{
static const int8_t diff_thresh = 20;
static int8_t prev_dark_perc;
static int8_t prev_prev_dark_perc;
static int8_t xmas_eff_perc; // to see how long dark-period continues
static uint8_t i;
int8_t dark_perc = get_ldr_darkness_perc();
int8_t delta_dark_perc = ( ( int8_t )dark_perc - prev_prev_dark_perc );
prev_prev_dark_perc = prev_dark_perc;
prev_dark_perc = dark_perc;
bool to_dark = ( delta_dark_perc >= diff_thresh );
bool to_light = ( delta_dark_perc <= -diff_thresh );
CR_BEGIN;
for ( ;; ) {
*enable = false;
// Wait until light goes off
do { CR_YIELD; } while ( !to_dark );
blink_green();
CR_YIELD; // stabilise after transition
// Wait T_dark ( 1s <= T_dark <= 3s )
CR_YIELD; // don't check for 1st second XXX should be stable
for ( i = 0; i < 2; i++ ) {
CR_YIELD;
if ( to_dark ) { blink_for_counting( 1 ); CR_RESET; }
if ( to_light ) break;
}
if ( !to_light ) { blink_for_counting( 2 ); CR_RESET; }
blink_green();
CR_YIELD; // stabilise after transition
// Wait T_light ( 1s <= T_light <= 3s )
CR_YIELD; // don't check for 1st second XXX should be stable
for ( i = 0; i < 2; i++ ) {
CR_YIELD;
if ( to_light ) { blink_for_counting( 3 ); CR_RESET; }
if ( to_dark ) break;
}
if ( !to_dark ) { blink_for_counting( 4 ); CR_RESET; }
blink_green();
// Enable, and latch current darkness
*enable = true;
xmas_eff_perc = dark_perc;
// Indicate start-of-fun
blink_red( 5, 100 );
// Keep enabled as long as it's dark
while ( ( xmas_eff_perc - dark_perc ) < diff_thresh ) CR_YIELD;
// Indicate end-of-fun, and redo from start
blink_red( 5, 100 );
}
CR_END;
}
static void TASK_pwm_leds( void )
{
CR_BEGIN;
static const uint8_t ledmasks[] = { OUTM_LED_GRE, OUTM_LED_RED, OUTM_LED_YEL, 0 };
for ( ;; ) {
static uint8_t ee_idx;
// ee_idx++;
// ee_idx &= 63;
for ( ee_idx = 0; ee_idx < EE_SIZE; ee_idx++ ) {
static uint8_t byte;
byte = eeprom_read_byte( EE_ADDR( ee_idx ) );
static uint8_t i;
for ( i = 0; i < 2; i++ ) {
uint8_t ledmask = ledmasks[ byte & 0x03 ];
byte >>= 2;
pwm_led( ledmask );
static uint8_t delay;
delay = ( ( byte & 0x03 ) + 1 );
byte >>= 2;
while ( delay-- ) CR_YIELD;
}
}
}
CR_END;
}
int main( void )
{
// Disable input-buffer for all ADC-pins
DIDR0 = (
_BV( ADC2D ) | // V_ldr_cap
_BV( ADC3D ) ); // V_batt
init_leds();
init_wdt();
blink_red( 5, 100 );
bool enable;
for ( ;; ) {
power_down_1s();
TASK_monitor_darkness( &enable );
if ( enable ) {
TASK_pwm_leds();
}
else {
if ( !batt_is_ok() ) {
blink_red( 1, 0 );
}
}
}
return 0;
}