Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic ADC support for SAMx7x #935

Merged
merged 2 commits into from
Dec 26, 2022
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
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
37 changes: 37 additions & 0 deletions examples/samv71_xplained_ultra/adc/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2021, Jeff McBride
* Copyright (c) 2022, Christopher Durand
*
* This file is part of the modm project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

#include <modm/board.hpp>
#include <modm/io/iostream.hpp>

using namespace modm::platform;
using namespace modm::literals;

using Ad2 = GpioA19::Ad; // channel 0, pin AD2 on board
using Ad3 = GpioD30::Ad; // channel 8, pin AD3 on board

int main()
{
Board::initialize();

Afec0::initialize<Board::SystemClock>();
Afec0::connect<Ad2::Ad, Ad3::Ad>();

while (true)
{
MODM_LOG_INFO << "ADC Readings: ";
MODM_LOG_INFO.printf("%5d ", Afec0::readChannel(Afec0::getPinChannel<Ad2>()));
MODM_LOG_INFO.printf("%5d ", Afec0::readChannel(Afec0::getPinChannel<Ad3>()));
MODM_LOG_INFO << modm::endl;

modm::delay(500ms);
}
}
10 changes: 10 additions & 0 deletions examples/samv71_xplained_ultra/adc/project.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<library>
<extends>modm:samv71-xplained-ultra</extends>
<options>
<option name="modm:build:build.path">../../../build/samv71_xplained_ultra/adc</option>
</options>
<modules>
<module>modm:build:scons</module>
<module>modm:platform:adc:0</module>
</modules>
</library>
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)))