With the project nearly finished I have finally got around to writing some comprehensive factory test code.
And I found that serial port loopback does not work at baud rates over about 38k.
The serial ports all work and have been heavily used with various devices (at/below 38k), and outputting data has been tested up to megabit speeds, but it turns out that at say 115k the 1st byte sent, on a loopback test, is never received. Subsequent ones are. If I send just one byte, the RX ISR is never hit. This behaviour could easily have never been noticed in practical usage...
I've been "doing UARTs" since about 1980... In general, you
- initialise it (the 32F4 UART transmits "an empty frame" there, apparently without a start bit, so I have a baud rate dependent wait to clear this silly feature)
- flush RX buffer, as many times as the FIFO is deep (2x for the 32F4 one)
- set up the RX interrupt
There tend to be weird things like framing errors which need to be cleared on each RX ISR entry (unless you especially want to log them, etc) but these don't normally prevent the RX interrupt.
The simple test I am running is
- output 1 byte (with loopback, TX to RX)
- wait for it to come back (it never does)
There is a 1k circular buffer on RX but as I said above this is never touched because there is no interrupt. If I send two bytes, I get the interrupt and the data read from DR is the 1st byte. If I send 256 bytes then I get back 255.
On a scope, the data looks fine.
This is the RX ISR
void Kde_serial_receive_irq_handler(UART_HandleTypeDef *huart)
{
uint8_t temp = huart->Instance->DR; // fetch the byte received
uint8_t port = KDE_SERIAL_MAX_PORT; // max port number
int32_t count = 0;
__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_RXNE);
// Find which port this is in order to know which buffer to use
while (port > 0 && huart->Instance != kde_serial_port_handles[port-1]->Instance)
{
port--; // port is 1-4
}
// Put the char into the queue only if port # valid and the queue is not full
if (port > 0)
{
port--; // offset from 0
// If 7 bit, mask off any parity in the MS bit
// If 8 bit with parity, the parity would be in bit 8 which we've already lost by reading into
// and 8 bit value
if (kde_serial_port_configs[port].bits_per_word == 7) {
temp &= 0x7f;
}
kde_serial_receive_buffer[port][kde_serial_receive_buffer_rx_index[port]++] = temp;
kde_serial_receive_buffer_rx_index[port] %= KDE_SERIAL_RX_BUFFER_SIZE;
if (kde_serial_receive_buffer_rx_index[port] == kde_serial_receive_buffer_get_index[port])
{
// Buffer is full - discard a byte from it. This is necessary to resolve the ambiguity of
// the two pointers being equal if FULL or EMPTY. We make sure they are never equal after an input.
// Increment the get index to ensure that only the last BUFFER-1 bytes can be accessed
kde_serial_receive_buffer_get_index[port]++;
kde_serial_receive_buffer_get_index[port] %= KDE_SERIAL_RX_BUFFER_SIZE;
}
}
}
I am doing the RX clearing diligently after UART init, but I wonder if anyone has any ideas.
One thing I wondered was whether the GPIO (RX) is sampled fast enough; there is a range of sampling rates. I see code setting it up for "very high", but also enabling a UART on certain GPIOs may set up those pins; the extent to which this happens varies with the peripheral.
EDIT: reading SR and then DR manually returns 10010000 (TX empty, ok, and Idle Line detected, not ok) and in DR I see the correct byte value.
Basically, if I transmit by writing to the DR directly, it works, but if I transmit using a more complex TX function (which enables the TX empty and RX complete interrupts) it fails on the first byte. If I comment out the TXE and TC interrupt enable code, it works, but obviously I then get a very slow transmit, 1 byte at a time only. So is there something about these TX interrupts which might block the RX interrupt?