реклама на сайте
подробности

 
 
 
Reply to this topicStart new topic
> Помогите грамотно организовать кольцевую очередь, без операционной системы...
InsolentS
сообщение Oct 29 2012, 09:24
Сообщение #1


Местный
***

Группа: Свой
Сообщений: 414
Регистрация: 8-06-06
Пользователь №: 17 897



Добрый день!
В моём проекте не используется ОС, но, думаю, что актуальнее всего будет задать вопрос именно в этом форуме.
Есть драйвер (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, чтобы они не мешали друг-другу, будучи вызванными одновременно из разных потоков?
Извиняюсь за "многабуков" sm.gif


--------------------
Курильщик даташитов со стажем
Go to the top of the page
 
+Quote Post
_Артём_
сообщение Oct 29 2012, 11:59
Сообщение #2


Гуру
******

Группа: Свой
Сообщений: 2 128
Регистрация: 21-05-06
Пользователь №: 17 322



Цитата(InsolentS @ Oct 29 2012, 11:24) *
В данный момент приходится запрещать прерывания от SPI в начале функции SPI_Write и заного разрешать их в конце - но такой подход мне не очень нравится

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

Цитата(InsolentS @ Oct 29 2012, 11:24) *
1) Нужно помнить и следить за всеми вызовами функций очереди и обрамлять их в подобные критическии секции, очень велик риск забыть и получить труднообнаруживаемый глюк при эксплуатации.

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

Цитата(InsolentS @ Oct 29 2012, 11:24) *
2) Много времени система проводит при запрещённых прерываниях, т.е. увеличивается время реакции на прерывание -> велик риск пропустить входящий байт (если SPI в слейве).

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


Go to the top of the page
 
+Quote Post
xemul
сообщение Oct 29 2012, 12:50
Сообщение #3



*****

Группа: Свой
Сообщений: 1 928
Регистрация: 11-07-06
Пользователь №: 18 731



Если операции с _queue->Count атомарны, то достаточно объявить его volatile.
Если нет, то придётся или взводить флаг на время операций с _queue->Count вне прерываний, или добавлять критические секции там же.
Go to the top of the page
 
+Quote Post
vshemm
сообщение Oct 29 2012, 13:04
Сообщение #4


Частый гость
**

Группа: Участник
Сообщений: 167
Регистрация: 15-08-07
Пользователь №: 29 803



Если у процессора нет атомарных операций (или LL/SC, или HTM и т.д.), то без запрещения прерываний на время работы с _queue->Count не обойтись.
С флагом не получится, т.к. ждать в прерывании пока отработает QUEUE_Write() можно до бесконечности.
Go to the top of the page
 
+Quote Post
xemul
сообщение Oct 29 2012, 13:12
Сообщение #5



*****

Группа: Свой
Сообщений: 1 928
Регистрация: 11-07-06
Пользователь №: 18 731



Цитата(vshemm @ Oct 29 2012, 17:04) *
С флагом не получится, т.к. ждать в прерывании пока отработает QUEUE_Write() можно до бесконечности.

Разве я сказал, что в прерывании нужно ждать сброса флага?
Go to the top of the page
 
+Quote Post
InsolentS
сообщение Oct 29 2012, 13:16
Сообщение #6


Местный
***

Группа: Свой
Сообщений: 414
Регистрация: 8-06-06
Пользователь №: 17 897



Цитата(xemul @ Oct 29 2012, 18:50) *
Если операции с _queue->Count атомарны, то достаточно объявить его volatile.

Операция инкремента (INC) - атомарна, а разыменование указателя - нет sad.gif
Код
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


--------------------
Курильщик даташитов со стажем
Go to the top of the page
 
+Quote Post
vshemm
сообщение Oct 29 2012, 13:22
Сообщение #7


Частый гость
**

Группа: Участник
Сообщений: 167
Регистрация: 15-08-07
Пользователь №: 29 803



Цитата(xemul @ Oct 29 2012, 17:12) *
Разве я сказал, что в прерывании нужно ждать сброса флага?

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

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

Сообщение отредактировал vshemm - Oct 29 2012, 13:26
Go to the top of the page
 
+Quote Post
xemul
сообщение Oct 29 2012, 13:41
Сообщение #8



*****

Группа: Свой
Сообщений: 1 928
Регистрация: 11-07-06
Пользователь №: 18 731



Цитата(vshemm @ Oct 29 2012, 17:22) *
А что вы предлагаете делать? Код ошибки вернуть нельзя, вызвать UnderflowFunc() тоже...

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

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

Цитата(InsolentS)
Код
_queue->Count++;
    LD        A, [0x00.w]
    INC       A
    LD        [0x00.w], A

Как-то не похоже оно на атомарность. С stm8 тоже не работал, но конструкция для современного проца и компилятора выглядит чудесато.
Go to the top of the page
 
+Quote Post
AHTOXA
сообщение Oct 29 2012, 14:09
Сообщение #9


фанат дивана
******

Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684



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


--------------------
Если бы я знал, что такое электричество...
Go to the top of the page
 
+Quote Post
InsolentS
сообщение Oct 30 2012, 04:37
Сообщение #10


Местный
***

Группа: Свой
Сообщений: 414
Регистрация: 8-06-06
Пользователь №: 17 897



Цитата(AHTOXA @ Oct 29 2012, 20:09) *
Есть же неблокирующий вариант очереди с read_ptr и write_ptr, без count-а.

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


--------------------
Курильщик даташитов со стажем
Go to the top of the page
 
+Quote Post
AHTOXA
сообщение Oct 30 2012, 06:05
Сообщение #11


фанат дивана
******

Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684



Да там всё элементарно. Есть два индекса - 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

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


--------------------
Если бы я знал, что такое электричество...
Go to the top of the page
 
+Quote Post
ReAl
сообщение Oct 30 2012, 06:54
Сообщение #12


Нечётный пользователь.
******

Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417



Цитата(AHTOXA @ Oct 30 2012, 08:05) *
При условии: один писатель, один читатель - прерывания можно не запрещать.
для случая sizeof(put) <= sizeof(sig_atomic_t)
Иначе надо подумать о возможных эффектах при прерывании, например, в середине чтения основной програмой индекса, изменяемого в прерывании.


--------------------
Ну, я пошёл… Если что – звоните…
Go to the top of the page
 
+Quote Post
AHTOXA
сообщение Oct 30 2012, 08:53
Сообщение #13


фанат дивана
******

Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684



Цитата(ReAl @ Oct 30 2012, 12:54) *
для случая sizeof(put) <= sizeof(sig_atomic_t)
Да, точно. Я не придумал, как это сформулироватьsm.gif
Цитата(ReAl @ Oct 30 2012, 12:54) *
Иначе надо подумать о возможных эффектах при прерывании, например, в середине чтения основной програмой индекса, изменяемого в прерывании.
В этом случае можно читать индекс до совпадения результатов двух последовательных чтений.


--------------------
Если бы я знал, что такое электричество...
Go to the top of the page
 
+Quote Post
MBR
сообщение Nov 20 2012, 13:18
Сообщение #14


Частый гость
**

Группа: Участник
Сообщений: 107
Регистрация: 26-09-10
Пользователь №: 59 748



Фьютексы же.

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

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

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

lockFlag = 1;
<read data here>
readed += <readed size>
lockFlag = 0;

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

if (!lockFlag)
{
real_size -= readed;
readed = 0;
}
Go to the top of the page
 
+Quote Post

Reply to this topicStart new topic
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 


RSS Текстовая версия Сейчас: 27th July 2025 - 20:43
Рейтинг@Mail.ru


Страница сгенерированна за 0.01493 секунд с 7
ELECTRONIX ©2004-2016