|
|
  |
Работа с переменными в прерывании и основном теле, жутко неудобно выходит |
|
|
|
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: У меня там и нет указателей.
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|