[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 |