STM32 и интерфейс LIN. Настройка и обмен данными. STM32CubeMx.

Приветствую всех на нашем сайте! Выходит в свет вторая часть из обещанных трех, посвященных работе с интерфейсом LIN. Сегодня вооружимся микроконтроллером STM32 и реализуем связь по LIN в разных режимах работы с использованием аппаратных средств. Напоминаю, что в третьей статье будем писать свой драйвер LIN с нуля на голом UART’е!

Что ж, подготовительный этап будет протекать как обычно – открываем STM32CubeMx и настраиваем нужную периферию. Для этого проекта мы будем использовать два модуля USART, настроенных на работу в режиме LIN, соединенных между собой. Вообще, строго говоря, подключение необходимо выполнять через микросхемы LIN-трансиверов:

Шина LIN.

Но у меня сейчас, к сожалению, нет под рукой TJA1021, так что соединяем модули USART просто напрямую. На процесс написания программы это никак не повлияет и на работоспособности не отразится 🙂

Схема для теста LIN.

USART1 и USART2 настраиваем абсолютно одинаково, не забываем включить прерывания:

Настройка LIN в STM32CubeMx.
USART прерывания в STM32CubeMx.

Кроме того, как обычно, конфигурируем тактирование микроконтроллера:

Настройки тактирования STM32.

На этом взаимодействие с Cube закончено, генерируем проект и открываем его для дальнейшей работы!

Итак, в чем же заключается аппаратная поддержка LIN в STM32. На самом деле, функционал фактически минимальный. Для Master’а есть возможность аппаратно сгенерировать поле Break, используя функцию:

HAL_LIN_SendBreak(UART_HandleTypeDef *huart)

Для Slave есть возможность отловить все тот же брейк в прерывании. В общем-то вот и все 🙂

Давайте чуть подробнее разберем механизм обнаружения поля Break подчиненным устройством. Стоит отметить, что в HAL Driver по какой-то причине решили не делать никаких функций для этого, поэтому будем работать “руками”. В общем, то нам нужно будет всего лишь включить прерывание по обнаружению брейка:

__HAL_UART_ENABLE_IT(&huart2, UART_IT_LBD);

В данном случае у меня использован USART2.

И далее в прерывании, после стандартной обработки прерывания HAL’ом отлавливаем событие обнаружения брейка:

uint32_t isrflags = huart2.Instance->SR;

if ((isrflags & USART_SR_LBD) != RESET)
{
	CLEAR_BIT(huart2.Instance->SR, USART_SR_LBD);
	
	// .....
}

Все, на этом аппаратные возможности заканчиваются, все остальное мы сейчас сделаем сами!

На USART1 у нас будет Master, соответственно, на USART2 – Slave. И начнем с более сложного, а именно с кода подчиненного устройства. Но прежде всего прочего нужно обсудить, что мы вообще собираемся делать 🙂

Итак, реализуем два режима работы:

  • Master отправляет заголовок (header) пакета с определенным PID, после чего Slave переходит в режим приема данных.
  • Master отправляет заголовок с идентификатором, который сигнализирует Slave’у о том, что надо ответить ведущему порцией данных. Соответственно, Master после отправки заголовка должен встать на прием.

Пусть значения PID для примера будут такими:

  • 0x3A – соответствует первому случаю, то есть Master отправляет заголовок и данные.
  • 0x3B – это уже второй случай из перечисленных.

Важное дополнение – как вы помните, байт PID помимо 6-ти битов идентификатора включает в себя еще два бита четности. Для примера же будем просто использовать вышеупомянутые значения, без учета четности.

Добавляем в наш проект эти значения и пару переменных (ссылку на полный проект я обязательно добавлю в конце статьи):

#define LIN_TX_ID                                                         0x3A
#define LIN_RX_ID                                                         0x3B
uint8_t linTxId = LIN_TX_ID;
uint8_t linRxId = LIN_RX_ID;

Кроме того, объявляем массивы для хранения данных ведущего и подчиненного:

#define LIN_DATA_BYTES_NUM                                                9
uint8_t linMasterData[LIN_DATA_BYTES_NUM];
uint8_t linSlaveData[LIN_DATA_BYTES_NUM];

В проекте все находится в разных файлах, здесь же для наглядности я буду комбинировать код чуть иначе. Принимать и отправлять мы будем по 8 байт, то есть максимально возможное количество байт в одном фрейме. Но обратите внимание, что в массивах по 9 байт – еще 1 байт мы выделили под прием и отправку контрольной суммы.

И раз уж об этом зашла речь, то вот функция для расчета контрольной суммы (будем использовать классический алгоритм):

uint8_t LIN_CalcCheckSum(uint8_t *data, uint8_t len)
{
	uint16_t sum = 0;
	
	for (uint8_t i = 0; i < len; i++)
	{
		sum += data[i];
	}
  
	while(sum > 0xFF)
	{
		sum -= 0xFF;
	}
  
	sum = 0xFF - sum;
  
	return (uint8_t)sum;
}

И для систематизации работы программы добавим возможные режимы, в которых может находиться Slave:

typedef enum
{
	LIN_RECEIVING_BREAK     = 0x01,
	LIN_RECEIVING_SYNC      = 0x02,
	LIN_RECEIVING_ID        = 0x03,
	LIN_RECEIVING_DATA      = 0x04,
	LIN_SENDING_DATA        = 0x05,
} LIN_State;

Суть тут ясна из названий, так что даже не будем останавливаться на этом отдельно 🙂

Итак, изначально Slave у нас готов к приему данных и находится в состоянии ожидания Break’а:

LIN_State slaveState = LIN_RECEIVING_BREAK;

Код приема брейка, как мы уже обсудили, поместим в обработчик прерывания:

/**
	* @brief This function handles USART2 global interrupt.
	*/
void USART2_IRQHandler(void)
{
	/* USER CODE BEGIN USART2_IRQn 0 */

	/* USER CODE END USART2_IRQn 0 */
	HAL_UART_IRQHandler(&huart2);
	/* USER CODE BEGIN USART2_IRQn 1 */

	if (slaveState == LIN_RECEIVING_BREAK)
	{
		uint32_t isrflags = huart2.Instance->SR;
    
		if ((isrflags & USART_SR_LBD) != RESET)
		{
			CLEAR_BIT(huart2.Instance->SR, USART_SR_LBD);
			uint16_t temp = huart2.Instance->DR;
			slaveState = LIN_RECEIVING_SYNC;
			HAL_UART_Receive_IT(&huart2, &rxByte, 1);
		}
	}
        
  /* USER CODE END USART2_IRQn 1 */
}

Первым делом, проверяем, что Slave находится в ожидании поля Break, затем проверяем, не вызвано ли прерывание как раз-таки приемом этого поля, и, если да, то переводим Slave в режим ожидания поля Sync. Тут есть небольшой нюанс в виде чтения регистра данных USART’а, это нужно для корректного приема последующих байт:

uint16_t temp = huart2.Instance->DR;

Вся остальная логика у нас будет в callback-функциях по окончанию приема и передачи. Начинаем с приема и двигаемся по всем возможным состояниям Slave устройства:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if (huart == &huart2)
	{
		uint8_t checkSum = 0;

		switch(slaveState)
		{
			case LIN_RECEIVING_SYNC:
				if (rxByte == LIN_SYNC_BYTE)
				{
					slaveState = LIN_RECEIVING_ID;
					HAL_UART_Receive_IT(&huart2, &rxByte, 1);
				}
				else
				{
					slaveState = LIN_RECEIVING_BREAK;
				}
				break;
        
			default:
				break;
		}
	}
}

Проверяем принятый байт, если значение верное – 0x55 – встаем на прием PID, иначе возвращаемся в исходную точку. Добавляем ветку для приема идентификатора:

case LIN_RECEIVING_ID:
	if (rxByte == LIN_RX_ID)
	{
		slaveState = LIN_SENDING_DATA;

		for (uint8_t i = 0; i < LIN_DATA_BYTES_NUM - 1; i++)
		{
			linSlaveData[i] = 0x30 + i;
		}

		linSlaveData[LIN_DATA_BYTES_NUM - 1] = LIN_CalcCheckSum(linSlaveData, LIN_DATA_BYTES_NUM - 1);

		HAL_UART_Transmit_IT(&huart2, linSlaveData, LIN_DATA_BYTES_NUM);
	}
	else
	{
		if (rxByte == LIN_TX_ID)
		{
			slaveState = LIN_RECEIVING_DATA;
			HAL_UART_Receive_IT(&huart2, linSlaveData, LIN_DATA_BYTES_NUM);
		}
		else
		{
			slaveState = LIN_RECEIVING_BREAK;
		}
	}
	break;

Здесь все чуть сложнее, но, в принципе, тоже довольно прозрачно 🙂 Проверяем PID и по его значению определяем, что нам следует делать в дальнейшем. И тут, как вы помните, два варианта:

  • Ожидать приема байт данных от Master’а
  • Или отправить свою порцию данных

Для случая передачи данных Slave’ом заполняем массив тестовыми значениями и дополняем его 9-м байтом – контрольной суммой. А если PID не соответствует тем 2-м значениям, которые мы определили для этого примера, то возвращаем устройство в начальное положение, а именно в состояние ожидания брейка.

И, наконец, последняя часть callback-а по приему:

case LIN_RECEIVING_DATA:
	checkSum = LIN_CalcCheckSum(linSlaveData, LIN_DATA_BYTES_NUM - 1);
        
	if (linSlaveData[LIN_DATA_BYTES_NUM - 1] == checkSum)
	{
		linSlaveRxCnt++;
	}
        
	slaveState = LIN_RECEIVING_BREAK;          
	break;

Как мы обсудили ранее, принимаем 9 байт, то есть 8 байт данных и байт контрольной суммы. И когда все данные приняты – рассчитываем контрольную сумму по первым 8-ми байтам и сравниваем ее значение с 9-ым байтом массива данных. В случае успеха инкрементируем переменную linSlaveRxCnt. Этот счетчик будет для нас сигналом успешно принятых данных.

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

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	if (huart == &huart2)
	{
		switch(slaveState)
		{        
			case LIN_SENDING_DATA:
				linSlaveTxCnt++;
				slaveState = LIN_RECEIVING_BREAK;
				break;
        
			default:
				break;
		}
	}
}

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

При работе Master’а в режиме отправки данных Slave’у:

for (uint8_t i = 0; i < LIN_DATA_BYTES_NUM - 1; i++)
{
	linMasterData[i] = 0x40 + i;
}
      
HAL_LIN_SendBreak(&huart1);

HAL_UART_Transmit(&huart1, &linSync, 1, 10);
HAL_UART_Transmit(&huart1, &linTxId, 1, 10);

linMasterData[LIN_DATA_BYTES_NUM - 1] = LIN_CalcCheckSum(linMasterData, LIN_DATA_BYTES_NUM - 1);

HAL_UART_Transmit(&huart1, linMasterData, LIN_DATA_BYTES_NUM, 10);

linMasterTxCnt++;
HAL_Delay(1000);

Заполняем данные тестовыми значениями и последовательно отправляем:

  • Break
  • Sync
  • PID
  • Данные
  • Контрольную сумму

Все четко по формату пакета интерфейса LIN. Для примера я сделал отправку раз в секунду.

В режиме, когда Master отправляет в шину заголовок и ожидает данные от подчиненного все практически идентично:

for (uint8_t i = 0; i < LIN_DATA_BYTES_NUM; i++)
{
	linMasterData[i] = 0x00;
}
        
HAL_LIN_SendBreak(&huart1);

HAL_UART_Transmit(&huart1, &linSync, 1, 10);
HAL_UART_Transmit(&huart1, &linRxId, 1, 10);

HAL_UART_Receive(&huart1, linMasterData, LIN_DATA_BYTES_NUM, 10);

uint8_t checkSum = LIN_CalcCheckSum(linMasterData, LIN_DATA_BYTES_NUM - 1);

if (linMasterData[LIN_DATA_BYTES_NUM - 1] == checkSum)
{
  linMasterRxCnt++;
}

HAL_Delay(1000);

Последний аргумент функций HAL_UART_Transmit() и HAL_UART_Receive() – это величина таймаута в миллисекундах. Если функция не закончит свое выполнение за это время, то произойдет возврат из функции с кодом ошибки HAL_TIMEOUT.

Разница только в том, что после отправки PID устройство встает на прием данных, а не начинает передачу, плюс по окончанию приема проверяем контрольную сумму. Для индикации работоспособности здесь также будем пользоваться обычными счетчиками пакетов linMasterRxCnt и linMasterTxCnt.

Переключение между режимами работы в проекте осуществляется переменной linMasterTask. Master отправляет:

uint8_t linMasterTask = LIN_MASTER_TX;

Master принимает:

uint8_t linMasterTask = LIN_MASTER_RX;

Собираем, прошиваем, запускаем, проверяем!

Для случая, когда ведущий отправляет данные подчиненному смотрим на счетчики linMasterTxCnt и linSlaveRxCnt. А также на значения в массивах linMasterData и linSlaveData. Под отладчиком можно наблюдать как счетчики параллельно инкрементируются, то есть отправляемые пакеты успешно принимаются:

Программа для передачи по интерфейсу LIN.

Аналогично и в режиме, когда Master получает данные от Slave:

Программа для приема по интерфейсу LIN.

Здесь уже вступают в игру счетчики linMasterRxCnt и linSlaveTxCnt. Ну и кроме того, значения в массивах принимаемых и отправляемых данных в точности совпадают!

И на этой позитивной ноте заканчиваем сегодняшнюю статью, спасибо за внимание! При возникновении любых вопросов, смело пишите в комментарии, во всем разберемся!

Ссылка на проект – MT_LIN_HW_Example.

Поделиться!

Подписаться
Уведомление о
guest
20 комментариев
старее
новее большинство голосов
Inline Feedbacks
View all comments
Алексей
Алексей
24 дней назад

Привет. Спасибо за статью, по Lin шине, почему то, очень мало информации в сети. Но, к сожалению, повторить проект у меня, пока что, не получилось. Хотел сделать мастера, который будет просто кидать информацию в лин шину, для проверки концепции. Подключить плату с микросхемой tja1021 к лин анализатору. В кубе сделал один юарт, в прошивке main добавил код, который мастером отправляет данные слейву (тогда же ни какие колбеки не нужны? ). Залил и стал дебагом смотреть. В результате, если не подключать к tja1021 +12, то в дебаге linMasterTxCnt++ значение растет. Если подключать +12, то все валиться с ошибками по uart. Пока застрял, дальше не знаю что делать

Алексей
Алексей
Reply to  Aveal
23 дней назад

SPL_N подтянуто резистором к +5в. На ней 4.9 вольта. На лин, скорее всего не будет ни какого сигнала, Lin анализатор ловит что то только в момент подачи напряжение на контроллер. Замеры на выводах сделаю чуть позже. Сам проект тут https://yadi.sk/d/BVPZubR2hxtQWg .
Stm32f105+tja1021 . Cube Mx v6, keil 5.

Алексей
Алексей
Reply to  Aveal
23 дней назад

На выводе lin нет ничего. На выводе Tx, если не подавать +12 видно что идут пакеты данных. На выводе Rx ничего нет. Если подключить +12, то данные в Tx идти перестают. Если убрать +12 данные не появляются, пока не сделать перезагрузку.

Алексей
Алексей
Reply to  Aveal
22 дней назад

разобрался с передачей мастера. С ноля собрал проект и он заработал. https://yadi.sk/d/Et1czQsDfCLTVg
Буду разбираться с приемом. Спасибо.

Алексей
Алексей
21 дней назад

Вопрос по приему сообщений мастером. В примере после передачи сразу становимся на прием, и этот прием не ограничен по времени. В боевых условиях, насколько я понимаю, прием должен быть ограничен по времени, что б программа не зависла в случае отсутствия приема?. есть ли какие то ограничения, сколько стоять на приеме?

Алексей
Алексей
21 дней назад

Что то не разобрался в вопросе приема. На шине стоит прибор из кан хакера, который отвечает на запросы лин. Написал в main

HAL_LIN_SendBreak(&huart2);
HAL_UART_Transmit(&huart2, &linSync, 1, 10);
HAL_UART_Transmit(&huart2, &linTxId2, 1, 10);
HAL_UART_Receive(&huart2, linMasterData, LIN_DATA_BYTES_NUM, 10); //id 33
uint8_t checkSum = LIN_CalcCheckSum(linMasterData, LIN_DATA_BYTES_NUM - 1);
if (linMasterData[LIN_DATA_BYTES_NUM - 1] == checkSum)
{
  //linMasterRxCnt++;
	HAL_LIN_SendBreak(&huart2);
HAL_UART_Transmit(&huart2, &linSync, 1, 10);
HAL_UART_Transmit(&huart2, &linTxId, 1, 10); //Id 3A
linMasterData[LIN_DATA_BYTES_NUM - 1] = LIN_CalcCheckSum(linMasterData, LIN_DATA_BYTES_NUM - 1);
HAL_UART_Transmit(&huart2, linMasterData, LIN_DATA_BYTES_NUM, 10);
//linMasterTxCnt++;
HAL_Delay(1000);
}
HAL_Delay(1000);

Как я это понимал – после передачи запроса с id 33 стм будет бесконечно ждать ответа. По факту оказалось, что он ответа вообще не ждет. запросы идут один за одним с одинаковыми интервалами. В результате lin хакер показывает что идет запрос с ID 33 с интервалами в 1013 мс. На ответы lin хакера, прикидывающемся слейвом не реагирует. А, по моей задумке, должен полученное отправить мастером с id 3A.

Алексей
Алексей
13 дней назад

Еще вопрос назрел. В статье написано ” Проверяем PID и по его значению определяем, что нам следует делать в дальнейшем. И тут, как вы помните, два варианта

  • Ожидать приема байт данных от Master’а
  • Или отправить свою порцию данных

Мне нужно создать лин фильтр и фильтровать одно значение. Как мне по ID понять мне надо будет отвечать на запрос мастера или ждать от него данные.
В этом примере будем мы отвечать или нет зависит от принятого ID, который мы указали ранее в прошивке.
Правильно я понмаю, что в шине слейв по одному и тому же id не может и слушать и отвечать.
То есть для того, что б отфильтровать значения надо сначала прослушать весь трафик и понят по каким ID слейвы отвечают, а по каким слушают? создавать внутри прошивки список ID которые слушают и которые отвечают и исходя из этого списка решать что делать?

Алексей
Алексей
5 дней назад

А мастером слать данные можно только из Main()? у меня затея – lin1 посылает данные в lin2. Lin2 ловит данные и эти данные кидает lin1, но уже с другим ID. Пытаюсь фильтр сделать. В коллбеке после приема данных сразу вставил код

     if (rxByte == LIN_TX_ID)
     {
      slaveState = LIN_RECEIVING_DATA;
      HAL_UART_Receive_IT(&huart2, linSlaveData, LIN_DATA_BYTES_NUM);
						HAL_Delay(2); 
						
			HAL_LIN_SendBreak(&huart1);
   HAL_UART_Transmit(&huart1, &linSyncSend, 1, 10);
   HAL_UART_Transmit(&huart1, &linBCMId, 1, 10);
    
   HAL_UART_Transmit(&huart1, linSlaveData, LIN_DATA_BYTES_NUM, 10);
     }
     else

но такого же сообщения с другим ID не приходит. Получается из коллбека по второму лину нельзя сразу же послать сообщение в первый лин?

Алексей
Алексей
Reply to  Aveal
15 часов назад

https://yadi.sk/d/_fzaKzL0ycNs5A

почти полностью Ваш пример.
когда добавляю в колбеке

HAL_LIN_SendBreak(&huart1);

мк зависает. Если убираю – мк работает.

Last edited 15 часов назад by Алексей
Алексей
Алексей
Reply to  Aveal
12 часов назад

пока тесты идут без автомобиля и разбираюсь как это работает. А так надо будет порвать лин и корректировать некоторые данные. В колбеке приняли данные от мастера в лин2. Пока, временно, мастером выступает лин1. Позже там будет мастер автомобиля. В лин 2 приняли данные, и надо их перекинуть в лин 1 (в примере что б не путаться хотел их слать с другим ID, ну и что б не уйти в бесконечный цикл). Хотел реализовать это в колбеке сразу.

взят колбек из примера, где собираем данные, принятые в лин2. После принятия данных добавил сразу отправку брейка в первый лин. То есть взяли ваш пример и только добавил одну строчку

HAL_LIN_SendBreak(&huart1);

После этого мк зависает. Получается из колбека нельзя брейк посылать? Пробовал добавлять задержки – не помогает.

Реализовал отправку данных от мастера с помощью глобальной переменной – данные бегают как надо. Но в лин шине, которую надо порвать, есть еще и запросы от мастера. По хорошему надо в том же колбеке скопировать запрос мастера из лин 2 в лин1, дождаться ответа от слейва автомобиля и перекинуть ответ в лин2. Вот тут пока остановился на подумать, так как уперся в то, что из колбека не получается брейк отправить.

Присоединяйтесь!

Profile Profile Profile Profile Profile
Vkontakte
Twitter

Язык сайта

Ноябрь 2020
Пн Вт Ср Чт Пт Сб Вс
 1
2345678
9101112131415
16171819202122
23242526272829
30  

© 2013-2020 MicroTechnics.ru