i've used an Atmega328P µC to get a string over UART and convert it to an int.
I've tried to use atoi() function or sscanf() to convert it but they are taking to long to convert so that they are blocking the interrupt.
If I stop converting and just receiving over UART all symbols are transmitted but if I convert after receiving some characters of transmission are missed.
Is there any way to speed up conversion to stop blocking RX interrupt?
If you need to convert multicharacter numbers to integer you need to use buffer. There example of cycle buffer uses:
#define BUF_LEN 128
#define BUF_MSK (BUF_LEN-1)
uint8_t buf[BUF_LEN], b_in = 0, b_out = 0;
void usart_irq_handler(void)
{
buf[b_in&BUF_MSK] = USART_DATA_REG;
b_in++;
}
int main(void)
{
uint8_t tmp;
while (b_in!=b_out) {
tmp = buf[b_out&BUF_MSK];
b_out++;
// parse here
}
}
If you need to convert single character numbers you may not use buffer (but if USART frequency not much less than CPU frequency it's not recommended). For ASCII encoded received characters each number will have values 0x30..0x39. If received characters encoded with another charset you need refer to their tables.
uint8_t num = USART_DATA_REG - 0x30;
if (num >= 0 && num <= 9) {
// is number
}
EDIT 1 [due to new information from OP] For convert decimal number from string to integer I use this function:
uint32_t parse_num(uint8_t * ptr)
{
uint32_t res = (uint32_t)0x0;
uint8_t chr = 0x0, pchr = 0xA;
while (*ptr != (uint8_t)0x0) {
chr = (uint8_t)(*ptr - (uint8_t)0x30);
if (chr < 0xA) {
res *= (uint32_t) 0xA;
res += (uint32_t) chr;
} else {
if (pchr < 0xA) break;
}
pchr = chr;
ptr++;
}
return res;
}
It skip non-numbers chars and convert first founded number, but doesn't return final position of parse buffer. You can modify it as you need.
EDIT 2 [due to chat with OP] about good approach to processor time management:
Pictures below (from this answer) illustrate normal processor timing in program:
So, the smaller the interrupt handler time - the more likely success of the other handlers, and more time to perform main process.
By increasing of MCU clock is reduced run time code and increases idle time.
By decreasing of periferal clock is reduced frequency of interrupts by perifery and increases idle time.
In conclusion idle time must be used for power save.
Yes, the function runs in main loop and copies the chars out of buffer which is filled in the interrupt routine. After detecting the \n in string, it converts the string into values using sscanf or char by char. I've first used a very slow baud rate and then a very fast one. There are also two more interrupt routines (for timing of 100us and one for a pwm driver). I've tried to take out code of these routines to speed them up and it was successful. Now the interrupt routine gets every character of the uart transmission. Now I am implementing an algorithm to convert the strings to values without using sscanf because of performance issues. Using the algorithm of imbearr with -0x30 should work properly.
Related
I'm learning embedded c for microcontrollers using a PICKIT. When I send the voltage readings from an LDR via serial communication to RealTerm I end up with the voltages just continuing on the line (see pic) and not starting a new line every reading, how would I change it to look like this? Thanks in advance.
0.9V
0.89V
0.9V
RealTerm data
#pragma config FEXTOSC = HS // External Oscillator mode Selection bits (HS (crystal oscillator)above 8 MHz; PFM set to high power)
#pragma config RSTOSC = EXTOSC_4PLL// Power-up default value for COSC bits (EXTOSC with 4x PLL, with EXTOSC operating per FEXTOSC bits)
// CONFIG3L
#pragma config WDTE = OFF // WDT operating mode (WDT enabled regardless of sleep)
#include <xc.h>
#include <stdio.h>
#include "LCD.h"`
#include "serial.h"
#include ADC.h
define _XTAL_FREQ 64000000 //note intrinsic _delay function is 62.5ns at 64,000,000Hz
void main(void) {
LCD_Init();
initUSART4(); //Initialise EUSART4
ADC_init();
char LDR_arr[16];
unsigned int x;
unsigned int int_part; //initialise int part of voltage
unsigned int frac_part; // initialise fraction part of voltage
//char buf = getCharSerial4();
//Clear Screen
LCD_sendbyte(0b00000001, 0);
while (1)
{
x = ADC_getval();
//ADC2String(LDR_arr, x);
/* max voltage = 3.3V, max LDR value = 255
* 255/3.3 = 77.3 so use 77 for division
* int_part = LDR value/77
* frac_part = (LDRvalue * 100)/77 - int_part*100, giving first 2 DP as integer
*/
int_part = x/77;
frac_part = (x*100)/77 - int_part * 100;
// and format as a string using sprintf (see GitHub readme)
// %02d ensures that 2 numbers are always displayed frac_part
// e.g. frac_part = 5, get 0.05 after decimal instead of 0.5
// which would get if used %01d or 0.005 if used %03d
sprintf(LDR_arr,"%d.%02dV",int_part,frac_part);
sendStringSerial4(LDR_arr); // send voltage to RealTerm
__delay_ms(1000);
LCD_sendbyte(0b00000001, 0); //clear screen
__delay_ms(1.53);
}
}
void sendCharSerial4(char charToSend) {
while (!PIR4bits.TX4IF); // wait for flag to be set
TX4REG = charToSend; //transfer char to transmitter
void sendStringSerial4(char *string){
while(*string!=0){
sendCharSerial4(*string++);
}
}
Edit if I do
sprintf(LDR_arr,"%d.%02dV \n",int_part,frac_part);
or
sprintf(LDR_arr,"%d.%02dV '\n'",int_part,frac_part);
I end up with what the voltages going along a diagonal
RealTerm with '\n' inside
If you look at your ascii table 0x0D is a carriage return CR, 0x0A is a newline. The unix world where C came from or where they developed together tends to be limited to just the new line, before during and after serial terminals default to the separate characters of carriage return (start on a new column) and new line (move down to a new line) the combination is the preferred way to move down and left. \r is carriage return 0x0D and \n is newline 0x0A.
While you can change some dumb terminals to turn a newline into both it is more useful and portable to just do it right in the program. So as pointed out in a comment add \r\n to the end of the string to be printed (or the beginning, your choice).
You need to do one of perhaps three things:
Configure your terminal for "LF-only" line ends, so it implicitly inserted a CR before each LF - all terminal emulation software supports such a configuration,
explicitly output CR+LF line end: "\r\n" in your sprintf() calls.
Modify your low level serial I/O layer to insert a \r before every \n ("text mode").
Of those I'd recommend the first one, since for either of the other two you'd still have to configure the terminal software for CR+LF in any case. It is a coin toss what any particular terminal might be configured for by default - though largely determined by whether it was originally developed for Windows or POSIX perhaps.
In TeraTerm for example:
I am working on a library for controlling the M95128-W EEPROM from an STM32 device. I have the library writing and reading back data however the first byte of each page it not as expected and seems to be fixed at 0x04.
For example I write 128 bytes across two pages starting at 0x00 address with value 0x80. When read back I get:
byte[0] = 0x04;
byte[1] = 0x80;
byte[2] = 0x80;
byte[3] = 0x80;
.......
byte[64] = 0x04;
byte[65] = 0x80;
byte[66] = 0x80;
byte[67] = 0x80;
I have debugged the SPI with a logic analyzer and confirmed the correct bytes are being sent. When using the logic analyzer on the read command the mysterios 0x04 is transmitted from the EEPROM.
Here is my code:
void FLA::write(const void* data, unsigned int dataLength, uint16_t address)
{
int pagePos = 0;
int pageCount = (dataLength + 64 - 1) / 64;
int bytePos = 0;
int startAddress = address;
while (pagePos < pageCount)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2, GPIO_PIN_SET); // WP High
chipSelect();
_spi->transfer(INSTRUCTION_WREN);
chipUnselect();
uint8_t status = readRegister(INSTRUCTION_RDSR);
chipSelect();
_spi->transfer(INSTRUCTION_WRITE);
uint8_t xlow = address & 0xff;
uint8_t xhigh = (address >> 8);
_spi->transfer(xhigh); // part 1 address MSB
_spi->transfer(xlow); // part 2 address LSB
for (unsigned int i = 0; i < 64 && bytePos < dataLength; i++ )
{
uint8_t byte = ((uint8_t*)data)[bytePos];
_spi->transfer(byte);
printConsole("Wrote byte to ");
printConsoleInt(startAddress + bytePos);
printConsole("with value ");
printConsoleInt(byte);
printConsole("\n");
bytePos ++;
}
_spi->transfer(INSTRUCTION_WRDI);
chipUnselect();
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2, GPIO_PIN_RESET); //WP LOW
bool writeComplete = false;
while (writeComplete == false)
{
uint8_t status = readRegister(INSTRUCTION_RDSR);
if(status&1<<0)
{
printConsole("Waiting for write to complete....\n");
}
else
{
writeComplete = true;
printConsole("Write complete to page ");
printConsoleInt(pagePos);
printConsole("# address ");
printConsoleInt(bytePos);
printConsole("\n");
}
}
pagePos++;
address = address + 64;
}
printConsole("Finished writing all pages total bytes ");
printConsoleInt(bytePos);
printConsole("\n");
}
void FLA::read(char* returndata, unsigned int dataLength, uint16_t address)
{
chipSelect();
_spi->transfer(INSTRUCTION_READ);
uint8_t xlow = address & 0xff;
uint8_t xhigh = (address >> 8);
_spi->transfer(xhigh); // part 1 address
_spi->transfer(xlow); // part 2 address
for (unsigned int i = 0; i < dataLength; i++)
returndata[i] = _spi->transfer(0x00);
chipUnselect();
}
Any suggestion or help appreciated.
UPDATES:
I have tried writing sequentially 255 bytes increasing data to check for rollover. The results are as follows:
byte[0] = 4; // Incorrect Mystery Byte
byte[1] = 1;
byte[2] = 2;
byte[3] = 3;
.......
byte[63] = 63;
byte[64] = 4; // Incorrect Mystery Byte
byte[65] = 65;
byte[66] = 66;
.......
byte[127] = 127;
byte[128] = 4; // Incorrect Mystery Byte
byte[129} = 129;
Pattern continues. I have also tried writing just 8 bytes from address 0x00 and the same problem persists so I think we can rule out rollover.
I have tried removing the debug printConsole and it has had no effect.
Here is a SPI logic trace of the write command:
And a close up of the first byte that is not working correctly:
Code can be viewed on gitlab here:
https://gitlab.com/DanielBeyzade/stm32f107vc-home-control-master/blob/master/Src/flash.cpp
Init code of SPI can be seen here in MX_SPI_Init()
https://gitlab.com/DanielBeyzade/stm32f107vc-home-control-master/blob/master/Src/main.cpp
I have another device on the SPI bus (RFM69HW RF Module) which works as expected sending and receiving data.
The explanation was actually already given by Craig Estey in his answer. You do have a rollover. You write full page and then - without cycling the CS pin - you send INSTRUCTION_WRDI command. Guess what's the binary code of this command? If you guessed that it's 4, then you're absolutely right.
Check your code here:
chipSelect();
_spi->transfer(INSTRUCTION_WRITE);
uint8_t xlow = address & 0xff;
uint8_t xhigh = (address >> 8);
_spi->transfer(xhigh); // part 1 address MSB
_spi->transfer(xlow); // part 2 address LSB
for (unsigned int i = 0; i < 64 && bytePos < dataLength; i++ )
{
uint8_t byte = ((uint8_t*)data)[bytePos];
_spi->transfer(byte);
// ...
bytePos ++;
}
_spi->transfer(INSTRUCTION_WRDI); // <-------------- ROLLOEVER!
chipUnselect();
With these devices, each command MUST start with cycling CS. After CS goes low, the first byte is interpreted as command. All remaining bytes - until CS is cycled again - are interpreted as data. So you cannot send multiple commands in a single "block" with CS being constantly pulled low.
Another thing is that you don't need WRDI command at all - after the write instruction is terminated (by CS going high), the WEL bit is automatically reset. See page 18 of the datasheet:
The Write Enable Latch (WEL) bit, in fact, becomes reset by any of the
following events:
• Power-up
• WRDI instruction execution
• WRSR instruction completion
• WRITE instruction completion.
Caveat: I don't have a definitive solution, just some observations and suggestions [that would be too large for a comment].
From 6.6: Each time a new data byte is shifted in, the least significant bits of the internal address counter are incremented. If more bytes are sent than will fit up to the end of the page, a condition known as “roll-over” occurs. In case of roll-over, the bytes exceeding the page size are overwritten from location 0 of the same page.
So, in your write loop code, you do: for (i = 0; i < 64; i++). This is incorrect in the general case if the LSB of address (xlow) is non-zero. You'd need to do something like: for (i = xlow % 64; i < 64; i++)
In other words, you might be getting the page boundary rollover. But, you mentioned that you're using address 0x0000, so it should work, even with the code as it exists.
I might remove the print statements from the loop as they could have an effect on the serialization timing.
I might try this with an incrementing data pattern: (e.g.) 0x01,0x02,0x03,... That way, you could see which byte is rolling over [if any].
Also, try writing a single page from address zero, and write less than the full page size (i.e. less that 64 bytes) to guarantee that you're not getting rollover.
Also, from figure 13 [the timing diagram for WRITE], it looks like once you assert chip select, the ROM wants a continuous bit stream clocked precisely, so you may have a race condition where you're not providing the data at precisely the clock edge(s) needed. You may want to use the logic analyzer to verify that the data appears exactly in sync with clock edge as required (i.e. at clock rising edge)
As you've probably already noticed, offset 0 and offset 64 are getting the 0x04. So, this adds to the notion of rollover.
Or, it could be that the first data byte of each page is being written "late" and the 0x04 is a result of that.
I don't know if your output port has a SILO so you can send data as in a traditional serial I/O port or do you have to maintain precise bit-for-bit timing (which I presume the _spi->transfer would do)
Another thing to try is to write a shorter pattern (e.g. 10 bytes) starting at a non-zero address (e.g. xhigh = 0; xlow = 4) and the incrementing pattern and see how things change.
UPDATE:
From your update, it appears to be the first byte of each page [obviously].
From the exploded view of the timing, I notice SCLK is not strictly uniform. The pulse width is slightly erratic. Since the write data is sampled on the clock rising edge, this shouldn't matter. But, I wonder where this comes from. That is, is SCLK asserted/deasserted by the software (i.e. transfer) and SCLK is connected to another GPIO pin? I'd be interested in seeing the source for the transfer function [or a disassembly].
I've just looked up SPI here: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus and it answers my own question.
From that, here is a sample transfer function:
/*
* Simultaneously transmit and receive a byte on the SPI.
*
* Polarity and phase are assumed to be both 0, i.e.:
* - input data is captured on rising edge of SCLK.
* - output data is propagated on falling edge of SCLK.
*
* Returns the received byte.
*/
uint8_t SPI_transfer_byte(uint8_t byte_out)
{
uint8_t byte_in = 0;
uint8_t bit;
for (bit = 0x80; bit; bit >>= 1) {
/* Shift-out a bit to the MOSI line */
write_MOSI((byte_out & bit) ? HIGH : LOW);
/* Delay for at least the peer's setup time */
delay(SPI_SCLK_LOW_TIME);
/* Pull the clock line high */
write_SCLK(HIGH);
/* Shift-in a bit from the MISO line */
if (read_MISO() == HIGH)
byte_in |= bit;
/* Delay for at least the peer's hold time */
delay(SPI_SCLK_HIGH_TIME);
/* Pull the clock line low */
write_SCLK(LOW);
}
return byte_in;
}
So, the delay times need be at least the ones the ROM needs. Hopefully, you can verify that is the case.
But, I also notice that on the problem byte, the first bit of the data appears to lag its rising clock edge. That is, I would want the data line to be stabilized before clock rising edge.
But, that assumes CPOL=0,CPHA=1. Your ROM can be programmed for that mode or CPOL=0,CPHA=0, which is the mode used by the sample code above.
This is what I see from the timing diagram. It implies that the transfer function does CPOL=0,CPHA=0:
SCLK
__
| |
___| |___
DATA
___
/ \
/ \
This is what I originally expected (CPOL=0,CPHA=1) based on something earlier in the ROM document:
SCLK
__
| |
___| |___
DATA
___
/ \
/ \
The ROM can be configured to use either CPOL=0,CPHA=0 or CPOL=1,CPHA=1. So, you may need to configure these values to match the transfer function (or vice-versa) And, verify that the transfer function's delay times are adequate for your ROM. The SDK may do all this for you, but, since you're having trouble, double checking this may be worthwhile (e.g. See Table 18 et. al. in the ROM document).
However, since the ROM seems to respond well for most byte locations, the timing may already be adequate.
One thing you might also try. Since it's the first byte that is the problem, and here I mean first byte after the LSB address byte, the memory might need some additional [and undocumented] setup time.
So, after the transfer(xlow), add a small spin loop after that before entering the data transfer loop, to give the ROM time to set up for the write burst [or read burst].
This could be confirmed by starting xlow at a non-zero value (e.g. 3) and shortening the transfer. If the problem byte tracks xlow, that's one way to verify that the setup time may be required. You'd need to use a different data value for each test to be sure you're not just reading back a stale value from a prior test.
I am using ATMEGA128 microcontroller and AVR studio for my project. I am using a Receive interrupt ISR_USART0 for receiving 8 bytes of data as a data packet and this interrupt is called after it finishes receiving data the data is used to stimulate some actuators. Here is the Interrupt routine.
Now i want to add a timeout of 10 ms in this routine such that as soon as the first byte is received it starts counting the timeout and this lasts for 10 ms and then the software skips waiting for the bytes and returns to the main loop.
Being a newbie i found very less data on interrupt on implementing such a timeout inside the interrupt routine...can anyone suggest the best way to implement it.?
ISR(USART0_RX_vect)
{
uint8_t u8temp;
u8temp=UDR0;
data_count++;
//UDR0=u8temp;
//check if period char or end of buffer
if (BufferWrite(&buf, u8temp)==1){
Buffer_OverFlow_ERROR=1; TRANSMISSION_ERROR [6]=Buffer_OverFlow_ERROR;
MT_ERROR_FLAGS_INIT();BufferInit(&buf);data_count=0;REC_ERROR_FLAG=0;}
if (u8temp==0x58){
BufferSort(&buf);BufferInit(&buf);
REC_ERROR_FLAG=0;data_count=0;//PORTA|=(1<<PORTA3);
}
/*else if{
REC_ERROR_FLAG=0;BufferInit(&buf);*/
}
I would use such a function to implement a receive of 8 bytes from the UART with a timeout of n ms.
char buf[8], c = 0;
unsigned int t = 65535, th = 65535;
do {
if (UCSRA & (1 << RXC))
buf[c++] = UDR;
if (!th--) t--;
} while ((c < 8) && t);
I use two variables t and th here, because decrementing a single integer probably isn't enough to get a delay of 10ms.
Now you just need to adjust t to a value such that the loop completes in about 10ms (this depends mainly on your clock). You could probably develop some formula utilizing the macro F_CPU, i.e. t2 = F_CPU/1000; or similar.
I need advice on proper way of handling UART communication. I feel like I've done handling sending serial commands over UART well but I don't know if the way I'm parsing the response or receiving serial data is the best way to do it. Any tips are appreciated but I just want to know if there's a better more and elegant way to parse UART RX.
This is for an MSP430 uC by the way...
First I have these declared in the header file:
const unsigned char *UART_TX_Buffer;
unsigned char UART_TX_Index;
unsigned char UART_TX_Length;
unsigned char UART_TX_Pkt_Complete;
unsigned char UART_RX_Buffer[25];
unsigned char UART_RX_Pkt_Complete;
unsigned char UART_RX_Index;
Here is the function that is called once the flag UART_RX_Pkt_Complete is set in the ISR:
void Receive_Resp()
{
switch (UART_RX_Buffer[UART_RX_Index - 3])
{
case 0x4B:
break;
case 0x56:
P1OUT &= ~(tos_sel0 + tos_sel1);
break;
case 0x43:
P1OUT |= tos_sel0;
P1OUT &= ~tos_sel1;
break;
case 0x34:
P1OUT |= tos_sel1;
P1OUT &= ~tos_sel0;
break;
case 0x33:
P1OUT |= tos_sel0 + tos_sel1;
break;
default:
break;
}
UART_RX_Pkt_Complete = 0;
UART_RX_Index = 0;
}
For reference here's the RX ISR:
#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCIA0RX_ISR(void)
{
UART_RX_Buffer[UART_RX_Index++] = UCA0RXBUF;
if (UART_RX_Buffer[UART_RX_Index - 1] == 0x0A)
{
UART_RX_Pkt_Complete = 1;
_BIC_SR_IRQ(LPM3_bits);
}
IFG2 &= ~UCA0RXIFG;
}
Also here's the TX ISR and send UART command routine:
if (UART_TX_Index < UART_TX_Length) // Check if there are more bytes to be sent
{
UCA0TXBUF = UART_TX_Buffer[UART_TX_Index++];
}
else // Last byte has been sent
{
UART_TX_Pkt_Complete = 1; // Set flag to show last byte was sent
_BIC_SR_IRQ(LPM3_bits);
}
IFG2 &= ~UCA0TXIFG;
void Send_CMD (const unsigned char *Data, const unsigned char Length)
{
UART_TX_Buffer = Data; // Move into global variables
UART_TX_Length = Length;
UART_TX_Pkt_Complete = 0; // Starting values
UART_RX_Pkt_Complete = 0;
UART_TX_Index = 0;
UCA0TXBUF = UART_TX_Buffer[UART_TX_Index++];
while(!UART_TX_Pkt_Complete)
{
Delay(5,'u');
}
while(!UART_RX_Pkt_Complete)
{
Delay(5,'u');
}
}
If this works and meets your system's requirements then it's fine. But there are several ways it could be improved.
Receive_Resp() and USCIA0RX_ISR() are tightly coupled, which is undesirable. They both manipulate UART_RX_Index for the other (USCIA0RX_ISR() increments it and Receive_Resp() clears it) and they both rely on the other for part of the framing of each message (USCIA0RX_ISR() finds the end of the frame while Receive_Resp() interprets and resets for the next frame). It would be better if these routines were decoupled.
The character buffer should be a circular buffer with a head pointer (where characters get added) and a tail pointer (where characters get removed). The ISR should only add chars to the circular buffer and advance the head pointer. The ISR should also handle wrapping of the head pointer back to the beginning of the circular buffer. And the ISR should protect from overruns by making sure the head pointer doesn't pass the tail pointer.
The Receive routine should be responsible for framing the message. This includes pulling chars from the tail pointer and identifying the beginning and end of the message. The Receive routine increments and wraps the tail pointer. Typically the Receive routine is implemented as a state machine with states for identifying the start, body, and end of a frame.
For even less coupling you might have a separate function that interprets the content of the message (i.e., separate the framing from the interpretation of the message).
Your ReceiveResp() routine doesn't handle any errors such as a dropped character. It assumes that all three characters were received correctly. Maybe that's fine for your application and requirements. But typically there should be some error checking performed here. At the very least you should ensure that UART_RX_Index >= 3 before you subtract 3 from it. In other words, make sure the message length is sane. A more robust serial protocol would have a checksum or CRC in each frame to ensure the frame was received correctly but that is probably overkill for your application.
Your Transmit side could be improved with some of the same advice. Typically there is a circular buffer for transmit chars. The Transmit routine adds chars to the buffer and manages the head pointer. The TX ISR copies chars from the buffer to the UART and manages the tail pointer.
Do the calls to Delay in Send_CMD() mean that your application is totally stalled while it's waiting to finish the transmission? Again, maybe that's OK for your application but typically that is undesirable. Typically you want the application to continue to function even while it's waiting for the UART to be ready to transmit. By using a circular transmit buffer, it would be possible for multiple messages to queue up in the transmit buffer and you wouldn't have to wait for the previous message to finish before queuing up another. But then you should add protection for a buffer overrun and this may complicate things unnecessarily for you. Which brings me back to my first point, if what you have works and meets your requirements then it is fine.
Personally, I'd write a completely separate UART module that contained methods for write and read operations. Under the hood I'd create two circular buffers (of whatever the appropriate size may be) and use those for storing bytes as data comes in or goes out. This would allow an interrupt driven solution with buffers.
For instance, my interrupt for receive would do something like:
#pragma vector=USCIAB0RX_VECTOR
__interrupt void UartRxIsr()
{
...
// Add new byte to my receive buffer.
...
}
Then I can call my Uart.read() method to read out that byte. The read method might look something like the following:
char read()
{
if (Uart.rxBuffer.length > 0)
{
return Uart.rxBuffer.buffer[Uart.rxBuffer.write++];
}
}
This is assuming that you've implemented circular buffers using pointers.
I have a solution lying around somewhere. I'll try to find it and post it.
I am looking for ideas for a receive buffer for a small application dealing with 15 byte packets at 921.6Kbaud over rs485. I am thinking of using a circular buffer as the interface between the UART ISR and main. As it is a microprocessor I was wanting to put
while (uartindex!=localindex) { do stuff }
in the
while (;;) {do forever}
part of main but I have been told this is not acceptable.
How do people deal with their uarts under similar circumstances?
ISR should fill a FIFO. Main should consume it.
Bellow a very simple fifo algorithm:
#define RINGFIFO_SIZE (1024) /* serial buffer in bytes (power 2) */
#define RINGFIFO_MASK (RINGFIFO_SIZE-1ul) /* buffer size mask */
/* Buffer read / write macros */
#define RINGFIFO_RESET(ringFifo) {ringFifo.rdIdx = ringFifo.wrIdx = 0;}
#define RINGFIFO_WR(ringFifo, dataIn) {ringFifo.data[RINGFIFO_MASK & ringFifo.wrIdx++] = (dataIn);}
#define RINGFIFO_RD(ringFifo, dataOut){ringFifo.rdIdx++; dataOut = ringFifo.data[RINGFIFO_MASK & (ringFifo.rdIdx-1)];}
#define RINGFIFO_EMPTY(ringFifo) (ringFifo.rdIdx == ringFifo.wrIdx)
#define RINGFIFO_FULL(ringFifo) ((RINGFIFO_MASK & ringFifo.rdIdx) == (RINGFIFO_MASK & (ringFifo.wrIdx+1)))
#define RINGFIFO_COUNT(ringFifo) (RINGFIFO_MASK & (ringFifo.wrIdx - ringFifo.rdIdx))
/* buffer type */
typedef struct{
uint32_t size;
uint32_t wrIdx;
uint32_t rdIdx;
uint8_t data[RINGFIFO_SIZE];
} RingFifo_t;
RingFifo_t gUartFifo;
(Care must be taken with this FIFO algorithm, size MUST be power of 2)
The ISR should behave like this:
void ISR_Handler()
{
uint8_t c;
while(UART_NotEmpty()) {
c = UART_GetByte();
RINGFIFO_WR(gUartFifo, c);
}
}
And the Main:
while(1)
{
if (!RINGFIFO_EMPTY(gUartFifo)) {
/* consume fifo using RINGFIFO_RD */
}
}
This algorithm reads the FIFO directly from the main loop, you should use a intermediate layer that checks if there is a full packet in the buffer, and deals with it, in such a manner that main would be like this:
uint8_t ptrToPacket;
uint32_t packetSize;
while(1)
{
if (!Uart_HasValidPacket()) {
Uart_GetPacket(&ptrToPacket, &packetSize)
/* Process packet using ptrToPacket and packetSize */
}
}
The approach you suggest would probably be workable if the uartindex is never written in the main loop (except to initialize it while interrupts are disabled), and localindex is never touched by the interrupt routine.
I would suggest that you make your buffer size be a power of 2, use unsigned integers for the two indices, and allow them to count freely over their full 32-bit size; use bit masking when indexing your buffer in both the "stuff" and "fetch" routines. If you do that, then
(unsigned)(uartindex-localindex)
should indicate how many characters are in the buffer, even when it's completely full, without requiring special-case behavior in the buffer-full case and without limiting an N-byte buffer to holding N-1 items.
Note that while the typecast in the aforementioned expression isn't strictly necessary, I would recommend including it since it makes obvious that the wrapping behavior when subtracting unsigned quantities is deliberate and expected.