CAN-шина применяется в автомобилях для связи разных блоков между собой. Довольно легко можно подключиться к этому интерфейсу, с целью прослушивания либо передачи собственных данных.

В отрыве от автомобилей, шину CAN можно использовать как основную среду передачи в своих проектах — она отлично подходит для умного дома, для сети датчиков, а также просто как удобный развязанный сетевой интерфейс без заморочек с Ethernet.

Подробное описание шины CAN я приводил здесь.

Для связи потребуется CAN-драйвер, он описан мной в прошлой статье. Вкратце повторюсь — используем микросхему PCA82C250, опторазвязку и несколько компонентов для защиты от помех. Используется один драйвер на каждое устройство, то есть таких драйверов нужно минимум два (при наличии двух устройств на шине).

Hello world, или передадим что-нибудь в пределах процессора

Инженеры ST предусмотрели для отладочных целей удобный режим «тихий loopback». В этом режиме выход интерфейса не отправляет информацию наружу, а замыкается на вход — типа того, как накидывают перемычку на RX и TX в USART. В остальном работа в этом режиме ничем не отличается от стандарта.

Режим silent loopback

CAN-модуль STM32, режим Silent + Loopback

На самом деле, этот режим является комбинацией двух других режимов:

Режим silent

CAN-модуль STM32, режим Silent

Режим loopback

CAN-модуль STM32, режим Loopback

Сначала конфигурируем интерфейс CAN:

CAN_InitTypeDef        CAN_InitStructure;
CAN_FilterInitTypeDef  CAN_FilterInitStructure;
CanTxMsg TxMessage;

void CAN1_Config(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;

	/* CAN GPIOs configuration */

	/* Enable GPIO clock */
	RCC_AHB1PeriphClockCmd(CAN1_GPIO_CLK, ENABLE);

	/* Connect CAN pins to AF9 */
	GPIO_PinAFConfig(CAN1_GPIO_PORT, CAN1_RX_SOURCE, CAN1_AF_PORT);
	GPIO_PinAFConfig(CAN1_GPIO_PORT, CAN1_TX_SOURCE, CAN1_AF_PORT); 

	/* Configure CAN RX and TX pins */
	GPIO_InitStructure.GPIO_Pin = CAN1_RX_PIN | CAN1_TX_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
	GPIO_Init(CAN1_GPIO_PORT, &GPIO_InitStructure);

	/* CAN configuration */
	/* Enable CAN clock */
	RCC_APB1PeriphClockCmd(CAN1_CLK, ENABLE);

	/* CAN register init */
	CAN_DeInit(CAN1);

	/* CAN cell init */
	CAN_InitStructure.CAN_TTCM = DISABLE;
	CAN_InitStructure.CAN_ABOM = DISABLE;
	CAN_InitStructure.CAN_AWUM = DISABLE;
	CAN_InitStructure.CAN_NART = ENABLE;
	CAN_InitStructure.CAN_RFLM = DISABLE;
	CAN_InitStructure.CAN_TXFP = DISABLE;
	CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
	CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;

	/* CAN Baudrate = 125 kBps (CAN clocked at 42 MHz) */
	CAN_InitStructure.CAN_BS1 = CAN_BS1_3tq;
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
	CAN_InitStructure.CAN_Prescaler = 12;
	CAN_Init(CAN1, &CAN_InitStructure);
	/* CAN filter init */
	CAN_FilterInitStructure.CAN_FilterNumber = 0;
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
	CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = 0;
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
	CAN_FilterInit(&CAN_FilterInitStructure);

	/* Transmit Structure preparation */
	TxMessage.StdId = 0x321;
	TxMessage.ExtId = 0x01;
	TxMessage.RTR = CAN_RTR_DATA;
	TxMessage.IDE = CAN_ID_STD;
	TxMessage.DLC = 1;

	/* Enable FIFO 0 message pending Interrupt */
	CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
	CAN_ITConfig(CAN1, CAN_IT_FMP1, ENABLE);
	CAN_ITConfig(CAN1, CAN_IT_TME, ENABLE);
}

Всё как обычно — запускаем тактирование GPIO и CAN (используем CAN1), настраиваем ножки порта GPIO, и переходим к настройке CAN — общие настройки, скорость (точнее, тайминги), настройки приёмника сообщений и параметры адресации. В конце включаем прерывания по приёму.

Ремарка про приём сообщений. Входящие сообщения сначала проходят фильтр. Фильтр можно настроить на отсев только какого-то конкретного сообщения, или группы адресов — благодаря этому уменьшается нагрузка на процессор, ему не приходится самому отфильтровывать ненужные сообщения — это делается аппаратно.

CAN-модуль STM32, структура

После отсева сообщения попадают в один из трёх «почтовых ящиков», в зависимости от заполненности каждого из них. Я не наблюдал случаи приёма в два и тем более в три ящика — возможно, это будет проявляться только на больших скоростях приёма и медленной обработке сообщений процессором. После попадания сообщения в ящик вызывается соответствующее прерывание.

Окей, интерфейс настроен. Передача очень проста:

CanTxMsg TxMessage;
TxMessage.StdId=134222929;
TxMessage.ExtId=458227712;  
TxMessage.Data[0] = 0;
TxMessage.Data[1] = 1;
TxMessage.Data[2] = 2;
TxMessage.Data[3] = 3;
TxMessage.Data[4] = 4;
TxMessage.Data[5] = 5;
TxMessage.Data[6] = 6;
TxMessage.Data[7] = 7;
CAN_Transmit(CAN1, &TxMessage);

Задаём адрес сообщения и данные. Передаём функцией из StdPeriph.

Нужно ещё указать обработчик прерывания приёма сообщения.

void CAN1_RX0_IRQHandler (void)
{
	if (CAN_GetITStatus(CAN1,CAN_IT_FMP0))
	{
		CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0);
		CanRxMsg msg_buf;
		CAN_Receive(CAN1, CAN_FIFO0, &msg_buf);

		uint32_t a, b, data;
		data=((((((msg_buf.Data[0]*255+msg_buf.Data[1])*255+msg_buf.Data[2])*255+msg_buf.Data[3])*255+msg_buf.Data[4])*255+msg_buf.Data[5])*255+msg_buf.Data[6])*255+msg_buf.Data[7];
		a=data/100000; b=data%100000;
		printf("%d%drn", a, b);
	}
}

При получении сообщения — сбрасываем бит прерывания, принимаем данные, превращаем принятый массив в число и выводим. Функция printf — только для примера, вы можете выводить данные в USART, USB на индикатор или куда-то ещё.

Тайминги

Синхронизация и тайминги в CAN — отдельный, важный и сложный вопрос. Однако, благодаря сложности и продуманности становится не так важна возможная рассинхронизация и нестабильность тактовых частот узлов сети, и связь становится возможной даже в тяжёлых условиях.

CAN-модуль STM32, синхронизация битов

Всё время делится на кванты длиной t_q, и номинальная длительность бита равна 1+BS1+BS2 квантов. Захват значения бита происходит на границе BS1 и BS2. В процессе приёма приёмник определяет, в какой из временных периодов произошёл перепад сигнала (т.е. начало приёма нового бита). В норме перепад должен произойти на границе SYNC и BS1, если он произошёл раньше — контроллер уменьшает BS1, если позже — увеличивает BS2 на величину SJW (от 1 до 4 квантов времени). Таким образом, происходит постоянная пересинхронизация с частотой других приёмников.

Необходимо чётко понимать этот процесс, потому что при правильной настройке он даёт отличные результаты захвата частоты передатчика. Рекомендуется принимать следующие значения таймингов:

  • SJW = 1
  • BS1 = 3-5
  • BS2 = 3-5

Конечно, конкретные значения зависят от частоты шины и частоты процессора, однако от них можно отталкиваться. Стоит начать с равенства периодов BS1 и BS2, а в случае ошибок приёма — пытаться их изменять.  К сожалению, для чёткой настройки в сложных случаях всё-таки понадобится осциллограф.

С другой стороны, при связи двух узлов сети можно просто принять одинаковые тайминги, и всё будет работать.

Для работы с автомобильной CAN-шиной на скорости 125 кбит/с и частотой процессора 42МГц я использовал прескалер величиной 12, SJW=1, BS1=3, BS2=3.