Skip to content

Commit

Permalink
ADC driver for samg series
Browse files Browse the repository at this point in the history
Adds basic polling driver for ADC, and updates the UART example to
include ADC readings.
  • Loading branch information
mcbridejc committed Oct 23, 2021
1 parent 6e9f000 commit 82bc4a9
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 15 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,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
Expand Up @@ -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<Board::DebugUart, modm::IOBuffer::BlockIfFull> 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<Board::SystemClock>();
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);
}
}
6 changes: 6 additions & 0 deletions examples/samg55_xplained_pro/adc-uart/openocd.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

# Configure for ATSAMG55-XPRO dev board
source [find interface/cmsis-dap.cfg]

set CHIPNAME ATSAMG55J19
source [find target/at91samg5x.cfg]
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<library>
<extends>modm:samg55-xplained-pro</extends>
<options>
<option name="modm:build:build.path">../../../build/samg55_xplained_pro/uart</option>
<option name="modm:build:build.path">../../../build/samg55_xplained_pro/adc-uart</option>
<option name="modm:build:openocd.cfg">openocd.cfg</option>
</options>
<modules>
<module>modm:build:scons</module>
<module>modm:platform:adc</module>
</modules>
</library>
170 changes: 170 additions & 0 deletions src/modm/platform/adc/samg/adc.hpp
Original file line number Diff line number Diff line change
@@ -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 <modm/architecture/interface/adc.hpp>
#include <modm/math/algorithm/prescaler.hpp>
#include <modm/platform/clock/clockgen.hpp>
#include <modm/platform/gpio/connector.hpp>


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<class Pin, class... Pins>
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<Peripherals::Adc, Peripherals::Adc::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)>
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<result.frequency, frequency, tolerance>();

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

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<class Pin>
static inline constexpr uint8_t
getPinChannel()
{
using Connector = typename Pin::template Connector<Peripherals::Adc, Peripherals::Adc::Ad>;
static_assert(Connector::PinSignal::AdcChannel >= 0, "Pin cannot be used as ADC input");
return Connector::PinSignal::AdcChannel;
}
};

} // namespace modm::platform
42 changes: 42 additions & 0 deletions src/modm/platform/adc/samg/module.lb
Original file line number Diff line number Diff line change
@@ -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")
11 changes: 8 additions & 3 deletions src/modm/platform/gpio/sam/config.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -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<int index>
Expand All @@ -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<>
Expand Down Expand Up @@ -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

Expand Down
16 changes: 11 additions & 5 deletions src/modm/platform/gpio/sam/module.lb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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())
Expand Down
9 changes: 8 additions & 1 deletion src/modm/platform/gpio/sam/pin.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ enum class PeripheralPin
Wo,
Sck,
Miso,
Mosi
Mosi,
Ad,
};

template<typename... Tuples>
Expand Down Expand Up @@ -407,6 +408,7 @@ public:
using Sck = As<PeripheralPin::Sck>;
using Miso = As<PeripheralPin::Miso>;
using Mosi = As<PeripheralPin::Mosi>;
using Ad = As<PeripheralPin::Ad>;

inline static bool
read()
Expand Down Expand Up @@ -484,7 +486,12 @@ struct Gpio<PinConfig>::As : public Gpio<PinConfig>
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 }})
Expand Down

0 comments on commit 82bc4a9

Please sign in to comment.