2024.6.24 수업날
오늘부터 내일까지 ATmega128a를 활용하여 다시 공부해보는 시간을 가진다.
< I2C 통신 >
I2C통신은 여러 주변장치들이 최소한의 연결선만을 사용하여 저속으로 통신하는 방법이다.
I2C는 송신과 수신이 동시에 이루어질 수 없는 반이중 방식을 사용한다.
슬레이브 장치의 개수와 무관하게 데이터 전송을 위한 SDA, 클록 전송을 위한 SCL의 2개의 선을 필요로한다.
I2C는 동기 방식이다. 따라서 수신된 데이터는 SCL이 HIGH인 경우 SDA의 데이터는 안정된 상태에 있어야만 한다. 데이터 전이는 SCL이 LOW인 상태에서만 가능하다.
하지만 SCL이 HIGH인 경우에도 데이터 전이가 발생하는 두 가지 예외 상황이 있는데, 데이터 전송 시작과 종료를 나타내는 경우다.
SCL이 HIGH인 경우 SDA가 HIGH에서 LOW로 바뀌는 경우는 데이터가 전송되기 시작한다는 것을 나타내고,
데이터 전송이 끝났을 때는 SCL이 HIGH가 되며 SDA도 LOW에서 HIGH로 바뀐다.
I2C는 7비트 주소를 사용하는데, 나머지 1비트는 읽기/쓰기를 선택하기 위해 사용한다.
읽기/쓰기 비트가 HIGH인 경우 마스터는 지정한 슬레이브로부터 전송되는 데이터를 SDA라인에서 read하고,
읽기/쓰기 비트가 LOW인인 경우 마스터는 지정한 슬레이브로 SDA라인을 통해 전송(write) 하는 것임을 나타낸다.
마스터가 시작신호(s)와 7비트의 주소를 보내고 LOW값을 보냈다면, 슬레이브는 수신대기 상태에 돌입한다.
마지막에 HIGH값을 보냈다면, 슬레이브는 마스터로 1바이트의 데이터를 전송한다.
아래의 그래프는 ATmega128a 데이터시트 교재에 있는 I2C 통신 동작 원리를 나타낸 것이다.
< I2C 통신을 ATmega128a와 LCD에서실행하기 >
우선 ATmega128a에서 I2C 통신을 실행하기 위해 먼저 데이터 시트를 살펴보자.
데이터시트에 보면 PD0, PD1이 순서대로 SCL, SDA에 해당되는 것을 알 수 있다
소스코드를 작성할때, ATmega 128a 데이터시트 교재의 226p~229p에 있는 레지스터 디스크립션을 참고한다.
다음으로 LCD를 살펴보자.
이번에는 LCD 뒷면에 결합된 pcf8574라는 부품을 사용해본다.
아래 사진의 까만 부품이 pcf8574이며, 파란색 부품은 십자드라이버로 돌릴 수 있는 가변저항이다.
pcf8574의 우측에는 GND, Vcc, SDA, SCL이 있는데
Gnd는 (-), Vcc는 (+), SDA는 ATmega 128a의 PD1, SCL은 ATmega128a의 PD0에 연결한다.
아래는 pcf8574의 데이터시트이다.
I2C_LCD 소스코드
/*
* I2C.h
*
* Created: 2024-06-24 오전 9:39:30
* Author: USER
*/
#ifndef I2C_H_
#define I2C_H_
#include <avr/io.h>
// 데이터 시트 보고 정해진 포트 위치를 지정해준것임
#define I2C_DDR DDRD
#define I2C_SCL 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.c
*
* Created: 2024-06-24 오전 9:39:19
* 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);
while(!(TWCR & (1<<TWINT)));
}
void I2C_Stop()
{
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
}
void I2C_TxData(uint8_t data)
{
TWDR = data; //데이터를 받으면 8비트 레지스터에 집어넣음
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.c에서 TWBR를 72라고 설정한 이유는 ATmega128a 데이터시트 교재의 공식을(207p) 활용하여 코드에 TWBR를 구현한 것이다.
/*
* I2C_LCD.h
*
* Created: 2024-06-24 오전 9:40:21
* 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
#define LCD_RW 1
#define LCD_E 2
#define LCD_BACKLIGHT 3
#define LCD_DEV_ADDR (0x27<<1) // 주소는 0x27, <<은 W를 유지하기 위해
// lcd4bit.h에서 복사해온 코드
#define COMMAND_DISPLAY_CLEAR 0x01 // Clear all display
#define COMMAND_DISPLAY_ON 0x0C // 화면 ON, 커서 OFF, 커서 점멸 OFF
#define COMMAND_DISPLAY_OFF 0x08 // 화면 OFF, 커서 OFF, 커서 점멸 OFF
#define COMMAND_ENTRY_MODE 0x06
#define COMMAND_4BIT_MODE 0x28 // 4비트, 화면 2행, 5X8 Font-->8비트
void LCD_Data4Bit(uint8_t data); // 4bit
void LCD_EnablePin();
void LCD_WriteCommand(uint8_t commandData);
void LCD_WriteData(uint8_t charData);
void LCD_BackLightOn(); //이거는 추가한거임
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_ */
I2C_LCD에서 주소를 0x27로 쓴 이유는 아래와 같다.
주소는 읽기/쓰기 비트를 제외한 7비트이고, 0x27이라는 비트를 1로 이동한 것이다.
위의 표에서 A2, A1, A0이 3, 2, 1비트에서 HIGH인 이유는 아래의 표에서 맨 밑의 경우를 사용하기 때문이다.
/*
* I2C_LCD.c
*
* Created: 2024-06-24 오전 9:40:07
* Author: USER
*/
#include "I2C_LCD.h"
uint8_t I2C_LCD_Data;
void LCD_Data4Bit(uint8_t data) // 4bit
{
I2C_LCD_Data = (I2C_LCD_Data & 0x0f) | (data & 0xf0); // 상위4비트
LCD_EnablePin();
I2C_LCD_Data = (I2C_LCD_Data & 0x0f) | ((data & 0x0f) << 4); //하위4비트
LCD_EnablePin();
}
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); // 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);
_delay_us(1800);
}
void LCD_WriteCommand(uint8_t commandData)
{
I2C_LCD_Data &= ~(1<<LCD_RS);
I2C_LCD_Data &= ~(1<<LCD_RW);
LCD_Data4Bit(commandData);
}
void LCD_WriteData(uint8_t charData)
{
I2C_LCD_Data |= (1<<LCD_RS);
I2C_LCD_Data &= ~(1<<LCD_RW);
LCD_Data4Bit(charData);
}
void LCD_BackLightOn()
{
I2C_LCD_Data |= (1<<LCD_BACKLIGHT);
I2C_TxByte(LCD_DEV_ADDR, I2C_LCD_Data);
}
void LCD_GotoXY(uint8_t row, uint8_t col)
{
col %= 16;
row %= 2;
uint8_t address = (0x40 * row) + col;
uint8_t command = 0x80 + address;
LCD_WriteCommand(command);
}
void LCD_WriteString(char *string)
{
for (uint8_t i = 0; string[i]; i++)
{
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_4BIT_MODE);
LCD_WriteCommand(COMMAND_DISPLAY_OFF);
LCD_WriteCommand(COMMAND_DISPLAY_CLEAR);
LCD_WriteCommand(COMMAND_ENTRY_MODE);
LCD_WriteCommand(COMMAND_DISPLAY_ON);
LCD_BackLightOn();
}
I2C_LCD.c 에서 아래 사진처럼 스크롤 한 부분이 '전역변수'이다.
즉, 지정한 함수 밖에있는 변수이다.
/*
* I2C_LCD.c
*
* Created: 2024-06-24 오전 9:33:45
* Author : USER
*/
#include <stdio.h>
#include "I2C_LCD.h"
int main(void)
{
uint16_t count = 0;
uint16_t buff[30];
LCD_Init();
LCD_WriteStringXY(0,0, "Hello ATMEGA128A");
while (1)
{
//카운터 출력
sprintf(buff, "count : %-5d", count++);
LCD_WriteStringXY(1,0,buff);
_delay_ms(300);
}
}
이번 소스코드에서는 pcf8574가 결합되어있는 LCD에 문자를 같이 출력하기 위해서 파일 갯수가 증가했다.
I2C.h와 I2C.c는 통신과 관련된 파일이고, I2C_LCD.h와 I2C_LCD.c는 lcd와 관련된 파일이다.
pcf8574가 결합되어있는 LCD에 위의 소스코드를 프로그래밍 하면 아래와 같이 문자가 출력되는 것을 확인할 수 있다.
< HC-SR04(Ultrasonic) >
HC-SR04는 초음파센서라고 불린다.
초음파를 이용하여 물체와의 거리를 측정할 수 있는데,
초음파를 발사시키고 장애물과 부딫친 뒤 반사되어 돌아오는 시간차를 이용해 음파의 속력을 이용하여 거리를 계산하는 것이다.
위의 사진처럼 핀은 총 4개가 있다. Vcc, Trig, Echo, Gnd이다.
Trig, Echo 의 핀 위치는 정해진 것이 아니지만,
pcf8574가 결합되어있는 LCD와 같이 사용하기 위하여 PD2, PD3으로 지정하였다.
즉, Gnd는 (-), Vcc는 (+), Trig는 ATmega 128a의 PD2, Echo은 ATmega128a의 PD3에 연결한다.
초음파 센서의 원리를 펄스와 관련하여 표현하면 아래와 같다.
Echo를 바라보고 있다가 하이에서 로우로 떨어질때까지의 시간을 계산해야하는데 카운터를 사용해서 계산하면 된다
단위를 us로 두고,
ATmega 128a에서 마이크로 단위로 딱 떨어지지 않기 때문에 64분주로 하여 계산하기 편하게 한 주기를 4us로 설정했다.
ULTRASONIC 거리 측정 결과를 comportmaster로 출력하는 소스코드
/*
* uart0.h
*
* Created: 2024-06-11 오후 2:11:59
* Author: USER
*/
#ifndef UART0_H_
#define UART0_H_
#define F_CPU 16000000UL
#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_Recevie();
#endif /* UART0_H_ */
/*
* uart0.c
*
* Created: 2024-06-11 오후 2:11:35
* Author: USER
*/
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>
void UART0_Init()
{
UBRR0H = 0x00; // 9~11 bit
UBRR0L = 207; // 9600bps 설정
UCSR0A = (1<<U2X0); // 2배속 설정
// 비동기 모드, 8비트, 패리티비트 없음, 스톱비트 1개
// UCSR0C = 0x06; // 초기값으로 대체 0000 0110
UCSR0B |= (1<<RXEN0); // 수신가능
UCSR0B |= (1<<TXEN0); // 송신가능
UCSR0B |= (1<<RXCIE0); // 수신 인터럽트 인에이블
}
void UART0_Transmit(char data)
{
while(!(UCSR0A & (1<<UDRE0))); // 송신 가능 하냐?(대기중), UDR이 비어 있는지? //비슷한 구문이 ctc쯤에 있을것이다
UDR0 = data;
}
unsigned UART0_Recevie()
{
while(!(UCSR0A & (1<<RXC0))); // 수신 대기중
return UDR0;
}
/*
* ULTRASONIC.c
*
* Created: 2024-06-24 오후 12:25:30
* Author : USER
*/
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include "uart0.h"
FILE OUTPUT = FDEV_SETUP_STREAM(UART0_Transmit, NULL, _FDEV_SETUP_WRITE);
void timerInit()
{
TCCR1B |= (1<<CS11) | (1<<CS10); // 64분주
}
void triggerPin()
{
//PORTD &= ~(1<<PORTD2); // 트리거핀 LOW
//_delay_us(1);
PORTD |= (1<<PORTD2); // 트리거 High
_delay_us(10);
PORTD &= ~(1<<PORTD2); // 트리거 LOW
}
uint8_t meanDistance()
{
TCNT1 = 0;
while(!(PIND & (0x08))) // 0b 0000 1000 HIGH 까지 대기, LCD 써보기 위해 두자리 비움
// 0b xxxx 1xxx <<-(PIND & (0x08)
//!(PIND & (0x08)가 거짓이 되기 때문에 while에서 튕겨져 나옴
{
if(TCNT1 > 65000)
{
return 0;
}
}
TCNT1 = 0;
while (PIND & 0x08) // echo핀이 LOW까지 대기
{
if(TCNT1 > 65000)
{
TCNT1 = 0;
break;
}
}
double pulseWidth = 1000000.0 * TCNT1 * 64/16000000;
return pulseWidth / 58;
}
int main(void)
{
uint8_t distance;
stdout = &OUTPUT;
UART0_Init();
DDRD |= (1<<PORTD2); // triger
DDRD &= 0xf7;
//DDRD &= ~(1<<PORTD3); // echo
timerInit();
sei();
while (1)
{
triggerPin();
distance = meanDistance();
printf("Distance : %d cm\r\n", distance);
_delay_ms(1000);
}
}
아래의 사진은 강의실에서 초음파 센서로 거리를 측정한 것이다.
152~157cm는 강의실 책상에서 브레드보드에 꽃혀있는 초음파센서롤 천장으로 향하게 하여 초음파 센서부터 천장까지의 높이를 나타낸 것이다. 그 외는 다양한 거리를 나타내어 출력한 것이다.
이제 ComportMaster에 출력되는 거리 값을 pcf8574가 결합되어있는 LCD에 출력되도록 소스코드를 작성해보자.
I2C 통신 때 작성했던
통신과 관련된 파일인 I2C.h와 I2C.c, lcd와 관련된 파일인 I2C_LCD.h와 I2C_LCD.c,
uart 통신과 관련된 파일인 uart0.h와 uart.c를 그대로 복사하여 ULTRASONIC_LCD에 붙여넣기를 한 후
main.c를 아래와 같이 작성한다.
ULTRASONIC_LCD 소스코드
/*
* ULTRASONIC_LCD.c
*
* Created: 2024-06-24 오후 3:17:09
* Author : USER
*/
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include "uart0.h"
#include "I2C_LCD.h"
FILE OUTPUT = FDEV_SETUP_STREAM(UART0_Transmit, NULL, _FDEV_SETUP_WRITE);
void timerInit()
{
TCCR1B |= (1<<CS11) | (1<<CS10); // 64분주
}
void triggerPin()
{
//PORTD &= ~(1<<PORTD2); // 트리거핀 LOW
//_delay_us(1);
PORTD |= (1<<PORTD2); // 트리거 High
_delay_us(10);
PORTD &= ~(1<<PORTD2); // 트리거 LOW
}
uint8_t meanDistance()
{
TCNT1 = 0;
while(!(PIND & (0x08))) // 0b 0000 1000 HIGH 까지 대기, LCD 써보기 위해 두자리 비움
// 0b xxxx 1xxx <<-(PIND & (0x08)
//!(PIND & (0x08)가 거짓이 되기 때문에 while에서 튕겨져 나옴
{
if(TCNT1 > 65000)
{
return 0;
}
}
TCNT1 = 0;
while (PIND & 0x08) // echo핀이 LOW까지 대기
{
if(TCNT1 > 65000)
{
TCNT1 = 0;
break;
}
}
double pulseWidth = 1000000.0 * TCNT1 * 64/16000000;
return pulseWidth / 58;
}
int main(void)
{
uint8_t distance;
uint16_t buff[30];
LCD_Init();
LCD_WriteStringXY(0,0, "HC-SR04 Distance");
stdout = &OUTPUT;
UART0_Init();
DDRD |= (1<<PORTD2); // triger
DDRD &= 0xf7;
//DDRD &= ~(1<<PORTD3); // echo
timerInit();
sei();
while (1)
{
// LCD에 출력되는
sprintf(buff, ": %-3d cm", distance);
//LCD_WirteCommand(COMMAND_DISPLAY_CLEAR); --> 이 줄을 추가하거나 아니면 자릿수를 지정해주면 cm가 중복으로 출력되지 않음(덮어쓰지 않음, 초기화 시키기)
LCD_WriteStringXY(1,0,buff);
//comportmaster에 출력되는
triggerPin();
distance = meanDistance();
printf("Distance : %d cm\r\n", distance);
_delay_ms(1000);
}
}
이 소스코드를 실행하면 아래와 같이 pcf8574가 결합되어있는 LCD와 comportmaster에 출력되는 것을 확인할 수 있다.
I2C 통신, ULTRASONIC(HC-SR04) 끝!
'Harman 세미콘 아카데미 > Atmega128a' 카테고리의 다른 글
2024.7.16 [ATmega128a]10 - 다기능 선풍기 만들기 팀프로젝트 (0) | 2024.07.16 |
---|---|
2024.6.25 [ATmega128a]⑨ - Ultrasonic(int, ICP), EZ motor R300 (0) | 2024.06.25 |
[ATmega 128a]⑦ - UART 통신 (0) | 2024.06.18 |
[ATmega 128a]⑥ - LCD 8bit, LCD 8bit counter, LCD 4bit (0) | 2024.06.18 |
[ATmega 128a]⑤ - CTC, NOMAL, FastPWM, PWM16BIT, dalay 함수 (0) | 2024.06.18 |