HAL_ChibiOS: implement half-duplex delay and read locking

This commit is contained in:
Andrew Tridgell 2018-12-19 22:25:41 +11:00
parent c99dd5f9e8
commit 8c43db1a00
2 changed files with 85 additions and 14 deletions

View File

@ -399,6 +399,13 @@ void UARTDriver::rxbuff_full_irq(void* self, uint32_t flags)
return; return;
} }
if (uart_drv->half_duplex) {
uint32_t now = AP_HAL::micros();
if (now - uart_drv->hd_write_us < uart_drv->hd_read_delay_us) {
len = 0;
}
}
uart_drv->_readbuf.write(uart_drv->rx_bounce_buf, len); uart_drv->_readbuf.write(uart_drv->rx_bounce_buf, len);
uart_drv->receive_timestamp_update(); uart_drv->receive_timestamp_update();
@ -468,7 +475,7 @@ bool UARTDriver::tx_pending() { return false; }
/* Empty implementations of Stream virtual methods */ /* Empty implementations of Stream virtual methods */
uint32_t UARTDriver::available() { uint32_t UARTDriver::available() {
if (!_initialised) { if (!_initialised || lock_read_key) {
return 0; return 0;
} }
if (sdef.is_usb) { if (sdef.is_usb) {
@ -492,7 +499,7 @@ uint32_t UARTDriver::txspace()
int16_t UARTDriver::read() int16_t UARTDriver::read()
{ {
if (_uart_owner_thd != chThdGetSelfX()){ if (lock_read_key != 0 || _uart_owner_thd != chThdGetSelfX()){
return -1; return -1;
} }
if (!_initialised) { if (!_initialised) {
@ -510,10 +517,28 @@ int16_t UARTDriver::read()
return byte; return byte;
} }
int16_t UARTDriver::read_locked(uint32_t key)
{
if (lock_read_key != 0 && key != lock_read_key) {
return -1;
}
if (!_initialised) {
return -1;
}
uint8_t byte;
if (!_readbuf.read_byte(&byte)) {
return -1;
}
if (!_rts_is_active) {
update_rts_line();
}
return byte;
}
/* Empty implementations of Print virtual methods */ /* Empty implementations of Print virtual methods */
size_t UARTDriver::write(uint8_t c) size_t UARTDriver::write(uint8_t c)
{ {
if (lock_key != 0 || !_write_mutex.take_nonblocking()) { if (lock_write_key != 0 || !_write_mutex.take_nonblocking()) {
return 0; return 0;
} }
@ -539,7 +564,7 @@ size_t UARTDriver::write(uint8_t c)
size_t UARTDriver::write(const uint8_t *buffer, size_t size) size_t UARTDriver::write(const uint8_t *buffer, size_t size)
{ {
if (!_initialised || lock_key != 0) { if (!_initialised || lock_write_key != 0) {
return 0; return 0;
} }
@ -569,15 +594,20 @@ size_t UARTDriver::write(const uint8_t *buffer, size_t size)
} }
/* /*
lock the uart for exclusive use by write_locked() with the right key lock the uart for exclusive use by write_locked() and read_locked() with the right key
*/ */
bool UARTDriver::lock_port(uint32_t key) bool UARTDriver::lock_port(uint32_t write_key, uint32_t read_key)
{ {
if (lock_key && key != lock_key && key != 0) { if (lock_write_key && write_key != lock_write_key && read_key != 0) {
// someone else is using it // someone else is using it
return false; return false;
} }
lock_key = key; if (lock_read_key && read_key != lock_read_key && read_key != 0) {
// someone else is using it
return false;
}
lock_write_key = write_key;
lock_read_key = read_key;
return true; return true;
} }
@ -587,7 +617,7 @@ bool UARTDriver::lock_port(uint32_t key)
*/ */
size_t UARTDriver::write_locked(const uint8_t *buffer, size_t size, uint32_t key) size_t UARTDriver::write_locked(const uint8_t *buffer, size_t size, uint32_t key)
{ {
if (lock_key != 0 && key != lock_key) { if (lock_write_key != 0 && key != lock_write_key) {
return 0; return 0;
} }
if (!_write_mutex.take_nonblocking()) { if (!_write_mutex.take_nonblocking()) {
@ -686,6 +716,11 @@ void UARTDriver::write_pending_bytes_DMA(uint32_t n)
tx_len = max_tx_bytes; tx_len = max_tx_bytes;
} }
} }
if (half_duplex) {
half_duplex_setup_delay(tx_len);
}
tx_bounce_buf_ready = false; tx_bounce_buf_ready = false;
osalDbgAssert(txdma != nullptr, "UART TX DMA allocation failed"); osalDbgAssert(txdma != nullptr, "UART TX DMA allocation failed");
dmaStreamSetMemory0(txdma, tx_bounce_buf); dmaStreamSetMemory0(txdma, tx_bounce_buf);
@ -707,6 +742,8 @@ void UARTDriver::write_pending_bytes_DMA(uint32_t n)
void UARTDriver::write_pending_bytes_NODMA(uint32_t n) void UARTDriver::write_pending_bytes_NODMA(uint32_t n)
{ {
ByteBuffer::IoVec vec[2]; ByteBuffer::IoVec vec[2];
uint16_t nwritten = 0;
const auto n_vec = _writebuf.peekiovec(vec, n); const auto n_vec = _writebuf.peekiovec(vec, n);
for (int i = 0; i < n_vec; i++) { for (int i = 0; i < n_vec; i++) {
int ret = -1; int ret = -1;
@ -725,7 +762,7 @@ void UARTDriver::write_pending_bytes_NODMA(uint32_t n)
} }
if (ret > 0) { if (ret > 0) {
_last_write_completed_us = AP_HAL::micros(); _last_write_completed_us = AP_HAL::micros();
_total_written += ret; nwritten += ret;
} }
_writebuf.advance(ret); _writebuf.advance(ret);
@ -734,8 +771,13 @@ void UARTDriver::write_pending_bytes_NODMA(uint32_t n)
break; break;
} }
} }
}
_total_written += nwritten;
if (half_duplex) {
half_duplex_setup_delay(nwritten);
}
}
/* /*
write any pending bytes to the device write any pending bytes to the device
@ -793,6 +835,19 @@ void UARTDriver::write_pending_bytes(void)
} }
} }
/*
setup a delay after writing bytes to a half duplex UART to prevent
read-back of the same bytes that we wrote. half-duplex protocols
tend to have quite loose timing, which makes this possible
*/
void UARTDriver::half_duplex_setup_delay(uint16_t len)
{
const uint16_t pad_us = 1000;
hd_write_us = AP_HAL::micros();
hd_read_delay_us = ((1000000UL * len * 10) / _baudrate) + pad_us;
}
/* /*
push any pending bytes to/from the serial port. This is called at push any pending bytes to/from the serial port. This is called at
1kHz in the timer thread. Doing it this way reduces the system call 1kHz in the timer thread. Doing it this way reduces the system call
@ -865,6 +920,12 @@ void UARTDriver::_timer_tick(void)
if (ret < 0) { if (ret < 0) {
break; break;
} }
if (half_duplex) {
uint32_t now = AP_HAL::micros();
if (now - hd_write_us < hd_read_delay_us) {
break;
}
}
_readbuf.commit((unsigned)ret); _readbuf.commit((unsigned)ret);
receive_timestamp_update(); receive_timestamp_update();
@ -1150,6 +1211,7 @@ bool UARTDriver::set_options(uint8_t options)
if (options & OPTION_HDPLEX) { if (options & OPTION_HDPLEX) {
cr3 |= USART_CR3_HDSEL; cr3 |= USART_CR3_HDSEL;
_cr3_options |= USART_CR3_HDSEL; _cr3_options |= USART_CR3_HDSEL;
half_duplex = true;
} else { } else {
cr3 &= ~USART_CR3_HDSEL; cr3 &= ~USART_CR3_HDSEL;
} }

View File

@ -44,13 +44,14 @@ public:
uint32_t available() override; uint32_t available() override;
uint32_t txspace() override; uint32_t txspace() override;
int16_t read() override; int16_t read() override;
int16_t read_locked(uint32_t key) override;
void _timer_tick(void) override; void _timer_tick(void) override;
size_t write(uint8_t c) override; size_t write(uint8_t c) override;
size_t write(const uint8_t *buffer, size_t size) override; size_t write(const uint8_t *buffer, size_t size) override;
// lock a port for exclusive use. Use a key of 0 to unlock // lock a port for exclusive use. Use a key of 0 to unlock
bool lock_port(uint32_t key) override; bool lock_port(uint32_t write_key, uint32_t read_key) override;
// control optional features // control optional features
bool set_options(uint8_t options) override; bool set_options(uint8_t options) override;
@ -125,7 +126,8 @@ private:
uint8_t serial_num; uint8_t serial_num;
// key for a locked port // key for a locked port
uint32_t lock_key; uint32_t lock_write_key;
uint32_t lock_read_key;
uint32_t _baudrate; uint32_t _baudrate;
uint16_t tx_len; uint16_t tx_len;
@ -174,6 +176,13 @@ private:
uint32_t _cr3_options; uint32_t _cr3_options;
uint32_t _cr2_options; uint32_t _cr2_options;
// half duplex control. After writing we throw away bytes for 4 byte widths to
// prevent reading our own bytes back
bool half_duplex;
uint32_t hd_read_delay_us;
uint32_t hd_write_us;
void half_duplex_setup_delay(uint16_t len);
// set to true for unbuffered writes (low latency writes) // set to true for unbuffered writes (low latency writes)
bool unbuffered_writes; bool unbuffered_writes;