| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
This is the current proposal for a new hardware API. It is a work in progress. Discussion: https://github.com/micropython/micropython/issues/1430
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 (i.e. it shouldn't be heavily overloaded).
The existing pyb module already provides such abstraction but it is not as clean or consistent or general as it could be. The new hardware API will co-exist alongside the pyb module (only for stmhal port, e.g. PyBoard) so that existing scripts still run. The pyb module will eventually be deprecated, but only after all functionality finds another home (which may be some time).
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:
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. Whenever possible, a port should allow an ID to be an integer, but in various cases this will not be possible. Besides this recommendation, a format of an id is arbitrary (common cases: integer, strings, tuple of string/integers). There can be "virtual" peripherals, e.g. bitbanging I2C port, software timer, etc. These are recommended to have negative ids, with -1 being a default choice. If there're multiple types of the same virtual peripheral (e.g. OS software timers with different properties), further negative values can be given, and symbolic constants are recommended to be provided for them. (But another implementation strategy for this case is to define separate classes for such cases, instead of overloading default Timer class - e.g. OSSlowTimer, OSFastTimer. This is informal comment.)
All periphs provide a constructor, .init() and .deinit() methods. The Pin and the IRQ classes are the exception here, Pin doesn't provide .deinit() because a single pin cannot be completely de-initalized, and also because of the dynamic nature of pins which can be used as GPIO and also by other peripherals.
Peripherals should provide default values for all the initialization arguments, this way when calling .init() with no params, or the constructor with only the peripheral id (or no id at all, then the default one is used), it will be initialized with the default configuration.
If a peripheral is in the non-initialized state, any operation on it except for .init() SHOULD raise OSError with EBUSY code.
When connecting a periph to some pins, one uses the pins= keyword in the constructor/init function. This is a tuple/list of pins, where each pin can be an integer, string or Pin instance. keyword arguments with names of the pins should be used, e.g. tx=, miso=, etc.
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 machine, therefore, by doing:
import machine
dir(machine)one can easily see what's supported on the board.
Provided by mem8 (NOHEAP), mem16 (NOHEAP), mem32 (HEAP) virtual arrays. Mind that only mem8 and mem16 guarantee no heap access. For systems with virtual memory, these functions access hardware-defined physical address space (which includes memory-mapped I/O devices, etc.), not virtual memory map of a current process.
An interrupt request (IRQ) is an asynchronous and pre-emptive action triggered by a peripheral. Peripherals that support interrupts provide the irq method which returns an irq object. This can be used to execute a function when an IRQ is triggered, or wake up the device, or both.
peripheral.irq(*, trigger, priority=1, handler=None, wake=None)The irq is always enabled when created. Calling the irq method with no arguments simply returns the existing object (or creates it for the first time) without re-configuring it, just as with any other constructor.
The created irq object supports the following methods:
Signature of an irq handler: def my_handler(peripheral)
Example:
def pin_handler(pin):
print('Interrupt from pin {}'.format(pin.id()))
flags = pin.irq().flags()
if flags & Pin.IRQ_RISING:
# handle rising edge
else:
# handle falling edge
# disable the interrupt
pin.irq().deinit()Creates and initilizes a pin.
pin = Pin(id, mode, pull=None, *, value, drive, slew, alt, ...)
As specified above, Pin class allows to set alternate function for particular pin, but does not specify any further operations on such pin (such pins are usually not used as GPIO, but driven by other hardware blocks in MCU). The only operation supported on such pin is re-initializing, by calling constructor or .init() method. If a pin with alternate function set is re-initialized with Pin.IN, Pin.OUT, or Pin.OPEN_DRAIN, the alternate function will be removed from such pin (it will be used as GPIO).
TOD: Do we really need 2+ ways to set the value?
Getters and setters
uart = UART(id, baudrate=9600, bits=8, parity=None, stop=1, *, pins, ...)
Methods:
Changes from pyb: re-cast for master-only support (slave moved to I2CSlave class), so "mode" arg removed, changed method names.
i2c = I2C(id, *, baudrate, addr, pins)
pins is a tuple/list of SDA,SCL pins in that order (TODO: or should it be SCL, SDA to be consistent with the clock first for SPI?).
This class implements only master mode operations:
Master mode transfers:
Master mode mem transfers:
For master transfers we don't use read/write names because the type signature here is different to standard read/write (here we need to specify the address of the target slave). Other option would be to provide i2c.set_addr(addr) to set the slave address and then we can simply use standard read/readinto/write methods. But that introduces state into the I2C master (being the slave address) and really turns it into an endpoint, which is a higher level concept than simply providing basic methods to read/write on the I2C bus. An endpoint wrapper can very easily be written in Python.
i2csl = I2CSlave(id, *, baudrate, addr, pins)
See I2C class for arguments (id's refer to the same underlying hardware blocks as master I2C class).
Slave mode:
TODO: Details TBD. The general idea is that slave end-point can be configured in such a way that it can be accessed by master I2C.readfrom_mem(), etc. methods. Actual ability to do that depends largely on underlying hardware, and would require low-latency interrupts and DMA support.
Changes from pyb: re-cast for master-only support (slave moved to SPISlave class), so "mode" arg removed, changed method names.
spi = SPI(id, *, 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:
TODO: Details TBD. As SPI usually offer (much) higher transfer speeds, implementing slave support would require even more performant resources that I2C slave. One of the usecases may be high-speed communication between 2 or more systems, akin to "remote DMA" (i.e. slave will be initialized with a single bytearary buffer, which master can read at any time).
**Note that I2S has not yet officially supported by any existing MicroPython port; support for stmhal (pyboard) is currently under development by @blmorris. The proposed API for I2S is based on the new model for SPI, with certain modifications based on the I2S development discussions on GitHub, and will provide guidance as the I2S code is prepared for merging. **
i2s = I2S(id, mode, dataformat=I2S_DATAFORMAT_16B_EXTENDED, standard=I2S_STANDARD_PHILIPS, polarity=0, audiofreq=I2S_AUDIOFREQ_48K, clksrc=I2S_CLOCK_PLL, mclkout=0, pins)
pins is a tuple/list of BCK,WS,TX,RX pins in that order. BCK, WS, and at least one of either TX or RX are required. If Both TX and RX are provided, the I2S port is initialized in duplex mode, otherwise it initilizes as simplex in the direction specified by the provided pin.
All write and read methods for I2S will utilize DMA and be non-blocking when IRQ's are enabled.
Buffer oriented:
Stream oriented:
Need to decide on standard for return value (eg always 12-bits maximum value?). See discussion in https://github.com/micropython/micropython/pull/1130 for suggestion to always use 14- or 30-bit value (maximum MicroPython unsigned value which fits into 16- or 32-bit machine word).
Constructor:
adc = ADC(id, *, bits, ...)
Methods:
Constructor:
rtc = RTC(id=0, datetime=(year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None)) create an RTC object instance and set the current time.
Methods:
Constants:
Constructor:
wdt = WDT(id, [timeout]) instantiate and optionally enable the WDT with the specified timeout.
Methods:
Note: danicampora: Just as WLAN below, I think it should be possible to retrieve the existing WDT instance when calling the constructor with no params besides the id.
The Timer class provide access to the hardware timers of the SoC. It allows to generate periodic events, count events, and create PWM signals.
Constructor:
timer = Timer(id, mode=Timer.PERIODIC, *, freq, period_ns, counter_config=(prescaler, period_counts))
Since timers are used for various applications, many of which require high accuracy, having 3 ways of setting the timer frequency (freq itself, period in us or ns, and period in timer counts together with the prescaler) it's important. Having period in time units also helps readability and ease of use.
Suggestion: On some devices, all channels of a timer must have the same frequency, but the mode can be changed. On some other devices is the other way around. Proposal: The basic API ties both things to the Timer object, but we could also accept the following:
timer = Timer(id, mode=(Timer.PERIODIC, Timer.PWM) *, ....)
Which setups the timer and configures channel 0 in periodic mode and channel 1 in PWM mode. The same could be done for the frequency:
timer = Timer(id, mode=Timer.PERIODIC *, frequency=(1000, 100))
And of course, both mode and frequency could accept a tuple. The availability of this would be hardware dependent and needs to be documented properly. Requirement is that all ports support at least the first method that fixes all channels to have the same mode and frequency.
Timer methods:
TimerChannel methods:
The WLAN class belongs to the network module.
Constructor:
wlan = WLAN(id, mode=WLAN.STA, *, ssid='wlan', auth=None, channel=1, iface=None)
Note: Since WLAN might be a system feature of the platform (this is the case of the WiPy and the ESP8266), calling the constructor without params (besides the id) will return the existing WLAN object.
Methods:
Constants:
To specify from which sleep mode a callback can wake the machine:
Reset causes:
Wake reason:
The time module has uPy specific functions:
# Wait for GPIO pin to be asserted, but at most 500us
start = time.ticks_us()
while pin.value() == 0:
if time.ticks_diff(start, time.ticks_us()) > 500:
raise TimeoutError
The os module has uPy specific functions:
| Back | FazBrowse Home | New Git URL |