Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Работа с переменными в прерывании и основном теле
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
Страницы: 1, 2
Alexashka
Добрый вечер sm.gif
Что имеем: 8 битный проц, программа в прерывании читает из кольцевого буфера, указатель чтения -просто индекс массива, двухбайтное число. В основном теле мне приходит поток байт, который я контролирую (запускаю/останавливаю поток) и размещаю байты в этом же кольцевом буфере по другому указателю -указателю записи (тоже индекс массива). До этих пор все замечательно, но чтобы управлять потоком мне нужно знать сколько байт содержится в буфере, для этого я беру разницу между указателями записи и чтения и побитно умножаю ее на (размер_массива - 1). Эту разницу приходится брать в нескольких местах программы, при этом на время вычисления нужно блокировать прерывание, поскольку операция не атомарная, а одна из переменных модифицируется в прерывании. Если этого не делать происходит глюк, если делать -появляется задержка обработки прерывания, а это нехорошо, т.к в нем происходит формирования аудио сигнала. Да и некрасиво это и неудобно -следить какую перменную обрабатываю, нужно или нет блокировать прерывание, ну и поскольку кольцевой буфер используется в различных прерываниях нужно еще и учитывать какое именно прерывание блокировать biggrin.gif
Может это както по-другому можно делать, подскажите.
gerber
Цитата(Alexashka @ Oct 3 2015, 22:40) *
Может это както по-другому можно делать, подскажите.

Заведите переменную "количество байт в буфере" и обновляйте её вместе с изменением указателей чтения и записи.
Alexashka
Цитата(gerber @ Oct 3 2015, 22:44) *
Заведите переменную "количество байт в буфере" и обновляйте её вместе с изменением указателей чтения и записи.

неа, смотрите, эта переменная тоже двухбайтная выходит. Допустим если байт в буфере больше 2000 нужно остановить поток, для этого я сравниваю ее с числом 2000, при этом сначала сравнивается младшие байты, потом старшие, если между этими операциями произойдет вызов подпрограммы, которая изменит старший байт "количество байт в буфере", то в результате сравнения можно получить очень неверный результат.
gerber
Значит, можно сделать порог кратным 256 и ограничиться сравнением одного старшего байта. rolleyes.gif
Да и вообще, логично начинать сравнение со старшего байта, а не с младшего, а младшие сравнивать только в случае равенства старших...
aaarrr
Разделите свой большой буфер на N маленьких и оперируйте буферами, а не байтами.
smalcom
Цитата
Разделите свой большой буфер на N маленьких и оперируйте буферами, а не байтами.

поддерживаю. при работе с аудиовыводом делал набор простых буферов: вычисляет быстро, запись и чтение разделены в одно время по разным буферам.

Примечик
Код
#define PLAYER_CONF_BUFSNDOUT_TIME_MS    20
#define PLAYER_CONF_BUFSNDOUT_SIZE        (PLAYER_CONF_BUFSNDOUT_TIME_MS * (44100 * 2 * 2 / 1000))
#define PLAYER_CONF_BUFSNDOUT_COUNT        3

template<uint32_t TBufSize> struct SSoundBuffer
{
    __attribute__((__aligned__(4))) uint8_t Data[TBufSize];
    volatile uint32_t Count;

    void Clear() { Count = 0; }
    bool IsEmpty() { return Count == 0; }
    uint32_t Size() { return TBufSize; }
    uint32_t Free() { return TBufSize - Count; }

    void AddData(const uint8_t* pData, const uint16_t pDataSize)
    {
        uint32_t toadd = pDataSize;

        if(pDataSize > Free()) toadd = Free();

        memcpy(&Data[Count], pData, toadd);
        Count += toadd;
    }
};

typedef SSoundBuffer<PLAYER_CONF_BUFSNDOUT_SIZE> BufferSndOut;

static BufferSndOut mBufferSndOut[PLAYER_CONF_BUFSNDOUT_COUNT];
Alexashka
Цитата
Значит, можно сделать порог кратным 256 и ограничиться сравнением одного старшего байта. rolleyes.gif


Цитата
Разделите свой большой буфер на N маленьких и оперируйте буферами, а не байтами.


Ну да, это получается примерно одно и тоже, старший байт по сути будет являться номером буфера.
jcxz
Цитата(Alexashka @ Oct 4 2015, 01:49) *
неа, смотрите, эта переменная тоже двухбайтная выходит. Допустим если байт в буфере больше 2000 нужно остановить поток, для этого я сравниваю ее с числом 2000, при этом сначала сравнивается младшие байты, потом старшие, если между этими операциями произойдет вызов подпрограммы, которая изменит старший байт "количество байт в буфере", то в результате сравнения можно получить очень неверный результат.

Конечно не нужно никаких "количеств байт". Достаточно двух указателей (чтения/записи). И достаточно операции чтения/записи этих указателей сделать атомарными. И для вычисления кол-ва байт достаточно найти разность указателей (с учётом их цикличности), не нужно никаких "побитных умножений".
Неужто запрет прерывания на пару команд так влияет на выполнение алгоритма??? Тогда у Вас вероятно неправильно построен алгоритм.
_Pasha
Цитата(jcxz @ Oct 4 2015, 10:47) *
Неужто запрет прерывания на пару команд так влияет на выполнение алгоритма??? Тогда у Вас вероятно неправильно построен алгоритм.

можно проверку делать с учетом неатомарности, поскольку указатели в данном случае readonly
100500 раз об этом говорили, но давно, лет 6-7 назад. Пора обновлять rolleyes.gif
например.
Код
volatile char *r_pos, w_pos;
char * nonatomic_ptr(char ** dsc)
{
  char * res = *dsc;
  while(res != *dsc) res = *dsc;
  return res;
}

char *p  = nonatomic_ptr(&r_pos);

если оптимизатор выкидывает - dsc должен быть volatile, наверное
Alexashka
Цитата(jcxz @ Oct 4 2015, 10:47) *
Конечно не нужно никаких "количеств байт". Достаточно двух указателей (чтения/записи). И достаточно операции чтения/записи этих указателей сделать атомарными. И для вычисления кол-ва байт достаточно найти разность указателей (с учётом их цикличности), не нужно никаких "побитных умножений".
Неужто запрет прерывания на пару команд так влияет на выполнение алгоритма??? Тогда у Вас вероятно неправильно построен алгоритм.

Запрет прерывания на пару команд не повлияет я так думаю, но вычисление разницы двухбайтных чисел с последующим умножением на маску это всё же не 2 операции, с другой стороны как я уже писал, проблема не в этом, а в том, что при каждом таком обращении нужно добавлять запрет и разрешение прерывания, либо оборачивать это в функцию, плюс нужно следить -если изменил прерывание например с АЦП на UART, то нужно переписывать функции чтения переменной, а также чтения/записи в буфер.
А вот сделать чтение двухбайтного указателя атомарной операцией невозможно, поскольку ядро 8-разрядное sm.gif Ну и до кучи объясните как сделать это
Цитата
И для вычисления кол-ва байт достаточно найти разность указателей (с учётом их цикличности), не нужно никаких "побитных умножений".

Допустим есть буфер, он занимает пространство от 0 до 2047, указатель есно двухбайтный, если мы возьмем разницу двух указателей например как "0"-"1" (значит в буфере 2047 байт данных) получим 0xFFFF, хотя должно быть 2047.

Цитата(_Pasha @ Oct 4 2015, 14:27) *
можно проверку делать с учетом неатомарности, поскольку указатели в данном случае readonly

Поясните пожалуйста, что значит readonly если я как читаю указатель, так и пишу его (инкрементирую) wacko.gif
И что делает эта строчка
Код
while(res != *dsc) res = *dsc;
К сожалению не читал обсуждение темы, о которой Вы говорите, если есть ссылочка прошу направить в нужном направлении sm.gif
jcxz
Цитата(Alexashka @ Oct 5 2015, 03:53) *
Запрет прерывания на пару команд не повлияет я так думаю, но вычисление разницы двухбайтных чисел с последующим умножением на маску это всё же не 2 операции, с другой стороны как я уже писал, проблема не в этом, а в том, что при каждом таком обращении нужно добавлять запрет и разрешение прерывания, либо оборачивать это в функцию, плюс нужно следить -если изменил прерывание например с АЦП на UART, то нужно переписывать функции чтения переменной, а также чтения/записи в буфер.

ЗАЧЕМ???? wacko.gif

Цитата(Alexashka @ Oct 5 2015, 03:53) *
А вот сделать чтение двухбайтного указателя атомарной операцией невозможно, поскольку ядро 8-разрядное sm.gif Ну и до кучи объясните как сделать это

Сделать чтение двухбайтового числа на 8-разрядном процессоре атомарным (при отсутствии в нём команды чтения 16-разрядных чисел), элементарно:
u16 i;
__disable_interrupt();
i = var;
__enable_interrupt();
всё!

Цитата(Alexashka @ Oct 5 2015, 03:53) *
Допустим есть буфер, он занимает пространство от 0 до 2047, указатель есно двухбайтный, если мы возьмем разницу двух указателей например как "0"-"1" (значит в буфере 2047 байт данных) получим 0xFFFF, хотя должно быть 2047.


Код
//предполагаем размерность int >=16бит
int volatile rpos = 0, wpos = 0;
u8 buf[N];

int read()
{
  int ir = rpos;
  int iw = wpos;
  if (ir == iw) return -1;
  if (--ir < 0) ir = sizeof(buf) - 1;
  iw = buf[ir];
  rpos = ir;
  return iw;
}

int write(int data)
{
  int ir = rpos;
  int iw = wpos;
  if (--iw < 0) iw = sizeof(buf) - 1;
  if (ir == iw) return -1;
  buf[iw] = data;
  wpos = iw;
  return 0;
}

//кол-во данных в кольц. буфере
int occupy()
{
  int ir = rpos;
  int iw = wpos, i;
  if ((i = ir - iw) < 0) i += sizeof(buf);
  return i;
}

//свободное место в кольц. буфере
int free()
{
  int ir = rpos;
  int iw = wpos, i;
  if ((i = iw - ir - 1) < 0) i += sizeof(buf);
  return i;
}

Это классика.
Если операции чтения/записи wpos, rpos в данном CPU атомарны, то можно использовать read(), write() в разных процессах/ISR как есть, без запретов прерываний.
Только обязательно read() и write() каждая должна вызываться только из одного процесса!
Если чтение/запись указателей неатомарны, то необходимо сделать их таковыми с помощью например запретов прерываний. В процессе, вызывающем read(), запрет прерываний необходим
при чтении wpos и записи rpos. Для write() - наоборот. Для occupy() и free() - аналогично - запрет прерываний для чтения того указателя, который модифицируется в другом процессе.

Цитата(_Pasha @ Oct 4 2015, 17:27) *
можно проверку делать с учетом неатомарности, поскольку указатели в данном случае readonly
100500 раз об этом говорили, но давно, лет 6-7 назад. Пора обновлять rolleyes.gif
например.

Это то же самое, что просто запрет прерывания на время чтения *dsc. Никакого преимущества не даёт по сранению с обычным запретом прерываний.
По скорости может быть как медленнее так и быстрее запрета - в зависимости от CPU.
Alexashka
Цитата(jcxz @ Oct 5 2015, 07:12) *
ЗАЧЕМ???? wacko.gif
... элементарно:
u16 i;
__disable_interrupt();
i = var;
__enable_interrupt();
всё!

<РУКАЛИЦО> Я так и делаю (см.первый пост) и уже дважды объяснил почему мне не нравится этот метод.

Цитата
Если чтение/запись указателей неатомарны, то необходимо сделать их таковыми с помощью например запретов прерываний. В процессе, вызывающем read(), запрет прерываний необходим
при чтении wpos и записи rpos. Для write() - наоборот. Для occupy() и free() - аналогично - запрет прерываний для чтения того указателя, который модифицируется в другом процессе.
Вот об этом и речь -прикиньте, сколько таких мест, где нужно локально блокирова/разрешать прерывание. Можно конечно проще -блокировать прерывание перед вызовом функции read/write/free, но тогда задержка обработки прерывания станет недопустимо большой. Есть у меня идея использовать теневые регистры, которые будут обновляться в прерывании в том случае, если в основном теле к ним не производится обращение, но это уже в другой раз, пока что обошелся локальной блокировкой прерываний.
Посмотрел Ваш код, в принципе у меня примерно также, только не понял зачем Вы копируете глобальные rpos, wpos в локальные ir,iw ведь можно работать непосредственно с глобальными?
Да, к слову, указатели не использую, слишком медленно они обрабатываются в 8051: сначала ф-я чтения по указателю должна определить какую область памяти адресует указатель, а затем перейти к соответствующей команде чтения (там вроде как 4 варианта обращения к памяти и соответственно 4 разные ассемблерные команды) , с массивом такой ерунды не происходит -компилятор уже на этапе компиляции знает к какому типу памяти мы обращаемся поэутому все делается очень быстро.
jcxz
Цитата(Alexashka @ Oct 5 2015, 18:25) *
-прикиньте, сколько таких мест, где нужно локально блокирова/разрешать прерывание.

Прикинул. От одного до двух мест в каждой моей функции. Разве это критично?

Цитата(Alexashka @ Oct 5 2015, 18:25) *
Посмотрел Ваш код, в принципе у меня примерно также, только не понял зачем Вы копируете глобальные rpos, wpos в локальные ir,iw ведь можно работать непосредственно с глобальными?

Потому что rpos и wpos объявлены с volatile и если написать i = rpos - wpos, то компилятор выдаст варнинг и будет прав.
К тому-же в варианте с копированием легко сделать нужное чтение атомарным с помощью запрета прерывания или так как предложил _Pasha.

Цитата(Alexashka @ Oct 5 2015, 18:25) *
Да, к слову, указатели не использую, слишком медленно они обрабатываются в 8051:

У меня там и нет указателей.
Сергей Борщ
Цитата(Alexashka @ Oct 5 2015, 15:25) *
слишком медленно они обрабатываются в 8051: сначала ф-я чтения по указателю должна определить какую область памяти адресует указатель, а затем перейти к соответствующей команде чтения (там вроде как 4 варианта обращения к памяти и соответственно 4 разные ассемблерные команды)
Все три известные мне компилятора для x51 (Кейл, Иар и SDCC) позволяли объявлять указатель на конкретный тип памяти. Такие указатели и меньше места занимают и работают без этого ветвления. Посмотрите документацию на свой компилятор, наверняка они там есть.
jcxz
Цитата(Alexashka @ Oct 5 2015, 18:25) *
Посмотрел Ваш код, в принципе у меня примерно также, только не понял зачем Вы копируете глобальные rpos, wpos в локальные ir,iw ведь можно работать непосредственно с глобальными?

И что значит "работать с глобальными"? Там где они модифицируются, можно делать только так, как я написал. А не напрямую глобальные.
Если Вы этого не поняли, значит не поняли ничего в моих функциях.
_Pasha
Цитата(Alexashka @ Oct 5 2015, 00:53) *
Поясните пожалуйста, что значит readonly если я как читаю указатель, так и пишу его (инкрементирую) wacko.gif
И что делает эта строчка
Код
while(res != *dsc) res = *dsc;
К сожалению не читал обсуждение темы, о которой Вы говорите, если есть ссылочка прошу направить в нужном направлении sm.gif

не помню ссылочек уже.
Мы читаем указатель, но не запрещаем прерывания. Указатель считается валидным, если за два чтения подряд он не был изменен процессом более высокого приоритета. Это и есть неатомарное чтение. Быстро ли, медленно ли? Наверное, если это имеет значение - пора утяжелять прерывание.
Alexashka
Цитата(jcxz @ Oct 5 2015, 15:58) *
Там где они модифицируются, можно делать только так, как я написал. А не напрямую глобальные.
Если Вы этого не поняли, значит не поняли ничего в моих функциях.

Да вроде понятно всЁ. Ну я например работаю непосредственно с глобальными и вроде все работает (блокирую прерывание непосредственно перед сравнением двух глобальных переменных).
С копированием получается наглядней в том смысле что имеем только 2 места где нужно ставить блокировку прерывания, и к локальной переменной можно обращаться сколько угодно раз. Я правильно понимаю идею?

Цитата(Сергей Борщ @ Oct 5 2015, 15:53) *
Все три известные мне компилятора для x51 (Кейл, Иар и SDCC) позволяли объявлять указатель на конкретный тип памяти. Такие указатели и меньше места занимают и работают без этого ветвления. Посмотрите документацию на свой компилятор, наверняка они там есть.

Проверил- и действительно: добавил спецификатор памяти в объявление указателя и всё лишнее в коде исчезло. Вот оно как оказывается, Михалыч (с) laughing.gif Спасибо за информацию, Сергей! a14.gif

Цитата(_Pasha @ Oct 5 2015, 16:36) *
Мы читаем указатель, но не запрещаем прерывания. Указатель считается валидным, если за два чтения подряд он не был изменен процессом более высокого приоритета. Это и есть неатомарное чтение. Быстро ли, медленно ли? Наверное, если это имеет значение - пора утяжелять прерывание.

Понял идею, но это ведь можно провернуть не только с указателем, но и с любой переменной?
Добавлено: попробовал Вашу функцию, вроде работает, только выдает предупреждение
Цитата
Pointer truncation
A spaced pointer has been assigned some constant value which exceeds the
range covered by the pointers memory space. For example:

char idata *p1 = 0x1234; /* result is 0x34 */
jcxz
Цитата(Alexashka @ Oct 6 2015, 13:29) *
Да вроде понятно всЁ. Ну я например работаю непосредственно с глобальными и вроде все работает (блокирую прерывание непосредственно перед сравнением двух глобальных переменных).
С копированием получается наглядней в том смысле что имеем только 2 места где нужно ставить блокировку прерывания, и к локальной переменной можно обращаться сколько угодно раз. Я правильно понимаю идею?

Не только в сравнении дело.
Я не понимаю что значит "заменить на глобальные". Если просто тупо подменить в моих функциях все использования локальных на соответствующие им глобальные - работать не будет.
Alexashka
Цитата(jcxz @ Oct 6 2015, 11:10) *
просто тупо подменить в моих функциях все использования локальных на соответствующие им глобальные - работать не будет.

Загадка rolleyes.gif...

В предположении, что read() не вызывается и rpos не модифицируется в прерывании:
Код
int read()
{
__disable_interrupt();
  if (rpos == wpos) return -1;
__enable_interrupt();
  if (--rpos < 0) rpos = sizeof(buf) - 1;
  return  buf[rpos];
}

Не будет работать??
MrYuran
Цитата(jcxz @ Oct 5 2015, 07:12) *
Это то же самое, что просто запрет прерывания на время чтения *dsc. Никакого преимущества не даёт по сранению с обычным запретом прерываний.
По скорости может быть как медленнее так и быстрее запрета - в зависимости от CPU.

Преимущество - не надо городить критические секции.
_Pasha
Цитата(Alexashka @ Oct 6 2015, 10:29) *
Добавлено: попробовал Вашу функцию, вроде работает, только выдает предупреждение

Ну я как бы тоже "из головы" писал, не с реального примера. У меня не случались неатомарные чтения по указателю. Мог и что-то упустить.
Alexashka
Цитата(_Pasha @ Oct 6 2015, 14:51) *
Ну я как бы тоже "из головы" писал, не с реального примера. У меня не случались неатомарные чтения по указателю. Мог и что-то упустить.

Да нет все ок, разобрался sm.gif
Заменил
Код
char *p  = nonatomic_ptr(&r_pos);

на
Код
char *p;
p = nonatomic_ptr(&r_pos);

и ашИпка ушла
jcxz
Цитата(MrYuran @ Oct 6 2015, 16:49) *
Преимущество - не надо городить критические секции.

И что такого страшного в критических секциях????
Valentine Loginov
Вариант с так называемым "nonatomic" по-русски называется костыль. Если есть проблемы с атомарным доступом к индексам кольцевого буфера - нужно задуматься о разделении доступа к самому буферу через флаг с атомарным доступом. Вы слабо описали задачу, т.к. совершенно непонятно где именно мешает атомарность.
zltigo
QUOTE (Valentine Loginov @ Oct 7 2015, 10:13) *
нужно задуматься о разделении доступа к самому буферу через флаг с атомарным доступом.

Задумался...sm.gif получил замену атомарной работы с указателями на замену работы с атомарным флагом и добавление самого флага sm.gif sm.gif sm.gif
На самом деле атомарная работа с флагами может быть еще более гнусной вещью. У заметного количества контроллеров работа с модификацией памяти категорически не атомарная sad.gif.
И при попытке изменить флаг, который меняется и в прерывании наступает &*%&^*%
Valentine Loginov
Идея в том, что атомарный флаг делается тоже через критические секции, другое дело, что к нему обращается программа куда реже, чем к индексам буфера, за счет чего снижается количество критических секций.
Alexashka
Цитата(Valentine Loginov @ Oct 8 2015, 11:35) *
Идея в том, что атомарный флаг делается тоже через критические секции, другое дело, что к нему обращается программа куда реже, чем к индексам буфера, за счет чего снижается количество критических секций.

Не знаю может это тоже самое, но у меня была была идея такая: в основной программе работаем не с самими индексами буфера, а с их копиями, причем перед обращением к переменным-копиями устанавливаем спец.флаг занятости, потом сбрасываем его. В прерывании текущее значение индекса копируется в переменную-копию в том случае, если флаг занятости не установлен, если же он установлен -копирование не производится (оно произойдет при последующих заходах в прерывание). Есть однако вероятность (хоть и небольшая, но есть) что при каждом заходе в прерывание флаг занятости будет установлен и мы так и не сможем обновить наши копии индексов. Однако в этом случае проблема с атомарностью не возникает, т.е. критические секции вообще не нужны и нет задержки входа в прерывание.
jcxz
Цитата(Alexashka @ Oct 8 2015, 13:58) *
Есть однако вероятность (хоть и небольшая, но есть) что при каждом заходе в прерывание флаг занятости будет установлен и мы так и не сможем обновить наши копии индексов. Однако в этом случае проблема с атомарностью не возникает, т.е. критические секции вообще не нужны и нет задержки входа в прерывание.

Есть вероятность, что Ваше ПО сейчас работает, а через 10 секунд - есть вероятность, что не работает. Сейчас работает, а если добавить ещё строчку в исходник - есть вероятность, что не заработает.
Вероятно наиболее вероятное место такому вероятному ПО - на помойке wink.gif
zltigo
QUOTE (Valentine Loginov @ Oct 8 2015, 10:35) *
Идея в том, что атомарный флаг делается тоже через критические секции, другое дело, что к нему обращается программа куда реже...

Не бывает так, что какой-то флаг, который что-то вне прерывания прикрывает, используется реже, чем то, что он прикрывает.
Посему лишняя сущность в таких простых задачах. Если речь идет о множественных действиях каким-то перемеными, тогда да - экономнее один раз взвести-уронить, чем заниматься оформлением в критичекие секции каждой из переменных. Хотя и в таком случае нужно думать о простом расширении критической секции.

Ну а вообше не забываем о том, что кроме огульного запрещения всех прерываний запрещать в критической секции можно ТОЛЬКО конкретные источники прерываний с которыми может быть конфликт.

ViKo
Цитата(_Pasha @ Oct 4 2015, 14:27) *
Код
volatile char *r_pos, w_pos;
char * nonatomic_ptr(char ** dsc)
{
  char * res = *dsc;
  while(res != *dsc) res = *dsc;
  return res;
}

char *p  = nonatomic_ptr(&r_pos);

если оптимизатор выкидывает - dsc должен быть volatile, наверное

Читать и сравнивать целиком 2-байтовый указатель - неоптимально. Можно прочитать старший байт, затем младший, затем снова старший, и если он не изменился, значит, 2-байтовое число верное.
_Pasha
Цитата(ViKo @ Oct 8 2015, 12:31) *
Читать и сравнивать целиком 2-байтовый указатель - неоптимально. Можно прочитать старший байт, затем младший, затем снова старший, и если он не изменился, значит, 2-байтовое число верное.

Может и так rolleyes.gif
Но погружение на уровень байтов - не кроссплатформенно. Тут кому что важнее
Alexashka
Цитата(jcxz @ Oct 8 2015, 13:09) *
Есть вероятность, что Ваше ПО сейчас работает, а через 10 секунд - есть вероятность, что не работает...

Да это понятно, только объясните чем этот механизм отличается от предложенного с разделением доступа?
jcxz
Цитата(Alexashka @ Oct 9 2015, 00:17) *
Да это понятно, только объясните чем этот механизм отличается от предложенного с разделением доступа?

Тем, что если делать корректно, то работает 100%-но.
ASN
Alexashka
Так и не понял, почему запрет прерываний так критичен?
Аудиосигнал - это 8 кГц. Тактовая частота современного ОМЭВМ с архитектурой 8051 - не менее 25 МГц. Цикл команды работы с внешней памятью - не более 5 тактов.
То есть, количество команд между отсчётами (в самом худшем случае) - более 500.
Почему добавление тройки команд так сильно "тормозит" обработку прерываний?
Может лучше, как Вам и предложили, работать полными буферами по 256 байт? Тогда все операции будут байтовые и вызываться обработчик будет в 256 раз реже.
Valentine Loginov
Вообще долгая работа с данными в прерывании плохо. В прерывании будим поток (для однопоточки меняем состояние машины состояний), выходим из прерывания и выполняем всю работу сразу.
Если же работы с данными мало и это не раздувает прерывание, то использование критических секций не должно создавать каких-либо проблем.
Alexashka
Цитата(Valentine Loginov @ Oct 9 2015, 10:51) *
Вообще долгая работа с данными в прерывании плохо. В прерывании будим поток (для однопоточки меняем состояние машины состояний), выходим из прерывания и выполняем всю работу сразу.
Если же работы с данными мало и это не раздувает прерывание, то использование критических секций не должно создавать каких-либо проблем.

Я хочу понять логику работы с семафорами. Допустим в прерывании читаю из буфера 1 байт, это действие приоритетное, его откладывать нельзя. В основном теле я пишу в буфер тоже по 1 байту за раз. Также в прерывании и в основном теле мне нужно знать сколько байт в буфере имеется и не произошло ли переполнение (для этого мне нужно обращаться к указателям головы и хвоста буфера, что собственно и хотелось бы сделать без использования запретов прерываний). Как в таком случае нужно задействовать семафоры?

Цитата(ASN @ Oct 9 2015, 10:05) *
Alexashka
Так и не понял, почему запрет прерываний так критичен?
Аудиосигнал - это 8 кГц. Тактовая частота современного ОМЭВМ с архитектурой 8051 - не менее 25 МГц. Цикл команды работы с внешней памятью - не более 5 тактов.
То есть, количество команд между отсчётами (в самом худшем случае) - более 500.
Почему добавление тройки команд так сильно "тормозит" обработку прерываний?

Просто не хочу использовать этот метод вот и все. Хочу чтобы была единая логика в независимости от источника прерываний и типа переменных, потому что функции работы с кольцевым буфером использую постоянно и в разных задачах и каждый раз приходится их допиливать-перепиливать, и это уже достало.
Да и к тому же сейчас у меня частота 22050 Гц, потом планирую добавить буфер для ЦАП, который будет писать в него на частоте 200кГц и задержки будут совсем нежелательны. Дело не в том, что я не успею вывести очередной отсчет, а в том что будет дрожание момента вывода отсчета, отсюда искажения в спектре.
zltigo
QUOTE (Alexashka @ Oct 9 2015, 10:40) *
Я хочу понять логику работы с семафорами.

Лучше поймите, как стоить системы БЕЗ семафоров. Семафоры это костыли. Я как-то о семафорах не думал - ну пользовал и пользовал. А в году так 2004 попалась в руки FreeRTOS, где семафоры сделаны были, как макросы на очередях. Подумал, какой ужас, еще раз подумал и понял, что лишняя сущность.

Alexashka
Цитата(zltigo @ Oct 9 2015, 12:33) *
Лучше поймите, как стоить системы БЕЗ семафоров.

А как бы Вы сделали? rolleyes.gif
rudy_b
Цитата(Alexashka @ Oct 3 2015, 22:40) *
Добрый вечер sm.gif
Что имеем: 8 битный проц, программа в прерывании читает из кольцевого буфера, указатель чтения -просто индекс массива, двухбайтное число. В основном теле мне приходит поток байт, который я контролирую (запускаю/останавливаю поток) и размещаю байты в этом же кольцевом буфере по другому указателю -указателю записи (тоже индекс массива)...

Как-то странно выглядит. Обычно все наоборот делается - приходящие байты запихиваются в буфер по прерываниям (хард) и ставится (или суммируется) флаг+(возможно) управление потоком.
Тут можно работать с указателями просто - в функции обработки прерываний другие прерывания запрещены (обычно).

А считывание из буфера идет в main loop при наличии флага (или ненулевого счетчика).
При этом все операции с указателями (и флагом) делаются в main loop с кратковременным запретом прерываний.
Есть какая-то специфика?
zltigo
QUOTE (Alexashka @ Oct 9 2015, 16:09) *
А как бы Вы сделали? rolleyes.gif

Я не могу ответить на вопрос, куда пришить пуговицу не видя всего костюма. Но разумный совет здесь http://electronix.ru/forum/index.php?showt...t&p=1369549
Alexashka
Цитата(rudy_b @ Oct 9 2015, 17:19) *
Как-то странно выглядит. Обычно все наоборот делается - приходящие байты запихиваются в буфер по прерываниям (хард) и ставится (или суммируется) флаг+(возможно) управление потоком.
Тут можно работать с указателями просто - в функции обработки прерываний другие прерывания запрещены (обычно).

А считывание из буфера идет в main loop при наличии флага (или ненулевого счетчика).
При этом все операции с указателями (и флагом) делаются в main loop с кратковременным запретом прерываний.
Есть какая-то специфика?

Не ну честно, господа совершенно не желают ничего читать. Объяснял уже трижды, но каждый раз задаются одни и те же вопросы. Ладно, Вам лично объясню в четвертый раз sm.gif Данные приходят по UART, я их совершенно не напрягаясь успеваю забирать в главном цикле и складывать в кольцевой буфер. Скорость UART 230кБит. В прерывании (которое вырабатывает таймер) я что делаю - читаю из буфера одно значение (сэмпл) и тут же записываю его в регистр PCA для обновления генератора ШИМ сигнала. Частота генерации 22050. Если это делать не в прерывании, то будет совершенно очевидный джиттер. Это не обсуждается.
Поскольку частота сыпанья байтов из UART ровно 23000 в секунду, т.е чуть больше чем скорость их чтения (22050) приходится периодически прерывать этот поток отсылая в компУтер команду "ПОГОДИ!", когда буфер чутка освободится я шлю команду "ПЕРЕДАВАЙ!"
Ясно, что можно обработку обоих потоков сделать в прерывании, но мне удобней работать с UART по опросу флагов. Да и вообще дело не в данной конкретной задаче, а просто назрело - захотел переделать логику работы с буферами чтобы было удобнее, тут уже было озвучено много разных вариантов, но у всех у них есть достоинства и недостатки sm.gif
Так что обсуждение еще продолжается.
Да, помимо всего прочего много нового для себя узнал, за что всем коллегам большое спасибо a14.gif
ViKo
Так зачем "чутка освободится..."? Сделайте 2 буфера. В один принимаете, из другого кидаете в ШИМ. Когда в том, что в ШИМ, данные закончатся, переключаетесь на тот, в который принимали. Он уже будет весь забит. И т.д. По-моему, это называется "двойная буферизация". А принимайте тоже по прерыванию, только приоритет задайте ниже, чем у таймера выкидывания. И... это, зачем большие буфера? sm.gif sm.gif
rudy_b
Цитата(ViKo @ Oct 9 2015, 21:19) *
Так зачем "чутка освободится..."? Сделайте 2 буфера. В один принимаете, из другого кидаете в ШИМ...
...А принимайте тоже по прерыванию, только приоритет задайте ниже, чем у таймера выкидывания. И... это, зачем большие буфера? sm.gif sm.gif

Все так, но не нужно два буфера - нужен только один - кольцевой.
И, как и предложено, и принимать и считывать по прерываниям. Если они одного уровня - друг друга прерывать не будут, обычно на 8-ми битках так сделано. Приоритет работает только при одновременном возникновении двух запросов - вот тогда первым будет более приоритетный.
Т.е. при работе с указателями вообще никаких проблем не будет.

Большой размер буфера иногда нужен при работе с писюком для разравнивания данных - он иногда любит отключать задачу чуть не на секунду.

Джиттера все равно не избежать, можно только снизить его до минимума, если сделать прерывание считывания более высокоуровневым (т.е. разрешить ему прерывать прерывание записи).
Тогда есть два варианта. Можно аккуратно работать с указатем записи (кратковременный запрет прерываний) - но это джиттер, хоть и малый.
А можно и без запрета прерываний.
За буфером следит более высокоуровневое прерывание и оно, при приближении к заполнению, ставит (и снимает, с командой продолжения приема или иначе) флаг, который более низкоприоритеное прерывание использует для остановки входного потока.
Указатель чтения оно меняет как ему вздумается (это его параметр), а вот указателей записи - два.
Один из них использует только низкоприоритетное прерывание, а второй - более высокоприоритетное.
Периодически происходит синхронизация этих указателей в старшем прерывании - для определения остатка свободного места в буфере. Тут может возникнуть болтанка и нужен запас свободного места в буфере на максимальное число неудачных попыток синхронизации (ну и плюс запас).
Для синхронизации, младшее прерывание меняет свой указатель поэтапно, каждый раз выставляя и снимая бит валидности в отдельной переменной.
Т.е., перед изменением оно сначала снимает бит валидности, затем меняет указатель, затем ставит бит валидности. Это можно делать и для каждого байта по отдельности.
Старший приоритет синхронизирует свой указатель только если стоит бит валидности.
Это, практически, критическая секция, но специфическая, с пропуском, но без зависания.

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

Можно использовать и несколько фокусов типа - при неудачной синхронизации инкрементировать указатель приема в старшем прерывании (это снизит болтанку буфера) и т.п.

Заморочно, конечно, но джиттер будет минимален.

Аналогичное взаимодействие можно организовать и между main loop и прерыванием считывания. Тогда будет только одно прерывание и джиттер будет меньше.
Dog Pawlowa
Какие компоненты в систему заложишь, так она и поплывет.

PC + восьмибитник + требование к джиттеру = бульк

Извините за ОФФ.
Alexashka
Цитата(ViKo @ Oct 9 2015, 22:19) *
Так зачем "чутка освободится..."? Сделайте 2 буфера. В один принимаете, из другого кидаете в ШИМ. Когда в том, что в ШИМ, данные закончатся, переключаетесь на тот, в который принимали. Он уже будет весь забит. И т.д. По-моему, это называется "двойная буферизация". А принимайте тоже по прерыванию, только приоритет задайте ниже, чем у таймера выкидывания. И... это, зачем большие буфера? sm.gif sm.gif

Это не будет работать. Если один источник пишет быстрее, чем другой читает, то рано или поздно (сколько бы у вас ни было буферов) произойдет переполнение буфера. Скорости обоих процессов должны быть в среднем одинаковы.
А по поводу прерывания. Если приоритеты обоих одинаковы, то получаем джиттер в виде задержки входа в прерывание ШИМ равное по времени выполнению прерывания UART.
В случае если прерывание UART имеет меньший приоритет по сравнению с ШИМ, то будет тоже самое в плане критических секций - придется в прерывании UART временно блокировать прерывание ШИМ.

Цитата(rudy_b @ Oct 10 2015, 03:08) *
Большой размер буфера иногда нужен при работе с писюком для разравнивания данных - он иногда любит отключать задачу чуть не на секунду.

Именно поэтому использую почти всю встроенную память, а чего ее жалеть ради хорошего дела sm.gif
Цитата(rudy_b @ Oct 10 2015, 03:08) *
Джиттера все равно не избежать, можно только снизить его до минимума, если сделать прерывание считывания более высокоуровневым (т.е. разрешить ему прерывать прерывание записи).
Но тогда это будет работать также, как если UART будет в основном теле.
Цитата(rudy_b @ Oct 10 2015, 03:08) *
Тогда есть два варианта. Можно аккуратно работать с указатем записи (кратковременный запрет прерываний) - но это джиттер, хоть и малый.
Сейчас так и делаю. Причем что интересно полностью избежать запрета прерываний нельзя - его можно избежать когда в основном теле читаем переменную, изменяемую в прерывании -тут помогает метод двух чтений, который предложил Pasha. А вот в случае когда в прерывании читается переменная, изменяемая в основном теле (это нужно чтобы определить не нагнало ли чтение запись) этот метод не поможет.
Цитата(rudy_b @ Oct 10 2015, 03:08) *
А можно и без запрета прерываний.
...
Периодически происходит синхронизация этих указателей в старшем прерывании - для определения остатка свободного места в буфере.
...
Для синхронизации, младшее прерывание меняет свой указатель поэтапно, каждый раз выставляя и снимая бит валидности в отдельной переменной.
Т.е., перед изменением оно сначала снимает бит валидности, затем меняет указатель, затем ставит бит валидности. Это можно делать и для каждого байта по отдельности.
Старший приоритет синхронизирует свой указатель только если стоит бит валидности.
Это, практически, критическая секция, но специфическая, с пропуском, но без зависания.
...Аналогичное взаимодействие можно организовать и между main loop и прерыванием считывания.

Посмотрите, что я писал в сообщении #27, но то мое предложение оплевали и посоветовали выкинуть в утиль sm.gif.
Это не сложно, но опять-таки не универсальное решение, потому как требует персональной корректировки функций по работе с кольцевым буфером под конкретную задачу.

Цитата(Dog Pawlowa @ Oct 10 2015, 07:51) *
Какие компоненты в систему заложишь, так она и поплывет.

PC + восьмибитник + требование к джиттеру = бульк

Не согласен с Вами. Восьмибитник отлично справляется, да у него нет канала DMA который можно накрутить в ШИМ имея какойнибудь Кортекс, но для моей задачи вполне хватает. И потом эта работа -мое хобби, а мне 8-битники нравятся гораздо больше, чем 32разрядные монстры с DMA контроллерами, ускорителями памяти и прочим и прочим.
Имеющихся аппаратных возможностей контроллера более чем достаточно для этой задачи.
ViKo
Цитата(Alexashka @ Oct 10 2015, 14:19) *
Это не будет работать. Если один источник пишет быстрее, чем другой читает, то рано или поздно (сколько бы у вас ни было буферов) произойдет переполнение буфера. Скорости обоих процессов должны быть в среднем одинаковы.

Так вы порциями (размером с целый буфер) выкидывайте с PC, по команде от вашего устройства. Как только один буфер использовали, переходите на другой, и давайте команду PC передавать данных на целый буфер (в который только что освободился). И не надо ломать голову, когда притормаживать, когда "газовать".
И сравнивать указатели, кто кого догоняет, не придется. Просто проходите по буферу от начала до конца. Один указатель записи, бежит по одному буферу, другой указатель чтения, бежит по другому. Красиво? Указатель записи дойдет до конца раньше и притихнет (в это время его можно на начало другого буфера переключить, но писать пока нечего). Когда указатель чтения дойдет до конца, переключаете его на другой буфер, и даете команду компьютеру выдать новую порцию.
Tanya
Цитата(Alexashka @ Oct 10 2015, 14:19) *
И потом эта работа -мое хобби, а мне 8-битники нравятся гораздо больше, чем 32разрядные монстры с DMA контроллерами, ускорителями памяти и прочим и прочим.
Имеющихся аппаратных возможностей контроллера более чем достаточно для этой задачи.

Вот Вы уже (и другие...) потратили на это обсуждение столько времени, что его цена многократно перекрывает разницу в цене между монстром и достаточным контроллером. Если бы Вы планировали миллионную серию, это имело бы смысл. А так - нет.
Alexashka
Цитата(ViKo @ Oct 10 2015, 15:39) *
Красиво?

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

Цитата(Tanya @ Oct 10 2015, 15:47) *
Вот Вы уже (и другие...) потратили на это обсуждение столько времени, что его цена многократно перекрывает разницу в цене между монстром и достаточным контроллером. Если бы Вы планировали миллионную серию, это имело бы смысл. А так - нет.

Таня, Вы меня удивляете! sm.gif Какая цена, о чем Вы говорите? Деньги не имеют ровным счетом никакого значения. Я не планирую ничего выпускать, хобби -это для удовольствия, тут дело в самом процессе. Хотя конечно согласен, результат тоже хочется получить. И освоить очередной камень. Но я консерватор sm.gif и больше люблю то, к чему привык. Так что извиняйте, я буду делать на том, что у меня сейчас. Как говорят рыбаки -время за рыбалкой (и я бы сказал - за паяльником) в счет жизни не входит cool.gif Жаль если Вы этого не понимаете.
Tanya
Цитата(Alexashka @ Oct 10 2015, 15:09) *
Жаль если Вы этого не понимаете.


Понимаю, что Вам не результат нужен, а процесс борьбы.
ViKo
Можно и одним буфером обойтись, если разбить его на 2 части. А дальше, как описал.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.