-
Notifications
You must be signed in to change notification settings - Fork 7.3k
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
pulse_cnt now prevents user PCNT code from accounting for pending overflow interrupt. (IDFGH-8726) #10167
Comments
@wiegleyj To compensate the counter overflow, I think you can add a watchpoint and register a callback. e.g. https://github.com/espressif/esp-idf/blob/master/tools/unit-test-app/components/test_utils/ref_clock_impl_rmt_pcnt.c#L67-L72
Sorry, we don't recommend users read/write registers from the application layer in this way (one reason is that, the register name/layout can be changed in other ESP chips). Please check if the callback functions can meet your requirement.
That's right. One of the goals of the new PCNT driver is to hide the implementation details. The user doesn't have to know the underlying PCNT channel number. The driver will manage the resource pool and allocate the channel for you. |
Nope. Your idea won't work. Your comment is addressing the basic problem of "how do I attach an interrupt to be notified when limits are reached". It does not do it in a way that prevents errors from occurring due to shared data and the fact that the hardware continues to count and overflow while you are adding together multiple pieces of accounting data. There is a race condition that you have to be able to take care of. I totally get the idea of hiding the implementation details and that concept is great but you are going to have to provide an atomic way to access two pieces of information, 1) the current count register value, and 2) whether an overflow is pending but hasn't been serviced by an interrupt. To make software counters that can handle more than 16-bits you need an "on reach" interrupt to increment/decrement a user-space accumulator variable but this interrupt and the user code for getting current total count needs to be synchronized...
And even that low level system has a problem if the limits are small enough and the count is advancing fast enough because this function could get blocked by a higher priority task in the middle and the counter could spin over several times and you (and the interrupt) would have no way of knowing how many times it overflowed while waiting to be chosen for running again. To Hide this implementation from the user you will need to provide an atomic function such as: This, in one inseparable shot gives you the ability to determine what the total value of the count really is at the moment the data was retrieved. You still need the critical section which protects the tied nature of the userland counter and the register value. But now you have the ability to account for the tied nature of userland counter, register value, AND if there is an overflow waiting to be accounted for. I think even that may have problems because there the overflow status register and the pulse count register are two separate registers, and you need to retrieve both atomically as well. But at the low level I think that can be achieved in a fashion similar to above. "get-test-get" You have either hidden too much of the implementation or you have not provided a method for exposing sufficient information. But as it stands in 5.0 there is no way, that I can see, to make a reliable accumulating counter (more than 16-bits) that isn't subject to errors equal to the high or low limit caused by overflow during value acquisition. You can reduce your error magnitude by reducing the limits but that increases interrupt inefficiency. |
@wiegleyj Thank you for this comprehensive explanation! Now I see the issue is between the overflow happening and the watch-point interrupt getting serviced, if the user reads the value by So if we can move the above strategy to the esp_err_t pcnt_unit_get_count(pcnt_unit_handle_t unit, int *value)
{
int result = 0;
pcnt_group_t *group = NULL;
ESP_RETURN_ON_FALSE_ISR(unit && value, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
group = unit->group;
pcnt_hal_context_t *hal = &group->hal;
int unit_id = unit->unit_id;
uint32_t intr_status = pcnt_ll_get_intr_status(hal->dev);
// overflow happened, but interrupt is not serviced yet
if (intr_status & PCNT_LL_UNIT_WATCH_EVENT(unit_id)) {
uint32_t event_status = pcnt_ll_get_event_status(hal->dev, unit_id);
// compensate the overflow loss
if (event_status & (1 << PCNT_LL_WATCH_EVENT_LOW_LIMIT)) {
result = unit->low_limit;
} else if (event_status & (1 << PCNT_LL_WATCH_EVENT_HIGH_LIMIT)) {
result = unit->high_limit;
}
}
result += pcnt_ll_get_count(hal->dev, unit_id);
*value = result;
return ESP_OK;
} will that be OK for you? |
Close... You clearly understand the issue now, but I think there is still a race condition since this solution doesn't lock out the interrupt from being serviced while it runs. So...
I don't fully understand the
This will also require changes to
It might be possible to increase the efficiency of the lock if you can lock on the individual unit's PCNT spinlock and not on the group lock. I don't fully understand the dynamics of the abstraction library and the low level registers. |
Ah... I see you did all the accumulation within the library and effectively extended the counter to be a signed 32-bit for the user without them having to do anything other than assign a true value to a struct member when creating the counter. That works for me. Thanks for the taking the time, especially as it was up against all the work up against the very immediate release of 5.0. Very responsive. Thanks! |
pcnt_unit_t
use to be atypedef enum
(in other words, the "handle" was a simple number.)Thus, you could do:
if (PCNT.int_st.val & BIT(unit)) {
so that you could correctly account for overflows and make a counter that could handle more than 16-bits.
Now the handle has been changed to
pcnt_unit_handle_t
which istypedef struct pcnt_unit_t *
that is no longer a simple int or a pointer to an int. It's a pointer to a struct. The big problem is that the definition ofstruct pcnt_unit_t
is hidden in pulse_cnt.c and thus not available to user code.There is no way to get at
pcnt_unit_handle_t->unit_id
to be able to detect pending overflow interrupts and you cannot make a reliable counter that can handle greater than 16-bits as a result.Am I missing some better/correct method to account for pending overflows while in a spinlock correctly? I think there needs to be an accessor function to get at elements of
struct pcnt_unit_t
or it needs to be moved into a.h
header file so that its fields are accessible. (An accessor to thespinlock
andunit_id
fields would seem to be sufficient. (spinlock
for protecting access per pcnt_unit instead of having to make another, andunit_id
so you can get intoPCNT.int_st
and such correctly.)The text was updated successfully, but these errors were encountered: