CH32V003 CFGLR and bitwise or "|="? - Page 1
Forum section: electronics / general

author: kgavionics
author_rank: Regular Contributor
timestamp: February 25, 2025, 12:19:35 pm
content: Hello guysSorry if a silly question, but when setting up GPIO on the CH32V003, if I use the following instruction:Code:GPIOC->CFGLR |=0xBB03003; // Doesn't set up the GPIOC at allwhen I change it to:GPIOC->CFGLR =0xBB03003 ; // without |, everything is working as expectedComing for STM32, I could use the bitwise operation "|=" one the MODER register then later on the program I can modify some other GPIO pins (same port) by simply use "|=" operator.Is there any reason why I can't do that on the CH32V003?TIA

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 25, 2025, 12:36:09 pm
content: That would obviously depend on what the initial values are.Turns out that the reset value of the CNF bits is not 00, but 01. So that if you're oring with what you want to set it to, you may not end up with the right bits set.Just a thought.

author: corgon
author_rank: Contributor
timestamp: February 25, 2025, 02:19:47 pm
content: Just like SiliconWizard said. You need to check what the register value is after reset.In this case, MODE is 00b and CNF is 01b.The best way to change the bits you want is to set them to 1 first and then write the desired value.Something like this:Code:GPIOC->CFGLR &= ~(0xF << (2 * 4)); // Reset PC2 configGPIOC->CFGLR |= (0x1 << (2 * 4)); // Set PC2 to OUTPUT, GPIO_Speed_10MHz

author: kgavionics
author_rank: Regular Contributor
timestamp: February 25, 2025, 02:45:36 pm
content: Thank you guys for the clarification! all good now!

author: HwAoRrDk
author_rank: Super Contributor
timestamp: February 25, 2025, 08:00:52 pm
content: To illustrate the problem more explicitly:Default:0100 0100 0100 0100 0100 0100 0100 0100OR-ed With:0000 1011 1011 0000 0011 0000 0000 0011Result:0100 1111 1111 0100 0111 0100 0100 0111So, for CNF6 and CNF5, you end up with the value 0b11, and for CNF3 and CNF0 0b01, which for output MODE (0b11) isopen-drainrather than push-pull. So if you don't happen to have any external pull-up resistors on these lines, they will be floating whenever the output is switched high, causing things to potentially misbehave.

author: brucehoult
author_rank: Super Contributor
timestamp: February 26, 2025, 02:43:09 am
content: Quote from: corgon on February 25, 2025, 02:19:47 pmReally it's better to make a function or macro for bitfield insertion and debug it once, not do it by hand every time.Code:#define INSERT(DST, SRC, WIDTH, OFFSET) ((DST) & (((1<<(WIDTH))-1)<<(OFFSET)) | ((SRC)<<(OFFSET)))void test(long *dst, long src) {*dst = INSERT(*dst, src, 4, 2*4);}... giving ...Code:00000000 <test>:0: 411c lw a5,0(a0)2: 777d lui a4,0xfffff4: 0ff70713 addi a4,a4,255 # fffff0ff <test+0xfffff0ff>8: 8ff9 and a5,a5,a4a: 05a2 slli a1,a1,0x8c: 8fcd or a5,a5,a1e: c11c sw a5,0(a0)10: 8082 retNote: I'm assuming that src has a value that will fit. If not then you need to mask it off too.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 26, 2025, 06:21:50 am
content: Usually it's better idea to assign what you need in configuration registers instead of read-modify-write. So just = operator. You get well-defined values regardless of existing values / reset values, plus code will be smaller (flash saving) and faster (that would usually not matter in initialization, though). Part of the deal is then, you have to write the whole register at once, so need to understand what every bit does.For partial modification of some bits, you should read the register to temporary variable, then clear bits (&= ~(mask)), then apply ones (|= mask), then write back. If you only OR, you are turning bits from 0->1 but not from 1->0.

author: brucehoult
author_rank: Super Contributor
timestamp: February 26, 2025, 09:53:18 am
content: Quote from: Siwastaja on February 26, 2025, 06:21:50 amI really dislike that because it makes your code non-portable to other similar CPUs that have different default values. It's even worse if the CPU you're using has "reserved" fields that are 0s on the CPU you test with but may be non-zero in ways that are not even decided when you write your code.Unless you are running on the exact same CPU forever -- and carefully understand every field -- I'd always prefer to update only the field(s) that you actually need.QuoteExactly as the macro I showed in the message before yours does.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 26, 2025, 01:28:34 pm
content: Quote from: brucehoult on February 26, 2025, 09:53:18 amThen It Depends^tm. If just the defaults are different (but the bits have the same behavior), do you need to use the defaults, or do you need to use your own values? The correct solution depends on the answer to that question, you can't generalize it. If you rely on defaults, and defaults are different on the new target, then the application is unlikely to work. So full assignment is likely better. Then again, if the target has some totally unrelated config in the same register, it could stop working if you write them to the same values as on the old target. Possible, but IMO less likely.If the feature set of the similarly named register is partially different but some bits map similarly, then it's probably a good idea to do RMW and let the "I don't know what these bits are" just retain their values. But you can't just assume that, you need to understand registers for both MCU A and MCU B before you can do it. In which case you can also use assignment and choose the correct value based on the MCU you are compiling for, soQuoteAnd then again, some other CPU might have a register where a bit reads as x, and writing x triggers an unwanted action. STM32 series is full of stuff like this. In these cases, RMW is a disaster, and unintended RMW (from |=) instead of assignment is a common issue with young players who have not worked with STM32 peripherals before. And those traps are not limited to status registers and clearing them, they are here and there also in configuration registers.You can't generalize this, unfortunately. I don't believe in the idea that you could make peripheral register access part between incompatible parts easily portable in any other way than writing the necessary wrapper for every MCU with incompatible register interface. In which case you need to read the manual for each. And in which case you can write every bit to the correct value and not rely on reset values. Which will not reset if you just jump to reset vector or otherwise call your init function again.