Skip to content

Commit

Permalink
[sam] Add basic ADC support for SAMx7x devices
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-durand committed Dec 4, 2022
1 parent d73895e commit 58fa908
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 36 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
<td align="center">✅</td>
<td align="center">○</td>
<td align="center">○</td>
<td align="center"></td>
<td align="center"></td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">○</td>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2021, Jeff McBride
* Copyright (c) 2022, Christopher Durand
*
* This file is part of the modm project.
*
Expand All @@ -14,36 +15,37 @@
#include <modm/platform/clock/clockgen.hpp>
#include <modm/platform/gpio/connector.hpp>

%% if type == "afec"
%% set reg = "AFEC"
%% else
%% set reg = "ADC"
%% endif

namespace modm::platform
{

/// @ingroup modm_platform_adc
class Adc : modm::Adc
class {{type | capitalize}}{{instance}} : modm::Adc
{
static const modm::frequency_t MaxAdcFrequency = modm::MHz(10);
static const modm::frequency_t MaxAdcFrequency = modm::MHz({{max_frequency}});

// Work around a name collision between 'struct modm::Adc' and 'struct Adc'
// defined in the CMSIS headers
static inline ::Adc*
static inline auto*
Regs()
{
return (::Adc*)ADC;
return (::{{type | capitalize}}*){{reg}}{{instance}};
};

public:
enum class Channel : uint8_t
{
Ch0 = 0,
Ch1,
Ch2,
Ch3,
Ch4,
Ch5,
Ch6,
Ch7
%% for i in range(channel_count)
Ch{{i}} = {{i}}{% if not loop.last %},{% endif %}
%% endfor
};

%% if target.family != "e7x/s7x/v7x"
/** Analog settling time setting
*
* Controls how many periods of ADC clock are allowed for input to settle.
Expand All @@ -55,6 +57,7 @@ class Adc : modm::Adc
AST9,
AST17
};
%% endif

public:
// start inherited documentation
Expand All @@ -64,15 +67,20 @@ class Adc : modm::Adc
{
// Pins are automatically connected to the ADC when they are enabled,
// so all this does is set the pin mode to input.
using Connector = typename Pin::template Connector<Peripherals::Adc, Peripherals::Adc::Ad>;
%% if instance
%% set peripheral = type.capitalize() + "<" + instance + ">"
%% else
%% set peripheral = type.capitalize()
%% endif
using Connector = typename Pin::template Connector<Peripherals::{{peripheral}}, Peripherals::{{peripheral}}::Ad>;
static_assert(Connector::PinSignal::AdcChannel >= 0, "Pin cannot be used as ADC input");
Pin::setInput();

// Call recursively for all Pins
if constexpr (sizeof...(Pins)) { connect<Pins...>(); }
}

template<class SystemClock, frequency_t frequency = MHz(10), percent_t tolerance = pct(10)>
template<class SystemClock, frequency_t frequency = MHz({{max_frequency}}), percent_t tolerance = pct(10)>
static inline void
initialize()
{
Expand All @@ -81,33 +89,63 @@ class Adc : modm::Adc
frequency, // desired adc frequency
0, // lowest prescaler value
255, // highest prescaler value
%% if target.family == "e7x/s7x/v7x"
[](uint32_t x) { return (x + 1); } // transform function
%% else
[](uint32_t x) { return 2 * (x + 1); } // transform function
%% endif
);
static_assert(result.frequency <= MaxAdcFrequency,
"Generated ADC frequency is above maximum frequency!");
assertBaudrateInTolerance<result.frequency, frequency, tolerance>();

ClockGen::enable<ClockPeripheral::Adc>();

Regs()->ADC_MR = ADC_MR_PRESCAL(result.index) | ADC_MR_TRANSFER(2);
ClockGen::enable<ClockPeripheral::{{type | capitalize}}{{instance}}>();

Regs()->{{reg}}_MR =
{{reg}}_MR_PRESCAL(result.index) |
%% if target.family == "e7x/s7x/v7x"
// 2: recommended value according to datasheet
{{reg}}_MR_TRANSFER(2) |
// The datasheet states TRACKTIM should be set to 15 AFE clock cycles.
// According to app note 44046 this is achieved with a value of 14.
// If the datasheets guidance is followed to not change this value the
// ADC reads wrong values half of the time.
// Also, none of the 3 vendor HALs use the reset value of 0.
{{reg}}_MR_TRACKTIM(14) |
{{reg}}_MR_ONE; // reserved, must always be set
%% else
{{reg}}_MR_TRANSFER(2);
%% endif

%% if target.family == "e7x/s7x/v7x"
// Enable PGAs and set up biasing
Regs()->{{reg}}_ACR = {{reg}}_ACR_PGA1EN | {{reg}}_ACR_PGA0EN |
AFEC_ACR_IBCTL((result.frequency >= MHz(20) ? 0b11 : 0b10));

// set 10-bit offset DACs to zero offset (value 512, mid scale)
for (int channel = 0; channel < {{channel_count}}; ++channel) {
Regs()->{{reg}}_CSELR = channel; // select channel of COCR register
Regs()->{{reg}}_COCR = 512;
}
%% endif
}

static inline void
startConversion()
{
Regs()->ADC_CR = ADC_CR_START;
Regs()->{{reg}}_CR = {{reg}}_CR_START;
}

static inline bool
isConversionFinished()
{
return Regs()->ADC_ISR & ADC_ISR_DRDY;
return Regs()->{{reg}}_ISR & {{reg}}_ISR_DRDY;
}

static inline uint16_t
getValue()
{
return (uint16_t)(Regs()->ADC_LCDR & 0xffff);
return (uint16_t)(Regs()->{{reg}}_LCDR & 0xffff);
}

static inline uint16_t
Expand All @@ -130,39 +168,42 @@ class Adc : modm::Adc
static inline bool
setChannel(uint8_t channel)
{
if (channel > 7) return false;
Regs()->ADC_CHDR = 0xff;
Regs()->ADC_CHER = (1 << channel);
if (channel >= {{channel_count}}) return false;
Regs()->{{reg}}_CHDR = (1u << {{channel_count}}) - 1u;
Regs()->{{reg}}_CHER = (1 << channel);
return true;
}

static inline void
enableFreeRunningMode()
{
Regs()->ADC_MR |= ADC_MR_FREERUN;
Regs()->{{reg}}_MR |= {{reg}}_MR_FREERUN;
}

static inline void
disableFreeRunningMode()
{
Regs()->ADC_MR &= ~ADC_MR_FREERUN;
Regs()->{{reg}}_MR &= ~{{reg}}_MR_FREERUN;
}

// end inherited documentation

%% if target.family != "e7x/s7x/v7x"
/** Configure the amount of time the ADC input is allowed to settle before sampling
*/
static inline void
setSettlingTime(SettlingTime time)
{
Regs()->ADC_MR = (Regs()->ADC_MR & ~ADC_MR_SETTLING_Msk) | ADC_MR_SETTLING((uint8_t)time);
Regs()->{{reg}}_MR = (Regs()->{{reg}}_MR & ~{{reg}}_MR_SETTLING_Msk) | {{reg}}_MR_SETTLING((uint8_t)time);
}
%% endif

template<class Pin>
static inline constexpr uint8_t
getPinChannel()
{
using Connector = typename Pin::Ad::template Connector<Peripherals::Adc, Peripherals::Adc::Ad>;
using Connector = typename Pin::Ad::template Connector<Peripherals::{{peripheral}}, Peripherals::{{peripheral}}::Ad>;

static_assert(Connector::PinSignal::AdcChannel >= 0, "Pin cannot be used as ADC input");
return Connector::PinSignal::AdcChannel;
}
Expand Down
76 changes: 68 additions & 8 deletions src/modm/platform/adc/samg/module.lb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2021, Jeff McBride
# Copyright (c) 2022, Christopher Durand
#
# This file is part of the modm project.
#
Expand All @@ -14,16 +15,59 @@ def init(module):
module.name = ":platform:adc"
module.description = "Analog-to-Digital Converter (ADC)"


def get_driver(device):
if device.has_driver("afec:sam*"):
return device.get_driver("afec")
else:
return device.get_driver("adc")


def get_adc_type(device):
return "afec" if device.has_driver("afec:sam*") else "adc"


def get_substitutions(device, instance=""):
driver = get_driver(device)
adc_type = get_adc_type(device)

properties = {}
properties["target"] = device.identifier
properties["driver"] = driver
properties["type"] = adc_type
properties["max_frequency"] = 40 if adc_type == "afec" else 10
properties["channel_count"] = 12 if adc_type == "afec" else 8
properties["instance"] = str(instance)
return properties


class Instance(Module):
def __init__(self, instance):
self.instance = instance

def init(self, module):
module.name = str(self.instance)
module.description = "Instance {}".format(self.instance)

def prepare(self, module, options):
return True

def build(self, env):
device = env[":target"]
driver = get_driver(device)

properties = {}
properties.update(get_substitutions(device, self.instance))
env.substitutions = properties
env.outbasepath = "modm/src/modm/platform/adc"

env.template("adc.hpp.in", "{}_{}.hpp".format(get_adc_type(device), self.instance))


def prepare(module, options):
device = options[":target"]
if not device.has_driver("adc:samg*"):
if not device.has_driver("adc:samg*") and not device.has_driver("afec:sam*"):
return False
global props
props = {}
driver = device.get_driver("adc")
props["target"] = device.identifier
props["driver"] = driver
props["instances"] = []

module.depends(
":architecture:adc",
Expand All @@ -34,9 +78,25 @@ def prepare(module, options):
":math:algorithm",
":utils")

driver = get_driver(device)
# If there is only one instance of the peripheral it is not numbered and
# merged into the generic adc module.
if "instance" in driver:
for instance in listify(driver["instance"]):
module.add_submodule(Instance(int(instance)))

return True


def build(env):
device = env[":target"]
driver = get_driver(device)

properties = {}
properties.update(get_substitutions(device))
env.substitutions = properties

env.outbasepath = "modm/src/modm/platform/adc"

env.copy("adc.hpp")
if "instance" not in driver:
env.template("adc.hpp.in", "{}.hpp".format(get_adc_type(device)))

0 comments on commit 58fa908

Please sign in to comment.