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
1 change: 1 addition & 0 deletions cpu/sam0_common/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
config CPU_COMMON_SAM0
bool
select HAS_PERIPH_CPUID
select HAS_PERIPH_ADC_CONTINUOUS
select HAS_PERIPH_FLASHPAGE
select HAS_PERIPH_FLASHPAGE_IN_ADDRESS_SPACE
select HAS_PERIPH_FLASHPAGE_PAGEWISE
Expand Down
1 change: 1 addition & 0 deletions cpu/sam0_common/Makefile.features
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ ifeq (,$(filter $(CPU_MODELS_WITHOUT_DMA),$(CPU_MODEL)))
FEATURES_PROVIDED += periph_dma
endif

FEATURES_PROVIDED += periph_adc_continuous
FEATURES_PROVIDED += periph_flashpage
FEATURES_PROVIDED += periph_flashpage_in_address_space
FEATURES_PROVIDED += periph_flashpage_pagewise
Expand Down
153 changes: 120 additions & 33 deletions cpu/sam0_common/periph/adc.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,6 @@ static int _adc_configure(Adc *dev, adc_res_t res);

static mutex_t _lock = MUTEX_INIT;

static inline void _prep(void)
{
mutex_lock(&_lock);
}

static inline void _done(void)
{
mutex_unlock(&_lock);
}

static inline void _wait_syncbusy(Adc *dev)
{
#ifdef ADC_STATUS_SYNCBUSY
Expand Down Expand Up @@ -263,7 +253,7 @@ int adc_init(adc_t line)
const uint8_t adc = 0;
#endif

_prep();
mutex_lock(&_lock);

uint8_t muxpos = (adc_channels[line].inputctrl & ADC_INPUTCTRL_MUXPOS_Msk)
>> ADC_INPUTCTRL_MUXPOS_Pos;
Expand All @@ -284,42 +274,51 @@ int adc_init(adc_t line)
gpio_init_mux(sam0_adc_pins[adc][muxneg], GPIO_MUX_B);
}

_done();
mutex_unlock(&_lock);

return 0;
}

int32_t adc_sample(adc_t line, adc_res_t res)
static Adc *_dev(adc_t line)
{
if (line >= ADC_NUMOF) {
DEBUG("adc: line arg not applicable\n");
return -1;
}
/* The SAMD5x/SAME5x family has two ADCs: ADC0 and ADC1. */
#ifdef ADC0
return adc_channels[line].dev;
#else
(void)line;
return ADC;
#endif
}

static Adc *_adc(uint8_t dev)
{
/* The SAMD5x/SAME5x family has two ADCs: ADC0 and ADC1. */
#ifdef ADC0
Adc *dev = adc_channels[line].dev;
switch (dev) {
case 0:
return ADC0;
case 1:
return ADC1;
default:
return NULL;
}
#else
Adc *dev = ADC;
(void)dev;
return ADC;
#endif
}

static int32_t _sample(adc_t line)
{
Adc *dev = _dev(line);
bool diffmode = adc_channels[line].inputctrl & ADC_INPUTCTRL_DIFFMODE;

_prep();

if (_adc_configure(dev, res) != 0) {
_done();
DEBUG("adc: configuration failed\n");
return -1;
}

dev->INPUTCTRL.reg = ADC_GAIN_FACTOR_DEFAULT
| adc_channels[line].inputctrl
| (diffmode ? 0 : ADC_NEG_INPUT);
#ifdef ADC_CTRLB_DIFFMODE
dev->CTRLB.bit.DIFFMODE = diffmode;
#endif

_wait_syncbusy(dev);

/* Start the conversion */
Expand All @@ -331,21 +330,109 @@ int32_t adc_sample(adc_t line, adc_res_t res)
uint16_t sample = dev->RESULT.reg;
int result;

_adc_poweroff(dev);
_done();

/* in differential mode we lose one bit for the sign */
if (diffmode) {
result = 2 * (int16_t)sample;
} else {
result = sample;
}

return result;
}

static uint8_t _shift_from_res(adc_res_t res)
{
/* 16 bit mode is implemented as oversampling */
if ((res & 0x3) == 1) {
/* ADC does automatic right shifts beyond 16 samples */
result <<= (4 - MIN(4, res >> 2));
return 4 - MIN(4, res >> 2);
}
return 0;
}

return result;
static void _get_adcs(bool *adc0, bool *adc1)
{
#ifndef ADC1
*adc0 = true;
*adc1 = false;
return;
#else
for (unsigned i = 0; i < ADC_NUMOF; ++i) {
if (adc_channels[i].dev == ADC0) {
*adc0 = true;
} else if (adc_channels[i].dev == ADC1) {
*adc1 = true;
}
}
#endif
}

static uint8_t _shift;
void adc_continuous_begin(adc_res_t res)
{
bool adc0, adc1;
_get_adcs(&adc0, &adc1);

mutex_lock(&_lock);

if (adc0) {
_adc_configure(_adc(0), res);
}
if (adc1) {
_adc_configure(_adc(1), res);
}

_shift = _shift_from_res(res);
}

int32_t adc_continuous_sample(adc_t line)
{
int val;
assert(line < ADC_NUMOF);

mutex_lock(&_lock);
val = _sample(line) << _shift;
mutex_unlock(&_lock);

return val;
}

void adc_continuous_stop(void)
{
bool adc0, adc1;
_get_adcs(&adc0, &adc1);

if (adc0) {
_adc_poweroff(_adc(0));
}
if (adc1) {
_adc_poweroff(_adc(1));
}

mutex_unlock(&_lock);
}

int32_t adc_sample(adc_t line, adc_res_t res)
{
if (line >= ADC_NUMOF) {
DEBUG("adc: line arg not applicable\n");
return -1;
}

mutex_lock(&_lock);

Adc *dev = _dev(line);

if (_adc_configure(dev, res) != 0) {
DEBUG("adc: configuration failed\n");
mutex_unlock(&_lock);
return -1;
}

int val = _sample(line) << _shift_from_res(res);

_adc_poweroff(dev);
mutex_unlock(&_lock);

return val;
}
27 changes: 27 additions & 0 deletions drivers/include/periph/adc.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,33 @@ int adc_init(adc_t line);
*/
int32_t adc_sample(adc_t line, adc_res_t res);

/**
* @brief Configure the ADC with a given resolution for continuous sampling
*
* @note requires the `periph_adc_continuous` feature
*
* @param[in] res resolution to use for conversion
*/
void adc_continuous_begin(adc_res_t res);

/**
* @brief Sample an ADC line without powering off the ADC afterward
*
* @note requires the `periph_adc_continuous` feature
*
* @brief Sample a value from the given ADC line
*
* @return the sampled value on success
*/
int32_t adc_continuous_sample(adc_t line);

/**
* @brief Disable the ADC to save power
*
* @note requires the `periph_adc_continuous` feature
*/
void adc_continuous_stop(void);

#ifdef __cplusplus
}
#endif
Expand Down
5 changes: 5 additions & 0 deletions kconfigs/Kconfig.features
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ config HAS_PERIPH_ADC
help
Indicates that an ADC peripheral is present.

config HAS_PERIPH_ADC_CONTINUOUS
bool
help
Indicates that an ADC peripheral can be left on between measurements.

config HAS_PERIPH_CAN
bool
help
Expand Down
9 changes: 9 additions & 0 deletions tests/periph/adc_continuous/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
BOARD ?= same54-xpro
include ../Makefile.periph_common

FEATURES_REQUIRED += periph_adc
FEATURES_REQUIRED += periph_adc_continuous
USEMODULE += ztimer
USEMODULE += ztimer_msec

include $(RIOTBASE)/Makefile.include
3 changes: 3 additions & 0 deletions tests/periph/adc_continuous/Makefile.ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
BOARD_INSUFFICIENT_MEMORY := \
atmega8 \
#
13 changes: 13 additions & 0 deletions tests/periph/adc_continuous/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Expected result
===============
When running this test, you should see the samples of all configured ADC lines
continuously streamed to std-out.

Background
==========
This test application will initialize each configured ADC lines to sample with
10-bit accuracy. Once configured the application will continuously convert each
available channel and print the conversion results to std-out.

For verification of the output connect the ADC pins to known voltage levels
and compare the output.
58 changes: 58 additions & 0 deletions tests/periph/adc_continuous/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2014-2015 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/

/**
* @ingroup tests
* @{
*
* @file
* @brief Test application for peripheral ADC drivers
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
*
* @}
*/

#include <stdio.h>

#include "ztimer.h"
#include "periph/adc.h"

#define RES ADC_RES_10BIT
#define DELAY_MS 100U

int main(void)
{
int sample = 0;

puts("\nRIOT ADC peripheral driver test\n");
puts("This test will sample all available ADC lines once every 100ms with\n"
"a 10-bit resolution and print the sampled results to STDIO\n\n");

/* initialize all available ADC lines */
for (unsigned i = 0; i < ADC_NUMOF; i++) {
if (adc_init(ADC_LINE(i)) < 0) {
printf("Initialization of ADC_LINE(%u) failed\n", i);
return 1;
} else {
printf("Successfully initialized ADC_LINE(%u)\n", i);
}
}

adc_continuous_begin(RES);
while (1) {
for (unsigned i = 0; i < ADC_NUMOF; i++) {
sample = adc_continuous_sample(ADC_LINE(i));
printf("ADC_LINE(%u): %i\n", i, sample);
}
ztimer_sleep(ZTIMER_MSEC, DELAY_MS);
}

adc_continuous_stop();
return 0;
}