[STM32] UART

myblack·2024년 8월 13일
0

STM32

목록 보기
2/8

UART

통신이론

통신 방법

단방향 / 반이중-양방향&동시X /전이중 - 양방향&동시

동기식(synchronous)

  • 데이터를 클럭에 동기화 시켜서 한비트씩 보냄
  • 물리적 클럭 전송라인 필요함

비동기식(Asynchronous)

  • 시간간격으로 비트를 보냄
  • 초당비트 전송 보레이트가 동
  • 둘다 보레이트가 일치해야 함
  • 9600 baudrate는 1초에 9600개의 Symbol(ex. 8bit - ASCII 코드)를 보낼 수 있음

UART는 비동기식, USART는 동기식

Uart 종류

  • Polling :
    데이터량이 적은 경우, 폴링 Blocking 시간 지연이 다른 기능에 영향이 적을 경우 사용 권장
    폴링방식 - 정해진 시간, 순서에서 상태를 확인하여 상태의 변화가 있는지 없는 지를 확인

  • interrupt :
    데이터 송수신을 대기하지 않음. 따라서 다른 기능의 정지가 없음. 하지만 데이터 송수신량이 많을 경우 동작의 정지가 있을 수 있음.
    인터럽트 - MCU가 작업을 중단하고 인터럽트 서비스 루틴을 실행하는 것

  • DMA
    MCU에서 수행하지 않고 UART가 직접 RAM에 데이터를 저장함. 가장 효율이 좋음.

UART 송신 함수
HAL_UART_Transmit(huart, pData, size, Timeout);
Uart 핸들러, 송신 데이터 포인터, 데이터 길이, ms단위 타임아웃(시간내 작업 끝나지 않으면 오류 간주)

소스코드

초기설정

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART3_UART_Init();
  
  // GPIO 핀 상태 설정
  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_RESET);  // GPIOC 포트의 6번 핀을 LOW 상태로 설정 (출력 핀 OFF)
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);  // GPIOB 포트의 0번 핀을 LOW 상태로 설정 (출력 핀 OFF)
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);  // GPIOB 포트의 5번 핀을 LOW 상태로 설정 (출력 핀 OFF)
  HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);   // GPIOD 포트의 12번 핀을 HIGH 상태로 설정 (출력 핀 ON)
  HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);   // GPIOD 포트의 13번 핀을 HIGH 상태로 설정 (출력 핀 ON)
  HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_SET);   // GPIOD 포트의 14번 핀을 HIGH 상태로 설정 (출력 핀 ON)
  
  

데이터 들어오면 1byte씩 'a'라는 변수에 넣음
a라는 변수값을 1byte씩 읽어서 전송

 uint8_t a = 'a';

  while (1)
  {

      if(HAL_UART_Receive(&huart3, &a, 1, 10) == HAL_OK)
      //데이터 수신후 저장시
	  {
	  		  HAL_UART_Transmit(&huart3, &a, 1, 10);
	  }
   }

}

예제코드1

UART를 통해 "Hello, World!" 메시지를 주기적으로 송신

main() 함수: 기본적인 HAL 및 시스템 클럭 초기화 후 GPIO 및 UART 초기화를 수행합니다. 이후 무한 루프에서 Hello, World! 메시지를 1초 간격으로 UART를 통해 전송합니다.

MX_USART1_UART_Init() 함수: USART1의 기본 설정을 수행합니다. 보드레이트는 9600bps로 설정되며, 8비트 데이터, 1비트 스톱비트, 패리티 없음으로 구성됩니다.

HAL_UART_Transmit() 함수: UART를 통해 데이터를 송신하는 함수입니다. 메시지의 길이와 최대 대기 시간을 설정하여 데이터를 보냅니다.

#include "main.h"
#include "stm32f0xx_hal.h"

// 전역 변수 선언
UART_HandleTypeDef huart1;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);

int main(void)
{
  // HAL 라이브러리 초기화
  HAL_Init();

  // 시스템 클럭 구성
  SystemClock_Config();

  // GPIO 및 UART 초기화
  MX_GPIO_Init();
  MX_USART1_UART_Init();

  // "Hello, World!" 메시지
  char *message = "Hello, World!\r\n";

  while (1)
  {
    // UART로 메시지 송신
    HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);

    // 1초 대기
    HAL_Delay(1000);
  }
}

// USART1 초기화 함수
static void MX_USART1_UART_Init(void)
{
  huart1.Instance = USART1;                        // USART1 사용
  huart1.Init.BaudRate = 9600;                     // 보드레이트 9600bps
  huart1.Init.WordLength = UART_WORDLENGTH_8B;     // 데이터 길이 8비트
  huart1.Init.StopBits = UART_STOPBITS_1;          // 스탑비트 1비트
  huart1.Init.Parity = UART_PARITY_NONE;           // 패리티 비트 없음
  huart1.Init.Mode = UART_MODE_TX_RX;              // 송수신 모드 활성화
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;     // 하드웨어 플로우 컨트롤 없음
  huart1.Init.OverSampling = UART_OVERSAMPLING_16; // 오버샘플링 16
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    // 초기화 오류 처리
    Error_Handler();
  }
}

예제코드2

버퍼 초기화 및 메시지 전송:

  • uint8_t buffer[256];: 256 바이트 크기의 버퍼를 선언합니다. UART로 전송할 데이터를 임시로 저장하기 위해 사용됩니다.

  • sprintf((char *)buffer, "Hello, World!\n");: sprintf 함수를 사용하여 buffer에 "Hello, World!\n" 문자열을 저장합니다.

  • HAL_UART_Transmit(&huart2, buffer, strlen((char *)buffer), 100);: UART2를 통해 buffer에 저장된 데이터를 전송합니다. 전송할 데이터의 길이는 strlen 함수를 통해 계산되며, 타임아웃은 100ms로 설정됩니다.

/* USER CODE BEGIN Includes */
#include <string.h>  // 문자열 처리 함수 사용을 위한 헤더파일 포함
#include <stdio.h>   // 표준 입출력 함수 사용을 위한 헤더파일 포함
/* USER CODE END Includes */

  /* USER CODE BEGIN 2 */
  uint8_t buffer[256];  // 전송할 데이터를 저장할 버퍼 선언 (최대 256 바이트)

  // buffer에 "Hello, World!\n" 문자열을 저장
  sprintf((char *)buffer, "Hello, World!\n");

  // UART2를 통해 buffer에 저장된 문자열 전송
  // strlen을 사용해 전송할 데이터의 길이를 계산하고, 타임아웃은 100ms로 설정
  HAL_UART_Transmit(&huart2, buffer, strlen((char *)buffer), 100);
  /* USER CODE END 2 */

  /* USER CODE BEGIN WHILE */
  while (1)  // 무한 루프 시작
  {
    // buffer에 "Loop! count\n" 형태로 현재 루프 카운트를 저장
    // count는 루프의 반복 횟수를 나타내는 변수로, 1씩 증가
    sprintf((char *)buffer, "Loop! %d\n", ++count);

    // UART2를 통해 buffer에 저장된 문자열 전송
    HAL_UART_Transmit(&huart2, buffer, strlen((char *)buffer), 100);

    // 1초(1000ms) 동안 대기
    HAL_Delay(1000);
    /* USER CODE END WHILE */

UART_Printf

소스코드

write 함수 printf실행시 호출, printf함수 출력을 UART3으로 리디렉션

#include <stdio.h>  //표준출력정의
static void MX_GPIO_Init(void);
static void MX_USART3_UART_Init(void);

/* USER CODE BEGIN PFP */
int _write(int file, char *p, int len)
{
	HAL_UART_Transmit(&huart3, p, len, 10);
	return len;
}
  float f = 1.234;

  while (1)
  {
	  printf("Hello %f\n", f);
 	  HAL_Delay(1000);
  }

※ 표준입출력의 printf는 많이 무겁기 때문에 stm 라이브러리 Tiny printf를 사용함. 디버깅할때 printf 사용하지만 Release버전에는 printf를 사용하지 않음

float 변수 출력
프로젝트 속성 / C/C++ 빌드 / 세팅 / 툴세팅
[ Other flags ] or [Miscellaneous]
-u _printf_float

UART_Interrupt

설정 및 함수

  • 글로벌 인터럽트를 enable 함.

  • NVIC 코드를 생성하도록 함

  • 인터럽트 코드를 따라가면 다음과 같이 몸체가 나타남

    UART3_IRQHandler -> HAL_UART_IRQHandler -> UART_Receive_IT -> HAL_UART_RxCpltCallback 호출

  • 함수 앞에 "__weak"는 사용자가 재정의 하여 사용하라는 의미임. 실제 인터럽트의 몸체로 사용자가 다시 정의하면 됨

Uart 송신 인터럽트 함수: Uart Tx로 데이터를 Size만큼 전송하면 인터럽트가 발생한다.
// huart: uart 인스턴스
// pData: 송신 데이터 버퍼
// Size: 송신 데이터 개수
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

Uart 수신 인터럽트 함수: Uart Rx로 데이터가 Size만큼 들어오면 인터럽트가 발생한다.
// huart: uart 인스턴스
// pData: 수신 데이터 버퍼
// Size: 수신 데이터 개수
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

소스코드

예제0

uint8_t rx_data;


// 인터럽트 콜백 함수: 인터럽트가 발생되면 이 함수가 호출된다.
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
	if (huart->Instance == USART1) {
        // 데이터 1개를 수신하면 인터럽트를 발생시킨다.
		HAL_UART_Receive_IT(&huart1, &rx_data, 1);
        
        // 받은 데이터를 전송한다.
		HAL_UART_Transmit(&huart1, &rx_data, 1, 10);
	}
}

 MX_USART1_UART_Init();

  /* Initialize interrupts */
  MX_NVIC_Init();
  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_IT(&huart1, &rx_data, 1);


UART_HandleTypeDef huart3;
uint8_t rx3_data;


// 수신한 데이터 1byte 저장
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART3)
	{
		HAL_UART_Receive_IT(&huart3, &rx3_data, 1);
        //첫번째 인터럽트 후 다시 인터럽트 발생
		HAL_UART_Transmit(&huart3, &rx3_data, 1, 10);
	}
}

// UART3를 통해 rx3_data에 1바이트 데이터 수신시 인터럽트 생성
// 무한루프 이전에 한번 실행, vodi UART4_IRQHandler(void) 함수 실행 위함
HAL_UART_Receive_IT(&huart3, &rx3_data, 1); 


배열 인터럽트

  • 10바이트의 데이터 수신해 rx3_data 저장할때 HAL_UART_RxCpltCallback() 실행
  • 정리하면 "UART4_IRQHandler" 함수가 10번 동작하는 동안 "HAL_UART_RxCpltCallback" 함수는 1번 수행한다는 의미
uint8_t rx3_data[10];
HAL_UART_Receive_IT(&huart3, &rx3_data, 10);

에러 발생 종류
PE: 하드웨어적으로 패리티 체크 시 불일치 할 때 발생
NE: 하드웨어적으로 Noise detection 시 발생
FE: 하드웨어적으로 수신한 Frame 에 오류가 있는 경우 발생
ORE: 하드웨어적을 RDR 레지스터에 오버런이 발생한 경우 발생
DMA: DMA 전송 시 오류가 발생한 경우








Ref
https://louie0724.tistory.com/357
https://huroint.tistory.com/entry/STM32-Serial-%ED%86%B5%EC%8B%A0-UART
https://jeonhj.tistory.com/37
https://www.youtube.com/watch?v=6uq9v4nfuag&list=PLUaCOzp6U-RqMo-QEJQOkVOl1Us8BNgXk&index=5

profile
기록과 분석, 이해

0개의 댓글