|
| 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: Ch32vSdiHeader254) |
| 12 | +class Ch32vSdiHeader(ProgrammingConnector): |
| 13 | + """Abstract programming header for the CH32V using the one-pin SDI interface with SWIO pin.""" |
| 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.swio = self.Port(DigitalBidir.empty()) |
| 21 | + self.reset = self.Port(DigitalBidir.empty(), optional=True) # may not be connected internally |
| 22 | + |
| 23 | + |
| 24 | +class Ch32vSdiHeader254(Ch32vSdiHeader): |
| 25 | + """3-pin minimal programming header for CH32V.""" |
| 26 | + |
| 27 | + @override |
| 28 | + def contents(self) -> None: |
| 29 | + super().contents() |
| 30 | + |
| 31 | + self.gnd.init_from(Ground()) |
| 32 | + self.pwr.init_from(VoltageSink()) |
| 33 | + self.swio.init_from(DigitalBidir()) |
| 34 | + self.reset.init_from(DigitalBidir()) # not connected |
| 35 | + |
| 36 | + self.conn = self.Block(PinHeader254()).connected({"1": self.pwr, "2": self.gnd, "3": self.swio}) |
| 37 | + |
| 38 | + |
| 39 | +class Ch32vSdiTc2030(Ch32vSdiHeader): |
| 40 | + """UNOFFICIAL tag connect header, based on the SWD pinout and mapping SWDIO to SWIO.""" |
| 41 | + |
| 42 | + @override |
| 43 | + def contents(self) -> None: |
| 44 | + super().contents() |
| 45 | + |
| 46 | + self.gnd.init_from(Ground()) |
| 47 | + self.pwr.init_from(VoltageSink()) |
| 48 | + self.swio.init_from(DigitalBidir()) |
| 49 | + self.reset.init_from(DigitalBidir()) |
| 50 | + |
| 51 | + self.conn = self.Block(TagConnect(6)).connected({"1": self.pwr, "5": self.gnd, "2": self.swio, "3": self.reset}) |
| 52 | + # unused: 4 = swclk, 6 = swo |
| 53 | + |
| 54 | + |
| 55 | +class Ch32v003_Device( |
| 56 | + IoControllerI2cTarget, |
| 57 | + InternalSubcircuit, |
| 58 | + BaseIoControllerPinmapGenerator, |
| 59 | + GeneratorBlock, |
| 60 | + JlcPart, |
| 61 | + FootprintBlock, |
| 62 | +): |
| 63 | + _PIN_MAPPING = { # F4P6 version, TSSOP-20 |
| 64 | + "PD4": "1", |
| 65 | + "PD5": "2", |
| 66 | + "PD6": "3", |
| 67 | + # "PD7": "4", # NRST |
| 68 | + # "PA1": "5", # OSCI |
| 69 | + # "PA2": "6", # OSCO |
| 70 | + "PD0": "8", |
| 71 | + "PC0": "10", |
| 72 | + "PC1": "11", |
| 73 | + "PC2": "12", |
| 74 | + "PC3": "13", |
| 75 | + "PC4": "14", |
| 76 | + "PC5": "15", |
| 77 | + "PC6": "16", |
| 78 | + "PC7": "17", |
| 79 | + # "PD1": "18", # SWIO |
| 80 | + "PD2": "19", |
| 81 | + "PD3": "20", |
| 82 | + } |
| 83 | + |
| 84 | + def __init__(self, **kwargs: Any) -> None: |
| 85 | + super().__init__(**kwargs) |
| 86 | + |
| 87 | + # Additional ports (on top of BaseIoController) |
| 88 | + self.vdd = self.Port( |
| 89 | + VoltageSink( |
| 90 | + voltage_limits=(2.8, 5.5) * Volt, # stricter range when ADC used |
| 91 | + current_draw=(0.009, 8.0) * mAmp + self.io_current_draw.upper(), # standby min to 48MHz run max |
| 92 | + ), |
| 93 | + [Power], |
| 94 | + ) |
| 95 | + self.vss = self.Port(Ground(), [Common]) |
| 96 | + |
| 97 | + self.nrst = self.Port( # Table 3-19 |
| 98 | + DigitalSink.from_supply( |
| 99 | + self.vss, |
| 100 | + self.vdd, |
| 101 | + voltage_limit_tolerance=(-0.3, 0.3) * Volt, |
| 102 | + input_threshold_abs=( |
| 103 | + 0.28 * (self.vdd.link().voltage.lower() - 1.8) + 0.6 + self.vss.link().voltage.lower(), |
| 104 | + 0.41 * (self.vdd.link().voltage.upper() - 1.8) + 1.3 + self.vss.link().voltage.upper(), |
| 105 | + ), |
| 106 | + pullup_capable=True, |
| 107 | + ), |
| 108 | + optional=True, |
| 109 | + ) # note, switched internal pull-up resistor, 35-55 kOhm |
| 110 | + |
| 111 | + self.osc = self.Port( |
| 112 | + CrystalDriver(frequency_limits=(4, 25) * MHertz, voltage_out=self.vdd.link().voltage), optional=True |
| 113 | + ) # Table 3-10 crystal / resonator specs, typ 24 MHz |
| 114 | + |
| 115 | + self._dio_ft_model = DigitalBidir.from_supply( |
| 116 | + self.vss, |
| 117 | + self.vdd, |
| 118 | + voltage_limit_abs=(-0.3, 5.5) * Volt, # table 3.1 |
| 119 | + current_limits=(-20, 20) * mAmp, # table 3.1 |
| 120 | + input_threshold_abs=( # table 3-16 |
| 121 | + 0.19 * (self.vdd.link().voltage.lower() - 2.7) + 0.65 + self.vss.link().voltage.lower(), |
| 122 | + 0.22 * (self.vdd.link().voltage.upper() - 2.7) + 1.55 + self.vss.link().voltage.upper(), |
| 123 | + ), |
| 124 | + pullup_capable=True, # 35-55 kOhm |
| 125 | + pulldown_capable=True, # 35-55 kOhm |
| 126 | + ) |
| 127 | + self._dio_std_model = DigitalBidir.from_supply( |
| 128 | + self.vss, |
| 129 | + self.vdd, |
| 130 | + voltage_limit_tolerance=(-0.3, 0.3) * Volt, # table 3.1 |
| 131 | + current_limits=(-20, 20) * mAmp, # table 3.1 |
| 132 | + input_threshold_abs=( # table 3-16 |
| 133 | + 0.19 * (self.vdd.link().voltage.lower() - 2.7) + 0.65 + self.vss.link().voltage.lower(), |
| 134 | + 0.22 * (self.vdd.link().voltage.upper() - 2.7) + 1.55 + self.vss.link().voltage.upper(), |
| 135 | + ), |
| 136 | + pullup_capable=True, # 35-55 kOhm |
| 137 | + pulldown_capable=True, # 35-55 kOhm |
| 138 | + ) |
| 139 | + |
| 140 | + self.swio = self.Port(self._dio_std_model) |
| 141 | + |
| 142 | + @override |
| 143 | + def _system_pinmap(self) -> Mapping[Union[Iterable[str], str], Union[Passive, HasPassivePort]]: |
| 144 | + return { |
| 145 | + "7": self.vss, |
| 146 | + "9": self.vdd, |
| 147 | + "5": self.osc.xtal_in, # TODO remappable to PA1 |
| 148 | + "6": self.osc.xtal_out, # TODO remappable to PA2 |
| 149 | + "4": self.nrst, |
| 150 | + "18": self.swio, |
| 151 | + } |
| 152 | + |
| 153 | + @override |
| 154 | + def _io_pinmap(self) -> PinMapUtil: |
| 155 | + # Port models |
| 156 | + adc_model = AnalogSink.from_supply( |
| 157 | + self.vss, |
| 158 | + self.vdd, |
| 159 | + voltage_limit_tolerance=(-0.3, 0.3) * Volt, # table 3.1 |
| 160 | + impedance=(100, float("inf")) * kOhm, |
| 161 | + ) |
| 162 | + |
| 163 | + uart_model = UartPort(DigitalBidir.empty()) |
| 164 | + spi_model = SpiController(DigitalBidir.empty()) |
| 165 | + # TODO SPI peripherals, which have fixed-pin CS lines |
| 166 | + i2c_model = I2cController(DigitalBidir.empty()) |
| 167 | + i2c_target_model = I2cTarget(DigitalBidir.empty()) |
| 168 | + |
| 169 | + return PinMapUtil( |
| 170 | + [ # table 2-1 |
| 171 | + PinResource("PD4", {"PD4": self._dio_std_model, "A7": adc_model}), |
| 172 | + PinResource("PD5", {"PD5": self._dio_std_model, "A5": adc_model}), |
| 173 | + PinResource("PD6", {"PD6": self._dio_std_model, "A6": adc_model}), |
| 174 | + PinResource("PD7", {"PD7": self._dio_std_model}), # NRST |
| 175 | + PinResource("PA1", {"PA1": self._dio_std_model, "A1": adc_model}), |
| 176 | + PinResource("PA2", {"PA2": self._dio_std_model, "A0": adc_model}), |
| 177 | + PinResource("PD0", {"PD0": self._dio_std_model}), |
| 178 | + PinResource("PC0", {"PC0": self._dio_std_model}), |
| 179 | + PinResource("PC1", {"PC1": self._dio_ft_model}), |
| 180 | + PinResource("PC2", {"PC2": self._dio_ft_model}), |
| 181 | + PinResource("PC3", {"PC3": self._dio_std_model}), |
| 182 | + PinResource("PC4", {"PC4": self._dio_ft_model, "A2": adc_model}), |
| 183 | + PinResource("PC5", {"PC5": self._dio_ft_model}), |
| 184 | + PinResource("PC6", {"PC6": self._dio_ft_model}), |
| 185 | + PinResource("PC7", {"PC7": self._dio_std_model}), |
| 186 | + PinResource("PD1", {"PD1": self._dio_std_model}), # SWIO |
| 187 | + PinResource("PD2", {"PD2": self._dio_std_model, "A3": adc_model}), |
| 188 | + PinResource("PD3", {"PD3": self._dio_std_model, "A4": adc_model}), |
| 189 | + PeripheralFixedResource( |
| 190 | + "U", uart_model, {"tx": ["PD5", "PD0", "PD6", "PC0"], "rx": ["PD6", "PD1", "PD5", "PC1"]} |
| 191 | + ), |
| 192 | + PeripheralFixedResource("SPI", spi_model, {"sck": ["PC5"], "miso": ["PC7"], "mosi": ["PC6"]}), |
| 193 | + PeripheralFixedResource("I2C", i2c_model, {"scl": ["PC2", "PD1", "PC5"], "sda": ["PC1", "PD0", "PC6"]}), |
| 194 | + PeripheralFixedResource( |
| 195 | + "I2C_T", i2c_target_model, {"scl": ["PC2", "PD1", "PC5"], "sda": ["PC1", "PD0", "PC6"]} |
| 196 | + ), |
| 197 | + ] |
| 198 | + ).remap_pins(self._PIN_MAPPING) |
| 199 | + |
| 200 | + @override |
| 201 | + def generate(self) -> None: |
| 202 | + super().generate() |
| 203 | + |
| 204 | + self.footprint( |
| 205 | + "U", |
| 206 | + "Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm", |
| 207 | + self._make_pinning(), |
| 208 | + mfr="WCH", |
| 209 | + part="CH32V003F4P6", |
| 210 | + datasheet="https://www.wch-ic.com/downloads/CH32V003DS0_PDF.html", |
| 211 | + ) |
| 212 | + self.assign(self.lcsc_part, "C5187096") |
| 213 | + self.assign(self.actual_basic_part, False) |
| 214 | + |
| 215 | + |
| 216 | +class Ch32v003( |
| 217 | + Resettable, |
| 218 | + IoControllerI2cTarget, |
| 219 | + Microcontroller, |
| 220 | + WithCrystalGenerator, |
| 221 | + IoControllerPowerRequired, |
| 222 | + GeneratorBlock, |
| 223 | +): |
| 224 | + """Low-cost, bare bones RISC-V (RV32EC) microcontroller""" |
| 225 | + |
| 226 | + DEFAULT_CRYSTAL_FREQUENCY = 24 * MHertz(tol=0.005) # 24 MHz as typical in datasheet |
| 227 | + |
| 228 | + def __init__(self, **kwargs: Any) -> None: |
| 229 | + super().__init__(**kwargs) |
| 230 | + self.generator_param(self.reset.is_connected()) |
| 231 | + |
| 232 | + @override |
| 233 | + def generate(self) -> None: |
| 234 | + super().generate() |
| 235 | + |
| 236 | + with self.implicit_connect(ImplicitConnect(self.pwr, [Power]), ImplicitConnect(self.gnd, [Common])) as imp: |
| 237 | + self.ic = imp.Block(Ch32v003_Device(pin_assigns=ArrayStringExpr())) |
| 238 | + self._wrap_inner(self.ic) |
| 239 | + |
| 240 | + self.sdi = imp.Block(Ch32vSdiHeader()) |
| 241 | + self.connect(self.ic.swio, self.sdi.swio) |
| 242 | + self.connect(self.ic.nrst, self.sdi.reset) |
| 243 | + |
| 244 | + self.connect(self.xtal_node, self.ic.osc) |
| 245 | + |
| 246 | + self.vdd_cap = imp.Block(DecouplingCapacitor(0.1 * uFarad(tol=0.2))) |
| 247 | + |
| 248 | + if self.get(self.reset.is_connected()): |
| 249 | + self.connect(self.reset, self.ic.nrst) |
0 commit comments