Last active
April 17, 2022 23:45
-
-
Save abfo/4e8c12f48d65e8d8e6113172c9729428 to your computer and use it in GitHub Desktop.
Code to run a long term time lapse on an Arduino. See https://ithoughthecamewithyou.com/post/long-term-solar-powered-time-lapse-camera-using-arduino
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Wire.h> | |
#include <ArduCAM.h> | |
#include <SPI.h> | |
#include <SD.h> | |
#include <avr/power.h> | |
#include <avr/sleep.h> | |
#include "memorysaver.h" | |
//#define USE_SERIAL | |
const int CS = 10; // chip select pin for camera | |
const int SD_CS = 7; // chip select pin for SD reader | |
const int MaxWait = 100; // how long to wait during initialization | |
ArduCAM myCAM(OV5642, CS); | |
int filenum = 1; | |
uint8_t read_fifo_burst(ArduCAM myCAM); | |
void setup() { | |
uint8_t temp; | |
uint8_t vid, pid; | |
char fnbuf[15]; | |
int w = 0; | |
// configure minimal power usage | |
lowPowerSetup(); | |
// internal LED on during setup | |
digitalWrite(LED_BUILTIN, HIGH); | |
// start SPI bus | |
Wire.begin(); | |
// serial for debug messages, comment USE_SERIAL out above for release | |
#if defined USE_SERIAL | |
Serial.begin(115200); | |
while(!Serial) {} | |
sMessage(F("Serial connected.")); | |
#endif | |
// set camera chip select pin HIGH | |
pinMode(CS, OUTPUT); | |
digitalWrite(CS, HIGH); | |
// start SPI | |
SPI.begin(); | |
delay(100); | |
// reset CPLD | |
myCAM.write_reg(0x07, 0x80); | |
delay(100); | |
myCAM.write_reg(0x07, 0x00); | |
delay(100); | |
// wait for ArduCAM SPI | |
w = 0; | |
while(w < MaxWait) { | |
myCAM.write_reg(ARDUCHIP_TEST1, 0x55); | |
temp = myCAM.read_reg(ARDUCHIP_TEST1); | |
if (temp != 0x55){ | |
sMessage(F("Waiting for ArduCAM on SPI bus.")); | |
delayWithFlash(); | |
} else { | |
sMessage(F("Found ArduCAM on SPI bus.")); | |
break; | |
} | |
w++; | |
if (w >= MaxWait) { | |
sMessage(F("Waited too long, restarting.")); | |
delay(100); | |
reset(); | |
} | |
} | |
// detect ArduCAM | |
w = 0; | |
while(w < MaxWait) { | |
myCAM.wrSensorReg16_8(0xff, 0x01); | |
myCAM.rdSensorReg16_8(OV5642_CHIPID_HIGH, &vid); | |
myCAM.rdSensorReg16_8(OV5642_CHIPID_LOW, &pid); | |
if((vid != 0x56) || (pid != 0x42)){ | |
sMessage(F("Waiting to detect ArduCAM as OV5642.")); | |
delayWithFlash(); | |
} else{ | |
sMessage(F("Detected ArduCAM as OV5642.")); | |
break; | |
} | |
w++; | |
if (w >= MaxWait) { | |
sMessage(F("Waited too long, restarting.")); | |
delay(100); | |
reset(); | |
} | |
} | |
sMessage(F("Initializing ArduCAM OV5642.")); | |
myCAM.set_format(JPEG); | |
myCAM.InitCAM(); | |
myCAM.write_reg(ARDUCHIP_TIM, VSYNC_LEVEL_MASK); | |
sMessage(F("Size 2592x1944")); | |
myCAM.OV5642_set_JPEG_size(OV5642_2592x1944); | |
delay(1000); | |
sMessage(F("Advanced AWB")); | |
myCAM.OV5642_set_Light_Mode(Advanced_AWB); | |
delay(250); | |
sMessage(F("High Quality Compression")); | |
myCAM.OV5642_set_Compress_quality(high_quality); | |
delay(250); | |
sMessage(F("Saturation +1")); | |
myCAM.OV5642_set_Color_Saturation(Saturation1); | |
delay(250); | |
sMessage(F("Brightness 0")); | |
myCAM.OV5642_set_Brightness(Brightness0); | |
delay(250); | |
sMessage(F("Contrast +1")); | |
myCAM.OV5642_set_Contrast(Contrast1); | |
delay(250); | |
sMessage(F("Hue 0")); | |
myCAM.OV5642_set_hue(degree_0); | |
delay(250); | |
sMessage(F("Special Effects Normal")); | |
myCAM.OV5642_set_Special_effects(Normal); | |
delay(250); | |
sMessage(F("Exposure Default")); | |
myCAM.OV5642_set_Exposure_level(Exposure_default); | |
delay(250); | |
sMessage(F("Sharpness Auto + 1")); | |
myCAM.OV5642_set_Sharpness(Auto_Sharpness1); | |
delay(250); | |
myCAM.flush_fifo(); | |
myCAM.clear_fifo_flag(); | |
myCAM.write_reg(ARDUCHIP_FRAMES,0x00); | |
//Initialize SD Card | |
w = 0; | |
sMessage(F("Waiting for SD Card")); | |
while(!SD.begin(SD_CS)){ | |
//Serial.println(F("SD Card Error!")); | |
delayWithFlash(); | |
w++; | |
if (w >= MaxWait) { | |
sMessage(F("Waited too long, restarting.")); | |
delay(100); | |
reset(); | |
} | |
} | |
sMessage(F("SD Card Detected, finding first filename")); | |
// figure out first filename | |
sprintf(fnbuf, "%08d.jpg", filenum); | |
while(SD.exists(fnbuf)) | |
{ | |
filenum += 1000; | |
sprintf(fnbuf, "%08d.jpg", filenum); | |
} | |
sMessage(F("Setup complete!")); | |
digitalWrite(LED_BUILTIN, LOW); | |
} | |
void loop() { | |
char fnbuf[15]; | |
byte buf[256]; | |
static int i = 0; | |
int sleepCount = 2; | |
int s = 0; | |
uint8_t temp = 0,temp_last = 0; | |
uint32_t length = 0; | |
bool is_header = false; | |
File outFile; | |
// take a photo | |
sMessage(F("Waking camera and capturing...")); | |
myCAM.clear_bit(ARDUCHIP_GPIO, GPIO_PWDN_MASK); | |
delay(800); | |
//Flush the FIFO | |
myCAM.flush_fifo(); | |
//Clear the capture done flag | |
myCAM.clear_fifo_flag(); | |
//Start capture | |
myCAM.start_capture(); | |
// wait for capture | |
while(!myCAM.get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK)); | |
sMessage(F("Capture complete")); | |
length = myCAM.read_fifo_length(); | |
if (length > 200000) | |
{ | |
// Large file, short sleep so we capture quickly | |
sleepCount = 3; | |
} | |
else | |
{ | |
// Small file, assume night time so sleep longer to save power | |
sleepCount = 200; | |
} | |
// Construct a file name | |
sprintf(fnbuf, "%08d.jpg", filenum); | |
//Open the new file | |
outFile = SD.open(fnbuf, O_WRITE | O_CREAT | O_TRUNC); | |
filenum++; | |
// copy image data to file | |
myCAM.CS_LOW(); | |
myCAM.set_fifo_burst(); | |
while ( length-- ) | |
{ | |
temp_last = temp; | |
temp = SPI.transfer(0x00); | |
//Read JPEG data from FIFO | |
if ( (temp == 0xD9) && (temp_last == 0xFF) ) //If find the end ,break while, | |
{ | |
buf[i++] = temp; //save the last 0XD9 | |
//Write the remain bytes in the buffer | |
myCAM.CS_HIGH(); | |
outFile.write(buf, i); | |
//Close the file | |
outFile.close(); | |
sMessage(F("Image Saved to SD")); | |
is_header = false; | |
i = 0; | |
} | |
if (is_header == true) | |
{ | |
//Write image data to buffer if not full | |
if (i < 256) | |
buf[i++] = temp; | |
else | |
{ | |
//Write 256 bytes image data to file | |
myCAM.CS_HIGH(); | |
outFile.write(buf, 256); | |
i = 0; | |
buf[i++] = temp; | |
myCAM.CS_LOW(); | |
myCAM.set_fifo_burst(); | |
} | |
} | |
else if ((temp == 0xD8) & (temp_last == 0xFF)) | |
{ | |
is_header = true; | |
buf[i++] = temp_last; | |
buf[i++] = temp; | |
} | |
} | |
// sleep for a while... | |
sMessage(F("Going to sleep...")); | |
myCAM.set_bit(ARDUCHIP_GPIO, GPIO_PWDN_MASK); | |
delay(1000); | |
for(s = 0; s < sleepCount; s++) | |
{ | |
// seems to be around 9 seconds per sleep | |
goToSleep(); | |
} | |
} | |
void sMessage(const __FlashStringHelper* m) { | |
#if defined USE_SERIAL | |
Serial.println(m); | |
#endif | |
} | |
void delayWithFlash() { | |
digitalWrite(LED_BUILTIN, LOW); | |
delay(500); | |
digitalWrite(LED_BUILTIN, HIGH); | |
delay(500); | |
} | |
void lowPowerSetup () { | |
// working from https://forum.arduino.cc/t/power-saving-techniques-on-a-stock-mega-2560/425563 | |
pinMode(A0, OUTPUT); | |
pinMode(A1, OUTPUT); | |
pinMode(A2, OUTPUT); | |
pinMode(A3, OUTPUT); | |
pinMode(A4, OUTPUT); | |
pinMode(A5, OUTPUT); | |
pinMode(A6, OUTPUT); | |
pinMode(A7, OUTPUT); | |
pinMode(A8, OUTPUT); | |
pinMode(A9, OUTPUT); | |
pinMode(A10, OUTPUT); | |
pinMode(A11, OUTPUT); | |
pinMode(A12, OUTPUT); | |
pinMode(A13, OUTPUT); | |
pinMode(A14, OUTPUT); | |
pinMode(A15, OUTPUT); | |
digitalWrite(A0, LOW); | |
digitalWrite(A1, LOW); | |
digitalWrite(A2, LOW); | |
digitalWrite(A3, LOW); | |
digitalWrite(A4, LOW); | |
digitalWrite(A5, LOW); | |
digitalWrite(A6, LOW); | |
digitalWrite(A7, LOW); | |
digitalWrite(A8, LOW); | |
digitalWrite(A9, LOW); | |
digitalWrite(A10, LOW); | |
digitalWrite(A11, LOW); | |
digitalWrite(A12, LOW); | |
digitalWrite(A13, LOW); | |
digitalWrite(A14, LOW); | |
digitalWrite(A15, LOW); | |
for (int i = 0; i <= 53; i++) { | |
if (i == CS) { continue; } | |
if (i == SD_CS) { continue; } | |
pinMode(i, OUTPUT); | |
digitalWrite(i, LOW); | |
} | |
power_adc_disable(); | |
//power_spi_disable(); | |
//power_usart0_disable(); | |
power_usart2_disable(); | |
power_timer1_disable(); | |
power_timer2_disable(); | |
power_timer3_disable(); | |
power_timer4_disable(); | |
power_timer5_disable(); | |
//power_twi_disable(); | |
} | |
void goToSleep() { | |
/*** Setup the Watch Dog Timer ***/ | |
/* Clear the reset flag. */ | |
MCUSR &= ~(1<<WDRF); | |
/* set new watchdog timeout prescaler value */ | |
WDTCSR |= (1<<WDCE) | (1<<WDE); | |
WDTCSR = 1<<WDP0 | 0<<WDP1 | 0<<WDP2 | 1<<WDP3; | |
WDTCSR |= _BV(WDIE); | |
set_sleep_mode(SLEEP_MODE_PWR_DOWN); | |
sleep_enable(); | |
sleep_mode(); | |
sleep_disable(); | |
} | |
ISR(WDT_vect) { | |
// Dummy watchdog timer handler to prevent reset | |
} | |
void reset() { asm volatile ("jmp 0"); } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment