개인 프로젝트
보드: STM32F429ZI
Tool: STM32CubeIDE 1.16.1, Tera Term 5.3
재료: 3x4 키패드(NT-804AN-BW), EEPROM(24C256), 홀센서, 솔레노이드(LY-03-LE DC 12V), 릴레이 모듈 V3.1, NFC 모듈(PN532), 택트 스위치 2개, 피에조 부저
1. 키패드 숫자 입력 구현 및 UART 출력
4x4 키패드를 사용하는데 도어락 키패드로 사용할 것이기 때문에 오른쪽 마지막 열은 사용하지 않는다.
키패드 동작 원리는 다음과 같다.
4개의 행(1, 2, 3, 4)은 풀업저항을 사용하여 항상 High인 상태이고,
3개의 열(5, 6, 7)은 타이머 인터럽트를 사용하여 50ms마다 (state1) L, H, H → (state2) H, L, H → (state3) H, H, L로 계속 타이머 콜백에서 상태를 바꿔준다.
예를 들어 1번 행의 외부 인터럽트가 발생하였을 시점(=스위치가 눌린 시점)에 state를 읽어서 state1이였으면 1번 스위치가 눌린 것이고, state2였으면 2번 스위치가 눌린 것이다.
GPIO EXTI는 PA0~PH0핀이 EXTI0에 연결되는 식으로 되어 있기 때문에 같은 숫자를 동시에 EXTI로 설정할 수 없다.
그리고 터미널 출력을 위해 USART3을 Asynchronous 설정한다.
타이머 콜백에서 STATE1이면 STATE2로, STATE2면 STATE3으로, STATE3이면 STATE1로 바꿔주는 방식으로 스캔하기 때문에 GPIO 콜백에서 state가 하나씩 밀린다.
● 키를 누르고 있을 때 GPIO 인터럽트가 계속해서 발생하여 같은 숫자가 계속 출력되는 문제 발생
키가 눌린 상태인지 확인하는 chk 전역변수를 선언하여 키가 눌리지 않은 상태(0)면 스캔하고, 키가 눌린 상태(1)이면 스캔을 중단한다.
키를 누른 시점의 state는 기억되어 있으므로 키를 눌렀을 때 스캔을 중단하고 눌린 키의 상태를 확인한다.
타이머 콜백에서 스캔을 중단한 상태에 4개 행의 현재 상태를 읽어서 모두 키가 눌리지 않은 상태(High)면 다시 스캔을 시작한다.
● 채터링 현상 억제하기 위한 디바운스
2. 비밀번호 8자리 입력, 지정 및 일치 여부 확인
● 임의로 지정한 비밀번호와 입력한 비밀번호가 일치하면 문 열림(=LED 켜기)
비밀번호 8자리 입력 후 *을 누르면 입력 완료
● 문이 열린 상태에서 도어락 내의 설정 버튼을 누르면 비밀번호 지정
SETUP_SW = Falling Edge, Pull-up 설정
도어락 후면부 스위치를 누르면 문열림
비밀번호 8자리 설정으로 정해져 있기 때문에 비밀번호 입력할 때 8자리를 초과하면 다시 입력하도록 설정
3. 문 열림 감지
상태 유지하는 토글 스위치(=원래는 자석센서(리드스위치)를 사용해야 하지만 임시로) : (5V 오른쪽 GND 왼쪽)오른쪽 - 누른 상태 / 왼쪽 - 떨어진 상태
토글 스위치(STATE_SW) = Rising Edge & Falling Edge : 풀업저항 걸려있어 스위치 켜지면 H->L로 가며 문이 열린 상태임을 표현, 스위치 꺼지면 L->H로 가며 문이 닫힌 상태임을 표현 (핀 상태를 읽어서)
● 문 잠금장치와 문 여닫이 상태를 UART로 출력하고 문이 열리면 LED가 켜지도록 구현
● 문이 열리고 닫히는 상태가 채터링으로 오작동
토글스위치 ON(문 열림) 후 2초 후에 다시 상태 확인하여 여전히 열려 있으면 진짜 열린 것으로 간주, 토글스위치 OFF(문 닫힘) 후 2초 후에 다시 상태 확인하여 여전히 닫혀 있으면 진짜 닫힌 것으로 간주하여 문 잠금장치 ON(서보모터 0도)
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
/* USER CODE END Includes */
/* USER CODE BEGIN PTD */
#define STATE1 0
#define STATE2 1
#define STATE3 2
#define DEBOUNCE_DELAY 200
/* USER CODE END PTD */
/* USER CODE BEGIN PV */
uint8_t state = STATE1;
uint8_t chk = 0;
char tx_buf[50];
char num = '\0';
char password[9] = "12345678";
char input_pw[9] = "";
uint8_t pw_index = 0;
uint8_t is_door_lock = 1;
uint8_t is_door_open = 0;
uint8_t is_door_really_open = 0;
uint8_t setup_pw = 0;
char new_pw[9] = "";
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim3);
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1);
/* USER CODE END 2 */
/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
GPIO_PinState KEYPAD1, KEYPAD2, KEYPAD3, KEYPAD4, KEYPAD5, KEYPAD6, KEYPAD7;
if(htim->Instance == TIM3) {
if(chk == 0) { // 스캔
if(state == STATE1) {
KEYPAD5 = 0; KEYPAD6 = 1; KEYPAD7 = 1;
state = STATE2;
}
else if(state == STATE2) {
KEYPAD5 = 1; KEYPAD6 = 0; KEYPAD7 = 1;
state = STATE3;
}
else if(state == STATE3) {
KEYPAD5 = 1; KEYPAD6 = 1; KEYPAD7 = 0;
state = STATE1;
}
HAL_GPIO_WritePin(KEYPAD5_GPIO_Port, KEYPAD5_Pin, KEYPAD5);
HAL_GPIO_WritePin(KEYPAD6_GPIO_Port, KEYPAD6_Pin, KEYPAD6);
HAL_GPIO_WritePin(KEYPAD7_GPIO_Port, KEYPAD7_Pin, KEYPAD7);
}
else {
KEYPAD1 = HAL_GPIO_ReadPin(KEYPAD1_GPIO_Port, KEYPAD1_Pin);
KEYPAD2 = HAL_GPIO_ReadPin(KEYPAD2_GPIO_Port, KEYPAD2_Pin);
KEYPAD3 = HAL_GPIO_ReadPin(KEYPAD3_GPIO_Port, KEYPAD3_Pin);
KEYPAD4 = HAL_GPIO_ReadPin(KEYPAD4_GPIO_Port, KEYPAD4_Pin);
if(KEYPAD1 == 1 && KEYPAD2 == 1 && KEYPAD3 == 1 && KEYPAD4 == 1) { // 키가 눌리지 않았을 때
chk = 0;
}
}
}
else if(htim->Instance == TIM2) {
if(is_door_lock == 0 && is_door_open == 0) {
is_door_lock = 1;
TIM4->CCR1 = 499;
sprintf(tx_buf, "\n\r시간 초과로 문이 닫혔습니다.\n\r");
HAL_UART_Transmit_IT(&huart3, (uint8_t*)tx_buf, strlen(tx_buf));
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
}
HAL_TIM_Base_Stop_IT(&htim2);
}
else if(htim->Instance == TIM5) {
GPIO_PinState STATE_SW = HAL_GPIO_ReadPin(STATE_SW_GPIO_Port, STATE_SW_Pin);
if(is_door_open == 1) {
if(STATE_SW == 0) {
is_door_really_open = 1;
sprintf(tx_buf, "\n\r문이 열렸습니다.\n\r");
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
}
else {
sprintf(tx_buf, "\n\r문이 열리지 않았습니다.\n\r");
}
}
else {
if(STATE_SW == 1) {
is_door_really_open = 0;
sprintf(tx_buf, "\n\r문이 닫혔습니다.\n\r");
is_door_lock = 1; // 문 잠금장치 ON
TIM4->CCR1 = 499;
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
}
else {
sprintf(tx_buf, "\n\r문이 닫히지 않았습니다.\n\r");
}
}
HAL_UART_Transmit_IT(&huart3, (uint8_t*)tx_buf, strlen(tx_buf));
HAL_TIM_Base_Stop_IT(&htim5);
}
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
static uint32_t last_interrupt_time; // 값 유지, 자동으로 0으로 초기화
uint32_t current_time = HAL_GetTick();
if((current_time - last_interrupt_time) < DEBOUNCE_DELAY) {
return;
}
last_interrupt_time = current_time;
chk = 1; // 스캔 중단하고 눌린 키의 상태 확인
if(GPIO_Pin == KEYPAD1_Pin) {
if(state == STATE2) num = '1';
else if(state == STATE3) num = '2';
else if(state == STATE1) num = '3';
}
else if(GPIO_Pin == KEYPAD2_Pin) {
if(state == STATE2) num = '4';
else if(state == STATE3) num = '5';
else if(state == STATE1) num = '6';
}
else if(GPIO_Pin == KEYPAD3_Pin) {
if(state == STATE2) num = '7';
else if(state == STATE3) num = '8';
else if(state == STATE1) num = '9';
}
else if(GPIO_Pin == KEYPAD4_Pin) {
if(state == STATE2) num = '*';
else if(state == STATE3) num = '0';
else if(state == STATE1) num = '#';
}
if(is_door_open == 1 && GPIO_Pin == SETUP_SW_Pin) {
setup_pw = 1;
pw_index = 0;
sprintf(tx_buf, "\n\r새 비밀번호를 입력하세요.\n\r");
HAL_UART_Transmit(&huart3, (uint8_t*)tx_buf, strlen(tx_buf), 100);
}
if(GPIO_Pin == KEYPAD1_Pin || GPIO_Pin == KEYPAD2_Pin || GPIO_Pin == KEYPAD3_Pin || GPIO_Pin == KEYPAD4_Pin) {
if(setup_pw == 1) {
if(num != '*') {
if(pw_index < 8) {
new_pw[pw_index++] = num;
sprintf(tx_buf, "%c", num);
HAL_UART_Transmit_IT(&huart3, (uint8_t*)tx_buf, strlen(tx_buf));
}
else {
sprintf(tx_buf, "\n\r다시 입력하세요.\n\r");
HAL_UART_Transmit_IT(&huart3, (uint8_t*)tx_buf, strlen(tx_buf));
pw_index = 0;
memset(new_pw, 0, sizeof(new_pw));
}
}
else {
new_pw[pw_index] = '\0';
strcpy(password, new_pw); // 기존 비밀번호를 새 비밀번호로 교체
sprintf(tx_buf, "\n\r비밀번호가 설정되었습니다.\n\r");
HAL_UART_Transmit(&huart3, (uint8_t*)tx_buf, strlen(tx_buf), 100);
setup_pw = 0; // 비밀번호 설정 모드 비활성화
pw_index = 0;
}
}
else {
if(num != '*') {
if(pw_index < 8) {
input_pw[pw_index++] = num;
sprintf(tx_buf, "%c", num);
HAL_UART_Transmit_IT(&huart3, (uint8_t*)tx_buf, strlen(tx_buf));
}
else {
sprintf(tx_buf, "\n\r비밀번호가 틀렸습니다.\n\r");
HAL_UART_Transmit_IT(&huart3, (uint8_t*)tx_buf, strlen(tx_buf));
pw_index = 0;
memset(input_pw, 0, sizeof(input_pw));
}
}
else {
input_pw[pw_index] = '\0';
if(strcmp(input_pw, password) == 0) {
is_door_lock = 0; // 문 열림 활성화
HAL_TIM_Base_Start_IT(&htim2);
TIM4->CCR1 = 1499;
sprintf(tx_buf, "\n\r비밀번호가 맞았습니다.\n\r");
}
else {
pw_index = 0;
memset(input_pw, 0, sizeof(input_pw));
sprintf(tx_buf, "\n\r비밀번호가 틀렸습니다.\n\r");
}
HAL_UART_Transmit_IT(&huart3, (uint8_t*)tx_buf, strlen(tx_buf));
pw_index = 0;
}
}
}
if(GPIO_Pin == STATE_SW_Pin) {
GPIO_PinState STATE_SW = HAL_GPIO_ReadPin(STATE_SW_GPIO_Port, STATE_SW_Pin);
if(is_door_lock == 0) { // 문 잠금장치 OFF
if(STATE_SW == 0) { // 토글스위치 ON
is_door_open = 1; // 문 열림
HAL_TIM_Base_Start_IT(&htim5);
}
else { // 토글스위치 OFF
is_door_open = 0; // 문 닫힘
HAL_TIM_Base_Start_IT(&htim5);
}
HAL_UART_Transmit_IT(&huart3, (uint8_t*)tx_buf, strlen(tx_buf));
}
else { // 문 잠금장치 ON
sprintf(tx_buf, "\n\r문이 잠겨있습니다.\n\r");
HAL_UART_Transmit_IT(&huart3, (uint8_t*)tx_buf, strlen(tx_buf));
}
}
if(GPIO_Pin == DOOR_SW_Pin) {
is_door_lock = !is_door_lock;
if(is_door_lock == 0) {
HAL_TIM_Base_Start_IT(&htim2);
TIM4->CCR1 = 1499;
sprintf(tx_buf, "\n\rDOOR_SW ON\n\r");
}
else {
TIM4->CCR1 = 499;
sprintf(tx_buf, "\n\rDOOR_SW OFF\n\r");
}
HAL_UART_Transmit_IT(&huart3, (uint8_t*)tx_buf, strlen(tx_buf));
}
}
/* USER CODE END 4 */
[처음부터 다시 시작 = NewDoorLock]
0. UART 디버깅 명령어
1. 인터럽트란
(주로 입/출력)하드웨어 장치의 처리, 혹은 예외 상황의 처리가 필요할 때 CPU에게 알려 이를 처리할 수 있도록 하는 일종의 신호다. CPU에 인터럽트 신호가 들어오면, CPU는 현재 수행 중인 작업을 멈추고 즉시 인터럽트를 처리하기 위한 루틴(ISR = Interrupt Service Routine)이 있는 곳으로 제어권을 넘겨 해당 인터럽트를 처리한다. 이후 처리가 완료되면 CPU는 원래 수행하고 있던 동작을 재개한다.
인터럽트가 발생하면 반드시 제어권을 적절한 ISR에게 넘겨야 한다. 일반적으로 ISR을 가리키는 포인터 테이블(배열)을 사용하여 인터럽트를 빠르게 처리하는데, 보통 이 포인터 테이블은 메모리의 낮은 주소에 위치한다. ISR들의 주소에 대한 배열(테이블)을 인터럽트 벡터(Interrupt Vector)라고 하는데, 인터럽트 벡터는 인터럽트 요청에 포함된 고유 숫자(id)로 해당 인터럽트를 인덱싱하여 이 인터럽트를 처리하는 ISR의 주소를 제공한다.
이후 인터럽트 처리가 끝나면 원래 수행하던 상태로 되돌아와야 하므로 인터럽트가 발생한 당시의 상태를 반드시 어딘가에 저장해야만 한다. 프로세스의 상태를 저장함으로써, 인터럽트를 처리하고 나면 다시 원래 상태로 돌아와서 마치 아무 일도 일어나지 않은 것처럼 기존의 동작을 이어서 수행할 수 있다.
인터럽트와 콜백은 다르다. 인터럽트는 계속해서 실행되고, 콜백은 특정 이벤트나 작업 완료 시 호출되는 함수
따라서 도어락에서 인터럽트를 사용할 때 콜백함수는
2. 키패드 polling방식
4개 행 = input, 3개 열 = output
USART3 활성화(Asynchronous) → PD9 = USART3_RX, PD8 = USART3_TX 설정
GPIO: PF12(KEYPAD1), PF13(KEYPAD2), PF14(KEYPAD3), PF15(KEYPAD4) = Input mode, Pull-up
PB6(KEYPAD5), PB2(KEYPAD6), PG0(KEYPAD7) = High(GPIO output level), Output Push Pull, No Pull-up and no pull-down
HAL_GetTick()을 사용하여 10ms마다 state를 변경하는 동안 4개 행의 상태를 읽어서 data에 비트연산하여 값을 넣는다. data 값이 0x0F이면 아무것도 눌리지 않은 상태이고, 0x0F가 아니면 4개 중 하나가 눌린 상태이다. 키패드 1, 2, 3, 4, 5, 6, 7, 8, 9, *, 0, #을 table 배열로 순서는 연산하기 편하게 만든다. 키가 눌렸을 경우 data 값을 반전하고 반전한 값을 0x0F와 &연산하면 1, 2, 4, 8을 뽑아낼 수 있다. 비트를 이동연산하여 1, 2, 4, 8의 지수 0, 1, 2, 3을 뽑아내고, table 인덱스를 계산하여 1, 2, 3, 4, 5, 6, 7, 8, 9, *, 0, #을 뽑아낼 수 있다. 키가 눌렸을 때의 디바운스 처리도 HAL_GetTick()을 사용해서 50ms 딜레이를 둔다. 키를 누르고 있으면 그 키에 해당하는 값이 계속해서 출력되는 것을 막기 위해 chk 상태변수를 둔다.
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
/* USER CODE END Includes */
/* USER CODE BEGIN PD */
#define STATE1 2
#define STATE2 0
#define STATE3 1
/* USER CODE END PD */
/* USER CODE BEGIN PV */
uint8_t state = STATE1;
GPIO_PinState KEYPAD1, KEYPAD2, KEYPAD3, KEYPAD4, KEYPAD5, KEYPAD6, KEYPAD7;
uint8_t data = 0;
uint8_t table[] = {'1', '4', '7', '*', '2', '5', '8', '0', '3', '6', '9', '#'};
char tx_buf[10];
uint32_t last_key_time = 0;
uint8_t chk = 0;
/* USER CODE END PV */
/* USER CODE BEGIN 1 */
uint32_t current_time = HAL_GetTick();
/* USER CODE END 1 */
/* USER CODE BEGIN WHILE */
while (1)
{
if(HAL_GetTick() - current_time >= 10) { // 10ms마다 state 변경
current_time = HAL_GetTick();
if(chk == 0) {
if(state == STATE1) {
KEYPAD5 = 0; KEYPAD6 = 1; KEYPAD7 = 1;
state = STATE2;
}
else if(state == STATE2) {
KEYPAD5 = 1; KEYPAD6 = 0; KEYPAD7 = 1;
state = STATE3;
}
else if(state == STATE3) {
KEYPAD5 = 1; KEYPAD6 = 1; KEYPAD7 = 0;
state = STATE1;
}
HAL_GPIO_WritePin(KEYPAD5_GPIO_Port, KEYPAD5_Pin, KEYPAD5);
HAL_GPIO_WritePin(KEYPAD6_GPIO_Port, KEYPAD6_Pin, KEYPAD6);
HAL_GPIO_WritePin(KEYPAD7_GPIO_Port, KEYPAD7_Pin, KEYPAD7);
}
KEYPAD1 = HAL_GPIO_ReadPin(KEYPAD1_GPIO_Port, KEYPAD1_Pin);
KEYPAD2 = HAL_GPIO_ReadPin(KEYPAD2_GPIO_Port, KEYPAD2_Pin);
KEYPAD3 = HAL_GPIO_ReadPin(KEYPAD3_GPIO_Port, KEYPAD3_Pin);
KEYPAD4 = HAL_GPIO_ReadPin(KEYPAD4_GPIO_Port, KEYPAD4_Pin);
data = 0;
data |= (KEYPAD1 << 0) | (KEYPAD2 << 1) | (KEYPAD3 << 2) | (KEYPAD4 << 3);
if(data != 0x0F && chk == 0) {
if((HAL_GetTick() - last_key_time) > 50) {
last_key_time = HAL_GetTick();
uint8_t index = -1;
uint8_t exponent = 0;
data = (~data & 0x0F); // 반전한 값을 0x0F와 &연산해서 1, 2, 4, 8 뽑아냄
while(data > 1) { // 지수 0, 1, 2, 3 뽑아냄
data >>= 1;
exponent++;
}
index = state * 4 + exponent;
sprintf(tx_buf, "%c\n\r", table[index]);
HAL_UART_Transmit(&huart3, (uint8_t*)tx_buf, strlen(tx_buf), 100);
}
chk = 1;
}
else if(data == 0x0F && chk == 1) {
chk = 0;
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
3. 24c256 EEPROM I2C 사용
(1) EEPROM 적용 예제 실행
(참고: https://www.micropeta.com/video66)
I2C1 활성화 → standard mode(100KHz), PB9 = I2C1_SDA, PB8 = I2C1_SCL 설정, Pull-up 설정
라이브러리 추가할 필요없이 EEPROM 적용할 수 있다.
디버그로 run해서 while문 안에 breakpoint 걸어놓고 resume하여 toWrite와 toRead에 값이 들어갔는지 확인한다. (Live Expressions에서 값이 들어갔는지 확인도 가능)
EEPROM은 쓰고 읽는 데 시간이 걸리기 때문에 HAL_Delay()를 write과 read 사이 중간중간 넣어줘야 read가 된다.
/* USER CODE BEGIN PV */
#define EEPROM_ADDR 0xA0
uint8_t toWrite1[] = "English is a WestGermanic language of the IndoEuropean language";
uint8_t toWrite2[] = "The STM32 family of 32-bit microcontrollers based on Arm Cortex";
uint8_t toWriteA[] = "Hello";
uint8_t toWriteB[] = "Hi";
uint8_t toWriteM[] = "Microcontroller is a compressed micro computer";
uint8_t toWriteL[] = "EEPROM stands for electrically erasable programmable readonly M";
uint8_t toRead1[64];
uint8_t toRead2[64];
uint8_t toReadA[5];
uint8_t toReadB[2];
uint8_t toReadM[64];
uint8_t toReadL[64];
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
// HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress,
// uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDR, 0, 2, toWrite1, sizeof(toWrite1), 1000);
HAL_Delay(10);
HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDR, 64, 2, toWrite2, sizeof(toWrite2), 1000);
HAL_Delay(10);
HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDR, 129, 2, toWriteA, sizeof(toWriteA), 1000);
HAL_Delay(10);
HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDR, 189, 2, toWriteB, sizeof(toWriteB), 1000);
HAL_Delay(10);
HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDR, 16000, 2, toWriteM, sizeof(toWriteM), 1000);
HAL_Delay(10);
HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDR, 32704, 2, toWriteL, sizeof(toWriteL), 1000);
HAL_Delay(10);
// HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress,
// uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_I2C_Mem_Read(&hi2c1, EEPROM_ADDR, 0, 2, toRead1, sizeof(toRead1), 1000);
HAL_I2C_Mem_Read(&hi2c1, EEPROM_ADDR, 64, 2, toRead2, sizeof(toRead2), 1000);
HAL_I2C_Mem_Read(&hi2c1, EEPROM_ADDR, 129, 2, toReadA, sizeof(toReadA), 1000);
HAL_I2C_Mem_Read(&hi2c1, EEPROM_ADDR, 189, 2, toReadB, sizeof(toReadB), 1000);
HAL_I2C_Mem_Read(&hi2c1, EEPROM_ADDR, 16000, 2, toReadM, sizeof(toReadM), 1000);
HAL_I2C_Mem_Read(&hi2c1, EEPROM_ADDR, 32704, 2, toReadL, sizeof(toReadL), 1000);
/* USER CODE END 2 */
0xA0은 EEPROM 내부 주소 (A0, A1, A2 있음), 256bits = 32k
uint16_t MemAddress = 내가 지정하는 주소, uint16_t MemAddSize = 1이면 1바이트 주소 사용(0x00~0xFF, 256개의 주소) 2이면 2바이트 주소 사용(0x0000~0xFFFF, 65,536개의 주소)
(2) EEPROM 사용하여 비밀번호 저장
EEPROM은 덮어쓰기 형식으로 초기화 = 0xFFFF
전원을 켜면 0xFFFF로 초기화한 후 지정해 놓은 임시 비밀번호 0x1234로 변경된다.
오실로스코프로 EEPROM 데이터 값 들어가는지 확인해보면
I2C 통신 → SCL = 클락(100KHz, standard mode), SDA = 데이터
SDA라인을 보면 처음에 EEPROM의 주소 0xA0(10100000)가 나오고, 그 다음에는 2개 byte(0xFFFF, 16bit = 00000000 00000000)가 나오고, 그 다음에는 내가 집어넣은 데이터 12345678이 순서대로 나오는데, 1은 아스키코드(16진수)로 0x31이므로 00110001으로 나오고, 2는 0x32 = 00110010, 3은 0x33 = 00110011 ... 이런 식으로 데이터가 나오는 것을 확인할 수 있다.
진행 중