cancel
Showing results for 
Search instead for 
Did you mean: 

Blocking UART Receiver Implementation Saving Only One Character, Overrun flag

ilyus
Senior II

STM32F746 Disco, UART1 connected to integrated ST-Link, Windows 10, STM32CubeIDE 1.7.0, Putty on PC

I'm writing my own implementation of UART on registers in C, have datasheet and reference manual right here. I have no problem sending one character or an array of characters, I have no problem receiving one character (which means I've set it up OK). But I can't receive an array of characters, and I don't see why, because algorithmically I think I'm doing it right (as it always seems until someone points out an error). Been reading the reference manual for hours.

It's a most basic driver, no interrupts, no noise flags, overrun flags used, just learning stuff. Basic send-receive. Baudrate 9600.

Now, the features of my receiver method are the following:

  1. The method receives an array pointer and array length as two parameters
  2. On call, it enables the receiver.
  3. It waits indefinitely for the start of transmission to be received
  4. It fills up (supposed to) the buffer array as new values come in.
  5. If transmission ends before the buffer array is full, detect it and not wait for more data, disable receiver and return.
  6. If buffer array is full, but the transmission keeps going, just wait until the transmission is over and only then disable receiver and return.

Code:

void uart1_receiveArray(uint8_t *arraypointer, uint32_t length) {
	USART1->CR1 |= USART_CR1_RE; //USART Receiver enabled, line idle
	uint32_t pointer = 0;
	while ((((USART1->ISR) >> USART_ISR_RXNE_Pos) & 1U) == 0);	 //wait while first data comes from shift register
	arraypointer[pointer] = USART1->RDR;
	pointer++;
	while ((((USART1->ISR >> USART_ISR_IDLE_Pos) & 1U) == 0) && (pointer < length)) { //if line not idle and buffer array not full
		while ((((USART1->ISR) >> USART_ISR_RXNE_Pos) & 1U) == 0);	 //wait while data comes from shift register
		arraypointer[pointer] = USART1->RDR;
		pointer++;
	}
	while (((USART1->ISR >> USART_ISR_IDLE_Pos) & 1U) == 0); //if buffer is full, but transmission is still going, wait for it to end
	USART1->CR1 &= ~USART_CR1_RE; //Receiver disabled
}

I enter stuff to send in Putty. Array buffer is of length 8, I enter something shorter.

In debugging, I've set a breakpoint on Line 13 of the presented code and looked at UART1 ISR. The contents of it are:

10000000000000011111000

So of all interesting bits related to reception. it signals Overrun Error, Idle Line (ok this makes sense) and Read Data Register (RDR) not empty. At the same time, my "pointer" variable has a value of 1. As if the transmission sent all 4 (2 3 5 6) bytes before the "While Line Not Idle" loop.

Can anyone suggest a solution? How can I receive UP TO buffer length?

1 ACCEPTED SOLUTION

Accepted Solutions
ilyus
Senior II

Found solution! I re-wrote the whole thing using Receiver Timeout!

EDITED (bugfix)

void uart1_receiveArray(uint8_t *arraypointer, uint32_t length) {
	uint8_t *currentpointer = arraypointer;
	for (uint32_t k = 0; k < length; k++) { //fill the buffer with nulls so that it doesn't retain data from previous transmissions
		*(arraypointer + k) = '\0';
	}
	while (!(USART1->ISR & USART_ISR_RXNE)); //wait indefinitely for the beginning of the transmission
	USART1->ICR |= USART_ICR_RTOCF; //make sure receiver timeout flag is cleared
	while (!(USART1->ISR & USART_ISR_RTOF)) { //while not receiver timeout
		if (((USART1->ISR & USART_ISR_RXNE)) && (currentpointer < arraypointer + length)) { //if buffer is not full yet and there is new data
			*currentpointer = USART1->RDR;
			currentpointer++;
		}
	}
	USART1->ICR |= USART_ICR_RTOCF; //when receiver timed out and we're done, clear the flag
}

Tested with buffer of size 8. Works perfectly with transmissions of length 1, 3-7, 8 and 10 (retains only first 8 characters). Shorter timeout (0 bits) messes the entire thing up (makes sense).

View solution in original post

6 REPLIES 6
TDK
Guru

asdf

If you feel a post has answered your question, please click "Accept as Solution".

but what if the character is not available, but the transmission is still going? Like the character is only being transmitted and not yet in RDR? How exactly do you propose to change the code?

As for readability hint, thank you, I will take notice, looks much better. Should've come up with it myself heh.

EDIT: I tried ignoring IDLE part for a moment and expecing strict amount of data:

USART1->CR1 |= USART_CR1_RE; //USART Receiver enabled, line idle
	uint32_t pointer = 0;
	while ((((USART1->ISR) >> USART_ISR_RXNE_Pos) & 1U) == 0);	 //wait while first data comes from shift register
	arraypointer[pointer] = USART1->RDR;
	pointer++;
	while (((((USART1->ISR) >> USART_ISR_RXNE_Pos) & 1U) == 0) && (pointer < length)) {
		arraypointer[pointer] = USART1->RDR;
		pointer++;
	}
	while (((USART1->ISR >> USART_ISR_IDLE_Pos) & 1U) == 0); //if buffer is full, but transmission is still going, wait for it to end
	USART1->CR1 &= ~USART_CR1_RE; //Receiver disabled

yeah the code is still ugly, it's from an hour ago. Anyway, this gives me full array of the first symbol. Like if I send to MCU abcdefgh, I end up with aaaaaaaa in the buffer.

TDK
Guru

asdf

If you feel a post has answered your question, please click "Accept as Solution".

In my original question code (which is the only one I care about), the buffer has only one symbol. In the second snippet with strict length, the buffer is actually full, but filled with first symbols. It directly contradicts your statement about RXNE, because if loop never ran though, I would have had only 1 symbol in the buffer, or am I wrong?

P.S. don't be unnecessarily toxic. I think I get the logic of how this thing works, I must have gotten confused or overlooked something.

Now you're reading RDR even when RXNE=0. So maybe you get a full string, but mostly/all duplicated characters.

This should work, although I did not test it. May need to clear IDLE bit. Need to decide what to do if you get IDLE before the whole string is there.

void uart1_receiveArray(uint8_t *arraypointer, uint32_t length) {
	USART1->CR1 |= USART_CR1_RE; //USART Receiver enabled, line idle
	uint32_t pointer = 0;
	while (pointer < length) { //if line not idle and buffer array not full
		if (USART1->ISR & USART_ISR_RXNE) {
			arraypointer[pointer] = USART1->RDR;
			pointer++;
		}
	}
	while (!(USART1->ISR & USART_ISR_IDLE)); //if buffer is full, but transmission is still going, wait for it to end
	USART1->CR1 &= ~USART_CR1_RE; //Receiver disabled
}

If you feel a post has answered your question, please click "Accept as Solution".
ilyus
Senior II

Found solution! I re-wrote the whole thing using Receiver Timeout!

EDITED (bugfix)

void uart1_receiveArray(uint8_t *arraypointer, uint32_t length) {
	uint8_t *currentpointer = arraypointer;
	for (uint32_t k = 0; k < length; k++) { //fill the buffer with nulls so that it doesn't retain data from previous transmissions
		*(arraypointer + k) = '\0';
	}
	while (!(USART1->ISR & USART_ISR_RXNE)); //wait indefinitely for the beginning of the transmission
	USART1->ICR |= USART_ICR_RTOCF; //make sure receiver timeout flag is cleared
	while (!(USART1->ISR & USART_ISR_RTOF)) { //while not receiver timeout
		if (((USART1->ISR & USART_ISR_RXNE)) && (currentpointer < arraypointer + length)) { //if buffer is not full yet and there is new data
			*currentpointer = USART1->RDR;
			currentpointer++;
		}
	}
	USART1->ICR |= USART_ICR_RTOCF; //when receiver timed out and we're done, clear the flag
}

Tested with buffer of size 8. Works perfectly with transmissions of length 1, 3-7, 8 and 10 (retains only first 8 characters). Shorter timeout (0 bits) messes the entire thing up (makes sense).