printf("ho_tari\n");

5일차 본문

2025.03.10

 

오늘의 학습 목표

1. UART 통신

- RX INT

- Circular Queue 적용

- uart command programming

2. 회로도 작성

 

Comport Master 다운로드

 

UART 시리얼 통신

- 마이크로컨트롤러는 비트 단위의 데이터를 핀 단위로 전송
- 바이트 단위 데이터 전송을 위한 방법
     - 병렬 전송 : 8개의 핀을 통해 1번에 1바이트 데이터 전송
          - 연결이 복잡해짐
          - 핀 수가 제한된 마이크로컨트롤러에서는 핀 부족으로 연결이 불가능할 수 있음
 
     - 직렬 전송 : 1개의 핀으로 8번에 나누어 1바이트 데이터 전송
          - UART 통신은 시리얼/직렬 통신의 한 종류임

 

 

- UART 통신은 비동기식 통신임
     - 데이터를 위한 별도를 클록을 사용하지 않으므로 약속된 속도로 송수신을 수행함
     - 통신 단위로 보율(baud rate) 사용

 

비동기 vs. 동기

- 비동기 : (동기가 아님) clock 신호와 무관하게 별도의 부호를 가지고 Data 송수신 시 Data를 구분하는 방식

- 동기 : clock 신호에 맞춰서 Data 송수신하는 방식

 

보율(Baud Rate) : 변조 속도, 신호가 변화한 횟수

BPS(Bits Per Second) : 전송 속도

 

데이터 전송 규칙

- 데이터의 시작과 끝을 표시하기 위한 표시 필요

- 데이터 비트 포함 10비트 데이터를 전송하는 것이 일반적

- 시작 비트 : 1비트의 LOW

- 데이터 비트 : 8비트의 데이터

- 정지 비트 : 1비트의 HIGH

 

ATmega128A의 UART 통신

- 송신(RX, receive data)과 수신(TX, transmit data)2개 핀을 사용
- 2개의 장치를 UART 통신을 위해 연결하는 경우 RXTX는 교차하여 연결되어야 함

 

벡터 번호 : 19

프로그램 주소 : 0x0024

소스 : USART0 RX

인터럽트 정의 : USART0 Rx Complete

UBRR 레지스터와 보율

UCSRnA 레지스터 비트

 

UCSRnB 레지스터 비트

 

 

<uart0.h>

/*
 * uart0.h
 *
 * Created: 2025-03-10 오전 10:32:09
 *  Author: microsoft
 */ 


#ifndef UART0_H_
#define UART0_H_

#define F_CPU 16000000L
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h> // sei()

// led_all_on\n
// led_all_off\n
volatile uint8_t rx_buff[80]; // uart0로부터 들어온 문자를 저장하는 버퍼(변수)

#endif /* UART0_H_ */

 

<uart0.c>

/*
 * uart0.c
 *
 * Created: 2025-03-10 오전 10:32:24
 *  Author: microsoft
 */ 

#include "uart0.h"

void init_uart0(void);
void UART0_transmit(uint8_t data);

/*
	PC comportmaster로부터 1byte가 들어올 때마다 이곳으로 들어온다. (RX INT)
	예) led_all_on\n ==> 11번 이곳으로 들어온다.
		led_all_off\n
*/

volatile uint8_t rx_msg_received = 0;

ISR(USART0_RX_vect)
{
	volatile uint8_t rx_data;
	volatile static int i = 0;
	
	rx_data = UDR0; // uart0의 H/W register(UDR0)로부터 1byte를 읽어들인다.
					// rx_data = UDR0;를 실행하면 UDR0의 내용이 빈다. (empty)
	if(rx_data == '\n')
	{
		rx_msg_received = 1; // 완전한 message 수신
		rx_buff[i] = '\0'; // 문장의 끝을 NULL로 집어 넣는다.
		i = 0;
	}
	else
	{
		rx_buff[i++] = rx_data;
	}
}

/*
	1. 전송 속도 : 9600bps : 총 글자수 : 9600/10bit ==> 960자
		(1글자를 송, 수신하는 데 소요 시간 : 약 1ms)
	2. 비동기 방식(start/stop 부호 비트로 데이터를 구분
	3. RX(수신) : 인터럽트 방식으로 구현 (TX는 폴링 방식으로 구현)
*/

void init_uart0(void)
 {
	// 1. 9600bps로 설정
	UBRR0H = 0x00;
	UBRR0L = 207; // 9600bps
	// 2. 2배속 통신
	UCSR0A |= 1 << U2X0; // 2배속 통신
	UCSR0C |= 0x06; // 비동기 / data8bits / none parity
	
	// RXEN0 : UART0로부터 수신이 가능하도록
	// TXEN0 : UART0로부터 송신이 가능하도록
	// RXCIE0 : UART0로부터 1byte가 들어오면 (stop bit가 들어오면) rx interrupt를 발생시켜라
	UCSR0B |= 1 << RXEN0 | 1 << TXEN0 | 1 << RXCIE0;
}

// UART0로 1byte를 전송하는 함수 (polling 방식)
void UART0_transmit(uint8_t data)
{
	// 데이터 전송 중이면 전송이 끝날 때까지 기다린다.
	while(!(UCSR0A & 1 << UDRE0))
		; // no operation
	UDR0 = data; // data를 H/W 전송 register에 쏜다.
}

 

<main.c>

/*
 * 01.LED_CONTROL.c
 *
 * Created: 2025-03-04 오후 4:25:34
 * Author : microsoft
 */ 

#define  F_CPU 16000000UL  // 16MHZ
#include <avr/io.h>
#include <util/delay.h>  // _delay_ms _delay_us
#include <avr/interrupt.h> // sei()
#include <stdio.h> // printf, scanf, fgets, puts, gets 등이 들어있다.

#include "button.h"

extern int led_main(void);   // 선언
extern void init_button(void);
extern int get_button(int button_num, int button_pin);

extern void led_all_on(void);
extern void led_all_off(void);
extern void shift_left_ledon(void);
extern void shift_right_ledon(void);
extern void shift_left_keep_ledon(void);
extern void shift_right_keep_ledon(void);
extern void flower_on(void);
extern void flower_off(void);
extern int fnd_main(void);
extern void init_uart0(void);
extern void UART0_transmit(uint8_t data);

extern volatile rx_buff[80]; // uart0로부터 들어온 문자를 저장하는 버퍼(변수)
extern volatile uint8_t rx_msg_received;

volatile int msec_count = 0;
// for printf 
FILE OUTPUT = FDEV_SETUP_STREAM(UART0_transmit, NULL, _FDEV_SETUP_WRITE);
// volatile 변수 type 앞에 volatile을 선언하는 이유 : compiler한테 최적화 방지 지시
// cc -O
/*
예) 1 ~ 10까지 합을 구하는 프로그램
int i = 0; int sum = 0;
while( i < 10)
{
	i++; sum += i;	
}
printf("sum-->%d\n", sum); // 1 ~ 10 : 55가 된다.
*/

// TIMER0_OVF_vect
// ISR(Interrupt Service Routine)(함수)
// --> H/W가 S/W한테 event(상황변화)가 일어났다고 알려 주는 공간
// 250개의 pulse를 count(1ms)하면 이곳으로 자동적으로 들어온다.
// ISR루틴(함수)는 가능한 짧게 작성한다.
int led_toggle = 0;
ISR(TIMER0_OVF_vect)
{
	// 6 ~ 256 : 250(1ms) 그래서 TCNT0를 6으로 설정하는 것이다.
	TCNT0 = 6;
	msec_count++; // 1ms마다 1씩 증가
}

int main(void)
{
	DDRA = 0b11111111;   // PORTA를 출력 모드(1)로 설정
	
	init_timer0();
	init_uart0();
	stdout = &OUTPUT; // printf가 동작될 수 있도록 stdout에 OUTPUT 파일 포인터 assign
	sei(); // 전역(대문)으로 interrupt 허용
	
	// led_main();
	
	printf("init_uart0\n");
	
	while(1)
	{
		if(rx_msg_received)
		{
			printf("%s\n", rx_buff);
			led_all_on();
			rx_msg_received = 0;
		}
	}
}

// none O/S 또는 loop monitor방식 
#if 0
int main(void)
{
	int button0_state=0;  // 초기 상태를 0으로 출발
	
	init_timer0();
	
	led_main();
    //fnd_main();
	
	void (*fp[]) (void) =
	{
		led_all_off,
		led_all_on,
		shift_left_ledon,
		shift_right_ledon,
		shift_left_keep_ledon,
		shift_right_keep_ledon,
		flower_on,
		flower_off
	};
	
	init_button();
	// led_main();    
	       //76543210    
	DDRA = 0b11111111;   // PORTA를 출력 모드(1)로 설정
	       //---- 상위 nibble : 상위 4bits
		   //    ---- 하위 nibble 
	                     // DDR(Data Direction Register) : 방향 설정 
						 // 1: 출력 0: 입력을 의미 
						 // 0b : 2진수
						 // 0x : hex 
						 // DDRA = 0xff;
#if 0   // 함수 포인터 배열 
	while (1)
	{
		if (get_button(BUTTON0, BUTTON0PIN))
		{
			button0_state++;
			button0_state %= 8;
		}
		fp[button0_state] ();
	}
#endif

#if 0   // switch ~ case 
	while (1)
	{
		if (get_button(BUTTON0, BUTTON0PIN))
		{
			button0_state++;
			button0_state %= 8;
		}
		switch (button0_state) {
		case 0:
			led_all_off();
			break;
		case 1:
			led_all_on();
			break;
		case 2:
			shift_left_ledon();
			break;
		case 3:
			shift_right_ledon();
			break;
		case 4:
			shift_left_keep_ledon();
			break;
		case 5:
			shift_right_keep_ledon();
			break;
		case 6:
			flower_on();
			break;
		case 7:
			flower_off();
			break;
		}
	}
#endif

#if 0	// org
    while (1)   // for(;;) 
    {
		// 1 button버튼 처리 (toggle)
		// button0를 1번 누르면 led_all_on
		//                      led_all_off
		if (get_button(BUTTON0, BUTTON0PIN))
		{
			button0_state = !button0_state;  // 반전 0 <--> 1
			if (button0_state)
				led_all_on();
			else led_all_off();
		}
    }
#endif 
}
#endif

// timer0를 초기화시키는 부분
// AVR에서 8bit timer 0/2번 중 0번을 초기화
// 임베디드/FPGA 등에서 제일 중요한 것은 초기화를 정확히 해주는 것이다.
// 그래서 이 부분을 특별히 신경을 써서 작성한다.
void init_timer0(void)
{
	// 16MHz/64 분주(down) : divider / prescale
	// ---------- 분주비 계산 ----------
	// (1) 16000000Hz/64 ==> 250,000Hz
	// (2) T(주기) 1clock의 소요 시간 : 1/f = 1/250,000 ==> 0.000004sec(4us) : 0.004ms
	// (3) 8bit timer OV(OVflow) : 0.004ms x 256 = 0.001024sec --> 1.024ms
	// 1ms마다 정확하게 INT를 띄우고 싶으면 0.004ms x 250개를 count = 0.001sec ==> 1ms
	TCNT0 = 6; // TCNT : 0 ~ 256 1ms마다 TIMER0_OVF_vect로 진입한다.
			   // TCNT0 = 6으로 설정한 이유 : 6 ~ 256 : 250개의 펄스를 count하기 때문에 정확히 1ms가 된다.
	// (4) 분주비 설정 64분주 (250,000Hz --> 250KHz)
	TCCR0 |= 1 << CS02 | 0 << CS01 | 0 << CS00; // TCCR0 |= 0xf4; 보다는 좌측 코드 권장
	// (5) Timer0 overflow INT를 허용(enable)
	TIMSK |= 1 << TOIE0; // TIMSK |= 0x01;
}

 

<실행 결과>

 

원형큐

 

<main.c>

/*
 * 01.LED_CONTROL.c
 *
 * Created: 2025-03-04 오후 4:25:34
 * Author : microsoft
 */ 

#define  F_CPU 16000000UL  // 16MHZ
#include <avr/io.h>
#include <util/delay.h>  // _delay_ms _delay_us
#include <avr/interrupt.h> // sei()
#include <stdio.h> // printf, scanf, fgets, puts, gets 등이 들어있다.

#include "button.h"

extern int led_main(void);   // 선언
extern void init_button(void);
extern int get_button(int button_num, int button_pin);

extern void led_all_on(void);
extern void led_all_off(void);
extern void shift_left_ledon(void);
extern void shift_right_ledon(void);
extern void shift_left_keep_ledon(void);
extern void shift_right_keep_ledon(void);
extern void flower_on(void);
extern void flower_off(void);
extern int fnd_main(void);
extern void init_uart0(void);
extern void UART0_transmit(uint8_t data);
extern void pc_command_processing(void);

extern volatile uint8_t rx_buff[COMMAND_NUMBER][COMMAND_LENGTH]; // uart0로부터 들어온 문자를 저장하는 버퍼(변수)
extern volatile uint8_t rx_msg_received;

volatile int msec_count = 0;
// for printf 
FILE OUTPUT = FDEV_SETUP_STREAM(UART0_transmit, NULL, _FDEV_SETUP_WRITE);
// volatile 변수 type 앞에 volatile을 선언하는 이유 : compiler한테 최적화 방지 지시
// cc -O
/*
예) 1 ~ 10까지 합을 구하는 프로그램
int i = 0; int sum = 0;
while( i < 10)
{
	i++; sum += i;	
}
printf("sum-->%d\n", sum); // 1 ~ 10 : 55가 된다.
*/

// TIMER0_OVF_vect
// ISR(Interrupt Service Routine)(함수)
// --> H/W가 S/W한테 event(상황변화)가 일어났다고 알려 주는 공간
// 250개의 pulse를 count(1ms)하면 이곳으로 자동적으로 들어온다.
// ISR루틴(함수)는 가능한 짧게 작성한다.
int led_toggle = 0;
ISR(TIMER0_OVF_vect)
{
	// 6 ~ 256 : 250(1ms) 그래서 TCNT0를 6으로 설정하는 것이다.
	TCNT0 = 6;
	msec_count++; // 1ms마다 1씩 증가
}

int main(void)
{
	DDRA = 0b11111111;   // PORTA를 출력 모드(1)로 설정
	
	init_timer0();
	init_uart0();
	stdout = &OUTPUT; // printf가 동작될 수 있도록 stdout에 OUTPUT 파일 포인터 assign
	sei(); // 전역(대문)으로 interrupt 허용
	
	// led_main();
	
	printf("init_uart0\n");
	
	while(1)
	{
		pc_command_processing();
	}
}

// none O/S 또는 loop monitor방식 
#if 0
int main(void)
{
	int button0_state=0;  // 초기 상태를 0으로 출발
	
	init_timer0();
	
	led_main();
    //fnd_main();
	
	void (*fp[]) (void) =
	{
		led_all_off,
		led_all_on,
		shift_left_ledon,
		shift_right_ledon,
		shift_left_keep_ledon,
		shift_right_keep_ledon,
		flower_on,
		flower_off
	};
	
	init_button();
	// led_main();    
	       //76543210    
	DDRA = 0b11111111;   // PORTA를 출력 모드(1)로 설정
	       //---- 상위 nibble : 상위 4bits
		   //    ---- 하위 nibble 
	                     // DDR(Data Direction Register) : 방향 설정 
						 // 1: 출력 0: 입력을 의미 
						 // 0b : 2진수
						 // 0x : hex 
						 // DDRA = 0xff;
#if 0   // 함수 포인터 배열 
	while (1)
	{
		if (get_button(BUTTON0, BUTTON0PIN))
		{
			button0_state++;
			button0_state %= 8;
		}
		fp[button0_state] ();
	}
#endif

#if 0   // switch ~ case 
	while (1)
	{
		if (get_button(BUTTON0, BUTTON0PIN))
		{
			button0_state++;
			button0_state %= 8;
		}
		switch (button0_state) {
		case 0:
			led_all_off();
			break;
		case 1:
			led_all_on();
			break;
		case 2:
			shift_left_ledon();
			break;
		case 3:
			shift_right_ledon();
			break;
		case 4:
			shift_left_keep_ledon();
			break;
		case 5:
			shift_right_keep_ledon();
			break;
		case 6:
			flower_on();
			break;
		case 7:
			flower_off();
			break;
		}
	}
#endif

#if 0	// org
    while (1)   // for(;;) 
    {
		// 1 button버튼 처리 (toggle)
		// button0를 1번 누르면 led_all_on
		//                      led_all_off
		if (get_button(BUTTON0, BUTTON0PIN))
		{
			button0_state = !button0_state;  // 반전 0 <--> 1
			if (button0_state)
				led_all_on();
			else led_all_off();
		}
    }
#endif 
}
#endif

// timer0를 초기화시키는 부분
// AVR에서 8bit timer 0/2번 중 0번을 초기화
// 임베디드/FPGA 등에서 제일 중요한 것은 초기화를 정확히 해주는 것이다.
// 그래서 이 부분을 특별히 신경을 써서 작성한다.
void init_timer0(void)
{
	// 16MHz/64 분주(down) : divider / prescale
	// ---------- 분주비 계산 ----------
	// (1) 16000000Hz/64 ==> 250,000Hz
	// (2) T(주기) 1clock의 소요 시간 : 1/f = 1/250,000 ==> 0.000004sec(4us) : 0.004ms
	// (3) 8bit timer OV(OVflow) : 0.004ms x 256 = 0.001024sec --> 1.024ms
	// 1ms마다 정확하게 INT를 띄우고 싶으면 0.004ms x 250개를 count = 0.001sec ==> 1ms
	TCNT0 = 6; // TCNT : 0 ~ 256 1ms마다 TIMER0_OVF_vect로 진입한다.
			   // TCNT0 = 6으로 설정한 이유 : 6 ~ 256 : 250개의 펄스를 count하기 때문에 정확히 1ms가 된다.
	// (4) 분주비 설정 64분주 (250,000Hz --> 250KHz)
	TCCR0 |= 1 << CS02 | 0 << CS01 | 0 << CS00; // TCCR0 |= 0xf4; 보다는 좌측 코드 권장
	// (5) Timer0 overflow INT를 허용(enable)
	TIMSK |= 1 << TOIE0; // TIMSK |= 0x01;
}

 

<uart0.c>

/*
 * uart0.c
 *
 * Created: 2025-03-10 오전 10:32:24
 *  Author: microsoft
 */ 

#include "uart0.h"
#include <string.h> // strcpy, strncmp, strcmp 등

void init_uart0(void);
void UART0_transmit(uint8_t data);
void pc_command_processing(void);

/*
	PC comportmaster로부터 1byte가 들어올 때마다 이곳으로 들어온다. (RX INT)
	예) led_all_on\n ==> 11번 이곳으로 들어온다.
		led_all_off\n
*/

volatile uint8_t rx_msg_received = 0;

ISR(USART0_RX_vect)
{
	volatile uint8_t rx_data;
	volatile static int i = 0;
	
	rx_data = UDR0; // uart0의 H/W register(UDR0)로부터 1byte를 읽어들인다.
					// rx_data = UDR0;를 실행하면 UDR0의 내용이 빈다. (empty)
	if(rx_data == '\n')
	{
		rx_buff[rear++][i] = '\0';
		rear %= COMMAND_NUMBER; // 0~9
		i = 0; // 다음 string을 저장하기 위한 1차원 index값을 0으로
		// !!!! rx_buff queue full check하는 logic 추가
	}
	else
	{
		rx_buff[rear][i++] = rx_data;
		// COMMAND_LENGTH를 check하는 logic 추가
		
	}
}

/*
	1. 전송 속도 : 9600bps : 총 글자수 : 9600/10bit ==> 960자
		(1글자를 송, 수신하는 데 소요 시간 : 약 1ms)
	2. 비동기 방식(start/stop 부호 비트로 데이터를 구분
	3. RX(수신) : 인터럽트 방식으로 구현 (TX는 폴링 방식으로 구현)
*/

void init_uart0(void)
 {
	// 1. 9600bps로 설정
	UBRR0H = 0x00;
	UBRR0L = 207; // 9600bps
	// 2. 2배속 통신
	UCSR0A |= 1 << U2X0; // 2배속 통신
	UCSR0C |= 0x06; // 비동기 / data8bits / none parity
	
	// RXEN0 : UART0로부터 수신이 가능하도록
	// TXEN0 : UART0로부터 송신이 가능하도록
	// RXCIE0 : UART0로부터 1byte가 들어오면 (stop bit가 들어오면) rx interrupt를 발생시켜라
	UCSR0B |= 1 << RXEN0 | 1 << TXEN0 | 1 << RXCIE0;
}

// UART0로 1byte를 전송하는 함수 (polling 방식)
void UART0_transmit(uint8_t data)
{
	// 데이터 전송 중이면 전송이 끝날 때까지 기다린다.
	while(!(UCSR0A & 1 << UDRE0))
		; // no operation
	UDR0 = data; // data를 H/W 전송 register에 쏜다.
}

void pc_command_processing(void)
{
	if(front != rear) // rx_buff에 data가 존재
	{
		printf("%s\n", rx_buff[front]); // &rx_buff[front][0]와 동일
		if(strncmp(rx_buff[front], "led_all_on", strlen("led_all_on")) == NULL)
		{
			printf("Find: led_all_on\n");
		}
		front++;
		front %= COMMAND_NUMBER;
		// !!!! queue full check하는 logic들어가야 한다. !!!!
	}
}

 

<uart0.h>

/*
 * uart0.h
 *
 * Created: 2025-03-10 오전 10:32:09
 *  Author: microsoft
 */ 


#ifndef UART0_H_
#define UART0_H_

#define F_CPU 16000000L
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h> // sei()
#include "def.h"

// led_all_on\n
// led_all_off\n
volatile uint8_t rx_buff[COMMAND_NUMBER][COMMAND_LENGTH]; // uart0로부터 들어온 문자를 저장하는 버퍼(변수)
volatile int rear = 0; // input index : USART0_RX_vect에서 집어 넣어주는 index
volatile int front = 0; // output index

#endif /* UART0_H_ */

 

<def.h>

/*
 * def.h
 *
 * Created: 2025-03-10 오후 3:05:49
 *  Author: microsoft
 */ 


#ifndef DEF_H_
#define DEF_H_

#define COMMAND_NUMBER 10
#define COMMAND_LENGTH 40



#endif /* DEF_H_ */

 

<실행 결과>

 

KiCAD 사용하여 회로도 제작

 

 

 

LED CONTROL with UART

 

<main.c>

/*
 * 01.LED_CONTROL.c
 *
 * Created: 2025-03-04 오후 4:25:34
 * Author : microsoft
 */ 

#define  F_CPU 16000000UL  // 16MHZ
#include <avr/io.h>
#include <util/delay.h>  // _delay_ms _delay_us
#include <avr/interrupt.h> // sei()
#include <stdio.h> // printf, scanf, fgets, puts, gets 등이 들어있다.

#include "button.h"
#include "def.h"

extern int led_main(void);   // 선언
extern void init_button(void);
extern int get_button(int button_num, int button_pin);

extern void led_all_on(void);
extern void led_all_off(void);
extern void shift_left_ledon(void);
extern void shift_right_ledon(void);
extern void shift_left_keep_ledon(void);
extern void shift_right_keep_ledon(void);
extern void flower_on(void);
extern void flower_off(void);
extern int fnd_main(void);
extern void init_uart0(void);
extern void UART0_transmit(uint8_t data);
extern void pc_command_processing(void);

extern volatile uint8_t rx_buff[COMMAND_NUMBER][COMMAND_LENGTH]; // uart0로부터 들어온 문자를 저장하는 버퍼(변수)
extern volatile uint8_t rx_msg_received;

volatile int msec_count = 0;
// for printf 
FILE OUTPUT = FDEV_SETUP_STREAM(UART0_transmit, NULL, _FDEV_SETUP_WRITE);
// volatile 변수 type 앞에 volatile을 선언하는 이유 : compiler한테 최적화 방지 지시
// cc -O
/*
예) 1 ~ 10까지 합을 구하는 프로그램
int i = 0; int sum = 0;
while( i < 10)
{
	i++; sum += i;	
}
printf("sum-->%d\n", sum); // 1 ~ 10 : 55가 된다.
*/

// TIMER0_OVF_vect
// ISR(Interrupt Service Routine)(함수)
// --> H/W가 S/W한테 event(상황변화)가 일어났다고 알려 주는 공간
// 250개의 pulse를 count(1ms)하면 이곳으로 자동적으로 들어온다.
// ISR루틴(함수)는 가능한 짧게 작성한다.
int led_toggle = 0;
ISR(TIMER0_OVF_vect)
{
	// 6 ~ 256 : 250(1ms) 그래서 TCNT0를 6으로 설정하는 것이다.
	TCNT0 = 6;
	msec_count++; // 1ms마다 1씩 증가
}

int main(void)
{
	DDRA = 0b11111111;   // PORTA를 출력 모드(1)로 설정
	
	init_timer0();
	init_uart0();
	stdout = &OUTPUT; // printf가 동작될 수 있도록 stdout에 OUTPUT 파일 포인터 assign
	sei(); // 전역(대문)으로 interrupt 허용
	
	// led_main();
	
	printf("init_uart0\n");
	
	while(1)
	{
		pc_command_processing();
	}
}

// none O/S 또는 loop monitor방식 
#if 0
int main(void)
{
	int button0_state=0;  // 초기 상태를 0으로 출발
	
	init_timer0();
	
	led_main();
    //fnd_main();
	
	void (*fp[]) (void) =
	{
		led_all_off,
		led_all_on,
		shift_left_ledon,
		shift_right_ledon,
		shift_left_keep_ledon,
		shift_right_keep_ledon,
		flower_on,
		flower_off
	};
	
	init_button();
	// led_main();    
	       //76543210    
	DDRA = 0b11111111;   // PORTA를 출력 모드(1)로 설정
	       //---- 상위 nibble : 상위 4bits
		   //    ---- 하위 nibble 
	                     // DDR(Data Direction Register) : 방향 설정 
						 // 1: 출력 0: 입력을 의미 
						 // 0b : 2진수
						 // 0x : hex 
						 // DDRA = 0xff;
#if 0   // 함수 포인터 배열 
	while (1)
	{
		if (get_button(BUTTON0, BUTTON0PIN))
		{
			button0_state++;
			button0_state %= 8;
		}
		fp[button0_state] ();
	}
#endif

#if 0   // switch ~ case 
	while (1)
	{
		if (get_button(BUTTON0, BUTTON0PIN))
		{
			button0_state++;
			button0_state %= 8;
		}
		switch (button0_state) {
		case 0:
			led_all_off();
			break;
		case 1:
			led_all_on();
			break;
		case 2:
			shift_left_ledon();
			break;
		case 3:
			shift_right_ledon();
			break;
		case 4:
			shift_left_keep_ledon();
			break;
		case 5:
			shift_right_keep_ledon();
			break;
		case 6:
			flower_on();
			break;
		case 7:
			flower_off();
			break;
		}
	}
#endif

#if 0	// org
    while (1)   // for(;;) 
    {
		// 1 button버튼 처리 (toggle)
		// button0를 1번 누르면 led_all_on
		//                      led_all_off
		if (get_button(BUTTON0, BUTTON0PIN))
		{
			button0_state = !button0_state;  // 반전 0 <--> 1
			if (button0_state)
				led_all_on();
			else led_all_off();
		}
    }
#endif 
}
#endif

// timer0를 초기화시키는 부분
// AVR에서 8bit timer 0/2번 중 0번을 초기화
// 임베디드/FPGA 등에서 제일 중요한 것은 초기화를 정확히 해주는 것이다.
// 그래서 이 부분을 특별히 신경을 써서 작성한다.
void init_timer0(void)
{
	// 16MHz/64 분주(down) : divider / prescale
	// ---------- 분주비 계산 ----------
	// (1) 16000000Hz/64 ==> 250,000Hz
	// (2) T(주기) 1clock의 소요 시간 : 1/f = 1/250,000 ==> 0.000004sec(4us) : 0.004ms
	// (3) 8bit timer OV(OVflow) : 0.004ms x 256 = 0.001024sec --> 1.024ms
	// 1ms마다 정확하게 INT를 띄우고 싶으면 0.004ms x 250개를 count = 0.001sec ==> 1ms
	TCNT0 = 6; // TCNT : 0 ~ 256 1ms마다 TIMER0_OVF_vect로 진입한다.
			   // TCNT0 = 6으로 설정한 이유 : 6 ~ 256 : 250개의 펄스를 count하기 때문에 정확히 1ms가 된다.
	// (4) 분주비 설정 64분주 (250,000Hz --> 250KHz)
	TCCR0 |= 1 << CS02 | 0 << CS01 | 0 << CS00; // TCCR0 |= 0xf4; 보다는 좌측 코드 권장
	// (5) Timer0 overflow INT를 허용(enable)
	TIMSK |= 1 << TOIE0; // TIMSK |= 0x01;
}

 

<uart0.c>

/*
 * uart0.c
 *
 * Created: 2025-03-10 오전 10:32:24
 *  Author: microsoft
 */ 

#include "uart0.h"
#include <string.h> // strcpy, strncmp, strcmp 등

void init_uart0(void);
void UART0_transmit(uint8_t data);
void pc_command_processing(void);

#define FUNC_NUMBER 8

extern void (*fp[FUNC_NUMBER]) (void);

/*
	PC comportmaster로부터 1byte가 들어올 때마다 이곳으로 들어온다. (RX INT)
	예) led_all_on\n ==> 11번 이곳으로 들어온다.
		led_all_off\n
*/

volatile uint8_t rx_msg_received = 0;

ISR(USART0_RX_vect)
{
	volatile uint8_t rx_data;
	volatile static int i = 0;
	
	rx_data = UDR0; // uart0의 H/W register(UDR0)로부터 1byte를 읽어들인다.
					// rx_data = UDR0;를 실행하면 UDR0의 내용이 빈다. (empty)
	if(rx_data == '\n')
	{
		rx_buff[rear++][i] = '\0';
		rear %= COMMAND_NUMBER; // 0~9
		i = 0; // 다음 string을 저장하기 위한 1차원 index값을 0으로
		// !!!! rx_buff queue full check하는 logic 추가
	}
	else
	{
		rx_buff[rear][i++] = rx_data;
		// COMMAND_LENGTH를 check하는 logic 추가
		
	}
}

/*
	1. 전송 속도 : 9600bps : 총 글자수 : 9600/10bit ==> 960자
		(1글자를 송, 수신하는 데 소요 시간 : 약 1ms)
	2. 비동기 방식(start/stop 부호 비트로 데이터를 구분
	3. RX(수신) : 인터럽트 방식으로 구현 (TX는 폴링 방식으로 구현)
*/

void init_uart0(void)
 {
	// 1. 9600bps로 설정
	UBRR0H = 0x00;
	UBRR0L = 207; // 9600bps
	// 2. 2배속 통신
	UCSR0A |= 1 << U2X0; // 2배속 통신
	UCSR0C |= 0x06; // 비동기 / data8bits / none parity
	
	// RXEN0 : UART0로부터 수신이 가능하도록
	// TXEN0 : UART0로부터 송신이 가능하도록
	// RXCIE0 : UART0로부터 1byte가 들어오면 (stop bit가 들어오면) rx interrupt를 발생시켜라
	UCSR0B |= 1 << RXEN0 | 1 << TXEN0 | 1 << RXCIE0;
}

// UART0로 1byte를 전송하는 함수 (polling 방식)
void UART0_transmit(uint8_t data)
{
	// 데이터 전송 중이면 전송이 끝날 때까지 기다린다.
	while(!(UCSR0A & 1 << UDRE0))
		; // no operation
	UDR0 = data; // data를 H/W 전송 register에 쏜다.
}

void pc_command_processing(void)
{
	if(front != rear) // rx_buff에 data가 존재
	{
		printf("%s\n", rx_buff[front]); // &rx_buff[front][0]와 동일
		if(strncmp(rx_buff[front], "led_all_on", strlen("led_all_on")) == NULL)
		{
			fp[0]();
		}
		else if(strncmp(rx_buff[front], "led_all_off", strlen("led_all_off")) == NULL)
		{
			fp[1]();
		}
		else if(strncmp(rx_buff[front], "shift_left_ledon", strlen("shift_left_ledon")) == NULL)
		{
			while(1)
			{
				fp[2]();
				if(UCSR0A & 1 << RXC0)
					break;
			}
		}
		else if(strncmp(rx_buff[front], "shift_right_ledon", strlen("shift_right_ledon")) == NULL)
		{
			while(1)
			{
				fp[3]();
				if(UCSR0A & 1 << RXC0)
					break;
			}
		}
		else if(strncmp(rx_buff[front], "shift_left_keep_ledon", strlen("shift_left_keep_ledon")) == NULL)
		{
			while(1)
			{
				fp[4]();
				if(UCSR0A & 1 << RXC0)
					break;
			}
		}
		else if(strncmp(rx_buff[front], "shift_right_keep_ledon", strlen("shift_right_keep_ledon")) == NULL)
		{
			while(1)
			{
				fp[5]();
				if(UCSR0A & 1 << RXC0)
					break;
			}
		}
		else if(strncmp(rx_buff[front], "flower_on", strlen("flower_on")) == NULL)
		{
			while(1)
			{
				fp[6]();
				if(UCSR0A & 1 << RXC0)
					break;
			}
		}
		else if(strncmp(rx_buff[front], "flower_off", strlen("flower_off")) == NULL)
		{
			 while(1)
			 {
				 fp[7]();
				 if(UCSR0A & 1 << RXC0)
					break;
			 }
		}
		front++; 
		front %= COMMAND_NUMBER;
		// !!!! queue full check하는 logic들어가야 한다. !!!!
	}
}

 

<uart0.h>

/*
 * uart0.h
 *
 * Created: 2025-03-10 오전 10:32:09
 *  Author: microsoft
 */ 


#ifndef UART0_H_
#define UART0_H_

#define F_CPU 16000000L
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h> // sei()
#include "def.h"

// led_all_on\n
// led_all_off\n
volatile uint8_t rx_buff[COMMAND_NUMBER][COMMAND_LENGTH]; // uart0로부터 들어온 문자를 저장하는 버퍼(변수)
volatile int rear = 0; // input index : USART0_RX_vect에서 집어 넣어주는 index
volatile int front = 0; // output index

#endif /* UART0_H_ */

 

<led.c>

/*
 * led.c
 *
 * Created: 2025-03-05 오전 10:21:53
 *  Author: microsoft
 */ 

#include "led.h"
#include "button.h"

int led_main(void);   // 선언
void led_all_on(void);
void led_all_off(void);
void shift_left_ledon(void);
void shift_right_ledon(void);
void shift_left_keep_ledon(void);
void shift_right_keep_ledon(void);
void flower_on(void);
void flower_off(void);
void state_transition(void);

extern void init_button(void);
extern int get_button(int button_num, int button_pin);

extern volatile int msec_count;
#define FUNC_NUMBER 8

int state = 0;
int button0_state = 0;
int button1_state = 0;

void (*fp[]) (void)= 
{
	led_all_on,
	led_all_off,
	shift_left_ledon,
	shift_right_ledon,
	shift_left_keep_ledon,  // _delay_ms(200)
	shift_right_keep_ledon,
	flower_on,
	flower_off
};

int led_main(void)   // 정의 
{
	DDRA = 0xff;   // PORTA에 연결된 pin8개를 output mode로 
	init_button();

#if 1
	while(1)
	{
		if (get_button(BUTTON0, BUTTON0PIN))
		{
			button0_state = !button0_state;
		}
		if (get_button(BUTTON1, BUTTON1PIN))
		{
			button1_state++;
			button1_state %= FUNC_NUMBER;
		}
		
		fp[state]();
		
	}
#else
	while(1) 
	{
		shift_left_ledon();
		shift_right_ledon();
		shift_left_keep_ledon();  // _delay_ms(200)
		shift_right_keep_ledon();
		flower_on(); 
		flower_off();
	}	
#endif
}

void shift_left_ledon(void)
{
#if 1
	// (1) for문 제거 (2) _delay_ms(30) 제거
	static int i = 0;
	
	if(msec_count >= 100)
	{
		msec_count = 0;
		if(i >= 8)
		{
			i = 0;
			PORTA = 0;
			state_transition();
			
		}
		else
		{
			PORTA = 0b00000001 << i++; // (1) PORTA = 0b00000001 << i (2) i++
		}
	}
#else // org
	for (int i=0; i < 8; i++)
	{
		PORTA = 0b00000001 << i;
		//               1 i=0;
	    //        00000010 i=1;
	    //        10000000 i=7;
		_delay_ms(30);
	}	
#endif
}

void shift_right_ledon(void)
{
#if 1
	static int i = 0;
	
	if(msec_count >= 100)
	{
		msec_count = 0;
		if(i >= 8)
		{
			i = 0;
			PORTA = 0;
			state_transition();
		}
		else
		{
			PORTA = 0b10000000 >> i++;
		}
	}
#else
	PORTA=0;
	for (int i=0; i < 8; i++)
	{
		PORTA = 0b10000000 >> i;
		//        10000000 i=0;
		//        01000000 i=1;
		_delay_ms(30);
	}
#endif
}

void shift_left_keep_ledon(void)
{
#if 1
	static int i = 0;
	
	if(msec_count >= 100)
	{
		msec_count = 0;
		if(i >= 8)
		{
			i = 0;
			PORTA = 0;
			state_transition();
		}
		else
		{
			PORTA |= 0b00000001 << i;
			i++;
		}
	}
#else
	for (int i=0; i < 8; i++)
	{
		PORTA |= 0b00000001 << i;
		//               1 i=0;
		//        00000010 i=1;
		//        10000000 i=7;
		_delay_ms(30);
	}
#endif
}

void shift_right_keep_ledon(void)
{
#if 1
	static int i = 0;
	
	if(msec_count >= 100)
	{
		msec_count = 0;
		if(i >= 8)
		{
			i = 0;
			PORTA = 0;
			state_transition();
		}
		else
		{
			PORTA |= 0b10000000 >> i;
			i++;
		}
	}
#else
	PORTA=0;
	for (int i=0; i < 8; i++)
	{
		PORTA |= 0b10000000 >> i;
		//        10000000 i=0;
		//        01000000 i=1;
		_delay_ms(30);
	}
#endif
}

void flower_on(void)
{
#if 1
	// (1) for문 제거 (2) _delay_ms(30) 제거
	static int i = 0;

	if(msec_count >= 100)
	{
		msec_count = 0;
		if(i >= 4)
		{
			i = 0;
			PORTA = 0;
			state_transition();
		}
		else
		{
			PORTA |= 0x10 << i | 0x08 >> i;
			i++;
		}
	}
#else
	PORTA=0;
	for (int i=0; i < 4; i++)
	{
		PORTA |= 0x10 << i | 0x08 >> i;
		_delay_ms(30);
	}
#endif
}

void flower_off(void)
{
#if 1
	unsigned char h=0xf0;
	unsigned char l=0x0f;
	static int i = 0;

	if(msec_count >= 100)
	{
		msec_count = 0;
		if(i >= 4)
		{
			i = 0;
			PORTA = 0;
			state_transition();
		}
		else
		{
			PORTA = (h >> i) & 0xf0 | (l << i) & 0x0f;
			i++;
		}
	}
#else
	unsigned char h=0xf0;
	unsigned char l=0x0f;
	
	PORTA=0;
	for (int i=0; i < 4; i++)
	{
		PORTA = (h >> i) & 0xf0 | (l << i) & 0x0f;
		_delay_ms(30);
	}
	PORTA=0;
	_delay_ms(30);
#endif
}

void led_all_on(void)
{
	PORTA = 0xff;
}

void led_all_off(void)
{
	PORTA = 0x00;
}

void state_transition(void)
{
	if(!button0_state)
	{
		state++;
		state = state % FUNC_NUMBER;
	}
	else
	{
		state = button1_state;
	}
}

 

<실행 결과>

https://youtube.com/shorts/6XBp6Kz4Ejc

 

Stack Calculator

 

<stack_calculator.c>

#include <stdio.h>    // 표준 입출력 함수들 (printf, fgets 등)을 사용하기 위한 헤더 파일
#include <stdlib.h>   // 표준 라이브러리 함수들 (exit 등)을 사용하기 위한 헤더 파일
#include <ctype.h>    // 문자 분류 함수들 (isdigit 등)을 사용하기 위한 헤더 파일

// 스택의 최대 크기를 정의합니다.
#define MAX_SIZE 100

// 숫자들을 저장할 스택을 위한 배열과, 스택의 꼭대기를 나타내는 인덱스를 초기화합니다.
double numStack[MAX_SIZE];
int numTop = -1;  // -1은 스택이 비어있음을 의미합니다.

// 연산자들을 저장할 스택을 위한 배열과, 스택의 꼭대기를 나타내는 인덱스를 초기화합니다.
char opStack[MAX_SIZE];
int opTop = -1;  // -1은 스택이 비어있음을 의미합니다.

// 주어진 연산자(op)의 우선순위를 반환하는 함수입니다.
// '+'와 '-'의 우선순위는 1, '*'와 '/'의 우선순위는 2로 설정합니다.
// 다른 문자의 경우 0을 반환합니다.
int precedence(char op) {
    return (op == '+' || op == '-') ? 1 : (op == '*' || op == '/') ? 2 : 0;
}

// 연산자 op를 이용하여 두 피연산자 a, b에 대해 연산을 수행하고 결과를 숫자 스택에 push하는 함수입니다.
void applyOperator() {
    // 숫자 스택에서 가장 최근에 push한 숫자(b)를 pop합니다.
    double b = numStack[numTop--];
    // 그 다음 숫자(a)를 pop합니다.
    double a = numStack[numTop--];
    // 연산자 스택에서 연산자를 pop합니다.
    char op = opStack[opTop--];
    double result;  // 연산 결과를 저장할 변수

    // 연산자에 따라 적절한 계산을 수행합니다.
    if (op == '+')
        result = a + b;
    else if (op == '-')
        result = a - b;
    else if (op == '*')
        result = a * b;
    else if (op == '/')
        result = a / b;

    // 계산된 결과를 숫자 스택에 push합니다.
    numStack[++numTop] = result;
}

int main() {
    // 사용자로부터 입력받을 수식을 저장할 배열을 선언합니다.
    char expr[256];
    // 사용자에게 수식 입력을 요청합니다.
    printf("수식을 입력하세요: ");

    // 표준 입력으로부터 한 줄을 읽어 expr에 저장합니다.
    if (fgets(expr, sizeof(expr), stdin) == NULL) {
        // 입력이 실패할 경우 에러 메시지 출력 후 프로그램 종료
        printf("입력 오류!\n");
        return 1;
    }

    // 수식의 각 문자를 순차적으로 처리하기 위한 인덱스 변수
    int i = 0;
    // 문자열의 끝(NULL 문자)을 만날 때까지 반복합니다.
    while (expr[i] != '\0') {
        // 공백 문자는 무시합니다.
        if (expr[i] == ' ') {
            i++;  // 인덱스를 증가시키고 다음 문자로 넘어갑니다.
            continue;
        }

        // 현재 문자가 숫자인 경우 (정수 또는 실수)
        if (isdigit(expr[i])) {
            double num = 0;  // 숫자 값을 저장할 변수 초기화
            // 정수 부분을 처리합니다: 연속된 숫자들을 읽어 num에 누적합니다.
            while (isdigit(expr[i])) {
                num = num * 10 + (expr[i] - '0');  // 문자 -> 숫자 변환 후 누적
                i++;  // 다음 문자로 이동
            }
            // 소수점이 있을 경우 소수 부분을 처리합니다.
            if (expr[i] == '.') {
                i++;  // 소수점 문자 건너뛰기
                double factor = 0.1;  // 소수점 이하 자릿수를 계산하기 위한 배수
                // 소수점 이하의 숫자들을 처리합니다.
                while (isdigit(expr[i])) {
                    num += (expr[i] - '0') * factor;  // 각 자리수 값을 누적
                    factor *= 0.1;  // 다음 자리수의 가중치를 줄임
                    i++;  // 다음 문자로 이동
                }
            }
            // 완성된 숫자 값을 숫자 스택에 push합니다.
            numStack[++numTop] = num;
            continue;  // 다음 문자 처리로 넘어갑니다.
        }

        // 현재 문자가 왼쪽 괄호 '('인 경우, 연산자 스택에 push합니다.
        if (expr[i] == '(') {
            opStack[++opTop] = expr[i];
            i++;  // 다음 문자로 이동
            continue;
        }

        // 현재 문자가 오른쪽 괄호 ')'인 경우,
        // 왼쪽 괄호 '('가 나올 때까지 연산자 스택에 있는 연산자들을 이용하여 계산을 수행합니다.
        if (expr[i] == ')') {
            // opStack의 top이 '('가 될 때까지 반복합니다.
            while (opTop >= 0 && opStack[opTop] != '(') {
                applyOperator();  // 연산자 적용
            }
            // 왼쪽 괄호 '('를 스택에서 제거합니다.
            opTop--;
            i++;  // 다음 문자로 이동
            continue;
        }

        // 현재 문자가 연산자('+', '-', '*', '/')인 경우 처리합니다.
        if (expr[i] == '+' || expr[i] == '-' || expr[i] == '*' || expr[i] == '/') {
            // 스택에 있는 연산자 중, '('가 아닌 것이고 현재 연산자보다 우선순위가 높거나 같은 경우 계산을 먼저 수행합니다.
            while (opTop >= 0 && opStack[opTop] != '(' &&
                precedence(opStack[opTop]) >= precedence(expr[i])) {
                applyOperator();
            }
            // 현재 연산자를 연산자 스택에 push합니다.
            opStack[++opTop] = expr[i];
            i++;  // 다음 문자로 이동
            continue;
        }

        // 위의 조건에 해당하지 않는 다른 문자는 무시하고 다음 문자로 넘어갑니다.
        i++;
    }

    // 수식 전체를 처리한 후, 연산자 스택에 남아있는 모든 연산자에 대해 계산을 수행합니다.
    while (opTop >= 0) {
        applyOperator();
    }

    // 최종 결과는 숫자 스택의 top에 남아 있으므로 이를 출력합니다.
    printf("결과: %lf\n", numStack[numTop]);

    // 프로그램을 정상 종료합니다.
    return 0;
}

 

<실행 결과>

'(Telechips) AI 시스템 반도체 SW 개발자 교육 > ATmega128A 마이크로컨트롤러 프로그래밍' 카테고리의 다른 글

7일차  (0) 2025.03.12
6일차  (0) 2025.03.11
4일차  (0) 2025.03.07
3일차  (0) 2025.03.06
2일차  (0) 2025.03.05