|
|
  |
Работа с переменными в прерывании и основном теле, жутко неудобно выходит |
|
|
|
Oct 3 2015, 19:40
|

Практикующий маг
     
Группа: Свой
Сообщений: 3 634
Регистрация: 28-04-05
Из: Дубна, Моск.обл
Пользователь №: 4 576

|
Добрый вечер Что имеем: 8 битный проц, программа в прерывании читает из кольцевого буфера, указатель чтения -просто индекс массива, двухбайтное число. В основном теле мне приходит поток байт, который я контролирую (запускаю/останавливаю поток) и размещаю байты в этом же кольцевом буфере по другому указателю -указателю записи (тоже индекс массива). До этих пор все замечательно, но чтобы управлять потоком мне нужно знать сколько байт содержится в буфере, для этого я беру разницу между указателями записи и чтения и побитно умножаю ее на (размер_массива - 1). Эту разницу приходится брать в нескольких местах программы, при этом на время вычисления нужно блокировать прерывание, поскольку операция не атомарная, а одна из переменных модифицируется в прерывании. Если этого не делать происходит глюк, если делать -появляется задержка обработки прерывания, а это нехорошо, т.к в нем происходит формирования аудио сигнала. Да и некрасиво это и неудобно -следить какую перменную обрабатываю, нужно или нет блокировать прерывание, ну и поскольку кольцевой буфер используется в различных прерываниях нужно еще и учитывать какое именно прерывание блокировать Может это както по-другому можно делать, подскажите.
|
|
|
|
|
Oct 3 2015, 19:44
|
Знающий
   
Группа: Участник
Сообщений: 750
Регистрация: 1-11-11
Пользователь №: 68 088

|
Цитата(Alexashka @ Oct 3 2015, 22:40)  Может это както по-другому можно делать, подскажите. Заведите переменную "количество байт в буфере" и обновляйте её вместе с изменением указателей чтения и записи.
--------------------
"... часами я мог наблюдать, как люди работают." (М. Горький)
|
|
|
|
|
Oct 3 2015, 19:49
|

Практикующий маг
     
Группа: Свой
Сообщений: 3 634
Регистрация: 28-04-05
Из: Дубна, Моск.обл
Пользователь №: 4 576

|
Цитата(gerber @ Oct 3 2015, 22:44)  Заведите переменную "количество байт в буфере" и обновляйте её вместе с изменением указателей чтения и записи. неа, смотрите, эта переменная тоже двухбайтная выходит. Допустим если байт в буфере больше 2000 нужно остановить поток, для этого я сравниваю ее с числом 2000, при этом сначала сравнивается младшие байты, потом старшие, если между этими операциями произойдет вызов подпрограммы, которая изменит старший байт "количество байт в буфере", то в результате сравнения можно получить очень неверный результат.
|
|
|
|
|
Oct 3 2015, 21:43
|

Профессионал
    
Группа: Свой
Сообщений: 1 292
Регистрация: 26-06-07
Пользователь №: 28 718

|
Цитата Разделите свой большой буфер на 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];
|
|
|
|
|
Oct 4 2015, 07:47
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(Alexashka @ Oct 4 2015, 01:49)  неа, смотрите, эта переменная тоже двухбайтная выходит. Допустим если байт в буфере больше 2000 нужно остановить поток, для этого я сравниваю ее с числом 2000, при этом сначала сравнивается младшие байты, потом старшие, если между этими операциями произойдет вызов подпрограммы, которая изменит старший байт "количество байт в буфере", то в результате сравнения можно получить очень неверный результат. Конечно не нужно никаких "количеств байт". Достаточно двух указателей (чтения/записи). И достаточно операции чтения/записи этих указателей сделать атомарными. И для вычисления кол-ва байт достаточно найти разность указателей (с учётом их цикличности), не нужно никаких "побитных умножений". Неужто запрет прерывания на пару команд так влияет на выполнение алгоритма??? Тогда у Вас вероятно неправильно построен алгоритм.
|
|
|
|
|
Oct 4 2015, 11:27
|
;
     
Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509

|
Цитата(jcxz @ Oct 4 2015, 10:47)  Неужто запрет прерывания на пару команд так влияет на выполнение алгоритма??? Тогда у Вас вероятно неправильно построен алгоритм. можно проверку делать с учетом неатомарности, поскольку указатели в данном случае readonly 100500 раз об этом говорили, но давно, лет 6-7 назад. Пора обновлять например. Код 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, наверное
Сообщение отредактировал _Pasha - Oct 4 2015, 11:28
|
|
|
|
|
Oct 4 2015, 21:53
|

Практикующий маг
     
Группа: Свой
Сообщений: 3 634
Регистрация: 28-04-05
Из: Дубна, Моск.обл
Пользователь №: 4 576

|
Цитата(jcxz @ Oct 4 2015, 10:47)  Конечно не нужно никаких "количеств байт". Достаточно двух указателей (чтения/записи). И достаточно операции чтения/записи этих указателей сделать атомарными. И для вычисления кол-ва байт достаточно найти разность указателей (с учётом их цикличности), не нужно никаких "побитных умножений". Неужто запрет прерывания на пару команд так влияет на выполнение алгоритма??? Тогда у Вас вероятно неправильно построен алгоритм. Запрет прерывания на пару команд не повлияет я так думаю, но вычисление разницы двухбайтных чисел с последующим умножением на маску это всё же не 2 операции, с другой стороны как я уже писал, проблема не в этом, а в том, что при каждом таком обращении нужно добавлять запрет и разрешение прерывания, либо оборачивать это в функцию, плюс нужно следить -если изменил прерывание например с АЦП на UART, то нужно переписывать функции чтения переменной, а также чтения/записи в буфер. А вот сделать чтение двухбайтного указателя атомарной операцией невозможно, поскольку ядро 8-разрядное  Ну и до кучи объясните как сделать это Цитата И для вычисления кол-ва байт достаточно найти разность указателей (с учётом их цикличности), не нужно никаких "побитных умножений". Допустим есть буфер, он занимает пространство от 0 до 2047, указатель есно двухбайтный, если мы возьмем разницу двух указателей например как "0"-"1" (значит в буфере 2047 байт данных) получим 0xFFFF, хотя должно быть 2047. Цитата(_Pasha @ Oct 4 2015, 14:27)  можно проверку делать с учетом неатомарности, поскольку указатели в данном случае readonly Поясните пожалуйста, что значит readonly если я как читаю указатель, так и пишу его (инкрементирую)  И что делает эта строчка Код while(res != *dsc) res = *dsc; К сожалению не читал обсуждение темы, о которой Вы говорите, если есть ссылочка прошу направить в нужном направлении
|
|
|
|
|
Oct 5 2015, 04:12
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(Alexashka @ Oct 5 2015, 03:53)  Запрет прерывания на пару команд не повлияет я так думаю, но вычисление разницы двухбайтных чисел с последующим умножением на маску это всё же не 2 операции, с другой стороны как я уже писал, проблема не в этом, а в том, что при каждом таком обращении нужно добавлять запрет и разрешение прерывания, либо оборачивать это в функцию, плюс нужно следить -если изменил прерывание например с АЦП на UART, то нужно переписывать функции чтения переменной, а также чтения/записи в буфер. ЗАЧЕМ???? Цитата(Alexashka @ Oct 5 2015, 03:53)  А вот сделать чтение двухбайтного указателя атомарной операцией невозможно, поскольку ядро 8-разрядное  Ну и до кучи объясните как сделать это Сделать чтение двухбайтового числа на 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 назад. Пора обновлять например. Это то же самое, что просто запрет прерывания на время чтения *dsc. Никакого преимущества не даёт по сранению с обычным запретом прерываний. По скорости может быть как медленнее так и быстрее запрета - в зависимости от CPU.
|
|
|
|
|
Oct 5 2015, 12:25
|

Практикующий маг
     
Группа: Свой
Сообщений: 3 634
Регистрация: 28-04-05
Из: Дубна, Моск.обл
Пользователь №: 4 576

|
Цитата(jcxz @ Oct 5 2015, 07:12)  ЗАЧЕМ???? ... элементарно: u16 i; __disable_interrupt(); i = var; __enable_interrupt(); всё! <РУКАЛИЦО> Я так и делаю (см.первый пост) и уже дважды объяснил почему мне не нравится этот метод. Цитата Если чтение/запись указателей неатомарны, то необходимо сделать их таковыми с помощью например запретов прерываний. В процессе, вызывающем read(), запрет прерываний необходим при чтении wpos и записи rpos. Для write() - наоборот. Для occupy() и free() - аналогично - запрет прерываний для чтения того указателя, который модифицируется в другом процессе. Вот об этом и речь -прикиньте, сколько таких мест, где нужно локально блокирова/разрешать прерывание. Можно конечно проще -блокировать прерывание перед вызовом функции read/write/free, но тогда задержка обработки прерывания станет недопустимо большой. Есть у меня идея использовать теневые регистры, которые будут обновляться в прерывании в том случае, если в основном теле к ним не производится обращение, но это уже в другой раз, пока что обошелся локальной блокировкой прерываний. Посмотрел Ваш код, в принципе у меня примерно также, только не понял зачем Вы копируете глобальные rpos, wpos в локальные ir,iw ведь можно работать непосредственно с глобальными? Да, к слову, указатели не использую, слишком медленно они обрабатываются в 8051: сначала ф-я чтения по указателю должна определить какую область памяти адресует указатель, а затем перейти к соответствующей команде чтения (там вроде как 4 варианта обращения к памяти и соответственно 4 разные ассемблерные команды) , с массивом такой ерунды не происходит -компилятор уже на этапе компиляции знает к какому типу памяти мы обращаемся поэутому все делается очень быстро.
|
|
|
|
|
Oct 5 2015, 12:51
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(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: У меня там и нет указателей.
|
|
|
|
|
Oct 5 2015, 13:36
|
;
     
Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509

|
Цитата(Alexashka @ Oct 5 2015, 00:53)  Поясните пожалуйста, что значит readonly если я как читаю указатель, так и пишу его (инкрементирую)  И что делает эта строчка Код while(res != *dsc) res = *dsc; К сожалению не читал обсуждение темы, о которой Вы говорите, если есть ссылочка прошу направить в нужном направлении  не помню ссылочек уже. Мы читаем указатель, но не запрещаем прерывания. Указатель считается валидным, если за два чтения подряд он не был изменен процессом более высокого приоритета. Это и есть неатомарное чтение. Быстро ли, медленно ли? Наверное, если это имеет значение - пора утяжелять прерывание.
Сообщение отредактировал _Pasha - Oct 5 2015, 13:38
|
|
|
|
|
Oct 6 2015, 07:29
|

Практикующий маг
     
Группа: Свой
Сообщений: 3 634
Регистрация: 28-04-05
Из: Дубна, Моск.обл
Пользователь №: 4 576

|
Цитата(jcxz @ Oct 5 2015, 15:58)  Там где они модифицируются, можно делать только так, как я написал. А не напрямую глобальные. Если Вы этого не поняли, значит не поняли ничего в моих функциях. Да вроде понятно всЁ. Ну я например работаю непосредственно с глобальными и вроде все работает (блокирую прерывание непосредственно перед сравнением двух глобальных переменных). С копированием получается наглядней в том смысле что имеем только 2 места где нужно ставить блокировку прерывания, и к локальной переменной можно обращаться сколько угодно раз. Я правильно понимаю идею? Цитата(Сергей Борщ @ Oct 5 2015, 15:53)  Все три известные мне компилятора для x51 (Кейл, Иар и SDCC) позволяли объявлять указатель на конкретный тип памяти. Такие указатели и меньше места занимают и работают без этого ветвления. Посмотрите документацию на свой компилятор, наверняка они там есть. Проверил- и действительно: добавил спецификатор памяти в объявление указателя и всё лишнее в коде исчезло. Вот оно как оказывается, Михалыч (с)  Спасибо за информацию, Сергей! Цитата(_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 */
|
|
|
|
|
Oct 6 2015, 08:41
|

Практикующий маг
     
Группа: Свой
Сообщений: 3 634
Регистрация: 28-04-05
Из: Дубна, Моск.обл
Пользователь №: 4 576

|
Цитата(jcxz @ Oct 6 2015, 11:10)  просто тупо подменить в моих функциях все использования локальных на соответствующие им глобальные - работать не будет. Загадка  ... В предположении, что read() не вызывается и rpos не модифицируется в прерывании: Код int read() { __disable_interrupt(); if (rpos == wpos) return -1; __enable_interrupt(); if (--rpos < 0) rpos = sizeof(buf) - 1; return buf[rpos]; } Не будет работать??
|
|
|
|
|
Oct 6 2015, 16:36
|

Практикующий маг
     
Группа: Свой
Сообщений: 3 634
Регистрация: 28-04-05
Из: Дубна, Моск.обл
Пользователь №: 4 576

|
Цитата(_Pasha @ Oct 6 2015, 14:51)  Ну я как бы тоже "из головы" писал, не с реального примера. У меня не случались неатомарные чтения по указателю. Мог и что-то упустить. Да нет все ок, разобрался  Заменил Код char *p = nonatomic_ptr(&r_pos); на Код char *p; p = nonatomic_ptr(&r_pos); и ашИпка ушла
|
|
|
|
|
Oct 8 2015, 07:58
|

Практикующий маг
     
Группа: Свой
Сообщений: 3 634
Регистрация: 28-04-05
Из: Дубна, Моск.обл
Пользователь №: 4 576

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

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
QUOTE (Valentine Loginov @ Oct 8 2015, 10:35)  Идея в том, что атомарный флаг делается тоже через критические секции, другое дело, что к нему обращается программа куда реже... Не бывает так, что какой-то флаг, который что-то вне прерывания прикрывает, используется реже, чем то, что он прикрывает. Посему лишняя сущность в таких простых задачах. Если речь идет о множественных действиях каким-то перемеными, тогда да - экономнее один раз взвести-уронить, чем заниматься оформлением в критичекие секции каждой из переменных. Хотя и в таком случае нужно думать о простом расширении критической секции. Ну а вообше не забываем о том, что кроме огульного запрещения всех прерываний запрещать в критической секции можно ТОЛЬКО конкретные источники прерываний с которыми может быть конфликт.
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Oct 9 2015, 07:40
|

Практикующий маг
     
Группа: Свой
Сообщений: 3 634
Регистрация: 28-04-05
Из: Дубна, Моск.обл
Пользователь №: 4 576

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

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
QUOTE (Alexashka @ Oct 9 2015, 10:40)  Я хочу понять логику работы с семафорами. Лучше поймите, как стоить системы БЕЗ семафоров. Семафоры это костыли. Я как-то о семафорах не думал - ну пользовал и пользовал. А в году так 2004 попалась в руки FreeRTOS, где семафоры сделаны были, как макросы на очередях. Подумал, какой ужас, еще раз подумал и понял, что лишняя сущность.
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Oct 9 2015, 13:19
|
Знающий
   
Группа: Свой
Сообщений: 888
Регистрация: 25-09-08
Из: Питер
Пользователь №: 40 458

|
Цитата(Alexashka @ Oct 3 2015, 22:40)  Добрый вечер Что имеем: 8 битный проц, программа в прерывании читает из кольцевого буфера, указатель чтения -просто индекс массива, двухбайтное число. В основном теле мне приходит поток байт, который я контролирую (запускаю/останавливаю поток) и размещаю байты в этом же кольцевом буфере по другому указателю -указателю записи (тоже индекс массива)... Как-то странно выглядит. Обычно все наоборот делается - приходящие байты запихиваются в буфер по прерываниям (хард) и ставится (или суммируется) флаг+(возможно) управление потоком. Тут можно работать с указателями просто - в функции обработки прерываний другие прерывания запрещены (обычно). А считывание из буфера идет в main loop при наличии флага (или ненулевого счетчика). При этом все операции с указателями (и флагом) делаются в main loop с кратковременным запретом прерываний. Есть какая-то специфика?
|
|
|
|
|
Oct 9 2015, 17:09
|

Практикующий маг
     
Группа: Свой
Сообщений: 3 634
Регистрация: 28-04-05
Из: Дубна, Моск.обл
Пользователь №: 4 576

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

Универсальный солдатик
     
Группа: Модераторы
Сообщений: 8 634
Регистрация: 1-11-05
Из: Минск
Пользователь №: 10 362

|
Так зачем "чутка освободится..."? Сделайте 2 буфера. В один принимаете, из другого кидаете в ШИМ. Когда в том, что в ШИМ, данные закончатся, переключаетесь на тот, в который принимали. Он уже будет весь забит. И т.д. По-моему, это называется "двойная буферизация". А принимайте тоже по прерыванию, только приоритет задайте ниже, чем у таймера выкидывания. И... это, зачем большие буфера?
|
|
|
|
|
Oct 9 2015, 23:08
|
Знающий
   
Группа: Свой
Сообщений: 888
Регистрация: 25-09-08
Из: Питер
Пользователь №: 40 458

|
Цитата(ViKo @ Oct 9 2015, 21:19)  Так зачем "чутка освободится..."? Сделайте 2 буфера. В один принимаете, из другого кидаете в ШИМ... ...А принимайте тоже по прерыванию, только приоритет задайте ниже, чем у таймера выкидывания. И... это, зачем большие буфера?  Все так, но не нужно два буфера - нужен только один - кольцевой. И, как и предложено, и принимать и считывать по прерываниям. Если они одного уровня - друг друга прерывать не будут, обычно на 8-ми битках так сделано. Приоритет работает только при одновременном возникновении двух запросов - вот тогда первым будет более приоритетный. Т.е. при работе с указателями вообще никаких проблем не будет. Большой размер буфера иногда нужен при работе с писюком для разравнивания данных - он иногда любит отключать задачу чуть не на секунду. Джиттера все равно не избежать, можно только снизить его до минимума, если сделать прерывание считывания более высокоуровневым (т.е. разрешить ему прерывать прерывание записи). Тогда есть два варианта. Можно аккуратно работать с указатем записи (кратковременный запрет прерываний) - но это джиттер, хоть и малый. А можно и без запрета прерываний. За буфером следит более высокоуровневое прерывание и оно, при приближении к заполнению, ставит (и снимает, с командой продолжения приема или иначе) флаг, который более низкоприоритеное прерывание использует для остановки входного потока. Указатель чтения оно меняет как ему вздумается (это его параметр), а вот указателей записи - два. Один из них использует только низкоприоритетное прерывание, а второй - более высокоприоритетное. Периодически происходит синхронизация этих указателей в старшем прерывании - для определения остатка свободного места в буфере. Тут может возникнуть болтанка и нужен запас свободного места в буфере на максимальное число неудачных попыток синхронизации (ну и плюс запас). Для синхронизации, младшее прерывание меняет свой указатель поэтапно, каждый раз выставляя и снимая бит валидности в отдельной переменной. Т.е., перед изменением оно сначала снимает бит валидности, затем меняет указатель, затем ставит бит валидности. Это можно делать и для каждого байта по отдельности. Старший приоритет синхронизирует свой указатель только если стоит бит валидности. Это, практически, критическая секция, но специфическая, с пропуском, но без зависания. Тут могут возникнуть некоторые проблемы с временными накладками, но если держать запас по буферу и блокировать прием (запись) при длительных неудачах синхронизации (буфер перестанет меняться и будет стоять бит валидности), то все разруливается. Можно использовать и несколько фокусов типа - при неудачной синхронизации инкрементировать указатель приема в старшем прерывании (это снизит болтанку буфера) и т.п. Заморочно, конечно, но джиттер будет минимален. Аналогичное взаимодействие можно организовать и между main loop и прерыванием считывания. Тогда будет только одно прерывание и джиттер будет меньше.
|
|
|
|
|
Oct 10 2015, 11:19
|

Практикующий маг
     
Группа: Свой
Сообщений: 3 634
Регистрация: 28-04-05
Из: Дубна, Моск.обл
Пользователь №: 4 576

|
Цитата(ViKo @ Oct 9 2015, 22:19)  Так зачем "чутка освободится..."? Сделайте 2 буфера. В один принимаете, из другого кидаете в ШИМ. Когда в том, что в ШИМ, данные закончатся, переключаетесь на тот, в который принимали. Он уже будет весь забит. И т.д. По-моему, это называется "двойная буферизация". А принимайте тоже по прерыванию, только приоритет задайте ниже, чем у таймера выкидывания. И... это, зачем большие буфера?  Это не будет работать. Если один источник пишет быстрее, чем другой читает, то рано или поздно (сколько бы у вас ни было буферов) произойдет переполнение буфера. Скорости обоих процессов должны быть в среднем одинаковы. А по поводу прерывания. Если приоритеты обоих одинаковы, то получаем джиттер в виде задержки входа в прерывание ШИМ равное по времени выполнению прерывания UART. В случае если прерывание UART имеет меньший приоритет по сравнению с ШИМ, то будет тоже самое в плане критических секций - придется в прерывании UART временно блокировать прерывание ШИМ. Цитата(rudy_b @ Oct 10 2015, 03:08)  Большой размер буфера иногда нужен при работе с писюком для разравнивания данных - он иногда любит отключать задачу чуть не на секунду. Именно поэтому использую почти всю встроенную память, а чего ее жалеть ради хорошего дела  Цитата(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, но то мое предложение оплевали и посоветовали выкинуть в утиль  . Это не сложно, но опять-таки не универсальное решение, потому как требует персональной корректировки функций по работе с кольцевым буфером под конкретную задачу. Цитата(Dog Pawlowa @ Oct 10 2015, 07:51)  Какие компоненты в систему заложишь, так она и поплывет.
PC + восьмибитник + требование к джиттеру = бульк Не согласен с Вами. Восьмибитник отлично справляется, да у него нет канала DMA который можно накрутить в ШИМ имея какойнибудь Кортекс, но для моей задачи вполне хватает. И потом эта работа -мое хобби, а мне 8-битники нравятся гораздо больше, чем 32разрядные монстры с DMA контроллерами, ускорителями памяти и прочим и прочим. Имеющихся аппаратных возможностей контроллера более чем достаточно для этой задачи.
|
|
|
|
|
Oct 10 2015, 11:39
|

Универсальный солдатик
     
Группа: Модераторы
Сообщений: 8 634
Регистрация: 1-11-05
Из: Минск
Пользователь №: 10 362

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

Практикующий маг
     
Группа: Свой
Сообщений: 3 634
Регистрация: 28-04-05
Из: Дубна, Моск.обл
Пользователь №: 4 576

|
Цитата(ViKo @ Oct 10 2015, 15:39)  Красиво? Да, качели из двух буферов тоже вариант. Но переделывать сильно много надо, теперь уже не буду, возможно в следующий раз так и сделаю. Цитата(Tanya @ Oct 10 2015, 15:47)  Вот Вы уже (и другие...) потратили на это обсуждение столько времени, что его цена многократно перекрывает разницу в цене между монстром и достаточным контроллером. Если бы Вы планировали миллионную серию, это имело бы смысл. А так - нет. Таня, Вы меня удивляете!  Какая цена, о чем Вы говорите? Деньги не имеют ровным счетом никакого значения. Я не планирую ничего выпускать, хобби -это для удовольствия, тут дело в самом процессе. Хотя конечно согласен, результат тоже хочется получить. И освоить очередной камень. Но я консерватор  и больше люблю то, к чему привык. Так что извиняйте, я буду делать на том, что у меня сейчас. Как говорят рыбаки -время за рыбалкой (и я бы сказал - за паяльником) в счет жизни не входит  Жаль если Вы этого не понимаете.
|
|
|
|
|
Oct 10 2015, 15:31
|

Практикующий маг
     
Группа: Свой
Сообщений: 3 634
Регистрация: 28-04-05
Из: Дубна, Моск.обл
Пользователь №: 4 576

|
Цитата(Tanya @ Oct 10 2015, 16:19)  Понимаю, что Вам не результат нужен, а процесс борьбы. Таня, общение с профессионалами это всегда борьба полезно, я много для себя почерпнул. Наверное все варианты решения уже озвучены (и самый яркий из них -поменять контроллер  ). Лично мне больше нравится простой и надежный как автомат Калашникова вариант с двумя буферами предложенный Viko. Думаю вопрос можно считать решенным. Еще раз всем спасибо за обсуждение!
|
|
|
|
|
Oct 11 2015, 21:01
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
QUOTE (Alexashka @ Oct 11 2015, 23:56)  Вы про "разделить большой буфер на N маленьких"? А зачем, когда двух буферов вполне достаточно?! Если нужен большой буфер, то половинки могут быть слишком большими - типа сутки заполнять придется  до начала разгрузки. QUOTE P.S. А что плохого в этом варианте - он слишком простой? Ничуть не проще и не сложнее. Если буферов стало несколько, то уже без разницы два, четыре,...2048...
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Oct 12 2015, 06:38
|

Практикующий маг
     
Группа: Свой
Сообщений: 3 634
Регистрация: 28-04-05
Из: Дубна, Моск.обл
Пользователь №: 4 576

|
Цитата(zltigo @ Oct 12 2015, 01:01)  Если нужен большой буфер, то половинки могут быть слишком большими - типа сутки заполнять придется  до начала разгрузки. Ничуть не проще и не сложнее. Если буферов стало несколько, то уже без разницы два, четыре,...2048... Тут есть еще одна нехорошесть, о которой я не подумал - один буфер прочитан на 94%, а второй заполнен на 99% и в это время винда на несколько мс уходит в себя - получаем прерывание чтения: второй буфер еще не готов к чтению, а первый уже прочитан. Т.е когда скорости чтения-записи примерно одинаковы на краях буферов получается очень маленький запас по чтению. Насчет проще сомневаюсь - это опять возврат к кольцевому буферу с его непременными атрибутами -проверка количества заполненных буферов, управление потоком записи и пр.
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|