lpc176x: Implement a median filter on ADC readings

The lpc176x ADC is extremely noisy.  Implement a 5 order median filter
on each ADC read to weed out obviously incorrect readings.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2019-01-12 14:58:53 -05:00
parent cb2ecc68d2
commit d3ec103816
2 changed files with 69 additions and 19 deletions

View File

@ -6,6 +6,7 @@
#include "LPC17xx.h" // LPC_PINCON #include "LPC17xx.h" // LPC_PINCON
#include "autoconf.h" // CONFIG_CLOCK_FREQ #include "autoconf.h" // CONFIG_CLOCK_FREQ
#include "board/irq.h" // irq_save
#include "board/misc.h" // timer_from_us #include "board/misc.h" // timer_from_us
#include "command.h" // shutdown #include "command.h" // shutdown
#include "gpio.h" // gpio_adc_setup #include "gpio.h" // gpio_adc_setup
@ -24,6 +25,17 @@ static const uint8_t adc_pin_funcs[] = {
#define ADC_FREQ_MAX 13000000 #define ADC_FREQ_MAX 13000000
DECL_CONSTANT(ADC_MAX, 4095); DECL_CONSTANT(ADC_MAX, 4095);
// The lpc176x adc is extremely noisy. Implement a 5 entry median
// filter to weed out obviously incorrect readings.
static struct {
uint32_t adcr;
uint16_t pos;
uint16_t chan;
uint16_t samples[5];
} adc_status;
enum { ADC_DONE=0x0100 };
struct gpio_adc struct gpio_adc
gpio_adc_setup(uint8_t pin) gpio_adc_setup(uint8_t pin)
{ {
@ -36,20 +48,37 @@ gpio_adc_setup(uint8_t pin)
break; break;
} }
uint32_t prescal = DIV_ROUND_UP(CONFIG_CLOCK_FREQ*4, ADC_FREQ_MAX) - 1;
uint32_t adcr = (1<<21) | ((prescal & 0xff) << 8);
if (!is_enabled_pclock(PCLK_ADC)) { if (!is_enabled_pclock(PCLK_ADC)) {
// Power up ADC // Power up ADC
enable_pclock(PCLK_ADC); enable_pclock(PCLK_ADC);
LPC_ADC->ADCR = adcr; uint32_t prescal = DIV_ROUND_UP(CONFIG_CLOCK_FREQ*4, ADC_FREQ_MAX) - 1;
LPC_ADC->ADCR = adc_status.adcr = (1<<21) | ((prescal & 0xff) << 8);
LPC_ADC->ADINTEN = 0xff;
adc_status.chan = ADC_DONE;
NVIC_SetPriority(ADC_IRQn, 0);
NVIC_EnableIRQ(ADC_IRQn);
} }
gpio_peripheral(pin, adc_pin_funcs[chan], 0); gpio_peripheral(pin, adc_pin_funcs[chan], 0);
return (struct gpio_adc){ .cmd = adcr | (1 << chan) | (1 << 24) }; return (struct gpio_adc){ .chan = chan };
} }
static uint32_t adc_status; // ADC hardware irq handler
void __visible
ADC_IRQHandler(void)
{
uint32_t pos = adc_status.pos, chan = adc_status.chan & 0xff;
uint32_t result = (&LPC_ADC->ADDR0)[chan];
if (pos >= ARRAY_SIZE(adc_status.samples))
// All samples complete
return;
if (pos >= ARRAY_SIZE(adc_status.samples) - 2)
// Turn off burst mode
LPC_ADC->ADCR = adc_status.adcr | (1 << chan);
adc_status.samples[pos++] = (result >> 4) & 0x0fff;
adc_status.pos = pos;
}
// Try to sample a value. Returns zero if sample ready, otherwise // Try to sample a value. Returns zero if sample ready, otherwise
// returns the number of clock ticks the caller should wait before // returns the number of clock ticks the caller should wait before
@ -57,40 +86,61 @@ static uint32_t adc_status;
uint32_t uint32_t
gpio_adc_sample(struct gpio_adc g) gpio_adc_sample(struct gpio_adc g)
{ {
uint32_t status = adc_status; uint32_t chan = adc_status.chan;
if (status == g.cmd) { if (chan == g.chan) {
// Sample already underway - check if it is ready // Sample already underway - check if it is ready
uint32_t val = LPC_ADC->ADGDR; if (adc_status.pos >= ARRAY_SIZE(adc_status.samples))
if (val & (1<<31))
// Sample ready // Sample ready
return 0; return 0;
goto need_delay; goto need_delay;
} }
if (status) if (!(chan & ADC_DONE))
// ADC busy on some other channel // ADC busy on some other channel
goto need_delay; goto need_delay;
// Start new sample // Start new sample
adc_status = g.cmd; adc_status.pos = 0;
LPC_ADC->ADCR = g.cmd; adc_status.chan = g.chan;
LPC_ADC->ADCR = adc_status.adcr | (1 << g.chan) | (1<<16);
need_delay: need_delay:
return (65 * DIV_ROUND_UP(CONFIG_CLOCK_FREQ*4, ADC_FREQ_MAX) return ((64 * DIV_ROUND_UP(CONFIG_CLOCK_FREQ*4, ADC_FREQ_MAX)
+ timer_from_us(10)); * ARRAY_SIZE(adc_status.samples)) / 4 + timer_from_us(10));
} }
#define ORDER(r1, r2) do { \
if (r1 > r2) { uint32_t t = r1; r1 = r2; r2 = t; } \
} while (0)
// Read a value; use only after gpio_adc_sample() returns zero // Read a value; use only after gpio_adc_sample() returns zero
uint16_t uint16_t
gpio_adc_read(struct gpio_adc g) gpio_adc_read(struct gpio_adc g)
{ {
adc_status = 0; adc_status.chan |= ADC_DONE;
return (LPC_ADC->ADGDR >> 4) & 0x0fff; // Perform median filter on 5 read samples
uint16_t *p = adc_status.samples;
uint32_t v0 = p[0], v4 = p[1], v1 = p[2], v3 = p[3], v2 = p[4];
ORDER(v0, v4);
ORDER(v1, v3);
ORDER(v0, v1);
ORDER(v3, v4);
ORDER(v1, v3);
ORDER(v1, v2);
ORDER(v2, v3);
return v2;
} }
// Cancel a sample that may have been started with gpio_adc_sample() // Cancel a sample that may have been started with gpio_adc_sample()
void void
gpio_adc_cancel_sample(struct gpio_adc g) gpio_adc_cancel_sample(struct gpio_adc g)
{ {
if (adc_status == g.cmd) uint32_t chan = adc_status.chan;
adc_status = 0; if (chan != g.chan)
return;
irqstatus_t flag = irq_save();
LPC_ADC->ADCR = adc_status.adcr;
adc_status.chan = chan | ADC_DONE;
adc_status.pos = ARRAY_SIZE(adc_status.samples);
(&LPC_ADC->ADDR0)[chan & 0xff];
irq_restore(flag);
} }

View File

@ -22,7 +22,7 @@ void gpio_in_reset(struct gpio_in g, int8_t pull_up);
uint8_t gpio_in_read(struct gpio_in g); uint8_t gpio_in_read(struct gpio_in g);
struct gpio_adc { struct gpio_adc {
uint32_t cmd; uint32_t chan;
}; };
struct gpio_adc gpio_adc_setup(uint8_t pin); struct gpio_adc gpio_adc_setup(uint8_t pin);
uint32_t gpio_adc_sample(struct gpio_adc g); uint32_t gpio_adc_sample(struct gpio_adc g);