#; Filename: startup_ch32v003.S #; Author: Jake G #; Date: 2026-01-05 #; Version: 0.1.0 #; Description: Vectortable and startup code. #; Mostly it's just built from the QingKeV2 reference manual combined with the #; comments and definitions I've found online and from the RISC-V ISA for the #; ISA: RV32I with Ec extension, missing the multiplacation hardware from the #; CH32V002/4/5/6/7 series. #; First some info about the terms: #; handler: Actual code that "handles" events that causes the intterput. #; Vector table entry: address that points to the code/handler. #; Example: For SPI1_IRQHandler the order would go: #; vector_table --> SPI1_IRQHandler --> code or orverride. #; Create the init section, #; .init, places it at the reset address. #; a --> allocate(goes into memory) #; x --> executable. #; @progbits --> contains actual code/data not bss .section .init, "ax", @progbits .globl _start /* Export entry point, aka where cpu begins after reset.*/ .align 2 /* We pad until we align on 2byte boundry. */ _start: /* The start label */ .option norvc; /* Disables generation of compressed instructions*/ j handle_reset /* Starts from QingKeV2 Table 3-1 Exeception & interrupt vector table.*/ .word 0 /* All of these jump to zero/reset vector if hit, they are reserved / unused. */ .word NMI_Handler /* NMI Handler */ .word HardFault_Handler /* Hard Fault Handler */ .word 0 .word 0 .word 0 .word 0 .word 0 .word 0 .word 0 .word 0 .word SysTick_Handler /* SysTick Handler */ .word 0 .word SW_Handler /* SW Handler */ .word 0 /* External Interrupts */ .word WWDG_IRQHandler /* Window Watchdog */ .word PVD_IRQHandler /* PVD through EXTI Line detect */ .word FLASH_IRQHandler /* Flash */ .word RCC_IRQHandler /* RCC */ .word EXTI7_0_IRQHandler /* EXTI Line 7..0 */ .word AWU_IRQHandler /* AWU */ .word DMA1_Channel1_IRQHandler /* DMA1 Channel 1 */ .word DMA1_Channel2_IRQHandler /* DMA1 Channel 2 */ .word DMA1_Channel3_IRQHandler /* DMA1 Channel 3 */ .word DMA1_Channel4_IRQHandler /* DMA1 Channel 4 */ .word DMA1_Channel5_IRQHandler /* DMA1 Channel 5 */ .word DMA1_Channel6_IRQHandler /* DMA1 Channel 6 */ .word DMA1_Channel7_IRQHandler /* DMA1 Channel 7 */ .word ADC1_IRQHandler /* ADC1 */ .word I2C1_EV_IRQHandler /* I2C1 Event */ .word I2C1_ER_IRQHandler /* I2C1 Error */ .word USART1_IRQHandler /* USART1 */ .word SPI1_IRQHandler /* SPI1 */ .word TIM1_BRK_IRQHandler /* TIM1 Break */ .word TIM1_UP_IRQHandler /* TIM1 Update */ .word TIM1_TRG_COM_IRQHandler /* TIM1 Trigger and Commutation */ .word TIM1_CC_IRQHandler /* TIM1 Capture Compare */ .word TIM2_IRQHandler /* TIM2 */ /* Re-enable the usage of compressed instructions. */ /* The following aren't posision-sensative so compressed is okay.*/ .option rvc /* Section where we start code stuff (text), allocated and executable. */ /* The seperate section allows us to relocate it wherever we need. */ .section .text.vector_handler, "ax", @progbits /*Weak symbol declaractions, can be overriden without modifying startupcode. */ .weak NMI_Handler .weak HardFault_Handler .weak SysTick_Handler .weak SW_Handler .weak WWDG_IRQHandler .weak PVD_IRQHandler .weak FLASH_IRQHandler .weak RCC_IRQHandler .weak EXTI7_0_IRQHandler .weak AWU_IRQHandler .weak DMA1_Channel1_IRQHandler .weak DMA1_Channel2_IRQHandler .weak DMA1_Channel3_IRQHandler .weak DMA1_Channel4_IRQHandler .weak DMA1_Channel5_IRQHandler .weak DMA1_Channel6_IRQHandler .weak DMA1_Channel7_IRQHandler .weak ADC1_IRQHandler .weak I2C1_EV_IRQHandler .weak I2C1_ER_IRQHandler .weak USART1_IRQHandler .weak SPI1_IRQHandler .weak TIM1_BRK_IRQHandler .weak TIM1_UP_IRQHandler .weak TIM1_TRG_COM_IRQHandler .weak TIM1_CC_IRQHandler .weak TIM2_IRQHandler /* Define te default handler's*/ /* These handlers preform infinite loops if entered without an override.*/ /* ASM Instructions: `1` Local label, `j` jump, `1b` nearest label 1 backwards. */ /* From what I can see it's a single line way to impliment a inf loop.*/ /* These using the compressed instructions only take 2Bytes per line. */ NMI_Handler: 1: j 1b HardFault_Handler: 1: j 1b SysTick_Handler: 1: j 1b SW_Handler: 1: j 1b WWDG_IRQHandler: 1: j 1b PVD_IRQHandler: 1: j 1b FLASH_IRQHandler: 1: j 1b RCC_IRQHandler: 1: j 1b EXTI7_0_IRQHandler: 1: j 1b AWU_IRQHandler: 1: j 1b DMA1_Channel1_IRQHandler: 1: j 1b DMA1_Channel2_IRQHandler: 1: j 1b DMA1_Channel3_IRQHandler: 1: j 1b DMA1_Channel4_IRQHandler: 1: j 1b DMA1_Channel5_IRQHandler: 1: j 1b DMA1_Channel6_IRQHandler: 1: j 1b DMA1_Channel7_IRQHandler: 1: j 1b ADC1_IRQHandler: 1: j 1b I2C1_EV_IRQHandler: 1: j 1b I2C1_ER_IRQHandler: 1: j 1b USART1_IRQHandler: 1: j 1b SPI1_IRQHandler: 1: j 1b TIM1_BRK_IRQHandler: 1: j 1b TIM1_UP_IRQHandler: 1: j 1b TIM1_TRG_COM_IRQHandler: 1: j 1b TIM1_CC_IRQHandler: 1: j 1b TIM2_IRQHandler: 1: j 1b /* Another section that holds the text section, aka code for handle_reset*/ /* Same as previous sections, marked for allocation and execution. */ .section .text.handle_reset, "ax", @progbits /* We weakly define the handle_reset label */ .weak handle_reset /* Align 1 because of RVC(compressed instructions).*/ .align 1 handle_reset: /* Global Pointer(GP) register init.*/ .option push /* What does this mean? */ .option norelax /* Prevents optimizing the la(load address) instruction. */ la gp, __global_pointer$ /* __global_pointer$ is liker defined */ .option pop /* What does this mean? */ /* We need to do the stack setup because the MCU has no MMU or stack checking */ /* GNUC stuff needs some more inspection.*/ 1: la sp, _eusrstack /*Sets stack pointer to the top of user stack.*/ /* MAX: Addition needed here */ #if __GNUC__ > 10 .option arch, +zicsr /*Enables CSR(control and status registers) needed for newer GCC toolchains. */ #endif 2: /* Load data section from flash to RAM */ /* LMA (Load Memory Address) is a term used in embedded systems to */ /* indicate the address in memory where a program or data is loaded */ /* VMA: Virtual Memory Address. */ la a0, _data_lma /* SRC: Load arg/address a0 with (flash/LMA) */ la a1, _data_vma /* DST: load return/address a1 with (RAM/VMA) */ la a2, _edata /* End of data */ bgeu a1, a2, 2f /* If `.data` is empty then skip. */ /* The copy loop: local label*/ 1: lw t0, (a0) /*Load word into temp 0 reg, from a0 `()` from pointed loc? */ sw t0, (a1) /*Stores word from tmp0 into a1 reg pointed addr */ addi a0, a0, 4 /*Add immediate, */ addi a1, a1, 4 /*Add immediate, */ bltu a1, a2, 1b /*Unsigned comparision, with jump/loop back.*/ /* Clear the .bss: local label */ /* The .bss section is all for the zero initialized globals. */ 2: /* clear bss section */ la a0, _sbss /* Load address reg a0 with linker defined _sbss */ la a1, _ebss /* Load address reg a1 with linker defined _ebss */ bgeu a0, a1, 2f /* Compare and jump 2 forward */ /* */ 1: sw zero, (a0) /* Store Word into a0, aka zero out a0 */ addi a0, a0, 4 /* Add immediate*/ bltu a0, a1, 1b /* Compare and loop this local label */ /* This is jumped to if X */ /* */ 2: /* Sets the MIE, machine interrupt enable bit, enables interrupts globally */ /* Needed for the QingKeV2 otherwise all the interrupts are masked forever. */ li t0, 0x80 /* Load immediate to temp 0, with 0x80 */ csrw mstatus, t0 /* Control and status register write: t0 to mstatus. */ /* Sets/configures the QingKeV2 interrupt controller */ /* Enables: vectored interrupts, nested interrupts. */ li t0, 0x3 /* Load immediate to temp 0 with `0x3` */ csrw 0x804, t0 /* Control and status register write: t0. */ /* Load address into tmp 0 with address of _start code. */ la t0, _start ori t0, t0, 3 /* OR immediate, */ csrw mtvec, t0 /* Control status register write: _start to vector base table*/ /* mtvec: 00 --> direct mode, 11 --> vectored mode. */ /* Logical OR with 3(0b11) means we selected vector mode. */ /* Saves some space if we don't use it.*/ /* Optional C++ runtime support:*/ /* Run Constructors __libc_fini_array */ /* Destructors registered at `atexit` */ #if defined(__PIO_CPP_SUPPORT__) /* register fini (destructor array) call at exit if wanted (bloats up RAM+Flash) */ #if defined(__PIO_CPP_CALL_FINI__) la a0,__libc_fini_array call atexit #endif /* call into C++ constructors now */ call __libc_init_array #endif /* SystemInit: does stuff like clock setup, PLL cnofig, Periph reset. */ /* This mcu and microprocessor only has "M-Mode" or machine mode */ /* The other modes, U(user) and S(Supervisor) aren't availble. */ /* MEPC: Machine Exception Program Counter.*/ /* Jump's and links(ra --> return address) to SystemInit */ jal SystemInit la t0, main /* Load address of main into tmp 0 */ csrw mepc, t0 /* Control status register write t0 to the */ mret