|
| 1 | +from typing import * |
| 2 | + |
| 3 | +from typing_extensions import override |
| 4 | + |
| 5 | +from ...circuits import * |
| 6 | +from ...vendor_parts.jlc.JlcPart import JlcPart |
| 7 | +from ..connector.Headers import PinHeader254 |
| 8 | +from ..connector.TagConnect import TagConnect |
| 9 | + |
| 10 | + |
| 11 | +@abstract_block_default(lambda: Ch32vSdi2Header254) |
| 12 | +class Ch32vSdi2Header(ProgrammingConnector): |
| 13 | + """Abstract programming header for the CH32V using the two-pin SDI interface.""" |
| 14 | + |
| 15 | + def __init__(self) -> None: |
| 16 | + super().__init__() |
| 17 | + |
| 18 | + self.pwr = self.Port(VoltageSink.empty(), [Power]) |
| 19 | + self.gnd = self.Port(Ground.empty(), [Common]) |
| 20 | + self.swclk = self.Port(DigitalBidir.empty()) # despite similar naming, this is not ARM SWD |
| 21 | + self.swdio = self.Port(DigitalBidir.empty()) |
| 22 | + self.reset = self.Port(DigitalBidir.empty(), optional=True) # may not be connected internally |
| 23 | + |
| 24 | + |
| 25 | +class Ch32vSdi2Header254(Ch32vSdi2Header): |
| 26 | + """4-pin minimal programming header for CH32V.""" |
| 27 | + |
| 28 | + @override |
| 29 | + def contents(self) -> None: |
| 30 | + super().contents() |
| 31 | + |
| 32 | + self.gnd.init_from(Ground()) |
| 33 | + self.pwr.init_from(VoltageSink()) |
| 34 | + self.swclk.init_from(DigitalBidir()) |
| 35 | + self.swdio.init_from(DigitalBidir()) |
| 36 | + self.reset.init_from(DigitalBidir()) # not connected |
| 37 | + |
| 38 | + self.conn = self.Block(PinHeader254(4)).connected( |
| 39 | + {"1": self.pwr, "2": self.gnd, "3": self.swdio, "4": self.swclk} |
| 40 | + ) |
| 41 | + |
| 42 | + |
| 43 | +class Ch32vSdi2Tc2030(Ch32vSdi2Header): |
| 44 | + """UNOFFICIAL tag connect header, based on the SWD pinout.""" |
| 45 | + |
| 46 | + @override |
| 47 | + def contents(self) -> None: |
| 48 | + super().contents() |
| 49 | + |
| 50 | + self.gnd.init_from(Ground()) |
| 51 | + self.pwr.init_from(VoltageSink()) |
| 52 | + self.swclk.init_from(DigitalBidir()) |
| 53 | + self.swdio.init_from(DigitalBidir()) |
| 54 | + self.reset.init_from(DigitalBidir()) |
| 55 | + |
| 56 | + self.conn = self.Block(TagConnect(6)).connected( |
| 57 | + {"1": self.pwr, "5": self.gnd, "2": self.swdio, "4": self.swclk, "3": self.reset} |
| 58 | + ) |
| 59 | + # unused: 6 = swo |
| 60 | + |
| 61 | + |
| 62 | +class Ch32v203_Device( |
| 63 | + IoControllerI2cTarget, |
| 64 | + IoControllerUsb, |
| 65 | + IoControllerCan, |
| 66 | + InternalSubcircuit, |
| 67 | + BaseIoControllerPinmapGenerator, |
| 68 | + GeneratorBlock, |
| 69 | + JlcPart, |
| 70 | + FootprintBlock, |
| 71 | +): |
| 72 | + _PIN_MAPPING = { # -K6/K8 version, LQFP-32 package |
| 73 | + # "PD0": "2", # OSCI |
| 74 | + # "PD1": "3", # OSCO |
| 75 | + "PA0-WKUP": "6", |
| 76 | + "PA1": "7", |
| 77 | + "PA2": "8", |
| 78 | + "PA3": "9", |
| 79 | + "PA4": "10", |
| 80 | + "PA5": "11", |
| 81 | + "PA6": "12", |
| 82 | + "PA7": "13", |
| 83 | + "PB0": "14", |
| 84 | + "PB1": "15", |
| 85 | + "PA8": "18", |
| 86 | + "PA9": "19", |
| 87 | + "PA10": "20", |
| 88 | + "PA11": "21", |
| 89 | + "PA12": "22", |
| 90 | + # "PA13": "23", # SWDIO |
| 91 | + # "PA14": "24", # SWCLK |
| 92 | + "PA15": "25", |
| 93 | + "PB3": "26", |
| 94 | + "PB4": "27", |
| 95 | + "PB5": "28", |
| 96 | + "PB6": "29", |
| 97 | + "PB7": "30", |
| 98 | + # "PB8": "31", # BOOT0 |
| 99 | + } |
| 100 | + |
| 101 | + def __init__(self, **kwargs: Any) -> None: |
| 102 | + super().__init__(**kwargs) |
| 103 | + |
| 104 | + # Additional ports (on top of BaseIoController) |
| 105 | + self.vdd = self.Port( |
| 106 | + VoltageSink( |
| 107 | + voltage_limits=(3.0, 3.6) * Volt, # stricter range when USB used, otherwise down to 2.7 |
| 108 | + current_draw=(0.0005, 23.37) * mAmp + self.io_current_draw.upper(), # standby min to 144MHz run max |
| 109 | + ), |
| 110 | + [Power], |
| 111 | + ) |
| 112 | + self.vss = self.Port(Ground(), [Common]) |
| 113 | + |
| 114 | + self.nrst = self.Port( # Table 3-19 |
| 115 | + DigitalSink.from_supply( |
| 116 | + self.vss, |
| 117 | + self.vdd, |
| 118 | + voltage_limit_tolerance=(-0.3, 0.3) * Volt, |
| 119 | + input_threshold_abs=( |
| 120 | + 0.28 * (self.vdd.link().voltage.lower() - 1.8) + 0.6 + self.vss.link().voltage.lower(), |
| 121 | + 0.41 * (self.vdd.link().voltage.upper() - 1.8) + 1.3 + self.vss.link().voltage.upper(), |
| 122 | + ), |
| 123 | + pullup_capable=True, |
| 124 | + ), |
| 125 | + optional=True, |
| 126 | + ) # note, switched internal pull-up resistor, 30-50 kOhm |
| 127 | + |
| 128 | + self.osc = self.Port( |
| 129 | + CrystalDriver(frequency_limits=(3, 25) * MHertz, voltage_out=self.vdd.link().voltage), optional=True |
| 130 | + ) # Table 4-11 crystal / resonator specs, typ 8 MHz |
| 131 | + |
| 132 | + self._dio_ft_model = DigitalBidir.from_supply( |
| 133 | + self.vss, |
| 134 | + self.vdd, |
| 135 | + voltage_limit_abs=(-0.3, 5.5) * Volt, # table 4-1 |
| 136 | + current_limits=(-20, 20) * mAmp, # 4.3.10 output drive current characteristics |
| 137 | + input_threshold_abs=( # table 3-16 |
| 138 | + 0.32 * (self.vdd.link().voltage.lower() - 1.8) + 0.55 + self.vss.link().voltage.lower(), |
| 139 | + 0.42 * (self.vdd.link().voltage.upper() - 1.8) + 1.3 + self.vss.link().voltage.upper(), |
| 140 | + ), |
| 141 | + pullup_capable=True, # 30-50 kOhm |
| 142 | + pulldown_capable=True, # 30-50 kOhm |
| 143 | + ) |
| 144 | + self._dio_std_model = DigitalBidir.from_supply( |
| 145 | + self.vss, |
| 146 | + self.vdd, |
| 147 | + voltage_limit_tolerance=(-0.3, 0.3) * Volt, # table 4-1 |
| 148 | + current_limits=(-20, 20) * mAmp, # 4.3.10 output drive current characteristics |
| 149 | + input_threshold_abs=( # table 3-16 |
| 150 | + 0.28 * (self.vdd.link().voltage.lower() - 1.8) + 0.6 + self.vss.link().voltage.lower(), |
| 151 | + 0.42 * (self.vdd.link().voltage.upper() - 1.8) + 1 + self.vss.link().voltage.upper(), |
| 152 | + ), |
| 153 | + pullup_capable=True, # 30-50 kOhm |
| 154 | + pulldown_capable=True, # 30-50 kOhm |
| 155 | + ) |
| 156 | + |
| 157 | + self.swdio = self.Port(self._dio_ft_model) |
| 158 | + self.swclk = self.Port(self._dio_ft_model) |
| 159 | + |
| 160 | + @override |
| 161 | + def _system_pinmap(self) -> Mapping[Union[Iterable[str], str], Union[Passive, HasPassivePort]]: |
| 162 | + return { |
| 163 | + ("16", "32"): self.vss, |
| 164 | + "5": self.vdd, # VddA |
| 165 | + ("17", "1"): self.vdd, |
| 166 | + "2": self.osc.xtal_in, # TODO remappable to PD0 |
| 167 | + "3": self.osc.xtal_out, # TODO remappable to PD1 |
| 168 | + "4": self.nrst, |
| 169 | + "23": self.swdio, # TODO remappable to PA13 |
| 170 | + "24": self.swclk, # TODO remappable to PA14 |
| 171 | + "31": self.vss, # BOOT0, TODO theoretically usable as output-only PB8 |
| 172 | + } |
| 173 | + |
| 174 | + @override |
| 175 | + def _io_pinmap(self) -> PinMapUtil: |
| 176 | + # Port models |
| 177 | + adc_model = AnalogSink.from_supply( |
| 178 | + self.vss, |
| 179 | + self.vdd, |
| 180 | + voltage_limit_tolerance=(-0.3, 0.3) * Volt, # table 4-1 |
| 181 | + signal_limit_tolerance=(0, 0) * Volt, # table 4-27 conversion range, up to Vref+ up to VddA |
| 182 | + # impedance depends on sampling rate |
| 183 | + ) |
| 184 | + |
| 185 | + uart_model = UartPort(DigitalBidir.empty()) |
| 186 | + spi_model = SpiController(DigitalBidir.empty()) |
| 187 | + # TODO SPI peripherals, which have fixed-pin CS lines |
| 188 | + i2c_model = I2cController(DigitalBidir.empty()) |
| 189 | + i2c_target_model = I2cTarget(DigitalBidir.empty()) |
| 190 | + |
| 191 | + return PinMapUtil( |
| 192 | + [ # table 2-1 |
| 193 | + PinResource("PC13-TAMPER-RTC", {"PC13": self._dio_std_model}), |
| 194 | + PinResource("PC14-OSC32_IN", {"PC14": self._dio_std_model}), |
| 195 | + PinResource("PC15-OSC32_OUT", {"PC15": self._dio_std_model}), |
| 196 | + PinResource("OSC_IN", {"PD0": self._dio_std_model}), |
| 197 | + PinResource("OSC_OUT", {"PD1": self._dio_std_model}), |
| 198 | + PinResource("PA0-WKUP", {"PA0": self._dio_std_model, "ADC_IN0": adc_model}), |
| 199 | + PinResource("PA1", {"PA1": self._dio_std_model, "ADC_IN1": adc_model}), |
| 200 | + PinResource("PA2", {"PA2": self._dio_std_model, "ADC_IN2": adc_model}), |
| 201 | + PinResource("PA3", {"PA3": self._dio_std_model, "ADC_IN3": adc_model}), |
| 202 | + PinResource("PA4", {"PA4": self._dio_std_model, "ADC_IN4": adc_model}), |
| 203 | + PinResource("PA5", {"PA5": self._dio_std_model, "ADC_IN5": adc_model}), |
| 204 | + PinResource("PA6", {"PA6": self._dio_std_model, "ADC_IN6": adc_model}), |
| 205 | + PinResource("PA7", {"PA7": self._dio_std_model, "ADC_IN7": adc_model}), |
| 206 | + PinResource("PB0", {"PB0": self._dio_std_model, "ADC_IN8": adc_model}), |
| 207 | + PinResource("PB1", {"PB1": self._dio_std_model, "ADC_IN9": adc_model}), |
| 208 | + PinResource("PB2", {"PB2": self._dio_ft_model}), # BOOT1 |
| 209 | + PinResource("PB10", {"PB10": self._dio_ft_model}), |
| 210 | + PinResource("PB11", {"PB11": self._dio_ft_model}), |
| 211 | + PinResource("PB12", {"PB12": self._dio_ft_model}), |
| 212 | + PinResource("PB13", {"PB13": self._dio_ft_model}), |
| 213 | + PinResource("PB14", {"PB14": self._dio_ft_model}), |
| 214 | + PinResource("PB15", {"PB15": self._dio_ft_model}), |
| 215 | + PinResource("PA8", {"PA8": self._dio_ft_model}), |
| 216 | + PinResource("PA9", {"PA9": self._dio_ft_model}), |
| 217 | + PinResource("PA10", {"PA10": self._dio_ft_model}), |
| 218 | + PinResource("PA11", {"PA11": self._dio_ft_model}), |
| 219 | + PinResource("PA12", {"PA12": self._dio_ft_model}), # merged w/ SWDIO on some devices |
| 220 | + PinResource("PA13", {"PA13": self._dio_ft_model}), # SWDIO |
| 221 | + PinResource("PA14", {"PA14": self._dio_ft_model}), # SWCLK |
| 222 | + PinResource("PA15", {"PA15": self._dio_ft_model}), |
| 223 | + PinResource("PB3", {"PB3": self._dio_ft_model}), |
| 224 | + PinResource("PB4", {"PB4": self._dio_ft_model}), |
| 225 | + PinResource("PB5", {"PB5": self._dio_ft_model}), |
| 226 | + PinResource("PB6", {"PB6": self._dio_ft_model}), |
| 227 | + PinResource("PB7", {"PB7": self._dio_ft_model}), |
| 228 | + PinResource("PB8", {"PB8": self._dio_ft_model}), # merged w/ BOOT0 on some devices |
| 229 | + PinResource("PB9", {"PB9": self._dio_ft_model}), |
| 230 | + PeripheralFixedResource( |
| 231 | + "USART1", uart_model, {"tx": ["PA9", "PB6", "PB15", "PA6"], "rx": ["PA10", "PB7", "PA8", "PA7"]} |
| 232 | + ), |
| 233 | + PeripheralFixedResource("USART2", uart_model, {"tx": ["PA2"], "rx": ["PA3"]}), |
| 234 | + # unavailable on K8 |
| 235 | + # PeripheralFixedResource("USART3", uart_model, {"tx": ["PB10"], "rx": ["PB11"]}), |
| 236 | + # PeripheralFixedResource("USART4", uart_model, {"tx": ["PB0", "PA5"], "rx": ["PB1", "PB5"]}), |
| 237 | + PeripheralFixedResource("USB", UsbDevicePort(DigitalBidir.empty()), {"dm": ["PA11"], "dp": ["PA12"]}), |
| 238 | + # PeripheralFixedResource("USBFS", UsbDevicePort(DigitalBidir.empty()), {"dm": ["PB6"], "dp": ["PB7"]}), |
| 239 | + PeripheralFixedResource("I2C1", i2c_model, {"scl": ["PB6", "PB8"], "sda": ["PB7", "PB9"]}), |
| 240 | + PeripheralFixedResource("I2C1_T", i2c_target_model, {"scl": ["PB6", "PB8"], "sda": ["PB7", "PB9"]}), |
| 241 | + # PeripheralFixedResource("I2C2", i2c_model, {"scl": ["PB10"], "sda": ["PB11"]}), |
| 242 | + # PeripheralFixedResource("I2C2_T", i2c_target_model, {"scl": ["PB10"], "sda": ["PB11"]}), |
| 243 | + PeripheralFixedResource( |
| 244 | + "SPI1", spi_model, {"sck": ["PA5", "PB3"], "miso": ["PA6", "PB4"], "mosi": ["PA7", "PB5"]} |
| 245 | + ), |
| 246 | + # PeripheralFixedResource("SPI2", spi_model, {"sck": ["PB13"], "miso": ["PB14"], "mosi": ["PB15"]}), |
| 247 | + PeripheralFixedResource( |
| 248 | + "CAN", |
| 249 | + CanControllerPort(DigitalBidir.empty()), |
| 250 | + {"rxd": ["PA11", "PB8"], "txd": ["PA12", "PB9"]}, |
| 251 | + ), |
| 252 | + ] |
| 253 | + ).remap_pins(self._PIN_MAPPING) |
| 254 | + |
| 255 | + @override |
| 256 | + def generate(self) -> None: |
| 257 | + super().generate() |
| 258 | + |
| 259 | + self.footprint( |
| 260 | + "U", |
| 261 | + "Package_QFP:LQFP-32_7x7mm_P0.8mm", |
| 262 | + self._make_pinning(), |
| 263 | + mfr="WCH", |
| 264 | + part="CH32V203K8T6", |
| 265 | + datasheet="https://www.wch-ic.com/downloads/CH32V203DS0_PDF.html", |
| 266 | + ) |
| 267 | + self.assign(self.lcsc_part, "C5372188") |
| 268 | + self.assign(self.actual_basic_part, False) |
| 269 | + |
| 270 | + |
| 271 | +class Ch32v203( |
| 272 | + Resettable, |
| 273 | + IoControllerI2cTarget, |
| 274 | + IoControllerUsb, |
| 275 | + IoControllerCan, |
| 276 | + Microcontroller, |
| 277 | + WithCrystalGenerator, |
| 278 | + IoControllerPowerRequired, |
| 279 | + GeneratorBlock, |
| 280 | +): |
| 281 | + """General-purpose RISC-V (RV32IMAC) microcontroller with USB""" |
| 282 | + |
| 283 | + DEFAULT_CRYSTAL_FREQUENCY = 8 * MHertz(tol=0.005) # 8 MHz as typical in datasheet |
| 284 | + |
| 285 | + def __init__(self, **kwargs: Any) -> None: |
| 286 | + super().__init__(**kwargs) |
| 287 | + self.generator_param(self.reset.is_connected(), self.usb.requested(), self.can.requested()) |
| 288 | + |
| 289 | + @override |
| 290 | + def generate(self) -> None: |
| 291 | + super().generate() |
| 292 | + |
| 293 | + with self.implicit_connect(ImplicitConnect(self.pwr, [Power]), ImplicitConnect(self.gnd, [Common])) as imp: |
| 294 | + self.ic = imp.Block(Ch32v203_Device(pin_assigns=ArrayStringExpr())) |
| 295 | + self._wrap_inner(self.ic) |
| 296 | + |
| 297 | + self.sdi = imp.Block(Ch32vSdi2Header()) |
| 298 | + self.connect(self.ic.swclk, self.sdi.swclk) |
| 299 | + self.connect(self.ic.swdio, self.sdi.swdio) |
| 300 | + self.connect(self.ic.nrst, self.sdi.reset) |
| 301 | + |
| 302 | + self.connect(self.xtal_node, self.ic.osc) |
| 303 | + |
| 304 | + self.vdd_cap = ElementDict[DecouplingCapacitor]() # assume one 0.1uF cap per power pair |
| 305 | + for i in range(2): |
| 306 | + self.vdd_cap[i] = imp.Block(DecouplingCapacitor(0.1 * uFarad(tol=0.2))) |
| 307 | + self.vdda_cap = imp.Block(DecouplingCapacitor(0.1 * uFarad(tol=0.2))) |
| 308 | + |
| 309 | + self.reset_cap = self.Block(DigitalCapacitor(0.1 * uFarad(tol=0.2))).connected(self.gnd, self.ic.nrst) |
| 310 | + |
| 311 | + if self.get(self.reset.is_connected()): |
| 312 | + self.connect(self.reset, self.ic.nrst) |
| 313 | + |
| 314 | + @override |
| 315 | + def _crystal_required(self) -> bool: # crystal needed for CAN or USB b/c tighter freq tolerance |
| 316 | + return ( |
| 317 | + len(self.get(self.can.requested())) > 0 |
| 318 | + or len(self.get(self.usb.requested())) > 0 |
| 319 | + or super()._crystal_required() |
| 320 | + ) |
0 commit comments