modm_data.html.stmicro.reference
1# Copyright 2022, Niklas Hauser 2# SPDX-License-Identifier: MPL-2.0 3 4import re 5from functools import cached_property, cache 6from collections import defaultdict 7import modm_data.html as html 8from .helper import split_device_filter, device_filter_from 9 10 11class ReferenceManual(html.Document): 12 def __init__(self, path: str): 13 super().__init__(path) 14 15 def __repr__(self) -> str: 16 return f"RM({self.fullname})" 17 18 @cached_property 19 def devices(self) -> list[str]: 20 # Find all occurrences of STM32* strings in the first chapter 21 chapter = self.chapter("chapter 0") 22 for heading, texts in chapter.heading_texts(): 23 all_text = "" 24 for text in texts: 25 all_text += text.text(br=" ") 26 # print(heading, all_text) 27 # Must also match "STM32L4+" !!! 28 rdevices = re.findall(r"STM32[\w/\+]+", all_text) 29 if rdevices: 30 break 31 32 # Split combo strings into individual devices 33 devices = [] 34 for device in list(set(rdevices)): 35 if len(parts := device.split("/")) >= 2: 36 base = parts[0] 37 devices.append(base) 38 base = base[:-len(parts[1])] 39 for part in parts[1:]: 40 devices.append(base + part) 41 else: 42 devices.append(device) 43 44 # Remove non-specific mentions: shortest subset 45 return list(sorted(set(devices))) 46 47 @property 48 def device_filters(self) -> list[str]: 49 # Match STM32L4+ with STM32[SRQP], since STM32L4Axx is STM32L4 (without +) 50 return [d.replace("x", ".").replace("+", r"[SRQP]").lower() for d in self.devices] 51 52 def filter_devices(self, devices): 53 dids = set() 54 for did in devices: 55 for device in self.device_filters: 56 if re.match(device, did.string): 57 dids.add(did) 58 break 59 return list(dids) 60 61 @cached_property 62 def flash_latencies(self): 63 # FLASH peripheral is not described in the reference manual because... reasons? 64 if "STM32F100xx" in self.devices: 65 return {"": {1800: [24]}} 66 if any(d.startswith("STM32F1") for d in self.devices): 67 return {"": {1800: [24, 48, 72]}} 68 # Tables too complicated to parse 69 if any(d.startswith("STM32L1") for d in self.devices): 70 # See Table 13 and Table 26 71 return { 72 "stm32l1...[68b]": { # Cat 1 73 1800: [16, 32], 74 1500: [8, 16], 75 1200: [2.1, 4.2] 76 }, 77 "": { # Cat 2,3,4,5,6 78 1800: [16, 32], 79 1500: [8, 16], 80 1200: [4.2, 8] 81 } 82 } 83 84 if (any(d.startswith("STM32F0") for d in self.devices) or 85 any(d.startswith("STM32F3") for d in self.devices)): 86 # Attempt to find the FLASH_ACR register and look at the LATENCY descriptions 87 chapter = self.chapter(r"flash") 88 headings = chapter.heading_tables(r"\(FLASH_ACR\)") 89 if not headings: 90 raise KeyError(f"Cannot find FLASH_ACR section in '{chapter._relpath}'") 91 bit_descr_table = headings[0][1][1] 92 cell = bit_descr_table.cell(1, -1) 93 matches = list(map(int, re.findall(r"CLK +≤ +(\d+) +MHz", cell.text()))) 94 return {1800: matches} 95 96 # Derive from the wait states table 97 if any(d.startswith("STM32F2") for d in self.devices): 98 chapter = self.chapter(r"memory and bus") 99 else: 100 chapter = self.chapters(r"flash")[0] 101 102 if tables := chapter.tables("power +range"): 103 table_data = defaultdict(list) 104 for row in tables[0].cell_rows(): 105 min_voltage = re.search(r"(\d.\d+).+?(\d.\d+)", row.match_value("power +range")[0].text()).group(1) 106 min_voltage = int(float(min_voltage) * 1000) 107 for wait_state in row.match_keys("wait +state"): 108 max_frequency = float(re.search(r"(.*?) +MHz", row[wait_state][0].text()).group(1)) 109 table_data[min_voltage].append(max_frequency) 110 return {"": {k:sorted(v) for k,v in table_data.items()}} 111 112 ws_tables = {} 113 for table in chapter.tables(r"wait states"): 114 table_data = defaultdict(list) 115 for row in table.cell_rows(): 116 for vkey in row.match_keys("voltage|range"): 117 if (vrange := re.search(r"(\d.\d+).+?(\d.\d+) *?V", vkey)) is not None: 118 min_voltage = int(float(vrange.group(1)) * 1000) 119 else: 120 vrange = re.search(r"Range(\d[bn]?)", vkey.replace(" ", "")) 121 min_voltage = {"0": 1280, "1b": 1280, 122 "1n": 1200, "1": 1200, 123 "2": 1000}[vrange.group(1)] 124 max_frequency = row[vkey][0].text( 125 **{r"-": "", r".*?CLK.*?(\d+).*": r"\1", 126 r".*?;(\d+) *MHz.*": r"\1", r".*?(\d+).*": r"\1"}) 127 if max_frequency: 128 table_data[min_voltage].append(float(max_frequency)) 129 dfilter = device_filter_from(table.caption()) 130 assert table_data 131 ws_tables[dfilter] = {v: list(sorted(set(f))) for v, f in table_data.items()} 132 133 # print(ws_tables) 134 assert ws_tables 135 return ws_tables 136 137 138 139 @cached_property 140 def vector_tables(self): 141 name_replace = {"p": r"\1,", r" +": "", r"\(.*?\)|_IRQn|_$|^-$|[Rr]eserved": "", 142 r"\+|and": ",", r"_(\w+)(LSE|BLE|I2C\d)_": r"_\1,\2_", 143 r"EXTI\[(\d+):(\d+)\]": r"EXTI\1_\2", r"\[(\d+):(\d+)\]": r"\2_\1"} 144 capt_replace = {"br": " ", r" +": " ", r"\((Cat\.\d.*?devices)\)": r"\1", r"\(.*?\)": "", 145 r"for +connectivity +line +devices": "for STM32F105/7", 146 r"for +XL\-density +devices": "for STM32F10xxF/G", 147 r"for +other +STM32F10xxx +devices": "for the rest",} 148 149 chapter = next(c for c in self.chapters(r"nvic|interrupt") if "exti" not in c.name) 150 tables = chapter.tables(r"vector +table|list +of +vector|NVIC|CPU") 151 assert tables 152 153 vtables = {} 154 for table in tables: 155 caption = table.caption(**capt_replace) 156 table_name = "VectorTable" 157 if len(tables) > 1: 158 # Create the right table filter 159 if (core := re.search(r"CPU(\d)|NVIC(\d)", caption)) is not None: 160 table_name += f":Core={core.group(1)}" # Multi-core device 161 elif devices := device_filter_from(caption): 162 table_name += f":Device={devices}" 163 elif categories := re.findall(r"Cat\.(\d)", caption): 164 # Size category filter 165 categories = "|".join(categories) 166 table_name += f":Categories={categories}" 167 168 vtable = defaultdict(set) 169 for pos, values in table.domains("position").cells("acro").items(): 170 if pos.isnumeric() and (name := values.match_value("acro")[0].text(**name_replace)): 171 vtable[int(pos)].update(html.listify(name)) 172 vtables[table_name] = dict(vtable) 173 174 return vtables 175 176 @cache 177 def peripheral_maps(self, chapter, assert_table=True): 178 off_replace = {r" +": "", "0x000x00": "0x00", "to": "-", "×": "*", r"\(\d+\)": ""} 179 dom_replace = {r"Register +size": "Bit position"} 180 reg_replace = { 181 r" +|\.+": "", r"\(COM(\d)\)": r"_COM\1", 182 r"^[Rr]es$||0x[\da-fA-FXx]+|\(.*?\)|-": "", 183 r"(?i)reserved|resetvalue.*": "", "enabled": "_EN", "disabled": "_DIS", 184 r"(?i)Outputcomparemode": "_Output", "(?i)Inputcapturemode": "_Input", "mode": "", 185 r"^TG_FS_": "OTG_FS_", "toRTC": "RTC", "SPI2S_": "SPI_", 186 r"andTIM\d+_.*": "", r"x=[\d,]+": ""} 187 fld_replace = { 188 r"\] +\d+(th|rd|nd|st)": "]", r" +|\.+|\[.*?\]|\[?\d+:\d+\]?|\(.*?\)|-|^[\dXx]+$|%|__|:0\]": "", 189 r"Dataregister|Independentdataregister": "DATA", 190 r"Framefilterreg0.*": "FRAME_FILTER_REG", 191 r"[Rr]es(erved)?|[Rr]egular|x_x(bits)?|NotAvailable|RefertoSection\d+:Comparator": "", 192 r"Sampletimebits|Injectedchannelsequence|channelsequence|conversioninregularsequencebits": "", 193 r"conversioninsequencebits|conversionininjectedsequencebits|or|first|second|third|fourth": ""} 194 bit_replace = {r".*:": ""} 195 glo_replace = {r"[Rr]eserved": ""} 196 197 print(chapter._relpath) 198 tables = chapter.tables(r"register +map") 199 if assert_table: assert tables 200 201 peripheral_data = {} 202 for table in tables: 203 caption = table.caption() 204 if any(n in caption for n in ["DFIFO", "global", "EXTI register map section", "vs swapping option"]): 205 continue 206 heading = table._heading.text(**{r"((\d+\.)+(\d+)?).*": r"\1"}) 207 print(table, caption) 208 209 register_data = {} 210 for row in table.cell_rows(): 211 rkey = next(k for k in row.match_keys("register") if not "size" in k) 212 register = row[rkey][0].text(**reg_replace).strip() 213 if not register: continue 214 offset = row.match_value(r"off-?set|addr")[0].text(**off_replace) 215 if not offset: continue 216 field_data = {} 217 for bits in row.match_keys(r"^[\d-]+$|.*?:[\d-]+$"): 218 field = row[bits][0].text(**fld_replace).strip() 219 if not field: continue 220 bits = sorted(html.listify(html.replace(bits, **bit_replace), r"-")) 221 if len(bits) == 2: bits = range(int(bits[0]), int(bits[1])) 222 for bit in bits: 223 bit = int(bit) 224 field_data[bit] = field 225 # print(f"{offset:>10} {register:60} {bit:>2} {field}") 226 register_data[register] = (offset, field_data) 227 assert register_data 228 peripheral_data[caption] = (heading, register_data) 229 230 if peripheral_data and all("HRTIM" in ca for ca in peripheral_data): 231 caption, heading = next((c,p) for c, (p, _) in peripheral_data.items()) 232 all_registers = {k:v for (_, values) in peripheral_data.values() for k,v in values.items()} 233 peripheral_data = {caption: (heading, all_registers)} 234 235 instance_offsets = {} 236 if tables := chapter.tables(r"ADC +global +register +map"): 237 for row in tables[0].cell_rows(): 238 if ifilter := row.match_value("register")[0].text(**glo_replace): 239 offset = int(row.match_value("offset")[0].text().split("-")[0], 16) 240 for instance in re.findall(r"ADC(\d+)", ifilter): 241 instance_offsets[f"ADC[{instance}]"] = offset 242 243 return peripheral_data, instance_offsets 244 245 @cached_property 246 def peripherals(self): 247 per_replace = { 248 r" +": "", r".*?\(([A-Z]+|DMA2D)\).*?": r"\1", 249 r"Reserved|Port|Power|Registers|Reset|\(.*?\)|_REG": "", 250 r"(\d)/I2S\d": r"\1", r"/I2S|CANMessageRAM|Cortex-M4|I2S\dext|^GPV$": "", 251 r"Ethernet": "ETH", r"Flash": "FLASH", r"(?i).*ETHERNET.*": "ETH", 252 r"(?i)Firewall": "FW", r"HDMI-|": "", "SPDIF-RX": "SPDIFRX", 253 r"SPI2S2": "SPI2", "Tamper": "TAMP", "TT-FDCAN": "FDCAN", 254 r"USBOTG([FH])S": r"USB_OTG_\1S", "LCD-TFT": "LTDC", "DSIHOST": "DSI", 255 "TIMER": "TIM", r"^VREF$": "VREFBUF", "DelayBlock": "DLYB", 256 "I/O": "M", "I/O": "", "DAC1/2": "DAC12", 257 r"[a-z]": ""} 258 adr_replace = {r" |\(\d\)": ""} 259 sct_replace = {r"-": ""} 260 hdr_replace = {r"Peripheral +register +map": "map"} 261 bus_replace = {r".*(A\wB\d).*": r"\1", "-": ""} 262 263 # RM0431 has a bug where the peripheral table is in chapter 1 264 if "RM0431" in self.name: 265 chapters = self.chapters(r"chapter 1 ") 266 else: 267 chapters = self.chapters(r"memory +and +bus|memory +overview") 268 assert chapters 269 print(chapters[0]._relpath) 270 271 tables = chapters[0].tables(r"register +boundary") 272 assert tables 273 274 peripherals = defaultdict(list) 275 for table in tables: 276 print(table.caption()) 277 for row in table.cell_rows(**hdr_replace): 278 regmaps = row.match_value("map") 279 if regmaps: 280 regmap = regmaps[0].text(**sct_replace).strip() 281 sections = re.findall(r"Section +(\d+\.\d+(\.\d+)?)", regmap) 282 if not sections: continue 283 sections = [s[0] for s in sections] 284 else: 285 sections = [] 286 names = html.listify(row.match_value("peri")[0].text(**per_replace), r"[-\&\+/,]") 287 if not names: continue 288 address = row.match_value("address")[0].text(**adr_replace) 289 address_min = int(address.split("-")[0], 16) 290 address_max = int(address.split("-")[1], 16) 291 bus = row.match_value("bus")[0].text(**bus_replace).strip() 292 peripherals[table.caption()].append( (names, address_min, address_max, bus, sections) ) 293 print(f"{','.join(names):20} @[{hex(address_min)}, {hex(address_max)}] {bus:4} -> {', '.join(sections)}") 294 295 return peripherals
12class ReferenceManual(html.Document): 13 def __init__(self, path: str): 14 super().__init__(path) 15 16 def __repr__(self) -> str: 17 return f"RM({self.fullname})" 18 19 @cached_property 20 def devices(self) -> list[str]: 21 # Find all occurrences of STM32* strings in the first chapter 22 chapter = self.chapter("chapter 0") 23 for heading, texts in chapter.heading_texts(): 24 all_text = "" 25 for text in texts: 26 all_text += text.text(br=" ") 27 # print(heading, all_text) 28 # Must also match "STM32L4+" !!! 29 rdevices = re.findall(r"STM32[\w/\+]+", all_text) 30 if rdevices: 31 break 32 33 # Split combo strings into individual devices 34 devices = [] 35 for device in list(set(rdevices)): 36 if len(parts := device.split("/")) >= 2: 37 base = parts[0] 38 devices.append(base) 39 base = base[:-len(parts[1])] 40 for part in parts[1:]: 41 devices.append(base + part) 42 else: 43 devices.append(device) 44 45 # Remove non-specific mentions: shortest subset 46 return list(sorted(set(devices))) 47 48 @property 49 def device_filters(self) -> list[str]: 50 # Match STM32L4+ with STM32[SRQP], since STM32L4Axx is STM32L4 (without +) 51 return [d.replace("x", ".").replace("+", r"[SRQP]").lower() for d in self.devices] 52 53 def filter_devices(self, devices): 54 dids = set() 55 for did in devices: 56 for device in self.device_filters: 57 if re.match(device, did.string): 58 dids.add(did) 59 break 60 return list(dids) 61 62 @cached_property 63 def flash_latencies(self): 64 # FLASH peripheral is not described in the reference manual because... reasons? 65 if "STM32F100xx" in self.devices: 66 return {"": {1800: [24]}} 67 if any(d.startswith("STM32F1") for d in self.devices): 68 return {"": {1800: [24, 48, 72]}} 69 # Tables too complicated to parse 70 if any(d.startswith("STM32L1") for d in self.devices): 71 # See Table 13 and Table 26 72 return { 73 "stm32l1...[68b]": { # Cat 1 74 1800: [16, 32], 75 1500: [8, 16], 76 1200: [2.1, 4.2] 77 }, 78 "": { # Cat 2,3,4,5,6 79 1800: [16, 32], 80 1500: [8, 16], 81 1200: [4.2, 8] 82 } 83 } 84 85 if (any(d.startswith("STM32F0") for d in self.devices) or 86 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, 123 "1n": 1200, "1": 1200, 124 "2": 1000}[vrange.group(1)] 125 max_frequency = row[vkey][0].text( 126 **{r"-": "", r".*?CLK.*?(\d+).*": r"\1", 127 r".*?;(\d+) *MHz.*": r"\1", r".*?(\d+).*": r"\1"}) 128 if max_frequency: 129 table_data[min_voltage].append(float(max_frequency)) 130 dfilter = device_filter_from(table.caption()) 131 assert table_data 132 ws_tables[dfilter] = {v: list(sorted(set(f))) for v, f in table_data.items()} 133 134 # print(ws_tables) 135 assert ws_tables 136 return ws_tables 137 138 139 140 @cached_property 141 def vector_tables(self): 142 name_replace = {"p": r"\1,", r" +": "", r"\(.*?\)|_IRQn|_$|^-$|[Rr]eserved": "", 143 r"\+|and": ",", r"_(\w+)(LSE|BLE|I2C\d)_": r"_\1,\2_", 144 r"EXTI\[(\d+):(\d+)\]": r"EXTI\1_\2", r"\[(\d+):(\d+)\]": r"\2_\1"} 145 capt_replace = {"br": " ", r" +": " ", r"\((Cat\.\d.*?devices)\)": r"\1", r"\(.*?\)": "", 146 r"for +connectivity +line +devices": "for STM32F105/7", 147 r"for +XL\-density +devices": "for STM32F10xxF/G", 148 r"for +other +STM32F10xxx +devices": "for the rest",} 149 150 chapter = next(c for c in self.chapters(r"nvic|interrupt") if "exti" not in c.name) 151 tables = chapter.tables(r"vector +table|list +of +vector|NVIC|CPU") 152 assert tables 153 154 vtables = {} 155 for table in tables: 156 caption = table.caption(**capt_replace) 157 table_name = "VectorTable" 158 if len(tables) > 1: 159 # Create the right table filter 160 if (core := re.search(r"CPU(\d)|NVIC(\d)", caption)) is not None: 161 table_name += f":Core={core.group(1)}" # Multi-core device 162 elif devices := device_filter_from(caption): 163 table_name += f":Device={devices}" 164 elif categories := re.findall(r"Cat\.(\d)", caption): 165 # Size category filter 166 categories = "|".join(categories) 167 table_name += f":Categories={categories}" 168 169 vtable = defaultdict(set) 170 for pos, values in table.domains("position").cells("acro").items(): 171 if pos.isnumeric() and (name := values.match_value("acro")[0].text(**name_replace)): 172 vtable[int(pos)].update(html.listify(name)) 173 vtables[table_name] = dict(vtable) 174 175 return vtables 176 177 @cache 178 def peripheral_maps(self, chapter, assert_table=True): 179 off_replace = {r" +": "", "0x000x00": "0x00", "to": "-", "×": "*", r"\(\d+\)": ""} 180 dom_replace = {r"Register +size": "Bit position"} 181 reg_replace = { 182 r" +|\.+": "", r"\(COM(\d)\)": r"_COM\1", 183 r"^[Rr]es$||0x[\da-fA-FXx]+|\(.*?\)|-": "", 184 r"(?i)reserved|resetvalue.*": "", "enabled": "_EN", "disabled": "_DIS", 185 r"(?i)Outputcomparemode": "_Output", "(?i)Inputcapturemode": "_Input", "mode": "", 186 r"^TG_FS_": "OTG_FS_", "toRTC": "RTC", "SPI2S_": "SPI_", 187 r"andTIM\d+_.*": "", r"x=[\d,]+": ""} 188 fld_replace = { 189 r"\] +\d+(th|rd|nd|st)": "]", r" +|\.+|\[.*?\]|\[?\d+:\d+\]?|\(.*?\)|-|^[\dXx]+$|%|__|:0\]": "", 190 r"Dataregister|Independentdataregister": "DATA", 191 r"Framefilterreg0.*": "FRAME_FILTER_REG", 192 r"[Rr]es(erved)?|[Rr]egular|x_x(bits)?|NotAvailable|RefertoSection\d+:Comparator": "", 193 r"Sampletimebits|Injectedchannelsequence|channelsequence|conversioninregularsequencebits": "", 194 r"conversioninsequencebits|conversionininjectedsequencebits|or|first|second|third|fourth": ""} 195 bit_replace = {r".*:": ""} 196 glo_replace = {r"[Rr]eserved": ""} 197 198 print(chapter._relpath) 199 tables = chapter.tables(r"register +map") 200 if assert_table: assert tables 201 202 peripheral_data = {} 203 for table in tables: 204 caption = table.caption() 205 if any(n in caption for n in ["DFIFO", "global", "EXTI register map section", "vs swapping option"]): 206 continue 207 heading = table._heading.text(**{r"((\d+\.)+(\d+)?).*": r"\1"}) 208 print(table, caption) 209 210 register_data = {} 211 for row in table.cell_rows(): 212 rkey = next(k for k in row.match_keys("register") if not "size" in k) 213 register = row[rkey][0].text(**reg_replace).strip() 214 if not register: continue 215 offset = row.match_value(r"off-?set|addr")[0].text(**off_replace) 216 if not offset: continue 217 field_data = {} 218 for bits in row.match_keys(r"^[\d-]+$|.*?:[\d-]+$"): 219 field = row[bits][0].text(**fld_replace).strip() 220 if not field: continue 221 bits = sorted(html.listify(html.replace(bits, **bit_replace), r"-")) 222 if len(bits) == 2: bits = range(int(bits[0]), int(bits[1])) 223 for bit in bits: 224 bit = int(bit) 225 field_data[bit] = field 226 # print(f"{offset:>10} {register:60} {bit:>2} {field}") 227 register_data[register] = (offset, field_data) 228 assert register_data 229 peripheral_data[caption] = (heading, register_data) 230 231 if peripheral_data and all("HRTIM" in ca for ca in peripheral_data): 232 caption, heading = next((c,p) for c, (p, _) in peripheral_data.items()) 233 all_registers = {k:v for (_, values) in peripheral_data.values() for k,v in values.items()} 234 peripheral_data = {caption: (heading, all_registers)} 235 236 instance_offsets = {} 237 if tables := chapter.tables(r"ADC +global +register +map"): 238 for row in tables[0].cell_rows(): 239 if ifilter := row.match_value("register")[0].text(**glo_replace): 240 offset = int(row.match_value("offset")[0].text().split("-")[0], 16) 241 for instance in re.findall(r"ADC(\d+)", ifilter): 242 instance_offsets[f"ADC[{instance}]"] = offset 243 244 return peripheral_data, instance_offsets 245 246 @cached_property 247 def peripherals(self): 248 per_replace = { 249 r" +": "", r".*?\(([A-Z]+|DMA2D)\).*?": r"\1", 250 r"Reserved|Port|Power|Registers|Reset|\(.*?\)|_REG": "", 251 r"(\d)/I2S\d": r"\1", r"/I2S|CANMessageRAM|Cortex-M4|I2S\dext|^GPV$": "", 252 r"Ethernet": "ETH", r"Flash": "FLASH", r"(?i).*ETHERNET.*": "ETH", 253 r"(?i)Firewall": "FW", r"HDMI-|": "", "SPDIF-RX": "SPDIFRX", 254 r"SPI2S2": "SPI2", "Tamper": "TAMP", "TT-FDCAN": "FDCAN", 255 r"USBOTG([FH])S": r"USB_OTG_\1S", "LCD-TFT": "LTDC", "DSIHOST": "DSI", 256 "TIMER": "TIM", r"^VREF$": "VREFBUF", "DelayBlock": "DLYB", 257 "I/O": "M", "I/O": "", "DAC1/2": "DAC12", 258 r"[a-z]": ""} 259 adr_replace = {r" |\(\d\)": ""} 260 sct_replace = {r"-": ""} 261 hdr_replace = {r"Peripheral +register +map": "map"} 262 bus_replace = {r".*(A\wB\d).*": r"\1", "-": ""} 263 264 # RM0431 has a bug where the peripheral table is in chapter 1 265 if "RM0431" in self.name: 266 chapters = self.chapters(r"chapter 1 ") 267 else: 268 chapters = self.chapters(r"memory +and +bus|memory +overview") 269 assert chapters 270 print(chapters[0]._relpath) 271 272 tables = chapters[0].tables(r"register +boundary") 273 assert tables 274 275 peripherals = defaultdict(list) 276 for table in tables: 277 print(table.caption()) 278 for row in table.cell_rows(**hdr_replace): 279 regmaps = row.match_value("map") 280 if regmaps: 281 regmap = regmaps[0].text(**sct_replace).strip() 282 sections = re.findall(r"Section +(\d+\.\d+(\.\d+)?)", regmap) 283 if not sections: continue 284 sections = [s[0] for s in sections] 285 else: 286 sections = [] 287 names = html.listify(row.match_value("peri")[0].text(**per_replace), r"[-\&\+/,]") 288 if not names: continue 289 address = row.match_value("address")[0].text(**adr_replace) 290 address_min = int(address.split("-")[0], 16) 291 address_max = int(address.split("-")[1], 16) 292 bus = row.match_value("bus")[0].text(**bus_replace).strip() 293 peripherals[table.caption()].append( (names, address_min, address_max, bus, sections) ) 294 print(f"{','.join(names):20} @[{hex(address_min)}, {hex(address_max)}] {bus:4} -> {', '.join(sections)}") 295 296 return peripherals
devices: list[str]
19 @cached_property 20 def devices(self) -> list[str]: 21 # Find all occurrences of STM32* strings in the first chapter 22 chapter = self.chapter("chapter 0") 23 for heading, texts in chapter.heading_texts(): 24 all_text = "" 25 for text in texts: 26 all_text += text.text(br=" ") 27 # print(heading, all_text) 28 # Must also match "STM32L4+" !!! 29 rdevices = re.findall(r"STM32[\w/\+]+", all_text) 30 if rdevices: 31 break 32 33 # Split combo strings into individual devices 34 devices = [] 35 for device in list(set(rdevices)): 36 if len(parts := device.split("/")) >= 2: 37 base = parts[0] 38 devices.append(base) 39 base = base[:-len(parts[1])] 40 for part in parts[1:]: 41 devices.append(base + part) 42 else: 43 devices.append(device) 44 45 # Remove non-specific mentions: shortest subset 46 return list(sorted(set(devices)))
flash_latencies
62 @cached_property 63 def flash_latencies(self): 64 # FLASH peripheral is not described in the reference manual because... reasons? 65 if "STM32F100xx" in self.devices: 66 return {"": {1800: [24]}} 67 if any(d.startswith("STM32F1") for d in self.devices): 68 return {"": {1800: [24, 48, 72]}} 69 # Tables too complicated to parse 70 if any(d.startswith("STM32L1") for d in self.devices): 71 # See Table 13 and Table 26 72 return { 73 "stm32l1...[68b]": { # Cat 1 74 1800: [16, 32], 75 1500: [8, 16], 76 1200: [2.1, 4.2] 77 }, 78 "": { # Cat 2,3,4,5,6 79 1800: [16, 32], 80 1500: [8, 16], 81 1200: [4.2, 8] 82 } 83 } 84 85 if (any(d.startswith("STM32F0") for d in self.devices) or 86 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, 123 "1n": 1200, "1": 1200, 124 "2": 1000}[vrange.group(1)] 125 max_frequency = row[vkey][0].text( 126 **{r"-": "", r".*?CLK.*?(\d+).*": r"\1", 127 r".*?;(\d+) *MHz.*": r"\1", r".*?(\d+).*": r"\1"}) 128 if max_frequency: 129 table_data[min_voltage].append(float(max_frequency)) 130 dfilter = device_filter_from(table.caption()) 131 assert table_data 132 ws_tables[dfilter] = {v: list(sorted(set(f))) for v, f in table_data.items()} 133 134 # print(ws_tables) 135 assert ws_tables 136 return ws_tables
vector_tables
140 @cached_property 141 def vector_tables(self): 142 name_replace = {"p": r"\1,", r" +": "", r"\(.*?\)|_IRQn|_$|^-$|[Rr]eserved": "", 143 r"\+|and": ",", r"_(\w+)(LSE|BLE|I2C\d)_": r"_\1,\2_", 144 r"EXTI\[(\d+):(\d+)\]": r"EXTI\1_\2", r"\[(\d+):(\d+)\]": r"\2_\1"} 145 capt_replace = {"br": " ", r" +": " ", r"\((Cat\.\d.*?devices)\)": r"\1", r"\(.*?\)": "", 146 r"for +connectivity +line +devices": "for STM32F105/7", 147 r"for +XL\-density +devices": "for STM32F10xxF/G", 148 r"for +other +STM32F10xxx +devices": "for the rest",} 149 150 chapter = next(c for c in self.chapters(r"nvic|interrupt") if "exti" not in c.name) 151 tables = chapter.tables(r"vector +table|list +of +vector|NVIC|CPU") 152 assert tables 153 154 vtables = {} 155 for table in tables: 156 caption = table.caption(**capt_replace) 157 table_name = "VectorTable" 158 if len(tables) > 1: 159 # Create the right table filter 160 if (core := re.search(r"CPU(\d)|NVIC(\d)", caption)) is not None: 161 table_name += f":Core={core.group(1)}" # Multi-core device 162 elif devices := device_filter_from(caption): 163 table_name += f":Device={devices}" 164 elif categories := re.findall(r"Cat\.(\d)", caption): 165 # Size category filter 166 categories = "|".join(categories) 167 table_name += f":Categories={categories}" 168 169 vtable = defaultdict(set) 170 for pos, values in table.domains("position").cells("acro").items(): 171 if pos.isnumeric() and (name := values.match_value("acro")[0].text(**name_replace)): 172 vtable[int(pos)].update(html.listify(name)) 173 vtables[table_name] = dict(vtable) 174 175 return vtables
@cache
def
peripheral_maps(self, chapter, assert_table=True):
177 @cache 178 def peripheral_maps(self, chapter, assert_table=True): 179 off_replace = {r" +": "", "0x000x00": "0x00", "to": "-", "×": "*", r"\(\d+\)": ""} 180 dom_replace = {r"Register +size": "Bit position"} 181 reg_replace = { 182 r" +|\.+": "", r"\(COM(\d)\)": r"_COM\1", 183 r"^[Rr]es$||0x[\da-fA-FXx]+|\(.*?\)|-": "", 184 r"(?i)reserved|resetvalue.*": "", "enabled": "_EN", "disabled": "_DIS", 185 r"(?i)Outputcomparemode": "_Output", "(?i)Inputcapturemode": "_Input", "mode": "", 186 r"^TG_FS_": "OTG_FS_", "toRTC": "RTC", "SPI2S_": "SPI_", 187 r"andTIM\d+_.*": "", r"x=[\d,]+": ""} 188 fld_replace = { 189 r"\] +\d+(th|rd|nd|st)": "]", r" +|\.+|\[.*?\]|\[?\d+:\d+\]?|\(.*?\)|-|^[\dXx]+$|%|__|:0\]": "", 190 r"Dataregister|Independentdataregister": "DATA", 191 r"Framefilterreg0.*": "FRAME_FILTER_REG", 192 r"[Rr]es(erved)?|[Rr]egular|x_x(bits)?|NotAvailable|RefertoSection\d+:Comparator": "", 193 r"Sampletimebits|Injectedchannelsequence|channelsequence|conversioninregularsequencebits": "", 194 r"conversioninsequencebits|conversionininjectedsequencebits|or|first|second|third|fourth": ""} 195 bit_replace = {r".*:": ""} 196 glo_replace = {r"[Rr]eserved": ""} 197 198 print(chapter._relpath) 199 tables = chapter.tables(r"register +map") 200 if assert_table: assert tables 201 202 peripheral_data = {} 203 for table in tables: 204 caption = table.caption() 205 if any(n in caption for n in ["DFIFO", "global", "EXTI register map section", "vs swapping option"]): 206 continue 207 heading = table._heading.text(**{r"((\d+\.)+(\d+)?).*": r"\1"}) 208 print(table, caption) 209 210 register_data = {} 211 for row in table.cell_rows(): 212 rkey = next(k for k in row.match_keys("register") if not "size" in k) 213 register = row[rkey][0].text(**reg_replace).strip() 214 if not register: continue 215 offset = row.match_value(r"off-?set|addr")[0].text(**off_replace) 216 if not offset: continue 217 field_data = {} 218 for bits in row.match_keys(r"^[\d-]+$|.*?:[\d-]+$"): 219 field = row[bits][0].text(**fld_replace).strip() 220 if not field: continue 221 bits = sorted(html.listify(html.replace(bits, **bit_replace), r"-")) 222 if len(bits) == 2: bits = range(int(bits[0]), int(bits[1])) 223 for bit in bits: 224 bit = int(bit) 225 field_data[bit] = field 226 # print(f"{offset:>10} {register:60} {bit:>2} {field}") 227 register_data[register] = (offset, field_data) 228 assert register_data 229 peripheral_data[caption] = (heading, register_data) 230 231 if peripheral_data and all("HRTIM" in ca for ca in peripheral_data): 232 caption, heading = next((c,p) for c, (p, _) in peripheral_data.items()) 233 all_registers = {k:v for (_, values) in peripheral_data.values() for k,v in values.items()} 234 peripheral_data = {caption: (heading, all_registers)} 235 236 instance_offsets = {} 237 if tables := chapter.tables(r"ADC +global +register +map"): 238 for row in tables[0].cell_rows(): 239 if ifilter := row.match_value("register")[0].text(**glo_replace): 240 offset = int(row.match_value("offset")[0].text().split("-")[0], 16) 241 for instance in re.findall(r"ADC(\d+)", ifilter): 242 instance_offsets[f"ADC[{instance}]"] = offset 243 244 return peripheral_data, instance_offsets
peripherals
246 @cached_property 247 def peripherals(self): 248 per_replace = { 249 r" +": "", r".*?\(([A-Z]+|DMA2D)\).*?": r"\1", 250 r"Reserved|Port|Power|Registers|Reset|\(.*?\)|_REG": "", 251 r"(\d)/I2S\d": r"\1", r"/I2S|CANMessageRAM|Cortex-M4|I2S\dext|^GPV$": "", 252 r"Ethernet": "ETH", r"Flash": "FLASH", r"(?i).*ETHERNET.*": "ETH", 253 r"(?i)Firewall": "FW", r"HDMI-|": "", "SPDIF-RX": "SPDIFRX", 254 r"SPI2S2": "SPI2", "Tamper": "TAMP", "TT-FDCAN": "FDCAN", 255 r"USBOTG([FH])S": r"USB_OTG_\1S", "LCD-TFT": "LTDC", "DSIHOST": "DSI", 256 "TIMER": "TIM", r"^VREF$": "VREFBUF", "DelayBlock": "DLYB", 257 "I/O": "M", "I/O": "", "DAC1/2": "DAC12", 258 r"[a-z]": ""} 259 adr_replace = {r" |\(\d\)": ""} 260 sct_replace = {r"-": ""} 261 hdr_replace = {r"Peripheral +register +map": "map"} 262 bus_replace = {r".*(A\wB\d).*": r"\1", "-": ""} 263 264 # RM0431 has a bug where the peripheral table is in chapter 1 265 if "RM0431" in self.name: 266 chapters = self.chapters(r"chapter 1 ") 267 else: 268 chapters = self.chapters(r"memory +and +bus|memory +overview") 269 assert chapters 270 print(chapters[0]._relpath) 271 272 tables = chapters[0].tables(r"register +boundary") 273 assert tables 274 275 peripherals = defaultdict(list) 276 for table in tables: 277 print(table.caption()) 278 for row in table.cell_rows(**hdr_replace): 279 regmaps = row.match_value("map") 280 if regmaps: 281 regmap = regmaps[0].text(**sct_replace).strip() 282 sections = re.findall(r"Section +(\d+\.\d+(\.\d+)?)", regmap) 283 if not sections: continue 284 sections = [s[0] for s in sections] 285 else: 286 sections = [] 287 names = html.listify(row.match_value("peri")[0].text(**per_replace), r"[-\&\+/,]") 288 if not names: continue 289 address = row.match_value("address")[0].text(**adr_replace) 290 address_min = int(address.split("-")[0], 16) 291 address_max = int(address.split("-")[1], 16) 292 bus = row.match_value("bus")[0].text(**bus_replace).strip() 293 peripherals[table.caption()].append( (names, address_min, address_max, bus, sections) ) 294 print(f"{','.join(names):20} @[{hex(address_min)}, {hex(address_max)}] {bus:4} -> {', '.join(sections)}") 295 296 return peripherals