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:
- #define COMMAND_BUFFER_DATA_LENGTH 50
- #define COMMAND_BUFFER_CRC_LENGTH 2
- #define CIRC_BUF_SIZE 15
- typedef struct RecCommand_TypedefStruct{
- uint8_t CommandStartCode;
- uint8_t CommandLength;
- uint8_t CommandCode;
- uint8_t CommandData[COMMAND_BUFFER_DATA_LENGTH];
- uint8_t CommandCrc[COMMAND_BUFFER_CRC_LENGTH];
- }UART_Rec_Command_Typedef;
- typedef struct {
- volatile UART_Rec_Command_Typedef *Col_Rec_Comm_TypDef;
- uint8_t head;
- uint8_t tail;
- } circ_buffer_t;
Definicja danych:
- volatile UART_Rec_Command_Typedef Col_Rec_Comm_TypDef[15];
- volatile circ_buffer_t Rx_CircBuffer = { Col_Rec_Comm_TypDef, 0, 0 };
Inicjalizacja UART:
- void UART_MB_Initialize(void)
- {
- PINSEL_CFG_Type PinCfg;
- UART_CFG_Type UARTConfigStruct;
- PinCfg.Funcnum = 1;
- PinCfg.OpenDrain = 0;
- PinCfg.Pinmode = 0;
- PinCfg.Pinnum = UARTX_MB_PIN_TX;
- PinCfg.Portnum = UARTX_MB_PORT_NUM_TX;
- PINSEL_ConfigPin(&PinCfg);
- PinCfg.Pinnum = UARTX_MB_PIN_RX;
- PinCfg.Portnum = UARTX_MB_PORT_NUM_RX;
- PINSEL_ConfigPin(&PinCfg);
- UARTConfigStruct.Baud_rate = UARTX_MB_BAUDRATE;
- UARTConfigStruct.Databits = UARTX_MB_DATABITS;
- UARTConfigStruct.Parity = UARTX_MB_PARITY;
- UARTConfigStruct.Stopbits = UARTX_MB_STOPBITS;
- UART_Init((LPC_UART_TypeDef *)UARTX_MB_PORT, &UARTConfigStruct);
- //Rozpocznij transmisję
- UART_TxCmd((LPC_UART_TypeDef *)UARTX_MB_PORT, ENABLE);
- //Uruchom przerwania od UARTA
- UART_IntConfig((LPC_UART_TypeDef *)UARTX_MB_PORT, UART_INTCFG_RBR, ENABLE);
- NVIC_SetPriority(UARTX_MB_INTERRUPT, ((1)|0));
- NVIC_EnableIRQ(UARTX_MB_INTERRUPT);
- }
Dane odbierane są z przerwania:
- static uint8_t UART_MB_RcvData(uint8_t * rxbuf, uint32_t buflen)
- {
- uint32_t bToRecv;
- uint32_t bRecv;
- volatile uint32_t timeOut;
- uint8_t *pChar = rxbuf;
- bToRecv = buflen;
- bRecv = 0;
- while (bToRecv)
- {
- timeOut = UART_BLOCKING_TIMEOUT;
- while (!(LPC_UART0->LSR & UART_LSR_RDR)) {
- if (timeOut == 0) { break; }
- timeOut--;
- }
- if(timeOut == 0) {
- break;
- }
- uint8_t rcvByte = UART_ReceiveData((LPC_UART_TypeDef *)LPC_UART0);
- (*pChar++) = rcvByte;
- if(bRecv == 1) {
- recFrameLength = rcvByte;
- }
- bToRecv--;
- bRecv++;
- if(bRecv == recFrameLength && bRecv > 4) {
- break;
- }
- }
- return bRecv;
- }
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.
- void UART0_IRQHandler(void)
- {
- uint32_t intsrc = 0;
- static uint32_t counterDataInUart;
- intsrc = LPC_UART0->IIR;
- uint32_t tmp = intsrc & UART_IIR_INTID_MASK;
- if (tmp == UART_IIR_INTID_RLS) {
- uint8_t rar = LPC_UART0->LSR;
- rar = rar;
- }
- intsrc &= UART_IIR_INTID_MASK;
- if(intsrc == UART_IIR_INTID_RDA) {
- memset(uartMbRecBuffer, 0, sizeof(uartMbRecBuffer)/sizeof(uartMbRecBuffer[0]));
- receiveDataLength = UART_MB_RcvData(uartMbRecBuffer, sizeof(uartMbRecBuffer)/sizeof(uartMbRecBuffer[0]));
- if(receiveDataLength > 0) {
- if(uartMbRecBuffer[0] != 0x01) {
- flag_uartMbDataReceived = false;
- }
- else {
- startPuttingDataToBuffer = 1;
- circ_buffer_put_buff(&Rx_CircBuffer, &uartMbRecBuffer[0]);
- startPuttingDataToBuffer = 0;
- flag_uartMbDataReceived = true;
- }
- }
- }
- }
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:
- uint8_t CircBuff_GetData(volatile circ_buffer_t *q, UART_Rec_Command_Typedef *data)
- {
- if (CheckPositionInBuffer(q->head, q->tail) == 1) { return 1; }
- q->tail++;
- if (CheckIfLastElement(q->tail) == 1) { q->tail = 0; }
- data->CommandStartCode = Col_Rec_Comm_TypDef[q->tail].CommandStartCode;
- data->CommandLength = Col_Rec_Comm_TypDef[q->tail].CommandLength;
- data->CommandCode = Col_Rec_Comm_TypDef[q->tail].CommandCode;
- for (uint8_t i = 0; i < (data->CommandLength - 5); i++)
- {
- data->CommandData[i] = q->Col_Rec_Comm_TypDef[q->tail].CommandData[i];
- }
- data->CommandCrc[0] = q->Col_Rec_Comm_TypDef[q->tail].CommandCrc[0];
- data->CommandCrc[1] = q->Col_Rec_Comm_TypDef[q->tail].CommandCrc[1];
- return 0;
- }
- uint8_t CircBuff_PutData(volatile circ_buffer_t *q, volatile uint8_t* data)
- {
- uint8_t head_temp = q->head + 1;
- if (CheckIfBufLastElement(head_temp) == 1) { head_temp = 0; }
- if (CheckPositionInBuffer(head_temp, q->tail) == 1) { return 1; }
- q->Col_Rec_Comm_TypDef[head_temp].CommandStartCode = data[0];
- q->Col_Rec_Comm_TypDef[head_temp].CommandLength = data[1];
- q->Col_Rec_Comm_TypDef[head_temp].CommandCode = data[2];
- for (uint8_t i = 0; i < (data[1] - 5); i++)
- {
- q->Col_Rec_Comm_TypDef[head_temp].CommandData[i] = data[i + 3];
- }
- q->Col_Rec_Comm_TypDef[head_temp].CommandCrc[0] = data[data[1] - 2]; //Przedostatni
- q->Col_Rec_Comm_TypDef[head_temp].CommandCrc[1] = data[data[1] - 1]; //Ostatni
- q->head = head_temp; // Zapisujemy nowy indeks head
- return 0; // wszystko przebiegło pozytywnie, więc zwracamy 0
- }
- static uint8_t CheckPositionInBuffer(uint8_t head, uint8_t tail) {
- if (head == tail) { return 1; }
- return 0;
- }
- static uint8_t CheckIfBufLastElement(uint8_t head) {
- if (head == CIRC_BUF_SIZE) { return 1; }
- return 0;
- }
- static uint8_t CheckIfLastElement(uint8_t tail) {
- if (tail == CIRC_BUF_SIZE) { return 1; }
- return 0;
- }
Obsługa odebranych danych następuje w głównej pętli programu:
- while(1) {
- if(circ_buffer_get_buff(&Colibri_Rx_CircBuffer, &uartMbCopyBuffer[0]) == 0)
- {
- UART_MB_DecodeReceiveMsg(uartMbCopyBuffer);
- }
- //...reszta programu
- }
Testy jednostkowe:
Poniżej wykaz prostych testów pozwalających na dodawanie i usuwanie ramek danych z bufora kołowego:
- TEST(CircularBuffer_Test, Put_GetDataFromBuffer) {
- volatile uint8_t example_frame[9] = { 0x01, 0x09, 0x23, 0x56, 0x22, 0x89, 0x99, 0x48, 0x92 };
- volatile circ_buffer_t Rx_CircBuffer_test = { Col_Rec_Comm_TypDef, 0, 0 };
- UART_Rec_Command_Typedef readedData;
- int8_t responseCode = -1;
- RecCommandTypedefSetDefaultValues();
- responseCode = CircBuff_PutData(&Rx_CircBuffer_test, &example_frame[0]);
- EXPECT_EQ(responseCode, 0);
- EXPECT_EQ(Rx_CircBuffer_test.head, 1);
- EXPECT_EQ(Rx_CircBuffer_test.tail, 0);
- responseCode = CircBuff_GetData(&Rx_CircBuffer_test, &readedData);
- EXPECT_EQ(responseCode, 0);
- EXPECT_EQ(Rx_CircBuffer_test.head, 1);
- EXPECT_EQ(Rx_CircBuffer_test.tail, 1);
- EXPECT_EQ(readedData.CommandStartCode, example_frame[0]);
- EXPECT_EQ(readedData.CommandLength, example_frame[1]);
- EXPECT_EQ(readedData.CommandCode, example_frame[2]);
- EXPECT_EQ(readedData.CommandData[0], example_frame[3]);
- EXPECT_EQ(readedData.CommandData[1], example_frame[4]);
- EXPECT_EQ(readedData.CommandData[2], example_frame[5]);
- EXPECT_EQ(readedData.CommandData[3], example_frame[6]);
- EXPECT_EQ(readedData.CommandCrc[0], example_frame[7]);
- EXPECT_EQ(readedData.CommandCrc[1], example_frame[8]);
- }
- TEST(CircularBuffer_Test, PutGet_All_DataToBuffer) {
- volatile uint8_t example_frame_1[9] = { 0x01, 0x09, 0x23, 0x56, 0x22, 0x89, 0x99, 0x48, 0x92 };
- volatile circ_buffer_t Rx_CircBuffer_test = { Col_Rec_Comm_TypDef, 0, 0 };
- UART_Rec_Command_Typedef readedData;
- int8_t responseCode = -1;
- RecCommandTypedefSetDefaultValues();
- for (uint8_t loop = 0; loop < (CIRC_BUF_SIZE - 1); loop++)
- {
- responseCode = CircBuff_PutData(&Rx_CircBuffer_test, &example_frame_1[0]);
- example_frame_1[3]++;
- example_frame_1[4]++;
- example_frame_1[5]++;
- EXPECT_EQ(responseCode, 0);
- EXPECT_EQ(Rx_CircBuffer_test.tail, 0);
- EXPECT_EQ(Rx_CircBuffer_test.head, loop + 1);
- }
- responseCode = CircBuff_PutData(&Rx_CircBuffer_test, &example_frame_1[0]);
- EXPECT_EQ(responseCode, 1);
- EXPECT_EQ(Rx_CircBuffer_test.tail, 0);
- EXPECT_EQ(Rx_CircBuffer_test.head, CIRC_BUF_SIZE - 1);
- responseCode = CircBuff_GetData(&Rx_CircBuffer_test, &readedData);
- EXPECT_EQ(responseCode, 0x00);
- EXPECT_EQ(Rx_CircBuffer_test.tail, 1);
- EXPECT_EQ(Rx_CircBuffer_test.head, CIRC_BUF_SIZE - 1);
- responseCode = CircBuff_GetData(&Rx_CircBuffer_test, &readedData);
- EXPECT_EQ(responseCode, 0x00);
- EXPECT_EQ(Rx_CircBuffer_test.tail, 2);
- EXPECT_EQ(Rx_CircBuffer_test.head, CIRC_BUF_SIZE - 1);
- responseCode = CircBuff_PutData(&Rx_CircBuffer_test, &example_frame_1[0]);
- EXPECT_EQ(responseCode, 0x00);
- EXPECT_EQ(Rx_CircBuffer_test.tail, 2);
- EXPECT_EQ(Rx_CircBuffer_test.head, 0);
- responseCode = CircBuff_PutData(&Rx_CircBuffer_test, &example_frame_1[0]);
- EXPECT_EQ(responseCode, 0x00);
- EXPECT_EQ(Rx_CircBuffer_test.tail, 2);
- EXPECT_EQ(Rx_CircBuffer_test.head, 1);
- }
Projekt jest do pobrania z dysku Google pod tym linkiem.