Hackbadge Hackery

Hackbadge to be honest is one of the most fun projects I have ever had the pleasure to work on. It was a relatively simple badge concept wise, but due to the nature of the CH32V003 and the requirement for Low Power and low cost it forced me to make some design decisions which I would not usually be exposed to.

Concept

So here are some concept messages:

Messages
I think if we want to do a hackbash badge
1. we optimise for a metric besides computation/ hardware concept
- Cost? - whats' the budget
- Blinky/ look cool/ art? (Colour Printing? I have experience now)
2. Main aim should be to
- Attract people to hackbash - Look cool
- Encourage engagement with Hackbash
- the reason I thought about this even was that last hackbash, we did certs, stickers giveaway to encourage people to come back the 2nd day. Maybe we can do this badge instead
- it is NOT to challenge them hardware wise - that's for GreyCTF

Think more SINCON style badges and less Off By One/ GreyMecha/Army
The goal is to blink blink and be happy
maybe add flags to like
encourage engagement
then hardware flags can be like the tryhards

I was heavily inspired by the f5 badge at BlackHat, one which aimed to change booth engagement with electronic badges. Maybe we can do the same for Secondary School/ Pre University Cyber Training?

Art

For a change of pace, I decided to handle the artwork entirely by myself. Generally in NUS Greyhats, I'm the person who is ideating the badge concepts and artwork.

I had some ideas for a Dog style badge in the past purely to contrast our predominantly cat badges LOL.
Another idea was to have 8 bit pixelated cats like so:

!400
!400

Eventually given the theme of Greycademy, I figured a StackOverflow kind of theme might fit nicer.
Each item in the stack will have an LED and you are supposed to overflow the stack. There would be different categories like web and crypto (lock) for unlockables

!200

!300 !300 !300

I then redrew in Krita and got out the artwork. I then extracted out the outline in inkscape and imported it into PCB design software. The image can be imported straight into EasyEDA.

Pasted image 20260207022546.png|600

PCB Design

Design wise we started out with KiCAD then moved to EasyEDA. The import tool in EasyEDA sucks apparently, so in hindsight should have done EasyEDA from the start.
I didn't have much hand in the design, other than setting the general guidelines such as what MCU to use, what functions the badge should have etc, deciding the rough layout. There isn't anything too noteworthy here I suppose (mousebites handled by JLCPCB)

Code

I worked mainly on the firmware on the badge. The main framework used is the ch32fun. I ended up not using the HAL Libraries and did most of the low level register coding.
I used the reference manual: http://nic.vajn.icu/PDF/WCH-IC/RISC-V/CH32V003_RM.pdf.
It was a fun exercise that got me to appreciate the peripherals of the MCU (there was code to copy anyway).

I worked with Interrupts, Timers, Flash Memory, and the Low Power modes. I want to go through some experiences working with peripherals

Timers

The Timers were really interesting to work on it, due to the way IO was muxed. The main reason we wanted to use Timers is for PWM of the LEDs to conserve power (and my eyesight

Looking at the pinout you would see some of the IO mappings. Unfortunately, I do not think there was an easy way to have done the schematic such that it would have been easy to use the timers, but maybe understanding the MCU before designing the schematic better would have helped.

Pasted image 20260207093205.png|600

So most pins have a timer allocated to them, and thankfully all the LED pins chosen have a timer. However, the problem is that they do not have UNIQUE timers.
Which means that some LEDs have to reuse timers.

Pasted image 20260207093546.png|700

Furthermore, from the Reference Manual, there is only 1 alternative remapping mode.
We cannot selectively choose the timer channel only for that individual pin.
If we want to change the timer for that pin, we would need to change it for all other pins.

Pasted image 20260207093738.png|600
Pasted image 20260207093752.png|600

This means that if we want all LEDs to use the timer, we would need to switch between the different pin mappings in software (using the main CPU), like software PWM.

The concept of this is not new, and is in this example: https://github.com/cnlohr/ch32fun/blob/master/examples/tim2_pwm_remap/tim2_pwm_remap.c . However, the consequence of this is that the LED power sequence becomes very important. If you switch the IO mapping without turning off the LED, you would be left with some LEDs being on for a split second of time, short enough to be seen (as ironically, another form of PWM).

This was quite an interesting concept, and playing with it was quite fun. I had to

  1. find the best timer mapping (they all fit on 1 timer, so that was nice), and
  2. distribute each LED among the different pin mappings.
  3. After that in software, I'll mux between the modes, and
    1. enable the specific timer values for that pin.
void LED_PWM_Pass(int purple, int red, int orange, int yellow, int green, int blue){
    // Purple
    // Delay_Ms( 1000 );
    t1pwm_clear(0);
    AFIO->PCFR1 &= ~AFIO_PCFR1_TIM1_REMAP_FULLREMAP; 
    funPinMode(PD2, GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF );
    funPinMode(PC7, GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF );
    funPinMode(PC6, GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF );

    t1pwm_setpw(0, red); 
    #ifdef LED_USE_TIMER2
        t2pwm_setpw(1, purple); 
    #endif
    t1pwm_setpw(3, blue);


    // Delay_Ms( 1000 );
    t1pwm_clear(0);
    funPinModeClear(PC4, 4);
    AFIO->PCFR1 |=  AFIO_PCFR1_TIM1_REMAP_PARTIALREMAP1;

    t1pwm_setpw(0, yellow);
    t1pwm_setpw(1, orange); // linked to blue?
    #ifndef LED_USE_TIMER2
        t1pwm_setpw(3, purple); 
    #endif
    
    // Delay_Ms( 1000 );
    t1pwm_clear(0);
    funPinModeClear(PD2, 2);
    funPinModeClear(PC7, 7);
    funPinModeClear(PC6, 6);
    AFIO->PCFR1 |=  AFIO_PCFR1_TIM1_REMAP_PARTIALREMAP2;
    funPinMode( PC4, GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF ); // BLUE
    t1pwm_setpw(2, green);
}

Low Power Modes

So 10h before Greycademy, an important issue surfaced.

The badge was meant to be distributed to Greycademy participants, run on a coin cell battery. But after about 2min, the badge would brown out and shut off. One of my friends identified that the dropout voltage of the ch32v003 was too low, and given the battery chemistry, it just wasn't feasible. However, given that I saw other projects with the same MCU also, I refused to believe there wasn't a way with existing hardware. An example project is https://github.com/wagiminator/CH32V003-GameConsole, which used a CH32v003 and a coin cell battery.

I looked into the low power modes and clock speed registers in the user guides. I wondered what if I entered the low power mode but did not re-enable all the normal peripherals and system clocks. In practice, this means that the MCU would be using the low speed oscillator instead of a higher clock speed.

The result is that the badge worked much more reliably on the coin cell, resulting in at least a 90x improvement in battery life (I did not scientifically calculate this). Given my internship experience and my digital design experience with peripherals and clock gating, and even the theoretical experience in school modules, a lower clock speed would cause less switching and dynamic power consumption. Yet nothing beats seeing it in action.

I could have optimised it further by leaving the MCU mostly in sleep mode and only turning it on, but at that point it worked well enough so I moved on quickly, fixed code, took a nap, rushed to school and reprogram.

	// enable power interface module clock
	RCC->APB1PCENR |= RCC_APB1Periph_PWR;
	// enable low speed oscillator (LSI)
	RCC->RSTSCKR |= RCC_LSION;
	while ((RCC->RSTSCKR & RCC_LSIRDY) == 0) {}
	// enable AutoWakeUp event
	EXTI->EVENR |= EXTI_Line9;
	EXTI->FTENR |= EXTI_Line9;
	// configure AWU prescaler
	PWR->AWUPSC |= PWR_AWU_Prescaler_4096;
	// configure AWU window comparison value
	PWR->AWUWR &= ~0x3f;
	PWR->AWUWR |=   1; // immediately wake up
	// enable AWU
	PWR->AWUCSR |= (1 << 1);
	// select standby on power-down
	PWR->CTLR |= PWR_CTLR_PDDS;
	// peripheral interrupt controller send to deep sleep
	PFIC->SCTLR |= (1 << 2);

    // mode_ctf_main();
    debug_print("Welcome to Hackbadge\n");
    ModeBlinky_Setup();

    
    __WFE();
    // SystemInit(); // the special line commented out

The code I referenced was here: https://github.com/cnlohr/ch32fun/blob/master/examples_x00x/standby_autowake/standby_autowake.c

Interrupts

Nothing too special other than that both buttons share the same interrupt line (pressing either button would trigger the same IRQ) so logic needs to be done to handle that.

Flash Memory

Generally the code is the same as GreyCat, and most of the code was copied & modified for the ch32fun framework.

If there is one pointer I'll like to take note, it would be this:

ADC

So sound is a wave, and as such, when using the ADC to measure volume, you can calculate the volume using the Peak to Peak value, not just the raw magnitude.
I didn't exactly implement the code (sayo did) but I did give the idea sometime ago.

Other Features

Some other features I was thinking about were native USB support (the USB bootloader works on the badge), but due to time and size constraints I cut them from the final badge. I wouldn't want people just dumping firmware so easily anyway.

Conclusion

Overall it was a fun badge to play with and work on. From what I see some participants at Greycademy engaged a fair bit with it and put it even on their social media, which was nice.

Appendix: ChatGPT Summary of Handling the Toolchain

Sure — here’s a tight, condensed walkthrough that keeps the essentials and reads like a checklist, without a bunch of section headers.


Install dependencies in Linux/WSL:

sudo apt-get update
sudo apt-get install -y build-essential libnewlib-dev gcc-riscv64-unknown-elf \
  libusb-1.0-0-dev libudev-dev gdb-multiarch

Clone rv003usb into your project and build it:

git submodule add https://github.com/cnlohr/rv003usb.git code/env/rv003usb
cd code/env/rv003usb
git submodule update --init --recursive
make

Build minichlink and optionally install it system-wide:

cd ch32v003fun/minichlink
make
sudo cp minichlink /usr/local/bin/minichlink

Set udev rules so Linux can access the WCH-LinkE and rv003usb without sudo:

sudo tee /etc/udev/rules.d/99-wch-link.rules <<EOF
SUBSYSTEM=="usb", ATTR{idVendor}=="1a86", ATTR{idProduct}=="8010", MODE="0666"
EOF

echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="b003", MODE="0666"' \
| sudo tee /etc/udev/rules.d/50-rv003usb.rules

sudo udevadm control --reload-rules
sudo udevadm trigger

On Windows (PowerShell as admin), attach USB devices to WSL:

winget install usbipd
usbipd list
usbipd bind --busid 2-3
usbipd attach --wsl --busid 2-3

Verify inside WSL:

lsusb
# should show: 1209:b003 Generic rv003usb

Configure USB pins in bootloader/usb_config.h (Port C, DP=PC1, DM=PC3, DPU=PC0 or per your wiring).
Optionally increase bootloader timeout in bootloader.c:

#define BOOTLOADER_TIMEOUT_PWR 670

Build and flash the bootloader (manual flash is important):

cd bootloader
make
minichlink -E
minichlink -a -w bootloader.bin 0x0000 -B

View USB printf/debug output:

minichlink -T

Build application code (example):

cd code/tests/timer_pwm
make

Flash application over USB:

sudo minichlink -w breathing_led.bin 0x08000000 -c 0x1209b003 -b

If using a ~1920-byte bootloader, update the linker script:

FLASH (rx) : ORIGIN = 0x00000800, LENGTH = 14K

Then flash apps at the offset:

sudo minichlink -w CH32V003A4M.bin 0x00000800 -c 0x1209b003

Notes: every time the USB is unplugged or WSL restarts, you must re-attach it with usbipd. If flashing works but code doesn’t run, check clock speed, flash origin, and bootloader timeout.