The CH32V003 reference manual does not explain some key information for using the I2C peripheral. The issue is mainly with the I2C1_CKCFGR register, but there is also confusion around the FREQ field of the I2C1_CTLR2 register.
Digging a bit, it turns out that WCH appears to be using the same I2C peripheral IP as Gigadevices and Puya use in many of their microcontrollers. Both of the other vendors have a bit more documentation in their manuals.
The explanation of the I2C1_CKCFGR register turns out to be fairly simple, and the values used by the I2C examples going around the net appear to be correct. Its documentation in the reference manual says the following
However, what is left unsaid is the following
Mode | Duty | T_high clock cycles | T_low clock cycles |
F/S=0 | Either | CCR | CCR |
F/S=1 | DUTY=0 | CCR | 2*CCR |
F/S=1 | DUTY=1 | 9*CCR | 16*CCR |
The bitrate is just BR = F_APB1 / (T_high + T_low), and thus the magical factors of 2, 3 and 25 used in the examples are explained. This also explains where the quoted duty cycles of 33% and 36% come from.
This still leaves the FREQ field in I2C1_CTLR2 to be explained. It is described as
The Gigadevices and Puya documentation agree with WCH's documentation on this: you're expected to program the field with a value, which is the peripheral clock frequency given in integer megahertz.
All examples for the CH32V003 I've seen on the net, however, program it as F_APB1 / 2000000, which is half of the correct value. Also, many of the examples document the value 2000000 as the frequency of the peripheral, which is not correct at all. WCH's documentation is worded in less obvious terms than the other vendors, which may be the source of the confusion.
But what does this register do? If it's possible to write only half of the correct value to the field and still have the peripheral seemingly work, it can't be very critical. This is only covered in the Puya documentation, and still with not enough detail. The English translation states:
This register must be configured with the value of the APB clock frequency to generate data setup and hold times that are compatible with the I2C protocol.