diff --git a/README.md b/README.md
index a8c758785e..b83e3da1f8 100644
--- a/README.md
+++ b/README.md
@@ -137,7 +137,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✅ |
✅ |
○ |
-○ |
+✅ |
○ |
○ |
✅ |
diff --git a/examples/samg55_xplained_pro/uart/main.cpp b/examples/samg55_xplained_pro/adc-uart/main.cpp
similarity index 51%
rename from examples/samg55_xplained_pro/uart/main.cpp
rename to examples/samg55_xplained_pro/adc-uart/main.cpp
index 9baa11721b..e9f4a220a5 100644
--- a/examples/samg55_xplained_pro/uart/main.cpp
+++ b/examples/samg55_xplained_pro/adc-uart/main.cpp
@@ -15,19 +15,41 @@ using namespace modm::platform;
using namespace modm::literals;
// Create IO wrapper for the debug UART, which is connected to the built-in
-// USB debugger virtual COM port
+// USB debugger's virtual COM port
modm::IODeviceWrapper debugDevice;
modm::IOStream debugStream(debugDevice);
+// Rename because the ::Adc typename defined in the CMSIS header conflicts with
+// the name imported from the modm::platform namespace
+using AdcDev = modm::platform::Adc;
+
int
main()
{
+ /** This example reads all 6 ADC channels and prints their values to the
+ * debug UART.
+ **/
+
Board::initialize();
- uint32_t cycle = 0;
+ AdcDev::initialize();
+ AdcDev::connect<
+ GpioA17::Ad,
+ GpioA18::Ad,
+ GpioA19::Ad,
+ GpioA20::Ad,
+ GpioB0::Ad,
+ GpioB1::Ad>();
+
while (true)
{
- modm::delay(1s);
- debugStream.printf("Cycle: %lu\r\n", cycle);
+ debugStream.printf("ADC Readings: ");
+ for(uint32_t i=0; i<6; i++)
+ {
+ debugStream.printf("%5d ", AdcDev::readChannel(i));
+ }
+ debugStream.printf("\r\n");
+
+ modm::delay(500ms);
}
}
\ No newline at end of file
diff --git a/examples/samg55_xplained_pro/adc-uart/openocd.cfg b/examples/samg55_xplained_pro/adc-uart/openocd.cfg
new file mode 100644
index 0000000000..233bc3ba94
--- /dev/null
+++ b/examples/samg55_xplained_pro/adc-uart/openocd.cfg
@@ -0,0 +1,6 @@
+
+# Configure for ATSAMG55-XPRO dev board
+source [find interface/cmsis-dap.cfg]
+
+set CHIPNAME ATSAMG55J19
+source [find target/at91samg5x.cfg]
diff --git a/examples/samg55_xplained_pro/uart/project.xml b/examples/samg55_xplained_pro/adc-uart/project.xml
similarity index 62%
rename from examples/samg55_xplained_pro/uart/project.xml
rename to examples/samg55_xplained_pro/adc-uart/project.xml
index a79d630b84..159b733451 100644
--- a/examples/samg55_xplained_pro/uart/project.xml
+++ b/examples/samg55_xplained_pro/adc-uart/project.xml
@@ -1,9 +1,11 @@
modm:samg55-xplained-pro
-
+
+
modm:build:scons
+ modm:platform:adc
\ No newline at end of file
diff --git a/src/modm/platform/adc/samg/adc.hpp b/src/modm/platform/adc/samg/adc.hpp
new file mode 100644
index 0000000000..9838723b59
--- /dev/null
+++ b/src/modm/platform/adc/samg/adc.hpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2021, Jeff McBride
+ *
+ * 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/.
+ */
+#pragma once
+
+#include
+#include
+#include
+#include
+
+
+namespace modm::platform
+{
+
+class Adc : modm::Adc
+{
+ static const modm::frequency_t MaxAdcFrequency = modm::MHz(10);
+
+ // Work around a name collision between 'struct modm::Adc' and 'struct Adc'
+ // defined in the CMSIS headers
+ static inline ::Adc*
+ Regs()
+ {
+ return (::Adc*)ADC;
+ };
+
+public:
+ enum class Channel : uint8_t
+ {
+ Ch0 = 0,
+ Ch1,
+ Ch2,
+ Ch3,
+ Ch4,
+ Ch5,
+ Ch6,
+ Ch7
+ };
+
+ /** Analog settling time setting
+ *
+ * Controls how many periods of ADC clock are allowed for input to settle.
+ */
+ enum class SettlingTime : uint8_t
+ {
+ AST3 = 0,
+ AST5,
+ AST9,
+ AST17
+ };
+
+public:
+ // start inherited documentation
+ template
+ static inline void
+ connect()
+ {
+ // 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;
+ 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(); }
+ }
+
+ template
+ static inline void
+ initialize()
+ {
+ constexpr auto result = modm::Prescaler::from_function(
+ SystemClock::Frequency, // input frequency
+ frequency, // desired adc frequency
+ 0, // lowest prescaler value
+ 255, // highest prescaler value
+ [](uint32_t x) { return 2 * (x + 1); } // transform function
+ );
+ static_assert(result.frequency <= MaxAdcFrequency,
+ "Generated ADC frequency is above maximum frequency!");
+ assertBaudrateInTolerance();
+
+ ClockGen::enable();
+
+ Regs()->ADC_MR = ADC_MR_PRESCAL(result.index) | ADC_MR_TRANSFER(2);
+ }
+
+ static inline void
+ startConversion()
+ {
+ Regs()->ADC_CR = ADC_CR_START;
+ }
+
+ static inline bool
+ isConversionFinished()
+ {
+ return Regs()->ADC_ISR & ADC_ISR_DRDY;
+ }
+
+ static inline uint16_t
+ getValue()
+ {
+ return (uint16_t)(Regs()->ADC_LCDR & 0xffff);
+ }
+
+ static inline uint16_t
+ readChannel(uint8_t channel)
+ {
+ if (!setChannel(channel)) return 0;
+
+ startConversion();
+ while (!isConversionFinished()) {}
+
+ return getValue();
+ }
+
+ static inline bool
+ setChannel(Channel channel)
+ {
+ return setChannel((uint8_t)channel);
+ }
+
+ static inline bool
+ setChannel(uint8_t channel)
+ {
+ if (channel > 7) return false;
+ Regs()->ADC_CHDR = 0xff;
+ Regs()->ADC_CHER = (1 << channel);
+ return true;
+ }
+
+ static inline void
+ enableFreeRunningMode()
+ {
+ Regs()->ADC_MR |= ADC_MR_FREERUN;
+ }
+
+ static inline void
+ disableFreeRunningMode()
+ {
+ Regs()->ADC_MR &= ~ADC_MR_FREERUN;
+ }
+
+ // end inherited documentation
+
+ /** 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);
+ }
+
+ template
+ static inline constexpr uint8_t
+ getPinChannel()
+ {
+ using Connector = typename Pin::template Connector;
+ static_assert(Connector::PinSignal::AdcChannel >= 0, "Pin cannot be used as ADC input");
+ return Connector::PinSignal::AdcChannel;
+ }
+};
+
+} // namespace modm::platform
\ No newline at end of file
diff --git a/src/modm/platform/adc/samg/module.lb b/src/modm/platform/adc/samg/module.lb
new file mode 100644
index 0000000000..d3f991bf83
--- /dev/null
+++ b/src/modm/platform/adc/samg/module.lb
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2021, Jeff McBride
+#
+# 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/.
+# -----------------------------------------------------------------------------
+
+def init(module):
+ module.name = ":platform:adc"
+ module.description = "Analog-to-Digital Converter (ADC)"
+
+def prepare(module, options):
+ device = options[":target"]
+ if not device.has_driver("adc:samg*"):
+ return False
+ global props
+ props = {}
+ driver = device.get_driver("adc")
+ props["target"] = device.identifier
+ props["driver"] = driver
+ props["instances"] = []
+
+ module.depends(
+ ":architecture:adc",
+ ":architecture:register",
+ ":cmsis:device",
+ ":platform:gpio",
+ ":platform:clockgen",
+ ":math:algorithm",
+ ":utils")
+
+ return True
+
+def build(env):
+ env.outbasepath = "modm/src/modm/platform/adc"
+
+ env.copy("adc.hpp")
\ No newline at end of file
diff --git a/src/modm/platform/gpio/sam/config.hpp.in b/src/modm/platform/gpio/sam/config.hpp.in
index 00ddad8427..3a4f9d6e6d 100644
--- a/src/modm/platform/gpio/sam/config.hpp.in
+++ b/src/modm/platform/gpio/sam/config.hpp.in
@@ -35,7 +35,7 @@ struct Peripherals
struct {{ name }}
{
%% for signal, index_list in peripheral["signals"].items()
-%% if not index_list
+%% if not index_list or name == "Adc"
struct {{ signal }} {};
%% else
template
@@ -62,7 +62,7 @@ struct Peripherals::{{ name }}<{{ instance }}>::{{ signal }} {};
%% endif
%% endfor
%% endfor
-%% else
+%% elif name != "Adc"
%% for signal, index_list in peripheral["signals"].items()
%% for index in index_list
template<>
@@ -99,11 +99,16 @@ struct {{ gpio["port"] ~ gpio["pin"] }}
%% else
using peripheral = Peripherals::{{ signal["peripheral"] }};
%% endif
- %% if "index" in signal
+ %% if "index" in signal and signal["peripheral"] != "Adc"
using signal = peripheral::{{ signal["name"] }}<{{ signal["index"] }}>;
%% else
using signal = peripheral::{{ signal["name"] }};
%% endif
+ %% if signal["peripheral"] == "Adc" and "index" in signal
+ static constexpr int32_t AdcChannel = {{ signal["index"] }};
+ %% else
+ static constexpr int32_t AdcChannel = -1;
+ %% endif
};
%% endfor
diff --git a/src/modm/platform/gpio/sam/module.lb b/src/modm/platform/gpio/sam/module.lb
index 58ea5bd0f9..ac5081d7d2 100644
--- a/src/modm/platform/gpio/sam/module.lb
+++ b/src/modm/platform/gpio/sam/module.lb
@@ -70,10 +70,6 @@ def build(env):
"function": signal["function"].capitalize(),
"name": signal["name"].capitalize(),
})
- # SAMG devices have X1 peripheral connection, which is "extra function",
- # but it is not configured by the normal alternate IO function. Skip these.
- if signal['function'] == 'X1':
- continue
# This is a hack for L variant devices, which have a Ac without instance *and*
# a Ac with instance 1. Why???
if (device.identifier.get("variant") == "l"
@@ -84,7 +80,17 @@ def build(env):
peripheral = peripherals.setdefault(signal["peripheral"], dict())
if "instance" in signal:
signal["full_name"] += signal["instance"]
- signal["instance"] = int(signal["instance"])
+ try:
+ signal["instance"] = int(signal["instance"])
+ except ValueError:
+ # On SAMV devices, some signals (e.g. PIODC/data capture) belong to
+ # PIOs, and have alphabetic instance values. The config.hpp.in
+ # template requires integers, so convert "a", "b", "c" etc to
+ # integer values
+ if len(signal["instance"]) == 1:
+ signal["instance"] = ord(signal["instance"].lower()) - ord("a")
+ else:
+ raise ValueError(f"Encountered invalid signal instance {signal['instance']}")
peripheral.setdefault("instances", set()).add(signal["instance"])
signal["full_name"] += signal["name"]
p_signal = peripheral.setdefault("signals", dict()).setdefault(signal["name"], set())
diff --git a/src/modm/platform/gpio/sam/pin.hpp.in b/src/modm/platform/gpio/sam/pin.hpp.in
index acba29e479..f7208138e5 100644
--- a/src/modm/platform/gpio/sam/pin.hpp.in
+++ b/src/modm/platform/gpio/sam/pin.hpp.in
@@ -39,7 +39,8 @@ enum class PeripheralPin
Wo,
Sck,
Miso,
- Mosi
+ Mosi,
+ Ad,
};
template
@@ -407,6 +408,7 @@ public:
using Sck = As;
using Miso = As;
using Mosi = As;
+ using Ad = As;
inline static bool
read()
@@ -484,7 +486,12 @@ struct Gpio::As : public Gpio
inline static void
connect()
{
+
%% if target["family"] in ["g", "v"]
+ // X1 denotes an "extra function", such as an ADC pin, which is not
+ // enabled by the PIO ABCD register.
+ static_assert(PinSignal::function != PinFunction::X1,
+ "This is a system connection, and cannot be connected by Gpio connect()");
Pio* PIOBase;
%% for port in ports
if constexpr (PinConfig::port == PortName::{{ port }})