Skip to content

Commit 82bc4a9

Browse files
committed
ADC driver for samg series
Adds basic polling driver for ADC, and updates the UART example to include ADC readings.
1 parent 6e9f000 commit 82bc4a9

File tree

9 files changed

+275
-15
lines changed

9 files changed

+275
-15
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
137137
<td align="center">✅</td>
138138
<td align="center">✅</td>
139139
<td align="center">○</td>
140-
<td align="center"></td>
140+
<td align="center"></td>
141141
<td align="center">○</td>
142142
<td align="center">○</td>
143143
<td align="center">✅</td>

examples/samg55_xplained_pro/uart/main.cpp renamed to examples/samg55_xplained_pro/adc-uart/main.cpp

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,41 @@ using namespace modm::platform;
1515
using namespace modm::literals;
1616

1717
// Create IO wrapper for the debug UART, which is connected to the built-in
18-
// USB debugger virtual COM port
18+
// USB debugger's virtual COM port
1919
modm::IODeviceWrapper<Board::DebugUart, modm::IOBuffer::BlockIfFull> debugDevice;
2020
modm::IOStream debugStream(debugDevice);
2121

22+
// Rename because the ::Adc typename defined in the CMSIS header conflicts with
23+
// the name imported from the modm::platform namespace
24+
using AdcDev = modm::platform::Adc;
25+
2226
int
2327
main()
2428
{
29+
/** This example reads all 6 ADC channels and prints their values to the
30+
* debug UART.
31+
**/
32+
2533
Board::initialize();
2634

27-
uint32_t cycle = 0;
35+
AdcDev::initialize<Board::SystemClock>();
36+
AdcDev::connect<
37+
GpioA17::Ad,
38+
GpioA18::Ad,
39+
GpioA19::Ad,
40+
GpioA20::Ad,
41+
GpioB0::Ad,
42+
GpioB1::Ad>();
43+
2844
while (true)
2945
{
30-
modm::delay(1s);
31-
debugStream.printf("Cycle: %lu\r\n", cycle);
46+
debugStream.printf("ADC Readings: ");
47+
for(uint32_t i=0; i<6; i++)
48+
{
49+
debugStream.printf("%5d ", AdcDev::readChannel(i));
50+
}
51+
debugStream.printf("\r\n");
52+
53+
modm::delay(500ms);
3254
}
3355
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
# Configure for ATSAMG55-XPRO dev board
3+
source [find interface/cmsis-dap.cfg]
4+
5+
set CHIPNAME ATSAMG55J19
6+
source [find target/at91samg5x.cfg]
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
<library>
22
<extends>modm:samg55-xplained-pro</extends>
33
<options>
4-
<option name="modm:build:build.path">../../../build/samg55_xplained_pro/uart</option>
4+
<option name="modm:build:build.path">../../../build/samg55_xplained_pro/adc-uart</option>
5+
<option name="modm:build:openocd.cfg">openocd.cfg</option>
56
</options>
67
<modules>
78
<module>modm:build:scons</module>
9+
<module>modm:platform:adc</module>
810
</modules>
911
</library>

src/modm/platform/adc/samg/adc.hpp

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright (c) 2021, Jeff McBride
3+
*
4+
* This file is part of the modm project.
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public
7+
* License, v. 2.0. If a copy of the MPL was not distributed with this
8+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
*/
10+
#pragma once
11+
12+
#include <modm/architecture/interface/adc.hpp>
13+
#include <modm/math/algorithm/prescaler.hpp>
14+
#include <modm/platform/clock/clockgen.hpp>
15+
#include <modm/platform/gpio/connector.hpp>
16+
17+
18+
namespace modm::platform
19+
{
20+
21+
class Adc : modm::Adc
22+
{
23+
static const modm::frequency_t MaxAdcFrequency = modm::MHz(10);
24+
25+
// Work around a name collision between 'struct modm::Adc' and 'struct Adc'
26+
// defined in the CMSIS headers
27+
static inline ::Adc*
28+
Regs()
29+
{
30+
return (::Adc*)ADC;
31+
};
32+
33+
public:
34+
enum class Channel : uint8_t
35+
{
36+
Ch0 = 0,
37+
Ch1,
38+
Ch2,
39+
Ch3,
40+
Ch4,
41+
Ch5,
42+
Ch6,
43+
Ch7
44+
};
45+
46+
/** Analog settling time setting
47+
*
48+
* Controls how many periods of ADC clock are allowed for input to settle.
49+
*/
50+
enum class SettlingTime : uint8_t
51+
{
52+
AST3 = 0,
53+
AST5,
54+
AST9,
55+
AST17
56+
};
57+
58+
public:
59+
// start inherited documentation
60+
template<class Pin, class... Pins>
61+
static inline void
62+
connect()
63+
{
64+
// Pins are automatically connected to the ADC when they are enabled,
65+
// so all this does is set the pin mode to input.
66+
using Connector = typename Pin::template Connector<Peripherals::Adc, Peripherals::Adc::Ad>;
67+
static_assert(Connector::PinSignal::AdcChannel >= 0, "Pin cannot be used as ADC input");
68+
Pin::setInput();
69+
70+
// Call recursively for all Pins
71+
if constexpr (sizeof...(Pins)) { connect<Pins...>(); }
72+
}
73+
74+
template<class SystemClock, frequency_t frequency = MHz(10), percent_t tolerance = pct(10)>
75+
static inline void
76+
initialize()
77+
{
78+
constexpr auto result = modm::Prescaler::from_function(
79+
SystemClock::Frequency, // input frequency
80+
frequency, // desired adc frequency
81+
0, // lowest prescaler value
82+
255, // highest prescaler value
83+
[](uint32_t x) { return 2 * (x + 1); } // transform function
84+
);
85+
static_assert(result.frequency <= MaxAdcFrequency,
86+
"Generated ADC frequency is above maximum frequency!");
87+
assertBaudrateInTolerance<result.frequency, frequency, tolerance>();
88+
89+
ClockGen::enable<ClockPeripheral::Adc>();
90+
91+
Regs()->ADC_MR = ADC_MR_PRESCAL(result.index) | ADC_MR_TRANSFER(2);
92+
}
93+
94+
static inline void
95+
startConversion()
96+
{
97+
Regs()->ADC_CR = ADC_CR_START;
98+
}
99+
100+
static inline bool
101+
isConversionFinished()
102+
{
103+
return Regs()->ADC_ISR & ADC_ISR_DRDY;
104+
}
105+
106+
static inline uint16_t
107+
getValue()
108+
{
109+
return (uint16_t)(Regs()->ADC_LCDR & 0xffff);
110+
}
111+
112+
static inline uint16_t
113+
readChannel(uint8_t channel)
114+
{
115+
if (!setChannel(channel)) return 0;
116+
117+
startConversion();
118+
while (!isConversionFinished()) {}
119+
120+
return getValue();
121+
}
122+
123+
static inline bool
124+
setChannel(Channel channel)
125+
{
126+
return setChannel((uint8_t)channel);
127+
}
128+
129+
static inline bool
130+
setChannel(uint8_t channel)
131+
{
132+
if (channel > 7) return false;
133+
Regs()->ADC_CHDR = 0xff;
134+
Regs()->ADC_CHER = (1 << channel);
135+
return true;
136+
}
137+
138+
static inline void
139+
enableFreeRunningMode()
140+
{
141+
Regs()->ADC_MR |= ADC_MR_FREERUN;
142+
}
143+
144+
static inline void
145+
disableFreeRunningMode()
146+
{
147+
Regs()->ADC_MR &= ~ADC_MR_FREERUN;
148+
}
149+
150+
// end inherited documentation
151+
152+
/** Configure the amount of time the ADC input is allowed to settle before sampling
153+
*/
154+
static inline void
155+
setSettlingTime(SettlingTime time)
156+
{
157+
Regs()->ADC_MR = (Regs()->ADC_MR & ~ADC_MR_SETTLING_Msk) | ADC_MR_SETTLING((uint8_t)time);
158+
}
159+
160+
template<class Pin>
161+
static inline constexpr uint8_t
162+
getPinChannel()
163+
{
164+
using Connector = typename Pin::template Connector<Peripherals::Adc, Peripherals::Adc::Ad>;
165+
static_assert(Connector::PinSignal::AdcChannel >= 0, "Pin cannot be used as ADC input");
166+
return Connector::PinSignal::AdcChannel;
167+
}
168+
};
169+
170+
} // namespace modm::platform

src/modm/platform/adc/samg/module.lb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Copyright (c) 2021, Jeff McBride
5+
#
6+
# This file is part of the modm project.
7+
#
8+
# This Source Code Form is subject to the terms of the Mozilla Public
9+
# License, v. 2.0. If a copy of the MPL was not distributed with this
10+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
11+
# -----------------------------------------------------------------------------
12+
13+
def init(module):
14+
module.name = ":platform:adc"
15+
module.description = "Analog-to-Digital Converter (ADC)"
16+
17+
def prepare(module, options):
18+
device = options[":target"]
19+
if not device.has_driver("adc:samg*"):
20+
return False
21+
global props
22+
props = {}
23+
driver = device.get_driver("adc")
24+
props["target"] = device.identifier
25+
props["driver"] = driver
26+
props["instances"] = []
27+
28+
module.depends(
29+
":architecture:adc",
30+
":architecture:register",
31+
":cmsis:device",
32+
":platform:gpio",
33+
":platform:clockgen",
34+
":math:algorithm",
35+
":utils")
36+
37+
return True
38+
39+
def build(env):
40+
env.outbasepath = "modm/src/modm/platform/adc"
41+
42+
env.copy("adc.hpp")

src/modm/platform/gpio/sam/config.hpp.in

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct Peripherals
3535
struct {{ name }}
3636
{
3737
%% for signal, index_list in peripheral["signals"].items()
38-
%% if not index_list
38+
%% if not index_list or name == "Adc"
3939
struct {{ signal }} {};
4040
%% else
4141
template<int index>
@@ -62,7 +62,7 @@ struct Peripherals::{{ name }}<{{ instance }}>::{{ signal }} {};
6262
%% endif
6363
%% endfor
6464
%% endfor
65-
%% else
65+
%% elif name != "Adc"
6666
%% for signal, index_list in peripheral["signals"].items()
6767
%% for index in index_list
6868
template<>
@@ -99,11 +99,16 @@ struct {{ gpio["port"] ~ gpio["pin"] }}
9999
%% else
100100
using peripheral = Peripherals::{{ signal["peripheral"] }};
101101
%% endif
102-
%% if "index" in signal
102+
%% if "index" in signal and signal["peripheral"] != "Adc"
103103
using signal = peripheral::{{ signal["name"] }}<{{ signal["index"] }}>;
104104
%% else
105105
using signal = peripheral::{{ signal["name"] }};
106106
%% endif
107+
%% if signal["peripheral"] == "Adc" and "index" in signal
108+
static constexpr int32_t AdcChannel = {{ signal["index"] }};
109+
%% else
110+
static constexpr int32_t AdcChannel = -1;
111+
%% endif
107112
};
108113
%% endfor
109114

src/modm/platform/gpio/sam/module.lb

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,6 @@ def build(env):
7070
"function": signal["function"].capitalize(),
7171
"name": signal["name"].capitalize(),
7272
})
73-
# SAMG devices have X1 peripheral connection, which is "extra function",
74-
# but it is not configured by the normal alternate IO function. Skip these.
75-
if signal['function'] == 'X1':
76-
continue
7773
# This is a hack for L variant devices, which have a Ac without instance *and*
7874
# a Ac with instance 1. Why???
7975
if (device.identifier.get("variant") == "l"
@@ -84,7 +80,17 @@ def build(env):
8480
peripheral = peripherals.setdefault(signal["peripheral"], dict())
8581
if "instance" in signal:
8682
signal["full_name"] += signal["instance"]
87-
signal["instance"] = int(signal["instance"])
83+
try:
84+
signal["instance"] = int(signal["instance"])
85+
except ValueError:
86+
# On SAMV devices, some signals (e.g. PIODC/data capture) belong to
87+
# PIOs, and have alphabetic instance values. The config.hpp.in
88+
# template requires integers, so convert "a", "b", "c" etc to
89+
# integer values
90+
if len(signal["instance"]) == 1:
91+
signal["instance"] = ord(signal["instance"].lower()) - ord("a")
92+
else:
93+
raise ValueError(f"Encountered invalid signal instance {signal['instance']}")
8894
peripheral.setdefault("instances", set()).add(signal["instance"])
8995
signal["full_name"] += signal["name"]
9096
p_signal = peripheral.setdefault("signals", dict()).setdefault(signal["name"], set())

src/modm/platform/gpio/sam/pin.hpp.in

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ enum class PeripheralPin
3939
Wo,
4040
Sck,
4141
Miso,
42-
Mosi
42+
Mosi,
43+
Ad,
4344
};
4445

4546
template<typename... Tuples>
@@ -407,6 +408,7 @@ public:
407408
using Sck = As<PeripheralPin::Sck>;
408409
using Miso = As<PeripheralPin::Miso>;
409410
using Mosi = As<PeripheralPin::Mosi>;
411+
using Ad = As<PeripheralPin::Ad>;
410412

411413
inline static bool
412414
read()
@@ -484,7 +486,12 @@ struct Gpio<PinConfig>::As : public Gpio<PinConfig>
484486
inline static void
485487
connect()
486488
{
489+
487490
%% if target["family"] in ["g", "v"]
491+
// X1 denotes an "extra function", such as an ADC pin, which is not
492+
// enabled by the PIO ABCD register.
493+
static_assert(PinSignal::function != PinFunction::X1,
494+
"This is a system connection, and cannot be connected by Gpio connect()");
488495
Pio* PIOBase;
489496
%% for port in ports
490497
if constexpr (PinConfig::port == PortName::{{ port }})

0 commit comments

Comments
 (0)