Author Topic: $0.11 PY32F002A: Cortex-M0+ MCU, actually a PY32F030! 32/4KB, 48MHz, PLL, DMA...  (Read 201896 times)

0 Members and 28 Guests are viewing this topic.

Offline RogerClark

  • Regular Contributor
  • *
  • Posts: 66
  • Country: au
    • Roger Clark's blog
OK.
I tried resetting the PC and SP back to their start addresses and then typed 'continue' which runs the application, and it got a SIGTRAP immediatly

Code: [Select]
Program received signal SIGTRAP, Trace/breakpoint trap.
0xfffffffe in ?? ()

I then reset everything again and single stepped , and the PC went wrong at exactly the same place in the code
Code: [Select]
(gdb)
0x200002b4 in ?? ()


So it something in the firmware at address 0x200002b4 or possibly 0x200002b6 is where the hard fault is happening

If you changed the WDT prescaler, then the WDT can't be the problem, unless the prescaler change isn't working
 

Online DavidAlfaTopic starter

  • Super Contributor
  • ***
  • Posts: 6926
  • Country: es
Its the wdt.
The wdt doesn't stop when halted unless you entable the dbg_mcu freeze bits.
Plus 00AA enables both IWDG and WWDG...
I tried few things but none worked.
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 

Offline RogerClark

  • Regular Contributor
  • *
  • Posts: 66
  • Country: au
    • Roger Clark's blog
dumb question, but wouldn't it be possible to keep constantly 'feeding' both the WDT's

Reading the data sheet... about the IWDG

IWDG_RLR reload value defaults to its highest value 0x00000FFF

IWDG_PR is set to its lowest value, which gives 000: divider /4,  But I think you said that you already changed this to its maximum value 111: divider /256

I read the reference manual, about feeding the watchdog,

IWDG_KR

"These bits must be written by software at regular intervals with
the key value 0XAAAA, otheRWise the watchdog generates a
reset when the counter reaches 0."

and also that  0x5555 needs to be written to this reg before you change the prescaler



I'm not sure what you mean about WWDG

I checked the reference manual for the PY32F002 and there is nothing about WWDG

But the PY32F030 has the WWDT and perhaps all chips labelled as 002 are actually 030

Anyway. According to the reference sheet..

It looks like

WWDG_SR

bit 1

"Early wakeup interrupt flag
This bit is set by hardware when the counter has
reached the value 40h. It must be cleared by
software by writing ‘0’. A write of ‘1’ has no effect.
This bit is also set if the interrupt is not enabled."


I'm not sure if this can only be cleared after this bit has reached 1, or whether you can just constantly clear it

i.e in my tests I think there it looks like there are 87 bytes of instructions between when the program starts and the WDT gets triggered.
So if they are 4 byte instructions, that's round 21 instructions...

Then allowing for perhaps 8 instructions to clear both WDT's

That would leave around 12 instructions before the WDT would again need to be fed.

I'm not sure if this is achievable in C.

Possibly something would need to be coded in assembler


Can you post your code and I'll try setting up a dev env so I can compile and link it.
I'd need your c file and also your linker file


« Last Edit: August 19, 2026, 10:18:19 pm by RogerClark »
 

Offline RogerClark

  • Regular Contributor
  • *
  • Posts: 66
  • Country: au
    • Roger Clark's blog
FYI.

I've started to work on the code to prevent the WDT's from triggering, as a first step in being able to change the option bytes

This is currently untested code, but from reading the manuals for the F002 and F030 what I need to do is

1. Unlock the IWDG prescaler register by writing 0x5555 to the key register
2. Set the prescaler to the maximum division
3. If I'm reading the reference manual correctly,  to 'feed' the watchdog I need to write 0xAAAA to the key register

The WWDG is a bit more complex in that you seem to need to update the downcounter value when its value is within a specific range of values,

The reference manual for the F030 says
"When the downcounter reaches the value 0x40, an EWI interrupt is PY32F030 generated "
and
"The downcounter must be reloaded when its value is lower than the window register value and greater than 0x3F"

And the window value appears to be 0x7F.

i.e as soon as the downcounter value < 0x7F then it can be set again to 0x7F. 

Hence my first pass at feeding the watchdogs is ...


Code: [Select]
#define IWDG_KR (*(volatile unsigned int *)0x40003000)
#define IWDG_PR (*(volatile unsigned int *)0x40003004)
#define IWDG_SR (*(volatile unsigned int *)0x4000300C)


#define WWDG_CR  (*(volatile unsigned int *)0x40002C00)
#define WWDG_CFR (*(volatile unsigned int *)0x40002C04)
#define WWDG_SR  (*(volatile unsigned int *)0x40002C08)


int main(void)
{
while(IWDG_SR&0x01 );// wait until the prescaler updatable bit is cleared
IWDG_KR = 0X5555; // allow access to key register IWDG_PR
IWDG_PR = 0x7; // set prescaler divider to 256
 
for (;;)
{
IWDG_KR = 0XAAAA;// feed the watchdog
if ((WWDG_CR & 0x7F) < 0x7F)
{
WWDG_CR = 0x7F;
}
}
       
}


 

Offline RogerClark

  • Regular Contributor
  • *
  • Posts: 66
  • Country: au
    • Roger Clark's blog
Well.

This is very strange.

I wrote this test program to constantly feed the watchdogs and to also read the option bytes

(test.c)
Code: [Select]
#define IWDG_KR (*(volatile unsigned int *)0x40003000)
#define IWDG_PR (*(volatile unsigned int *)0x40003004)
#define IWDG_SR (*(volatile unsigned int *)0x4000300C)


#define WWDG_CR  (*(volatile unsigned int *)0x40002C00)
#define WWDG_CFR (*(volatile unsigned int *)0x40002C04)
#define WWDG_SR  (*(volatile unsigned int *)0x40002C08)


#define FLASH_BASE      0x40022000UL

#define FLASH_SR        (*(volatile unsigned int *)(FLASH_BASE + 0x10))
#define FLASH_CR        (*(volatile unsigned int *)(FLASH_BASE + 0x14))
#define FLASH_OPTR      (*(volatile unsigned int *)(FLASH_BASE + 0x20))

#define OPTION_WORD     (*(volatile unsigned int *)0x1FFF0E80UL)

volatile unsigned int option_mem;
volatile unsigned int option_reg;
volatile unsigned int flash_sr;
volatile unsigned int flash_cr;


void init(void)
{
while(IWDG_SR&0x01 );// wait until the prescaler updatable bit is cleared
IWDG_KR = 0X5555; // allow access to key register IWDG_PR
IWDG_PR = 0x7; // set prescaler divider to 256
}

void feedWatchDog(void)
{
IWDG_KR = 0XAAAA;// feed the watchdog
if ((WWDG_CR & 0x7F) < 0x70)
{
WWDG_CR = 0x7F;
}
}

volatile unsigned int counter = 0;

void readOptionOptionsRegisters(void)
{
option_mem = OPTION_WORD;
    option_reg = FLASH_OPTR;
    flash_sr   = FLASH_SR;
    flash_cr   = FLASH_CR;
}

int main(void)
{

init();


readOptionOptionsRegisters();

for (;;)
{
feedWatchDog();
counter++;
}
       
}



and used this linker file to link it

(ram.ld)
Code: [Select]
ENTRY(main)

MEMORY
{
    RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 3K
}

SECTIONS
{
    .text :
    {
        . = ALIGN(4);
        *(.text*)
        *(.rodata*)
        . = ALIGN(4);
    } > RAM

    .data :
    {
        . = ALIGN(4);
        *(.data*)
        . = ALIGN(4);
    } > RAM

    .bss (NOLOAD) :
    {
        . = ALIGN(4);
        *(.bss*)
        *(COMMON)
        . = ALIGN(4);
    } > RAM
}

and compiled on the command line, using this bat file


(build_ram.bat)
Code: [Select]
arm-none-eabi-gcc ^
    -mcpu=cortex-m0 ^
    -mthumb ^
    -ffreestanding ^
    -fno-builtin ^
    -nostdlib ^
    -nostartfiles ^
    -Wl,-T,ram.ld ^
    -Wl,-e,main ^
    -Wl,-Map,ram.map ^
    -Os ^
-g ^
    test.c ^
    -o ram.elf

if errorlevel 1 exit /b 1

arm-none-eabi-objcopy ^
    -O binary ^
    ram.elf ^
    ram.bin

if errorlevel 1 exit /b 1

arm-none-eabi-size ram.elf
arm-none-eabi-nm -n ram.elf


Then loaded the elf using JLink GDB server connected to the board and the generic arm gdb exe

note the application is simply called ram at the moment ie ram.bin ram.elf etc

so GDB to load the ram elf file is


arm-none-eabi-gdb ram.elf


Then inside GDB. connect to the jlinkgdb server, set the SP and the PC and then run, and it no longer hard resets.


target extended-remote localhost:2331
load
monitor reset
set $sp = 0x20000c00
set $pc = main
continue


Then halting the firmware (^C in GDB)

and then looking at the various options registers using GDB commands

p/x option_mem
p/x option_reg
p/x flash_sr
p/x flash_cr
p/x counter

I get

option_mem = 0x4155BEAA
option_reg = 0x0000BEAA
flash_sr   = 0x00000000
flash_cr   = 0xC0000000


i.e the options reg now seems to contain the correct value of 0xBEAA  not 0x00AA

Power cycling the board and double checking the options reg, and its still OK

I've also just recompiled my test program so that it no longer feeds the watchdogs and it also runs without resetting

I can also connect with JFlash and readback the flash etc


At the moment I've no idea why the MCU is now OK


@DavidAlfa   if your MCU is still bricked, can you try replicating this





 

Online DavidAlfaTopic starter

  • Super Contributor
  • ***
  • Posts: 6926
  • Country: es
The problem is the watchdogs not stoppping with the debugger (DBG_MCU freeze bits not enabled by default).
After a LOT of testing I managed to find a way that always works.

In this version I manually set the watchdogs and freeze bits from the debugger, this allows to continue the mcu resetting itself.
Then the fw does the rest.
One of the big issues was FLASH_WaitForLastOperation() calling LL_mDelay(), which depends on Systick() (Which is not enabled).
I replaced that wait with simple flag check, clearing the watchdogs while not done.


Still, nothing worked at first, or only from time to time...
Then I accidentally found that waiting ~1 second before issuing the final g command made a huge difference, always working!

DO NOT set config bits to 0x00AA...  |O :-DD
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 

Offline RogerClark

  • Regular Contributor
  • *
  • Posts: 66
  • Country: au
    • Roger Clark's blog
Thanks.

Definitely setting the Option bytes to 0x00AA or 0x0055 is definitely NOT a good idea !

Its my bad for doing this on my board, because I relied on an AI overview saying that the option byte needs to be set to 0xAA to unlock, and I didn't check whether the Option Byte didn't do anything else.

What's strange is that all I had to do is run my firmware which fed both watchdogs and my option bytes fixed themselves.

I wonder, for some unknown reason, the version of the option byte in 0x1FFF0E80UL  was perhaps correct but was not being copied across to 0x40022020

I know the value in 0x40022020 was 0x00AA but I don't think I checked 0x1FFF0E80UL 

I'm going to do some more experiments with setting the option byte to 0x00AA and see if I can recover cleanly without needing to delay hitting g the JLinkCLI

My code is totally bare metal so won't be calling functions like FLASH_WaitForLastOperation()

I may also try checking whether my PY32F002 has both WDT's i.e if its a F030X6 or whether it is really a F002 which doesn't have both
 

Online DavidAlfaTopic starter

  • Super Contributor
  • ***
  • Posts: 6926
  • Country: es
I only have 002A's. For sure they have both wdg!
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 

Offline RogerClark

  • Regular Contributor
  • *
  • Posts: 66
  • Country: au
    • Roger Clark's blog
OK.

Thats going to confuse anyone who believes what is written in the data sheets, as the spec shows one of the WDT option bits as being "reserved".

 

Offline RogerClark

  • Regular Contributor
  • *
  • Posts: 66
  • Country: au
    • Roger Clark's blog
BTW.

I checked the source files for the FLM files (debugger files) used by OpenOCD and SeggerJlink etc, and the code handles the IWDG but NOT the WWDG

The FLM files could be modified so that the WWDT is also handled, then everything would be fine !

 

Offline bingo600

  • Super Contributor
  • ***
  • Posts: 2315
  • Country: dk
Thats going to confuse anyone who believes what is written in the data sheets, as the spec shows one of the WDT option bits as being "reserved".

@Rodger
When i played with the PY32's in the beginning of this thread, was also the 20-pin F002A.
I seem to remember that the "general understanding" was that it was the same die as the F030X6, just bonded to other pins.
And that the F030X6 DS was the one describing it the best, except for pinout.

/Bingo
« Last Edit: August 22, 2026, 08:00:05 am by bingo600 »
 

Offline RogerClark

  • Regular Contributor
  • *
  • Posts: 66
  • Country: au
    • Roger Clark's blog
@bingo600

Yes. For people who read this thread, they would be well aware about this.

I'm very late to the party and the only reason I ended up here, is that I have been writing my own firmware for some GPS Speedometer's which I bought from AliExpress and they use the PY32F002AW16 (16 pins), and removing the RDP initially proved difficult

Anyway

Thanks all.
 

Online DavidAlfaTopic starter

  • Super Contributor
  • ***
  • Posts: 6926
  • Country: es
A power board I made for a friend's ride-on car.
Originally it was very slow, old 6V lead battery and weak motor.

He replaced the system with 2x powerful motor and 2x 12 Li-ion batteries...
Very good at first but... guess what? He forgot about the wire resistivity.
The wires and switches melted after 5 minutes  :-DD.

It's no joke, the current exceeds 30 Amps uphill!
So I rewired the car so the wiring and switches now are only for low power signals.
The power goes straight from the battery to the motors through the power board.
The initial prototype is crazy powerful, as now the voltage drop is minimal.

Why the py32? Mainly to read the signals, but the real deal is the soft pwm start/stop, the motors were really violent when switched on directly.

I'm thinking on using the fwd/rwd gear lever as a sequential box, so while the pedal is pressed each change unlocks 20% additional PWM.
That kid is going to have the best car on the city  :D.
« Last Edit: August 22, 2026, 06:24:43 pm by DavidAlfa »
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 
The following users thanked this post: voltsandjolts

Offline voltsandjolts

  • Supporter
  • ****
  • Posts: 3846
  • Country: gb
"FWD RWD"

So is one motor driving front axle and one for the rear - four wheel drive kiddie cart?

Most important - can you fit in it?
« Last Edit: August 22, 2026, 07:36:51 pm by voltsandjolts »
 

Online DavidAlfaTopic starter

  • Super Contributor
  • ***
  • Posts: 6926
  • Country: es
No! It's the gear selector, Forward or reverse.
Instead torturing the switches, it's read by the py32 and used to drive the H-Bridge.
I could fit if I squished myself into neutron star in the process, my friend's son is 4 ½ !
« Last Edit: August 23, 2026, 12:55:50 am by DavidAlfa »
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 

Offline vollmars

  • Newbie
  • Posts: 2
Has anyone here tested the PY32 ADC close to ground? I'm seeing an apparent dead zone on two PY32F030K18T6 boards in an eight-channel LED dimmer.

With approximately 10 mV measured directly at the ADC pin during repeated conversions, both boards consistently report zero. Comparable STM32F030 and AT32F421 boards give around 9–11 counts.

Disabling ADC calibration, increasing sampling time from 28.5 to 239.5 cycles, and reducing the ADC clock from 12 to 6 MHz made no difference. Channel selection works, and larger signals are measured normally. I'm using bare-metal firmware.

For a separate test, I connected the PWM output through 22 kΩ to the ADC input, with 10 kΩ and 1 µF in parallel to ground.

A slow PWM sweep produced:
Code: [Select]
Calculated mean input    ADC reading (averaged)
10.07 mV                 0
12.08 mV                 0
14.10 mV                 0.78
16.11 mV                 3.07
32.23 mV                 22.23
These sweep voltages are calculated assuming 0–3.3 V PWM levels, not individually measured. Some PWM ripple remains. Each reported ADC value averages repeated eight-conversion bursts.

Above this region, the response is nearly linear, consistent with an effective negative offset of about 17 counts.

Has anyone seen similar behavior on a PY32F030 or F002A? Any calibration or initialization details worth checking before blaming the ADC itself?
 

Offline smokeless0864

  • Contributor
  • Posts: 16
  • Country: hu
Occasionally I get HardFault on PY32F003x8 when I do hardware reset via pin.

Seems like it's at the very beginning of the run, WDT is not initialized so it just halts, which is not ideal!

R0: 0x0800 2E95 is:

Program entry
  "__iar_program_start": 0x08002e95
// Not sure if it should be 4byte aligned, then again it doesn't fail most of the time

startup file:
Reset_Handler
        LDR     R0, =SystemInit
        BLX     R0
        LDR     R0, =__iar_program_start
        BX      R0

Call stack is empty in IDE.
Anyone got an idea?

 

Offline ataradov

  • Super Contributor
  • ***
  • Posts: 12494
  • Country: us
    • Personal site
Something is strange here. You normally would get more information from the values stacked during the exception, but neither MSP or PSP point to valid memory. I would start looking at that. Step to that part when issue is not reproduced and check if stacks are setup correctly.

Once you figure out the stacks, and if the issue still happens, dump the values around the SP value. They will tell you the exact PC that failed.

It should not be aligned and odd value is fine, lower bit indicates Thumb mode, which is always true for CM0+.
« Last Edit: September 23, 2026, 12:46:58 am by ataradov »
Alex
 

Offline smokeless0864

  • Contributor
  • Posts: 16
  • Country: hu
Seems like a HW issue.

For every 4th RESETN it goes into hardfault, even with this minimal code.
POR should ensure consistent register state, C runtime should ignore RAM content, yet again it remembers that RESETN was pressed 4 times.
Could someone else try to reset a PY32 for a good 25times?

I will try to force IWDG via option bytes as a recovery.
 

Offline ataradov

  • Super Contributor
  • ***
  • Posts: 12494
  • Country: us
    • Personal site
This looks like some issue with the startup code. Your SP is still wrong, so nothing will work. You need  to trace the place where it is initialized and se why it is messed up.

Every 4th reset may be explained by it using some uninitialized memory location, which increments each reset, so you get consistent fail sequence.

I really doubt it is a hardware issue.
Alex
 

Online DavidAlfaTopic starter

  • Super Contributor
  • ***
  • Posts: 6926
  • Country: es
I've seen this but only when debugging, doesn't seem to happen when running normally.
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 

Offline smokeless0864

  • Contributor
  • Posts: 16
  • Country: hu
I managed to capture it.
At the very beginning (reset vector) CPU registers are initialized with these values.
Labeled which run will result HardFault and which don't.
As you said SP/MSP has wrong value. There is no code that ran at this point so it's all HW.
There is no BOOT0/1 pin.

Bug fix was to override SP on startup to known good one:

Reset_Handler
        LDR     R0, =0x200010A8           ; force MSP
        MSR     MSP, R0
        LDR     R0, =SystemInit
 

Offline ataradov

  • Super Contributor
  • ***
  • Posts: 12494
  • Country: us
    • Personal site
Yes, this would be a hardware issue.  It looks like flash may not be fully ready by the time core reads it.
Alex
 

Offline josip

  • Regular Contributor
  • *
  • Posts: 178
  • Country: hr
- they will NOT accept debugging on 5V supply only 3V3. could be my ST-Link or maybe its the chip.

Debugging on 5V with MCU-Link is working fine.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

-->