cpu/sam0_common: adc: add support for differential mode
Contribution description
In differential mode the ADC measures the difference between two input voltages by setting the muxneg bits to the ADC pin number instead of GND. This voltage differential can also be negative, which results in us reading a singed 16 bit value from the ADC result register.
The muxneg component is written together with the muxpos, the .muxpos field of the ADC config already gets written straight to the INPUTCTRL register, so we can just add muxneg there as well.
To avoid having to introduce a second gpio pin member to adc_conf_chan_t (and keeping that in sync with the .muxpos member, I instead added an array of all ADC Pins to the sam0 CPUs.
Those are fixed and have a 1:1 mapping to GPIOs. This allows us to just set .muxpos and derive the GPIO pin from that. The .pin member is now unused and will be removed by a follow-up (API breaking) PR. Then we can also rename the .muxpos member to the more fitting .inputctrl.
Testing procedure
To test differential mode I used a 3.3V CAN transceiver to create a differential voltage. In dominant mode we should be able to measure the voltage differential between CANH and CANL. In recessive mode the voltage differential should be 0V. A second ADC line was configured to also measure the voltage differential against GND.
- ADC reference is set to 3.3 V Vcc
- ADC line 0 is differential input: voltage between CANH and CANL
- ADC line 1 is normal input: voltage between CANH or CANL and GND
same54-xpro
ADC config
#define ADC_REF_DEFAULT ADC_REFCTRL_REFSEL_INTVCC1
static const adc_conf_chan_t adc_channels[] = {
/* muxpos, dev */
{ .muxpos = ADC_INPUTCTRL_MUXPOS_AIN4 | ADC_INPUTCTRL_MUXNEG_AIN5 | ADC_INPUTCTRL_DIFFMODE, .dev = ADC0},
{ .muxpos = ADC_INPUTCTRL_MUXPOS_AIN4 | ADC_INPUTCTRL_MUXNEG_GND, .dev = ADC0},
};
2022-05-30 18:14:20,801 - INFO # ADC_LINE(0): 724 (2333 mV)
2022-05-30 18:14:20,803 - INFO # ADC_LINE(1): 962 (3100 mV)
2022-05-30 18:14:20,906 - INFO # ADC_LINE(0): 724 (2333 mV)
2022-05-30 18:14:20,908 - INFO # ADC_LINE(1): 962 (3100 mV)
2022-05-30 18:14:21,011 - INFO # ADC_LINE(0): 724 (2333 mV)
2022-05-30 18:14:21,013 - INFO # ADC_LINE(1): 962 (3100 mV)
2022-05-30 18:14:21,117 - INFO # ADC_LINE(0): 724 (2333 mV)
2022-05-30 18:14:21,119 - INFO # ADC_LINE(1): 962 (3100 mV)
2022-05-30 18:14:21,221 - INFO # ADC_LINE(0): 724 (2333 mV)
# swap wires around
2022-05-30 18:14:23,847 - INFO # ADC_LINE(0): -726 (-2339 mV)
2022-05-30 18:14:23,849 - INFO # ADC_LINE(1): 236 (760 mV)
2022-05-30 18:14:23,952 - INFO # ADC_LINE(0): -726 (-2339 mV)
2022-05-30 18:14:23,954 - INFO # ADC_LINE(1): 236 (760 mV)
2022-05-30 18:14:24,056 - INFO # ADC_LINE(0): -726 (-2339 mV)
2022-05-30 18:14:24,059 - INFO # ADC_LINE(1): 236 (760 mV)
2022-05-30 18:14:24,161 - INFO # ADC_LINE(0): -726 (-2339 mV)
2022-05-30 18:14:24,164 - INFO # ADC_LINE(1): 236 (760 mV)
2022-05-30 18:14:24,266 - INFO # ADC_LINE(0): -726 (-2339 mV)
2022-05-30 18:14:24,269 - INFO # ADC_LINE(1): 237 (763 mV)
# recessive mode
2022-05-30 19:02:53,789 - INFO # ADC_LINE(0): -2 (-6 mV)
2022-05-30 19:02:53,791 - INFO # ADC_LINE(1): 699 (2252 mV)
2022-05-30 19:02:53,893 - INFO # ADC_LINE(0): -2 (-6 mV)
2022-05-30 19:02:53,896 - INFO # ADC_LINE(1): 699 (2252 mV)
2022-05-30 19:02:53,998 - INFO # ADC_LINE(0): -2 (-6 mV)
2022-05-30 19:02:54,000 - INFO # ADC_LINE(1): 699 (2252 mV)
2022-05-30 19:02:54,102 - INFO # ADC_LINE(0): -2 (-6 mV)
2022-05-30 19:02:54,105 - INFO # ADC_LINE(1): 700 (2255 mV)
2022-05-30 19:02:54,207 - INFO # ADC_LINE(0): -2 (-6 mV)
2022-05-30 19:02:54,209 - INFO # ADC_LINE(1): 699 (2252 mV)
2022-05-30 19:02:54,312 - INFO # ADC_LINE(0): -2 (-6 mV)
2022-05-30 19:02:54,314 - INFO # ADC_LINE(1): 699 (2252 mV)
samd20-xpro
ADC config
#define ADC_REF_DEFAULT ADC_REFCTRL_REFSEL_AREFB /* PA04 connected to Vcc */
static const adc_conf_chan_t adc_channels[] = {
/* muxpos, dev */
{ .muxpos = ADC_INPUTCTRL_MUXPOS_PIN0 | ADC_INPUTCTRL_MUXNEG_PIN1 | ADC_INPUTCTRL_DIFFMODE },
{ .muxpos = ADC_INPUTCTRL_MUXPOS_PIN0 | ADC_INPUTCTRL_MUXNEG_GND },
};
2022-05-30 18:58:23,286 - INFO # ADC_LINE(0): 726 (2339 mV)
2022-05-30 18:58:23,287 - INFO # ADC_LINE(1): 962 (3100 mV)
2022-05-30 18:58:23,288 - INFO # ADC_LINE(0): 726 (2339 mV)
2022-05-30 18:58:23,290 - INFO # ADC_LINE(1): 962 (3100 mV)
2022-05-30 18:58:23,291 - INFO # ADC_LINE(0): 726 (2339 mV)
2022-05-30 18:58:23,292 - INFO # ADC_LINE(1): 963 (3103 mV)
2022-05-30 18:58:23,294 - INFO # ADC_LINE(0): 726 (2339 mV)
2022-05-30 18:58:23,294 - INFO # ADC_LINE(1): 962 (3100 mV)
2022-05-30 18:58:23,295 - INFO # ADC_LINE(0): 726 (2339 mV)
2022-05-30 18:58:23,296 - INFO # ADC_LINE(1): 962 (3100 mV)
2022-05-30 18:58:23,296 - INFO # ADC_LINE(0): 726 (2339 mV)
2022-05-30 18:58:23,296 - INFO # ADC_LINE(1): 962 (3100 mV)
# swap lines around
2022-05-30 18:58:34,147 - INFO # ADC_LINE(0): -728 (-2346 mV)
2022-05-30 18:58:34,149 - INFO # ADC_LINE(1): 237 (763 mV)
2022-05-30 18:58:34,253 - INFO # ADC_LINE(0): -728 (-2346 mV)
2022-05-30 18:58:34,255 - INFO # ADC_LINE(1): 237 (763 mV)
2022-05-30 18:58:34,359 - INFO # ADC_LINE(0): -728 (-2346 mV)
2022-05-30 18:58:34,361 - INFO # ADC_LINE(1): 237 (763 mV)
2022-05-30 18:58:34,465 - INFO # ADC_LINE(0): -728 (-2346 mV)
2022-05-30 18:58:34,467 - INFO # ADC_LINE(1): 237 (763 mV)
2022-05-30 18:58:34,571 - INFO # ADC_LINE(0): -728 (-2346 mV)
2022-05-30 18:58:34,573 - INFO # ADC_LINE(1): 238 (766 mV)
2022-05-30 18:58:34,677 - INFO # ADC_LINE(0): -728 (-2346 mV)
2022-05-30 18:58:34,679 - INFO # ADC_LINE(1): 237 (763 mV)
# recessive mode
2022-05-30 19:01:13,792 - INFO # ADC_LINE(0): 0 (0 mV)
2022-05-30 19:01:13,794 - INFO # ADC_LINE(1): 704 (2268 mV)
2022-05-30 19:01:13,897 - INFO # ADC_LINE(0): -2 (-6 mV)
2022-05-30 19:01:13,900 - INFO # ADC_LINE(1): 704 (2268 mV)
2022-05-30 19:01:14,003 - INFO # ADC_LINE(0): 0 (0 mV)
2022-05-30 19:01:14,005 - INFO # ADC_LINE(1): 703 (2265 mV)
2022-05-30 19:01:14,108 - INFO # ADC_LINE(0): 0 (0 mV)
2022-05-30 19:01:14,111 - INFO # ADC_LINE(1): 705 (2271 mV)
Issues/PRs references
Maybe I should push a bit more for ADC NG?
Something so I can look at the periph_conf and know how to connect the board.
I wrote
This script
#include <stdbool.h>
#include <stdio.h>
#define GPIO_PIN(a, b) { #a, b }
#define ARRAY_SIZE(a) (sizeof((a)) / sizeof((a)[0]))
typedef struct {
const char *port;
unsigned pin;
} gpio_t;
#define MUXNEG_MAX 7
#define IS_SAMD2X 0
/**
* @brief Pins that can be used for ADC input
*/
static const gpio_t sam0_adc_pins[2][16] = {
{ /* ADC0 pins */
GPIO_PIN(PA, 2), GPIO_PIN(PA, 3), GPIO_PIN(PB, 8), GPIO_PIN(PB, 9),
GPIO_PIN(PA, 4), GPIO_PIN(PA, 5), GPIO_PIN(PA, 6), GPIO_PIN(PA, 7),
GPIO_PIN(PA, 8), GPIO_PIN(PA, 9), GPIO_PIN(PA, 10), GPIO_PIN(PA, 11),
GPIO_PIN(PB, 0), GPIO_PIN(PB, 1), GPIO_PIN(PB, 2), GPIO_PIN(PB, 3)
},
{ /* ADC1 pins */
GPIO_PIN(PB, 8), GPIO_PIN(PB, 9), GPIO_PIN(PA, 8), GPIO_PIN(PA, 9),
GPIO_PIN(PC, 2), GPIO_PIN(PC, 3), GPIO_PIN(PB, 4), GPIO_PIN(PB, 5),
GPIO_PIN(PB, 6), GPIO_PIN(PB, 7), GPIO_PIN(PC, 0), GPIO_PIN(PC, 1),
GPIO_PIN(PC, 30), GPIO_PIN(PC, 31), GPIO_PIN(PD, 0), GPIO_PIN(PD, 1)
}
};
static const char* _num(unsigned i)
{
static char buf[4];
snprintf(buf, sizeof(buf), "%u", i);
return buf;
}
static void print_alias(bool neg)
{
for (unsigned j = 0; j < ARRAY_SIZE(sam0_adc_pins); ++j) {
if (j) {
puts("");
}
for (unsigned i = 0; i < ARRAY_SIZE(sam0_adc_pins[j]); ++i) {
if (neg && i > MUXNEG_MAX) {
break;
}
printf("#define ADC%s_INPUTCTRL_MUX%s_%s%02u ADC_INPUTCTRL_MUXPOS_%cIN%u /**< Alias for %cIN%u */\n",
ARRAY_SIZE(sam0_adc_pins) > 1 ? _num(j) : "",
neg ? "NEG" : "POS",
sam0_adc_pins[j][i].port, sam0_adc_pins[j][i].pin,
IS_SAMD2X ? 'P' : 'A', i,
IS_SAMD2X ? 'P' : 'A', i
);
}
}
}
int main(void)
{
printf("/**\n * @brief ADC pin aliases\n * @{\n */\n");
print_alias(false);
puts("");
print_alias(true);
printf("/** @} */\n");
return 0;
}
to generate the alias defines.