STM32 CAN Loop Back모드 테스트 중, Tx가 되지 않을때 문제해결, CAN의 동작모드(operating modes)에 관하여

워너비임베·2024년 12월 21일

STM32

목록 보기
3/4

CAN Loop Back모드 테스트를 하는 중, CAN 메시지가 정상적으로 전송이 안되는 문제가 있었다.

진짜 원인을 잘 모르겠어서 고생을 했다..
이는 CAN의 동작 모드(operating mode)에 관한 이해가 부족했기 때문이였다..

우선 CAN Loopback테스트의 개념부터 알아보자

CAN Loopback 테스트란?

보통, CAN 통신을 시작하고자 할때 테스트 용도로 Loopback test를 이용해 TX와 RX를 테스트 한다.

Loopback test는 버스에 오직 하나의 CAN peripheral만 있음을 의미한다.

그래서 사용자가 무엇을 전달하든 그 TX메시지가 다시 RX로 루프백 된다.

Loopback 모드를 사용하면 TX data가 CAN bus에 전송되고, RX에도 그 TX 메시지가 loop back된다.

수신측에서는 그림과 같이 CAN_RX쪽 연결이 끊겨진 것을 확인할 수 있는데
이로인해 rx측에서는 CAN bus를 listen(감지)하지 못하고 tx로부터 looped back되어진 자기 자신의 메시지를 받는 것이다.

CAN의 동작 모드와 문제 해결 과정

다음 그림처럼 CAN의 동작 모드에는 3가지가 있다
1. Sleep
2. Initialization
3. Normal

MCU의 초기 reset 상태에서는 Sleep 모드에서 시작한다.
CAN 통신을 하기 위해선 CAN Controller의 초기화가 필요하다
이를 위해,CAN Controller의 상태를 초기 상태인 Sleep Mode에서 ➡️ Initialization모드로 변경이 필요하다

Sleep Mode에서 Initalization모드로 변경하는 방법으로 CAN MCR레지스터의 SLEEP bit를 clear해주는 것인데 이는
HAL_CAN_Init() API 함수에 구현되어 있다

⚠️Initialization모드에서는, 초기화 상태에 있는 것이라 아무것도 송신하지 않고 수신하지 않는다. 이 상태에서 CAN_Tx()함수를 호출했으니 CAN 신호가 전송이 되지 않았다..

⭐Normal Mode
Normal Mode에서 CAN Controller는 정상적으로 작동 할 수 있다.
즉, 이 상태에는 Tx와 Rx를 사용할 수 있다.
HAL_CAN_Start함수를 통해 CAN Controller의 상태를 Initialization ➡️ Normal 모드로 변경할 수 있다.
HAL_CAN_Start함수는 다음과 같이 INRQ비트를 clear시킨다

⭐문제의 원인
나의 코드에서는 CAN1_Tx()로 CAN메시지를 전송하는 부분이 HAL_CAN_Start()함수 보다 먼저 호출이 되므로, Initalization 모드상태에서 CAN메시지를 전송하는 것이였어서 당연히 전송이 안되었던 것이다!..

⭐문제 해결
다음과 같이 HAL_CAN_Start()함수를 호출하여 Normal Mode로 진입 후, CAN1_Tx()를 통해 메시지를 전송하였다.
성공적으로 디버깅 화면에서, CAN1_Tx()함수의 HAL_CAN_AddTxMessage()함수에서 전송이 완료되어
TxMailbox변수가 1로 update되는 것을 관찰하였고 대기중(pending상태)인 전송 요청이 없어서 while(HAL_CAN_IsTxMessagePending(&hcan1,TxMailbox));이 0을 return하여 통과해 'HELLO'라고 전송한 CAN Message가 정상적으로 전달되고 다시 그 'HELLO' 메시지가 RX로 루프백 되는 것을 시리얼통신 터미널 화면을 통해 확인할 수 있었다.

전체코드

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "can.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "extern.h"
#include <string.h>
#include <stdio.h>
#include <stdint.h>
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
uint8_t rx_data;

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int _write(int file, char *ptr, int len)
{
  (void)file;
  HAL_UART_Transmit(&huart2, ptr, len, HAL_MAX_DELAY);
  return len;
}


void CAN1_Init(void);
void CAN1_Tx(void);
void CAN1_Rx(void);
void CAN_Filter_Config(void);
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
	int cnt = 0;
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
//  MX_CAN1_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
//  HAL_TIM_Base_Start_IT(&htim3);
  HAL_UART_Receive_IT(&huart2, &rx_data,1); // size : 1byte
  CAN1_Init();
  CAN_Filter_Config();
  if(HAL_CAN_Start(&hcan1) != HAL_OK){
	  Error_Handler();
  }
  CAN1_Tx();
  CAN1_Rx();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  if(uart2RxIndex1 != uart2RxIndex2){
		  printf("%c \r\n",uart2RxBuf[uart2RxIndex2++]);
		  if(uart2RxIndex2 >= UART2_BUF_MAX){
			  uart2RxIndex2 = 0;
		  }
	  }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 50;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 2;
  RCC_OscInitStruct.PLL.PLLR = 2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
/*void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
	volatile static uint8_t cnt_100ms = 0;
	if(htim->Instance == TIM3){
		cnt_100ms++;
		if(cnt_100ms >= 5){
			cnt_100ms = 0;
			HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
		}
	}
}*/
void CAN1_Tx(void){
	char msg[50];
	// create TxHeader variable
	CAN_TxHeaderTypeDef TxHeader;

	uint32_t TxMailbox;

	uint8_t our_message[5] = {'H','E','L','L','O'};

	TxHeader.DLC = 5; // 5byte(we will send "HELLO")
	// use standard ID
	TxHeader.StdId = 0x65D;
	TxHeader.IDE = CAN_ID_STD;
	TxHeader.RTR = CAN_RTR_DATA;

	if(HAL_CAN_AddTxMessage(&hcan1, &TxHeader, our_message, &TxMailbox) != HAL_OK){
		// here, variable TxMailbox is updated by HAL_CAN_AddTxMessage API!
		Error_Handler();
	}

	// use a while loop in order to wait until the message is transmitted successfully.
	while(HAL_CAN_IsTxMessagePending(&hcan1,TxMailbox));
	// if HAL_CAN_IsTxMessagePending return 1, there is a pending transmission request

	// After while loop, i will just print that message got transmitted successfully.
	sprintf(msg,"Message Transmitted\r\n");
	HAL_UART_Transmit(&huart2,(uint8_t*)msg,strlen(msg),HAL_MAX_DELAY);
}

void CAN1_Init(void){
	hcan1.Instance = CAN1;
	hcan1.Init.Mode = CAN_MODE_LOOPBACK;
	hcan1.Init.AutoBusOff = DISABLE;
	hcan1.Init.AutoRetransmission = ENABLE;
	hcan1.Init.AutoWakeUp = DISABLE; // This is to do with low power modes
	hcan1.Init.ReceiveFifoLocked = DISABLE;
	hcan1.Init.TimeTriggeredMode = DISABLE;
	hcan1.Init.TransmitFifoPriority = DISABLE;// priority driven by the identifier of the msg

/*	hcan1.Init.Prescaler = 6;
	hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
	hcan1.Init.TimeSeg1 = CAN_BS1_11TQ;
	hcan1.Init.TimeSeg2 = CAN_BS2_2TQ;*/


	// Settings related to CAN bit timings
	// Bit rate is 500kbps
	hcan1.Init.Prescaler = 5;
	hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
	hcan1.Init.TimeSeg1 = CAN_BS1_8TQ;
	hcan1.Init.TimeSeg2 = CAN_BS2_1TQ;

	if(HAL_CAN_Init(&hcan1) != HAL_OK){
		Error_Handler();
	}
}
void CAN1_Rx(void){
	char msg[50];
	CAN_RxHeaderTypeDef RxHeader;
	uint8_t rcvd_msg[5];// to save received data

	// wait until some message comes to the FIFO( not euqal to 0)
	while(!HAL_CAN_GetRxFifoFillLevel(&hcan1,CAN_RX_FIFO0));

	if(HAL_CAN_GetRxMessage(&hcan1,CAN_RX_FIFO0
						,&RxHeader,rcvd_msg) != HAL_OK)
	{
		Error_Handler();
	}
	sprintf(msg,"Message Received : %s\r\n",rcvd_msg);
	HAL_UART_Transmit(&huart2,(uint8_t*)msg,strlen(msg),HAL_MAX_DELAY);
}
void CAN_Filter_Config(void){
	CAN_FilterTypeDef can1_filter_init;
	can1_filter_init.FilterActivation = ENABLE;
	can1_filter_init.FilterBank = 0;
	can1_filter_init.FilterFIFOAssignment = CAN_RX_FIFO0;
	can1_filter_init.FilterIdHigh = 0x000;
	can1_filter_init.FilterIdLow = 0x000;
	can1_filter_init.FilterMaskIdHigh = 0x000;
	can1_filter_init.FilterMaskIdLow = 0x000;
	// use MASK Mode
	can1_filter_init.FilterMode = CAN_FILTERMODE_IDMASK;
	can1_filter_init.FilterScale = CAN_FILTERSCALE_32BIT;

	if(HAL_CAN_ConfigFilter(&hcan1, &can1_filter_init) != HAL_OK){
		Error_Handler();
	}
}

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

0개의 댓글