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
32 комментариев
старее
новее большинство голосов
Inline Feedbacks
View all comments
Алексей
Алексей
3 месяцев назад

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

Алексей
Алексей
Reply to  Aveal
3 месяцев назад

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

Алексей
Алексей
Reply to  Aveal
3 месяцев назад

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

Алексей
Алексей
Reply to  Aveal
3 месяцев назад

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

Алексей
Алексей
3 месяцев назад

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

Алексей
Алексей
3 месяцев назад

Что то не разобрался в вопросе приема. На шине стоит прибор из кан хакера, который отвечает на запросы лин. Написал в 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.

Алексей
Алексей
2 месяцев назад

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

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

»

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

Алексей
Алексей
2 месяцев назад

А мастером слать данные можно только из 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
2 месяцев назад

https://yadi.sk/d/_fzaKzL0ycNs5A

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

HAL_LIN_SendBreak(&huart1);

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

Last edited 2 месяцев назад by Алексей
Алексей
Алексей
Reply to  Aveal
2 месяцев назад

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

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

HAL_LIN_SendBreak(&huart1);

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

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

Алексей
Алексей
2 месяцев назад

Это опять я. Что то какая то фигня у меня с приемом данных.

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

что то оно как то криво принимает.

Делаем запрос мастером в первую лин шину. Вторая лин шина слейвом отвечает. После этого полученные данные кидаем мастером в первую лин шину

код из майна

        HAL_Delay(20);

            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);
        HAL_Delay(20);
        

                HAL_LIN_SendBreak(&huart1);
        HAL_UART_Transmit(&huart1, &linSync, 1, 10);
        HAL_UART_Transmit(&huart1, &linBCM1Id, 1, 10);
            linMasterData[LIN_DATA_BYTES_NUM - 1] = LIN_CalcCheckSum(linMasterData, LIN_DATA_BYTES_NUM - 1);
            
        HAL_UART_Transmit(&huart1, linMasterData, 9, 10);

слейв присылает данные

40 41 42 43 44 45 46 47 crc E1

мастер их принимает и шлет с другим ID, но шлет со сдвигом

E1 40 41 42 43 44 45 46 crc 47

причем это проблема с приемом. Если в коде задать данные и их слать, то все приходит без сдвига. Я так понмаю что то не так у меня с приемом.

Last edited 2 месяцев назад by Алексей
Алексей
Алексей
Reply to  Aveal
2 месяцев назад

это я так отметил контрольную сумму. CRC как разделитель. Я все контролирую через лин хакера, у него контральная сумма вынесена в отдельный столбец. то есть символы crc это отметка, что дальше идет байт контрольной суммы.

Алексей
Алексей
Reply to  Aveal
2 месяцев назад

вот скриншот

2020-11-28_22-32-40.png
Алексей
Алексей
Reply to  Aveal
16 дней назад

уже после приема данные перепутанные. Оставил только однин запрос мастером данных в дебаге показало что там данные идут со сдвигом

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

видимо надо осциллографом или логическим анализатором смотреть, что твориться на выводе Rx, видимо там что то проскакивает. Пока сделал в функции приема сдвиг данных.

Павел
Павел
Reply to  Алексей
10 часов назад

Здравствуйте, не разобрались, почему сдвигает? на F303 у меня тоже прием уже со сдвигом идет, причем всегда разным. На F103 все работало корректно

Влад
Влад
1 месяц назад

Здравствуйте! Не могу скачать архив проекта. Спасибо.

Last edited 1 месяц назад by Влад
Влад
Влад
Reply to  Aveal
1 месяц назад

Все нормально. Спасибо!

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

Profile Profile Profile Profile Profile
Vkontakte
Twitter

Язык сайта

Февраль 2021
Пн Вт Ср Чт Пт Сб Вс
1234567
891011121314
15161718192021
22232425262728

© 2013-2020 MicroTechnics.ru