|
| 1 | +"""Control the PSLab's UART bus and devices connected on the bus. |
| 2 | +
|
| 3 | +Examples |
| 4 | +-------- |
| 5 | +Set UART2 bus baudrate to 1000000: |
| 6 | +
|
| 7 | +>>> from pslab.bus.uart improt UART |
| 8 | +>>> bus = UART() |
| 9 | +>>> bus.configure(1e6) |
| 10 | +
|
| 11 | +Send a byte over UART: |
| 12 | +
|
| 13 | +>>> bus.write_byte(0x55) |
| 14 | +""" |
| 15 | + |
| 16 | +import sys |
| 17 | +from typing import Tuple |
| 18 | + |
| 19 | +import pslab.protocol as CP |
| 20 | +from pslab.serial_handler import SerialHandler |
| 21 | + |
| 22 | +__all__ = "UART" |
| 23 | +_BRGVAL = 0x22 # BaudRate = 460800. |
| 24 | +_MODE = (0, 0) # 8-bit data and no parity, 1 stop bit. |
| 25 | + |
| 26 | + |
| 27 | +class classmethod_(classmethod): |
| 28 | + """Support chaining classmethod and property.""" |
| 29 | + |
| 30 | + def __init__(self, f): |
| 31 | + self.f = f |
| 32 | + super().__init__(f) |
| 33 | + |
| 34 | + def __get__(self, obj, cls=None): |
| 35 | + """Check python version and return appropriate getter.""" |
| 36 | + # classmethod() to support chained decorators; new in python 3.9. |
| 37 | + if sys.version_info < (3, 9) and isinstance(self.f, property): |
| 38 | + return self.f.__get__(cls) |
| 39 | + else: |
| 40 | + return super().__get__(obj, cls) |
| 41 | + |
| 42 | + |
| 43 | +class _UARTPrimitive: |
| 44 | + """UART primitive commands. |
| 45 | +
|
| 46 | + Handles all the UART subcommands coded in pslab-firmware. |
| 47 | +
|
| 48 | + Parameters |
| 49 | + ---------- |
| 50 | + device : :class:`SerialHandler`, optional |
| 51 | + Serial connection to PSLab device. If not provided, a new one will be created. |
| 52 | + """ |
| 53 | + |
| 54 | + _MIN_BRGVAL = 0 |
| 55 | + _MAX_BRGVAL = 2 ** 16 - 1 |
| 56 | + |
| 57 | + _brgval = _BRGVAL |
| 58 | + _mode = _MODE |
| 59 | + |
| 60 | + def __init__(self, device: SerialHandler = None): |
| 61 | + self._device = device if device is not None else SerialHandler() |
| 62 | + |
| 63 | + @classmethod_ |
| 64 | + @property |
| 65 | + def _baudrate(cls) -> float: |
| 66 | + return cls._get_uart_baudrate(cls._brgval) |
| 67 | + |
| 68 | + @staticmethod |
| 69 | + def _get_uart_brgval(baudrate: float, BRGH: int = 1) -> int: |
| 70 | + return round(((CP.CLOCK_RATE / baudrate) / (4 if BRGH else 16)) - 1) |
| 71 | + |
| 72 | + @staticmethod |
| 73 | + def _get_uart_baudrate(brgval: int, BRGH: int = 1) -> float: |
| 74 | + return (CP.CLOCK_RATE / (brgval + 1)) / (4 if BRGH else 16) |
| 75 | + |
| 76 | + @staticmethod |
| 77 | + def _save_config(brgval: int = None, mode: Tuple[int] = None): |
| 78 | + """Save the UART barval and mode bits. |
| 79 | +
|
| 80 | + Parameters |
| 81 | + ---------- |
| 82 | + brgval : int, optional |
| 83 | + Set value to `_UARTPrimitive._brgval`. Will be skipped if None. |
| 84 | + Defaults to None. |
| 85 | + mode : tuple of int, optional |
| 86 | + Set value to `_UARTPrimitive._mode`. Will be skipped if None. |
| 87 | + Defaults to None. |
| 88 | + """ |
| 89 | + if brgval is not None: |
| 90 | + _UARTPrimitive._brgval = brgval |
| 91 | + if mode is not None: |
| 92 | + _UARTPrimitive._mode = mode |
| 93 | + |
| 94 | + def _set_uart_baud(self, baudrate: int): |
| 95 | + """Set the baudrate of the UART bus. |
| 96 | +
|
| 97 | + It is a primitive UART method, prefered to use :meth:`UART.configure`. |
| 98 | +
|
| 99 | + Parameters |
| 100 | + ---------- |
| 101 | + baudrate : int |
| 102 | + Baudrate to set on the UART bus. |
| 103 | +
|
| 104 | + Raises |
| 105 | + ------ |
| 106 | + ValueError |
| 107 | + If given baudrate in not supported by PSLab board. |
| 108 | + """ |
| 109 | + brgval = self._get_uart_brgval(baudrate) |
| 110 | + |
| 111 | + if self._MIN_BRGVAL <= brgval <= self._MAX_BRGVAL: |
| 112 | + self._device.send_byte(CP.UART_2) |
| 113 | + self._device.send_byte(CP.SET_BAUD) |
| 114 | + self._device.send_int(brgval) |
| 115 | + self._device.get_ack() |
| 116 | + self._save_config(brgval=brgval) |
| 117 | + else: |
| 118 | + min_baudrate = self._get_uart_baudrate(self._MIN_BRGVAL) |
| 119 | + max_baudrate = self._get_uart_baudrate(self._MAX_BRGVAL) |
| 120 | + e = f"Baudrate must be between {min_baudrate} and {max_baudrate}." |
| 121 | + raise ValueError(e) |
| 122 | + |
| 123 | + def _set_uart_mode(self, pd: int, st: int): |
| 124 | + """Set UART mode. |
| 125 | +
|
| 126 | + Parameters |
| 127 | + ---------- |
| 128 | + pd : {0, 1, 2, 3} |
| 129 | + Parity and data selection bits. |
| 130 | + {0: 8-bit data and no parity, |
| 131 | + 1: 8-bit data and even parity, |
| 132 | + 2: 8-bit data and odd parity, |
| 133 | + 3: 9-bit data and no parity} |
| 134 | + st : {0, 1} |
| 135 | + Selects number of stop bits for each one-byte UART transmission. |
| 136 | + {0: one stop bit, |
| 137 | + 1: two stop bits} |
| 138 | +
|
| 139 | + Raises |
| 140 | + ------ |
| 141 | + ValueError |
| 142 | + If any one of arguments is not in its shown range. |
| 143 | + RuntimeError |
| 144 | + If this functionality is not supported by the firmware. |
| 145 | + Since it is newly implemented, earlier firmware version don't support. |
| 146 | + """ |
| 147 | + error_message = [] |
| 148 | + if pd not in range(0, 4): |
| 149 | + error_message.append("Parity and data selection bits must be 2-bits.") |
| 150 | + if st not in (0, 1): |
| 151 | + error_message.append("Stop bits select must be a bit.") |
| 152 | + |
| 153 | + self._device.send_byte(CP.UART_2) |
| 154 | + self._device.send_byte(CP.SET_MODE) |
| 155 | + |
| 156 | + # Verifying whether the firmware support current subcommand. |
| 157 | + if self._device.get_byte() == 4: |
| 158 | + self._device.send_byte((pd << 1) | st) |
| 159 | + self._device.get_ack() |
| 160 | + self._save_config(mode=(pd, st)) |
| 161 | + else: |
| 162 | + raise RuntimeError( |
| 163 | + "This firmware version doesn't support this functionality." |
| 164 | + ) |
| 165 | + |
| 166 | + def _read_uart_status(self) -> int: |
| 167 | + """Return whether receive buffer has data. |
| 168 | +
|
| 169 | + Returns |
| 170 | + ------- |
| 171 | + status : int |
| 172 | + 1 if at least one more character can be read else 0. |
| 173 | + """ |
| 174 | + self._device.send_byte(CP.UART_2) |
| 175 | + self._device.send_byte(CP.READ_UART2_STATUS) |
| 176 | + return self._device.get_byte() |
| 177 | + |
| 178 | + def _write_byte(self, data: int): |
| 179 | + """Write a single byte to the UART bus. |
| 180 | +
|
| 181 | + It is a primitive UART method, prefered to use :meth:`UART.write_byte`. |
| 182 | +
|
| 183 | + Parameters |
| 184 | + ---------- |
| 185 | + data : int |
| 186 | + Byte value to write to the UART bus. |
| 187 | + """ |
| 188 | + self._device.send_byte(CP.UART_2) |
| 189 | + self._device.send_byte(CP.SEND_BYTE) |
| 190 | + self._device.send_byte(data) |
| 191 | + self._device.get_ack() |
| 192 | + |
| 193 | + def _write_int(self, data: int): |
| 194 | + """Write a single int to the UART bus. |
| 195 | +
|
| 196 | + It is a primitive UART method, prefered to use :meth:`UART.write_int`. |
| 197 | +
|
| 198 | + Parameters |
| 199 | + ---------- |
| 200 | + data : int |
| 201 | + Int value to write to the UART bus. |
| 202 | + """ |
| 203 | + self._device.send_byte(CP.UART_2) |
| 204 | + self._device.send_byte(CP.SEND_INT) |
| 205 | + self._device.send_int(data) |
| 206 | + self._device.get_ack() |
| 207 | + |
| 208 | + def _read_byte(self) -> int: |
| 209 | + """Read a single byte from the UART bus. |
| 210 | +
|
| 211 | + It is a primitive UART method, prefered to use :meth:`UART.read_byte`. |
| 212 | +
|
| 213 | + Returns |
| 214 | + ------- |
| 215 | + data : int |
| 216 | + A Byte interpreted as a uint8 read from the UART bus. |
| 217 | + """ |
| 218 | + self._device.send_byte(CP.UART_2) |
| 219 | + self._device.send_byte(CP.READ_BYTE) |
| 220 | + return self._device.get_byte() |
| 221 | + |
| 222 | + def _read_int(self) -> int: |
| 223 | + """Read a two byte value from the UART bus. |
| 224 | +
|
| 225 | + It is a primitive UART method, prefered to use :meth:`UART.read_int`. |
| 226 | +
|
| 227 | + Returns |
| 228 | + ------- |
| 229 | + data : int |
| 230 | + Two bytes interpreted as a uint16 read from the UART bus. |
| 231 | + """ |
| 232 | + self._device.send_byte(CP.UART_2) |
| 233 | + self._device.send_byte(CP.READ_INT) |
| 234 | + return self._device.get_int() |
| 235 | + |
| 236 | + |
| 237 | +class UART(_UARTPrimitive): |
| 238 | + """UART2 bus. |
| 239 | +
|
| 240 | + Parameters |
| 241 | + ---------- |
| 242 | + device : :class:`SerialHandler`, optional |
| 243 | + Serial connection to PSLab device. If not provided, a new one will be created. |
| 244 | + """ |
| 245 | + |
| 246 | + def __init__(self, device: SerialHandler = None): |
| 247 | + super().__init__(device) |
| 248 | + # Reset baudrate and mode |
| 249 | + self.configure(self._get_uart_baudrate(_BRGVAL)) |
| 250 | + |
| 251 | + try: |
| 252 | + self._set_uart_mode(*_MODE) |
| 253 | + except RuntimeError: |
| 254 | + pass |
| 255 | + |
| 256 | + def configure(self, baudrate: float): |
| 257 | + """Configure UART bus baudrate. |
| 258 | +
|
| 259 | + Parameters |
| 260 | + ---------- |
| 261 | + baudrate : float |
| 262 | +
|
| 263 | + Raises |
| 264 | + ------ |
| 265 | + ValueError |
| 266 | + If given baudrate is not supported by PSLab board. |
| 267 | + """ |
| 268 | + self._set_uart_baud(baudrate) |
| 269 | + |
| 270 | + def write_byte(self, data: int): |
| 271 | + """Write a single byte to the UART bus. |
| 272 | +
|
| 273 | + Parameters |
| 274 | + ---------- |
| 275 | + data : int |
| 276 | + Byte value to write to the UART bus. |
| 277 | + """ |
| 278 | + self._write_byte(data) |
| 279 | + |
| 280 | + def write_int(self, data: int): |
| 281 | + """Write a single int to the UART bus. |
| 282 | +
|
| 283 | + Parameters |
| 284 | + ---------- |
| 285 | + data : int |
| 286 | + Int value to write to the UART bus. |
| 287 | + """ |
| 288 | + self._write_int(data) |
| 289 | + |
| 290 | + def read_byte(self) -> int: |
| 291 | + """Read a single byte from the UART bus. |
| 292 | +
|
| 293 | + Returns |
| 294 | + ------- |
| 295 | + data : int |
| 296 | + A Byte interpreted as a uint8 read from the UART bus. |
| 297 | + """ |
| 298 | + return self._read_byte() |
| 299 | + |
| 300 | + def read_int(self) -> int: |
| 301 | + """Read a two byte value from the UART bus. |
| 302 | +
|
| 303 | + It is a primitive UART method, prefered to use :meth:`UART.read_int`. |
| 304 | +
|
| 305 | + Returns |
| 306 | + ------- |
| 307 | + data : int |
| 308 | + Two bytes interpreted as a uint16 read from the UART bus. |
| 309 | + """ |
| 310 | + return self._read_int() |
0 commit comments