Compare commits
	
		
			21 Commits
		
	
	
		
			113aaa8a54
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | ea93980795 | ||
|  | dac3a33e9e | ||
|  | 618c75b72b | ||
|  | 6f962c0d88 | ||
|  | 9ecb2f5579 | ||
|  | 5ed8e8f226 | ||
|  | 9420eb8293 | ||
|  | 4e90b217cb | ||
|  | bbb53a31b3 | ||
|  | 1f17f87822 | ||
|  | 25d7967d69 | ||
|  | 6599bf151d | ||
|  | b6004c42e0 | ||
|  | 7773dc7c1f | ||
|  | 5728233bad | ||
|  | 580ba5e577 | ||
|  | 232df37f5c | ||
|  | 3101c3add6 | ||
|  | fe6734dcba | ||
|  | bfa80a629e | ||
|  | c2f41e41c7 | 
							
								
								
									
										7
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | Copyright 2022 Darwin Schuppan <darwin@nobrain.org> | ||||||
|  |  | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||||
|  |  | ||||||
|  | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||||
|  |  | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||||
| @@ -2,7 +2,7 @@ | |||||||
|  |  | ||||||
| A simple voice recorder for Arduino using an SD card and an electret microphone amplifier circuit. | A simple voice recorder for Arduino using an SD card and an electret microphone amplifier circuit. | ||||||
|  |  | ||||||
| Independent of any 3rd party libraries (unless delayed recording is used). | Independent of any third-party libraries. | ||||||
|  |  | ||||||
| ## Wiring (also documented in spybug/spybug.ino) | ## Wiring (also documented in spybug/spybug.ino) | ||||||
| ### SD Card Wiring | ### SD Card Wiring | ||||||
| @@ -29,3 +29,9 @@ GND      |  GND | |||||||
| Out      |  A0 | Out      |  A0 | ||||||
|  |  | ||||||
| Out defaults to A0 (AdcChannel0), but can be set manually in `ADC_CHANNEL`. | Out defaults to A0 (AdcChannel0), but can be set manually in `ADC_CHANNEL`. | ||||||
|  |  | ||||||
|  | ## Power Consumption (Arduino Nano) | ||||||
|  | Mode      | Unmodified | No voltage regulator | No TTL module or voltage regulator | ||||||
|  | ----------|------------|----------------------|----------------------------------- | ||||||
|  | Waiting   | ~10mA      | ~5.8mA               | ~6μA | ||||||
|  | Recording | ~30mA      | ~25.1mA              | ~25mA | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								reset_sketch/reset_sketch.ino
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								reset_sketch/reset_sketch.ino
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | // Copyright 2022 Darwin Schuppan <darwin@nobrain.org> | ||||||
|  | // SPDX license identifier: MIT | ||||||
|  |  | ||||||
|  | #include "settings.hh" | ||||||
|  |  | ||||||
|  | void setup() { | ||||||
|  | 	Serial.begin(9600); | ||||||
|  | 	Serial.print("Resetting EEPROM..."); | ||||||
|  | 	EEPROM_Settings_Class settings; | ||||||
|  | 	settings.save(); | ||||||
|  | 	Serial.println("done."); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void loop() {} | ||||||
							
								
								
									
										1
									
								
								reset_sketch/settings.hh
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								reset_sketch/settings.hh
									
									
									
									
									
										Symbolic link
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | ../spybug/settings.hh | ||||||
							
								
								
									
										31
									
								
								spybug/aaa_config.hh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								spybug/aaa_config.hh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | // Copyright 2022 Darwin Schuppan <darwin@nobrain.org> | ||||||
|  | // SPDX license identifier: MIT | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | /************************ | ||||||
|  |  BEGIN USER CONFIGURATION | ||||||
|  |  ************************/ | ||||||
|  | //#define DEBUG_RECORDING | ||||||
|  |  | ||||||
|  | #define PIN_COMPONENT_SWITCH 2 /* Use a digital signal to switch on/off the microphone and SD card for less power draw. */ | ||||||
|  | #define COMPONENT_SWITCH_ON HIGH | ||||||
|  |  | ||||||
|  | #define SAMPLE_MODE_U8 | ||||||
|  | //#define SAMPLE_MODE_S16 | ||||||
|  |  | ||||||
|  | //#define ADC_PRESCALE_16 /* Up to ~60kHz. */ | ||||||
|  | //#define ADC_PRESCALE_32 /* Up to ~27kHz. */ | ||||||
|  | #define ADC_PRESCALE_64 /* Up to ~18kHz. */ | ||||||
|  |  | ||||||
|  | //#define U8_AMPLIFY_X2 /* (U8 sampling mode only) amplify audio by factor 2. */ | ||||||
|  |  | ||||||
|  | #define ADC_CHANNEL AdcChannel0 | ||||||
|  | #define TIMER_COMPARE 1000 /* 16MHz / 1000 = 16kHz. */ | ||||||
|  | #define FLUSH_SAMPLES 64000 /* Flush WAV file every n samples. */ | ||||||
|  | #define PIN_SS 10 | ||||||
|  |  | ||||||
|  | #define REC_FILE_FMT "REC_%03u.WAV" /* Must be all caps; must use %u in some form exactly once. */ | ||||||
|  | /********************** | ||||||
|  |  END USER CONFIGURATION | ||||||
|  |  **********************/ | ||||||
							
								
								
									
										191
									
								
								spybug/cmd.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								spybug/cmd.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | |||||||
|  | // Copyright 2022 Darwin Schuppan <darwin@nobrain.org> | ||||||
|  | // SPDX license identifier: MIT | ||||||
|  |  | ||||||
|  | #include "cmd.hh" | ||||||
|  |  | ||||||
|  | #include <Arduino.h> | ||||||
|  | #include <SD.h> | ||||||
|  |  | ||||||
|  | #include "aaa_config.hh" | ||||||
|  | #include "fstr.hh" | ||||||
|  | #include "io.hh" | ||||||
|  | #include "settings.hh" | ||||||
|  | #include "sys.hh" | ||||||
|  |  | ||||||
|  | static bool sd_initialized = false; | ||||||
|  |  | ||||||
|  | static void try_init_sd() { | ||||||
|  | 	if (sd_initialized) | ||||||
|  | 		return; | ||||||
|  | 	printf(F("Initializing SD card...")); | ||||||
|  | 	#ifdef PIN_COMPONENT_SWITCH | ||||||
|  | 		digitalWrite(PIN_COMPONENT_SWITCH, COMPONENT_SWITCH_ON); | ||||||
|  | 		delay(500); /* Wait for components to initialize. */ | ||||||
|  | 	#endif | ||||||
|  | 	if (SD.begin(PIN_SS)) { | ||||||
|  | 		printf(F("Done.\n")); | ||||||
|  | 		sd_initialized = true; | ||||||
|  | 	} else | ||||||
|  | 		printf(F("\nError initializing SD card.\n")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void sd_error() { | ||||||
|  | 	printf(F("Error reading from SD card.\n")); | ||||||
|  | 	sd_initialized = false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void command_loop() { | ||||||
|  | 	// Process Commands | ||||||
|  | 	if (Serial.available()) { | ||||||
|  | 		char args[6][20]; | ||||||
|  | 		size_t len = 0; | ||||||
|  | 		size_t n_args = 0; | ||||||
|  | 		int c = getchar(); | ||||||
|  | 		while (c != '\n' && n_args < 4) { | ||||||
|  | 			if (c == ' ') | ||||||
|  | 				c = getchar(); | ||||||
|  | 			else { | ||||||
|  | 				do { | ||||||
|  | 					args[n_args][len++] = c; | ||||||
|  | 					c = getchar(); | ||||||
|  | 				} while (c != ' ' && c != '\n' && len < 20-1); | ||||||
|  | 				args[n_args][len] = 0; | ||||||
|  | 				len = 0; | ||||||
|  | 				n_args++; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if (n_args >= 1) { | ||||||
|  | 			if (fstreq(args[0], F("set"))) { | ||||||
|  | 				if ((n_args == 3 || n_args == 4) && fstreq(args[1], F("wait"))) { | ||||||
|  | 					float n = atof(args[2]); | ||||||
|  | 					unsigned long mins; | ||||||
|  | 					if (n_args == 4 && (fstreq(args[3], F("hours")) || fstreq(args[3], F("hour")))) | ||||||
|  | 						mins = 60.f * n; | ||||||
|  | 					else | ||||||
|  | 						mins = n; | ||||||
|  | 					settings.recording_delay = mins; | ||||||
|  | 					settings.save(); | ||||||
|  | 					printf(F("Set waiting time to %lu minutes or "), settings.recording_delay); | ||||||
|  | 					print_special((float)settings.recording_delay / 60.f); | ||||||
|  | 					printf(F(" hours.\n")); | ||||||
|  | 				} else if (n_args == 3 && fstreq(args[1], F("serial"))) { | ||||||
|  | 					if (fstreq(args[2], F("on"))) { | ||||||
|  | 						settings.serial_log = true; | ||||||
|  | 						settings.save(); | ||||||
|  | 						printf(F("Serial log enabled.\n")); | ||||||
|  | 					} else if (fstreq(args[2], F("off"))) { | ||||||
|  | 						settings.serial_log = false; | ||||||
|  | 						settings.save(); | ||||||
|  | 						printf(F("Serial log disabled.\n")); | ||||||
|  | 					} else { | ||||||
|  | 						printf(F("Usage: 'set serial [on|off]'.\n")); | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					printf(F("Usage: 'set wait <number> [minutes|hours]' or 'set serial [on|off]'.\n")); | ||||||
|  | 				} | ||||||
|  | 			} else if (fstreq(args[0], F("get"))) { | ||||||
|  | 				if (n_args == 2 && fstreq(args[1], F("wait"))) { | ||||||
|  | 					printf(F("Current waiting time: %lu minutes or "), settings.recording_delay); | ||||||
|  | 					print_special((float)settings.recording_delay / 60.f); | ||||||
|  | 					printf(F(" hours.\n")); | ||||||
|  | 				} else { | ||||||
|  | 					printf(F("Usage: 'get wait'.\n")); | ||||||
|  | 				} | ||||||
|  | 			} else if (fstreq(args[0], F("sd"))) { | ||||||
|  | 				try_init_sd(); | ||||||
|  | 				if (sd_initialized) { | ||||||
|  | 					if (n_args == 2 && fstreq(args[1], F("list"))) { | ||||||
|  | 						File root = SD.open("/"); | ||||||
|  | 						if (root) { | ||||||
|  | 							printf(F("Files:\n")); | ||||||
|  | 							bool files = false; | ||||||
|  | 							size_t cols = 0; | ||||||
|  | 							while (1) { | ||||||
|  | 								File entry = root.openNextFile(); | ||||||
|  | 								if (!entry) | ||||||
|  | 									break; | ||||||
|  | 								files = true; | ||||||
|  | 								if (cols >= 3) { | ||||||
|  | 									cols = 0; | ||||||
|  | 									printf(F("\n")); | ||||||
|  | 								} | ||||||
|  | 								printf(F("  %s"), entry.name()); | ||||||
|  | 								cols++; | ||||||
|  | 								entry.close(); | ||||||
|  | 							} | ||||||
|  | 							if (!files) | ||||||
|  | 								printf(F("[NONE]")); | ||||||
|  | 							printf(F("\n")); | ||||||
|  | 						} else | ||||||
|  | 							sd_error(); | ||||||
|  | 						root.close(); | ||||||
|  | 					} else if (n_args == 3 && fstreq(args[1], F("remove"))) { | ||||||
|  | 						if (SD.remove(args[2])) | ||||||
|  | 							printf(F("Deleted '%s'.\n"), args[2]); | ||||||
|  | 						else | ||||||
|  | 							printf(F("Error removing '%s'.\n"), args[2]); | ||||||
|  | 					} else if (n_args == 2 && fstreq(args[1], F("remove_all"))) { | ||||||
|  | 						File root = SD.open("/"); | ||||||
|  | 						if (root) { | ||||||
|  | 							printf(F("Deleted:\n")); | ||||||
|  | 							bool deleted = false; | ||||||
|  | 							size_t cols = 0; | ||||||
|  | 							while (1) { | ||||||
|  | 								File entry = root.openNextFile(); | ||||||
|  | 								if (!entry) | ||||||
|  | 									break; | ||||||
|  | 								const char *name = entry.name(); | ||||||
|  | 								unsigned int garbage; | ||||||
|  | 								if (!entry.isDirectory() && sscanf(name, REC_FILE_FMT, &garbage) == 1) { | ||||||
|  | 									if (cols >= 3) { | ||||||
|  | 										cols = 0; | ||||||
|  | 										printf(F("\n")); | ||||||
|  | 									} | ||||||
|  | 									deleted = true; | ||||||
|  | 									SD.remove(name); | ||||||
|  | 									printf(F("  %s"), name); | ||||||
|  | 									cols++; | ||||||
|  | 								} | ||||||
|  | 								entry.close(); | ||||||
|  | 							} | ||||||
|  | 							if (!deleted) | ||||||
|  | 								printf(F("[NONE]")); | ||||||
|  | 							printf(F("\n")); | ||||||
|  | 						} else | ||||||
|  | 							sd_error(); | ||||||
|  | 						root.close(); | ||||||
|  | 					} else if (n_args != 1) { | ||||||
|  | 						printf(F("Usage: 'sd list', 'sd remove <filename>' or 'sd remove_all'.\n")); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} else if (fstreq(args[0], F("help"))) { | ||||||
|  | 				printf(F("Commands:\n")); | ||||||
|  | 				printf(F("  help                               --  Display this page.\n")); | ||||||
|  | 				printf(F("  exit                               --  Leave command mode.\n")); | ||||||
|  | 				printf(F("  get wait                           --  Display current delay setting.\n")); | ||||||
|  | 				printf(F("  set wait <number> [minutes|hours]  --  Change current delay setting.\n")); | ||||||
|  | 				printf(F("  set serial [on|off]                --  Write log to serial output.\n")); | ||||||
|  | 				printf(F("  sd                                 --  Initialize the SD card.\n")); | ||||||
|  | 				printf(F("  sd list                            --  List file on SD card.\n")); | ||||||
|  | 				printf(F("  sd remove <filename>               --  Delete a file from SD card.\n")); | ||||||
|  | 				printf(F("  sd remove_all                      --  Delete all recordings from SD card.\n")); | ||||||
|  | 			} else if (fstreq(args[0], F("exit"))) { | ||||||
|  | 				printf(F("Bye!\n")); | ||||||
|  | 				Serial.flush(); | ||||||
|  | 				full_reset(); | ||||||
|  | 			} else | ||||||
|  | 				printf(F("Invalid command: '%s'. Type 'help' for a list of commands.\n"), args[0]); | ||||||
|  | 		} else { | ||||||
|  | 			printf(F("Please specify a command. Type 'help' for a list of commands.\n")); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void cmd() { | ||||||
|  | 	printf(F("You are now in command mode. Type 'help' for a list of commands.\n")); | ||||||
|  | 	while (Serial.available()) Serial.read(); | ||||||
|  | 	while (1) { | ||||||
|  | 		command_loop(); | ||||||
|  | 		delay(50); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								spybug/cmd.hh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								spybug/cmd.hh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | // Copyright 2022 Darwin Schuppan <darwin@nobrain.org> | ||||||
|  | // SPDX license identifier: MIT | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | void cmd(); | ||||||
							
								
								
									
										37
									
								
								spybug/fstr.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								spybug/fstr.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | // Copyright 2022 Darwin Schuppan <darwin@nobrain.org> | ||||||
|  | // SPDX license identifier: MIT | ||||||
|  |  | ||||||
|  | #include "fstr.hh" | ||||||
|  |  | ||||||
|  | size_t fstrlen(const __FlashStringHelper *s) { | ||||||
|  | 	PGM_P sp = (PGM_P)s; | ||||||
|  | 	size_t len = 0; | ||||||
|  | 	while (pgm_read_byte(sp++)) | ||||||
|  | 		len++; | ||||||
|  | 	return len; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool fstreq(const char *a, const __FlashStringHelper *b_fsh) { | ||||||
|  | 	PGM_P b = (PGM_P)b_fsh; | ||||||
|  | 	while (1) { | ||||||
|  | 		if (*a != pgm_read_byte(b)) | ||||||
|  | 			return false; | ||||||
|  | 		if (*a == 0) | ||||||
|  | 			return true; | ||||||
|  | 		a++; b++; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int printf(const __FlashStringHelper *fmt, ...) { | ||||||
|  | 	size_t len = fstrlen(fmt); | ||||||
|  | 	char buf[len + 1]; | ||||||
|  | 	buf[len] = 0; | ||||||
|  | 	memcpy_P(buf, fmt, len + 1); | ||||||
|  |  | ||||||
|  | 	va_list args; | ||||||
|  | 	va_start(args, fmt); | ||||||
|  | 	int ret = vprintf(buf, args); | ||||||
|  | 	va_end(args); | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								spybug/fstr.hh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								spybug/fstr.hh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | // Copyright 2022 Darwin Schuppan <darwin@nobrain.org> | ||||||
|  | // SPDX license identifier: MIT | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <Arduino.h> | ||||||
|  |  | ||||||
|  | size_t fstrlen(const __FlashStringHelper *s); | ||||||
|  |  | ||||||
|  | bool fstreq(const char *a, const __FlashStringHelper *b_fsh); | ||||||
|  |  | ||||||
|  | int printf(const __FlashStringHelper *fmt, ...); | ||||||
							
								
								
									
										22
									
								
								spybug/io.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								spybug/io.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | // Copyright 2022 Darwin Schuppan <darwin@nobrain.org> | ||||||
|  | // SPDX license identifier: MIT | ||||||
|  |  | ||||||
|  | #include "io.hh" | ||||||
|  |  | ||||||
|  | static int serial_putch(char c, FILE *f) { | ||||||
|  | 	(void)f; | ||||||
|  | 	return Serial.write(c) == 1 ? 0 : 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int serial_getch(FILE *f) { | ||||||
|  | 	(void)f; | ||||||
|  | 	while(Serial.available() == 0); | ||||||
|  | 	return Serial.read(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static FILE serial_in_out; | ||||||
|  |  | ||||||
|  | void io_setup() { | ||||||
|  | 	fdev_setup_stream(&serial_in_out, serial_putch, serial_getch, _FDEV_SETUP_RW); | ||||||
|  | 	stdout = stdin = stderr = &serial_in_out; | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								spybug/io.hh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								spybug/io.hh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | // Copyright 2022 Darwin Schuppan <darwin@nobrain.org> | ||||||
|  | // SPDX license identifier: MIT | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <Arduino.h> | ||||||
|  |  | ||||||
|  | void io_setup(); | ||||||
|  |  | ||||||
|  | #define print_special(x) { Serial.print(x); } | ||||||
|  | #define die(fmt, ...) { disable_recording_interrupts(); if (settings.serial_log) { printf(F("Fatal: ")); printf(fmt, ##__VA_ARGS__); Serial.flush(); } while(1); } | ||||||
|  | #define dbg(fmt, ...) { printf(F("Debug: ")); printf(fmt, ##__VA_ARGS__); } | ||||||
|  | #define info(fmt, ...) { if (settings.serial_log) printf(fmt, ##__VA_ARGS__); } | ||||||
|  | #define info_special(x) { if (settings.serial_log) Serial.print(x); } | ||||||
							
								
								
									
										6
									
								
								spybug/settings.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								spybug/settings.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | // Copyright 2022 Darwin Schuppan <darwin@nobrain.org> | ||||||
|  | // SPDX license identifier: MIT | ||||||
|  |  | ||||||
|  | #include "settings.hh" | ||||||
|  |  | ||||||
|  | EEPROM_Settings_Class settings; | ||||||
							
								
								
									
										17
									
								
								spybug/settings.hh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								spybug/settings.hh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | // Copyright 2022 Darwin Schuppan <darwin@nobrain.org> | ||||||
|  | // SPDX license identifier: MIT | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <EEPROM.h> | ||||||
|  |  | ||||||
|  | #define EEADDR_SETTINGS 0x00 | ||||||
|  | struct EEPROM_Settings_Class { | ||||||
|  | 	unsigned long recording_delay = 0l; | ||||||
|  | 	bool          serial_log      = true; | ||||||
|  |  | ||||||
|  | 	inline void load() { EEPROM.get(EEADDR_SETTINGS, *this); } | ||||||
|  | 	inline void save() { EEPROM.put(EEADDR_SETTINGS, *this); } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | extern EEPROM_Settings_Class settings; | ||||||
| @@ -1,3 +1,6 @@ | |||||||
|  | // Copyright 2022 Darwin Schuppan <darwin@nobrain.org> | ||||||
|  | // SPDX license identifier: MIT | ||||||
|  |  | ||||||
| /* | /* | ||||||
| 	*** SD Card Wiring *** | 	*** SD Card Wiring *** | ||||||
| 	SD       | Nano | 	SD       | Nano | ||||||
| @@ -27,107 +30,18 @@ | |||||||
|  |  | ||||||
| #include <SD.h> | #include <SD.h> | ||||||
| #include <SPI.h> | #include <SPI.h> | ||||||
| #include <avr/interrupt.h> |  | ||||||
| #include <avr/io.h> |  | ||||||
| #include <avr/wdt.h> |  | ||||||
|  |  | ||||||
| /************************ | #include "aaa_config.hh" | ||||||
|  BEGIN USER CONFIGURATION | #include "cmd.hh" | ||||||
|  ************************/ | #include "fstr.hh" | ||||||
| //#define DEBUG_RECORDING | #include "io.hh" | ||||||
| //#define SERIAL_OUTPUT | #include "settings.hh" | ||||||
|  | #include "sys.hh" | ||||||
|  |  | ||||||
| #define SAMPLE_MODE_U8 | #if !defined(__AVR_ATmega328P__) || F_CPU != 16000000 | ||||||
| //#define SAMPLE_MODE_S16 | #error "This program only works on ATmega328P devices with a clock frequency of 16MHz!" | ||||||
|  |  | ||||||
| //#define ADC_PRESCALE_16 /* Up to ~60kHz. */ |  | ||||||
| //#define ADC_PRESCALE_32 /* Up to ~27kHz. */ |  | ||||||
| #define ADC_PRESCALE_64 /* Up to ~18kHz. */ |  | ||||||
|  |  | ||||||
| #define U8_EXTRA_PRECISION /* (U8 sampling mode only) use 9th ADC reading bit and chop off 1st bit for more precision (sacrificing half of the bandwidth) */ |  | ||||||
|  |  | ||||||
| #define RECORDING_DELAY_IN_MINUTES 0 /* Wait n minutes before starting to record. */ |  | ||||||
| #define ADC_CHANNEL AdcChannel0 |  | ||||||
| #define TIMER_COMPARE 1000 /* 16MHz / 1000 = 16kHz. */ |  | ||||||
| #define FLUSH_SAMPLES 64000 /* Flush WAV file every n samples. */ |  | ||||||
| #define PIN_SS 10 |  | ||||||
| /********************** |  | ||||||
|  END USER CONFIGURATION |  | ||||||
|  **********************/ |  | ||||||
|  |  | ||||||
| #ifdef SERIAL_OUTPUT |  | ||||||
| static int serial_putch(char c, FILE *f) { |  | ||||||
| 	(void)f; |  | ||||||
| 	return Serial.write(c) == 1 ? 0 : 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static int serial_getch(FILE *f) { |  | ||||||
| 	(void)f; |  | ||||||
| 	while(Serial.available() == 0); |  | ||||||
| 	return Serial.read(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static FILE serial_in_out; |  | ||||||
|  |  | ||||||
| static void setup_serial_in_out() { |  | ||||||
| 	fdev_setup_stream(&serial_in_out, serial_putch, serial_getch, _FDEV_SETUP_RW); |  | ||||||
| 	stdout = stdin = stderr = &serial_in_out; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static size_t fstrlen(const __FlashStringHelper *s) { |  | ||||||
| 	PGM_P sp = (PGM_P)s; |  | ||||||
| 	size_t len = 0; |  | ||||||
| 	while (pgm_read_byte(sp++)) |  | ||||||
| 		len++; |  | ||||||
| 	return len; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static int printf(const __FlashStringHelper *fmt, ...) { |  | ||||||
| 	size_t len = fstrlen(fmt); |  | ||||||
| 	char buf[len + 1]; |  | ||||||
| 	buf[len] = 0; |  | ||||||
| 	memcpy_P(buf, fmt, len + 1); |  | ||||||
|  |  | ||||||
| 	va_list args; |  | ||||||
| 	va_start(args, fmt); |  | ||||||
| 	int ret = vprintf(buf, args); |  | ||||||
| 	va_end(args); |  | ||||||
| 	return ret; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #define die(fmt, ...) { disable_recording_interrupts(); printf(F("Fatal: ")); printf(fmt, ##__VA_ARGS__); Serial.flush(); while(1); } |  | ||||||
| #define dbg(fmt, ...) { printf(F("Debug: ")); printf(fmt, ##__VA_ARGS__); } |  | ||||||
| #define print_special(x) Serial.print(x) |  | ||||||
| #else |  | ||||||
| #define printf(fmt, ...) {} |  | ||||||
| #define die(fmt, ...) { disable_recording_interrupts(); while(1); } |  | ||||||
| #define dbg(fmt, ...) {} |  | ||||||
| #define print_special(x) {} |  | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #if defined(RECORDING_DELAY_IN_MINUTES) && RECORDING_DELAY_IN_MINUTES != 0 |  | ||||||
| #include <LowPower.h> /* https://github.com/rocketscream/Low-Power */ |  | ||||||
| static void low_power_sleep_minutes(unsigned long t) { |  | ||||||
| 	for (unsigned long i = 0; 8ul * i < 60ul * t; i++) { |  | ||||||
| 		/* Power down for 8s. */ |  | ||||||
| 		LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| static void start_watchdog_with_full_reset() { |  | ||||||
| 	MCUSR &= ~B00001000; /* Clear reset flag. */ |  | ||||||
| 	WDTCSR |= B00011000; /* Prepare prescaler change. */ |  | ||||||
| 	WDTCSR = B00100001; /* Set watchdog timeout to 8s. */ |  | ||||||
| 	// Enable Watchdog Timer |  | ||||||
| 	WDTCSR |= B01000000; |  | ||||||
| 	MCUSR = MCUSR & B11110111; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static inline void disable_recording_interrupts() { |  | ||||||
| 	TIMSK1 &= ~(_BV(OCIE1A) | _BV(OCIE1B)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| enum AdcChannel : uint8_t { | enum AdcChannel : uint8_t { | ||||||
| 	AdcChannel0    = 0, | 	AdcChannel0    = 0, | ||||||
| 	AdcChannel1    = 1, | 	AdcChannel1    = 1, | ||||||
| @@ -171,9 +85,9 @@ ISR(TIMER1_COMPA_vect) { | |||||||
| 		sei(); | 		sei(); | ||||||
| 		const size_t bufsz = sizeof(SAMPLE_BUF_TYPE) * samples_in_buffer[!which_buffer]; | 		const size_t bufsz = sizeof(SAMPLE_BUF_TYPE) * samples_in_buffer[!which_buffer]; | ||||||
| 		if (file.write((char*)sample_buffer[!which_buffer], bufsz) != bufsz) { | 		if (file.write((char*)sample_buffer[!which_buffer], bufsz) != bufsz) { | ||||||
| 			printf(F("Lost ")); | 			info(F("Lost ")); | ||||||
| 			print_special((float)samples_hanging / (float)(F_CPU / TIMER_COMPARE)); /* Printf doesn't handle floats. */ | 			info_special((float)samples_hanging / (float)(F_CPU / TIMER_COMPARE)); /* Printf doesn't handle floats. */ | ||||||
| 			printf(F(" seconds of recording.\n")); | 			info(F(" seconds of recording.\n")); | ||||||
| 			die(F("Error writing to SD card. You can ignore this if you removed the SD card intentionally.\n")); | 			die(F("Error writing to SD card. You can ignore this if you removed the SD card intentionally.\n")); | ||||||
| 		} | 		} | ||||||
| 		samples_hanging += samples_in_buffer[!which_buffer]; | 		samples_hanging += samples_in_buffer[!which_buffer]; | ||||||
| @@ -191,7 +105,7 @@ ISR(TIMER1_COMPA_vect) { | |||||||
| ISR(TIMER1_COMPB_vect) { | ISR(TIMER1_COMPB_vect) { | ||||||
| 	// Retrieve ADC Value and Write to Buffer | 	// Retrieve ADC Value and Write to Buffer | ||||||
| #if defined(SAMPLE_MODE_U8) | #if defined(SAMPLE_MODE_U8) | ||||||
| #ifdef U8_EXTRA_PRECISION | #ifdef U8_AMPLIFY_X2 | ||||||
| 	uint8_t l = ADCL; /* Read ADC registers. (Order matters!) */ | 	uint8_t l = ADCL; /* Read ADC registers. (Order matters!) */ | ||||||
| 	uint8_t h = ADCH; | 	uint8_t h = ADCH; | ||||||
| 	uint8_t adcval = (h << 7) | (l >> 1); | 	uint8_t adcval = (h << 7) | (l >> 1); | ||||||
| @@ -264,18 +178,41 @@ static void wav_write_header(uint32_t nsamples) { | |||||||
|  |  | ||||||
| void setup() { | void setup() { | ||||||
| 	// Serial Setup | 	// Serial Setup | ||||||
| #ifdef SERIAL_OUTPUT |  | ||||||
| 	Serial.begin(9600); /* Set baud rate. */ | 	Serial.begin(9600); /* Set baud rate. */ | ||||||
| 	setup_serial_in_out(); /* Add printf support. */ | 	io_setup(); /* Add printf support. */ | ||||||
|  | 	// Component Switch Setup | ||||||
|  | #ifdef PIN_COMPONENT_SWITCH | ||||||
|  | 	pinMode(PIN_COMPONENT_SWITCH, OUTPUT); | ||||||
| #endif | #endif | ||||||
| 	// Delay Triggering | 	// Load EEPROM Data | ||||||
| #if defined(RECORDING_DELAY_IN_MINUTES) && RECORDING_DELAY_IN_MINUTES != 0 | 	settings.load(); | ||||||
| 	printf(F("Waiting %u minute%s before starting to record...\n"), RECORDING_DELAY_IN_MINUTES, RECORDING_DELAY_IN_MINUTES == 1 ? "" : "s"); | 	// Handle Commands | ||||||
| 	Serial.flush(); | 	info(F("Type anything in the next 4s to enter command mode.\n")); | ||||||
| 	low_power_sleep_minutes(RECORDING_DELAY_IN_MINUTES); /* Draws ~12.5mA instead of ~30mA when using delay(). */ | 	for (size_t i = 0; i < 4 * 4; i++) { | ||||||
|  | 		if (Serial.available()) | ||||||
|  | 			cmd(); | ||||||
|  | 		delay(250); | ||||||
|  | 	} | ||||||
|  | 	// Delayed Triggering | ||||||
|  | 	if (settings.recording_delay) { | ||||||
|  | #ifdef PIN_COMPONENT_SWITCH | ||||||
|  | 		digitalWrite(PIN_COMPONENT_SWITCH, !COMPONENT_SWITCH_ON); | ||||||
|  | #endif | ||||||
|  | 		info(F("Sleeping for %lu minute%s before starting to record...\n"), settings.recording_delay, settings.recording_delay == 1 ? "" : "s"); | ||||||
|  | 		Serial.flush(); | ||||||
|  | 		/* Using this function, an Arduino Nano (with its voltage regulator and TTL module removed) draws ~6μA. */ | ||||||
|  | 		low_power_sleep_minutes(settings.recording_delay); | ||||||
|  | 		/* Reset wait time. */ | ||||||
|  | 		settings.recording_delay = 0; | ||||||
|  | 		settings.save(); | ||||||
|  | 	} | ||||||
|  | 	// Activate Components | ||||||
|  | #ifdef PIN_COMPONENT_SWITCH | ||||||
|  | 	digitalWrite(PIN_COMPONENT_SWITCH, COMPONENT_SWITCH_ON); | ||||||
|  | 	delay(500); /* Wait for components to initialize. */ | ||||||
| #endif | #endif | ||||||
| 	// Start Watchdog (wdt_enable() doesn't fully reset) | 	// Start Watchdog (wdt_enable() doesn't fully reset) | ||||||
| 	start_watchdog_with_full_reset(); | 	wdt_enable_with_full_reset(); | ||||||
| 	// SD Card Setup | 	// SD Card Setup | ||||||
| 	if (!SD.begin(PIN_SS)) | 	if (!SD.begin(PIN_SS)) | ||||||
| 		die(F("Error initializing SD card!\n")); | 		die(F("Error initializing SD card!\n")); | ||||||
| @@ -284,11 +221,11 @@ void setup() { | |||||||
| 	char filename[32]; | 	char filename[32]; | ||||||
| 	do { | 	do { | ||||||
| 		filenum++; | 		filenum++; | ||||||
| 		snprintf(filename, 32, "rec_%03u.wav", filenum); | 		snprintf(filename, 32, REC_FILE_FMT, filenum); | ||||||
| 	} while (SD.exists(filename)); | 	} while (SD.exists(filename)); | ||||||
| 	// Open File | 	// Open File | ||||||
| 	file = SD.open(filename, O_READ | O_WRITE | O_CREAT); /* Seeking doesn't seem to work with FILE_WRITE?! */ | 	file = SD.open(filename, O_READ | O_WRITE | O_CREAT); /* Seeking doesn't seem to work with FILE_WRITE?! */ | ||||||
| 	printf(F("Recording to file '%s'.\n"), filename); | 	info(F("Recording to file '%s'.\n"), filename); | ||||||
| 	if (!file) | 	if (!file) | ||||||
| 		die(F("Error opening '%s' for writing!\n"), filename); | 		die(F("Error opening '%s' for writing!\n"), filename); | ||||||
| 	wav_write_header(0); | 	wav_write_header(0); | ||||||
| @@ -305,7 +242,7 @@ void setup() { | |||||||
| #endif | #endif | ||||||
| 	ADCSRB = _BV(ADTS2) | _BV(ADTS0); /* Auto-trigger source select: "Timer/Counter1 Compare Match B". */ | 	ADCSRB = _BV(ADTS2) | _BV(ADTS0); /* Auto-trigger source select: "Timer/Counter1 Compare Match B". */ | ||||||
| 	ADMUX = _BV(REFS0) /* Use AREF pin (VCC by default) as reference voltage. */ | 	ADMUX = _BV(REFS0) /* Use AREF pin (VCC by default) as reference voltage. */ | ||||||
| #if defined(SAMPLE_MODE_U8) && !defined(U8_EXTRA_PRECISION) | #if defined(SAMPLE_MODE_U8) && !defined(U8_AMPLIFY_X2) | ||||||
| 	      | _BV(ADLAR) /* Left adjust ADC output so we only need to read ADCH. */ | 	      | _BV(ADLAR) /* Left adjust ADC output so we only need to read ADCH. */ | ||||||
| #endif | #endif | ||||||
| 		  | (0xF & ADC_CHANNEL); /* Select our ADC input channel. */ | 		  | (0xF & ADC_CHANNEL); /* Select our ADC input channel. */ | ||||||
| @@ -318,8 +255,9 @@ void setup() { | |||||||
| 	       | _BV(OCIE1B); /* Enable "Output Compare B Match Interrupt". */ | 	       | _BV(OCIE1B); /* Enable "Output Compare B Match Interrupt". */ | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| void loop() { | void loop() { | ||||||
| 	delay(1000); | 	delay(2000); | ||||||
| 	wdt_reset(); /* Reset watchdog timer. */ | 	wdt_reset(); /* Reset watchdog timer. */ | ||||||
| #ifdef DEBUG_RECORDING | #ifdef DEBUG_RECORDING | ||||||
| 	dbg(F("n=%lu\tavg=%lu\tmin=%d\tmax=%d\n"), dbg_total, dbg_sum / (dbg_samples ? dbg_samples : 1), dbg_min, dbg_max); | 	dbg(F("n=%lu\tavg=%lu\tmin=%d\tmax=%d\n"), dbg_total, dbg_sum / (dbg_samples ? dbg_samples : 1), dbg_min, dbg_max); | ||||||
| @@ -328,5 +266,5 @@ void loop() { | |||||||
| 	dbg_min = 32767; | 	dbg_min = 32767; | ||||||
| 	dbg_max = -32768; | 	dbg_max = -32768; | ||||||
| #endif | #endif | ||||||
| 	printf(F("samples: written=%lu, hanging=%lu, dropped=%lu\n"), samples_written, samples_hanging, samples_dropped); | 	info(F("samples: written=%lu, hanging=%lu, dropped=%lu\n"), samples_written, samples_hanging, samples_dropped); | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								spybug/sys.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								spybug/sys.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | // Copyright 2022 Darwin Schuppan <darwin@nobrain.org> | ||||||
|  | // SPDX license identifier: MIT | ||||||
|  |  | ||||||
|  | #include "sys.hh" | ||||||
|  |  | ||||||
|  | void (*full_reset)() = nullptr; | ||||||
|  |  | ||||||
|  | static volatile bool wdt_in_sleep_mode = false; | ||||||
|  | ISR (WDT_vect) { | ||||||
|  | 	if (wdt_in_sleep_mode) | ||||||
|  | 		wdt_disable(); | ||||||
|  | 	else | ||||||
|  | 		full_reset(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Based on https://github.com/rocketscream/Low-Power. */ | ||||||
|  | void low_power_sleep_minutes(unsigned long t) { | ||||||
|  | 	wdt_in_sleep_mode = true; | ||||||
|  | 	ADCSRA &= ~_BV(ADEN); /* Disable ADC. */ | ||||||
|  | 	for (unsigned long i = 0; 8ul * i < 60ul * t; i++) { | ||||||
|  | 		// Power Down for 8s | ||||||
|  | 		wdt_enable(WDTO_8S);   /* Start watchdog timer for 8s. */ | ||||||
|  | 		WDTCSR |= (1 << WDIE); /* Enable watchdog interrupt. */ | ||||||
|  | 		do { | ||||||
|  | 			set_sleep_mode(SLEEP_MODE_PWR_DOWN); | ||||||
|  | 			cli(); | ||||||
|  | 			sleep_enable(); | ||||||
|  | 			sleep_bod_disable(); | ||||||
|  | 			sei(); | ||||||
|  | 			sleep_cpu(); | ||||||
|  | 			sleep_disable(); | ||||||
|  | 			sei(); | ||||||
|  | 		} while (0); | ||||||
|  | 	} | ||||||
|  | 	ADCSRA |= _BV(ADEN); /* Re-enable ADC. */ | ||||||
|  | 	wdt_in_sleep_mode = false; | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								spybug/sys.hh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								spybug/sys.hh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | // Copyright 2022 Darwin Schuppan <darwin@nobrain.org> | ||||||
|  | // SPDX license identifier: MIT | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <avr/interrupt.h> | ||||||
|  | #include <avr/io.h> | ||||||
|  | #include <avr/wdt.h> | ||||||
|  | #include <avr/sleep.h> | ||||||
|  |  | ||||||
|  | extern void (*full_reset)(); | ||||||
|  |  | ||||||
|  | void low_power_sleep_minutes(unsigned long t); | ||||||
|  |  | ||||||
|  | static inline void wdt_enable_with_full_reset() { | ||||||
|  | 	wdt_enable(WDTO_8S);   /* Start watchdog timer for 8s. */ | ||||||
|  | 	WDTCSR |= (1 << WDIE); /* Enable watchdog interrupt. */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static inline void disable_recording_interrupts() { | ||||||
|  | 	TIMSK1 &= ~(_BV(OCIE1A) | _BV(OCIE1B)); | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user