sys/arduino: add implementation for analogWrite#12523
sys/arduino: add implementation for analogWrite#12523gschorcht merged 7 commits intoRIOT-OS:masterfrom
Conversation
haukepetersen
left a comment
There was a problem hiding this comment.
Nice effort! Just some things that should be clarified before this can be merged.
| int pin; /**< Arduino pin number */ | ||
| int dev; /**< PWM device index of pin */ | ||
| int chan; /**< PWM channel index */ | ||
| } arduino_pwm_t; |
There was a problem hiding this comment.
Any reason this type is (a) duplicated and (b) not defined somewhere more fitting, e.g. at least in the CPU if not in the global Arduino module?
There was a problem hiding this comment.
I would say a bit of laziness and the fact that I was not sure where this would fit better. The mapping could depend on the underlying PWM implementation.
There was a problem hiding this comment.
The periph_pwm API defines PWM devices and channels of these devices. IMHO, a PWM channel is completely specified by the tupel (dev, chan) which is routed to pin. The struct should fit all. platforms. If not, the platform could extend the structure.
There was a problem hiding this comment.
I moved the struct to periph/pwm.h, guarded by MODULE_ARDUINO. Not sure if it's the best place. It cannot be in arduino.hpp since this one already includes arduino_board.h...
sys/arduino/base.cpp
Outdated
|
|
||
| /* Check if the given value is correct */ | ||
| assert(value >= 0); | ||
| assert(value < 256); |
There was a problem hiding this comment.
This seems dangerous to me, triggering a assertion on a parameters out of range situation can IMHO be solved at runtime -> although the Arduino API does not say anything about this (see https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite), I would expect it is just setting the PWM to min/max value whenever an arg is given out of range?! This would be the more desirable behavior I'd say.
There was a problem hiding this comment.
Agreed, I'll change that.
sys/arduino/base.cpp
Outdated
| #if MODULE_PERIPH_PWM | ||
| #define PWM_MODE PWM_LEFT | ||
| #define PWM_FREQU (1000U) | ||
| #define PWM_STEPS (256U) |
There was a problem hiding this comment.
Not sure to define these values statically here in the code is the best idea. It seems, that the actually Arduino API defines these dependent on the target board (see https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/), so we might at least allow for redefinition for RIOT?!
There was a problem hiding this comment.
You are right, I also read that frequency values depend on the boards in Arduino. This could be moved elsewhere: default defines in the Arduino.hpp and values overridden in boards to match the ones in official Arduino.
There was a problem hiding this comment.
Until now, there is no Arduino.hpp. It will be introduced for compatibility reasons with PR #10592 which is waiting for merge. But Arduino.hpp will be just a wrapper and all definitions should got to arduino.hpp.
There was a problem hiding this comment.
I moved the defines in arduino.hpp (don't worry @gschorcht, there was a typo in my previous comment). Only frequency can be overridden. The other values seems to be fixed in Arduino.
sys/arduino/base.cpp
Outdated
| * 0: Not initialized | ||
| * 1: Successfully initialized | ||
| */ | ||
| static uint16_t pwm_dev_state; |
There was a problem hiding this comment.
I fear its not quite that simple: If one would call a digital_write() on that same pin in the mean time, the bit value in this variable is not valid anymore. So it seems to me, that this value must be shared for all functions in this module and it must be also updated by other functions using these pins...
There was a problem hiding this comment.
Not sure, here it's only used to check if the PWM device is already initialized. It has nothing to do with the pin itself.
There was a problem hiding this comment.
but pwm_init also takes care of configuring the output state/mode for the effected pin. So maybe this might be a more generic problem. What happens for code like the following in Arduino?
pinMode(X, OUTPUT);
digitalWrite(X, HIGH);
analogWtrite(X, 100);
pinMode(X, OUTPUT);
digitalWrite(X, HIGH);
analogWrite(X, 50);Here one would expect, that the wave form at pin X is active after the last analogWrite call. Now the bitfield above will actually prevent that from happen, as it thinks the pin is already initialized as PWM output, which is not the case anymore...
There was a problem hiding this comment.
You are right. Looking at the samd21 implementation of analogWrite in Arduino, it seems the GPIO is reinitialized all time but not the TCC. This is a design incompatibility between Arduino and RIOT here.
There was a problem hiding this comment.
Considering that Arduino is not really geared towards maximum effenciency, I think we should be good with re-initializing the PWM on every call, right? IMHO API complience is more important than efficiency considerations.
Only question is how to/if we need to deal with signal ripples at the pin that could potentially be caused by this re-initialization. But the exact same behavior is triggered on the very first call in any case, so we might just be able to ignore that issue.
There was a problem hiding this comment.
If the function is necessary, shouldn't it be part of the PWM API. It's a bit weird that then name of a function in the GPIO API starts with pwm.
It's added in the pwm API but maybe the name is not good.
Yes, of course 😕 Sorry, my fault. For any reason I saw it in gpio.h.
It could be called
pwm_channel_initinstead ?
Not sure, while pwm_gpio_init says what it does, pwm_channel_init would be more consistent with terminology in pwm.h. Maybe, pwm_channel_init would be better.
There was a problem hiding this comment.
pinMode(x OUTPUT);
digitalWrite(x, HIGH);
analogWrite(y, 128);
digitalWrite(x, LOW);
This won't work if X and Y are 2 pins configured for 2 channels on the same PWM device because they are reinitialized for PWM in the first call to
analogWrite. The second call todigitalWritemight not work (untested but with the current implementation that should be the case).
It could work with your proposed change to split the functionality of pwm_init Into pwm_dev_init and pwm_channel_init. What if PWM channels were never initialized in pwm_init, but always in pwm_set when a certain channel is used for the first time?
There was a problem hiding this comment.
What if PWM channels were never initialized in pwm_init, but always in pwm_set when a certain channel is used for the first time?
Doing this will make pwm_set slightly less efficient but it will also be a little more robust (if the same pin is initialized differently between 2 calls to pwm_set.
May I suggest to drop the refactoring proposal from this PR and work on this separately ?
There was a problem hiding this comment.
Doing this will make
pwm_setslightly less efficient
Yes, but how often is it called in real applications? I woud guess not so often so that this overhead seems to be acceptable.
May I suggest to drop the refactoring proposal from this PR and work on this separately ?
Sure.
There was a problem hiding this comment.
but how often is it called in real applications? I woud guess not so often so that this overhead seems to be acceptable.
I tend to agree here. And it's just 2 extra function calls. Doing this change, e.g not initialize all channels in pwm_init and only initialize one channel on demand in pwm_set, would also save 52 bytes on samd21...
|
@aabadie Would it make sense to provide a small test app together with this PR, lets say |
005c514 to
59d692e
Compare
Sounds good. Maybe an |
ce616bd to
d60e037
Compare
|
@gschorcht, I added a test sketch for the analogRead/analogWrite functions. It's untested (I don't have hardware with me for the moment). @haukepetersen, regarding the idempotence of |
d60e037 to
aea9827
Compare
Great. I have 8 hours of lectures today. I will test it later today, hopefully. |
aea9827 to
eabfb35
Compare
|
I fixed a couple of issues and could make the test sketch work on arduino-zero. |
eabfb35 to
e4972d7
Compare
|
When I started to test I realized that my Arduino Mega isn't supported and only Sam21D boards are supported. How difficult would it be also to support ATmega boards? What will be the process to support other platforms, e.g., ESP32? |
It should not be too difficult. I can adapt the PWM mapping for them. |
e4972d7 to
c3606ef
Compare
|
@gschorcht, I pushed an update adding the mapping for arduino atmega boards. Not tested (and won't be able to test in the next 10 days). |
Thanks. I will test it ASAP. |
|
@aabadie After changing the led pin to 13, the test works on my Arduino Mega2560. |
c3606ef to
12fca9e
Compare
12fca9e to
8bb64e2
Compare
|
@haukepetersen Are the changes ok for you? |
aa0e617 to
32da842
Compare
The goal of this application is to test the analogRead and analogWrite Arduino function
32da842 to
65e0247
Compare
ping @haukepetersen :) |
@haukepetersen, do you still have concerns here ? |
|
@haukepetersen gentle ping :) |
haukepetersen
left a comment
There was a problem hiding this comment.
Looks good enough to me, so no further concerns.
|
@aabadie Thanks for contributing the |
|
Many thanks guys! |
Contribution description
This PR adds an implementation for the analogWrite function of the Arduino API. It's based on the PWM peripheral interface of RIOT.
The
arduino_pwmfeature is introduced to only have to provide this implementation on a subset of boards (arduino-zero, sodaq-automono and arduino-mkrs) which already provides arduino and periph_pwm features. Other boards can then be adapted later: Arduino atmega, STM32 nucleos, etc.Since there's no one to one mapping possible between Arduino PWM pins and RIOT, there's a not so nice logic to look for an index in a list of pin/PWM dev/channel mapping. I tried to use designated initializers but doesn't build with c++ unless empty elements are also initialized with dummy values. And it makes the array static initialization quite ugly.
Testing procedure
Plug a LED on D9 and flash the following Arduino sketch on arduino-zero:
And watch it oscillate.
Issues/PRs references
Now based on #12541