Skip to content

Improve analogRead() and add DAC support#175

Open
soburi wants to merge 12 commits intozephyrproject-rtos:nextfrom
soburi:update_analog
Open

Improve analogRead() and add DAC support#175
soburi wants to merge 12 commits intozephyrproject-rtos:nextfrom
soburi:update_analog

Conversation

@soburi
Copy link
Copy Markdown
Member

@soburi soburi commented Apr 17, 2026

No description provided.

@soburi soburi changed the title Update analog Improve analogRead() and add DAC support Apr 17, 2026
Fixes compilation error found at https://www.cnx-software.com/2024/12/10/arduino-core-for-zephyr-beta-released/

Co-Authored-by: Martino Facchin <m.facchin@arduino.cc>
Signed-off-by: TOKITA Hiroshi <tokita.hiroshi@gmail.com>
facchinm and others added 11 commits April 18, 2026 07:12
Co-Authored-by: Martino Facchin <m.facchin@arduino.cc>
Signed-off-by: TOKITA Hiroshi <tokita.hiroshi@gmail.com>
Co-Authored-by: Kurt Eckhardt <kurte@rockisland.com>
Signed-off-by: TOKITA Hiroshi <tokita.hiroshi@gmail.com>
Take into account the analog switch

Co-Authored-by: Martino Facchin <m.facchin@arduino.cc>
Signed-off-by: TOKITA Hiroshi <tokita.hiroshi@gmail.com>
Co-Authored-by: Martino Facchin <m.facchin@arduino.cc>
Signed-off-by: TOKITA Hiroshi <tokita.hiroshi@gmail.com>
Co-Authored-by: Martino Facchin <m.facchin@arduino.cc>
Signed-off-by: TOKITA Hiroshi <tokita.hiroshi@gmail.com>
Formatting ADC/DAC codes.

Signed-off-by: TOKITA Hiroshi <tokita.hiroshi@gmail.com>
- Remove duplicated `analogReadResolution` declaration
- Add the same #ifdef conditions used in the implementation
  to the `analogWriteResolution` as well.

Signed-off-by: TOKITA Hiroshi <tokita.hiroshi@gmail.com>
- Fixed input max value calculation (Needs to be -1 after shift)
- Clamp calculations are performed first, eliminating cast

Signed-off-by: TOKITA Hiroshi <tokita.hiroshi@gmail.com>
- Correct max value calculation
- Add guard for DAC entry is not defined case
- initialize only if DAC not initialized

Signed-off-by: TOKITA Hiroshi <tokita.hiroshi@gmail.com>
Co-Authored-by: Giovanni Bruno <g.bruno@arduino.cc>
Signed-off-by: TOKITA Hiroshi <tokita.hiroshi@gmail.com>
Explicitly state the license in all source files to comply with SPDX
best practices. In addition, update the copyright notice to the new
format.

Note: At the time of cherry-picking, this was applied only to
      cores/arduino/time_macros.h and
      cores/arduino/overloads.h.

Co-Authored-by: Luca Burelli <l.burelli@arduino.cc>
Signed-off-by: TOKITA Hiroshi <tokita.hiroshi@gmail.com>
@soburi soburi marked this pull request as ready for review April 17, 2026 22:29
@soburi soburi requested review from DhruvaG2000 and Copilot April 17, 2026 22:29
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the Zephyr-based Arduino core to improve analog I/O behavior by adding configurable read/write resolutions, scaling PWM output to match the configured resolution, and introducing initial DAC support, along with a small set of Arduino-compatible time helper macros.

Changes:

  • Add DAC device/channel support and a DAC-specific analogWrite() overload.
  • Add analogReadResolution() / analogWriteResolution() APIs and scale analogRead()/PWM output accordingly.
  • Introduce time_macros.h and overloads.h to provide additional Arduino-compatibility APIs for C++ builds.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.

File Description
cores/arduino/zephyrCommon.cpp Adds DAC support, read/write resolution handling, PWM scaling, and analog read scaling logic.
cores/arduino/time_macros.h Adds Arduino-style clock cycle/time conversion macros using Zephyr time utilities.
cores/arduino/overloads.h Declares C++ overloads/getters (DAC analogWrite overload and resolution getters).
cores/arduino/Arduino.h Exposes new APIs/types (DAC pins enum, analog read/write resolution), and includes new headers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cores/arduino/overloads.h
*
* SPDX-License-Identifier: Apache-2.0
*/

Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overloads.h is a header but has no include guard / #pragma once. This can lead to repeated processing and makes it inconsistent with the rest of the core headers (which all use #pragma once). Add an include guard or #pragma once at the top.

Suggested change
#pragma once

Copilot uses AI. Check for mistakes.
Comment thread cores/arduino/overloads.h
Comment on lines +13 to +15
// In c++ mode, we also provide analogReadResolution and analogWriteResolution getters
int analogReadResolution();
int analogWriteResolution();
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

analogReadResolution() and analogWriteResolution() getters are declared unconditionally, but their definitions are compiled only when CONFIG_ADC and CONFIG_PWM || CONFIG_DAC are enabled respectively. This can cause undefined references for C++ sketches built without those Kconfig options. Wrap these declarations in matching #if guards (or provide always-available stubs that return a default).

Suggested change
// In c++ mode, we also provide analogReadResolution and analogWriteResolution getters
int analogReadResolution();
int analogWriteResolution();
// In c++ mode, we also provide analogReadResolution and analogWriteResolution getters
#ifdef CONFIG_ADC
int analogReadResolution();
#endif
#if defined(CONFIG_PWM) || defined(CONFIG_DAC)
int analogWriteResolution();
#endif

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +13
#define clockCyclesPerMicrosecond() (1000000 / k_cyc_to_ns_near64(1000))
#define clockCyclesToMicroseconds(a) (a / clockCyclesPerMicrosecond())
#define microsecondsToClockCycles(a) (a * clockCyclesPerMicrosecond())
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clockCyclesToMicroseconds(a) / microsecondsToClockCycles(a) macros don't parenthesize a, so expressions like x + 1 will be evaluated with unexpected precedence. Also clockCyclesPerMicrosecond() can evaluate to 0 on low-frequency targets due to integer division, which would make clockCyclesToMicroseconds() divide by zero. Parenthesize macro arguments and consider computing cycles/us with 64-bit math and rounding so it never returns 0.

Suggested change
#define clockCyclesPerMicrosecond() (1000000 / k_cyc_to_ns_near64(1000))
#define clockCyclesToMicroseconds(a) (a / clockCyclesPerMicrosecond())
#define microsecondsToClockCycles(a) (a * clockCyclesPerMicrosecond())
#define clockCyclesPerMicrosecond() ((1000000ULL + k_cyc_to_ns_near64(1000) - 1) / k_cyc_to_ns_near64(1000))
#define clockCyclesToMicroseconds(a) ((a) / clockCyclesPerMicrosecond())
#define microsecondsToClockCycles(a) ((a) * clockCyclesPerMicrosecond())

Copilot uses AI. Check for mistakes.
static int _analog_write_resolution = 8;

void analogWriteResolution(int bits) {
_analog_write_resolution = bits;
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

analogWriteResolution(int bits) accepts any value, but later BIT(_analog_write_resolution) is used to compute maxInput. If bits <= 0 this yields maxInput == 0 and causes a division-by-zero in map64(). If bits is negative or too large, the shift inside BIT() is undefined. Clamp/validate bits to a safe range (e.g., 1..31) and consider storing it as an unsigned type.

Suggested change
_analog_write_resolution = bits;
_analog_write_resolution = CLAMP(bits, 1, 31);

Copilot uses AI. Check for mistakes.
Comment on lines +427 to +435
#ifdef CONFIG_DAC
void analogWrite(enum dacPins dacName, int value) {
#if DT_PROP_LEN_OR(DT_PATH(zephyr_user), dac_channels, 0) > 0
const int maxInput = BIT(_analog_write_resolution) - 1U;
int ret = 0;

if (dacName >= NUM_OF_DACS) {
return;
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding analogWrite(enum dacPins, int) introduces an overload that can make existing calls like analogWrite(3, value) ambiguous on C++ (an int can convert to both pin_size_t and an unscoped enum). Consider avoiding the overload (e.g., use pin_size_t for DAC pins too, or provide a differently named API / a scoped enum class with explicit conversion).

Copilot uses AI. Check for mistakes.
Comment on lines 464 to 472
@@ -395,9 +472,20 @@ void analogReference(uint8_t mode) {
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

analogReference() updates the separate channel_cfg[] array, but analogRead() still calls adc_channel_setup() with &arduino_adc[idx].channel_cfg. That means changes made by analogReference() are never applied to actual reads. Use channel_cfg[idx] in adc_channel_setup() (and ensure the indexing matches), or update arduino_adc[idx].channel_cfg instead of maintaining a separate array.

Copilot uses AI. Check for mistakes.
Comment on lines +475 to +480
// Note: We can not update the arduino_adc structure as it is read only...
static int read_resolution = 10;

void analogReadResolution(int bits) {
read_resolution = bits;
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

analogReadResolution(int bits) stores bits directly, but analogRead() later shifts by (seq.resolution - read_resolution) / (read_resolution - seq.resolution). If bits is negative or very large, this can trigger undefined shifts and/or signed overflow in the return expression. Validate/clamp bits to a sensible range (e.g., 1..16 or 1..31) and do the scaling using an unsigned wider type before converting to int.

Copilot uses AI. Check for mistakes.
Comment on lines +520 to +528
* number of bits the user has asked for
*/
if (read_resolution == seq.resolution) {
return buf;
}
if (read_resolution < seq.resolution) {
return buf >> (seq.resolution - read_resolution);
}
return buf << (read_resolution - seq.resolution);
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scaling logic in analogRead() does shifts based on user-controlled read_resolution. Without clamping, buf >> (seq.resolution - read_resolution) / buf << (read_resolution - seq.resolution) can shift by a negative or excessively large amount, and the left-shift is done on an int after promotion which can overflow (UB). Clamp read_resolution to a safe range and perform the shift on a wider unsigned type before returning.

Suggested change
* number of bits the user has asked for
*/
if (read_resolution == seq.resolution) {
return buf;
}
if (read_resolution < seq.resolution) {
return buf >> (seq.resolution - read_resolution);
}
return buf << (read_resolution - seq.resolution);
* number of bits the user has asked for.
*
* Clamp the user-controlled resolution to a range that is safe for
* shifting a 32-bit temporary and that still fits in the positive
* range of the int return type.
*/
int effective_read_resolution = read_resolution;
if (effective_read_resolution < 0) {
effective_read_resolution = 0;
} else if (effective_read_resolution > 31) {
effective_read_resolution = 31;
}
if (effective_read_resolution == seq.resolution) {
return buf;
}
if (effective_read_resolution < seq.resolution) {
return static_cast<int>(static_cast<uint32_t>(buf) >>
(seq.resolution - effective_read_resolution));
}
return static_cast<int>(static_cast<uint32_t>(buf)
<< (effective_read_resolution - seq.resolution));

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants