The firmware runs FreeRTOS, confirmed by two signature strings found in the binary:
IDLE— the FreeRTOS idle task name (created automatically by the kernel)Tmr Svc— the FreeRTOS timer service daemon task name
These are the default task names assigned by vTaskStartScheduler() and are definitive identifiers of FreeRTOS.
| Finding | FreeRTOS Function |
|---|---|
| PendSV handler (vector 14, 0x08028D51) | xPortPendSVHandler — RTOS context switch |
| SVCall handler (vector 11, 0x08028DC1) | vPortSVCHandler — first task launch |
| SysTick handler (vector 15, 0x0802A995) | xPortSysTickHandler — tick increment + yield check |
isCurrentModePrivileged() call |
FreeRTOS kernel privilege check |
DataSynchronizationBarrier / InstructionSynchronizationBarrier |
Memory barriers in context switch |
| SCB_ICSR write (0xE000ED04 = 0x10000000) | Trigger PendSV for context switch |
| Idle loop stuck at 0x0803A634 | prvIdleTask — runs when no other task is ready |
Despite being a Chinese product on a GD32 chip (where RT-Thread is dominant), FNIRSI chose FreeRTOS. Possible reasons:
- FreeRTOS has a smaller kernel footprint (~5-10KB vs RT-Thread's full stack)
- GigaDevice provides FreeRTOS examples alongside RT-Thread
- FreeRTOS is simpler when you only need basic task scheduling
The firmware is multi-threaded with FreeRTOS managing concurrent tasks:
FreeRTOS Kernel (Cortex-M4 port)
│
├── Display Task
│ ├── Renders UI to framebuffer (RGB565)
│ ├── Handles menu drawing
│ ├── Waveform display
│ └── Uses draw_text (0x08032f6c), draw_ui_element (0x08008154)
│
├── Acquisition Task
│ ├── Communicates with FPGA via USART2
│ ├── Reads ADC sample data
│ ├── Processes waveform data
│ └── Triggers on signal events
│
├── Input Task
│ ├── Touch panel via I2C
│ ├── Button state via GPIO
│ └── Posts events to other tasks via FreeRTOS queues
│
├── Measurement Task
│ ├── Calculates frequency, voltage, duty cycle, etc.
│ ├── Auto-ranging (floating point math in draw_measurement_display)
│ └── Updates measurement display
│
├── USB Task
│ ├── USB mass storage class (FAT32 filesystem)
│ ├── "USB Sharing" mode
│ └── USB interrupt handler at vector 36
│
├── Timer Service (Tmr Svc)
│ ├── FreeRTOS software timers
│ ├── Likely handles auto-shutdown countdown
│ └── Buzzer timing
│
└── IDLE Task
├── Runs when no other task needs CPU
├── May include power-saving (WFI instruction)
└── Stuck point in emulator (0x0803A634)
| Address | Likely FreeRTOS Function | Description |
|---|---|---|
| 0803a610 | vTaskSwitchContext or xTaskResumeAll |
Task scheduling with PendSV trigger |
| 08028D51 | xPortPendSVHandler |
Context switch — saves/restores task registers |
| 08028DC1 | vPortSVCHandler |
Starts first task after scheduler launch |
| 0802A995 | xPortSysTickHandler |
Increments tick count, checks for yield |
| 08033cfc | pvPortMalloc |
FreeRTOS memory allocator (called as memory_alloc) |
Unicorn doesn't model the NVIC interrupt controller. The FreeRTOS scheduler depends on:
- SysTick firing periodically — increments the tick count and checks if a higher-priority task is ready
- PendSV being triggered — performs the actual context switch between tasks
- Proper interrupt priority — FreeRTOS uses NVIC priority levels to manage critical sections
Without SysTick interrupts, the scheduler never runs, all tasks stay blocked, and the CPU sits in the idle task forever.
Renode properly models:
- NVIC with priority levels and preemption
- SysTick with configurable frequency (120MHz for GD32F307)
- PendSV/SVCall handling
- Exception entry/return with proper stack frame management
This means FreeRTOS tasks would actually run, display code would execute, and we'd see framebuffer output.
For a firmware rewrite, FreeRTOS is freely available (MIT license) and well-documented:
- Use the same RTOS — FreeRTOS Cortex-M4 port, same task structure
- GD32F307 BSP available — GigaDevice provides FreeRTOS board support packages
- Task architecture is known — create the same task structure (display, acquisition, input, measurement, USB)
- Existing FreeRTOS config — could extract
FreeRTOSConfig.hvalues from the firmware (heap size, tick rate, max priorities, stack sizes)
The FreeRTOSConfig.h values can be inferred from the binary:
- configTICK_RATE_HZ — determined by SysTick reload value
- configMINIMAL_STACK_SIZE — visible in idle task stack allocation
- configTOTAL_HEAP_SIZE — visible in heap memory pool size
- configMAX_PRIORITIES — visible in priority level checks
These would be found by examining the RTOS initialization code around the reset vector (0x08007311).