Skip to content
Daniel Campora edited this page Aug 24, 2015 · 115 revisions

The aim is to make the pyb module obsolete, make all its functionality cleaner and more board-generic, and move such functionality elsewhere. Below is the current proposal, very much a work in progress.

Design goals and priciples

The main aim is to provide Python modules/functions/classes that abstract the hardware in a Pythonic way.

The API should be Pythonic, obvious and relatively minimal. There should be a close mapping from functions to hardware and there should be as little magic as possible. The API should be as consistent across peripherals (Pin, UART, I2C, ADC, etc) as possible. There should usually be only one way to do something. A method name should do exactly what it says and no more (ie it shouldn't be heavily overloaded).

Use cases

Create a UART with various pin configurations:

uart = UART(0) # reference (existing) UART(0) entity, without (re-)initialising it
uart = UART(0, 9600) # create and initialise UART(0) using default pins, no flow control
uart = UART(1, 9600, 8, pins=('GP1', 'GP2')) # specified pins, no hardware flow control
uart = UART(1, 9600, 8, pins=('GP1', 'GP2', 'GP7', 'GP6')) # RTS/CTS flow control
uart = UART(1, 9600, 8, pins=('GP1', None)) # Tx only
uart = UART(1, 9600, 8, pins=(None, 'GP2')) # Rx only
uart = UART(1, 9600, 8, pins=('GP1')) # raise 
uart = UART(1, 9600, 8, pins=('GP1', 'GP2', 'GP7')) # raise
uart = UART(1, 9600, 8, pins=('GP1', 'GP2', 'GP7', None)) # OK, RTS only

Create a UART and enable pull-up on the pins:

# create and initialize UART1 with TX and RX on GP1 and GP2 respectively.
UART(1, 9600, pins=('GP1', 'GP2')) 
# enable the pull-ups on both UART1 pins
Pin('GP1', mode=Pin.ALT, pull=Pin.PULL_UP)
Pin('GP2', mode=Pin.ALT, pull=Pin.PULL_UP)

These use-cases need to be written:

  • Basic I/O on a pin
  • PWM on a pin
  • ADC on a pin
  • Using a Timer to do a one-shot callback
  • Using a Timer to do a repeated callback

General conventions of the API

You make objects corresponding to physical entities in the MCU (eg Pin, UART, Timer). Some entities like UART can be connected to Pin's.

Conventions are:

  • Selection of a peripheral/entity is done by an id. A port must allow to specify ids by a sequential integer starting at 0 and going to MAX (MAX is a constant defined in the periph class). Ports can optionally allow to specify periphs by a string (this allows to write portable code across boards that provide similar periphs, specify by MCU name, etc).

  • All periphs provide a constructor, .init() and .deinit().

  • If a periph is constructed just using its id then it is not (re-)initialised. This allows to reference existing periphs without changing them.

  • When connecting a periph to some pins, one uses the pins= keywoard in the constructor/init function. This is a tuple/list of pins, where each pin can be an integer, string or Pin instance.

In the method specs below, NOHEAP means the function cannot allocate on the heap. For some ports this may be difficult if the function needs to return an integer value that does not fit in a small integer. Don't know what to do about this.

New hardware module

The classes to control the peripherals of the board will reside in a new module called hardware, therefore, by doing:

import hardware
dir(hardware)

one can easily see what's supported on the board.

The Pin class

Create and init a pin

pin = Pin(id, mode, pull=Pin.PULL_NONE, *, value, drive, slew, alt, ...)

  • id, and mode are mandatory and positional (mode can kw).
  • pull is optional and positional (also can be named).
  • The rest of args are kwonly.
  • Only value is required for a port to implement (initial value if given).
  • The rest are optional, and a port can define them and also define others.
  • alt specifies the alternate function, it is optional and the values it takes vary per port. Exists to allow advanced pin operations for ports that support it.

Methods

Do we really need 3 ways to set the value?

  • pin.init(...) re init.
  • pin.high() NOHEAP; set high.
  • pin.low() NOHEAP; set low.
  • pin.value() NOHEAP; get value.
  • pin.value(x) NOHEAP; set value.
  • pin() NOHEAP; fast method to get the value of the pin.
  • pin(value) NOHEAP; fast method to set the value of the pin.
  • pin.toggle() NOHEAP

Getters and setters

  • pin.id() NOHEAP; get only. - need to clarify what this really returns. I'm assuming a board ID.
  • pin.mode([mode]) NOHEAP
  • pin.pull([pull]) NOHEAP
  • pin.drive([drive]) NOHEAP
  • pin.slew([slew]) NOHEAP

Constants

  • for mode: Pin.IN, Pin.OUT, Pin.OPEN_DRAIN, Pin.ALT, Pin.ALT_OPEN_DRAIN
  • for pull: Pin.PULL_UP, Pin.PULL_DOWN, Pin.PULL_NONE, optional: Pin.PULL_UP_STRONG, Pin.PULL_DOWN_WEAK, ...
  • for drive: Pin.LOW_POWER, Pin.MED_POWER, Pin.HIGH_POWER
  • for slew: Pin.FAST_RISE, Pin.SLOW_RISE
  • for interrupt (callback) mode: pin.INT_RISING, pin.INT_FALLING, pin.INT_RISING_FALLING, pin.INT_HIGH_LEVEL, pin.INT_LOW_LEVEL

The UART class

uart = UART(id, baudrate=9600, bits=8, parity=None, stop=0, *, pins, ...)

  • pins is a 4 or 2 item list indicating the TX, RX, RTS and CTS pins (in that order). Any of the pins can be None if one wants the UART to operate with limited functionality. If the RTS pin is given the the RX pin must be given as well. The same applies to CTS. When no pins are given (or all are None), then the default set of TX and RX pins is taken, and hardware flow control will be disabled.

Methods:

  • uart.init(...)
  • uart.read([nbytes])
  • uart.readinto(buf[, nbytes]) NOHEAP
  • uart.readall()
  • uart.readline([max_size])
  • uart.write(buf) NOHEAP
  • uart.sendbreak() NOHEAP

The I2C class

i2c = I2C(id, mode, *, baudrate, addr, pins)

pins is a tuple/list of SDA,SCL pins in that order (or should it be SCL,SDA to be consistent with the clock first for SPI?).

Master mode transfers (do we need the char methods?):

  • i2c.readfrom(addr, nbytes)
  • i2c.readfrom_into(addr, buf) NOHEAP
  • i2c.writeto(addr, buf, *, stop=True) NOHEAP; stop is if we want to send a stop bit at the end

Master mode mem transfers:

  • i2c.readfrom_mem(addr, memaddr, nbytes, memaddr, *, addrsize=8)
  • i2c.readfrom_mem_into(addr, memaddr, buf, *, addrsize=8) NOHEAP
  • i2c.writeto_mem(addr, memaddr, buf, *, addrsize=8) NOHEAP

Slave mode:

  • i2c.read(nbytes)
  • i2c.readinto(buf) NOHEAP
  • i2c.write(buf) NOHEAP

The SPI class

spi = SPI(id, mode, *, baudrate, polarity=1, phase=0, bits=8, firstbit=SPI.MSB, pins)

pins is a tuple/list of SCK,MOSI,MISO pins in that order. Optionally the list can also have NSS at the end.

Methods:

  • spi.write(buf) NOHEAP
  • spi.read(nbytes, *, write=0x00) write is the byte to output on MOSI for each byte read in
  • spi.readinto(buf, *, write=0x00) NOHEAP
  • spi.write_readinto(write_buf, read_buf) NOHEAP; write_buf and read_buf can be the same

The ADC class

Need to decide on standard for return value (eg always 12-bits maximum value?).

Constructor:

adc = ADC(id, *, bits, ...)

Methods:

  • adc.init(...) re-init
  • adc.readchannel(channel) NOHEAP; read a specific channel
  • apin = adc(pin) make an analog pin (will choose the correct channel for the pin)
  • apin.read() NOHEAP? depends what the return value is, probably should be integer
  • apin.readtimed(buf, timer)

The Timer class

Constructor:

timer = Timer(id, *, freq, ...)

Methods:

  • timer.source_freq() NOHEAP; get the source frequency of the clock (needed?)
  • timer.counter([counter]) NOHEAP; get/set the timer counter
  • timer.freq([freq]) NOHEAP*; get/set frequency in Hz (may be a floating point number)
  • timer.prescaler([prescaler]) NOHEAP; get/set the prescaler
  • timer.period([period]) NOHEAP; get/set the period

There will probably also need to be timer channels, which can be attached to pins and used for PWM etc.

machine module

  • machine.freq([freq, ...]) NOHEAP; get or set CPU and/or bus frequencies
  • machine.idle([... options]) NOHEAP; idle the CPU, may require external event to leave idle mode
  • machine.sleep([... options]) NOHEAP; enter sleep mode that retains RAM and continues execution when woken
  • machine.deepsleep([... options]) NOHEAP; enter sleep mode that may not retain RAM and may reset when woken

Constants

To specify from which sleep mode a callback can wake the machine:

  • machine.SLEEP
  • machine.DEEPSLEEP

Reset causes:

  • machine.PWR_ON_RESET
  • machine.HARD_RESET
  • machine.SOFT_RESET
  • machine.WDT_RESET
  • machine.DEEPSLEEP_RESET

Wake reasons:

  • machine.NETWORK_WAKE
  • machine.PIN_WAKE
  • machine.TIMER_WAKE

sys module additions

The sys module has uPy specific functions:

  • sys.dup_stdio([stream_obj]) get or set duplication of global stdio, so that REPL can be redirected to UART or other (note: could allow to duplicate to multiple stream objs but that's arguably overkill and could anyway be done in Python by making a stream multiplexer)

time module additions

The time module has uPy specific functions:

  • time.sleep_ms(ms) NOHEAP
  • time.sleep_us(us) NOHEAP
  • time.ticks_ms() NOHEAP
  • time.ticks_us() NOHEAP
  • time.ticks_cpu() NOHEAP
  • time.ticks_diff(t0, t1) NOHEAP

os module additions

The os module has uPy specific functions:

  • os.mount(block_dev, mount_point, *, readonly)
  • os.umount(mount_point)
  • os.mkfs(device or path, *, options...)

Clone this wiki locally