На основе примера 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 не используются.
В примере 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 к чему-нибудь, что передает пакеты CAN. Я использовал интерфейс USB-CAN компании Systec Electronic (USB-CANmodul GW-002), Германия. Установка драйверов в систему Windows никаких проблем не вызывает, подключается к компьютеру стандартным шнуром USB.
Для подключения нужен шнур 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 показана на рисунке (вид снаружи на штырьки адаптера).
[Программа PCANView]
Для работы с адаптером USB-CANmodul GW-002 имеется готовая тестовая программа PCANView, позволяющая передавать и принимать данные.
Сразу после запуска программа PCANView просит выбрать адаптер USB-CAN USB-CANmodul, с которым будет осуществляться работа (Device-Nr.:), скорость работы CAN (Baudrate:) и номер канала (для двухканального адаптера). В большинстве случаев здесь надо указать только Baudrate (выбрать из выпадающего списка).
Следующее окно (Connect to net) позволяет выбрать фильтр для принимаемых сообщений (Message filter), где указывается режим адресации - стандартный или расширенный, и можно задать диапазон для приема. Можно просто нажать кнопку OK, тогда будет выбран стандартный режим адресации, и будут приниматься все сообщения без ограничений.
После нажатия на OK запустится основное окно программы.
Главное окно программы содержит два поля - верхнее (для приема сообщений) и нижнее (для отправки сообщений). Если Вы запустили программу на 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... Появится нехитрое окошко, в котором нужно заполнить параметры для передаваемого пакета.
В поле 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 в пользовательскую программу]
Для того, чтобы написать собственную программу на компьютере, которая сможет передавать и принимать данные по шине 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. |
Комментарии
и ещё, судя по картинке, в USB-CANModule можно задать произвольную частоту, рассчитав регистры
BTR0 = (SJW & 3)
microsin: спасибо за комментарий. Подозреваю, что Вы правы - можно самому разобраться в кишочках CAN и настроить его на любую нужную скорость. Я пока не копал так глубоко, а всего лишь использовал готовый софт Atmel (из примеров IAR). Статья и была про это - как воспользоваться готовеньким, а не про то, как раскочегарить CAN на полную мощность.
RSS лента комментариев этой записи