printf("ho_tari\n");

7일차 본문

2025.03.12

 

LED CONTROL with Bluetooth Serial Port

 

<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 init_uart1(void);
extern void UART0_transmit(uint8_t data);
extern void UART1_transmit(uint8_t data);
extern void pc_command_processing(void);
extern void bt_command_processing(void);

extern volatile uint8_t rx_buff[COMMAND_NUMBER][COMMAND_LENGTH]; // uart0로부터 들어온 문자를 저장하는 버퍼(변수)
extern volatile uint8_t rx1_buff[COMMAND_NUMBER][COMMAND_LENGTH]; // uart0로부터 들어온 문자를 저장하는 버퍼(변수)
extern volatile uint8_t rx_msg_received;
extern volatile uint8_t rx1_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();
	init_uart1();
	stdout = &OUTPUT; // printf가 동작될 수 있도록 stdout에 OUTPUT 파일 포인터 assign
	sei(); // 전역(대문)으로 interrupt 허용
	
	// led_main();
	
	// printf("init_uart0\n");
	
	while(1)
	{
		// pc_command_processing();
		bt_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;
}

 

<uart1.c>

/*
 * uart1.c
 *
 * Created: 2025-03-11 오후 4:35:37
 *  Author: microsoft
 */ 
#include "uart1.h"
#include <string.h> // strcpy, strncmp, strcmp 등

void init_uart1(void);
void UART1_transmit(uint8_t data);
void bt_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 rx1_msg_received = 0;

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

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

void init_uart1(void)
 {
	// 1. 9600bps로 설정
	UBRR1H = 0x00;
	UBRR1L = 207; // 9600bps
	// 2. 2배속 통신
	UCSR1A |= 1 << U2X1; // 2배속 통신
	UCSR1C |= 0x06; // 비동기 / data8bits / none parity
	
	// RXEN1 : UART1로부터 수신이 가능하도록
	// TXEN1 : UART1로부터 송신이 가능하도록
	// RXCIE1 : UART1로부터 1byte가 들어오면 (stop bit가 들어오면) rx interrupt를 발생시켜라
	UCSR1B |= 1 << RXEN1 | 1 << TXEN1 | 1 << RXCIE1;
}

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

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

 

<uart0.c>

/*
 * uart1.h
 *
 * Created: 2025-03-11 오후 4:35:26
 *  Author: microsoft
 */ 


#ifndef UART1_H_
#define UART1_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 rx1_buff[COMMAND_NUMBER][COMMAND_LENGTH]; // uart0로부터 들어온 문자를 저장하는 버퍼(변수)
volatile int rear1 = 0; // input index : USART1_RX_vect에서 집어 넣어주는 index
volatile int front1 = 0; // output index


#endif /* UART1_H_ */

 

 

<실행 결과>

https://youtube.com/shorts/C54kKWzfsrA

 

초음파 센서 (Ultra Sonic)

HC-SR04

 

- 사람이 들을 수 있는 가청 주파수(20kHz까지)이상의 주파수를 가진 소리를 초음파

- 초음파 센서는 수신부와 송신부로 이루어짐

- 송신부에서 초음파를 쏘아 올리고 벽 혹은 물체에 반사되어 오는 초음파를 수신부에서 인식

- 이 때 보낸 시간과 반사되어 돌아온 시간을 측정하여 거리를 계산

동작 원리

1. TRIG 핀으로 최소 10us의 펄스를 주면 TRIG소자에 초음파가 발사

2. ECHO소자로 반사파가 들어 온다. (거리에 비례)

3. 거리값 구하기

- 소리 속도 : 340 m/s

- us단위 변환 : 0.034cm / us (1us 동안 0.034cm 이동)

- 초음파가 1cm 이동 소요시간 T = 2 * 0.01 / 340 = 58.824 us (왕복 시간) 편도 소요시간: 29 us

※ 초음파 속도

- 초당 340m 이동

- 29us당 1cm 이동

 

- 초음파 이동거리 = 왕복시간 / 1cm 이동 시간 / 2 이다.

- distance = duration / 29 / 2; <=== 센치미터로 환산

 

EICRA 레지스터가 INT0 ~ INT3까지의 외부 인터럽트 발생 시점을 결정한다면

EICRB (External Interrupt Control Register B) 레지스터는 INT4 ~ INT7까지의 외부 인터럽트 발생 시점을 결정한다.

 

Ultrasonic 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"
#include "ultrasonic.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 init_ultrasonic(void);
extern void distance_ultrasonic(void);

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

volatile int msec_count = 0;
volatile int ultrasonic_check_timer = 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씩 증가
	ultrasonic_check_timer++;
}

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

 

<ultrasonic.c>

/*
 * ultrasonic.c
 *
 * Created: 2025-03-12 오후 2:49:17
 *  Author: microsoft
 */ 

#include "ultrasonic.h"

extern volatile int ultrasonic_check_timer;

void init_ultrasonic();
void trigger_ultrasonic();
void distance_ultrasonic();

volatile int ultrasonic_dis = 0;
volatile char scm[50];

// PE4 : 외부 INT4 초음파 센서의 상승, 하강 에지 둘다 INT가 ISR(INT4_vect)로 들어온다.
// 결국 2번 (상승 : 1번, 하강 : 1번) 들어온다.
ISR(INT4_vect)
{
	// 1. 상승에지
	if(ECHO_PIN & 1 << ECHO)
	{
		TCNT1 = 0;
	}
	else // 2. 하강에지
	{
		// ECHO 핀에 들어온 펄스 개수를 US로 환산
		ultrasonic_dis = 1000000.0 * TCNT1 * 1024 / F_CPU;
		// 예) TCINT에 10이 들어 있다고 가정하자
		// 15.625KHz의 1주기 64us이다.
		// 0.000064sec(64us) * 10 ==> 0.00064sec(640us)
		// 640us / 58us(1cm 이동하는데 소요시간) ==> 11 cm이다.
		// --- 1cm : 58us
		sprintf(scm, "dis: %dcm\n", ultrasonic_dis / 58); // cm 환산
	}
}

void init_ultrasonic(void)
{
	TRIG_DDR |= 1 << TRIG; // 출력 모드로 설정
	ECHO_DDR &= ~(1 << TRIG); // 입력 모드로 설정 ECHO_DDR &= 0b11110111;
	// 0 1 : 상승에지(rising edge)와 하강에지(falling edge) 둘다 INT를 띄우도록 요청
	EICRB |= 0 << ISC41 || 1 << ISC40;
	// 16bit timer1번을 설정해서 사용 65535(max) : 0xffff
	// 16MHz를 1024로 분주 16000000Hz/1024 --> 15625Hz --> 15.625KHz
	// 1주기 T(주기) = 1/f = 1/15625 ==> 0.000064sec ==> 64us
	TCCR1B |= 1 << CS12 | 1 << CS10; // 1024로 분주
	EIMSK |= 1 << INT4; // EXTERNAL INT4 (ECHO 핀)
}

void trigger_ultrasonic(void)
{
	TRIG_PORT &= ~(1 << TRIG); // low
	_delay_us(1);
	TRIG_PORT |= 1 << TRIG; // high
	_delay_us(15); // 규격에는 10us인데 여유를 둬서 15us로 설정
	TRIG_PORT &= ~(1 << TRIG); // low
	
}

void distance_ultrasonic(void)
{
	if(ultrasonic_check_timer >= 1000) // 1초 체크
	{
		ultrasonic_check_timer = 0;
		printf("%s", scm);
		trigger_ultrasonic();
	}
}

 

<ultrasonic.h>

/*
 * ultrasonic.h
 *
 * Created: 2025-03-12 오후 2:49:03
 *  Author: microsoft
 */ 


#ifndef ULTRASONIC_H_
#define ULTRASONIC_H_

#define  F_CPU 16000000UL  // 16MHZ
#include <avr/io.h>
#include <util/delay.h>  // _delay_ms _delay_us
#include <avr/interrupt.h> // sei()

#define TRIG 4
#define TRIG_PORT PORTG
#define TRIG_DDR DDRG

#define ECHO 4
#define ECHO_PIN PINE // External INT 4
#define ECHO_DDR DDRE

#endif /* ULTRASONIC_H_ */

 

<실행 결과>

https://youtu.be/DoK49W4usq4

 

Ultrasonic with UART LED, FND Control

 

<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"
#include "ultrasonic.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 init_ultrasonic(void);
extern void distance_ultrasonic(void);
extern void sonic_led(void);
extern void sonic_fnd(void);

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

volatile int msec_count = 0;
volatile int ultrasonic_check_timer = 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씩 증가
	ultrasonic_check_timer++;
}

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

 

<ultrasonic.c>

/*
 * ultrasonic.c
 *
 * Created: 2025-03-12 오후 2:49:17
 *  Author: microsoft
 */ 

#include "ultrasonic.h"
#include "fnd.h"

extern volatile int ultrasonic_check_timer;

void init_ultrasonic();
void trigger_ultrasonic();
void distance_ultrasonic();
void sonic_led(void);
void sonic_fnd(void);

volatile int ultrasonic_dis = 0;
volatile int ultrasonic_cm = 0;
volatile char scm[50];

// PE4 : 외부 INT4 초음파 센서의 상승, 하강 에지 둘다 INT가 ISR(INT4_vect)로 들어온다.
// 결국 2번 (상승 : 1번, 하강 : 1번) 들어온다.
ISR(INT4_vect)
{
	// 1. 상승에지
	if(ECHO_PIN & 1 << ECHO)
	{
		TCNT1 = 0;
	}
	else // 2. 하강에지
	{
		// ECHO 핀에 들어온 펄스 개수를 US로 환산
		ultrasonic_dis = 1000000.0 * TCNT1 * 1024 / F_CPU;
		// 예) TCINT에 10이 들어 있다고 가정하자
		// 15.625KHz의 1주기 64us이다.
		// 0.000064sec(64us) * 10 ==> 0.00064sec(640us)
		// 640us / 58us(1cm 이동하는데 소요시간) ==> 11 cm이다.
		// --- 1cm : 58us
		ultrasonic_cm = ultrasonic_dis / 58;
		sprintf(scm, "dis: %dcm\n", ultrasonic_cm); // cm 환산
	}
}

void init_ultrasonic(void)
{
	TRIG_DDR |= 1 << TRIG; // 출력 모드로 설정
	ECHO_DDR &= ~(1 << TRIG); // 입력 모드로 설정 ECHO_DDR &= 0b11110111;
	// 0 1 : 상승에지(rising edge)와 하강에지(falling edge) 둘다 INT를 띄우도록 요청
	EICRB |= 0 << ISC41 || 1 << ISC40;
	// 16bit timer1번을 설정해서 사용 65535(max) : 0xffff
	// 16MHz를 1024로 분주 16000000Hz/1024 --> 15625Hz --> 15.625KHz
	// 1주기 T(주기) = 1/f = 1/15625 ==> 0.000064sec ==> 64us
	TCCR1B |= 1 << CS12 | 1 << CS10; // 1024로 분주
	EIMSK |= 1 << INT4; // EXTERNAL INT4 (ECHO 핀)
}

void trigger_ultrasonic(void)
{
	TRIG_PORT &= ~(1 << TRIG); // low
	_delay_us(1);
	TRIG_PORT |= 1 << TRIG; // high
	_delay_us(15); // 규격에는 10us인데 여유를 둬서 15us로 설정
	TRIG_PORT &= ~(1 << TRIG); // low
	
}

void distance_ultrasonic(void)
{
	
	if(ultrasonic_check_timer >= 1000) // 1초 체크
	{
		ultrasonic_check_timer = 0;
		printf("%s", scm);
			
		trigger_ultrasonic();
	}
}

void sonic_led(void)
{
	DDRA = 0xff;
	PORTA = 0x00;
	 
	if(ultrasonic_cm >= 1 && ultrasonic_cm < 3)
		PORTA = 0x01;
	if(ultrasonic_cm >= 3 && ultrasonic_cm < 6)
		PORTA = 0x03;
	if(ultrasonic_cm >= 6 && ultrasonic_cm < 9)
		PORTA = 0x07;
	if(ultrasonic_cm >= 9 && ultrasonic_cm < 12)
		PORTA = 0x0f;
	if(ultrasonic_cm >= 12 && ultrasonic_cm < 14)
		PORTA = 0x1f;
	if(ultrasonic_cm >= 14 && ultrasonic_cm < 16)
		PORTA = 0x3f;
	if(ultrasonic_cm >= 16 && ultrasonic_cm < 18)
		PORTA = 0x7f;
	if(ultrasonic_cm >= 18)
		PORTA = 0xff;
}

void sonic_fnd(void)
{
	uint8_t fnd_font[] = {~0xc0,~0xf9,~0xa4,~0xb0,~0x99,~0x92,~0x82,~0xd8,~0x80,~0x90,~0x7f};
		
	int fnd_number=ultrasonic_cm;
	
	int f0=fnd_number/1000; //천의 자리 숫자 0~9;
	int f1=(fnd_number/100) % 10; //백의 자리
	int f2=(fnd_number/10) % 10; //십의 자리
	int f3=(fnd_number) % 10; //일의자리
	
	static int digit_select=0;
	FND_DATA_PORT=0x00;
	
	switch(digit_select)
	{
		case 0: // FND 첫번째칸
			FND_DIGIT_PORT=~0x10;
			FND_DATA_PORT = fnd_font[f0];
			break;
		case 1: // FND 두번째칸
			FND_DIGIT_PORT=~0x20;
			FND_DATA_PORT = fnd_font[f1];
			break;
		case 2: // FND 세번째칸
			FND_DIGIT_PORT=~0x40;
			FND_DATA_PORT =fnd_font[f2];
			break;
		case 3: // FND 네번째칸
			FND_DIGIT_PORT=~0x80;
			FND_DATA_PORT=fnd_font[f3];
			break;
	}
	digit_select ++;
	digit_select %=4;
}

 

<ultrasonic.h>

/*
 * ultrasonic.h
 *
 * Created: 2025-03-12 오후 2:49:03
 *  Author: microsoft
 */ 


#ifndef ULTRASONIC_H_
#define ULTRASONIC_H_

#define  F_CPU 16000000UL  // 16MHZ
#include <avr/io.h>
#include <util/delay.h>  // _delay_ms _delay_us
#include <avr/interrupt.h> // sei()

#define TRIG 4
#define TRIG_PORT PORTG
#define TRIG_DDR DDRG

#define ECHO 4
#define ECHO_PIN PINE // External INT 4
#define ECHO_DDR DDRE

#endif /* ULTRASONIC_H_ */

 

<실행 결과>

https://youtu.be/Xhp1W9BOuLk

 

회로도

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

9일차  (0) 2025.03.16
8일차  (0) 2025.03.13
6일차  (0) 2025.03.11
5일차  (0) 2025.03.10
4일차  (0) 2025.03.07