[Harman교육] ARM아키텍쳐

[23.04.05] UART - PC, Bluetooth

블링블링이즈정원 2023. 4. 5. 15:06

1. UART 통신

 

1-1 기본 개념

송신 : polling방식    ex) printf("Hello world!!");   -> AVR과 STM 모두 

수신: interrupt방식 (Rx Interrupt)

 

(1) 비동기: 클럭 신호에 맞춰 데이터를 송수신하지 않고 별도의 부호비트(start/stop bit)를 붙여서 데이터를 주고 받는 방식

-> 동기에 비해 효율이 80%, 실질적으로 보내는 data는 8bit인데, start/stop 2bit가 추가되오 10비트를 주고받아야 함

-> '0' '1' 송수신 --> st(0)00110000stop(1) 0(st)00110001(1)  ;값은 아스키코드로 표현

-오실로스코프로 Raw Data를 확인해본다. 

 

(2) 동기식: 클럭 신호에 맞춰서 데이터를 송수신하는 방식

 

 

2-2  Circular Queue

※기본 요소 기술: Circular Queue(원형큐, 환형 버퍼, Ring 버퍼) -> 이전까지 1차원 array로 송신함, 이제 원형큐 방식 사용

 

- AVR에서는 UART RX Interrupt에서 들어온 data1 차원 배열에 1개의 command 저장하는 방식 사용

char rx_buff[80];

- 그러나 여러 개의 command 빠른 속도로 들어올 경우 user program( pc_command_processing함수)

   에서 처리 하지 못하고 놓치는 경우가 발생

- 이러한 문제점을 해결 하기 위해 circular queue 자료구조를 활용 

여러 개의 command가 한 번에 들어와도 이를 circular queuebuffering(저장)해 두면 user program에서 UART interrupt dataloss 없이 처리가 가능 하다.  

 

공백상태와 포화상태를 구분하기 위해 하나의 공간은 비워둬야 함, 즉 꽉 차면 오류 상태

 

그림처럼 처음에 input과 output pointer가 0에서 시작하고, 값이 들어오면 input pointer 가 +1 이 된다.

 

데이터가 다 들어오면 들어온 순서대로 첫번째 데이터부터 출력된다. 즉 FIFO 이다. 

 

 

2. UART  구현

 

#include "main.h"
#include "uart.h"
#include <stdio.h>  //printf gets scanf fgets etc.
#include <string.h> //strncmp strcpy strcat strcmp etc.....

#define COMMAND_MAX 20 	 // 최대로 저장할 수 있는 command 수 (Buffer의 방의 개수)
#define COMMAND_LENGTH 50 // 1 line에 최대로 save 할수 있는 data 수

volatile int input_pointer = 0;   // interrupt에서 queue에 data를 저장하는 위치값
volatile int output_pointer = 0;  // pc_command_processing에서 가져가는 위치값
volatile unsigned char rx_buff[COMMAND_MAX][COMMAND_LENGTH];

volatile uint8_t rx_byte; // ComPortMaster로부터 들어온 RX Interrupt 1Byte를 수신하는 저장 변수.
volatile int rx_index = 0; // 1차원 배열 index

extern UART_HandleTypeDef huart1;
extern UART_HandleTypeDef huart2;

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

/* 1.Copy from HAL_UART_RxCplitCallback of stm32f4xx_hal_uart to here
   	 DRIVER/STM32F4XX_HAL_Driver/Src/stm32f4xx.hal_uart.c/HAL_UART_RxCpltCallback
	UART로 부터 1Byte를 수신되면 H/W(CPU)가 call을 해준다.
	UART로 부터 STOP BIT가 뜨면 Rx interrupt가 발생한다.


*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart == &huart2)	//PC
	{
		unsigned char rx_data;
		rx_data = rx_byte;

		if(output_pointer % COMMAND_MAX == (input_pointer+1) % COMMAND_MAX) //queue full 상태
		{
				printf("Queue Full!!!!!!!\n");
				return;
		}
		else // normal 상태
		{
			if(rx_data == '\n' || rx_data == '\r')
			{
				rx_buff[input_pointer][rx_index] = '\0'; // 문장의 끝을 null로 replace.
				rx_index = 0;
				input_pointer++;
				input_pointer %= COMMAND_MAX;
				 //개선 사항 output_pointer
			}
			else
			{
				rx_buff[input_pointer][rx_index++] = rx_data;
				//rx_index++;
			}

		}
		//주의!!!: 반드시 HAL_UART_Receive_IT를 해줘야 다음 인터럽트가 발생된다.(즉, 서비스를 받을수있다.)
		HAL_UART_Receive_IT(&huart2, &rx_byte, 1); //PC UART RX Interrupt를 enable 시켜준다.
	}
    }

Circular Queue를 코드로 구현하였다. output_pointer % COMMAND_MAX == (input_pointer+1) % COMMAND_MAX는 포화 상태인 경우를 나타내며, 포화 상태가 되면 Queue Full을 프린트한다.

 

포화상태가 아닌 노멀 상태에서, rx_data가 문장의 끝이 아닌 경우에는 rx_buff에 데이터를 저장한다. 문장이 끝나서 \n 혹은 r이 오면 문장의 끝을 null로 replace하여 저장하고, input_pointer++하여 다음 위치를 가리키도록 한다. rx_index를 0으로 셋팅하여 첫 비트부터 다시 저장할 수 있도록 하고,  input_pointer %= COMMAND_MAX; 를 선언하여 input pointer 가 0 부터 command Max-1 의 값이 되도록 한다. 

 

huart2는 pc와 연결된 uart이고 huart1로 바꾸면 bluetooth와 연결이 가능하다. 

 

 

extern void led_all_on();
extern void led_all_off();
extern void led_bar_blink_up();
extern void led_bar_blink_down();
extern void led_bar_sequential_up();
extern void led_bar_sequential_down();
extern void flower_on();
extern void flower_off();

void pc_command_processing()
{
	if(input_pointer != output_pointer) // rx_buff에 data가 들어 있다.
		{
	if(print)
		printf("rx_buff[%d] : %s\n", output_pointer, rx_buff[output_pointer]);
	//rx_buff[output_pointer] == rx_buff[output_pointer][0]와 같다.
	//&rx_buff[output_pointer][0]
	if(strncmp((const char *)rx_buff[output_pointer], "print_on", strlen("print_on")) == 0)  // strlen("print_on")) == 0 -> 8로 쓸 수 있음, but 정확도 위해
	{
		print = 1;
	}
	else if(strncmp((const char *)rx_buff[output_pointer], "print_off", strlen("print_off")) ==0)
	{
		print = 0;
	}
    }

print 값이 1 일 때만 rx_buff의 값을 출력하게 하는 코드이다. 

 

의도대로 동작하는 것을 확인하였다. 체크박스는 꼭 체크한 후에 해야 한다. new line을 가능하게 한다.

 

 

2-1  PC통신

led 동작들을  uart pc 통신으로 제어해보았다.

void pc_command_processing()
{
	if(input_pointer != output_pointer) // rx_buff에 data가 들어 있다.
		{
	if(print)
		printf("rx_buff[%d] : %s\n", output_pointer, rx_buff[output_pointer]);
	//rx_buff[output_pointer] == rx_buff[output_pointer][0]와 같다.
	//&rx_buff[output_pointer][0]
	if(strncmp((const char *)rx_buff[output_pointer], "print_on", strlen("print_on")) == 0)  // strlen("print_on")) == 0 -> 8로 쓸 수 있음, but 정확도 위해
	{
		print = 1;
	}
	else if(strncmp((const char *)rx_buff[output_pointer], "print_off", strlen("print_off")) ==0)
	{
		print = 0;
	}
	if(strncmp((const char *)rx_buff[output_pointer], "led_all_on", strlen("led_all_on")) == 0)
	{
		led_all_on();
	}
	else if(strncmp((const char *)rx_buff[output_pointer], "led_all_off", strlen("led_all_off")) ==0)
	{
		led_all_off();
	}
	if(strncmp((const char *)rx_buff[output_pointer], "led_bar_blink_up", strlen("led_bar_blink_up")) == 0)
	{
		led_bar_blink_up();
	}
	else if(strncmp((const char *)rx_buff[output_pointer], "led_bar_blink_down", strlen("led_bar_blink_down")) ==0)
	{
		led_bar_blink_down();
	}
	if(strncmp((const char *)rx_buff[output_pointer], "led_bar_sequential_up", strlen("led_bar_sequential_up")) == 0)
	{
		led_bar_sequential_up();
	}
	else if(strncmp((const char *)rx_buff[output_pointer], "led_bar_sequential_down", strlen("led_bar_sequential_down")) ==0)
	{
		led_bar_sequential_down();
	}
	if(strncmp((const char *)rx_buff[output_pointer], "flower_on", strlen("flower_on")) == 0)
	{
		flower_on();
	}
	else if(strncmp((const char *)rx_buff[output_pointer], "flower_off", strlen("flower_off")) ==0)
	{
		flower_off();
	}

		output_pointer = (output_pointer+1) % COMMAND_MAX;
		//output_pointer를 1씩 증가.
	}

}

 

 

2-2 UART - BT 통신

void bt_command_processing()
{
	if(input_pointer != output_pointer) // rx_buff에 data가 들어 있다.
	{
		if(print)
			printf("rx_buff[%d] : %s\n", output_pointer, rx_buff[output_pointer]);
		//rx_buff[output_pointer] == rx_buff[output_pointer][0]와 같다.
		//&rx_buff[output_pointer][0]
		if(strncmp((const char *)rx_buff[output_pointer], "print_on", strlen("print_on")) == 0)  // strlen("print_on")) == 0 -> 8로 쓸 수 있음, but 정확도 위해
		{
			print = 1;
		}
		else if(strncmp((const char *)rx_buff[output_pointer], "print_off", strlen("print_off")) ==0)
		{
			print = 0;
		}
		if(strncmp((const char *)rx_buff[output_pointer], "led_all_on", strlen("led_all_on")) == 0)
		{
			led_all_on();
		}
		else if(strncmp((const char *)rx_buff[output_pointer], "led_all_off", strlen("led_all_off")) ==0)
		{
			led_all_off();
		}
		if(strncmp((const char *)rx_buff[output_pointer], "led_bar_blink_up", strlen("led_bar_blink_up")) == 0)
		{
			led_bar_blink_up();
		}
		else if(strncmp((const char *)rx_buff[output_pointer], "led_bar_blink_down", strlen("led_bar_blink_down")) ==0)
		{
			led_bar_blink_down();
		}
		if(strncmp((const char *)rx_buff[output_pointer], "led_bar_sequential_up", strlen("led_bar_sequential_up")) == 0)
		{
			led_bar_sequential_up();
		}
		else if(strncmp((const char *)rx_buff[output_pointer], "led_bar_sequential_down", strlen("led_bar_sequential_down")) ==0)
		{
			led_bar_sequential_down();
		}
		if(strncmp((const char *)rx_buff[output_pointer], "flower_on", strlen("flower_on")) == 0)
		{
			flower_on();
		}
		else if(strncmp((const char *)rx_buff[output_pointer], "flower_off", strlen("flower_off")) ==0)
		{
			flower_off();
		}

		output_pointer = (output_pointer+1) % COMMAND_MAX;
		//output_pointer를 1씩 증가.
	}

}

pc와 코드는 동일하다.

 

 

 

 

 

3. 과제

전화번호 뒷자리 네자리를 uart통신으로 pc에 출력하고, 이 때 송신되는 데이터값이 정확한지 오실로스코프로 분석한다.

이 때 데이터는 아스키코드 형태로 송신되므로, ex) '2' 는 0x32이고, '3' 은 0x33 임에 유의한다. 

 

2350이 프린트되고 있다.