Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80e42f1952 | ||
|
|
28f49e8463 | ||
|
|
b77e1d5a51 | ||
|
|
0aea4c8129 | ||
|
|
c396dbf3f6 | ||
|
|
ebf338c97f | ||
|
|
46a2bec551 | ||
|
|
251f7ed376 | ||
|
|
23fae3e290 | ||
|
|
b0ba83ca4b | ||
|
|
9c805359e8 |
No files matched your search
@@ -14,7 +14,16 @@ FireFly Solar is a small outdoor LED blinker inspired by the idea of an LED thro
|
|||||||
During the day, the solar cell charges the energy storage.
|
During the day, the solar cell charges the energy storage.
|
||||||
At night, the circuit wakes up and sends a simple Morse-style light signal.
|
At night, the circuit wakes up and sends a simple Morse-style light signal.
|
||||||
|
|
||||||
The project is partly a small garden object, partly a low-power electronics experiment, and partly a design exercise for future DIY kits.
|
## About this Project
|
||||||
|
|
||||||
|
FireFly Solar is my **technology playground**. It is a small garden object, but its real purpose is to learn by building something complete. With it I learn about:
|
||||||
|
|
||||||
|
- **Circuit design at the edge**: in this case, as low power as possible
|
||||||
|
- **PCB design**
|
||||||
|
- **Weatherproof casing**
|
||||||
|
- **ATtiny programming**
|
||||||
|
|
||||||
|
Possible next technology step: everything about DIY SMD design and soldering.
|
||||||
|
|
||||||
## Project Goals
|
## Project Goals
|
||||||
|
|
||||||
@@ -24,6 +33,40 @@ The project is partly a small garden object, partly a low-power electronics expe
|
|||||||
- Test mechanical concepts for outdoor 3D-printed parts
|
- Test mechanical concepts for outdoor 3D-printed parts
|
||||||
- Learn what is needed to turn a small electronics project into a possible DIY kit
|
- Learn what is needed to turn a small electronics project into a possible DIY kit
|
||||||
|
|
||||||
|
## Software
|
||||||
|
|
||||||
|
The firmware runs on an **ATtiny85** at 1 MHz (internal clock, ATTinyCore: `clock=1internal`, `millis=disabled`). The LED itself is used as the light sensor. When it is dark, the firefly sends a short Morse text ("TGO 26") about once a minute.
|
||||||
|
|
||||||
|
### Current Firmware version: v0.4 (2026-11-08)
|
||||||
|
|
||||||
|
Goal: lowest possible average current.
|
||||||
|
|
||||||
|
Changes vs v0.3:
|
||||||
|
|
||||||
|
- No `delay()` / `millis()`; Timer0 and Timer1 are gated off in PRR.
|
||||||
|
- The watchdog (interrupt mode) is the only timebase: Morse units (~96 ms), the sensing window and the 8 s sleeps. The CPU sleeps in power-down in between.
|
||||||
|
- Darkness sensing: the CPU sleeps in 16 ms steps while the LED junction discharges, instead of a busy `digitalRead()` loop. Bright light exits after the first step. `darknessThreshold` is replaced by `darkSteps`.
|
||||||
|
- The ADC is disabled before it is gated off in PRR; pins are set up before PRR.
|
||||||
|
- After a power-on or external reset, no blinking for `RECOVER_CYCLES` (about 16 min), so the supercap can recharge.
|
||||||
|
- `DEBUG_TEST` switch for bench testing: 8 s cycle, no recovery wait, short flash when the sketch decides "bright". Set it to 0 for the field.
|
||||||
|
- Optional `BOD_FUSE_ENABLED` (BOD off during sleep); unused with the BOD fuse off.
|
||||||
|
|
||||||
|
Tested: compiles with ATTinyCore and runs on hardware. Current draw is not yet measured.
|
||||||
|
|
||||||
|
## PCB
|
||||||
|
|
||||||
|
- Finished: a batch of 10 is assembled, tested and working.
|
||||||
|
- Latest adjustment: fine-tuned the resistors for the LED, and the 3.9 V Zener diode is confirmed safe avoid overcharging 4V supercap.
|
||||||
|
- Next version: the PCB should be a little shorter. The wiring space is a bit too tight now.
|
||||||
|
|
||||||
|
## Casing
|
||||||
|
|
||||||
|
Works basically as intended. Lessons for the next version:
|
||||||
|
|
||||||
|
- Better locking of the solar modules.
|
||||||
|
- The rope structure works, but building it is too complicated and takes a lot of nerves.
|
||||||
|
- Using material tension to secure the modules via rope was basically a good idea.
|
||||||
|
|
||||||
## Naming
|
## Naming
|
||||||
|
|
||||||
The English project name remains:
|
The English project name remains:
|
||||||
@@ -39,9 +82,12 @@ The name roughly means **sun-disc firefly**. It keeps the solar theme, adds a sl
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
PRs and issues welcome. Keep it simple.
|
|
||||||
|
PRs and issues welcome. Keep it simple.
|
||||||
Photos of your build you send me go in `Documentation/gallery/` with a short caption and license notice.
|
Photos of your build you send me go in `Documentation/gallery/` with a short caption and license notice.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Default license**: [CC-BY-NC-4.0](https://creativecommons.org/licenses/by-nc/4.0/)
|
## License
|
||||||
|
|
||||||
|
Default license: [CC-BY-NC-4.0](https://creativecommons.org/licenses/by-nc/4.0/)
|
||||||
@@ -0,0 +1,512 @@
|
|||||||
|
/*
|
||||||
|
=============================================================================
|
||||||
|
FireFly Solar / 日輪蛍 – Nichirin Hotaru / a Solar Morse Throwie
|
||||||
|
|
||||||
|
太陽を宿し、夜にモールスを灯す蛍。
|
||||||
|
Taiyō o yadoshi, yoru ni Mōrusu o tomosu hotaru.
|
||||||
|
A firefly carrying the sun, lighting Morse in the night.
|
||||||
|
=============================================================================
|
||||||
|
Project definitions, sources
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
Version : 0.5 - ATTiny85, 1 MHz, BOD fuse disabled
|
||||||
|
used Flash: 1284 bytes
|
||||||
|
|
||||||
|
gitea : https://gitea.togo-lab.io/tgohle/0001-FireFly
|
||||||
|
Date : 2026-11-10
|
||||||
|
|
||||||
|
|
||||||
|
Key changes Version 0.5 (goal: lowest possible average current):
|
||||||
|
|
||||||
|
- No delay(), no millis(): Timer0 and Timer1 are not needed any more and
|
||||||
|
are switched off in PRR.
|
||||||
|
- ALL timing (Morse units, sensing window, 8 s sleep) is done by the watchdog
|
||||||
|
in interrupt mode while the CPU is in POWER-DOWN. The CPU runs only for a
|
||||||
|
few microseconds per wake-up. LED pins keep their state in power-down.
|
||||||
|
- Darkness sensing by LED no longer polls digitalRead() in a busy loop (that was
|
||||||
|
~0.3-1 s awake per cycle). The LED junction is charged, then the CPU sleeps
|
||||||
|
in 16 ms steps and checks the pin: bright light exits after the first step.
|
||||||
|
- ADC is disabled BEFORE it is gated off in PRR (order matters).
|
||||||
|
- After a power-on, external, brown-out or watchdog reset the sketch waits
|
||||||
|
RECOVER_CYCLES cycles before blinking, allowing the supercap to recharge.
|
||||||
|
Default ~13 min 20 s (nominal); the debug option bypasses recovery.
|
||||||
|
- Startup code saves MCUSR and disables the watchdog early (.init3), avoiding
|
||||||
|
a watchdog-reset loop before setup() can execute.
|
||||||
|
- Optional: BOD disable during sleep (only useful if BOD fuse is enabled).
|
||||||
|
But found out that also BOD Fuse for 1.8V shorten unnecessary blinking time,
|
||||||
|
will still work ~1V, meaning ~20 min longer blinking time.
|
||||||
|
- Field Test: After a sunny day in Winter, outdoor -1 ... 8°C, with a payload
|
||||||
|
of 10 signs) it blinks for >> 12h.
|
||||||
|
|
||||||
|
Timing note: the watchdog oscillator is not precise (changes with Vcc and
|
||||||
|
temperature). All Morse timing comes from the same clock, so dit/dah/gap
|
||||||
|
ratios stay correct; only the absolute speed drifts a little.
|
||||||
|
The HW actual allowes 5V and 3.3V, standard is 3.3V. So don't expec exact timing.
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Inspired by Karl Lunt's FireFly project:
|
||||||
|
http://www.seanet.com/~karllunt/fireflyLED.html
|
||||||
|
|
||||||
|
Morse code reference:
|
||||||
|
'A', ".-" 'B', "-..." 'C', "-.-."
|
||||||
|
'D', "-.." 'E', "." 'F', "..-."
|
||||||
|
'G', "--." 'H', "...." 'I', ".."
|
||||||
|
'J', ".---" 'K', "-.-" 'L', ".-.."
|
||||||
|
'M', "--" 'N', "-." 'O', "---"
|
||||||
|
'P', ".--." 'Q', "--.-" 'R', ".-."
|
||||||
|
'S', "..." 'T', "-" 'U', "..-"
|
||||||
|
'V', "...-" 'W', ".--" 'X', "-..-"
|
||||||
|
'Y', "-.--" 'Z', "--.."
|
||||||
|
|
||||||
|
'1', ".----" '2', "..---" '3', "...--"
|
||||||
|
'4', "....-" '5', "....." '6', "-...."
|
||||||
|
'7', "--..." '8', "---.." '9', "----."
|
||||||
|
'0', "-----"
|
||||||
|
|
||||||
|
'.', ".-.-.-" ',', "--..--" '?', "..--.."
|
||||||
|
'!', "-.-.--" ':', "---..." ';', "-.-.-."
|
||||||
|
'(', "-.--." ')', "-.--.-" '"', ".-..-."
|
||||||
|
'@', ".--.-." '&', ".-..." '-', "-....-"
|
||||||
|
'/', "-..-." ''', ".----." '+', ".-.-."
|
||||||
|
'=', "-...-" '_', "..--.-"
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Legal stuff / Copyright:
|
||||||
|
License_-_CC_BY-NC_4.0
|
||||||
|
https://creativecommons.org/licenses/by-nc/4.0/
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <avr/sleep.h>
|
||||||
|
#include <avr/wdt.h>
|
||||||
|
#include <avr/pgmspace.h>
|
||||||
|
#include <avr/interrupt.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// DEBUG_TEST 1 = bench testing: no reset-recovery wait, a blink attempt every
|
||||||
|
// 8 s, and a short flash when the sketch thinks it is BRIGHT.
|
||||||
|
// SET TO 0 FOR THE FIELD (40 s idle sleep, ~13m20s reset recovery).
|
||||||
|
#define DEBUG_TEST 0
|
||||||
|
|
||||||
|
#ifndef cbi
|
||||||
|
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
|
||||||
|
#endif
|
||||||
|
#ifndef sbi
|
||||||
|
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Watchdog timeout codes (nominal, at ~5 V; real values vary with Vcc/temp):
|
||||||
|
// actual HW uses 3.3 (standard) or 5V (for blue LED experimental setup)
|
||||||
|
// so don't expect exact timing)
|
||||||
|
// 0=16ms 1=32ms 2=64ms 3=128ms 4=250ms 5=500ms
|
||||||
|
// 6=1s 7=2s 8=4s 9=8s
|
||||||
|
//
|
||||||
|
// One Morse unit = UNIT_WDT_A + UNIT_WDT_B -> 64 ms + 32 ms = ~96 ms
|
||||||
|
// A=3, B=0 -> ~144 ms (easier to read, more LED time)
|
||||||
|
// A=3, B=0xFF -> ~128 ms
|
||||||
|
// A=2, B=0xFF -> ~64 ms (fast, less LED time, harder to read by eye)
|
||||||
|
// Use 0xFF for B to skip the second sleep.
|
||||||
|
#define UNIT_WDT_A 2
|
||||||
|
#define UNIT_WDT_B 1
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Sleep interval between two sensing/blink attempts: sleepCycles x 8 s.
|
||||||
|
// 5 x 8 s = 40 s. Goal ~ 1 blink/min.
|
||||||
|
#if DEBUG_TEST
|
||||||
|
const uint8_t sleepCycles = 1;
|
||||||
|
#else
|
||||||
|
const uint8_t sleepCycles = 5;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Darkness window in 16 ms steps (nominal).
|
||||||
|
// After charging the LED junction the CPU sleeps step by step. If the pin
|
||||||
|
// reads LOW within the window the LED discharged quickly = bright = no blink.
|
||||||
|
// Still HIGH after the full window = dark = blink.
|
||||||
|
// Longer window = triggers in darker conditions
|
||||||
|
// The window costs almost no energy (CPU sleeps), so it is only a threshold.
|
||||||
|
// Calibrate at real dusk. Old threshold 17000 loops: measure the old
|
||||||
|
// sensDarkness() time with micros() at your dusk level and divide it by 16 ms.
|
||||||
|
const uint8_t darkSteps = 40; // ~640 ms
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// After a power-on / brown-out / external / watchdog reset, do not blink for
|
||||||
|
// this many cycles (each has 40 s of sleep) so the supercap can recharge.
|
||||||
|
// 20 -> ~13 min 20 s nominal. Set to 0 for bench testing/uploading.
|
||||||
|
#if DEBUG_TEST
|
||||||
|
#define RECOVER_CYCLES 0
|
||||||
|
#else
|
||||||
|
#define RECOVER_CYCLES 20
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Set to 1 only if the BOD fuse is enabled (e.g. hfuse 0xDE = 1.8 V).
|
||||||
|
// Then BOD is switched off during power-down to save its ~20 uA.
|
||||||
|
// With BOD fuse disabled this has no effect, leave it 0.
|
||||||
|
#define BOD_FUSE_ENABLED 0
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// LED pin definitions (N-side = cathode, P-side = anode for sensing/driving).
|
||||||
|
#define LED1_N_SIDE 3
|
||||||
|
#define LED1_P_SIDE 4
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ATTiny85 Arduino pins 0, 1, 2 are unused in actual design and physically open.
|
||||||
|
// Keep them in INPUT_PULLUP so they do not float and waste current.
|
||||||
|
#define UNUSED_PIN_0 0
|
||||||
|
#define UNUSED_PIN_1 1
|
||||||
|
#define UNUSED_PIN_2 2
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ATTiny85 has 512 bytes SRAM; this keeps the Morse text in flash.
|
||||||
|
// Only uppercase letters, digits, spaces, and the special chars in the switch
|
||||||
|
// below are emitted. Lowercase letters are converted to uppercase.
|
||||||
|
// Shorter text = less LED time = less energy.
|
||||||
|
const char morseText[] PROGMEM = "TOGO-LAB.IO FIREFLY";
|
||||||
|
// Ruler: 0....0....1....1....2
|
||||||
|
// Ruler: 01...5....0....5....0
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Watchdog interrupt flag - volatile because it is written in an ISR.
|
||||||
|
volatile bool f_wdt = false;
|
||||||
|
|
||||||
|
// Remaining cycles without blinking after a reset.
|
||||||
|
static uint8_t recoverCycles = 0;
|
||||||
|
|
||||||
|
// Preserve the reset flags before Arduino startup and stop a watchdog left
|
||||||
|
// running by a previous reset. AVR-libc's recommended .init3 pattern.
|
||||||
|
// .noinit is not cleared by C runtime initialization.
|
||||||
|
static uint8_t resetCauseEarly __attribute__((section(".noinit")));
|
||||||
|
|
||||||
|
void earlyWatchdogInit(void) __attribute__((used, naked, section(".init3")));
|
||||||
|
void earlyWatchdogInit(void)
|
||||||
|
{
|
||||||
|
resetCauseEarly = MCUSR;
|
||||||
|
MCUSR = 0; // Clear WDRF before disabling WDT.
|
||||||
|
wdt_disable(); // Prevent repeated ~15 ms watchdog resets.
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Forward declarations.
|
||||||
|
static void sleepWdt(uint8_t ii);
|
||||||
|
static void waitUnits(uint8_t n);
|
||||||
|
void configureUnusedPins();
|
||||||
|
void releaseLedPins(int LED_N, int LED_P);
|
||||||
|
bool isDark(int LED_N, int LED_P);
|
||||||
|
void morse(int LED_N, int LED_P);
|
||||||
|
void dit(int LED_P);
|
||||||
|
void dah(int LED_P);
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Setup
|
||||||
|
// ===========================================================================
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
// Reset cause was captured before C/Arduino initialization in .init3.
|
||||||
|
// Include WDRF: a watchdog reset also needs supercap recovery time.
|
||||||
|
if (resetCauseEarly & ((1 << PORF) | (1 << BORF) |
|
||||||
|
(1 << EXTRF) | (1 << WDRF))) {
|
||||||
|
recoverCycles = RECOVER_CYCLES;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pins first (while the Arduino core can still reach all timer registers
|
||||||
|
// for its turnOffPWM() handling in digitalWrite()).
|
||||||
|
configureUnusedPins();
|
||||||
|
releaseLedPins(LED1_N_SIDE, LED1_P_SIDE);
|
||||||
|
|
||||||
|
// ADC off FIRST, then gate its clock. With PRADC set, ADC register
|
||||||
|
// writes are ignored, so the order matters.
|
||||||
|
cbi(ADCSRA, ADEN);
|
||||||
|
|
||||||
|
// Analog comparator off.
|
||||||
|
ACSR |= (1 << ACD);
|
||||||
|
|
||||||
|
// Power reduction: ADC, USI, Timer0, Timer1 all off.
|
||||||
|
// Nothing in this sketch uses them: no delay(), no millis(), no analogWrite().
|
||||||
|
PRR = (1 << PRADC) | (1 << PRUSI) | (1 << PRTIM0) | (1 << PRTIM1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Main loop
|
||||||
|
// ===========================================================================
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
if (recoverCycles > 0) {
|
||||||
|
recoverCycles--;
|
||||||
|
} else if (isDark(LED1_N_SIDE, LED1_P_SIDE)) {
|
||||||
|
morse(LED1_N_SIDE, LED1_P_SIDE);
|
||||||
|
}
|
||||||
|
#if DEBUG_TEST
|
||||||
|
else {
|
||||||
|
// Sketch decided "bright": one short ~128 ms flash so you can see it.
|
||||||
|
pinMode(LED1_N_SIDE, OUTPUT);
|
||||||
|
pinMode(LED1_P_SIDE, OUTPUT);
|
||||||
|
digitalWrite(LED1_N_SIDE, LOW);
|
||||||
|
digitalWrite(LED1_P_SIDE, HIGH);
|
||||||
|
sleepWdt(3);
|
||||||
|
digitalWrite(LED1_P_SIDE, LOW);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Return LED pins to high-Z before sleeping.
|
||||||
|
// Internal pullups stay disabled for the LED pins.
|
||||||
|
releaseLedPins(LED1_N_SIDE, LED1_P_SIDE);
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < sleepCycles; i++) {
|
||||||
|
sleepWdt(9); // 8 s each
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Pin helpers
|
||||||
|
// ===========================================================================
|
||||||
|
void configureUnusedPins()
|
||||||
|
{
|
||||||
|
pinMode(UNUSED_PIN_0, INPUT_PULLUP);
|
||||||
|
pinMode(UNUSED_PIN_1, INPUT_PULLUP);
|
||||||
|
pinMode(UNUSED_PIN_2, INPUT_PULLUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
void releaseLedPins(int LED_N, int LED_P)
|
||||||
|
{
|
||||||
|
// digitalWrite LOW before INPUT disables the internal pullup on Arduino cores.
|
||||||
|
digitalWrite(LED_N, LOW);
|
||||||
|
digitalWrite(LED_P, LOW);
|
||||||
|
pinMode(LED_N, INPUT);
|
||||||
|
pinMode(LED_P, INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Sleep helpers (watchdog = only timebase)
|
||||||
|
// ===========================================================================
|
||||||
|
ISR(WDT_vect)
|
||||||
|
{
|
||||||
|
f_wdt = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep in POWER-DOWN until the watchdog interrupt fires after timeout code ii
|
||||||
|
// (0=16 ms ... 9=8 s). Reconfigures the watchdog on every call, so the full
|
||||||
|
// interval always starts fresh.
|
||||||
|
static void sleepWdt(uint8_t ii)
|
||||||
|
{
|
||||||
|
if (ii > 9) ii = 9;
|
||||||
|
|
||||||
|
uint8_t bb = ii & 7;
|
||||||
|
if (ii > 7) bb |= (1 << WDP3);
|
||||||
|
|
||||||
|
// Interrupt-only mode (WDE = 0). WDIF written as 1 clears a stale flag.
|
||||||
|
const uint8_t cfg = bb | (1 << WDIE) | (1 << WDIF);
|
||||||
|
|
||||||
|
f_wdt = false;
|
||||||
|
cli();
|
||||||
|
wdt_reset();
|
||||||
|
|
||||||
|
// WDCE/WDE unlock and configuration: two consecutive OUT instructions.
|
||||||
|
// The second write is guaranteed to occur within the required four cycles.
|
||||||
|
const uint8_t unlock = _BV(WDCE) | _BV(WDE);
|
||||||
|
__asm__ __volatile__(
|
||||||
|
"out %[wdt], %[unlock] \n\t"
|
||||||
|
"out %[wdt], %[config] \n\t"
|
||||||
|
:
|
||||||
|
: [wdt] "I" (_SFR_IO_ADDR(WDTCR)),
|
||||||
|
[unlock] "r" (unlock),
|
||||||
|
[config] "r" (cfg)
|
||||||
|
: "memory"
|
||||||
|
);
|
||||||
|
|
||||||
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||||
|
|
||||||
|
// Atomic sleep entry: after sei() the next instruction (sleep_cpu) still
|
||||||
|
// executes before any interrupt is serviced, so the wake-up cannot be missed.
|
||||||
|
// Loop again if something else woke the CPU.
|
||||||
|
while (!f_wdt) {
|
||||||
|
sleep_enable();
|
||||||
|
#if BOD_FUSE_ENABLED
|
||||||
|
sleep_bod_disable();
|
||||||
|
#endif
|
||||||
|
sei();
|
||||||
|
sleep_cpu();
|
||||||
|
sleep_disable();
|
||||||
|
cli();
|
||||||
|
}
|
||||||
|
sei();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait n Morse units in power-down.
|
||||||
|
static void waitUnits(uint8_t n)
|
||||||
|
{
|
||||||
|
while (n--) {
|
||||||
|
sleepWdt(UNIT_WDT_A);
|
||||||
|
#if UNIT_WDT_B != 0xFF
|
||||||
|
sleepWdt(UNIT_WDT_B);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Light sensor
|
||||||
|
// ===========================================================================
|
||||||
|
// Returns true if it is dark.
|
||||||
|
// Charges the LED junction capacitance, then lets it bleed through the
|
||||||
|
// reverse-biased LED while the CPU sleeps. Checks the pin every ~16 ms.
|
||||||
|
// LOW within the window -> bright (discharged fast)
|
||||||
|
// still HIGH at the end -> dark
|
||||||
|
// In power-down the input buffer of the floating pin is disabled by hardware,
|
||||||
|
// so the slowly falling voltage causes no shoot-through current.
|
||||||
|
bool isDark(int LED_N, int LED_P)
|
||||||
|
{
|
||||||
|
// Charge the LED junction capacitance.
|
||||||
|
pinMode(LED_N, OUTPUT);
|
||||||
|
pinMode(LED_P, OUTPUT);
|
||||||
|
digitalWrite(LED_N, HIGH);
|
||||||
|
digitalWrite(LED_P, LOW);
|
||||||
|
|
||||||
|
// Let the N-side float (no pullup) and measure bleed-down time.
|
||||||
|
pinMode(LED_N, INPUT);
|
||||||
|
digitalWrite(LED_N, LOW);
|
||||||
|
|
||||||
|
bool dark = true;
|
||||||
|
for (uint8_t k = 0; k < darkSteps; k++) {
|
||||||
|
sleepWdt(0); // ~16 ms
|
||||||
|
if (digitalRead(LED_N) == LOW) {
|
||||||
|
dark = false; // bright: stop early
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up after sensing: both LED pins low, caller releases them.
|
||||||
|
pinMode(LED_N, OUTPUT);
|
||||||
|
digitalWrite(LED_N, LOW);
|
||||||
|
pinMode(LED_P, OUTPUT);
|
||||||
|
digitalWrite(LED_P, LOW);
|
||||||
|
|
||||||
|
return dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Morse helpers
|
||||||
|
// ===========================================================================
|
||||||
|
void dit(int LED_P)
|
||||||
|
{
|
||||||
|
digitalWrite(LED_P, HIGH);
|
||||||
|
waitUnits(1);
|
||||||
|
digitalWrite(LED_P, LOW);
|
||||||
|
waitUnits(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dah(int LED_P)
|
||||||
|
{
|
||||||
|
digitalWrite(LED_P, HIGH);
|
||||||
|
waitUnits(3);
|
||||||
|
digitalWrite(LED_P, LOW);
|
||||||
|
waitUnits(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Morse sender
|
||||||
|
// ===========================================================================
|
||||||
|
void morse(int LED_N, int LED_P)
|
||||||
|
{
|
||||||
|
pinMode(LED_N, OUTPUT);
|
||||||
|
pinMode(LED_P, OUTPUT);
|
||||||
|
digitalWrite(LED_N, LOW);
|
||||||
|
digitalWrite(LED_P, LOW);
|
||||||
|
|
||||||
|
const size_t len = strlen_P(morseText);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
char c = (char)pgm_read_byte(&morseText[i]);
|
||||||
|
|
||||||
|
// Allow lowercase source text without silently breaking timing.
|
||||||
|
if (c >= 'a' && c <= 'z') {
|
||||||
|
c = c - 'a' + 'A';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool symbolSent = false;
|
||||||
|
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
// ----- Letters -----
|
||||||
|
case 'A': dit(LED_P); dah(LED_P); symbolSent = true; break; // .-
|
||||||
|
case 'B': dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // -...
|
||||||
|
case 'C': dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // -.-.
|
||||||
|
case 'D': dah(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // -..
|
||||||
|
case 'E': dit(LED_P); symbolSent = true; break; // .
|
||||||
|
case 'F': dit(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // ..-.
|
||||||
|
case 'G': dah(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // --.
|
||||||
|
case 'H': dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // ....
|
||||||
|
case 'I': dit(LED_P); dit(LED_P); symbolSent = true; break; // ..
|
||||||
|
case 'J': dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // .---
|
||||||
|
case 'K': dah(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // -.-
|
||||||
|
case 'L': dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // .-..
|
||||||
|
case 'M': dah(LED_P); dah(LED_P); symbolSent = true; break; // --
|
||||||
|
case 'N': dah(LED_P); dit(LED_P); symbolSent = true; break; // -.
|
||||||
|
case 'O': dah(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // ---
|
||||||
|
case 'P': dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // .--.
|
||||||
|
case 'Q': dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // --.-
|
||||||
|
case 'R': dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // .-.
|
||||||
|
case 'S': dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // ...
|
||||||
|
case 'T': dah(LED_P); symbolSent = true; break; // -
|
||||||
|
case 'U': dit(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // ..-
|
||||||
|
case 'V': dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // ...-
|
||||||
|
case 'W': dit(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // .--
|
||||||
|
case 'X': dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // -..-
|
||||||
|
case 'Y': dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // -.--
|
||||||
|
case 'Z': dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // --..
|
||||||
|
|
||||||
|
// ----- Digits -----
|
||||||
|
case '1': dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // .----
|
||||||
|
case '2': dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // ..---
|
||||||
|
case '3': dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // ...--
|
||||||
|
case '4': dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // ....-
|
||||||
|
case '5': dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // .....
|
||||||
|
case '6': dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // -....
|
||||||
|
case '7': dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // --...
|
||||||
|
case '8': dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // ---..
|
||||||
|
case '9': dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // ----.
|
||||||
|
case '0': dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // -----
|
||||||
|
|
||||||
|
// ----- Punctuation & Specials -----
|
||||||
|
case '.': dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // .-.-.-
|
||||||
|
case ',': dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // --..--
|
||||||
|
case '?': dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // ..--..
|
||||||
|
case '!': dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); symbolSent = true; break; // -.-.--
|
||||||
|
case ':': dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // ---...
|
||||||
|
case ';': dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // -.-.-.
|
||||||
|
case '(': dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // -.--.
|
||||||
|
case ')': dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // -.--.-
|
||||||
|
case '"': dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // .-..-.
|
||||||
|
case '@': dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // .--.-.
|
||||||
|
case '&': dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); symbolSent = true; break; // .-...
|
||||||
|
case '-': dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // -....-
|
||||||
|
case '/': dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // -..-.
|
||||||
|
case '\'': dit(LED_P); dah(LED_P); dah(LED_P);dah(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // .----.
|
||||||
|
case '+': dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); symbolSent = true; break; // .-.-.
|
||||||
|
case '=': dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // -...-
|
||||||
|
case '_': dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); symbolSent = true; break; // ..--.-
|
||||||
|
|
||||||
|
// Space handling:
|
||||||
|
// Previous symbol already ended with 1 unit OFF and then received the
|
||||||
|
// normal 2 unit character gap. Add 4 units here -> total word gap 7 units.
|
||||||
|
case ' ':
|
||||||
|
waitUnits(4);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Unsupported chars are ignored without adding an artificial gap.
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inter-character gap: 3 units total.
|
||||||
|
// dit()/dah() already trail 1 unit OFF, so add the remaining 2 units.
|
||||||
|
if (symbolSent) {
|
||||||
|
waitUnits(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
digitalWrite(LED_P, LOW);
|
||||||
|
digitalWrite(LED_N, LOW);
|
||||||
|
}
|
||||||
@@ -183,8 +183,8 @@
|
|||||||
// theoretical you can use up to 3 LED. For example you can use it for a
|
// theoretical you can use up to 3 LED. For example you can use it for a
|
||||||
// landmark beacon. So if you point this LEDs to different directions you can
|
// landmark beacon. So if you point this LEDs to different directions you can
|
||||||
// detect the position in dependence of your location to the bacon.
|
// detect the position in dependence of your location to the bacon.
|
||||||
#define LED1_N_SIDE 4
|
#define LED1_N_SIDE 3
|
||||||
#define LED1_P_SIDE 3
|
#define LED1_P_SIDE 4
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// Variables
|
// Variables
|
||||||
|
|||||||
@@ -0,0 +1,311 @@
|
|||||||
|
/*
|
||||||
|
=============================================================================
|
||||||
|
FireFly Morse Throwie
|
||||||
|
- a light controlled (LED as Sensor) morse blinker throwie with ATTiny85
|
||||||
|
=============================================================================
|
||||||
|
|
||||||
|
Project definitions, sources
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
Version: 0.2 - ATTiny85, 1 MHz, BOD fuse disabled
|
||||||
|
gitea : https://gitea.togo-lab.io/tgohle/0001-FireFly
|
||||||
|
Date : 2026-06-13
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Inspired by Karl Lunt's FireFly project:
|
||||||
|
http://www.seanet.com/~karllunt/fireflyLED.html
|
||||||
|
|
||||||
|
Morse code reference:
|
||||||
|
'A', ".-" 'B', "-..." 'C', "-.-."
|
||||||
|
'D', "-.." 'E', "." 'F', "..-."
|
||||||
|
'G', "--." 'H', "...." 'I', ".."
|
||||||
|
'J', ".---" 'K', "-.-" 'L', ".-.."
|
||||||
|
'M', "--" 'N', "-." 'O', "---"
|
||||||
|
'P', ".--." 'Q', "--.-" 'R', ".-."
|
||||||
|
'S', "..." 'T', "-" 'U', "..-"
|
||||||
|
'V', "...-" 'W', ".--" 'X', "-..-"
|
||||||
|
'Y', "-.--" 'Z', "--.."
|
||||||
|
|
||||||
|
'1', ".----" '2', "..---" '3', "...--"
|
||||||
|
'4', "....-" '5', "....." '6', "-...."
|
||||||
|
'7', "--..." '8', "---.." '9', "----."
|
||||||
|
'0', "-----"
|
||||||
|
|
||||||
|
'.', ".-.-.-" ',', "--..--" '?', "..--.."
|
||||||
|
'!', "-.-.--" ':', "---..." ';', "-.-.-."
|
||||||
|
'(', "-.--." ')', "-.--.-" '"', ".-..-."
|
||||||
|
'@', ".--.-." '&', ".-..."
|
||||||
|
|
||||||
|
Legal stuff / Copyright:
|
||||||
|
License_-_CC_BY-NC_4.0
|
||||||
|
https://creativecommons.org/licenses/by-nc/4.0/
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <avr/sleep.h>
|
||||||
|
#include <avr/wdt.h>
|
||||||
|
#include <avr/pgmspace.h> // FIX 3: needed for PROGMEM / pgm_read_byte
|
||||||
|
|
||||||
|
#ifndef cbi
|
||||||
|
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
|
||||||
|
#endif
|
||||||
|
#ifndef sbi
|
||||||
|
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Timing: unit length in ms.
|
||||||
|
// At 1 MHz, delay() is accurate when F_CPU=1000000L is set in boards.txt.
|
||||||
|
// 100 ms gives readable optical Morse; raise to 150 if readability is poor.
|
||||||
|
#define unitLength 100
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// LED pin definitions (N-side = cathode, P-side = anode for sensing/driving)
|
||||||
|
#define LED1_N_SIDE 3
|
||||||
|
#define LED1_P_SIDE 4
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ATTiny85 has 512 bytes SRAM; this frees all of it for the stack.
|
||||||
|
// Only uppercase letters, digits, and the special chars in the switch below.
|
||||||
|
// Test only: 20 x "0" due 0 = "-----" most energy draining
|
||||||
|
const char morseText[] PROGMEM = "0000000000000000000";
|
||||||
|
// Ruler: 0...0....1...1....2 Attention: more Text
|
||||||
|
// Ruler: 1...5....0...5....0 will cost more power!
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Darkness threshold.
|
||||||
|
// Higher value = triggers in brighter conditions.
|
||||||
|
// Best calibrated at actual dusk/dawn with the chosen LED type.
|
||||||
|
unsigned int darknessThreshold = 17000;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Watchdog interrupt flag — volatile because it is written in an ISR
|
||||||
|
volatile boolean f_wdt = 1;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Forward declarations
|
||||||
|
void setup_watchdog(int ii);
|
||||||
|
void system_sleep();
|
||||||
|
unsigned int sensDarkness(int LED_N, int LED_P);
|
||||||
|
void morse(int LED_N, int LED_P);
|
||||||
|
void dit(int LED_P);
|
||||||
|
void dah(int LED_P);
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Setup
|
||||||
|
// ===========================================================================
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
// Disable unused peripherals immediately to save some µ/mA.
|
||||||
|
// PRTIM1, PRUSI: genuinely unused, safe to gate off
|
||||||
|
// PRADC: controlled manually around sensDarkness()
|
||||||
|
// PRTIM0: must stay ON — delay() and millis() depend on Timer0
|
||||||
|
PRR = (1 << PRTIM1) | (1 << PRUSI) | (1 << PRADC);
|
||||||
|
|
||||||
|
// ADC is gated via PRR above; also clear ADEN just in case
|
||||||
|
cbi(ADCSRA, ADEN);
|
||||||
|
|
||||||
|
setup_watchdog(9); // 8-second watchdog interval
|
||||||
|
// simple repeat if more delay is need, eg 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Main loop
|
||||||
|
// ===========================================================================
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
if (f_wdt == 1) {
|
||||||
|
f_wdt = 0;
|
||||||
|
|
||||||
|
// Enable ADC only for the sensing window, then shut it off again.
|
||||||
|
cbi(PRR, PRADC); // un-gate ADC clock
|
||||||
|
sbi(ADCSRA, ADEN); // power ADC on
|
||||||
|
|
||||||
|
if (sensDarkness(LED1_N_SIDE, LED1_P_SIDE) > darknessThreshold) {
|
||||||
|
morse(LED1_N_SIDE, LED1_P_SIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
cbi(ADCSRA, ADEN); // ADC off
|
||||||
|
sbi(PRR, PRADC); // re-gate ADC clock
|
||||||
|
|
||||||
|
// Return LED pins to input (high-Z) before sleeping
|
||||||
|
pinMode(LED1_N_SIDE, INPUT);
|
||||||
|
pinMode(LED1_P_SIDE, INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sleep 4 x 8 s = ~32 s between activations
|
||||||
|
for (uint8_t i = 0; i < 1; i++) {
|
||||||
|
system_sleep();
|
||||||
|
}
|
||||||
|
// f_wdt is set to 1 by the WDT ISR when the 8 s expire; no manual clear needed here.
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Sleep helpers
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
void system_sleep()
|
||||||
|
{
|
||||||
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||||
|
sleep_enable();
|
||||||
|
sleep_mode(); // CPU halts here until WDT fires
|
||||||
|
sleep_disable();
|
||||||
|
// ADC stays off - re-enable it in loop() only when sensing
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watchdog setup - ii selects timeout:
|
||||||
|
// 0=16ms 1=32ms 2=64ms 3=128ms 4=250ms 5=500ms
|
||||||
|
// 6=1s 7=2s 8=4s 9=8s
|
||||||
|
void setup_watchdog(int ii)
|
||||||
|
{
|
||||||
|
byte bb;
|
||||||
|
if (ii > 9) ii = 9;
|
||||||
|
bb = ii & 7;
|
||||||
|
if (ii > 7) bb |= (1 << 5);
|
||||||
|
bb |= (1 << WDCE);
|
||||||
|
|
||||||
|
MCUSR &= ~(1 << WDRF);
|
||||||
|
WDTCR |= (1 << WDCE) | (1 << WDE); // timed sequence - must not be split
|
||||||
|
WDTCR = bb;
|
||||||
|
WDTCR |= _BV(WDIE);
|
||||||
|
}
|
||||||
|
|
||||||
|
ISR(WDT_vect)
|
||||||
|
{
|
||||||
|
f_wdt = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Light sensor
|
||||||
|
// ===========================================================================
|
||||||
|
// Returns a "darkness level": higher = darker.
|
||||||
|
// Charges the LED junction capacitor, then times how long it takes to bleed
|
||||||
|
// back through the reverse-biased diode to a logic LOW.
|
||||||
|
// ~30000 = pitch black, ~0 = bright light
|
||||||
|
|
||||||
|
// return type is unsigned int (counter can reach 30000, fits in 16 bits)
|
||||||
|
unsigned int sensDarkness(int LED_N, int LED_P)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
// Charge the LED (forward-bias momentarily)
|
||||||
|
pinMode(LED_N, OUTPUT);
|
||||||
|
pinMode(LED_P, OUTPUT);
|
||||||
|
digitalWrite(LED_N, HIGH);
|
||||||
|
digitalWrite(LED_P, LOW);
|
||||||
|
|
||||||
|
// Let the N-end float and measure bleed-down time
|
||||||
|
pinMode(LED_N, INPUT);
|
||||||
|
digitalWrite(LED_N, LOW); // disable internal pull-up
|
||||||
|
|
||||||
|
for (i = 0; i < 30000; i++) {
|
||||||
|
if (digitalRead(LED_N) == 0) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up after sensing
|
||||||
|
pinMode(LED_N, OUTPUT);
|
||||||
|
digitalWrite(LED_N, LOW);
|
||||||
|
pinMode(LED_P, OUTPUT);
|
||||||
|
digitalWrite(LED_P, LOW);
|
||||||
|
return i;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Morse helpers
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
void dit(int LED_P)
|
||||||
|
{
|
||||||
|
digitalWrite(LED_P, HIGH); delay(unitLength);
|
||||||
|
digitalWrite(LED_P, LOW); delay(unitLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dah(int LED_P)
|
||||||
|
{
|
||||||
|
digitalWrite(LED_P, HIGH); delay(unitLength * 3);
|
||||||
|
digitalWrite(LED_P, LOW); delay(unitLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Morse sender
|
||||||
|
// ===========================================================================
|
||||||
|
void morse(int LED_N, int LED_P)
|
||||||
|
{
|
||||||
|
pinMode(LED_N, OUTPUT);
|
||||||
|
pinMode(LED_P, OUTPUT);
|
||||||
|
digitalWrite(LED_N, LOW);
|
||||||
|
|
||||||
|
// read each character from flash with pgm_read_byte()
|
||||||
|
// use < morseText length, not <= (avoids reading past the null terminator)
|
||||||
|
uint8_t len = strlen_P(morseText);
|
||||||
|
for (uint8_t i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
char c = (char)pgm_read_byte(&morseText[i]);
|
||||||
|
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
// ----- Letters -----
|
||||||
|
case 'A': dit(LED_P); dah(LED_P); break; // .-
|
||||||
|
case 'B': dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); break; // -...
|
||||||
|
case 'C': dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); break; // -.-.
|
||||||
|
case 'D': dah(LED_P); dit(LED_P); dit(LED_P); break; // -..
|
||||||
|
case 'E': dit(LED_P); break; // .
|
||||||
|
case 'F': dit(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); break; // ..-.
|
||||||
|
case 'G': dah(LED_P); dah(LED_P); dit(LED_P); break; // --.
|
||||||
|
case 'H': dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); break; // ....
|
||||||
|
case 'I': dit(LED_P); dit(LED_P); break; // ..
|
||||||
|
case 'J': dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); break; // .---
|
||||||
|
case 'K': dah(LED_P); dit(LED_P); dah(LED_P); break; // -.-
|
||||||
|
case 'L': dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); break; // .-..
|
||||||
|
case 'M': dah(LED_P); dah(LED_P); break; // --
|
||||||
|
case 'N': dah(LED_P); dit(LED_P); break; // -.
|
||||||
|
case 'O': dah(LED_P); dah(LED_P); dah(LED_P); break; // ---
|
||||||
|
case 'P': dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); break; // .--.
|
||||||
|
case 'Q': dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); break; // --.-
|
||||||
|
case 'R': dit(LED_P); dah(LED_P); dit(LED_P); break; // .-.
|
||||||
|
case 'S': dit(LED_P); dit(LED_P); dit(LED_P); break; // ...
|
||||||
|
case 'T': dah(LED_P); break; // -
|
||||||
|
case 'U': dit(LED_P); dit(LED_P); dah(LED_P); break; // ..-
|
||||||
|
case 'V': dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); break; // ...-
|
||||||
|
case 'W': dit(LED_P); dah(LED_P); dah(LED_P); break; // .--
|
||||||
|
case 'X': dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); break; // -..-
|
||||||
|
case 'Y': dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); break; // -.--
|
||||||
|
case 'Z': dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); break; // --..
|
||||||
|
|
||||||
|
// ----- Digits -----
|
||||||
|
case '1': dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); break; // .----
|
||||||
|
case '2': dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); break; // ..---
|
||||||
|
case '3': dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); break; // ...--
|
||||||
|
case '4': dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); break; // ....-
|
||||||
|
case '5': dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); break; // .....
|
||||||
|
case '6': dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); break; // -....
|
||||||
|
case '7': dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); break; // --...
|
||||||
|
case '8': dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); break; // ---.
|
||||||
|
case '9': dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); break; // ----.
|
||||||
|
case '0': dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); dah(LED_P); break; // -----
|
||||||
|
|
||||||
|
// ----- Punctuation -----
|
||||||
|
case ' ': delay(unitLength * 5); break; // word gap (5 + 3 = 8 units total)
|
||||||
|
case '.': dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); break; // .-.-.-
|
||||||
|
case ',': dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); break; // --..--
|
||||||
|
case '?': dit(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); break; // ..--..
|
||||||
|
case '!': dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); break; // -.-.--
|
||||||
|
case ':': dah(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); break; // ---...
|
||||||
|
case ';': dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); break; // -.-.-.
|
||||||
|
case '(': dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); break; // -.--.
|
||||||
|
case ')': dah(LED_P); dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); break; // -.--.-
|
||||||
|
case '"': dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); break; // .-..-.
|
||||||
|
case '@': dit(LED_P); dah(LED_P); dah(LED_P); dit(LED_P); dah(LED_P); dit(LED_P); break; // .--.-.
|
||||||
|
case '&': dit(LED_P); dah(LED_P); dit(LED_P); dit(LED_P); dit(LED_P); break; // .-...
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inter-character gap: 3 units (the dit/dah functions already trail 1 unit,
|
||||||
|
// so this adds the remaining 2 to reach the standard 3-unit gap).
|
||||||
|
// Space case already handles word gap - no extra delay needed there.
|
||||||
|
if (c != ' ') {
|
||||||
|
delay(unitLength * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Submodule
+1
Submodule hardware/0001_FireFly-Solar/.history added at 1c8d4d7c5c.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,5 @@
|
|||||||
|
(version 1)
|
||||||
|
(rule Minimum_text_height_and_thickness_1
|
||||||
|
(constraint text_height (min 0.6mm))
|
||||||
|
(constraint text_thickness (min 0.1mm))
|
||||||
|
)
|
||||||
File diff suppressed because it is too large.
Load diff
Binary file not shown.
@@ -67,8 +67,30 @@
|
|||||||
"version": 5
|
"version": 5
|
||||||
},
|
},
|
||||||
"net_inspector_panel": {
|
"net_inspector_panel": {
|
||||||
"col_hidden": [],
|
"col_hidden": [
|
||||||
"col_order": [],
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"col_order": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
8,
|
||||||
|
9
|
||||||
|
],
|
||||||
"col_widths": [],
|
"col_widths": [],
|
||||||
"custom_group_rules": [],
|
"custom_group_rules": [],
|
||||||
"expanded_rows": [],
|
"expanded_rows": [],
|
||||||
|
|||||||
@@ -1,6 +1,377 @@
|
|||||||
{
|
{
|
||||||
"board": {
|
"board": {
|
||||||
"3dviewports": [],
|
"3dviewports": [],
|
||||||
|
"design_settings": {
|
||||||
|
"defaults": {
|
||||||
|
"apply_defaults_to_fp_barcodes": false,
|
||||||
|
"apply_defaults_to_fp_dimensions": false,
|
||||||
|
"apply_defaults_to_fp_fields": false,
|
||||||
|
"apply_defaults_to_fp_shapes": false,
|
||||||
|
"apply_defaults_to_fp_text": false,
|
||||||
|
"board_outline_line_width": 0.05,
|
||||||
|
"copper_line_width": 0.2,
|
||||||
|
"copper_text_italic": false,
|
||||||
|
"copper_text_size_h": 1.5,
|
||||||
|
"copper_text_size_v": 1.5,
|
||||||
|
"copper_text_thickness": 0.3,
|
||||||
|
"copper_text_upright": false,
|
||||||
|
"courtyard_line_width": 0.05,
|
||||||
|
"dimension_precision": 4,
|
||||||
|
"dimension_units": 3,
|
||||||
|
"dimensions": {
|
||||||
|
"arrow_length": 1270000,
|
||||||
|
"extension_offset": 500000,
|
||||||
|
"keep_text_aligned": true,
|
||||||
|
"suppress_zeroes": true,
|
||||||
|
"text_position": 0,
|
||||||
|
"units_format": 0
|
||||||
|
},
|
||||||
|
"fab_line_width": 0.1,
|
||||||
|
"fab_text_italic": false,
|
||||||
|
"fab_text_size_h": 1.0,
|
||||||
|
"fab_text_size_v": 1.0,
|
||||||
|
"fab_text_thickness": 0.15,
|
||||||
|
"fab_text_upright": false,
|
||||||
|
"other_line_width": 0.1,
|
||||||
|
"other_text_italic": false,
|
||||||
|
"other_text_size_h": 1.0,
|
||||||
|
"other_text_size_v": 1.0,
|
||||||
|
"other_text_thickness": 0.15,
|
||||||
|
"other_text_upright": false,
|
||||||
|
"pads": {
|
||||||
|
"drill": 0.7,
|
||||||
|
"height": 1.4,
|
||||||
|
"width": 1.4
|
||||||
|
},
|
||||||
|
"silk_line_width": 0.1,
|
||||||
|
"silk_text_italic": false,
|
||||||
|
"silk_text_size_h": 1.0,
|
||||||
|
"silk_text_size_v": 1.0,
|
||||||
|
"silk_text_thickness": 0.1,
|
||||||
|
"silk_text_upright": false,
|
||||||
|
"zones": {
|
||||||
|
"border_display_style": 2,
|
||||||
|
"border_hatch_pitch": 0.5,
|
||||||
|
"corner_radius": 0.0,
|
||||||
|
"corner_smoothing": 0,
|
||||||
|
"fill_mode": 0,
|
||||||
|
"hatch_gap": 1.5,
|
||||||
|
"hatch_orientation": 0.0,
|
||||||
|
"hatch_smoothing_level": 0,
|
||||||
|
"hatch_smoothing_value": 0.1,
|
||||||
|
"hatch_thickness": 1.0,
|
||||||
|
"min_clearance": 0.5,
|
||||||
|
"min_island_area": 10.0,
|
||||||
|
"min_thickness": 0.25,
|
||||||
|
"pad_connection": 1,
|
||||||
|
"remove_islands": 0,
|
||||||
|
"thermal_relief_gap": 0.5,
|
||||||
|
"thermal_relief_spoke_width": 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"diff_pair_dimensions": [
|
||||||
|
{
|
||||||
|
"gap": 0.0,
|
||||||
|
"via_gap": 0.0,
|
||||||
|
"width": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"drc_exclusions": [
|
||||||
|
[
|
||||||
|
"copper_edge_clearance|131952996|49276030|d5a8d279-1277-4128-8111-479753d97832|74ba0eb0-a42b-49db-8990-e7e337244d89",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"copper_edge_clearance|135254996|49276021|d5a8d279-1277-4128-8111-479753d97832|6760cf2b-9d51-44e9-8541-9b08cc6bed3a",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"copper_edge_clearance|138556996|49276011|d5a8d279-1277-4128-8111-479753d97832|5b16be99-1a09-4cc0-a4d6-0e5fae921e96",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"footprint_symbol_mismatch|128778000|155702000|4cb9c5b1-91d7-410e-abfd-4524e164235e|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"footprint_symbol_mismatch|128905000|50800000|83fa7340-1a82-4ee7-beb0-b053e75572c6|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"footprint_symbol_mismatch|130048000|64008000|3f35818a-3121-4da6-816f-074ff65fe175|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"footprint_symbol_mismatch|131445000|147955000|cc936a59-684b-4981-b7a8-14760437093b|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"footprint_symbol_mismatch|131572000|67564000|edc602aa-88c4-47cd-93b6-01e8bf071e1a|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"footprint_symbol_mismatch|133475349|116205000|9bd3cac8-ff58-4d4a-a460-8e1c2b9fd035|00000000-0000-0000-0000-000000000000",
|
||||||
|
"50F minimal"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"footprint_symbol_mismatch|137701000|99568000|93ada756-676f-4bd9-be40-1a2b9ae83693|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"footprint_symbol_mismatch|141224000|106299000|1c4f319b-6195-4cfd-9c26-3783c66451a4|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"footprint_symbol_mismatch|141605000|50800000|faca4083-4c71-4e8e-9842-9023fcfe2020|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_height|141224000|109999000|18cc7c24-1e1e-47b4-bd81-2abaec8c3831|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|129032000|122936000|57275588-b94d-44a7-b263-ab2e942abbb9|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|129032000|126746000|ac73bb6c-6b4c-4683-bc6b-c539afa6834f|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|129032000|130556000|dfc1c2d5-221c-45bf-8f4f-92d8a3ddf04a|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|129032000|134366000|e9026731-8c2e-491a-9b73-4cda2fa80a02|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|129032000|138176000|cf4eeb69-e06f-463f-be45-22626ac2c2b4|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|129032000|141986000|c29774df-bdb9-44e2-a17d-cfa3cd693d71|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|129184400|150241000|88401ad0-1dea-4344-ab33-329fd06bd493|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|135148000|106152000|7c4ccd6a-1b52-4734-bd95-092ada163852|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|135382000|84709000|88b0a397-e4a2-4145-86f6-0fa60b4a4bd9|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|141224000|109999000|18cc7c24-1e1e-47b4-bd81-2abaec8c3831|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|141325600|150144800|5da7a07f-f063-43b3-a149-e672965b459b|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|141478000|122936000|77ee43fb-9700-42ab-8702-560042b5edd3|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|141478000|126746000|833d2a60-9010-4cb2-8045-b8050c8ee771|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|141478000|130556000|5a756363-e24d-4c12-82cf-59fc84a986ae|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|141478000|134366000|685c5797-de13-4bd0-a3b9-5fb75b4b9dad|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|141478000|138176000|15931c23-e5f6-4be3-ba3e-7a720c730e9b|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"text_thickness|141478000|141986000|e240b788-8dbb-43c9-8c9f-717e069201be|00000000-0000-0000-0000-000000000000",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"version": 2
|
||||||
|
},
|
||||||
|
"rule_severities": {
|
||||||
|
"annular_width": "error",
|
||||||
|
"clearance": "error",
|
||||||
|
"connection_width": "warning",
|
||||||
|
"copper_edge_clearance": "error",
|
||||||
|
"copper_sliver": "warning",
|
||||||
|
"courtyards_overlap": "error",
|
||||||
|
"creepage": "error",
|
||||||
|
"diff_pair_gap_out_of_range": "error",
|
||||||
|
"diff_pair_uncoupled_length_too_long": "error",
|
||||||
|
"drill_out_of_range": "error",
|
||||||
|
"duplicate_footprints": "warning",
|
||||||
|
"extra_footprint": "warning",
|
||||||
|
"footprint": "error",
|
||||||
|
"footprint_filters_mismatch": "ignore",
|
||||||
|
"footprint_symbol_field_mismatch": "warning",
|
||||||
|
"footprint_symbol_mismatch": "warning",
|
||||||
|
"footprint_type_mismatch": "ignore",
|
||||||
|
"hole_clearance": "error",
|
||||||
|
"hole_to_hole": "warning",
|
||||||
|
"holes_co_located": "warning",
|
||||||
|
"invalid_outline": "error",
|
||||||
|
"isolated_copper": "warning",
|
||||||
|
"item_on_disabled_layer": "error",
|
||||||
|
"items_not_allowed": "error",
|
||||||
|
"length_out_of_range": "error",
|
||||||
|
"lib_footprint_issues": "warning",
|
||||||
|
"lib_footprint_mismatch": "warning",
|
||||||
|
"malformed_courtyard": "error",
|
||||||
|
"microvia_drill_out_of_range": "error",
|
||||||
|
"mirrored_text_on_front_layer": "warning",
|
||||||
|
"missing_courtyard": "ignore",
|
||||||
|
"missing_footprint": "warning",
|
||||||
|
"missing_tuning_profile": "warning",
|
||||||
|
"net_conflict": "warning",
|
||||||
|
"nonmirrored_text_on_back_layer": "warning",
|
||||||
|
"npth_inside_courtyard": "error",
|
||||||
|
"padstack": "warning",
|
||||||
|
"pth_inside_courtyard": "error",
|
||||||
|
"shorting_items": "error",
|
||||||
|
"silk_edge_clearance": "ignore",
|
||||||
|
"silk_over_copper": "warning",
|
||||||
|
"silk_overlap": "ignore",
|
||||||
|
"skew_out_of_range": "error",
|
||||||
|
"solder_mask_bridge": "error",
|
||||||
|
"starved_thermal": "ignore",
|
||||||
|
"text_height": "warning",
|
||||||
|
"text_on_edge_cuts": "error",
|
||||||
|
"text_thickness": "warning",
|
||||||
|
"through_hole_pad_without_hole": "error",
|
||||||
|
"too_many_vias": "error",
|
||||||
|
"track_angle": "error",
|
||||||
|
"track_dangling": "warning",
|
||||||
|
"track_not_centered_on_via": "ignore",
|
||||||
|
"track_on_post_machined_layer": "error",
|
||||||
|
"track_segment_length": "error",
|
||||||
|
"track_width": "error",
|
||||||
|
"tracks_crossing": "error",
|
||||||
|
"tuning_profile_track_geometries": "ignore",
|
||||||
|
"unconnected_items": "error",
|
||||||
|
"unresolved_variable": "error",
|
||||||
|
"via_dangling": "warning",
|
||||||
|
"zones_intersect": "error"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"max_error": 0.005,
|
||||||
|
"min_clearance": 0.0,
|
||||||
|
"min_connection": 0.0,
|
||||||
|
"min_copper_edge_clearance": 0.5,
|
||||||
|
"min_groove_width": 0.0,
|
||||||
|
"min_hole_clearance": 0.25,
|
||||||
|
"min_hole_to_hole": 0.25,
|
||||||
|
"min_microvia_diameter": 0.2,
|
||||||
|
"min_microvia_drill": 0.1,
|
||||||
|
"min_resolved_spokes": 2,
|
||||||
|
"min_silk_clearance": 0.0,
|
||||||
|
"min_text_height": 0.8,
|
||||||
|
"min_text_thickness": 0.08,
|
||||||
|
"min_through_hole_diameter": 0.3,
|
||||||
|
"min_track_width": 0.2,
|
||||||
|
"min_via_annular_width": 0.1,
|
||||||
|
"min_via_diameter": 0.5,
|
||||||
|
"solder_mask_to_copper_clearance": 0.0,
|
||||||
|
"use_height_for_length_calcs": true
|
||||||
|
},
|
||||||
|
"teardrop_options": [
|
||||||
|
{
|
||||||
|
"td_onpthpad": true,
|
||||||
|
"td_onroundshapesonly": false,
|
||||||
|
"td_onsmdpad": true,
|
||||||
|
"td_ontrackend": false,
|
||||||
|
"td_onvia": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"teardrop_parameters": [
|
||||||
|
{
|
||||||
|
"td_allow_use_two_tracks": true,
|
||||||
|
"td_curve_segcount": 0,
|
||||||
|
"td_height_ratio": 1.0,
|
||||||
|
"td_length_ratio": 0.5,
|
||||||
|
"td_maxheight": 2.0,
|
||||||
|
"td_maxlen": 1.0,
|
||||||
|
"td_on_pad_in_zone": false,
|
||||||
|
"td_target_name": "td_round_shape",
|
||||||
|
"td_width_to_size_filter_ratio": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"td_allow_use_two_tracks": true,
|
||||||
|
"td_curve_segcount": 0,
|
||||||
|
"td_height_ratio": 1.0,
|
||||||
|
"td_length_ratio": 0.5,
|
||||||
|
"td_maxheight": 2.0,
|
||||||
|
"td_maxlen": 1.0,
|
||||||
|
"td_on_pad_in_zone": false,
|
||||||
|
"td_target_name": "td_rect_shape",
|
||||||
|
"td_width_to_size_filter_ratio": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"td_allow_use_two_tracks": true,
|
||||||
|
"td_curve_segcount": 0,
|
||||||
|
"td_height_ratio": 1.0,
|
||||||
|
"td_length_ratio": 0.5,
|
||||||
|
"td_maxheight": 2.0,
|
||||||
|
"td_maxlen": 1.0,
|
||||||
|
"td_on_pad_in_zone": false,
|
||||||
|
"td_target_name": "td_track_end",
|
||||||
|
"td_width_to_size_filter_ratio": 0.9
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"track_widths": [
|
||||||
|
0.0,
|
||||||
|
0.127,
|
||||||
|
0.254,
|
||||||
|
0.508,
|
||||||
|
0.635,
|
||||||
|
1.27
|
||||||
|
],
|
||||||
|
"tuning_pattern_settings": {
|
||||||
|
"diff_pair_defaults": {
|
||||||
|
"corner_radius_percentage": 80,
|
||||||
|
"corner_style": 1,
|
||||||
|
"max_amplitude": 1.0,
|
||||||
|
"min_amplitude": 0.2,
|
||||||
|
"single_sided": false,
|
||||||
|
"spacing": 1.0
|
||||||
|
},
|
||||||
|
"diff_pair_skew_defaults": {
|
||||||
|
"corner_radius_percentage": 80,
|
||||||
|
"corner_style": 1,
|
||||||
|
"max_amplitude": 1.0,
|
||||||
|
"min_amplitude": 0.2,
|
||||||
|
"single_sided": false,
|
||||||
|
"spacing": 0.6
|
||||||
|
},
|
||||||
|
"single_track_defaults": {
|
||||||
|
"corner_radius_percentage": 80,
|
||||||
|
"corner_style": 1,
|
||||||
|
"max_amplitude": 1.0,
|
||||||
|
"min_amplitude": 0.2,
|
||||||
|
"single_sided": false,
|
||||||
|
"spacing": 0.6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"via_dimensions": [
|
||||||
|
{
|
||||||
|
"diameter": 0.0,
|
||||||
|
"drill": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"zones_allow_external_fillets": false
|
||||||
|
},
|
||||||
"ipc2581": {
|
"ipc2581": {
|
||||||
"bom_rev": "",
|
"bom_rev": "",
|
||||||
"dist": "",
|
"dist": "",
|
||||||
@@ -8,7 +379,7 @@
|
|||||||
"internal_id": "",
|
"internal_id": "",
|
||||||
"mfg": "",
|
"mfg": "",
|
||||||
"mpn": "",
|
"mpn": "",
|
||||||
"sch_revision": "0"
|
"sch_revision": "3"
|
||||||
},
|
},
|
||||||
"layer_pairs": [],
|
"layer_pairs": [],
|
||||||
"layer_presets": [],
|
"layer_presets": [],
|
||||||
@@ -294,6 +665,7 @@
|
|||||||
"netlist": "",
|
"netlist": "",
|
||||||
"plot": "",
|
"plot": "",
|
||||||
"specctra_dsn": "",
|
"specctra_dsn": "",
|
||||||
|
"step": "",
|
||||||
"vrml": ""
|
"vrml": ""
|
||||||
},
|
},
|
||||||
"page_layout_descr_file": ""
|
"page_layout_descr_file": ""
|
||||||
@@ -416,7 +788,7 @@
|
|||||||
"uuid": "d6486f66-f0ec-4484-bce4-0ec6b0e57e1d"
|
"uuid": "d6486f66-f0ec-4484-bce4-0ec6b0e57e1d"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"used_designators": "",
|
"used_designators": "J1-2",
|
||||||
"variants": []
|
"variants": []
|
||||||
},
|
},
|
||||||
"sheets": [
|
"sheets": [
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Binary file not shown.
@@ -0,0 +1,26 @@
|
|||||||
|
*
|
||||||
|
|
||||||
|
.subckt 0001_FireFly-Solar
|
||||||
|
|
||||||
|
|
||||||
|
J2 __J2
|
||||||
|
D2 __D2
|
||||||
|
D3 __D3
|
||||||
|
J3 __J3
|
||||||
|
R1 Net-_D1-K_ Net-_D4-K_ 33
|
||||||
|
D1 __D1
|
||||||
|
J1 __J1
|
||||||
|
J4 __J4
|
||||||
|
D5 __D5
|
||||||
|
U1 __U1
|
||||||
|
L1 Net-_D4-K_ Net-_D5-A_ 22u
|
||||||
|
D4 __D4
|
||||||
|
C1 Net-_D4-K_ GND 50F SuperCap
|
||||||
|
C2 Net-_D4-K_ GND 150u
|
||||||
|
C3 Net-_D5-K_ GND 100u
|
||||||
|
C4 Net-_U1-REF_ GND 0.1u
|
||||||
|
U2 __U2
|
||||||
|
D6 Net-_D6-K_ Net-_D6-A_ LED
|
||||||
|
R2 Net-_U2-XTAL1/PB3_ Net-_D6-K_ R
|
||||||
|
|
||||||
|
.ends
|
||||||
Binary file not shown.
@@ -1,288 +0,0 @@
|
|||||||
/*
|
|
||||||
FireFly tetrahedron adapter
|
|
||||||
Base STL: tetrahedron40mmC.stl
|
|
||||||
|
|
||||||
Purpose:
|
|
||||||
- Import original 40 mm foldable tetrahedron STL
|
|
||||||
- Scale it to 60 mm base geometry
|
|
||||||
- Add cable holes, vent/drain holes, and optional PCB mounting holes
|
|
||||||
|
|
||||||
Workflow:
|
|
||||||
1. Set PART = "upper"
|
|
||||||
2. Render with F6
|
|
||||||
3. Export STL
|
|
||||||
4. Set PART = "lower"
|
|
||||||
5. Render with F6
|
|
||||||
6. Export STL
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Hole coordinates are defined on the original 40 mm STL coordinate system.
|
|
||||||
- The script scales them automatically to 60 mm.
|
|
||||||
- First export should be treated as v0.1 prototype.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
Main selection
|
|
||||||
========================= */
|
|
||||||
|
|
||||||
PART = "upper"; // "upper", "lower", or "both_preview"
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
Input file
|
|
||||||
========================= */
|
|
||||||
|
|
||||||
input_stl = "tetrahedron40mmC.stl";
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
Scaling
|
|
||||||
========================= */
|
|
||||||
|
|
||||||
// Original tetrahedron edge length
|
|
||||||
original_edge_mm = 40;
|
|
||||||
|
|
||||||
// Target mechanical base size
|
|
||||||
target_edge_mm = 60;
|
|
||||||
|
|
||||||
// XY scale from 40 mm to 60 mm
|
|
||||||
scale_xy = target_edge_mm / original_edge_mm; // 1.5
|
|
||||||
|
|
||||||
// Keep original thickness.
|
|
||||||
// Set to 1.2 or 1.3 if you want a slightly thicker part.
|
|
||||||
scale_z = 1.0;
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
General hole settings
|
|
||||||
========================= */
|
|
||||||
|
|
||||||
hole_fn = 40;
|
|
||||||
cut_height = 20;
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
Feature switches
|
|
||||||
========================= */
|
|
||||||
|
|
||||||
add_upper_cable_holes = true;
|
|
||||||
add_upper_hanger_hole = true;
|
|
||||||
|
|
||||||
add_lower_vent_holes = true;
|
|
||||||
add_lower_cable_holes = true;
|
|
||||||
add_lower_pcb_mount_holes = true;
|
|
||||||
|
|
||||||
// Keep this false for the first prototype.
|
|
||||||
// Standoffs may interfere with folding depending on print orientation.
|
|
||||||
add_lower_pcb_standoffs = false;
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
Hole sizes
|
|
||||||
========================= */
|
|
||||||
|
|
||||||
cable_hole_d = 3.2; // for small solar-panel wires
|
|
||||||
hanger_hole_d = 3.5; // for small ring / wire / eyelet
|
|
||||||
vent_hole_d = 2.0; // condensation vent / drain holes
|
|
||||||
pcb_screw_hole_d = 2.2; // M2 clearance-ish
|
|
||||||
pcb_standoff_d = 6.0;
|
|
||||||
pcb_standoff_h = 3.0;
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
Coordinate reference
|
|
||||||
=========================
|
|
||||||
|
|
||||||
The uploaded STL is approximately:
|
|
||||||
|
|
||||||
X: -34.64 to +34.64
|
|
||||||
Y: -20.00 to +60.00
|
|
||||||
Z: -0.10 to +2.00
|
|
||||||
|
|
||||||
The original model is a flat foldable tetrahedron net.
|
|
||||||
Coordinates below are before scaling.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
Upper part hole positions
|
|
||||||
========================= */
|
|
||||||
|
|
||||||
// Cable holes for three solar panels.
|
|
||||||
// Adjust after checking where your real panel wires exit.
|
|
||||||
upper_cable_points = [
|
|
||||||
[-18, 20],
|
|
||||||
[ 24, 7],
|
|
||||||
[ 24, 33]
|
|
||||||
];
|
|
||||||
|
|
||||||
// Optional hanger hole close to one upper fold/vertex area.
|
|
||||||
// If this weakens the corner too much, disable it and use an external loop instead.
|
|
||||||
upper_hanger_points = [
|
|
||||||
[0, 38]
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
Lower part hole positions
|
|
||||||
========================= */
|
|
||||||
|
|
||||||
// Cable transfer holes between upper solar section and lower electronics bay.
|
|
||||||
lower_cable_points = [
|
|
||||||
[-10, 20],
|
|
||||||
[ 12, 13],
|
|
||||||
[ 12, 27]
|
|
||||||
];
|
|
||||||
|
|
||||||
// Small vent/drain hole clusters near likely lower/edge areas.
|
|
||||||
// These are deliberately small.
|
|
||||||
lower_vent_centres = [
|
|
||||||
[ 0, 3],
|
|
||||||
[ 0, 37],
|
|
||||||
[-30, 20],
|
|
||||||
[ 31, 20]
|
|
||||||
];
|
|
||||||
|
|
||||||
// Approximate triangular PCB mounting pattern.
|
|
||||||
// Tune this after measuring your actual red triangle PCB.
|
|
||||||
lower_pcb_mount_points = [
|
|
||||||
[ 0, 8],
|
|
||||||
[ 25, 20],
|
|
||||||
[ 0, 32]
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
Basic modules
|
|
||||||
========================= */
|
|
||||||
|
|
||||||
module base_import_scaled()
|
|
||||||
{
|
|
||||||
scale([scale_xy, scale_xy, scale_z])
|
|
||||||
import(input_stl, convexity = 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module vertical_hole(p, d)
|
|
||||||
{
|
|
||||||
translate([p[0] * scale_xy, p[1] * scale_xy, 1])
|
|
||||||
cylinder(h = cut_height, d = d, center = true, $fn = hole_fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module vent_cluster(p)
|
|
||||||
{
|
|
||||||
// Small 2 x 3 vent/drain pattern.
|
|
||||||
// Pitch is in final millimetres, not original model coordinates.
|
|
||||||
pitch = 3.0;
|
|
||||||
|
|
||||||
for (ix = [-1, 0, 1])
|
|
||||||
for (iy = [0, 1])
|
|
||||||
translate([
|
|
||||||
p[0] * scale_xy + ix * pitch,
|
|
||||||
p[1] * scale_xy + iy * pitch,
|
|
||||||
1
|
|
||||||
])
|
|
||||||
cylinder(h = cut_height, d = vent_hole_d, center = true, $fn = hole_fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module pcb_standoff(p)
|
|
||||||
{
|
|
||||||
// Standoff added on top of the flat STL.
|
|
||||||
// Use only if you confirm it does not block folding.
|
|
||||||
translate([p[0] * scale_xy, p[1] * scale_xy, 2.1 * scale_z])
|
|
||||||
cylinder(h = pcb_standoff_h, d = pcb_standoff_d, center = false, $fn = hole_fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
Upper tetrahedron
|
|
||||||
========================= */
|
|
||||||
|
|
||||||
module upper_part()
|
|
||||||
{
|
|
||||||
difference()
|
|
||||||
{
|
|
||||||
base_import_scaled();
|
|
||||||
|
|
||||||
if (add_upper_cable_holes)
|
|
||||||
{
|
|
||||||
for (p = upper_cable_points)
|
|
||||||
vertical_hole(p, cable_hole_d);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (add_upper_hanger_hole)
|
|
||||||
{
|
|
||||||
for (p = upper_hanger_points)
|
|
||||||
vertical_hole(p, hanger_hole_d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
Lower tetrahedron
|
|
||||||
========================= */
|
|
||||||
|
|
||||||
module lower_part()
|
|
||||||
{
|
|
||||||
difference()
|
|
||||||
{
|
|
||||||
union()
|
|
||||||
{
|
|
||||||
base_import_scaled();
|
|
||||||
|
|
||||||
if (add_lower_pcb_standoffs)
|
|
||||||
{
|
|
||||||
for (p = lower_pcb_mount_points)
|
|
||||||
pcb_standoff(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (add_lower_cable_holes)
|
|
||||||
{
|
|
||||||
for (p = lower_cable_points)
|
|
||||||
vertical_hole(p, cable_hole_d);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (add_lower_vent_holes)
|
|
||||||
{
|
|
||||||
for (p = lower_vent_centres)
|
|
||||||
vent_cluster(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (add_lower_pcb_mount_holes)
|
|
||||||
{
|
|
||||||
for (p = lower_pcb_mount_points)
|
|
||||||
vertical_hole(p, pcb_screw_hole_d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
Output
|
|
||||||
========================= */
|
|
||||||
|
|
||||||
if (PART == "upper")
|
|
||||||
{
|
|
||||||
upper_part();
|
|
||||||
}
|
|
||||||
else if (PART == "lower")
|
|
||||||
{
|
|
||||||
lower_part();
|
|
||||||
}
|
|
||||||
else if (PART == "both_preview")
|
|
||||||
{
|
|
||||||
translate([-80, 0, 0])
|
|
||||||
upper_part();
|
|
||||||
|
|
||||||
translate([80, 0, 0])
|
|
||||||
lower_part();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
echo("Invalid PART setting. Use upper, lower, or both_preview.");
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user