1. RTC(Real Time Clock)
: 현재 시간을 유지시켜주는 컴퓨터 시계
DS1307 RTC 모듈에는 아래와 같은 소자가 있다.
- CR2032 : 수은 전지
- 24C32 : 32kBit EEPROM
DS1307(RTC)
좌) 연도가 뒷자리 00 ~ 99만 저장되기 때문에 앞 2자리는 사용자가 알아서 판단해야하며 2100년과 2000년을 구분할 수 있는 기능은 없다.
우) DS1307은 BCD(Binary coded decimal)로 설계되어 있다.
- 0000 0000 == 0
- 0000 0001 == 01
- 0000 1000 == 08
- 0000 1001 == 09
- 0001 0000 == 10
→ 이런식으로 4bit가 1의 자리, 다음 4bit가 10의 자리
- Slave Address : 1101 000x (x = R/W bit)
- 8bit 표현 : 0b1101 0000(=D0)
- 7bit 표현 : 0b0110 1000(=68)
- 데이터를 읽기 위해서는 Slave Address를 먼저 전송하고 데이터를 읽어야함
I2C 동작 flow
: Slave Address(write 모드) → word Address → Slave Address(read 모드) → 데이터 읽기
- word Address : 레지스터에 저장된 값(시,분,초 등) 중 어떤 값에 접근할지 주소(00 ~ 3F)
24C32(32kBit EEPROM)
[ROM 종류]
- ROM : 읽기 전용(사용자가 데이터를 저장할 수 없음)
- PROM : 사용자가 데이터를 한번만 저장할 수 있음, 이후 읽기만 가능한 ROM
- EPROM : 데이터를 지울 수 있는 ROM
- EEPROM : 전기 신호로 데이터 지울 수 있는 ROM
- FLASH : 블록 단위로 데이터를 지우고 쓸 수 있는 메모리
- Control Byte(1Byte) : Slave Address(장치 주소)
- Address Byte(2Byte) : 메모리에 저장된 값의 주소
1-1. 주소 확인하기
printf("I2C Address scan start\n");
for(int address = 0; address < 256; address++)
{
if(HAL_I2C_IsDeviceReady(&hi2c1, address, 0, 10) == HAL_OK)
printf("%02x is ready\n", address);
}
- 4e, 4f : LCD Slave Address(R/W)
- a0, a1 : 24C32 Slave Address(R/W)
- d0, d1 : DS1307(RTC) Slave Address(R/W)
1-2. 초, 분 읽어오기
- RTC enable 위해 CH bit 초기화
→ CH bit를 0으로 초기화해야 동작
- 초 데이터 register에 접근 (write)
- 초, 분 데이터 read
/* USER CODE BEGIN WHILE */
uint8_t buffer[2] = {0, 0};
// CH bit clear to 0 for enable
HAL_I2C_Master_Transmit(&hi2c1, 0xd0, buffer, 2, 10);
// 0xd0 , 0(word address), 0(write data) -> CH bit clear to 0
// 0xd0 : slave address
// word address : memory address
while (1)
{
uint8_t buffer[2] = {0, 0};
// I2C data transmit(mater -> slave)
HAL_I2C_Master_Transmit(&hi2c1, 0xd0, buffer, 1, 10);
// 0xd0 : write mode
// 2Byte transmit : Address(1Byte) + Data(buffer 1Byte)
// 0xd0, 0(word address) transmit -> sec data register에 접근
HAL_I2C_Master_Receive(&hi2c1, 0xd1, buffer, 2, 10);
// 0xd1 : read mode
// 1Byte transmit + 2Byte data receive(data save in buffer)
// sec, min data read
printf("%02x, %02x\n", buffer[0], buffer[1]);
// print sec, min
HAL_Delay(500);
/* USER CODE END WHILE */
}
코드 리뷰
1)
`HAL_I2C_Master_Transmit(&hi2c1, 0xd0, buffer, 2, 10);`
- I2C 송신 함수
- 0xd0 , 0x00(word address), 0x00(write data) → CH bit clear to 0
- datasheet에서 Data Write protocol과 같이 Slave Address → Word Address → Data(0) → Data(1) …. 순서로 전송됨
- 0xd0 : Slave Address
- buffer : Word Address를 포함한 데이터가 저장되어 있는 포인터 변수
- 2 : 보낼 데이터의 Byte 크기
- 실제 전송되는 데이터는 2byte가 아니라 함수에서 자동으로 Slave Address를 먼저 전송하기 때문에 3byte가 전송된다.
- 10 : Wait time
⇒ 4번째 매개변수가 2이므로 buffer[0]과 buffer[1]이 전송됨
buffer[0] = Word Address
buffer[1] = Data(0)
→ 즉, buffer[0] 메모리 주소에 접근하여 buffer[1]을 저장
`HAL_I2C_Master_Receive(&hi2c1, 0xd1, buffer, 2, 10);`
- I2C 수신 함수
- 수신 함수이지만 I2C Data Read protocol에 따라 Slave Address 1byte 전송 후 데이터를 수신함(이 함수에서는 2byte 수신)
- 데이터를 수신하여 buffer에 저장
uint8_t buffer[2] = {0, 0};
HAL_I2C_Master_Transmit(&hi2c1, 0xd0, buffer, 1, 10);
HAL_I2C_Master_Receive(&hi2c1, 0xd1, buffer, 2, 10);
위와 같이 코드를 작성하면 `0xd0` Slave Address의 `00` Word Address(초 데이터 저장되어 있는 레지스터 주소)에 접근( Transmit)하여 2byte의 데이터를 읽어 buffer에 저장(Receive)
⇒ Q. 초 데이터는 1byte인데 2byte를 읽으면 어떻게 되는가?
Datasheet에 따르면 레지스터 포인터는 Read/Write 이후 자동으로 증가하기 때문에 초 데이터를 읽고 다음 레지스터 주소에 저장된 값인 분 데이터를 읽는다.
⇒ buffer[0] : 초 데이터 저장, buffer[1] : 분 데이터 저장
1-3. BCD ↔ 10진수 변환
- RTC는 데이터를 BCD 저장
- 데이터를 사용자가 읽기 편하도록 BCD → 10진수 변환
- RTC에 데이터를 저장할 때 10진수 → BCD 변환
/* USER CODE BEGIN 0 */
// BCD -> 10진수
uint8_t BCD2Decimal(uint8_t inData)
{
uint8_t upper = inData >> 4; // 상위 비트 저장
uint8_t lower = inData & 0x0f; // 하위 비트 저장
return upper * 10 + lower; // 상위 비트 : 10의 자리, 하위 비트 : 1의 자리
}
// 10진수 -> BCD
uint8_t Decimal2BCD(uint8_t inData)
{
uint8_t upper = inData / 10; // 10진수 10의 자리를 upper에 저장
uint8_t lower = inData % 10; // 10진수 1의 자리를 lower에 저장
return upper << 4 | lower; // 10의 자리 : 상위 비트, 1의 자리 : 하위 비트
}
/* USER CODE END 0 */
1-4. 연월일시분초 출력
/* Infinite loop */
/* USER CODE BEGIN WHILE */
uint8_t buffer[7] = {0,};
// CH bit clear to 0 for enable
HAL_I2C_Master_Transmit(&hi2c1, 0xd0, buffer, 2, 10);
// 0xd0 , 0(word address), 0(write data) -> CH bit clear to 0
// 0xd0 : slave address
// word address : memory address
while (1)
{
buffer[0] = 0;
// I2C data transmit(mater -> slave)
HAL_I2C_Master_Transmit(&hi2c1, 0xd0, buffer, 1, 10);
// 0xd0 : write mode
// 2Byte transmit : Address(1Byte) + Data(buffer 1Byte)
// 0xd0, 0(word address) transmit -> sec data register에 접근
HAL_I2C_Master_Receive(&hi2c1, 0xd1, buffer, 7, 10);
// 0xd1 : read mode
// 1Byte transmit + 7Byte data receive(data save in buffer)
// 연 월 일 시 분 초 data read
printf("20%02d-%02d-%02d %02d:%02d:%02d\n",
BCD2Decimal(buffer[6]), // year
BCD2Decimal(buffer[5]), // month
BCD2Decimal(buffer[4]), // date
BCD2Decimal(buffer[2]), // hour
BCD2Decimal(buffer[1]), // minute
BCD2Decimal(buffer[0])); // second
// print sec, min
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
1-5. 리셋해도 시간 저장
/* Infinite loop */
/* USER CODE BEGIN WHILE */
uint8_t address = 0;
uint8_t buffer[0x40] = {0,}; // word address = 0x40개
HAL_I2C_Master_Transmit(&hi2c1, 0xd0, &address, 1, 10);
HAL_I2C_Master_Receive(&hi2c1, 0xd1, buffer, 1, 10); // 초 데이터 읽기
buffer[1] = buffer[0] & 0x7f; // 초 데이터를 buffer[1]에 저장 + MSB 1bit 0으로 -> CH 초기화 위해
buffer[0] = 0; // 저장을 시작할 주소
HAL_I2C_Master_Transmit(&hi2c1, 0xd0, buffer, 2, 10);
/* 0xd0 , 0(word address), 0xxx xxxx(write data) -> CH bit clear to 0 + 시작 할 초 데이터 write
*
* write data == buffer[1]
* buffer[1] MSB는 0이기 때문에 CH bit를 초기화 시킴
* 또한 buffer[1]에는 리셋을 누르기 전 초 data가 저장되어 있기 때문에 그 값을 초 레지스터에 저장하여
* RTC의 초 data가 buffer[1]에 저장된 값 부터 시작
* -> 리셋해도 멈추기 전 시간부터 시작
*/
while (1)
{
buffer[0] = 0;
HAL_I2C_Master_Transmit(&hi2c1, 0xd0, &address, 1, 10);
HAL_I2C_Master_Receive(&hi2c1, 0xd1, buffer, 7, 10);
printf("\n20%02d-%02d-%02d %02d:%02d:%02d\n",
BCD2Decimal(buffer[6]), // year
BCD2Decimal(buffer[5]), // month
BCD2Decimal(buffer[4]), // date
BCD2Decimal(buffer[2]), // hour
BCD2Decimal(buffer[1]), // minute
BCD2Decimal(buffer[0])); // second
// char str[200];
// for(int i = 8; i < 0x40; i++)
// {
// sprintf(str, "%s%02x ", str, buffer[i]);
// }
// printf(str);
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
이전 코드와는 다르게 리셋을 해도 버퍼를 초기화 하지 않고 이전 시간 값부터 시작
1-6. 시작 시간 설정
- 최초 동작 시작 시간 코드 구현
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "UART.h"
/* USER CODE END Includes */
/* Private variables ---------------------------------------------------------*/
I2C_HandleTypeDef hi2c1;
UART_HandleTypeDef huart2;
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_I2C1_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE BEGIN 0 */
// BCD -> 10진수
uint8_t BCD2Decimal(uint8_t inData)
{
uint8_t upper = inData >> 4; // 상위 비트 저장
uint8_t lower = inData & 0x0f; // 하위 비트 저장
return upper * 10 + lower; // 상위 비트 : 10의 자리, 하위 비트 : 1의 자리
}
// 10진수 -> BCD
uint8_t Decimal2BCD(uint8_t inData)
{
uint8_t upper = inData / 10; // 10진수 10의 자리를 upper에 저장
uint8_t lower = inData % 10; // 10진수 1의 자리를 lower에 저장
return upper << 4 | lower; // 10의 자리 : 상위 비트, 1의 자리 : 하위 비트
}
#define RTC_ADD 0xD0
#define ROM_ADD 0xA0
#define READ 1
#define MagicNumber 0x93837410 // 값의 의미는 없음
// 처음 사용하는 것인지 아닌지 판별하기 위함
// 메모리에 저장해두고 나중에 이 값이 있으면 처음 사용 X 없으면 처음 사용
// EEPROM map table
#define eeMagicNumberBase 0
#define eeMagicNumberSize 4
typedef struct
{
uint8_t year;
uint8_t month;
uint8_t date;
uint8_t day;
uint8_t hour;
uint8_t min;
uint8_t sec;
}DateTime_t;
// start time data set
void setRTC(DateTime_t inData)
{
uint8_t txBuffer[8];
txBuffer[7] = Decimal2BCD(inData.year);
txBuffer[6] = Decimal2BCD(inData.month);
txBuffer[5] = Decimal2BCD(inData.date);
txBuffer[3] = Decimal2BCD(inData.hour);
txBuffer[2] = Decimal2BCD(inData.min);
txBuffer[1] = Decimal2BCD(inData.sec);
txBuffer[0] = 0; // 시작 주소
HAL_I2C_Master_Transmit(&hi2c1, RTC_ADD, txBuffer, sizeof(txBuffer), 10);
}
// get time data
DateTime_t getRTC()
{
DateTime_t result;
uint8_t rxBuffer[7];
uint8_t address = 0;
HAL_I2C_Master_Transmit(&hi2c1, RTC_ADD, address, 1, 10);
HAL_I2C_Master_Receive(&hi2c1, RTC_ADD | READ, rxBuffer, 7, 10);
result.year = BCD2Decimal(rxBuffer[6]);
result.month = BCD2Decimal(rxBuffer[5]);
result.date = BCD2Decimal(rxBuffer[4]);
result.hour = BCD2Decimal(rxBuffer[2]);
result.min = BCD2Decimal(rxBuffer[1]);
result.sec = BCD2Decimal(rxBuffer[0]);
return result;
}
// I2C RAM R/W
// address = 0x00 ~ 0x37
void writeRAM(uint8_t address, uint8_t data)
{
// uint8_t txBuffer[2];
// txBuffer[0] = address - 8; // sec address
// txBuffer[1] = data;
// HAL_I2C_Master_Transmit(&hi2c1, RTC_ADD, txBuffer, sizeof(txBuffer), 10);
HAL_I2C_Mem_Write(&hi2c1, RTC_ADD, address, 1, &data, 1, 10);
/*
* RTC_ADD : Slave Address
* address : Memory Address
* 1 : Address size -> RTC Memory Address : 1Byte
* &data : read or write 할 data
* 1 : 얼마나 읽을지, 얼마나 입력할지
* 10 : wait time
*/
}
uint8_t readRAM(uint8_t address)
{
uint8_t result;
// uint8_t address2 = address - 8;
// HAL_I2C_Master_Transmit(&hi2c1, RTC_ADD, &address2, 1, 10);
// HAL_I2C_Master_Receive(&hi2c1, RTC_ADD | READ, &result, 1, 10);
HAL_I2C_Mem_Read(&hi2c1, RTC_ADD, address, 1, &result, 1, 10);
// Transmit 함수와 Receive 함수 두개 기능을 모두 수행
}
// EEPROM R/W
void writeEEPROM(uint16_t address, uint8_t data)
{
HAL_I2C_Mem_Write(&hi2c1, ROM_ADD, address, 2, &data, 1, 10);
// EEPROM Memory Address : 2Byte
HAL_Delay(5); // write time cycle on EEPROM datasheet = 5ms
}
uint8_t readEEPROM(uint16_t address)
{
uint8_t result;
HAL_I2C_Mem_Read(&hi2c1, ROM_ADD, address, 2, &result, 1, 10);
return result;
}
void write2ByteEEPROM(uint16_t address, uint8_t data)
{
HAL_I2C_Mem_Write(&hi2c1, ROM_ADD, address, 2, &data, 2, 10);
HAL_Delay(10);
}
uint16_t read2ByteEEPROM(uint16_t address)
{
uint16_t result;
HAL_I2C_Mem_Read(&hi2c1, ROM_ADD, address, 2, &result, 2, 10);
return result;
}
void write4ByteEEPROM(uint16_t address, uint32_t data)
{
HAL_I2C_Mem_Write(&hi2c1, ROM_ADD, address, 2, &data, 4, 10);
HAL_Delay(20);
}
uint32_t read4ByteEEPROM(uint16_t address)
{
uint32_t result;
HAL_I2C_Mem_Read(&hi2c1, ROM_ADD, address, 2, &result, 4, 10);
return result;
}
/* USER CODE END 0 */
int main(void)
{
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
initUart(&huart2);
DateTime_t dateTime;
if(MagicNumber != read4ByteEEPROM(eeMagicNumberBase))
// 같지 않으면 최초 사용이라는 뜻
{
// 초기 설정
dateTime.year = 24;
dateTime.month = 3;
dateTime.date = 29;
dateTime.hour = 14;
dateTime.min = 28;
dateTime.sec = 0;
setRTC(dateTime);
// EEPROM에 MagicNumber 저장
write4ByteEEPROM(eeMagicNumberBase, MagicNumber);
} // 이 부분이 없으면 동작 시킬때마다 시작 시간이 다름
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
dateTime = getRTC();
printf("\n20%02d-%02d-%02d %02d:%02d:%02d\n",
dateTime.year,
dateTime.month,
dateTime.date,
dateTime.hour,
dateTime.min,
dateTime.sec);
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
코드 리뷰
1)
uint8_t readRAM(uint8_t address)
{
uint8_t result;
// uint8_t address2 = address - 8;
// HAL_I2C_Master_Transmit(&hi2c1, RTC_ADD, &address2, 1, 10);
// HAL_I2C_Master_Receive(&hi2c1, RTC_ADD | READ, &result, 1, 10);
HAL_I2C_Mem_Read(&hi2c1, RTC_ADD, address, 1, &result, 1, 10);
// Transmit 함수와 Receive 함수 두개 기능을 모두 수행
}
`HAL_I2C_Master_Receive()` : Slave 장치에서 데이터 읽음
- Slave 장치 주소 전송 + 데이터 read
- 메모리 주소를 전송하지 않기 때문에 특정 메모리의 값을 읽기 위해서는 `HAL_I2C_Master_Transmit()` 함수를 이용하여 메모리 주소를 지정해야함
`HAL_I2C_Mem_Read()` : Slave 장치의 특정 메모리 주소에서 데이터를 직접 읽음
- Slave 장치 주소 전송 + 메모리 주소 전송 + 데이터 read
- 특정 메모리의 값을 읽을 수 있음 ⇒ `Transmit` 함수와 `Receive` 함수 두개 기능을 모두 수행
매개변수
HAL_StatusTypeDef HAL_I2C_Mem_Read(
I2C_HandleTypeDef *hi2c, // I2C 핸들러 주소
uint16_t DevAddress, // Slave Address
uint16_t MemAddress, // Memory Address of Slave device
uint16_t MemAddSize, // Memory Address size(byte)
uint8_t *pData, // Buffer Address to save the read data
uint16_t Size, // read data size(byte)
uint32_t Timeout // MAX wait time(ms)
)
2)
void writeEEPROM(uint16_t address, uint8_t data)
{
HAL_I2C_Mem_Write(&hi2c1, ROM_ADD, address, 2, &data, 1, 10);
// EEPROM Memory Address : 2Byte
HAL_Delay(5); // EEPROM datasheet write time cycle = 5ms
}
위 함수에서 딜레이를 주는 이유는 아래 EEPROM datasheet Write cycle time 때문
3)
#define MagicNumber 0x93837410 // 값의 의미는 없음
// 처음 사용하는 것인지 아닌지 판별하기 위함
// 메모리에 저장해두고 나중에 이 값이 있으면 처음 사용 X 없으면 처음 사용
...
if(MagicNumber != read4ByteEEPROM(eeMagicNumberBase))
// 같지 않으면 최초 사용이라는 뜻
{
// 초기 설정
dateTime.year = 24;
dateTime.month = 3;
dateTime.date = 29;
dateTime.hour = 14;
dateTime.min = 28;
dateTime.sec = 0;
setRTC(dateTime);
// EEPROM에 MagicNumber 저장
write4ByteEEPROM(eeMagicNumberBase, MagicNumber);
} // 이 부분이 없으면 동작 시킬때마다 시작 시간이 다름
`MagicNumber` : 일종의 Key 역할
→ 만일 장치를 처음 동작시킨다면 ROM 메모리에 `MagicNumber` 를 저장하고 시작 시간을 설정함
(이후 동작에서는 if문이 실행되지 않음)
2. Switch
사용한 스위치는 Tact Switch(4-pin Switch)
HW 구성
- 1번 핀 : VCC, 2번 핀 : MCU GPIO(Pull-up setting), 3번 핀 : GND
- Switch ON = 0
- Switch OFF = 1
2-1. 버튼 눌렀을 때 Count UP/DOWN
int main(void)
{
/* USER CODE BEGIN 2 */
initUart(&huart2);
/* USER CODE BEGIN WHILE */
while (1)
{
static int count = 0;
// count up
if(HAL_GPIO_ReadPin(UP_GPIO_Port, UP_Pin) == 0 ) // Pull-up : 버튼 눌렀을 때 0
{
count++;
printf("%d\n", count);
}
// count down
if(HAL_GPIO_ReadPin(DOWN_GPIO_Port, DOWN_Pin) == 0)
{
count--;
printf("%d\n", count);
}
}
}
→ 문제점 : 누르고있으면 계속 UP/DOWN
2-2. 스위치 한번 눌렀을 때 동작 한번만 하는 방법(한번 누르면 1 UP)
1. delay
∘ 버튼 입력 후 delay를 사용함으로써 스위치를 연속으로 입력되지 않게 할 수 있지만
delay동안 다른 동작을 할 수 없기 때문에 가급적 지양해야 함
2. 스위치를 놓을 때까지 기다리는 방법
∘ 스위치를 누르고 있는 동안 반복문에 갇히게 한후 스위치를 떼면 count +1 or -1
int main(void)
{
/* USER CODE BEGIN 2 */
initUart(&huart2);
/* USER CODE BEGIN WHILE */
while (1)
{
static int count = 0;
// count up
if(HAL_GPIO_ReadPin(UP_GPIO_Port, UP_Pin) == 0) // Pull-up : 버튼 눌렀을 때 0
{
// 스위치 누르고 있는 동안 while문 안에 갇힘
while(HAL_GPIO_ReadPin(UP_GPIO_Port, UP_Pin) == 0);
count++;
printf("%d\\n", count);
}
// count down
if(HAL_GPIO_ReadPin(DOWN_GPIO_Port, DOWN_Pin) == 0)
{
while(HAL_GPIO_ReadPin(DOWN_GPIO_Port, DOWN_Pin) == 0);
count--;
printf("%d\\n", count);
}
}
}
→ 문제점 : 채터링 발생하여 오동작 할 때 있음
3. Falling/Rising edge 때에만 동작하도록 하는 방법
∘ 이전 상태와 현재 상태를 비교
∘ Falling edge : 이전 상태 High → 현재 상태 Low로 변할 때
∘ Rising edge : 이전 상태 Low → 현재 상태 High로 변할 때
int main(void)
{
/* USER CODE BEGIN 2 */
initUart(&huart2);
/* USER CODE BEGIN WHILE */
while (1)
{
static int count = 0;
static int stateA, stateB, oldStateA, oldStateB;
stateA = HAL_GPIO_ReadPin(UP_GPIO_Port, UP_Pin);
stateB = HAL_GPIO_ReadPin(DOWN_GPIO_Port, DOWN_Pin);
// edge detection
if(stateA != oldStateA)
{
if(stateA == 0) // Falling edge
{
count++;
printf("%d\n", count);
}
else if(stateA == 1) // Rising edge
{
}
oldStateA = stateA;
}
if(stateB != oldStateB)
{
if (stateB == 0) // Falling edge
{
count--;
printf("%d\n", count);
}
else if (stateB == 1) // Rising edge
{
}
oldStateB = stateB;
}
}
}
→ 문제점 : 2번과 같이 채터링에 대응할 수 없음
4. Systick 사용
∘ 버튼의 상태 정보를 일정 시간 버퍼에 저장하여 채터링이 발생하는 구간인 edge를 무시
∘ 채터링은 edge 구간에서 노이즈가 발생하는 것이기 때문에 일정 시간
같은 상태가 유지되었다는 것은 채터링이 발생하지 않았다는 것을 의미한다.
→ `Systick_Handler()`는 무조건 1ms마다 실행(시스템 클럭과는 상관 X)
/* USER CODE BEGIN 0 */
_Bool stateSwitchA;
_Bool stateSwitchB;
void SystickCallback() // Systick 1ms 마다 실행
{
static uint32_t bufferSwitch[2];
// update buffer
bufferSwitch[0] = bufferSwitch[0] << 1; // LSB == 0
bufferSwitch[1] = bufferSwitch[1] << 1;
/*
* 버퍼의 저장된 값을 left shift
* -> LSB를 0으로 비워둠과 동시에 이전에 저장되어 있던 데이터를 잃지 않음
* -> 비워둔 LSB에 새로운 데이터를 저장
*/
// Save state in LSB
bufferSwitch[0] |= HAL_GPIO_ReadPin(UP_GPIO_Port, UP_Pin);
bufferSwitch[1] |= HAL_GPIO_ReadPin(DOWN_GPIO_Port, DOWN_Pin);
/*
* 버튼 입력 상태 LSB에 저장 -> 왼쪽 1 shift -> ... 반복
* 32ms 동안(buffer 인덱스 하나의 크기:32bit, 1ms마다 update)의 버튼 상태 정보가 버퍼에 저장됨
*/
//judgement buffer
if(bufferSwitch[0] == 0) stateSwitchA = 1; // 버튼 누른지 32ms 지남
if(bufferSwitch[0] == 0xFFFFFFFF) stateSwitchA = 0; // 버튼 뗀지 32ms 지남
if(bufferSwitch[1] == 0) stateSwitchB = 1; // 버튼 누른지 32ms 지남
if(bufferSwitch[1] == 0xFFFFFFFF) stateSwitchB = 0; // 버튼 뗀지 32ms 지남
/*
* bufferSwitch[0] == 0 이라는 의미는 32ms 동안 버튼을 눌렀다는 의미
* -> state = 1 저장
* stateSwitch에 0 or 1이 저장될 때에는 채터링이 발생하는 edge 구간은 무시하고
* 같은 상태가 일정 시간 유지됐을 때에만 저장됨.
* => 채터링 무시
*/
}
/* USER CODE END 0 */
int main(void)
{
/* USER CODE BEGIN 2 */
initUart(&huart2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
static int count = 0;
_Bool oldStateA, oldStateB;
// state detection
if(stateSwitchA != oldStateA)
{
if(stateSwitchA == 0)
{
count++;
printf("%d\n", count);
}
else if(stateSwitchA == 1)
{
}
oldStateA = stateSwitchA;
}
if(stateSwitchB != oldStateB)
{
if (stateSwitchB == 0)
{
count--;
printf("%d\n", count);
}
else if (stateSwitchB == 1)
{
}
oldStateB = stateSwitchB;
}
}
}
Q. 일정 시간(32ms) 동안 스위치를 눌러야 정상적으로 실행되는데, 스위치를 아주 빠르게 눌렀다 뗀다면 정상 작동을 하지 않는 것이 아닌가?
A. 테스트 결과 아무리 빠르게 눌렀다 떼도 32ms 이상 걸리더라.. → 코드 동작에는 문제가 없음
Made By Minseok KIM
'ARM > 1_Study' 카테고리의 다른 글
[ARM] I2C Level Shifter (0) | 2024.04.10 |
---|---|
[ARM] STM32_FND,DHT11 (0) | 2024.04.08 |
[ARM] STM32_LCD I2C, ADC(polling 방식, DMA 방식), CDS (0) | 2024.03.29 |
[ARM] STM32_Radar (0) | 2024.03.24 |
[ARM] STM32_Buzzer, Ulatrasonic, 필터링, ServoMotor (0) | 2024.03.19 |
Let's Be Happy!
도움이 되었으면 좋겠어요 :)