├── MCU ├── seracc_bsp.h ├── seracc.h ├── seracc_sthal.c ├── seracc_mspm0.c └── seracc.c ├── README.md └── jupyter ├── example.ipynb └── seracc.py /MCU/seracc_bsp.h: -------------------------------------------------------------------------------- 1 | // version 4.1 - updated 2025/10/19 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | #include 9 | 10 | void seracc_init_bsp(); 11 | 12 | void seracc_transmit_bsp(const uint8_t* data, size_t size); 13 | 14 | uint16_t seracc_crc_bsp(const uint8_t* data, size_t size); 15 | 16 | size_t seracc_dma_remain_bsp(); 17 | void seracc_dma_start_bsp(uint8_t* buf, size_t size); 18 | void seracc_dma_stop_bsp(); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /MCU/seracc.h: -------------------------------------------------------------------------------- 1 | // version 4.1 - updated 2025/10/19 2 | 3 | #ifndef INC_SERACC_H_ 4 | #define INC_SERACC_H_ 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #include 11 | #include 12 | 13 | void seracc_init(); 14 | 15 | void seracc_transmit(const uint8_t* data, size_t size); 16 | 17 | typedef void (*SerAccHandlerType)(uint8_t*, size_t); 18 | 19 | void seracc_register_handler(const char*, SerAccHandlerType); 20 | 21 | void seracc_idle_handler(); 22 | 23 | #ifdef __cplusplus 24 | } 25 | #endif 26 | 27 | #endif /* INC_SERACC_H_ */ 28 | -------------------------------------------------------------------------------- /MCU/seracc_sthal.c: -------------------------------------------------------------------------------- 1 | // version 4.1 - updated 2025/10/19 2 | 3 | #include "main.h" 4 | 5 | #include "seracc_bsp.h" 6 | 7 | #ifndef SERACC_HUART 8 | #error "You should #define SERACC_HUART huartX in main.h" 9 | #endif 10 | #ifndef SERACC_HDMA 11 | #error "You should #define SERACC_HDMA hdma_uartX_rx in main.h" 12 | #endif 13 | 14 | extern UART_HandleTypeDef SERACC_HUART; 15 | extern DMA_HandleTypeDef SERACC_HDMA; 16 | extern CRC_HandleTypeDef hcrc; 17 | 18 | void seracc_init_bsp() 19 | { 20 | __HAL_DMA_DISABLE_IT(&SERACC_HDMA, DMA_IT_HT); 21 | } 22 | 23 | void seracc_transmit_bsp(const uint8_t* data, size_t size) 24 | { 25 | HAL_UART_Transmit(&SERACC_HUART, data, size, size+1); 26 | } 27 | 28 | uint16_t seracc_crc_bsp(const uint8_t* data, size_t size) 29 | { 30 | return HAL_CRC_Calculate(&hcrc, (uint32_t*)data, size); 31 | } 32 | 33 | size_t seracc_dma_remain_bsp() 34 | { 35 | __HAL_UART_CLEAR_IDLEFLAG(&SERACC_HUART); 36 | return __HAL_DMA_GET_COUNTER(&SERACC_HDMA); 37 | } 38 | 39 | void seracc_dma_start_bsp(uint8_t* buf, size_t size) 40 | { 41 | __HAL_UART_DISABLE_IT(&SERACC_HUART, UART_IT_IDLE); 42 | HAL_UART_Receive_DMA(&SERACC_HUART, buf, size); 43 | __HAL_UART_CLEAR_IDLEFLAG(&SERACC_HUART); 44 | __HAL_UART_ENABLE_IT(&SERACC_HUART, UART_IT_IDLE); 45 | } 46 | 47 | void seracc_dma_stop_bsp() 48 | { 49 | HAL_UART_DMAStop(&SERACC_HUART); 50 | } 51 | 52 | -------------------------------------------------------------------------------- /MCU/seracc_mspm0.c: -------------------------------------------------------------------------------- 1 | // version 4.0 - updated 2025/6/19 2 | 3 | #include "ti_msp_dl_config.h" 4 | 5 | #include "seracc.h" 6 | #include "seracc_bsp.h" 7 | 8 | #ifndef UART_SERACC_INST 9 | #error "You should name the UART instance UART_SERACC or manually #define UART_SERACC_INST in ti_msp_dl_config.h" 10 | #endif 11 | #ifndef DMA_UART_RX_CHAN_ID 12 | #error "You should name the DMA channel DMA_UART_RX or manually #define DMA_UART_RX_CHAN_ID in ti_msp_dl_config.h" 13 | #endif 14 | 15 | extern CRC_Regs* const CRC; 16 | extern DMA_Regs* const DMA; 17 | 18 | void seracc_init_bsp() 19 | { 20 | ; 21 | } 22 | 23 | void seracc_transmit_bsp(const uint8_t* data, size_t size) 24 | { 25 | for (int i = 0; i != size; ++i) 26 | DL_UART_Main_transmitDataBlocking(UART_SERACC_INST, data[i]); 27 | } 28 | 29 | uint16_t seracc_crc_bsp(const uint8_t* data, size_t size) 30 | { 31 | uint32_t i; 32 | 33 | DL_CRC_setSeed16(CRC, 0x0000); 34 | 35 | for (i = 0; i < size; i++) { 36 | DL_CRC_feedData8(CRC, data[i]); 37 | } 38 | 39 | return (DL_CRC_getResult16(CRC)); 40 | } 41 | 42 | size_t seracc_dma_remain_bsp() 43 | { 44 | return DL_DMA_getTransferSize(DMA, DMA_UART_RX_CHAN_ID); 45 | } 46 | 47 | void seracc_dma_start_bsp(uint8_t* buf, size_t size) 48 | { 49 | NVIC_DisableIRQ(UART_SERACC_INST_INT_IRQN); 50 | DL_DMA_setSrcAddr(DMA, DMA_UART_RX_CHAN_ID, (uint32_t)&(UART_SERACC_INST->RXDATA)); 51 | DL_DMA_setDestAddr(DMA, DMA_UART_RX_CHAN_ID, (uint32_t)&buf[0]); 52 | DL_DMA_setTransferSize(DMA, DMA_UART_RX_CHAN_ID, size); 53 | DL_DMA_enableChannel(DMA, DMA_UART_RX_CHAN_ID); 54 | NVIC_EnableIRQ(UART_SERACC_INST_INT_IRQN); 55 | } 56 | 57 | void seracc_dma_stop_bsp() 58 | { 59 | DL_DMA_disableChannel(DMA, DMA_UART_RX_CHAN_ID); 60 | } 61 | 62 | void UART_SERACC_INST_IRQHandler(void) 63 | { 64 | switch (DL_UART_Main_getPendingInterrupt(UART_SERACC_INST)) 65 | { 66 | case DL_UART_MAIN_IIDX_RX_TIMEOUT_ERROR: 67 | // trigger RX DMA 68 | UART_SERACC_INST->DMA_TRIG_RX.ISET = UART_DMA_TRIG_RX_ISET_RXINT_SET; 69 | // wait for RX FIFO empty 70 | while (!DL_UART_Main_isRXFIFOEmpty(UART_SERACC_INST)) 71 | ; 72 | // no break 73 | 74 | case DL_UART_MAIN_IIDX_RX: 75 | seracc_idle_handler(); 76 | break; 77 | 78 | default: 79 | break; 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /MCU/seracc.c: -------------------------------------------------------------------------------- 1 | // version 4.1 - updated 2025/10/19 2 | 3 | #include "seracc.h" 4 | #include "seracc_bsp.h" 5 | 6 | #include 7 | 8 | // all little-endian 9 | static inline uint16_t access16(const uint8_t* ptr) 10 | { 11 | #if defined(__ARM_ARCH) && (__ARM_ARCH >= 7) 12 | return *(uint16_t*)ptr; 13 | #else 14 | return *(ptr+1) << 8 | *ptr; 15 | #endif 16 | } 17 | 18 | static inline uint32_t access32(const uint8_t* ptr) 19 | { 20 | #if defined(__ARM_ARCH) && (__ARM_ARCH >= 7) 21 | return *(uint32_t*)ptr; 22 | #else 23 | return *(ptr+3) << 24 | *(ptr+2) << 16 | *(ptr+1) << 8 | *ptr; 24 | #endif 25 | } 26 | 27 | static void receive_start(); 28 | static void receive_stop(); 29 | static void reg_handler(uint8_t* data, size_t size); 30 | 31 | static uint8_t rx_buf[1040]; 32 | static uint8_t* handle_ptr = rx_buf; 33 | static int rx_error = 0; 34 | static int tx_synced = 0; 35 | 36 | void seracc_init() 37 | { 38 | seracc_register_handler("_", reg_handler); 39 | 40 | seracc_init_bsp(); 41 | 42 | receive_start(); 43 | } 44 | 45 | void seracc_transmit(const uint8_t* data, size_t size) 46 | { 47 | receive_stop(); 48 | receive_start(); 49 | tx_synced = 1; 50 | 51 | uint16_t tx_buf = size; // little-endian 52 | seracc_transmit_bsp((uint8_t*)&tx_buf, 2); 53 | uint16_t crc = seracc_crc_bsp(data, size); 54 | seracc_transmit_bsp(data, size); 55 | tx_buf = crc; 56 | seracc_transmit_bsp((uint8_t*)&tx_buf, 2); 57 | } 58 | 59 | static void seracc_sync() 60 | { 61 | receive_stop(); 62 | receive_start(); 63 | uint8_t tx_buf[2] = {'O', 'K'}; 64 | seracc_transmit_bsp(tx_buf, 2); 65 | } 66 | 67 | static int handler_count = 0; 68 | static char keys[16][8]; 69 | static void (*handlers[16])(uint8_t*, size_t); 70 | 71 | void seracc_register_handler(const char* cmd, SerAccHandlerType cb) 72 | { 73 | if (handler_count >= 16) 74 | return; 75 | if (strlen(cmd) > 7) 76 | return; 77 | strcpy(keys[handler_count], cmd); 78 | handlers[handler_count] = cb; 79 | handler_count += 1; 80 | } 81 | 82 | static SerAccHandlerType find_handler(const char* cmd) 83 | { 84 | for (int i = 0; i != handler_count; ++i) 85 | { 86 | if (strcmp(cmd, keys[i]) == 0) 87 | { 88 | return handlers[i]; 89 | } 90 | } 91 | return 0; 92 | } 93 | 94 | #define UART_SYNC -1 95 | #define UART_TR_ERROR -2 96 | #define UART_FMT_ERROR -3 97 | // return value: 98 | // sync - -1 99 | // transmission error - -2 100 | // format error - -3 101 | // for incomplete error - -size 102 | // no error - size 103 | static int seracc_manager(uint8_t* begin, uint8_t* end) 104 | { 105 | if (end - begin < 2) 106 | return -6; // not complete 107 | 108 | size_t size = access16(begin); // little-endian 109 | 110 | if (size == 0xAA55) 111 | { 112 | seracc_sync(); 113 | return UART_SYNC; // sync 114 | } 115 | 116 | if (size < 2) 117 | return UART_TR_ERROR; // illegal size 118 | 119 | if (end - begin < size + 4) 120 | return -size - 4; // not complete 121 | 122 | int crc = begin[size+2] + (begin[size+3] << 8); 123 | int calc = seracc_crc_bsp(begin+2, size); 124 | if (calc != crc) 125 | return UART_TR_ERROR; // CRC error 126 | 127 | begin += 2; 128 | end = begin + size; 129 | uint8_t* p = begin; 130 | for (; p != end; ++p) 131 | { 132 | if (*p == ':') 133 | break; 134 | } 135 | if (p == end) 136 | return UART_FMT_ERROR; // no colon, format error 137 | 138 | char key[8]; 139 | if (p - begin > 7) 140 | return UART_FMT_ERROR; // key too long, format error 141 | 142 | memcpy(key, begin, p - begin); 143 | key[p - begin] = '\0'; 144 | 145 | SerAccHandlerType handler = find_handler(key); 146 | if (!handler) 147 | return UART_FMT_ERROR; // no handler, format error 148 | 149 | ++p; 150 | handler(p, end-p); 151 | 152 | if (tx_synced) 153 | { 154 | tx_synced = 0; 155 | return UART_SYNC; 156 | } 157 | 158 | return size + 4; 159 | } 160 | 161 | void seracc_idle_handler() 162 | { 163 | size_t dma_remain = seracc_dma_remain_bsp(); 164 | size_t size = sizeof(rx_buf) - dma_remain; 165 | uint8_t* end = rx_buf + size; 166 | 167 | if (rx_error) 168 | { 169 | if (access32(end-4) == 0xAA55AA55) 170 | seracc_sync(); 171 | return; 172 | } 173 | 174 | while (handle_ptr < end) 175 | { 176 | int res = seracc_manager(handle_ptr, end); 177 | if (res == UART_SYNC) 178 | { 179 | return; 180 | } 181 | else if (res == UART_TR_ERROR) 182 | { 183 | rx_error = 1; 184 | break; 185 | } 186 | else if (res == UART_FMT_ERROR) 187 | { 188 | handle_ptr += res; 189 | } 190 | else if (res < 0) 191 | { 192 | if (end - rx_buf >= -size) 193 | rx_error = 1; 194 | break; 195 | } 196 | else 197 | { 198 | handle_ptr += res; 199 | } 200 | } 201 | } 202 | 203 | static void receive_start() 204 | { 205 | handle_ptr = rx_buf; 206 | rx_error = 0; 207 | seracc_dma_start_bsp(rx_buf, sizeof(rx_buf)); 208 | } 209 | 210 | static void receive_stop() 211 | { 212 | seracc_dma_stop_bsp(); 213 | } 214 | 215 | typedef struct 216 | { 217 | uint32_t value; 218 | } __attribute__((packed)) uint32_unaligned; 219 | 220 | static void reg_handler(uint8_t* data, size_t size) 221 | { 222 | uint_fast8_t id = data[0] & 0b11; 223 | uint32_t addr = access32(data); 224 | addr &= ~0b11u; 225 | uint32_t result = 0; 226 | uint_fast8_t len = 0; 227 | switch (size*4+id) 228 | { 229 | case 17: // 8-bit 4-aligned read 230 | result = *(volatile uint8_t*)addr; 231 | len = 1; 232 | break; 233 | case 18: // 16-bit 4-aligned read 234 | result = *(volatile uint16_t*)addr; 235 | len = 2; 236 | break; 237 | case 16: // 32-bit 4-aligned read 238 | result = *(volatile uint32_t*)addr; 239 | len = 4; 240 | break; 241 | case 21: // 8-bit 4-aligned write 242 | *(volatile uint8_t*)addr = data[4]; 243 | break; 244 | case 24: // 16-bit 4-aligned write 245 | *(volatile uint16_t*)addr = access16(data+4); 246 | break; 247 | case 32: // 32-bit 4-aligned write 248 | *(volatile uint32_t*)addr = access32(data+4); 249 | break; 250 | case 29: 251 | case 30: 252 | case 31: 253 | addr = access32(data); 254 | if (data[6] < 0x10) // 8/16/32-bit unaligned read 255 | { 256 | len = data[6]; 257 | memcpy(&result, (void*)addr, len); 258 | } 259 | else if (data[6] == 0x11) // 8-bit unaligned write 260 | { 261 | *(volatile uint8_t*)addr = data[4]; 262 | } 263 | else if (data[6] == 0x12) // 16-bit unaligned write 264 | { 265 | if (addr & 1) 266 | memcpy((void*)addr, data+4, 2); 267 | else 268 | *(volatile uint16_t*)addr = access16(data+4); 269 | } 270 | break; 271 | case 37: // 32-bit unaligned write 272 | case 38: 273 | case 39: 274 | addr = access32(data); 275 | if (addr & 1) 276 | memcpy((void*)addr, data+4, 4); 277 | else 278 | { 279 | *(volatile uint16_t*)(addr+0) = access16(data+4); 280 | *(volatile uint16_t*)(addr+2) = access16(data+6); 281 | } 282 | break; 283 | case 20: // single bit set 284 | *(volatile uint32_t*)addr |= 1u << data[4]; 285 | break; 286 | case 22: // single bit clear 287 | *(volatile uint32_t*)addr &= ~(1u << data[4]); 288 | break; 289 | case 33: // |= 290 | *(volatile uint32_t*)addr |= access32(data+4); 291 | break; 292 | case 34: // &= 293 | *(volatile uint32_t*)addr &= access32(data+4); 294 | break; 295 | case 48: // modify masked bits 296 | { 297 | volatile uint32_t* reg = (volatile uint32_t*)addr; 298 | uint32_t mask, value; 299 | if ((uint32_t)data % 4) // not word-aligned, prevent LDRD instruction 300 | { 301 | mask = ((uint32_unaligned*)(data+4))->value; 302 | value = ((uint32_unaligned*)(data+8))->value; 303 | } 304 | else 305 | { 306 | mask = access32(data+4); 307 | value = access32(data+8); 308 | } 309 | *reg = (*reg & ~mask) | value; 310 | break; 311 | } 312 | default: 313 | return; 314 | } 315 | 316 | if (len > 0) 317 | seracc_transmit((uint8_t*)&result, len); 318 | } 319 | 320 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Serial Accessor 2 | 3 | A framework for interactive MCU register access through UART. 4 | 5 | ## Introduction 6 | 7 | This is a framework for interactive register access. It consists of C code for MCU side and Python code for PC side. 8 | 9 | The Python code include a parser and a base library. The parser can parse the register and bit field definitions from an [SVD file](https://arm-software.github.io/CMSIS_5/SVD/html/svd_Format_pg.html) and generate Python definitions for them, resulting in a device-dependent module. Once the module is generated, you can access the registers in MCU in simple semantics, e.g., 10 | 11 | ``` Python 12 | GPIOA.ODR.ODR15 = 1 13 | TIM1.PSC = 100 - 1 14 | TIM1.CNT = TIM1.ARR.read() - 1 15 | ``` 16 | 17 | These statements further invokes low-level functions in the base library, sending commands to MCU through UART. The C code handles the protocol in MCU, executing commands, and returning the results, if any. 18 | 19 | Python programming on PC is flexible and can be interactive in Jupyter Notebook/Lab. This means you no longer need to code, build, run (these are really time-consuming) each time you make changes to MCU control logic. Instead, you can implement minimal logic, i.e., an adapter, in MCU, for once, then put all control logic on PC. For register accesses, this adapter in already contained in this framework. Register accesses can even be translated back to C for deployment. 20 | 21 | Currently this framework is only supported on STM32 with HAL and MSPM0 with DriverLib, but in principle it can work with all MCUs with UART (even without DMA). 22 | 23 | ## MCU Usage 24 | 25 | 1. Enable a U(S)ART with **1000000 bps**, 8 data bits, 1 stop bit and no parity. 26 | 27 | 2. Enable the interrupt for this UART. For MSPM0, enable both RX and RX timeout interrupt, and set the RX FIFO threshold to full. 28 | 29 | 3. Configure a DMA channel for UART RX (circular mode for better failure recovery, normal mode also okay). 30 | 31 | 4. Enable CRC: 32 | - Default Polynomial State: Disable 33 | - CRC Length: 16-bit 34 | - CRC Generating Polynomial: X12+X5+X0 35 | - Default Init Value State: Disable 36 | - Init Value for CRC Computation: 0 37 | 38 | 5. Save and generate code. 39 | 40 | 6. Add `seracc.h`, `seracc_bsp.h` and `seracc.c` to your project (it's recommended to copy these files). Add `seracc_sthal.c` for HAL-based STM32 project or `seracc_mspm0.c` for DriverLib-based MSPM0 project. For other platforms, you can implement these BSP functions yourself. 41 | 42 | 7. For STM32, in the IRQ of the UART instance in `stm32xxxx_it.c`, call `uart_idle_handler()` before the HAL handler. For TI, skip this step. 43 | 44 | 8. Call `seracc_init()` to start the framework. Before that you need to `#define` some symbols. See the error messages when you compile the project at this time. 45 | 46 | ## PC Usage 47 | 48 | 1. Install the dependencies: 49 | ``` shell 50 | pip install pyserial 51 | pip install crc 52 | ``` 53 | 54 | 2. Run `parse.ipynb` to generate the device-dependent module. Follow the instructions in the notebook. I have generated the modules for some parts. If you find the one for your part, you can skip this step. 55 | 56 | 3. Import the generated module. Here take STM32G474 as an example. 57 | ``` Python 58 | from g474 import * 59 | ``` 60 | This may take several seconds. 61 | 62 | 4. During importing, the framework will ask you which COM port to use. Look up the COM number in device manager and tell it. If you are using the UART bridge from ST-LINK/V2-1 or XDS110 and have the driver installed, the framework can automatically detect it. 63 | - If you accidentally disconnected the UART bridge, you can restart the kernel to reestablish the connection. If you don't want to restart, call `serial_init` in `seracc` to reestablish. 64 | - Enter `0` to enter evaluation mode. In this mode, all writes are omitted and all reads return 0's. You can test the functionalities and syntaxes without connecting to the MCU. 65 | 66 | 5. You can evaluate a peripheral, a register or a bit field by typing it in Jupyter Notebook/Lab: 67 | ``` 68 | In[1] : TIM1 69 | Out[1]: 70 | ``` 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 |
OffsetRegisterContent
0x00CR1DEC: 0, HEX: 0x00000000
0x04CR2DEC: 0, HEX: 0x00000000
0x0CDIERDEC: 0, HEX: 0x00000000
0x10SRDEC: 0, HEX: 0x00000000
0x14EGRDEC: 0, HEX: 0x00000000
0x24CNTDEC: 0, HEX: 0x00000000
0x28PSCDEC: 0, HEX: 0x00000000
0x2CARRDEC: 0, HEX: 0x00000000
118 | 119 | ``` 120 | In[2] : TIM1.CR1 121 | Out[2]: 122 | ``` 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 |
31302928272625242322212019181716
0000000000000000
1514131211109876543210
DITHENUIFREMAPARPEOPMURSUDISCEN
0000000000000000
214 | 215 | ``` 216 | In[3] : TIM6.CR2.MMS 217 | Out[3]: 0b000 218 | ``` 219 | Essentially, the environment calls the object's `__repr__` method, which shows a table in HTML and returns a string. Note that, however, do not write `TIM1.CNT = TIM1.ARR - 1`; if you want to directly make use of the value of a register, use `.read()`: `TIM1.CNT = TIM1.ARR.read() - 1`. 220 | Hovering the mouse on a register or bit field name shows its description. In this readme I cannot make it work. You can open the `example.ipynb` to experience this feature. 221 | 222 | 6. You can write to a register or a bit field by making an assignment: 223 | ``` Python 224 | TIM6.PSC = 15 225 | TIM6.ARR = 99 226 | ``` 227 | The assignment and `__repr__` method access the register in 32-bit width. If you want to read/write in 8- or 16-bit, use `read8()`/`read16()`/`write8()`/`write16()`. This makes sense for data registers of USART and SPI. 228 | 229 | 7. For more functionalities, refer to `example.ipynb`. 230 | 231 | ## Recording 232 | 233 | The framework can record your operations and generate C code so that you can deploy your code into MCU. Note that this feature does not record branch or loop statements within the block, nor the expressions on right hand side; only the EXACT accesses with the EXACT masks and values are recorded. 234 | 235 | Operations inside the `with logging():` block are recorded. The `logging()` function can take one parameter, which is the filename to dump the records. If left empty, it prints on the console. 236 | 237 | If you intend to fully configure a register, it's recommended to call `.reset()` before writing bit fields. This also generates more compact code. 238 | 239 | If you intend to wait for some flag in the register, it's recommended to use the `wait_until_equal` function. Wihout recording, this is equivalent to a explicit `while` loop, but regarding C code generation, the latter will be converted to several reads while the former to a `while` loop in C. 240 | 241 | The generation engine automatically combines contiguous write accesses to the same register into one access. If you intend to seperate them, you can use `barrier()` between accesses. 242 | 243 | Example: 244 | 245 | ``` Python 246 | with logging() as log: 247 | SPI1.CR1.SPE = 1 248 | SPI1.DR.write8(0x80) 249 | wait_until_equal(SPI1.SR.BSY, 0) 250 | TIM1.CR1.CEN = 0 251 | log.barrier() 252 | TIM1.CR1.DIR = 1 253 | TIM1.CR2.reset() 254 | TIM1.CR2.MMS = 0b0001 255 | TIM1.CR2.OIS1 = 1 256 | ``` 257 | 258 | Output: 259 | 260 | ``` C 261 | *(volatile uint32_t*)0x40013000 |= 1u << 6; // SPI1.CR1.SPE = 0b1 262 | *(volatile uint8_t*)0x4001300C = 128; // SPI1.DR = 128 263 | volatile uint32_t* _reg = (volatile uint32_t*)0x40013008; 264 | while ((*_reg & 0x00000080) != 0x00000000); // SPI1.SR.BSY != 0b0 265 | *(volatile uint32_t*)0x40012C00 &= ~(1u << 0); // TIM1.CR1.CEN = 0b0 266 | *(volatile uint32_t*)0x40012C00 |= 1u << 4; // TIM1.CR1.DIR = 0b1 267 | *(volatile uint32_t*)0x40012C04 = 272; // TIM1.CR2 = 0 268 | // TIM1.CR2.MMS = 0b0001 269 | // TIM1.CR2.OIS1 = 0b1 270 | ``` 271 | 272 | ## Custom Handler 273 | 274 | You can implement your own handler in addition to the register accessor based on the UART communication infrastructure provided by the framework. 275 | 276 | Some limitations: 277 | - The number of handlers, including the built-in register access protocol handler, is limit to 16. 278 | - The keys of handlers (explained below) should not exceed 7 characters. It is recommended that the key contains letters and numbers only. The key must not contain colon `:`, nor start with underline `_`, which are reserved characters. 279 | - The maximum length of a single UART command is limited to 1024. This can be changed accordingly. 280 | - The handler is invoked in UART interrupt. If your code will use `HAL_Delay`, the UART interrupt should have lower priority than SysTick. 281 | These limitations may be changed or removed in future releases. 282 | 283 | Let's consider a Jupyter-to-I2C bridge: you can construct the data packet in Jupyter and send it to an I2C slave via the MCU. 284 | 285 | 1. Write wrapper functions in Python. 286 | - Let `"I2C"` be the **key** for the handler. 287 | - Following the colon is the **content** of your protocol. The first byte of the content is either `W` for write or `R` for read. For writing, `W` is followed by the slave address (left-aligned), then the packet. You don't need to explicitly encode the length. For reading, `R` is followed by the slave address, then the size (no larger than 255). 288 | - In either cases, MCU will return one byte indicating if the I2C operation is successful (e.g., if the slave sends ACK). 0 indicates success. 289 | - Here is just an example. You are free to change the implementation details. 290 | ``` Python 291 | from seracc import serial_transmit, serial_receive 292 | 293 | def i2c_write(addr, val): 294 | if not isinstance(val, list): 295 | val = [val] 296 | cmd = "I2C:W".encode() 297 | cmd += bytes([addr]) 298 | cmd += bytes(val) 299 | serial_transmit(cmd) 300 | rec = serial_receive(1) 301 | if rec[0] != 0: 302 | print("I2C error") 303 | 304 | def i2c_read(addr, size): 305 | cmd = "I2C:R".encode() 306 | cmd += bytes([addr]) 307 | cmd += bytes([size]) 308 | serial_transmit(cmd) 309 | rec = serial_receive(size+1) 310 | if rec[0] != 0: 311 | print("I2C error") 312 | return rec[1:] 313 | ``` 314 | 315 | 2. Configure related peripherals in CubeMX. 316 | - In this case, configure the I2C and GPIO. 317 | 318 | 3. Write handler functions in C. 319 | - The handler function must have signature `void(uint8_t*, size_t)`. The first parameter is a pointer to the content, the second being its size. 320 | - According to the protocol defined above, the function should first judge if the first byte is `W` or `R`. 321 | - Then it sends the data with length implied by the content size, or receives data with specified length, with HAL functions. 322 | - Finally, it sends back the result to PC, including a byte indicating success and, in the `R` case, the received data. 323 | ``` C 324 | void i2c_handler(uint8_t* data, size_t size) 325 | { 326 | if (data[0] == 'W') 327 | { 328 | uint8_t addr = data[1]; 329 | uint8_t len = size - 2; 330 | uint8_t ret = 0; 331 | if (HAL_I2C_Master_Transmit(&hi2c1, addr, data+2, len, len+1) != HAL_OK) 332 | ret = -1; 333 | uart_transmit(&ret, 1); 334 | } 335 | else if (data[0] == 'R') 336 | { 337 | uint8_t addr = data[1]; 338 | uint8_t len = data[2]; 339 | uint8_t ret[len+1]; 340 | ret[0] = 0; 341 | if (HAL_I2C_Master_Receive(&hi2c1, addr, ret+1, len, len+1) != HAL_OK) 342 | ret[0] = -1; 343 | uart_transmit(ret, len+1); 344 | } 345 | } 346 | ``` 347 | 348 | 4. Register the handlers. 349 | - Register the handler with a call to `uart_register_handler`. The first parameter should match the key specified in Python wrapper function. 350 | ``` C 351 | uart_register_handler("I2C", i2c_handler); 352 | ``` 353 | 354 | ## Todo 355 | 356 | ~Access to global variables: Global variables have fixed address in RAM. This can be found in `.map` files. In the near future this library will support parsing `.map` files and provide access to the global variables. This eliminate the need to implement custom protocols to access the variable, making parameter tuning easier, e.g., PID.~ It's recommended to use STM32CubeMonitor for accessing global variables and plotting them. 357 | 358 | Non-blocking UART transfer: at this time the custom handlers must be blocking, otherwise commands may be missed. A circular buffer with DMA will be implemented. 359 | 360 | UI improvement: descriptions that are too long cannot be completely displayed. (Need help, I can't do front-end.) 361 | 362 | More tests on other platforms, including other series in STM32 and MCUs from other manufacturers, are needed. 363 | 364 | **Contributions are welcome!** 365 | 366 | ## Changelog 367 | 368 | ### Version 4.1 - Unaligned Access 369 | Now support access that are not 4-byte aligned. This is useful when accessing external memory mounted on M(S)MC and Octo-SPI. 370 | STM32G431 and STM32N657 headers are provided for teaching purposes. 371 | 372 | ### Version 4.0 - HAL Refactor and Support for MSPM0 373 | `seracc.c` is completely platform-independent. The hardware-dependent functions are extracted to `seracc_bsp.h` and implementations are provided for STM32 and MSPM0 in `seracc_sthal.c` and `seracc_mspm0.c`, respectively. 374 | 375 | ### Version 3.1 - Communication Optimization 376 | The UART communication does not rely on `HAL_UARTEx_ReceiveToIdle_DMA` any more, because we found that the ST-LINK V2/1 accidentally inserts idle character when baud rate is 115200, and packs data in 16 bytes under 1000000. Now the UART RX uses circular DMA and the idle interrupt directly. 377 | 378 | ### Version 3.0 - Uploaded to Github 379 | This library is made open-source. It is renamed to "Serial Accessor" to reflect its main functionality. 380 | 381 | ### Version 2.4 - Waiting for Flag 382 | Added `wait_until_equal`. This can be archived by a while statement at runtime, but not for code generation. `wait_until_equal` provides a method to wait for some flag in generated C code. 383 | 384 | ### Version 2.3 - Code Generation Optimization 385 | Contiguous write accesses to the same register is now combined into one access in generated code. If you intend to seperate them, call `barrier()` between accesses. 386 | 387 | ### Version 2.2 - Code Generation 388 | Transactions made in Python can be translated to C simply by enclosing the statements with a `with logging():` block. Note that this feature does not record branch or loop statements within the block; only the EXACT masks and values are recorded. 389 | The `reset()` method resets a bitfield or a register. For peripheral reset, please use RCC reset registers. 390 | 391 | ### Version 2.1 - Protocol Optimization 392 | Previously all write commands were 12-byte long, including a 32-bit address, a 32-bit value and a 32-bit mask. However, some accesses, e.g., setting or clearing a single bit, are common and should be optimized. Now the protocol is remastered and the average length of commands in typical use cases is reduced to, say, 8 bytes. 393 | 394 | ### Version 2.0 - SVD Parsing 395 | Parsing source is switched to the SVD file. This makes the register map more complete and accurate. 396 | Registers and bit fields can now be accessed with subsripts, e.g., `TIM1.CCR[1]` is equivalent to `TIM1.CCR1`. This enables a simpler syntax for multi-instance/channel treatment. 397 | Taking the `repr` of a peripheral now shows all registers in it. Similar for those subscriptable names including `TIM1.CCR`. 398 | In all tables, hovering the mouse on a name shows the description of that register or bit field. 399 | 400 | ### Version 1.5 - Register Details 401 | Evaluating a register now shows a table consisting its bit fields and the corresponding values. 402 | 403 | ### Version 1.0 - Initial Release 404 | This project is started for teaching purposes. The registers and data fields are parsed from C headers, e.g., "[stm32g474xx.h](https://github.com/STMicroelectronics/cmsis-device-g4/blob/master/Include/stm32g474xx.h)". This may be inaccurate or even fails with some advanced peripherals, but still okay for teaching purpose. 405 | 406 | -------------------------------------------------------------------------------- /jupyter/example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "ae0d8dbf-08e0-4f9a-927e-3a2cbcb6cd32", 7 | "metadata": {}, 8 | "outputs": [ 9 | { 10 | "name": "stdout", 11 | "output_type": "stream", 12 | "text": [ 13 | "Automatically detected:\n", 14 | "\tCOM7: STMicroelectronics STLink Virtual COM Port (COM7) [USB VID:PID=0483:374B SER=066DFF3530454D3043032331 LOCATION=1-3.3:x.2]\n" 15 | ] 16 | } 17 | ], 18 | "source": [ 19 | "from g474 import *" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 2, 25 | "id": "6a40e66b", 26 | "metadata": {}, 27 | "outputs": [ 28 | { 29 | "data": { 30 | "text/html": [ 31 | "\n", 85 | "\n", 86 | "\n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 99 | " \n", 100 | "\t\n", 101 | " \n", 102 | " \n", 103 | " \n", 108 | " \n", 109 | "\t\n", 110 | " \n", 111 | " \n", 112 | " \n", 117 | " \n", 118 | "\t\n", 119 | " \n", 120 | " \n", 121 | " \n", 126 | " \n", 127 | "\t\n", 128 | " \n", 129 | " \n", 130 | " \n", 135 | " \n", 136 | "\t\n", 137 | " \n", 138 | " \n", 139 | " \n", 144 | " \n", 145 | "\t\n", 146 | " \n", 147 | " \n", 148 | " \n", 153 | " \n", 154 | "\t\n", 155 | " \n", 156 | " \n", 157 | " \n", 162 | " \n", 163 | "\t\n", 164 | "
OffsetRegisterContent
0x00\n", 95 | "
CR1\n", 96 | " control register 1\n", 97 | "
\n", 98 | "
DEC: 0, HEX: 0x00000000
0x04\n", 104 | "
CR2\n", 105 | " control register 2\n", 106 | "
\n", 107 | "
DEC: 0, HEX: 0x00000000
0x0C\n", 113 | "
DIER\n", 114 | " DMA/Interrupt enable register\n", 115 | "
\n", 116 | "
DEC: 0, HEX: 0x00000000
0x10\n", 122 | "
SR\n", 123 | " status register\n", 124 | "
\n", 125 | "
DEC: 0, HEX: 0x00000000
0x14\n", 131 | "
EGR\n", 132 | " event generation register\n", 133 | "
\n", 134 | "
DEC: 0, HEX: 0x00000000
0x24\n", 140 | "
CNT\n", 141 | " counter\n", 142 | "
\n", 143 | "
DEC: 0, HEX: 0x00000000
0x28\n", 149 | "
PSC\n", 150 | " prescaler\n", 151 | "
\n", 152 | "
DEC: 0, HEX: 0x00000000
0x2C\n", 158 | "
ARR\n", 159 | " auto-reload register\n", 160 | "
\n", 161 | "
DEC: 0, HEX: 0x00000000
\n" 165 | ], 166 | "text/plain": [ 167 | "" 168 | ] 169 | }, 170 | "metadata": {}, 171 | "output_type": "display_data" 172 | }, 173 | { 174 | "data": { 175 | "text/plain": [ 176 | "Basic-timers" 177 | ] 178 | }, 179 | "execution_count": 2, 180 | "metadata": {}, 181 | "output_type": "execute_result" 182 | } 183 | ], 184 | "source": [ 185 | "TIM6" 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": 3, 191 | "id": "b2ea26ff", 192 | "metadata": {}, 193 | "outputs": [ 194 | { 195 | "data": { 196 | "text/html": [ 197 | "\n", 282 | "\n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " \n", 287 | " \n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | " \n", 339 | " \n", 340 | " \n", 345 | " \n", 350 | " \n", 351 | " \n", 352 | " \n", 353 | " \n", 358 | " \n", 359 | " \n", 360 | " \n", 361 | " \n", 366 | " \n", 371 | " \n", 376 | " \n", 381 | " \n", 382 | " \n", 383 | " \n", 384 | " \n", 385 | " \n", 386 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | " \n", 398 | " \n", 399 | " \n" 400 | ], 401 | "text/plain": [ 402 | "" 403 | ] 404 | }, 405 | "metadata": {}, 406 | "output_type": "display_data" 407 | }, 408 | { 409 | "data": { 410 | "text/plain": [ 411 | "DEC: 0, HEX: 0x00000000" 412 | ] 413 | }, 414 | "execution_count": 3, 415 | "metadata": {}, 416 | "output_type": "execute_result" 417 | } 418 | ], 419 | "source": [ 420 | "TIM6.CR1" 421 | ] 422 | }, 423 | { 424 | "cell_type": "code", 425 | "execution_count": 4, 426 | "id": "d3d45f2e", 427 | "metadata": {}, 428 | "outputs": [ 429 | { 430 | "data": { 431 | "text/plain": [ 432 | "0b000" 433 | ] 434 | }, 435 | "execution_count": 4, 436 | "metadata": {}, 437 | "output_type": "execute_result" 438 | } 439 | ], 440 | "source": [ 441 | "TIM6.CR2.MMS" 442 | ] 443 | }, 444 | { 445 | "cell_type": "code", 446 | "execution_count": 5, 447 | "id": "7430efe2", 448 | "metadata": {}, 449 | "outputs": [ 450 | { 451 | "name": "stdout", 452 | "output_type": "stream", 453 | "text": [ 454 | "*(volatile uint32_t*)0x40013000 |= 1u << 6; // SPI1.CR1.SPE = 0b1\n", 455 | "*(volatile uint8_t*)0x4001300C = 128; // SPI1.DR = 128\n", 456 | "volatile uint32_t* _reg = (volatile uint32_t*)0x40013008;\n", 457 | "while ((*_reg & 0x00000080) != 0x00000000); // SPI1.SR.BSY != 0b0\n", 458 | "*(volatile uint32_t*)0x40012C00 &= ~(1u << 0); // TIM1.CR1.CEN = 0b0\n", 459 | "*(volatile uint32_t*)0x40012C00 |= 1u << 4; // TIM1.CR1.DIR = 0b1\n", 460 | "*(volatile uint32_t*)0x40012C04 = 272; // TIM1.CR2 = 0\n", 461 | " // TIM1.CR2.MMS = 0b0001\n", 462 | " // TIM1.CR2.OIS1 = 0b1\n" 463 | ] 464 | } 465 | ], 466 | "source": [ 467 | "with logging() as log:\n", 468 | " SPI1.CR1.SPE = 1\n", 469 | " SPI1.DR.write8(0x80)\n", 470 | " wait_until_equal(SPI1.SR.BSY, 0)\n", 471 | " TIM1.CR1.CEN = 0\n", 472 | " log.barrier()\n", 473 | " TIM1.CR1.DIR = 1\n", 474 | " TIM1.CR2.reset()\n", 475 | " TIM1.CR2.MMS = 0b0001\n", 476 | " TIM1.CR2.OIS1 = 1" 477 | ] 478 | }, 479 | { 480 | "cell_type": "code", 481 | "execution_count": 6, 482 | "id": "691b1ab9", 483 | "metadata": {}, 484 | "outputs": [ 485 | { 486 | "name": "stdout", 487 | "output_type": "stream", 488 | "text": [ 489 | "RCC.APB1ENR1.TIM6EN: TIM6 timer clock enable. Set and cleared by software.\n", 490 | "RCC.APB1RSTR1.TIM6RST: TIM6 timer reset. Set and cleared by software.\n", 491 | "RCC.APB1SMENR1.TIM6SMEN: TIM6 timer clocks enable during Sleep and Stop modes. Set and cleared by software.\n", 492 | "RCC.APB2ENR.TIM16EN: TIM16 timer clock enable. Set and cleared by software.\n", 493 | "RCC.APB2RSTR.TIM16RST: TIM16 timer reset. Set and cleared by software.\n", 494 | "RCC.APB2SMENR.TIM16SMEN: TIM16 timer clocks enable during Sleep and Stop modes. Set and cleared by software.\n" 495 | ] 496 | } 497 | ], 498 | "source": [ 499 | "RCC.find(\"timer 6\")" 500 | ] 501 | }, 502 | { 503 | "cell_type": "code", 504 | "execution_count": 7, 505 | "id": "e4f00a48-fb12-448a-9015-a02477c7278e", 506 | "metadata": {}, 507 | "outputs": [], 508 | "source": [ 509 | "RCC.APB1ENR1.TIM6EN = 1" 510 | ] 511 | }, 512 | { 513 | "cell_type": "code", 514 | "execution_count": 8, 515 | "id": "67f77802-1073-472a-ae16-73a5bb5ef535", 516 | "metadata": {}, 517 | "outputs": [ 518 | { 519 | "data": { 520 | "text/plain": [ 521 | "0b010" 522 | ] 523 | }, 524 | "execution_count": 8, 525 | "metadata": {}, 526 | "output_type": "execute_result" 527 | } 528 | ], 529 | "source": [ 530 | "TIM6.CR2.MMS = 0b010\n", 531 | "TIM6.CR2.MMS" 532 | ] 533 | }, 534 | { 535 | "cell_type": "code", 536 | "execution_count": 9, 537 | "id": "d8d1fe8f-6e8f-4f18-98af-96534d5bd391", 538 | "metadata": {}, 539 | "outputs": [ 540 | { 541 | "data": { 542 | "text/html": [ 543 | "\n", 597 | "\n", 598 | "
31302928272625242322212019181716
0000000000000000
1514131211109876543210
\n", 341 | "
DITHEN\n", 342 | " Dithering Enable\n", 343 | "
\n", 344 | "
\n", 346 | "
UIFREMAP\n", 347 | " UIF status bit remapping\n", 348 | "
\n", 349 | "
\n", 354 | "
ARPE\n", 355 | " Auto-reload preload enable\n", 356 | "
\n", 357 | "
\n", 362 | "
OPM\n", 363 | " One-pulse mode\n", 364 | "
\n", 365 | "
\n", 367 | "
URS\n", 368 | " Update request source\n", 369 | "
\n", 370 | "
\n", 372 | "
UDIS\n", 373 | " Update disable\n", 374 | "
\n", 375 | "
\n", 377 | "
CEN\n", 378 | " Counter enable\n", 379 | "
\n", 380 | "
0000000000000000
\n", 599 | " \n", 600 | " \n", 601 | " \n", 602 | " \n", 603 | " \n", 604 | " \n", 605 | " \n", 606 | " \n", 611 | " \n", 612 | "\t\n", 613 | " \n", 614 | " \n", 615 | " \n", 620 | " \n", 621 | "\t\n", 622 | " \n", 623 | " \n", 624 | " \n", 629 | " \n", 630 | "\t\n", 631 | " \n", 632 | " \n", 633 | " \n", 638 | " \n", 639 | "\t\n", 640 | " \n", 641 | " \n", 642 | " \n", 647 | " \n", 648 | "\t\n", 649 | " \n", 650 | " \n", 651 | " \n", 656 | " \n", 657 | "\t\n", 658 | " \n", 659 | " \n", 660 | " \n", 665 | " \n", 666 | "\t\n", 667 | " \n", 668 | " \n", 669 | " \n", 674 | " \n", 675 | "\t\n", 676 | "
OffsetRegisterContent
0x00\n", 607 | "
CR1\n", 608 | " control register 1\n", 609 | "
\n", 610 | "
DEC: 0, HEX: 0x00000000
0x04\n", 616 | "
CR2\n", 617 | " control register 2\n", 618 | "
\n", 619 | "
DEC: 32, HEX: 0x00000020
0x0C\n", 625 | "
DIER\n", 626 | " DMA/Interrupt enable register\n", 627 | "
\n", 628 | "
DEC: 0, HEX: 0x00000000
0x10\n", 634 | "
SR\n", 635 | " status register\n", 636 | "
\n", 637 | "
DEC: 0, HEX: 0x00000000
0x14\n", 643 | "
EGR\n", 644 | " event generation register\n", 645 | "
\n", 646 | "
DEC: 0, HEX: 0x00000000
0x24\n", 652 | "
CNT\n", 653 | " counter\n", 654 | "
\n", 655 | "
DEC: 0, HEX: 0x00000000
0x28\n", 661 | "
PSC\n", 662 | " prescaler\n", 663 | "
\n", 664 | "
DEC: 0, HEX: 0x00000000
0x2C\n", 670 | "
ARR\n", 671 | " auto-reload register\n", 672 | "
\n", 673 | "
DEC: 65535, HEX: 0x0000FFFF
\n" 677 | ], 678 | "text/plain": [ 679 | "" 680 | ] 681 | }, 682 | "metadata": {}, 683 | "output_type": "display_data" 684 | }, 685 | { 686 | "data": { 687 | "text/plain": [ 688 | "Basic-timers" 689 | ] 690 | }, 691 | "execution_count": 9, 692 | "metadata": {}, 693 | "output_type": "execute_result" 694 | } 695 | ], 696 | "source": [ 697 | "TIM6" 698 | ] 699 | }, 700 | { 701 | "cell_type": "code", 702 | "execution_count": 10, 703 | "id": "84d435fc-eaed-4dc0-934d-1ff40f4dd2ec", 704 | "metadata": {}, 705 | "outputs": [ 706 | { 707 | "data": { 708 | "text/html": [ 709 | "\n", 794 | "\n", 795 | " \n", 796 | " \n", 797 | " \n", 798 | " \n", 799 | " \n", 800 | " \n", 801 | " \n", 802 | " \n", 803 | " \n", 804 | " \n", 805 | " \n", 806 | " \n", 807 | " \n", 808 | " \n", 809 | " \n", 810 | " \n", 811 | " \n", 812 | " \n", 813 | " \n", 814 | " \n", 815 | " \n", 816 | " \n", 817 | " \n", 818 | " \n", 819 | " \n", 820 | " \n", 821 | " \n", 822 | " \n", 823 | " \n", 824 | " \n", 825 | " \n", 826 | " \n", 827 | " \n", 828 | " \n", 829 | " \n", 830 | " \n", 831 | " \n", 832 | " \n", 833 | " \n", 834 | " \n", 835 | " \n", 836 | " \n", 837 | " \n", 838 | " \n", 839 | " \n", 840 | " \n", 841 | " \n", 842 | " \n", 843 | " \n", 844 | " \n", 845 | " \n", 846 | " \n", 847 | " \n", 848 | " \n", 849 | " \n", 850 | " \n", 851 | " \n", 852 | " \n", 853 | " \n", 854 | " \n", 855 | " \n", 856 | " \n", 857 | " \n", 858 | " \n", 863 | " \n", 864 | " \n", 865 | " \n", 866 | " \n", 867 | " \n", 868 | " \n", 869 | " \n", 870 | " \n", 871 | " \n", 872 | " \n", 873 | " \n", 874 | " \n", 875 | " \n", 876 | " \n", 877 | " \n", 878 | " \n", 879 | " \n", 880 | " \n", 881 | " \n", 882 | " \n", 883 | " \n", 884 | " \n", 885 | " \n" 886 | ], 887 | "text/plain": [ 888 | "" 889 | ] 890 | }, 891 | "metadata": {}, 892 | "output_type": "display_data" 893 | }, 894 | { 895 | "data": { 896 | "text/plain": [ 897 | "DEC: 32, HEX: 0x00000020" 898 | ] 899 | }, 900 | "execution_count": 10, 901 | "metadata": {}, 902 | "output_type": "execute_result" 903 | } 904 | ], 905 | "source": [ 906 | "TIM6.CR2" 907 | ] 908 | }, 909 | { 910 | "cell_type": "code", 911 | "execution_count": null, 912 | "id": "4398447a", 913 | "metadata": {}, 914 | "outputs": [], 915 | "source": [] 916 | } 917 | ], 918 | "metadata": { 919 | "kernelspec": { 920 | "display_name": "base", 921 | "language": "python", 922 | "name": "python3" 923 | }, 924 | "language_info": { 925 | "codemirror_mode": { 926 | "name": "ipython", 927 | "version": 3 928 | }, 929 | "file_extension": ".py", 930 | "mimetype": "text/x-python", 931 | "name": "python", 932 | "nbconvert_exporter": "python", 933 | "pygments_lexer": "ipython3", 934 | "version": "3.12.7" 935 | } 936 | }, 937 | "nbformat": 4, 938 | "nbformat_minor": 5 939 | } 940 | -------------------------------------------------------------------------------- /jupyter/seracc.py: -------------------------------------------------------------------------------- 1 | # version 4.1 - updated 2025/10/19 2 | 3 | if "ser" not in globals(): 4 | ser = None 5 | _crc = None 6 | _ev = False 7 | _count = 0 8 | SER_LEN = 1040 9 | 10 | def serial_init(which=None): 11 | import serial 12 | global ser, _crc, _ev, _count 13 | 14 | if isinstance(ser, serial.Serial): 15 | ser.close() 16 | 17 | if which is None: 18 | count = 0 19 | port_st, desc_st, hwid_st = None, None, None 20 | import serial.tools.list_ports 21 | for port, desc, hwid in serial.tools.list_ports.comports(): 22 | if desc.startswith("STMicroelectronics") or desc.startswith("XDS110 Class App"): 23 | count += 1 24 | port_st, desc_st, hwid_st = port, desc, hwid 25 | if count == 1: 26 | print(f"Automatically detected:\n\t{port_st}: {desc_st} [{hwid_st}]") 27 | which = port_st 28 | 29 | if which is None: 30 | print("Listing serial ports:") 31 | for port, desc, hwid in serial.tools.list_ports.comports(): 32 | print(f"\t{port}: {desc} [{hwid}]") 33 | which = input("Which COM port? Enter 0 for evaluation mode") 34 | 35 | if isinstance(which, int) or which.isnumeric(): 36 | which = f"COM{which}" 37 | 38 | if which == "COM0": 39 | _ev = True 40 | print("Entering evaluation mode") 41 | return 42 | 43 | ser = serial.Serial(which, baudrate=1000000, timeout=1) 44 | _count = 0 45 | 46 | from crc import Configuration, Calculator 47 | config = Configuration( 48 | width=16, 49 | polynomial=0x1021, 50 | init_value=0x0000, 51 | final_xor_value=0x0000, 52 | reverse_input=False, 53 | reverse_output=False, 54 | ) 55 | _crc = Calculator(config) 56 | 57 | serial_init() 58 | 59 | def serial_transmit(bs): 60 | # print([hex(b) for b in bs]) 61 | 62 | global ser, _crc, _ev, _count 63 | 64 | if _ev: 65 | return 66 | 67 | if isinstance(bs, str): 68 | bs = bs.encode() 69 | 70 | l = len(bs) 71 | data = [l % 256, l // 256] 72 | data += bs 73 | crc = _crc.checksum(bs) 74 | data += [crc % 256, crc // 256] 75 | 76 | l = len(data) 77 | if l >= SER_LEN: 78 | print("Error: packet too long") 79 | return 80 | 81 | if _count + l >= SER_LEN: 82 | serial_sync() 83 | _count += l 84 | 85 | ser.write(data) 86 | 87 | def serial_receive(size): 88 | global ser, _crc, _ev, _count 89 | 90 | if _ev: 91 | return bytes([0] * size) 92 | 93 | bs = ser.read(size+4) 94 | 95 | if len(bs) == 0: 96 | print("No response") 97 | return bytes() 98 | 99 | if len(bs) != size+4 \ 100 | or bs[0]+bs[1]*256 != size: 101 | print("Format error") 102 | ser.read_all() 103 | return bytes() 104 | 105 | crc = bs[-2] + bs[-1] * 256 106 | bs = bs[2:-2] 107 | if _crc.checksum(bs) != crc: 108 | print("CRC error") 109 | return bytes() 110 | 111 | _count = 0 112 | return bs 113 | 114 | def serial_clear(): 115 | global ser, _crc, _ev, _count 116 | 117 | if _ev: 118 | return 119 | ser.read_all() 120 | 121 | def serial_sync(): 122 | global ser, _crc, _ev, _count 123 | 124 | if _ev: 125 | return 126 | 127 | ser.write(bytes([0x55, 0xAA])) 128 | read = ser.read(2) 129 | if len(read) != 2 or read[0] != ord('O') or read[1] != ord('K'): 130 | print("Sync failed") 131 | 132 | _count = 0 133 | 134 | def mask_shl(value, mask): 135 | result = 0 136 | j = 0 137 | for i in range(32): 138 | if mask & (1 << i): 139 | if value & (1 << j): 140 | result |= 1 << i 141 | j += 1 142 | return result 143 | 144 | def mask_shr(value, mask): 145 | result = 0 146 | j = 0 147 | for i in range(32): 148 | if mask & (1 << i): 149 | if value & (1 << i): 150 | result |= 1 << j 151 | j += 1 152 | return result 153 | 154 | def mask_to_pos(mask): 155 | pos = [] 156 | for i in range(32): 157 | if 1 << i & mask: 158 | pos.append(i) 159 | return pos 160 | 161 | def bin_repr(v, n): 162 | return f"0b{v:0{n}b}" 163 | 164 | def hex_repr(v, n=32): 165 | return f"0x{v:0{(n+3)//4}X}" 166 | 167 | def from_bin(s): 168 | if isinstance(s, BitField): 169 | s = s.__repr__() 170 | if s.startswith("0b"): 171 | s = s[2:] 172 | return int(s, base=2) 173 | 174 | def from_hex(s): 175 | if isinstance(s, RegisterBase): 176 | s = s.__repr__() 177 | if s.startswith("0x"): 178 | s = s[2:] 179 | return int(s, base=16) 180 | 181 | def simplify_access(mask, value): 182 | mask_toset = mask & value 183 | mask_toclear = mask & ~value 184 | n_toset = bin(mask_toset ).count("1") 185 | n_toclear = bin(mask_toclear).count("1") 186 | n_mask = bin(mask ).count("1") 187 | 188 | if mask == MASK_32B: 189 | return ["write-only"] 190 | elif n_toset == 1 and n_toclear == 0: 191 | return ["single-set", mask_to_pos(mask_toset)[0]] 192 | elif n_toset == 0 and n_toclear == 1: 193 | return ["single-clear", mask_to_pos(mask_toclear)[0]] 194 | elif n_toset == n_mask and n_toclear == 0: 195 | return ["set-only"] 196 | elif n_toset == 0 and n_toclear == n_mask: 197 | return ["clear-only"] 198 | else: 199 | return ["full-modify"] 200 | 201 | _logger = None 202 | 203 | # set filename to log into a file which you can `#include` in your code 204 | # otherwise, it prints on the console 205 | # note that the logging feature does not record branch or loop statements within the `with` block 206 | # it only records the read and write accesses 207 | # for write accesses, the exact masks and values are produced 208 | class AccessLogger: 209 | def __init__(self, filename): 210 | self.filename = filename 211 | self.ops = [] # typ, addr, mask, value, width, comment 212 | # typ=0 for write, 1 for read, 2 for barrier, 3 for wait 213 | self.node = None 214 | self.waiting = False 215 | 216 | def __enter__(self): 217 | global _logger 218 | _logger = self 219 | return self 220 | 221 | def __exit__(self, tp, v, tb): 222 | if tp is not None: 223 | raise tp(v).with_traceback(tb) 224 | 225 | addr, mask, value = 0, 0, 0 226 | temp_cmt = [] 227 | declared = False 228 | commands = [] 229 | comments = [] 230 | 231 | def flush(): 232 | nonlocal addr, mask, value, temp_cmt, declared, commands, comments 233 | 234 | if addr == 0: 235 | return 236 | 237 | cls = simplify_access(mask, value) 238 | if cls[0] == "write-only": 239 | cmd = f"*(volatile uint32_t*){hex_repr(addr)} = {value};" 240 | elif cls[0] == "single-set": 241 | cmd = f"*(volatile uint32_t*){hex_repr(addr)} |= 1u << {cls[1]};" 242 | elif cls[0] == "single-clear": 243 | cmd = f"*(volatile uint32_t*){hex_repr(addr)} &= ~(1u << {cls[1]});" 244 | elif cls[0] == "set-only": 245 | cmd = f"*(volatile uint32_t*){hex_repr(addr)} |= {hex_repr(mask)};" 246 | elif cls[0] == "clear-only": 247 | cmd = f"*(volatile uint32_t*){hex_repr(addr)} &= ~{hex_repr(mask)};" 248 | elif cls[0] == "full-modify": 249 | cmd = "volatile uint32_t* " if not declared else "" 250 | declared = True 251 | cmd += f"_reg = (volatile uint32_t*){hex_repr(addr)};" 252 | commands.append(cmd) 253 | comments.append("") 254 | cmd = f"*_reg = (*_reg & ~{hex_repr(mask)}) | {hex_repr(value)};" 255 | 256 | commands.append(cmd) 257 | commands += [""] * (len(temp_cmt) - 1) 258 | comments += temp_cmt 259 | 260 | addr, mask, value = 0, 0, 0 261 | temp_cmt = [] 262 | 263 | for op in self.ops: 264 | if op[0] == 0: # write 265 | if op[4] in [8, 16]: # 8/16-bit write 266 | flush() 267 | commands.append(f"*(volatile uint{op[4]}_t*){hex_repr(op[1])} = {op[3]};") 268 | comments.append(op[5]) 269 | else: # 32-bit write 270 | if op[1] != addr: 271 | flush() 272 | addr = op[1] 273 | value = (value & (MASK_32B-op[2])) | (op[3] & op[2]) 274 | mask |= op[2] 275 | temp_cmt.append(op[5]) 276 | else: 277 | flush() 278 | if op[0] == 1: # read 279 | commands.append(f"(void) *(volatile uint{op[4]}_t*){hex_repr(op[1])};") 280 | comments.append(op[5]) 281 | elif op[0] == 2: # barrier 282 | pass 283 | elif op[0] == 3: # wait 284 | cmd = "volatile uint32_t* " if not declared else "" 285 | declared = True 286 | cmd += f"_reg = (volatile uint32_t*){hex_repr(op[1])};" 287 | commands.append(cmd) 288 | comments.append("") 289 | cmd = f"while ((*_reg & {hex_repr(op[2])}) != {hex_repr(op[3])});" 290 | commands.append(cmd) 291 | comments.append(op[5]) 292 | flush() 293 | 294 | code = "" 295 | width = len(max(zip(commands, comments), key= 296 | lambda p: len(p[0]) if len(p[1]) > 0 else 0) 297 | [0]) 298 | for c, cmt in zip(commands, comments): 299 | code += c + " " * (width - len(c)) 300 | if len(cmt) > 0: 301 | code += " // " 302 | code += cmt + "\n" 303 | 304 | if self.filename is None: 305 | print(code[:-1]) 306 | else: 307 | with open(self.filename, "w") as f: 308 | f.write(code) 309 | 310 | global _logger 311 | _logger = None 312 | 313 | def barrier(self): 314 | self.ops.append([2]) 315 | 316 | def set_node(self, node): 317 | if self.node is None: 318 | self.node = node 319 | 320 | def log_read(self, addr, width): 321 | if self.waiting: 322 | return 323 | comment = self.node.get_full_name() 324 | self.ops.append([1, addr, 0, 0, width, comment]) 325 | self.node = None 326 | 327 | def log_write(self, addr, mask, value, width): 328 | comment = self.node.get_full_name() + " = " 329 | if isinstance(self.node, BitField): 330 | comment += bin_repr(mask_shr(value, mask), self.node.n) 331 | else: 332 | comment += f"{value}" 333 | self.ops.append([0, addr, mask, value, width, comment]) 334 | self.node = None 335 | 336 | def log_wait(self, addr, mask, value): 337 | if addr == 0: 338 | self.waiting = True 339 | else: 340 | self.waiting = False 341 | 342 | comment = self.node.get_full_name() + " != " 343 | if isinstance(self.node, BitField): 344 | comment += bin_repr(value, self.node.n) 345 | else: 346 | comment += f"{value}" 347 | self.ops.append([3, addr, mask, mask_shl(value, mask), 32, comment]) 348 | self.node = None 349 | 350 | def logging(filename=None): 351 | return AccessLogger(filename) 352 | 353 | # FORMAT (little-ended): 354 | # CMD 0-3 4 5 6 7 8 9-11 355 | # 8-bit read (4-AL) _: (4) 356 | # 8-bit read _: x x h01 (7) 357 | # 16-bit read (4-AL) _: (4) 358 | # 16-bit read _: x x h02 (7) 359 | # 32-bit read (4-AL) _: (4) 360 | # 32-bit read _: x x h04 (7) 361 | # 8-bit write (4-AL) _: (5) 362 | # 8-bit write _: x h11 (7) 363 | # 16-bit write (4-AL) _: (6) 364 | # 16-bit write _: h12 (7) 365 | # 32-bit write (4-AL) _: (8) 366 | # 32-bit write _: x (9) 367 | # single bit set _: (5) 368 | # single bit clear _: (5) 369 | # set bits _: (8) 370 | # clear bits _: (8) 371 | # modify masked bits _: (12) 372 | 373 | # assume all registers are 32-bit 374 | MASK_32B = 2**32 - 1 375 | 376 | def to_4bytes(word): 377 | bs = [0, 0, 0, 0] 378 | for i in range(4): 379 | bs[i] = word & 0xFF 380 | word >>= 8 381 | return bytes(bs) 382 | 383 | # mask: extract the bits where mask is 1 384 | # direct: if True, preserve the bit positions in the word 385 | # width: 8, 16 or 32 386 | def read_register(addr, mask=MASK_32B, direct=False, width=32): 387 | bs = "_:".encode() 388 | 389 | if addr & 0b11 != 0: 390 | 391 | c = 0 392 | if width == 32: 393 | c = 4 394 | elif width == 16: 395 | c = 2 396 | elif width == 8: 397 | c = 1 398 | else: 399 | raise NotImplementedError("Unknown access width") 400 | bs += to_4bytes(addr) + bytes([0xFE, 0xEF, c]) 401 | 402 | else: 403 | 404 | ored = 0 405 | if width == 32: 406 | pass 407 | elif width == 16: 408 | ored = 2 409 | elif width == 8: 410 | ored = 1 411 | else: 412 | raise NotImplementedError("Unknown access width") 413 | bs += to_4bytes(addr | ored) 414 | 415 | serial_clear() 416 | serial_transmit(bs) 417 | 418 | bs = serial_receive(width//8) 419 | if len(bs) != width//8: 420 | raise EOFError("Reading error") 421 | 422 | if width == 32: 423 | value = bs[0] | bs[1]<<8 | bs[2]<<16 | bs[3]<<24 424 | elif width == 16: 425 | value = bs[0] | bs[1]<<8 426 | else: 427 | value = bs[0] 428 | 429 | if direct: 430 | value &= mask 431 | else: 432 | value = mask_shr(value, mask) 433 | 434 | if _logger: 435 | _logger.log_read(addr, width) 436 | 437 | return int(value) 438 | 439 | # masked write: change bits with mask 1 only, mask only applies when width=32 440 | # direct: if True, preserve the bit positions in the word 441 | # width: 8, 16 or 32 442 | def write_register(addr, value, mask=MASK_32B, direct=False, width=32): 443 | bs = "_:".encode() 444 | bs += to_4bytes(addr) 445 | 446 | if addr & 0b11 != 0: 447 | 448 | if mask not in [MASK_32B, 2**width-1]: 449 | raise NotImplementedError("Masked write with unaligned address is forbidden") 450 | 451 | if width == 32: 452 | bs += to_4bytes(value) + bytes([0xFF]) 453 | 454 | elif width == 16: 455 | bs += to_4bytes(value)[:2] + bytes([0x12]) 456 | 457 | elif width == 8: 458 | bs += to_4bytes(value)[:1] + bytes([0xFF, 0x11]) 459 | 460 | else: 461 | raise NotImplementedError("Unknown access width") 462 | 463 | serial_transmit(bs) 464 | 465 | else: 466 | 467 | if not direct: 468 | value = mask_shl(value, mask) 469 | ored = 0 470 | 471 | if width == 32: 472 | cls = simplify_access(mask, value) 473 | if cls[0] == "write-only": 474 | bs += to_4bytes(value) 475 | elif cls[0] == "single-set": 476 | bs += bytes([cls[1]]) 477 | elif cls[0] == "single-clear": 478 | ored = 2 479 | bs += bytes([cls[1]]) 480 | elif cls[0] == "set-only": 481 | ored = 1 482 | bs += to_4bytes(mask) 483 | elif cls[0] == "clear-only": 484 | ored = 2 485 | bs += to_4bytes(MASK_32B - mask) 486 | elif cls[0] == "full-modify": 487 | bs += to_4bytes(mask) 488 | bs += to_4bytes(value) 489 | 490 | elif width == 16: 491 | bs += to_4bytes(value)[:2] 492 | 493 | elif width == 8: 494 | ored = 1 495 | bs += to_4bytes(value)[:1] 496 | 497 | else: 498 | raise NotImplementedError("Unknown access width") 499 | 500 | bs = bs[:2] + bytes([bs[2] | ored]) + bs[3:] 501 | serial_transmit(bs) 502 | 503 | if _logger: 504 | _logger.log_write(addr, mask, value, width) 505 | 506 | # the generated code does not include the timeout 507 | def wait_until_equal(field, value, timeout=1): 508 | if _logger: 509 | _logger.log_wait(0, 0, 0) 510 | _logger.set_node(field) 511 | 512 | import time 513 | start_time = time.time() 514 | while field.read() != value: 515 | if _ev: 516 | break 517 | if time.time() > start_time + timeout: 518 | raise EOFError("Timeout") 519 | 520 | if _logger: 521 | _logger.log_wait(field.register.address, field.mask, value) 522 | 523 | 524 | class InstanceSetter: 525 | def __setattr__(self, attr, value): 526 | try: 527 | at = super().__getattribute__(attr) 528 | at.__set__(self, value) 529 | except AttributeError: 530 | super().__setattr__(attr, value) 531 | 532 | class BitField: 533 | def __init__(self, register, mask, name, desc): 534 | self.register = register 535 | self.mask = mask 536 | self.n = bin(mask).count("1") 537 | self.name = name 538 | self.desc = desc 539 | 540 | def get_full_name(self): 541 | return self.register.get_full_name() + "." + self.name 542 | 543 | def __repr__(self): 544 | return bin_repr(self.read(), self.n) 545 | 546 | def __set__(self, instance, value): 547 | self.write(value) 548 | return value 549 | 550 | def read(self): 551 | result = self.register.read(mask=self.mask) 552 | return result 553 | 554 | def write(self, value, direct=False): 555 | if _logger: 556 | _logger.set_node(self) 557 | self.register.write(value, mask=self.mask, direct=direct) 558 | 559 | def reset(self): 560 | self.write(self.register.reset_value, direct=True) 561 | 562 | import IPython.display as ipydisp 563 | 564 | class RegisterBase(InstanceSetter): 565 | 566 | def __init__(self, peripheral, offset, reset, name, desc): 567 | self.peripheral = peripheral 568 | self.address = peripheral.base + offset 569 | self.offset = offset 570 | self.reset_value = reset 571 | self.name = name 572 | self.desc = desc 573 | 574 | def get_full_name(self): 575 | return self.peripheral.name + "." + self.name 576 | 577 | def structure(self, value=-1): 578 | if value < 0: 579 | bits = ["N/A"] * 32 580 | else: 581 | bits = bin_repr(value, 32)[2:] 582 | 583 | html = """\ 584 | 669 | """ 670 | 671 | # I cannot understand the code below after writing it, 672 | # but it does print a table 673 | # mask2 is mainly for dual-role registers like timer CCMR 674 | mask = [None] * 32 675 | mask2 = [None] * 32 676 | for name in dir(self): 677 | bf = getattr(self, name) 678 | if not isinstance(bf, BitField): 679 | continue 680 | pos = mask_to_pos(bf.mask) 681 | if all(m is None for m in [mask[p] for p in pos]): 682 | if bf.n == 1: 683 | mask[pos[0]] = [name, -1, bf.desc] 684 | else: 685 | for i, p in enumerate(pos): 686 | mask[p] = [name, i, bf.desc] 687 | elif all(m is None for m in [mask2[p] for p in pos]): 688 | if bf.n == 1: 689 | mask2[pos[0]] = [name, -1, bf.desc] 690 | else: 691 | for i, p in enumerate(pos): 692 | mask2[p] = [name, i, bf.desc] 693 | mask = list(reversed(mask)) 694 | mask2 = list(reversed(mask2)) 695 | 696 | html += """\ 697 |
31302928272625242322212019181716
0000000000000000
1514131211109876543210
\n", 859 | "
MMS[2:0]\n", 860 | " Master mode selection\n", 861 | "
\n", 862 | "
0000000000100000
698 | """ 699 | 700 | for base in [0, 16]: 701 | 702 | html += """\ 703 | 704 | """ 705 | 706 | for i in range(base, base+16): 707 | html += f"""\ 708 | 709 | """ 710 | html += """\ 711 | 712 | """ 713 | 714 | for mm in [mask, mask2]: 715 | mm = mm[base:base+16] 716 | if not all(m is None for m in mm): 717 | html += """\ 718 | 719 | """ 720 | i = 0 721 | while i < 16: 722 | if mm[i] is None: 723 | html += """\ 724 | 725 | """ 726 | i += 1 727 | elif mm[i][1] == -1: 728 | html += f"""\ 729 | 734 | """ 735 | i += 1 736 | else: 737 | j = i + 1 738 | while j < 16 and mm[j] is not None and \ 739 | mm[j][0] == mm[j-1][0] and mm[j][1] == mm[j-1][1]-1: 740 | j += 1 741 | if j == i + 1: 742 | html += f"""\ 743 | 748 | """ 749 | else: 750 | html += f"""\ 751 | 756 | """ 757 | i = j 758 | html += """\ 759 | 760 | """ 761 | html += """\ 762 | 763 | """ 764 | for i in range(base, base+16): 765 | html += f"""\ 766 | 767 | """ 768 | html += """\ 769 | 770 | """ 771 | 772 | ipydisp.display(ipydisp.HTML(html)) 773 | 774 | def __repr__(self): 775 | return self.get_repr() 776 | 777 | def __set__(self, instance, value): 778 | self.write(value) 779 | return value 780 | 781 | def read(self, mask=MASK_32B, direct=False): 782 | if _logger: 783 | _logger.set_node(self) 784 | result = read_register(self.address, mask, direct) 785 | return result 786 | 787 | def read8(self): 788 | if _logger: 789 | _logger.set_node(self) 790 | return read_register(self.address, width=8) 791 | 792 | def read16(self): 793 | if _logger: 794 | _logger.set_node(self) 795 | return read_register(self.address, width=16) 796 | 797 | # warning: value is always right-aligned 798 | # e.g. to write high 4 bits to 0b0100, set value=0b0100 and mask=0xF0000000 799 | # set direct=True to bypass the shifting 800 | def write(self, value, mask=MASK_32B, direct=False): 801 | if _logger: 802 | _logger.set_node(self) 803 | write_register(self.address, value, mask, direct) 804 | 805 | def write8(self, value): 806 | if _logger: 807 | _logger.set_node(self) 808 | write_register(self.address, value, width=8) 809 | 810 | def write16(self, value): 811 | if _logger: 812 | _logger.set_node(self) 813 | write_register(self.address, value, width=16) 814 | 815 | def reset(self): 816 | self.write(self.reset_value) 817 | 818 | def get_repr(self, show=True): 819 | value = self.read() 820 | info = f"DEC: {value}, HEX: {hex_repr(value)}" 821 | 822 | if show: 823 | self.structure(value) 824 | 825 | return info 826 | 827 | class PeripheralBase(InstanceSetter): 828 | 829 | def __init__(self, base, name, desc): 830 | self.base = base 831 | self.name = name 832 | self.desc = desc 833 | 834 | def __repr__(self): 835 | return self.get_repr() 836 | 837 | def get_repr(self, regnames=None, show=True): 838 | if regnames is None: 839 | names = [] 840 | offsets = [] 841 | for attr in dir(self): 842 | if isinstance(getattr(self, attr), RegisterBase) and\ 843 | not attr.endswith("_Input") and not attr.endswith("_Output"): 844 | names.append(attr) 845 | reg = getattr(self, attr) 846 | offsets.append(reg.offset) 847 | 848 | sort = sorted(zip(names, offsets), key=lambda x: x[1]) 849 | names = [p[0] for p in sort] 850 | n_offset = len(f"{max(offsets):b}") 851 | else: 852 | names = regnames 853 | n_offset = max([len(f"{getattr(self, attr).offset:b}") for attr in names]) 854 | 855 | html = """\ 856 | 910 | 911 |
{31-i}
730 |
{mm[i][0]} 731 | {mm[i][2]} 732 |
733 |
744 |
{mm[i][0]}[{mm[i][1]}] 745 | {mm[i][2]} 746 |
747 |
752 |
{mm[i][0]}[{mm[i][1]}:{mm[j-1][1]}] 753 | {mm[i][2]} 754 |
755 |
{bits[i]}
912 | 913 | 914 | 915 | 916 | 917 | """ 918 | 919 | for name in names: 920 | reg = getattr(self, name) 921 | html += f"""\ 922 | 923 | 924 | 929 | 930 | 931 | """ 932 | 933 | html += """\ 934 |
OffsetRegisterContent
{hex_repr(reg.offset, n_offset)} 925 |
{name} 926 | {reg.desc} 927 |
928 |
{reg.get_repr(False)}
935 | """ 936 | 937 | if show: 938 | ipydisp.display(ipydisp.HTML(html)) 939 | 940 | return f"{self.desc}" 941 | 942 | def find(self, query): 943 | 944 | def tokenize(s): 945 | import re 946 | tokens = set(re.split(r":|;|,|\.| ", s.lower())) 947 | tokens.discard('') 948 | return tokens 949 | 950 | keywords = tokenize(query) 951 | if len(keywords) == 0: 952 | return 953 | 954 | def match(desc): 955 | import re 956 | count = 0 957 | desc_tokens = tokenize(desc) 958 | for word in keywords: 959 | if word in desc_tokens: 960 | count += 1 961 | elif word.isnumeric() and desc.find(word) >= 0: 962 | idx = desc.find(word) 963 | if idx+len(word) == len(desc) or not desc[idx+len(word)].isdigit(): 964 | count += 1 965 | if len(keywords) >= 2 and count <= len(keywords) / 2: 966 | return False 967 | 968 | result = True 969 | has_name = False 970 | for name in re.findall(r"[A-Z][A-Z\d]+", query): 971 | has_name = True 972 | idx = desc.find(name) 973 | if idx < 0: 974 | result = False 975 | break 976 | if name[0].isalpha() and idx > 0 and desc[idx-1].isalpha(): 977 | result = False 978 | break 979 | if name[-1].isdigit() and idx+len(name) < len(desc) and desc[idx+len(name)].isdigit(): 980 | # if name.lower() not in desc_tokens: 981 | result = False 982 | break 983 | if len(keywords) == 1 and count == 0 and not has_name: 984 | return False 985 | return result 986 | 987 | for attr in dir(self): 988 | if not isinstance(getattr(self, attr), RegisterBase) or\ 989 | attr.endswith("_Input") or attr.endswith("_Output"): 990 | continue 991 | reg = getattr(self, attr) 992 | desc = reg.get_full_name() + ": " + reg.desc 993 | if match(desc): 994 | print(desc) 995 | 996 | for att in dir(reg): 997 | if not isinstance(getattr(reg, att), BitField): 998 | continue 999 | field = getattr(reg, att) 1000 | if match(field.desc): 1001 | print(field.get_full_name() + ": " + field.desc) 1002 | 1003 | class Subscriptor(): 1004 | 1005 | def __init__(self, parent, name): 1006 | self.parent = parent 1007 | self.name = name 1008 | 1009 | def __getitem__(self, idx): 1010 | return getattr(self.parent, self.name.format(idx)) 1011 | 1012 | def __setitem__(self, idx, value): 1013 | return setattr(self.parent, self.name.format(idx), value) 1014 | 1015 | def __repr__(self): 1016 | if isinstance(self.parent, PeripheralBase): 1017 | names = [] 1018 | for idx in range(32): 1019 | attr = self.name.format(idx) 1020 | if hasattr(self.parent, attr): 1021 | names.append(attr) 1022 | self.parent.get_repr(names, True) 1023 | 1024 | else: 1025 | html = """\ 1026 | 1080 | 1081 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | """ 1088 | 1089 | for idx in range(32): 1090 | attr = self.name.format(idx) 1091 | if hasattr(self.parent, attr): 1092 | bf = getattr(self.parent, attr) 1093 | html += f"""\ 1094 | 1095 | 1096 | 1101 | 1102 | 1103 | """ 1104 | 1105 | html += """\ 1106 |
MaskFieldContent
{hex_repr(bf.mask)} 1097 |
{attr} 1098 | {bf.desc} 1099 |
1100 |
{repr(bf)}
1107 | """ 1108 | 1109 | ipydisp.display(ipydisp.HTML(html)) 1110 | 1111 | return "" 1112 | 1113 | def __set__(self, instance, value): 1114 | raise NotImplementedError(f"Please specify the index: {self.name.format('')}[n]") 1115 | 1116 | --------------------------------------------------------------------------------