본문 바로가기
# Semiconductor/[Semicon Academy]

[Harman 세미콘 아카데미] 39일차 - ATmega128(Ultrasonic, ADC)

by Graffitio 2023. 8. 21.
[Harman 세미콘 아카데미] 39일차 - ATmega128(Ultrasonic, ADC)
728x90
반응형
[Ultrasonic]

 

Ultra Sonic(HC-SR04)

 

HC-SR04
2~400cm, 15º의 각도까지 측정 가능하다.

 

위 센서는 초음파를 이용해 물체와의 거리를 측정할 수 있으며,

그 원리는 초음파를 발사하고 장애물과 부딪힌 뒤 반사되어 돌아오는 시간차와 음파의 속력을 측정해 거리를 계산한다.

 

 

<Timing Diagram 해석>

  ① Module의 Trigger(수신부)에 10us High puler 인가

  ② 40KHz의 8개 Ultrasonic Burst 발생(자체 발진)

  ③ Echo(송신부)는 초음파 발신 직후 High가 되고, 반항을 감지하면 Low가 된다.

       - Echo pulse : 초음파가 장애물을 만나 다시 Echo로 돌아올 때까지의 왕복 시간

  ④ 거리 = Echo High pulse time(왕복시간) × 음파의 속도(340m/s) / 2

       - 왕복이므로 2를 나누어 준다.

 

거리 측정에 관한 공식은 위와 같다.

 

 

 


 

Data Sheet

HCSR04 DATASHEET.pdf
2.26MB

 


 

Example #1 : Ultrasonic_Basic

 

초음파 센서를 작동시키는 코드를 작성하고,

센서와 물체 사이의 거리를 측정하여 출력하라.

 

<Header file>

UART_2.h

더보기
/*
 * UART_2.h
 *
 * Created: 2023-07-26 오전 10:33:19
 *  Author: USER
 */ 


#ifndef UART_2_H_
#define UART_2_H_


#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>



void UART0_Init();
void UART0_Transmit(char data);
unsigned UART0_Receive(void);


#endif /* UART_2_H_ */

 

<Source file>

UART_2.c

더보기
/*
 * UART_2.c
 *
 * Created: 2023-07-26 오전 10:33:10
 *  Author: USER
 */ 

#include "UART_2.h"


void UART0_Init() // UART 통신을 쓰려면, 이거 세팅해줘야 한다.
// PE0, PE1을 쓸 것이다.
// 프로그램 다운로드하는 것과 맞물려 있다.
// UART 통신을 사용하려면, 스위치를 오른쪽으로 토글해줘야 한다. 꼭!
// 1. 프로그램 다운로드 해 -> 해당 창 close
// 2. 스위치 오른쪽으로 토글
// 3. compo 어쩌고 파일 열고
// 4. 프로그램 수정하려면, compo 어쩌고 파일 close하고 스위치 왼쪽으로 토글하고 1번
{
	UBRR0H = 0x00;
	UBRR0L = 0xcf; // 9600bps, 2배속모드를 쓸 것이다.
	
	UCSR0A = (1<<U2X0); // 2배속 모드 셋
	
	// 비동기 모드, 8bit 데이터, 패리티비트 없음, 1비트 stop bit
	//UCSR0C |= (0<<UMSEL0); // 비동기 모드
	//UCSR0C |= (0<<UPM01) | (0<<UPM00); // 패리티 비트 없음
	//UCSR0C |= (0<<USBS0); // 1비트 stop mode
	
	UCSR0B |= (1<<RXEN0); // 수신 가능(RX PIN 허용)
	UCSR0B |= (1<<TXEN0); // 송신 가능(TX PIN 허용)
	
	UCSR0B |= (1<<RXCIE0); // 수신 interrupt Enable
}


void UART0_Transmit(char data)
{
	while (!(UCSR0A & (1<<UDRE0))); // 송신 가능 대기, UDR(송신버퍼)이 비어있는지 체크
	// 비어있으면 UDRE0가 1로 세팅됨.
	// while 문 거짓되면, 빠져 나가서 다음 라인으로
	UDR0 = data; // 데이터 버퍼 레지스터에 데이터 전송
	
	
}


unsigned UART0_Receive(void) // 리턴해주는 값은 없고, 받기만 하므로
{
	while (!(UCSR0A & (1<<RXC0))); // 데이터 수신 대기 // 수신
	return UDR0; // URD0에 있는 데이터를 리턴만 해주면 된다.
}

 

<main.c>

/*
 * UltraSonic_basic.c
 *
 * Created: 2023-08-21 오전 11:03:56
 * Author : USER
 */ 

#define F_CPU 16000000UL
#include "UART_2.h"

// 출력 스트림 설정 <stdio.h>에 있는 표준 입출력
// 일종의 버퍼
FILE OUTPUT = FDEV_SETUP_STREAM(UART0_Transmit, NULL, _FDEV_SETUP_WRITE);

#define PRESCALER	1024

// 16 bit 타이머 1번, 분주비는 1024
void Timer_init()
{
	TCCR1B |= (1<<CS12) | (1<<CS10);
}


uint8_t measure_distance()
{
	// 트리거 핀으로 펄스 출력(10us TTL)
	PORTB &= ~(1<<PORTB1); // Low 시작
	_delay_us(1);
	PORTB |= (1<<PORTB1); // High 출력
	_delay_us(10);
	PORTB &= ~(1<<PORTB1); // Low 출력
	
	// Echo pin이 High가 될 때까지 대기
	TCNT1 = 0;
	while (!(PINB & 0x01)) // 0번 핀
	if (TCNT1 > 65000) return 0; // 장애물이 없는 경우(얘가 잴 수 있는 건 2~400cm)
	
	// Echo pin이 Low가 될 때까지 시간 측정
	TCNT1 = 0;
	while(PINB & 0x01)
	{
		if (TCNT1 > 65000)
		{
			TCNT1 = 0;
			break;
		}
	}
	
	// Echo Pin의 펄스폭을 마이크로초로 계산
	double pulse_width = 1000000.0 * TCNT1 * PRESCALER / F_CPU;
	
	// 계산된 펄스의 폭을 cm로 변환한 뒤 반환
	return pulse_width / 58;
}


int main(void)
{
	uint8_t distance;
	
	stdout = &OUTPUT;
	UART0_Init();
	
	DDRB |= 0x02; // 트리거핀 출력 설정()
	DDRB &= 0xFE; // 에코 핀 입력 설정(PORTD PIN0)
	
	//DDRD |= (1<<PIND5); // 트리거핀 출력 설정()
	//DDRD &= ~(1<<PIND4); // 에코 핀 입력 설정(PORTD PIN0)
	
	Timer_init();
	
	while (1)
	{

		printf("Distance : %d cm\r\n", distance);
		_delay_ms(1000);
	}
}

 


 

Example #2 : Ultrasonic_LCD

 

초음파 센서를 작동시키는 코드를 작성하고,

센서와 물체 사이의 거리를 측정하여 I2C통신으로 LCD에 출력하라.

 

<Header file>

UART_2.h

더보기
/*
 * UART_2.h
 *
 * Created: 2023-07-26 오전 10:33:19
 *  Author: USER
 */ 


#ifndef UART_2_H_
#define UART_2_H_


#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>



void UART0_Init();
void UART0_Transmit(char data);
unsigned UART0_Receive(void);


#endif /* UART_2_H_ */

I2C.h

더보기

 

/*
 * I2C.h
 *
 * Created: 2023-07-25 오후 2:29:25
 *  Author: USER
 */ 


#ifndef I2C_H_
#define I2C_H_

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>

#define I2C_DDR	DDRD
#define I2C_SCL	PORTD0 // clock은 PORTD0
#define I2C_SDA	PORTD1 // 


void I2C_Init();
void I2C_Start();
void I2C_Stop();
void I2C_TxData(uint8_t data);
void I2C_TxByte(uint8_t devAddrRW, uint8_t data);


#endif /* I2C_H_ */

I2C_LCD.h

더보기
/*
 * I2C_LCD.h
 *
 * Created: 2023-07-25 오후 3:06:22
 *  Author: USER
 */ 


#ifndef I2C_LCD_H_
#define I2C_LCD_H_

//#define F_CPU 16000000UL
//#include <avr/io.h>
//#include <util/delay.h>
#include "I2C.h"

#define LCD_RS	0 // Register Select
// 제어 레지스터 : 0 , 데이터 레지스터 : 1  
#define LCD_RW	1 // Read/Write 설정하는 레지스터 bit / Read : 1, Write : 0
#define LCD_E	2 // 여기서 2는 LCD board상 2번 핀(E핀)을 뜻한다.
#define LCD_BACKLIGHT	3

#define LCD_DEV_ADDR	(0x27<<1) // I2C LCD 주소 0x27, <<1 : Write 모드 유지

#define COMMAND_DISPLAY_CLEAR	0x01
#define COMMAND_DISPLAY_ON		0x0c
#define COMMAND_DISPLAY_OFF		0x08
#define COMMAND_4_BIT_MODE		0x28
#define COMMAND_ENTRY_MODE		0x06

// 함수 원형 선언 //
void LCD_Data4bit(uint8_t data);
void LCD_EnablePin();
void LCD_WriteCommand(uint8_t commandData);
void LCD_WriteData(uint8_t charData);
void LCD_BackLight();
void LCD_gotoXY(uint8_t row, uint8_t col);
void LCD_WriteString(char *string);
void LCD_WriteStringXY(uint8_t row, uint8_t col, char *string);
void LCD_Init();



#endif /* I2C.LCD_H_ */

 

<Source file>

UART_2.c

더보기
/*
 * UART_2.c
 *
 * Created: 2023-07-26 오전 10:33:10
 *  Author: USER
 */ 

#include "UART_2.h"


void UART0_Init() // UART 통신을 쓰려면, 이거 세팅해줘야 한다.
// PE0, PE1을 쓸 것이다.
// 프로그램 다운로드하는 것과 맞물려 있다.
// UART 통신을 사용하려면, 스위치를 오른쪽으로 토글해줘야 한다. 꼭!
// 1. 프로그램 다운로드 해 -> 해당 창 close
// 2. 스위치 오른쪽으로 토글
// 3. compo 어쩌고 파일 열고
// 4. 프로그램 수정하려면, compo 어쩌고 파일 close하고 스위치 왼쪽으로 토글하고 1번
{
	UBRR0H = 0x00;
	UBRR0L = 0xcf; // 9600bps, 2배속모드를 쓸 것이다.
	
	UCSR0A = (1<<U2X0); // 2배속 모드 셋
	
	// 비동기 모드, 8bit 데이터, 패리티비트 없음, 1비트 stop bit
	//UCSR0C |= (0<<UMSEL0); // 비동기 모드
	//UCSR0C |= (0<<UPM01) | (0<<UPM00); // 패리티 비트 없음
	//UCSR0C |= (0<<USBS0); // 1비트 stop mode
	
	UCSR0B |= (1<<RXEN0); // 수신 가능(RX PIN 허용)
	UCSR0B |= (1<<TXEN0); // 송신 가능(TX PIN 허용)
	
	UCSR0B |= (1<<RXCIE0); // 수신 interrupt Enable
}


void UART0_Transmit(char data)
{
	while (!(UCSR0A & (1<<UDRE0))); // 송신 가능 대기, UDR(송신버퍼)이 비어있는지 체크
	// 비어있으면 UDRE0가 1로 세팅됨.
	// while 문 거짓되면, 빠져 나가서 다음 라인으로
	UDR0 = data; // 데이터 버퍼 레지스터에 데이터 전송
	
	
}


unsigned UART0_Receive(void) // 리턴해주는 값은 없고, 받기만 하므로
{
	while (!(UCSR0A & (1<<RXC0))); // 데이터 수신 대기 // 수신
	return UDR0; // URD0에 있는 데이터를 리턴만 해주면 된다.
}

I2C.c

더보기

 

/*
 * I2C_LCD.c
 *
 * Created: 2023-07-25 오후 2:29:12
 *  Author: USER
 */ 
#include "I2C.h"


void I2C_Init()
{
	I2C_DDR |= (1<<I2C_SCL) | (1<<I2C_SDA); // 출력 설정
	TWBR = 72; // 100KHz
	// TWBR = 32; // 200KHz
	// TWBR = 12; // 400KHz
}

void I2C_Start()
{
	TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN); // 1을 보내는 것은, S/W적으로 플래그를 Clear해주는 것이다.
	                                            // 1 Set하여 인터럽트 발생시키는 것이 아님.
	while(!(TWCR & (1<<TWINT))); // 하드웨어적으로 TWINT 시점을 결정
	                             // 기다렸다가 조건이 만족되면 while문 수행
}

void I2C_Stop()
{
	TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO); // Stop 비트 설정
}

void I2C_TxData(uint8_t data) // data 1바이트 전송
{
	TWDR = data;
	TWCR = (1<<TWINT) | (1<<TWEN);
	while (!(TWCR & (1<<TWINT))); // 전송 완료 대기
}

void I2C_TxByte(uint8_t devAddrRW, uint8_t data)
{
	I2C_Start();
	I2C_TxData(devAddrRW); // 읽을 것이냐 쓸 것이냐
	I2C_TxData(data); // 입/출력할 데이터 보냄
	I2C_Stop();
}

I2C_LCD.c

더보기
/*
 * I2C.c
 *
 * Created: 2023-07-25 오후 3:06:06
 *  Author: USER
 */ 

#include "I2C_LCD.h"

uint8_t I2C_LCD_Data;

void LCD_Data4bit(uint8_t data)
{
	I2C_LCD_Data = (I2C_LCD_Data & 0x0f) | (data & 0xf0); // 상위 4bit 출력
	// 이전 상위 비트는 다 날라감 | data의 상위 비트는 살림 
	LCD_EnablePin();
	I2C_LCD_Data = (I2C_LCD_Data & 0x0f) | ((data & 0x0f)<<4); // 하위 4bit
	LCD_EnablePin();
	// 상위 4bit 받고 -> Enable해서 출력하고 -> 하위 4bit 받고 상위로 시프트하고 -> Enabla해서 출력하고
	// [7:4] 핀만 사용해서 반반 나눠서 출력하면, 총 8bit를 출력할 수 있다.
}

void LCD_EnablePin()
{
	I2C_LCD_Data &= ~(1<<LCD_E); // E low 설정
	I2C_TxByte(LCD_DEV_ADDR, I2C_LCD_Data);
	
	I2C_LCD_Data |= (1<<LCD_E); // High 설정
	I2C_TxByte(LCD_DEV_ADDR, I2C_LCD_Data);
	
	I2C_LCD_Data &= ~(1<<LCD_E); // E low 설정
	I2C_TxByte(LCD_DEV_ADDR, I2C_LCD_Data);
	
	// 0->1 일때, data 출력 -> 다음 출력을 위해 0으로 다시 설정
	
	_delay_us(1600);
}

void LCD_WriteCommand(uint8_t commandData)
{
	I2C_LCD_Data &= ~(1<<LCD_RS); // Command일 때는 control register(TWCR)을 쓰고
	I2C_LCD_Data &= ~(1<<LCD_RW); // Write 모드
	LCD_Data4bit(commandData);
}

void LCD_WriteData(uint8_t charData)
{
	I2C_LCD_Data |= (1<<LCD_RS); // Data받을 때는 Data Register(TWDR) 쓰고
	I2C_LCD_Data &= ~(1<<LCD_RW); // Write 모드
	LCD_Data4bit(charData);
}


void LCD_BackLight()
{
	I2C_LCD_Data |= (1<<LCD_BACKLIGHT);
	I2C_TxByte(LCD_DEV_ADDR, I2C_LCD_Data);
}

void LCD_gotoXY(uint8_t row, uint8_t col) // LCD 화면의 커서를 지정된 행(row)과 열(col) 위치로 이동시키는 함수
{
	col %= 16; // 0~15 사이의 값 지정 가능
	row %= 2; // 0~1 사이의 값 지정 가능
	uint8_t address = (0x40 * row) + col; // 주어진 row와 col 값을 이용하여 LCD 화면의 주소(address, 커서 위치)룰 계산한다.
	// 첫 번째 행(row 0)의 주소 범위는 0x00 ~ 0x0f(0~15)
	// 두 번째 행(row 1)의 주소 범위는 0x40 ~ 0x4f(64~79)
	// 예시 : row 1, col 3 -> address = 0x43
	uint8_t command = 0x80 + address; // 계산된 주소를 이용하여 이동시키는 명령어 command 생성
	// 0x80을 사용하는 이유는 특정 주소값이 아닌, 첫 번째 행의 시작을 나타내는 상징적인 값으로 사용된다,
	// 이렇게 함으로써 코드의 가독성을 높이고, 행과 열 값을 쉽게 결합하여 원하는 주소 값을 계산할 수 있다.
	LCD_WriteCommand(command);
}

void LCD_WriteString(char *string)
{
	for (uint8_t i = 0 ; string[i] ; i++) // 받은 문자열의 포인터가 보는 곳을 한 비트씩 분해해서 LCD_WriteData 함수에 하나씩 뿌려줌
	{
		LCD_WriteData(string[i]);
	}
}

void LCD_WriteStringXY(uint8_t row, uint8_t col, char *string)
{
	LCD_gotoXY(row,col); // 행과 열의 위치를 받음. -> 어디서부터 쓸 것인지를 좌표를 받음
	LCD_WriteString(string); // 문자열 입력을 받음
}

void LCD_Init()
{
	I2C_Init();
	
	_delay_ms(20);
	LCD_WriteCommand(0x03);
	_delay_ms(10);
	LCD_WriteCommand(0x03);
	_delay_ms(1);
	LCD_WriteCommand(0x03);
	
	LCD_WriteCommand(0x02);
	LCD_WriteCommand(COMMAND_4_BIT_MODE);
	LCD_WriteCommand(COMMAND_DISPLAY_OFF);
	LCD_WriteCommand(COMMAND_DISPLAY_CLEAR);
	LCD_WriteCommand(COMMAND_ENTRY_MODE);
	LCD_WriteCommand(COMMAND_DISPLAY_ON);
	LCD_BackLight();
}

 

<main.c>

/*
 * UltraSonic_LCD
 *
 * Created: 2023-08-21 오전 9:20:26
 * Author : USER
 */ 

// 클럭 주파수 설정(16MHz)
#define F_CPU 16000000UL
#include "UART_2.h"

// 출력 스트림 설정(stdio.h에 있는 표준 입출력)
// 이 스트림은 출력용 버퍼 역할을 한다.
FILE OUTPUT = FDEV_SETUP_STREAM(UART0_Transmit, NULL, _FDEV_SETUP_WRITE);

#define PRESCALER	1024 // 타이머 분주비 설정

// 16 bit Timer1 초기화 함수
void Timer_init()
{
	// Timer1의 분주비 설정(1024)
	TCCR1B |= (1<<CS12) | (1<<CS10);
}

// 거리 측정 함수
uint8_t measure_distance()
{
	// 트리거 핀으로 펄스 출력(10us TTL)
	PORTB &= ~(1<<PORTB1); // Low 시작
	_delay_us(1);
	PORTB |= (1<<PORTB1); // High 출력
	_delay_us(10);
	PORTB &= ~(1<<PORTB1); // Low 출력
	
	// Echo pin이 High가 될 때까지 대기
	TCNT1 = 0;
	while (!(PINB & (1<<PINB0))) // 0번 핀 
	if (TCNT1 > 65000) return 0; // 장애물이 없는 경우(얘가 잴 수 있는 건 2~400cm)
	
	// Echo pin이 Low가 될 때까지 시간 측정
	TCNT1 = 0;
	while(PINB & 1<<PINB0)
	{
		if (TCNT1 > 65000)
		{
			TCNT1 = 0;
			break;
		}
	}
	
	// Echo Pin의 펄스폭을 마이크로초로 계산
	double pulse_width = 1000000.0 * TCNT1 * PRESCALER / F_CPU;
	
	// 계산된 펄스의 폭을 cm로 변환한 뒤 반환
	return pulse_width / 58;
}


int main(void)
{
    uint8_t distance;
	char buff[30];
	
	// 표준 출력을 UART로 설정
	stdout = &OUTPUT;
	UART0_Init();
	
	DDRB |= (1<<PORTB1); // 트리거 핀을 출력으로 설정 (PORTB1)
	DDRB &= ~(1<<PORTB0); // 에코 핀을 입력으로 설정 (PORTB0)
	
	Timer_init();
	LCD_Init();
	LCD_WriteStringXY(0, 0, "UltraSonic Test");
	
    while (1) 
    {
		distance = measure_distance(); // 거리 측정
		// 측정된 거리를 문자열로 변환하여 버퍼에 저장
		sprintf(buff, "Distance : %-3dcm\r\n", distance);
		// LCD에 버퍼 내용 출력
		LCD_WriteStringXY(1, 0, buff);
		_delay_ms(1000);
    }
}

 


 

[ADC]

 

개요

 

ADC란?

아날로그 신호를 디지털 신호로 변환시켜주는 장치

ATmega128에서 측정 범위는 기본적으로 프로세서의 동작 전압을 기준으로 하고(ADMUX Register로 설정)

프로세서의 레퍼런스 전압이 5V이면, 0~5V 신호 범위가 되며 이 범위를 10 bit의 범위로 인식하게 된다.

 

ADC mode

Single Conversion Mode(단일 변환 모드)

    : 한 번씩 변환하는 모드

     ▶ADSCRA 레지스터의 ADSC=1(ADC Start Conversion)로 설정함으로써 시작되고

        변환이 끝나면 0이 되고 인터럽트 발생

     ▶ AD 변환 중 입력 채널이 바뀌었다면, ADC는 현재의 변환을 마치고 새로 선택된 채널로 변경된다.

     ▶ 장점은 한 번씩 변환하니까 연속 변환모드에 비해 소비전력이 적다.

Free running mode(연속 변환 모드, 자유 동작 모드)

     : 연속해서 변환하는 모드

     ▶ ADSCRA 레지스터의 ADFR = 1로 설정함으로써 ADC는 지속적으로 샘플링과 변환을 수행하여

         ADC 데이터 레지스터를 갱신하게 된다.

     ▶ 장점은 계속 변환하므로 입력값에 대해 신뢰성이 단일 변환모드보다 높다.

 


 

ADC Register

 

ADC Block diagram

1. ADMUX(ADC Multiplexer Selection Register)

 

    Bit 7, 6 - REFS1, REFS0(Reference Selection Bit)

      - ADC에서 사용하는 전압을 선택하는 비트

      - AREF(0 0) : 5V 이하

      - AVCC(0 1) : 5V

      - 내부 2.56V 사용(1 1)

      - 예를 들어 기준 전압이 5V였다면, 1023이 5V이고,

         2.5V가 인가되면 1023의 절반이 ADC값으로 출력된다.

         5/1023 ≒ 0.00489 = 4.89mV(1당 4.89mV씩 차이가 난다.)

  

    Bit 5 - ADLAR(ADC Left ADjust Result)

       - 변환 결과가 ADC 레지스터에 저장될 때 정렬 방식을 정하는 데 사용된다.

       - 1 : 변환 결과를 ADCH/L에 저장할 때 좌측으로 끝을 맞추어 저장

       - 0 : 변환 결과를 ADCH/L에 저장할 때 우측으로 끝을 맞추어 저장

 

    Bit 4,3,2,1,0 - MUX4 ~ MUX0 (Analog Channel and Gain Selection Bit)

      - 입력 채널 선택 비트

         ADC 변환기의 아날로그 입력 채널 및 gain을 결정한다.

      - 단극성 입력 : 하나의 아날로그 입력을 의미

      - 차동 입력 : 두 아날로그의 차이를 변환

 

2. ADCSRA(ADC Control and Status Register A)

 

    Bit 7 - ADEN(ADC Enable)

      - 1 : ADC 활성화

      - 0 : ADC 비활성화

 

    Bit 6 - ADSC(ADC Start Conversion)

      - 1 : ADC 변환이 시작된다.(단일 변환모드일 때 1번만 작동, 프리러닝 모드일 때 변환 동작 반복)

      - 0 : ADC 변환이 시작되지 않는다.

 

    Bit 5 - ADFR(ADC Free Running Select)

      - 1 : 연속 변환 모드

      - 0 : 단일 변환 모드

 

    Bit 4 - ADIF(ADC Interrupt Flag)

      - ADC 변환이 되면 1로 set되고, 변환 완료 인터럽트를 요청한다.

        이때 SREG의 I비트가 1이고 ADIE 비트가 1이면, 인터럽트가 발생되어 처리된다.

      - 인터럽트가 처리되면 ADIF가 0으로 클리어된다. 물론 1을 넣어줘도 클리어 됨.

 

    Bit 3 - ADIE(ADC Interrupt Enable)

      - 1 : ADC Interrupt enable(이때 SREG 레지스터의 I비트가 set되어 있어야 한다.)

      - 0 : ADC Interrupt DIsable

 

    Bit 2, 1, 0 - ADPS(ADC prescaler Select Bit)

      - ADC에 인가되는 Clock의 분주비 설정

 

3. ADCH/L (ADC Data Register)

 

 

    ▶ ADC 변환이 되었을 때, ADCH/ADCL 두 레지스터에 값이 저장된다.

    ▶ ADMUX 레지스터의 ADLAR에 따라 변환 결과를 저장하는 방식이 다르다

    ▶ ADLAR = 0 일때, ADCL은 0~255, ADCH는 0~768이며 총 1024개로 0~1023을 표현할 수 있다.

 


 

Example #1 : CDS 출력

 

풀업저항 연결하듯이 연결하면 된다.


<Header file>

UART_2.h

더보기
/*
 * UART_2.h
 *
 * Created: 2023-07-26 오전 10:33:19
 *  Author: USER
 */ 


#ifndef UART_2_H_
#define UART_2_H_


#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>



void UART0_Init();
void UART0_Transmit(char data);
unsigned UART0_Receive(void);


#endif /* UART_2_H_ */


<Source file>

UART_2.c

더보기
/*
 * UART_2.c
 *
 * Created: 2023-07-26 오전 10:33:10
 *  Author: USER
 */ 

#include "UART_2.h"


void UART0_Init() // UART 통신을 쓰려면, 이거 세팅해줘야 한다.
// PE0, PE1을 쓸 것이다.
// 프로그램 다운로드하는 것과 맞물려 있다.
// UART 통신을 사용하려면, 스위치를 오른쪽으로 토글해줘야 한다. 꼭!
// 1. 프로그램 다운로드 해 -> 해당 창 close
// 2. 스위치 오른쪽으로 토글
// 3. compo 어쩌고 파일 열고
// 4. 프로그램 수정하려면, compo 어쩌고 파일 close하고 스위치 왼쪽으로 토글하고 1번
{
	UBRR0H = 0x00;
	UBRR0L = 0xcf; // 9600bps, 2배속모드를 쓸 것이다.
	
	UCSR0A = (1<<U2X0); // 2배속 모드 셋
	
	// 비동기 모드, 8bit 데이터, 패리티비트 없음, 1비트 stop bit
	//UCSR0C |= (0<<UMSEL0); // 비동기 모드
	//UCSR0C |= (0<<UPM01) | (0<<UPM00); // 패리티 비트 없음
	//UCSR0C |= (0<<USBS0); // 1비트 stop mode
	
	UCSR0B |= (1<<RXEN0); // 수신 가능(RX PIN 허용)
	UCSR0B |= (1<<TXEN0); // 송신 가능(TX PIN 허용)
	
	UCSR0B |= (1<<RXCIE0); // 수신 interrupt Enable
}


void UART0_Transmit(char data)
{
	while (!(UCSR0A & (1<<UDRE0))); // 송신 가능 대기, UDR(송신버퍼)이 비어있는지 체크
	// 비어있으면 UDRE0가 1로 세팅됨.
	// while 문 거짓되면, 빠져 나가서 다음 라인으로
	UDR0 = data; // 데이터 버퍼 레지스터에 데이터 전송
	
	
}


unsigned UART0_Receive(void) // 리턴해주는 값은 없고, 받기만 하므로
{
	while (!(UCSR0A & (1<<RXC0))); // 데이터 수신 대기 // 수신
	return UDR0; // URD0에 있는 데이터를 리턴만 해주면 된다.
}

 

<main.c>

/*
 * ADC_CDS.c
 *
 * Created: 2023-08-21 오후 2:33:13
 * Author : USER
 */ 

#define F_CPU 16000000UL
#include "UART_2.h"

FILE OUTPUT = FDEV_SETUP_STREAM(UART0_Transmit, NULL, _FDEV_SETUP_WRITE);

void ADC_Init()
{
	ADMUX |= (1<<REFS0); // AVCC 기준 전압 설정
	ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // 분주비 : 128
	// ADCSRA |= 0x07; // 이렇게 써줘도 됨
	ADCSRA |= (1<<ADEN); // ADC 활성화
	ADCSRA &= ~(1<<ADFR); // 단일 running mode	
}

int read_ADC(uint8_t channel)
{
	ADMUX = ((ADMUX & 0xE0) | channel); // 단일 입력 채널 선택
	ADCSRA |= (1<<ADSC); // 변환 시작
	while (!(ADCSRA & (1<<ADIF))); // 변환 종료 대기
	
	return ADC; // 10 bit값 변환
}


int main(void)
{
    int read;
	
	stdout = &OUTPUT;
	
	UART0_Init();
	ADC_Init();
	
    while (1) 
    {
		read = read_ADC(0);
		printf("channel 0 : %d\r\n", read);
		_delay_ms(1000);
    }
}

 


 

Example #2 : CDS, 가변저항 출력

 

<Header file>

UART_2.h

더보기
/*
 * UART_2.h
 *
 * Created: 2023-07-26 오전 10:33:19
 *  Author: USER
 */ 


#ifndef UART_2_H_
#define UART_2_H_


#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>



void UART0_Init();
void UART0_Transmit(char data);
unsigned UART0_Receive(void);


#endif /* UART_2_H_ */


<Source file>

UART_2.c

더보기
/*
 * UART_2.c
 *
 * Created: 2023-07-26 오전 10:33:10
 *  Author: USER
 */ 

#include "UART_2.h"


void UART0_Init() // UART 통신을 쓰려면, 이거 세팅해줘야 한다.
// PE0, PE1을 쓸 것이다.
// 프로그램 다운로드하는 것과 맞물려 있다.
// UART 통신을 사용하려면, 스위치를 오른쪽으로 토글해줘야 한다. 꼭!
// 1. 프로그램 다운로드 해 -> 해당 창 close
// 2. 스위치 오른쪽으로 토글
// 3. compo 어쩌고 파일 열고
// 4. 프로그램 수정하려면, compo 어쩌고 파일 close하고 스위치 왼쪽으로 토글하고 1번
{
	UBRR0H = 0x00;
	UBRR0L = 0xcf; // 9600bps, 2배속모드를 쓸 것이다.
	
	UCSR0A = (1<<U2X0); // 2배속 모드 셋
	
	// 비동기 모드, 8bit 데이터, 패리티비트 없음, 1비트 stop bit
	//UCSR0C |= (0<<UMSEL0); // 비동기 모드
	//UCSR0C |= (0<<UPM01) | (0<<UPM00); // 패리티 비트 없음
	//UCSR0C |= (0<<USBS0); // 1비트 stop mode
	
	UCSR0B |= (1<<RXEN0); // 수신 가능(RX PIN 허용)
	UCSR0B |= (1<<TXEN0); // 송신 가능(TX PIN 허용)
	
	UCSR0B |= (1<<RXCIE0); // 수신 interrupt Enable
}


void UART0_Transmit(char data)
{
	while (!(UCSR0A & (1<<UDRE0))); // 송신 가능 대기, UDR(송신버퍼)이 비어있는지 체크
	// 비어있으면 UDRE0가 1로 세팅됨.
	// while 문 거짓되면, 빠져 나가서 다음 라인으로
	UDR0 = data; // 데이터 버퍼 레지스터에 데이터 전송
	
	
}


unsigned UART0_Receive(void) // 리턴해주는 값은 없고, 받기만 하므로
{
	while (!(UCSR0A & (1<<RXC0))); // 데이터 수신 대기 // 수신
	return UDR0; // URD0에 있는 데이터를 리턴만 해주면 된다.
}

 

<main.c>

/*
 * ADC_CDS_VariableReg.c
 *
 * Created: 2023-08-21 오후 2:33:13
 * Author : USER
 */ 

#define F_CPU 16000000UL
#include "UART_2.h"

FILE OUTPUT = FDEV_SETUP_STREAM(UART0_Transmit, NULL, _FDEV_SETUP_WRITE);

void ADC_Init()
{
	ADMUX |= (1<<REFS0); // AVCC 기준 전압 설정
	ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // 분주비 : 128
	// ADCSRA |= 0x07; // 이렇게 써줘도 됨
	ADCSRA |= (1<<ADEN); // ADC 활성화
	ADCSRA &= ~(1<<ADFR); // 단일 running mode	
}

int read_ADC(uint8_t channel)
{
	ADMUX = ((ADMUX & 0xE0) | channel); // 단일 입력 채널 선택
	ADCSRA |= (1<<ADSC); // 변환 시작
	while (!(ADCSRA & (1<<ADIF))); // 변환 종료 대기
	
	return ADC; // 10 bit값 변환
}


int main(void)
{
    int read;
	
	stdout = &OUTPUT;
	
	UART0_Init();
	ADC_Init();
	
    while (1) 
    {
		read = read_ADC(0);
		printf("channel 0 : %d\r\n", read);
		_delay_ms(5);
		
		read = read_ADC(1);
		printf("channel 1 : %d\r\n", read);
		_delay_ms(1000);
    }
}

 


 

Example #3 : I2C로 출력

 

<Header file>

I2C.h

더보기
/*
 * I2C.h
 *
 * Created: 2023-07-25 오후 2:29:25
 *  Author: USER
 */ 


#ifndef I2C_H_
#define I2C_H_

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>

#define I2C_DDR	DDRD
#define I2C_SCL	PORTD0 // clock은 PORTD0
#define I2C_SDA	PORTD1 // 


void I2C_Init();
void I2C_Start();
void I2C_Stop();
void I2C_TxData(uint8_t data);
void I2C_TxByte(uint8_t devAddrRW, uint8_t data);


#endif /* I2C_H_ */

I2C_LCD.h

더보기
/*
 * I2C.h
 *
 * Created: 2023-07-25 오후 3:06:22
 *  Author: USER
 */ 


#ifndef I2C_LCD_H_
#define I2C_LCD_H_

//#define F_CPU 16000000UL
//#include <avr/io.h>
//#include <util/delay.h>
#include "I2C.h"

#define LCD_RS	0 // Register Select
// 제어 레지스터 : 0 , 데이터 레지스터 : 1  
#define LCD_RW	1 // Read/Write 설정하는 레지스터 bit / Read : 1, Write : 0
#define LCD_E	2 // 여기서 2는 LCD board상 2번 핀(E핀)을 뜻한다.
#define LCD_BACKLIGHT	3

#define LCD_DEV_ADDR	(0x27<<1) // I2C LCD 주소 0x27, <<1 : Write 모드 유지

#define COMMAND_DISPLAY_CLEAR	0x01
#define COMMAND_DISPLAY_ON		0x0c
#define COMMAND_DISPLAY_OFF		0x08
#define COMMAND_4_BIT_MODE		0x28
#define COMMAND_ENTRY_MODE		0x06

// 함수 원형 선언 //
void LCD_Data4bit(uint8_t data);
void LCD_EnablePin();
void LCD_WriteCommand(uint8_t commandData);
void LCD_WriteData(uint8_t charData);
void LCD_BackLight();
void LCD_gotoXY(uint8_t row, uint8_t col);
void LCD_WriteString(char *string);
void LCD_WriteStringXY(uint8_t row, uint8_t col, char *string);
void LCD_Init();



#endif /* I2C.LCD_H_ */

 

<Source file>

I2C.c

더보기
/*
 * I2C.c
 *
 * Created: 2023-07-25 오후 2:29:12
 *  Author: USER
 */ 
#include "I2C.h"


void I2C_Init()
{
	I2C_DDR |= (1<<I2C_SCL) | (1<<I2C_SDA); // 출력 설정
	TWBR = 72; // 100KHz
	// TWBR = 32; // 200KHz
	// TWBR = 12; // 400KHz
}

void I2C_Start()
{
	TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN); // 1을 보내는 것은, S/W적으로 플래그를 Clear해주는 것이다.
	                                            // 1 Set하여 인터럽트 발생시키는 것이 아님.
	while(!(TWCR & (1<<TWINT))); // 하드웨어적으로 TWINT 시점을 결정
	                             // 기다렸다가 조건이 만족되면 while문 수행
}

void I2C_Stop()
{
	TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO); // Stop 비트 설정
}

void I2C_TxData(uint8_t data) // data 1바이트 전송
{
	TWDR = data;
	TWCR = (1<<TWINT) | (1<<TWEN);
	while (!(TWCR & (1<<TWINT))); // 전송 완료 대기
}

void I2C_TxByte(uint8_t devAddrRW, uint8_t data)
{
	I2C_Start();
	I2C_TxData(devAddrRW); // 읽을 것이냐 쓸 것이냐
	I2C_TxData(data); // 입/출력할 데이터 보냄
	I2C_Stop();
}

I2C_LCD.c

더보기
/*
 * I2C.c
 *
 * Created: 2023-07-25 오후 3:06:06
 *  Author: USER
 */ 

#include "I2C_LCD.h"

uint8_t I2C_LCD_Data;

void LCD_Data4bit(uint8_t data)
{
	I2C_LCD_Data = (I2C_LCD_Data & 0x0f) | (data & 0xf0); // 상위 4bit 출력
	// 이전 상위 비트는 다 날라감 | data의 상위 비트는 살림 
	LCD_EnablePin();
	I2C_LCD_Data = (I2C_LCD_Data & 0x0f) | ((data & 0x0f)<<4); // 하위 4bit
	LCD_EnablePin();
	// 상위 4bit 받고 -> Enable해서 출력하고 -> 하위 4bit 받고 상위로 시프트하고 -> Enabla해서 출력하고
	// [7:4] 핀만 사용해서 반반 나눠서 출력하면, 총 8bit를 출력할 수 있다.
}

void LCD_EnablePin()
{
	I2C_LCD_Data &= ~(1<<LCD_E); // E low 설정
	I2C_TxByte(LCD_DEV_ADDR, I2C_LCD_Data);
	
	I2C_LCD_Data |= (1<<LCD_E); // High 설정
	I2C_TxByte(LCD_DEV_ADDR, I2C_LCD_Data);
	
	I2C_LCD_Data &= ~(1<<LCD_E); // E low 설정
	I2C_TxByte(LCD_DEV_ADDR, I2C_LCD_Data);
	
	// 0->1 일때, data 출력 -> 다음 출력을 위해 0으로 다시 설정
	
	_delay_us(1600);
}

void LCD_WriteCommand(uint8_t commandData)
{
	I2C_LCD_Data &= ~(1<<LCD_RS); // Command일 때는 control register(TWCR)을 쓰고
	I2C_LCD_Data &= ~(1<<LCD_RW); // Write 모드
	LCD_Data4bit(commandData);
}

void LCD_WriteData(uint8_t charData)
{
	I2C_LCD_Data |= (1<<LCD_RS); // Data받을 때는 Data Register(TWDR) 쓰고
	I2C_LCD_Data &= ~(1<<LCD_RW); // Write 모드
	LCD_Data4bit(charData);
}


void LCD_BackLight()
{
	I2C_LCD_Data |= (1<<LCD_BACKLIGHT);
	I2C_TxByte(LCD_DEV_ADDR, I2C_LCD_Data);
}

void LCD_gotoXY(uint8_t row, uint8_t col) // LCD 화면의 커서를 지정된 행(row)과 열(col) 위치로 이동시키는 함수
{
	col %= 16; // 0~15 사이의 값 지정 가능
	row %= 2; // 0~1 사이의 값 지정 가능
	uint8_t address = (0x40 * row) + col; // 주어진 row와 col 값을 이용하여 LCD 화면의 주소(address, 커서 위치)룰 계산한다.
	// 첫 번째 행(row 0)의 주소 범위는 0x00 ~ 0x0f(0~15)
	// 두 번째 행(row 1)의 주소 범위는 0x40 ~ 0x4f(64~79)
	// 예시 : row 1, col 3 -> address = 0x43
	uint8_t command = 0x80 + address; // 계산된 주소를 이용하여 이동시키는 명령어 command 생성
	// 0x80을 사용하는 이유는 특정 주소값이 아닌, 첫 번째 행의 시작을 나타내는 상징적인 값으로 사용된다,
	// 이렇게 함으로써 코드의 가독성을 높이고, 행과 열 값을 쉽게 결합하여 원하는 주소 값을 계산할 수 있다.
	LCD_WriteCommand(command);
}

void LCD_WriteString(char *string)
{
	for (uint8_t i = 0 ; string[i] ; i++) // 받은 문자열의 포인터가 보는 곳을 한 비트씩 분해해서 LCD_WriteData 함수에 하나씩 뿌려줌
	{
		LCD_WriteData(string[i]);
	}
}

void LCD_WriteStringXY(uint8_t row, uint8_t col, char *string)
{
	LCD_gotoXY(row,col); // 행과 열의 위치를 받음. -> 어디서부터 쓸 것인지를 좌표를 받음
	LCD_WriteString(string); // 문자열 입력을 받음
}

void LCD_Init()
{
	I2C_Init();
	
	_delay_ms(20);
	LCD_WriteCommand(0x03);
	_delay_ms(10);
	LCD_WriteCommand(0x03);
	_delay_ms(1);
	LCD_WriteCommand(0x03);
	
	LCD_WriteCommand(0x02);
	LCD_WriteCommand(COMMAND_4_BIT_MODE);
	LCD_WriteCommand(COMMAND_DISPLAY_OFF);
	LCD_WriteCommand(COMMAND_DISPLAY_CLEAR);
	LCD_WriteCommand(COMMAND_ENTRY_MODE);
	LCD_WriteCommand(COMMAND_DISPLAY_ON);
	LCD_BackLight();
}

 

<main.c>

/*
 * ADC_LCD.c
 *
 * Created: 2023-08-21 오후 3:29:09
 * Author : USER
 */ 

#include "I2C_LCD.h"

// ADC를 초기화하는 함수
void ADC_Init()
{
	// ADC 전압 참조를 ADCC(5V)로 설정
    ADMUX |= (1<<REFS0); // AVCC 기준 전압 설정
    // ADC 클럭 분주비를 128로 설정하여 ADC 클럭 주파수 낮춤
	ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
	// ADCSRA |= 0x07; // 이렇게 써줘도 됨
	ADCSRA |= (1<<ADEN); // ADC 활성화
	ADCSRA &= ~(1<<ADFR); // 단일 running mode
}

// 지정된 채널에서 ADC값을 읽는 함수
int read_ADC(uint8_t channel)
{
	// ADc 다중화기를 설정하여 지정된 채널을 선택
    ADMUX = ((ADMUX & 0xE0) | channel); // 단일 입력 채널 선택
	ADCSRA |= (1<<ADSC); // 변환 시작
	while (!(ADCSRA & (1<<ADIF))); // 변환 종료 대기
	
	return ADC; // 10 bit값 변환
}


int main(void)
{
	int read;
	char buff[30];
	
	ADC_Init();
	LCD_Init();
	
	while (1)
	{
		read = read_ADC(0);
		sprintf(buff, "CDS : %-5d", read);
		LCD_WriteStringXY(0, 0, buff);
		read = read_ADC(1);
		sprintf(buff, "REG : %-5d", read);
		LCD_WriteStringXY(1, 0, buff);
		_delay_ms(1000);
	}
}

 

728x90
반응형