Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Bugfix для AT91SAM7X's SSC ( с кодеком AD73322L )
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > ARM
_dem
Делюсь опытом, может кто столкнется или укажет на кривизну рук smile.gif

Проблема - на плате стоит кодек AD73322l, подключен по SSC к SAM7X.
SSC работает в слейв-режиме, клокится от кодека.

Кодек двухканальный, формат данных потока ...[ 16 bit канал 1 ][ 16 bit канал 2 ]...
Проблема в том, что после некоторого времени работы теста (10-30 минут) SSC передатчик теряет один фрейм и каналы на выходе кодека "меняются местами". Через некоторое время ситуается повторяется и все возвращается в норму.
Приемник всегда работает четко.

Работаю через DMA, переключение буферов в прерывании.

Проверка времени реакции на прерывание, чтение свежей errata, различные телодвижения с бубном и выключением SPORT интерфейса кодека результата не дали.

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

Еще раз тьфу-тьфу-тьфу, ибо ловить такой баг в тестовой стойке с платами - занятие, несомненно, интересное, но крайне выматывающее... sad.gif

Вот код рабочего обработчика :
Код
__arm void PSSC_InterruptHandler(void)
{

    unsigned long STATUS = AT91C_BASE_SSC->SSC_SR;
    /////////    //////////////////

    // first, updating buffer count
    if (sscProcessor->mainBuffer == FIRST_BUFFER)
        {
        sscProcessor->mainBuffer = SECOND_BUFFER;
        sscProcessor->shadowBuffer = FIRST_BUFFER;
        }
    else
        {
        sscProcessor->mainBuffer = FIRST_BUFFER;
        sscProcessor->shadowBuffer = SECOND_BUFFER;
        }
    
    if (STATUS & AT91C_SSC_RXBUFF)
    {
        
        #ifdef SSC_ENABLE_IRQ_RESETS_SSC
        __SSC_PeripheryInit();

        // forcibly enable IRQ
        AT91C_BASE_SSC->SSC_IER = AT91C_SSC_ENDRX | AT91C_SSC_RXBUFF;
        #endif
        
        // we've lost both buffers, need to reprogram all and to restart transfer
        // restarting Xchange

        // disable transmitter and PDC

        #ifdef SSC_ENABLE_IRQ_RESETS_SSC
        AT91C_BASE_SSC->SSC_CR = AT91C_SSC_RXDIS | AT91C_SSC_TXDIS;        
        #endif
        
        AT91C_BASE_SSC->SSC_PTCR = AT91C_PDC_RXTDIS | AT91C_PDC_TXTDIS;
        //
        unsigned short read = AT91C_BASE_SSC->SSC_RHR; // reading that shit for SSC clears itself
      
        read = AT91C_BASE_SSC->SSC_RHR; // reading that shit !
               read = AT91C_BASE_SSC->SSC_RHR; // reading that shit !

        // current TX buffer
        AT91C_BASE_SSC->SSC_TPR      = (unsigned long)(sscProcessor->TXbuffers[ sscProcessor->mainBuffer ]);
        AT91C_BASE_SSC->SSC_TCR      = sscProcessor->buffersLength;        
        
        // current RX buffer
        AT91C_BASE_SSC->SSC_RPR      = (unsigned long)(sscProcessor->RXbuffers[ sscProcessor->mainBuffer ]);
        AT91C_BASE_SSC->SSC_RCR      = sscProcessor->buffersLength;        

            
        // starting RC/TM
            AT91C_BASE_SSC->SSC_PTCR = AT91C_PDC_RXTEN | AT91C_PDC_TXTEN;
        
            #ifdef SSC_ENABLE_IRQ_RESETS_SSC
        AT91C_BASE_SSC->SSC_CR = AT91C_SSC_RXEN | AT91C_SSC_TXEN;    
        #endif
        
        // posting semaphore to signal codec 'bout buffers switched
        OSSemPost( sscProcessor->buffersSwitchSemaphore );
    }


    ///////////////////////////    ///////////////////////////    ///////////////////////////
    AT91C_BASE_AIC->AIC_IVR = 0;                          
    
    AT91C_BASE_AIC->AIC_ICCR    =  (1 << AT91C_ID_SSC);    
    AT91C_BASE_AIC->AIC_EOICR   = 0;                      
    
}


И код инициализации :
Код
void __SSC_PeripheryInit( void )
{
    // SSC Control Register
    AT91C_BASE_SSC->SSC_CR = AT91C_SSC_SWRST;                                       /* (SSC) Software Reset */

    // SSC Receive Clock Mode Register
    AT91C_BASE_SSC->SSC_RCMR = ((0x2 << 0) & AT91C_SSC_CKS) | ((0x4 << 8) & AT91C_SSC_START);
//    AT91C_BASE_SSC->SSC_RCMR = ((0x2 << 0) & AT91C_SSC_CKS) | AT91C_SSC_CKI | ((0x3 << 8) & AT91C_SSC_START);    
    // SSC Receive Frame Mode Register
    AT91C_BASE_SSC->SSC_RFMR = ((15 << 0) & AT91C_SSC_DATLEN) | AT91C_SSC_MSBF;
    // SSC Transmit Clock Mode Register
    AT91C_BASE_SSC->SSC_TCMR = ((0x2 << 0) & AT91C_SSC_CKS) | AT91C_SSC_CKI | ((0x4 << 8) & AT91C_SSC_START);;

    // SSC Transmit Frame Mode Register
    AT91C_BASE_SSC->SSC_TFMR = ((15 << 0) & AT91C_SSC_DATLEN) | AT91C_SSC_MSBF;    
}
KRS
Я использую другой кодек и SAM7S но интерфейс такой же. А о проблеме синхронизации даже и не думал законфигурил в 32 битном режиме с синхронизацие по falling edge и все.
ljerry
Думаю, что проблема возникает из-за того, что программа не успевает подгрузить передающий канал PDC вовремя (из-за того, что прерывание запрещено слишком долго, скорее всего, на время обработки других прерываний). На сей случай рекомендую использовать двойную буферизацию, с помощью пар регистров SSC_TNPR - SSC_TNCR и SSC_RNPR - SSC_RNCR.

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

Собственно, я привел обработчик, почищенный от лишнего кода - в полном есть и double buffer, и попытки отключить кодек (у него есть линия SE) на время реакции прерывания - не помогает.

Время входа в прерывание (реакция), общая загрузка системы - проверялось... Время обработки плавает (очень примерно) в пределах 10% размера такта SSC (по осциллу), и это под нагрузкой.

К тому же, кодек работает дуплексом - RX/TX слова для одинаковых каналов синхронны - а на приемнике такой проблемы не наблюдается, т.е. PDC вроде как и ни причем sad.gif
ljerry
Цитата(_dem @ Mar 27 2008, 14:56) *
Не хотел загромождать основной пост - двойная буферизация использовалась в оригинальном коде, до решения проблемы.

Собственно, я привел обработчик, почищенный от лишнего кода - в полном есть и double buffer, и попытки отключить кодек (у него есть линия SE) на время реакции прерывания - не помогает.


Хотелось бы посмотреть именно на исходный код.

Цитата(_dem @ Mar 27 2008, 14:56) *
К тому же, кодек работает дуплексом - RX/TX слова для одинаковых каналов синхронны - а на приемнике такой проблемы не наблюдается, т.е. PDC вроде как и ни причем sad.gif


Синхронны-то они только на шине, а в проце обрабатывать их надо по-разному. Для приемника можно включить и настроить канал PDC уже в момент приема первого слова, главное - успеть это сделать до завершения приема. С передатчиком ситуация другая: нужно сначала настроить и включить PDC, только потом включить передатчик (а иначе он может начать брать данные из буфера PDC только после отправки первого слова) и в самом конце разрешить работу внешнего устройства. Если эта последовательность нарушается - есть вероятность сбоя (как говорится, плавали, знаем smile.gif ). В общем, надо код смотреть

P.S. Кстати, еще один момент: приемный и передающий каналы PDC заканчивают работу в разные моменты времени - передающий после передачи предпоследнего слова, а приемный - после приема последнего слова. И для них нужны отдельные обработчики прерываний, иначе есть шанс либо не успеть прогрузить передающий канал (при использовании прерывания от приемного канала), либо можно не до конца использовать приемный буфер - последнее слово данных попадет в начало нового буфера (при использовании прерывания от передающего канала). А у Вас все сидит в одном прерывании. Правда, при использовании прерывания от приемного канала и при двойной буферизации работать должно.
_dem
Хм, а как же у меня работает остальная периферия, там везде PDC.... ?

Цитата(ljerry @ Mar 27 2008, 16:53) *
Хотелось бы посмотреть именно на исходный код.


Нет проблем smile.gif
Обращаю ваше внимание на последовательность :
Код
AT91C_BASE_SSC->SSC_CR = AT91C_SSC_RXDIS | AT91C_SSC_TXDIS;        
AT91C_BASE_SSC->SSC_PTCR = AT91C_PDC_RXTDIS | AT91C_PDC_TXTDIS;    
// current buffer
AT91C_BASE_SSC->SSC_TPR      = (unsigned long)(sscProcessor->TXbuffers[ sscProcessor->mainBuffer ]);
AT91C_BASE_SSC->SSC_TCR      = sscProcessor->buffersLength;        
// starting RC/TM
AT91C_BASE_SSC->SSC_PTCR = AT91C_PDC_RXTEN | AT91C_PDC_TXTEN;
        
AT91C_BASE_SSC->SSC_CR = AT91C_SSC_RXEN | AT91C_SSC_TXEN;


Вот весь код :

( перенес в аттач )
ljerry
В общем, я бы порекомендовал такую идеологию построения кода:

А. Инициализация:
1. Останов кодека;
2. Запрет SSC и PDC - приемника и передатчика (на всякий пожарный);
3. Загрузка указателей буферов и их длин в PDC для приемника и передатчика, причем как пар TPR-TCR и RPR-RCR, так и пар TNPR-TNCR и RNPR-RNCR;
4. Разрешение PDC для приемника и передатчика;
5. Разрешение приемника и передатчика;
6. Запуск кодека.

Б. Обработчик прерываний:
Можно использовать обработчик с проверкой только события ENDRX, но кошернее (ИМХО) использовать раздельные ветки с проверкой событий ENDRX и ENDTX. В обработчике выполняем запись в пары TNPR-TNCR и RNPR-RNCR. Если хотите использовать события TXBUFE и RXBUFF, то для них обязательно должны быть отдельные ветки.

Ну как-то вот так smile.gif
_dem
Вот так-то оно и работает, за исключением :
1. Останов/запуск кодека. Как видно из кода, это есть, но отключено - так как это вызывает слышимые щелчки в аудио-потоке, что неприемлимо.
2. Пробовал "использовать раздельные ветки с проверкой событий ENDRX и ENDTX", пробовал все делать в одном прерывании - разницы не обнаружил, оставил в одном (сэкономил себе 1 вызов) smile.gif
ljerry
Цитата(_dem @ Mar 27 2008, 17:21) *
Вот так-то оно и работает, за исключением :
1. Останов/запуск кодека. Как видно из кода, это есть, но отключено - так как это вызывает слышимые щелчки в аудио-потоке, что неприемлимо.

Из кода не видно - дефайны не показаны. К тому же останавливать его нужно только перед инициализацией (на всякий случай) и перед завершением работы. В прерывании трогать его просто нельзя.
Цитата(_dem @ Mar 27 2008, 17:21) *
2. Пробовал "использовать раздельные ветки с проверкой событий ENDRX и ENDTX", пробовал все делать в одном прерывании - разницы не обнаружил, оставил в одном (сэкономил себе 1 вызов) smile.gif

Если используется двойная буферизация, то разницы не будет, и один вызов действительно можно сэкономить. В противном случае обязательно нужны раздельные ветки, иначе обязательно словите проблему.
Теперь по приведенному Вами коду. Есть там ветка, начинающаяся с проверки условия RXBUFF. Так вот, в момент возникновения этого события потеря одного фрейма уже произошла и сдвиговый регистр передатчика уже содержит некорректные данные. Почему так происходит - см. мое сообщение, отправленное в 15:53. По какой причине это происходит - скорее всего, из-за запрета прерываний на большое время, возможно, обработчиками других прерываний.
_dem
Ок, я попробую разнести в разные ветки RX/TX, но потеря байта происходит независимо от времени реакции МК - я уже писал, что это была первая версия, пришедшая в голову, и она была проверена - увы, во время сбоя на осциллографе все красиво ( в процессе обхода граблей "удалось" добится потери через десятки секунд после запуска, чтобы прицепить осцилл).

Если МК специально нагружен кодом с большим количеством критических участков либо если в обработчик добавить dummy код, тормозящий его - то все идет так, как и должно - постоянные сбои, слеты каналов (как RX, так и TX), на осцилле видно большое смещение сигнала на тестовой ножке по сравнению с фрейм-синхронимпульсами.
С обычной прошивкой такого нет, за время фрейма можно хоть 100 раз войти в прерывание smile.gif

У вас SSC в каком режиме ? Master или Slave ?
Какой кодек Вы используете ?

ps.

//#define SSC_ENABLE_PDC_DOUBLE_BUFFER
//#define SSC_PDC_IRQ_SWITCHES_CODEC_SE
#define SSC_ENABLE_IRQ_RESETS_SSC
ljerry
В общем, рекомендую нарисовать что-либо навроде:

Код
void __SSC_PeripheryInit( void )
{
    // SSC Control Register
    AT91C_BASE_SSC->SSC_CR = AT91C_SSC_SWRST;  /* (SSC) Software Reset */

    // !!! PDC !!!
    AT91C_BASE_SSC->SSC_PTCR = AT91C_PDC_RXTDIS | AT91C_PDC_TXTDIS;    
    // current buffer
    AT91C_BASE_SSC->SSC_TPR      = (unsigned long)(sscProcessor->TXbuffers[ sscProcessor->mainBuffer ]);
    AT91C_BASE_SSC->SSC_TCR      = sscProcessor->buffersLength;        
    AT91C_BASE_SSC->SSC_TNPR      = (unsigned long)(sscProcessor->TXbuffers[ sscProcessor->shadowBuffer ]);
    AT91C_BASE_SSC->SSC_TNCR      = sscProcessor->buffersLength;        
    // starting RC/TM
    AT91C_BASE_SSC->SSC_PTCR = AT91C_PDC_RXTEN | AT91C_PDC_TXTEN;

    // !!! SSC !!!
    // SSC Receive Clock Mode Register
    AT91C_BASE_SSC->SSC_RCMR = ((0x2 << 0) & AT91C_SSC_CKS) | ((0x4 << 8) & AT91C_SSC_START);
//    AT91C_BASE_SSC->SSC_RCMR = ((0x2 << 0) & AT91C_SSC_CKS) | AT91C_SSC_CKI | ((0x3 << 8) & AT91C_SSC_START);    
    // SSC Receive Frame Mode Register
    AT91C_BASE_SSC->SSC_RFMR = ((15 << 0) & AT91C_SSC_DATLEN) | AT91C_SSC_MSBF;
    // SSC Transmit Clock Mode Register
    AT91C_BASE_SSC->SSC_TCMR = ((0x2 << 0) & AT91C_SSC_CKS) | AT91C_SSC_CKI | ((0x4 << 8) & AT91C_SSC_START);;
    // SSC Transmit Frame Mode Register
    AT91C_BASE_SSC->SSC_TFMR = ((15 << 0) & AT91C_SSC_DATLEN) | T91C_SSC_MSBF;
    // SSC Control Register
    AT91C_BASE_SSC->SSC_CR = AT91C_SSC_RXEN | AT91C_SSC_TXEN;
}

__arm void PSSC_InterruptHandler(void)
{
    unsigned long STATUS = AT91C_BASE_SSC->SSC_SR;

    if (STATUS & AT91C_SSC_ENDRX)
    {
        // first, updating buffer count
        if (sscProcessor->mainBuffer == FIRST_BUFFER)
        {
            sscProcessor->mainBuffer = SECOND_BUFFER;
            sscProcessor->shadowBuffer = FIRST_BUFFER;
        }
        else
        {
            sscProcessor->mainBuffer = FIRST_BUFFER;
            sscProcessor->shadowBuffer = SECOND_BUFFER;
        }
    
        // current TX buffer
        AT91C_BASE_SSC->SSC_TNPR      = (unsigned long)(sscProcessor->TXbuffers[ sscProcessor->mainBuffer ]);
        AT91C_BASE_SSC->SSC_TNCR      = sscProcessor->buffersLength;        
        
        // current RX buffer
        AT91C_BASE_SSC->SSC_RNPR      = (unsigned long)(sscProcessor->RXbuffers[ sscProcessor->mainBuffer ]);
        AT91C_BASE_SSC->SSC_RNCR      = sscProcessor->buffersLength;        

        // posting semaphore to signal codec 'bout buffers switched
        OSSemPost( sscProcessor->buffersSwitchSemaphore );
    }

    ///////////////////////////    ///////////////////////////    ///////////////////////////
    AT91C_BASE_AIC->AIC_IVR = 0;                          
    AT91C_BASE_AIC->AIC_ICCR    =  (1 << AT91C_ID_SSC);    
    AT91C_BASE_AIC->AIC_EOICR   = 0;
}


Вот smile.gif

Цитата(_dem @ Mar 27 2008, 18:15) *
У вас SSC в каком режиме ? Master или Slave ?
Какой кодек Вы используете ?


Использую PDC совместно с DBGU, UART и SPI. В принципе идеология использования PDC совместно с SPI такая же, как и при использовании с SSC. Так что пробуйте. Правда, инициализацию собственно SSC я взял из Вашего кода как есть, так что пусть она будет на Вашей совести smile.gif
_dem
Хм, у меня в проекте из внешних интерфейсов не используется только CAN, все остальные при деле. Везде, кроме USART - PDC... А тут наступил то ли на грабли, то ли кривизна рук...

В приведенном Вами варианте обработчика PDC, по-моему, будет проблема в случае, если мы пропустим прерывание "half buffer". Хоть это фактически и ЧП (если произойдет запрещение прерываний на *такое* время), но ИМХО это стоит обработать...
ljerry
Цитата(_dem @ Mar 28 2008, 10:41) *
Хм, у меня в проекте из внешних интерфейсов не используется только CAN, все остальные при деле. Везде, кроме USART - PDC... А тут наступил то ли на грабли, то ли кривизна рук...


У меня PDC и с USART 'ом используется, правда, только в передающем канале.

Цитата(_dem @ Mar 28 2008, 10:41) *
В приведенном Вами варианте обработчика PDC, по-моему, будет проблема в случае, если мы пропустим прерывание "half buffer". Хоть это фактически и ЧП (если произойдет запрещение прерываний на *такое* время), но ИМХО это стоит обработать...


Заодно этот обработчик будет хорошим индикатором системной ошибки.

Напишите потом, заработало или нет - самому интересно стало smile.gif
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.