The servo I use in my application uses a PWM signal duty cycle to represent servo amperage level.
So, to measure servo amperage, I use a GPIOTE channel to capture the time between rising and falling edges of the servo's PWM amperage signal. I then have an interrupt handler that reads the GPIO pin to determine whether or not the time was for a peak or valley and I calculate duty cycle appropriately.
The problem is at low duty cycles, when triggered by a peak, the signal occasionally changes prior to reading the pin in the interrupt handler routine. This result is an inaccurate spike in servo amperage. As I use the valley routine to calculate duty cycle when I should have used a peak routine.
I recognize that it is a timing issue and would like to overcome this with a robust solution.
Can I rely on the latch function instead of reading the pin to determine if I'm measuring the width of the peak or the width of a valley?
Or alternatively, Can a I make a pin read and GPIOTE event via PPI?
I've attached the code of my interrupt handler in case you need more detail.
void servo_amps_input_handler (nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
static uint32_t servo_first_edge_capture, servo_second_edge_capture;
static float32_t last_good_servo_amps;
static uint16_t state;
// PPI (channel3) channel event captures NRF_TIMER4 CC[0]
switch (servo_edge)
{
case 0UL:
servo_first_edge_capture = NRF_TIMER4->CC[0];
finished_getting_servo_data = false;
state = nrf_gpio_pin_read(I_OUT);
if (state == 1) hi_state = true;
else hi_state = false;
servo_edge++;
//SEGGER_RTT_printf(0," 1st I_OUT edge capture %d, \r\n", servo_first_edge_capture);
break;
case (1UL):
nrf_drv_gpiote_in_event_disable(I_OUT); // disable I_OUT pin interrupt
nrf_drv_timer_compare_int_disable(&TIMER_AMPS, NRF_TIMER_CC_CHANNEL2); // disable NO_PULSE interupt
servo_second_edge_capture = NRF_TIMER4->CC[0];
servo_pulse_count = servo_second_edge_capture - servo_first_edge_capture;
if (servo_pulse_count == 0)
{
servo_AMPS = last_good_servo_amps;
}
else
{
if (state == 1) //we measured duration of a high pulse
{
servo_AMPS = servo_duty_amps * (float32_t)servo_pulse_count / servo_period_count;
}
else //we measured duration of a low pulse
{
servo_AMPS = servo_duty_amps * (1.F - ((float32_t)servo_pulse_count / servo_period_count));
}
last_good_servo_amps = servo_AMPS;
}
IFLG_get_servo_amps = false;
finished_getting_servo_data = true;
servo_edge++;
//SEGGER_RTT_printf(0," 2nd I_OUT edge capture %d, \r\n", servo_second_edge_capture);
break;
default: //We should never hit this
servo_edge++;
break;
}
}