-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for RP2040 GPIN/GPOUT (clkio) #9009
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this change! I haven't looked in detail yet but have some thoughts and questions.
- Generally I'd expand
clkio
toclockio
becauseclk
isn't as clear asclock
. - What is the difference between auxsrc and index?
- Could this be a shared API for clock inputs and outputs where the clock names are defined by a micro-specific module?
bindings/samd
also does some clock stuff. - What are you using this for?
Thanks for taking a look @tannewt.
|
I agree standardizing it is a bigger task than need be. Let's call this |
Sounds good, I'll make those changes. |
What do you think about rp2clock instead of rp2clockio? There are other clock related functions in the SDK that may be useful to integrate in the future such as starting/stopping clocks and measuring the frequency of clocks using the onboard hardware. |
Fine with me! |
Made the necessary changes. Let me know if it looks ok. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for moving everything around!
locale/circuitpython.pot
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please try to reuse messages in this file instead of adding new ones. That way there is less to translate and less to store in the firmware.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried my best to combine them on latest commits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious, do all translations end up in every build regardless of the modules compiled in? I just assumed that these would only be pulled in when CIRCUITPY_RP2CLOCK=1. I can make the messages more generic to reuse them but we'd lose some information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only translations for enabled modules are included. I assume we'll have RP2CLOCK enabled for all RP2040 boards where it'll fit. Striking the generic vs specific balance can be tricky.
MP_DEFINE_CONST_FUN_OBJ_KW(rp2clock_inputpin_disable_obj, 1, rp2clock_inputpin_disable); | ||
|
||
//| def set_freq(self, src_freq: int, target_freq: int) -> None: | ||
//| """Configures the src and target frequency. Must be set before enable() is called.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this just so you can change an existing InputPin? I think you can just delete this, enable and disable and just rely on deinit and reconstruct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can remove enable()
, disable()
and enabled
too. Instead, folks can just use the constructor and deinit()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed on latest commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are use cases where you'd want to create an external pin object and then enable/disable the clock at runtime. For instance when putting the external chip in low power mode you shouldn't drive the clock to it. My use case actually requires this. This is compounded by the current issue where resources aren't released until garbage collection is called. My opinion is the functionality is required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.deinit()
is the way to turn it off immediately instead of waiting for the finalizer. Do you want to keep the pin "in use" even when it isn't active?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point on .deinit(). Since it's a dedicated hardware pin, I think logically it should be use whether the clock is enabled or not. It's akin to driving a normal GPIO, you wouldn't want to release the object and create a new one just to change the value from low to high. The only difference is a high level puts out a clock.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, then you can leave the .enabled
property but make it writable instead of having enable()
and disable()
functions. I'll mark the spot with a pointer to an example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for continuing to refine this! It is getting better and better.
You can test the docs locally with make html
from the root circuitpython directory. I suspect you are missing ...
function bodies that are used in Python stub files as placeholders.
One comment on enabled and friends and I'd like to hear your thoughts about reducing the number of new error messages. They take up firmware space for everyone.
//| def __init__(self) -> AuxSrc: | ||
//| """Enum-like class to define the clock src.""" | ||
//| PLL_SYS: "AuxSrc Clock" | ||
//| """Undivided PLL used to derive SYS clock.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
//| def __init__(self) -> AuxSrc: | |
//| """Enum-like class to define the clock src.""" | |
//| PLL_SYS: "AuxSrc Clock" | |
//| """Undivided PLL used to derive SYS clock.""" | |
//| def __init__(self) -> AuxSrc: | |
//| """Enum-like class to define the clock src.""" | |
//| ... | |
//| | |
//| PLL_SYS: "AuxSrc Clock" | |
//| """Undivided PLL used to derive SYS clock.""" |
//| target_freq: Desired frequency for index. | ||
//| """ | ||
|
||
STATIC mp_obj_t rp2clock_inputpin_make_new(const mp_obj_type_t *type, size_t n_args, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
//| target_freq: Desired frequency for index. | |
//| """ | |
STATIC mp_obj_t rp2clock_inputpin_make_new(const mp_obj_type_t *type, size_t n_args, | |
//| target_freq: Desired frequency for index. | |
//| """ | |
//| ... | |
//| | |
STATIC mp_obj_t rp2clock_inputpin_make_new(const mp_obj_type_t *type, size_t n_args, |
MP_DEFINE_CONST_FUN_OBJ_KW(rp2clock_inputpin_disable_obj, 1, rp2clock_inputpin_disable); | ||
|
||
//| def set_freq(self, src_freq: int, target_freq: int) -> None: | ||
//| """Configures the src and target frequency. Must be set before enable() is called.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can remove enable()
, disable()
and enabled
too. Instead, folks can just use the constructor and deinit()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the responses. I've made more specific suggestions.
return mp_obj_new_bool(self->enabled); | ||
} | ||
MP_DEFINE_CONST_FUN_OBJ_1(rp2clock_inputpin_get_enabled_obj, rp2clock_inputpin_get_enabled); | ||
MP_PROPERTY_GETTER(rp2clock_inputpin_enabled_obj, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make this settable instead of having enable()
and disable()
.
circuitpython/shared-bindings/digitalio/DigitalInOut.c
Lines 236 to 260 in 8027efe
//| value: bool | |
//| """The digital logic level of the pin.""" | |
STATIC mp_obj_t digitalio_digitalinout_obj_get_value(mp_obj_t self_in) { | |
digitalio_digitalinout_obj_t *self = MP_OBJ_TO_PTR(self_in); | |
check_for_deinit(self); | |
bool value = common_hal_digitalio_digitalinout_get_value(self); | |
return mp_obj_new_bool(value); | |
} | |
MP_DEFINE_CONST_FUN_OBJ_1(digitalio_digitalinout_get_value_obj, digitalio_digitalinout_obj_get_value); | |
STATIC mp_obj_t digitalio_digitalinout_obj_set_value(mp_obj_t self_in, mp_obj_t value) { | |
digitalio_digitalinout_obj_t *self = MP_OBJ_TO_PTR(self_in); | |
check_for_deinit(self); | |
if (common_hal_digitalio_digitalinout_get_direction(self) == DIRECTION_INPUT) { | |
mp_raise_AttributeError(MP_ERROR_TEXT("Cannot set value when direction is input.")); | |
return mp_const_none; | |
} | |
common_hal_digitalio_digitalinout_set_value(self, mp_obj_is_true(value)); | |
return mp_const_none; | |
} | |
MP_DEFINE_CONST_FUN_OBJ_2(digitalio_digitalinout_set_value_obj, digitalio_digitalinout_obj_set_value); | |
MP_PROPERTY_GETSET(digitalio_digitalinout_value_obj, | |
(mp_obj_t)&digitalio_digitalinout_get_value_obj, | |
(mp_obj_t)&digitalio_digitalinout_set_value_obj); |
} | ||
MP_DEFINE_CONST_FUN_OBJ_1(rp2clock_outputpin_get_enabled_obj, rp2clock_outputpin_get_enabled); | ||
MP_PROPERTY_GETTER(rp2clock_outputpin_enabled_obj, | ||
(mp_obj_t)&rp2clock_outputpin_get_enabled_obj); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This too.
void common_hal_rp2clock_outputpin_validate_src_pin(const mcu_pin_obj_t *pin) { | ||
if ((pin->number != 21) && (pin->number != 23) && | ||
(pin->number != 24) && (pin->number != 25)) { | ||
mp_raise_ValueError_varg(MP_ERROR_TEXT("Pin %d invalid, valid pins are: 21,23,24,25"), pin->number); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest just using
NORETURN void raise_ValueError_invalid_pin(void); |
// GPIN must be [20,22] | ||
void common_hal_rp2clock_inputpin_validate_index_pin(const mcu_pin_obj_t *pin) { | ||
if ((pin->number != 20) && (pin->number != 22)) { | ||
mp_raise_ValueError_varg(MP_ERROR_TEXT("Pin %d invalid, valid pins are: 20,22"), pin->number); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just use
NORETURN void raise_ValueError_invalid_pin(void); |
if (src == 0) { | ||
mp_raise_ValueError_varg(MP_ERROR_TEXT("Invalid freq: %u"), src); | ||
} | ||
if (target == 0) { | ||
mp_raise_ValueError_varg(MP_ERROR_TEXT("Invalid freq: %u"), target); | ||
} | ||
if (target > src) { | ||
mp_raise_ValueError_varg(MP_ERROR_TEXT("Invalid freqs: %u > %u"), target, src); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can switch these to these validators:
Lines 180 to 206 in 8027efe
mp_int_t mp_arg_validate_int(mp_int_t i, mp_int_t required_i, qstr arg_name) { | |
if (i != required_i) { | |
mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be %d"), arg_name, required_i); | |
} | |
return i; | |
} | |
mp_int_t mp_arg_validate_int_min(mp_int_t i, mp_int_t min, qstr arg_name) { | |
if (i < min) { | |
mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be >= %d"), arg_name, min); | |
} | |
return i; | |
} | |
mp_int_t mp_arg_validate_int_max(mp_int_t i, mp_int_t max, qstr arg_name) { | |
if (i > max) { | |
mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be <= %d"), arg_name, max); | |
} | |
return i; | |
} | |
mp_int_t mp_arg_validate_int_range(mp_int_t i, mp_int_t min, mp_int_t max, qstr arg_name) { | |
if (i < min || i > max) { | |
mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be %d-%d"), arg_name, min, max); | |
} | |
return i; | |
} |
You can give the arguments MP_QSTR_src
for example for which variable is wrong.
Sorry for the delay. I went on vacation and have been putting out fires since I got back. Hopefully I can clean this up soon. |
@tinylabs any plans to get back to this PR? |
Tested on raspberry pi pico.
Add feature to boards/xx/mpconfigboard.mk
CIRCUITPY_RP2CLOCK = 1
Example usage:
import board,rp2clock
clkout = rp2clock.OutputPin(board.GP21, rp2clock.AuxSrc.SYS, 62.5)
// Output on GP21 will now be 2MHz from 125MHz clock SYS clock.
import board,rp2clock
clkin = rp2clock.InputPin(board.GP22, rp2clock.Index.RTC, 1000000, 10000)
// RTC will now be 10kHz from 1MHz input clock on GP22.
Suggestions welcome.