1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
// Analog to digital support on lpc176x
//
// Copyright (C) 2018-2019 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include "LPC17xx.h" // LPC_PINCON
#include "autoconf.h" // CONFIG_CLOCK_FREQ
#include "board/irq.h" // irq_save
#include "board/misc.h" // timer_from_us
#include "command.h" // shutdown
#include "gpio.h" // gpio_adc_setup
#include "internal.h" // gpio_peripheral
#include "sched.h" // sched_shutdown
static const uint8_t adc_pins[] = {
GPIO(0, 23), GPIO(0, 24), GPIO(0, 25), GPIO(0, 26),
GPIO(1, 30), GPIO(1, 31), GPIO(0, 3), GPIO(0, 2),
};
static const uint8_t adc_pin_funcs[] = {
1, 1, 1, 1, 3, 3, 2, 2
};
#define ADC_FREQ_MAX 13000000
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
gpio_adc_setup(uint8_t pin)
{
// Find pin in adc_pins table
int chan;
for (chan=0; ; chan++) {
if (chan >= ARRAY_SIZE(adc_pins))
shutdown("Not a valid ADC pin");
if (adc_pins[chan] == pin)
break;
}
if (!is_enabled_pclock(PCLK_ADC)) {
// Power up ADC
enable_pclock(PCLK_ADC);
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);
return (struct gpio_adc){ .chan = chan };
}
// 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
// returns the number of clock ticks the caller should wait before
// retrying this function.
uint32_t
gpio_adc_sample(struct gpio_adc g)
{
uint32_t chan = adc_status.chan;
if (chan == g.chan) {
// Sample already underway - check if it is ready
if (adc_status.pos >= ARRAY_SIZE(adc_status.samples))
// Sample ready
return 0;
goto need_delay;
}
if (!(chan & ADC_DONE))
// ADC busy on some other channel
goto need_delay;
// Start new sample
adc_status.pos = 0;
adc_status.chan = g.chan;
LPC_ADC->ADCR = adc_status.adcr | (1 << g.chan) | (1<<16);
need_delay:
return ((64 * DIV_ROUND_UP(CONFIG_CLOCK_FREQ*4, ADC_FREQ_MAX)
* 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
uint16_t
gpio_adc_read(struct gpio_adc g)
{
adc_status.chan |= ADC_DONE;
// 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()
void
gpio_adc_cancel_sample(struct gpio_adc g)
{
uint32_t chan = adc_status.chan;
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);
}
|