Интерфейс CAN в микроконтроллере ARM AT91SAM7X256 Печать
Добавил(а) microsin   

На основе примера basic-can-project от Atmel (проект для IAR EWB) описывается, как CAN-интерфейс добавить в свою программу для микроконтроллера AT91SAM7X256 (или AT91SAM7X128, AT91SAM7X512). К сожалению, Atmel максимально постаралась запутать алгоритм примера. В статье сделан акцент на практику - как с минимальными усилиями добиться рабочего результата.

Сокращение CAN означает "Controller Area Network". Эта шина широко применяется в промышленности, особенно в автомобилестроении. Перед тем, как начать использовать шину CAN, нужно хотя бы бегло ознакомиться с её возможностями, см. Ссылки [1]. Сильно вчитываться не стоит, так как можно сломать голову в премудростях протокола. Для начала нужно определиться с параметрами подключения:

- скорость передачи - в этом примере используется 125 кбит/сек.
- адресация на шине - нужно задать адреса устройств, которые будут присутствовать на шине, и режим адресации (стандартный или расширенный). В статье в качестве примера будут использоваться только два устройства, на одном конце адрес 0x000 (ARM), на другом 0x077 (программа на компьютере с адаптером USB-CAN) в стандартном режиме адресации.

[Теория]

Теперь несколько слов о протоколе CAN и о том, как он представлен в AT91SAM7X256.

1. На том уровне протокола CAN, который удобно использовать (этот уровень используется в нашем рассматриваемом примере), передача ведется порциями по 8 байт, без запроса о подтверждении, как в протоколе UDP, т. е. отправили - и забыли. Однако на приемном конце имеется полный набор диагностики о прохождении пакета, т. е. если он пришел, то можно не заботиться о его целостности (нет необходимости заморачиваться контрольными суммами - это уже имеется в протоколе CAN).

2. В AT91SAM7X256 имеется только один интерфейс CAN. Скорость, на которую его можно настроить, выбирается из стандартного ряда скоростей 1000, 800, 500, 250, 125, 100, 50, 25, 20, 10 кбод, или кбит/сек. Внимание: атмеловский пример инициализации интерфейса CAN (процедура can.c\CAN_Init) якобы может настраивать скорости 1000, 800, 500, 250, 125, 50, 25, 10 кбод, однако 800 и 10 кбод настраиваются некорректно, а скорости 100 и 25 кбод мне проверить не удалось, так как на другом конце аппаратура их не поддерживала (это была программа PcanView и адаптер USB-CAN компании SYS TEC electronic GmbH, см. Ссылки [2]). Поэтому остаются скорости 1000, 500, 250, 125, 50 (я выбрал 125) кбод.

3. Порции данных, которые передаются по шине CAN - 8-байтные посылки - передаются на основе абстракции - так называемых почтовых ящиков (mailbox). С непривычки это может снести крышу, но как утверждают разработчики протокола (и те счастливчики, которые хоть немного разобрались в протоколе CAN) - это очень удобная фича. В микроконтроллере AT91SAM7X256 всего 8 почтовых ящиков (далее просто mailbox). Все mailbox имеют одинаковую скорость, но каждый mailbox индивидуально настраивается на прием или на передачу, и каждый mailbox может иметь свой собственный адрес на шине - так называемый CAN ID. Mailbox имеют аппаратный механизм, позволяющий прозрачно для процессора (т. е. программно не загружая его) фильтровать приходящие сообщения по CAN ID, и ненужные сообщения отбрасывать (подробнее далее).

Mailbox-ы удобно рассматривать как 8 независимых каналов передачи данных, и с помощью их настройки можно регулировать пропускающую способность шины CAN в прямом и обратном направлении. Например, если нам нужно передавать больше данных, чем принимать, то мы можем настроить, как вариант, 2 mailbox на прием, а 6 на передачу. В примере basic-can-project от Atmel все mailbox настроены изначально на прием, а при необходимости передачи нужный mailbox перенастраивается на передачу. Я такое поведение у себя в программе поменял - настроил постоянно 2 ящика на прием, и 6 на передачу, хотя реально и на прием и на передачу в программе используется только по одному ящику (один на прием, и один на передачу).

4. Интерфейс CAN AT91SAM7X256 имеет гибкую систему прерываний. В примере basic-can-project от Atmel прерывания используются на обработку ошибок и на прием и передачу сообщений, хотя реально ничего в обработчике при приеме и передаче не делается, выводятся только диагностические сообщения (передача и прием реальных данных происходят методом поллинга в основном цикле main).

5. Адресация почтовых ящиков может происходить в стандартном режиме, тогда адрес 11-битный (0x000..0x7FF), или в расширенном режиме, тогда к адресу добавляется еще 18 бит, и адрес становится 29-битным (0x00000000..0x1FFFFFFF). Какой режим адресации активен (стандартный или расширенный) - зависит от 29-го бита MIDE (AT91C_CAN_MIDE), который находится в регистре CAN_MIDx (x равен номеру почтового ящика) почтового ящика. Если бит MIDE равен 0 - стандартный режим, 1 - расширенный. В том же регистре находится адрес CAN ID - биты 28..18 для стандартного режима (11-битное поле MIDvA) и биты 17..0 расширенного режима (добавочное 18-битное поле MIDvB). Биты 31 и 30 в регистре CAN_MIDx не используются.

CAN-MIDx.png

В примере basic-can-project от Atmel бит MIDE используется, но непонятно как. Я у себя принудительно включил стандартный режим.

6. Есть возможность задать для mailbox не один адрес, а группу адресов. Это делается с помощью битовой маски в регистре CAN_MAMx. Структура регистра CAN_MAMx точно такая же, как и у CAN_MIDx - имеются те же самые поля MIDE, MIDvA, MIDvB. Если MIDE==0, то используется маска MIDvA, а если MIDE==1, то используется маска MIDvAMIDvB. Маска работает следующим образом - в тех битах, где в маске нули, биты адреса могут меняться, и адрес все равно остается валидным. Например, если маска MIDvA равна 11111111100 (стандартный режим), то допустимыми для приема сообщения будут 4 адреса:

11111111100
11111111101
11111111110
11111111111

Процедура приема сообщения следующая - из принятого фрейма берется поле адреса, и на него накладывается по & маска, получается результат A. На адрес MID также накладывается та же самая маска, получается результат B. Если A==B, то сообщение считается принятым, и результат B копируется в адрес MID, а если A не равно B, то сообщение отбрасывается (это делается аппаратно, без участия процессора). Поэтому в процессе приема адрес в регистре MID может измениться (биты адреса ящика, соответствующие нулевым битам в адресе, могут сброситься). Например, если нужно настроить почтовый ящик на прием ВСЕХ сообщений (сообщений с любым адресом), то маску в регистре CAN_MAMx нужно сбросить в 0, и при этом нет никакого смысла задавать какой-либо адрес в регистре CAN_MIDx, так как он не будет в процессе приема оказывать никакого влияния, и все равно сбросится в 0 при приеме первого же фрейма.

У каждого mailbox имеется также регистр CAN_MFIDx - family ID register. В даташите написано, что он содержит "объединение замаскированных бит адреса CAN_MIDx, и предназначен для облегчения декодирования адреса.". На самом деле смысл его состоит в том, что в нем находится порядковый номер адреса сообщения в пределах семейства, ограниченного маской. Например, в маске два нулевых бита в любом месте маски, т. е. маска соответствует 4 возможным адресам. Тогда при успешном приеме сообщения (адрес совпал с маской) в регистре CAN_MFIDx будут числа от 0 до 3, т. е. порядковый номер адреса в группе. Этот номер можно использовать в качестве индекса в массиве адресов обработчиков сообщений в почтовых ящиков, чем облегчается декодирование адреса полученного сообщения.

В примере basic-can-project от Atmel маска настраивается просто от балды. Я у себя задал маску таким образом, чтобы прием сообщения срабатывал только на один адрес.

7. Пример от Atmel basic-can-project работает примерно следующим образом. Передаются и принимаются данные в основной программе (main), с помощью вызовов CAN_Write и CAN_Read соответственно. Физическая работа с регистрами CAN при приеме и передаче ведется через обработчик CAN_Handler. И прием, и передача через CAN осуществляется через структуру CanTransfer. Переменная CanTransfer одна-единственная, так как пример basic-can-project очень упрощенный - экземпляр переменной CanTransfer работает и на прием, и на передачу. В результате после передачи нужно ждать освобождения CanTransfer, и после возвращать CanTransfer в режим приема и снова вызвать CAN_InitMailboxRegisters. Общее впечатление от примера - мусорный код, который делался впопыхах или, может быть, был выдернут из какого-то проекта. Пример я переделал под себя - в обработчике прерывания принимаемые данные пишутся в кольцевой буфер, которые впоследствии могут быть обработаны в основной программе. В результате процедура CAN_Read оказалась не нужна. Передачу я сделал из основной программы, и переменная CanTransfer у меня работает только на передачу. Структура переменной CanTransfer:

typedef struct
{
    volatile u8 canstate;             //состояние CAN. Сюда пишутся константы CAN_IDLE, CAN_SENDING, CAN_RECEIVING.
    volatile u8 can_number;           //номер интерфейса CAN. Я это поле удалил, так как интерфейс у нас только один.
    volatile u8 mailbox_number;       //номер mailbox. Именно в его регистры пишутся параметры CanTransfer.
    volatile u8 test_can;             //переменная для начальной проверки интерфейса CAN.
    volatile u32 mode_reg;            //в каком состоянии должен находиться mailbox (данные для регистра CAN_MB_MMR).
    volatile u32 acceptance_mask_reg; //данные для регистра CAN_MB_MAM.
    volatile u32 identifier;          //данные для регистра CAN_MB_MID.
    volatile u32 data_low_reg;        //передаваемые или принимаемые данные (младшие 4 байта из восьми).
    volatile u32 data_high_reg;       //передаваемые или принимаемые данные (старшие 4 байта из восьми).
    volatile u32 control_reg;         //данные для регистра CAN_MB_MCR.
    volatile u32 mailbox_in_use;      //маска для тех ящиков, которые используются (если все 8, то это поле 
                                      //равно 0xFF), я это поле удалил.
volatile s32 size;          //размер передаваемых данных, всегда 8. Надо бы это поле тоже удалить. } CanTransfer;

Поле canstate для оригинального примера basic-can-project очень важное, оно определяет режим работы ВСЕГО интерфейса CAN по отношению к программе, работающей в ARM. Сюда пишутся константы CAN_IDLE, CAN_SENDING, CAN_RECEIVING. Т. е. в любой момент времени программа ARM может либо воспользоваться CAN на чтение или запись, либо ждать завершения текущей операции. Алгоритм работы зависит от того, какая операция выполняется (CAN_Write или CAN_Read). Освобождает интерфейс CAN путем записи в canstate значения CAN_IDLE обработчик прерывания CAN_Handler. У меня это поле потеряло актуальность, так как переменная с типом CanTransfer у меня используется только на передачу, и canstate используется только для того, чтобы определить - закончена ли передача.

Например, как работает CAN_Write: если текущая операция чтение (canstate==CAN_RECEIVING), то прерываем её, освобождая CAN (canstate=CAN_IDLE), еще раз проверяем состояние canstate, и если он ==CAN_IDLE, переводим его в состояние передачи (canstate=CAN_SENDING) и запускаем передачу из выбранного почтового ящика (путем записи флага почтового ящика в регистры CAN_TCR и CAN_IER). Внимание! Перед вызовом CAN_Write должны быть проинициализированы все поля CanTransfer, и сделан вызов CAN_InitMailboxRegisters(&canTransfer).

Как работает CAN_Read: если CAN занят (canstate!=CAN_IDLE), то ничего не делаем, выходим. Иначе переводим CAN на операцию чтения (canstate=CAN_RECEIVING), и запускаем прием почтового ящика (путем записи флага почтового ящика в регистр CAN_IER). Таким образом, для приема данных CAN_Read взводит режим в CAN_RECEIVING, и ждет, пока флаг не станет CAN_IDLE.

Как работает CAN_Handler: в режиме приема (определяется по полю MOT регистра CAN_MMRx почтового ящика x) данные выгружаются в структуру CanTransfer, CAN для программы освобождается (canstate=CAN_IDLE), разрешается для ящика прием следующего сообщения. В режиме передачи не делается ничего, просто CAN для программы освобождается (canstate=CAN_IDLE).

[Практика]

Брать пример basic-can-project "как есть" нецелесообразно, нужно выкинуть оттуда мутную логику, и взять только то, что надо. Лучше всего прием делать по одной группе почтовых ящиков, а передачу - по другой. Прием нужно реализовать исключительно в обработчике прерывания - пусть он всегда заполняет кольцевой буфер данными (вызовы CAN_Read нам уже не нужны, и не нужно заполнять CanTransfer принятыми данными и флагами). Передачу можно сделать периодическими вызовами из основной программы, при этом можно использовать CanTransfer для отслеживания занятости CAN на передачу.

Как добавить поддержку CAN в свой проект, процесс по шагам.

1. Добавляем в проект модуль at91lib\peripherals\can\can.c. Я повыкидывал из can.c весь мусорный код (относящийся к другому чипу, у которого два интерфейса CAN), чтобы было понятнее, как все работает.

2. Добавляем в файл at91lib\board\board.h определения ножек для входа и выхода CAN (в этом примере используются только ножка приема и ножка передачи):

/// RXD: Receive data output, ножка 46
#define PINS_CAN_TRANSCEIVER_RXD {1 << 19, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT}
/// TXD: Transmit data input, ножка 47
#define PINS_CAN_TRANSCEIVER_TXD {1 << 20, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT} 

Ненужные определения ножек PIN_CAN_TRANSCEIVER_RS, PIN_CAN_TRANSCEIVER_RXEN и использующий их код я удалил.

3. Добавляем в начало программы (до основного цикла main) код инициализации CAN (в этом примере выбрана скорость 125 кбод):

   if( CAN_Init( 125, &canTransferTX ) != 1 ) 
   {
      trace_LOG(trace_INFO, "CAN INIT Err!\n\r");
   }
   else
   {
      //настройка 2 ящиков на прием (принимаю данных меньше, чем передаю)
      InitCANInRecept();   //ящики 0 и 1 работают на прием
      //настройка 6 ящиков на передачу
      InitCANOutSend();    //ящики 2..7 работают на передачу
   }

4. Пишем процедуру для опроса интерфейса CAN (её вызовы тупо вставляем в главный цикл main):

void CANpoll (void)
{
   if (CAN_IDLE == canTransferTX.canstate)
   {
      //берем данные для передачи
      if (inCAN != outCAN)
      {
         //либо из приемного кольцевого буфера CAN
         canTransferTX.data_low_reg  = CANbuf[outCAN].d32[0];
         canTransferTX.data_high_reg = CANbuf[outCAN].d32[1];
         outCAN++;
         outCAN &= CAN_BUF_MASK;
         LED_CHANGE();
      }
      else
      {
         //а если их нет, то просто от балды
         canTransferTX.data_low_reg  = rand();
         canTransferTX.data_high_reg = rand();
      }
      //теперь передаем
      canTransferTX.mailbox_number = 2;   //всегда ящик 2, хотя можно использовать еще ящики 3..7
      canTransferTX.mode_reg = AT91C_CAN_MOT_TX | AT91C_CAN_PRIOR;
      canTransferTX.acceptance_mask_reg = AT91C_CAN_MIDvA;
      canTransferTX.identifier = AT91C_CAN_MIDvA & CANID_TX << 18);
      canTransferTX.control_reg   = (AT91C_CAN_MDLC & (0x8 << 16)); // Mailbox Data Length Code
      CAN_InitMailboxRegisters(&canTransferTX);
      CAN_Write(&canTransferTX);
   }
}

Немного комментариев по коду. Процедура, настраивающая почтовый ящик перед передачей - CAN_InitMailboxRegisters. Она требует в качестве параметра структуру CanTransfer с установленными параметрами. В общем, нам это код уже не нужен, так как ящики у нас и так уже настроены на прием (нужно только вписать в регистры ящика новые данные и запустить передачу). Процедура CAN_Write делает запуск передачи. Так как мне было банально лень переписывать, то я оставил мусорный код инициализации canTransferTX, вызов CAN_InitMailboxRegisters и CAN_Write. Когда-нибудь и это тоже пойдет под нож.

Это все! Проект настроен на прием и передачу данных по CAN.

[Аппаратура]

Здесь приведен пример принципиальной схемы физического подключения интерфейса CAN микроконтроллера ARM AT91SAM7X256 к линии передачи. Здесь линия передачи/приема CAN гальванически развязана от микроконтроллера с помощью DC-DC преобразователя AM1L-0505SH30-NZ (он питает драйвер CAN PCA82C250) и изолятора сигналов ADUM3201BRZ (через этот изолятор сигналы CAN_RxD и CAN_TxD подключаются на ножки микроконтроллера 46 и 47 соответственно).

CAN-connecting.png

Для проверки, как работает прием, нужно подключить интерфейс CAN к чему-нибудь, что передает пакеты CAN. Я использовал интерфейс USB-CAN компании Systec Electronic (USB-CANmodul GW-002), Германия. Установка драйверов в систему Windows никаких проблем не вызывает, подключается к компьютеру стандартным шнуром USB.

CAN-USB-CAN-IMG_0825.JPG

Для подключения нужен шнур DB9 мама (втыкается в USB-CAN адаптер Systec Electronic) - DB9 папа (подключается к разъему, соединенному с драйвером CAN PCA82C250). Параллельно сигналам CANL и CANH обязательно должен стоять резистор 120..150 Ом. Вот цоколевка шнура (линия состоит только двух проводов CANL и CANH, даже земли нет):

DB9 мама    DB9 папа
2 CANL      1 CANL
7 CANH      2 CANH

На разъеме адаптера USB-CAN используются только ножки 2 и 7. Полная цоколевка коннектора DB9 папа адаптера USB-CAN USB-CANmodul GW-002 показана на рисунке (вид снаружи на штырьки адаптера).

CAN-USB-CAN-male-connector.png

[Программа PCANView]

Для работы с адаптером USB-CANmodul GW-002 имеется готовая тестовая программа PCANView, позволяющая передавать и принимать данные.

Сразу после запуска программа PCANView просит выбрать адаптер USB-CAN USB-CANmodul, с которым будет осуществляться работа (Device-Nr.:), скорость работы CAN (Baudrate:) и номер канала (для двухканального адаптера). В большинстве случаев здесь надо указать только Baudrate (выбрать из выпадающего списка).

CAN-PCANView-connect-params.png

Следующее окно (Connect to net) позволяет выбрать фильтр для принимаемых сообщений (Message filter), где указывается режим адресации - стандартный или расширенный, и можно задать диапазон для приема. Можно просто нажать кнопку OK, тогда будет выбран стандартный режим адресации, и будут приниматься все сообщения без ограничений.

CAN-PCANView-address-mode-select.png

После нажатия на OK запустится основное окно программы.

CAN-PCANView-main-window.png

Главное окно программы содержит два поля - верхнее (для приема сообщений) и нижнее (для отправки сообщений). Если Вы запустили программу на ARM AT91SAM7X256 и подключили её к адаптеру USB-CANmodul, то в верхнем окне сразу увидите скачущие циферки данных - это то, что передает ARM. В таблице есть следующие столбцы:

Message  показывает адрес ящика, которому пришло сообщение, равно CAN_MB_MID передающих ящиков 2..7 ARM (задается макросом CANID_TX).
Length   показывает длину пакета данных в ящике (в нашем примере всегда 8 байт).
Data     тут прыгают данные, которые генерят вызовы rand() в CANpoll программы ARM
Period   измеренная длительность между принятыми пакетами CAN в миллисекундах
Count    счетчик принятых пакетов
RTR-Per. не разобрался, что это такое
RTR-Cnt. не разобрался, что это такое

Для того, чтобы что-нибудь передать нашей программе в ARM, нужно в меню выбрать Transmit -> New... Появится нехитрое окошко, в котором нужно заполнить параметры для передаваемого пакета.

CAN-PCANView-send-packet-setup.png

В поле ID (Hex): заполняется адрес ящика, на который будет отослан пакет. Здесь вводится шестнадцатеричное число, значение которого должно совпадать с значением, которое записано в регистры CAN_MB_MID принимающих ящиков 0 и 1 ARM (задается макросом CANID_RX, я выбрал адрес 000h). В поле Period задается период в миллисекундах, с которым будут автоматически отправляться сообщения - если оставить 0, то отправка будет срабатывать при нажатии кнопки Пробел в нижнем поле программы PCANView (в этом случае в столбце Period нижнего поля окна программы PCANView будет стоять Wait). В поле Data (1..8): можно вписать значения байт, которые будут передаваться. Если поставить галочку Extended Frame, то будет использоваться не стандартный режим адресации, а расширенный (я эту галку не ставил, так как в моем примере используется стандартный режим). Галочка Remote Request относится к отправке фрейма remote, когда получатель данных запрашивает их у отправителя (это специальная фича протокола CAN).

Вот пример вывода тестовой программы ARM в консоль DBGU - после того, как ей передаст данные программа PCANView:

CAN-DBGU-console.png

[Встраивание обмена по CAN в пользовательскую программу]

Для того, чтобы написать собственную программу на компьютере, которая сможет передавать и принимать данные по шине CAN через USB-CANmodul GW-002, компания Systec Electronic предоставила библиотеку USBCAN32.DLL с подробным описанием её функций и примерами кода (см. документацию в архиве по Ссылке [3]).

[UPD140207 - поле MDLC в регистрах почтового ящика CAN_MSRx и CAN_MCRx]

Аббревиатура MDLC расшифровывается как Mailbox Data Length Code (код длины сообщения). Это поле четырехбитное, и оно присутствует в регистрах CAN_MCRx (используется при передаче) и CAN_MSRx (используется при приеме). Это поле удобно использовать для передачи дополнительной информации, например для передачи кода команды. Однако эта дополнительная возможность работает только в том случае, когда с обоих сторон обмена по шине CAN (со стороны отправителя и получателя) стоят микроконтроллеры AT91SAM7.

4 битами можно закодировать 16 вариантов команд (от 0 до 15), и это весьма полезная возможность, учитывая маленький объем данных полезных данных почтового ящика. Однако помните, что значение MDLC меньше 8 обрезает количество передаваемых данных в ящике. Так например, если указать MDLC=0, то все 8 байт в почтовом ящике придут нулевые. Если указать MDLC=1, то будут переданы данные только в 0 байте (байты 1..7 придут нулевые), если MDLC=2, то только в 0 и 1 байте (байты 2..7 придут нулевые) и так далее. При значении MDLC больше 7 будут приходить данные всех 8 байт ящика.

[Ссылки]

1. Controller Area Network site:ru.wikipedia.orgЧто такое CAN шина? site:equs.ru.
2. USB-CANmodul1 site:www.systec-electronic.com.
3. Исправленный проект basic-can-project - там максимально все упрощено. В архиве проекта имеется документация по адаптеру USB-CANmodul GW-002, установке драйверов, программе PCANView и функциям библиотеки USBCAN32.DLL (файл doc\USB-CAN\L-487e_13.pdf).
4. Оригинальный пример basic-can-project от Atmel.
5. Visual Studio C#: работа с USB-CAN адаптером SYSTEC.
6. AT91SAM7X: контроллер CAN.