|
// SPDX-License-Identifier: CC-BY-SA-4.0 |
|
// Copyright 2019 Peter A. Bigot |
|
|
|
/** See: |
|
* https://gist.github.com/pabigot/10775b4d62ee2edec36b51ec448e7f76 |
|
* https://devzone.nordicsemi.com/f/nordic-q-a/45339/saadc-burst-problems-in-scan-vs-non-scan-acquisitions |
|
* */ |
|
|
|
#include <cstdio> |
|
|
|
#include <nrfcxx/clock.hpp> |
|
#include <nrfcxx/periph.hpp> |
|
#include <nrfcxx/sensor/adc.hpp> |
|
|
|
/* Set to 0 to enable workaround */ |
|
#define CONFIG_BURST 1 |
|
#define CONFIG_OVERSAMPLE 2 |
|
|
|
namespace { |
|
|
|
#define EVT_CALADC 0x01 |
|
#define EVT_ALARM 0x02 |
|
#define EVT_PROCESS 0x04 |
|
|
|
#define SEVT_CALIBRATEDONE 0x01 |
|
#define SEVT_STARTED 0x02 |
|
#define SEVT_END 0x04 |
|
#define SEVT_DONE 0x08 |
|
#define SEVT_RESULTDONE 0x10 |
|
#define SEVT_STOPPED 0x20 |
|
#define SEVT_RESTARTED 0x100 |
|
nrfcxx::event_set events; |
|
|
|
auto& SAADC = *nrfcxx::nrf5::SAADC.instance(); |
|
|
|
int16_t volatile results[8]; |
|
|
|
void |
|
start_saadc () |
|
{ |
|
SAADC.EVENTS_STARTED = 0; |
|
SAADC.EVENTS_END = 0; |
|
SAADC.EVENTS_DONE = 0; |
|
SAADC.EVENTS_STOPPED = 0; |
|
SAADC.EVENTS_CALIBRATEDONE = 0; |
|
SAADC.INTENSET = SAADC_INTENSET_CH7LIMITL_Msk; |
|
SAADC.ENABLE = 1; |
|
SAADC.TASKS_START = 1; |
|
} |
|
|
|
void |
|
stop_saadc () |
|
{ |
|
SAADC.ENABLE = 0; |
|
} |
|
|
|
void |
|
setup_acquisition (bool multi) |
|
{ |
|
unsigned int nresults; |
|
unsigned int ain; |
|
if (multi) { |
|
nresults = 2; |
|
ain = 4; |
|
} else { |
|
nresults = 1; |
|
ain = 1; |
|
} |
|
#if 1 |
|
/* Workaround for PAN-212. |
|
* |
|
* This works when placed immediately before changing the CONFIG |
|
* registers. It does not work if placed around ENABLE or after |
|
* EVENTS_STARTED. |
|
* |
|
* From review of the documentation this appears to do a power-cycle |
|
* of the SAADC peripheral. The following has been observed: |
|
* * Settings to the INTEN register are preserved by this sequence |
|
* * The CH, RESOLUTION, OVERSAMPLE, and RESULT registers are reset |
|
* to their POR values by this sequence. |
|
*/ |
|
*(volatile uint32_t *)0x40007FFC = 0; |
|
*(volatile uint32_t *)0x40007FFC = 1; |
|
#endif |
|
SAADC.RESOLUTION = SAADC_RESOLUTION_VAL_14bit << SAADC_RESOLUTION_VAL_Pos; |
|
SAADC.OVERSAMPLE = CONFIG_OVERSAMPLE; |
|
for (auto ci = 0U; ci < 8; ++ci) { |
|
results[ci] = 0; |
|
if (ci < nresults) { |
|
SAADC.CH[ci].CONFIG = 0x00051200; // REFSEL Vdd, 40 us |
|
SAADC.CH[ci].PSELP = (SAADC_CH_PSELP_PSELP_AnalogInput0 + ain + ci) << SAADC_CH_PSELP_PSELP_Pos; |
|
} else { |
|
SAADC.CH[ci].CONFIG = 0x00020000; |
|
SAADC.CH[ci].PSELP = SAADC_CH_PSELP_PSELP_NC << SAADC_CH_PSELP_PSELP_Pos; |
|
} |
|
#if (CONFIG_BURST - 0) |
|
SAADC.CH[ci].CONFIG |= 0x01000000; |
|
#endif |
|
SAADC.CH[ci].PSELN = SAADC_CH_PSELN_PSELN_NC << SAADC_CH_PSELN_PSELN_Pos; |
|
} |
|
SAADC.RESULT.PTR = reinterpret_cast<uintptr_t>(results); |
|
SAADC.RESULT.MAXCNT = nresults; |
|
} |
|
|
|
unsigned int |
|
saadc_process () |
|
{ |
|
auto evts = 0U; |
|
if (SAADC.EVENTS_STARTED) { |
|
evts |= SEVT_STARTED; |
|
SAADC.EVENTS_STARTED = 0; |
|
SAADC.TASKS_SAMPLE = 1; |
|
} |
|
if (SAADC.EVENTS_END) { |
|
evts |= SEVT_END; |
|
SAADC.EVENTS_END = 0; |
|
|
|
/* Explicit stop to ensure the ADC isn't consuming power after the |
|
* sampling sequence has completed, and to ensure the START |
|
* required for the next acquisition begins in a clean state. */ |
|
SAADC.EVENTS_STOPPED = 0; |
|
SAADC.TASKS_STOP = 1; |
|
} |
|
if (SAADC.EVENTS_DONE) { |
|
evts |= SEVT_DONE; |
|
SAADC.EVENTS_DONE = 0; |
|
#if !(CONFIG_BURST - 0) |
|
/* Simulate BURST by triggering another sample if the collection |
|
* is not complete. */ |
|
if (!(SEVT_END & evts)) { |
|
evts |= SEVT_RESTARTED; |
|
SAADC.TASKS_SAMPLE = 1; |
|
} |
|
#endif |
|
} |
|
if (SAADC.EVENTS_RESULTDONE) { |
|
evts |= SEVT_RESULTDONE; |
|
SAADC.EVENTS_RESULTDONE = 0; |
|
} |
|
if (SAADC.EVENTS_STOPPED) { |
|
evts |= SEVT_STOPPED; |
|
SAADC.EVENTS_STOPPED = 0; |
|
} |
|
return evts; |
|
} |
|
|
|
void |
|
dump_adc () |
|
{ |
|
using namespace nrfcxx; |
|
|
|
printf("ADC: ST %ld END %ld DONE %ld RDONE %ld CD %ld STOP %ld\n", |
|
SAADC.EVENTS_STARTED, SAADC.EVENTS_END, |
|
SAADC.EVENTS_DONE, SAADC.EVENTS_RESULTDONE, |
|
SAADC.EVENTS_CALIBRATEDONE, SAADC.EVENTS_STOPPED); |
|
printf("INTEN %lx STAT %lx ENA %lx\n", |
|
SAADC.INTEN, SAADC.STATUS, SAADC.ENABLE); |
|
printf("OVERSAMPLE %lu ; RESOLUTION %lu \n", SAADC.OVERSAMPLE, SAADC.RESOLUTION); |
|
printf("RESULT %lu of %lu to %lx\n", SAADC.RESULT.AMOUNT, SAADC.RESULT.MAXCNT, SAADC.RESULT.PTR); |
|
for (unsigned int ci = 0; ci < nrf5::SAADC.AUX; ++ci) { |
|
auto& ch = SAADC.CH[ci]; |
|
printf("%u: %08lx %08lx %08lx\n", ci, ch.PSELP, ch.PSELN, ch.CONFIG); |
|
} |
|
} |
|
|
|
} // anonymous namespace |
|
|
|
int |
|
main (void) |
|
{ |
|
using namespace nrfcxx; |
|
board::initialize(); |
|
|
|
using clock::uptime; |
|
|
|
setvbuf(stdout, NULL, _IONBF, 0); |
|
puts("\n\n" __FILE__ " " __DATE__ " " __TIME__); |
|
|
|
auto alarm = clock::alarm::for_event<EVT_ALARM, true>(events); |
|
alarm |
|
.set_interval(1 * uptime::Frequency_Hz) |
|
.set_deadline(alarm.interval()) |
|
.schedule(); |
|
|
|
events.set(EVT_ALARM); |
|
|
|
unsigned int ctr = 0; |
|
bool ready = true; |
|
while (true) { |
|
uptime::text_type buf; |
|
event_set::cev(); |
|
|
|
auto pending = events.copy_and_clear(); |
|
int rc = saadc_process(); |
|
if (rc) { |
|
if (SEVT_STOPPED & rc) { |
|
stop_saadc(); |
|
printf("%s: %02x Completed %lu of %lu:", uptime::as_text(buf, uptime::now()), rc, SAADC.RESULT.AMOUNT, SAADC.RESULT.MAXCNT); |
|
for (auto ci = 0U; ci < 8; ++ci) { |
|
printf(" %d", results[ci]); |
|
} |
|
putchar('\n'); |
|
if (ctr <= 2) { |
|
dump_adc(); |
|
} |
|
ready = true; |
|
} else { |
|
printf("sevt %02x\n", rc); |
|
} |
|
|
|
} |
|
if (!pending.empty()) { |
|
if (pending.test_and_clear(EVT_ALARM)) { |
|
if (!ready) { |
|
printf("Sample failed to complete: %lu of %lu\n\t", |
|
SAADC.RESULT.AMOUNT, SAADC.RESULT.MAXCNT); |
|
for (auto ci = 0U; ci < 8; ++ci) { |
|
printf(" %d", results[ci]); |
|
} |
|
putchar('\n'); |
|
dump_adc(); |
|
break; |
|
} |
|
printf("%s: Setting up %u\n", uptime::as_text(buf, uptime::now()), ctr); |
|
setup_acquisition(1 & ctr); |
|
start_saadc(); |
|
++ctr; |
|
ready = false; |
|
} |
|
} |
|
} |
|
puts("Failed"); |
|
} |