niedziela, 3 kwietnia 2022

LPC1769 - Bufor kołowy

W tym poście chciałbym opisać prostą implementację bufora kołowego do komunikacji po UART.



Program:


W buforze musimy zdefiniować wskaźnik na miejsce w pamięci w której zostanie przechowywana, w tym przypadku tablica z zdefiniowaną komendą oraz wskaźnik na koniec i początek danych umieszczonych w buforze kołowym. Dodatkowo można wprowadzić informacje o ilości danych, flagi błędów itp itd.

Bufor będzie przechowywał ramki danych w zdefiniowanej wcześniej formie. Czyli bajt startu, bajt długości, bajt komendy, bajty danych oraz dwa bajty CRC. 

Struktura przechowująca dane wygląda następująco:

  1. #define COMMAND_BUFFER_DATA_LENGTH  50
  2. #define COMMAND_BUFFER_CRC_LENGTH   2
  3. #define CIRC_BUF_SIZE 15
  4.  
  5. typedef struct RecCommand_TypedefStruct{
  6.     uint8_t CommandStartCode;
  7.     uint8_t CommandLength;
  8.     uint8_t CommandCode;
  9.     uint8_t CommandData[COMMAND_BUFFER_DATA_LENGTH];
  10.     uint8_t CommandCrc[COMMAND_BUFFER_CRC_LENGTH];
  11. }UART_Rec_Command_Typedef;
  12.  
  13. typedef struct {
  14.     volatile UART_Rec_Command_Typedef *Col_Rec_Comm_TypDef;
  15.     uint8_t head;
  16.     uint8_t tail;
  17. } circ_buffer_t;

Definicja danych:

  1. volatile UART_Rec_Command_Typedef Col_Rec_Comm_TypDef[15];
  2. volatile circ_buffer_t Rx_CircBuffer = { Col_Rec_Comm_TypDef, 0, 0 };

Inicjalizacja UART:

  1. void UART_MB_Initialize(void)
  2. {
  3.     PINSEL_CFG_Type PinCfg;
  4.     UART_CFG_Type UARTConfigStruct;
  5.  
  6.     PinCfg.Funcnum = 1;
  7.     PinCfg.OpenDrain = 0;
  8.     PinCfg.Pinmode = 0;
  9.     PinCfg.Pinnum = UARTX_MB_PIN_TX;
  10.     PinCfg.Portnum = UARTX_MB_PORT_NUM_TX;
  11.     PINSEL_ConfigPin(&PinCfg);
  12.     PinCfg.Pinnum = UARTX_MB_PIN_RX;
  13.     PinCfg.Portnum = UARTX_MB_PORT_NUM_RX;
  14.     PINSEL_ConfigPin(&PinCfg);
  15.  
  16.     UARTConfigStruct.Baud_rate = UARTX_MB_BAUDRATE;
  17.     UARTConfigStruct.Databits = UARTX_MB_DATABITS;
  18.     UARTConfigStruct.Parity = UARTX_MB_PARITY;
  19.     UARTConfigStruct.Stopbits = UARTX_MB_STOPBITS;
  20.     UART_Init((LPC_UART_TypeDef *)UARTX_MB_PORT, &UARTConfigStruct);
  21.  
  22.     //Rozpocznij transmisję
  23.     UART_TxCmd((LPC_UART_TypeDef *)UARTX_MB_PORT, ENABLE);
  24.  
  25.     //Uruchom przerwania od UARTA
  26.     UART_IntConfig((LPC_UART_TypeDef *)UARTX_MB_PORT, UART_INTCFG_RBR, ENABLE);
  27.  
  28.     NVIC_SetPriority(UARTX_MB_INTERRUPT, ((1)|0));
  29.     NVIC_EnableIRQ(UARTX_MB_INTERRUPT);
  30. }

Dane odbierane są z przerwania:

  1. static uint8_t UART_MB_RcvData(uint8_t * rxbuf, uint32_t buflen)
  2. {
  3.     uint32_t bToRecv;
  4.     uint32_t bRecv;
  5.     volatile uint32_t timeOut;
  6.     uint8_t *pChar = rxbuf;
  7.  
  8.     bToRecv = buflen;
  9.     bRecv = 0;
  10.  
  11.     while (bToRecv)
  12.     {
  13.         timeOut = UART_BLOCKING_TIMEOUT;
  14.         while (!(LPC_UART0->LSR & UART_LSR_RDR)) {
  15.             if (timeOut == 0) { break; }
  16.             timeOut--;
  17.         }
  18.  
  19.         if(timeOut == 0) {
  20.             break;
  21.         }
  22.  
  23.         uint8_t rcvByte = UART_ReceiveData((LPC_UART_TypeDef *)LPC_UART0);
  24.         (*pChar++) = rcvByte;
  25.  
  26.         if(bRecv == 1) {
  27.             recFrameLength = rcvByte;
  28.         }
  29.  
  30.         bToRecv--;
  31.         bRecv++;
  32.  
  33.         if(bRecv == recFrameLength && bRecv > 4) {
  34.             break;
  35.         }
  36.     }
  37.     return bRecv;
  38. }

Dane odbierane są pojedynczo w przerwaniu do osiągnięcia zdefiniowanej długości ramki danych lub przekroczenia czasu potrzebnego na odebranie danych. Można to także wykonać poprzez odbieranie pojedynczych danych w przerwaniu i składanie całej ramki w pętli, co skróci czas obsługi przerwania w mikrokontrolerze. Wszystko zależy od potrzeb i zakresu działań projektu.

  1. void UART0_IRQHandler(void)
  2. {
  3.     uint32_t intsrc = 0;
  4.     static uint32_t counterDataInUart;
  5.  
  6.     intsrc = LPC_UART0->IIR;
  7.  
  8.     uint32_t tmp = intsrc & UART_IIR_INTID_MASK;
  9.     if (tmp == UART_IIR_INTID_RLS) {
  10.         uint8_t rar = LPC_UART0->LSR;
  11.         rar = rar;
  12.     }
  13.  
  14.     intsrc &= UART_IIR_INTID_MASK;
  15.     if(intsrc == UART_IIR_INTID_RDA) {
  16.         memset(uartMbRecBuffer, 0, sizeof(uartMbRecBuffer)/sizeof(uartMbRecBuffer[0]));
  17.         receiveDataLength = UART_MB_RcvData(uartMbRecBuffer, sizeof(uartMbRecBuffer)/sizeof(uartMbRecBuffer[0]));
  18.  
  19.         if(receiveDataLength > 0) {
  20.             if(uartMbRecBuffer[0] != 0x01) {
  21.                 flag_uartMbDataReceived = false;
  22.             }
  23.             else {
  24.                 startPuttingDataToBuffer = 1;
  25.                 circ_buffer_put_buff(&Rx_CircBuffer, &uartMbRecBuffer[0]);
  26.                 startPuttingDataToBuffer = 0;
  27.                 flag_uartMbDataReceived = true;
  28.             }
  29.         }
  30.     }
  31. }

Dodawanie danych do bufora kołowego jest blokowane przez flagę w celu nie nałożenia się ściągania i dodawania danych w celu zminimalizowania możliwych błędów podczas jednoczesnej obsługi tych zadań. 

Dane odbierane są w przerwaniu. Dodawane są one do globalnego bufora. 

Bufor kołowy, dodawanie i ściągnie danych z bufora:

  1. uint8_t CircBuff_GetData(volatile circ_buffer_t *q, UART_Rec_Command_Typedef *data)
  2. {
  3.     if (CheckPositionInBuffer(q->head, q->tail) == 1) { return 1; }
  4.  
  5.     q->tail++;
  6.     if (CheckIfLastElement(q->tail) == 1) { q->tail = 0; }
  7.  
  8.     data->CommandStartCode = Col_Rec_Comm_TypDef[q->tail].CommandStartCode;
  9.     data->CommandLength = Col_Rec_Comm_TypDef[q->tail].CommandLength;
  10.     data->CommandCode = Col_Rec_Comm_TypDef[q->tail].CommandCode;
  11.  
  12.     for (uint8_t i = 0; i < (data->CommandLength - 5); i++)
  13.     {
  14.         data->CommandData[i] = q->Col_Rec_Comm_TypDef[q->tail].CommandData[i];
  15.     }
  16.  
  17.     data->CommandCrc[0] = q->Col_Rec_Comm_TypDef[q->tail].CommandCrc[0];
  18.     data->CommandCrc[1] = q->Col_Rec_Comm_TypDef[q->tail].CommandCrc[1];
  19.  
  20.     return 0;
  21. }
  22.  
  23. uint8_t CircBuff_PutData(volatile circ_buffer_t *q, volatile uint8_t* data)
  24. {
  25.     uint8_t head_temp = q->head + 1;
  26.  
  27.     if (CheckIfBufLastElement(head_temp) == 1) { head_temp = 0; }
  28.     if (CheckPositionInBuffer(head_temp, q->tail) == 1) { return 1; }
  29.  
  30.     q->Col_Rec_Comm_TypDef[head_temp].CommandStartCode = data[0];
  31.     q->Col_Rec_Comm_TypDef[head_temp].CommandLength = data[1];
  32.     q->Col_Rec_Comm_TypDef[head_temp].CommandCode = data[2];
  33.  
  34.     for (uint8_t i = 0; i < (data[1] - 5); i++)
  35.     {
  36.         q->Col_Rec_Comm_TypDef[head_temp].CommandData[i] = data[i + 3];
  37.     }
  38.  
  39.     q->Col_Rec_Comm_TypDef[head_temp].CommandCrc[0] = data[data[1] - 2];    //Przedostatni
  40.     q->Col_Rec_Comm_TypDef[head_temp].CommandCrc[1] = data[data[1] - 1];        //Ostatni
  41.  
  42.     q->head = head_temp;            // Zapisujemy nowy indeks head
  43.  
  44.     return 0;   // wszystko przebiegło pozytywnie, więc zwracamy 0
  45. }
  46.  
  47. static uint8_t CheckPositionInBuffer(uint8_t head, uint8_t tail) {
  48.     if (head == tail) { return 1; }
  49.     return 0;
  50. }
  51.  
  52. static uint8_t CheckIfBufLastElement(uint8_t head) {
  53.     if (head == CIRC_BUF_SIZE) { return 1; }
  54.     return 0;
  55. }
  56.  
  57. static uint8_t CheckIfLastElement(uint8_t tail) {
  58.     if (tail == CIRC_BUF_SIZE) { return 1; }
  59.     return 0;
  60. }

Obsługa odebranych danych następuje w głównej pętli programu:

  1. while(1) {
  2.     if(circ_buffer_get_buff(&Colibri_Rx_CircBuffer, &uartMbCopyBuffer[0]) == 0)
  3.     {
  4.        UART_MB_DecodeReceiveMsg(uartMbCopyBuffer);
  5.     }
  6.     //...reszta programu
  7. }

Testy jednostkowe:


Poniżej wykaz prostych testów pozwalających na dodawanie i usuwanie ramek danych z bufora kołowego:

  1. TEST(CircularBuffer_Test, Put_GetDataFromBuffer) {
  2.     volatile uint8_t example_frame[9] = { 0x01, 0x09, 0x23, 0x56, 0x22, 0x89, 0x99, 0x48, 0x92 };
  3.  
  4.     volatile circ_buffer_t Rx_CircBuffer_test = { Col_Rec_Comm_TypDef, 0, 0 };
  5.  
  6.     UART_Rec_Command_Typedef readedData;
  7.     int8_t responseCode = -1;
  8.  
  9.     RecCommandTypedefSetDefaultValues();
  10.  
  11.     responseCode = CircBuff_PutData(&Rx_CircBuffer_test, &example_frame[0]);
  12.  
  13.     EXPECT_EQ(responseCode, 0);
  14.     EXPECT_EQ(Rx_CircBuffer_test.head, 1);
  15.     EXPECT_EQ(Rx_CircBuffer_test.tail, 0);
  16.  
  17.     responseCode = CircBuff_GetData(&Rx_CircBuffer_test, &readedData);
  18.  
  19.     EXPECT_EQ(responseCode, 0);
  20.     EXPECT_EQ(Rx_CircBuffer_test.head, 1);
  21.     EXPECT_EQ(Rx_CircBuffer_test.tail, 1);
  22.  
  23.     EXPECT_EQ(readedData.CommandStartCode, example_frame[0]);
  24.     EXPECT_EQ(readedData.CommandLength, example_frame[1]);
  25.     EXPECT_EQ(readedData.CommandCode, example_frame[2]);
  26.  
  27.     EXPECT_EQ(readedData.CommandData[0], example_frame[3]);
  28.     EXPECT_EQ(readedData.CommandData[1], example_frame[4]);
  29.     EXPECT_EQ(readedData.CommandData[2], example_frame[5]);
  30.     EXPECT_EQ(readedData.CommandData[3], example_frame[6]);
  31.  
  32.     EXPECT_EQ(readedData.CommandCrc[0], example_frame[7]);
  33.     EXPECT_EQ(readedData.CommandCrc[1], example_frame[8]);
  34. }
  35.  
  36. TEST(CircularBuffer_Test, PutGet_All_DataToBuffer) {
  37.     volatile uint8_t example_frame_1[9] = { 0x01, 0x09, 0x23, 0x56, 0x22, 0x89, 0x99, 0x48, 0x92 };
  38.     volatile circ_buffer_t Rx_CircBuffer_test = { Col_Rec_Comm_TypDef, 0, 0 };
  39.     UART_Rec_Command_Typedef readedData;
  40.  
  41.     int8_t responseCode = -1;
  42.  
  43.     RecCommandTypedefSetDefaultValues();
  44.  
  45.     for (uint8_t loop = 0; loop < (CIRC_BUF_SIZE - 1); loop++)
  46.     {
  47.         responseCode = CircBuff_PutData(&Rx_CircBuffer_test, &example_frame_1[0]);
  48.  
  49.         example_frame_1[3]++;
  50.         example_frame_1[4]++;
  51.         example_frame_1[5]++;
  52.         EXPECT_EQ(responseCode, 0);
  53.         EXPECT_EQ(Rx_CircBuffer_test.tail, 0);
  54.         EXPECT_EQ(Rx_CircBuffer_test.head, loop + 1);
  55.     }
  56.  
  57.     responseCode = CircBuff_PutData(&Rx_CircBuffer_test, &example_frame_1[0]);
  58.     EXPECT_EQ(responseCode, 1);
  59.     EXPECT_EQ(Rx_CircBuffer_test.tail, 0);
  60.     EXPECT_EQ(Rx_CircBuffer_test.head, CIRC_BUF_SIZE - 1);
  61.  
  62.     responseCode = CircBuff_GetData(&Rx_CircBuffer_test, &readedData);
  63.     EXPECT_EQ(responseCode, 0x00);
  64.     EXPECT_EQ(Rx_CircBuffer_test.tail, 1);
  65.     EXPECT_EQ(Rx_CircBuffer_test.head, CIRC_BUF_SIZE - 1);
  66.  
  67.     responseCode = CircBuff_GetData(&Rx_CircBuffer_test, &readedData);
  68.     EXPECT_EQ(responseCode, 0x00);
  69.     EXPECT_EQ(Rx_CircBuffer_test.tail, 2);
  70.     EXPECT_EQ(Rx_CircBuffer_test.head, CIRC_BUF_SIZE - 1);
  71.  
  72.     responseCode = CircBuff_PutData(&Rx_CircBuffer_test, &example_frame_1[0]);
  73.     EXPECT_EQ(responseCode, 0x00);
  74.     EXPECT_EQ(Rx_CircBuffer_test.tail, 2);
  75.     EXPECT_EQ(Rx_CircBuffer_test.head, 0);
  76.  
  77.     responseCode = CircBuff_PutData(&Rx_CircBuffer_test, &example_frame_1[0]);
  78.     EXPECT_EQ(responseCode, 0x00);
  79.     EXPECT_EQ(Rx_CircBuffer_test.tail, 2);
  80.     EXPECT_EQ(Rx_CircBuffer_test.head, 1);
  81. }

Projekt jest do pobrania z dysku Google pod tym linkiem.