modm_data.html.stmicro.datasheet
1# Copyright 2022, Niklas Hauser 2# SPDX-License-Identifier: MPL-2.0 3 4import re 5import itertools 6from pathlib import Path 7from functools import cached_property 8from collections import defaultdict 9 10from .helper import split_device_filter, split_package 11from ...html.text import ReDict 12from ...owl import DeviceIdentifier 13from ...owl.stmicro import did_from_string 14import modm_data.html as html 15 16 17class DatasheetMicro(html.Document): 18 def __init__(self, path: str): 19 super().__init__(path) 20 self._id = {} 21 self._devices = {} 22 23 def __repr__(self) -> str: 24 return f"DSµC({self.fullname})" 25 26 @cached_property 27 def device_family(self) -> str: 28 return re.search(r"STM32\w\w", self.chapter("chapter 0").headings()[0].text(br=" ")).group(0) 29 30 @cached_property 31 def devices(self) -> list[DeviceIdentifier]: 32 # Find the device summary table or use the heading 33 chapter = self.chapter("chapter 0") 34 ndevices = [] 35 if tables := chapter.tables("Device summary"): 36 for row in tables[0].cell_rows("number"): 37 ndevices.extend(c.text(br=" ", sup=" ").replace(",", " ").strip() 38 for cells in row.values() for c in cells) 39 ndevices = [d for dev in ndevices for d in dev.split(" ") if "STM32" in d] 40 else: 41 # Check all uncaptioned tables for "Product summary" domain 42 for table in chapter.tables(): 43 if table.domains_x("Product +summary"): 44 for row in table.cell_rows(): 45 for cells in row.values(): 46 ndevices.extend(html.listify(cells[-1].text(br=" ", sup=" "))) 47 break 48 else: 49 # print(chapter._path.relative_to(Path().cwd())) 50 ndevices = chapter.headings()[0].text(br=" ").replace(",", " ").strip() 51 ndevices = [d.replace("-", "xx") for d in ndevices.split(" ") if "STM32" in d] 52 # print(ndevices) 53 54 # Split combo strings into individual devices 55 devices = [] 56 for device in ndevices: 57 device = device.replace("STM32479", "STM32F479").replace("STM32469", "STM32F469") 58 if not re.match(r"STM32[A-Z][0-57BL][0-9ABCERSPQ][0-9AB]", device): 59 raise ValueError(f"Filter ID '{device}' does not match schema!") 60 devices.extend(split_device_filter(device)) 61 62 # Find the Ordering Information to complete the identifiers (x=?) 63 did = defaultdict(dict) 64 current = "" 65 chapter = self.chapter("ordering|numbering") 66 for heading, texts in chapter.heading_texts("ordering|numbering"): 67 for text in texts: 68 for para in text.text(br="\n").split("\n"): 69 if "=" in para or ":" in para: 70 key, value = para.split("=" if "=" in para else ":", 1) 71 did[current][key.strip()] = value.strip() 72 else: 73 current = para 74 self._id = did = ReDict(did) 75 # print(did) 76 did_map = [ 77 ("count", "pin"), 78 ("size", "size"), 79 ("package", "package"), 80 ("temperature", "temperature"), 81 ("option|identification|dedicated +pinout|power pairs|specificity|packing", "variant"), 82 ] 83 sdids = [(dname, {}) for dname in set(dev[:9] for dev in devices)] 84 for pattern, name in did_map: 85 if keys := did.match_keys(pattern): 86 new_ids = [] 87 if name == "variant": 88 for oid in sdids: 89 new_ids.append((oid[0], oid[1] | {f"{name}": "normal"})) 90 for value, descr in did[keys[0]].items(): 91 value = re.sub(r"\(\d\)", "", value) 92 if len(value) > 1: 93 if name == "variant" and (match := re.search(r"x += +([NA])", descr)): 94 # We need to specially deal with the -N variants 95 value = match.group(1) 96 else: 97 value = "" 98 if name == "variant": 99 continue 100 for oid in sdids: 101 new_ids.append((oid[0] + value, oid[1] | {f"{name}": descr})) 102 sdids = new_ids 103 elif name != "variant": 104 raise KeyError(f"Cannot find '{pattern}' in {did.keys()}!") 105 106 # Some datasheets have the specific table at the very end 107 if tables := chapter.tables("order codes"): 108 for row in tables[0].cell_rows("code"): 109 for cells in row.values(): 110 for cell in cells: 111 devices.extend(html.listify(cell.text())) 112 113 # convert to proper device identifiers 114 dids = set() 115 for string, descr in sdids: 116 did = did_from_string(string) 117 # print(did) 118 # filter through the device strings: 119 for device in devices: 120 if re.match(device.replace("x", ".").lower(), did.string): 121 dids.add(did) 122 break 123 124 # print(devices) 125 if not dids: 126 raise ValueError(f"Could not find devices for {self.fullname}!") 127 return list(dids) 128 129 @property 130 def _chapter_pins(self): 131 return self.chapter("pin description|pinout") 132 133 @property 134 def _table_pinout(self): 135 tables = self._chapter_pins.tables( 136 "pin( +and +ball|/ball| +assignment +and)? +(definition|description)", br=" ") 137 if not tables: 138 raise ValueError(f"Unable to find pin definition table in chapter! {self._chapter_pins._relpath}") 139 return tables[0] 140 141 @property 142 def _tables_alternate_functions(self): 143 tables = self._chapter_pins.tables("alternate +function", br=" ") 144 if not tables and "STM32F1" not in self.device_family: 145 raise ValueError(f"Unable to find alternate function table in chapter! {self._chapter_pins._relpath}") 146 return tables 147 148 @cached_property 149 def packages(self): 150 domains = self._table_pinout.domains_x(r"num(<br>)?ber|pins?:|pin/ball +name|:WLCSP.*?", br="<br>") 151 if not domains: 152 raise ValueError(f"Unable to find package domains in table! {self._chapter_pins._relpath} {tables[0].caption()}") 153 packages = [] 154 for domain in domains: 155 if domain == "Pin (UFQFPN48):Number": continue 156 ndpackage = domain.split(":")[-1].replace("UFBGA/TFBGA64", "UFBGA64/TFBGA64") 157 ndpackage = ndpackage.replace("LPQF", "LQFP").replace("TSSPOP", "TSSOP") 158 ndpackage = ndpackage.replace("UFBG100", "UFBGA100").replace("UBGA", "UFBGA") 159 ndpackage = ndpackage.replace("UQFN", "UFQFPN").replace("WLCSP20L", "WLCSP20") 160 ndpackage = ndpackage.replace("UFQFPN48E", "UFQFPN48+E").replace("UFQFN", "UFQFPN") 161 ndpackage = ndpackage.replace("LQFP48 SMPS<br>UFQFPN48 SMPS", "LQFP48/UFQFPN48+SMPS") 162 ndpackage = ndpackage.replace("LQFP<br>64", "LQFP64").replace("LQFP<br>48", "LQFP48") 163 ndpackage = ndpackage.replace("UFQFPN<br>32", "UFQFPN32").replace("UFQFP<br>N48", "UFQFPN48") 164 ndpackage = ndpackage.replace("WLCSP<br>25", "WLCSP25") 165 ndpackage = re.sub(r"^BGA", "UFBGA", ndpackage) 166 ndpackage = re.sub(r"\(\d\)| ", "", ndpackage) 167 ndpackage = re.sub(r"[Ee]xt-?/?", "", ndpackage) 168 ndpackage = re.sub(r"<br>SMPS", "SMPS", ndpackage) 169 ndpackage = re.sub(r"SMSP", "SMPS", ndpackage) 170 ndpackage = re.sub(r"with|-|_", "+", ndpackage) 171 ndpackage = re.sub(r"or", "/", ndpackage) 172 ndpackage = re.sub(r"\(STM32L0.*?UxSonly\)", "+S", ndpackage) 173 ndpackage = re.sub(r"(\d+)SMPS", r"\1+SMPS", ndpackage) 174 ndpackage = re.sub(r"\+GP", "", ndpackage) 175 if "DS13311" in self.name or "DS13312" in self.name: 176 ndpackage = ndpackage.replace("+SMPS", "") 177 if (match := re.search(r":(STM32.*?):", domain)) is not None: 178 devs = html.listify(match.group(1)) 179 ndpackage += "+" + "|".join(d.replace("x", ".") for d in devs) 180 ndpackage, *filters = ndpackage.split("+") 181 filters = ("+" + "+".join(filters)) if filters else "" 182 spackage = [p for p in re.split(r"[/,]| or |<br>", ndpackage) if p] 183 # print(domain, spackage) 184 for package in spackage: 185 if (pack := split_package(package)) is not None: 186 packages.append((domain, package + filters, *pack)) 187 else: 188 print(f"Unknown package {package}!") 189 if not packages: 190 if "DS13259" in self.name: 191 packages = [("Pin:Number", "UFQFPN48", "UFQFPN48", "UFQFPN", 48)] 192 elif "DS13047" in self.name: 193 packages = [("Pin (UFQFPN48):Number", "UFQFPN48", "UFQFPN48", "UFQFPN", 48)] 194 else: 195 raise ValueError(f"Unable to find packages! {self._chapter_pins._relpath} {domains}") 196 return packages 197 198 @property 199 def packages_pins(self): 200 # Add pinouts and signals 201 pin_replace = {r"sup| |D?NC|/$|\(.*?\)|.*?connected.*": "", "–": "-", r"VREF_\+": "VREF+"} 202 add_replace = {r"[- ]|\(.*?\)|.*?selection.*|.*?reset.*": "", 203 r"OPAMP": ",OPAMP", r"COMP": ",COMP", r"OPAMP,1": "OPAMP1"} 204 afs_replace = {r"[- ]|\(.*?\)": "", "LCD_SEG": ",LCD_SEG"} 205 pos_replace = {r'[“”\-"]|NC|\(.*?\)': ""} 206 sig_replace = {r"[- ]|\(.*?\)": "", r"(MOSI|SCK|NSS)I2S": r"\1,I2S", "_µM": "_M", 207 r"(CH\d)TIM": r"\1,TIM", r"_([RT]XD\d?|DV|EN|CLK)ETH_": r"_\1,ETH_"} 208 209 data_packages = defaultdict(list) 210 data_pins = defaultdict(dict) 211 212 packages = set((d[0], d[1]) for d in self.packages) 213 # Import the pin definitions incl. additional function 214 for row in self._table_pinout.cell_rows(br="<br>"): 215 pin_name = row.match_value("pin +name|:name")[0].text(**pin_replace).strip() 216 if not pin_name: continue 217 ios = row.match_value("I ?/ ?O")[0].text(**{"-":""}) 218 ptype = row.match_value("type")[0].text() 219 # Hack to make fix the PD0/PD1 pins 220 if pin_name.startswith("OSC") and (remap := row.match_value("remap")): 221 if (pin := remap[0].text()).startswith("P"): 222 pin_name = f"{pin}-{pin_name}" 223 224 data_pin = data_pins[pin_name] 225 if ios: data_pin["structure"] = ios 226 if ptype: data_pin["type"] = ptype 227 if ptype == "I/O" and "STM32F1" not in self.device_family: 228 signals = html.listify(row.match_value("additional")[0].text(**add_replace)) 229 data_pin["additional"] = set(signals) 230 231 for domain, package_name in packages: 232 if ppos := html.listify(row[domain][0].text(**pos_replace)): 233 data_packages[package_name].append( (pin_name, ppos) ) 234 235 # Import the alternate functions 236 for table in self._tables_alternate_functions: 237 cells = table.domains("port|pin +name").cells(r"AF(IO)?\d+") 238 for pin, afs in cells.items(): 239 name = html.replace(pin.split(":")[-1], **pin_replace) 240 data_pin = data_pins[name] 241 if "alternate" not in data_pin: 242 data_pin["alternate"] = defaultdict(list) 243 for af, csignals in afs.items(): 244 af = int(re.search(r"AF(IO)?(\d{1,2})", af).group(2)) 245 for csignal in csignals: 246 signals = html.listify(csignal.text(**sig_replace)) 247 data_pin["alternate"][af].extend(signals) 248 249 return data_packages, data_pins 250 251 252class DatasheetSensor(html.Document): 253 def __init__(self, path: str): 254 super().__init__(path) 255 256 def __repr__(self) -> str: 257 return f"DSsens({self.fullname})"
18class DatasheetMicro(html.Document): 19 def __init__(self, path: str): 20 super().__init__(path) 21 self._id = {} 22 self._devices = {} 23 24 def __repr__(self) -> str: 25 return f"DSµC({self.fullname})" 26 27 @cached_property 28 def device_family(self) -> str: 29 return re.search(r"STM32\w\w", self.chapter("chapter 0").headings()[0].text(br=" ")).group(0) 30 31 @cached_property 32 def devices(self) -> list[DeviceIdentifier]: 33 # Find the device summary table or use the heading 34 chapter = self.chapter("chapter 0") 35 ndevices = [] 36 if tables := chapter.tables("Device summary"): 37 for row in tables[0].cell_rows("number"): 38 ndevices.extend(c.text(br=" ", sup=" ").replace(",", " ").strip() 39 for cells in row.values() for c in cells) 40 ndevices = [d for dev in ndevices for d in dev.split(" ") if "STM32" in d] 41 else: 42 # Check all uncaptioned tables for "Product summary" domain 43 for table in chapter.tables(): 44 if table.domains_x("Product +summary"): 45 for row in table.cell_rows(): 46 for cells in row.values(): 47 ndevices.extend(html.listify(cells[-1].text(br=" ", sup=" "))) 48 break 49 else: 50 # print(chapter._path.relative_to(Path().cwd())) 51 ndevices = chapter.headings()[0].text(br=" ").replace(",", " ").strip() 52 ndevices = [d.replace("-", "xx") for d in ndevices.split(" ") if "STM32" in d] 53 # print(ndevices) 54 55 # Split combo strings into individual devices 56 devices = [] 57 for device in ndevices: 58 device = device.replace("STM32479", "STM32F479").replace("STM32469", "STM32F469") 59 if not re.match(r"STM32[A-Z][0-57BL][0-9ABCERSPQ][0-9AB]", device): 60 raise ValueError(f"Filter ID '{device}' does not match schema!") 61 devices.extend(split_device_filter(device)) 62 63 # Find the Ordering Information to complete the identifiers (x=?) 64 did = defaultdict(dict) 65 current = "" 66 chapter = self.chapter("ordering|numbering") 67 for heading, texts in chapter.heading_texts("ordering|numbering"): 68 for text in texts: 69 for para in text.text(br="\n").split("\n"): 70 if "=" in para or ":" in para: 71 key, value = para.split("=" if "=" in para else ":", 1) 72 did[current][key.strip()] = value.strip() 73 else: 74 current = para 75 self._id = did = ReDict(did) 76 # print(did) 77 did_map = [ 78 ("count", "pin"), 79 ("size", "size"), 80 ("package", "package"), 81 ("temperature", "temperature"), 82 ("option|identification|dedicated +pinout|power pairs|specificity|packing", "variant"), 83 ] 84 sdids = [(dname, {}) for dname in set(dev[:9] for dev in devices)] 85 for pattern, name in did_map: 86 if keys := did.match_keys(pattern): 87 new_ids = [] 88 if name == "variant": 89 for oid in sdids: 90 new_ids.append((oid[0], oid[1] | {f"{name}": "normal"})) 91 for value, descr in did[keys[0]].items(): 92 value = re.sub(r"\(\d\)", "", value) 93 if len(value) > 1: 94 if name == "variant" and (match := re.search(r"x += +([NA])", descr)): 95 # We need to specially deal with the -N variants 96 value = match.group(1) 97 else: 98 value = "" 99 if name == "variant": 100 continue 101 for oid in sdids: 102 new_ids.append((oid[0] + value, oid[1] | {f"{name}": descr})) 103 sdids = new_ids 104 elif name != "variant": 105 raise KeyError(f"Cannot find '{pattern}' in {did.keys()}!") 106 107 # Some datasheets have the specific table at the very end 108 if tables := chapter.tables("order codes"): 109 for row in tables[0].cell_rows("code"): 110 for cells in row.values(): 111 for cell in cells: 112 devices.extend(html.listify(cell.text())) 113 114 # convert to proper device identifiers 115 dids = set() 116 for string, descr in sdids: 117 did = did_from_string(string) 118 # print(did) 119 # filter through the device strings: 120 for device in devices: 121 if re.match(device.replace("x", ".").lower(), did.string): 122 dids.add(did) 123 break 124 125 # print(devices) 126 if not dids: 127 raise ValueError(f"Could not find devices for {self.fullname}!") 128 return list(dids) 129 130 @property 131 def _chapter_pins(self): 132 return self.chapter("pin description|pinout") 133 134 @property 135 def _table_pinout(self): 136 tables = self._chapter_pins.tables( 137 "pin( +and +ball|/ball| +assignment +and)? +(definition|description)", br=" ") 138 if not tables: 139 raise ValueError(f"Unable to find pin definition table in chapter! {self._chapter_pins._relpath}") 140 return tables[0] 141 142 @property 143 def _tables_alternate_functions(self): 144 tables = self._chapter_pins.tables("alternate +function", br=" ") 145 if not tables and "STM32F1" not in self.device_family: 146 raise ValueError(f"Unable to find alternate function table in chapter! {self._chapter_pins._relpath}") 147 return tables 148 149 @cached_property 150 def packages(self): 151 domains = self._table_pinout.domains_x(r"num(<br>)?ber|pins?:|pin/ball +name|:WLCSP.*?", br="<br>") 152 if not domains: 153 raise ValueError(f"Unable to find package domains in table! {self._chapter_pins._relpath} {tables[0].caption()}") 154 packages = [] 155 for domain in domains: 156 if domain == "Pin (UFQFPN48):Number": continue 157 ndpackage = domain.split(":")[-1].replace("UFBGA/TFBGA64", "UFBGA64/TFBGA64") 158 ndpackage = ndpackage.replace("LPQF", "LQFP").replace("TSSPOP", "TSSOP") 159 ndpackage = ndpackage.replace("UFBG100", "UFBGA100").replace("UBGA", "UFBGA") 160 ndpackage = ndpackage.replace("UQFN", "UFQFPN").replace("WLCSP20L", "WLCSP20") 161 ndpackage = ndpackage.replace("UFQFPN48E", "UFQFPN48+E").replace("UFQFN", "UFQFPN") 162 ndpackage = ndpackage.replace("LQFP48 SMPS<br>UFQFPN48 SMPS", "LQFP48/UFQFPN48+SMPS") 163 ndpackage = ndpackage.replace("LQFP<br>64", "LQFP64").replace("LQFP<br>48", "LQFP48") 164 ndpackage = ndpackage.replace("UFQFPN<br>32", "UFQFPN32").replace("UFQFP<br>N48", "UFQFPN48") 165 ndpackage = ndpackage.replace("WLCSP<br>25", "WLCSP25") 166 ndpackage = re.sub(r"^BGA", "UFBGA", ndpackage) 167 ndpackage = re.sub(r"\(\d\)| ", "", ndpackage) 168 ndpackage = re.sub(r"[Ee]xt-?/?", "", ndpackage) 169 ndpackage = re.sub(r"<br>SMPS", "SMPS", ndpackage) 170 ndpackage = re.sub(r"SMSP", "SMPS", ndpackage) 171 ndpackage = re.sub(r"with|-|_", "+", ndpackage) 172 ndpackage = re.sub(r"or", "/", ndpackage) 173 ndpackage = re.sub(r"\(STM32L0.*?UxSonly\)", "+S", ndpackage) 174 ndpackage = re.sub(r"(\d+)SMPS", r"\1+SMPS", ndpackage) 175 ndpackage = re.sub(r"\+GP", "", ndpackage) 176 if "DS13311" in self.name or "DS13312" in self.name: 177 ndpackage = ndpackage.replace("+SMPS", "") 178 if (match := re.search(r":(STM32.*?):", domain)) is not None: 179 devs = html.listify(match.group(1)) 180 ndpackage += "+" + "|".join(d.replace("x", ".") for d in devs) 181 ndpackage, *filters = ndpackage.split("+") 182 filters = ("+" + "+".join(filters)) if filters else "" 183 spackage = [p for p in re.split(r"[/,]| or |<br>", ndpackage) if p] 184 # print(domain, spackage) 185 for package in spackage: 186 if (pack := split_package(package)) is not None: 187 packages.append((domain, package + filters, *pack)) 188 else: 189 print(f"Unknown package {package}!") 190 if not packages: 191 if "DS13259" in self.name: 192 packages = [("Pin:Number", "UFQFPN48", "UFQFPN48", "UFQFPN", 48)] 193 elif "DS13047" in self.name: 194 packages = [("Pin (UFQFPN48):Number", "UFQFPN48", "UFQFPN48", "UFQFPN", 48)] 195 else: 196 raise ValueError(f"Unable to find packages! {self._chapter_pins._relpath} {domains}") 197 return packages 198 199 @property 200 def packages_pins(self): 201 # Add pinouts and signals 202 pin_replace = {r"sup| |D?NC|/$|\(.*?\)|.*?connected.*": "", "–": "-", r"VREF_\+": "VREF+"} 203 add_replace = {r"[- ]|\(.*?\)|.*?selection.*|.*?reset.*": "", 204 r"OPAMP": ",OPAMP", r"COMP": ",COMP", r"OPAMP,1": "OPAMP1"} 205 afs_replace = {r"[- ]|\(.*?\)": "", "LCD_SEG": ",LCD_SEG"} 206 pos_replace = {r'[“”\-"]|NC|\(.*?\)': ""} 207 sig_replace = {r"[- ]|\(.*?\)": "", r"(MOSI|SCK|NSS)I2S": r"\1,I2S", "_µM": "_M", 208 r"(CH\d)TIM": r"\1,TIM", r"_([RT]XD\d?|DV|EN|CLK)ETH_": r"_\1,ETH_"} 209 210 data_packages = defaultdict(list) 211 data_pins = defaultdict(dict) 212 213 packages = set((d[0], d[1]) for d in self.packages) 214 # Import the pin definitions incl. additional function 215 for row in self._table_pinout.cell_rows(br="<br>"): 216 pin_name = row.match_value("pin +name|:name")[0].text(**pin_replace).strip() 217 if not pin_name: continue 218 ios = row.match_value("I ?/ ?O")[0].text(**{"-":""}) 219 ptype = row.match_value("type")[0].text() 220 # Hack to make fix the PD0/PD1 pins 221 if pin_name.startswith("OSC") and (remap := row.match_value("remap")): 222 if (pin := remap[0].text()).startswith("P"): 223 pin_name = f"{pin}-{pin_name}" 224 225 data_pin = data_pins[pin_name] 226 if ios: data_pin["structure"] = ios 227 if ptype: data_pin["type"] = ptype 228 if ptype == "I/O" and "STM32F1" not in self.device_family: 229 signals = html.listify(row.match_value("additional")[0].text(**add_replace)) 230 data_pin["additional"] = set(signals) 231 232 for domain, package_name in packages: 233 if ppos := html.listify(row[domain][0].text(**pos_replace)): 234 data_packages[package_name].append( (pin_name, ppos) ) 235 236 # Import the alternate functions 237 for table in self._tables_alternate_functions: 238 cells = table.domains("port|pin +name").cells(r"AF(IO)?\d+") 239 for pin, afs in cells.items(): 240 name = html.replace(pin.split(":")[-1], **pin_replace) 241 data_pin = data_pins[name] 242 if "alternate" not in data_pin: 243 data_pin["alternate"] = defaultdict(list) 244 for af, csignals in afs.items(): 245 af = int(re.search(r"AF(IO)?(\d{1,2})", af).group(2)) 246 for csignal in csignals: 247 signals = html.listify(csignal.text(**sig_replace)) 248 data_pin["alternate"][af].extend(signals) 249 250 return data_packages, data_pins
devices: list[modm_data.owl.identifier.DeviceIdentifier]
31 @cached_property 32 def devices(self) -> list[DeviceIdentifier]: 33 # Find the device summary table or use the heading 34 chapter = self.chapter("chapter 0") 35 ndevices = [] 36 if tables := chapter.tables("Device summary"): 37 for row in tables[0].cell_rows("number"): 38 ndevices.extend(c.text(br=" ", sup=" ").replace(",", " ").strip() 39 for cells in row.values() for c in cells) 40 ndevices = [d for dev in ndevices for d in dev.split(" ") if "STM32" in d] 41 else: 42 # Check all uncaptioned tables for "Product summary" domain 43 for table in chapter.tables(): 44 if table.domains_x("Product +summary"): 45 for row in table.cell_rows(): 46 for cells in row.values(): 47 ndevices.extend(html.listify(cells[-1].text(br=" ", sup=" "))) 48 break 49 else: 50 # print(chapter._path.relative_to(Path().cwd())) 51 ndevices = chapter.headings()[0].text(br=" ").replace(",", " ").strip() 52 ndevices = [d.replace("-", "xx") for d in ndevices.split(" ") if "STM32" in d] 53 # print(ndevices) 54 55 # Split combo strings into individual devices 56 devices = [] 57 for device in ndevices: 58 device = device.replace("STM32479", "STM32F479").replace("STM32469", "STM32F469") 59 if not re.match(r"STM32[A-Z][0-57BL][0-9ABCERSPQ][0-9AB]", device): 60 raise ValueError(f"Filter ID '{device}' does not match schema!") 61 devices.extend(split_device_filter(device)) 62 63 # Find the Ordering Information to complete the identifiers (x=?) 64 did = defaultdict(dict) 65 current = "" 66 chapter = self.chapter("ordering|numbering") 67 for heading, texts in chapter.heading_texts("ordering|numbering"): 68 for text in texts: 69 for para in text.text(br="\n").split("\n"): 70 if "=" in para or ":" in para: 71 key, value = para.split("=" if "=" in para else ":", 1) 72 did[current][key.strip()] = value.strip() 73 else: 74 current = para 75 self._id = did = ReDict(did) 76 # print(did) 77 did_map = [ 78 ("count", "pin"), 79 ("size", "size"), 80 ("package", "package"), 81 ("temperature", "temperature"), 82 ("option|identification|dedicated +pinout|power pairs|specificity|packing", "variant"), 83 ] 84 sdids = [(dname, {}) for dname in set(dev[:9] for dev in devices)] 85 for pattern, name in did_map: 86 if keys := did.match_keys(pattern): 87 new_ids = [] 88 if name == "variant": 89 for oid in sdids: 90 new_ids.append((oid[0], oid[1] | {f"{name}": "normal"})) 91 for value, descr in did[keys[0]].items(): 92 value = re.sub(r"\(\d\)", "", value) 93 if len(value) > 1: 94 if name == "variant" and (match := re.search(r"x += +([NA])", descr)): 95 # We need to specially deal with the -N variants 96 value = match.group(1) 97 else: 98 value = "" 99 if name == "variant": 100 continue 101 for oid in sdids: 102 new_ids.append((oid[0] + value, oid[1] | {f"{name}": descr})) 103 sdids = new_ids 104 elif name != "variant": 105 raise KeyError(f"Cannot find '{pattern}' in {did.keys()}!") 106 107 # Some datasheets have the specific table at the very end 108 if tables := chapter.tables("order codes"): 109 for row in tables[0].cell_rows("code"): 110 for cells in row.values(): 111 for cell in cells: 112 devices.extend(html.listify(cell.text())) 113 114 # convert to proper device identifiers 115 dids = set() 116 for string, descr in sdids: 117 did = did_from_string(string) 118 # print(did) 119 # filter through the device strings: 120 for device in devices: 121 if re.match(device.replace("x", ".").lower(), did.string): 122 dids.add(did) 123 break 124 125 # print(devices) 126 if not dids: 127 raise ValueError(f"Could not find devices for {self.fullname}!") 128 return list(dids)
packages
149 @cached_property 150 def packages(self): 151 domains = self._table_pinout.domains_x(r"num(<br>)?ber|pins?:|pin/ball +name|:WLCSP.*?", br="<br>") 152 if not domains: 153 raise ValueError(f"Unable to find package domains in table! {self._chapter_pins._relpath} {tables[0].caption()}") 154 packages = [] 155 for domain in domains: 156 if domain == "Pin (UFQFPN48):Number": continue 157 ndpackage = domain.split(":")[-1].replace("UFBGA/TFBGA64", "UFBGA64/TFBGA64") 158 ndpackage = ndpackage.replace("LPQF", "LQFP").replace("TSSPOP", "TSSOP") 159 ndpackage = ndpackage.replace("UFBG100", "UFBGA100").replace("UBGA", "UFBGA") 160 ndpackage = ndpackage.replace("UQFN", "UFQFPN").replace("WLCSP20L", "WLCSP20") 161 ndpackage = ndpackage.replace("UFQFPN48E", "UFQFPN48+E").replace("UFQFN", "UFQFPN") 162 ndpackage = ndpackage.replace("LQFP48 SMPS<br>UFQFPN48 SMPS", "LQFP48/UFQFPN48+SMPS") 163 ndpackage = ndpackage.replace("LQFP<br>64", "LQFP64").replace("LQFP<br>48", "LQFP48") 164 ndpackage = ndpackage.replace("UFQFPN<br>32", "UFQFPN32").replace("UFQFP<br>N48", "UFQFPN48") 165 ndpackage = ndpackage.replace("WLCSP<br>25", "WLCSP25") 166 ndpackage = re.sub(r"^BGA", "UFBGA", ndpackage) 167 ndpackage = re.sub(r"\(\d\)| ", "", ndpackage) 168 ndpackage = re.sub(r"[Ee]xt-?/?", "", ndpackage) 169 ndpackage = re.sub(r"<br>SMPS", "SMPS", ndpackage) 170 ndpackage = re.sub(r"SMSP", "SMPS", ndpackage) 171 ndpackage = re.sub(r"with|-|_", "+", ndpackage) 172 ndpackage = re.sub(r"or", "/", ndpackage) 173 ndpackage = re.sub(r"\(STM32L0.*?UxSonly\)", "+S", ndpackage) 174 ndpackage = re.sub(r"(\d+)SMPS", r"\1+SMPS", ndpackage) 175 ndpackage = re.sub(r"\+GP", "", ndpackage) 176 if "DS13311" in self.name or "DS13312" in self.name: 177 ndpackage = ndpackage.replace("+SMPS", "") 178 if (match := re.search(r":(STM32.*?):", domain)) is not None: 179 devs = html.listify(match.group(1)) 180 ndpackage += "+" + "|".join(d.replace("x", ".") for d in devs) 181 ndpackage, *filters = ndpackage.split("+") 182 filters = ("+" + "+".join(filters)) if filters else "" 183 spackage = [p for p in re.split(r"[/,]| or |<br>", ndpackage) if p] 184 # print(domain, spackage) 185 for package in spackage: 186 if (pack := split_package(package)) is not None: 187 packages.append((domain, package + filters, *pack)) 188 else: 189 print(f"Unknown package {package}!") 190 if not packages: 191 if "DS13259" in self.name: 192 packages = [("Pin:Number", "UFQFPN48", "UFQFPN48", "UFQFPN", 48)] 193 elif "DS13047" in self.name: 194 packages = [("Pin (UFQFPN48):Number", "UFQFPN48", "UFQFPN48", "UFQFPN", 48)] 195 else: 196 raise ValueError(f"Unable to find packages! {self._chapter_pins._relpath} {domains}") 197 return packages
packages_pins
199 @property 200 def packages_pins(self): 201 # Add pinouts and signals 202 pin_replace = {r"sup| |D?NC|/$|\(.*?\)|.*?connected.*": "", "–": "-", r"VREF_\+": "VREF+"} 203 add_replace = {r"[- ]|\(.*?\)|.*?selection.*|.*?reset.*": "", 204 r"OPAMP": ",OPAMP", r"COMP": ",COMP", r"OPAMP,1": "OPAMP1"} 205 afs_replace = {r"[- ]|\(.*?\)": "", "LCD_SEG": ",LCD_SEG"} 206 pos_replace = {r'[“”\-"]|NC|\(.*?\)': ""} 207 sig_replace = {r"[- ]|\(.*?\)": "", r"(MOSI|SCK|NSS)I2S": r"\1,I2S", "_µM": "_M", 208 r"(CH\d)TIM": r"\1,TIM", r"_([RT]XD\d?|DV|EN|CLK)ETH_": r"_\1,ETH_"} 209 210 data_packages = defaultdict(list) 211 data_pins = defaultdict(dict) 212 213 packages = set((d[0], d[1]) for d in self.packages) 214 # Import the pin definitions incl. additional function 215 for row in self._table_pinout.cell_rows(br="<br>"): 216 pin_name = row.match_value("pin +name|:name")[0].text(**pin_replace).strip() 217 if not pin_name: continue 218 ios = row.match_value("I ?/ ?O")[0].text(**{"-":""}) 219 ptype = row.match_value("type")[0].text() 220 # Hack to make fix the PD0/PD1 pins 221 if pin_name.startswith("OSC") and (remap := row.match_value("remap")): 222 if (pin := remap[0].text()).startswith("P"): 223 pin_name = f"{pin}-{pin_name}" 224 225 data_pin = data_pins[pin_name] 226 if ios: data_pin["structure"] = ios 227 if ptype: data_pin["type"] = ptype 228 if ptype == "I/O" and "STM32F1" not in self.device_family: 229 signals = html.listify(row.match_value("additional")[0].text(**add_replace)) 230 data_pin["additional"] = set(signals) 231 232 for domain, package_name in packages: 233 if ppos := html.listify(row[domain][0].text(**pos_replace)): 234 data_packages[package_name].append( (pin_name, ppos) ) 235 236 # Import the alternate functions 237 for table in self._tables_alternate_functions: 238 cells = table.domains("port|pin +name").cells(r"AF(IO)?\d+") 239 for pin, afs in cells.items(): 240 name = html.replace(pin.split(":")[-1], **pin_replace) 241 data_pin = data_pins[name] 242 if "alternate" not in data_pin: 243 data_pin["alternate"] = defaultdict(list) 244 for af, csignals in afs.items(): 245 af = int(re.search(r"AF(IO)?(\d{1,2})", af).group(2)) 246 for csignal in csignals: 247 signals = html.listify(csignal.text(**sig_replace)) 248 data_pin["alternate"][af].extend(signals) 249 250 return data_packages, data_pins