Программирование ARM AT91SAM7X256: работа с портом SSC Mon, December 09 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


AT91SAM7X256: работа с портом SSC Печать
Добавил(а) microsin   

В статье описан порт SSC и методы работы с ним.

Сразу хочу заметить, что порт SSC - не подарок, у него есть глюки (как описанные в errata даташита, так и не описанные). Доступных примеров кода очень мало, и чтобы разобраться, как SSC работает, придется все перепробовать самому. Возможно, этот материал чем-нибудь поможет.

Сначала обсудим несколько терминов, которые будут встречаться далее в статье. Передаваемая информация делится на биты, биты укладываются в слова (в слове может быть 2..32 бита, задается полем DATLEN регистра SSC_TFMR), слова укладываются во фреймы (количество слов в фрейме задается полем DATNB регистра SSC_TFMR). Все настроечные параметры хранятся в регистрах и/или наборах их битов (полях). Далее для простоты имя регистра с именем поля буду указывать друг за другом через точку.

1. SSC содержит приемник и передатчик. И приемник, и передатчик имеют по 3 сигнала - данные, такты бит и такты фрейма. Под фреймом подразумевают порцию бит, которую передают за 1 раз (зависит от количества и длины слов, т. е. от поля DATLEN регистра SSC_TFMR и поля DATNB регистра SSC_TFMR). Для приемника и передатчика эти сигналы называются следующим образом:

RD вход, внешние данные для приемника
RK может быть как входом, так и выходом - такты бит приемника
RF может быть как входом, так и выходом - такты фрейма приемника
TD выход, данные передатчика
TK может быть как входом, так и выходом - такты бит передатчика
TF может быть как входом, так и выходом - такты фрейма передатчика

Ножки SSC совмещены с портами ввода вывода PIO, поэтому перед использованием SSC надо специальным образом запрограммировать PIO. Тактируется SSC через PMC, поэтому нужно также запрограммировать и его. Проще все это сделать, воспользовавшись стандартными подпрограммами, поставляемыми в модуле ssc.c от IAR (примеры см. далее).

И приемник, и передатчик работают независимо друг от друга, но используют общий делитель тактовой частоты. Благодаря гибкости в конфигурировании SSC позволяет легко генерить ряд стандартных протоколов - I2S, Short Frame Sync, Long Frame Sync.

2. Из последовательного потока данных биты принимаются и передаются синхронно с тактовым сигналом. Такты бит для приемника могут быть получены из:
• внешний тактовый сигнал, поданный на ножку RK (ножка RK работает как вход)
• тактовый сигнал передатчика
• внутренний делитель частоты (делит частоту MCK)

Аналогично, такты бит для передатчика могут быть получены из:
• внешний тактовый сигнал, поданный на ножку TK (ножка TK работает как вход)
• тактовый сигнал приемника
• внутренний делитель частоты (делит частоту MCK)

Приемник может также выводить тактовый сигнал на ножку RK, а передатчик - на ножку TK. Обычно тактовую частоту генерирует передатчик, а приемник использует внешний сигнал, хотя это и необязательно.

Когда тактовая частота генерируется внутри, то используется частота процессора MCK, поделенная на 2 и далее на коэффициент программируемого делителя. И для передатчика, и для приемника этот коэффициент задается в 12-битном поле SSC_CMR.DIV (CMR расшифровывается как Clock Mode Register) В итоге можно генерить частоту от MCK/2 (SSC_CMR.DIV = 000000000001b) до MCK/8190 (SSC_CMR.DIV = 111111111111b). Другими словами, длительность нолика тактового сигнала равна длительности единицы (меандр), и равна длительности периода MCK, умноженного на SSC_CMR.DIV. Если в SSC_CMR.DIV записаны нули, то тактовый сигнал вообще не генерируется.

3. Тактовая частота передатчика выбирается SSC_TCMR.CKS (TCMR расшифровывается Transmit Clock Mode Register). Инверсия тактового сигнала задается в SSC_TCMR.CKI.

Передатчик может генерировать такты на ножке TK постоянно, либо только во время передачи данных. Это задается в SSC_TCMR.CKO. Нельзя одновременно запрограммировать постоянную генерацию тактов и выбрать ножку TK для тактирования (например, SSC_TCMR.CKO == 1 и SSC_TCMR.CKS == 2) - это замыкает вход на выход и может привести к непредсказуемым последствиям.

У меня почему-то режим с генерацией синхроимпульсов только во время передачи работал некорректно (проверял по двухканальному осциллографу сигналы TK и TD) - на последний отправляемый бит не было синхронизирующего импульса, причем вначале слова был ненужный бит. Лучше использовать постоянное генерирование тактового сигнала, так как иначе проявляется неприятный глюк порта - последний передаваемый бит не получает тактового импульса. Выглядит это так, как будто тактовая последовательность сигнала TK на 1 бит опережает поток данных на ножке TD, причем первый такт получается пустой (не соответствует никакому полезному биту), а последний бит не имеет нужного такта. Обойти этот глюк можно двумя путями - либо постоянно генерируя такты на ножек TK, либо программно скорректировать передаваемый поток (выбрать режим с одним лишним битом, и передавать данные, сдвинутые на 1 бит влево). В даташите предлагается другой метод, на мой взгляд совершенно убогий - применить для коррекции задержки тактов специальную схему.

4. Для работы с SSC доступны 2 выделенных канала PDC (DMA), что позволяет сильно разгрузить процессор. Можно также воспользоваться прерываниями.

5. Максимальная тактовая частота для бит (ножки TK и RK) равна MCK/2. Делитель частоты управляется 12-битным полем DIV регистра SSC_CMR (0..4095), что позволяет делить частоту максимум на 8190. Когда поле DIV равно 0, то делитель частоты неактивен (такты не генерируются вообще и порт не работает). Например, если MCK=48МГц и поле DIV=17, то тактовая частота сигнала TK (или RK) составит примерно 1.4МГц. Если DIV=1, то тактовая частота составит 24МГц. Можно менять значение поля DIV регистра SSC_CMR прямо в дебаггере IAR (View -> Register -> SSC), тактовая частота меняется сразу - даже если программа стоит на точке останова.

6. Передатчик конфигурируется двумя регистрами - SSC_TCMR (режим синхронизации передачи) и SSC_TFMR (режим фреймов передачи).

7. Пример инициализации передатчика порта SSC у AT91SAM7X256:

#define BOARD_MCK 48000000
 
/// Макрос вычисляет величину поля STTDLY по количеству циклов тактов перед первым битом
/// нового передаваемого фрейма:
#define SSC_STTDLY(bits) (bits << 16)
 
/// Вычисляет величину поля PERIOD регистра Transmit Clock Mode Register
/// интерфейса SSC по желаемой величине делителя тактов:
#define SSC_PERIOD(divider) (((divider / 2) - 1) << 24)
 
/// Вычисляет величину поля DATLEN регистра Transmit Frame Mode Register
/// интерфейса SSC по количеству бит в одной выборке:
#define SSC_DATLEN(bits) (bits - 1)
 
/// Вычисляет величину поля DATNB регистра Transmit Frame Mode Register
/// интерфейса SSC по количеству выборок в одном фрейме.
#define SSC_DATNB(samples) ((samples -1) << 8)
 
/// Вычисляет величину поля FSLEN в Transmit Frame Mode Register
/// интерфейса SSC по количеству тактовых периодов передачи, которые 
/// должен получить сигнал синхронизации фрейма (frame sync signal).
#define SSC_FSLEN(periods) ((periods - 1) << 16)
 
// Макроопределения для ножек порта SSC
#define PIN_SSC_TF {1 << 21, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT}
#define PIN_SSC_TK {1 << 22, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT}
#define PIN_SSC_TD {1 << 23, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT}
#define PIN_SSC_RD {1 << 24, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT}
#define PIN_SSC_RK {1 << 25, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT}
#define PIN_SSC_RF {1 << 26, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT}
#define PINS_SSC PIN_SSC_TF, PIN_SSC_TK, PIN_SSC_TD, PIN_SSC_RD, PIN_SSC_RK, PIN_SSC_RF
const Pin SSC_pins[] = {PINS_SSC};
 
//конфигурирование ножек порта SSC
PIO_Configure(DSP_pins, PIO_LISTSIZE(DSP_pins));
//конфигурирование порта SSC
SSC_Configure(AT91C_BASE_SSC,
              AT91C_ID_SSC,
              1411200 /* частота TK будет 1.4МГц*/,
              BOARD_MCK/* masterClock */);
//конфигурирование передатчика
SSC_ConfigureTransmitter(AT91C_BASE_SSC, 
       (AT91C_SSC_CKS_DIV | AT91C_SSC_CKO_DATA_TX | AT91C_SSC_START_FALL_RF |
        SSC_STTDLY(1) | SSC_PERIOD(32)),
       (SSC_DATLEN(8) | AT91C_SSC_MSBF | SSC_DATNB(1) | SSC_FSLEN(16) |
        AT91C_SSC_FSOS_NEGATIVE));
//включение передатчика
SSC_EnableTransmitter(AT91C_BASE_SSC);

8. Поле START регистра SSC_TCMR задает условие начала передачи (обычно по одному из параметров сигнала TF). Обычно выбирается спад сигнала TF (SSC_TCMR.START == 4).

9. Поле PERIOD регистра SSC_TCMR задает период сигнала TF, причем работает не совсем так, как указано в описании. Минимальное значение, при котором TF еще генерится, равно 0x08 (единички равны 1.4 мкс, нолики 11.25 мкс для примера настройки п. 6 чем нолики). При увеличении PERIOD увеличивается только длительность единички. При значении 0x0F сигнал TF - меандр с периодом 23 мкс.

Кроме того, на сигнал TF влияет 4-битное поле FSLEN регистра SSC_TFMR. Это число (0..15) задает длительность TF в периодах синхросигнала TK (1..16).

Тип синхросигнала TF (отрицательный, положительный импульс, низкий, высокий уровень, переключение) задается 3-битным полем FSOS регистра SSC_TFMR.

10. 5-битовое поле DATLEN регистра SSC_TFMR программирует длину передаваемых данных в битах (длину фрейма). Числа 1..31 (0 запрещенное значение) программируют длину фрейма 2..32 бита.

11. SSC_TFMR.DATDEF определяет значение ножки TD по умолчанию (0 или 1) - до или после передачи.

12. SSC_TFMR.MSBF определяет порядок передачи бит. Если 0, то младший бит передается первым.

13. Как и многие другие периферийные устройства AT91SAM7X256, SSC имеет 2 канала PDC. Один канал обслуживает прием, а другой передачу. Регистры PDC имеют смещение 0x100 относительно базового адреса пользовательского интерфейса SSC, т. е. начинаются с адреса AT91C_BASE_SSC + 0x100 = 0xFFFD4100.

14. Запускается процесс передачи с использованием PDC просто:

AT91S_SSC *ssc;
ssc = AT91C_BASE_SSC;
ssc->SSC_TPR = (unsigned int) buffer;
ssc->SSC_TCR = length;
ssc->SSC_PTCR = AT91C_PDC_TXTEN;

15. Для обеспечения непрерывности перемещения (приема или передачи) данных PDC имеет 2 пары регистров для каждого канала. На примере SSC это следующие регистры:
RPR, TPR 32-битные указатели на начало перемещаемых данных (на буфер) приема, передачи соответственно
RNPR, TNPR 32-битные указатели на начало следующих перемещаемых данных (на буфер) приема, передачи соответственно

Если RNPR, TNPR не равно 0, то при достижении нуля RCR, TCR соответственно регистры RPR, TPR перегружаются значениями RNPR, TNPR и RCR, TCR перегружаются RNCR, TNCR.

RCR, TCR 16-битные декрементируемые счетчики единиц уже переданных данных (единица - либо байт, либо 2, либо 4 байта)
RNCR, TNCR 16-битные декрементируемые счетчики единиц уже переданных данных (единица - либо байт, либо 2, либо 4 байта)

Рассмотрим работу PDC на примере канала передачи. Итак, алгоритм работы с двумя буферами на примере передачи SSC следующий:
1. В памяти выделяются 2 буфера bufA и bufB определенного размера (максимум в 65536 байт, так как счетчик SSC_TPR 16-битный).
2. TPR = bufA;
TCR = sizeof(bufA);
TNPR = bufB;
TNCR = sizeof(bufB);
3. Настраиваем прерывание на ENDTX (окончание передачи буфера bufA, когда TCR обнуляется) и TXBUFE (когда передавать нечего, оба буфера bufA и bufB переданы).
4. Обработчик прерывания должен делать следующее:
- если TCR==0, то загружаем в буфер bufA новые данные, инициализируем TPR, TCR буфером bufA (передача должна запуститься по новой), затем загружаем данные в bufB и инициализируем TNPR, TNCR буфером bufB.
- иначе если TNCR==0, то загружаем данные в bufB и инициализируем TNPR, TNCR буфером bufB.
5. Конфигурируем ножки SSC вызовом PIO_Configure.
6. Конфигурируем SSC вызовом SSC_Configure (конфигурируется скорость работы SSC).
7. Конфигурируем передатчик SSC вызовом SSC_ConfigureTransmitter (конфигурируются параметры передачи).
8. Запускаем передачу через PDC
SSC_CR = AT91C_SSC_TXEN;
Внимание! Обязательно должны соответствовать друг другу размер буфера в байтах, то что вы пишете в счетчики TCR, TNCR и параметры SSC_DATLEN(8), SSC_DATNB(1). Закон соответствия такой (здесь DATLEN и DATNB это то, что подставляем в макросы SSC_DATLEN() и SSC_DATNB() соответственно):
размер_буфера_в_байтах = значение_TCR * (DATLEN/8)
DATNB = DATLEN/8

16. Алгоритм передачи без использования прерываний:
1. Настраиваем порт SSC как обычно, за исключением того, что не настраиваем прерывание:

//конфигурируем ножки у SSC
PIO_Configure(DSP_pins, PIO_LISTSIZE(DSP_pins));
//конфигурируем SSC на скорость 1411200 бит/сек
SSC_Configure(AT91C_BASE_SSC,
              AT91C_ID_SSC,
              1411200,
              BOARD_MCK/*masterClock*/);
//конфигурируем передатчик
// - такты бит генерятся внутри, используется поделенная частота процессора MCK
// (AT91C_SSC_CKS_DIV)
// - такты бит всегда, непрерывно (AT91C_SSC_CKO_CONTINOUS)
// - старт передачи по спаду сигнала фрейма TF (AT91C_SSC_START_FALL_RF)
// - задержка начала передачи 0 такт (SSC_STTDLY(0))
// - поле PERIOD для генерации фрейма 1 (SSC_PERIOD(32)) - определяет 
// длительность периода фрейма
// - длина посылки 8 бит (SSC_DATLEN(8))
// - передача идет старшими битами вперед (AT91C_SSC_MSBF)
// - передается одно слово данных (SSC_DATNB(1))
// - длительность синхросигнала фрейма (нолика) равна 16 тактам (SSC_FSLEN(16)). 
// У нас этот синхросигнал не используется (FSDEN == 0), поэтому FSLEN можно 
// задать любой длины.
// - тип импульса синхронизации фрейма - нолик, отрицательные импульсы
// (AT91C_SSC_FSOS_NEGATIVE)
SSC_ConfigureTransmitter(AT91C_BASE_SSC,
       (AT91C_SSC_CKS_DIV | AT91C_SSC_CKO_CONTINOUS | AT91C_SSC_START_FALL_RF |
        SSC_STTDLY(0) | SSC_PERIOD(32)),
       (SSC_DATLEN(8) | AT91C_SSC_MSBF | SSC_DATNB(1) | SSC_FSLEN(16) |
        AT91C_SSC_FSOS_NEGATIVE));
//включаем передатчик
SSC_EnableTransmitter(AT91C_BASE_SSC);
//конфигурируем приемник (подразумевается, что вход приемника замкнут 
// на выход передатчика)
// - тактовая частота бит берется со входа RK (AT91C_SSC_CKS_RK)
// - старт приема по спаду сигнала фрейма RF (AT91C_SSC_START_FALL_RF)
// - задержка начала передачи 0 такт (SSC_STTDLY(0))
// - длина посылки 8 бит (SSC_DATLEN(8))
// - передача идет старшими битами вперед (AT91C_SSC_MSBF)
// - принимается одно слово данных (SSC_DATNB(1))
// - длительность синхросигнала фрейма (нолика) равна 16 тактам (SSC_FSLEN(16)).
// У нас этот синхросигнал не используется (FSDEN == 0), поэтому FSLEN можно 
// задать любой длины.
SSC_ConfigureReceiver(AT91C_BASE_SSC,
       (AT91C_SSC_CKS_RK | AT91C_SSC_START_FALL_RF | SSC_STTDLY(0)),
       (SSC_DATLEN(8) | AT91C_SSC_MSBF | SSC_DATNB(1) | SSC_FSLEN(16)));
//включаем приемник
SSC_EnableReceiver(AT91C_BASE_SSC);

После этой последовательности действий передатчик сразу начинает генерить сигнал синхронизации бит TK и сигнал синхронизации фрейма TF. Ножка данных TD оказывается в состоянии логического 0.

2. Передаем фрейм, т. е. слово данных (4 байта зараз, если выбрали SSC_DATLEN(32), и только младший байт, если SSC_DATLEN(8)):

u32 dataword = 0x12345678;
SSC_Write(AT91C_BASE_SSC, dataword);

Передача сразу же начинается, как только вызвана SSC_Write (на ножке TD можно наблюдать передаваемые данные). Если перед вызовом SSC_Write была уже запущена передача (например, в цикле предыдущим вызовом SSC_Write), то очередная передача не начнется, пока не окончена предыдущая (так работает SSC_Write - она опрашивает флаг SSC_TXRDY, см. ssc.c).

Если мы хотим сразу принять байт, то нужно предварительно дождаться, когда байт выйдет из регистра сдвига (надо проверять не SSC_SR.TXRDY, а SSC_SR.TXEMPTY):

//ждем, что байт успешно ушел
while ((AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXEMPTY) == 0);
//теперь примем байт
u8 RX;
RX = SSC_Read(AT91C_BASE_SSC);

17. Можно соединить выход передатчика со входом приемника физически (TF->RF, TK->RK, TD->RD), а можно используя бит SSC_RFMR.LOOP. Он замыкает входы приемника на выходы передатчика (все как и написано в описании TF->RF, TK->RK, TD->RD), но только внутри AT91SAM7X256 - на ножках RF, RK, RD сигналы не видны, а на TF, TK, TD наблюдаются. Несмотря на это, приемник работает так, как будто сигналы с передатчика приходят, причем замыкание выходов передатчика на землю проводками не влияет на прохождение данных.

18. Если используются прерывания, то нужно перед конфигурированием SSC предварительно сконфигурировать AIC.

19. Что означают биты статуса SSC:
SSC_SR.TXRDY регистр, который служит для отправки данных (SSC_THR) готов к принятию очередного слова данных. Это не означает, что слово, которое туда было записано ранее, уже физически отправлено - просто это слово благополучно ушло в регистр сдвига, и, если передатчик не запрещен, то сейчас отправится наружу.

SSC_SR.TXEMPTY фрейм, записанный в SSC_THR успешно ушел наружу, и передавать больше нечего.

Бит SSC_RFMR.LOOP замыкает входы приемника на выходы передатчика (все как и написано в описании TF->RF, TK->RK, TD->RD), но только внутри AT91SAM7X256 - на ножках RF, RK, RD сигналы не видны, а на TF, TK, TD наблюдаются. Несмотря на это, приемник работает так, как будто сигналы с передатчика приходят.

20. Наконец-то разобрался, почему не получалось делать единичные передачи. Просто я не дожидался окончания передачи - читал регистр приема до того, как фрейм ушел (надо было проверять установку флажка TXEMPTY, а я проверял TXRDY).

21. Результаты проверки работы порта в режиме LOOP:
SSC_xFMR.FSLEN ни на что не влияет, пробовал 0..F
SSC_xCMR.STTDLY ни на что не влияет, самое главное, чтобы было одинаковым.
SSC_TCMR.PERIOD ни на что не влияет.

22. Пример отправки блока данных произвольного размера с использованием PDC (DMA) и прерывания. Для простоты в примере предполагается, что данные отправляются байтами, по 8 бит на фрейм SSC.

Принцип работы - данные посылаются порциями по 256 байт (это для демонстрации, так как можно было бы их сразу отправить одной настройкой PDC). При этом в PDC используются регистры SSC_TPR, SSC_TCR (для первого буфера PDC) и SSC_TNPR, SSC_TNCR (для второго буфера PDC). Исходные данные - адрес буфера в памяти buf (данные, которые передаем) и размер данных в байтах len. Для работы используются 3 глобальные переменные (глобальные для того, чтобы к ним был доступ как из основной программы, так и из обработчика прерывания SSC) SSCbytecnt (оставшаяся для отправки длина данных в байтах), SSCpnt (указатель на данные, которые пока не отправлены) и bSSCsendDone (флажок, сигнализирующий об окончании передачи). Коротко алгоритм такой:
- настраиваем передатчик (см. пример ранее) на нужные параметры.
- SSCbytecnt = len, SSCpnt = buf
- запихиваем в PDC первую порцию данных (передача начнется сразу, как только загрузится регистр SSC_TPR). Если SSCbytecnt < 256, то вызов SSC_WriteBuffer будет однократным, если больше, то двухкратным (будут использоваться первый и второй буферы PDC). С каждой порцией данных счетчик данных SSCbytecnt уменьшается на размер порции.
- ожидаем окончания передачи. Всю дальнейшую поддержку загрузок буферов PDC берет на себя обработчик прерывания SSC. Он точно так же загружает буферы PDC порциями, пока не закончатся данные для передачи.

Вот выжимка из кода (процедуры SSC_WriteBuffer, SSC_DisableInterrupts и прочие взяты из библиотеки ssc.c от Atmel):

void ConfigureSSC (void)
{
    //конфигурируем ножки у SSC
    PIO_Configure(DSP_pins, PIO_LISTSIZE(DSP_pins));
    //конфигурируем SSC на скорость 1411200 бит/сек
    SSC_Configure(AT91C_BASE_SSC,
                  AT91C_ID_SSC,
                  1411200,
                  BOARD_MCK/*masterClock*/);
    //конфигурируем передатчик:
    // [TCMR]
    // - для тактирования используется внутренняя поделенная тактовая частота
    // (AT91C_SSC_CKS_DIV)
    // - такты бит всегда (AT91C_SSC_CKO_CONTINOUS)
    // - старт приема по спаду RF (AT91C_SSC_START_FALL_RF)
    // - задержка начала передачи 0 такт (SSC_STTDLY(0))
    // - поле PERIOD для генерации фрейма 1 (SSC_PERIOD(32))
    // [TFMR]
    // - длина посылки 8 бит (SSC_DATLEN(8))
    // - передача идет старшими битами вперед (AT91C_SSC_MSBF)
    // - передается одно слово за фрейм (SSC_DATNB(1))
    // - длительность синхросигнала фрейма (нолика) равна 16 тактам (SSC_FSLEN(16))
    // - тип импульса синхронизации фрейма TF - отрицательные импульсы 
    // (AT91C_SSC_FSOS_NEGATIVE)
    // Другие вариации опций:
    // - такты бит только во время передачи (AT91C_SSC_CKO_DATA_TX) - не заработало
    // - задержка начала передачи 1 такт (SSC_STTDLY(1)) - нужно конфигурить 
    // синхронно с приемником
    SSC_ConfigureTransmitter(AT91C_BASE_SSC,
           (AT91C_SSC_CKS_DIV | AT91C_SSC_CKO_CONTINOUS | AT91C_SSC_START_FALL_RF |
            SSC_STTDLY(0) | SSC_PERIOD(32)),
           (SSC_DATLEN(8) | AT91C_SSC_MSBF | SSC_DATNB(1) | SSC_FSLEN(16) |
            AT91C_SSC_FSOS_NEGATIVE));
    //включаем передатчик
    SSC_EnableTransmitter(AT91C_BASE_SSC);
    //конфигурируем приемник
    SSC_ConfigureReceiver(AT91C_BASE_SSC,
           (AT91C_SSC_CKS_RK | AT91C_SSC_START_FALL_RF | SSC_STTDLY(0)),
           (SSC_DATLEN(8) | AT91C_SSC_MSBF | SSC_DATNB(1) | SSC_FSLEN(16)));
    //включаем приемник
    SSC_EnableReceiver(AT91C_BASE_SSC);
    //конфигурируем и разрешаем SSC interrupt
    AIC_ConfigureIT(AT91C_ID_SSC, 0, ISR_Ssc);
    AIC_EnableIT(AT91C_ID_SSC);
}
 
//------------------------------------------------------------------------------
/// Interrupt handler for the SSC. Порциями по 256 байт отправляет данные. 
/// Устанавливает флажок bSSCsendDone, который сигнализирует об окончании передачи.
//------------------------------------------------------------------------------
void ISR_Ssc(void)
{
    unsigned int status = AT91C_BASE_SSC->SSC_SR;
    unsigned int portion;
 
    // Данные отправляются порциями по 256 байт.
    // Отправлен последний буфер
    if ((status & AT91C_SSC_TXBUFE) != 0) 
    {
        SSC_DisableInterrupts(AT91C_BASE_SSC, AT91C_SSC_ENDTX | AT91C_SSC_TXBUFE);
        AT91C_BASE_SSC->SSC_PTCR = AT91C_PDC_TXTDIS;
        bSSCsendDone = true;
    }
    // Один буфер отправлен, и есть еще буферы для отправки
    else if (SSCbytecnt > 0) 
    {
        if (SSCbytecnt <= 256)
        {
            portion = SSCbytecnt;
        }
        else
        {
            portion = 256;
        }
        SSCbytecnt -= portion;
        SSC_WriteBuffer(AT91C_BASE_SSC, (void *) SSCpnt, portion);
        SSCpnt += portion;
    }
    // Один буфер отправлен, больше буферов нет
    else 
    {
        SSC_DisableInterrupts(AT91C_BASE_SSC, AT91C_SSC_ENDTX);
    }
}
 
//------------------------------------------------------------------------------
/// Код, запускающий отправку данных.
//------------------------------------------------------------------------------
    ConfigureSSC();
    ...
    bSSCsendDone = false;
    SSCbytecnt = to_allocate;
    SSCpnt = dspdata;
    // Fill first PDC buffer
    if (SSCbytecnt <= 256)
    {
        portion = SSCbytecnt;
    }
    else
    {
        portion = 256;
    }
    SSCbytecnt -= portion;
    SSC_WriteBuffer(AT91C_BASE_SSC, (void *) SSCpnt, portion);
    SSCpnt += portion;
    // Fill second buffer if necessary
    if (SSCbytecnt > 0) 
    {
        if (SSCbytecnt <= 256)
        {
            portion = SSCbytecnt;
        }
        else
        {
            portion = 256;
        }
        SSCbytecnt -= portion;
        SSC_WriteBuffer(AT91C_BASE_SSC, (void *) SSCpnt, portion);
        SSCpnt += portion;
    }
    //Для разрешим прерывание по окончанию передачи
    SSC_EnableInterrupts(AT91C_BASE_SSC, AT91C_SSC_TXBUFE | AT91C_SSC_ENDTX);
    //ждем завершения передачи
    while (!bSSCsendDone);

При работе PDC прерывания можно и не использовать вообще, если отправить данные одним блоком зараз (одним вызовом SSC_WriteBuffer). Завершение передачи нужно при этом отслеживать по флажку AT91C_BASE_SSC->SSC_SR.TXBUFE - он устанавливается, когда оба счетчика буферов PDC (SSC_TCR и SSC_TNCR) равны нулю. В этом случае глобальные переменные SSCbytecnt, SSCpnt и bSSCsendDone не нужны. Недостаток такого метода - нет контроля за процессом передачи.

23. Синхронизация старта приема работает несколько не так, как ожидалось:
- событие старта не запускает прием, прием нужно включить в обработчике прерываний по событию старта (проверив флажок SSC_CP0 в регистре статуса SSC_SR). Включение приема заключается в том, что нужно перезаписать поле START в регистре SSC_RCMR, заменив значение AT91C_SSC_START_0 на значение AT91C_SSC_START_FALL_RF.
- событие старта не срабатывает, пока количество бит для сравнения (определяется полем SSC_RFMR.FSLEN) будет больше количества бит во фрейме (определяется полем SSC_RFMR.DATLEN). Точнее говоря, остальные принятые биты будут нулевые (если значение по умолчанию сигнала данных TD нулевое, что определяется SSC_TFMR.DATDEF).

24. Внимание, ВАЖНО: в регистры PDC (DMA), определяющие количество передаваемых/принимаемых данных (это регистры SSC_TCR, SSC_TNCR, SSC_RCR, SSC_RNCR) пишется не всегда количество данных в байтах. Единицы, записываемые в этот регистр, зависят от количества бит в фрейме (т. е. от значения поля SSC_TFMR.DATLEN) следующим образом:
- бит во фрейме 2..8   (DATLEN == 1..7)   значение пишется в байтах
- бит во фрейме 9..16  (DATLEN == 8..15)  значение пишется в полусловах (16-битных единицах)
- бит во фрейме 17..32 (DATLEN == 16..31) значение пишется в словах (32-битных единицах)

Адрес, загружаемый в указатель PDC (DMA), определяющий местоположение данных (это регистры SSC_TPR, SSC_TNPR, SSC_RPR, SSC_RNPR), инкрементируется соответственно на следующие величины:
- бит во фрейме 2..8   (DATLEN == 1..7)   инкремент кратен 1 (с каждой передачей фрейма указатель инкрементируется на 1)
- бит во фрейме 9..16  (DATLEN == 8..15)  инкремент кратен 2 (с каждой передачей фрейма указатель инкрементируется на 2)
- бит во фрейме 17..32 (DATLEN == 16..31) инкремент кратен 4 (с каждой передачей фрейма указатель инкрементируется на 4)

[Пример программирования]

В этом примере порт SSC использовался для связи микроконтроллера AT91SAM7X256 с DSP-процессором BlackFin компании AnalogDevices ADSP-BF532SBST400. Кроме 6 стандартных сигналов порта SSC (см. схему), обслуживаемых аппаратно, для упрощения протокола обмена добавлены 2 дополнительных сигнала (на схеме не показаны), управляемых и анализируемых программно. Эти сигналы указывают на момент начала блока передачи (передача всегда идет блоками по SSC_BLOCK_LEN байт, размер блока кратен 4, поскольку выбран размер фрейма 32 бита).

SSC-schARM01.png SSC-schDSP01.png

Сигналы тактирования данных (TK, RK) и фреймов (TF, RF) настроены таким образом, что декодируются как стандартные (SSI) логическим анализатором АКИП-9103. На рисунке приведена диаграмма сигналов приема и передачи, выданная анализатором.
SSC-receiv01.jpg

SSC-transmit01.jpg

Далее привожу куски кода, иллюстрирующие работу с портом SSC в AT91SAM7X256.

typedef struct _SSC
{
    u32 txcnt;                  // сколько было передач
    u32 rxcnt;                  // сколько было успешных приемов
    u32 rxcntOK;                // сколько было успешных приемов
    u8 header_detected;         // синхробайты начала 0xA5 0xA5 0xA5 0xA5 0xA5 0xA5 прошли
    u8 tail_detected;           // синхробайты конца 0xE6 0xE6 0xE6 0xE6 0xE6 прошли
    u8 txdone;                  // передача окончена, можно запустить новую передачу
    u8 rxdone;                  // прием окончен, можно читать буфер
    u32 errmissheader;          // счетчик не найденных заголовков
    u32 errcrc;                 // счетчик ошибок CRC
    u32 errtimeout;             // ошибка таймаута
    u8 pdcbufRX[SSC_BLOCK_LEN]; // буфер PDC (DMA) приема
    u8 bufRX   [SSC_BLOCK_LEN]; // выходной буфер приема
    u8 pdcbufTX[SSC_BLOCK_LEN]; // буфер PDC (DMA) передачи
    u32 rectimeout;
} tSSC;
 
tSSC vSSC;  //параметры приема SSC
 
//интерфейс SSC, соединенный с платой DSP, сигнал тактирования фрейма передачи TF
#define PIN_SSC_TF {1 << 21, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT | PIO_PULLUP}
//интерфейс SSC, соединенный с платой DSP, сигнал тактирования бит передачи TK
#define PIN_SSC_TK {1 << 22, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT}
//интерфейс SSC, соединенный с платой DSP, сигнал данных передачи TD
#define PIN_SSC_TD {1 << 23, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT}
//интерфейс SSC, соединенный с платой DSP, сигнал данных приема RD
#define PIN_SSC_RD {1 << 24, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT}
//интерфейс SSC, соединенный с платой DSP, сигнал тактирования бит приема RK
#define PIN_SSC_RK {1 << 25, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT}
//интерфейс SSC, соединенный с платой DSP, сигнал тактирования фрейма приема RF
#define PIN_SSC_RF {1 << 26, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT}
#define PINS_SSC PIN_SSC_TF, PIN_SSC_TK, PIN_SSC_TD, PIN_SSC_RD, PIN_SSC_RK, PIN_SSC_RF
 
const Pin SSC_pins[] = {PINS_SSC};
//конфигурируем ножки у SSC
PIO_Configure(SSC_pins, PIO_LISTSIZE(SSC_pins));
//конфигурируем SSC на скорость 1411200 бит/сек
SSC_Configure(AT91C_BASE_SSC,
                  AT91C_ID_SSC,
                  1411200/*скорость 1411200 бит/сек*/,
                  BOARD_MCK/*masterClock==48000000*/);
 
#define SSC_BIT_PER_FRAME 32

Процедура настройки порта SSC для AT91SAM7X256:

//конфигурируем передатчик:
SSC_ConfigureTransmitter(AT91C_BASE_SSC,
           (AT91C_SSC_CKS_DIV | AT91C_SSC_CKO_CONTINOUS | AT91C_SSC_START_FALL_RF |
SSC_STTDLY(0) | SSC_PERIOD(33) | AT91C_SSC_CKI)/*регистр TCMR*/,
(SSC_DATLEN(SSC_BIT_PER_FRAME) | SSC_DATNB(1) | SSC_FSLEN(1) |
AT91C_SSC_FSOS_POSITIVE)/*регистр TFMR*/); 
//конфигурируем приемник
SSC_ConfigureReceiver(AT91C_BASE_SSC,
           (AT91C_SSC_CKS_RK | AT91C_SSC_START_FALL_RF | SSC_STTDLY(0) | AT91C_SSC_CKI),
           (SSC_DATLEN(SSC_BIT_PER_FRAME) | SSC_DATNB(1) | SSC_FSLEN(1)));

Процедуры запуска и останова порта SSC:

void SSC_EnableTransmitter(AT91S_SSC *ssc)
{
    ssc->SSC_CR = AT91C_SSC_TXEN;
}
 
void SSC_DisableTransmitter(AT91S_SSC *ssc)
{
    ssc->SSC_CR = AT91C_SSC_TXDIS;
}
 
void SSC_EnableReceiver(AT91S_SSC *ssc)
{
    ssc->SSC_CR = AT91C_SSC_RXEN;
}
 
void SSC_DisableReceiver(AT91S_SSC *ssc)
{
    ssc->SSC_CR = AT91C_SSC_RXDIS;
}

Обработчик прерывания порта SSC:

//------------------------------------------------------------------------------
/// Interrupt handler for the SSC. Устанавливает флажок, который 
/// сигнализирует об окончании передачи.
//------------------------------------------------------------------------------
void ISR_Ssc(void)
{
    unsigned int status = AT91C_BASE_SSC->SSC_SR;
 
    if ((status & AT91C_SSC_TXBUFE)/* буфер DMA пуст */ 
     && (status & AT91C_SSC_TXENA) /* передатчик включен */) 
    {
        // Передача с использованием PDC закончена
        SSC_DisableInterrupts(AT91C_BASE_SSC, AT91C_SSC_ENDTX | AT91C_SSC_TXBUFE);
        AT91C_BASE_SSC->SSC_PTCR = AT91C_PDC_TXTDIS;
        //ждем, когда последний фрейм выскочит из передатчика
        while (!(AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXEMPTY));
        PIO_Set(&outINTarm);
        SSC_DisableTransmitter(AT91C_BASE_SSC);
        vSSC.txdone = true;
    }
    if ((status & AT91C_SSC_RXBUFF)/* буфер DMA полон */) 
    {
        if (status & AT91C_SSC_RXENA)/* приемник включен */
        {
            // Прием с использованием PDC закончен
            SSC_DisableInterrupts(AT91C_BASE_SSC, AT91C_SSC_ENDRX | AT91C_SSC_RXBUFF);
            AT91C_BASE_SSC->SSC_PTCR = AT91C_PDC_RXTDIS;
            SSC_DisableReceiver(AT91C_BASE_SSC);
            vSSC.rxdone = true;
            vSSC.rxcnt++;
            //LED(1);
        }
    }
}

Прием и передача работают с участием контроллера PDC (или DMA). Окончание приема отслеживается в обработчике прерывания ISR_Ssc, и там же взводится соответствующий флаг vSSC.rxdone. Во время приема отслеживается таймаут, и если прием не завершился в течение ожидаемого времени, то регистрируется ошибка.

Вот так запускается передача (SSC_BLOCK_LEN - размер буфера передачи в байтах. Делится он на 4 потому, что размер фрейма выбран 32 бита):

SSC_WriteBuffer(AT91C_BASE_SSC, (void *) vSSC.pdcbufTX, SSC_BLOCK_LEN/4);
//разрешим прерывание по окончанию передачи
SSC_EnableInterrupts(AT91C_BASE_SSC, AT91C_SSC_TXBUFE);
//разрешаем передатчик
SSC_EnableTransmitter(AT91C_BASE_SSC);

[Ссылки]

1. Диаграммы сигналов порта SSC.

 

Комментарии  

 
0 #1 Денис 11.02.2019 17:44
Спасибо тебе огромное! Понадобилось использовать SSC в качестве I2S master receiver на SAM3X8E. И осциллографа под рукой не было. Эта статья очень помогла!
Цитировать
 

Добавить комментарий


Защитный код
Обновить

Top of Page