Кільцевий буферКільцевий буфер, або як його ще називають FIFO – буфер, використовують в програмуванні для зберігання і відправки даних між різними системами які працюють в асинхронному режимі. В програмуванні мікроконтролерів, його найчастіше використовують для прийому і передачі даних між UARTом і ще чимось, що задіяно в мікроконтролері для обробки даних. Приведу приклад: Потрібно передати дані з UART в шину i2c при тому, що мікроконтролер Slave. В режимі Slave шина неможе ініціалізувати передачу даних, а має чекати коли прийде запит від Master для початку передачі, а UART тим часом приймає дані і вони затираються, тому що i2c нічого не відправляє. Максимум що ми отримаємо це якийсь обірваний, незмістовний пакет.

І тут нас виручить кільцевий буфер! В нього ми можемо побайтово поскладати всю посилку даних з UART і забути про неї, до тих пір доки ці дані не прийде час зчитати.
Чого його називають “кільцевим”? Все через те, що він сам себе перезаписує. Технічно, ми маємо масив із заданою величиною, наприклад 10 байт. При записі всіх 10 байт в масив, виконується його “обнулення” і процес починається зпочатку.

1
2
3
4
5
6
7
// Оголошуемо і ініціалізуємо змінні, а також розім буфера
#define BUFFER_SIZE 10
 
unsigned char buffer[BUFFER_SIZE];
unsigned char head = 0;
unsigned char tail = 0;
unsigned char counter = 0;

Для оперування даними в масиві використовуємо три допоміжні змінні, які будуть рахувати заповненість масиву і порівнюватись із його розміром.

1
2
3
4
5
6
7
8
9
void pushInBuffer(unsigned char data)
{
if (counter < BUFFER_SIZE){             // Якщо лічиль counter менший за розмір буфера
buffer[tail] = data;                // То записуємо дані в масив по індексу який вказує змінна tail
counter++;                          // Нарощуємо лічильник counter
tail++;                             // При наступному виклику функції запису, дані будуть записан по іншому індексу
if (tail == BUFFER_SIZE) tail = 0;  // Перевіряємо чи ми не дойшли до кінця масиву, якщо так, обнуляємо
}                                     // tail і при наступному виклику функції, дані будуть писатись в початок масиву
}

Майже аналогічно виглядає функція зчитування даних з буфера

1
2
3
4
5
6
7
8
9
10
11
unsigned char getFromBuffer(void)
{
unsigned char data = 0;                // Ініціалізуємо тимчасову функцію яка буде повертати дані з масиву
if (counter > 0){                      // Перевіряємо чи лічильник більший за 0
data = buffer[head];                // При першому виклику функції зчитування диних, читаємо їх із нульової комірки масиву
counter--;                          // Зменшуємо лічильник         
head++;                             // При наступному виклику функції, вже будемо читати першу комірку масиву і так далі...
if (head == BUFFER_SIZE) head = 0;  // Перевіряємо чи ми не дойшли до кінця масиву, інікше починаємо зпочатку
}
return data;                           // Вертаємо отриманий байт з комірки масиву
}

Також за допомогою лічильника counter ми можемо взнати чи присутні дані в буфері і їх кількість. При виклику функції “pushInBuffer” ми його нарощували, а при зчитуванні з буфера “getFromBuffer”, зменшували. Отже можна організувати перевірку даних в буфері.

1
2
3
4
5
6
7
8
9
10
11
unsigned char isAvailable(void)
{
if(!counter == 0)                 // Якщо лічильник не дорівнює нулю
{
return 1;                 // То повертаємо 1
}
else if(counter == 0)             // Якщо рівний нулю
{
return 0;                 // Вертаємо нуль
}
}

А також взнаємо кількість байт в буфері.

1
2
3
4
unsigned char getCounter(void)
{
return counter;                   // Просто повертаємо дані лічильника
}

Код оформив для ознайомлення в бібліотеку.
Скачати можна з GitHub

Comments:

4 думки про «Кільцевий буфер»

  1. Декілька зауважень:
    1. Код можна винести в модульні файли *.h та *.c, що дозволить використовувати його як бібліотеку.
    2. замість unsigned char краще використовувати переносимі типи uint8_t, це дозволить коду працювати на різних платформах МК
    3. окремі змінні:
    unsigned char buffer[BUFFER_SIZE];
    unsigned char head = 0;
    unsigned char tail = 0;
    unsigned char counter = 0;
    краще оформити структурою

    typedef struct {
    uint8_t buffer[BUFFER_SIZE];
    uint8_t head;
    uint8_t tail;
    uint8_tr counter;
    }ring;

    дуже класна реалізація кільцевого буфера тут:
    http://microsin.net/programming/avr/ring-buffer.html

    Відповів
    • Що до зауважень. Цікавить яку перевагу структура має перед моєю реалізацією? Вроді все те саме тільки спосіб доступу трохи буде відрізнятись

      Відповів
      • 1.Структура зробить код більш акуратнішим.
        2.Якщо ви трохи зміните код, і будете передавати вказівник на структуру, це дозволить вам створити кілька кільцевих буферів в одному проекті, і працювати з ним через ті самі функції, не створюючи нових.

        наприклад
        typedef struct {
        uint8_t buffer[BUFFER_SIZE];
        uint8_t head;
        uint8_t tail;
        uint8_tr counter;
        }ring;

        ring myRing1;
        ring ring2.

        void pushInBuffer(ring* rn,uint8_t data)
        {
        if (rn ->counter buffer[rn->tail] = data; // То записуємо дані в масив по індексу який вказує змінна tail
        rn ->counter++; // Нарощуємо лічильник counter
        rn ->tail++; // При наступному виклику функції запису, дані будуть записан по іншому індексу
        if (rn ->tail == BUFFER_SIZE) rn ->tail = 0; // Перевіряємо чи ми не дойшли до кінця масиву, якщо так, обнуляємо
        } // tail і при наступному виклику функції, дані будуть писатись в початок масиву
        }

        виклик функції для роботи з двома кільцевими буферами:

        void pushInBuffer(&myRing1,UART_TX);
        void pushInBuffer(&ring2,UART_RX);

        Відповів
        • Класно! Вік живи, вік учись.
          З’явиться час, я перепишу код по ваших рекомендаціях.
          Цікаво наскільки зміниться використання ресурсів, якщо брати до прикладу МК AVR.

          Відповів

Написати відповідь

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> 

вимагається