One is always torn between posting too little (and getting replies from irritated people saying I didn't post enough info) and posting too much (and getting no replies at all)

Transmit mode terminates with TX interrupt finding no more data to send:
void serial_transmit_irq_handler(UART_HandleTypeDef *huart, uint8_t port)
{
port--; // offset from 0
// Is there anything left to send?
if (serial_transmit_buffer_tx_index[port] == serial_transmit_buffer_put_index[port])
{
// Disable the TXEmpty interrupt
huart->Instance->CR1 &= ~(UART_IT_TXE);
}
else
{
// Send the next byte
huart->Instance->DR = serial_transmit_buffer[port][serial_transmit_buffer_tx_index[port]++];
if( serial_transmit_buffer_tx_index[port] >= SERIAL_TX_BUFFER_SIZE)
{
serial_transmit_buffer_tx_index[port] = 0;
}
g_comms_act[port+1]=LED_COM_TC; // indicate data flow on LED 1-4
}
}
So, after TX int has been disabled by the above, when new data has been placed into the circular buffer, you have to load 1 byte into the UART TX and enable TX interrupt. This is standard "UART stuff". Then the thing goes off by itself, transmitting under interrupt.
The UART_IT_TXE bit is also being used - as suggested further back - as an indicator as to whether transmit mode is active. If 1, then you just put new data into the buffer. If 0, then you have to do the above. Previously I used a separate flag for this but it turned out to be just a copy of IT_TXE bit.
TC is a separate thing here. The TC ISR is basically used just for disabling RS485 drivers:
// This ISR turns off the 485 driver on p3/p4
// https://peter-ftp.co.uk/screenshots/202210015115900110.jpg
void serial_transmit_complete_irq_handler(UART_HandleTypeDef *huart, uint8_t port)
{
if ( (port == 3) || (port == 4) )
{
// If this is an RS485 port and auto driver mode is set, disable the driver now
if (serial_driver_mode[port-1] == 2)
{
serial_485_driver_control_internal(port, 0);
}
}
// Transmit complete
serial_transmit_active[port-1] = false;
// Clear and disable the TC interrupt. This is supposed to be done by a read of SR and a write to DR
// but in this case we have nothing to write to DR so we clear the TC bit by writing a 0 to it but
// avoiding RMW on SR - see http://efton.sk/STM32/gotcha/g9.html
huart->Instance->SR = ~(UART_FLAG_TC);
huart->Instance->CR1 &= ~(UART_IT_TC);
}
32F417 yes
LED(true) and LED(false) set/reset a GPIO pin, producing a pulse.
Maybe I have done something stupid but a) I can't see what it is and b) the issue occurs extremely rarely.
To re-iterate: After TX int has been disabled (in the TXE ISR, upon finding the circular queue empty) when
new data has been placed into the circular buffer, and the 1 byte has been loaded
directly into the UART TX holding register, the UART appears to be
very occassionally discarding this byte, even though
before that the TXE interrupt has been generated which could only be if there is room in the holding register. That is what TXE is: there is room for 1+ bytes. In the 32F417 it will be just one byte (although if you got the timing just right you might be able to get two bytes in, but I am not doing that). The hack (waiting for TXE=1, before loading this byte) works but the wait takes a little too long in this particular application.
Putting it another way, it appears that the TXE interrupt is sometimes generated
before the byte in the tx holding register has been moved to the tx shift register.
The data loss is of the order of 1 byte in 10^7 bytes so in the vast majority of applications would not be noticed. This is the frequency of the pulse I see on the scope. Just one every few hours or so. Just seen one which was 52us which is even less related to 19200 baud than previous ones.
If anyone can think if a better way to address this, I am all ears, otherwise I will implement the wait for TXE=1 just above the DI/EI region where an extra few tens of us doesn't matter.