modm_data.html.stmicro
1# Copyright 2022, Niklas Hauser 2# SPDX-License-Identifier: MPL-2.0 3 4from .datasheet_sensor import DatasheetSensor 5from .datasheet_stm32 import DatasheetStm32 6from .reference import ReferenceManual 7from .document import load_documents, load_document_devices, datasheet_for_device, reference_manual_for_device 8from .helper import find_device_filter 9 10__all__ = [ 11 "DatasheetSensor", 12 "DatasheetStm32", 13 "ReferenceManual", 14 "load_documents", 15 "load_document_devices", 16 "datasheet_for_device", 17 "reference_manual_for_device", 18 "find_device_filter", 19]
class
DatasheetSensor(modm_data.html.document.Document):
11class DatasheetSensor(Document): 12 def __init__(self, path: str): 13 super().__init__(path) 14 15 def __repr__(self) -> str: 16 return f"DSsensor({self.fullname})" 17 18 @cache 19 def register_map(self, assert_table=True): 20 pass
Inherited Members
- modm_data.html.document.Document
- path
- relpath
- fullname
- name
- version
- path_pdf
- chapters
- chapter
class
DatasheetStm32(modm_data.html.document.Document):
16class DatasheetStm32(Document): 17 def __init__(self, path: str): 18 super().__init__(path) 19 self._id = {} 20 self._devices = {} 21 22 def __repr__(self) -> str: 23 return f"DSstm32({self.fullname})" 24 25 @cached_property 26 def device_family(self) -> str: 27 return re.search(r"STM32\w\w", self.chapter("chapter 0").headings()[0].text(br=" ")).group(0) 28 29 @cached_property 30 def devices(self) -> list[DeviceIdentifier]: 31 # Find the device summary table or use the heading 32 chapter = self.chapter("chapter 0") 33 ndevices = [] 34 if tables := chapter.tables("Device summary"): 35 for row in tables[0].cell_rows("number"): 36 ndevices.extend( 37 c.text(br=" ", sup=" ").replace(",", " ").strip() for cells in row.values() for c in cells 38 ) 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 ) 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}") 154 packages = [] 155 for domain in domains: 156 if domain == "Pin (UFQFPN48):Number": 157 continue 158 ndpackage = domain.split(":")[-1].replace("UFBGA/TFBGA64", "UFBGA64/TFBGA64") 159 ndpackage = ndpackage.replace("LPQF", "LQFP").replace("TSSPOP", "TSSOP") 160 ndpackage = ndpackage.replace("UFBG100", "UFBGA100").replace("UBGA", "UFBGA") 161 ndpackage = ndpackage.replace("UQFN", "UFQFPN").replace("WLCSP20L", "WLCSP20") 162 ndpackage = ndpackage.replace("UFQFPN48E", "UFQFPN48+E").replace("UFQFN", "UFQFPN") 163 ndpackage = ndpackage.replace("LQFP48 SMPS<br>UFQFPN48 SMPS", "LQFP48/UFQFPN48+SMPS") 164 ndpackage = ndpackage.replace("LQFP<br>64", "LQFP64").replace("LQFP<br>48", "LQFP48") 165 ndpackage = ndpackage.replace("UFQFPN<br>32", "UFQFPN32").replace("UFQFP<br>N48", "UFQFPN48") 166 ndpackage = ndpackage.replace("WLCSP<br>25", "WLCSP25") 167 ndpackage = re.sub(r"^BGA", "UFBGA", ndpackage) 168 ndpackage = re.sub(r"\(\d\)| ", "", ndpackage) 169 ndpackage = re.sub(r"[Ee]xt-?/?", "", ndpackage) 170 ndpackage = re.sub(r"<br>SMPS", "SMPS", ndpackage) 171 ndpackage = re.sub(r"SMSP", "SMPS", ndpackage) 172 ndpackage = re.sub(r"with|-|_", "+", ndpackage) 173 ndpackage = re.sub(r"or", "/", ndpackage) 174 ndpackage = re.sub(r"\(STM32L0.*?UxSonly\)", "+S", ndpackage) 175 ndpackage = re.sub(r"(\d+)SMPS", r"\1+SMPS", ndpackage) 176 ndpackage = re.sub(r"\+GP", "", ndpackage) 177 if "DS13311" in self.name or "DS13312" in self.name: 178 ndpackage = ndpackage.replace("+SMPS", "") 179 if (match := re.search(r":(STM32.*?):", domain)) is not None: 180 devs = html_listify(match.group(1)) 181 ndpackage += "+" + "|".join(d.replace("x", ".") for d in devs) 182 ndpackage, *filters = ndpackage.split("+") 183 filters = ("+" + "+".join(filters)) if filters else "" 184 spackage = [p for p in re.split(r"[/,]| or |<br>", ndpackage) if p] 185 # print(domain, spackage) 186 for package in spackage: 187 if (pack := split_package(package)) is not None: 188 packages.append((domain, package + filters, *pack)) 189 else: 190 print(f"Unknown package {package}!") 191 if not packages: 192 if "DS13259" in self.name: 193 packages = [("Pin:Number", "UFQFPN48", "UFQFPN48", "UFQFPN", 48)] 194 elif "DS13047" in self.name: 195 packages = [("Pin (UFQFPN48):Number", "UFQFPN48", "UFQFPN48", "UFQFPN", 48)] 196 else: 197 raise ValueError(f"Unable to find packages! {self._chapter_pins._relpath} {domains}") 198 return packages 199 200 @property 201 def packages_pins(self): 202 # Add pinouts and signals 203 pin_replace = {r"sup| |D?NC|/$|\(.*?\)|.*?connected.*": "", "–": "-", r"VREF_\+": "VREF+"} 204 add_replace = { 205 r"[- ]|\(.*?\)|.*?selection.*|.*?reset.*": "", 206 r"OPAMP": ",OPAMP", 207 r"COMP": ",COMP", 208 r"OPAMP,1": "OPAMP1", 209 } 210 afs_replace = {r"[- ]|\(.*?\)": "", "LCD_SEG": ",LCD_SEG"} # noqa: F841 211 pos_replace = {r'[“”\-"]|NC|\(.*?\)': ""} 212 sig_replace = { 213 r"[- ]|\(.*?\)": "", 214 r"(MOSI|SCK|NSS)I2S": r"\1,I2S", 215 "_µM": "_M", 216 r"(CH\d)TIM": r"\1,TIM", 217 r"_([RT]XD\d?|DV|EN|CLK)ETH_": r"_\1,ETH_", 218 } 219 220 data_packages = defaultdict(list) 221 data_pins = defaultdict(dict) 222 223 packages = set((d[0], d[1]) for d in self.packages) 224 # Import the pin definitions incl. additional function 225 for row in self._table_pinout.cell_rows(br="<br>"): 226 pin_name = row.match_value("pin +name|:name")[0].text(**pin_replace).strip() 227 if not pin_name: 228 continue 229 ios = row.match_value("I ?/ ?O")[0].text(**{"-": ""}) 230 ptype = row.match_value("type")[0].text() 231 # Hack to make fix the PD0/PD1 pins 232 if pin_name.startswith("OSC") and (remap := row.match_value("remap")): 233 if (pin := remap[0].text()).startswith("P"): 234 pin_name = f"{pin}-{pin_name}" 235 236 data_pin = data_pins[pin_name] 237 if ios: 238 data_pin["structure"] = ios 239 if ptype: 240 data_pin["type"] = ptype 241 if ptype == "I/O" and "STM32F1" not in self.device_family: 242 signals = html_listify(row.match_value("additional")[0].text(**add_replace)) 243 data_pin["additional"] = set(signals) 244 245 for domain, package_name in packages: 246 if ppos := html_listify(row[domain][0].text(**pos_replace)): 247 data_packages[package_name].append((pin_name, ppos)) 248 249 # Import the alternate functions 250 for table in self._tables_alternate_functions: 251 cells = table.domains("port|pin +name").cells(r"AF(IO)?\d+") 252 for pin, afs in cells.items(): 253 name = html_replace(pin.split(":")[-1], **pin_replace) 254 data_pin = data_pins[name] 255 if "alternate" not in data_pin: 256 data_pin["alternate"] = defaultdict(list) 257 for af, csignals in afs.items(): 258 af = int(re.search(r"AF(IO)?(\d{1,2})", af).group(2)) 259 for csignal in csignals: 260 signals = html_listify(csignal.text(**sig_replace)) 261 data_pin["alternate"][af].extend(signals) 262 263 return data_packages, data_pins
devices: list[modm_data.owl.DeviceIdentifier]
29 @cached_property 30 def devices(self) -> list[DeviceIdentifier]: 31 # Find the device summary table or use the heading 32 chapter = self.chapter("chapter 0") 33 ndevices = [] 34 if tables := chapter.tables("Device summary"): 35 for row in tables[0].cell_rows("number"): 36 ndevices.extend( 37 c.text(br=" ", sup=" ").replace(",", " ").strip() for cells in row.values() for c in cells 38 ) 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)
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}") 154 packages = [] 155 for domain in domains: 156 if domain == "Pin (UFQFPN48):Number": 157 continue 158 ndpackage = domain.split(":")[-1].replace("UFBGA/TFBGA64", "UFBGA64/TFBGA64") 159 ndpackage = ndpackage.replace("LPQF", "LQFP").replace("TSSPOP", "TSSOP") 160 ndpackage = ndpackage.replace("UFBG100", "UFBGA100").replace("UBGA", "UFBGA") 161 ndpackage = ndpackage.replace("UQFN", "UFQFPN").replace("WLCSP20L", "WLCSP20") 162 ndpackage = ndpackage.replace("UFQFPN48E", "UFQFPN48+E").replace("UFQFN", "UFQFPN") 163 ndpackage = ndpackage.replace("LQFP48 SMPS<br>UFQFPN48 SMPS", "LQFP48/UFQFPN48+SMPS") 164 ndpackage = ndpackage.replace("LQFP<br>64", "LQFP64").replace("LQFP<br>48", "LQFP48") 165 ndpackage = ndpackage.replace("UFQFPN<br>32", "UFQFPN32").replace("UFQFP<br>N48", "UFQFPN48") 166 ndpackage = ndpackage.replace("WLCSP<br>25", "WLCSP25") 167 ndpackage = re.sub(r"^BGA", "UFBGA", ndpackage) 168 ndpackage = re.sub(r"\(\d\)| ", "", ndpackage) 169 ndpackage = re.sub(r"[Ee]xt-?/?", "", ndpackage) 170 ndpackage = re.sub(r"<br>SMPS", "SMPS", ndpackage) 171 ndpackage = re.sub(r"SMSP", "SMPS", ndpackage) 172 ndpackage = re.sub(r"with|-|_", "+", ndpackage) 173 ndpackage = re.sub(r"or", "/", ndpackage) 174 ndpackage = re.sub(r"\(STM32L0.*?UxSonly\)", "+S", ndpackage) 175 ndpackage = re.sub(r"(\d+)SMPS", r"\1+SMPS", ndpackage) 176 ndpackage = re.sub(r"\+GP", "", ndpackage) 177 if "DS13311" in self.name or "DS13312" in self.name: 178 ndpackage = ndpackage.replace("+SMPS", "") 179 if (match := re.search(r":(STM32.*?):", domain)) is not None: 180 devs = html_listify(match.group(1)) 181 ndpackage += "+" + "|".join(d.replace("x", ".") for d in devs) 182 ndpackage, *filters = ndpackage.split("+") 183 filters = ("+" + "+".join(filters)) if filters else "" 184 spackage = [p for p in re.split(r"[/,]| or |<br>", ndpackage) if p] 185 # print(domain, spackage) 186 for package in spackage: 187 if (pack := split_package(package)) is not None: 188 packages.append((domain, package + filters, *pack)) 189 else: 190 print(f"Unknown package {package}!") 191 if not packages: 192 if "DS13259" in self.name: 193 packages = [("Pin:Number", "UFQFPN48", "UFQFPN48", "UFQFPN", 48)] 194 elif "DS13047" in self.name: 195 packages = [("Pin (UFQFPN48):Number", "UFQFPN48", "UFQFPN48", "UFQFPN", 48)] 196 else: 197 raise ValueError(f"Unable to find packages! {self._chapter_pins._relpath} {domains}") 198 return packages
packages_pins
200 @property 201 def packages_pins(self): 202 # Add pinouts and signals 203 pin_replace = {r"sup| |D?NC|/$|\(.*?\)|.*?connected.*": "", "–": "-", r"VREF_\+": "VREF+"} 204 add_replace = { 205 r"[- ]|\(.*?\)|.*?selection.*|.*?reset.*": "", 206 r"OPAMP": ",OPAMP", 207 r"COMP": ",COMP", 208 r"OPAMP,1": "OPAMP1", 209 } 210 afs_replace = {r"[- ]|\(.*?\)": "", "LCD_SEG": ",LCD_SEG"} # noqa: F841 211 pos_replace = {r'[“”\-"]|NC|\(.*?\)': ""} 212 sig_replace = { 213 r"[- ]|\(.*?\)": "", 214 r"(MOSI|SCK|NSS)I2S": r"\1,I2S", 215 "_µM": "_M", 216 r"(CH\d)TIM": r"\1,TIM", 217 r"_([RT]XD\d?|DV|EN|CLK)ETH_": r"_\1,ETH_", 218 } 219 220 data_packages = defaultdict(list) 221 data_pins = defaultdict(dict) 222 223 packages = set((d[0], d[1]) for d in self.packages) 224 # Import the pin definitions incl. additional function 225 for row in self._table_pinout.cell_rows(br="<br>"): 226 pin_name = row.match_value("pin +name|:name")[0].text(**pin_replace).strip() 227 if not pin_name: 228 continue 229 ios = row.match_value("I ?/ ?O")[0].text(**{"-": ""}) 230 ptype = row.match_value("type")[0].text() 231 # Hack to make fix the PD0/PD1 pins 232 if pin_name.startswith("OSC") and (remap := row.match_value("remap")): 233 if (pin := remap[0].text()).startswith("P"): 234 pin_name = f"{pin}-{pin_name}" 235 236 data_pin = data_pins[pin_name] 237 if ios: 238 data_pin["structure"] = ios 239 if ptype: 240 data_pin["type"] = ptype 241 if ptype == "I/O" and "STM32F1" not in self.device_family: 242 signals = html_listify(row.match_value("additional")[0].text(**add_replace)) 243 data_pin["additional"] = set(signals) 244 245 for domain, package_name in packages: 246 if ppos := html_listify(row[domain][0].text(**pos_replace)): 247 data_packages[package_name].append((pin_name, ppos)) 248 249 # Import the alternate functions 250 for table in self._tables_alternate_functions: 251 cells = table.domains("port|pin +name").cells(r"AF(IO)?\d+") 252 for pin, afs in cells.items(): 253 name = html_replace(pin.split(":")[-1], **pin_replace) 254 data_pin = data_pins[name] 255 if "alternate" not in data_pin: 256 data_pin["alternate"] = defaultdict(list) 257 for af, csignals in afs.items(): 258 af = int(re.search(r"AF(IO)?(\d{1,2})", af).group(2)) 259 for csignal in csignals: 260 signals = html_listify(csignal.text(**sig_replace)) 261 data_pin["alternate"][af].extend(signals) 262 263 return data_packages, data_pins
Inherited Members
- modm_data.html.document.Document
- path
- relpath
- fullname
- name
- version
- path_pdf
- chapters
- chapter
class
ReferenceManual(modm_data.html.document.Document):
13class ReferenceManual(Document): 14 def __init__(self, path: str): 15 super().__init__(path) 16 17 def __repr__(self) -> str: 18 return f"RM({self.fullname})" 19 20 @cached_property 21 def devices(self) -> list[str]: 22 # Find all occurrences of STM32* strings in the first chapter 23 chapter = self.chapter("chapter 0") 24 for heading, texts in chapter.heading_texts(): 25 all_text = "" 26 for text in texts: 27 all_text += text.text(br=" ") 28 # print(heading, all_text) 29 # Must also match "STM32L4+" !!! 30 rdevices = re.findall(r"STM32[\w/\+]+", all_text) 31 if rdevices: 32 break 33 34 # Split combo strings into individual devices 35 devices = [] 36 for device in list(set(rdevices)): 37 if len(parts := device.split("/")) >= 2: 38 base = parts[0] 39 devices.append(base) 40 base = base[: -len(parts[1])] 41 for part in parts[1:]: 42 devices.append(base + part) 43 else: 44 devices.append(device) 45 46 # Remove non-specific mentions: shortest subset 47 return list(sorted(set(devices))) 48 49 @property 50 def device_filters(self) -> list[str]: 51 # Match STM32L4+ with STM32[SRQP], since STM32L4Axx is STM32L4 (without +) 52 return [d.replace("x", ".").replace("+", r"[SRQP]").lower() for d in self.devices] 53 54 def filter_devices(self, devices): 55 dids = set() 56 for did in devices: 57 for device in self.device_filters: 58 if re.match(device, did.string): 59 dids.add(did) 60 break 61 return list(dids) 62 63 @cached_property 64 def flash_latencies(self): 65 # FLASH peripheral is not described in the reference manual because... reasons? 66 if "STM32F100xx" in self.devices: 67 return {"": {1800: [24]}} 68 if any(d.startswith("STM32F1") for d in self.devices): 69 return {"": {1800: [24, 48, 72]}} 70 # Tables too complicated to parse 71 if any(d.startswith("STM32L1") for d in self.devices): 72 # See Table 13 and Table 26 73 return { 74 "stm32l1...[68b]": { # Cat 1 75 1800: [16, 32], 76 1500: [8, 16], 77 1200: [2.1, 4.2], 78 }, 79 "": { # Cat 2,3,4,5,6 80 1800: [16, 32], 81 1500: [8, 16], 82 1200: [4.2, 8], 83 }, 84 } 85 86 if any(d.startswith("STM32F0") for d in self.devices) or any(d.startswith("STM32F3") for d in self.devices): 87 # Attempt to find the FLASH_ACR register and look at the LATENCY descriptions 88 chapter = self.chapter(r"flash") 89 headings = chapter.heading_tables(r"\(FLASH_ACR\)") 90 if not headings: 91 raise KeyError(f"Cannot find FLASH_ACR section in '{chapter._relpath}'") 92 bit_descr_table = headings[0][1][1] 93 cell = bit_descr_table.cell(1, -1) 94 matches = list(map(int, re.findall(r"CLK +≤ +(\d+) +MHz", cell.text()))) 95 return {1800: matches} 96 97 # Derive from the wait states table 98 if any(d.startswith("STM32F2") for d in self.devices): 99 chapter = self.chapter(r"memory and bus") 100 else: 101 chapter = self.chapters(r"flash")[0] 102 103 if tables := chapter.tables("power +range"): 104 table_data = defaultdict(list) 105 for row in tables[0].cell_rows(): 106 min_voltage = re.search(r"(\d.\d+).+?(\d.\d+)", row.match_value("power +range")[0].text()).group(1) 107 min_voltage = int(float(min_voltage) * 1000) 108 for wait_state in row.match_keys("wait +state"): 109 max_frequency = float(re.search(r"(.*?) +MHz", row[wait_state][0].text()).group(1)) 110 table_data[min_voltage].append(max_frequency) 111 return {"": {k: sorted(v) for k, v in table_data.items()}} 112 113 ws_tables = {} 114 for table in chapter.tables(r"wait states"): 115 table_data = defaultdict(list) 116 for row in table.cell_rows(): 117 for vkey in row.match_keys("voltage|range"): 118 if (vrange := re.search(r"(\d.\d+).+?(\d.\d+) *?V", vkey)) is not None: 119 min_voltage = int(float(vrange.group(1)) * 1000) 120 else: 121 vrange = re.search(r"Range(\d[bn]?)", vkey.replace(" ", "")) 122 min_voltage = {"0": 1280, "1b": 1280, "1n": 1200, "1": 1200, "2": 1000}[vrange.group(1)] 123 max_frequency = row[vkey][0].text( 124 **{r"-": "", r".*?CLK.*?(\d+).*": r"\1", r".*?;(\d+) *MHz.*": r"\1", r".*?(\d+).*": r"\1"} 125 ) 126 if max_frequency: 127 table_data[min_voltage].append(float(max_frequency)) 128 dfilter = device_filter_from(table.caption()) 129 assert table_data 130 ws_tables[dfilter] = {v: list(sorted(set(f))) for v, f in table_data.items()} 131 132 # print(ws_tables) 133 assert ws_tables 134 return ws_tables 135 136 @cached_property 137 def vector_tables(self): 138 name_replace = { 139 "p": r"\1,", 140 r" +": "", 141 r"\(.*?\)|_IRQn|_$|^-$|[Rr]eserved": "", 142 r"\+|and": ",", 143 r"_(\w+)(LSE|BLE|I2C\d)_": r"_\1,\2_", 144 r"EXTI\[(\d+):(\d+)\]": r"EXTI\1_\2", 145 r"\[(\d+):(\d+)\]": r"\2_\1", 146 } 147 capt_replace = { 148 "br": " ", 149 r" +": " ", 150 r"\((Cat\.\d.*?devices)\)": r"\1", 151 r"\(.*?\)": "", 152 r"for +connectivity +line +devices": "for STM32F105/7", 153 r"for +XL\-density +devices": "for STM32F10xxF/G", 154 r"for +other +STM32F10xxx +devices": "for the rest", 155 } 156 157 chapter = next(c for c in self.chapters(r"nvic|interrupt") if "exti" not in c.name) 158 tables = chapter.tables(r"vector +table|list +of +vector|NVIC|CPU") 159 assert tables 160 161 vtables = {} 162 for table in tables: 163 caption = table.caption(**capt_replace) 164 table_name = "VectorTable" 165 if len(tables) > 1: 166 # Create the right table filter 167 if (core := re.search(r"CPU(\d)|NVIC(\d)", caption)) is not None: 168 table_name += f":Core={core.group(1)}" # Multi-core device 169 elif devices := device_filter_from(caption): 170 table_name += f":Device={devices}" 171 elif categories := re.findall(r"Cat\.(\d)", caption): 172 # Size category filter 173 categories = "|".join(categories) 174 table_name += f":Categories={categories}" 175 176 vtable = defaultdict(set) 177 for pos, values in table.domains("position").cells("acro").items(): 178 if pos.isnumeric() and (name := values.match_value("acro")[0].text(**name_replace)): 179 vtable[int(pos)].update(html.listify(name)) 180 vtables[table_name] = dict(vtable) 181 182 return vtables 183 184 @cache 185 def peripheral_maps(self, chapter, assert_table=True): 186 off_replace = {r" +": "", "0x000x00": "0x00", "to": "-", "×": "*", r"\(\d+\)": ""} 187 dom_replace = {r"Register +size": "Bit position"} # noqa: F841 188 reg_replace = { 189 r" +|\.+": "", 190 r"\(COM(\d)\)": r"_COM\1", 191 r"^[Rr]es$||0x[\da-fA-FXx]+|\(.*?\)|-": "", 192 r"(?i)reserved|resetvalue.*": "", 193 "enabled": "_EN", 194 "disabled": "_DIS", 195 r"(?i)Outputcomparemode": "_Output", 196 "(?i)Inputcapturemode": "_Input", 197 "mode": "", 198 r"^TG_FS_": "OTG_FS_", 199 "toRTC": "RTC", 200 "SPI2S_": "SPI_", 201 r"andTIM\d+_.*": "", 202 r"x=[\d,]+": "", 203 } 204 fld_replace = { 205 r"\] +\d+(th|rd|nd|st)": "]", 206 r" +|\.+|\[.*?\]|\[?\d+:\d+\]?|\(.*?\)|-|^[\dXx]+$|%|__|:0\]": "", 207 r"Dataregister|Independentdataregister": "DATA", 208 r"Framefilterreg0.*": "FRAME_FILTER_REG", 209 r"[Rr]es(erved)?|[Rr]egular|x_x(bits)?|NotAvailable|RefertoSection\d+:Comparator": "", 210 r"Sampletimebits|Injectedchannelsequence|channelsequence|conversioninregularsequencebits": "", 211 r"conversioninsequencebits|conversionininjectedsequencebits|or|first|second|third|fourth": "", 212 } 213 bit_replace = {r".*:": ""} 214 glo_replace = {r"[Rr]eserved": ""} 215 216 print(chapter._relpath) 217 tables = chapter.tables(r"register +map") 218 if assert_table: 219 assert tables 220 221 peripheral_data = {} 222 for table in tables: 223 caption = table.caption() 224 if any(n in caption for n in ["DFIFO", "global", "EXTI register map section", "vs swapping option"]): 225 continue 226 heading = table._heading.text(**{r"((\d+\.)+(\d+)?).*": r"\1"}) 227 print(table, caption) 228 229 register_data = {} 230 for row in table.cell_rows(): 231 rkey = next(k for k in row.match_keys("register") if "size" not in k) 232 register = row[rkey][0].text(**reg_replace).strip() 233 if not register: 234 continue 235 offset = row.match_value(r"off-?set|addr")[0].text(**off_replace) 236 if not offset: 237 continue 238 field_data = {} 239 for bits in row.match_keys(r"^[\d-]+$|.*?:[\d-]+$"): 240 field = row[bits][0].text(**fld_replace).strip() 241 if not field: 242 continue 243 bits = sorted(html.listify(html.replace(bits, **bit_replace), r"-")) 244 if len(bits) == 2: 245 bits = range(int(bits[0]), int(bits[1])) 246 for bit in bits: 247 bit = int(bit) 248 field_data[bit] = field 249 # print(f"{offset:>10} {register:60} {bit:>2} {field}") 250 register_data[register] = (offset, field_data) 251 assert register_data 252 peripheral_data[caption] = (heading, register_data) 253 254 if peripheral_data and all("HRTIM" in ca for ca in peripheral_data): 255 caption, heading = next((c, p) for c, (p, _) in peripheral_data.items()) 256 all_registers = {k: v for (_, values) in peripheral_data.values() for k, v in values.items()} 257 peripheral_data = {caption: (heading, all_registers)} 258 259 instance_offsets = {} 260 if tables := chapter.tables(r"ADC +global +register +map"): 261 for row in tables[0].cell_rows(): 262 if ifilter := row.match_value("register")[0].text(**glo_replace): 263 offset = int(row.match_value("offset")[0].text().split("-")[0], 16) 264 for instance in re.findall(r"ADC(\d+)", ifilter): 265 instance_offsets[f"ADC[{instance}]"] = offset 266 267 return peripheral_data, instance_offsets 268 269 @cached_property 270 def peripherals(self): 271 per_replace = { 272 r" +": "", 273 r".*?\(([A-Z]+|DMA2D)\).*?": r"\1", 274 r"Reserved|Port|Power|Registers|Reset|\(.*?\)|_REG": "", 275 r"(\d)/I2S\d": r"\1", 276 r"/I2S|CANMessageRAM|Cortex-M4|I2S\dext|^GPV$": "", 277 r"Ethernet": "ETH", 278 r"Flash": "FLASH", 279 r"(?i).*ETHERNET.*": "ETH", 280 r"(?i)Firewall": "FW", 281 r"HDMI-|": "", 282 "SPDIF-RX": "SPDIFRX", 283 r"SPI2S2": "SPI2", 284 "Tamper": "TAMP", 285 "TT-FDCAN": "FDCAN", 286 r"USBOTG([FH])S": r"USB_OTG_\1S", 287 "LCD-TFT": "LTDC", 288 "DSIHOST": "DSI", 289 "TIMER": "TIM", 290 r"^VREF$": "VREFBUF", 291 "DelayBlock": "DLYB", 292 "I/O": "", 293 "DAC1/2": "DAC12", 294 r"[a-z]": "", 295 } 296 adr_replace = {r" |\(\d\)": ""} 297 sct_replace = {r"-": ""} 298 hdr_replace = {r"Peripheral +register +map": "map"} 299 bus_replace = {r".*(A\wB\d).*": r"\1", "-": ""} 300 301 # RM0431 has a bug where the peripheral table is in chapter 1 302 if "RM0431" in self.name: 303 chapters = self.chapters(r"chapter 1 ") 304 else: 305 chapters = self.chapters(r"memory +and +bus|memory +overview") 306 assert chapters 307 print(chapters[0]._relpath) 308 309 tables = chapters[0].tables(r"register +boundary") 310 assert tables 311 312 peripherals = defaultdict(list) 313 for table in tables: 314 print(table.caption()) 315 for row in table.cell_rows(**hdr_replace): 316 regmaps = row.match_value("map") 317 if regmaps: 318 regmap = regmaps[0].text(**sct_replace).strip() 319 sections = re.findall(r"Section +(\d+\.\d+(\.\d+)?)", regmap) 320 if not sections: 321 continue 322 sections = [s[0] for s in sections] 323 else: 324 sections = [] 325 names = html.listify(row.match_value("peri")[0].text(**per_replace), r"[-\&\+/,]") 326 if not names: 327 continue 328 address = row.match_value("address")[0].text(**adr_replace) 329 address_min = int(address.split("-")[0], 16) 330 address_max = int(address.split("-")[1], 16) 331 bus = row.match_value("bus")[0].text(**bus_replace).strip() 332 peripherals[table.caption()].append((names, address_min, address_max, bus, sections)) 333 print( 334 f"{','.join(names):20} @[{hex(address_min)}, {hex(address_max)}] {bus:4} -> {', '.join(sections)}" 335 ) 336 337 return peripherals
devices: list[str]
20 @cached_property 21 def devices(self) -> list[str]: 22 # Find all occurrences of STM32* strings in the first chapter 23 chapter = self.chapter("chapter 0") 24 for heading, texts in chapter.heading_texts(): 25 all_text = "" 26 for text in texts: 27 all_text += text.text(br=" ") 28 # print(heading, all_text) 29 # Must also match "STM32L4+" !!! 30 rdevices = re.findall(r"STM32[\w/\+]+", all_text) 31 if rdevices: 32 break 33 34 # Split combo strings into individual devices 35 devices = [] 36 for device in list(set(rdevices)): 37 if len(parts := device.split("/")) >= 2: 38 base = parts[0] 39 devices.append(base) 40 base = base[: -len(parts[1])] 41 for part in parts[1:]: 42 devices.append(base + part) 43 else: 44 devices.append(device) 45 46 # Remove non-specific mentions: shortest subset 47 return list(sorted(set(devices)))
flash_latencies
63 @cached_property 64 def flash_latencies(self): 65 # FLASH peripheral is not described in the reference manual because... reasons? 66 if "STM32F100xx" in self.devices: 67 return {"": {1800: [24]}} 68 if any(d.startswith("STM32F1") for d in self.devices): 69 return {"": {1800: [24, 48, 72]}} 70 # Tables too complicated to parse 71 if any(d.startswith("STM32L1") for d in self.devices): 72 # See Table 13 and Table 26 73 return { 74 "stm32l1...[68b]": { # Cat 1 75 1800: [16, 32], 76 1500: [8, 16], 77 1200: [2.1, 4.2], 78 }, 79 "": { # Cat 2,3,4,5,6 80 1800: [16, 32], 81 1500: [8, 16], 82 1200: [4.2, 8], 83 }, 84 } 85 86 if any(d.startswith("STM32F0") for d in self.devices) or any(d.startswith("STM32F3") for d in self.devices): 87 # Attempt to find the FLASH_ACR register and look at the LATENCY descriptions 88 chapter = self.chapter(r"flash") 89 headings = chapter.heading_tables(r"\(FLASH_ACR\)") 90 if not headings: 91 raise KeyError(f"Cannot find FLASH_ACR section in '{chapter._relpath}'") 92 bit_descr_table = headings[0][1][1] 93 cell = bit_descr_table.cell(1, -1) 94 matches = list(map(int, re.findall(r"CLK +≤ +(\d+) +MHz", cell.text()))) 95 return {1800: matches} 96 97 # Derive from the wait states table 98 if any(d.startswith("STM32F2") for d in self.devices): 99 chapter = self.chapter(r"memory and bus") 100 else: 101 chapter = self.chapters(r"flash")[0] 102 103 if tables := chapter.tables("power +range"): 104 table_data = defaultdict(list) 105 for row in tables[0].cell_rows(): 106 min_voltage = re.search(r"(\d.\d+).+?(\d.\d+)", row.match_value("power +range")[0].text()).group(1) 107 min_voltage = int(float(min_voltage) * 1000) 108 for wait_state in row.match_keys("wait +state"): 109 max_frequency = float(re.search(r"(.*?) +MHz", row[wait_state][0].text()).group(1)) 110 table_data[min_voltage].append(max_frequency) 111 return {"": {k: sorted(v) for k, v in table_data.items()}} 112 113 ws_tables = {} 114 for table in chapter.tables(r"wait states"): 115 table_data = defaultdict(list) 116 for row in table.cell_rows(): 117 for vkey in row.match_keys("voltage|range"): 118 if (vrange := re.search(r"(\d.\d+).+?(\d.\d+) *?V", vkey)) is not None: 119 min_voltage = int(float(vrange.group(1)) * 1000) 120 else: 121 vrange = re.search(r"Range(\d[bn]?)", vkey.replace(" ", "")) 122 min_voltage = {"0": 1280, "1b": 1280, "1n": 1200, "1": 1200, "2": 1000}[vrange.group(1)] 123 max_frequency = row[vkey][0].text( 124 **{r"-": "", r".*?CLK.*?(\d+).*": r"\1", r".*?;(\d+) *MHz.*": r"\1", r".*?(\d+).*": r"\1"} 125 ) 126 if max_frequency: 127 table_data[min_voltage].append(float(max_frequency)) 128 dfilter = device_filter_from(table.caption()) 129 assert table_data 130 ws_tables[dfilter] = {v: list(sorted(set(f))) for v, f in table_data.items()} 131 132 # print(ws_tables) 133 assert ws_tables 134 return ws_tables
vector_tables
136 @cached_property 137 def vector_tables(self): 138 name_replace = { 139 "p": r"\1,", 140 r" +": "", 141 r"\(.*?\)|_IRQn|_$|^-$|[Rr]eserved": "", 142 r"\+|and": ",", 143 r"_(\w+)(LSE|BLE|I2C\d)_": r"_\1,\2_", 144 r"EXTI\[(\d+):(\d+)\]": r"EXTI\1_\2", 145 r"\[(\d+):(\d+)\]": r"\2_\1", 146 } 147 capt_replace = { 148 "br": " ", 149 r" +": " ", 150 r"\((Cat\.\d.*?devices)\)": r"\1", 151 r"\(.*?\)": "", 152 r"for +connectivity +line +devices": "for STM32F105/7", 153 r"for +XL\-density +devices": "for STM32F10xxF/G", 154 r"for +other +STM32F10xxx +devices": "for the rest", 155 } 156 157 chapter = next(c for c in self.chapters(r"nvic|interrupt") if "exti" not in c.name) 158 tables = chapter.tables(r"vector +table|list +of +vector|NVIC|CPU") 159 assert tables 160 161 vtables = {} 162 for table in tables: 163 caption = table.caption(**capt_replace) 164 table_name = "VectorTable" 165 if len(tables) > 1: 166 # Create the right table filter 167 if (core := re.search(r"CPU(\d)|NVIC(\d)", caption)) is not None: 168 table_name += f":Core={core.group(1)}" # Multi-core device 169 elif devices := device_filter_from(caption): 170 table_name += f":Device={devices}" 171 elif categories := re.findall(r"Cat\.(\d)", caption): 172 # Size category filter 173 categories = "|".join(categories) 174 table_name += f":Categories={categories}" 175 176 vtable = defaultdict(set) 177 for pos, values in table.domains("position").cells("acro").items(): 178 if pos.isnumeric() and (name := values.match_value("acro")[0].text(**name_replace)): 179 vtable[int(pos)].update(html.listify(name)) 180 vtables[table_name] = dict(vtable) 181 182 return vtables
@cache
def
peripheral_maps(self, chapter, assert_table=True):
184 @cache 185 def peripheral_maps(self, chapter, assert_table=True): 186 off_replace = {r" +": "", "0x000x00": "0x00", "to": "-", "×": "*", r"\(\d+\)": ""} 187 dom_replace = {r"Register +size": "Bit position"} # noqa: F841 188 reg_replace = { 189 r" +|\.+": "", 190 r"\(COM(\d)\)": r"_COM\1", 191 r"^[Rr]es$||0x[\da-fA-FXx]+|\(.*?\)|-": "", 192 r"(?i)reserved|resetvalue.*": "", 193 "enabled": "_EN", 194 "disabled": "_DIS", 195 r"(?i)Outputcomparemode": "_Output", 196 "(?i)Inputcapturemode": "_Input", 197 "mode": "", 198 r"^TG_FS_": "OTG_FS_", 199 "toRTC": "RTC", 200 "SPI2S_": "SPI_", 201 r"andTIM\d+_.*": "", 202 r"x=[\d,]+": "", 203 } 204 fld_replace = { 205 r"\] +\d+(th|rd|nd|st)": "]", 206 r" +|\.+|\[.*?\]|\[?\d+:\d+\]?|\(.*?\)|-|^[\dXx]+$|%|__|:0\]": "", 207 r"Dataregister|Independentdataregister": "DATA", 208 r"Framefilterreg0.*": "FRAME_FILTER_REG", 209 r"[Rr]es(erved)?|[Rr]egular|x_x(bits)?|NotAvailable|RefertoSection\d+:Comparator": "", 210 r"Sampletimebits|Injectedchannelsequence|channelsequence|conversioninregularsequencebits": "", 211 r"conversioninsequencebits|conversionininjectedsequencebits|or|first|second|third|fourth": "", 212 } 213 bit_replace = {r".*:": ""} 214 glo_replace = {r"[Rr]eserved": ""} 215 216 print(chapter._relpath) 217 tables = chapter.tables(r"register +map") 218 if assert_table: 219 assert tables 220 221 peripheral_data = {} 222 for table in tables: 223 caption = table.caption() 224 if any(n in caption for n in ["DFIFO", "global", "EXTI register map section", "vs swapping option"]): 225 continue 226 heading = table._heading.text(**{r"((\d+\.)+(\d+)?).*": r"\1"}) 227 print(table, caption) 228 229 register_data = {} 230 for row in table.cell_rows(): 231 rkey = next(k for k in row.match_keys("register") if "size" not in k) 232 register = row[rkey][0].text(**reg_replace).strip() 233 if not register: 234 continue 235 offset = row.match_value(r"off-?set|addr")[0].text(**off_replace) 236 if not offset: 237 continue 238 field_data = {} 239 for bits in row.match_keys(r"^[\d-]+$|.*?:[\d-]+$"): 240 field = row[bits][0].text(**fld_replace).strip() 241 if not field: 242 continue 243 bits = sorted(html.listify(html.replace(bits, **bit_replace), r"-")) 244 if len(bits) == 2: 245 bits = range(int(bits[0]), int(bits[1])) 246 for bit in bits: 247 bit = int(bit) 248 field_data[bit] = field 249 # print(f"{offset:>10} {register:60} {bit:>2} {field}") 250 register_data[register] = (offset, field_data) 251 assert register_data 252 peripheral_data[caption] = (heading, register_data) 253 254 if peripheral_data and all("HRTIM" in ca for ca in peripheral_data): 255 caption, heading = next((c, p) for c, (p, _) in peripheral_data.items()) 256 all_registers = {k: v for (_, values) in peripheral_data.values() for k, v in values.items()} 257 peripheral_data = {caption: (heading, all_registers)} 258 259 instance_offsets = {} 260 if tables := chapter.tables(r"ADC +global +register +map"): 261 for row in tables[0].cell_rows(): 262 if ifilter := row.match_value("register")[0].text(**glo_replace): 263 offset = int(row.match_value("offset")[0].text().split("-")[0], 16) 264 for instance in re.findall(r"ADC(\d+)", ifilter): 265 instance_offsets[f"ADC[{instance}]"] = offset 266 267 return peripheral_data, instance_offsets
peripherals
269 @cached_property 270 def peripherals(self): 271 per_replace = { 272 r" +": "", 273 r".*?\(([A-Z]+|DMA2D)\).*?": r"\1", 274 r"Reserved|Port|Power|Registers|Reset|\(.*?\)|_REG": "", 275 r"(\d)/I2S\d": r"\1", 276 r"/I2S|CANMessageRAM|Cortex-M4|I2S\dext|^GPV$": "", 277 r"Ethernet": "ETH", 278 r"Flash": "FLASH", 279 r"(?i).*ETHERNET.*": "ETH", 280 r"(?i)Firewall": "FW", 281 r"HDMI-|": "", 282 "SPDIF-RX": "SPDIFRX", 283 r"SPI2S2": "SPI2", 284 "Tamper": "TAMP", 285 "TT-FDCAN": "FDCAN", 286 r"USBOTG([FH])S": r"USB_OTG_\1S", 287 "LCD-TFT": "LTDC", 288 "DSIHOST": "DSI", 289 "TIMER": "TIM", 290 r"^VREF$": "VREFBUF", 291 "DelayBlock": "DLYB", 292 "I/O": "", 293 "DAC1/2": "DAC12", 294 r"[a-z]": "", 295 } 296 adr_replace = {r" |\(\d\)": ""} 297 sct_replace = {r"-": ""} 298 hdr_replace = {r"Peripheral +register +map": "map"} 299 bus_replace = {r".*(A\wB\d).*": r"\1", "-": ""} 300 301 # RM0431 has a bug where the peripheral table is in chapter 1 302 if "RM0431" in self.name: 303 chapters = self.chapters(r"chapter 1 ") 304 else: 305 chapters = self.chapters(r"memory +and +bus|memory +overview") 306 assert chapters 307 print(chapters[0]._relpath) 308 309 tables = chapters[0].tables(r"register +boundary") 310 assert tables 311 312 peripherals = defaultdict(list) 313 for table in tables: 314 print(table.caption()) 315 for row in table.cell_rows(**hdr_replace): 316 regmaps = row.match_value("map") 317 if regmaps: 318 regmap = regmaps[0].text(**sct_replace).strip() 319 sections = re.findall(r"Section +(\d+\.\d+(\.\d+)?)", regmap) 320 if not sections: 321 continue 322 sections = [s[0] for s in sections] 323 else: 324 sections = [] 325 names = html.listify(row.match_value("peri")[0].text(**per_replace), r"[-\&\+/,]") 326 if not names: 327 continue 328 address = row.match_value("address")[0].text(**adr_replace) 329 address_min = int(address.split("-")[0], 16) 330 address_max = int(address.split("-")[1], 16) 331 bus = row.match_value("bus")[0].text(**bus_replace).strip() 332 peripherals[table.caption()].append((names, address_min, address_max, bus, sections)) 333 print( 334 f"{','.join(names):20} @[{hex(address_min)}, {hex(address_max)}] {bus:4} -> {', '.join(sections)}" 335 ) 336 337 return peripherals
Inherited Members
- modm_data.html.document.Document
- path
- relpath
- fullname
- name
- version
- path_pdf
- chapters
- chapter
def
load_documents() -> list:
20def load_documents() -> list: 21 documents = defaultdict(dict) 22 for path in sorted(ext_path("stmicro/html").glob("*-v*")): 23 # This doc is parsed wrongly since it has a DRAFT background 24 if "DS12960-v5" in path.stem: 25 continue 26 # This doc has a preliminary ordering information STM32WBA52CGU6TR 27 if "DS14127" in path.stem: 28 continue 29 doc = Document(path) 30 if "DS" in doc.name and (chap := doc.chapters("chapter 0")): 31 # FIXME: Better detection that DS13252 is a STM32WB55 module, not a chip! 32 if ( 33 any("STM32" in h.html for h in chap[0].headings()) 34 and "DS13252" not in doc.name 35 and "DS14096" not in doc.name 36 ): 37 documents[doc.name][doc.version] = DatasheetStm32(path) 38 else: 39 documents[doc.name][doc.version] = DatasheetSensor(path) 40 elif "RM" in doc.name: 41 documents[doc.name][doc.version] = ReferenceManual(path) 42 return documents
def
load_document_devices( use_cached=True) -> tuple[dict[modm_data.owl.DeviceIdentifier, DatasheetStm32], dict[modm_data.owl.DeviceIdentifier, ReferenceManual]]:
45def load_document_devices( 46 use_cached=True, 47) -> tuple[dict[DeviceIdentifier, DatasheetStm32], dict[DeviceIdentifier, ReferenceManual]]: 48 global DOCUMENT_CACHE 49 if DOCUMENT_CACHE is not None: 50 return DOCUMENT_CACHE 51 52 global MAP_DEVICE_DOC_FILE 53 if MAP_DEVICE_DOC_FILE.exists() and use_cached: 54 with MAP_DEVICE_DOC_FILE.open("r", encoding="utf-8") as fh: 55 json_data = json.load(fh) 56 57 docs = {} 58 for path in set(json_data["ds"].values()): 59 docs[path] = DatasheetStm32(path) 60 for path in set(json_data["rm"].values()): 61 docs[path] = ReferenceManual(path) 62 datasheets = {did_from_string(did): docs[path] for did, path in json_data["ds"].items()} 63 reference_manuals = {did_from_string(did): docs[path] for did, path in json_data["rm"].items()} 64 else: 65 dss = defaultdict(set) 66 rms = defaultdict(set) 67 for name, versions in load_documents().items(): 68 # Always choose the latest version 69 doc = list(versions.values())[-1] 70 # print(doc.path_pdf.relative_to(Path().cwd()), doc.path.relative_to(Path().cwd())) 71 # print(doc.devices) 72 if isinstance(doc, DatasheetStm32): 73 if not doc.devices: 74 raise ValueError(f"{doc} has no associated devices!") 75 for dev in doc.devices: 76 dss[dev].add(doc) 77 elif isinstance(doc, ReferenceManual): 78 if not doc.devices: 79 raise ValueError(f"{doc} has no associated devices!") 80 for dev in doc.devices: 81 rms[dev].add(doc) 82 83 for dev, docs in dss.items(): 84 if len(docs) != 1: 85 raise ValueError(f"One device points to multiple datasheets! {dev} -> {docs}") 86 datasheets = {did: list(ds)[0] for did, ds in dss.items()} 87 # print(len(datasheets.keys()), sorted(list(d.string for d in datasheets.keys()))) 88 89 manuals = defaultdict(set) 90 for dev, docs in rms.items(): 91 if len(docs) != 1: 92 raise ValueError(f"One device points to multiple reference manuals! {dev} -> {docs}") 93 for dev in list(docs)[0].filter_devices(datasheets.keys()): 94 manuals[dev].add(list(docs)[0]) 95 96 for dev, docs in manuals.items(): 97 if len(docs) != 1: 98 raise ValueError(f"One device points to multiple reference manuals! {dev} -> {docs}") 99 100 reference_manuals = {did: list(rm)[0] for did, rm in manuals.items()} 101 102 # Cache the results for the next call 103 json_data = { 104 "ds": {did.string: str(doc.path) for did, doc in datasheets.items()}, 105 "rm": {did.string: str(doc.path) for did, doc in reference_manuals.items()}, 106 } 107 MAP_DEVICE_DOC_FILE.parent.mkdir(parents=True, exist_ok=True) 108 with MAP_DEVICE_DOC_FILE.open("w", encoding="utf-8") as fh: 109 json.dump(json_data, fh, indent=4) 110 111 DOCUMENT_CACHE = (datasheets, reference_manuals) 112 return datasheets, reference_manuals
def
find_device_filter(did, device_filters):