Реализация циклического буфера и Serial.print()

serial loop

Я пытаюсь реализовать простейший кольцевой буфер в Arduino. Я думаю, что я в состоянии реализовать это, потому что мои функции push и pop не возвращают никаких ошибок. Однако, когда я хочу распечатать данные в буфере с помощью Serial.print(), код переходит в бесконечный цикл. Вот мой код:

#include <stdio.h>

typedef struct circular_buffer
{
    int buffer_arr[5];
    int head;
    int tail;
} circular_buffer;

void setup()
{  
  Serial.begin(9600); 
  Serial.println("Circular buffer started");
}

void loop()
{  
  circular_buffer *cb;
  cb_init(cb);
  if(Serial.available()){
    char instruction = Serial.read();
    Serial.println(instruction);
    if(instruction == 'a'){
      if(Serial.available()){
        char instruction = Serial.read();
        Serial.println("here");
      }
      char key = Serial.read();
      cb_push_front(cb, key);
      Serial.print(String(key));
      Serial.println("    is entered");
    }
    else if(instruction == 'd'){
      cb_pop_back(cb);
      Serial.println("last item removed");
    }
//    for(int i=0; i<5; i++){
//      Serial.print(cb->buffer_arr[i]);
//      
//    }
    Serial.println("-");
  }
}



void cb_init(circular_buffer *cb)
{
    // cb->buffer_arr = {0,0,0,0,0};
    for(int i=0; i<5; i++){
      cb->buffer_arr[i] = 'c';
//      Serial.print(sizeof(*cb));
    }
//      Serial.print(cb->buffer_arr[0]);
    cb->head = 0;
    cb->tail = 0;
}


void cb_push_front(circular_buffer *cb, char item)
{
    cb->head++;
    cb->buffer_arr[cb->head] = item;
}

void cb_pop_back(circular_buffer *cb)
{
    cb->tail++;
    cb->buffer_arr[cb->tail] = 0;
}

Цикл for, который я закомментировал, является проблемным - или, по крайней мере, я так считаю. Моя проблема может быть во многом связана с моими плохими навыками владения языком Си. Я думал, что cb-> buffer_arr = {0,0,0,0,0}; фрагмент кода в функции инициализации должен был сработать (очень похоже на Python-ic), но этого не произошло.

Так что, если кто-нибудь сможет увидеть проблему и помочь мне ее устранить, я был бы вам очень признателен. Заранее благодарю.

Редактировать

После ответа Маенко я обновил код таким образом. Однако у меня все еще есть проблемы с печатью. Я не уверен, печатаю ли я адрес циклического буфера в формате ASCII таким образом. Я попытался передать cb в функцию печати двумя способами. Хотя, похоже, ни один из них не работает ...

#include <stdio.h>

typedef struct circular_buffer
{
    int buffer_arr[5];
    int head;
    int tail;
} circular_buffer;

circular_buffer cb;

void setup()
{  
  Serial.begin(9600); 
  cb_init(cb);
  Serial.println("Circular buffer started");
}

void loop()
{  
  if(Serial.available()){
    char instruction = Serial.read();
    Serial.println(instruction);
    if(instruction == 'a'){
      Serial.print("a pressed");  
      cb_push_front(cb, instruction);
    }
    else if(instruction == 'd'){
      Serial.print("d pressed");
    }
    else{
      Serial.print("UNKNOWN");
    }
    Serial.println("-");
  }
}



void cb_init(circular_buffer &cb)
{
    for(int i=0; i<5; i++){
      cb.buffer_arr[i] = 'c';
    }
    Serial.println("here init");
    cb_print1(&cb);
    cb_print2(cb);
    cb.head = 0;
    cb.tail = 0;
}


void cb_push_front(circular_buffer &cb, char item)
{
    cb.head++;
    cb.buffer_arr[cb.head] = item;

    cb_print1(&cb);
    cb_print2(cb);
}

void cb_pop_back(circular_buffer &cb)
{
    cb.tail++;
    cb.buffer_arr[cb.tail] = 0;
}

void cb_print1(circular_buffer *cb)
{
  Serial.println("here print1");
  for(int i=0; i<5; i++){
    Serial.print(cb->buffer_arr[i]);
    
  }
}

void cb_print2(circular_buffer cb)
{
  Serial.println("here print2");
  for(int i=0; i<5; i++){
    Serial.print(cb.buffer_arr[i]);
    
  }
}

И возвращаемый результат выглядит следующим образом:

here init
here print1
9999999999here print2
9999999999Circular buffer started
a
a pressedhere print1
9997999999here print2
9997999999-
a
a pressedhere print1
9997979999here print2
9997979999-

, 👍1

Обсуждение

На самом деле это не кольцевой буфер. Индексы должны быть обернуты вокруг предела буфера (5). В противном случае вы будете записывать сообщения за пределы буфера достаточно долго., @chrisl

Возможно, вам это покажется полезным: https://github.com/MajenkoLibraries/CircularBuffer, @Majenko

Большое вам спасибо за помощь. У меня, конечно, есть проблемы с тем, как я передаю адреса и указываю на переменные. Вы правы в отношении ограничения размера буфера, но в этом конкретном приложении нет способа заполнить буфер - если я не ошибаюсь в коде :) Вот почему я хотел, чтобы все было как можно проще., @TheClem


2 ответа


4

Вы создаете переменную, которая может указывать на кольцевой буфер (*cb), но ни в коем случае вы на самом деле не указываете ее на кольцевой буфер.

Либо вам нужно создать новый циклический буфер, либо не создавать указатель, а создать статически выделенный циклический буфер. Последний метод является предпочтительным.

Просто удалите * из вашего объявления cb, которое создает стандартную структурную переменную, затем возьмите ее адрес (с &) для передачи в качестве указателя на другие ваши функции. Или измените другие функции, чтобы они передавались по ссылке.

Например:

circular_buffer *cb;

становится

circular_buffer cb;

А потом

cb_init(cb);

становится:

cb_init(&cb);

Как и все другие функции, которым вы передаете переменную cb.

Кроме того, каждый раз, когда вы проходите через loop(), вы будете стирать и создавать новую переменную circular_buffer . Вместо этого cb действительно должен быть глобальным, а вызов cb_init() должен быть в setup().

,

Спасибо вам за ответ. Я обновил код в соответствии с вашим ответом. Однако, похоже, у меня все еще есть проблемы с печатью. Извините, что беспокою вас этими вопросами для начинающих., @TheClem


1

С этим кодом связано несколько проблем. Маенко уже показал основное, я добавлю здесь еще несколько.

Во-первых, о реализации циклического буфера. Как было указано в комментарии, для того чтобы буфер считался циклическим, индексы должны обтекаться. Я бы добавил небольшую придирку: в C ++ принято начинать заполнение массива с индекса 0. Таким образом, вы должны постинкрементировать (а не предварительно инкрементировать) индексы:

void cb_push_front(circular_buffer *cb, char item)
{
    cb->buffer_arr[cb->head] = item;
    cb->head++;                            // последующее приращение
    if(cb->head == circular_buffer_size)   // с обтеканием
      cb->head = 0;
}

Однако все еще существует проблема: буфер может переполниться. Вы должны добавлять элемент только в том случае, если для этого есть место. И вы должны удалить элемент только в том случае, если буфер не пуст. Вы знаете, что буфер пуст, когда tail=head , но как вы узнаете, когда он заполнен? Если вы полностью заполните буфер и индексы будут обтекаться так, как должны, вы получите head= tail , и вы не сможете отличить полный буфер от пустого буфера. Есть два способа обойти эту проблему:

  • Вы можете сохранить в структуре дополнительную переменную, которая поможет вам определить разницу. Это может быть количество элементов в буфере, или логическое значение, сообщающее вам, когда буфер пуст, или логическое значение, сообщающее вам, когда он заполнен.

  • Вы можете воздержаться от полного заполнения массива и считать, что буфер заполнен, когда он заполнен до полной емкости, где “емкость” определяется как размер массива минус единица. Затем буфер заполняется, когда
    (голова + 1) = хвост [по модулю длины массива].

Вот реализация второй стратегии:

void cb_push_front(circular_buffer *cb, char item)
{
    int next_head = cb->head + 1;          // увеличить
    if(next_head >= circular_buffer_size)  // с переносом вокруг
      next_head = 0;
    if(next_head == cb->tail){
      Serial.println("buffer overflow");
      return;
    }
    cb->buffer_arr[cb->head] = item;
    cb->head = next_head;
}

int cb_pop_back(circular_buffer *cb)
{
    if(cb->tail == cb->head) {
      Serial.println("buffer underflow");
      return -1;
    }
    char item = cb->buffer_arr[cb->tail];
    cb->tail++;
    if(cb->tail >= circular_buffer_size)
      cb->tail = 0;
    return item;
}

Обратите внимание, что в cb_pop_back() нет смысла перезаписывать буфер. С другой стороны, часто бывает очень полезно, чтобы эта функция возвращала элемент, который только что был извлечен.

Теперь несколько случайных комментариев:

  • Вместо того, чтобы жестко кодировать размер массива (5) в нескольких местах, определите константу в начале программы (circular_buffer_size в моих примерах) и используйте его везде. Это делает более понятным, что означает число, и облегчает изменение размера, если вам когда-нибудь понадобится.

  • Поскольку вы храните символы, используйте массив символов.

  • Ваш код для обработки Serial плохо справляется с ситуацией , когда существует небольшая задержка между инструкцией 'a' и добавляемым символом. Если вы можете позволить себе блокировать код, самое простое решение - блокировать до тех пор, пока пользователь не предоставит символ:

if(instruction == 'a'){
  while(!Serial.available())
    ;  // дождитесь ввода ключа
  char key = Serial.read();
  cb_push_front(&cb, key);
  Serial.print(key);
  Serial.println(" is entered");
}
,

Очень важные моменты, спасибо! У меня были проблемы с правильной передачей cb функциям, поэтому я не прилагал особых усилий к оптимизации, так сказать. У меня все еще есть некоторые проблемы с печатью. Я обновил вопрос. Но я буду реализовывать эти пункты после этого. Большое вам спасибо., @TheClem

Re “_ Я буду реализовывать эти пункты после этого_”: это не очень хорошая идея. Устраните эти проблемы прямо сейчас, и тогда ваша программа будет работать должным образом. Или, если этого не произойдет, тогда будет намного легче понять, что происходит не так., @Edgar Bonet