-
Notifications
You must be signed in to change notification settings - Fork 0
Hardware API
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.
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).
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 onlyCreate 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
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.
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.
Need to work this one out. Would be good to abstract each IRQ as an object that can be enabled, disabled, get/set callback, and get/set priority.
pin = Pin(id, mode, pull=Pin.PULL_NONE, *, value, drive, slew, alt, ...)
-
id, andmodeare mandatory and positional (modecan kw). -
pullis optional and positional (also can be named). - The rest of args are kwonly.
- Only
valueis required for a port to implement (initial value if given). - The rest are optional, and a port can define them and also define others.
-
altspecifies 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.
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
-
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
uart = UART(id, baudrate=9600, bits=8, parity=None, stop=0, *, pins, ...)
-
pinsis a 4 or 2 item list indicating the TX, RX, RTS and CTS pins (in that order). Any of the pins can beNoneif one wants the UART to operate with limited functionality. If theRTSpin is given the theRXpin must be given as well. The same applies toCTS. When no pins are given (or all areNone), then the default set ofTXandRXpins 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
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
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
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)
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.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
To specify from which sleep mode a callback can wake the machine:
machine.SLEEPmachine.DEEPSLEEP
Reset causes:
machine.PWR_ON_RESETmachine.HARD_RESETmachine.SOFT_RESETmachine.WDT_RESETmachine.DEEPSLEEP_RESET
Wake reasons:
machine.NETWORK_WAKEmachine.PIN_WAKEmachine.TIMER_WAKE
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)
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
The os module has uPy specific functions:
os.mount(block_dev, mount_point, *, readonly)os.umount(mount_point)os.mkfs(device or path, *, options...)