null
vuild_
Nodes
Flows
Hubs
Login
MENU
GO
Notifications
Login
⌂
Embedded C Intermediate — 인터럽트·주변장치·펌웨어 설계 패턴
Structure
interrupt-system
•
인터럽트 개념 — 벡터 테이블과 ISR 등록 원리
•
인터럽트 라우팅 — SRC 레지스터로 코어에 연결하기
•
ISR 작성 규칙 — 재진입 금지, 실행 시간 최소화
•
ISR 공유 데이터 — volatile, 임계구역, 원자 접근
timers
•
타이머 개념 — 카운터, 오버플로, 프리스케일러
•
PWM 생성 — 원리, 듀티사이클, GTM TOM
•
입력 캡처 — 주파수·펄스폭 측정, GTM TIM
serial-communication
•
UART 심화 — FIFO, 오류 핸들링, 인터럽트 기반 송수신
•
SPI 프로토콜 — Master/Slave, CPOL/CPHA, QSPI
•
I2C 프로토콜 — 주소 지정, ACK/NACK, 멀티마스터
adc
•
ADC 기초 — 분해능, 샘플링 레이트, EVADC
•
ADC 변환 모드 — 단일, 연속, 스캔 모드
•
ADC 인터럽트 & DMA — 변환 완료 처리, 자동 수집 패턴
firmware-patterns
•
상태 머신 — FSM 설계, enum + 함수 포인터 테이블
•
링버퍼 — 원형 버퍼 구현, 인터럽트 안전 설계
•
협력형 스케줄러 — 타임 슬롯 기반 직접 구현
Flow Structure
ISR 작성 규칙 — 재진입 금지, 실행 시간 최소화
4 / 16
타이머 개념 — 카운터, 오버플로, 프리스케일러
☆ Star
↗ Full
ISR 공유 데이터 — volatile, 임계구역, 원자 접근
#volatile
#critical-section
#atomic
#tc37x
#ldmst
@devpc
|
2026-04-02 06:39:38
|
GET /api/v1/flows/11/nodes/212?fv=1&nv=1
Context:
Flow v1
→
Node v1
0
Views
3
Calls
# ISR 공유 데이터 — volatile, 임계구역, 원자 접근 ## 문제: ISR과 메인 코드가 같은 변수를 쓴다 ISR과 메인 루프가 동일한 변수를 공유할 때, 컴파일러 최적화와 하드웨어 특성이 맞물려 **예상치 못한 버그**가 발생할 수 있습니다. ``` 메인 루프: ISR: a = 0; a = 1; if (a == 0) { ← 여기서 인터럽트가 끼어들면? do_something(); } ``` --- ## volatile 키워드 ### 역할 컴파일러에게 "이 변수는 언제든 외부에서 바뀔 수 있으니 캐시하지 말고 **매번 메모리에서 직접 읽어라**"고 지시합니다. ### volatile 없을 때 문제 ```c // volatile 없음 uint8_t g_flag = 0; // 메인 루프 while (g_flag == 0) { // 컴파일러가 g_flag를 레지스터에 캐시 // 무한 루프 탈출 불가 (ISR이 g_flag=1 써도 모름) } // ISR void someISR(void) { g_flag = 1; // 메인 루프는 이 변경을 감지 못할 수 있음 } ``` 최적화 레벨이 올라갈수록 이 문제가 더 자주 나타납니다. ### volatile 적용 ```c volatile uint8_t g_flag = 0; // volatile 추가 // 메인 루프 — 이제 매번 메모리에서 읽음 while (g_flag == 0) { // ISR이 g_flag = 1 쓰면 루프 탈출 } ``` ### ISR과 공유하는 변수에는 항상 volatile ```c volatile uint8_t g_rxByte; volatile boolean g_rxReady; volatile uint32_t g_tick1ms; ``` --- ## 임계구역 (Critical Section) volatile만으로는 부족한 경우가 있습니다. **읽기-수정-쓰기(Read-Modify-Write)** 시퀀스 도중에 인터럽트가 끼어들면 데이터가 손상될 수 있습니다. ``` 문제 시나리오 (counter++ 연산): CPU 내부: 1. 메모리에서 counter 값 읽기 (값: 5) ← 여기서 인터럽트 발생, ISR이 counter++ → counter = 6 2. 5 + 1 = 6 계산 3. 6을 메모리에 쓰기 (값: 6, ISR의 증가 소실!) 실제로는 7이어야 하는데 6이 됨 ``` ### 임계구역 보호 방법 — 인터럽트 비활성화 ```c // TC37x / TriCore 방식 uint32 savedIcr = IfxCpu_disableInterrupts(); // 인터럽트 비활성화, 이전 상태 저장 // --- 임계구역 시작 --- g_counter++; // --- 임계구역 끝 --- IfxCpu_restoreInterrupts(savedIcr); // 인터럽트 상태 복원 ``` 인터럽트를 완전히 끄는 대신 **이전 상태를 저장 후 복원**하는 패턴이 중요합니다. 중첩된 임계구역에서도 안전하게 동작합니다. ``` 중첩 임계구역 안전 처리: disableInterrupts() → ICR 저장 (인터럽트 ON 상태였음) disableInterrupts() → ICR 저장 (인터럽트 OFF 상태였음) restoreInterrupts() → OFF 복원 (아직 OFF 유지) restoreInterrupts() → ON 복원 (원래대로 ON) ``` --- ## 원자 접근 (Atomic Access) 임계구역보다 더 효율적인 방법은 **하드웨어 수준의 원자 연산**을 사용하는 것입니다. 원자 연산은 실행 도중 인터럽트가 끼어들 수 없도록 보장합니다. ### TC37x — `__ldmst` 명령어 TriCore는 **LDMST (Load-Modify-Store)** 라는 원자 연산 명령어를 제공합니다. ``` LDMST address, mask_and_value 한 번의 명령으로: 1. 메모리 주소에서 32비트 읽기 2. mask로 특정 비트만 선택 3. 새 값으로 수정 4. 다시 메모리에 쓰기 → 이 4단계가 원자적으로 (나눌 수 없게) 실행됨 ``` ### iLLD에서의 원자 접근 ```c #include "IfxCpu_Intrinsics.h" // 특정 비트를 원자적으로 세트 __ldmst(&g_statusFlags, 0x01, 0x01); // bit 0 세트 // 특정 비트를 원자적으로 클리어 __ldmst(&g_statusFlags, 0x01, 0x00); // bit 0 클리어 // 사용 예: 여러 ISR이 같은 플래그 변수를 공유할 때 ``` ### 일반 MCU에서의 원자 접근 (비교) ARM Cortex-M은 `LDREX/STREX` (exclusive load/store) 명령어를 사용합니다. TriCore의 LDMST와 목적은 같지만 구현 방식이 다릅니다. --- ## 공유 데이터 패턴 정리 ``` 상황 권장 방법 ────────────────────────────────────────────────────── 단순 플래그 읽기/쓰기 volatile 8/16/32비트 단일 값 갱신 volatile (단일 쓰기면 대부분 원자적) 복합 연산 (++, |=, &= 등) 임계구역 또는 __ldmst 여러 변수를 묶어서 갱신 임계구역 (인터럽트 비활성화) 링버퍼 인덱스 갱신 임계구역 또는 원자 연산 ``` --- ## 실전 예제 — 링버퍼 공유 ```c #define BUF_SIZE 64 typedef struct { volatile uint8_t data[BUF_SIZE]; volatile uint16_t head; // ISR이 씀 volatile uint16_t tail; // 메인이 씀 } RingBuffer; RingBuffer g_rxBuf = {0}; // ISR — 수신 데이터 삽입 IFX_INTERRUPT(uartRxISR, 0, ISR_PRIORITY_ASCLIN0_RX) { uint16_t nextHead = (g_rxBuf.head + 1) % BUF_SIZE; if (nextHead != g_rxBuf.tail) { // 버퍼 풀이 아닐 때 g_rxBuf.data[g_rxBuf.head] = IfxAsclin_getReadData(&MODULE_ASCLIN0); g_rxBuf.head = nextHead; // 단일 쓰기 → 대부분 원자적 } } // 메인 루프 — 데이터 꺼내기 uint8_t rxPop(void) { if (g_rxBuf.tail == g_rxBuf.head) return 0; // 비어있음 uint8_t byte = g_rxBuf.data[g_rxBuf.tail]; g_rxBuf.tail = (g_rxBuf.tail + 1) % BUF_SIZE; return byte; } ``` `head`는 ISR만 쓰고, `tail`은 메인만 씁니다. 서로 다른 쪽이 각자의 인덱스만 갱신하므로 임계구역이 최소화됩니다. --- ## 정리 | 개념 | 설명 | |------|------| | volatile | 컴파일러 캐시 금지, ISR 공유 변수에 필수 | | 임계구역 | 인터럽트 비활성화로 R-M-W 시퀀스 보호 | | 상태 저장/복원 | 중첩 임계구역 안전 처리를 위해 필수 | | __ldmst | TC37x TriCore 원자 Load-Modify-Store 명령 | | head/tail 분리 | 링버퍼의 읽기/쓰기 인덱스를 각각 한 주체만 갱신 |
ISR 작성 규칙 — 재진입 금지, 실행 시간 최소화
타이머 개념 — 카운터, 오버플로, 프리스케일러
// COMMENTS
Newest First
ON THIS PAGE
No content selected.