Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions cores/arduino/Arduino.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/dac.h>
#include <zephyr/drivers/i2c.h>

#if DT_PROP_LEN(DT_PATH(zephyr_user), digital_pin_gpios) > 0
Expand Down Expand Up @@ -103,13 +104,42 @@ enum analogPins {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), adc_pin_gpios, AN_ENUMS)
};

// We provide analogReadResolution APIs
void analogReadResolution(int bits);

#endif

#ifdef CONFIG_DAC

#undef DAC0
#undef DAC1
#undef DAC2
#undef DAC3
#define DAC_ENUMS(n, p, i) DAC##i = i,

enum dacPins {
#if DT_PROP_LEN_OR(DT_PATH(zephyr_user), dac_channels, 0) > 0
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), dac_channels, DAC_ENUMS)
#endif
NUM_OF_DACS
};

#endif

void interrupts(void);
void noInterrupts(void);

int digitalPinToInterrupt(pin_size_t pin);

#define digitalPinToPort(x) (x)
#define digitalPinToBitMask(x) (x)
#define portOutputRegister(x) (x)
#define portInputRegister(x) (x)

#if defined(CONFIG_PWM) || defined(CONFIG_DAC)
void analogWriteResolution(int bits);
#endif

#include <variant.h>

#if !defined(LED_BUILTIN) && defined(ZARD_LED_BUILTIN)
Expand All @@ -118,4 +148,12 @@ int digitalPinToInterrupt(pin_size_t pin);

#ifdef __cplusplus
#include <zephyrSerial.h>
#include <strings.h>
#include <api/itoa.h>
#include <time_macros.h>
#include <overloads.h>

// Allow namespace-less operations if Arduino.h is included
using namespace arduino;

#endif // __cplusplus
15 changes: 15 additions & 0 deletions cores/arduino/overloads.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright (c) Arduino s.r.l. and/or its affiliated companies
*
* 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.
#ifdef CONFIG_DAC

void analogWrite(enum dacPins pinNumber, int value);

#endif

// In c++ mode, we also provide analogReadResolution and analogWriteResolution getters
int analogReadResolution();
int analogWriteResolution();
Comment on lines +13 to +15
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.
13 changes: 13 additions & 0 deletions cores/arduino/time_macros.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright (c) Arduino s.r.l. and/or its affiliated companies
*
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <zephyr/sys/time_units.h>

#define clockCyclesPerMicrosecond() (1000000 / k_cyc_to_ns_near64(1000))
#define clockCyclesToMicroseconds(a) (a / clockCyclesPerMicrosecond())
#define microsecondsToClockCycles(a) (a * clockCyclesPerMicrosecond())
Comment on lines +11 to +13
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.
120 changes: 109 additions & 11 deletions cores/arduino/zephyrCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,14 @@ size_t pwm_pin_index(pin_size_t pinNumber) {
DT_PHA_BY_IDX(DT_PATH(zephyr_user), p, i, pin)),
#define ADC_CH_CFG(n, p, i) arduino_adc[i].channel_cfg,

const struct adc_dt_spec arduino_adc[] = {
static const struct adc_dt_spec arduino_adc[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, ADC_DT_SPEC)};

/* io-channel-pins node provides a mapping digital pin numbers to adc channels */
const pin_size_t arduino_analog_pins[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), adc_pin_gpios, ADC_PINS)};

struct adc_channel_cfg channel_cfg[ARRAY_SIZE(arduino_analog_pins)] = {
struct adc_channel_cfg channel_cfg[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, ADC_CH_CFG)};

size_t analog_pin_index(pin_size_t pinNumber) {
Expand All @@ -175,6 +175,32 @@ size_t analog_pin_index(pin_size_t pinNumber) {

#endif // CONFIG_ADC

#ifdef CONFIG_DAC

#if (DT_NODE_HAS_PROP(DT_PATH(zephyr_user), dac))

#define DAC_NODE DT_PHANDLE(DT_PATH(zephyr_user), dac)
#define DAC_RESOLUTION DT_PROP(DT_PATH(zephyr_user), dac_resolution)
static const struct device *const dac_dev = DEVICE_DT_GET(DAC_NODE);

#define DAC_CHANNEL_DEFINE(n, p, i) \
{ \
.channel_id = DT_PROP_BY_IDX(n, p, i), \
.resolution = DAC_RESOLUTION, \
.buffered = true, \
},

#if DT_PROP_LEN_OR(DT_PATH(zephyr_user), dac_channels, 0) > 0
static const struct dac_channel_cfg dac_ch_cfg[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), dac_channels, DAC_CHANNEL_DEFINE)};

static bool dac_channel_initialized[NUM_OF_DACS];
#endif

#endif

#endif // CONFIG_DAC

static unsigned int irq_key;
static bool interrupts_disabled = false;
} // namespace
Expand Down Expand Up @@ -354,9 +380,27 @@ unsigned long millis(void) {
return k_uptime_get_32();
}

#if defined(CONFIG_DAC) || defined(CONFIG_PWM)
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.
}

int analogWriteResolution() {
return _analog_write_resolution;
}
#endif

#ifdef CONFIG_PWM

static uint32_t map64(uint32_t x, uint32_t in_min, uint32_t in_max, uint32_t out_min,
uint32_t out_max) {
return ((uint64_t)(x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min);
}

void analogWrite(pin_size_t pinNumber, int value) {
const int maxInput = BIT(_analog_write_resolution) - 1U;
size_t idx = pwm_pin_index(pinNumber);

if (idx >= ARRAY_SIZE(arduino_pwm)) {
Expand All @@ -367,24 +411,57 @@ void analogWrite(pin_size_t pinNumber, int value) {
return;
}

if (((uint32_t)value) > arduino_pwm[idx].period) {
value = arduino_pwm[idx].period;
} else if (value < 0) {
value = 0;
}
value = CLAMP(value, 0, maxInput);

const uint32_t pulse = map64(value, 0, maxInput, 0, arduino_pwm[idx].period);

/*
* A duty ratio determines by the period value defined in dts
* and the value arguments. So usually the period value sets as 255.
*/
(void)pwm_set_pulse_dt(&arduino_pwm[idx], value);
(void)pwm_set_pulse_dt(&arduino_pwm[idx], pulse);
}

#endif

#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;
}
Comment on lines +427 to +435
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.

if (!dac_channel_initialized[dacName]) {
if (!device_is_ready(dac_dev)) {
return;
}

ret = dac_channel_setup(dac_dev, &dac_ch_cfg[dacName]);
if (ret != 0) {
return;
}
dac_channel_initialized[dacName] = true;
}

value = CLAMP(value, 0, maxInput);

const int max_dac_value = BIT(dac_ch_cfg[dacName].resolution) - 1;
const uint32_t output = map(value, 0, maxInput, 0, max_dac_value);

(void)dac_write_value(dac_dev, dac_ch_cfg[dacName].channel_id, output);
#else
ARG_UNUSED(dacName);
ARG_UNUSED(value);
#endif
}
#endif

#ifdef CONFIG_ADC

void analogReference(uint8_t mode) {
void __attribute__((weak)) analogReference(uint8_t mode) {
/*
* The Arduino API not clearly defined what means of
* the mode argument of analogReference().
Expand All @@ -395,9 +472,20 @@ void analogReference(uint8_t mode) {
}
Comment on lines 464 to 472
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.
}

// 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;
}
Comment on lines +475 to +480
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.

int analogReadResolution() {
return read_resolution;
}

int analogRead(pin_size_t pinNumber) {
int err;
int16_t buf;
uint16_t buf;
struct adc_sequence seq = {.buffer = &buf, .buffer_size = sizeof(buf)};
size_t idx = analog_pin_index(pinNumber);

Expand Down Expand Up @@ -427,7 +515,17 @@ int analogRead(pin_size_t pinNumber) {
return err;
}

return buf;
/*
* If necessary map the return value to the
* 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);
Comment on lines +520 to +528
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.
}

#endif
Expand Down
Loading