Перейти к содержанию
    

Помогите грамотно организовать кольцевую очередь

Добрый день!

В моём проекте не используется ОС, но, думаю, что актуальнее всего будет задать вопрос именно в этом форуме.

Есть драйвер (SPI), который использует кольцевую очередь.

У очереди есть "методы" Write и Read

void QUEUE_Write(Queue *_queue, uint8 _data)
{
  if(_queue->Count < _queue->Size) // If buffer not full
  {
    _queue->Buff[_queue->WriteIndex++] = _data; // Write next byte to buffer
    _queue->WriteIndex &= _queue->Size - 1; // If buffer ends, jump to begin
    _queue->Count++; 
  }
  else OverflowFunc(); // Call overflow error handler
}


uint8 QUEUE_Read(Queue *_queue)
{  
  uint8 data;
  if(_queue->Count) // If queue has a data
  {
    data = _queue->Buff[_queue->ReadIndex++]; // Read next byte from buffer
    _queue->ReadIndex &= _queue->Size - 1; // If buffer ends, jump to begin
    _queue->Count--;
    return data;  
  }
  else 
  {
    UnderflowFunc(); // Call underflow error handler
    return 0;
  }  
}

 

в драйвере SPI метод Write вызывается из основного потока программы:

void SPI_WriteByte(uint8 _val)
{
  while(TxQueue.Count == TxQueue.Size) asm("nop");  // Если в очереди нет места, ждём пока освободится
  QUEUE_Write(&TxQueue, _val); // Записываем байт в очередь
}

а метод Read вызывается из прерывания:

#pragma vector = SPI_TXE_vector
__interrupt void SPI_Isr(void)
{  
  ...
  if(TxQueue.Count) // Если в очереди что-то есть
  {   
    SPI_DR = QUEUE_Read(&TxQueue); // записываем байт из очереди в регистр передатчика
  }
  ...
}

и всё работает хорошо при не очень интенсивном обмене данными, а если данных много, бывает, что прерывание по передаче возникает в момент, когда идёт выполнение функции Write, т.е. QUEUE_Read вызывается во время выполнения QUEUE_Write, данные в структуре очереди портятся и наступает жвах!

В данный момент приходится запрещать прерывания от SPI в начале функции SPI_Write и заного разрешать их в конце - но такой подход мне не очень нравится, т.к.

1) Нужно помнить и следить за всеми вызовами функций очереди и обрамлять их в подобные критическии секции, очень велик риск забыть и получить труднообнаруживаемый глюк при эксплуатации.

2) Много времени система проводит при запрещённых прерываниях, т.е. увеличивается время реакции на прерывание -> велик риск пропустить входящий байт (если SPI в слейве).

Может быть есть способ красиво разрулить эту ситуацию, внеся какие-то изменения в функции QUEUE_Read и QUEUE_Write, чтобы они не мешали друг-другу, будучи вызванными одновременно из разных потоков?

Извиняюсь за "многабуков" :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

В данный момент приходится запрещать прерывания от SPI в начале функции SPI_Write и заного разрешать их в конце - но такой подход мне не очень нравится

Запрет прерываний в таком случае - вполне приемлимый способ.

 

1) Нужно помнить и следить за всеми вызовами функций очереди и обрамлять их в подобные критическии секции, очень велик риск забыть и получить труднообнаруживаемый глюк при эксплуатации.

Вставте критическую секцию в функцию SPI_WriteByte. На выходе из функции разрешайте прерывания.

 

2) Много времени система проводит при запрещённых прерываниях, т.е. увеличивается время реакции на прерывание -> велик риск пропустить входящий байт (если SPI в слейве).

Если запрещать прырывания только на запись байта, то запрет недолгий.

 

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Если операции с _queue->Count атомарны, то достаточно объявить его volatile.

Если нет, то придётся или взводить флаг на время операций с _queue->Count вне прерываний, или добавлять критические секции там же.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Если у процессора нет атомарных операций (или LL/SC, или HTM и т.д.), то без запрещения прерываний на время работы с _queue->Count не обойтись.

С флагом не получится, т.к. ждать в прерывании пока отработает QUEUE_Write() можно до бесконечности.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

С флагом не получится, т.к. ждать в прерывании пока отработает QUEUE_Write() можно до бесконечности.

Разве я сказал, что в прерывании нужно ждать сброса флага?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Если операции с _queue->Count атомарны, то достаточно объявить его volatile.

Операция инкремента (INC) - атомарна, а разыменование указателя - нет :(

QUEUE_Write:
    CALL      ?push_w4
    LDW       ?b4, X
    LD        ?b6, A
if(_queue->Count < _queue->Size) // If buffer not full
    ADDW      X, #?b4
    LDW       ?b2, X
    LDW       X, ?b4
    ADDW      X, #?b5
    LDW       0x00, X
    LD        A, [0x00.w]
    CP        A, [?b2.w]
    JRNC      ??QUEUE_Write_0
_queue->Buff[_queue->WriteIndex++] = _data; // Write next byte to buffer
    LDW       X, ?b4
    ADDW      X, #?b3
    LDW       Y, X
    LD        A, (Y)
    CLRW      X
    LD        XL, A
    LDW       ?b8, X
    LDW       X, [?b4.w]
    ADDW      X, ?b8
    LD        A, ?b6
    LD        (X), A
    LD        A, (Y)
    INC       A
    LD        (Y), A
_queue->WriteIndex &= _queue->Size - 1; // If buffer ends, jump to begin
    LD        A, [?b2.w]
    DEC       A
    AND       A, (Y)
    LD        (Y), A
_queue->Count++; 
    LD        A, [0x00.w]
    INC       A
    LD        [0x00.w], A
    JP        ?epilogue_w4

Процессор - stm8

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Разве я сказал, что в прерывании нужно ждать сброса флага?

А что вы предлагаете делать? Код ошибки вернуть нельзя, вызвать UnderflowFunc() тоже...

 

UPD: Нужны атомарные чтение-запись + инкремент. Разыменование это тоже чтение. Правда, с stm8 я не работал.

Изменено пользователем vshemm

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А что вы предлагаете делать? Код ошибки вернуть нельзя, вызвать UnderflowFunc() тоже...

По-моему, очевидно - ничего не делать.

__interrupt void SPI_Isr(void)
{  
  ...
  if(!TxQueue.Busy)
    if(TxQueue.Count) // Если в очереди что-то есть
    {   
      SPI_DR = QUEUE_Read(&TxQueue); // записываем байт из очереди в регистр передатчика
    };
  ...
}

Содержимого ... не знаю, может потребуются уточнения.

 

_queue->Count++; 
    LD        A, [0x00.w]
    INC       A
    LD        [0x00.w], A

Как-то не похоже оно на атомарность. С stm8 тоже не работал, но конструкция для современного проца и компилятора выглядит чудесато.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Есть же неблокирующий вариант очереди с read_ptr и write_ptr, без count-а.

Очень интересно, не могли бы Вы дать ссылку или привести пример? Count, по большому счёту, нужен только чтобы отслеживать переполнение и опустошение очереди, не хотелось бы потерять эту функцию.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Да там всё элементарно. Есть два индекса - put и get. Буфер пуст, если put == get. Буфер полон, если put + 1 == get.

запись в буфер:

 

temp = (put + 1) % SIZE;

if (temp == get) return -1; // buffer full

buffer[put] = ch;

put = temp;

 

чтение из буфера:

if (put == get) return -1; // empty

ch = buffer[get++];

get %= SIZE; // limit the pointer

return ch

 

При условии: один писатель, один читатель - прерывания можно не запрещать.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

При условии: один писатель, один читатель - прерывания можно не запрещать.
для случая sizeof(put) <= sizeof(sig_atomic_t)

Иначе надо подумать о возможных эффектах при прерывании, например, в середине чтения основной програмой индекса, изменяемого в прерывании.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

для случая sizeof(put) <= sizeof(sig_atomic_t)
Да, точно. Я не придумал, как это сформулировать:)

Иначе надо подумать о возможных эффектах при прерывании, например, в середине чтения основной програмой индекса, изменяемого в прерывании.
В этом случае можно читать индекс до совпадения результатов двух последовательных чтений.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Фьютексы же.

 

Можно использовать два буфера (что, собственно, реализуется большинством ОС) - один на чтение, второй на запись. Буфер на запись можно использовать свободно.

 

Дальше используем две дополнительные переменные - lockFlag и readed.

 

в основной программе:

 

lockFlag = 1;

<read data here>

readed += <readed size>

lockFlag = 0;

 

в обработчике:

 

if (!lockFlag)

{

real_size -= readed;

readed = 0;

}

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...