This is WIP!
The LD2410S is a FMCW Radar based on the
- ICLegend ICL1112: 24G 1T1R mmWave Sensor SoC
- Puya F030K28: Cortex-M0, 64KBytes Flash, 8 KBytes SRAM, I2C/SPI/UART
I'm not going to make any screenshots from the ICL1112 datasheet in this project as they have a huge "CONFIDENTAL" mark over them. Just visit the public website which is linked on the device name and access them by yourself.
System diagram of the ICLegend IC1112. Taken from the public website of ICLegend.
The I2C runs at 3MHz (sic) and seems to be used for the initial configuration and trigger of the radar scan.
After power-on 4 rapid burts in quick succession. During normal operation a burst can be seen every 2.05s.
A sniff with a Saleae can be found here. The power-up sequence and normal operation is visible in the trace.
Guess this is bit banged I2C as the hardware engine of the microcontroller only supports rates up to 400kHz.
The SPI runs at 10MHz clock rate. The ICL1112 acts as a SPI master.
This one was rather tricky to capture as my Logic Analyzer was loosing frames because of the high frequency (fmax. 20MHz) so I had to fall back to my DSO.
According to the datasheet this board is using the "Range FFT data frame".
Example Frame:
0 10101010 00110000 00000000 00010001
1 10000000 00000000 00101001 11110111
2 01010010 11010111 11101111 11111110
3 11111111 01011111 11111111 10100011
4 00000000 00010001 11111111 11111100
5 00000000 01001010 00000000 00100100
6 00000000 00001010 11111111 11101111
7 00000000 00000010 00000000 00010010
8 00000000 00010010 11111111 11111011
9 00000000 00010101 00000000 00001101
10 00000000 00001101 11111111 11111101
11 00000000 00010010 00000000 00000100
12 00000000 00001110 00000000 00000011
13 00000000 00001101 00000000 00000100
14 00000000 00001101 00000000 00000110
15 00000000 00001010 11111111 11111010
16 00000000 00001100 00000000 00000100
17 11101100 11101110 00010000 01010101
Lets try to interpret them.
Word 0:
0 10101010 00110000 00000000 00010001
10101010 = Preamble
0011 = Range Data
0000 0000 0 = FFT Chirp Index
00 00010001 = CFG FFT TX MAX
Word 2-16: Range FFT Data
2 10000000 00000000 00101001 11110111
3 01010010 11010111 11101111 11111110
4 11111111 01011111 11111111 10100011
5 00000000 00010001 11111111 11111100
6 00000000 01001010 00000000 00100100
7 00000000 00001010 11111111 11101111
8 00000000 00000010 00000000 00010010
9 00000000 00010010 11111111 11111011
10 00000000 00010101 00000000 00001101
11 00000000 00001101 11111111 11111101
12 00000000 00010010 00000000 00000100
13 00000000 00001110 00000000 00000011
14 00000000 00001101 00000000 00000100
15 00000000 00001101 00000000 00000110
16 00000000 00001010 11111111 11111010
\ / \ /
FFT Real FFT Imaginary
Word 17:
17 11101100 11101110 00010000 01010101
11101100 11101110 = Checksum
0001 = Frame Index
00 = Not Used
00 CFG Message
01010101 = Postamble
For connecting to the running device pyOCD with a ST-Link V2 was used. The great thing about pyOCD is that it can use standardized .pack files. The requred .pack can be downloaded from the Puya Repository
# pyocd cmd --pack "Puya.PY32F0xx_DFP.1.2.0.pack" -t "py32f030x8"
Connected to PY32F030x8 [Running]: 6068FF2...
pyocd> show target
Target type: py32f030x8
Vendor: Puya
Part number: PY32F030x8
DAP IDCODE: 0x0bc
Jackpot! We have running connection.
pyocd> show map
Region Type Start End Size Access Sector Page
IROM1 Flash 0x08000000 0x0800ffff 0x00010000 rx 0x00001000 0x00000080
IRAM1 Ram 0x20000000 0x20001fff 0x00002000 rw - -
pyocd> save 0x08000000 0x00010000 flash_dump.bin
Saved 65536 bytes to flash_dump.bin
had@es-ws:~/Code> md5sum flash_dump.bin
1574bfeb56acbc239730b190bbbf028a flash_dump.bin
No read protection is set.
# strings flash_dump.bin
error: unsupport dataType
Error: dataLen is too long
========= Error State =========
========= Error State =========
PY32_SPI_Read_Regs Error
PY32_SPI_Write_Regs Error
hspi->ErrorCode = %d
Error: unsupport dataType
Error: dataLen is too long
PY32_SPI_Read_Regs Error
PY32_SPI_Write_Regs Error
hspi->ErrorCode = %d
========= Error State =========
========= Error State =========
Without symbols it will be very painful to reverse engineer the function names by using Ghidra.
--- PY32F030 Interrupt Vector Table ---
Interrupt Name IRQn Vector Address Handler Address
--------------------------------------------------------------------------------
Initial Stack Pointer N/A 0x00000000 0x200018f8
Reset N/A 0x00000004 0x080000d5
NMI N/A 0x00000008 0x08001f13
HardFault N/A 0x0000000c 0x08001e43
Reserved_4 N/A 0x00000010 0x00000000
Reserved_5 N/A 0x00000014 0x00000000
Reserved_6 N/A 0x00000018 0x00000000
Reserved_7 N/A 0x0000001c 0x00000000
Reserved_8 N/A 0x00000020 0x00000000
Reserved_9 N/A 0x00000024 0x00000000
Reserved_10 N/A 0x00000028 0x00000000
SVCall N/A 0x0000002c 0x08001fc7
Reserved_12 N/A 0x00000030 0x00000000
Reserved_13 N/A 0x00000034 0x00000000
PendSV N/A 0x00000038 0x08001f95
SysTick N/A 0x0000003c 0x08001ff3
WWDG 0 0x00000040 0x080000e7
PVD 1 0x00000044 0x080000e7
RTC 2 0x00000048 0x080000e7
Flash 3 0x0000004c 0x080000e7
RCC 4 0x00000050 0x080000e7
EXTI0_1 5 0x00000054 0x080000e7
EXTI2_3 6 0x00000058 0x08000999
EXTI4_15 7 0x0000005c 0x080000e7
Reserved_24 8 0x00000060 0x00000000
DMA_Channel1 9 0x00000064 0x08000995
DMA_Channel2_3 10 0x00000068 0x08000997
Reserved_27 11 0x0000006c 0x00000000
ADC_COMP 12 0x00000070 0x080000e7
TIM1_BRK_UP_TRG_COM 13 0x00000074 0x080000e7
TIM1_CC 14 0x00000078 0x080000e7
Reserved_31 15 0x0000007c 0x00000000
TIM3 16 0x00000080 0x080000e7
LPTIM1 17 0x00000084 0x08001f11
Reserved_34 18 0x00000088 0x00000000
TIM14 19 0x0000008c 0x0800202d
Reserved_36 20 0x00000090 0x00000000
TIM16 21 0x00000094 0x080000e7
TIM17 22 0x00000098 0x080000e7
I2C1 23 0x0000009c 0x080000e7
Reserved_40 24 0x000000a0 0x00000000
SPI1 25 0x000000a4 0x08001fc5
SPI2 26 0x000000a8 0x080000e7
USART1 27 0x000000ac 0x080000e7
USART2 28 0x000000b0 0x08002389
Reserved_45 29 0x000000b4 0x00000000
LED 30 0x000000b8 0x080000e7
Reserved_47 31 0x000000bc 0x00000000
That's one of the great pyOCD features: it's able to dump registers while at the same time parsing the register names.
pyocd> show peripherals
0x40000400: TIM3
0x40002000: TIM14
0x40002400: LED
0x40002800: RTC
0x40002c00: WWDG
0x40003000: IWDG
0x40003800: SPI2
0x40004400: USART2
0x40005400: I2C
0x40007000: PWR
0x40007c00: LPTIM
0x40010000: SYSCFG
0x40010200: COMP1
0x40010210: COMP2
0x40012400: ADC
0x40012c00: TIM1
0x40013000: SPI1
0x40013800: USART1
0x40014400: TIM16
0x40014800: TIM17
0x40015800: DBGMCU
0x40020000: DMA
0x40021000: RCC
0x40021800: EXTI
0x40022000: FLASH
0x40023000: CRC
0x50000000: GPIOA
0x50000400: GPIOB
0x50001400: GPIOF
pyocd> reg -p TIM3
TIM3.CR1 @ 40000400 = 00000000
TIM3.CR2 @ 40000404 = 00000000
TIM3.SMCR @ 40000408 = 00000000
TIM3.DIER @ 4000040c = 00000000
TIM3.SR @ 40000410 = 00000000
TIM3.EGR @ 40000414 = 00000000
TIM3.CCMR1_Output @ 40000418 = 00000000
TIM3.CCMR1_Input @ 40000418 = 00000000
TIM3.CCMR2_Output @ 4000041c = 00000000
TIM3.CCMR2_Input @ 4000041c = 00000000
TIM3.CCER @ 40000420 = 00000000
TIM3.CNT @ 40000424 = 00000000
TIM3.PSC @ 40000428 = 00000000
TIM3.ARR @ 4000042c = 0000ffff
TIM3.CCR1 @ 40000434 = 00000000
TIM3.CCR2 @ 40000438 = 00000000
TIM3.CCR3 @ 4000043c = 00000000
TIM3.CCR4 @ 40000440 = 00000000
TIM3.DCR @ 40000448 = 00000000
TIM3.DMAR @ 4000044c = 00000000
pyocd> reg -p TIM14
TIM14.CR1 @ 40002000 = 00000000
TIM14.DIER @ 4000200c = 00000000
TIM14.SR @ 40002010 = 00000001
TIM14.EGR @ 40002014 = 00000000
TIM14.CCMR1_Output @ 40002018 = 00000000
TIM14.CCMR1_Input @ 40002018 = 00000000
TIM14.CCER @ 40002020 = 00000000
TIM14.CNT @ 40002024 = 00000000
TIM14.PSC @ 40002028 = 000012bf
TIM14.ARR @ 4000202c = 0000752f
TIM14.CCR1 @ 40002034 = 00000000
TIM14.OR @ 40002050 = 00000000
pyocd> reg -p LED
LED.CR @ 40002400 = 00000000
LED.PR @ 40002404 = 00000000
LED.TR @ 40002408 = 00000000
LED.DR0 @ 4000240c = 00000000
LED.DR1 @ 40002410 = 00000000
LED.DR2 @ 40002414 = 00000000
LED.DR3 @ 40002418 = 00000000
LED.IR @ 4000241c = 00000000
pyocd>
pyocd> reg -p RTC
RTC.CRH @ 40002800 = 00000000
RTC.CRL @ 40002804 = 00000000
RTC.PRLH @ 40002808 = 00000000
RTC.PRLL @ 4000280c = 00000000
RTC.DIVH @ 40002810 = 00000000
RTC.DIVL @ 40002814 = 00008000
RTC.CNTH @ 40002818 = 00000000
RTC.CNTL @ 4000281c = 00000000
RTC.ALRH @ 40002820 = 00000000
RTC.ALRL @ 40002824 = 00000000
RTC.RTCCR @ 4000282c = 00000000
pyocd> reg -p WWDG
WWDG.CR @ 40002c00 = 0000007f
WWDG.CFR @ 40002c04 = 0000007f
WWDG.SR @ 40002c08 = 00000000
pyocd> reg -p IWDG
IWDG.KR @ 40003000 = 00000000
IWDG.PR @ 40003004 = 00000000
IWDG.RLR @ 40003008 = 00000fff
IWDG.SR @ 4000300c = 00000000
IWDG.WINR @ 40003010 = 00000ff
pyocd> reg -p SPI2
SPI2.CR1 @ 40003800 = 00000000
SPI2.CR2 @ 40003804 = 00000700
SPI2.SR @ 40003808 = 00000002
SPI2.DR @ 4000380c = 00000000
pyocd> reg -p USART2
USART2.SR @ 40004400 = 000000c0
USART2.DR @ 40004404 = 00000000
USART2.BRR @ 40004408 = 00000000
USART2.CR1 @ 4000440c = 00000000
USART2.CR2 @ 40004410 = 00000000
USART2.CR3 @ 40004414 = 00000000
pyocd> reg -p I2C
I2C.CR1 @ 40005400 = 00000000
I2C.CR2 @ 40005404 = 00000000
I2C.OAR1 @ 40005408 = 00000000
I2C.DR @ 40005410 = 00000000
I2C.SR1 @ 40005414 = 00000000
I2C.SR2 @ 40005418 = 00000000
I2C.CCR @ 4000541c = 00000000
I2C.TRISE @ 40005420 = 00000002
pyocd> reg -p PWR
PWR.CR1 @ 40007000 = 00004300
PWR.CR2 @ 40007004 = 00000500
PWR.SR @ 40007014 = 00000000
pyocd> reg -p LPTIM
LPTIM.ISR @ 40007c00 = 00000000
LPTIM.ICR @ 40007c04 = 00000000
LPTIM.IER @ 40007c08 = 00000002
LPTIM.CFGR @ 40007c0c = 00000e00
LPTIM.CR @ 40007c10 = 00000001
LPTIM.ARR @ 40007c18 = 00000040
LPTIM.CNT @ 40007c1c = 00000019
pyocd> reg -p SYSCFG
SYSCFG.CFGR1 @ 40010000 = 00000000
SYSCFG.CFGR2 @ 40010018 = 00000000
SYSCFG.CFGR3 @ 4001001c = 00000200
pyocd> reg -p COMP1
COMP1.CSR @ 40010200 = 00000000
COMP1.FR @ 40010204 = 00000000
pyocd> reg -p COMP2
COMP2.CSR @ 40010210 = 00000000
COMP2.FR @ 40010214 = 00000000
pyocd> reg -p ADC
ADC.ISR @ 40012400 = 00000000
ADC.IER @ 40012404 = 00000000
ADC.CR @ 40012408 = 00000000
ADC.CFGR1 @ 4001240c = 00000000
ADC.CFGR2 @ 40012410 = 00000000
ADC.SMPR @ 40012414 = 00000000
ADC.TR @ 40012420 = 0fff0000
ADC.CHSELR @ 40012428 = 00000000
ADC.DR @ 40012440 = 00000000
ADC.CCSR @ 40012444 = 00000000
ADC.CALRR1 @ 40012448 = 0003fcff
ADC.CALRR2 @ 4001244c = 00000000
ADC.CALFIR1 @ 40012450 = 00000000
ADC.CALFIR2 @ 40012454 = 00000000
ADC.CCR @ 40012708 = 00000000
pyocd> reg -p TIM1
TIM1.CR1 @ 40012c00 = 00000000
TIM1.CR2 @ 40012c04 = 00000000
TIM1.SMCR @ 40012c08 = 00000000
TIM1.DIER @ 40012c0c = 00000000
TIM1.SR @ 40012c10 = 00000000
TIM1.EGR @ 40012c14 = 00000000
TIM1.CCMR1_Output @ 40012c18 = 00000000
TIM1.CCMR1_Input @ 40012c18 = 00000000
TIM1.CCMR2_Output @ 40012c1c = 00000000
TIM1.CCMR2_Input @ 40012c1c = 00000000
TIM1.CCER @ 40012c20 = 00000000
TIM1.CNT @ 40012c24 = 00000000
TIM1.PSC @ 40012c28 = 00000000
TIM1.ARR @ 40012c2c = 0000ffff
TIM1.RCR @ 40012c30 = 00000000
TIM1.CCR1 @ 40012c34 = 00000000
TIM1.CCR2 @ 40012c38 = 00000000
TIM1.CCR3 @ 40012c3c = 00000000
TIM1.CCR4 @ 40012c40 = 00000000
TIM1.BDTR @ 40012c44 = 00000000
TIM1.DCR @ 40012c48 = 00000000
TIM1.DMAR @ 40012c4c = 00000000
pyocd> reg -p USART1
USART1.SR @ 40013800 = 000000c0
USART1.DR @ 40013804 = 00000000
USART1.BRR @ 40013808 = 00000000
USART1.CR1 @ 4001380c = 00000000
USART1.CR2 @ 40013810 = 00000000
USART1.CR3 @ 40013814 = 00000000
pyocd> reg -p TIM16
TIM16.CR1 @ 40014400 = 00000000
TIM16.CR2 @ 40014404 = 00000000
TIM16.DIER @ 4001440c = 00000000
TIM16.SR @ 40014410 = 00000000
TIM16.EGR @ 40014414 = 00000000
TIM16.CCMR1_Output @ 40014418 = 00000000
TIM16.CCMR1_Input @ 40014418 = 00000000
TIM16.CCER @ 40014420 = 00000000
TIM16.CNT @ 40014424 = 00000000
TIM16.PSC @ 40014428 = 00000000
TIM16.ARR @ 4001442c = 0000ffff
TIM16.RCR @ 40014430 = 00000000
TIM16.CCR1 @ 40014434 = 00000000
TIM16.BDTR @ 40014444 = 00000000
TIM16.DCR @ 40014448 = 00000000
TIM16.DMAR @ 4001444c = 00000000
pyocd> reg -p TIM17
TIM17.CR1 @ 40014800 = 00000000
TIM17.CR2 @ 40014804 = 00000000
TIM17.DIER @ 4001480c = 00000000
TIM17.SR @ 40014810 = 00000000
TIM17.EGR @ 40014814 = 00000000
TIM17.CCMR1_Output @ 40014818 = 00000000
TIM17.CCMR1_Input @ 40014818 = 00000000
TIM17.CCER @ 40014820 = 00000000
TIM17.CNT @ 40014824 = 00000000
TIM17.PSC @ 40014828 = 00000000
TIM17.ARR @ 4001482c = 0000ffff
TIM17.RCR @ 40014830 = 00000000
TIM17.CCR1 @ 40014834 = 00000000
TIM17.BDTR @ 40014844 = 00000000
TIM17.DCR @ 40014848 = 00000000
TIM17.DMAR @ 4001484c = 00000000
pyocd> reg -p DBGMCU
DBGMCU.IDCODE @ 40015800 = 60001000
DBGMCU.CR @ 40015804 = 00000002
DBGMCU.APB_FZ1 @ 40015808 = 00000000
DBGMCU.APB_FZ2 @ 4001580c = 00000000
pyocd> reg -p DMA
DMA.ISR @ 40020000 = 00000000
DMA.IFCR @ 40020004 = 00000000
DMA.CCR1 @ 40020008 = 00000000
DMA.CNDTR1 @ 4002000c = 00000000
DMA.CPAR1 @ 40020010 = 00000000
DMA.CMAR1 @ 40020014 = 00000000
DMA.CCR2 @ 4002001c = 000030af
DMA.CNDTR2 @ 40020020 = 00000048
DMA.CPAR2 @ 40020024 = 4001300c
DMA.CMAR2 @ 40020028 = 200014a8
DMA.CCR3 @ 40020030 = 00000000
DMA.CNDTR3 @ 40020034 = 00000000
DMA.CPAR3 @ 40020038 = 00000000
DMA.CMAR3 @ 4002003c = 00000000
pyocd> reg -p RCC
RCC.CR @ 40021000 = 03000500
RCC.ICSCR @ 40021004 = 00fd9100
RCC.CFGR @ 40021008 = 00000000
RCC.PLLCFGR @ 4002100c = 00000000
RCC.ECSCR @ 40021010 = 00010000
RCC.CIER @ 40021018 = 00000000
RCC.CIFR @ 4002101c = 00000000
RCC.CICR @ 40021020 = 00000000
RCC.IOPRSTR @ 40021024 = 00000000
RCC.AHBRSTR @ 40021028 = 00000000
RCC.APBRSTR1 @ 4002102c = 00000000
RCC.APBRSTR2 @ 40021030 = 00000000
RCC.IOPENR @ 40021034 = 00000003
RCC.AHBENR @ 40021038 = 00000301
RCC.APBENR1 @ 4002103c = 98024000
RCC.APBENR2 @ 40021040 = 00009001
RCC.CCIPR @ 40021054 = 00040000
RCC.BDCR @ 4002105c = 00000000
RCC.CSR @ 40021060 = 18000003
pyocd> reg -p exti
EXTI.RTSR @ 40021800 = 00000008
EXTI.FTSR @ 40021804 = 00000008
EXTI.SWIER @ 40021808 = 00000000
EXTI.PR @ 4002180c = 00000000
EXTI.EXTICR1 @ 40021860 = 00000000
EXTI.EXTICR2 @ 40021864 = 00000000
EXTI.EXTICR3 @ 40021868 = 00000000
EXTI.IMR @ 40021880 = 20080008
EXTI.EMR @ 40021884 = 00000000
pyocd> reg -p flash
FLASH.ACR @ 40022000 = 00000001
FLASH.KEYR @ 40022008 = 00000000
FLASH.OPTKEYR @ 4002200c = 00000000
FLASH.SR @ 40022010 = 00000000
FLASH.CR @ 40022014 = c0000000
FLASH.OPTR @ 40022020 = 0000beaa
FLASH.SDKR @ 40022024 = 0000001f
FLASH.WRPR @ 4002202c = 0000ffff
FLASH.STCR @ 40022090 = 00006400
FLASH.TS0 @ 40022100 = 000000b4
FLASH.TS1 @ 40022104 = 000001b0
FLASH.TS2P @ 40022108 = 000000b4
FLASH.TPS3 @ 4002210c = 000006c0
FLASH.TS3 @ 40022110 = 000000b4
FLASH.PERTPE @ 40022114 = 0000ea60
FLASH.SMERTPE @ 40022118 = 0000fd20
FLASH.PRGTPE @ 4002211c = 00008ca0
FLASH.PRETPE @ 40022120 = 000012c0
pyocd> reg -p CRC
CRC.DR @ 40023000 = 00000000
CRC.IDR @ 40023004 = 00000000
CRC.CR @ 40023008 = 00000000
pyocd> reg -p GPIOA
GPIOA.MODER @ 50000000 = e9ffff1f
GPIOA.OTYPER @ 50000004 = 00000000
GPIOA.OSPEEDR @ 50000008 = 0e000020
GPIOA.PUPDR @ 5000000c = 24000040
GPIOA.IDR @ 50000010 = 0000700c
GPIOA.ODR @ 50000014 = 00001004
GPIOA.BSRR @ 50000018 = 00000000
GPIOA.LCKR @ 5000001c = 00000000
GPIOA.AFRL @ 50000020 = 00000000
GPIOA.AFRH @ 50000024 = 00000000
GPIOA.BRR @ 50000028 =
pyocd> reg -p GPIOB
GPIOB.MODER @ 50000400 = 0003ffff
GPIOB.OTYPER @ 50000404 = 00000000
GPIOB.OSPEEDR @ 50000408 = 00000000
GPIOB.PUPDR @ 5000040c = 00000000
GPIOB.IDR @ 50000410 = 00000000
GPIOB.ODR @ 50000414 = 00000000
GPIOB.BSRR @ 50000418 = 00000000
GPIOB.LCKR @ 5000041c = 00000000
GPIOB.AFRL @ 50000420 = 00000000
GPIOB.AFRH @ 50000424 = 00000000
GPIOB.BRR @ 50000428 = 00000000
pyocd> reg -p GPIOF
GPIOF.MODER @ 50001400 = 000000ff
GPIOF.OTYPER @ 50001404 = 00000000
GPIOF.OSPEEDR @ 50001408 = 00000000
GPIOF.PUPDR @ 5000140c = 00000200
GPIOF.IDR @ 50001410 = 00000000
GPIOF.ODR @ 50001414 = 00000000
GPIOF.BSRR @ 50001418 = 00000000
GPIOF.LCKR @ 5000141c = 00000000
GPIOF.AFRL @ 50001420 = 00000000
GPIOF.AFRH @ 50001424 = 00000000
GPIOF.BRR @ 50001428 = 00000000





