cancel
Showing results for 
Search instead for 
Did you mean: 

printf with HAL_UART_Transmit_IT?

toby2
Senior
Posted on November 09, 2016 at 10:34

I am new to the hal libraries (but not stm32 devices) and struggle to see howHAL_UART_Transmit_IT is expected to be used for something simple like printf over uart.

Take a blocking example:

void PRINT_MESG_UART(const char * format, ... )
{
va_list ap;
uint8_t buffer [128];
int n;
va_start(ap, format);
n = vsnprintf ((char*)buffer, 128, format, ap);
va_end(ap);
//notify_uart(buffer, n);
if(HAL_UART_Transmit(&UartHandle, (uint8_t*)buffer, n, 300) != HAL_OK) {
Error_Handler();
}
}

That works fine. But what if I want it non blocking under interrupts? What I have come up with is as follows:

uint8_t buffer [128];
void PRINT_MESG_UART(const char * format, ... )
{
va_list ap;
int n;
while (UartHandle.gState != HAL_UART_STATE_READY)
; // will overwrite the buffer otherwise, (needs a timeout and error handling)
va_start(ap, format);
n = vsnprintf ((char*)buffer, 128, format, ap);
va_end(ap);
HAL_UART_Transmit_IT(&UartHandle, (uint8_t*)buffer, n) == HAL_BUSY) ;
}

This works, but is pretty poor as it still blocks if the buffer is already in use. Is there a better way? The obvious way is to use the traditional method of a circular buffer. eg

bool u_put_byte(uint8_t data)
{
bool ok = FALSE;
if ((uart_buffer.txin + 1) % S_BUF_SIZE != uart_buffer.txout)
{
uart_buffer.txbuf[uart_buffer.txin] = data;
uart_buffer.txin = (uart_buffer.txin + 1) % S_BUF_SIZE;
ok = TRUE;
}
SET_BIT(UartHandle.Instance->CR1, USART_CR1_TXEIE);
return ok;
}
void PRINT_MESG_UART(const char * format, ... )
{
va_list ap;
uint8_t buffer[S_BUF_SIZE];
int n;
va_start(ap, format);
n = vsnprintf ((char*)buffer, S_BUF_SIZE, format, ap);
va_end(ap);
int i = 0;
while ((buffer[i] != 0) && (i < S_BUF_SIZE) && (i < n))
{
while (!u_put_byte(buffer[i])) // keep attempting until it succeeds
; // timeout and error handling here.
i++;
}
}

However the HAL uart library functions do not appear to support this sort of buffer. Am I correct? #nucleo #stm32 #hal #uart
30 REPLIES 30
Posted on March 30, 2017 at 17:05

I agree with Aaron, your use of single byte buffer is not a great idea.

In the first example you are only giving the HAL RX interrupt a 1 byte buffer so must call 

getch_1() quicker than the rx data is received or you will get an overrun. I don't know in this example what printf() is writing to but if it ever blocks (eg writing to a uart) then it will cause this RX overrun.

The second example I cannot comment on too much as I don't know what 

byteq_get/put do or what &g_handles[SCI_CH1]->tx_queue is. I guess these manage a larger buffer but would be good to see these routines and the initialisation of the buffer pointers. Again though you only have the buffer on the TX I think, if it is still a single byte buffer on the RX then anything that holds up the main loop will cause the RX to overrun the buffer.

Posted on March 30, 2017 at 20:55

The reason why print 1 char is because i do not know how many will follow. And if i input 1 char with keyboard i want 1 printed. A 48mhz cpu has no problems with it, something like this with a 8 bit pic is no problem.

Posted on March 30, 2017 at 21:13

Here my CubeMX generated project. The receive using que does work. The print with printf uses the blocking routine this works. The sci1_printf uses the que this does not work.

When entering chars on uart1 it prints them directly on uart1. CTRL-V lots of chars and no more receiving irq's. So i made a byteq to print using irq's

You can type 'send' to direct send aHAL_UART_Transmit_IT or 'send2' to fill the byteq but i cannot start that!

Problem is how the start the printing see usart.c line 419

The idea is filling the buffer using sci1_printf, enable the tx buffer empty IRQ and when the char is printed an irq gets the next char and so on.

The project runs on my homemade pcb STM32f103 but the routine should work on every STM32

________________

Attachments :

Test_Uart_103.zip : https://st--c.eu10.content.force.com/sfc/dist/version/download/?oid=00Db0000000YtG6&ids=0680X000006Hye0&d=%2Fa%2F0X0000000bBm%2FN.7INZed4I7pUVLP.qeG4nrUjI3GnS6_UTaHc2bmc38&asPdf=false
T J
Lead
Posted on March 31, 2017 at 01:29

I am sorry that I don't have time to load your code and try to fix, but I can give you hints from my set;

I had to poll the HAL progress in the foreground for non-blocking Tx, this works from 1 byte to 1024 bytes, without issue.

 void IO::CheckTxDMABufferProgress(void) {
if (TxDMABufHasData) {
char uartState = HAL_UART_GetState(&huart1);
if ((uartState == HAL_UART_STATE_READY) || (uartState == HAL_UART_STATE_BUSY_RX)) {
TxDMABufHasData = false;// sending now
if (HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Usart1TxDMABuffer + U1TxBufferPtrOUT, U1TxBufferPtrIN - U1TxBufferPtrOUT) == HAL_OK) {
HAL_UART_Transmit_DMA_Status = UartDMAsuccess;
U1TxBufferPtrOUT = U1TxBufferPtrIN;
}
else {
Error_Handler();/* Transfer error in transmission process */
HAL_UART_Transmit_DMA_Status = UartDMAfailed;
}
}
}
 }�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

hope this helps.

Posted on March 31, 2017 at 10:21

I understand that you only want to read and write a single character at a time but you should still buffer both the receive and transmit to allow the main loop to run asynchronously from the serial hardware. In my opinion anyway!

It is not to do with the processor, that works fine, you just need to find the bug in your software

Posted on March 31, 2017 at 10:35

I haven't been following this too closely, but is your printf() writing chars directly to the UART data register? If so, you need to think about separation of concerns. My logger class uses vsnprintf() to create the formatted output (there is a maximum allowed length for strings, of course, but you can be generous). The formatted string is then written to the UART driver class. I mentioned earlier in this thread that I use a queue of DMA transfers in conjunction with a TX buffer - the formatted string is basically memcpy'd (fast) into the TX buffer and a DMA transfer is queued. And that's it.

void DebugLogger::write(DebugLogger::LogType type, bool to_file, const char* format, ...)

{

    // Omitted mutex and other irrelevant stuff ...

    // Static just to keep this off the thread's stack.

    static char msg_buffer[DEBUG_TX_MSG_BUFFER_SIZE];

    uint32_t    msg_length = 0;

    ...

    std::va_list args;

    va_start(args, format);

    msg_length += std::vsnprintf(msg_buffer + msg_length, DEBUG_TX_MSG_BUFFER_SIZE - msg_length, format, args);

    va_end(args);

    ...

    // You always know how long the output is - simplified: I actually wrap the text in encrypted packets first.

    debug_uart().write(msg_buffer);

}

The debug UART runs at 230400 baud with no delays at all between bytes.

The UART driver also uses DMA for RX data (I poll every millisecond to see how many new bytes have been received), and this is perfectly concurrent with TX without blocking. The received bytes are added to an RX queue and I emit an event to indicate that new data is available. Or I could just poll the RX buffer. I don't recommend directly polling the hardware. The received bytes are fed into a simple state machine which parses the input to find valid packets. Packets are passed to a command handler for decode and dispatch. Separation of concerns.

The application is also using interrupts and/or DMA to read data from a bunch of sensors (I2C, SPI, CAN) at a high rate. None of this blocks at all. In fact, the program spends most of its time spinning its wheels in the scheduler.

My advice in a nutshell: let the hardware do the work. Umpteen man hours have been spent designing clever microcontroller peripherals that allow you to offload a lot of tedious, expensive, and blocking byte shifting onto silicon.

Posted on March 31, 2017 at 10:58

I am afraid that, like Nick, I don't have time to try and understand your code properly. I have had a quick look though and it does appear to buffer both the rx and tx so I don't understand what we were talking about above.

I have also noticed a couple of issues though:

1) in start_uart() you activate the RX before setting up the queues. That doesn't sound the right order to me although I don't think it is the problem in this case.

2) I think your rx callback is incorrect, you appear to be reading the uart DR. This has already been done by the UART irq routine which will have put the data in Rx_data1 which you don't appear to use. 

I think you need to read the HAL description again but basically the sequence for receiving is:

- start the RX with  HAL_UART_Receive_IT(&huart1, Rx_data1, 1); 

- the irq routine puts any data in Rx_data1 and calls the callback when it has received enough data (1 byte in this case)

- your callback routine should take the data from Rx_data1 and do something with it. (in this case put it in the queue?)

Still not sure if this the root of your problem but it does need sorting. Given this error I would also look more carefully at the tx stuff.

Posted on March 31, 2017 at 11:14

his printf appears to be using the blocking HAL transmit:

void vprint(const char *fmt, va_list argp)

{

char string[200];

if (0 < vsprintf(string, fmt, argp)) // build string

{

HAL_UART_Transmit(&huart1, (uint8_t*) string, strlen(string), 0xffffff); // send message via UART

}

}

void my_printf(const char *fmt, ...) // custom printf() function

{

va_list argp;

va_start(argp, fmt);

vprint(fmt, argp);

va_end(argp);

}
Posted on March 31, 2017 at 13:11

It is somewhat strange to use the 

HAL_UART_Receive_IT(&huart1, Rx_data1, 1);  and not using the RX_data1. It does work and is only used the enable the receive irq again.

The problem is sci1_printf i cannot get it started. It is not possible to use the 

HAL_UART_Transmit_IT() in the sci1_printf function because it can be busy printing.

Posted on March 31, 2017 at 15:16

Well, I suspect the RX works only because you have a single byte buffer so the value in DR is what the interrupt routine has just put in Rx_data1. Still I would fix the Rx_data1 problem even if it not the fault you are seeing now.

Anyway, I just had a look at 

sci1_printf() and it does not use HAL_UART_Transmit_IT() so what is the code you were having the problem with? 

What I see is :

void sci1_printf(const char *fmt, ...) // custom printf() function

{

&sharpdefine S_BUF_SIZE 256

va_list ap;

uint8_t buffer[S_BUF_SIZE];

int n;

va_start(ap, fmt);

n = vsnprintf((char*) buffer, S_BUF_SIZE, fmt, ap);

va_end(ap);

CLEAR_BIT(huart1.Instance->CR1, USART_CR1_TXEIE);

int i = 0;

while ((buffer[i] != 0) && (i < S_BUF_SIZE) && (i < n))

{

while (!byteq_put(g_handles[SCI_CH1]->tx_queue, buffer[i]))

; // timeout and error handling here.

i++;

}

SET_BIT(huart1.Instance->CR1, USART_CR1_TXEIE);

}

I don't see how this will work with HAL as it is not telling HAL where the transmit buffer is?

I do see that your TX callback sends a byte at a time from the buffer but that will not be triggered unless it has already sent some data and the only thing that will cause that is the 'Hello' transmit, I am not sure if you are triggering that?

Lastly, the TX callback is not right, it takes a byte from the queue into ch and then does a HAL_UART_Transmit_IT(&huart1, ch, 1), but ch is local so will become invalid as soon as the callback exits. Actually I am confused, are you not also passing a uint8_t where HAL_UART_Transmit_IT wants a uint8_t *. Do you not get a compiler warning?