@ -98,6 +98,7 @@ void RCOutput::init()
@@ -98,6 +98,7 @@ void RCOutput::init()
}
# endif
chMtxObjectInit ( & trigger_mutex ) ;
chVTObjectInit ( & _dshot_rate_timer ) ;
// setup default output rate of 50Hz
set_freq ( 0xFFFF ^ ( ( 1U < < chan_offset ) - 1 ) , 50 ) ;
@ -122,6 +123,7 @@ void RCOutput::init()
@@ -122,6 +123,7 @@ void RCOutput::init()
void RCOutput : : rcout_thread ( )
{
uint32_t last_thread_run_us = 0 ; // last time we did a 1kHz run of rcout
uint32_t last_cycle_run_us = 0 ;
rcout_thread_ctx = chThdGetSelfX ( ) ;
@ -130,33 +132,62 @@ void RCOutput::rcout_thread()
@@ -130,33 +132,62 @@ void RCOutput::rcout_thread()
hal . scheduler - > delay_microseconds ( 1000 ) ;
}
// dshot is quite sensitive to timing, it's important to output pulses as
// regularly as possible at the correct bitrate
while ( true ) {
const uint32_t period_us = AP_HAL : : micros ( ) - last_thread_run_us ;
if ( period_us < 1000 ) {
chEvtWaitOneTimeout ( EVT_PWM_SEND , chTimeUS2I ( 1000 - period_us ) ) ;
}
chEvtWaitOne ( EVT_PWM_SEND ) ;
// start the clock
last_thread_run_us = AP_HAL : : micros ( ) ;
// main thread requested a new dshot send
// this is when the cycle is supposed to start
if ( _dshot_cycle = = 0 ) {
last_cycle_run_us = AP_HAL : : micros ( ) ;
// register a timer for the next tick if push() will not be providing it
if ( _dshot_rate ! = 1 ) {
chVTSet ( & _dshot_rate_timer , chTimeUS2I ( _dshot_period_us ) , dshot_update_tick , this ) ;
}
}
// if DMA sharing is in effect there can be quite a delay between the request to begin the cycle and
// actually sending out data - thus we need to work out how much time we have left to collect the locks
uint32_t time_out_us = ( _dshot_cycle + 1 ) * _dshot_period_us + last_cycle_run_us ;
if ( ! _dshot_rate ) {
time_out_us = last_thread_run_us + _dshot_period_us ;
}
// main thread requested a new dshot send or we timed out - if we are not running
// as a multiple of loop rate then ignore EVT_PWM_SEND events to preserve periodicity
if ( ! serial_group ) {
// do a blocking send now, to guarantee DShot sends at
// above 1000 Hz. This makes the protocol more reliable on
// long cables, and also keeps some ESCs happy that don't
// like low rates
dshot_send_groups ( ) ;
dshot_send_groups ( time_out_us ) ;
// now unlock everything
dshot_collect_dma_locks ( last_thread_run_us ) ;
dshot_collect_dma_locks ( time_out_us ) ;
_dshot_cycle = ( _dshot_cycle + 1 ) % _dshot_rate ;
}
// process any pending RC output requests
timer_tick ( last_thread_run_us ) ;
timer_tick ( time_out _us) ;
}
}
void RCOutput : : dshot_update_tick ( void * p )
{
chSysLockFromISR ( ) ;
RCOutput * rcout = ( RCOutput * ) p ;
if ( rcout - > _dshot_cycle < rcout - > _dshot_rate - 1 ) {
chVTSetI ( & rcout - > _dshot_rate_timer , chTimeUS2I ( rcout - > _dshot_period_us ) , dshot_update_tick , p ) ;
}
chEvtSignalI ( rcout - > rcout_thread_ctx , EVT_PWM_SEND ) ;
chSysUnlockFromISR ( ) ;
}
# ifndef HAL_NO_SHARED_DMA
// release locks on the groups that are pending in reverse order
void RCOutput : : dshot_collect_dma_locks ( uint32_t last_run_us )
void RCOutput : : dshot_collect_dma_locks ( uint32_t time_out _us)
{
if ( NUM_GROUPS = = 0 ) {
return ;
@ -165,11 +196,17 @@ void RCOutput::dshot_collect_dma_locks(uint32_t last_run_us)
@@ -165,11 +196,17 @@ void RCOutput::dshot_collect_dma_locks(uint32_t last_run_us)
pwm_group & group = pwm_group_list [ i ] ;
if ( group . dma_handle ! = nullptr & & group . dma_handle - > is_locked ( ) ) {
// calculate how long we have left
const uint32_t deltat = AP_HAL : : micros ( ) - last_run_us ;
uint32_t now = AP_HAL : : micros ( ) ;
// if we have time left wait for the event
eventmask_t mask = 0 ;
if ( deltat < 1000 ) {
mask = chEvtWaitOneTimeout ( group . dshot_event_mask , chTimeUS2I ( 1000 - deltat ) ) ;
const uint32_t pulse_elapsed_us = now - group . last_dmar_send_us ;
if ( now < time_out_us ) {
mask = chEvtWaitOneTimeout ( group . dshot_event_mask ,
chTimeUS2I ( MAX ( time_out_us - now , group . dshot_pulse_send_time_us - pulse_elapsed_us ) ) ) ;
} else if ( pulse_elapsed_us < group . dshot_pulse_send_time_us ) {
// better to let the burst write in progress complete rather than cancelling mid way through
mask = chEvtWaitOneTimeout ( group . dshot_event_mask ,
chTimeUS2I ( group . dshot_pulse_send_time_us - pulse_elapsed_us ) ) ;
}
// no time left cancel and restart
if ( ! mask ) {
@ -345,6 +382,34 @@ void RCOutput::set_default_rate(uint16_t freq_hz)
@@ -345,6 +382,34 @@ void RCOutput::set_default_rate(uint16_t freq_hz)
}
}
/*
Set the dshot rate as a multiple of the loop rate
*/
void RCOutput : : set_dshot_rate ( uint8_t dshot_rate , uint16_t loop_rate_hz )
{
// for low loop rates simply output at 1Khz on a timer
if ( loop_rate_hz < = 100 ) {
_dshot_period_us = 1000UL ;
_dshot_rate = 0 ;
return ;
}
uint16_t drate = dshot_rate * loop_rate_hz ;
_dshot_rate = dshot_rate ;
// never allow rates below 500hz
while ( drate < 500 ) {
_dshot_rate + + ;
drate = _dshot_rate * loop_rate_hz ;
}
// prevent stupidly high rates, ideally should also prevent high rates
// with slower dshot variants
if ( drate > 4000 ) {
_dshot_rate = 4000 / loop_rate_hz ;
drate = _dshot_rate * loop_rate_hz ;
}
_dshot_period_us = 1000000UL / drate ;
}
/*
find pwm_group and index in group given a channel number
*/
@ -631,7 +696,7 @@ bool RCOutput::setup_group_DMA(pwm_group &group, uint32_t bitrate, uint32_t bit_
@@ -631,7 +696,7 @@ bool RCOutput::setup_group_DMA(pwm_group &group, uint32_t bitrate, uint32_t bit_
group . dma_buffer_len = buffer_length ;
}
// reset the pulse time inside the lock
group . dshot_pulse_time_us = pulse_time_us ;
group . dshot_pulse_time_us = group . dshot_pulse_send_time_us = pulse_time_us ;
# ifdef HAL_WITH_BIDIR_DSHOT
// configure input capture DMA if required
@ -648,42 +713,30 @@ bool RCOutput::setup_group_DMA(pwm_group &group, uint32_t bitrate, uint32_t bit_
@@ -648,42 +713,30 @@ bool RCOutput::setup_group_DMA(pwm_group &group, uint32_t bitrate, uint32_t bit_
group . pwm_started = false ;
}
// adjust frequency to give an allowed value given the
// clock. There is probably a better way to do this
const uint32_t original_bitrate = bitrate ;
uint32_t freq = 0 ;
uint32_t target_frequency = bitrate * bit_width ;
while ( true ) {
uint32_t clock_hz = group . pwm_drv - > clock ;
target_frequency = bitrate * bit_width ;
uint32_t prescaler = clock_hz / target_frequency ;
freq = clock_hz / prescaler ;
// hal.console->printf("CLOCK=%u BW=%u FREQ=%u PRE=%u BR=%u\n", clock_hz, bit_width, freq/bit_width, prescaler, bitrate);
if ( prescaler > 0x8000 ) {
group . dma_handle - > unlock ( ) ;
return false ;
}
if ( ! choose_high ) {
break ;
}
// we want to choose a frequency that gives at least the
// target, erring on the high side not low side
uint32_t actual_bitrate = freq / bit_width ;
if ( actual_bitrate > = original_bitrate | | bitrate < 10000 ) {
break ;
}
bitrate + = 10000 ;
if ( bitrate > = 2 * original_bitrate ) {
break ;
}
// original prescaler calculation from betaflight. bi-dir dshot is incredibly sensitive to the bitrate
const uint32_t target_frequency = bitrate * bit_width ;
uint32_t prescaler = uint32_t ( lrintf ( ( float ) group . pwm_drv - > clock / ( bitrate * bit_width ) + 0.01f ) - 1 ) ;
uint32_t freq = group . pwm_drv - > clock / prescaler ;
if ( freq > target_frequency & & ! choose_high ) {
prescaler + + ;
} else if ( freq < target_frequency & & choose_high ) {
prescaler - - ;
}
if ( prescaler > 0x8000 ) {
group . dma_handle - > unlock ( ) ;
return false ;
}
freq = group . pwm_drv - > clock / prescaler ;
group . pwm_cfg . frequency = freq ;
group . pwm_cfg . period = bit_width ;
group . pwm_cfg . dier = TIM_DIER_UDE ;
group . pwm_cfg . cr2 = 0 ;
group . bit_width_mul = ( freq + ( target_frequency / 2 ) ) / target_frequency ;
//hal.console->printf("CLOCK=%u BW=%u FREQ=%u BR=%u MUL=%u PRE=%u\n", unsigned(group.pwm_drv->clock), unsigned(bit_width), unsigned(group.pwm_cfg.frequency),
// unsigned(bitrate), unsigned(group.bit_width_mul), unsigned(prescaler));
for ( uint8_t j = 0 ; j < 4 ; j + + ) {
pwmmode_t mode = group . pwm_cfg . channels [ j ] . mode ;
if ( mode ! = PWM_OUTPUT_DISABLED ) {
@ -775,7 +828,7 @@ void RCOutput::set_group_mode(pwm_group &group)
@@ -775,7 +828,7 @@ void RCOutput::set_group_mode(pwm_group &group)
// configure timer driver for DMAR at requested rate
if ( ! setup_group_DMA ( group , rate , bit_period , active_high ,
MAX ( DSHOT_BUFFER_LENGTH , GCR_TELEMETRY_BUFFER_LEN ) , false , pulse_send_time_us ) ) {
MAX ( DSHOT_BUFFER_LENGTH , GCR_TELEMETRY_BUFFER_LEN ) , group . current_mode ! = MODE_PWM_DSHOT150 , pulse_send_time_us ) ) {
group . current_mode = MODE_PWM_NORMAL ;
break ;
}
@ -1013,7 +1066,7 @@ void RCOutput::trigger_groups(void)
@@ -1013,7 +1066,7 @@ void RCOutput::trigger_groups(void)
periodic timer . This is used for oneshot and dshot modes , plus for
safety switch update . Runs every 1000u s .
*/
void RCOutput : : timer_tick ( uint32_t last_run _us)
void RCOutput : : timer_tick ( uint32_t time_out _us)
{
safety_update ( ) ;
@ -1022,20 +1075,21 @@ void RCOutput::timer_tick(uint32_t last_run_us)
@@ -1022,20 +1075,21 @@ void RCOutput::timer_tick(uint32_t last_run_us)
}
// if we have enough time left send out LED data
if ( serial_led_pending & & AP_HAL : : micros ( ) - last_run_us < 500 ) {
if ( serial_led_pending & & ( time_out_us > ( AP_HAL : : micros ( ) + ( _dshot_period_us > > 1 ) ) ) ) {
serial_led_pending = false ;
for ( auto & group : pwm_group_list ) {
serial_led_pending | = ! serial_led_send ( group ) ;
}
// release locks on the groups that are pending in reverse order
dshot_collect_dma_locks ( last_run _us) ;
dshot_collect_dma_locks ( time_out _us) ;
}
if ( min_pulse_trigger_us = = 0 ) {
return ;
}
# if !APM_BUILD_TYPE(APM_BUILD_ArduCopter)
// this exists simply to cater for non-multirotors whose loop rate might be 50Hz
uint32_t now = AP_HAL : : micros ( ) ;
if ( now > min_pulse_trigger_us & &
@ -1043,10 +1097,11 @@ void RCOutput::timer_tick(uint32_t last_run_us)
@@ -1043,10 +1097,11 @@ void RCOutput::timer_tick(uint32_t last_run_us)
// trigger at a minimum of 250Hz
trigger_groups ( ) ;
}
# endif
}
// send dshot for all groups that support it
void RCOutput : : dshot_send_groups ( )
void RCOutput : : dshot_send_groups ( uint32_t time_out_us )
{
if ( serial_group ) {
return ;
@ -1055,7 +1110,7 @@ void RCOutput::dshot_send_groups()
@@ -1055,7 +1110,7 @@ void RCOutput::dshot_send_groups()
// actually do a dshot send
for ( auto & group : pwm_group_list ) {
if ( group . can_send_dshot_pulse ( ) ) {
dshot_send ( group ) ;
dshot_send ( group , time_out_us ) ;
// delay sending the next group by the same amount as the IRQ dead time
// to avoid irq collisions
if ( group . bdshot . enabled ) {
@ -1154,7 +1209,7 @@ void RCOutput::fill_DMA_buffer_dshot(uint32_t *buffer, uint8_t stride, uint16_t
@@ -1154,7 +1209,7 @@ void RCOutput::fill_DMA_buffer_dshot(uint32_t *buffer, uint8_t stride, uint16_t
This call be called in blocking mode from the timer , in which case it waits for the DMA lock .
In normal operation it doesn ' t wait for the DMA lock .
*/
void RCOutput : : dshot_send ( pwm_group & group )
void RCOutput : : dshot_send ( pwm_group & group , uint32_t time_out_us )
{
# ifndef DISABLE_DSHOT
if ( irq . waiter | | ( group . dshot_state ! = DshotState : : IDLE & & group . dshot_state ! = DshotState : : RECV_COMPLETE ) ) {
@ -1165,6 +1220,14 @@ void RCOutput::dshot_send(pwm_group &group)
@@ -1165,6 +1220,14 @@ void RCOutput::dshot_send(pwm_group &group)
// first make sure we have the DMA channel before anything else
osalDbgAssert ( ! group . dma_handle - > is_locked ( ) , " DMA handle is already locked " ) ;
group . dma_handle - > lock ( ) ;
// if we are sharing UP channels then it might have taken a long time to get here,
// if there's not enough time to actually send a pulse then cancel
if ( AP_HAL : : micros ( ) + group . dshot_pulse_time_us > time_out_us ) {
group . dma_handle - > unlock ( ) ;
return ;
}
// only the timer thread releases the locks
group . dshot_waiter = rcout_thread_ctx ;
# ifdef HAL_WITH_BIDIR_DSHOT
@ -1406,8 +1469,16 @@ void RCOutput::dma_cancel(pwm_group& group)
@@ -1406,8 +1469,16 @@ void RCOutput::dma_cancel(pwm_group& group)
dmaStreamDisable ( group . bdshot . ic_dma [ group . bdshot . curr_telem_chan ] ) ;
}
# endif
// normally the CCR registers are reset by the final 0 in the DMA buffer
// since we are cancelling early they need to be reset to avoid infinite pulses
for ( uint8_t i = 0 ; i < 4 ; i + + ) {
if ( group . chan [ i ] ! = CHAN_DISABLED ) {
group . pwm_drv - > tim - > CCR [ i ] = 0 ;
}
}
chVTResetI ( & group . dma_timeout ) ;
chEvtGetAndClearEventsI ( group . dshot_event_mask ) ;
group . dshot_state = DshotState : : IDLE ;
chSysUnlock ( ) ;
}