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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions tiny_scope/adc.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ class ADCBase {
*buffer++ = ADCBase::readFast();
}
};
// current sampling mode (0 - getModeCount()-1)
uint8_t getMode(){ return curMode; }
// low-noise bulk read. AVR overrides this with a CPU-idle version; the
// portable fallback just defers to readMulti() so other platforms are unaffected.
void readMultiQuiet(uint16_t *buffer, unsigned size){ readMulti(buffer, size); }
// calibration function, return corrected AREF based on Vbg measured against it.
uint16_t calibrateAREF(){
return 0;
Expand Down
44 changes: 44 additions & 0 deletions tiny_scope/adc_avr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,16 @@
*/
#ifdef __AVR__
#include "adc_avr.h"
#include <avr/sleep.h>
#include <avr/interrupt.h>
#if defined(ADCSRA) && defined(ADCL)

// ADC conversion-complete ISR: intentionally empty. It exists only so the CPU
// wakes from sleep when a conversion finishes (readMultiQuiet sets ADIE).
// analogRead() in the Arduino AVR core only polls ADIF and never enables ADIE,
// so there is no conflict with the normal read path.
EMPTY_INTERRUPT(ADC_vect);

// Actual voltage of Vbg (internal 1.1V ref) in mV. This is different from chip to chip.
// = VrefMeasured*1100/5.000 (with accurate multimeter). VrefMeasured*1100/3.300 for 3.3V systems.
#define INTERNAL_REF_MV 1090
Expand Down Expand Up @@ -98,6 +106,42 @@ void ADCInput::readMulti(uint16_t *buffer, unsigned size){
cbi(ADCSRA, ADATE); // Disable free running
}

/*
* Low-noise variant of readMulti() (issue #13).
*
* Instead of free-running back-to-back conversions, take one single conversion
* at a time and put the CPU to sleep for the duration of each. With the core
* halted the AVR's ADC noise-canceler suppresses digital switching noise that
* would otherwise couple into the sample - a benefit free-running readMulti()
* cannot get because it never sleeps. Intended for the voltmeter/steady-signal
* display where per-sample sleep latency is irrelevant.
*
* SLEEP_MODE_IDLE (not SLEEP_MODE_ADC) is deliberate: ADC noise-reduction mode
* halts clkIO, which would stop Timer0 (micros/millis) and the PWM test signals.
* IDLE keeps clkIO running, so other interrupts (e.g. Timer0) may wake us early;
* the inner loop re-checks ADSC and sleeps again until the conversion is done.
* This uses the canonical race-free avr-libc sleep pattern (cli/sleep_enable/sei).
*/
void ADCInput::readMultiQuiet(uint16_t *buffer, unsigned size){
set_sleep_mode(SLEEP_MODE_IDLE);
sbi(ADCSRA, ADIF); // clear stale completion flag
sbi(ADCSRA, ADIE); // ISR wakes the CPU when conversion completes
for (; size; size--){
sbi(ADCSRA, ADSC);
cli();
while (bit_is_set(ADCSRA, ADSC)){
sleep_enable();
sei();
sleep_cpu(); // other interrupts (Timer0) may wake us early; loop re-checks
sleep_disable();
cli();
}
sei();
*buffer++ = ADCL | (ADCH << 8);
}
cbi(ADCSRA, ADIE);
}

/*
* Read internal reference voltage.
*/
Expand Down
4 changes: 3 additions & 1 deletion tiny_scope/adc_avr.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@

class ADCInput : public ADCBase {
protected:
uint8_t curMode;
// curMode is inherited from ADCBase; do not redeclare it here or it would
// shadow the base member that getMode() reads.
bool setPrescaler(uint8_t mode);
public:
static const uint8_t prescalers[];
Expand All @@ -40,6 +41,7 @@ class ADCInput : public ADCBase {
uint32_t getClock(); // Hz for information purposes only
uint32_t getSampleRate(); // Hz for information purposes only
void readMulti(uint16_t *buffer, unsigned size); // multi-sample read
void readMultiQuiet(uint16_t *buffer, unsigned size); // low-noise multi-sample read

inline uint16_t read() __attribute__((always_inline)){
return analogRead(input);
Expand Down
4 changes: 2 additions & 2 deletions tiny_scope/capture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ int Capture::init(ADCInput adc_in, unsigned samples_in, uint16_t rangemV_in){
* Capture the requested number of samples.
* on return, data contains the raw capture data.
*/
void Capture::capture(){
void Capture::capture(bool quiet){
uint32_t start;
uint16_t *dataCur;
uint16_t v;
Expand Down Expand Up @@ -57,7 +57,7 @@ void Capture::capture(){
}
}
start = micros();
adc.readMulti(dataCur, samples);
quiet ? adc.readMultiQuiet(dataCur, samples) : adc.readMulti(dataCur, samples);
elapsedus = micros() - start;
}

Expand Down
2 changes: 1 addition & 1 deletion tiny_scope/capture.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Capture {

int init(ADCInput adc, unsigned samples, uint16_t rangemV);
void calibrate();
void capture();
void capture(bool quiet=false);
void tomV();
};

Expand Down
17 changes: 14 additions & 3 deletions tiny_scope/tiny_scope.ino
Original file line number Diff line number Diff line change
Expand Up @@ -179,19 +179,26 @@ void setADCMode(){

void loop(){
static Scope scope = Scope(display, 0, SCREEN_WIDTH, 0, 47);
// Use low-noise quiet capture once the signal is steady (voltmeter) or at
// the slowest ADC mode, where the per-sample sleep latency doesn't matter.
static bool quietCapture = false;

if (!capture.samples){
// leave "Out of memory" printed by setup() onscreen.
delay(10000);
return;
}

capture.capture();
capture.capture(quietCapture);
capture.tomV();

// isFlatLine() mutates a rolling-average state, so call it exactly once per
// loop and reuse the result (this also fixes a preexisting double-sample quirk).
bool flatLine = scope.isFlatLine(capture);

display.clearDisplay();
// Enable voltmeter mode if line is flat
if (scope.isFlatLine(capture)){
if (flatLine){
scope.displayVoltMeter(capture);
} else {
scope.displayScope(capture);
Expand All @@ -204,11 +211,15 @@ void loop(){
Serial.print(F("sim: capture samples="));
Serial.println(capture.samples);
Serial.print(F("sim: flatline "));
Serial.println(scope.isFlatLine(capture) ? F("YES") : F("NO"));
Serial.println(flatLine ? F("YES") : F("NO"));
Serial.println(F("sim: TESTS COMPLETE"));
}
#endif

// mode 0 is the slowest prescaler on AVR; on portable builds getMode() is
// always 0 and readMultiQuiet falls back to readMulti, so behavior is unchanged.
quietCapture = flatLine || capture.adc.getMode() == 0;

setADCMode();
// displaying at max 20fps
delay(50);
Expand Down