2022-01-18 06:26 AM
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:
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?
Solved! Go to Solution.
2022-01-19 04:46 AM
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).
2022-01-18 07:18 AM
asdf
2022-01-18 07:34 AM
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.
2022-01-18 07:49 AM
asdf
2022-01-18 07:54 AM
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.
2022-01-18 08:12 AM
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
}
2022-01-19 04:46 AM
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).