Microcontroller Driver - arenaudineau/AwesomeArray-PythonDriver Wiki

For now, the lib is part of the Awesome Array Driver. You can import it with

from aad import mcd

If error messages appear, please refer to the Awesome Array Driver Wiki


m = mcd.MCDriver()

Several errors may occur:

Microcontroller not found

Exception: µc not found, please verify its connection or specify its PID

Make sure the microcontroller is connected. There are two USB port on the board:

  • PWR is used to power and program it and is NOT used by the driver, it may be connected to the PC the driver is run from, or another one ;
  • USER is used for serial communication and IS used by the driver, it must be connected to the PC the driver is run from ;

By default, the driver is looking for a USB device with a Product IDentifier (PID): 22336.
For some reason the board may not have this one. You can either change it by reprogramming the microcontroller (in STM32Cube, in the project, ioc file, search for USB_DEVICE on the left panel, go down, Device Descriptor panel, PID (Product IDentifier)) or provide it to the driver:
mcd.MCDriver.print_ports() will show every device connected and its associated PID. (⚠️ This is NOT STMicroelectronics STLink Virtual COM Port)

You can then use:

m = mcd.MCDriver(pid={correct_pid})

Microcontroller port already open

serial.serialutil.SerialException: could not open port '{PORT}': PermissionError(13, 'Access is denied.', None, 5)

The serial connection is already opened somewhere else, check if there are no other instance of MCDriver running (for example in another Jupyter Notebook ; you may want to interrupt/restart the kernel)

Lost microcontroller connection

serial.serialutil.SerialException: WriteFile failed (PermissionError(13, 'The device does not recognize the command.', None, 22))

This error will not happen right after the initialization but can appear when using it. It means the USB cable have been plugged out or that the connection has timed out.

Usage for the Awesome Array Driver

rv = m.cmd(argument0, argument1, ...) # with rv = None if cmd is a 'procedure' command

Commands reference

  • set_sr: ['procedure' command]
    Set Shift Register input bit

    • Argument 0: Shift Register identifier (see SR_LIST)
    • Argument 1: Input bit state (State.RESET or State.SET)
  • fill_srs: ['procedure' command]
    Fill the five Shift Registers

    • Arguments 0->7: Value of SR.WLE, encoded with little endianness
    • Arguments 8->15: Value of SR.WLO, encoded with little endianness
    • Arguments 16->23: Value of SR.SL , encoded with little endianness
    • Arguments 24->31: Value of SR.BL , encoded with little endianness
    • Arguments 32->39: Value of SR.BLB, encoded with little endianness
  • set_cs: ['procedure' command]
    Set a Control Signal

    • Argument 0: Control Signal identifier (see CS_LIST)
    • Argument 1: Input bit state (State.RESET or State.SET)
  • set_adr_r: ['procedure' command]
    Set Row address

    • Argument 0: The 5-bit address (int or bytes)
  • set_adr_c: ['procedure' command]
    Set Column address

    • Argument 0: The 5-bit address (int or bytes)
  • get_ctl: ['function' command]
    Get Shift Registers sanity bit

    • Argument 0: Shift Register identifier (see SR_LIST)
    • Returns the bit state (init a State with it for easier manipulation)
  • clk: ['procedure' command]
    Clock once

  • clk_sr: ['procedure' command]
    Clock once the shift registers

  • clock_xnor: ['procedure' command]
    Clock once the XNOR

  • ack_mode: [special command]
    Specify the commands which should send acknowledge after completion.

    • Argument 0: Flag of the commands (ACK.NONE, ACK_ALL or combinaison of ACK.{CMD_NAME_UPPERCASE} enum, see ACK_LIST)
    • Returns ack for ACK_MODE, if Argument 0 != ACK.NONE
    • Details The ack sent is the two bytes [0xAA, {CMD_CODE}]
    • Details There is no ack for commands that already returns data.
  • debug_echo: ['function' command]
    Sends back the provided argument

    • Argument 0: byte (only one) to echo
    • Returns the byte provided
  • debug_led: ['function' command]
    Turn on the three board LEDs for 2s

    • Returns b'Hello, C2N!'

General Usage

This driver is designed to be a generic interface between the microcontroller that will control the chip to test, and Python. Therefore each of its components are made to be extended very easily.

Every Python enumerations (in aad/mcd.py) used in the driver must exactly match the ones on the µc.
The main one is the commands (CMD) enum which are the instructions programmed on the microcontroller itself:

class CMD(IntEnum):
  SET_SR     = 0
  FILL_SRs   = en_auto() # Will return the next enum val, i.e. 1
  SET_CS     = en_auto() # 2
  SET_ADR_R  = en_auto() # 3
  SET_ADR_C  = en_auto() # etc...

  GET_CTL    = en_auto()

  CLK        = en_auto()
  CLK_SR     = en_auto()
  CLK_XNOR   = en_auto()

  ACK_MODE   = en_auto()

  DEBUG_ECHO = en_auto()
  DEBUG_LED  = en_auto()

For any item of this enumeration, MCDriver will generate a function of the exact same name, in lower case (e.g. MCDriver.set_adr_c for CMD.SET_ADR_C) taking any argument(s) (no verification is done, it is up to the user to respect the commands reference) and may return a value.

The parameters can either be integers - that will be converted into little-endian bytes (Least Significant Bit at the beginning) using the function mcd.as_bytes({int_param}) - or directly raw bytes.
The return value (if there is one) is a (an array of) byte(s), the endianness is µc-implementation-defined (refer to the reference again) but may be converted to integer afterward when they are little-endian bytes with mcd.as_int({little_endian_bytes}).

Whether the command will return a value or not depend on the nature of it:

A command that returns something is called a function command, one that does not is called a procedure command. For the MCDriver to know what type the command is, one must add the procedure commands to the ACK flag (with again, an exact match to the C equivalent):

class ACK(IntFlag):
  NONE      = 0x00
  SET_SR    = en_auto() # For a flag, the next values are 1, 2, 4, 8, 16, ...
  FILL_SRs  = en_auto()
  SET_CS    = en_auto()
  SET_ADR_R = en_auto()
  SET_ADR_C = en_auto()
  CLK       = en_auto()
  CLK_SR    = en_auto()
  CLK_XNOR  = en_auto()

⚠️ There is a TODO: for now, the max count of procedure command is 7 because the ACK is coded on 8 bits

If a command is defined as a function (i.e. not defined as a procedure), calling it will block until Python actually receive the return value from the microcontroller.
If a command is defined as a procedure, calling it will block until Python receive an ACKnowledgment if it has been asked for, and wont block otherwise.

ACKs are messages sent from the microcontroller to Python to acknowledge the complete execution of the procedure, in order not to call another one before the µc can handle it.

By default, ACK is not asked for any command (except when initialized with the AwesomeArrayDriver which activate them all). To change this setting, you must use the command ACK_MODE:

m.ack_mode(mcd.ACK.SET_SR | mcd.ACK.FILL_SRs) # Will now ask ACKs for the cmds SET_SR and FILL_SRs
m.ack_mode(mcd.ACK_ALL) # Will now ask ACKs for every cmds
m.ack_mode(mcd.ACK_NONE) # Will now never ask for ACKs

ℹ️ ACK_MODE will itself send an ACK if and only if its parameter is not mcd.ACK_NONE. It is all handled by the driver.