Ngingathanda ukuyifaka i-LEDs ye-TM4C123GH6PM yami ngosuku eyodwa, ukhangela isibuko se-heartbeat engcono. Ngingathanda ukuthi ngithole yonke into efanele: ukwakha i-SysTick, ukhangela i-PendSV, futhi i-initialized I-Thread Control Blocks (TCBs). Kodwa kunokuba isibuko eside, i-LEDs i-twitched nje, bese i-freeze-mocking. Lesi sikhathi i-crystallized ukuthi ukwakhiwa i-RTOS enhle ikakhulukazi: ukuhlangabezana ne-hardware quirks, ukuhlangabezana ama-bugs emangalisayo, futhi i-patching nge-coding eningi ukwenza yonke into ngokushesha. Kule nqaku
Umbala we-Backstory
Okungenani ngonyaka edlule, umfundi yami we-embedded-systems wabhala ukwakha i-RTOS efanelekayo kusukela ekupheleni kwe-TM4C123GH6PM (i-ARM Cortex-M4). Ngisebenzisa i-FreeRTOS ngaphambi, kodwa ngidinga ngokugcwele ukuthi okuholela emaphaketheni. Ngakho ngifuna ukunciphisa: akukho izici ezihambayo, kuphela ama-threads, ama-priorities, ama-semaphores eziyisisekelo, futhi i-shell phezu kwe-UART yokufaka ngaphansi kwe-cap.
Why?
- Ndingathanda ukubona kanjani i-CPU isihlukile kusuka ku-string eyodwa ku-string elinye.
- Ngingathanda ukuthi kungenzeka ukuthi izicelo ezinguqulo ezinguqulo ezinguqulo ezinguqulo ezinguqulo ezinguqulo ezinguqulo ezinguqulo ezinguqulo ezinguqulo ezinguqulo ezinguqulo ezinguqulo ezinguqulo ezinguqulo ezinguqulo ezinguqulo ezinguqulo ezinguqulo.
- Ngithanda imibuzo yokuphathelisa okuhle - njengesikhathi esithile ngosuku esifundisa ukuthi i-PendSV ayidlulanga (ngokuthi ngibhalisa ukuthi ngithumela i-priority efanayo ne-SysTick).
SPOILER: I-RTOS yam akuyona enhle. Kodwa ekusebenzeni imiphumela yayo, ngifunde ngaphezulu mayelana ne-ARM internals kunazo zonke izifundo.
Ukuhlelwa ku-Cortex-M4 World
Ngaphambi kokubhala umugqa elilodwa we-coding, ndingathanda ukujabulela kanjani i-Cortex-M4 ukulawula ukujabulela kanye nokuguqulwa kwe-context. Ngiyazi ukuthi i-bit (i-pun) yakhelwe kwami ekuqaleni:
- PendSV’s “Gentle” Role
- PendSV is designed to be the lowest-priority exception—meaning it only runs after all other interrupts finish. My first mistake was setting PendSV’s priority to 0 (highest). Of course, it never ran because every other interrupt always preempted it. Once I moved it to 0xFF, scheduling finally kicked in.
- Note to self (and you): write down your NVIC priorities on a Post-it. It’s easy to confuse “0 is highest” with “0 is lowest.”
- SysTick as the Heartbeat
- I aimed for a 1 ms tick. On a 16 MHz clock, that meant
LOAD = 16 000 – 1
. - I initially tried to do all scheduling decisions inside the SysTick ISR, but that got messy. Instead, I now just decrement sleep counters there and set the PendSV pending bit. Let the “real” context switch happen in PendSV.
- I aimed for a 1 ms tick. On a 16 MHz clock, that meant
- Exception Stack Frame
- When any exception fires, hardware auto-pushes R0–R3, R12, LR, PC, and xPSR. That means my “fake” initial stack for each thread must match this exact layout—else, on the very first run, the CPU will attempt to pop garbage and crash.
- I once forgot to set the Thumb bit (
0x01000000
) in xPSR. The result was an immediate hard fault. Lesson: that Thumb flag is non-negotiable.
Ukulungiswa kwe-Thread Control Block (TCB)
Yonke thread ku-RTOS yami ibhekwa:
typedef enum { READY, RUNNING, BLOCKED, SLEEPING } state_t;
typedef struct {
uint32_t *stack_ptr; // Saved PSP for context switches
uint8_t priority; // 0 = highest, larger = lower priority
state_t state; // READY, RUNNING, BLOCKED, or SLEEPING
uint32_t sleep_ticks; // How many SysTick ticks remain, if sleeping
} tcb_t;
Ngokuvamile, ngathi:
#define MAX_THREADS 9
#define STACK_WORDS 256
uint32_t thread_stacks[MAX_THREADS][STACK_WORDS];
tcb_t tcbs[MAX_THREADS];
uint8_t thread_count = 0;
int current_thread = -1;
I-A Stack-Overflow Horror Story
Ngemuva kokuqala ukubhalisaSTACK_WORDS = 128
, konke kubonakala okungenani – kuze kube “i-worker” wire yami, okuyinto waya izicelo ezithile zokusebenza zihlanganisa, waqala ukuphazamiseka ibhukumaki. I-LED akugqoka amabili, bese ushiye. Ngokuvimbela isikhwama ngamunye nge0xDEADBEEF
ekubuyekeza ukuthi kungcono ukuthi i-128 imiyalezo akuyona ngaphansi kwe-build flags. Ukukhuthaza ku-256 imiyalezo kwenziwa.
Ukuhlobisa i-thread entsha
Ukwakhiwa kwe-thread kubalulekile ukuchithwa kwe-stack yayo kanye nokuguqulwa kwe-hardware stacking eyenziwa ku-exception entry. Ngiyazi i-routine, ne-comments on amabhange amahora wam:
void rtos_create_thread(void (*fn)(void), uint8_t prio) {
int id = thread_count++;
tcb_t *t = &tcbs[id];
uint32_t *stk = &thread_stacks[id][STACK_WORDS - 1];
// Simulate hardware stacking (xPSR, PC, LR, R12, R3, R2, R1, R0)
*(--stk) = 0x01000000; // xPSR: Thumb bit set
*(--stk) = (uint32_t)fn; // PC → thread entry point
*(--stk) = 0xFFFFFFFD; // LR → return with PSP in Thread mode
*(--stk) = 0x12121212; // R12 (just a marker)
*(--stk) = 0x03030303; // R3
*(--stk) = 0x02020202; // R2
*(--stk) = 0x01010101; // R1
*(--stk) = 0x00000000; // R0
// Save space for R4–R11 (popped by the context switch)
for (int r = 4; r <= 11; r++) {
*(--stk) = 0x0; // or use a pattern if you want to measure usage
}
t->stack_ptr = stk;
t->priority = prio;
t->state = READY;
t->sleep_ticks = 0;
}
Pitfalls Ukuze Watch For
- Thumb Bit ku-xPSR. Ukukhangisa kungenzeka ukuthi i-CPU yakho uzama ukuhlola ikhodi njenge-INSTRUCTIONS ye-ARM.
- I-Magic 0xFFFFFFFD. Lokhu kubonisa i-CPU, "Ukuza okungenani, usebenzisa i-PSP futhi uye ku-Thread mode." Ngingathanda ukubuyekeza i-ARM ARM (Architecture Reference Manual) okungenani ezintathu ukuze uthole lokhu ngokufanele.
- Stack Order. Ukukhuthaza R4-R11 ngesandla kufuneka uxhumane isikhathi esifanele esithathwe umlawuli. A typpo elula (isib. Ukukhuthaza R11 okokuqala kunoma R4) ukuphazisa inkqubo ephelele.
Letting Time Pass: I-SysTick Handler
Ngiyazi i-SysTick ISR yami lokugqibela, eyenziwe ku-essentials:
void SysTick_Handler(void) {
for (int i = 0; i < thread_count; i++) {
if (tcbs[i].state == SLEEPING) {
if (--tcbs[i].sleep_ticks == 0) {
tcbs[i].state = READY;
}
}
}
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; // Pend PendSV for scheduling
}
Izingane zeNotes:
- Ngo-version yokuqala, ngihambe i-rtos_schedule() ngokufanayo ngaphakathi kwe-SysTick. Lokhu kwenza izixazululo ezihlangene kanye nokuhlukaniswa kwe-stack. Manje ngihlangana i-PendSV futhi ivumela ukulawula i-heavy lifting.
- Ngithole ukuthi uma i-SysTick_IRQn kanye ne-PendSV_IRQn zihlanganisa i-priority efanayo, i-PendSV ayizange asebenza. Zihlanganisa njalo i-PendSV yi-absolute lowest numeric priority (isib. i-NVIC_SetPriority(PendSV_IRQn, 0xFF)), futhi ukugcina i-SysTick aphansi (isib. i-2 noma i-3).
I-Big Switch: I-PendSV Handler
Uma i-PendSV ikakhulukazi ivula, ivumela ukuxhaswa kwe-context efanayo. Ukusebenza yami ku-GCC inline assembly (i-ARM syntax) kubona lokhu:
__attribute__((naked)) void PendSV_Handler(void) {
__asm volatile(
"MRS R0, PSP \n" // Get current PSP
"STMDB R0!, {R4-R11} \n" // Push R4–R11 onto stack
"LDR R1, =current_thread \n"
"LDR R2, [R1] \n"
"STR R0, [R2, #0] \n" // Save updated PSP into TCB
"BL rtos_schedule \n" // Decide next thread
"LDR R1, =current_thread \n"
"LDR R2, [R1] \n"
"LDR R0, [R2, #0] \n" // Load next thread’s PSP
"LDMIA R0!, {R4-R11} \n" // Pop R4–R11 from its stack
"MSR PSP, R0 \n" // Update PSP to new thread
"BX LR \n" // Exit exception, restore R0–R3, R12, LR, PC, xPSR
);
}
I-How I Tracked Down That Pesky 32-Byte Offset
Okokuqala, ikhodi yami "ukushintshwa / ukuguqulwa" yasungulwa nge-byte angu-32. I-symptom? I-threads ingasebenza, bese ngempumelelo ukuguqulwa emzimbeni emzimbeni emzimbeni emzimbeni – imiyalezo eyenziwe, imiyalezo eyenziwe ngempumelelo. Ngithumela amabili ama-toggles e-GPIO (ukushintshwa kwe-LED pin ngaphambi nangemva)STMDB
) ukucubungula ngokushesha ukuthi ama-byte angama-pushed. Ngo-debugger yami, ngehora ubukhulu we-PSP ye-numericaltcbs[].stack_ptr
Ngingathanda. Ngingathanda kakhulu, ngihambe usebenzisaSTMDB R0!, {R4-R11, R12}
Ngaphandle{R4-R11}
—ukushintshwa isicelo esifundeni esifundeni esifundeni esifundeni esifundeni esifundeni esifundeni esifundeni esifundeni
Ukukhetha I-Next Thread: I-Scheduler Logic
I-scheduler yami ikakhulukazi elula. I-TCBs zihlanganisa ukufumana i-READY thread ye-highest-priority, nge-robin-round tweak encane ye-threads e-equal priority:
void rtos_schedule(void) {
int next = -1;
uint8_t best_prio = 0xFF;
for (int i = 0; i < thread_count; i++) {
if (tcbs[i].state == READY) {
if (tcbs[i].priority < best_prio) {
best_prio = tcbs[i].priority;
next = i;
} else if (tcbs[i].priority == best_prio) {
// Simple round-robin: if i is after current, pick it
if (i > current_thread) {
next = i;
break;
}
}
}
}
if (next < 0) {
// No READY threads—fall back to idle thread (ID 0)
next = 0;
}
current_thread = next;
}
What I Learned Here:
- Uma izintambo ezimbili zihlanganisa i-priority 1 kodwa ngithole i-ID ephakeme kakhulu, elinye ayikho isikhathi se-CPU. Ngokusho i > current_thread, i-"twin" ikakhulukazi ithatha isikhathi yayo.
- I-trade idle (ID 0) iyisiseko esiyingqayizivele: UKUSEBENZI UKUSEBENZI, UKUSEBENZI UKUSEBENZI UKUSEBENZI (priority = 255), ngakho-ke uma akukho enye inguqulo engatholakala, kulula kuphela (noma ukubiza __WFI() ukuze ukugcina amandla).
Kernel Primitives: I-Yield, i-Sleep, ne-Semaphores
Ngemva kokufaka izinto eziyisisekelo, isinyathelo esilandelayo kuyinto ukwenza i-threads ukuxhumana ngokufanele.
Waze
void rtos_yield(void) {
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
}
Ngithanda i-Springlingrtos_yield()
at the end of long loops so other threads get a fair shot. Ngezinguqulo zokuqala, ukuncedayield()
Ngokuvamile, i-CPU iyahambisana ne-priority configurations ezithile.
Ukuhlobisa
void rtos_sleep(uint32_t ticks) {
tcb_t *self = &tcbs[current_thread];
self->sleep_ticks = ticks;
self->state = SLEEPING;
rtos_yield();
}
My "LED blinking" thread izivakashirtos_sleep(500)
Uma ngibuyekele ukujula ngalinye-second, ngazi ukuthi i-SysTick ne-PendSV zenza umsebenzi yabo ngokufanele.
Ukuhlobisa
Okokuqala, ngihambe indlela enhle:
typedef struct {
volatile int count;
int waiting_queue[MAX_THREADS];
int head, tail;
} semaphore_t;
void rtos_sem_wait(semaphore_t *sem) {
__disable_irq();
sem->count--;
if (sem->count < 0) {
sem->waiting_queue[sem->tail++] = current_thread;
tcbs[current_thread].state = BLOCKED;
__enable_irq();
rtos_yield();
} else {
__enable_irq();
}
}
void rtos_sem_post(semaphore_t *sem) {
__disable_irq();
sem->count++;
if (sem->count <= 0) {
int tid = sem->waiting_queue[sem->head++];
tcbs[tid].state = READY;
}
__enable_irq();
}
I-Priority Inversion I-Nightmare
Enye usuku, ngaba i-threads ezintathu:
- T0 (I-Priority 2): Inikeza i-semaphore.
- I-T1 (I-Priority 1): Ukukhangisa ukuthi i-semaphore.
- I-T2 (I-Priority 3): I-ready to run and higher than T0 but lower than T1.
Njengoba i-T1 iye yasungulwa, i-T2 iye yasungulwa - akuyona i-T0 inikezela ukufinyelela kwe-semaphore ye-T1. I-T1 iye yasungula. I-hack yayo yokushisa okusheshayo yaba ukwandisa i-priority ye-T0 lapho i-T1 iye yasungulwa - kuyinto isakhiwo se-priority enhle. Isisombululo ephelele uyavumela ukuthi ingxubevange inikeza i-semaphore futhi ukwandisa i-priority yayo ngokuzenzakalelayo. Kuyinto isitimela esithathwe ngemva.
Kicking yonke into off:rtos_start()
isixazululo (
Ngenamain()
, ngemuva kokuqala hardware init (amahora, GPIO for LEDs, UART for the shell), Ngiye:
// 1. Initialize SysTick and PendSV priorities
systick_init(16000); // 1 ms tick on a 16 MHz system
NVIC_SetPriority(PendSV_IRQn, 0xFF);
// 2. Create threads (ID 0 = idle thread)
rtos_create_thread(idle_thread, 255);
rtos_create_thread(shell_thread, 3);
rtos_create_thread(worker1, 1);
rtos_create_thread(worker2, 2);
// 3. Switch to PSP and unprivileged Thread mode
current_thread = 0;
__set_PSP((uint32_t)tcbs[0].stack_ptr);
__set_CONTROL(0x02); // Use PSP, unprivileged
__ISB();
// 4. Pend PendSV to start first context switch
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
// 5. Idle loop
while (1) {
__WFI(); // Save power until next interrupt
}
Umbala we-Note Final:
- I-Idle Thread (ID 0): Khetha nje i-LED e-low-priority. Uma unemibuzo embalwa, ngazi ukuthi ngibeke ngihambe ku-idle thread.
- I-UART Interrupt Priority: I-TX interrupt ye-UART kufanele kube lula kakhulu kunokuba yi-PendSV; ngaphandle kwalokho, izivakashi eziningi ze-printf ziye zithintshwe ku-mid-transmit, ukunciphisa ukukhipha.
I-UART Shell: I-Peeking Under the Hood
Ngithenge i-shell encane ukuze ngithole amamakhasimende phezu ku-UART futhi ukubuyekeza izimo ze-thread:
void shell_thread(void) {
char buf[64];
while (1) {
uart_print("> ");
uart_read_line(buf, sizeof(buf));
if (strncmp(buf, "ps", 2) == 0) {
for (int i = 0; i < thread_count; i++) {
uart_printf("TID %d: state=%d prio=%d\n",
i, tcbs[i].state, tcbs[i].priority);
}
} else if (strncmp(buf, "sleep ", 6) == 0) {
uint32_t msec = atoi(&buf[6]);
rtos_sleep(msec);
} else if (strncmp(buf, "kill ", 5) == 0) {
int tid = atoi(&buf[5]);
if (tid >= 1 && tid < thread_count) { // don’t kill idle
tcbs[tid].state = BLOCKED; // crude kill
uart_printf("Killed thread %d\n", tid);
}
} else {
uart_print("Unknown command\n");
}
}
}
Kuyinto i-shell yayo yokhuseleko: uma i-LED isebenza kahle, ngihambe ku-terminal yamps
, futhi ngokushesha ukubona ukuthi izindwangu ZOKUSEBENZA, ZOKUSEBENZA, noma UKUSEBENZA. Kuyashintshela imizuzu yokuthanda.
Ukufundwa kwe-Lessons Learned Along the Way
- I-Interrupt Priorities I-Everything I-PendSV I-PendSV Code I-PendSV Code I-PendSV Code I-PendSV Code I-PendSV I-PendSV I-PendSick I-PendSV I-Priority I-Priority I-Priority I-PendSV I-PendSV I-PendSV I-PendSV I-PendSV I-PendSV I-PendSV I-PendSV I-PendSV
- Ukubuyekeza Ukusetshenziswa kwe-Stack yakho Pre-fill zonke i-stack ye-thread nge-pattern ebonakalayo (0xDEADBEEF) ku-startup. Ngemva kokushesha, uchofoze ibhukumaki ukuze ubone ukuthi isampula yaziwe kakhulu. Uma umbhali wakho we-stack uhamba ku-pattern elinye, uyazi ukuthi uthatha isampula esikhulu. Ngithole lokhu ngempumelelo lapho isilinganiso esikhulu esebenzayo ku-worker thread yamukela isampula esihlalweni.
- Usebenzisa LEDs & GPIO ukuze Debug Uma unemibuzo emangalisayo, nje uxhumane i-GPIO pin (ukuguqula ku-LED). Ngithunyelwe i-LED swing elinye ekuqaleni kwe-PendSV_Handler kanye elinye ekupheleni yayo. Ukubuyekeza ama-blink ye-analyzer ye-logic ungasiza ukubuyekeza ukuthi i-handler isebenza inani elithathayo - futhi ngama-intervals eqinile.
- Imininingwane yami lokuqala ye-RTOS ihlolwe ukulungiselela ukulungiselela umsebenzi ephilayo ngexesha lokusebenza-ukugqwala massive. Ngithole ngama-memory futhi ama-crashes amangalisayo. Ngokuvimbela inani le-threads ku-startup (ama-slots amane kuphela), ngihambe emhlabeni sokuphendula.
- I-Document Every Magic Number That 0xFFFFFFFD value in the “fake” stack frame? Ngithole isithombe esincinane: “I-Link registry value okuyinto ibonisa ukuguqulwa ku-Thread mode usebenzisa i-PSP.” Ngaphandle kwalokho, ngithole “ARM exception return values” ngalinye ngalinye i-revisited ikhodi.
Izinyathelo ezilandelayo: Kusukela ku-Here to Go
Uma ufuna ukwakha kulesi isakhiwo, lapha izici ezimbalwa:
- I-Full Priority Inheritance ye-Semaphores Ngaphandle kwe-"boost-and-forget" yokushintshwa yami okusheshayo, isebenza i-protocol efanelekayo lapho i-priority ye-high-priority blocked thread iyahlukaniswa kuze kube lokukhishwa kwe-semaphore.
- I-Inter-Thread Message Queues Yenza ama-threads ukuxhumana ama-messages amancane noma ama-pointers ngokhuseleko. Yenziwe njenge-mailbox amancane yokuthumela idatha phakathi kwezimfuneko.
- I-Dynamic Task Creation / Deletion Tack ku-heap manager amancane noma i-memory pool ngakho-ke ungakwazi ukwakha futhi ukhohlwe ama-threads e-fly-mind the extra complexity!
- I-Profiling Hooks I-Extend your SysTick handler (noma usebenzisa i-timer eyodwa) ukuze ushiye inani le-ticks eyodwa esebenza. Ungayithumela i-output ye-report elula nge-UART ebonisa ukusetshenziswa kwe-CPU ngesisombululo.
- I-Real-Time Safety Checks Yenza ama-limits yokusebenzisa ama-stack futhi i-detect lapho i-stack pointer ye-trade ibheka kwindawo ye-0xDEADBEEF—ukushintshisa ukucindezeleka okuhuseleko noma ukucindezeleka okungagunyaziwe ngempumelelo.
Imibuzo yokuqala
Ukwakhiwa kwe-RTOS ephakeme kunzima, ngokuvamile ezinzima, futhi kuhlobisa ngokuphelele. Ukusuka ekusebenzeni kwe-PendSV enhle kuya ku-struggle ne-stack overflows, zonke ama-bug angacindezela ngithole i-internals ye-Cortex-M4 enhle kakhulu. Uma udinga lokhu ku-TM4C yakho noma enye ingxenye ye-Cortex-M, udinga amahora embalwa - kodwa futhi ukuhambisa okuphumelela okuphumelela lapho okungenani i-LED enhle lokuphumelela.
Uma ushiye lokhu, sicela sicela ushiye ukuthi ingxenye eyakuthuthaza u-wall. Ngingathanda ukubhuka "LED blinking" izivakashi yakho noma ezinye izincazelo ezaziwa ngenkathi ukubhuka ama-ghosts ku-scheduler yakho. Happy hacking!