Recent Posts
printf("ho_tari\n");
Mini Project1 - Device Driver Project (BMP180(I2C) & LCD1602) 본문
(Telechips) AI 시스템 반도체 SW 개발자 교육/STM32CubeIDE mini Project
Mini Project1 - Device Driver Project (BMP180(I2C) & LCD1602)
호타리 2025. 4. 4. 17:062025.04.04 ~ 2025.04.07
BMP180 (I2C)
I2C3
PA8 - SCL
PC9 - SDA
1. 기본 구조 및 레지스터 정의
- I2C 주소 및 레지스터 주소
- 코드에서는 BMP180_ADDRESS가 0x77 << 1로 정의되어 있습니다.
- BMP180 데이터시트(예, Table 5, 페이지 14)에 따르면 센서의 7비트 기본 주소는 0x77이며, HAL에서는 8비트 주소 형식을 사용하므로 왼쪽 시프트를 적용
- BMP180_REG_CALIB_START (0xAA), BMP180_REG_CONTROL (0xF4) 및 BMP180_REG_OUT_MSB (0xF6)는 데이터시트의 메모리 맵(페이지 19~20)에서 보정 데이터와 측정 결과 레지스터 주소로 지정
- 온도 및 기압 측정 명령어(BMP180_CMD_READ_TEMP 0x2E, BMP180_CMD_READ_PRESS 0x34)는 데이터시트의 Table 8 (페이지 22)에서 확인
- 코드에서는 BMP180_ADDRESS가 0x77 << 1로 정의되어 있습니다.
2. 보정 계수 읽기
- I2C 주소 및 레지스터 주소
- 코드에서는 BMP180_ADDRESS가 0x77 << 1로 정의되어 있습니다.
- BMP180 데이터시트(예, Table 5, 페이지 14)에 따르면 센서의 7비트 기본 주소는 0x77이며, HAL에서는 8비트 주소 형식을 사용하므로 왼쪽 시프트를 적용
- BMP180_REG_CALIB_START (0xAA), BMP180_REG_CONTROL (0xF4) 및 BMP180_REG_OUT_MSB (0xF6)는 데이터시트의 메모리 맵(페이지 19~20)에서 보정 데이터와 측정 결과 레지스터 주소로 지정
- 온도 및 기압 측정 명령어(BMP180_CMD_READ_TEMP 0x2E, BMP180_CMD_READ_PRESS 0x34)는 데이터시트의 Table 8 (페이지 22)에서 확인
- 코드에서는 BMP180_ADDRESS가 0x77 << 1로 정의되어 있습니다.
3. 원시 온도 읽기
- 동작 개요:
- 온도 측정을 위해 먼저 제어 레지스터(0xF4)에 0x2E 명령어를 기록
- 이후 4.5ms 이상의 대기 후, 0xF6에서 2바이트를 읽어 원시 온도 데이터(UT)를 획득
- 데이터시트와 비교:
- 데이터시트 Table 8(페이지 22)에서는 온도 변환 시간이 약 4.5ms라고 명시
- 이 함수는 해당 대기 시간(HAL_Delay(5))를 적용한 후 2바이트 데이터를 읽어와 UT를 구성
- 타임아웃 처리:
- 쓰기와 읽기 모두 BMP180_TIMEOUT_MS 값을 사용하여 통신 오류나 타임아웃 시 -1을 반환하도록 구현
4. 원시 기압 읽기
- 동작 개요:
- 기압 측정을 위해 oversampling 설정(oss)에 따라 명령어를 수정하여 제어 레지스터(0xF4)에 기록
- oss 값에 따라 5ms, 8ms, 14ms, 26ms 등 적절한 대기 시간을 둔 후 3바이트 데이터를 읽어옴
- 읽어온 3바이트를 결합한 후, 오른쪽 시프트(8 - oss)를 수행해 원시 기압(UP)을 계산
- 데이터시트와 비교:
- Table 8(페이지 22)에서 각 oversampling 모드에 따른 최대 변환 시간이 제공되며, 코드에서 사용한 대기 시간은 이 값과 거의 일치
- 또한, 데이터 시트에서는 기압 데이터가 16~19비트로 표현됨을 감안하여 오른쪽 시프트 연산을 수행
- 타임아웃 처리:
- 역시 쓰기 및 읽기 시 BMP180_TIMEOUT_MS를 적용해 타임아웃 시 -1을 반환
5. 온도 보정 계산
- 동작 개요:
- 원시 온도 값 UT와 보정 계수를 이용해 실제 온도를 계산
- 계산은 데이터시트에 제시된 공식에 따라 진행되며, 중간 변수 X1, X2, 그리고 B5를 사용
- 최종 결과는 0.1°C 단위로 반환
- 데이터시트와 비교:
- 페이지 15의 "Algorithm for pressure and temperature measurement"에서 제시된 공식과 동일한 단계로 계산
- 공식:
- X1 = ((UT – AC6) * AC5) / 2^15
- X2 = (MC * 2^11) / (X1 + MD)
- B5 = X1 + X2
- Temperature = (B5 + 8) / 2^4
- 코드에서는 비트 쉬프트 연산을 사용해 나눗셈을 수행함으로써 동일한 결과를 얻음
6. 기압 보정 계산
- 동작 개요:
- 이 함수는 UT(원시 온도)와 UP(원시 기압)을 이용하여 보정된 기압 값을 계산
- 여러 중간 변수(B5, B6, X1, X2, X3, B3, B4, B7 등)를 사용하여 단계별로 계산
- 데이터시트와 비교:
- 데이터시트 페이지 15 및 관련 섹션에서는 기압 계산 공식이 자세히 제시
- 코드의 단계는 다음과 같이 진행됩니다:
- 온도 보정값 B5를 재계산한 후, B6 = B5 - 4000
- X1, X2, X3를 계산해 B3를 결정하고, 이후 AC3, B1 등을 이용해 X1, X2, X3를 다시 계산하여 B4를 구함
- UP와 B3의 차이를 이용해 B7을 계산하고, B7과 B4를 이용해 p(기압)를 산출
- 마지막으로 추가 보정 단계를 거쳐 최종 기압 값을 도출
- 이 계산 과정은 Bosch에서 제공하는 "BMP180_API"에 기반하며, 데이터시트에 제시된 알고리즘과 일치
7. 메인 함수
- 동작 개요:
- LCD를 초기화하고, BMP180의 보정 계수를 먼저 읽어옴
- 무한 루프에서 원시 온도(UT)와 기압(UP)을 읽고, 각각 보정 계산을 수행하여 온도와 기압 값을 얻음
- UT 또는 UP가 -1이면 통신 오류로 판단해 LCD와 UART에 에러 메시지를 출력
- 정상적인 경우, 계산된 온도(0.1°C 단위)와 기압(Pa에서 hPa로 변환)을 LCD에 출력하고, UART로도 전송
- 데이터시트와 비교:
- 센서 사용 순서(보정 계수 읽기 → 측정 시작 → 데이터 읽기 → 보정 계산)는 데이터시트(페이지 10~15)의 설명과 일치
- 데이터시트에서는 측정 시 시작 명령, 변환 대기 시간, 그리고 결과 읽기 순서를 설명하고 있으며, 코드에서도 동일한 순서로 구현
8. 타임아웃 처리
- 목적 및 구현:
- 코드에서는 모든 I2C 통신 함수(HAL_I2C_Mem_Read, HAL_I2C_Mem_Write)에 대해 BMP180_TIMEOUT_MS (여기서는 2000ms)를 사용
- 데이터시트에는 타임아웃에 대한 직접 언급은 없지만, 안정적인 시스템 동작을 위해 센서가 무응답 시 프로그램이 무한 대기하지 않도록 타임아웃을 설정하는 것이 일반적인 구현 방식
<bmp180.c>
/*
* bmp180.c
*
* Created on: Apr 4, 2025
* Author: microsoft
*/
#include "stm32f4xx_hal.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h> // abs() 함수를 사용하기 위해 추가
#include "i2c_lcd.h"
// 타임아웃 1000ms 설정
#define BMP180_TIMEOUT_MS 1000
// BMP180 I2C 주소 (8비트 주소 체계: 좌측 쉬프트)
// BMP180 센서의 기본 I2C 주소인 0x77을 왼쪽으로 1비트 시프트하여 8비트 형식으로 변환
// HAL 라이브러리에서는 8비트 주소를 사용하므로
#define BMP180_ADDRESS (0x77 << 1)
// hi2c3는 BMP180 센서와 통신하기 위해 사용되는 I2C 인터페이스의 핸들러
extern I2C_HandleTypeDef hi2c3;
// BMP180 레지스터 및 명령어
// 보정 계수(캘리브레이션 데이터)를 저장한 EEPROM의 시작 주소 0xAA
#define BMP180_REG_CALIB_START 0xAA
// 센서 제어 레지스터 주소(명령어 쓰기를 통해 온도/압력 측정 시작)
#define BMP180_REG_CONTROL 0xF4
// 측정 결과(데이터)의 최상위 바이트를 읽는 주소
#define BMP180_REG_OUT_MSB 0xF6
// 온도 측정을 위한 명령어(0x2E를 제어 레지스터에 쓰면 온도 측정 시작)
#define BMP180_CMD_READ_TEMP 0x2E
// 기압 측정을 위한 기본 명령어(oversampling 설정에 따라 변경)
#define BMP180_CMD_READ_PRESS 0x34
// 보정 계수 변수
// BMP180 센서는 개별 칩마다 고유의 보정 계수를 EEPROM에 저장
// 온도와 기압을 정확하게 계산하기 위해 이 계수들을 읽어와서 사용
// AC1, AC2, AC3, B1, B2, MB, MC, MD는 부호가 있는 16비트 정수형
int16_t AC1, AC2, AC3, B1, B2, MB, MC, MD;
// AC4, AC5, AC6는 부호가 없는 16비트 정수형
uint16_t AC4, AC5, AC6;
// 보정 계수 읽기 함수
// I2C 인터페이스를 통해 BMP180의 보정 데이터를 읽어오는 함수
void BMP180_ReadCalibrationCoefficients(I2C_HandleTypeDef *hi2c)
{
// calib_data 배열(22바이트 크기)은 보정 데이터 전체 저장
uint8_t calib_data[22];
if (HAL_I2C_Mem_Read(hi2c, BMP180_ADDRESS, BMP180_REG_CALIB_START,
I2C_MEMADD_SIZE_8BIT, calib_data, 22, BMP180_TIMEOUT_MS) != HAL_OK)
{
// 타임아웃 또는 통신 오류 시 처리: 여기서는 간단히 리턴
return;
}
// 보정 데이터는 8비트 단위로 읽어오므로 상우 ㅣ바이트와 하위 바이트를 결합하여 16비트값으로 만듦
// (calib_data[0] << 8 | calib_data[1])는 첫 번째 16비트 값(AC1)을 만듦
// 나머지 보정 계수들도 같은 방식으로 계산
// 부호 있는 값은 (int16_t)로, 부호 없는 값은 (uint16_t)로 캐스팅
AC1 = (int16_t)(calib_data[0] << 8 | calib_data[1]);
AC2 = (int16_t)(calib_data[2] << 8 | calib_data[3]);
AC3 = (int16_t)(calib_data[4] << 8 | calib_data[5]);
AC4 = (uint16_t)(calib_data[6] << 8 | calib_data[7]);
AC5 = (uint16_t)(calib_data[8] << 8 | calib_data[9]);
AC6 = (uint16_t)(calib_data[10] << 8 | calib_data[11]);
B1 = (int16_t)(calib_data[12] << 8 | calib_data[13]);
B2 = (int16_t)(calib_data[14] << 8 | calib_data[15]);
MB = (int16_t)(calib_data[16] << 8 | calib_data[17]);
MC = (int16_t)(calib_data[18] << 8 | calib_data[19]);
MD = (int16_t)(calib_data[20] << 8 | calib_data[21]);
}
// 원시 온도 값 읽기
// 온도 측정을 위해 센서에 명령을 보내고 원시 온도 데이터를 읽어오는 함수
int16_t BMP180_ReadRawTemperature(I2C_HandleTypeDef *hi2c)
{
// cmd 변수에 온도 측정 명령어(0x2E)를 저장
uint8_t cmd = BMP180_CMD_READ_TEMP;
if (HAL_I2C_Mem_Write(hi2c, BMP180_ADDRESS, BMP180_REG_CONTROL,
I2C_MEMADD_SIZE_8BIT, &cmd, 1, BMP180_TIMEOUT_MS) != HAL_OK)
{
// 통신 타임아웃 또는 오류 발생 시 -1 반환
return -1;
}
// 센서가 온도 측정을 완료할 때까지 대기 시간
HAL_Delay(5);
// 0xF6 주소에서 2바이트를 읽어와 원시 온도 데이터(raw 배열) 저장
uint8_t raw[2];
if (HAL_I2C_Mem_Read(hi2c, BMP180_ADDRESS, BMP180_REG_OUT_MSB,
I2C_MEMADD_SIZE_8BIT, raw, 2, BMP180_TIMEOUT_MS) != HAL_OK)
{
return -1;
}
// 상위 바이트와 하위 바이트를 결합하여 16비트 정수형 온도 값을 반환
return (int16_t)((raw[0] << 8) | raw[1]);
}
// 원시 기압 값 읽기 (oss: oversampling setting, 0~3)
// 기압 측정을 위해 원기 기압 데이터를 읽어오는 함수
int32_t BMP180_ReadRawPressure(I2C_HandleTypeDef *hi2c, uint8_t oss)
{
// oss는 oversampling setting으로 0~3 사이의 값을 사용
// 기압 명령어에 oversampling 지트를 추가하기 위해 (oss << 6)을 더함
uint8_t cmd = BMP180_CMD_READ_PRESS + (oss << 6);
if (HAL_I2C_Mem_Write(hi2c, BMP180_ADDRESS, BMP180_REG_CONTROL,
I2C_MEMADD_SIZE_8BIT, &cmd, 1, BMP180_TIMEOUT_MS) != HAL_OK)
{
return -1;
}
// oversampling 설정에 따라 측정 완료까지 대기해야 하는 시간이 달라짐
// oss가 0이면 5ms, 1이면 8ms, 2이면 14ms, 3이면 26ms 대기
// 잘못된 값이 들어올 경우 기본적으로 5ms 대기
switch(oss)
{
case 0: HAL_Delay(5); break;
case 1: HAL_Delay(8); break;
case 2: HAL_Delay(14); break;
case 3: HAL_Delay(26); break;
default: HAL_Delay(5); break;
}
//0xF6 주소에서 3바이트를 읽어옴, 이 데이터는 기압 측정의 결과를 포함
uint8_t raw[3];
if (HAL_I2C_Mem_Read(hi2c, BMP180_ADDRESS, BMP180_REG_OUT_MSB, I2C_MEMADD_SIZE_8BIT, raw, 3, BMP180_TIMEOUT_MS) != HAL_OK)
{
return -1;
}
// 읽어온 3바이트 데이터를 결합하여 24비트 정수형 값을 만든 후 oversampling 설정에 따라 오른쪽으로 시프트하여 최종 원시 기압 값(up)을 계산
// 계산괸 up 값을 반환
int32_t up = (((int32_t)raw[0] << 16) | ((int32_t)raw[1] << 8) | raw[2]) >> (8 - oss);
return up;
}
// 온도 계산 함수 (BMP180 데이터시트에 따른 계산식)
// 반환값은 0.1°C 단위의 온도 값
// 원시 온도 값(UT)을 이용하여 보정된 온도를 계산하는 함수
int32_t BMP180_ComputeTemperature(int16_t UT)
{
// 중간 계산에 사용할 변수 X1, X2, B5 선언
int32_t X1, X2, B5;
// 데이터 시트에 따른 공식의 일부로 UT에서 AC6을 빼고 AC5를 곱한 후
// 2^15로 나누기 위해 비트 시프트 연산 (>> 15)을 수행
X1 = ((UT - AC6) * AC5) >> 15;
// MC를 2^11(2048)배한 후 X1과 MD의 합으로 나눔
// 이 계산은 온도 보정을 위한 중간 단계
X2 = (MC << 11) / (X1 + MD);
// X1과 X2를 더해 B5값을 얻음, B5는 최종 온도 계산에 중요한 중간 변수
B5 = X1 + X2;
// B5에 8을 더한 후 2^4(16)로 나눠 최종 보정 온도 구함
// 반환 값을 0.1도 단위의 온도
return (B5 + 8) >> 4;
}
// 기압 계산 함수 (BMP180 데이터시트에 따른 계산식)
// 단, 내부에서 온도 보정 값(B5)을 다시 계산합니다.
// 온도 보정을 포함하여 원시 기압(UP)과 온도(UT)를 이용해 보정된 기압 계산
int32_t BMP180_ComputePressure(int32_t UT, int32_t UP, uint8_t oss)
{
// 여러 중간 변수(X1, X2, B5, B6, X3, p)와 보정용 변수(B4, B7)를 선언
int32_t X1, X2, B5, B6, X3, p;
uint32_t B4, B7;
// 온도 보정 계산
// 기압 계산 시에도 온도 보정 값(B5)이 필요하므로 앞서 온도 계산과 동일한 공식으로 B5를 재계산
X1 = ((UT - AC6) * AC5) >> 15;
X2 = (MC << 11) / (X1 + MD);
B5 = X1 + X2;
// 기압 보정 계산
// B5에서 4000을 빼서 B6을 구함, 기압 보정 공식의 일부
B6 = B5 - 4000;
// B6의 제곱을 오른쪽으로 12비트 시프트한 후 B2와 곱하고 다시 2^11(시프트 11)로 나눔
X1 = (B2 * ((B6 * B6) >> 12)) >> 11;
// AC2와 B6을 곱한 후 2^11로 나눈 값을 X2에 저장하고 X1과 X2를 더해 X3을 구함
X2 = (AC2 * B6) >> 11;
X3 = X1 + X2;
// AC1에 4를 곱하고 X3를 더한 후 oversampling setting(oss)에 따라
// 왼쪽 시프트를 수행하고 2를 더한 후 4로 나누어 B3를 계산
int32_t B3 = (((AC1 * 4 + X3) << oss) + 2) / 4;
// AC3와 B6를 곱한 값을 오른쪽으로 13비트 시프트하여 X1
X1 = (AC3 * B6) >> 13;
// B6의 제곱을 오른쪽으로 12비트 시프트한 후 B1과 곱하고 2^16으로 나눈 값을 X2
X2 = (B1 * ((B6 * B6) >> 12)) >> 16;
// X1과 X2를 더하고 2를 더한 후 2^2(4)로 나누어 최종 X3
X3 = ((X1 + X2) + 2) >> 2;
// X3에 32768을 더한 후 AC4와 곱하고 오른쪽으로 15비트 시프트하여 B4값을 계산
// B4는 보정 과정의 분모 역할
B4 = (AC4 * (uint32_t)(X3 + 32768)) >> 15;
// 원시 기압 값 UP에서 B3를 뺀 후 50000을 oversampling에 맞게 오른쪽으로 시프트한 값과 곱하여 B7을 계산
B7 = ((uint32_t)UP - B3) * (50000 >> oss);
// B7의 값에 따라 두 가지 경우로 나누어 p값을 계산
// 조건문은 B7이 0x80000000보다 작은지 확인하여 p를 B4로 나누고 2를 곱하는 방식으로 계산
if(B7 < 0x80000000)
p = (B7 * 2) / B4;
else
p = (B7 / B4) * 2;
// p를 오른쪽으로 8비트 시프트한 후 제곱하여 X1에 저장하고 다시 3038을 곱한 후 2^16으로 나눔
X1 = (p >> 8) * (p >> 8);
X1 = (X1 * 3038) >> 16;
// X2는 p에 -7357을 곱한 후 2^16으로 나눔
X2 = (-7357 * p) >> 16;
// X1, X2 그리고 3791을 더한 후 2^4(16)로 나누어 p에 더함
// 이 계산을 통해 최종 보정된 기압 값 도출
p = p + (((X1 + X2 + 3791) >> 4));
// 계산된 기압 p값을 반환, 단위는 파스칼(Pa)
return p; // 단위: Pa
}
// BMP180 센서로부터 데이터를 읽어 LCD와 UART로 출력하는 메인 함수
void bmp_main(void)
{
// lcd_line1, lcd_line2는 16자를 저장할 문자열 배열, LCD의 각 행에 출력할 내용 저장
char lcd_line1[17];
char lcd_line2[17];
// I2C LCD 초기화
i2c_lcd_init(); // LCD 초기화 함수
// BMP180 보정 계수 읽기
// BMP180 센서의 보정 데이터를 읽어와 전역 변수들(AC1~MD)에 저장
// hi2c3 핸들러를 사용하여 I2C통신으로 센서의 EEPROM 영역에서 데이터를 읽어옴
BMP180_ReadCalibrationCoefficients(&hi2c3);
// 센서 측정 및 출력 작업을 계속 반복하기 위해 무한 루프 시작
while(1)
{
// 원시 온도 및 기압 읽기
// BMP180_ReadRawTemperature함수를 호출하여 원시 온도 값(UT)를 읽어옴
int16_t UT = BMP180_ReadRawTemperature(&hi2c3);
// BMP180_ReadRawPressure함수를 호출하여 원시 기압 값(UP)을 읽음, oversampling setting을 0으로 사용
int32_t UP = BMP180_ReadRawPressure(&hi2c3, 0);
// 통신 오류 시 -1 반환된 경우 에러 처리
if (UT == -1 || UP == -1)
{
move_cursor(0, 0);
lcd_string(" "); // 16칸 공백
move_cursor(1, 0);
lcd_string(" "); // 16칸 공백
sprintf(lcd_line1, "Sensor Error!");
sprintf(lcd_line2, "Timeout");
move_cursor(0, 0);
lcd_string(lcd_line1);
move_cursor(1, 0);
lcd_string(lcd_line2);
printf("BMP180 Timeout or Communication Error\r\n");
HAL_Delay(1000);
continue;
}
// 온도 계산 (0.1°C 단위)
// 읽어온 UT값을 이용하여 보정된 온도 계산
// 반환 값은 0.1도 단위
int32_t temperature = BMP180_ComputeTemperature(UT);
// 보정식을 이용한 실제 기압 계산 (Pa 단위 -> hPa로 변환)
// UT와 UP값을 사용해 보정된 기압 계산
int32_t pressure = BMP180_ComputePressure(UT, UP, 0) / 100;
// 첫 번째 줄에 온도값, 두 번째 줄에 기압값을 문자열로 포맷
sprintf(lcd_line1, "Temp: %2ld.%ld C", temperature / 10, abs(temperature) % 10);
sprintf(lcd_line2, "Press: %4ld hPa", pressure);
// LCD 출력
move_cursor(0, 0);
lcd_string(lcd_line1);
move_cursor(1, 0);
lcd_string(lcd_line2);
HAL_Delay(1000);
// UART를 통해 결과 출력 (온도는 소수 첫째자리까지 출력)
char msg[100];
sprintf(msg, "Temp: %ld.%ld C, Pressure: %ld hPa\r\n", temperature / 10, abs(temperature) % 10, pressure);
printf("%s", msg);
HAL_Delay(1000); // 1초 주기로 측정
}
}
main.c 관련 함수 구조
- i2c_lcd_main(): 테스트 메인 함수로, LCD 초기화 후 "Hello STM32!" 출력
- i2c_lcd_init(): LCD 초기화 시퀀스
- LCD_SendCommand(uint8_t cmd): 명령 전송
- LCD_SendData(uint8_t data): 문자(데이터) 전송
- lcd_string(uint8_t *str): 문자열 출력
- move_cursor(uint8_t row, uint8_t column): 커서 이동
I2C 방식 제어
- I2C를 사용하기 위해 PCF8574 I/O 확장 칩이 사용됨 (주소 0x27)
- LCD의 RS, RW, EN, D4~D7 등을 I/O 포트에 매핑하여 제어
- HAL_I2C_Master_Transmit()으로 4비트씩 데이터 전송
<lcd1602.c>
#include "main.h"
#include "stm32f4xx_hal.h"
#include <string.h>
#include <stdio.h>
#include "i2c_lcd.h"
// 외부에서 선언된 I2C 핸들러 (main.c 에 있을 것)
extern I2C_HandleTypeDef hi2c1;
// 함수 선언
void i2c_lcd_main(void);
void i2c_lcd_init(void);
// I2C 주소 설정 (0x27 은 PCF8574 의 기본 주소임, 왼쪽 쉬프트는 HAL이 8bit 주소를 요구해서)
#define I2C_LCD_ADDRESS (0x27 << 1)
// 테스트용 데이터 (아스키 코드로 '4', '3', '\0')
unsigned char lcd_test[4] = { '4','3', 0 };
// 테스트 메인 함수
void i2c_lcd_main(void)
{
i2c_lcd_init();
lcd_string("Hello STM32!");
// while (1) // 무한루프
// {
// // lcd_test 배열을 LCD로 송신 (2바이트만 전송)
// // 전송 실패 시 계속 재시도 (에러가 발생하면 HAL_OK가 아님)
// while(HAL_I2C_Master_Transmit(&hi2c1, I2C_LCD_ADDRESS,
// lcd_test, 2, 100)!=HAL_OK){
// // 필요하면 딜레이 추가 가능
// }
// HAL_Delay(1000); // 1초 대기
// }
#if 0 // 아래 코드는 주석처리되어 있어서 실제로 실행되진 않음 (테스트용 코드)
uint8_t value=0;
i2c_lcd_init(); // LCD 초기화
while(1)
{
move_cursor(0,0); // 0행 0열로 커서 이동
//날짜를 출력할 수 있게 사용
lcd_string("Hello World!!!"); // 문자열 출력
move_cursor(1,0); // 1행 0열로 커서 이동
//시간을 출력할 수 있게 사용
LCD_SendData(value + '0'); // 숫자를 아스키코드로 변환 후 출력
value++; // 값 증가
if(value>9)value=0; // 0~9 순환
HAL_Delay(500); // 0.5초 대기
}
#endif
}
void LCD_Enable(void) {
HAL_GPIO_WritePin(LCD_E_GPIO_Port, LCD_E_Pin, GPIO_PIN_SET);
HAL_Delay(1);
HAL_GPIO_WritePin(LCD_E_GPIO_Port, LCD_E_Pin, GPIO_PIN_RESET);
HAL_Delay(1);
}
void LCD_Send4Bits(uint8_t data) {
HAL_GPIO_WritePin(LCD_D4_GPIO_Port, LCD_D4_Pin, (data >> 0) & 0x01);
HAL_GPIO_WritePin(LCD_D5_GPIO_Port, LCD_D5_Pin, (data >> 1) & 0x01);
HAL_GPIO_WritePin(LCD_D6_GPIO_Port, LCD_D6_Pin, (data >> 2) & 0x01);
HAL_GPIO_WritePin(LCD_D7_GPIO_Port, LCD_D7_Pin, (data >> 3) & 0x01);
LCD_Enable();
}
// ========================= LCD 명령어 전송 함수 =========================
#if 1
void LCD_SendCommand(uint8_t cmd)
{
HAL_GPIO_WritePin(LCD_RS_GPIO_Port, LCD_RS_Pin, GPIO_PIN_RESET);
LCD_Send4Bits(cmd >> 4); // 상위 4비트
LCD_Send4Bits(cmd & 0x0F); // 하위 4비트
HAL_Delay(2);
}
#else
void lcd_command(uint8_t command){
// 고위 nibble (상위 4bit) / 저위 nibble (하위 4bit) 분리
uint8_t high_nibble, low_nibble;
uint8_t i2c_buffer[4];
high_nibble = command & 0xf0;
low_nibble = (command<<4) & 0xf0;
// en=1 -> en=0 으로 변화시 falling edge를 만들어야 LCD가 latch 함
// rs=0 (명령어), rw=0 (쓰기), backlight=1
i2c_buffer[0] = high_nibble | 0x04 | 0x08; // en=1
i2c_buffer[1] = high_nibble | 0x00 | 0x08; // en=0
i2c_buffer[2] = low_nibble | 0x04 | 0x08; // en=1
i2c_buffer[3] = low_nibble | 0x00 | 0x08; // en=0
// I2C 로 전송
while(HAL_I2C_Master_Transmit(&hi2c1, I2C_LCD_ADDRESS, i2c_buffer, 4, 100)!=HAL_OK){
// 필요하면 재시도 딜레이
}
return;
}
#endif
// ========================= LCD 데이터(문자) 전송 함수 =========================
#if 1
void LCD_SendData(uint8_t data)
{
HAL_GPIO_WritePin(LCD_RS_GPIO_Port, LCD_RS_Pin, GPIO_PIN_SET);
LCD_Send4Bits(data >> 4);
LCD_Send4Bits(data & 0x0F);
HAL_Delay(2);
}
#else
void lcd_data(uint8_t data){
uint8_t high_nibble, low_nibble;
uint8_t i2c_buffer[4];
high_nibble = data & 0xf0;
low_nibble = (data<<4) & 0xf0;
// rs=1 (데이터 모드), rw=0 (쓰기)
i2c_buffer[0] = high_nibble | 0x05 | 0x08; // en=1
i2c_buffer[1] = high_nibble | 0x01 | 0x08; // en=0
i2c_buffer[2] = low_nibble | 0x05 | 0x08; // en=1
i2c_buffer[3] = low_nibble | 0x01 | 0x08; // en=0
while(HAL_I2C_Master_Transmit(&hi2c1, I2C_LCD_ADDRESS, i2c_buffer, 4, 100)!=HAL_OK){
// 필요하면 재시도 딜레이
}
return;
}
#endif
// ========================= LCD 초기화 =========================
void i2c_lcd_init(void){
LCD_SendCommand(0x33); // 초기화 과정 (데이터시트 참고)
LCD_SendCommand(0x32); // 4-bit 모드 설정
LCD_SendCommand(0x28); // Function set: 4-bit, 2-line, 5x8 dots
LCD_SendCommand(DISPLAY_ON); // 화면 ON, 커서 OFF, 블링크 OFF (i2c_lcd.h 에 정의되어야 함)
LCD_SendCommand(0x06); // Entry Mode: Increment cursor
LCD_SendCommand(CLEAR_DISPLAY); // 화면 클리어
HAL_Delay(2); // LCD는 클리어 후 대기 필요
}
// ========================= 문자열 출력 =========================
void lcd_string(uint8_t *str){
// 문자열 끝(null 문자)까지 반복
while(*str) LCD_SendData(*str++);
}
// ========================= 커서 이동 =========================
void move_cursor(uint8_t row, uint8_t column){
// 커서 이동 명령어
// 1st line : 0x80 | column
// 2nd line : 0x80 | 0x40 | column
LCD_SendCommand(0x80 | row<<6 | column);
return;
}
<i2c_lcd.h>
/*
* lcd1602.h
*
* Created on: 2025. 4. 6.
* Author: microsoft
*/
#ifndef INC_LCD1602_H_
#define INC_LCD1602_H_
// --------------------[ LCD I2C 관련 설정 ]--------------------
// PCF8574의 기본 주소 (0x27) <<1 은 HAL_I2C 함수가 8비트 주소를 요구하기 때문
#define I2C_LCD_ADDRESS (0x27<<1)
// 백라이트 ON 설정 (PCF8574의 P3 핀에 연결됨, 보통 LCD 밝기 조절용)
#define BACKLIGHT_ON 0x08
// --------------------[ LCD 명령어 정의 ]--------------------
// LCD 화면 켜기 (화면 ON, 커서 OFF, 커서 깜빡임 OFF)
#define DISPLAY_ON 0x0C
// LCD 화면 끄기
#define DISPLAY_OFF 0x08
// LCD 화면 지우기 (클리어)
// 실행 후 반드시 2ms 이상의 딜레이 필요
#define CLEAR_DISPLAY 0x01
// 커서를 홈 위치 (0,0)으로 복귀
#define RETURN_HOME 0x02
// --------------------[ LCD 함수 선언 ]--------------------
// 명령어 전송 함수
void lcd_command(uint8_t command);
void LCD_SendCommand(uint8_t cmd);
// 데이터(문자) 전송 함수
void lcd_data(uint8_t data);
void LCD_SendData(uint8_t data);
// LCD 초기화 함수
void i2c_lcd_init(void);
// 문자열 출력 함수
void lcd_string(uint8_t *str);
// 커서 이동 함수 (row : 행, column : 열)
void move_cursor(uint8_t row, uint8_t column);
#endif /* INC_LCD1602_H_ */
<실행 결과>
https://youtube.com/shorts/IGYyfmROVl4
<프로젝트 결과 정리>
DeviceDriver_박성호_박준영.pdf
1.43MB
'(Telechips) AI 시스템 반도체 SW 개발자 교육 > STM32CubeIDE mini Project' 카테고리의 다른 글
Mini Project2 - Elevator Project (0) | 2025.04.11 |
---|