From 82bc4a99a7fbef08f8f5ffd94a76ece16461190a Mon Sep 17 00:00:00 2001 From: Jeff McBride Date: Tue, 21 Sep 2021 09:41:31 -0700 Subject: [PATCH] ADC driver for samg series Adds basic polling driver for ADC, and updates the UART example to include ADC readings. --- README.md | 2 +- .../{uart => adc-uart}/main.cpp | 30 +++- .../samg55_xplained_pro/adc-uart/openocd.cfg | 6 + .../{uart => adc-uart}/project.xml | 4 +- src/modm/platform/adc/samg/adc.hpp | 170 ++++++++++++++++++ src/modm/platform/adc/samg/module.lb | 42 +++++ src/modm/platform/gpio/sam/config.hpp.in | 11 +- src/modm/platform/gpio/sam/module.lb | 16 +- src/modm/platform/gpio/sam/pin.hpp.in | 9 +- 9 files changed, 275 insertions(+), 15 deletions(-) rename examples/samg55_xplained_pro/{uart => adc-uart}/main.cpp (51%) create mode 100644 examples/samg55_xplained_pro/adc-uart/openocd.cfg rename examples/samg55_xplained_pro/{uart => adc-uart}/project.xml (62%) create mode 100644 src/modm/platform/adc/samg/adc.hpp create mode 100644 src/modm/platform/adc/samg/module.lb 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 }})