어제 과제로 진행한 Button 또는 UART로 STM32보드를 동작시키기에서 입력이 약간씩 Delay되어 출력되는 현상이 있었습니다. 그리하여, HAL_GetTick이라는 1ms간격으로 1씩 증가하는 함수를 활용하여 총, 100ms 간격으로 동작하도록 수정해 주었습니다. 이렇게 되면, 버튼 입력에 대한 정보를 더 정확히 출력할 수 있습니다.
버튼을 개별적으로 동작하면 상관 없지만, 저희는 UART를 활용할 예정이기 때문에 아래의 코드와 같이 구현합니다. HAL_UART_Recieve 함수를 통해 값을 입력 받도록 하는데, Timeout을 1로 설정하였습니다. 동작에는 문제가 없지만, 항상 1ms를 기다려야 하기 때문에 CPU에게 있어 큰 문제가 됩니다.
그래서, 이를 해결하기 위해 UART 동작을 수행할 때만 발생하도록 인터럽트 설정을 진행해야 합니다. UART 인터럽트 동작을 위한 설정은 아래와 같습니다.
Connectiviy -> USART2 -> NVIC Setting -> Global Interrupt Enable
인터럽트 발생 시, UART의 Callback 함수를 불러 올 수 있도록 설계를 해줍니다. Listener의 UART Callback 함수는 UART에 대한 인터럽트 발생 시에 동작하도록 합니다.
아래의 코드는 UARTCallback 함수로, 인터럽트 발생 시 Interrupt를 Enable하고 flag를 1로 변경하게 됩니다.
그러면 이 flag의 값에 따라, Check UART의 함수가 실행이 되고 인터럽트에 따른 UART의 정상 동작이 수행됩니다.
**특징** :
HAL_UART_Receive_IT(&huart2, &rcvData, 1);는 한번의 인터럽트 동작 후, 자동으로 종료됩니다.
그리하여, Init에서 함수를 활용하여 한번 Interrupt 실행 및 등록시켜 준 후, 추후 시스템에서 UART Interrupt가 계속 발생할 수 있도록 CallBack 함수에서도 지속적으로 실행시켜 줍니다.
QUEUE : FIFO (First In First Out) 구조 VS Stack : LIFO (Last In First Out)
Queue : IN /OUT에 대한 두개의 포인터가 배열을 돌아가며 Write/ Read 동작을 수행
- enQueue (push) 입력 : "rear" 포인터가 1씩 증가하며 index를 순회
- deQueue (pop) 출력 : "front" 포인터가 1씩 증가하며 index를 순회
FIFO 구조에서 읽기 포인터인 front와 쓰기 포인터인 rear의 위치가 동일하다면, Queue 구조가 Empty 상황임을 알 수 있습니다. (쓰고, 읽기가 된 상태이기 때문에 값이 Queue에서 출력되고 없는 상태). 또한, front가 rear보다 1개 더 앞서 있다면 모두 읽은 상태이기 때문에 배열이 FULL 된 상황임을 알 수 있습니다.
Empty : front == rear , Full : front == (rear + 1) % (총 index)
- EnQueue : Full 상태인지 확인, Full 상태가 아니면 저장수행 Full이면 저장안함
1. 메모리에 저장
2. rear index 1 증가 % (총 index)
3. 1 과 2를 반복
- DeQueue : Empty 상태인지 확인, Empty 상태가 아니면 dequeue 수행 / Empty이면 dequeue 안함
1. front index가 지정한 메모리 위치 출력
2. front index가 1 증가 (총 index)
3. 1과 2를 반복
이를 코드로 구현하면 아래와 같습니다. 위에 기술되어 있는 내용을 그대로 작성하여 설계합니다. peekQueue 함수의 경우는 Queue 내부의 처음 값을 확인하기 위한 함수입니다.
Queue 구현 코드 :
https://github.com/Heeju99/embedded/blob/main/250625_TimeWatch_StopWatch_UART/Core/ap/src/Queue.c
embedded/250625_TimeWatch_StopWatch_UART/Core/ap/src/Queue.c at main · Heeju99/embedded
Contribute to Heeju99/embedded development by creating an account on GitHub.
github.com
이를 Listener에서 기존에 flag를 사용하여 UART를 사용했는데, Queue 구조를 통해 입력값을 Write/ Read하며 UART 동작을 수행하도록 합니다. Queue 구조로 되어있기 때문에 "SC" 라는 값을 입력하면, S와 C가 차례로 입력되어 실행되므로 Stop + Clear의 동작을 수행할 수 있습니다.
그동안 만들어온 Layer에 LCD를 추가해 보겠습니다. LCD의 두에 쪽보드가 포함되어 I2C 통신을 통해 LCD를 제어합니다.
부품 : I2C lcd 1602
PB8과 PB9가 Display와의 I2C 통신을 위한 SCL, SDA 선이므로, 이를 추가합니다.
I2C LCD 1602의 Schematic은 다음과 같습니다.
PCF8574는 8bit I2C Port를 지니고 있고, 나머지 핀들은 LCD와 연결되어 있음을 확인할 수 있습니다.
PCF8574의 Datasheet를 확인하면 Slave Address가 0100A2A1A0로 되어 있는데, Schematic을 확인하면 VCC와 연결되어 있음을 확인할 수 있습니다. 즉 Pull-up 저항이 달려 있는 셈이죠. 그래서 기본 상태에서는 0100111(0x27)의 Address를 갖습니다.
주소를 바꾸기 위해서는 A2, A1, A0을 쇼트시켜 값을 변화해 주소를 변경할 수 있습니다.
LCD Write Mode Timing :
- RS는 내부 명령어로 RS = 0 : Command, RS = 1 : Data(글자)
- RW = 0 : Write Mode, RW =1 : Read Mode
- E가 Falling Edge일 때 저장이 되어 LCD에 Data가 적용이 됩니다.
4bit, 8bit 모드가 있는데 4bit 데이터 전송 모드에서는 상위 4bit가 먼저 전송이되고, 하위 4bit가 전송이 됩니다.
ㄴ ex. 10101100 => 1010 send + 1100 send
LCD Datasheet 참고 :
https://www.waveshare.com/datasheet/LCD_en_PDF/LCD1602.pdf
LCD를 시작하기 위해서는 일련의 준비과정이 필요합니다. 4bit와 8bit의 데이터 모드가 존재하는데 저희는 4bit모드에 대한 Initialization Sequence를 살펴 봅니다.
1. 파워 인가
2. 15ms 대기 -> HAL_Delay() 활용
3. RS = 0, R/W = 0 -> Command Mode & Write Mode, 0x30이라는 상위 니블을 작성하라 !
4. 4.1ms 대기 -> HAL_Delay() 활용
5. RS = 0, R/W = 0 -> Command Mode & Write Mode, 0x30이라는 상위 니블을 작성
6. 100us 대기 -> HAL_Delay() 활용
7. RS = 0, R/W = 0 -> Command Mode & Write Mode, 0x30이라는 상위 니블을 작성
8. Function Set: 0x38
9. Display OFF: 0x08
10. Display Clear: 0x01
11. Entry Mode Set: 0x06 입력
12. Display ON: 0x0C 입력
이러한 과정을 거치며 LCD를 사용할 준비를 하게 되는 것 입니다.
그래서, 이를 LCD가 시작하기 위해 LCD_Init()으로 구현하여 ap가 시작될 때 마다 실행되어 LCD를 사용할 수 있도록 합니다.
아래의 함수들은 8bit의 Data를 4bit씩 나누기 위한 함수 입니다. Enable(E)의 값이 High에서 Low로 떨어질때 데이터가 전송이 되므로, 처음에 상위 4bit (nibble)를 보냅니다. 일련의 과정을 한번 더 진행하여 하위 4bit도 전송하여 총 8bit의 데이터를 처리합니다.
아래의 코드는 RS신호에 따라 Command Mode, charMode(data 출력) 모드를 선택할 수 있게 합니다. 0일때 cmdMode, 1일때 charMode입니다. 또한 R/W신호의 값에 따라 0일 경우에는 Write Mode를 진행하도록 합니다.
위에서 설명했던 모드 설정, Data 작성의 함수들을 묶어 한번에 값을 Write하도록 하였습니다. writeCmdData 함수는 Command를 입력하기 위해 묶어놓은 함수, writeCharData는 하나의 문자를 받아 출력, writeString은 문자열을 출력하도록 합니다.
또한, LCD는 2행 16열로 총 32자를 표현할 수 있기 때문에, 이를 함수로 만들어 주었습니다. 이를 통해, LCD의 1행과 2행에 원하는 문자나 문자열을 출력할 수 있습니다. gotoXY 함수를 통해 커서를 이동시키고, 해당 위치에 writeString하여 출력하고자 하는 문자를 출력합니다.
이 함수를 활용해서 Watch와 StopWatch의 값을 LCD로 출력해 보겠습니다. Sequence 구조에서 LCD는 Presenter(출력부)에 해당하기 때문에 Presenter.c에서 사용합니다. 또한, StopWatch와 Watch의 Data 값을 Controller로부터 받아와서 실시간으로 들어오는 시간 Data를 LCD에 출력합니다. (UART를 통해 모니터에 값을 출력하는 함수를 참고하였습니다.)
그리고 이를, ap_main의 while 조건을 통해 계속 출력을 하게 됩니다. 위에서 생성한 함수에 timeWatch와 Watch 구조체의 msec, sec, min, hour의 값들을 불러와 LCD에 출력
LCD 코드 :
https://github.com/Heeju99/embedded/blob/main/250625_TimeWatch_StopWatch_UART/Core/driver/LCD/Lcd.c
embedded/250625_TimeWatch_StopWatch_UART/Core/driver/LCD/Lcd.c at main · Heeju99/embedded
Contribute to Heeju99/embedded development by creating an account on GitHub.
github.com
ap_main 코드 :
https://github.com/Heeju99/embedded/blob/main/250625_TimeWatch_StopWatch_UART/Core/ap/src/ap_main.c
embedded/250625_TimeWatch_StopWatch_UART/Core/ap/src/ap_main.c at main · Heeju99/embedded
Contribute to Heeju99/embedded development by creating an account on GitHub.
github.com
동작 영상 :
LCD에 StopWatch/Watch의 실시간 값이 잘 출력되는 모습과 StopWatch의 Run/ Stop /Clear 시 출력되는 시간값이 LCD에 잘 출력되는 모습입니다.
'[HARMAN] 세미콘 아카데미 > Embedded' 카테고리의 다른 글
[Harman 세미콘 아카데미] Day_92(Embedded) (0) | 2025.06.24 |
---|---|
[Harman 세미콘 아카데미] Day_91(Embedded) (0) | 2025.06.23 |
[Harman 세미콘 아카데미] Day_90(Embedded) (0) | 2025.06.20 |
[Harman 세미콘 아카데미] Day_89(Embedded) (0) | 2025.06.19 |
[Harman 세미콘 아카데미] Day_88(Embedded) (0) | 2025.06.18 |