Top.Mail.Ru

Драйвер протокола LIN для микроконтроллеров на базе UART.

Встречайте третью часть из серии статей, посвященных протоколу LIN, предыдущие части доступны по ссылкам:

И в этой статье мы займемся ровно тем, чем и планировали, а именно напишем свой драйвер для обмена данными по LIN. Базой послужит обычный модуль UART микроконтроллера STM32. Но при желании можно будет легко портировать драйвер на любой другой микроконтроллер, внеся незначительные изменения. Никаких аппаратных возможностей именно STM32 для протокола LIN мы использовать не будем, все по-честному 👍

Итак физически драйвер по классике будет состоять из двух файлов - заголовочного и файла с исходным кодом:

  • lin.c
  • lin.h

Цель поставим себе такую - сделать использование LIN максимально простым, то есть вызвали одну функцию отправки/приема - получили результат. Вся остальная работа будет скрыта внутри драйвера.

Как и во второй статье цикла отправной точкой будут служить возможные состояния устройства при работе в качестве Master'а или Slave'а. Только здесь список состояний будет расширенным относительно нашего первого примера:

typedef enum
{
	LIN_IDLE,
	LIN_RECEIVING_BREAK,
	LIN_RECEIVING_SYNC,
	LIN_RECEIVING_ID,
	LIN_RECEIVING_DATA,
	LIN_RECEIVING_CHECKSUM,
	LIN_SENDING_BREAK,
	LIN_SENDING_SYNC,
	LIN_SENDING_ID,
	LIN_SENDING_DATA,
	LIN_SENDING_CHECKSUM,
} LIN_State;

Без лишних слов переходим сразу к делу. И первое на очереди - это генерация (в случае ведущего устройства) и детектирование (в случае подчиненного) поля Break. Для этой задачи мы задействуем один из таймеров - выбрать можно абсолютно любой. Кроме того, само собой, необходимо выделить один из модулей USART для работы с LIN. Настраиваем все вышеперечисленное в STM32CubeMx:

Настройка USART в STM32CubeMx.
Настройка таймера в STM32CubeMx.

Таймер у меня будет генерировать прерывание по переполнению каждые 25 мкс.

В программе объявим указатели для хранения данных обо всех выбранных периферийных модулях, то есть об USART'е, таймере и портах, которые будут работать в качестве сигналов Rx и Tx этого USART'а:

static UART_HandleTypeDef *uartHandle;
static TIM_HandleTypeDef *timerHandle;

static GPIO_TypeDef *rxPort;
static GPIO_TypeDef *txPort;

static uint32_t rxPin;
static uint32_t txPin;

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

Продолжаем... Все эти указатели нужны для того, чтобы при изменении конкретной периферии, то есть перехода, например, с таймера TIM4 на TIM3 правки необходимо было произвести только в одном месте. А именно в функции инициализации LIN, вот, собственно, ее код:

void LIN_Init(UART_HandleTypeDef *uHandle, TIM_HandleTypeDef *tHandle, LIN_Mode mode,
              GPIO_TypeDef *rPort, uint32_t rPin, GPIO_TypeDef *tPort, uint32_t tPin)
{  
	uartHandle = uHandle;
	timerHandle = tHandle;
	rxPort = rPort;
	rxPin = rPin;
	txPort = tPort;
	txPin = tPin;
  
	curMode = mode;
	curState = LIN_IDLE;
	curBaudrate = uartHandle->Init.BaudRate;

	HAL_TIM_Base_Start_IT(timerHandle);

	breakCntLimit = (1000000 * LIN_BREAK_SIZE_BITS / curBaudrate) / LIN_TIMER_PERIOD_US;
	breakCntUpperLimit = breakCntLimit + breakCntLimit * LIN_BREAK_DEVIATION_PERCENT / 100;
	breakCntLowerLimit = breakCntLimit - breakCntLimit * LIN_BREAK_DEVIATION_PERCENT / 100;

	isInit = 1;
}

В демо-примере для проверки работы драйвера у меня выбраны USART2, TIM4 и устройство работает в режиме Slave:

LIN_Init(&huart2, &htim4, LIN_SLAVE, GPIOA, GPIO_PIN_3, GPIOA, GPIO_PIN_2);

Кроме прочего, здесь еще выбираются ножки контроллера, относящиеся к USART'у. Концепция точно такая же - при изменении выводов нужно будет менять программу только в одном месте, а не искать обращения к периферии везде по коду драйвера.

Как видите, при инициализации мы также рассчитываем значения нескольких переменных:

  • breakCntLimit - это длительность поля Break, приведенная к периоду нашего таймера. В прерывании по переполнению таймера при генерации Break мы будем увеличивать счетчик. Соответственно, переменная breakCntLimit показывает, сколько раз должен переполниться таймер за время брейка.
  • breakCntUpperLimit и breakCntLowerLimit - это верхний и нижний порог для обнаружения Break. Если счетчик попадает в этот интервал, то мы считаем, что брейк обнаружен.

В дефайны вынесены некоторые конфигурационные параметры:

#define LIN_TIMER_PERIOD_US                                             25

#define LIN_BREAK_SIZE_BITS                                             13
#define LIN_BREAK_DEVIATION_PERCENT                                     10
  • LIN_TIMER_PERIOD_US - здесь мы указываем выбранный нами ранее период переполнения таймера.
  • LIN_BREAK_SIZE_BITS - размер поля Break в битах.
  • LIN_BREAK_DEVIATION_PERCENT - задает окно для обнаружения брейка относительно идеальной длительности, равной breakCntLimit. В нашем случае получаем ±10%.

Итак, давайте разберемся, как мы будем генерировать и отлавливать Break. С обнаружением все, в целом, несложно - если у нас Slave находится в режиме LIN_RECEIVING_BREAK, то в прерывании по таймеру проверяем уровень сигнала на ножке Rx:

uint8_t LIN_ReadRxPortState()
{
	uint8_t res = HAL_GPIO_ReadPin(rxPort, rxPin);
	return res;
}

Если точнее, то это происходит в функции LIN_TimerProcess(), которую мы вызываем из callback'а по переполнению. Если на ножке 0, то начинаем увеличивать счетчик breakCnt. Когда на Rx появляется единица - проверяем, что значение счетчика попадает в нужный нам интервал:

case LIN_RECEIVING_BREAK:
	rxPortState = LIN_ReadRxPortState();
    
	if (rxPortState == 0)
	{
		breakCnt++;
	}
	else
	{
		if (breakCnt != 0)
		{
			if ((breakCnt <= breakCntUpperLimit) && (breakCnt >= breakCntLowerLimit))
			{
				curState = LIN_RECEIVING_SYNC;
				uint16_t temp = uartHandle->Instance->DR;
				HAL_UART_Receive_IT(uartHandle, &rxByte, 1);
			}
          
			breakCnt = 0;
		}
	}
	break;

При отправке брейка задача усложняется. Нам нужно переконфигурировать Tx USART'а на работу в режиме обычного порта ввода-вывода, а затем, после отправки Break, вернуть все на круги своя:

void LIN_WriteTxPortState(uint8_t state)
{  
	GPIO_InitTypeDef GPIO_InitStruct = {0};
  
	if (state == 0)
	{
		HAL_GPIO_DeInit(txPort, txPin);
    
		GPIO_InitStruct.Pin = txPin;
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
		GPIO_InitStruct.Pull = GPIO_NOPULL;
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
		HAL_GPIO_Init(txPort, &GPIO_InitStruct);

		HAL_GPIO_WritePin(txPort, txPin, GPIO_PIN_RESET);
	}
	else
	{
		HAL_GPIO_WritePin(txPort, txPin, GPIO_PIN_SET);

		HAL_GPIO_DeInit(txPort, txPin);

		GPIO_InitStruct.Pin = txPin;
		GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
		HAL_GPIO_Init(txPort, &GPIO_InitStruct);
	}
}

И снова возвращаемся в обработчик прерывания таймера:

case LIN_SENDING_BREAK:
	if (breakCnt == 0)
	{
		LIN_WriteTxPortState(0);
		breakCnt++;
	}
	else
	{          
		if (breakCnt == breakCntLimit)
		{
			breakCnt = 0;
			LIN_WriteTxPortState(1);
			curState = LIN_SENDING_SYNC;
			HAL_UART_Transmit_IT(uartHandle, &syncByte, 1);
		}
		else
		{          
			breakCnt++;
		}
	}
	break;

Обратите внимание, что здесь нам нет необходимости использовать диапазон допустимых значений длительности брейка. Просто сравниваем с идеальным рассчитанным значением breakCntLimit. Это вытекает из того, что при приеме длительность может плавать в зависимости от разных факторов, а при генерации брейка мы четко инкрементируем счетчик и выдаем нужный временной интервал.

В прерывании таймера у нас решается еще одна задача, а именно проверка на потерю части LIN пакета:

case LIN_RECEIVING_ID:
case LIN_RECEIVING_DATA:
case LIN_RECEIVING_SYNC:
	rxTimeoutCnt++;

	if (rxTimeoutCnt >= rxTimeoutCntLimit)
	{
		HAL_UART_AbortReceive(uartHandle);
		curState = LIN_IDLE;
	}

	break;

Когда устройство находится в ожидании принятых данных (идентификатора, байта данных или поля синхронизации), начинаем увеличивать счетчик rxTimeoutCnt. Обнуляется этот счетчик в callback'е по окончанию приема данных по USART. Соответственно, если данные так и не пришли, то rxTimeoutCnt становится равен rxTimeoutCntLimit и мы обрываем прием и переходим в состояние ожидания. Значения порога в микросекундах задается так:

#define LIN_RX_TIMEOUT_US                                               100000

Перемещаемся в функцию LIN_UartProcess(), вызывается она из callback-ов по окончанию приема и передачи данных по USART. И в ней мы, соответственно, обрабатываем поочередно все состояния LIN-устройства. Многое кстати похоже на то, что мы делали в предыдущей статье по протоколу LIN.

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

void LIN_Init(UART_HandleTypeDef *uHandle, TIM_HandleTypeDef *tHandle, LIN_Mode mode, GPIO_TypeDef *rPort, uint32_t rPin, GPIO_TypeDef *tPort, uint32_t tPin);

Далее идет функция для организации процесса передачи данных:

LIN_Error LIN_Transmit(uint32_t id, uint8_t *ptr, uint8_t len);

Возможные возвращаемые значения выглядят следующим образом:

typedef enum {
	LIN_OK,
	LIN_BUSY,
	LIN_NOT_INIT,
} LIN_Error;

В качестве аргументов мы передаем PID, указатель на данные и количество байт для передачи. Контрольную сумму здесь не учитываем отдельным байтом и не добавляем в отправляемые данные, все произойдет автоматически. Чтобы отследить окончание передачи, можно использовать функцию:

LIN_State LIN_GetState();

Возвращаемое значение соответствует состоянию устройства. По окончанию передачи произойдет переход в режим ожидания - LIN_IDLE, что и можно отследить.

При использовании этой функции для Master'а в линию будут отправлены последовательно:

Передача данных по LIN.

Поскольку Slave в LIN не может сам инициировать обмен данными, то для подчиненного эту функцию имеет смысл вызывать только из callback'а по приему PID (в том случае, если из значения идентификатора Slave понимает, что необходимо выслать ведущему данные).

Если мы, будучи ведущим устройством, хотим получить данные от Slave, то для этого припасена функция:

LIN_Error LIN_Receive(uint32_t id, uint8_t *ptr, uint8_t len);

Возвращаемое значение и аргументы имеют точно такой же смысл как и для передачи. Аналогично, для Slave вызов этой функции должен происходить после приема идентификатора. Master же при вызове этой функции выдает в шину поля Break, Sync и PID и встает на прием данных:

Интерфейс LIN. Прием данных.

Для Slave'а все процессы работают несколько иначе - никаких функций вызывать не нужно. Ведомый изначально переходит в состояние приема LIN-фрейма, а точнее в режим ожидания Break. И далее вся работа уже протекает по факту приема данных от Master'а. Для работы со Slave'ом я добавил два callback'а:

  • LIN_SlaveIdRxCallback(uint8_t id) - по приему PID - для анализа полученного значения и принятия решения о дальнейших действиях. Ведь именно из значения PID подчиненный понимает, нужно передавать данные или принимать, а также количество байт данных.
  • LIN_RxCpltCallback() - по окончанию приема данных. Этот callback нужен уже непосредственно для обработки принятых данных, при этом контрольную сумму проверять не нужно, это происходит внутри драйвера. И callback будет вызван только в том случае, если контрольная сумма верна.

Работа с этими функциями протекает точно также, как и с другими callback-ами. Просто переопределяем эти функции в своем коде и добавляем в них любые необходимые действия.

С этим разобрались, теперь организуем демо-проект для проверки возможностей и работоспособности драйвера. И для тестирования добавим модуль USART1, настроенный на работу в режиме LIN, как в этом примере.

В итоге на USART1 с аппаратной поддержкой LIN у нас будет Master. А на USART2 у нас будет Slave, который будет работать исключительно на голом USART'е при помощи нашего драйвера. Ну и, соответственно, соединяем эти модули USART между собой.

Первый тест - Master передает данные, а Slave принимает. Код ведущего у нас в цикле while(1):

// Master transmitter section

HAL_LIN_SendBreak(&huart1);

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

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

uint8_t checkSum = LIN_CalcCheckSum(linMasterTxData, LIN_DATA_BYTES_NUM);
HAL_UART_Transmit(&huart1, &checkSum, 1, 10);

linMasterTxCnt++;

HAL_Delay(1000);

// End of master transmitter section

Для передачи данных Master'ом у нас используется PID 0x3A, а для запроса и приема данных от Slave - 0x3B.

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

Добавляем наш пользовательский код в callback по приему PID:

void LIN_SlaveIdRxCallback(uint8_t id)
{  
	if (id == LIN_RX_ID)
	{
		uint8_t txBytes = LIN_GetDataLength(id);    
		LIN_Transmit(0, linSlaveTxData, txBytes);
	}
	else
	{
		if (id == LIN_TX_ID)
		{
			uint8_t rxBytes = LIN_GetDataLength(id);
			LIN_Receive(0, linSlaveRxData, rxBytes);
		}
		else
		{
			LIN_Reset();
		}
	}
}

Анализируем принятый PID и решаем, принимаем или передаем. Кстати, опять же из идентификатора мы получаем количество байт для приема/передачи при помощи функции LIN_GetDataLength(id). Кстати, заметьте, для Slave'а не важно, какое значение идентификатора мы передадим в функции приема и передачи, поскольку за выдачу в линию PID отвечает Master. Так что можем спокойно в качестве первого аргумента использовать 0.

И также переопределяем callback по окончанию передачи:

void LIN_RxCpltCallback()
{
	linSlaveRxCnt++;
}

Здесь просто инкрементируем счетчик принятых фреймов. Запускаем программу и под отладчиком можем видеть, что счетчик переданных Master'ом пакетов (linMasterTxCnt) в точности соответствует счетчику пакетов, принятых Slave'ом:

Программа для передачи ведущим.

А теперь второй тестовый режим - Master отправляет заголовок пакета и начинает прием данных от Slave'а:

// Master receiver section

HAL_LIN_SendBreak(&huart1);

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

linMasterRequestCnt++;

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

uint8_t rxCheckSum = 0;
HAL_UART_Receive(&huart1, &rxCheckSum, 1, 10);

uint8_t calcCheckSum = LIN_CalcCheckSum(linMasterRxData, LIN_DATA_BYTES_NUM);

if (rxCheckSum == calcCheckSum)
{
  linMasterRxCnt++;
}

HAL_Delay(1000);

// End of master receiver section

Slave же про приему этого идентификатора начинает отправку своих данных. Для проверки у нас используются счетчик запросов ведущего linMasterRequestCnt и счетчик принятых, опять же ведущим, пакетов данных - linMasterRxCnt:

Программа для передачи подчиненным.

Все работает четко по плану 👍 Таким образом, можно просто добавить в свой проект драйвер LIN'а и использовать те функции, которые мы обсудили, для того, чтобы быстро и просто организовать работу с использованием LIN.

А теперь под занавес статьи, реализуем Master'а на нашем драйвере и поставим его на отправку данных раз в секунду. Кода минимум, раз:

LIN_Init(&huart2, &htim4, LIN_MASTER, GPIOA, GPIO_PIN_3, GPIOA, GPIO_PIN_2);

И два:

LIN_Transmit(linTxId, linMasterTxData, 8);

Вот и все. А теперь посмотрим на сигнал на экране осциллографа:

Осциллограмма LIN.

Все в точности соответствует теоретическим аспектам работы протокола LIN, которые рассматривали ранее.

На это заканчиваем статью, подписывайтесь на обновления, вступайте в группу ВКонтакте, мы будем рады видеть вас снова )

Проект можно скачать по ссылке - MT_LIN_SW_Example.

Подписаться
Уведомить о
guest

30 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Андрей
Андрей
2 лет назад

добрый вечер, спасибо за вашу библиотеку. Использую ее для одного проекта, мой девайс выступаем мастером. Шлю запрос на слейв блок (кнопки руля). На шине подключен сниффер канхакер, так вот слейв отлично реагирует на запрос мастера и шлет данные, я их вижу в логе канхакера. Но в девайсе данные от слейва не принимаются, т.е. в массиве принятых данных функции Lin_Receive по нулям, такое ощущение что прерывание, точнее колбек по приему данных из usart не срабатывает (при дебаге в него не попадаю), но стоит в мэйн добавиь hal_recivie_it то по приемк данных от слейва в колбек начинаю попадать. что может быть не так?

Андрей
Андрей
Ответ на комментарий  Aveal
2 лет назад

Не попадает и попадет судя по коду, я же мастер, а значит curMode == LIN_MASTER. Нигде в коде больше curState = LIN_RECEIVING_BREAK не устанавливается.

 switch (curState)
 {
  case LIN_IDLE:
   if (curMode == LIN_SLAVE)
   {
    curState = LIN_RECEIVING_BREAK;
   }
   break;

  case LIN_SENDING_BREAK:
   if (breakCnt == 0)
   {
    LIN_WriteTxPortState(0);
    breakCnt  ;
   }
   else
   {      
    if (breakCnt == breakCntLimit)
    {
     breakCnt = 0;
     LIN_WriteTxPortState(1);
     curState = LIN_SENDING_SYNC;
     HAL_UART_Transmit_IT(uartHandle, &syncByte, 1);
    }
    else
    {      
     breakCnt  ;
    }
   }
   break;
    
  case LIN_RECEIVING_BREAK:
   rxPortState = LIN_ReadRxPortState();
   
   if (rxPortState == 0)
   {
    breakCnt  ;
   }
   else
   {
    if (breakCnt != 0)
    {
     if ((breakCnt <= breakCntUpperLimit) && (breakCnt >= breakCntLowerLimit))
     {
      curState = LIN_RECEIVING_SYNC;

В main.c у меня такое:

 LIN_Init(&huart1, &htim3, LIN_MASTER, GPIOA, GPIO_PIN_10, GPIOA, GPIO_PIN_9);

 LIN_Transmit(0x0D, linMasterTxData1, 4);
 HAL_Delay(50);
 LIN_Receive(0x8E, linMasterRxData, 8);
 HAL_Delay(50);

первым запросом я шлю запрос, чтобы слейв "проснулся" , а вторым шлю запрос, на который жду ответ от слейва

Андрей
Андрей
Ответ на комментарий  Aveal
2 лет назад
Андрей
Андрей
Ответ на комментарий  Андрей
2 лет назад

Нашел место где ступор:

  if (rxDataNum > 0)
        {
          curState = LIN_RECEIVING_DATA;
          HAL_UART_Receive_IT(uartHandle, dataPtr, rxDataNum);
        }

Если заменить прием по превание на блокирующий прием HAL_UART_Receive(uartHandle, dataPtr, rxDataNum , HAL_MAX_DELAY); то тут виснем. Если заменить rxDataNum на 1, то прием проходит, но там 1 байт = 0, если поставить 2, то тоже виснем

Андрей
Андрей
Ответ на комментарий  Aveal
2 лет назад

вот прием данных, но сюда мы не попадем:

      if (curMode == LIN_MASTER)
      {
        curState = LIN_SENDING_BREAK;
      }
      else
      {
        curState = LIN_RECEIVING_DATA;
        HAL_UART_Receive_IT(uartHandle, dataPtr, rxDataNum);
      }

так как curMode == LIN_MASTER
Второе место более похоже на правду:

 case LIN_SENDING_ID:
      if (txDataNum > 0)
      {
        curState = LIN_SENDING_DATA;
        txCheckSum = LIN_CalcCheckSum(dataPtr, txDataNum);
        HAL_UART_Transmit_IT(uartHandle, dataPtr, txDataNum);
      }
      else
      {
        if (rxDataNum > 0)
        {
          curState = LIN_RECEIVING_DATA;
          HAL_UART_Receive_IT(uartHandle, dataPtr, rxDataNum);
        }
        else
        {
          curState = LIN_IDLE;
        }
      }
      break;

попадем в это место:

if (rxDataNum > 0)
        {
          curState = LIN_RECEIVING_DATA;
          HAL_UART_Receive_IT(uartHandle, dataPtr, rxDataNum);
        }

Но данные так и не приходят....

Андрей
Андрей
Ответ на комментарий  Aveal
2 лет назад

провел эксперимент, выключил TJA1020, логанализатор подключил к ножкам TX, RX. Шлю запрос, вижу его в анализаторе. Поставил дебаг на
curState = LIN_RECEIVING_DATA;
HAL_UART_Receive_IT(uartHandle, dataPtr, rxDataNum);

естественно произошел стоп на приеме, вручную на ногу RX через терминал послал несколько байт, и после их увидел как логанализаторе так в дебаге, т.е. проц и прога реагируют на данные. Но на данные от слейва, которые я вижу в логанализаторе - не реагирует, в общем странно. Хотя TJA 1020 полнстью подключена по даташиту, данные на LIN выходе вижу и они правильные, на ножках TX-RX TJA1020 тоже все есть, но проц упорно их не видит

Андрей
Андрей
Ответ на комментарий  Aveal
2 лет назад

речь про эти поля?
huart1.RxXferCount;
huart1.RxXferSize;

до вызова функции приема данных, эти значения по нулям, после вызова:
 if (rxDataNum > 0)
    {
     curState = LIN_RECEIVING_DATA;
     HAL_UART_Receive_IT(uartHandle, dataPtr, rxDataNum);
    }
оба счетчика равны = 8

Андрей
Андрей
Ответ на комментарий  Aveal
2 лет назад

В коллбеке обработки ошибок постоянно появляется 2 ошибки:
Frame error
Overrun error

Андрей
Андрей
Ответ на комментарий  Aveal
2 лет назад

в общем разобрался, дело в специфике моего проца - STM32F070, у него USART отличается от других процов, перед приемом байта, нужно сбрасывать флаги ошибок, вот так:
    __HAL_UART_CLEAR_FEFLAG(uartHandle);
     __HAL_UART_CLEAR_OREFLAG(uartHandle);
     HAL_UART_Receive_IT(uartHandle, dataPtr, rxDataNum);

Тогда все работает. Но возник другой вопрос:
я реализую слейв, ловлю посылку от мастера
PID 0x0D DLC=4 0x50 0xF9 0xFF 0xFF

Добавил колбек:
void LIN_SlaveIdRxCallback(uint8_t id)
{
//если приняли пакет с PID 0x0D от мастера
if (id == 0x0D)
 {
//что-то делаем
}

Но в этом колбеке я принял все только до PIDa, а мне нужно получить все данные, а они должны придти как я понял
в колбеке LIN_RxCpltCallback(); Но у меня до него не доходит.
Вопрос: как в коллбеке LIN_SlaveIdRxCallback после обработки PID продолжить прием данных и байта контрольной суммы? Это должно происходить автоматом и после отработает колбек LIN_RxCpltCallback(); или это надо руками сделать типа такого:
if (LIN_GetState()==LIN_RECEIVING_ID)
{
  LIN_Receive(0, linRxDataButtons, 4);
}

В коде написано так:
 case LIN_RECEIVING_ID:
   LIN_SlaveIdRxCallback(rxByte);
   break;
т.е. перешли в состояние приема PID - вызвали колбек и все, дальше данные не принимаем

Сергей
Сергей
1 год назад

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

Сергей
Сергей
Ответ на комментарий  Aveal
1 год назад

я уже написав коммент сообразил, что отправка идет через прерывание)
в исходниках отправка осуществляется без LIN_Transmit.
я правильно понимаю, что для отправки пакета с linRxId нужно длину данных указывать 0, и тогда оно встанет в прием? в примере не хватает кода в части приема, в остальном - суперская серия!

Антон
Антон
4 месяцев назад

Добрый день. Спасибо за библиотеку. Понравились статьи про лин шину, решил попробовать использовать драйвер в своем устройстве. Для начала научился принимать и отправлять данные в режиме мастера и слейва по отдельности, были небольшие проблемы, но с помощью комментариев ниже я их победил.
У меня проблема, я не могу разобраться как переключать режим из LIN_SLAVE в LIN_MASTER.
Идея у меня такая:

  /* USER CODE BEGIN 2 */
 LIN_Init(&huart1, &htim1, LIN_MASTER, GPIOB, GPIO_PIN_7, GPIOB, GPIO_PIN_6); // Мастер
 LIN_Init(&huart2, &htim1, LIN_SLAVE, GPIOA, GPIO_PIN_3, GPIOA, GPIO_PIN_2); // Слейв
  /* USER CODE END 2 */

После инициализации curMode == LIN_SLAVE

Получаю данные в режиме LIN_SLAVE через функцию LIN_Receive(0, linSlaveRxData, 8);
Далее хочу перейти в режим LIN_MASTER делая это через вызов функции LIN_Transmit(0x0D, LIGHT_DATA, 4); , но режим не меняется и так остается LIN_SLAVE и соответственно данные не передаются.
код из main.c:

  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

	   LIN_Receive(0, linSlaveRxData, 8);
	   HAL_Delay(40);
	   LIN_Transmit(0x0D, LIGHT_DATA, 4);
	   HAL_Delay(40);
	    }

  /* USER CODE END 3 */
Серго
Серго
3 месяцев назад

Переделываю на мк от Gigadevices, где нет HAL, фактически одни регистры. Нужен режим Slave, пока что слушатель в чистом виде. Весь день проколупался, но пока безрезультатно 🙂
Не догоняю, как выкрутиться с HAL_UART_Receive_IT(uartHandle, &rxByte, 1);
Еще такое ощущение, что данные по пути ломаются. Мастером раз в две секунды отправляю один и тот же пакет с одним и тем же PID, меняю только в цикле второй байта в пейлоаде (0-1-2), а приемник только один из десяти -двадцати таких пакетов вычитывает (в смысле совпадает PID с ожидаемым), и то с чексуммой не проходит. логический анализатор на входе в мк аномалий не видит, все четко. а откуда мк такие данные получает - не понимаю.

Покопал на github код этого HAL_UART_Receive_IT - как будто все сводится к тому, что оно разрешает прерывания на прием (USART_CR1_RXNEIE - в терминолоии это USART_INT_RBNE). Но получается, в теле прерывания после вычитывания байта - нужно отключать это прерывание?

Серго
Серго
Ответ на комментарий  Aveal
3 месяцев назад

просто не представляю, с какой стороны подойти 🙁

отправляю теперь уже одинаковые пакеты каждые 1,5-2 секунды. из них доходит в лучшем случае один из десяти. и ладно бы не доходил ни один - я бы это понял, но почему некоторые все же доходят - загадка.

для отладки в прерывание RBNE поставил проверку флагов разных ошибок (parity, overrun,frame). если возникает - счетчик увеличиваю, сбрасываю флаг. количество ошибок вывожу уже в основном теле программы пять раз в секунду. отладочный вывод из функций lin для чистоты эксперимента убрал.

в итоге выяснилось, у меня на каждом пакете счетчик frame error увеличивается на 1-2 - не знаю, как это связано. а перед успешной передачей - сразу на 6. сейчас на 22 полученных сообщения накопилось 660 ошибок.

прилагаю скриншоты из анализатора - как будто все правильно?

screenshot-2024-02-26-at-23.03.24
Серго
Серго
Ответ на комментарий  Серго
3 месяцев назад

второй скриншот.
ps когда выводил отладочные байты в прерывании, PID получался ошибочным - такое ощущение, что IBS в header sync он принимал за первый бит в PID.
попробую еще поменять передатчик и приемник местами..

screenshot-2024-02-26-at-23.02.10
Серго
Серго
Ответ на комментарий  Aveal
3 месяцев назад

видимо, нет! спутал логанализатор, который спокойно парсил сигнал, но меня смущала эта иголка в конце брейк-фрейма. - слишком короткая она была (меньше бита). переписал мастер (на этот раз использовал уже штатную send_break_frame), и оно сразу заработало, без ошибок в приемнике)

my_name
my_name
1 месяц назад

не могли бы вы подсказать, что делать в случае коллизии при передаче данных, когда множество slave,ов отвечают на один PID?

30
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x