Саму основу для сниффера я сделал еще давно — когда необходимо было разработать девборду для "тренировок" с USB и CAN (под контроллеры термодатчиков):

Протокол работы железки я уже описал, теперь опишу исходники ее прошивки.
Итак, первое, что нам нужно для работающего CAN-сниффера — это возможность одновременной работы и CAN, и USB. В дешевой нише STM32 это умеют STM32F0x2 (072 и 042; на работе для термодатчиков я закупал 042, для дома же купил на али 2 десятка 072, тогда это было примерно 60 рублей за штучку).
В отличие от 103-х, где служебный буфер в памяти CAN и USB делять не "по-братски", и он может принадлежать лишь одной из периферий, в 0x2 CAN забирает лишь 256 последних байт буфера USB. Поэтому для USB доступно 768 байт буфера (в принципе, этого вполне достаточно, если не делать сложное составное устройство).
Начнем с USB. Сниффер будет "выдавать" себя за PL2303 (мне нравится, что модуль ядра выделяет для него устройство /dev/ttyUSBx, а не позорный /dev/ttyACMx, как под обычный USB-CDC; кроме того, в некоторых некошерных дистрибутивах вроде бубунты могут быть проблемы с USB-CDC: при подключении запускается modemd и захватывает файл устройства в свое личное распоряжение).
В заголовочном файле usb_defs.h определяем USB_BTABLE_SIZE как 768 байт. Там же определяем размеры буферов конечных точек 0 и 1 (конечная точка 1 — interrupt IN — использоваться при работе не будет, и в принципе можно было бы ее не определять). Там же нам понадобится определить структуры служебных регистров:
typedef struct{
__IO uint32_t EPnR[STM32ENDPOINTS];
__IO uint32_t RESERVED[STM32ENDPOINTS];
__IO uint32_t CNTR;
__IO uint32_t ISTR;
__IO uint32_t FNR;
__IO uint32_t DADDR;
__IO uint32_t BTABLE;
__IO uint32_t LPMCSR;
__IO uint32_t BCDR;
} USB_TypeDef;
и регистров описания конечных точек:
typedef struct{
__IO uint16_t USB_ADDR_TX;
__IO uint16_t USB_COUNT_TX;
__IO uint16_t USB_ADDR_RX;
__IO uint16_t USB_COUNT_RX;
} USB_EPDATA_TypeDef;
В файлах usb_lib.c и usb_lib.h разместим "низкоуровневые" функции USB. В принципе, эту иерархию не я придумал: когда я только начал заниматься USB, вменяемой реализации на просторах интернета не нашел. Один из пользователей easyelectronix выложил простую реализацию USB-HID. Собственно, на основе ее я и сделал свои экземпляры USB-HID, CDC и эмуляцию PL2303.
В заголовочном файле определим различные стандартные типы запросов и прочее, что нам понадобится (строки с 35 по 77). Простые макросы для понимания, является ли пакет входящим или исходящим и есть ли там данные SETUP. Для работы с регистрами конечных точек нам понадобится определить пару макросов:
#define KEEP_DTOG_STAT(EPnR) (EPnR & ~(USB_EPnR_STAT_RX|USB_EPnR_STAT_TX|USB_EPnR_DTOG_RX|USB_EPnR_DTOG_TX))
#define KEEP_DTOG(EPnR) (EPnR & ~(USB_EPnR_DTOG_RX|USB_EPnR_DTOG_TX))
Здесь сразу скажу: надо помнить, что в регистрах EPnR некоторые флаги имеют свойство toggle. Поэтому не повторяйте моих ошибок: держите это всегда в уме, и когда нужно лишь какой-то флаг установить/сбросить, обнуляйте биты всех ненужных toggle-флагов! Эти макросы собственно и занимаются тем, что оставляют нетронутыми флаги DTOG (они нам не нужны, т.к. мы не пользуемся двойной буферизацией) и STAT (а это важно, когда мы получаем данные, но не хотим сразу же отправлять ACK, пока буфер не будет обработан).
Еще для работы конечного автомата состояния USB понадобится определить его состояния:
typedef enum{
USB_STATE_DEFAULT,
USB_STATE_ADDRESSED,
USB_STATE_CONFIGURED,
USB_STATE_CONNECTED
} USB_state;
Там же определяем макросы для задания строковой информации (_USB_STRING_, _USB_LANG_ID_) и вспомогательные структуры для разбора конфигурационного пакета (config_pack_t), настройки конечной точки (ep_t) самого USB(usb_dev_t — эта структура используется, чтобы поменьше глобальных переменных заводить). Из /usr/include/linux/usb/cdc.h копируем определение структуры usb_LineCoding (в принципе, можно обрабатывать SET_LINE_CODING, меняя скорость, скажем, USART1; но в данном случае это не нужно, а USART1 у меня использовался исключительно для отладочных сообщений).
В файле usb_lib.c определяемдескрипторы устройства (достать их несложно, если "натравить" на "настоящий китайский" PL2303 lsusb -v). Здесь же как WEAK определены заглушки для обработчиков стандартных запросов SET_LINE_CODING (изменение параметров последовательного порта: скорости, четности и т.п.), SET_CONTROL_LINE_STATE (аппаратное управление потоком) и SEND_BREAK (конец связи).
В отличие от "обычного" USB CDC у pl2303 есть еще и vendor-запросы. Нафиг они нужны — непонятно, однако, благодаря тому, что кто-то уже отреверсил подобную железяку и написал для нее модуль ядра, в исходниках
/usr/src/linux/drivers/usb/serial/pl2303.c можно посмотреть, как оно работает. Собственно, оттуда я и утащил функцию-обработчик:
void WEAK vendor_handler(config_pack_t *packet){
if(packet->bmRequestType & 0x80){ // read
uint8_t c;
switch(packet->wValue){
case 0x8484:
c = 2;
break;
case 0x0080:
c = 1;
break;
case 0x8686:
c = 0xaa;
break;
default:
c = 0;
}
EP_WriteIRQ(0, &c, 1);
}else{ // write ZLP
EP_WriteIRQ(0, (uint8_t *)0, 0);
}
}
Возможно, в оригинале эти запросы таили что-то эдакое, но в ядре они используются лишь для идентификации — что на том конце действительно PL2303. Во всяком случае, с таким минимумом устройство работает и под android (возможно, будет работать и под игровыми приставками, мне это безразлично).
При работе со строковыми дескрипторами иногда может оказаться, что дескриптор не влезает в стандартный объем 64-байтной посылки (особенно большими размерами славятся HID-дескрипторы), поэтому такие вещи надо разбить на несколько посылок. Отправляются такие дескрипторы только в начале коннекта, поэтому я решил не заморачиваться с конечным автоматом, а сделать блокирующую запись:
static void wr0(const uint8_t *buf, uint16_t size){
if(setup_packet.wLength < size) size = setup_packet.wLength; // shortened request
if(size < endpoints[0].txbufsz){
EP_WriteIRQ(0, buf, size);
return;
}
while(size){
uint16_t l = size;
if(l > endpoints[0].txbufsz) l = endpoints[0].txbufsz;
EP_WriteIRQ(0, buf, l);
buf += l;
size -= l;
uint8_t needzlp = (l == endpoints[0].txbufsz) ? 1 : 0;
if(size || needzlp){ // send last data buffer
uint16_t status = KEEP_DTOG(USB->EPnR[0]);
// keep DTOGs, clear CTR_RX,TX, set TX VALID, leave stat_Rx
USB->EPnR[0] = (status & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX|USB_EPnR_STAT_RX))
^ USB_EPnR_STAT_TX;
uint32_t ctr = 1000000;
while(--ctr && (USB->ISTR & USB_ISTR_CTR) == 0){IWDG->KR = IWDG_REFRESH;};
if((USB->ISTR & USB_ISTR_CTR) == 0){
return;
}
if(needzlp) EP_WriteIRQ(0, (uint8_t*)0, 0);
}
}
}
(здесь еще надо было учесть, что если мы отправляем в последней посылке ровно 64 байта, то нужно еще и ZPL — посылку нулевой длины — отправить).
Работать с USB мы будем на прерываниях, поэтому все самое интересное начинается в обработчике прерывания usb_isr():
void usb_isr(){
if (USB->ISTR & USB_ISTR_RESET){
// Reinit registers
USB->CNTR = USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_SUSPM | USB_CNTR_WKUPM;
USB->ISTR = 0;
// Endpoint 0 - CONTROL
// ON USB LS size of EP0 may be 8 bytes, but on FS it should be 64 bytes!
lastaddr = LASTADDR_DEFAULT; // roll back to beginning of buffer
EP_Init(0, EP_TYPE_CONTROL, USB_EP0_BUFSZ, USB_EP0_BUFSZ, EP0_Handler);
// clear address, leave only enable bit
USB->DADDR = USB_DADDR_EF;
// state is default - wait for enumeration
USB_Dev.USB_Status = USB_STATE_DEFAULT;
}
if(USB->ISTR & USB_ISTR_CTR){
// EP number
uint8_t n = USB->ISTR & USB_ISTR_EPID;
// copy status register
uint16_t epstatus = USB->EPnR[n];
// copy received bytes amount
endpoints[n].rx_cnt = USB_BTABLE->EP[n].USB_COUNT_RX & 0x3FF; // low 10 bits is counter
// check direction
if(USB->ISTR & USB_ISTR_DIR){ // OUT interrupt - receive data, CTR_RX==1 (if CTR_TX == 1 - two pending transactions: receive following by transmit)
if(n == 0){ // control endpoint
if(epstatus & USB_EPnR_SETUP){ // setup packet -> copy data to conf_pack
EP_Read(0, (uint8_t*)&setup_packet);
ep0dbuflen = 0;
// interrupt handler will be called later
}else if(epstatus & USB_EPnR_CTR_RX){ // data packet -> push received data to ep0databuf
ep0dbuflen = endpoints[0].rx_cnt;
EP_Read(0, (uint8_t*)&ep0databuf);
}
}
}
// call EP handler
if(endpoints[n].func) endpoints[n].func(endpoints[n]);
}
if(USB->ISTR & USB_ISTR_SUSP){ // suspend -> still no connection, may sleep
usbON = 0;
USB->CNTR |= USB_CNTR_FSUSP | USB_CNTR_LPMODE;
USB->ISTR = ~USB_ISTR_SUSP;
}
if(USB->ISTR & USB_ISTR_WKUP){ // wakeup
USB->CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LPMODE); // clear suspend flags
USB->ISTR = ~USB_ISTR_WKUP;
}
}
Здесь мы обрабатываем такие прерывания, как RESET — хост говорит устройству, что нужно заново инициализировать USB, CTR (correct transfer) — прерывание по приему или передаче данных, а также вспомогательные SUSP и WKUP — "засни" и "проснись".
В обработке OUT-запросов (т.е. входящих для устройства) пришлось пойти на хитрость: т.к. некоторые вещи (как тот же LINECODING) передаются в "два захода", необходимо отдельно заполнять данными setup_packet и вспомогательный ep0databuf (где и лежат эти данные по установке LINECODING). Данные для LINECODING приходят так: сначала без флага SETUP приходит нужная информация, а потом уже с этим флагом — команда запроса SET_LINECODING. И, соответственно, вызывается процедура обработчика запроса.
Чтобы USB корректно работало, сначала нам надо настроить все конечные точки: выдать им адреса и размеры буферов, определить направление, задать функцию-обработчик. Это делается в EP_Init:
int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*func)()){
if(number >= STM32ENDPOINTS) return 4; // out of configured amount
if(txsz > USB_BTABLE_SIZE || rxsz > USB_BTABLE_SIZE) return 1; // buffer too large
if(lastaddr + txsz + rxsz >= USB_BTABLE_SIZE) return 2; // out of btable
USB->EPnR[number] = (type << 9) | (number & USB_EPnR_EA);
USB->EPnR[number] ^= USB_EPnR_STAT_RX | USB_EPnR_STAT_TX_1;
if(rxsz & 1 || rxsz > 512) return 3; // wrong rx buffer size
uint16_t countrx = 0;
if(rxsz < 64) countrx = rxsz / 2;
else{
if(rxsz & 0x1f) return 3; // should be multiple of 32
countrx = 31 + rxsz / 32;
}
USB_BTABLE->EP[number].USB_ADDR_TX = lastaddr;
endpoints[number].tx_buf = (uint16_t *)(USB_BTABLE_BASE + lastaddr);
endpoints[number].txbufsz = txsz;
lastaddr += txsz;
USB_BTABLE->EP[number].USB_COUNT_TX = 0;
USB_BTABLE->EP[number].USB_ADDR_RX = lastaddr;
endpoints[number].rx_buf = (uint8_t *)(USB_BTABLE_BASE + lastaddr);
lastaddr += rxsz;
// buffer size: Table127 of RM
USB_BTABLE->EP[number].USB_COUNT_RX = countrx << 10;
endpoints[number].func = func;
return 0;
}
Кому-то нравится руками задавать адреса буферов данных, я же решил все упростить — в переменной lastaddr хранится последний свободный адрес в буфере (при RESET эта переменная реинициируется на начало буфера). Дальше все по коду понятно.
Для разбора данных, поступающих на управляющую точку EP0, вызывается функция EP0_Handler (не буду весь ее текст приводить здесь, она длинная). Из остальных конечных точек, как я уже говорил, точка EP1 у нас хоть и определена, но не используется. А для приема/передачи заводим односторонние точки EP2 и EP3 (все эти данные хранятся в дескрипторе устройства, поэтому размеры буферов и направление передачи конечных точек нужно согласовывать с данными в дескрипторе). Их обработчики будут уже в другом файле — с более высокоуровневыми вещами.
У STM32F0x2 регистры данных USB_TX пишутся по 16 бит (в STM32F103 эмулируется 32-битное хранилище, т.е. писать надо как 32-битный блок, но активны там только 16 бит), а USB_RX вообще можно читать побайтно (у 103 они тоже эмулируют 32-битные блоки). В общем, здесь все проще:
void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size){
uint8_t i;
if(size > USB_TXBUFSZ) size = USB_TXBUFSZ;
uint16_t N2 = (size + 1) >> 1;
// the buffer is 16-bit, so we should copy data as it would be uint16_t
uint16_t *buf16 = (uint16_t *)buf;
for (i = 0; i < N2; i++){
endpoints[number].tx_buf[i] = buf16[i];
}
USB_BTABLE->EP[number].USB_COUNT_TX = size;
}
void EP_Write(uint8_t number, const uint8_t *buf, uint16_t size){
EP_WriteIRQ(number, buf, size);
uint16_t status = KEEP_DTOG(USB->EPnR[number]);
// keep DTOGs, clear CTR_TX & set TX VALID to start transmission
USB->EPnR[number] = (status & ~(USB_EPnR_CTR_TX)) ^ USB_EPnR_STAT_TX;
}
int EP_Read(uint8_t number, uint8_t *buf){
int n = endpoints[number].rx_cnt;
if(n){
for(int i = 0; i < n; ++i)
buf[i] = endpoints[number].rx_buf[i];
}
return n;
}
EP_WriteIRQ отличается от EP_Write тем, что вызывается внутри обработчиков прерывания, поэтому в ней флаги EPnR не меняются.
В файле usb.c лежат сравнительно высокоуровневые функции (правда, я и USB_setup зачем-то здесь оставил).
Собственно, настройка USB и начинается с USB_setup:
void USB_setup(){
RCC->APB1ENR |= RCC_APB1ENR_CRSEN | RCC_APB1ENR_USBEN; // enable CRS (hsi48 sync) & USB
RCC->CFGR3 &= ~RCC_CFGR3_USBSW; // reset USB
RCC->CR2 |= RCC_CR2_HSI48ON; // turn ON HSI48
uint32_t tmout = 16000000;
while(!(RCC->CR2 & RCC_CR2_HSI48RDY)){if(--tmout == 0) break;}
FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;
CRS->CFGR &= ~CRS_CFGR_SYNCSRC;
CRS->CFGR |= CRS_CFGR_SYNCSRC_1; // USB SOF selected as sync source
CRS->CR |= CRS_CR_AUTOTRIMEN; // enable auto trim
CRS->CR |= CRS_CR_CEN; // enable freq counter & block CRS->CFGR as read-only
RCC->CFGR |= RCC_CFGR_SW;
// allow RESET and CTRM interrupts
USB->CNTR = USB_CNTR_RESETM | USB_CNTR_WKUPM;
// clear flags
USB->ISTR = 0;
// and activate pullup
USB->BCDR |= USB_BCDR_DPPU;
NVIC_EnableIRQ(USB_IRQn);
}
Тактируем USB от HSI48, что позволяет не цеплять внешний кварц. Используем автокоррекцию HSI48 от USB SOF. Для начала разрешаем только прерывания RESET и WKUP (остальное будем разрешать уже после настройки конечных точек).
IN/OUT запросы данных обрабатываем этими фукнциями:
static void transmit_Handler(){ // EP3IN
tx_succesfull = 1;
uint16_t epstatus = KEEP_DTOG_STAT(USB->EPnR[3]);
// clear CTR keep DTOGs & STATs
USB->EPnR[3] = (epstatus & ~(USB_EPnR_CTR_TX)); // clear TX ctr
}
static void receive_Handler(){ // EP2OUT
rxNE = 1;
uint16_t epstatus = KEEP_DTOG_STAT(USB->EPnR[2]);
USB->EPnR[2] = (epstatus & ~(USB_EPnR_CTR_RX)); // clear RX ctr
}
Обработчик IN (выходящие данные) ощичает флаг CTR_TX и выставляет внутренний флаг tx_succesfull (говорящий о том, что предыдущая посылка отправлена и можно отправлять следующую). А в обработчике OUT (входящие данные) мы выствляем внутренний флаг rxNE (говорящий, что в буфере есть данные и их можно считать) и очищаем флаг CTR_RX. Выставлять ACK мы будем лишь после того, как данные из буфера будут обработаны!
Для того, чтобы не блокировать МК на время отправки мелких объемов данных (а в основном они значительно меньше 64 байт), в функции USB_send есть возможность "отложенной" отправки данных:
void USB_send(const uint8_t *buf, uint16_t len){
if(!usbON || !len) return;
if(len > USB_TXBUFSZ-1 - buflen){
usbwr(usbbuff, buflen);
buflen = 0;
}
if(len > USB_TXBUFSZ-1){
USB_send_blk(buf, len);
return;
}
while(len--) usbbuff[buflen++] = *buf++;
}
(последняя строчка просто помещает маленькие объемы данных во вспомогательный буфер, который будет отправлен в последующем при помощи функции send_next()).
Из основного цикла main на каждом проходе надо запускать функцию usb_proc:
void usb_proc(){
switch(USB_Dev.USB_Status){
case USB_STATE_CONFIGURED:
// make new BULK endpoint
// Buffer have 1024 bytes, but last 256 we use for CAN bus (30.2 of RM: USB main features)
EP_Init(1, EP_TYPE_INTERRUPT, USB_EP1BUFSZ, 0, EP1_Handler); // IN1 - transmit
EP_Init(2, EP_TYPE_BULK, 0, USB_RXBUFSZ, receive_Handler); // OUT2 - receive data
EP_Init(3, EP_TYPE_BULK, USB_TXBUFSZ, 0, transmit_Handler); // IN3 - transmit data
USB_Dev.USB_Status = USB_STATE_CONNECTED;
break;
case USB_STATE_DEFAULT:
case USB_STATE_ADDRESSED:
if(usbON){
usbON = 0;
}
break;
default: // USB_STATE_CONNECTED - send next data portion
if(!usbON) return;
send_next();
}
}
Здесь анализируются состояния КА USB. Скажем, в состоянии USB_STATE_CONFIGURED (EP0 настроена, все дескрипторы отправлены) нужно настроить остальные конечные точки. В состояниях USB_STATE_DEFAULT (начальное) и USB_STATE_ADDRESSED (только прошла процедура адресации) USB еще пользоваться нельзя — поэтому снимаем флаг usbON. А в состоянии USB_STATE_CONNECTED вызываем ту самую send_next().
Более высокоуровневую функцию USB_receive вызываем уже откуда-нибудь извне. Она возвращает количество считанных данных и заполняет ими буфер. Ну, а т.к. данные теперь надежно сохранены, можно отправлять хосту ACK.
uint8_t USB_receive(uint8_t *buf){
if(!usbON || !rxNE) return 0;
uint8_t sz = EP_Read(2, buf);
uint16_t epstatus = KEEP_DTOG(USB->EPnR[2]);
// keep stat_tx & set ACK rx
USB->EPnR[2] = (epstatus & ~(USB_EPnR_STAT_TX)) ^ USB_EPnR_STAT_RX;
rxNE = 0;
return sz;
}
Уже в main.c размещаем функцию буферизованного чтения из USB с выставлением флага готовности по '\n':
static char *get_USB(){
static char tmpbuf[USBBUF+1], *curptr = tmpbuf;
static int rest = USBBUF;
uint8_t x = USB_receive((uint8_t*)curptr);
if(!x) return NULL;
curptr[x] = 0;
if(x == 1 && *curptr == 0x7f){ // backspace
if(curptr > tmpbuf){
--curptr;
USND("\b \b");
}
return NULL;
}
USB_sendstr(curptr); // echo
if(curptr[x-1] == '\n'){ // || curptr[x-1] == '\r'){
curptr = tmpbuf;
rest = USBBUF;
// omit empty lines
if(tmpbuf[0] == '\n') return NULL;
// and wrong empty lines
if(tmpbuf[0] == '\r' && tmpbuf[1] == '\n') return NULL;
return tmpbuf;
}
curptr += x; rest -= x;
if(rest <= 0){ // buffer overflow
usart_send("\nUSB buffer overflow!\n"); transmit_tbuf();
curptr = tmpbuf;
rest = USBBUF;
}
return NULL;
}
Здесь происходит эхо введенных символов и обработка backspace (чтобы можно было удалять неправильно набранные символы).
ОК, с USB закончили. Пора переходить к CAN.
В can.h определим структуру
typedef struct{
uint8_t data[8]; // up to 8 bytes of data
uint8_t length; // data length
uint16_t ID; // ID of receiver
} CAN_message;
В ней содержатся данные (до 8 байт) для стандартной CAN-посылки, длина данных и идентификатор получателя этих данных. И определим флаги состояния CAN:
typedef enum{
CAN_STOP,
CAN_READY,
CAN_BUSY,
CAN_OK,
CAN_FIFO_OVERRUN
} CAN_status;
В can.c определяем основные функции для работы с CAN. Входящие данные буферизуются в массиве messages[CAN_INMESSAGE_SIZE].
Настройку CAN делаем по сниппетам. Разве что аргументом этой функции является скорость в кбод, поэтому проверяем в начале, допустимым ли является значение скорости.
void CAN_setup(uint16_t speed){
LED_off(LED1);
if(speed == 0) speed = oldspeed;
else if(speed < 50) speed = 50;
else if(speed > 3000) speed = 3000;
oldspeed = speed;
uint32_t tmout = 16000000;
if(CANID == 0xFFFF) readCANID();
// Configure GPIO: PB8 - CAN_Rx, PB9 - CAN_Tx
/* (1) Select AF mode (10) on PB8 and PB9 */
/* (2) AF4 for CAN signals */
GPIOB->MODER = (GPIOB->MODER & ~(GPIO_MODER_MODER8 | GPIO_MODER_MODER9))
| (GPIO_MODER_MODER8_AF | GPIO_MODER_MODER9_AF); /* (1) */
GPIOB->AFR[1] = (GPIOB->AFR[1] &~ (GPIO_AFRH_AFRH0 | GPIO_AFRH_AFRH1))\
| (4 << (0 * 4)) | (4 << (1 * 4)); /* (2) */
/* Enable the peripheral clock CAN */
RCC->APB1ENR |= RCC_APB1ENR_CANEN;
/* Configure CAN */
/* (1) Enter CAN init mode to write the configuration */
/* (2) Wait the init mode entering */
/* (3) Exit sleep mode */
/* (4) Normal mode, set timing to 100kb/s: TBS1 = 4, TBS2 = 3, prescaler = 60 */
/* (5) Leave init mode */
/* (6) Wait the init mode leaving */
/* (7) Enter filter init mode, (16-bit + mask, bank 0 for FIFO 0) */
/* (8) Acivate filter 0 for two IDs */
/* (9) Identifier mask */
/* (10) Set the Id mask */
/* (12) Leave filter init */
/* (13) Set error interrupts enable */
CAN->MCR |= CAN_MCR_INRQ; /* (1) */
while((CAN->MSR & CAN_MSR_INAK)!=CAN_MSR_INAK) /* (2) */
{
if(--tmout == 0) break;
}
CAN->MCR &=~ CAN_MCR_SLEEP; /* (3) */
CAN->MCR |= CAN_MCR_ABOM; /* allow automatically bus-off */
CAN->BTR |= 2 << 20 | 3 << 16 | (6000/speed) << 0; /* (4) */
CAN->MCR &=~ CAN_MCR_INRQ; /* (5) */
tmout = 16000000;
while((CAN->MSR & CAN_MSR_INAK)==CAN_MSR_INAK) if(--tmout == 0) break; /* (6) */
// accept ALL
CAN->FMR = CAN_FMR_FINIT; /* (7) */
CAN->FA1R = CAN_FA1R_FACT0 | CAN_FA1R_FACT1; /* (8) */
/* (9, 10) */
CAN->sFilterRegister[0].FR1 = (1<<21)|(1<<5); // all odd IDs
CAN->FFA1R = 2; // filter 1 for FIFO1, filter 0 - for FIFO0
CAN->sFilterRegister[1].FR1 = (1<<21); // all even IDs
CAN->FMR &=~ CAN_FMR_FINIT; /* (12) */
CAN->IER |= CAN_IER_ERRIE | CAN_IER_FOVIE0 | CAN_IER_FOVIE1; /* (13) */
/* Configure IT */
/* (14) Set priority for CAN_IRQn */
/* (15) Enable CAN_IRQn */
NVIC_SetPriority(CEC_CAN_IRQn, 0); /* (14) */
NVIC_EnableIRQ(CEC_CAN_IRQn); /* (15) */
can_status = CAN_READY;
}
Поначалу фильтр позволяет принимать абсолютно все сообщения: нечетные в FIFO0 и четные в FIFO1. Далее фильтры можно будет перенастроить.
В отличие от USB, у CAN в прерывании анализируются лишь ошибки, а вот для работы с данными из main постоянно запускается can_proc() (объемная, листинг здесь размещать не буду). Здесь рассматриваются различные флаги и обрабатываются ошибки линии: если на шине нет никого, либо накапливается много ошибок, CAN переинициализируется.
Функция can_send(uint8_t *msg, uint8_t len, uint16_t target_id) отправляет сообщение msg длиной len с идентификатором target_id. В принципе, можно было бы уменьшить количество аргументов этой функции, если поместить эти данные в обертку — структуру CAN_message. Функция находит первый свободный "почтовый ящик", заполняет в нем регистры данных и инициализирует посылку.
Функция can_process_fifo(uint8_t fifo_num) запускается при наличии данных в соответствующем буфере FIFO. Все данные помещаются в массив-буфер, откуда впоследствии их можно считать. Если буфер полон, то функция "надеется", что к следующем запуску можно будет его опустошить (иначе происходит переполнение FIFO — ничего с этим не поделать).
Как все это работает.
При получении определенной команды по USB, она анализируется (proto.c), и если это команда на передачу данных (s), запускается функция
TRUE_INLINE void sendCANcommand(char *txt){
CAN_message *msg = parseCANmsg(txt);
if(!msg) return;
uint32_t N = 1000000;
while(CAN_BUSY == can_send(msg->data, msg->length, msg->ID)){
if(--N == 0) break;
}
}
В ней происходит парсинг введенных пользователем данных, и если все ОК, то отправляется сообщение.
При поступлении сообщения с CAN, его содержимое выводится на терминал.
Для упрощения создания фильтров я добавил еще и софтовый фильтр — для возможности отклонения сообщений с ID из списка (иначе пришлось бы лепить аппаратный фильтр с нужной маской и ID, а считать-то лень!).
Функция add_filter позволяет добавить или удалить (если фильтр не содержит данных) фильтр с номером от 0 до 27. Т.е. можно удалить фильтры по умолчанию и заменить их своими. В функции идет довольно-таки длинный парсинг текстовых данных: разбирается, в каком режиме фильтр (список или маска), а далее заполняются сами данные фильтра.
При разработке сниффера я тестировал его на платах термодатчиков и разрабатываемой управлялки шаговыми двигателями.

Journal information