[Ultrasonic]
Ultra Sonic(HC-SR04)
위 센서는 초음파를 이용해 물체와의 거리를 측정할 수 있으며,
그 원리는 초음파를 발사하고 장애물과 부딪힌 뒤 반사되어 돌아오는 시간차와 음파의 속력을 측정해 거리를 계산한다.
<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
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
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);
}
}
'# Semiconductor > [Semicon Academy]' 카테고리의 다른 글
[Harman 세미콘 아카데미] 40일차 - PSpice Mission (0) | 2023.08.22 |
---|---|
[Harman 세미콘 아카데미] 40일차 - PSpice(Flip-Flop, Oscillator) (0) | 2023.08.22 |
[Harman 세미콘 아카데미] 38일차 - ARM & RTOS 활용(ADC, Interrupt, Timer) (0) | 2023.08.18 |
[Harman 세미콘 아카데미] 37일차 - ARM & RTOS 활용(UART & ADC 활용) (0) | 2023.08.17 |
[Harman 세미콘 아카데미] 36일차 - ARM & RTOS 활용(STM32 Review, LED 실습) (0) | 2023.08.16 |