def adc_read(port, pin, raw=False): """ Reads a raw or converted ADC value. Args: port: The port of the GPIO pin to read from. pin: The pin number of the GPIO pin to read from. raw: If raw is True, a raw read should be performed; otherwise a converted read. Defaults to converted. Returns: The ADC reading as a 16-bit integer. Raises: ValueError: if the range of the input args is invalid. Exception: if we receive a nonzero status code. """ # If port is entered as a str, convert to int if isinstance(port, str): port = getattr(GpioPort, port.capitalize()) # Check the ranges of input args if port < 0 or port >= GpioPort.NUM_GPIO_PORTS: raise ValueError("ERROR: invalid GPIO port") if pin < 0 or pin >= NUM_PINS_PER_PORT: raise ValueError("ERROR: invalid GPIO pin number") if raw not in (True, False): raise ValueError("ERROR: raw must be a bool value") # Send CAN message data = [ (port, 1), (pin, 1), (raw, 1), ] data = can_util.can_pack(data) can_util.send_message(babydriver_id=BabydriverMessageId.ADC_READ_COMMAND, data=data) # Wait to receive the first CAN message with data data_msg = can_util.next_message(babydriver_id=BabydriverMessageId.ADC_READ_DATA) # Extract the low and high bytes of the ADC conversion result_low = data_msg.data[1] result_high = data_msg.data[2] # Wait to receive the second CAN message with status status_msg = can_util.next_message(babydriver_id=BabydriverMessageId.STATUS) status = status_msg.data[1] # Check if status is not ok if status != OK_STATUS: raise Exception("ERROR: received a nonzero STATUS_CODE: {}".format(status)) return (result_high << 8) | result_low
def i2c_read(port, address, rx_len, reg=None): """ Reads a number of bytes over I2C Args: port: I2C port (1 or 2) to read from address: I2C address to read from rx_len: expected response length reg: register to read from, or None if it is a normal read (non-register read) Raises: ValueError: if parameters passed into i2c_read are invalid Exception: if a non-zero status code was received """ is_reg = 0 if reg is None: reg = 0 else: is_reg = 1 if port not in (1, 2): raise ValueError("Expected port of 1 or 2 ") if address < 0 or address > 255: raise ValueError("Expected an uint8 address between 0 and 255") if rx_len < 0 or rx_len > 255: raise ValueError( "Expected an uint8 response length value between 0 and 255") if reg < 0 or reg > 255: raise ValueError("Expected an uint8 register between 0 and 255") # shift port to 0 or 1, and send can message data_pack = can_util.can_pack([(port - 1, 1), (address, 1), (rx_len, 1), (is_reg, 1), (reg, 1)]) can_util.send_message(babydriver_id=BabydriverMessageId.I2C_READ_COMMAND, data=data_pack) read_data = [] num_msgs = ceil(rx_len / 7) # Loop to receive the ceil(rx_len/7) CAN messages, with only the # last 7 bytes of each msg being data and storing only rx_len bytes # of data to the read_data list for message in range(num_msgs): read_msg = can_util.next_message( babydriver_id=BabydriverMessageId.I2C_READ_DATA) if message == (num_msgs - 1) and (rx_len % 7) != 0: read_data.extend(read_msg.data[1:rx_len % 7 + 1]) else: read_data.extend(read_msg.data[1:8]) # Wait to receive the status CAN message status_msg = can_util.next_message( babydriver_id=BabydriverMessageId.STATUS) status = status_msg.data[1] # Check if status is not STATUS_CODE_OK (0) if status != 0: raise Exception("Received STATUS_CODE {}".format(status)) return read_data
def can_send_raw(msg_id, data, device_id=BABYDRIVER_DEVICE_ID, channel=None): """ A wrapper over can_utils.send_message providing a friendlier interface to can_util.send_message. Args: msg_id: CAN message ID (if it's for babydriver messages, then msg_id=BABYDRIVER_CAN_MESSAGE_ID) data: a list of up to 8 bytes of data to send device_id: the device ID to send from channel: CAN channel """ can_util.send_message( data=data, channel=channel, msg_id=msg_id, device_id=device_id, )
def i2c_write(port, address, tx_bytes, reg=None): """ Writes over I2C to the given port/address Args: port: port of the I2C to write to (1 or 2) address: I2C address to write to (0-255) tx_bytes: list of bytes to write over I2C (a list of ints 0-255) reg: the register to write to, or None if no register is required Raises: Exception: if a non-zero status code was received """ if port not in (1, 2): raise ValueError("Expected port of 1 (I2C_PORT_1) or 2 (I2C_PORT_2)") if address < 0 or address > 255: raise ValueError("Expected address between 0 and 255") if not all(0 <= byte < 256 for byte in tx_bytes): raise ValueError("Expected list of bytes between 0 and 255") is_reg = 0 if reg is None: reg = 0 else: is_reg = 1 if reg < 0 or reg > 255: raise ValueError("Expected register to write to between 0 and 255") can_pack = can_util.can_pack([(port - 1, 1), (address, 1), (len(tx_bytes), 1), (is_reg, 1), (reg, 1)]) can_util.send_message( babydriver_id=BabydriverMessageId.I2C_WRITE_COMMAND, data=can_pack, ) # Sends CAN message for i in range(0, len(tx_bytes), 7): can_util.send_message( babydriver_id=BabydriverMessageId.I2C_WRITE_DATA, data=tx_bytes[i:i + 7], ) status_msg = can_util.next_message( babydriver_id=BabydriverMessageId.STATUS) # Raises Exception if status is non-OK if status_msg.data[1] != 0: raise Exception("Received STATUS_CODE {}".format(status_msg.data[1]))
def unregister_gpio_interrupt(port, pin): """ Unregisters a gpio interrupt on a pin Args: port: Port of the GPIO pin to register an interrupt in pin: Pin number of the GPIO pin to register an interrupt in Raises: Value error: if the parameters passed into register_gpio_interrupt are incorrect Attribute error: if the port parameter is invalid (refer gpio_port.py for acceptable port parameters) Exception: if a non-zero status code is received when attempting to uregister an interrupt Key error: if no interrupt is registered in the given port and pin """ if isinstance(port, str): port = getattr(GpioPort, port.capitalize()) if port < 0 or port >= GpioPort.NUM_GPIO_PORTS: raise ValueError("invalid GPIO port (Range: 'A' - 'F')") if pin < 0 or pin >= NUM_PINS_PER_PORT: raise ValueError("Invalid GPIO pin number (Range: 0 - 15)") msg_data_unregister_gpio_interrupt = [(port, 1), (pin, 1)] can_util.send_message( babydriver_id=message_defs.BabydriverMessageId.GPIO_IT_UNREGISTER_COMMAND, data=can_util.can_pack(msg_data_unregister_gpio_interrupt) ) status_message = can_util.next_message(babydriver_id=message_defs.BabydriverMessageId.STATUS) received_status = status_message.data[1] # Check if status is invalid (0 refers to STATUS_CODE_OK) if received_status != 0: raise Exception("Received a nonzero STATUS_CODE: {}".format(received_status)) # Clearing callback related to the interrupt that was unregistered if (port, pin) not in callback_dict: raise KeyError("No interrupt registered on given port and pin") del callback_dict[(port, pin)]
def gpio_get(port, pin): ''' Returns the state of the GPIO pin at the given port and pin number as a bool The port can be entered as either a string or int value (e.g. 'A' or 0). The pin is an int value ''' if isinstance(port, str): port = getattr(GpioPort, port.capitalize()) if port < 0 or port >= GpioPort.NUM_GPIO_PORTS: raise ValueError("ERROR: invalid GPIO port") if pin < 0 or pin >= GPIO_PINS_PER_PORT: raise ValueError("ERROR: invalid GPIO pin number") data = can_util.can_pack([(port, 1), (pin, 1)]) can_util.send_message( babydriver_id=BabydriverMessageId.GPIO_GET_COMMAND, data=data, ) gpio_data_msg = can_util.next_message( babydriver_id=BabydriverMessageId.GPIO_GET_DATA, ) status_msg = can_util.next_message( babydriver_id=BabydriverMessageId.STATUS, ) status = status_msg.data[1] if status != 0: raise Exception("ERROR: Non-OK status returned: {}".format(status)) raw_state = gpio_data_msg.data[1] gpio_pin_state = bool(raw_state) return gpio_pin_state
def gpio_set(port, pin, state): """ Sets a GPIO pin Args: port: port of the GPIO pin to set (can be entered as either a string or integer) pin: pin number of the GPIO pin to set state: 0 if setting low, 1 if setting high Raises: AttributeError: if port parameter is entered as a string and is invalid ValueError: if parameters passed into gpio_set are invalid Exception: if a non-zero status code was received """ # If given port as a string, it is converted to a string if isinstance(port, str): port = getattr(GpioPort, port.capitalize()) # Checks whether valid parameters were passed into gpio_set if port < 0 or port >= GpioPort.NUM_GPIO_PORTS: raise ValueError("Expected port between A and {}".format( chr(GpioPort.NUM_GPIO_PORTS + ord('A') - 1) )) if pin < 0 or pin >= NUM_PINS_PER_PORT: raise ValueError("Expected pin between 0 and {}".format(NUM_PINS_PER_PORT - 1)) if state not in (0, 1): raise ValueError("Expected state of 0 (low) or 1 (high)") can_pack_args = [(port, 1), (pin, 1), (state, 1)] can_util.send_message( babydriver_id=BabydriverMessageId.GPIO_SET, data=can_util.can_pack(can_pack_args) ) gpio_set_status = can_util.next_message(babydriver_id=BabydriverMessageId.STATUS) # Checks for valid status code if gpio_set_status.data[1] != 0: raise Exception("Received STATUS_CODE {}".format(gpio_set_status.data[1]))
def register_gpio_interrupt(port, pin, edge=InterruptEdge.RISING_AND_FALLING, callback=None): """ Registers a gpio interrupt on a pin Args: port: port of the GPIO pin to register an interrupt in pin: pin number of the GPIO pin to register an interrupt in edge: callback function is called when this interrupt edge occurs. Can be entered as a string or a number (RISING (0), FALLING (1) or RISING_AND_FALLING (2)) callback: if callback is None, a default callback function will be called that prints "Interrupt on P<port><pin>: <edge>" in this format. The user-defined callback function should follow this format: function_name(info) where the only parameter is info, a named tuple which holds the port (info.port),pin (info.pin) and edge (info.edge) of the GPIO interrupt that occured. Raises: Value error: if the parameters passed into register_gpio_interrupt are incorrect Attribute error: if the port parameter or interrupt edge is invalid (refer gpio_port.py for acceptable port parameters and InterruptEdge for appropriate edge parameters) Exception: if a non-zero status code is received when attempting to register an interrupt Type error: if the callback function (called when interrupt occurs) is of incorrect format Note: There is a hard STM32 limit that only one GPIO interrupt can be registered at a time per pin number. For example, PA2 and PB2 would share the same GPIO pin. """ if isinstance(port, str): port = getattr(GpioPort, port.capitalize()) if port < 0 or port >= GpioPort.NUM_GPIO_PORTS: raise ValueError("invalid GPIO port (Range: 'A' - 'F')") if pin < 0 or pin >= NUM_PINS_PER_PORT: raise ValueError("Invalid GPIO pin number (Range: 0 - 15)") if isinstance(edge, str): if edge.upper() not in InterruptEdge.__members__: raise AttributeError("Enter 'RISING', 'FALLING' or 'RISING_AND_FALLING' for " "interrupt edge") edge = InterruptEdge[edge.upper()] if edge < 0 or edge >= InterruptEdge.NUM_INTERRUPT_EDGES: raise ValueError("invalid interrupt edge (enter 0 (Rising), 1 (Falling) " "or 2 (Rising_and_falling") if callback is not None and not callable(callback): raise ValueError("invalid callback function") msg_data_register_gpio_interrupt = [(port, 1), (pin, 1), (edge, 1)] can_util.send_message( babydriver_id=message_defs.BabydriverMessageId.GPIO_IT_REGISTER_COMMAND, data=can_util.can_pack(msg_data_register_gpio_interrupt) ) status_message = can_util.next_message(babydriver_id=message_defs.BabydriverMessageId.STATUS) received_status = status_message.data[1] # Check if status is invalid (0 refers to STATUS_CODE_OK) if received_status != 0: raise Exception("received a nonzero STATUS_CODE: {}".format(received_status)) # Adding callback to dictionary to be called upon when interrupt occurs if callback is None: callback_dict[(port, pin)] = default_callback else: callback_dict[(port, pin)] = callback
def spi_exchange(tx_bytes, rx_len, spi_port=1, spi_mode=0, baudrate=6000000, cs=None): """ Sends data through SPI The tx_bytes is an int array The rx_len is a non-negative int. The spi_port is a non-negative int. The spi_mode is a non-negative int. The baudrate is a non-negative int. The CS pin, if not None, is a tuple (port, pin), where pin is an int in [0, NUM_PINS_PER_PORT) and port is either an int in [0, NUM_GPIO_PORTS), or a string 'a' through 'f', case insensitive Args: tx_bytes: The bytes to TX. rx_len: Number of bytes to RX spi_port: port of the pin to perform SPI exchange on (1 or 2) spi_mode: SPI mode to use (0, 1, 2 or 3) baudrate: baudrate to use (defaults to 6000000) cs: chip select port and pin to use (defaults to (1, 1)) Raises: AttributeError: if cs_port parameter is entered as a string and is invalid ValueError: if spi_port, spi_mode, rx_len, cs_port, cs_pin don't meet their requirements Exception: if a non-zero status code was received. """ if spi_port not in (1, 2): raise ValueError("ERROR: Expected SPI port 1 or 2") if spi_mode not in (0, 1, 2, 3): raise ValueError("ERROR: Expected mode between 0 and 3") # pylint: disable=superfluous-parens if not (0 <= len(tx_bytes) <= 255): raise ValueError( "ERROR: numbers of tx_bytes must be between 0 and 255") # pylint: disable=superfluous-parens if not (0 <= rx_len <= 255): raise ValueError( "ERROR: rx_len must be a non-negative integer between 0 and 255") if baudrate < 0: raise ValueError("ERROR: baudrate must be a non-negative integer") cs_port = 0 if cs is None else cs[0] cs_pin = 0 if cs is None else cs[1] use_cs = 0 if cs is None else 1 if isinstance(cs_port, str): cs_port = getattr(GpioPort, cs_port.capitalize()) if cs_port < 0 or cs_port >= GpioPort.NUM_GPIO_PORTS: raise ValueError("ERROR: Expected CS port between A and {}".format( chr(GpioPort.NUM_GPIO_PORTS + ord('A') - 1))) if cs_pin < 0 or cs_pin >= GPIO_PINS_PER_PORT: raise ValueError("ERROR: Expected CS pin between 0 and {}".format( GPIO_PINS_PER_PORT - 1)) data1 = [ (spi_port - 1, 1), # 0 for SPI_PORT 1, 1 for SPI_PORT_2 (spi_mode, 1), (len(tx_bytes), 1), # tx_len (rx_len, 1), (cs_port, 1), (cs_pin, 1), (use_cs, 1), ] data2 = [(baudrate, 4)] can_util.send_message( babydriver_id=BabydriverMessageId.SPI_EXCHANGE_METADATA_1, data=can_util.can_pack(data1)) can_util.send_message( babydriver_id=BabydriverMessageId.SPI_EXCHANGE_METADATA_2, data=can_util.can_pack(data2)) # collect bytes into groups of 7 chunks = [tx_bytes[x:x + 7] for x in range(0, len(tx_bytes), 7)] for data in chunks: # pad with 0s if length isn't 7 if len(data) < 7: data += [0] * (7 - len(data)) tmp = [(d, 1) for d in data] can_util.send_message( babydriver_id=BabydriverMessageId.SPI_EXCHANGE_TX_DATA, data=can_util.can_pack(tmp)) # Receive bytes rx_bytes_left = rx_len rx_data = [] while rx_bytes_left > 0: rx_msg = can_util.next_message( babydriver_id=BabydriverMessageId.SPI_EXCHANGE_RX_DATA) msg_data = rx_msg.data # list constructor to handle bytearrays rx_data += list(msg_data[1:min(rx_bytes_left + 1, 8)]) rx_bytes_left -= 7 status = can_util.next_message(babydriver_id=BabydriverMessageId.STATUS) if status.data[1] != 0: raise Exception("ERROR: Received non-zero status code: {}".format( status.data[1])) return rx_data