Update Micropython. ILI9341/XPT2046 fixes

This commit is contained in:
Amir Gonnen 2019-11-02 01:09:15 +02:00
parent bd027c9f04
commit 2c6d171f25
409 changed files with 22208 additions and 1431 deletions

3
.gitmodules vendored
View File

@ -30,3 +30,6 @@
[submodule "lib/tinyusb"] [submodule "lib/tinyusb"]
path = lib/tinyusb path = lib/tinyusb
url = https://github.com/hathach/tinyusb url = https://github.com/hathach/tinyusb
[submodule "lib/mynewt-nimble"]
path = lib/mynewt-nimble
url = https://github.com/apache/mynewt-nimble.git

View File

@ -32,8 +32,8 @@ jobs:
- sudo apt-get install libnewlib-arm-none-eabi - sudo apt-get install libnewlib-arm-none-eabi
- arm-none-eabi-gcc --version - arm-none-eabi-gcc --version
script: script:
- git submodule update --init lib/lwip lib/mbedtls lib/stm32lib
- make ${MAKEOPTS} -C mpy-cross - make ${MAKEOPTS} -C mpy-cross
- make ${MAKEOPTS} -C ports/stm32 submodules
- make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_F091RC - make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_F091RC
- make ${MAKEOPTS} -C ports/stm32 BOARD=PYBV11 MICROPY_PY_WIZNET5K=5200 MICROPY_PY_CC3K=1 - make ${MAKEOPTS} -C ports/stm32 BOARD=PYBV11 MICROPY_PY_WIZNET5K=5200 MICROPY_PY_CC3K=1
- make ${MAKEOPTS} -C ports/stm32 BOARD=PYBD_SF2 - make ${MAKEOPTS} -C ports/stm32 BOARD=PYBD_SF2
@ -66,8 +66,8 @@ jobs:
- gcc --version - gcc --version
- python3 --version - python3 --version
script: script:
- git submodule update --init lib/axtls lib/berkeley-db-1.xx lib/libffi
- make ${MAKEOPTS} -C mpy-cross - make ${MAKEOPTS} -C mpy-cross
- make ${MAKEOPTS} -C ports/unix submodules
- make ${MAKEOPTS} -C ports/unix deplibs - make ${MAKEOPTS} -C ports/unix deplibs
- make ${MAKEOPTS} -C ports/unix coverage - make ${MAKEOPTS} -C ports/unix coverage
# run the main test suite # run the main test suite
@ -87,23 +87,23 @@ jobs:
- stage: test - stage: test
env: NAME="unix port build and tests" env: NAME="unix port build and tests"
script: script:
- git submodule update --init lib/axtls lib/berkeley-db-1.xx lib/libffi
- make ${MAKEOPTS} -C mpy-cross - make ${MAKEOPTS} -C mpy-cross
- make ${MAKEOPTS} -C ports/unix submodules
- make ${MAKEOPTS} -C ports/unix deplibs - make ${MAKEOPTS} -C ports/unix deplibs
- make ${MAKEOPTS} -C ports/unix - make ${MAKEOPTS} -C ports/unix
- make ${MAKEOPTS} -C ports/unix test - make ${MAKEOPTS} -C ports/unix test
- (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/micropython ./run-perfbench.py 1000 1000) - (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/micropython ./run-perfbench.py 1000 1000)
# unix nanbox # unix nanbox (and using Python 2 to check it can run the build scripts)
- stage: test - stage: test
env: NAME="unix nanbox port build and tests" env: NAME="unix nanbox port build and tests"
install: install:
- sudo apt-get install gcc-multilib libffi-dev:i386 - sudo apt-get install gcc-multilib libffi-dev:i386
script: script:
- git submodule update --init lib/axtls lib/berkeley-db-1.xx lib/libffi - make ${MAKEOPTS} -C mpy-cross PYTHON=python2
- make ${MAKEOPTS} -C mpy-cross - make ${MAKEOPTS} -C ports/unix submodules
- make ${MAKEOPTS} -C ports/unix deplibs - make ${MAKEOPTS} -C ports/unix PYTHON=python2 deplibs
- make ${MAKEOPTS} -C ports/unix nanbox - make ${MAKEOPTS} -C ports/unix PYTHON=python2 nanbox
- (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/micropython_nanbox ./run-tests) - (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/micropython_nanbox ./run-tests)
# unix stackless # unix stackless
@ -112,8 +112,8 @@ jobs:
install: install:
- sudo apt-get install clang - sudo apt-get install clang
script: script:
- git submodule update --init lib/axtls lib/berkeley-db-1.xx lib/libffi
- make ${MAKEOPTS} -C mpy-cross CC=clang - make ${MAKEOPTS} -C mpy-cross CC=clang
- make ${MAKEOPTS} -C ports/unix submodules
- make ${MAKEOPTS} -C ports/unix CC=clang CFLAGS_EXTRA="-DMICROPY_STACKLESS=1 -DMICROPY_STACKLESS_STRICT=1" - make ${MAKEOPTS} -C ports/unix CC=clang CFLAGS_EXTRA="-DMICROPY_STACKLESS=1 -DMICROPY_STACKLESS_STRICT=1"
- make ${MAKEOPTS} -C ports/unix CC=clang test - make ${MAKEOPTS} -C ports/unix CC=clang test
@ -128,6 +128,13 @@ jobs:
after_failure: after_failure:
- (cd tests && for exp in *.exp; do testbase=$(basename $exp .exp); echo -e "\nFAILURE $testbase"; diff -u $testbase.exp $testbase.out; done) - (cd tests && for exp in *.exp; do testbase=$(basename $exp .exp); echo -e "\nFAILURE $testbase"; diff -u $testbase.exp $testbase.out; done)
# minimal unix port with tests
- stage: test
env: NAME="minimal unix port build and tests"
script:
- make ${MAKEOPTS} -C ports/unix minimal
- (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/micropython_minimal ./run-tests -e exception_chain -e self_type_check -e subclass_native_init -d basics)
# windows port via mingw # windows port via mingw
- stage: test - stage: test
env: NAME="windows port build via mingw" env: NAME="windows port build via mingw"
@ -149,18 +156,19 @@ jobs:
- git clone https://github.com/espressif/esp-idf.git - git clone https://github.com/espressif/esp-idf.git
- export IDF_PATH=$(pwd)/esp-idf - export IDF_PATH=$(pwd)/esp-idf
script: script:
- git submodule update --init lib/berkeley-db-1.xx
- make ${MAKEOPTS} -C mpy-cross - make ${MAKEOPTS} -C mpy-cross
# IDF v3 build # IDF v3 build
- git -C esp-idf checkout $(grep "ESPIDF_SUPHASH_V3 :=" ports/esp32/Makefile | cut -d " " -f 3) - git -C esp-idf checkout $(grep "ESPIDF_SUPHASH_V3 :=" ports/esp32/Makefile | cut -d " " -f 3)
- git -C esp-idf submodule update --init components/json/cJSON components/esp32/lib components/esptool_py/esptool components/expat/expat components/lwip/lwip components/mbedtls/mbedtls components/micro-ecc/micro-ecc components/nghttp/nghttp2 - git -C esp-idf submodule update --init components/json/cJSON components/esp32/lib components/esptool_py/esptool components/expat/expat components/lwip/lwip components/mbedtls/mbedtls components/micro-ecc/micro-ecc components/nghttp/nghttp2
- make ${MAKEOPTS} -C ports/esp32 submodules
- make ${MAKEOPTS} -C ports/esp32 - make ${MAKEOPTS} -C ports/esp32
# clean # clean
- git -C esp-idf clean -f -f -d components/json/cJSON components/esp32/lib components/expat/expat components/micro-ecc/micro-ecc components/nghttp/nghttp2 - git -C esp-idf clean -f -f -d components/json/cJSON components/esp32/lib components/expat/expat components/micro-ecc/micro-ecc components/nghttp/nghttp2
- make ${MAKEOPTS} -C ports/esp32 clean - make ${MAKEOPTS} -C ports/esp32 clean
# IDF v4 build # IDF v4 build
- git -C esp-idf checkout $(grep "ESPIDF_SUPHASH_V4 :=" ports/esp32/Makefile | cut -d " " -f 3) - git -C esp-idf checkout $(grep "ESPIDF_SUPHASH_V4 :=" ports/esp32/Makefile | cut -d " " -f 3)
- git -C esp-idf submodule update --init components/esp_wifi/lib_esp32 components/esptool_py/esptool components/lwip/lwip components/mbedtls/mbedtls - git -C esp-idf submodule update --init components/bt/controller/lib components/bt/host/nimble/nimble components/esp_wifi/lib_esp32 components/esptool_py/esptool components/lwip/lwip components/mbedtls/mbedtls
- make ${MAKEOPTS} -C ports/esp32 submodules
- make ${MAKEOPTS} -C ports/esp32 - make ${MAKEOPTS} -C ports/esp32
# esp8266 port # esp8266 port
@ -171,9 +179,10 @@ jobs:
- zcat xtensa-lx106-elf-standalone.tar.gz | tar x - zcat xtensa-lx106-elf-standalone.tar.gz | tar x
- export PATH=$(pwd)/xtensa-lx106-elf/bin:$PATH - export PATH=$(pwd)/xtensa-lx106-elf/bin:$PATH
script: script:
- git submodule update --init lib/axtls lib/berkeley-db-1.xx
- make ${MAKEOPTS} -C mpy-cross - make ${MAKEOPTS} -C mpy-cross
- make ${MAKEOPTS} -C ports/esp8266 submodules
- make ${MAKEOPTS} -C ports/esp8266 - make ${MAKEOPTS} -C ports/esp8266
- make ${MAKEOPTS} -C ports/esp8266 BOARD=GENERIC_512K
# nrf port # nrf port
- stage: test - stage: test
@ -183,7 +192,7 @@ jobs:
- sudo apt-get install libnewlib-arm-none-eabi - sudo apt-get install libnewlib-arm-none-eabi
- arm-none-eabi-gcc --version - arm-none-eabi-gcc --version
script: script:
- git submodule update --init lib/nrfx - make ${MAKEOPTS} -C ports/nrf submodules
- make ${MAKEOPTS} -C ports/nrf - make ${MAKEOPTS} -C ports/nrf
# bare-arm and minimal ports # bare-arm and minimal ports
@ -219,7 +228,7 @@ jobs:
- sudo apt-get install gcc-arm-none-eabi - sudo apt-get install gcc-arm-none-eabi
- sudo apt-get install libnewlib-arm-none-eabi - sudo apt-get install libnewlib-arm-none-eabi
script: script:
- git submodule update --init lib/asf4 lib/tinyusb - make ${MAKEOPTS} -C ports/samd submodules
- make ${MAKEOPTS} -C ports/samd - make ${MAKEOPTS} -C ports/samd
# teensy port # teensy port
@ -230,3 +239,12 @@ jobs:
- sudo apt-get install libnewlib-arm-none-eabi - sudo apt-get install libnewlib-arm-none-eabi
script: script:
- make ${MAKEOPTS} -C ports/teensy - make ${MAKEOPTS} -C ports/teensy
# powerpc port
- stage: test
env: NAME="powerpc port build"
install:
- sudo apt-get install gcc-powerpc64le-linux-gnu
- sudo apt-get install libc6-dev-ppc64el-cross
script:
- make ${MAKEOPTS} -C ports/powerpc CROSS_COMPILE=powerpc64le-linux-gnu-

53
CODEOFCONDUCT.md Normal file
View File

@ -0,0 +1,53 @@
MicroPython Code of Conduct
===========================
The MicroPython community is made up of members from around the globe with a
diverse set of skills, personalities, and experiences. It is through these
differences that our community experiences great successes and continued growth.
When you're working with members of the community, this Code of Conduct will
help steer your interactions and keep MicroPython a positive, successful, and
growing community.
Members of the MicroPython community are open, considerate, and respectful.
Behaviours that reinforce these values contribute to a positive environment, and
include: acknowledging time and effort, being respectful of differing viewpoints
and experiences, gracefully accepting constructive criticism, and using
welcoming and inclusive language.
Every member of our community has the right to have their identity respected.
The MicroPython community is dedicated to providing a positive experience for
everyone, regardless of age, gender identity and expression, sexual orientation,
disability, physical appearance, body size, ethnicity, nationality, race, or
religion (or lack thereof), education, or socio-economic status.
Unacceptable behaviour includes: harassment, trolling, deliberate intimidation,
violent threats or language directed against another person; insults, put downs,
or jokes that are based upon stereotypes, that are exclusionary, or that hold
others up for ridicule; unwelcome sexual attention or advances; sustained
disruption of community discussions; publishing others' private information
without explicit permission; and other conduct that is inappropriate for a
professional audience including people of many different backgrounds.
This code of conduct covers all online and offline presence related to the
MicroPython project, including GitHub and the forum. If a participant engages
in behaviour that violates this code of conduct, the MicroPython team may take
action as they deem appropriate, including warning the offender or expulsion
from the community. Community members asked to stop any inappropriate behaviour
are expected to comply immediately.
Thank you for helping make this a welcoming, friendly community for everyone.
If you believe that someone is violating the code of conduct, or have any other
concerns, please contact a member of the MicroPython team by emailing
contact@micropython.org.
License
-------
This Code of Conduct is licensed under the Creative Commons
Attribution-ShareAlike 3.0 Unported License.
Attributions
------------
Based on the Python code of conduct found at https://www.python.org/psf/conduct/

View File

@ -9,8 +9,9 @@ See also [Micropython + LittlevGL](https://blog.littlevgl.com/2019-02-20/micropy
1. `sudo apt-get install build-essential libreadline-dev libffi-dev git pkg-config libsdl2-2.0-0 libsdl2-dev python` 1. `sudo apt-get install build-essential libreadline-dev libffi-dev git pkg-config libsdl2-2.0-0 libsdl2-dev python`
2. `git clone --recurse-submodules https://github.com/littlevgl/lv_micropython.git` 2. `git clone --recurse-submodules https://github.com/littlevgl/lv_micropython.git`
3. `cd lv_micropython` 3. `cd lv_micropython`
4. `make -C ports/unix/` 4. `make -C mpy-cross`
5. `./ports/unix/micropython` 5. `make -C ports/unix/`
6. `./ports/unix/micropython`
### For ESP32 port ### For ESP32 port
@ -29,7 +30,7 @@ make -C ports/esp32 LV_CFLAGS="-DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=1" BOARD=G
Explanation about the paramters: Explanation about the paramters:
- `LV_CFLAGS` are used to override color depth and swap mode, for ILI9341 compatibility. - `LV_CFLAGS` are used to override color depth and swap mode, for ILI9341 compatibility.
- `LV_COLOR_DEPTH=16` is needed if you plan to use the ILI9341 driver. - `LV_COLOR_DEPTH=16` is needed if you plan to use the ILI9341 driver.
- `LV_COLOR_16_SWAP=1` is **only** needed if you plan to use the [Pure Micropython Display Driver](https://blog.littlevgl.com/2019-08-05/micropython-pure-display-driver). Otherwise, remove it. - `LV_COLOR_16_SWAP=1` is needed if you plan to use the [Pure Micropython Display Driver](https://blog.littlevgl.com/2019-08-05/micropython-pure-display-driver).
- `BOARD` - I use WROVER board with SPIRAM. You can choose other boards from `ports/esp32/boards/` directory. - `BOARD` - I use WROVER board with SPIRAM. You can choose other boards from `ports/esp32/boards/` directory.
- `PYTHON=python2` - depending on your installed `pyparsing` version, you might or might not need this. - `PYTHON=python2` - depending on your installed `pyparsing` version, you might or might not need this.
- `deploy` - make command will create ESP32 port of Micropython, and will try to deploy it through USB-UART bridge. - `deploy` - make command will create ESP32 port of Micropython, and will try to deploy it through USB-UART bridge.
@ -56,8 +57,6 @@ Now you can build the JavaScript port.
7. Run an HTTP server that serves files from the current directory. 7. Run an HTTP server that serves files from the current directory.
8. Browse to `/lvgl_editor.html` on the HTTP Server. 8. Browse to `/lvgl_editor.html` on the HTTP Server.
## Super Simple Example ## Super Simple Example
First, LittlevGL needs to be imported and initialized First, LittlevGL needs to be imported and initialized
@ -101,32 +100,28 @@ Here is an alternative example, for registering ILI9341 drivers on Micropython E
```python ```python
import lvgl as lv import lvgl as lv
# Import ESP32 and ILI9341 drivers (advnaces tick count and schedules tasks)
import ILI9341 as ili
import lvesp32 import lvesp32
# Initialize the driver and register it to LittlevGL # Import ILI9341 driver and initialized it
disp = ili.display(miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, backlight=2) from ili9341 import ili9341
disp.init() disp = ili9341()
# Register display driver # Import XPT2046 driver and initalize it
disp_buf1 = lv.disp_buf_t() from xpt2046 import xpt2046
buf1_1 = bytearray(480*10) touch = xpt2046()
lv.disp_buf_init(disp_buf1,buf1_1, None, len(buf1_1)//4)
disp_drv = lv.disp_drv_t()
lv.disp_drv_init(disp_drv)
disp_drv.buffer = disp_buf1
disp_drv.flush_cb = disp.flush
disp_drv.hor_res = 240
disp_drv.ver_res = 320
lv.disp_drv_register(disp_drv)
``` ```
Now you can create the GUI itself By default, both ILI9341 and XPT2046 are initialized on the same SPI bus with the following parameters:
- ILI9341: `miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, power=14, backlight=15, spihost=esp.HSPI_HOST, mhz=40, factor=4, hybrid=True`
- XPT2046: `cs=25, spihost=esp.HSPI_HOST, mhz=5, max_cmds=16, cal_x0 = 3783, cal_y0 = 3948, cal_x1 = 242, cal_y1 = 423, transpose = True, samples = 3`
You can change any of these parameters on ili9341/xpt2046 constructor.
You can also initalize them on different SPI buses if you want, by providing miso/mosi/clk parameters. Set them to -1 to use existing (initialized) spihost bus.
Now you can create the GUI itself:
```python ```python

View File

@ -10,3 +10,4 @@ See the `getting started guide
:maxdepth: 1 :maxdepth: 1
cmodules.rst cmodules.rst
qstr.rst

112
docs/develop/qstr.rst Normal file
View File

@ -0,0 +1,112 @@
MicroPython string interning
============================
MicroPython uses `string interning`_ to save both RAM and ROM. This avoids
having to store duplicate copies of the same string. Primarily, this applies to
identifiers in your code, as something like a function or variable name is very
likely to appear in multiple places in the code. In MicroPython an interned
string is called a QSTR (uniQue STRing).
A QSTR value (with type ``qstr``) is a index into a linked list of QSTR pools.
QSTRs store their length and a hash of their contents for fast comparison during
the de-duplication process. All bytecode operations that work with strings use
a QSTR argument.
Compile-time QSTR generation
----------------------------
In the MicroPython C code, any strings that should be interned in the final
firmware are written as ``MP_QSTR_Foo``. At compile time this will evaluate to
a ``qstr`` value that points to the index of ``"Foo"`` in the QSTR pool.
A multi-step process in the ``Makefile`` makes this work. In summary this
process has three parts:
1. Find all ``MP_QSTR_Foo`` tokens in the code.
2. Generate a static QSTR pool containing all the string data (including lengths
and hashes).
3. Replace all ``MP_QSTR_Foo`` (via the preprocessor) with their corresponding
index.
``MP_QSTR_Foo`` tokens are searched for in two sources:
1. All files referenced in ``$(SRC_QSTR)``. This is all C code (i.e. ``py``,
``extmod``, ``ports/stm32``) but not including third-party code such as
``lib``.
2. Additional ``$(QSTR_GLOBAL_DEPENDENCIES)`` (which includes ``mpconfig*.h``).
*Note:* ``frozen_mpy.c`` (generated by mpy-tool.py) has its own QSTR generation
and pool.
Some additional strings that can't be expressed using the ``MP_QSTR_Foo`` syntax
(e.g. they contain non-alphanumeric characters) are explicitly provided in
``qstrdefs.h`` and ``qstrdefsport.h`` via the ``$(QSTR_DEFS)`` variable.
Processing happens in the following stages:
1. ``qstr.i.last`` is the concatenation of putting every single input file
through the C pre-processor. This means that any conditionally disabled code
will be removed, and macros expanded. This means we don't add strings to the
pool that won't be used in the final firmware. Because at this stage (thanks
to the ``NO_QSTR`` macro added by ``QSTR_GEN_EXTRA_CFLAGS``) there is no
definition for ``MP_QSTR_Foo`` it passes through this stage unaffected. This
file also includes comments from the preprocessor that include line number
information. Note that this step only uses files that have changed, which
means that ``qstr.i.last`` will only contain data from files that have
changed since the last compile.
2. ``qstr.split`` is an empty file created after running ``makeqstrdefs.py split``
on qstr.i.last. It's just used as a dependency to indicate that the step ran.
This script outputs one file per input C file, ``genhdr/qstr/...file.c.qstr``,
which contains only the matched QSTRs. Each QSTR is printed as ``Q(Foo)``.
This step is necessary to combine the existing files with the new data
generated from the incremental update in ``qstr.i.last``.
3. ``qstrdefs.collected.h`` is the output of concatenating ``genhdr/qstr/*``
using ``makeqstrdefs.py cat``. This is now the full set of ``MP_QSTR_Foo``'s
found in the code, now formatted as ``Q(Foo)``, one-per-line, with duplicates.
This file is only updated if the set of qstrs has changed. A hash of the QSTR
data is written to another file (``qstrdefs.collected.h.hash``) which allows
it to track changes across builds.
4. ``qstrdefs.preprocessed.h`` adds in the QSTRs from qstrdefs*. It
concatenates ``qstrdefs.collected.h`` with ``qstrdefs*.h``, then it transforms
each line from ``Q(Foo)`` to ``"Q(Foo)"`` so they pass through the preprocessor
unchanged. Then the preprocessor is used to deal with any conditional
compilation in ``qstrdefs*.h``. Then the transformation is undone back to
``Q(Foo)``, and saved as ``qstrdefs.preprocessed.h``.
5. ``qstrdefs.generated.h`` is the output of ``makeqstrdata.py``. For each
``Q(Foo)`` in qstrdefs.preprocessed.h (plus some extra hard-coded ones), it outputs
``QDEF(MP_QSTR_Foo, (const byte*)"hash" "Foo")``.
Then in the main compile, two things happen with ``qstrdefs.generated.h``:
1. In qstr.h, each QDEF becomes an entry in an enum, which makes ``MP_QSTR_Foo``
available to code and equal to the index of that string in the QSTR table.
2. In qstr.c, the actual QSTR data table is generated as elements of the
``mp_qstr_const_pool->qstrs``.
.. _`string interning`: https://en.wikipedia.org/wiki/String_interning
Run-time QSTR generation
------------------------
Additional QSTR pools can be created at runtime so that strings can be added to
them. For example, the code::
foo[x] = 3
Will need to create a QSTR for the value of ``x`` so it can be used by the
"load attr" bytecode.
Also, when compiling Python code, identifiers and literals need to have QSTRs
created. Note: only literals shorter than 10 characters become QSTRs. This is
because a regular string on the heap always takes up a minimum of 16 bytes (one
GC block), whereas QSTRs allow them to be packed more efficiently into the pool.
QSTR pools (and the underlying "chunks" that store the string data) are allocated
on-demand on the heap with a minimum size.

View File

@ -77,7 +77,7 @@ The :mod:`network` module::
wlan.scan() # scan for access points wlan.scan() # scan for access points
wlan.isconnected() # check if the station is connected to an AP wlan.isconnected() # check if the station is connected to an AP
wlan.connect('essid', 'password') # connect to an AP wlan.connect('essid', 'password') # connect to an AP
wlan.config('mac') # get the interface's MAC adddress wlan.config('mac') # get the interface's MAC address
wlan.ifconfig() # get the interface's IP/netmask/gw/DNS addresses wlan.ifconfig() # get the interface's IP/netmask/gw/DNS addresses
ap = network.WLAN(network.AP_IF) # create access-point interface ap = network.WLAN(network.AP_IF) # create access-point interface
@ -203,7 +203,7 @@ Use the :ref:`machine.ADC <machine.ADC>` class::
adc = ADC(Pin(32)) # create ADC object on ADC pin adc = ADC(Pin(32)) # create ADC object on ADC pin
adc.read() # read value, 0-4095 across voltage range 0.0v - 1.0v adc.read() # read value, 0-4095 across voltage range 0.0v - 1.0v
adc.atten(ADC.ATTN_11DB) # set 11dB input attentuation (voltage range roughly 0.0v - 3.6v) adc.atten(ADC.ATTN_11DB) # set 11dB input attenuation (voltage range roughly 0.0v - 3.6v)
adc.width(ADC.WIDTH_9BIT) # set 9 bit return values (returned range 0-511) adc.width(ADC.WIDTH_9BIT) # set 9 bit return values (returned range 0-511)
adc.read() # read value using the newly configured attenuation and width adc.read() # read value using the newly configured attenuation and width
@ -257,7 +257,7 @@ class::
spi.init(baudrate=200000) # set the baudrate spi.init(baudrate=200000) # set the baudrate
spi.read(10) # read 10 bytes on MISO spi.read(10) # read 10 bytes on MISO
spi.read(10, 0xff) # read 10 bytes while outputing 0xff on MOSI spi.read(10, 0xff) # read 10 bytes while outputting 0xff on MOSI
buf = bytearray(50) # create a buffer buf = bytearray(50) # create a buffer
spi.readinto(buf) # read into the given buffer (reads 50 bytes in this case) spi.readinto(buf) # read into the given buffer (reads 50 bytes in this case)

View File

@ -140,7 +140,7 @@ The above may also happen after an application terminates and quits to the REPL
for any reason including an exception. Subsequent arrival of data provokes the for any reason including an exception. Subsequent arrival of data provokes the
failure with the above error message repeatedly issued. So, sockets should be failure with the above error message repeatedly issued. So, sockets should be
closed in any case, regardless whether an application terminates successfully closed in any case, regardless whether an application terminates successfully
or by an exeption, for example using try/finally:: or by an exception, for example using try/finally::
sock = socket(...) sock = socket(...)
try: try:

View File

@ -188,7 +188,7 @@ class::
spi.init(baudrate=200000) # set the baudrate spi.init(baudrate=200000) # set the baudrate
spi.read(10) # read 10 bytes on MISO spi.read(10) # read 10 bytes on MISO
spi.read(10, 0xff) # read 10 bytes while outputing 0xff on MOSI spi.read(10, 0xff) # read 10 bytes while outputting 0xff on MOSI
buf = bytearray(50) # create a buffer buf = bytearray(50) # create a buffer
spi.readinto(buf) # read into the given buffer (reads 50 bytes in this case) spi.readinto(buf) # read into the given buffer (reads 50 bytes in this case)
@ -243,6 +243,12 @@ See :ref:`machine.RTC <machine.RTC>` ::
rtc.datetime((2017, 8, 23, 1, 12, 48, 0, 0)) # set a specific date and time rtc.datetime((2017, 8, 23, 1, 12, 48, 0, 0)) # set a specific date and time
rtc.datetime() # get date and time rtc.datetime() # get date and time
# synchronize with ntp
# need to be connected to wifi
import ntptime
ntptime.settime() # set the rtc datetime from the remote server
rtc.datetime() # get the date and time in UTC
Deep-sleep mode Deep-sleep mode
--------------- ---------------

View File

@ -61,6 +61,7 @@ of the request you need to specify the page to retrieve.
Let's define a function that can download and print a URL:: Let's define a function that can download and print a URL::
def http_get(url): def http_get(url):
import socket
_, _, host, path = url.split('/', 3) _, _, host, path = url.split('/', 3)
addr = socket.getaddrinfo(host, 80)[0][-1] addr = socket.getaddrinfo(host, 80)[0][-1]
s = socket.socket() s = socket.socket()
@ -74,8 +75,7 @@ Let's define a function that can download and print a URL::
break break
s.close() s.close()
Make sure that you import the socket module before running this function. Then Then you can try::
you can try::
>>> http_get('http://micropython.org/ks/test.html') >>> http_get('http://micropython.org/ks/test.html')

View File

@ -74,11 +74,11 @@ it will fallback to loading the built-in ``ujson`` module.
:maxdepth: 1 :maxdepth: 1
builtins.rst builtins.rst
array.rst
cmath.rst cmath.rst
gc.rst gc.rst
math.rst math.rst
sys.rst sys.rst
uarray.rst
ubinascii.rst ubinascii.rst
ucollections.rst ucollections.rst
uerrno.rst uerrno.rst
@ -111,10 +111,24 @@ the following libraries.
machine.rst machine.rst
micropython.rst micropython.rst
network.rst network.rst
ubluetooth.rst
ucryptolib.rst ucryptolib.rst
uctypes.rst uctypes.rst
Port-specific libraries
-----------------------
In some cases the following port/board-specific libraries have functions or
classes similar to those in the :mod:`machine` library. Where this occurs, the
entry in the port specific library exposes hardware functionality unique to
that platform.
To write portable code use functions and classes from the :mod:`machine` module.
To access platform-specific hardware use the appropriate library, e.g.
:mod:`pyb` in the case of the Pyboard.
Libraries specific to the pyboard Libraries specific to the pyboard
--------------------------------- ---------------------------------

View File

@ -68,7 +68,7 @@ parameter should be `id`.
(password) required to access said service. There can be further (password) required to access said service. There can be further
arbitrary keyword-only parameters, depending on the networking medium arbitrary keyword-only parameters, depending on the networking medium
type and/or particular device. Parameters can be used to: a) type and/or particular device. Parameters can be used to: a)
specify alternative service identifer types; b) provide additional specify alternative service identifier types; b) provide additional
connection parameters. For various medium types, there are different connection parameters. For various medium types, there are different
sets of predefined/recommended parameters, among them: sets of predefined/recommended parameters, among them:

View File

@ -1,7 +1,7 @@
:mod:`array` -- arrays of numeric data :mod:`uarray` -- arrays of numeric data
====================================== =======================================
.. module:: array .. module:: uarray
:synopsis: efficient arrays of numeric data :synopsis: efficient arrays of numeric data
|see_cpython_module| :mod:`python:array`. |see_cpython_module| :mod:`python:array`.
@ -13,7 +13,7 @@ floating-point support).
Classes Classes
------- -------
.. class:: array.array(typecode, [iterable]) .. class:: array(typecode, [iterable])
Create array with elements of given type. Initial contents of the Create array with elements of given type. Initial contents of the
array are given by *iterable*. If it is not provided, an empty array are given by *iterable*. If it is not provided, an empty

337
docs/library/ubluetooth.rst Normal file
View File

@ -0,0 +1,337 @@
:mod:`ubluetooth` --- low-level Bluetooth
=========================================
.. module:: ubluetooth
:synopsis: Low-level Bluetooth radio functionality
This module provides an interface to a Bluetooth controller on a board.
Currently this supports Bluetooth Low Energy (BLE) in Central, Peripheral,
Broadcaster, and Observer roles, and a device may operate in multiple
roles concurrently.
This API is intended to match the low-level Bluetooth protocol and provide
building-blocks for higher-level abstractions such as specific device types.
class BLE
---------
Constructor
-----------
.. class:: BLE()
Returns the singleton BLE object.
Configuration
-------------
.. method:: BLE.active([active])
Optionally changes the active state of the BLE radio, and returns the
current state.
The radio must be made active before using any other methods on this class.
.. method:: BLE.config(name)
Queries a configuration value by *name*. Currently supported values are:
- ``'mac'``: Returns the device MAC address. If a device has a fixed address
(e.g. PYBD) then it will be returned. Otherwise (e.g. ESP32) a random
address will be generated when the BLE interface is made active.
Event Handling
--------------
.. method:: BLE.irq(handler, trigger=0xffff)
Registers a callback for events from the BLE stack. The *handler* takes two
arguments, ``event`` (which will be one of the codes below) and ``data``
(which is an event-specific tuple of values).
The optional *trigger* parameter allows you to set a mask of events that
your program is interested in. The default is all events.
An event handler showing all possible events::
def bt_irq(event, data):
if event == _IRQ_CENTRAL_CONNECT:
# A central has connected to this peripheral.
conn_handle, addr_type, addr = data
elif event == _IRQ_CENTRAL_DISCONNECT:
# A central has disconnected from this peripheral.
conn_handle, addr_type, addr = data
elif event == _IRQ_GATTS_WRITE:
# A central has written to this characteristic or descriptor.
conn_handle, attr_handle = data
elif event == _IRQ_GATTS_READ_REQUEST:
# A central has issued a read. Note: this is a hard IRQ.
# Return None to deny the read.
# Note: This event is not supported on ESP32.
conn_handle, attr_handle = data
elif event == _IRQ_SCAN_RESULT:
# A single scan result.
addr_type, addr, connectable, rssi, adv_data = data
elif event == _IRQ_SCAN_COMPLETE:
# Scan duration finished or manually stopped.
pass
elif event == _IRQ_PERIPHERAL_CONNECT:
# A successful gap_connect().
conn_handle, addr_type, addr = data
elif event == _IRQ_PERIPHERAL_DISCONNECT:
# Connected peripheral has disconnected.
conn_handle, addr_type, addr = data
elif event == _IRQ_GATTC_SERVICE_RESULT:
# Called for each service found by gattc_discover_services().
conn_handle, start_handle, end_handle, uuid = data
elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT:
# Called for each characteristic found by gattc_discover_services().
conn_handle, def_handle, value_handle, properties, uuid = data
elif event == _IRQ_GATTC_DESCRIPTOR_RESULT:
# Called for each descriptor found by gattc_discover_descriptors().
conn_handle, dsc_handle, uuid = data
elif event == _IRQ_GATTC_READ_RESULT:
# A gattc_read() has completed.
conn_handle, value_handle, char_data = data
elif event == _IRQ_GATTC_WRITE_STATUS:
# A gattc_write() has completed.
conn_handle, value_handle, status = data
elif event == _IRQ_GATTC_NOTIFY:
# A peripheral has sent a notify request.
conn_handle, value_handle, notify_data = data
elif event == _IRQ_GATTC_INDICATE:
# A peripheral has sent an indicate request.
conn_handle, value_handle, notify_data = data
The event codes are::
from micropython import const
_IRQ_CENTRAL_CONNECT = const(1 << 0)
_IRQ_CENTRAL_DISCONNECT = const(1 << 1)
_IRQ_GATTS_WRITE = const(1 << 2)
_IRQ_GATTS_READ_REQUEST = const(1 << 3)
_IRQ_SCAN_RESULT = const(1 << 4)
_IRQ_SCAN_COMPLETE = const(1 << 5)
_IRQ_PERIPHERAL_CONNECT = const(1 << 6)
_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7)
_IRQ_GATTC_SERVICE_RESULT = const(1 << 8)
_IRQ_GATTC_CHARACTERISTIC_RESULT = const(1 << 9)
_IRQ_GATTC_DESCRIPTOR_RESULT = const(1 << 10)
_IRQ_GATTC_READ_RESULT = const(1 << 11)
_IRQ_GATTC_WRITE_STATUS = const(1 << 12)
_IRQ_GATTC_NOTIFY = const(1 << 13)
_IRQ_GATTC_INDICATE = const(1 << 14)
In order to save space in the firmware, these constants are not included on the
:mod:`ubluetooth` module. Add the ones that you need from the list above to your
program.
Broadcaster Role (Advertiser)
-----------------------------
.. method:: BLE.gap_advertise(interval_us, adv_data=None, resp_data=None, connectable=True)
Starts advertising at the specified interval (in **micro**\ seconds). This
interval will be rounded down to the nearest 625us. To stop advertising, set
*interval_us* to ``None``.
*adv_data* and *resp_data* can be any type that implements the buffer
protocol (e.g. ``bytes``, ``bytearray``, ``str``). *adv_data* is included
in all broadcasts, and *resp_data* is send in reply to an active scan.
Note: if *adv_data* (or *resp_data*) is ``None``, then the data passed
to the previous call to ``gap_advertise`` will be re-used. This allows a
broadcaster to resume advertising with just ``gap_advertise(interval_us)``.
To clear the advertising payload pass an empty ``bytes``, i.e. ``b''``.
Observer Role (Scanner)
-----------------------
.. method:: BLE.gap_scan(duration_ms, [interval_us], [window_us])
Run a scan operation lasting for the specified duration (in **milli**\ seconds).
To scan indefinitely, set *duration_ms* to ``0``.
To stop scanning, set *duration_ms* to ``None``.
Use *interval_us* and *window_us* to optionally configure the duty cycle.
The scanner will run for *window_us* **micro**\ seconds every *interval_us*
**micro**\ seconds for a total of *duration_ms* **milli**\ seconds. The default
interval and window are 1.28 seconds and 11.25 milliseconds respectively
(background scanning).
For each scan result, the ``_IRQ_SCAN_RESULT`` event will be raised.
When scanning is stopped (either due to the duration finishing or when
explicitly stopped), the ``_IRQ_SCAN_COMPLETE`` event will be raised.
Peripheral Role (GATT Server)
-----------------------------
A BLE peripheral has a set of registered services. Each service may contain
characteristics, which each have a value. Characteristics can also contain
descriptors, which themselves have values.
These values are stored locally, and are accessed by their "value handle" which
is generated during service registration. They can also be read from or written
to by a remote central device. Additionally, a peripheral can "notify" a
characteristic to a connected central via a connection handle.
Characteristics and descriptors have a default maximum size of 20 bytes.
Anything written to them by a central will be truncated to this length. However,
any local write will increase the maximum size, so if you want to allow larger
writes from a central to a given characteristic, use
:meth:`gatts_write<BLE.gatts_write>` after registration. e.g.
``gatts_write(char_handle, bytes(100))``.
.. method:: BLE.gatts_register_services(services_definition)
Configures the peripheral with the specified services, replacing any
existing services.
*services_definition* is a list of **services**, where each **service** is a
two-element tuple containing a UUID and a list of **characteristics**.
Each **characteristic** is a two-or-three-element tuple containing a UUID, a
**flags** value, and optionally a list of *descriptors*.
Each **descriptor** is a two-element tuple containing a UUID and a **flags**
value.
The **flags** are a bitwise-OR combination of the
:data:`ubluetooth.FLAGS_READ`, :data:`bluetooth.FLAGS_WRITE` and
:data:`ubluetooth.FLAGS_NOTIFY` values defined below.
The return value is a list (one element per service) of tuples (each element
is a value handle). Characteristics and descriptor handles are flattened
into the same tuple, in the order that they are defined.
The following example registers two services (Heart Rate, and Nordic UART)::
HR_UUID = bluetooth.UUID(0x180D)
HR_CHAR = (bluetooth.UUID(0x2A37), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY,)
HR_SERVICE = (HR_UUID, (HR_CHAR,),)
UART_UUID = bluetooth.UUID('6E400001-B5A3-F393-E0A9-E50E24DCCA9E')
UART_TX = (bluetooth.UUID('6E400003-B5A3-F393-E0A9-E50E24DCCA9E'), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY,)
UART_RX = (bluetooth.UUID('6E400002-B5A3-F393-E0A9-E50E24DCCA9E'), bluetooth.FLAG_WRITE,)
UART_SERVICE = (UART_UUID, (UART_TX, UART_RX,),)
SERVICES = (HR_SERVICE, UART_SERVICE,)
( (hr,), (tx, rx,), ) = bt.gatts_register_services(SERVICES)
The three value handles (``hr``, ``tx``, ``rx``) can be used with
:meth:`gatts_read <BLE.gatts_read>`, :meth:`gatts_write <BLE.gatts_write>`,
and :meth:`gatts_notify <BLE.gatts_notify>`.
**Note:** Advertising must be stopped before registering services.
.. method:: BLE.gatts_read(value_handle)
Reads the local value for this handle (which has either been written by
:meth:`gatts_write <BLE.gatts_write>` or by a remote central).
.. method:: BLE.gatts_write(value_handle, data)
Writes the local value for this handle, which can be read by a central.
.. method:: BLE.gatts_notify(conn_handle, value_handle, [data])
Notifies a connected central that this value has changed and that it should
issue a read of the current value from this peripheral.
If *data* is specified, then the that value is sent to the central as part
of the notification, avoiding the need for a separate read request. Note
that this will not update the local value stored.
.. method:: BLE.gatts_set_buffer(value_handle, len, append=False)
Sets the internal buffer size for a value in bytes. This will limit the
largest possible write that can be received. The default is 20.
Setting *append* to ``True`` will make all remote writes append to, rather
than replace, the current value. At most *len* bytes can be buffered in
this way. When you use :meth:`gatts_read <BLE.gatts_read>`, the value will
be cleared after reading. This feature is useful when implementing something
like the Nordic UART Service.
Central Role (GATT Client)
--------------------------
.. method:: BLE.gap_connect(addr_type, addr, scan_duration_ms=2000)
Connect to a peripheral.
On success, the ``_IRQ_PERIPHERAL_CONNECT`` event will be raised.
.. method:: BLE.gap_disconnect(conn_handle)
Disconnect the specified connection handle.
On success, the ``_IRQ_PERIPHERAL_DISCONNECT`` event will be raised.
Returns ``False`` if the connection handle wasn't connected, and ``True``
otherwise.
.. method:: BLE.gattc_discover_services(conn_handle)
Query a connected peripheral for its services.
For each service discovered, the ``_IRQ_GATTC_SERVICE_RESULT`` event will be
raised.
.. method:: BLE.gattc_discover_characteristics(conn_handle, start_handle, end_handle)
Query a connected peripheral for characteristics in the specified range.
For each characteristic discovered, the ``_IRQ_GATTC_CHARACTERISTIC_RESULT``
event will be raised.
.. method:: BLE.gattc_discover_descriptors(conn_handle, start_handle, end_handle)
Query a connected peripheral for descriptors in the specified range.
For each descriptor discovered, the ``_IRQ_GATTC_DESCRIPTOR_RESULT`` event
will be raised.
.. method:: BLE.gattc_read(conn_handle, value_handle)
Issue a remote read to a connected peripheral for the specified
characteristic or descriptor handle.
On success, the ``_IRQ_GATTC_READ_RESULT`` event will be raised.
.. method:: BLE.gattc_write(conn_handle, value_handle, data)
Issue a remote write to a connected peripheral for the specified
characteristic or descriptor handle.
On success, the ``_IRQ_GATTC_WRITE_STATUS`` event will be raised.
class UUID
----------
Constructor
-----------
.. class:: UUID(value)
Creates a UUID instance with the specified **value**.
The **value** can be either:
- A 16-bit integer. e.g. ``0x2908``.
- A 128-bit UUID string. e.g. ``'6E400001-B5A3-F393-E0A9-E50E24DCCA9E'``.
Constants
---------
.. data:: ubluetooth.FLAG_READ
ubluetooth.FLAG_WRITE
ubluetooth.FLAG_NOTIFY

View File

@ -18,10 +18,10 @@ be implemented:
* SHA1 - A previous generation algorithm. Not recommended for new usages, * SHA1 - A previous generation algorithm. Not recommended for new usages,
but SHA1 is a part of number of Internet standards and existing but SHA1 is a part of number of Internet standards and existing
applications, so boards targeting network connectivity and applications, so boards targeting network connectivity and
interoperatiability will try to provide this. interoperability will try to provide this.
* MD5 - A legacy algorithm, not considered cryptographically secure. Only * MD5 - A legacy algorithm, not considered cryptographically secure. Only
selected boards, targeting interoperatibility with legacy applications, selected boards, targeting interoperability with legacy applications,
will offer this. will offer this.
Constructors Constructors

View File

@ -94,10 +94,10 @@ Filesystem access
* ``f_frsize`` -- fragment size * ``f_frsize`` -- fragment size
* ``f_blocks`` -- size of fs in f_frsize units * ``f_blocks`` -- size of fs in f_frsize units
* ``f_bfree`` -- number of free blocks * ``f_bfree`` -- number of free blocks
* ``f_bavail`` -- number of free blocks for unpriviliged users * ``f_bavail`` -- number of free blocks for unprivileged users
* ``f_files`` -- number of inodes * ``f_files`` -- number of inodes
* ``f_ffree`` -- number of free inodes * ``f_ffree`` -- number of free inodes
* ``f_favail`` -- number of free inodes for unpriviliged users * ``f_favail`` -- number of free inodes for unprivileged users
* ``f_flag`` -- mount flags * ``f_flag`` -- mount flags
* ``f_namemax`` -- maximum filename length * ``f_namemax`` -- maximum filename length
@ -187,25 +187,51 @@ implementation of this class will usually allow access to the memory-like
functionality a piece of hardware (like flash memory). A block device can be functionality a piece of hardware (like flash memory). A block device can be
used by a particular filesystem driver to store the data for its filesystem. used by a particular filesystem driver to store the data for its filesystem.
There are two compatible signatures for the ``readblocks`` and ``writeblocks``
methods (see below), in order to support a variety of use cases. A given block
device may implement one form or the other, or both at the same time.
.. class:: AbstractBlockDev(...) .. class:: AbstractBlockDev(...)
Construct a block device object. The parameters to the constructor are Construct a block device object. The parameters to the constructor are
dependent on the specific block device. dependent on the specific block device.
.. method:: readblocks(block_num, buf) .. method:: readblocks(block_num, buf)
.. method:: readblocks(block_num, buf, offset)
The first form reads aligned, multiples of blocks.
Starting at the block given by the index *block_num*, read blocks from Starting at the block given by the index *block_num*, read blocks from
the device into *buf* (an array of bytes). the device into *buf* (an array of bytes).
The number of blocks to read is given by the length of *buf*, The number of blocks to read is given by the length of *buf*,
which will be a multiple of the block size. which will be a multiple of the block size.
.. method:: writeblocks(block_num, buf) The second form allows reading at arbitrary locations within a block,
and arbitrary lengths.
Starting at block index *block_num*, and byte offset within that block
of *offset*, read bytes from the device into *buf* (an array of bytes).
The number of bytes to read is given by the length of *buf*.
.. method:: writeblocks(block_num, buf)
.. method:: writeblocks(block_num, buf, offset)
The first form writes aligned, multiples of blocks, and requires that the
blocks that are written to be first erased (if necessary) by this method.
Starting at the block given by the index *block_num*, write blocks from Starting at the block given by the index *block_num*, write blocks from
*buf* (an array of bytes) to the device. *buf* (an array of bytes) to the device.
The number of blocks to write is given by the length of *buf*, The number of blocks to write is given by the length of *buf*,
which will be a multiple of the block size. which will be a multiple of the block size.
The second form allows writing at arbitrary locations within a block,
and arbitrary lengths. Only the bytes being written should be changed,
and the caller of this method must ensure that the relevant blocks are
erased via a prior ``ioctl`` call.
Starting at block index *block_num*, and byte offset within that block
of *offset*, write bytes from *buf* (an array of bytes) to the device.
The number of bytes to write is given by the length of *buf*.
Note that implementations must never implicitly erase blocks if the offset
argument is specified, even if it is zero.
.. method:: ioctl(op, arg) .. method:: ioctl(op, arg)
Control the block device and query its parameters. The operation to Control the block device and query its parameters. The operation to
@ -219,6 +245,7 @@ used by a particular filesystem driver to store the data for its filesystem.
- 5 -- get the number of bytes in a block, should return an integer, - 5 -- get the number of bytes in a block, should return an integer,
or ``None`` in which case the default value of 512 is used or ``None`` in which case the default value of 512 is used
(*arg* is unused) (*arg* is unused)
- 6 -- erase a block, *arg* is the block number to erase
By way of example, the following class will implement a block device that stores By way of example, the following class will implement a block device that stores
its data in RAM using a ``bytearray``:: its data in RAM using a ``bytearray``::
@ -250,3 +277,34 @@ It can be used as follows::
uos.VfsFat.mkfs(bdev) uos.VfsFat.mkfs(bdev)
vfs = uos.VfsFat(bdev) vfs = uos.VfsFat(bdev)
uos.mount(vfs, '/ramdisk') uos.mount(vfs, '/ramdisk')
An example of a block device that supports both signatures and behaviours of
the :meth:`readblocks` and :meth:`writeblocks` methods is::
class RAMBlockDev:
def __init__(self, block_size, num_blocks):
self.block_size = block_size
self.data = bytearray(block_size * num_blocks)
def readblocks(self, block, buf, offset=0):
addr = block_num * self.block_size + offset
for i in range(len(buf)):
buf[i] = self.data[addr + i]
def writeblocks(self, block_num, buf, offset=None):
if offset is None:
# do erase, then write
for i in range(len(buf) // self.block_size):
self.ioctl(6, block_num + i)
offset = 0
addr = block_num * self.block_size + offset
for i in range(len(buf)):
self.data[addr + i] = buf[i]
def ioctl(self, op, arg):
if op == 4: # block count
return len(self.data) // self.block_size
if op == 5: # block size
return self.block_size
if op == 6: # block erase
return 0

View File

@ -20,7 +20,7 @@ When you save, the red light on the pyboard should turn on for about a second. T
So what does this code do? First we need some terminology. Python is an object-oriented language, almost everything in python is a *class* and when you create an instance of a class you get an *object*. Classes have *methods* associated to them. A method (also called a member function) is used to interact with or control the object. So what does this code do? First we need some terminology. Python is an object-oriented language, almost everything in python is a *class* and when you create an instance of a class you get an *object*. Classes have *methods* associated to them. A method (also called a member function) is used to interact with or control the object.
The first line of code creates an LED object which we have then called led. When we create the object, it takes a single parameter which must be between 1 and 4, corresponding to the 4 LEDs on the board. The pyb.LED class has three important member functions that we will use: on(), off() and toggle(). The other function that we use is pyb.delay() this simply waits for a given time in miliseconds. Once we have created the LED object, the statement while True: creates an infinite loop which toggles the led between on and off and waits for 1 second. The first line of code creates an LED object which we have then called led. When we create the object, it takes a single parameter which must be between 1 and 4, corresponding to the 4 LEDs on the board. The pyb.LED class has three important member functions that we will use: on(), off() and toggle(). The other function that we use is pyb.delay() this simply waits for a given time in milliseconds. Once we have created the LED object, the statement while True: creates an infinite loop which toggles the led between on and off and waits for 1 second.
**Exercise: Try changing the time between toggling the led and turning on a different LED.** **Exercise: Try changing the time between toggling the led and turning on a different LED.**

View File

@ -4,152 +4,197 @@ Glossary
.. glossary:: .. glossary::
baremetal baremetal
A system without a (full-fledged) OS, for example an A system without a (full-fledged) operating system, for example an
:term:`MCU`-based system. When running on a baremetal system, :term:`MCU`-based system. When running on a baremetal system,
MicroPython effectively becomes its user-facing OS with a command MicroPython effectively functions like a small operating system,
interpreter (REPL). running user programs and providing a command interpreter
(:term:`REPL`).
buffer protocol
Any Python object that can be automatically converted into bytes, such
as ``bytes``, ``bytearray``, ``memoryview`` and ``str`` objects, which
all implement the "buffer protocol".
board board
A PCB board. Oftentimes, the term is used to denote a particular Typically this refers to a printed circuit board (PCB) containing a
model of an :term:`MCU` system. Sometimes, it is used to actually :term:`microcontroller <MCU>` and supporting components.
refer to :term:`MicroPython port` to a particular board (and then MicroPython firmware is typically provided per-board, as the firmware
may also refer to "boardless" ports like contains both MCU-specific functionality but also board-level
:term:`Unix port <MicroPython Unix port>`). functionality such as drivers or pin names.
bytecode
A compact representation of a Python program that generated by
compiling the Python source code. This is what the VM actually
executes. Bytecode is typically generated automatically at runtime and
is invisible to the user. Note that while :term:`CPython` and
MicroPython both use bytecode, the format is different. You can also
pre-compile source code offline using the :term:`cross-compiler`.
callee-owned tuple callee-owned tuple
A tuple returned by some builtin function/method, containing data This is a MicroPython-specific construct where, for efficiency
which is valid for a limited time, usually until next call to the reasons, some built-in functions or methods may re-use the same
same function (or a group of related functions). After next call, underlying tuple object to return data. This avoids having to allocate
data in the tuple may be changed. This leads to the following a new tuple for every call, and reduces heap fragmentation. Programs
restriction on the usage of callee-owned tuples - references to should not hold references to callee-owned tuples and instead only
them cannot be stored. The only valid operation is extracting extract data from them (or make a copy).
values from them (including making a copy). Callee-owned tuples
is a MicroPython-specific construct (not available in the general CircuitPython
Python language), introduced for memory allocation optimization. A variant of MicroPython developed by `Adafruit Industries
The idea is that callee-owned tuple is allocated once and stored <https://circuitpython.org>`_.
on the callee side. Subsequent calls don't require allocation,
allowing to return multiple values when allocation is not possible
(e.g. in interrupt context) or not desirable (because allocation
inherently leads to memory fragmentation). Note that callee-owned
tuples are effectively mutable tuples, making an exception to
Python's rule that tuples are immutable. (It may be interesting
why tuples were used for such a purpose then, instead of mutable
lists - the reason for that is that lists are mutable from user
application side too, so a user could do things to a callee-owned
list which the callee doesn't expect and could lead to problems;
a tuple is protected from this.)
CPython CPython
CPython is the reference implementation of Python programming CPython is the reference implementation of the Python programming
language, and the most well-known one, which most of the people language, and the most well-known one. It is, however, one of many
run. It is however one of many implementations (among which implementations (including Jython, IronPython, PyPy, and MicroPython).
Jython, IronPython, PyPy, and many more, including MicroPython). While MicroPython's implementation differs substantially from CPython,
As there is no formal specification of the Python language, only it aims to maintain as much compatibility as possible.
CPython documentation, it is not always easy to draw a line
between Python the language and CPython its particular cross-compiler
implementation. This however leaves more freedom for other Also known as ``mpy-cross``. This tool runs on your PC and converts a
implementations. For example, MicroPython does a lot of things :term:`.py file` containing MicroPython code into a :term:`.mpy file`
differently than CPython, while still aspiring to be a Python containing MicroPython bytecode. This means it loads faster (the board
language implementation. doesn't have to compile the code), and uses less space on flash (the
bytecode is more space efficient).
driver
A MicroPython library that implements support for a particular
component, such as a sensor or display.
FFI
Acronym for Foreign Function Interface. A mechanism used by the
:term:`MicroPython Unix port` to access operating system functionality.
This is not available on :term:`baremetal` ports.
filesystem
Most MicroPython ports and boards provide a filesystem stored in flash
that is available to user code via the standard Python file APIs such
as ``open()``. Some boards also make this internal filesystem
accessible to the host via USB mass-storage.
frozen module
A Python module that has been cross compiled and bundled into the
firmware image. This reduces RAM requirements as the code is executed
directly from flash.
Garbage Collector
A background process that runs in Python (and MicroPython) to reclaim
unused memory in the :term:`heap`.
GPIO GPIO
General-purpose input/output. The simplest means to control General-purpose input/output. The simplest means to control electrical
electrical signals. With GPIO, user can configure hardware signals (commonly referred to as "pins") on a microcontroller. GPIO
signal pin to be either input or output, and set or get typically allows pins to be either input or output, and to set or get
its digital signal value (logical "0" or "1"). MicroPython their digital value (logical "0" or "1"). MicroPython abstracts GPIO
abstracts GPIO access using :class:`machine.Pin` and :class:`machine.Signal` access using the :class:`machine.Pin` and :class:`machine.Signal`
classes. classes.
GPIO port GPIO port
A group of :term:`GPIO` pins, usually based on hardware A group of :term:`GPIO` pins, usually based on hardware properties of
properties of these pins (e.g. controllable by the same these pins (e.g. controllable by the same register).
register).
heap
A region of RAM where MicroPython stores dynamic data. It is managed
automatically by the :term:`Garbage Collector`. Different MCUs and
boards have vastly different amounts of RAM available for the heap, so
this will affect how complex your program can be.
interned string interned string
A string referenced by its (unique) identity rather than its An optimisation used by MicroPython to improve the efficiency of
address. Interned strings are thus can be quickly compared just working with strings. An interned string is referenced by its (unique)
by their identifiers, instead of comparing by content. The identity rather than its address and can therefore be quickly compared
drawbacks of interned strings are that interning operation takes just by its identifier. It also means that identical strings can be
time (proportional to the number of existing interned strings, de-duplicated in memory. String interning is almost always invisible to
i.e. becoming slower and slower over time) and that the space the user.
used for interned strings is not reclaimable. String interning
is done automatically by MicroPython compiler and runtimer when
it's either required by the implementation (e.g. function keyword
arguments are represented by interned string id's) or deemed
beneficial (e.g. for short enough strings, which have a chance
to be repeated, and thus interning them would save memory on
copies). Most of string and I/O operations don't produce interned
strings due to drawbacks described above.
MCU MCU
Microcontroller. Microcontrollers usually have much less resources Microcontroller. Microcontrollers usually have much less resources
than a full-fledged computing system, but smaller, cheaper and than a desktop, laptop, or phone, but are smaller, cheaper and
require much less power. MicroPython is designed to be small and require much less power. MicroPython is designed to be small and
optimized enough to run on an average modern microcontroller. optimized enough to run on an average modern microcontroller.
micropython-lib micropython-lib
MicroPython is (usually) distributed as a single executable/binary MicroPython is (usually) distributed as a single executable/binary
file with just few builtin modules. There is no extensive standard file with just few builtin modules. There is no extensive standard
library comparable with :term:`CPython`. Instead, there is a related, but library comparable with :term:`CPython`'s. Instead, there is a related,
separate project but separate project `micropython-lib
`micropython-lib <https://github.com/micropython/micropython-lib>`_ <https://github.com/micropython/micropython-lib>`_ which provides
which provides implementations for many modules from CPython's implementations for many modules from CPython's standard library.
standard library. However, large subset of these modules require
POSIX-like environment (Linux, FreeBSD, MacOS, etc.; Windows may be
partially supported), and thus would work or make sense only with
`MicroPython Unix port`. Some subset of modules is however usable
for `baremetal` ports too.
Unlike monolithic :term:`CPython` stdlib, micropython-lib modules Some of the modules are are implemented in pure Python, and are able to
are intended to be installed individually - either using manual be used on all ports. However, the majority of these modules use
copying or using :term:`upip`. :term:`FFI` to access operating system functionality, and as such can
only be used on the :term:`MicroPython Unix port` (with limited support
for Windows).
Unlike the :term:`CPython` stdlib, micropython-lib modules are
intended to be installed individually - either using manual copying or
using :term:`upip`.
MicroPython port MicroPython port
MicroPython supports different :term:`boards <board>`, RTOSes, MicroPython supports different :term:`boards <board>`, RTOSes, and
and OSes, and can be relatively easily adapted to new systems. OSes, and can be relatively easily adapted to new systems. MicroPython
MicroPython with support for a particular system is called a with support for a particular system is called a "port" to that
"port" to that system. Different ports may have widely different system. Different ports may have widely different functionality. This
functionality. This documentation is intended to be a reference documentation is intended to be a reference of the generic APIs
of the generic APIs available across different ports ("MicroPython available across different ports ("MicroPython core"). Note that some
core"). Note that some ports may still omit some APIs described ports may still omit some APIs described here (e.g. due to resource
here (e.g. due to resource constraints). Any such differences, constraints). Any such differences, and port-specific extensions
and port-specific extensions beyond MicroPython core functionality, beyond the MicroPython core functionality, would be described in the
would be described in the separate port-specific documentation. separate port-specific documentation.
MicroPython Unix port MicroPython Unix port
Unix port is one of the major :term:`MicroPython ports <MicroPython port>`. The unix port is one of the major :term:`MicroPython ports
It is intended to run on POSIX-compatible operating systems, like <MicroPython port>`. It is intended to run on POSIX-compatible
Linux, MacOS, FreeBSD, Solaris, etc. It also serves as the basis operating systems, like Linux, MacOS, FreeBSD, Solaris, etc. It also
of Windows port. The importance of Unix port lies in the fact serves as the basis of Windows port. The Unix port is very useful for
that while there are many different :term:`boards <board>`, so quick development and testing of the MicroPython language and
two random users unlikely have the same board, almost all modern machine-independent features. It can also function in a similar way to
OSes have some level of POSIX compatibility, so Unix port serves :term:`CPython`'s ``python`` executable.
as a kind of "common ground" to which any user can have access.
So, Unix port is used for initial prototyping, different kinds .mpy file
of testing, development of machine-independent features, etc. The output of the :term:`cross-compiler`. A compiled form of a
All users of MicroPython, even those which are interested only :term:`.py file` that contains MicroPython bytecode instead of Python
in running MicroPython on :term:`MCU` systems, are recommended source code.
to be familiar with Unix (or Windows) port, as it is important
productivity helper and a part of normal MicroPython workflow. native
Usually refers to "native code", i.e. machine code for the target
microcontroller (such as ARM Thumb, Xtensa, x86/x64). The ``@native``
decorator can be applied to a MicroPython function to generate native
code instead of bytecode for that function, which will likely be
faster but use more RAM.
port port
Either :term:`MicroPython port` or :term:`GPIO port`. If not clear Usually short for :term:`MicroPython port`, but could also refer to
from context, it's recommended to use full specification like one :term:`GPIO port`.
of the above.
.py file
A file containing Python source code.
REPL
An acronym for "Read, Eval, Print, Loop". This is the interactive
Python prompt, useful for debugging or testing short snippets of code.
Most MicroPython boards make a REPL available over a UART, and this is
typically accessible on a host PC via USB.
stream stream
Also known as a "file-like object". An object which provides sequential Also known as a "file-like object". An Python object which provides
read-write access to the underlying data. A stream object implements sequential read-write access to the underlying data. A stream object
a corresponding interface, which consists of methods like ``read()``, implements a corresponding interface, which consists of methods like
``write()``, ``readinto()``, ``seek()``, ``flush()``, ``close()``, etc. ``read()``, ``write()``, ``readinto()``, ``seek()``, ``flush()``,
A stream is an important concept in MicroPython, many I/O objects ``close()``, etc. A stream is an important concept in MicroPython;
implement the stream interface, and thus can be used consistently and many I/O objects implement the stream interface, and thus can be used
interchangeably in different contexts. For more information on consistently and interchangeably in different contexts. For more
streams in MicroPython, see `uio` module. information on streams in MicroPython, see the `uio` module.
UART
Acronym for "Universal Asynchronous Receiver/Transmitter". This is a
peripheral that sends data over a pair of pins (TX & RX). Many boards
include a way to make at least one of the UARTs available to a host PC
as a serial port over USB.
upip upip
(Literally, "micro pip"). A package manage for MicroPython, inspired (Literally, "micro pip"). A package manager for MicroPython, inspired
by :term:`CPython`'s pip, but much smaller and with reduced functionality. by :term:`CPython`'s pip, but much smaller and with reduced
upip runs both on :term:`Unix port <MicroPython Unix port>` and on functionality.
:term:`baremetal` ports (those which offer filesystem and networking upip runs both on the :term:`Unix port <MicroPython Unix port>` and on
support). :term:`baremetal` ports which offer filesystem and networking support.

View File

@ -39,7 +39,7 @@ The MicroPython distribution package format is a well-known tar.gz
format, with some adaptations however. The Gzip compressor, used as format, with some adaptations however. The Gzip compressor, used as
an external wrapper for TAR archives, by default uses 32KB dictionary an external wrapper for TAR archives, by default uses 32KB dictionary
size, which means that to uncompress a compressed stream, 32KB of size, which means that to uncompress a compressed stream, 32KB of
contguous memory needs to be allocated. This requirement may be not contiguous memory needs to be allocated. This requirement may be not
satisfiable on low-memory devices, which may have total memory available satisfiable on low-memory devices, which may have total memory available
less than that amount, and even if not, a contiguous block like that less than that amount, and even if not, a contiguous block like that
may be hard to allocate due to memory fragmentation. To accommodate may be hard to allocate due to memory fragmentation. To accommodate
@ -132,7 +132,7 @@ Installing to a directory image involves using ``-p`` switch to `upip`::
micropython -m upip install -p install_dir micropython-pystone_lowmem micropython -m upip install -p install_dir micropython-pystone_lowmem
After this command, the package content (and contents of every depenency After this command, the package content (and contents of every dependency
packages) will be available in the ``install_dir/`` subdirectory. You packages) will be available in the ``install_dir/`` subdirectory. You
would need to transfer contents of this directory (without the would need to transfer contents of this directory (without the
``install_dir/`` prefix) to the device, at the suitable location, where ``install_dir/`` prefix) to the device, at the suitable location, where

View File

@ -214,7 +214,7 @@ There are certain limitations in the current implementation of the native code e
* Generators are not supported. * Generators are not supported.
* If ``raise`` is used an argument must be supplied. * If ``raise`` is used an argument must be supplied.
The trade-off for the improved performance (roughly twices as fast as bytecode) is an The trade-off for the improved performance (roughly twice as fast as bytecode) is an
increase in compiled code size. increase in compiled code size.
The Viper code emitter The Viper code emitter

View File

@ -1,19 +1,17 @@
Getting started with Blynk and the WiPy Getting started with Blynk and the WiPy
--------------------------------------- ---------------------------------------
Blynk is a platform with iOS and Android apps to control Blynk provides iOS and Android apps to control any hardware over the Internet
Arduino, Raspberry Pi and the likes over the Internet. or directly using Bluetooth. You can easily build graphic interfaces for all
You can easily build graphic interfaces for all your your projects by simply dragging and dropping widgets, right on your smartphone.
projects by simply dragging and dropping widgets.
There are several examples available that work out-of-the-box with Before anything else, make sure that your WiPy is running
the WiPy. Before anything else, make sure that your WiPy is running
the latest software, check :ref:`OTA How-To <wipy_firmware_upgrade>` for instructions. the latest software, check :ref:`OTA How-To <wipy_firmware_upgrade>` for instructions.
1. Get the `Blynk library <https://github.com/wipy/wipy/blob/master/lib/blynk/BlynkLib.py>`_ and put it in ``/flash/lib/`` via FTP. 1. Get the `Blynk library <https://github.com/vshymanskyy/blynk-library-python/blob/master/BlynkLib.py>`_ and put it in ``/flash/lib/`` via FTP.
2. Get the `Blynk examples <https://github.com/wipy/wipy/tree/master/examples/blynk>`_, edit the network settings, and afterwards 2. Get the `Blynk example for WiPy <https://github.com/vshymanskyy/blynk-library-python/blob/master/examples/hardware/PyCom_WiPy.py>`_, edit the network settings, and afterwards
upload them to ``/flash/lib/`` via FTP as well. upload it to ``/flash/`` via FTP as well.
3. Follow the instructions on each example to setup the Blynk dashboard on your smartphone or tablet. 3. Follow the instructions on each example to setup the Blynk dashboard on your smartphone or tablet.
4. Give it a try, for instance:: 4. Give it a try, for instance::
>>> execfile('01_simple.py') >>> execfile('sync_virtual.py')

222
drivers/cyw43/cywbt.c Normal file
View File

@ -0,0 +1,222 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <string.h>
#include "py/runtime.h"
#include "py/mphal.h"
#include "pin_static_af.h"
#include "uart.h"
#include "cywbt.h"
#include "nimble/hci_uart.h"
#if MICROPY_PY_NETWORK_CYW43
extern const char fw_4343WA1_7_45_98_50_start;
#define CYWBT_FW_ADDR (&fw_4343WA1_7_45_98_50_start + 749 * 512 + 29 * 256)
/******************************************************************************/
// CYW BT HCI low-level driver
static void cywbt_wait_cts_low(void) {
mp_hal_pin_config(pyb_pin_BT_CTS, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_UP, 0);
for (int i = 0; i < 200; ++i) {
if (mp_hal_pin_read(pyb_pin_BT_CTS) == 0) {
break;
}
mp_hal_delay_ms(1);
}
mp_hal_pin_config_alt_static(pyb_pin_BT_CTS, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, STATIC_AF_USART6_CTS);
}
static int cywbt_hci_cmd_raw(size_t len, uint8_t *buf) {
uart_tx_strn(&bt_hci_uart_obj, (void*)buf, len);
for (int i = 0; i < 6; ++i) {
while (!uart_rx_any(&bt_hci_uart_obj)) {
MICROPY_EVENT_POLL_HOOK
}
buf[i] = uart_rx_char(&bt_hci_uart_obj);
}
// expect a comand complete event (event 0x0e)
if (buf[0] != 0x04 || buf[1] != 0x0e) {
printf("unknown response: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]);
return -1;
}
/*
if buf[3:6] != cmd[:3]:
print('response doesn\'t match cmd:', cmd, ev)
return b''
*/
int sz = buf[2] - 3;
for (int i = 0; i < sz; ++i) {
while (!uart_rx_any(&bt_hci_uart_obj)) {
MICROPY_EVENT_POLL_HOOK
}
buf[i] = uart_rx_char(&bt_hci_uart_obj);
}
return 0;
}
static int cywbt_hci_cmd(int ogf, int ocf, size_t param_len, const uint8_t *param_buf) {
uint8_t *buf = bt_hci_cmd_buf;
buf[0] = 0x01;
buf[1] = ocf;
buf[2] = ogf << 2 | ocf >> 8;
buf[3] = param_len;
if (param_len) {
memcpy(buf + 4, param_buf, param_len);
}
return cywbt_hci_cmd_raw(4 + param_len, buf);
}
static void put_le16(uint8_t *buf, uint16_t val) {
buf[0] = val;
buf[1] = val >> 8;
}
static void put_le32(uint8_t *buf, uint32_t val) {
buf[0] = val;
buf[1] = val >> 8;
buf[2] = val >> 16;
buf[3] = val >> 24;
}
static int cywbt_set_baudrate(uint32_t baudrate) {
uint8_t buf[6];
put_le16(buf, 0);
put_le32(buf + 2, baudrate);
return cywbt_hci_cmd(0x3f, 0x18, 6, buf);
}
// download firmware
static int cywbt_download_firmware(const uint8_t *firmware) {
cywbt_hci_cmd(0x3f, 0x2e, 0, NULL);
bool last_packet = false;
while (!last_packet) {
uint8_t *buf = bt_hci_cmd_buf;
memcpy(buf + 1, firmware, 3);
firmware += 3;
last_packet = buf[1] == 0x4e;
if (buf[2] != 0xfc) {
printf("fail1 %02x\n", buf[2]);
break;
}
uint8_t len = buf[3];
memcpy(buf + 4, firmware, len);
firmware += len;
buf[0] = 1;
cywbt_hci_cmd_raw(4 + len, buf);
if (buf[0] != 0) {
printf("fail3 %02x\n", buf[0]);
break;
}
}
// RF switch must select high path during BT patch boot
mp_hal_pin_config(pyb_pin_WL_GPIO_1, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_UP, 0);
mp_hal_delay_ms(10); // give some time for CTS to go high
cywbt_wait_cts_low();
mp_hal_pin_config(pyb_pin_WL_GPIO_1, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_DOWN, 0); // Select chip antenna (could also select external)
nimble_hci_uart_set_baudrate(115200);
cywbt_set_baudrate(3000000);
nimble_hci_uart_set_baudrate(3000000);
return 0;
}
int cywbt_init(void) {
// This is called from Nimble via hal_uart_config which will have already initialized the UART.
mp_hal_pin_output(pyb_pin_BT_REG_ON);
mp_hal_pin_low(pyb_pin_BT_REG_ON);
mp_hal_pin_input(pyb_pin_BT_HOST_WAKE);
mp_hal_pin_output(pyb_pin_BT_DEV_WAKE);
mp_hal_pin_low(pyb_pin_BT_DEV_WAKE);
// TODO don't select antenna if wifi is enabled
mp_hal_pin_config(pyb_pin_WL_GPIO_4, MP_HAL_PIN_MODE_OUTPUT, MP_HAL_PIN_PULL_NONE, 0); // RF-switch power
mp_hal_pin_high(pyb_pin_WL_GPIO_4); // Turn the RF-switch on
return 0;
}
int cywbt_activate(void) {
uint8_t buf[256];
mp_hal_pin_low(pyb_pin_BT_REG_ON);
nimble_hci_uart_set_baudrate(115200);
mp_hal_delay_ms(100);
mp_hal_pin_high(pyb_pin_BT_REG_ON);
cywbt_wait_cts_low();
// Reset
cywbt_hci_cmd(0x03, 0x0003, 0, NULL);
// Change baudrate
cywbt_set_baudrate(3000000);
nimble_hci_uart_set_baudrate(3000000);
cywbt_download_firmware((const uint8_t*)CYWBT_FW_ADDR);
// Reset
cywbt_hci_cmd(0x03, 0x0003, 0, NULL);
// Set BD_ADDR (sent as little endian)
uint8_t bdaddr[6];
mp_hal_get_mac(MP_HAL_MAC_BDADDR, bdaddr);
buf[0] = bdaddr[5];
buf[1] = bdaddr[4];
buf[2] = bdaddr[3];
buf[3] = bdaddr[2];
buf[4] = bdaddr[1];
buf[5] = bdaddr[0];
cywbt_hci_cmd(0x3f, 0x0001, 6, buf);
// Set local name
// memset(buf, 0, 248);
// memcpy(buf, "PYBD-BLE", 8);
// cywbt_hci_cmd(0x03, 0x0013, 248, buf);
// Configure sleep mode
cywbt_hci_cmd(0x3f, 0x27, 12, (const uint8_t*)"\x01\x02\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00");
// HCI_Write_LE_Host_Support
cywbt_hci_cmd(3, 109, 2, (const uint8_t*)"\x01\x00");
mp_hal_pin_high(pyb_pin_BT_DEV_WAKE); // let sleep
return 0;
}
#endif

35
drivers/cyw43/cywbt.h Normal file
View File

@ -0,0 +1,35 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_DRIVERS_CYW43_CYWBT_H
#define MICROPY_INCLUDED_DRIVERS_CYW43_CYWBT_H
extern uint8_t bt_hci_cmd_buf[4 + 256];
extern pyb_uart_obj_t bt_hci_uart_obj;
int cywbt_init(void);
int cywbt_activate(void);
#endif // MICROPY_INCLUDED_DRIVERS_CYW43_CYWBT_H

View File

@ -13,7 +13,7 @@ class DS18X20:
self.buf = bytearray(9) self.buf = bytearray(9)
def scan(self): def scan(self):
return [rom for rom in self.ow.scan() if rom[0] == 0x10 or rom[0] == 0x28] return [rom for rom in self.ow.scan() if rom[0] in (0x10, 0x22, 0x28)]
def convert_temp(self): def convert_temp(self):
self.ow.reset(True) self.ow.reset(True)

View File

@ -0,0 +1,48 @@
# Helpers for generating BLE advertising payloads.
from micropython import const
import struct
# Advertising payloads are repeated packets of the following form:
# 1 byte data length (N + 1)
# 1 byte type (see constants below)
# N bytes type-specific data
_ADV_TYPE_FLAGS = const(0x01)
_ADV_TYPE_NAME = const(0x09)
_ADV_TYPE_UUID16_COMPLETE = const(0x3)
_ADV_TYPE_UUID32_COMPLETE = const(0x5)
_ADV_TYPE_UUID128_COMPLETE = const(0x7)
_ADV_TYPE_UUID16_MORE = const(0x2)
_ADV_TYPE_UUID32_MORE = const(0x4)
_ADV_TYPE_UUID128_MORE = const(0x6)
_ADV_TYPE_APPEARANCE = const(0x19)
# Generate a payload to be passed to gap_advertise(adv_data=...).
def advertising_payload(limited_disc=False, br_edr=False, name=None, services=None, appearance=0):
payload = bytearray()
def _append(adv_type, value):
nonlocal payload
payload += struct.pack('BB', len(value) + 1, adv_type) + value
_append(_ADV_TYPE_FLAGS, struct.pack('B', (0x01 if limited_disc else 0x02) + (0x00 if br_edr else 0x04)))
if name:
_append(_ADV_TYPE_NAME, name)
if services:
for uuid in services:
b = bytes(uuid)
if len(b) == 2:
_append(_ADV_TYPE_UUID16_COMPLETE, b)
elif len(b) == 4:
_append(_ADV_TYPE_UUID32_COMPLETE, b)
elif len(b) == 16:
_append(_ADV_TYPE_UUID128_COMPLETE, b)
# See org.bluetooth.characteristic.gap.appearance.xml
_append(_ADV_TYPE_APPEARANCE, struct.pack('<h', appearance))
return payload

View File

@ -0,0 +1,76 @@
# This example demonstrates a simple temperature sensor peripheral.
#
# The sensor's local value updates every second, and it will notify
# any connected central every 10 seconds.
import bluetooth
import random
import struct
import time
from ble_advertising import advertising_payload
from micropython import const
_IRQ_CENTRAL_CONNECT = const(1 << 0)
_IRQ_CENTRAL_DISCONNECT = const(1 << 1)
# org.bluetooth.service.environmental_sensing
_ENV_SENSE_UUID = bluetooth.UUID(0x181A)
# org.bluetooth.characteristic.temperature
_TEMP_CHAR = (bluetooth.UUID(0x2A6E), bluetooth.FLAG_READ|bluetooth.FLAG_NOTIFY,)
_ENV_SENSE_SERVICE = (_ENV_SENSE_UUID, (_TEMP_CHAR,),)
# org.bluetooth.characteristic.gap.appearance.xml
_ADV_APPEARANCE_GENERIC_THERMOMETER = const(768)
class BLETemperature:
def __init__(self, ble, name='mpy-temp'):
self._ble = ble
self._ble.active(True)
self._ble.irq(handler=self._irq)
((self._handle,),) = self._ble.gatts_register_services((_ENV_SENSE_SERVICE,))
self._connections = set()
self._payload = advertising_payload(name=name, services=[_ENV_SENSE_UUID], appearance=_ADV_APPEARANCE_GENERIC_THERMOMETER)
self._advertise()
def _irq(self, event, data):
# Track connections so we can send notifications.
if event == _IRQ_CENTRAL_CONNECT:
conn_handle, _, _, = data
self._connections.add(conn_handle)
elif event == _IRQ_CENTRAL_DISCONNECT:
conn_handle, _, _, = data
self._connections.remove(conn_handle)
# Start advertising again to allow a new connection.
self._advertise()
def set_temperature(self, temp_deg_c, notify=False):
# Data is sint16 in degrees Celsius with a resolution of 0.01 degrees Celsius.
# Write the local value, ready for a central to read.
self._ble.gatts_write(self._handle, struct.pack('<h', int(temp_deg_c * 100)))
if notify:
for conn_handle in self._connections:
# Notify connected centrals to issue a read.
self._ble.gatts_notify(conn_handle, self._handle)
def _advertise(self, interval_us=500000):
self._ble.gap_advertise(interval_us, adv_data=self._payload)
def demo():
ble = bluetooth.BLE()
temp = BLETemperature(ble)
t = 25
i = 0
while True:
# Write every second, notify every 10 seconds.
i = (i + 1) % 10
temp.set_temperature(t, notify=i == 0)
# Random walk the temperature.
t += random.uniform(-0.5, 0.5)
time.sleep_ms(1000)
if __name__ == '__main__':
demo()

View File

@ -0,0 +1,103 @@
# This example demonstrates a peripheral implementing the Nordic UART Service (NUS).
import bluetooth
from ble_advertising import advertising_payload
from micropython import const
_IRQ_CENTRAL_CONNECT = const(1 << 0)
_IRQ_CENTRAL_DISCONNECT = const(1 << 1)
_IRQ_GATTS_WRITE = const(1 << 2)
_UART_UUID = bluetooth.UUID('6E400001-B5A3-F393-E0A9-E50E24DCCA9E')
_UART_TX = (bluetooth.UUID('6E400003-B5A3-F393-E0A9-E50E24DCCA9E'), bluetooth.FLAG_NOTIFY,)
_UART_RX = (bluetooth.UUID('6E400002-B5A3-F393-E0A9-E50E24DCCA9E'), bluetooth.FLAG_WRITE,)
_UART_SERVICE = (_UART_UUID, (_UART_TX, _UART_RX,),)
# org.bluetooth.characteristic.gap.appearance.xml
_ADV_APPEARANCE_GENERIC_COMPUTER = const(128)
class BLEUART:
def __init__(self, ble, name='mpy-uart', rxbuf=100):
self._ble = ble
self._ble.active(True)
self._ble.irq(handler=self._irq)
((self._tx_handle, self._rx_handle,),) = self._ble.gatts_register_services((_UART_SERVICE,))
# Increase the size of the rx buffer and enable append mode.
self._ble.gatts_set_buffer(self._rx_handle, rxbuf, True)
self._connections = set()
self._rx_buffer = bytearray()
self._handler = None
# Optionally add services=[_UART_UUID], but this is likely to make the payload too large.
self._payload = advertising_payload(name=name, appearance=_ADV_APPEARANCE_GENERIC_COMPUTER)
self._advertise()
def irq(self, handler):
self._handler = handler
def _irq(self, event, data):
# Track connections so we can send notifications.
if event == _IRQ_CENTRAL_CONNECT:
conn_handle, _, _, = data
self._connections.add(conn_handle)
elif event == _IRQ_CENTRAL_DISCONNECT:
conn_handle, _, _, = data
if conn_handle in self._connections:
self._connections.remove(conn_handle)
# Start advertising again to allow a new connection.
self._advertise()
elif event == _IRQ_GATTS_WRITE:
conn_handle, value_handle, = data
if conn_handle in self._connections and value_handle == self._rx_handle:
self._rx_buffer += self._ble.gatts_read(self._rx_handle)
if self._handler:
self._handler()
def any(self):
return len(self._rx_buffer)
def read(self, sz=None):
if not sz:
sz = len(self._rx_buffer)
result = self._rx_buffer[0:sz]
self._rx_buffer = self._rx_buffer[sz:]
return result
def write(self, data):
for conn_handle in self._connections:
self._ble.gatts_notify(conn_handle, self._tx_handle, data)
def close(self):
for conn_handle in self._connections:
self._ble.gap_disconnect(conn_handle)
self._connections.clear()
def _advertise(self, interval_us=500000):
self._ble.gap_advertise(interval_us, adv_data=self._payload)
def demo():
import time
ble = bluetooth.BLE()
uart = BLEUART(ble)
def on_rx():
print('rx: ', uart.read().decode().strip())
uart.irq(handler=on_rx)
nums = [4, 8, 15, 16, 23, 42]
i = 0
try:
while True:
uart.write(str(nums[i]) + '\n')
i = (i + 1) % len(nums)
time.sleep_ms(1000)
except KeyboardInterrupt:
pass
uart.close()
if __name__ == '__main__':
demo()

View File

@ -0,0 +1,80 @@
# Proof-of-concept of a REPL over BLE UART.
#
# Tested with the Adafruit Bluefruit app on Android.
# Set the EoL characters to \r\n.
import bluetooth
import io
import os
import micropython
import machine
from ble_uart_peripheral import BLEUART
_MP_STREAM_POLL = const(3)
_MP_STREAM_POLL_RD = const(0x0001)
# TODO: Remove this when STM32 gets machine.Timer.
if hasattr(machine, 'Timer'):
_timer = machine.Timer(-1)
else:
_timer = None
# Batch writes into 50ms intervals.
def schedule_in(handler, delay_ms):
def _wrap(_arg):
handler()
if _timer:
_timer.init(mode=machine.Timer.ONE_SHOT, period=delay_ms, callback=_wrap)
else:
micropython.schedule(_wrap, None)
# Simple buffering stream to support the dupterm requirements.
class BLEUARTStream(io.IOBase):
def __init__(self, uart):
self._uart = uart
self._tx_buf = bytearray()
self._uart.irq(self._on_rx)
def _on_rx(self):
# Needed for ESP32.
if hasattr(os, 'dupterm_notify'):
os.dupterm_notify(None)
def read(self, sz=None):
return self._uart.read(sz)
def readinto(self, buf):
avail = self._uart.read(len(buf))
if not avail:
return None
for i in range(len(avail)):
buf[i] = avail[i]
return len(avail)
def ioctl(self, op, arg):
if op == _MP_STREAM_POLL:
if self._uart.any():
return _MP_STREAM_POLL_RD
return 0
def _flush(self):
data = self._tx_buf[0:100]
self._tx_buf = self._tx_buf[100:]
self._uart.write(data)
if self._tx_buf:
schedule_in(self._flush, 50)
def write(self, buf):
empty = not self._tx_buf
self._tx_buf += buf
if empty:
schedule_in(self._flush, 50)
def start():
ble = bluetooth.BLE()
uart = BLEUART(ble, name='mpy-repl')
stream = BLEUARTStream(uart)
os.dupterm(stream)

View File

@ -1 +0,0 @@
mpconfigport_minimal.h

View File

@ -0,0 +1 @@
#include "mpconfigport_minimal.h"

View File

@ -3,6 +3,29 @@
# this sets the config file for FatFs # this sets the config file for FatFs
CFLAGS_MOD += -DFFCONF_H=\"lib/oofatfs/ffconf.h\" CFLAGS_MOD += -DFFCONF_H=\"lib/oofatfs/ffconf.h\"
################################################################################
# VFS littlefs
LITTLEFS_DIR = lib/littlefs
ifeq ($(MICROPY_VFS_LFS1),1)
CFLAGS_MOD += -DMICROPY_VFS_LFS1=1
CFLAGS_MOD += -DLFS1_NO_MALLOC -DLFS1_NO_DEBUG -DLFS1_NO_WARN -DLFS1_NO_ERROR -DLFS1_NO_ASSERT
SRC_MOD += $(addprefix $(LITTLEFS_DIR)/,\
lfs1.c \
lfs1_util.c \
)
endif
ifeq ($(MICROPY_VFS_LFS2),1)
CFLAGS_MOD += -DMICROPY_VFS_LFS2=1
CFLAGS_MOD += -DLFS2_NO_MALLOC -DLFS2_NO_DEBUG -DLFS2_NO_WARN -DLFS2_NO_ERROR -DLFS2_NO_ASSERT
SRC_MOD += $(addprefix $(LITTLEFS_DIR)/,\
lfs2.c \
lfs2_util.c \
)
endif
################################################################################ ################################################################################
# ussl # ussl

965
extmod/modbluetooth.c Normal file
View File

@ -0,0 +1,965 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2018 Ayke van Laethem
* Copyright (c) 2019 Jim Mussared
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "py/binary.h"
#include "py/misc.h"
#include "py/mperrno.h"
#include "py/obj.h"
#include "py/objstr.h"
#include "py/objarray.h"
#include "py/qstr.h"
#include "py/runtime.h"
#include "extmod/modbluetooth.h"
#include <string.h>
#if MICROPY_PY_BLUETOOTH
#if !MICROPY_ENABLE_SCHEDULER
#error modbluetooth requires MICROPY_ENABLE_SCHEDULER
#endif
// This is used to protect the ringbuffer.
#ifndef MICROPY_PY_BLUETOOTH_ENTER
#define MICROPY_PY_BLUETOOTH_ENTER mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
#define MICROPY_PY_BLUETOOTH_EXIT MICROPY_END_ATOMIC_SECTION(atomic_state);
#endif
#define MP_BLUETOOTH_CONNECT_DEFAULT_SCAN_DURATION_MS 2000
#define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN 5
#define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN (MICROPY_PY_BLUETOOTH_RINGBUF_SIZE / 2)
STATIC const mp_obj_type_t bluetooth_ble_type;
STATIC const mp_obj_type_t bluetooth_uuid_type;
typedef struct {
mp_obj_base_t base;
mp_obj_t irq_handler;
mp_obj_t irq_data_tuple;
uint8_t irq_addr_bytes[6];
uint8_t irq_data_bytes[MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN];
mp_obj_t irq_data_uuid;
uint16_t irq_trigger;
ringbuf_t ringbuf;
} mp_obj_bluetooth_ble_t;
// TODO: this seems like it could be generic?
STATIC mp_obj_t bluetooth_handle_errno(int err) {
if (err != 0) {
mp_raise_OSError(err);
}
return mp_const_none;
}
// ----------------------------------------------------------------------------
// UUID object
// ----------------------------------------------------------------------------
// Parse string UUIDs, which are expected to be 128-bit UUIDs.
STATIC void mp_bluetooth_parse_uuid_128bit_str(mp_obj_t obj, uint8_t *uuid) {
size_t str_len;
const char *str_data = mp_obj_str_get_data(obj, &str_len);
int uuid_i = 32;
for (int i = 0; i < str_len; i++) {
char c = str_data[i];
if (c == '-') {
continue;
}
if (!unichar_isxdigit(c)) {
mp_raise_ValueError("invalid char in UUID");
}
c = unichar_xdigit_value(c);
uuid_i--;
if (uuid_i < 0) {
mp_raise_ValueError("UUID too long");
}
if (uuid_i % 2 == 0) {
// lower nibble
uuid[uuid_i/2] |= c;
} else {
// upper nibble
uuid[uuid_i/2] = c << 4;
}
}
if (uuid_i > 0) {
mp_raise_ValueError("UUID too short");
}
}
STATIC mp_obj_t bluetooth_uuid_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
mp_arg_check_num(n_args, n_kw, 1, 1, false);
mp_obj_bluetooth_uuid_t *self = m_new_obj(mp_obj_bluetooth_uuid_t);
self->base.type = &bluetooth_uuid_type;
if (mp_obj_is_int(all_args[0])) {
self->type = MP_BLUETOOTH_UUID_TYPE_16;
mp_int_t value = mp_obj_get_int(all_args[0]);
if (value > 65535) {
mp_raise_ValueError("invalid UUID");
}
self->data[0] = value & 0xff;
self->data[1] = (value >> 8) & 0xff;
} else {
self->type = MP_BLUETOOTH_UUID_TYPE_128;
mp_bluetooth_parse_uuid_128bit_str(all_args[0], self->data);
}
return self;
}
STATIC mp_obj_t bluetooth_uuid_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
mp_obj_bluetooth_uuid_t *self = MP_OBJ_TO_PTR(self_in);
switch (op) {
case MP_UNARY_OP_HASH: {
// Use the QSTR hash function.
return MP_OBJ_NEW_SMALL_INT(qstr_compute_hash(self->data, self->type));
}
default: return MP_OBJ_NULL; // op not supported
}
}
STATIC void bluetooth_uuid_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
mp_obj_bluetooth_uuid_t *self = MP_OBJ_TO_PTR(self_in);
mp_printf(print, "UUID%u(%s", self->type * 8, self->type <= 4 ? "0x" : "'");
for (int i = 0; i < self->type; ++i) {
if (i == 4 || i == 6 || i == 8 || i == 10) {
mp_printf(print, "-");
}
mp_printf(print, "%02x", self->data[self->type - 1 - i]);
}
if (self->type == MP_BLUETOOTH_UUID_TYPE_128) {
mp_printf(print, "'");
}
mp_printf(print, ")");
}
mp_int_t bluetooth_uuid_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) {
mp_obj_bluetooth_uuid_t *self = MP_OBJ_TO_PTR(self_in);
if (flags != MP_BUFFER_READ) {
return 1;
}
bufinfo->buf = self->data;
bufinfo->len = self->type;
bufinfo->typecode = 'B';
return 0;
}
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
STATIC void ringbuf_put_uuid(ringbuf_t *ringbuf, mp_obj_bluetooth_uuid_t *uuid) {
assert(ringbuf_free(ringbuf) >= uuid->type + 1);
ringbuf_put(ringbuf, uuid->type);
for (int i = 0; i < uuid->type; ++i) {
ringbuf_put(ringbuf, uuid->data[i]);
}
}
STATIC void ringbuf_get_uuid(ringbuf_t *ringbuf, mp_obj_bluetooth_uuid_t *uuid) {
assert(ringbuf_avail(ringbuf) >= 1);
uuid->type = ringbuf_get(ringbuf);
assert(ringbuf_avail(ringbuf) >= uuid->type);
for (int i = 0; i < uuid->type; ++i) {
uuid->data[i] = ringbuf_get(ringbuf);
}
}
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
STATIC const mp_obj_type_t bluetooth_uuid_type = {
{ &mp_type_type },
.name = MP_QSTR_UUID,
.make_new = bluetooth_uuid_make_new,
.unary_op = bluetooth_uuid_unary_op,
.locals_dict = NULL,
.print = bluetooth_uuid_print,
.buffer_p = { .get_buffer = bluetooth_uuid_get_buffer },
};
// ----------------------------------------------------------------------------
// Bluetooth object: General
// ----------------------------------------------------------------------------
STATIC mp_obj_t bluetooth_ble_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
if (MP_STATE_VM(bluetooth) == MP_OBJ_NULL) {
mp_obj_bluetooth_ble_t *o = m_new_obj(mp_obj_bluetooth_ble_t);
o->base.type = &bluetooth_ble_type;
o->irq_handler = mp_const_none;
// Pre-allocate the event data tuple to prevent needing to allocate in the IRQ handler.
o->irq_data_tuple = mp_obj_new_tuple(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN, NULL);
mp_obj_bluetooth_uuid_t *uuid = m_new_obj(mp_obj_bluetooth_uuid_t);
uuid->base.type = &bluetooth_uuid_type;
o->irq_data_uuid = MP_OBJ_FROM_PTR(uuid);
o->irq_trigger = 0;
ringbuf_alloc(&o->ringbuf, MICROPY_PY_BLUETOOTH_RINGBUF_SIZE);
MP_STATE_VM(bluetooth) = MP_OBJ_FROM_PTR(o);
}
return MP_STATE_VM(bluetooth);
}
STATIC mp_obj_t bluetooth_ble_active(size_t n_args, const mp_obj_t *args) {
if (n_args == 2) {
// Boolean enable/disable argument supplied, set current state.
if (mp_obj_is_true(args[1])) {
int err = mp_bluetooth_init();
if (err != 0) {
mp_raise_OSError(err);
}
} else {
mp_bluetooth_deinit();
}
}
// Return current state.
return mp_obj_new_bool(mp_bluetooth_is_enabled());
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_active_obj, 1, 2, bluetooth_ble_active);
STATIC mp_obj_t bluetooth_ble_config(mp_obj_t self_in, mp_obj_t param) {
if (param == MP_OBJ_NEW_QSTR(MP_QSTR_mac)) {
uint8_t addr[6];
mp_bluetooth_get_device_addr(addr);
return mp_obj_new_bytes(addr, MP_ARRAY_SIZE(addr));
} else {
mp_raise_ValueError("unknown config param");
}
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_config_obj, bluetooth_ble_config);
STATIC mp_obj_t bluetooth_ble_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_handler, ARG_trigger };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_handler, MP_ARG_OBJ|MP_ARG_REQUIRED, {.u_obj = mp_const_none} },
{ MP_QSTR_trigger, MP_ARG_INT, {.u_int = MP_BLUETOOTH_IRQ_ALL} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
mp_obj_t callback = args[ARG_handler].u_obj;
if (callback != mp_const_none && !mp_obj_is_callable(callback)) {
mp_raise_ValueError("invalid callback");
}
// Update the callback.
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t* o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
o->irq_handler = callback;
o->irq_trigger = args[ARG_trigger].u_int;
MICROPY_PY_BLUETOOTH_EXIT
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_ble_irq_obj, 1, bluetooth_ble_irq);
// ----------------------------------------------------------------------------
// Bluetooth object: GAP
// ----------------------------------------------------------------------------
STATIC mp_obj_t bluetooth_ble_gap_advertise(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_interval_us, ARG_adv_data, ARG_resp_data, ARG_connectable };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_interval_us, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(500000)} },
{ MP_QSTR_adv_data, MP_ARG_OBJ, {.u_obj = mp_const_none } },
{ MP_QSTR_resp_data, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } },
{ MP_QSTR_connectable, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_true } },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
if (args[ARG_interval_us].u_obj == mp_const_none) {
mp_bluetooth_gap_advertise_stop();
return mp_const_none;
}
mp_int_t interval_us = mp_obj_get_int(args[ARG_interval_us].u_obj);
bool connectable = mp_obj_is_true(args[ARG_connectable].u_obj);
mp_buffer_info_t adv_bufinfo = {0};
if (args[ARG_adv_data].u_obj != mp_const_none) {
mp_get_buffer_raise(args[ARG_adv_data].u_obj, &adv_bufinfo, MP_BUFFER_READ);
}
mp_buffer_info_t resp_bufinfo = {0};
if (args[ARG_resp_data].u_obj != mp_const_none) {
mp_get_buffer_raise(args[ARG_resp_data].u_obj, &resp_bufinfo, MP_BUFFER_READ);
}
return bluetooth_handle_errno(mp_bluetooth_gap_advertise_start(connectable, interval_us, adv_bufinfo.buf, adv_bufinfo.len, resp_bufinfo.buf, resp_bufinfo.len));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_ble_gap_advertise_obj, 1, bluetooth_ble_gap_advertise);
STATIC int bluetooth_gatts_register_service(mp_obj_t uuid_in, mp_obj_t characteristics_in, uint16_t **handles, size_t *num_handles) {
if (!mp_obj_is_type(uuid_in, &bluetooth_uuid_type)) {
mp_raise_ValueError("invalid service UUID");
}
mp_obj_bluetooth_uuid_t *service_uuid = MP_OBJ_TO_PTR(uuid_in);
mp_obj_t len_in = mp_obj_len(characteristics_in);
size_t len = mp_obj_get_int(len_in);
mp_obj_iter_buf_t iter_buf;
mp_obj_t iterable = mp_getiter(characteristics_in, &iter_buf);
mp_obj_t characteristic_obj;
// Lists of characteristic uuids and flags.
mp_obj_bluetooth_uuid_t **characteristic_uuids = m_new(mp_obj_bluetooth_uuid_t*, len);
uint8_t *characteristic_flags = m_new(uint8_t, len);
// Flattened list of descriptor uuids and flags. Grows (realloc) as more descriptors are encountered.
mp_obj_bluetooth_uuid_t **descriptor_uuids = NULL;
uint8_t *descriptor_flags = NULL;
// How many descriptors in the flattened list per characteristic.
uint8_t *num_descriptors = m_new(uint8_t, len);
// Inititally allocate enough room for the number of characteristics.
// Will be grown to accommodate descriptors as necessary.
*num_handles = len;
*handles = m_new(uint16_t, *num_handles);
// Extract out characteristic uuids & flags.
int characteristic_index = 0; // characteristic index.
int handle_index = 0; // handle index.
int descriptor_index = 0; // descriptor index.
while ((characteristic_obj = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
// (uuid, flags, (optional descriptors),)
size_t characteristic_len;
mp_obj_t *characteristic_items;
mp_obj_get_array(characteristic_obj, &characteristic_len, &characteristic_items);
if (characteristic_len < 2 || characteristic_len > 3) {
mp_raise_ValueError("invalid characteristic tuple");
}
mp_obj_t uuid_obj = characteristic_items[0];
if (!mp_obj_is_type(uuid_obj, &bluetooth_uuid_type)) {
mp_raise_ValueError("invalid characteristic UUID");
}
(*handles)[handle_index++] = 0xffff;
// Optional third element, iterable of descriptors.
if (characteristic_len >= 3) {
mp_obj_t descriptors_len_in = mp_obj_len(characteristic_items[2]);
num_descriptors[characteristic_index] = mp_obj_get_int(descriptors_len_in);
if (num_descriptors[characteristic_index] == 0) {
continue;
}
// Grow the flattened uuids and flags arrays with this many more descriptors.
descriptor_uuids = m_renew(mp_obj_bluetooth_uuid_t*, descriptor_uuids, descriptor_index, descriptor_index + num_descriptors[characteristic_index]);
descriptor_flags = m_renew(uint8_t, descriptor_flags, descriptor_index, descriptor_index + num_descriptors[characteristic_index]);
// Also grow the handles array.
*handles = m_renew(uint16_t, *handles, *num_handles, *num_handles + num_descriptors[characteristic_index]);
mp_obj_iter_buf_t iter_buf_desc;
mp_obj_t iterable_desc = mp_getiter(characteristic_items[2], &iter_buf_desc);
mp_obj_t descriptor_obj;
// Extract out descriptors for this characteristic.
while ((descriptor_obj = mp_iternext(iterable_desc)) != MP_OBJ_STOP_ITERATION) {
// (uuid, flags,)
mp_obj_t *descriptor_items;
mp_obj_get_array_fixed_n(descriptor_obj, 2, &descriptor_items);
mp_obj_t desc_uuid_obj = descriptor_items[0];
if (!mp_obj_is_type(desc_uuid_obj, &bluetooth_uuid_type)) {
mp_raise_ValueError("invalid descriptor UUID");
}
descriptor_uuids[descriptor_index] = MP_OBJ_TO_PTR(desc_uuid_obj);
descriptor_flags[descriptor_index] = mp_obj_get_int(descriptor_items[1]);
++descriptor_index;
(*handles)[handle_index++] = 0xffff;
}
// Reflect that we've grown the handles array.
*num_handles += num_descriptors[characteristic_index];
}
characteristic_uuids[characteristic_index] = MP_OBJ_TO_PTR(uuid_obj);
characteristic_flags[characteristic_index] = mp_obj_get_int(characteristic_items[1]);
++characteristic_index;
}
// Add service.
return mp_bluetooth_gatts_register_service(service_uuid, characteristic_uuids, characteristic_flags, descriptor_uuids, descriptor_flags, num_descriptors, *handles, len);
}
STATIC mp_obj_t bluetooth_ble_gatts_register_services(mp_obj_t self_in, mp_obj_t services_in) {
mp_obj_t len_in = mp_obj_len(services_in);
size_t len = mp_obj_get_int(len_in);
mp_obj_iter_buf_t iter_buf;
mp_obj_t iterable = mp_getiter(services_in, &iter_buf);
mp_obj_t service_tuple_obj;
mp_obj_tuple_t *result = mp_obj_new_tuple(len, NULL);
uint16_t **handles = m_new0(uint16_t*, len);
size_t *num_handles = m_new0(size_t, len);
// TODO: Add a `append` kwarg (defaulting to False) to make this behavior optional.
bool append = false;
int err = mp_bluetooth_gatts_register_service_begin(append);
if (err != 0) {
return bluetooth_handle_errno(err);
}
int i = 0;
while ((service_tuple_obj = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
// (uuid, chars)
mp_obj_t *service_items;
mp_obj_get_array_fixed_n(service_tuple_obj, 2, &service_items);
err = bluetooth_gatts_register_service(service_items[0], service_items[1], &handles[i], &num_handles[i]);
if (err != 0) {
return bluetooth_handle_errno(err);
}
++i;
}
// On Nimble, this will actually perform the registration, making the handles valid.
err = mp_bluetooth_gatts_register_service_end();
if (err != 0) {
return bluetooth_handle_errno(err);
}
// Return tuple of tuple of value handles.
// TODO: Also the Generic Access service characteristics?
for (i = 0; i < len; ++i) {
mp_obj_tuple_t *service_handles = mp_obj_new_tuple(num_handles[i], NULL);
for (int j = 0; j < num_handles[i]; ++j) {
service_handles->items[j] = MP_OBJ_NEW_SMALL_INT(handles[i][j]);
}
result->items[i] = MP_OBJ_FROM_PTR(service_handles);
}
return MP_OBJ_FROM_PTR(result);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gatts_register_services_obj, bluetooth_ble_gatts_register_services);
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
STATIC mp_obj_t bluetooth_ble_gap_connect(size_t n_args, const mp_obj_t *args) {
uint8_t addr_type = mp_obj_get_int(args[1]);
mp_buffer_info_t bufinfo = {0};
mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ);
if (bufinfo.len != 6) {
mp_raise_ValueError("invalid addr");
}
mp_int_t scan_duration_ms = MP_BLUETOOTH_CONNECT_DEFAULT_SCAN_DURATION_MS;
if (n_args == 4) {
scan_duration_ms = mp_obj_get_int(args[3]);
}
int err = mp_bluetooth_gap_peripheral_connect(addr_type, bufinfo.buf, scan_duration_ms);
return bluetooth_handle_errno(err);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gap_connect_obj, 3, 4, bluetooth_ble_gap_connect);
STATIC mp_obj_t bluetooth_ble_gap_scan(size_t n_args, const mp_obj_t *args) {
// Default is indefinite scan, with the NimBLE "background scan" interval and window.
mp_int_t duration_ms = 0;
mp_int_t interval_us = 1280000;
mp_int_t window_us = 11250;
if (n_args > 1) {
if (args[1] == mp_const_none) {
// scan(None) --> stop scan.
return bluetooth_handle_errno(mp_bluetooth_gap_scan_stop());
}
duration_ms = mp_obj_get_int(args[1]);
if (n_args > 2) {
interval_us = mp_obj_get_int(args[2]);
if (n_args > 3) {
window_us = mp_obj_get_int(args[3]);
}
}
}
return bluetooth_handle_errno(mp_bluetooth_gap_scan_start(duration_ms, interval_us, window_us));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gap_scan_obj, 1, 4, bluetooth_ble_gap_scan);
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
STATIC mp_obj_t bluetooth_ble_gap_disconnect(mp_obj_t self_in, mp_obj_t conn_handle_in) {
uint16_t conn_handle = mp_obj_get_int(conn_handle_in);
int err = mp_bluetooth_gap_disconnect(conn_handle);
if (err == 0) {
return mp_const_true;
} else if (err == MP_ENOTCONN) {
return mp_const_false;
} else {
return bluetooth_handle_errno(err);
}
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gap_disconnect_obj, bluetooth_ble_gap_disconnect);
// ----------------------------------------------------------------------------
// Bluetooth object: GATTS (Peripheral/Advertiser role)
// ----------------------------------------------------------------------------
STATIC mp_obj_t bluetooth_ble_gatts_read(mp_obj_t self_in, mp_obj_t value_handle_in) {
size_t len = 0;
uint8_t* buf;
mp_bluetooth_gatts_read(mp_obj_get_int(value_handle_in), &buf, &len);
return mp_obj_new_bytes(buf, len);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gatts_read_obj, bluetooth_ble_gatts_read);
STATIC mp_obj_t bluetooth_ble_gatts_write(mp_obj_t self_in, mp_obj_t value_handle_in, mp_obj_t data) {
mp_buffer_info_t bufinfo = {0};
mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_READ);
int err = mp_bluetooth_gatts_write(mp_obj_get_int(value_handle_in), bufinfo.buf, bufinfo.len);
if (err != 0) {
mp_raise_OSError(err);
}
return MP_OBJ_NEW_SMALL_INT(bufinfo.len);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(bluetooth_ble_gatts_write_obj, bluetooth_ble_gatts_write);
STATIC mp_obj_t bluetooth_ble_gatts_notify(size_t n_args, const mp_obj_t *args) {
mp_int_t conn_handle = mp_obj_get_int(args[1]);
mp_int_t value_handle = mp_obj_get_int(args[2]);
if (n_args == 4) {
mp_buffer_info_t bufinfo = {0};
mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ);
size_t len = bufinfo.len;
int err = mp_bluetooth_gatts_notify_send(conn_handle, value_handle, bufinfo.buf, &len);
if (err != 0) {
mp_raise_OSError(err);
}
return MP_OBJ_NEW_SMALL_INT(len);
} else {
int err = mp_bluetooth_gatts_notify(conn_handle, value_handle);
return bluetooth_handle_errno(err);
}
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gatts_notify_obj, 3, 4, bluetooth_ble_gatts_notify);
STATIC mp_obj_t bluetooth_ble_gatts_set_buffer(size_t n_args, const mp_obj_t *args) {
mp_int_t value_handle = mp_obj_get_int(args[1]);
mp_int_t len = mp_obj_get_int(args[2]);
bool append = n_args >= 4 && mp_obj_is_true(args[3]);
return bluetooth_handle_errno(mp_bluetooth_gatts_set_buffer(value_handle, len, append));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gatts_set_buffer_obj, 3, 4, bluetooth_ble_gatts_set_buffer);
// ----------------------------------------------------------------------------
// Bluetooth object: GATTC (Central/Scanner role)
// ----------------------------------------------------------------------------
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
STATIC mp_obj_t bluetooth_ble_gattc_discover_services(mp_obj_t self_in, mp_obj_t conn_handle_in) {
mp_int_t conn_handle = mp_obj_get_int(conn_handle_in);
return bluetooth_handle_errno(mp_bluetooth_gattc_discover_primary_services(conn_handle));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gattc_discover_services_obj, bluetooth_ble_gattc_discover_services);
STATIC mp_obj_t bluetooth_ble_gattc_discover_characteristics(size_t n_args, const mp_obj_t *args) {
mp_int_t conn_handle = mp_obj_get_int(args[1]);
mp_int_t start_handle = mp_obj_get_int(args[2]);
mp_int_t end_handle = mp_obj_get_int(args[3]);
return bluetooth_handle_errno(mp_bluetooth_gattc_discover_characteristics(conn_handle, start_handle, end_handle));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_discover_characteristics_obj, 4, 4, bluetooth_ble_gattc_discover_characteristics);
STATIC mp_obj_t bluetooth_ble_gattc_discover_descriptors(size_t n_args, const mp_obj_t *args) {
mp_int_t conn_handle = mp_obj_get_int(args[1]);
mp_int_t start_handle = mp_obj_get_int(args[2]);
mp_int_t end_handle = mp_obj_get_int(args[3]);
return bluetooth_handle_errno(mp_bluetooth_gattc_discover_descriptors(conn_handle, start_handle, end_handle));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_discover_descriptors_obj, 4, 4, bluetooth_ble_gattc_discover_descriptors);
STATIC mp_obj_t bluetooth_ble_gattc_read(mp_obj_t self_in, mp_obj_t conn_handle_in, mp_obj_t value_handle_in) {
mp_int_t conn_handle = mp_obj_get_int(conn_handle_in);
mp_int_t value_handle = mp_obj_get_int(value_handle_in);
return bluetooth_handle_errno(mp_bluetooth_gattc_read(conn_handle, value_handle));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(bluetooth_ble_gattc_read_obj, bluetooth_ble_gattc_read);
STATIC mp_obj_t bluetooth_ble_gattc_write(size_t n_args, const mp_obj_t *args) {
mp_int_t conn_handle = mp_obj_get_int(args[1]);
mp_int_t value_handle = mp_obj_get_int(args[2]);
mp_obj_t data = args[3];
mp_buffer_info_t bufinfo = {0};
mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_READ);
size_t len = bufinfo.len;
return bluetooth_handle_errno(mp_bluetooth_gattc_write(conn_handle, value_handle, bufinfo.buf, &len));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_write_obj, 4, 4, bluetooth_ble_gattc_write);
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
// ----------------------------------------------------------------------------
// Bluetooth object: Definition
// ----------------------------------------------------------------------------
STATIC const mp_rom_map_elem_t bluetooth_ble_locals_dict_table[] = {
// General
{ MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&bluetooth_ble_active_obj) },
{ MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&bluetooth_ble_config_obj) },
{ MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&bluetooth_ble_irq_obj) },
// GAP
{ MP_ROM_QSTR(MP_QSTR_gap_advertise), MP_ROM_PTR(&bluetooth_ble_gap_advertise_obj) },
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
{ MP_ROM_QSTR(MP_QSTR_gap_connect), MP_ROM_PTR(&bluetooth_ble_gap_connect_obj) },
{ MP_ROM_QSTR(MP_QSTR_gap_scan), MP_ROM_PTR(&bluetooth_ble_gap_scan_obj) },
#endif
{ MP_ROM_QSTR(MP_QSTR_gap_disconnect), MP_ROM_PTR(&bluetooth_ble_gap_disconnect_obj) },
// GATT Server (i.e. peripheral/advertiser role)
{ MP_ROM_QSTR(MP_QSTR_gatts_register_services), MP_ROM_PTR(&bluetooth_ble_gatts_register_services_obj) },
{ MP_ROM_QSTR(MP_QSTR_gatts_read), MP_ROM_PTR(&bluetooth_ble_gatts_read_obj) },
{ MP_ROM_QSTR(MP_QSTR_gatts_write), MP_ROM_PTR(&bluetooth_ble_gatts_write_obj) },
{ MP_ROM_QSTR(MP_QSTR_gatts_notify), MP_ROM_PTR(&bluetooth_ble_gatts_notify_obj) },
{ MP_ROM_QSTR(MP_QSTR_gatts_set_buffer), MP_ROM_PTR(&bluetooth_ble_gatts_set_buffer_obj) },
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
// GATT Client (i.e. central/scanner role)
{ MP_ROM_QSTR(MP_QSTR_gattc_discover_services), MP_ROM_PTR(&bluetooth_ble_gattc_discover_services_obj) },
{ MP_ROM_QSTR(MP_QSTR_gattc_discover_characteristics), MP_ROM_PTR(&bluetooth_ble_gattc_discover_characteristics_obj) },
{ MP_ROM_QSTR(MP_QSTR_gattc_discover_descriptors), MP_ROM_PTR(&bluetooth_ble_gattc_discover_descriptors_obj) },
{ MP_ROM_QSTR(MP_QSTR_gattc_read), MP_ROM_PTR(&bluetooth_ble_gattc_read_obj) },
{ MP_ROM_QSTR(MP_QSTR_gattc_write), MP_ROM_PTR(&bluetooth_ble_gattc_write_obj) },
#endif
};
STATIC MP_DEFINE_CONST_DICT(bluetooth_ble_locals_dict, bluetooth_ble_locals_dict_table);
STATIC const mp_obj_type_t bluetooth_ble_type = {
{ &mp_type_type },
.name = MP_QSTR_BLE,
.make_new = bluetooth_ble_make_new,
.locals_dict = (void*)&bluetooth_ble_locals_dict,
};
STATIC const mp_rom_map_elem_t mp_module_bluetooth_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ubluetooth) },
{ MP_ROM_QSTR(MP_QSTR_BLE), MP_ROM_PTR(&bluetooth_ble_type) },
{ MP_ROM_QSTR(MP_QSTR_UUID), MP_ROM_PTR(&bluetooth_uuid_type) },
{ MP_ROM_QSTR(MP_QSTR_FLAG_READ), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ) },
{ MP_ROM_QSTR(MP_QSTR_FLAG_WRITE), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE) },
{ MP_ROM_QSTR(MP_QSTR_FLAG_NOTIFY), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY) },
};
STATIC MP_DEFINE_CONST_DICT(mp_module_bluetooth_globals, mp_module_bluetooth_globals_table);
const mp_obj_module_t mp_module_ubluetooth = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_bluetooth_globals,
};
// Helpers
#include <stdio.h>
STATIC void ringbuf_extract(ringbuf_t* ringbuf, mp_obj_tuple_t *data_tuple, size_t n_u16, size_t n_u8, mp_obj_str_t *bytes_addr, size_t n_b, size_t n_i8, mp_obj_bluetooth_uuid_t *uuid, mp_obj_str_t *bytes_data) {
assert(ringbuf_avail(ringbuf) >= n_u16 * 2 + n_u8 + (bytes_addr ? 6 : 0) + n_b + n_i8 + (uuid ? 1 : 0) + (bytes_data ? 1 : 0));
int j = 0;
for (int i = 0; i < n_u16; ++i) {
data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT(ringbuf_get16(ringbuf));
}
if (n_u8) {
data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT(ringbuf_get(ringbuf));
}
if (bytes_addr) {
bytes_addr->len = 6;
for (int i = 0; i < bytes_addr->len; ++i) {
// cast away const, this is actually bt->irq_addr_bytes.
((uint8_t*)bytes_addr->data)[i] = ringbuf_get(ringbuf);
}
data_tuple->items[j++] = MP_OBJ_FROM_PTR(bytes_addr);
}
if (n_b) {
data_tuple->items[j++] = mp_obj_new_bool(ringbuf_get(ringbuf));
}
if (n_i8) {
// Note the int8_t got packed into the ringbuf as a uint8_t.
data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT((int8_t)ringbuf_get(ringbuf));
}
if (uuid) {
ringbuf_get_uuid(ringbuf, uuid);
data_tuple->items[j++] = MP_OBJ_FROM_PTR(uuid);
}
// The code that enqueues into the ringbuf should ensure that it doesn't
// put more than MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN bytes into
// the ringbuf.
if (bytes_data) {
bytes_data->len = ringbuf_get(ringbuf);
for (int i = 0; i < bytes_data->len; ++i) {
// cast away const, this is actually bt->irq_data_bytes.
((uint8_t*)bytes_data->data)[i] = ringbuf_get(ringbuf);
}
data_tuple->items[j++] = MP_OBJ_FROM_PTR(bytes_data);
}
data_tuple->len = j;
}
STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) {
// This is always executing in schedule context.
for (;;) {
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
mp_int_t event = event = ringbuf_get16(&o->ringbuf);
if (event < 0) {
// Nothing available in ringbuf.
MICROPY_PY_BLUETOOTH_EXIT
break;
}
// Although we're in schedule context, this code still avoids using any allocations:
// - IRQs are disabled (to protect the ringbuf), and we need to avoid triggering GC
// - The user's handler might not alloc, so we shouldn't either.
mp_obj_t handler = handler = o->irq_handler;
mp_obj_tuple_t *data_tuple = MP_OBJ_TO_PTR(o->irq_data_tuple);
// Some events need to pass bytes objects to their handler, using the
// pre-allocated bytes array.
mp_obj_str_t irq_data_bytes_addr = {{&mp_type_bytes}, 0, 6, o->irq_addr_bytes};
mp_obj_str_t irq_data_bytes_data = {{&mp_type_bytes}, 0, 0, o->irq_data_bytes};
if (event == MP_BLUETOOTH_IRQ_CENTRAL_CONNECT || event == MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT || event == MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT || event == MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT) {
// conn_handle, addr_type, addr
ringbuf_extract(&o->ringbuf, data_tuple, 1, 1, &irq_data_bytes_addr, 0, 0, NULL, NULL);
} else if (event == MP_BLUETOOTH_IRQ_GATTS_WRITE) {
// conn_handle, value_handle
ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, 0, NULL, NULL);
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
} else if (event == MP_BLUETOOTH_IRQ_SCAN_RESULT) {
// addr_type, addr, connectable, rssi, adv_data
ringbuf_extract(&o->ringbuf, data_tuple, 0, 1, &irq_data_bytes_addr, 1, 1, NULL, &irq_data_bytes_data);
} else if (event == MP_BLUETOOTH_IRQ_SCAN_COMPLETE) {
// No params required.
data_tuple->len = 0;
} else if (event == MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT) {
// conn_handle, start_handle, end_handle, uuid
ringbuf_extract(&o->ringbuf, data_tuple, 3, 0, NULL, 0, 0, MP_OBJ_TO_PTR(o->irq_data_uuid), NULL);
} else if (event == MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT) {
// conn_handle, def_handle, value_handle, properties, uuid
ringbuf_extract(&o->ringbuf, data_tuple, 3, 1, NULL, 0, 0, MP_OBJ_TO_PTR(o->irq_data_uuid), NULL);
} else if (event == MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT) {
// conn_handle, handle, uuid
ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, 0, MP_OBJ_TO_PTR(o->irq_data_uuid), NULL);
} else if (event == MP_BLUETOOTH_IRQ_GATTC_READ_RESULT || event == MP_BLUETOOTH_IRQ_GATTC_NOTIFY || event == MP_BLUETOOTH_IRQ_GATTC_INDICATE) {
// conn_handle, value_handle, data
ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, 0, NULL, &irq_data_bytes_data);
} else if (event == MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS) {
// conn_handle, value_handle, status
ringbuf_extract(&o->ringbuf, data_tuple, 3, 0, NULL, 0, 0, NULL, NULL);
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
}
MICROPY_PY_BLUETOOTH_EXIT
mp_call_function_2(handler, MP_OBJ_NEW_SMALL_INT(event), MP_OBJ_FROM_PTR(data_tuple));
}
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(bluetooth_ble_invoke_irq_obj, bluetooth_ble_invoke_irq);
// ----------------------------------------------------------------------------
// Port API
// ----------------------------------------------------------------------------
// Callbacks are called in interrupt context (i.e. can't allocate), so we need to push the data
// into the ringbuf and schedule the callback via mp_sched_schedule.
STATIC bool enqueue_irq(mp_obj_bluetooth_ble_t *o, size_t len, uint16_t event, bool *sched) {
*sched = false;
if (o && ringbuf_free(&o->ringbuf) >= len + 2 && (o->irq_trigger & event) && o->irq_handler != mp_const_none) {
*sched = ringbuf_avail(&o->ringbuf) == 0;
ringbuf_put16(&o->ringbuf, event);
return true;
} else {
return false;
}
}
STATIC void schedule_ringbuf(bool sched) {
if (sched) {
mp_sched_schedule(MP_OBJ_FROM_PTR(MP_ROM_PTR(&bluetooth_ble_invoke_irq_obj)), mp_const_none);
}
}
void mp_bluetooth_gap_on_connected_disconnected(uint16_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr) {
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
bool sched;
if (enqueue_irq(o, 2 + 1 + 6, event, &sched)) {
ringbuf_put16(&o->ringbuf, conn_handle);
ringbuf_put(&o->ringbuf, addr_type);
for (int i = 0; i < 6; ++i) {
ringbuf_put(&o->ringbuf, addr[i]);
}
}
MICROPY_PY_BLUETOOTH_EXIT
schedule_ringbuf(sched);
}
void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) {
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
bool sched;
if (enqueue_irq(o, 2 + 2, MP_BLUETOOTH_IRQ_GATTS_WRITE, &sched)) {
ringbuf_put16(&o->ringbuf, conn_handle);
ringbuf_put16(&o->ringbuf, value_handle);
}
MICROPY_PY_BLUETOOTH_EXIT
schedule_ringbuf(sched);
}
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
void mp_bluetooth_gap_on_scan_complete(void) {
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
bool sched;
if (enqueue_irq(o, 0, MP_BLUETOOTH_IRQ_SCAN_COMPLETE, &sched)) {
}
MICROPY_PY_BLUETOOTH_EXIT
schedule_ringbuf(sched);
}
void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, bool connectable, const int8_t rssi, const uint8_t *data, size_t data_len) {
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
bool sched;
data_len = MIN(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN, data_len);
if (enqueue_irq(o, 1 + 6 + 1 + 1 + 1 + data_len, MP_BLUETOOTH_IRQ_SCAN_RESULT, &sched)) {
ringbuf_put(&o->ringbuf, addr_type);
for (int i = 0; i < 6; ++i) {
ringbuf_put(&o->ringbuf, addr[i]);
}
ringbuf_put(&o->ringbuf, connectable ? 1 : 0);
// Note conversion of int8_t rssi to uint8_t. Must un-convert on the way out.
ringbuf_put(&o->ringbuf, (uint8_t)rssi);
ringbuf_put(&o->ringbuf, data_len);
for (int i = 0; i < data_len; ++i) {
ringbuf_put(&o->ringbuf, data[i]);
}
}
MICROPY_PY_BLUETOOTH_EXIT
schedule_ringbuf(sched);
}
void mp_bluetooth_gattc_on_primary_service_result(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, mp_obj_bluetooth_uuid_t *service_uuid) {
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
bool sched;
if (enqueue_irq(o, 2 + 2 + 2 + 1 + service_uuid->type, MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT, &sched)) {
ringbuf_put16(&o->ringbuf, conn_handle);
ringbuf_put16(&o->ringbuf, start_handle);
ringbuf_put16(&o->ringbuf, end_handle);
ringbuf_put_uuid(&o->ringbuf, service_uuid);
}
MICROPY_PY_BLUETOOTH_EXIT
schedule_ringbuf(sched);
}
void mp_bluetooth_gattc_on_characteristic_result(uint16_t conn_handle, uint16_t def_handle, uint16_t value_handle, uint8_t properties, mp_obj_bluetooth_uuid_t *characteristic_uuid) {
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
bool sched;
if (enqueue_irq(o, 2 + 2 + 2 + 1 + characteristic_uuid->type, MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT, &sched)) {
ringbuf_put16(&o->ringbuf, conn_handle);
ringbuf_put16(&o->ringbuf, def_handle);
ringbuf_put16(&o->ringbuf, value_handle);
ringbuf_put(&o->ringbuf, properties);
ringbuf_put_uuid(&o->ringbuf, characteristic_uuid);
}
MICROPY_PY_BLUETOOTH_EXIT
schedule_ringbuf(sched);
}
void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t handle, mp_obj_bluetooth_uuid_t *descriptor_uuid) {
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
bool sched;
if (enqueue_irq(o, 2 + 2 + 1 + descriptor_uuid->type, MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT, &sched)) {
ringbuf_put16(&o->ringbuf, conn_handle);
ringbuf_put16(&o->ringbuf, handle);
ringbuf_put_uuid(&o->ringbuf, descriptor_uuid);
}
MICROPY_PY_BLUETOOTH_EXIT
schedule_ringbuf(sched);
}
void mp_bluetooth_gattc_on_data_available(uint16_t event, uint16_t conn_handle, uint16_t value_handle, const uint8_t *data, size_t data_len) {
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
bool sched;
data_len = MIN(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN, data_len);
if (enqueue_irq(o, 2 + 2 + 1 + data_len, event, &sched)) {
ringbuf_put16(&o->ringbuf, conn_handle);
ringbuf_put16(&o->ringbuf, value_handle);
ringbuf_put(&o->ringbuf, data_len);
for (int i = 0; i < data_len; ++i) {
ringbuf_put(&o->ringbuf, data[i]);
}
}
MICROPY_PY_BLUETOOTH_EXIT
schedule_ringbuf(sched);
}
void mp_bluetooth_gattc_on_write_status(uint16_t conn_handle, uint16_t value_handle, uint16_t status) {
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
bool sched;
if (enqueue_irq(o, 2 + 2 + 2, MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS, &sched)) {
ringbuf_put16(&o->ringbuf, conn_handle);
ringbuf_put16(&o->ringbuf, value_handle);
ringbuf_put16(&o->ringbuf, status);
}
MICROPY_PY_BLUETOOTH_EXIT
schedule_ringbuf(sched);
}
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
#if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK
// This can only be enabled when the thread invoking this is a MicroPython thread.
// On ESP32, for example, this is not the case.
bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) {
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
if ((o->irq_trigger & MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST) && o->irq_handler != mp_const_none) {
// Use pre-allocated tuple because this is a hard IRQ.
mp_obj_tuple_t *data = MP_OBJ_FROM_PTR(o->irq_data_tuple);
data->items[0] = MP_OBJ_NEW_SMALL_INT(conn_handle);
data->items[1] = MP_OBJ_NEW_SMALL_INT(value_handle);
data->len = 2;
mp_obj_t irq_ret = mp_call_function_2_protected(o->irq_handler, MP_OBJ_NEW_SMALL_INT(MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST), o->irq_data_tuple);
// If the IRQ handler explicitly returned false, then deny the read. Otherwise if it returns None/True, allow it.
return irq_ret != MP_OBJ_NULL && (irq_ret == mp_const_none || mp_obj_is_true(irq_ret));
} else {
// No IRQ handler, allow the read.
return true;
}
}
#endif
#endif // MICROPY_PY_BLUETOOTH

256
extmod/modbluetooth.h Normal file
View File

@ -0,0 +1,256 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2018 Ayke van Laethem
* Copyright (c) 2019 Jim Mussared
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_H
#define MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_H
#include <stdbool.h>
#include "py/obj.h"
#include "py/objlist.h"
#include "py/ringbuf.h"
// Port specific configuration.
#ifndef MICROPY_PY_BLUETOOTH_RINGBUF_SIZE
#define MICROPY_PY_BLUETOOTH_RINGBUF_SIZE (128)
#endif
#ifndef MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
#define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (0)
#endif
#ifndef MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK
#define MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK (0)
#endif
// Common constants.
#ifndef MP_BLUETOOTH_MAX_ATTR_SIZE
#define MP_BLUETOOTH_MAX_ATTR_SIZE (20)
#endif
// Advertisement packet lengths
#define MP_BLUETOOTH_GAP_ADV_MAX_LEN (32)
#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ (1 << 1)
#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE (1 << 3)
#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY (1 << 4)
// Type value also doubles as length.
#define MP_BLUETOOTH_UUID_TYPE_16 (2)
#define MP_BLUETOOTH_UUID_TYPE_32 (4)
#define MP_BLUETOOTH_UUID_TYPE_128 (16)
// Address types (for the addr_type params).
// Ports will need to map these to their own values.
#define MP_BLUETOOTH_ADDR_PUBLIC (0x00) // Public (identity) address. (Same as NimBLE and NRF SD)
#define MP_BLUETOOTH_ADDR_RANDOM_STATIC (0x01) // Random static (identity) address. (Same as NimBLE and NRF SD)
#define MP_BLUETOOTH_ADDR_PUBLIC_ID (0x02) // (Same as NimBLE)
#define MP_BLUETOOTH_ADDR_RANDOM_ID (0x03) // (Same as NimBLE)
#define MP_BLUETOOTH_ADDR_RANDOM_PRIVATE_RESOLVABLE (0x12) // Random private resolvable address. (NRF SD 0x02)
#define MP_BLUETOOTH_ADDR_RANDOM_PRIVATE_NON_RESOLVABLE (0x13) // Random private non-resolvable address. (NRF SD 0x03)
// Event codes for the IRQ handler.
// Can also be combined to pass to the trigger param to select which events you
// are interested in.
// Note this is currently stored in a uint16_t (in irq_trigger, and the event
// arg to the irq handler), so one spare value remaining.
#define MP_BLUETOOTH_IRQ_CENTRAL_CONNECT (1 << 0)
#define MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT (1 << 1)
#define MP_BLUETOOTH_IRQ_GATTS_WRITE (1 << 2)
#define MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST (1 << 3)
#define MP_BLUETOOTH_IRQ_SCAN_RESULT (1 << 4)
#define MP_BLUETOOTH_IRQ_SCAN_COMPLETE (1 << 5)
#define MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT (1 << 6)
#define MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT (1 << 7)
#define MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT (1 << 8)
#define MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT (1 << 9)
#define MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT (1 << 10)
#define MP_BLUETOOTH_IRQ_GATTC_READ_RESULT (1 << 11)
#define MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS (1 << 12)
#define MP_BLUETOOTH_IRQ_GATTC_NOTIFY (1 << 13)
#define MP_BLUETOOTH_IRQ_GATTC_INDICATE (1 << 14)
#define MP_BLUETOOTH_IRQ_ALL (0xffff)
/*
These aren't included in the module for space reasons, but can be used
in your Python code if necessary.
from micropython import const
_IRQ_CENTRAL_CONNECT = const(1 << 0)
_IRQ_CENTRAL_DISCONNECT = const(1 << 1)
_IRQ_GATTS_WRITE = const(1 << 2)
_IRQ_GATTS_READ_REQUEST = const(1 << 3)
_IRQ_SCAN_RESULT = const(1 << 4)
_IRQ_SCAN_COMPLETE = const(1 << 5)
_IRQ_PERIPHERAL_CONNECT = const(1 << 6)
_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7)
_IRQ_GATTC_SERVICE_RESULT = const(1 << 8)
_IRQ_GATTC_CHARACTERISTIC_RESULT = const(1 << 9)
_IRQ_GATTC_DESCRIPTOR_RESULT = const(1 << 10)
_IRQ_GATTC_READ_RESULT = const(1 << 11)
_IRQ_GATTC_WRITE_STATUS = const(1 << 12)
_IRQ_GATTC_NOTIFY = const(1 << 13)
_IRQ_GATTC_INDICATE = const(1 << 14)
_IRQ_ALL = const(0xffff)
*/
// Common UUID type.
// Ports are expected to map this to their own internal UUID types.
// Internally the UUID data is little-endian, but the user should only
// ever see this if they use the buffer protocol, e.g. in order to
// construct an advertising payload (which needs to be in LE).
// Both the constructor and the print function work in BE.
typedef struct {
mp_obj_base_t base;
uint8_t type;
uint8_t data[16];
} mp_obj_bluetooth_uuid_t;
//////////////////////////////////////////////////////////////
// API implemented by ports (i.e. called from modbluetooth.c):
// TODO: At the moment this only allows for a single `Bluetooth` instance to be created.
// Ideally in the future we'd be able to have multiple instances or to select a specific BT driver or HCI UART.
// So these global methods should be replaced with a struct of function pointers (like the machine.I2C implementations).
// Any method returning an int returns errno on failure, otherwise zero.
// Note: All methods dealing with addresses (as 6-byte uint8 pointers) are in big-endian format.
// (i.e. the same way they would be printed on a device sticker or in a UI), so the user sees
// addresses in a way that looks like what they'd expect.
// This means that the lower level implementation will likely need to reorder them (e.g. Nimble
// works in little-endian, as does BLE itself).
// Enables the Bluetooth stack.
int mp_bluetooth_init(void);
// Disables the Bluetooth stack. Is a no-op when not enabled.
void mp_bluetooth_deinit(void);
// Returns true when the Bluetooth stack is enabled.
bool mp_bluetooth_is_enabled(void);
// Gets the MAC addr of this device in big-endian format.
void mp_bluetooth_get_device_addr(uint8_t *addr);
// Start advertisement. Will re-start advertisement when already enabled.
// Returns errno on failure.
int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len);
// Stop advertisement. No-op when already stopped.
void mp_bluetooth_gap_advertise_stop(void);
// Start adding services. Must be called before mp_bluetooth_register_service.
int mp_bluetooth_gatts_register_service_begin(bool append);
// // Add a service with the given list of characteristics to the queue to be registered.
// The value_handles won't be valid until after mp_bluetooth_register_service_end is called.
int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint8_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint8_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics);
// Register any queued services.
int mp_bluetooth_gatts_register_service_end();
// Read the value from the local gatts db (likely this has been written by a central).
int mp_bluetooth_gatts_read(uint16_t value_handle, uint8_t **value, size_t *value_len);
// Write a value to the local gatts db (ready to be queried by a central).
int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len);
// Notify the central that it should do a read.
int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle);
// Notify the central, including a data payload. (Note: does not set the gatts db value).
int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len);
// Indicate the central.
int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle);
// Resize and enable/disable append-mode on a value.
// Append-mode means that remote writes will append and local reads will clear after reading.
int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append);
// Disconnect from a central or peripheral.
int mp_bluetooth_gap_disconnect(uint16_t conn_handle);
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
// Start a discovery (scan). Set duration to zero to run continuously.
int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us);
// Stop discovery (if currently active).
int mp_bluetooth_gap_scan_stop(void);
// Connect to a found peripheral.
int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms);
// Find all primary services on the connected peripheral.
int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle);
// Find all characteristics on the specified service on a connected peripheral.
int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle);
// Find all descriptors on the specified characteristic on a connected peripheral.
int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle);
// Initiate read of a value from the remote peripheral.
int mp_bluetooth_gattc_read(uint16_t conn_handle, uint16_t value_handle);
// Write the value to the remote peripheral.
int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len);
#endif
/////////////////////////////////////////////////////////////////////////////
// API implemented by modbluetooth (called by port-specific implementations):
// Notify modbluetooth that a connection/disconnection event has occurred.
void mp_bluetooth_gap_on_connected_disconnected(uint16_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr);
// Call this when a characteristic is written to.
void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle);
#if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK
// Call this when a characteristic is read from. Return false to deny the read.
bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle);
#endif
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
// Notify modbluetooth that scan has finished, either timeout, manually, or by some other action (e.g. connecting).
void mp_bluetooth_gap_on_scan_complete(void);
// Notify modbluetooth of a scan result.
void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, bool connectable, const int8_t rssi, const uint8_t *data, size_t data_len);
// Notify modbluetooth that a service was found (either by discover-all, or discover-by-uuid).
void mp_bluetooth_gattc_on_primary_service_result(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, mp_obj_bluetooth_uuid_t *service_uuid);
// Notify modbluetooth that a characteristic was found (either by discover-all-on-service, or discover-by-uuid-on-service).
void mp_bluetooth_gattc_on_characteristic_result(uint16_t conn_handle, uint16_t def_handle, uint16_t value_handle, uint8_t properties, mp_obj_bluetooth_uuid_t *characteristic_uuid);
// Notify modbluetooth that a descriptor was found.
void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t handle, mp_obj_bluetooth_uuid_t *descriptor_uuid);
// Notify modbluetooth that a read has completed with data (or notify/indicate data available, use `event` to disambiguate).
void mp_bluetooth_gattc_on_data_available(uint16_t event, uint16_t conn_handle, uint16_t value_handle, const uint8_t *data, size_t data_len);
// Notify modbluetooth that a write has completed.
void mp_bluetooth_gattc_on_write_status(uint16_t conn_handle, uint16_t value_handle, uint16_t status);
#endif
#endif // MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_H

View File

@ -0,0 +1,818 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Damien P. George
* Copyright (c) 2019 Jim Mussared
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "py/runtime.h"
#include "py/mperrno.h"
#include "py/mphal.h"
#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE
#include "modbluetooth_nimble.h"
#include "modbluetooth.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "nimble/ble.h"
#include "nimble/nimble_port.h"
#include "services/gap/ble_svc_gap.h"
#ifndef MICROPY_PY_BLUETOOTH_DEFAULT_NAME
#define MICROPY_PY_BLUETOOTH_DEFAULT_NAME "PYBD"
#endif
#define DEBUG_EVENT_printf(...) //printf(__VA_ARGS__)
STATIC int8_t ble_hs_err_to_errno_table[] = {
[BLE_HS_EAGAIN] = MP_EAGAIN,
[BLE_HS_EALREADY] = MP_EALREADY,
[BLE_HS_EINVAL] = MP_EINVAL,
[BLE_HS_EMSGSIZE] = MP_EIO,
[BLE_HS_ENOENT] = MP_ENOENT,
[BLE_HS_ENOMEM] = MP_ENOMEM,
[BLE_HS_ENOTCONN] = MP_ENOTCONN,
[BLE_HS_ENOTSUP] = MP_EOPNOTSUPP,
[BLE_HS_EAPP] = MP_EIO,
[BLE_HS_EBADDATA] = MP_EIO,
[BLE_HS_EOS] = MP_EIO,
[BLE_HS_ECONTROLLER] = MP_EIO,
[BLE_HS_ETIMEOUT] = MP_ETIMEDOUT,
[BLE_HS_EDONE] = MP_EIO, // TODO: Maybe should be MP_EISCONN (connect uses this for "already connected").
[BLE_HS_EBUSY] = MP_EBUSY,
[BLE_HS_EREJECT] = MP_EIO,
[BLE_HS_EUNKNOWN] = MP_EIO,
[BLE_HS_EROLE] = MP_EIO,
[BLE_HS_ETIMEOUT_HCI] = MP_EIO,
[BLE_HS_ENOMEM_EVT] = MP_EIO,
[BLE_HS_ENOADDR] = MP_EIO,
[BLE_HS_ENOTSYNCED] = MP_EIO,
[BLE_HS_EAUTHEN] = MP_EIO,
[BLE_HS_EAUTHOR] = MP_EIO,
[BLE_HS_EENCRYPT] = MP_EIO,
[BLE_HS_EENCRYPT_KEY_SZ] = MP_EIO,
[BLE_HS_ESTORE_CAP] = MP_EIO,
[BLE_HS_ESTORE_FAIL] = MP_EIO,
[BLE_HS_EPREEMPTED] = MP_EIO,
[BLE_HS_EDISABLED] = MP_EIO,
};
STATIC int ble_hs_err_to_errno(int err) {
if (0 <= err && err < MP_ARRAY_SIZE(ble_hs_err_to_errno_table)) {
return ble_hs_err_to_errno_table[err];
} else {
return MP_EIO;
}
}
// Note: modbluetooth UUIDs store their data in LE.
STATIC ble_uuid_t* create_nimble_uuid(const mp_obj_bluetooth_uuid_t *uuid) {
if (uuid->type == MP_BLUETOOTH_UUID_TYPE_16) {
ble_uuid16_t *result = m_new(ble_uuid16_t, 1);
result->u.type = BLE_UUID_TYPE_16;
result->value = (uuid->data[1] << 8) | uuid->data[0];
return (ble_uuid_t*)result;
} else if (uuid->type == MP_BLUETOOTH_UUID_TYPE_32) {
ble_uuid32_t *result = m_new(ble_uuid32_t, 1);
result->u.type = BLE_UUID_TYPE_32;
result->value = (uuid->data[1] << 24) | (uuid->data[1] << 16) | (uuid->data[1] << 8) | uuid->data[0];
return (ble_uuid_t*)result;
} else if (uuid->type == MP_BLUETOOTH_UUID_TYPE_128) {
ble_uuid128_t *result = m_new(ble_uuid128_t, 1);
result->u.type = BLE_UUID_TYPE_128;
memcpy(result->value, uuid->data, 16);
return (ble_uuid_t*)result;
} else {
return NULL;
}
}
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
STATIC mp_obj_bluetooth_uuid_t create_mp_uuid(const ble_uuid_any_t *uuid) {
mp_obj_bluetooth_uuid_t result;
switch (uuid->u.type) {
case BLE_UUID_TYPE_16:
result.type = MP_BLUETOOTH_UUID_TYPE_16;
result.data[0] = uuid->u16.value & 0xff;
result.data[1] = (uuid->u16.value << 8) & 0xff;
break;
case BLE_UUID_TYPE_32:
result.type = MP_BLUETOOTH_UUID_TYPE_32;
result.data[0] = uuid->u32.value & 0xff;
result.data[1] = (uuid->u32.value << 8) & 0xff;
result.data[2] = (uuid->u32.value << 16) & 0xff;
result.data[3] = (uuid->u32.value << 24) & 0xff;
break;
case BLE_UUID_TYPE_128:
result.type = MP_BLUETOOTH_UUID_TYPE_128;
memcpy(result.data, uuid->u128.value, 16);
break;
default:
assert(false);
}
return result;
}
// modbluetooth (and the layers above it) work in BE for addresses, Nimble works in LE.
STATIC void reverse_addr_byte_order(uint8_t *addr_out, const uint8_t *addr_in) {
for (int i = 0; i < 6; ++i) {
addr_out[i] = addr_in[5-i];
}
}
STATIC ble_addr_t create_nimble_addr(uint8_t addr_type, const uint8_t *addr) {
ble_addr_t addr_nimble;
addr_nimble.type = addr_type;
// Incoming addr is from modbluetooth (BE), so copy and convert to LE for Nimble.
reverse_addr_byte_order(addr_nimble.val, addr);
return addr_nimble;
}
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
typedef struct {
// Pointer to heap-allocated data.
uint8_t *data;
// Allocated size of data.
size_t data_alloc;
// Current bytes in use.
size_t data_len;
// Whether new writes append or replace existing data (default false).
bool append;
} gatts_db_entry_t;
volatile int mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF;
STATIC void reset_cb(int reason) {
(void)reason;
}
STATIC void sync_cb(void) {
int rc;
ble_addr_t addr;
rc = ble_hs_util_ensure_addr(0); // prefer public address
if (rc != 0) {
// https://mynewt.apache.org/latest/tutorials/ble/eddystone.html#configure-the-nimble-stack-with-an-address
#if MICROPY_PY_BLUETOOTH_RANDOM_ADDR
rc = ble_hs_id_gen_rnd(1, &addr);
assert(rc == 0);
rc = ble_hs_id_set_rnd(addr.val);
assert(rc == 0);
#else
uint8_t addr_be[6];
mp_hal_get_mac(MP_HAL_MAC_BDADDR, addr_be);
reverse_addr_byte_order(addr.val, addr_be);
// ble_hs_id_set_pub(addr.val);
rc = ble_hs_id_set_rnd(addr.val);
assert(rc == 0);
#endif
rc = ble_hs_util_ensure_addr(0); // prefer public address
assert(rc == 0);
}
if (MP_BLUETOOTH_MAX_ATTR_SIZE > 20) {
rc = ble_att_set_preferred_mtu(MP_BLUETOOTH_MAX_ATTR_SIZE+3);
assert(rc == 0);
}
ble_svc_gap_device_name_set(MICROPY_PY_BLUETOOTH_DEFAULT_NAME);
mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE;
}
STATIC void create_gatts_db_entry(uint16_t handle) {
mp_map_elem_t *elem = mp_map_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, MP_OBJ_NEW_SMALL_INT(handle), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND);
gatts_db_entry_t *entry = m_new(gatts_db_entry_t, 1);
entry->data = m_new(uint8_t, MP_BLUETOOTH_MAX_ATTR_SIZE);
entry->data_alloc = MP_BLUETOOTH_MAX_ATTR_SIZE;
entry->data_len = 0;
entry->append = false;
elem->value = MP_OBJ_FROM_PTR(entry);
}
STATIC void gatts_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) {
switch (ctxt->op) {
case BLE_GATT_REGISTER_OP_SVC:
// Called when a service is successfully registered.
DEBUG_EVENT_printf("gatts_register_cb: svc uuid=%p handle=%d\n", &ctxt->svc.svc_def->uuid, ctxt->svc.handle);
break;
case BLE_GATT_REGISTER_OP_CHR:
// Called when a characteristic is successfully registered.
DEBUG_EVENT_printf("gatts_register_cb: chr uuid=%p def_handle=%d val_handle=%d\n", &ctxt->chr.chr_def->uuid, ctxt->chr.def_handle, ctxt->chr.val_handle);
// Note: We will get this event for the default GAP Service, meaning that we allocate storage for the
// "device name" and "appearance" characteristics, even though we never see the reads for them.
// TODO: Possibly check if the service UUID is 0x1801 and ignore?
// Allocate the gatts_db storage for this characteristic.
// Although this function is a callback, it's called synchronously from ble_hs_sched_start/ble_gatts_start, so safe to allocate.
create_gatts_db_entry(ctxt->chr.val_handle);
break;
case BLE_GATT_REGISTER_OP_DSC:
// Called when a descriptor is successfully registered.
// Note: This is event is not called for the CCCD.
DEBUG_EVENT_printf("gatts_register_cb: dsc uuid=%p handle=%d\n", &ctxt->dsc.dsc_def->uuid, ctxt->dsc.handle);
// See above, safe to alloc.
create_gatts_db_entry(ctxt->dsc.handle);
// Unlike characteristics, we have to manually provide a way to get the handle back to the register method.
*((uint16_t*)ctxt->dsc.dsc_def->arg) = ctxt->dsc.handle;
break;
default:
DEBUG_EVENT_printf("gatts_register_cb: unknown op %d\n", ctxt->op);
break;
}
}
STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) {
DEBUG_EVENT_printf("gap_event_cb: type=%d\n", event->type);
struct ble_gap_conn_desc desc;
uint8_t addr[6] = {0};
switch (event->type) {
case BLE_GAP_EVENT_CONNECT:
if (event->connect.status == 0) {
// Connection established.
ble_gap_conn_find(event->connect.conn_handle, &desc);
reverse_addr_byte_order(addr, desc.peer_id_addr.val);
mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_CONNECT, event->connect.conn_handle, desc.peer_id_addr.type, addr);
} else {
// Connection failed.
mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT, event->connect.conn_handle, 0xff, addr);
}
break;
case BLE_GAP_EVENT_DISCONNECT:
// Disconnect.
reverse_addr_byte_order(addr, event->disconnect.conn.peer_id_addr.val);
mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT, event->disconnect.conn.conn_handle, event->disconnect.conn.peer_id_addr.type, addr);
break;
}
return 0;
}
int mp_bluetooth_init(void) {
// Clean up if necessary.
mp_bluetooth_deinit();
ble_hs_cfg.reset_cb = reset_cb;
ble_hs_cfg.sync_cb = sync_cb;
ble_hs_cfg.gatts_register_cb = gatts_register_cb;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
MP_STATE_PORT(bluetooth_nimble_root_pointers) = m_new0(mp_bluetooth_nimble_root_pointers_t, 1);
MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db = m_new(mp_map_t, 1);
mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_STARTING;
mp_bluetooth_nimble_port_preinit();
nimble_port_init();
mp_bluetooth_nimble_port_postinit();
// By default, just register the default gap service.
ble_svc_gap_init();
mp_bluetooth_nimble_port_start();
// Wait for sync callback
while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE) {
MICROPY_EVENT_POLL_HOOK
}
return 0;
}
// Called when the host stop procedure has completed.
STATIC void ble_hs_shutdown_stop_cb(int status, void *arg) {
mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF;
}
STATIC struct ble_hs_stop_listener ble_hs_shutdown_stop_listener;
void mp_bluetooth_deinit(void) {
if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) {
return;
}
mp_bluetooth_gap_advertise_stop();
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
mp_bluetooth_gap_scan_stop();
#endif
ble_hs_stop(&ble_hs_shutdown_stop_listener, ble_hs_shutdown_stop_cb, NULL);
while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) {
MICROPY_EVENT_POLL_HOOK
}
mp_bluetooth_nimble_port_deinit();
MP_STATE_PORT(bluetooth_nimble_root_pointers) = NULL;
}
bool mp_bluetooth_is_enabled(void) {
return mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE;
}
void mp_bluetooth_get_device_addr(uint8_t *addr) {
#if MICROPY_PY_BLUETOOTH_RANDOM_ADDR
ble_hs_id_copy_addr(BLE_ADDR_RANDOM, addr, NULL);
#else
mp_hal_get_mac(MP_HAL_MAC_BDADDR, addr);
#endif
}
int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len) {
int ret;
mp_bluetooth_gap_advertise_stop();
if (adv_data) {
ret = ble_gap_adv_set_data(adv_data, adv_data_len);
if (ret != 0) {
return ble_hs_err_to_errno(ret);
}
}
if (sr_data) {
ret = ble_gap_adv_rsp_set_data(sr_data, sr_data_len);
if (ret != 0) {
return ble_hs_err_to_errno(ret);
}
}
struct ble_gap_adv_params adv_params = {
.conn_mode = connectable ? BLE_GAP_CONN_MODE_UND : BLE_GAP_CONN_MODE_NON,
.disc_mode = BLE_GAP_DISC_MODE_GEN,
.itvl_min = interval_us / BLE_HCI_ADV_ITVL, // convert to 625us units.
.itvl_max = interval_us / BLE_HCI_ADV_ITVL,
.channel_map = 7, // all 3 channels.
};
ret = ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL);
if (ret == 0) {
return 0;
}
ret = ble_gap_adv_start(BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL);
if (ret == 0) {
return 0;
}
ret = ble_gap_adv_start(BLE_OWN_ADDR_RPA_RANDOM_DEFAULT, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL);
if (ret == 0) {
return 0;
}
ret = ble_gap_adv_start(BLE_OWN_ADDR_RANDOM, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL);
if (ret == 0) {
return 0;
}
DEBUG_EVENT_printf("ble_gap_adv_start: %d\n", ret);
return ble_hs_err_to_errno(ret);
}
void mp_bluetooth_gap_advertise_stop(void) {
if (ble_gap_adv_active()) {
ble_gap_adv_stop();
}
}
static int characteristic_access_cb(uint16_t conn_handle, uint16_t value_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
DEBUG_EVENT_printf("characteristic_access_cb: conn_handle=%u value_handle=%u op=%u\n", conn_handle, value_handle, ctxt->op);
mp_map_elem_t *elem;
gatts_db_entry_t *entry;
switch (ctxt->op) {
case BLE_GATT_ACCESS_OP_READ_CHR:
case BLE_GATT_ACCESS_OP_READ_DSC:
#if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK
// Allow Python code to override (by using gatts_write), or deny (by returning false) the read.
if (!mp_bluetooth_gatts_on_read_request(conn_handle, value_handle)) {
return BLE_ATT_ERR_READ_NOT_PERMITTED;
}
#endif
elem = mp_map_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, MP_OBJ_NEW_SMALL_INT(value_handle), MP_MAP_LOOKUP);
if (!elem) {
return BLE_ATT_ERR_ATTR_NOT_FOUND;
}
entry = MP_OBJ_TO_PTR(elem->value);
os_mbuf_append(ctxt->om, entry->data, entry->data_len);
return 0;
case BLE_GATT_ACCESS_OP_WRITE_CHR:
case BLE_GATT_ACCESS_OP_WRITE_DSC:
elem = mp_map_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, MP_OBJ_NEW_SMALL_INT(value_handle), MP_MAP_LOOKUP);
if (!elem) {
return BLE_ATT_ERR_ATTR_NOT_FOUND;
}
entry = MP_OBJ_TO_PTR(elem->value);
size_t offset = 0;
if (entry->append) {
offset = entry->data_len;
}
entry->data_len = MIN(entry->data_alloc, OS_MBUF_PKTLEN(ctxt->om) + offset);
os_mbuf_copydata(ctxt->om, 0, entry->data_len - offset, entry->data + offset);
mp_bluetooth_gatts_on_write(conn_handle, value_handle);
return 0;
}
return BLE_ATT_ERR_UNLIKELY;
}
int mp_bluetooth_gatts_register_service_begin(bool append) {
int ret = ble_gatts_reset();
if (ret != 0) {
return ble_hs_err_to_errno(ret);
}
// Reset the gatt characteristic value db.
mp_map_init(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, 0);
// By default, just register the default gap service.
ble_svc_gap_init();
if (!append) {
// Unref any previous service definitions.
for (int i = 0; i < MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services; ++i) {
MP_STATE_PORT(bluetooth_nimble_root_pointers)->services[i] = NULL;
}
MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services = 0;
}
return 0;
}
int mp_bluetooth_gatts_register_service_end() {
int ret = ble_gatts_start();
if (ret != 0) {
return ble_hs_err_to_errno(ret);
}
return 0;
}
int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint8_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint8_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics) {
if (MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services == MP_BLUETOOTH_NIMBLE_MAX_SERVICES) {
return MP_E2BIG;
}
size_t handle_index = 0;
size_t descriptor_index = 0;
struct ble_gatt_chr_def *characteristics = m_new(struct ble_gatt_chr_def, num_characteristics + 1);
for (size_t i = 0; i < num_characteristics; ++i) {
characteristics[i].uuid = create_nimble_uuid(characteristic_uuids[i]);
characteristics[i].access_cb = characteristic_access_cb;
characteristics[i].arg = NULL;
characteristics[i].flags = characteristic_flags[i];
characteristics[i].min_key_size = 0;
characteristics[i].val_handle = &handles[handle_index];
++handle_index;
if (num_descriptors[i] == 0) {
characteristics[i].descriptors = NULL;
} else {
struct ble_gatt_dsc_def *descriptors = m_new(struct ble_gatt_dsc_def, num_descriptors[i] + 1);
for (size_t j = 0; j < num_descriptors[i]; ++j) {
descriptors[j].uuid = create_nimble_uuid(descriptor_uuids[descriptor_index]);
descriptors[j].access_cb = characteristic_access_cb;
descriptors[j].att_flags = descriptor_flags[i];
descriptors[j].min_key_size = 0;
// Unlike characteristic, Nimble doesn't provide an automatic way to remember the handle, so use the arg.
descriptors[j].arg = &handles[handle_index];
++descriptor_index;
++handle_index;
}
descriptors[num_descriptors[i]].uuid = NULL; // no more descriptors
characteristics[i].descriptors = descriptors;
}
}
characteristics[num_characteristics].uuid = NULL; // no more characteristics
struct ble_gatt_svc_def *service = m_new(struct ble_gatt_svc_def, 2);
service[0].type = BLE_GATT_SVC_TYPE_PRIMARY;
service[0].uuid = create_nimble_uuid(service_uuid);
service[0].includes = NULL;
service[0].characteristics = characteristics;
service[1].type = 0; // no more services
MP_STATE_PORT(bluetooth_nimble_root_pointers)->services[MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services++] = service;
// Note: advertising must be stopped for gatts registration to work
int ret = ble_gatts_count_cfg(service);
if (ret != 0) {
return ble_hs_err_to_errno(ret);
}
ret = ble_gatts_add_svcs(service);
if (ret != 0) {
return ble_hs_err_to_errno(ret);
}
return 0;
}
int mp_bluetooth_gap_disconnect(uint16_t conn_handle) {
return ble_hs_err_to_errno(ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM));
}
int mp_bluetooth_gatts_read(uint16_t value_handle, uint8_t **value, size_t *value_len) {
mp_map_elem_t *elem = mp_map_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, MP_OBJ_NEW_SMALL_INT(value_handle), MP_MAP_LOOKUP);
if (!elem) {
return MP_EINVAL;
}
gatts_db_entry_t *entry = MP_OBJ_TO_PTR(elem->value);
*value = entry->data;
*value_len = entry->data_len;
if (entry->append) {
entry->data_len = 0;
}
return 0;
}
int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len) {
mp_map_elem_t *elem = mp_map_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, MP_OBJ_NEW_SMALL_INT(value_handle), MP_MAP_LOOKUP);
if (!elem) {
return MP_EINVAL;
}
gatts_db_entry_t *entry = MP_OBJ_TO_PTR(elem->value);
if (value_len > entry->data_alloc) {
entry->data = m_new(uint8_t, value_len);
entry->data_alloc = value_len;
}
memcpy(entry->data, value, value_len);
entry->data_len = value_len;
return 0;
}
// TODO: Could use ble_gatts_chr_updated to send to all subscribed centrals.
int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle) {
// Confusingly, notify/notify_custom/indicate are "gattc" function (even though they're used by peripherals (i.e. gatt servers)).
// See https://www.mail-archive.com/dev@mynewt.apache.org/msg01293.html
return ble_hs_err_to_errno(ble_gattc_notify(conn_handle, value_handle));
}
int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len) {
struct os_mbuf *om = ble_hs_mbuf_from_flat(value, *value_len);
if (om == NULL) {
return -1;
}
// TODO: check that notify_custom takes ownership of om, if not os_mbuf_free_chain(om).
return ble_hs_err_to_errno(ble_gattc_notify_custom(conn_handle, value_handle, om));
}
int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) {
return ble_hs_err_to_errno(ble_gattc_indicate(conn_handle, value_handle));
}
int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append) {
mp_map_elem_t *elem = mp_map_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, MP_OBJ_NEW_SMALL_INT(value_handle), MP_MAP_LOOKUP);
if (!elem) {
return MP_EINVAL;
}
gatts_db_entry_t *entry = MP_OBJ_TO_PTR(elem->value);
entry->data = m_renew(uint8_t, entry->data, entry->data_alloc, len);
entry->data_alloc = len;
entry->data_len = 0;
entry->append = append;
return 0;
}
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
STATIC int gap_scan_cb(struct ble_gap_event *event, void *arg) {
DEBUG_EVENT_printf("gap_scan_cb: event=%d type=%d\n", event->type, event->type == BLE_GAP_EVENT_DISC ? event->disc.event_type : -1);
if (event->type == BLE_GAP_EVENT_DISC_COMPLETE) {
mp_bluetooth_gap_on_scan_complete();
return 0;
}
if (event->type != BLE_GAP_EVENT_DISC) {
return 0;
}
if (event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_ADV_IND || event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_NONCONN_IND) {
bool connectable = event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_ADV_IND;
uint8_t addr[6];
reverse_addr_byte_order(addr, event->disc.addr.val);
mp_bluetooth_gap_on_scan_result(event->disc.addr.type, addr, connectable, event->disc.rssi, event->disc.data, event->disc.length_data);
} else if (event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) {
// TODO
} else if (event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_IND) {
// TODO
}
return 0;
}
int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us) {
if (duration_ms == 0) {
duration_ms = BLE_HS_FOREVER;
}
struct ble_gap_disc_params discover_params = {
.itvl = MAX(BLE_HCI_SCAN_ITVL_MIN, MIN(BLE_HCI_SCAN_ITVL_MAX, interval_us / BLE_HCI_SCAN_ITVL)),
.window = MAX(BLE_HCI_SCAN_WINDOW_MIN, MIN(BLE_HCI_SCAN_WINDOW_MAX, window_us / BLE_HCI_SCAN_ITVL)),
.filter_policy = BLE_HCI_CONN_FILT_NO_WL,
.limited = 0,
.passive = 1, // TODO: Handle BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP in gap_scan_cb above.
.filter_duplicates = 0,
};
int err = ble_gap_disc(BLE_OWN_ADDR_PUBLIC, duration_ms, &discover_params, gap_scan_cb, NULL);
return ble_hs_err_to_errno(err);
}
int mp_bluetooth_gap_scan_stop(void) {
int err = ble_gap_disc_cancel();
if (err == 0) {
mp_bluetooth_gap_on_scan_complete();
return 0;
}
return ble_hs_err_to_errno(err);
}
// Central role: GAP events for a connected peripheral.
STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg) {
DEBUG_EVENT_printf("peripheral_gap_event_cb: event=%d\n", event->type);
struct ble_gap_conn_desc desc;
uint8_t buf[MP_BLUETOOTH_MAX_ATTR_SIZE];
size_t len;
uint8_t addr[6] = {0};
switch (event->type) {
case BLE_GAP_EVENT_CONNECT:
if (event->connect.status == 0) {
// Connection established.
ble_gap_conn_find(event->connect.conn_handle, &desc);
reverse_addr_byte_order(addr, desc.peer_id_addr.val);
mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT, event->connect.conn_handle, desc.peer_id_addr.type, addr);
} else {
// Connection failed.
mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT, event->connect.conn_handle, 0xff, addr);
}
break;
case BLE_GAP_EVENT_DISCONNECT:
// Disconnect.
reverse_addr_byte_order(addr, event->disconnect.conn.peer_id_addr.val);
mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT, event->disconnect.conn.conn_handle, event->disconnect.conn.peer_id_addr.type, addr);
break;
case BLE_GAP_EVENT_NOTIFY_RX:
len = MIN(MP_BLUETOOTH_MAX_ATTR_SIZE, OS_MBUF_PKTLEN(event->notify_rx.om));
os_mbuf_copydata(event->notify_rx.om, 0, len, buf);
if (event->notify_rx.indication == 0) {
mp_bluetooth_gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_NOTIFY, event->notify_rx.conn_handle, event->notify_rx.attr_handle, buf, len);
} else {
mp_bluetooth_gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_INDICATE, event->notify_rx.conn_handle, event->notify_rx.attr_handle, buf, len);
}
break;
case BLE_GAP_EVENT_CONN_UPDATE:
// TODO
break;
case BLE_GAP_EVENT_CONN_UPDATE_REQ:
// TODO
break;
default:
break;
}
return 0;
}
int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms) {
if (ble_gap_disc_active()) {
mp_bluetooth_gap_scan_stop();
}
// TODO: This is the same as ble_gap_conn_params_dflt (i.e. passing NULL).
STATIC const struct ble_gap_conn_params params = {
.scan_itvl = 0x0010,
.scan_window = 0x0010,
.itvl_min = BLE_GAP_INITIAL_CONN_ITVL_MIN,
.itvl_max = BLE_GAP_INITIAL_CONN_ITVL_MAX,
.latency = BLE_GAP_INITIAL_CONN_LATENCY,
.supervision_timeout = BLE_GAP_INITIAL_SUPERVISION_TIMEOUT,
.min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN,
.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN,
};
ble_addr_t addr_nimble = create_nimble_addr(addr_type, addr);
int err = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, &addr_nimble, duration_ms, &params, &peripheral_gap_event_cb, NULL);
return ble_hs_err_to_errno(err);
}
STATIC int peripheral_discover_service_cb(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_svc *service, void *arg) {
DEBUG_EVENT_printf("peripheral_discover_service_cb: conn_handle=%d status=%d start_handle=%d\n", conn_handle, error->status, service ? service->start_handle : -1);
if (error->status == 0) {
mp_obj_bluetooth_uuid_t service_uuid = create_mp_uuid(&service->uuid);
mp_bluetooth_gattc_on_primary_service_result(conn_handle, service->start_handle, service->end_handle, &service_uuid);
}
return 0;
}
int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle) {
int err = ble_gattc_disc_all_svcs(conn_handle, &peripheral_discover_service_cb, NULL);
return ble_hs_err_to_errno(err);
}
STATIC int ble_gatt_characteristic_cb(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_chr *characteristic, void *arg) {
DEBUG_EVENT_printf("ble_gatt_characteristic_cb: conn_handle=%d status=%d def_handle=%d val_handle=%d\n", conn_handle, error->status, characteristic ? characteristic->def_handle : -1, characteristic ? characteristic->val_handle : -1);
if (error->status == 0) {
mp_obj_bluetooth_uuid_t characteristic_uuid = create_mp_uuid(&characteristic->uuid);
mp_bluetooth_gattc_on_characteristic_result(conn_handle, characteristic->def_handle, characteristic->val_handle, characteristic->properties, &characteristic_uuid);
}
return 0;
}
int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle) {
int err = ble_gattc_disc_all_chrs(conn_handle, start_handle, end_handle, &ble_gatt_characteristic_cb, NULL);
return ble_hs_err_to_errno(err);
}
STATIC int ble_gatt_descriptor_cb(uint16_t conn_handle, const struct ble_gatt_error *error, uint16_t characteristic_val_handle, const struct ble_gatt_dsc *descriptor, void *arg) {
DEBUG_EVENT_printf("ble_gatt_descriptor_cb: conn_handle=%d status=%d chr_handle=%d dsc_handle=%d\n", conn_handle, error->status, characteristic_val_handle, descriptor ? descriptor->handle : -1);
if (error->status == 0) {
mp_obj_bluetooth_uuid_t descriptor_uuid = create_mp_uuid(&descriptor->uuid);
mp_bluetooth_gattc_on_descriptor_result(conn_handle, descriptor->handle, &descriptor_uuid);
}
return 0;
}
int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle) {
int err = ble_gattc_disc_all_dscs(conn_handle, start_handle, end_handle, &ble_gatt_descriptor_cb, NULL);
return ble_hs_err_to_errno(err);
}
STATIC int ble_gatt_attr_read_cb(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) {
DEBUG_EVENT_printf("ble_gatt_attr_read_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, attr ? attr->handle : -1);
// TODO: Maybe send NULL if error->status non-zero.
if (error->status == 0) {
uint8_t buf[MP_BLUETOOTH_MAX_ATTR_SIZE];
size_t len = MIN(MP_BLUETOOTH_MAX_ATTR_SIZE, OS_MBUF_PKTLEN(attr->om));
os_mbuf_copydata(attr->om, 0, len, buf);
mp_bluetooth_gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_READ_RESULT, conn_handle, attr->handle, buf, len);
}
return 0;
}
// Initiate read of a value from the remote peripheral.
int mp_bluetooth_gattc_read(uint16_t conn_handle, uint16_t value_handle) {
int err = ble_gattc_read(conn_handle, value_handle, &ble_gatt_attr_read_cb, NULL);
return ble_hs_err_to_errno(err);
}
STATIC int ble_gatt_attr_write_cb(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) {
DEBUG_EVENT_printf("ble_gatt_attr_write_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, attr ? attr->handle : -1);
mp_bluetooth_gattc_on_write_status(conn_handle, attr->handle, error->status);
return 0;
}
// Write the value to the remote peripheral.
int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len) {
int err = ble_gattc_write_flat(conn_handle, value_handle, value, *value_len, &ble_gatt_attr_write_cb, NULL);
return ble_hs_err_to_errno(err);
}
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE

View File

@ -0,0 +1,54 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Jim Mussared
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_NIMBLE_H
#define MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_NIMBLE_H
#define MP_BLUETOOTH_NIMBLE_MAX_SERVICES (8)
typedef struct _mp_bluetooth_nimble_root_pointers_t {
// Characteristic (and descriptor) value storage.
mp_map_t *gatts_db;
// Pending service definitions.
size_t n_services;
struct ble_gatt_svc_def *services[MP_BLUETOOTH_NIMBLE_MAX_SERVICES];
} mp_bluetooth_nimble_root_pointers_t;
enum {
MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF,
MP_BLUETOOTH_NIMBLE_BLE_STATE_STARTING,
MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE,
};
extern volatile int mp_bluetooth_nimble_ble_state;
void mp_bluetooth_nimble_port_preinit(void);
void mp_bluetooth_nimble_port_postinit(void);
void mp_bluetooth_nimble_port_deinit(void);
void mp_bluetooth_nimble_port_start(void);
#endif // MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_NIMBLE_H

View File

@ -320,7 +320,7 @@ STATIC const mp_obj_type_t btree_type = {
.locals_dict = (void*)&btree_locals_dict, .locals_dict = (void*)&btree_locals_dict,
}; };
STATIC FILEVTABLE btree_stream_fvtable = { STATIC const FILEVTABLE btree_stream_fvtable = {
mp_stream_posix_read, mp_stream_posix_read,
mp_stream_posix_write, mp_stream_posix_write,
mp_stream_posix_lseek, mp_stream_posix_lseek,

View File

@ -1468,7 +1468,7 @@ STATIC mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_
if (socket->state == STATE_NEW) { if (socket->state == STATE_NEW) {
// New sockets are not connected so set HUP // New sockets are not connected so set HUP
ret |= flags & MP_STREAM_POLL_HUP; ret |= MP_STREAM_POLL_HUP;
} else if (socket->state == STATE_PEER_CLOSED) { } else if (socket->state == STATE_PEER_CLOSED) {
// Peer-closed socket is both readable and writable: read will // Peer-closed socket is both readable and writable: read will
// return EOF, write - error. Without this poll will hang on a // return EOF, write - error. Without this poll will hang on a
@ -1476,11 +1476,14 @@ STATIC mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_
ret |= flags & (MP_STREAM_POLL_RD | MP_STREAM_POLL_WR); ret |= flags & (MP_STREAM_POLL_RD | MP_STREAM_POLL_WR);
} else if (socket->state == ERR_RST) { } else if (socket->state == ERR_RST) {
// Socket was reset by peer, a write will return an error // Socket was reset by peer, a write will return an error
ret |= flags & (MP_STREAM_POLL_WR | MP_STREAM_POLL_HUP); ret |= flags & MP_STREAM_POLL_WR;
ret |= MP_STREAM_POLL_HUP;
} else if (socket->state == _ERR_BADF) {
ret |= MP_STREAM_POLL_NVAL;
} else if (socket->state < 0) { } else if (socket->state < 0) {
// Socket in some other error state, use catch-all ERR flag // Socket in some other error state, use catch-all ERR flag
// TODO: may need to set other return flags here // TODO: may need to set other return flags here
ret |= flags & MP_STREAM_POLL_ERR; ret |= MP_STREAM_POLL_ERR;
} }
} else if (request == MP_STREAM_CLOSE) { } else if (request == MP_STREAM_CLOSE) {

View File

@ -169,21 +169,29 @@ STATIC mp_obj_ssl_socket_t *socket_new(mp_obj_t sock, struct ssl_args *args) {
mbedtls_ssl_set_bio(&o->ssl, &o->sock, _mbedtls_ssl_send, _mbedtls_ssl_recv, NULL); mbedtls_ssl_set_bio(&o->ssl, &o->sock, _mbedtls_ssl_send, _mbedtls_ssl_recv, NULL);
if (args->key.u_obj != MP_OBJ_NULL) { if (args->key.u_obj != mp_const_none) {
size_t key_len; size_t key_len;
const byte *key = (const byte*)mp_obj_str_get_data(args->key.u_obj, &key_len); const byte *key = (const byte*)mp_obj_str_get_data(args->key.u_obj, &key_len);
// len should include terminating null // len should include terminating null
ret = mbedtls_pk_parse_key(&o->pkey, key, key_len + 1, NULL, 0); ret = mbedtls_pk_parse_key(&o->pkey, key, key_len + 1, NULL, 0);
assert(ret == 0); if (ret != 0) {
ret = MBEDTLS_ERR_PK_BAD_INPUT_DATA; // use general error for all key errors
goto cleanup;
}
size_t cert_len; size_t cert_len;
const byte *cert = (const byte*)mp_obj_str_get_data(args->cert.u_obj, &cert_len); const byte *cert = (const byte*)mp_obj_str_get_data(args->cert.u_obj, &cert_len);
// len should include terminating null // len should include terminating null
ret = mbedtls_x509_crt_parse(&o->cert, cert, cert_len + 1); ret = mbedtls_x509_crt_parse(&o->cert, cert, cert_len + 1);
assert(ret == 0); if (ret != 0) {
ret = MBEDTLS_ERR_X509_BAD_INPUT_DATA; // use general error for all cert errors
goto cleanup;
}
ret = mbedtls_ssl_conf_own_cert(&o->conf, &o->cert, &o->pkey); ret = mbedtls_ssl_conf_own_cert(&o->conf, &o->cert, &o->pkey);
assert(ret == 0); if (ret != 0) {
goto cleanup;
}
} }
if (args->do_handshake.u_bool) { if (args->do_handshake.u_bool) {
@ -208,6 +216,10 @@ cleanup:
if (ret == MBEDTLS_ERR_SSL_ALLOC_FAILED) { if (ret == MBEDTLS_ERR_SSL_ALLOC_FAILED) {
mp_raise_OSError(MP_ENOMEM); mp_raise_OSError(MP_ENOMEM);
} else if (ret == MBEDTLS_ERR_PK_BAD_INPUT_DATA) {
mp_raise_ValueError("invalid key");
} else if (ret == MBEDTLS_ERR_X509_BAD_INPUT_DATA) {
mp_raise_ValueError("invalid cert");
} else { } else {
mp_raise_OSError(MP_EIO); mp_raise_OSError(MP_EIO);
} }
@ -219,6 +231,9 @@ STATIC mp_obj_t mod_ssl_getpeercert(mp_obj_t o_in, mp_obj_t binary_form) {
mp_raise_NotImplementedError(NULL); mp_raise_NotImplementedError(NULL);
} }
const mbedtls_x509_crt* peer_cert = mbedtls_ssl_get_peer_cert(&o->ssl); const mbedtls_x509_crt* peer_cert = mbedtls_ssl_get_peer_cert(&o->ssl);
if (peer_cert == NULL) {
return mp_const_none;
}
return mp_obj_new_bytes(peer_cert->raw.p, peer_cert->raw.len); return mp_obj_new_bytes(peer_cert->raw.p, peer_cert->raw.len);
} }
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_ssl_getpeercert_obj, mod_ssl_getpeercert); STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_ssl_getpeercert_obj, mod_ssl_getpeercert);
@ -331,8 +346,8 @@ STATIC const mp_obj_type_t ussl_socket_type = {
STATIC mp_obj_t mod_ssl_wrap_socket(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { STATIC mp_obj_t mod_ssl_wrap_socket(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
// TODO: Implement more args // TODO: Implement more args
static const mp_arg_t allowed_args[] = { static const mp_arg_t allowed_args[] = {
{ MP_QSTR_key, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_key, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} },
{ MP_QSTR_cert, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_cert, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} },
{ MP_QSTR_server_side, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, { MP_QSTR_server_side, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
{ MP_QSTR_server_hostname, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} }, { MP_QSTR_server_hostname, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} },
{ MP_QSTR_do_handshake, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} }, { MP_QSTR_do_handshake, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} },

1
extmod/nimble/bsp/bsp.h Normal file
View File

@ -0,0 +1 @@
// empty

View File

@ -0,0 +1 @@
// empty

View File

@ -0,0 +1,18 @@
#ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H
#define MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H
#include <stdint.h>
#define SYSINIT_PANIC_ASSERT_MSG(cond, msg)
#define HAL_UART_PARITY_NONE (0)
typedef int (*hal_uart_tx_cb_t)(void *arg);
typedef int (*hal_uart_rx_cb_t)(void *arg, uint8_t data);
int hal_uart_init_cbs(uint32_t port, hal_uart_tx_cb_t tx_cb, void *tx_arg, hal_uart_rx_cb_t rx_cb, void *rx_arg);
int hal_uart_config(uint32_t port, uint32_t baud, uint32_t bits, uint32_t stop, uint32_t parity, uint32_t flow);
void hal_uart_start_tx(uint32_t port);
int hal_uart_close(uint32_t port);
#endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H

95
extmod/nimble/nimble.mk Normal file
View File

@ -0,0 +1,95 @@
# Makefile directives for Apache mynewt nimble BLE component
ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1)
NIMBLE_LIB_DIR = lib/mynewt-nimble
NIMBLE_EXTMOD_DIR = extmod/nimble
SRC_LIB += $(addprefix $(NIMBLE_LIB_DIR)/, \
$(addprefix ext/tinycrypt/src/, \
aes_encrypt.c \
cmac_mode.c \
ecc.c \
ecc_dh.c \
utils.c \
) \
nimble/host/services/gap/src/ble_svc_gap.c \
nimble/host/services/gatt/src/ble_svc_gatt.c \
$(addprefix nimble/host/src/, \
ble_att.c \
ble_att_clt.c \
ble_att_cmd.c \
ble_att_svr.c \
ble_eddystone.c \
ble_gap.c \
ble_gattc.c \
ble_gatts.c \
ble_hs_adv.c \
ble_hs_atomic.c \
ble_hs.c \
ble_hs_cfg.c \
ble_hs_conn.c \
ble_hs_dbg.c \
ble_hs_flow.c \
ble_hs_hci.c \
ble_hs_hci_cmd.c \
ble_hs_hci_evt.c \
ble_hs_hci_util.c \
ble_hs_id.c \
ble_hs_log.c \
ble_hs_mbuf.c \
ble_hs_misc.c \
ble_hs_mqueue.c \
ble_hs_pvcy.c \
ble_hs_startup.c \
ble_hs_stop.c \
ble_ibeacon.c \
ble_l2cap.c \
ble_l2cap_coc.c \
ble_l2cap_sig.c \
ble_l2cap_sig_cmd.c \
ble_monitor.c \
ble_sm_alg.c \
ble_sm.c \
ble_sm_cmd.c \
ble_sm_lgcy.c \
ble_sm_sc.c \
ble_store.c \
ble_store_util.c \
ble_uuid.c \
) \
nimble/host/store/ram/src/ble_store_ram.c \
nimble/host/util/src/addr.c \
nimble/transport/uart/src/ble_hci_uart.c \
$(addprefix porting/nimble/src/, \
endian.c \
mem.c \
nimble_port.c \
os_mbuf.c \
os_mempool.c \
os_msys_init.c \
) \
)
EXTMOD_SRC_C += $(addprefix $(NIMBLE_EXTMOD_DIR)/, \
nimble/npl_os.c \
nimble/hci_uart.c \
)
CFLAGS_MOD += -DMICROPY_BLUETOOTH_NIMBLE=1
INC += -I$(TOP)/$(NIMBLE_EXTMOD_DIR)
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/ext/tinycrypt/include
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/host/include
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/host/services/gap/include
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/host/services/gatt/include
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/host/store/ram/include
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/host/util/include
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/include
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/transport/uart/include
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/porting/nimble/include
$(BUILD)/$(NIMBLE_LIB_DIR)/%.o: CFLAGS += -Wno-maybe-uninitialized -Wno-pointer-arith -Wno-unused-but-set-variable -Wno-format
endif

View File

@ -0,0 +1,88 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2018-2019 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "py/runtime.h"
#include "py/mphal.h"
#include "pin_static_af.h"
#include "nimble/ble.h"
#include "hal/hal_uart.h"
#include "hci_uart.h"
#if MICROPY_BLUETOOTH_NIMBLE
/******************************************************************************/
// Bindings Uart to Nimble
uint8_t bt_hci_cmd_buf[4 + 256];
static hal_uart_tx_cb_t hal_uart_tx_cb;
static void *hal_uart_tx_arg;
static hal_uart_rx_cb_t hal_uart_rx_cb;
static void *hal_uart_rx_arg;
int hal_uart_init_cbs(uint32_t port, hal_uart_tx_cb_t tx_cb, void *tx_arg, hal_uart_rx_cb_t rx_cb, void *rx_arg) {
hal_uart_tx_cb = tx_cb;
hal_uart_tx_arg = tx_arg;
hal_uart_rx_cb = rx_cb;
hal_uart_rx_arg = rx_arg;
return 0; // success
}
int hal_uart_config(uint32_t port, uint32_t baudrate, uint32_t bits, uint32_t stop, uint32_t parity, uint32_t flow) {
nimble_hci_uart_configure(port);
nimble_hci_uart_set_baudrate(baudrate);
return nimble_hci_uart_activate();
}
void hal_uart_start_tx(uint32_t port) {
size_t len = 0;
for (;;) {
int data = hal_uart_tx_cb(hal_uart_tx_arg);
if (data == -1) {
break;
}
bt_hci_cmd_buf[len++] = data;
}
#if 0
printf("[% 8d] BTUTX: %02x", mp_hal_ticks_ms(), hci_cmd_buf[0]);
for (int i = 1; i < len; ++i) {
printf(":%02x", hci_cmd_buf[i]);
}
printf("\n");
#endif
nimble_hci_uart_tx_strn((void*)bt_hci_cmd_buf, len);
}
int hal_uart_close(uint32_t port) {
return 0; // success
}
void nimble_uart_process(void) {
nimble_hci_uart_rx(hal_uart_rx_cb, hal_uart_rx_arg);
}
#endif // MICROPY_BLUETOOTH_NIMBLE

View File

@ -0,0 +1,43 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_NIMBLE_HCI_UART_H
#define MICROPY_INCLUDED_EXTMOD_NIMBLE_NIMBLE_HCI_UART_H
#include "extmod/nimble/hal/hal_uart.h"
// To be implemented by the port.
int nimble_hci_uart_configure(uint32_t port);
// This will default to MICROPY_HW_BLE_UART_BAUDRATE, but can be updated later.
int nimble_hci_uart_set_baudrate(uint32_t baudrate);
int nimble_hci_uart_activate(void);
void nimble_hci_uart_rx(hal_uart_rx_cb_t rx_cb, void *rx_arg);
void nimble_hci_uart_tx_strn(const char *str, uint len);
#endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_NIMBLE_HCI_UART_H

View File

@ -0,0 +1,66 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2018-2019 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H
#define MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H
#include <stdint.h>
#define BLE_NPL_OS_ALIGNMENT (4)
#define BLE_NPL_TIME_FOREVER (0xffffffff)
typedef uint32_t ble_npl_time_t;
typedef int32_t ble_npl_stime_t;
struct ble_npl_event {
ble_npl_event_fn *fn;
void *arg;
struct ble_npl_event *prev;
struct ble_npl_event *next;
};
struct ble_npl_eventq {
struct ble_npl_event *head;
struct ble_npl_eventq *nextq;
};
struct ble_npl_callout {
bool active;
uint32_t ticks;
struct ble_npl_eventq *evq;
struct ble_npl_event ev;
struct ble_npl_callout *nextc;
};
struct ble_npl_mutex {
volatile uint8_t locked;
};
struct ble_npl_sem {
volatile uint16_t count;
};
#endif // MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H

View File

@ -0,0 +1,387 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2018-2019 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdio.h>
#include "py/mphal.h"
#include "py/runtime.h"
#include "nimble/ble.h"
#include "nimble/nimble_npl.h"
#define DEBUG_OS_printf(...) //printf(__VA_ARGS__)
#define DEBUG_MALLOC_printf(...) //printf(__VA_ARGS__)
#define DEBUG_EVENT_printf(...) //printf(__VA_ARGS__)
#define DEBUG_MUTEX_printf(...) //printf(__VA_ARGS__)
#define DEBUG_SEM_printf(...) //printf(__VA_ARGS__)
#define DEBUG_CALLOUT_printf(...) //printf(__VA_ARGS__)
#define DEBUG_TIME_printf(...) //printf(__VA_ARGS__)
#define DEBUG_CRIT_printf(...) //printf(__VA_ARGS__)
bool ble_npl_os_started(void) {
DEBUG_OS_printf("ble_npl_os_started\n");
return true;
}
void *ble_npl_get_current_task_id(void) {
DEBUG_OS_printf("ble_npl_get_current_task_id\n");
return NULL;
}
/******************************************************************************/
// malloc
// Maintain a linked list of heap memory that we've passed to Nimble,
// discoverable via the bluetooth_nimble_memory root pointer.
// MP_STATE_PORT(bluetooth_nimble_memory) is a pointer to [next, prev, data...].
// TODO: This is duplicated from mbedtls. Perhaps make this a generic feature?
void *m_malloc_bluetooth(size_t size) {
void **ptr = m_malloc0(size + 2 * sizeof(uintptr_t));
if (MP_STATE_PORT(bluetooth_nimble_memory) != NULL) {
MP_STATE_PORT(bluetooth_nimble_memory)[0] = ptr;
}
ptr[0] = NULL;
ptr[1] = MP_STATE_PORT(bluetooth_nimble_memory);
MP_STATE_PORT(bluetooth_nimble_memory) = ptr;
return &ptr[2];
}
void m_free_bluetooth(void *ptr_in) {
void **ptr = &((void**)ptr_in)[-2];
if (ptr[1] != NULL) {
((void**)ptr[1])[0] = ptr[0];
}
if (ptr[0] != NULL) {
((void**)ptr[0])[1] = ptr[1];
} else {
MP_STATE_PORT(bluetooth_nimble_memory) = ptr[1];
}
m_free(ptr);
}
// Check if a nimble ptr is tracked.
// If it isn't, that means that it's from a previous soft-reset cycle.
STATIC bool is_valid_nimble_malloc(void *ptr) {
DEBUG_MALLOC_printf("NIMBLE is_valid_nimble_malloc(%p)\n", ptr);
void** search = MP_STATE_PORT(bluetooth_nimble_memory);
while (search) {
if (&search[2] == ptr) {
return true;
}
search = (void**)search[1];
}
return false;
}
void *nimble_malloc(size_t size) {
DEBUG_MALLOC_printf("NIMBLE malloc(%u)\n", (uint)size);
void* ptr = m_malloc_bluetooth(size);
DEBUG_MALLOC_printf(" --> %p\n", ptr);
return ptr;
}
// Only free if it's still a valid pointer.
void nimble_free(void *ptr) {
DEBUG_MALLOC_printf("NIMBLE free(%p)\n", ptr);
if (ptr && is_valid_nimble_malloc(ptr)) {
m_free_bluetooth(ptr);
}
}
// Only realloc if it's still a valid pointer. Otherwise just malloc.
void *nimble_realloc(void *ptr, size_t size) {
// This is only used by ble_gatts.c to grow the queue of pending services to be registered.
DEBUG_MALLOC_printf("NIMBLE realloc(%p, %u)\n", ptr, (uint)size);
void *ptr2 = nimble_malloc(size);
if (ptr && is_valid_nimble_malloc(ptr)) {
// If it's a realloc and we still have the old data, then copy it.
// This will happen as we add services.
memcpy(ptr2, ptr, size);
m_free_bluetooth(ptr);
}
return ptr2;
}
/******************************************************************************/
// EVENTQ
struct ble_npl_eventq *global_eventq = NULL;
void os_eventq_run_all(void) {
for (struct ble_npl_eventq *evq = global_eventq; evq != NULL; evq = evq->nextq) {
while (evq->head != NULL) {
struct ble_npl_event *ev = evq->head;
evq->head = ev->next;
if (ev->next) {
ev->next->prev = NULL;
ev->next = NULL;
}
ev->prev = NULL;
DEBUG_EVENT_printf("event_run(%p)\n", ev);
ev->fn(ev);
DEBUG_EVENT_printf("event_run(%p) done\n", ev);
}
}
}
void ble_npl_eventq_init(struct ble_npl_eventq *evq) {
DEBUG_EVENT_printf("ble_npl_eventq_init(%p)\n", evq);
evq->head = NULL;
struct ble_npl_eventq **evq2;
for (evq2 = &global_eventq; *evq2 != NULL; evq2 = &(*evq2)->nextq) {
}
*evq2 = evq;
evq->nextq = NULL;
}
void ble_npl_eventq_put(struct ble_npl_eventq *evq, struct ble_npl_event *ev) {
DEBUG_EVENT_printf("ble_npl_eventq_put(%p, %p (%p, %p))\n", evq, ev, ev->fn, ev->arg);
ev->next = NULL;
if (evq->head == NULL) {
evq->head = ev;
ev->prev = NULL;
} else {
struct ble_npl_event *ev2 = evq->head;
while (true) {
if (ev2 == ev) {
DEBUG_EVENT_printf(" --> already in queue\n");
return;
}
if (ev2->next == NULL) {
break;
}
DEBUG_EVENT_printf(" --> %p\n", ev2->next);
ev2 = ev2->next;
}
ev2->next = ev;
ev->prev = ev2;
}
}
void ble_npl_event_init(struct ble_npl_event *ev, ble_npl_event_fn *fn, void *arg) {
DEBUG_EVENT_printf("ble_npl_event_init(%p, %p, %p)\n", ev, fn, arg);
ev->fn = fn;
ev->arg = arg;
ev->next = NULL;
}
void *ble_npl_event_get_arg(struct ble_npl_event *ev) {
DEBUG_EVENT_printf("ble_npl_event_get_arg(%p) -> %p\n", ev, ev->arg);
return ev->arg;
}
void ble_npl_event_set_arg(struct ble_npl_event *ev, void *arg) {
DEBUG_EVENT_printf("ble_npl_event_set_arg(%p, %p)\n", ev, arg);
ev->arg = arg;
}
/******************************************************************************/
// MUTEX
ble_npl_error_t ble_npl_mutex_init(struct ble_npl_mutex *mu) {
DEBUG_MUTEX_printf("ble_npl_mutex_init(%p)\n", mu);
mu->locked = 0;
return BLE_NPL_OK;
}
ble_npl_error_t ble_npl_mutex_pend(struct ble_npl_mutex *mu, ble_npl_time_t timeout) {
DEBUG_MUTEX_printf("ble_npl_mutex_pend(%p, %u) locked=%u\n", mu, (uint)timeout, (uint)mu->locked);
mu->locked = 1;
return BLE_NPL_OK;
}
ble_npl_error_t ble_npl_mutex_release(struct ble_npl_mutex *mu) {
DEBUG_MUTEX_printf("ble_npl_mutex_release(%p) locked=%u\n", mu, (uint)mu->locked);
mu->locked = 0;
return BLE_NPL_OK;
}
/******************************************************************************/
// SEM
ble_npl_error_t ble_npl_sem_init(struct ble_npl_sem *sem, uint16_t tokens) {
DEBUG_SEM_printf("ble_npl_sem_init(%p, %u)\n", sem, (uint)tokens);
sem->count = tokens;
return BLE_NPL_OK;
}
ble_npl_error_t ble_npl_sem_pend(struct ble_npl_sem *sem, ble_npl_time_t timeout) {
DEBUG_SEM_printf("ble_npl_sem_pend(%p, %u) count=%u\n", sem, (uint)timeout, (uint)sem->count);
if (sem->count == 0) {
uint32_t t0 = mp_hal_ticks_ms();
while (sem->count == 0 && mp_hal_ticks_ms() - t0 < timeout) {
extern void nimble_uart_process(void);
nimble_uart_process();
if (sem->count != 0) {
break;
}
__WFI();
}
if (sem->count == 0) {
printf("timeout\n");
return BLE_NPL_TIMEOUT;
}
DEBUG_SEM_printf("got response in %u ms\n", (int)(mp_hal_ticks_ms() - t0));
}
sem->count -= 1;
return BLE_NPL_OK;
}
ble_npl_error_t ble_npl_sem_release(struct ble_npl_sem *sem) {
DEBUG_SEM_printf("ble_npl_sem_release(%p)\n", sem);
sem->count += 1;
return BLE_NPL_OK;
}
uint16_t ble_npl_sem_get_count(struct ble_npl_sem *sem) {
DEBUG_SEM_printf("ble_npl_sem_get_count(%p)\n", sem);
return sem->count;
}
/******************************************************************************/
// CALLOUT
static struct ble_npl_callout *global_callout = NULL;
void os_callout_process(void) {
uint32_t tnow = mp_hal_ticks_ms();
for (struct ble_npl_callout *c = global_callout; c != NULL; c = c->nextc) {
if (!c->active) {
continue;
}
if ((int32_t)(tnow - c->ticks) >= 0) {
DEBUG_CALLOUT_printf("callout_run(%p) tnow=%u ticks=%u evq=%p\n", c, (uint)tnow, (uint)c->ticks, c->evq);
c->active = false;
if (c->evq) {
ble_npl_eventq_put(c->evq, &c->ev);
} else {
c->ev.fn(&c->ev);
}
DEBUG_CALLOUT_printf("callout_run(%p) done\n", c);
}
}
}
void ble_npl_callout_init(struct ble_npl_callout *c, struct ble_npl_eventq *evq, ble_npl_event_fn *ev_cb, void *ev_arg) {
DEBUG_CALLOUT_printf("ble_npl_callout_init(%p, %p, %p, %p)\n", c, evq, ev_cb, ev_arg);
c->active = false;
c->ticks = 0;
c->evq = evq;
ble_npl_event_init(&c->ev, ev_cb, ev_arg);
struct ble_npl_callout **c2;
for (c2 = &global_callout; *c2 != NULL; c2 = &(*c2)->nextc) {
if (c == *c2) {
// callout already in linked list so don't link it in again
return;
}
}
*c2 = c;
c->nextc = NULL;
}
ble_npl_error_t ble_npl_callout_reset(struct ble_npl_callout *c, ble_npl_time_t ticks) {
DEBUG_CALLOUT_printf("ble_npl_callout_reset(%p, %u) tnow=%u\n", c, (uint)ticks, (uint)mp_hal_ticks_ms());
c->active = true;
c->ticks = ble_npl_time_get() + ticks;
return BLE_NPL_OK;
}
void ble_npl_callout_stop(struct ble_npl_callout *c) {
DEBUG_CALLOUT_printf("ble_npl_callout_stop(%p)\n", c);
c->active = false;
}
bool ble_npl_callout_is_active(struct ble_npl_callout *c) {
DEBUG_CALLOUT_printf("ble_npl_callout_is_active(%p)\n", c);
return c->active;
}
ble_npl_time_t ble_npl_callout_get_ticks(struct ble_npl_callout *c) {
DEBUG_CALLOUT_printf("ble_npl_callout_get_ticks(%p)\n", c);
return c->ticks;
}
ble_npl_time_t ble_npl_callout_remaining_ticks(struct ble_npl_callout *c, ble_npl_time_t now) {
DEBUG_CALLOUT_printf("ble_npl_callout_remaining_ticks(%p, %u)\n", c, (uint)now);
if (c->ticks > now) {
return c->ticks - now;
} else {
return 0;
}
}
void *ble_npl_callout_get_arg(struct ble_npl_callout *c) {
DEBUG_CALLOUT_printf("ble_npl_callout_get_arg(%p)\n", c);
return ble_npl_event_get_arg(&c->ev);
}
void ble_npl_callout_set_arg(struct ble_npl_callout *c, void *arg) {
DEBUG_CALLOUT_printf("ble_npl_callout_set_arg(%p, %p)\n", c, arg);
ble_npl_event_set_arg(&c->ev, arg);
}
/******************************************************************************/
// TIME
uint32_t ble_npl_time_get(void) {
DEBUG_TIME_printf("ble_npl_time_get -> %u\n", (uint)mp_hal_ticks_ms());
return mp_hal_ticks_ms();
}
ble_npl_error_t ble_npl_time_ms_to_ticks(uint32_t ms, ble_npl_time_t *out_ticks) {
DEBUG_TIME_printf("ble_npl_time_ms_to_ticks(%u)\n", (uint)ms);
*out_ticks = ms;
return BLE_NPL_OK;
}
ble_npl_time_t ble_npl_time_ms_to_ticks32(uint32_t ms) {
DEBUG_TIME_printf("ble_npl_time_ms_to_ticks32(%u)\n", (uint)ms);
return ms;
}
uint32_t ble_npl_time_ticks_to_ms32(ble_npl_time_t ticks) {
DEBUG_TIME_printf("ble_npl_time_ticks_to_ms32(%u)\n", (uint)ticks);
return ticks;
}
void ble_npl_time_delay(ble_npl_time_t ticks) {
mp_hal_delay_ms(ticks + 1);
}
/******************************************************************************/
// CRITICAL
uint32_t ble_npl_hw_enter_critical(void) {
DEBUG_CRIT_printf("ble_npl_hw_enter_critical()\n");
return raise_irq_pri(15);
}
void ble_npl_hw_exit_critical(uint32_t ctx) {
DEBUG_CRIT_printf("ble_npl_hw_exit_critical(%u)\n", (uint)ctx);
restore_irq_pri(ctx);
}

View File

@ -0,0 +1,160 @@
#ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_SYSCFG_H
#define MICROPY_INCLUDED_EXTMOD_NIMBLE_SYSCFG_H
#include "py/mphal.h"
#include "uart.h"
void *nimble_malloc(size_t size);
void nimble_free(void *ptr);
void *nimble_realloc(void *ptr, size_t size);
#define malloc(size) nimble_malloc(size)
#define free(ptr) nimble_free(ptr)
#define realloc(ptr, size) nimble_realloc(ptr, size)
int nimble_sprintf(char *str, const char *fmt, ...);
#define sprintf(str, fmt, ...) nimble_sprintf(str, fmt, __VA_ARGS__)
#define MYNEWT_VAL(x) MYNEWT_VAL_ ## x
#define MYNEWT_VAL_LOG_LEVEL (255)
/*** compiler/arm-none-eabi-m4 */
#define MYNEWT_VAL_HARDFLOAT (1)
/*** kernel/os */
#define MYNEWT_VAL_FLOAT_USER (0)
#define MYNEWT_VAL_MSYS_1_BLOCK_COUNT (12)
#define MYNEWT_VAL_MSYS_1_BLOCK_SIZE (292)
#define MYNEWT_VAL_MSYS_2_BLOCK_COUNT (0)
#define MYNEWT_VAL_MSYS_2_BLOCK_SIZE (0)
#define MYNEWT_VAL_OS_CPUTIME_FREQ (1000000)
#define MYNEWT_VAL_OS_CPUTIME_TIMER_NUM (0)
#define MYNEWT_VAL_OS_CTX_SW_STACK_CHECK (0)
#define MYNEWT_VAL_OS_CTX_SW_STACK_GUARD (4)
#define MYNEWT_VAL_OS_MAIN_STACK_SIZE (1024)
#define MYNEWT_VAL_OS_MAIN_TASK_PRIO (127)
#define MYNEWT_VAL_OS_MEMPOOL_CHECK (0)
#define MYNEWT_VAL_OS_MEMPOOL_POISON (0)
/*** nimble */
#define MYNEWT_VAL_BLE_EXT_ADV (0)
#define MYNEWT_VAL_BLE_EXT_ADV_MAX_SIZE (31)
#define MYNEWT_VAL_BLE_MAX_CONNECTIONS (4)
#define MYNEWT_VAL_BLE_MULTI_ADV_INSTANCES (0)
#define MYNEWT_VAL_BLE_ROLE_BROADCASTER (1)
#define MYNEWT_VAL_BLE_ROLE_CENTRAL (1)
#define MYNEWT_VAL_BLE_ROLE_OBSERVER (1)
#define MYNEWT_VAL_BLE_ROLE_PERIPHERAL (1)
#define MYNEWT_VAL_BLE_WHITELIST (1)
/*** nimble/host */
#define MYNEWT_VAL_BLE_ATT_PREFERRED_MTU (256)
#define MYNEWT_VAL_BLE_ATT_SVR_FIND_INFO (1)
#define MYNEWT_VAL_BLE_ATT_SVR_FIND_TYPE (1)
#define MYNEWT_VAL_BLE_ATT_SVR_INDICATE (1)
#define MYNEWT_VAL_BLE_ATT_SVR_MAX_PREP_ENTRIES (64)
#define MYNEWT_VAL_BLE_ATT_SVR_NOTIFY (1)
#define MYNEWT_VAL_BLE_ATT_SVR_QUEUED_WRITE (1)
#define MYNEWT_VAL_BLE_ATT_SVR_QUEUED_WRITE_TMO (30000)
#define MYNEWT_VAL_BLE_ATT_SVR_READ (1)
#define MYNEWT_VAL_BLE_ATT_SVR_READ_BLOB (1)
#define MYNEWT_VAL_BLE_ATT_SVR_READ_GROUP_TYPE (1)
#define MYNEWT_VAL_BLE_ATT_SVR_READ_MULT (1)
#define MYNEWT_VAL_BLE_ATT_SVR_READ_TYPE (1)
#define MYNEWT_VAL_BLE_ATT_SVR_SIGNED_WRITE (1)
#define MYNEWT_VAL_BLE_ATT_SVR_WRITE (1)
#define MYNEWT_VAL_BLE_ATT_SVR_WRITE_NO_RSP (1)
#define MYNEWT_VAL_BLE_GAP_MAX_PENDING_CONN_PARAM_UPDATE (1)
#define MYNEWT_VAL_BLE_GATT_DISC_ALL_CHRS (MYNEWT_VAL_BLE_ROLE_CENTRAL)
#define MYNEWT_VAL_BLE_GATT_DISC_ALL_DSCS (MYNEWT_VAL_BLE_ROLE_CENTRAL)
#define MYNEWT_VAL_BLE_GATT_DISC_ALL_SVCS (MYNEWT_VAL_BLE_ROLE_CENTRAL)
#define MYNEWT_VAL_BLE_GATT_DISC_CHR_UUID (MYNEWT_VAL_BLE_ROLE_CENTRAL)
#define MYNEWT_VAL_BLE_GATT_DISC_SVC_UUID (MYNEWT_VAL_BLE_ROLE_CENTRAL)
#define MYNEWT_VAL_BLE_GATT_FIND_INC_SVCS (MYNEWT_VAL_BLE_ROLE_CENTRAL)
#define MYNEWT_VAL_BLE_GATT_INDICATE (1)
#define MYNEWT_VAL_BLE_GATT_MAX_PROCS (4)
#define MYNEWT_VAL_BLE_GATT_NOTIFY (1)
#define MYNEWT_VAL_BLE_GATT_READ (MYNEWT_VAL_BLE_ROLE_CENTRAL)
#define MYNEWT_VAL_BLE_GATT_READ_LONG (MYNEWT_VAL_BLE_ROLE_CENTRAL)
#define MYNEWT_VAL_BLE_GATT_READ_MAX_ATTRS (8)
#define MYNEWT_VAL_BLE_GATT_READ_MULT (MYNEWT_VAL_BLE_ROLE_CENTRAL)
#define MYNEWT_VAL_BLE_GATT_READ_UUID (MYNEWT_VAL_BLE_ROLE_CENTRAL)
#define MYNEWT_VAL_BLE_GATT_RESUME_RATE (1000)
#define MYNEWT_VAL_BLE_GATT_SIGNED_WRITE (MYNEWT_VAL_BLE_ROLE_CENTRAL)
#define MYNEWT_VAL_BLE_GATT_WRITE (MYNEWT_VAL_BLE_ROLE_CENTRAL)
#define MYNEWT_VAL_BLE_GATT_WRITE_LONG (MYNEWT_VAL_BLE_ROLE_CENTRAL)
#define MYNEWT_VAL_BLE_GATT_WRITE_MAX_ATTRS (4)
#define MYNEWT_VAL_BLE_GATT_WRITE_NO_RSP (MYNEWT_VAL_BLE_ROLE_CENTRAL)
#define MYNEWT_VAL_BLE_GATT_WRITE_RELIABLE (MYNEWT_VAL_BLE_ROLE_CENTRAL)
#define MYNEWT_VAL_BLE_HOST (1)
#define MYNEWT_VAL_BLE_HS_DEBUG (0)
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL (0)
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL_ITVL (1000)
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL_THRESH (2)
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL_TX_ON_DISCONNECT (0)
#define MYNEWT_VAL_BLE_HS_PHONY_HCI_ACKS (0)
#define MYNEWT_VAL_BLE_HS_REQUIRE_OS (1)
#define MYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM (0)
#define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1)
#define MYNEWT_VAL_BLE_L2CAP_MAX_CHANS (3*MYNEWT_VAL_BLE_MAX_CONNECTIONS)
#define MYNEWT_VAL_BLE_L2CAP_RX_FRAG_TIMEOUT (30000)
#define MYNEWT_VAL_BLE_L2CAP_SIG_MAX_PROCS (1)
#define MYNEWT_VAL_BLE_MONITOR_CONSOLE_BUFFER_SIZE (128)
#define MYNEWT_VAL_BLE_MONITOR_RTT (0)
#define MYNEWT_VAL_BLE_MONITOR_RTT_BUFFERED (1)
#define MYNEWT_VAL_BLE_MONITOR_RTT_BUFFER_NAME ("monitor")
#define MYNEWT_VAL_BLE_MONITOR_RTT_BUFFER_SIZE (256)
#define MYNEWT_VAL_BLE_MONITOR_UART (0)
#define MYNEWT_VAL_BLE_MONITOR_UART_BAUDRATE (1000000)
#define MYNEWT_VAL_BLE_MONITOR_UART_BUFFER_SIZE (64)
#define MYNEWT_VAL_BLE_MONITOR_UART_DEV ("uart0")
#define MYNEWT_VAL_BLE_RPA_TIMEOUT (300)
#define MYNEWT_VAL_BLE_SM_BONDING (0)
#define MYNEWT_VAL_BLE_SM_IO_CAP (BLE_HS_IO_NO_INPUT_OUTPUT)
#define MYNEWT_VAL_BLE_SM_KEYPRESS (0)
#define MYNEWT_VAL_BLE_SM_LEGACY (1)
#define MYNEWT_VAL_BLE_SM_MAX_PROCS (1)
#define MYNEWT_VAL_BLE_SM_MITM (0)
#define MYNEWT_VAL_BLE_SM_OOB_DATA_FLAG (0)
#define MYNEWT_VAL_BLE_SM_OUR_KEY_DIST (0)
#define MYNEWT_VAL_BLE_SM_SC (1)
#define MYNEWT_VAL_BLE_SM_THEIR_KEY_DIST (0)
#define MYNEWT_VAL_BLE_STORE_MAX_BONDS (3)
#define MYNEWT_VAL_BLE_STORE_MAX_CCCDS (8)
/*** nimble/host/services/gap */
#define MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE (0)
#define MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE_WRITE_PERM (-1)
#define MYNEWT_VAL_BLE_SVC_GAP_CENTRAL_ADDRESS_RESOLUTION (-1)
#define MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME ("pybd")
#define MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_MAX_LENGTH (31)
#define MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_WRITE_PERM (-1)
#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_MAX_CONN_INTERVAL (0)
#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_MIN_CONN_INTERVAL (0)
#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_SLAVE_LATENCY (0)
#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_SUPERVISION_TMO (0)
/* Overridden by targets/porting-nimble (defined by nimble/transport) */
#define MYNEWT_VAL_BLE_HCI_TRANSPORT_NIMBLE_BUILTIN (0)
#define MYNEWT_VAL_BLE_HCI_TRANSPORT_RAM (0)
#define MYNEWT_VAL_BLE_HCI_TRANSPORT_SOCKET (0)
#define MYNEWT_VAL_BLE_HCI_TRANSPORT_UART (1)
/*** nimble/transport/uart */
#define MYNEWT_VAL_BLE_ACL_BUF_COUNT (12)
#define MYNEWT_VAL_BLE_ACL_BUF_SIZE (255)
#define MYNEWT_VAL_BLE_HCI_ACL_OUT_COUNT (12)
#define MYNEWT_VAL_BLE_HCI_EVT_BUF_SIZE (70)
#define MYNEWT_VAL_BLE_HCI_EVT_HI_BUF_COUNT (8)
#define MYNEWT_VAL_BLE_HCI_EVT_LO_BUF_COUNT (8)
/* Overridden by targets/porting-nimble (defined by nimble/transport/uart) */
#define MYNEWT_VAL_BLE_HCI_UART_BAUD (MICROPY_HW_BLE_UART_BAUDRATE)
#define MYNEWT_VAL_BLE_HCI_UART_DATA_BITS (8)
#define MYNEWT_VAL_BLE_HCI_UART_FLOW_CTRL (1)
#define MYNEWT_VAL_BLE_HCI_UART_PARITY (HAL_UART_PARITY_NONE)
#define MYNEWT_VAL_BLE_HCI_UART_PORT (MICROPY_HW_BLE_UART_ID)
#define MYNEWT_VAL_BLE_HCI_UART_STOP_BITS (1)
#endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_SYSCFG_H

View File

@ -53,6 +53,9 @@ static const char *_compilecode(const char *re, ByteProg *prog, int sizecode)
PC++; // Skip # of pair byte PC++; // Skip # of pair byte
prog->len++; prog->len++;
for (cnt = 0; *re != ']'; re++, cnt++) { for (cnt = 0; *re != ']'; re++, cnt++) {
if (*re == '\\') {
++re;
}
if (!*re) return NULL; if (!*re) return NULL;
EMIT(PC++, *re); EMIT(PC++, *re);
if (re[1] == '-' && re[2] != ']') { if (re[1] == '-' && re[2] != ']') {

View File

@ -38,18 +38,40 @@
#define MP_S_IFDIR (0x4000) #define MP_S_IFDIR (0x4000)
#define MP_S_IFREG (0x8000) #define MP_S_IFREG (0x8000)
// these are the values for mp_vfs_blockdev_t.flags
#define MP_BLOCKDEV_FLAG_NATIVE (0x0001) // readblocks[2]/writeblocks[2] contain native func
#define MP_BLOCKDEV_FLAG_FREE_OBJ (0x0002) // fs_user_mount_t obj should be freed on umount
#define MP_BLOCKDEV_FLAG_HAVE_IOCTL (0x0004) // new protocol with ioctl
#define MP_BLOCKDEV_FLAG_NO_FILESYSTEM (0x0008) // the block device has no filesystem on it
// constants for block protocol ioctl // constants for block protocol ioctl
#define BP_IOCTL_INIT (1) #define MP_BLOCKDEV_IOCTL_INIT (1)
#define BP_IOCTL_DEINIT (2) #define MP_BLOCKDEV_IOCTL_DEINIT (2)
#define BP_IOCTL_SYNC (3) #define MP_BLOCKDEV_IOCTL_SYNC (3)
#define BP_IOCTL_SEC_COUNT (4) #define MP_BLOCKDEV_IOCTL_BLOCK_COUNT (4)
#define BP_IOCTL_SEC_SIZE (5) #define MP_BLOCKDEV_IOCTL_BLOCK_SIZE (5)
#define MP_BLOCKDEV_IOCTL_BLOCK_ERASE (6)
// At the moment the VFS protocol just has import_stat, but could be extended to other methods // At the moment the VFS protocol just has import_stat, but could be extended to other methods
typedef struct _mp_vfs_proto_t { typedef struct _mp_vfs_proto_t {
mp_import_stat_t (*import_stat)(void *self, const char *path); mp_import_stat_t (*import_stat)(void *self, const char *path);
} mp_vfs_proto_t; } mp_vfs_proto_t;
typedef struct _mp_vfs_blockdev_t {
uint16_t flags;
size_t block_size;
mp_obj_t readblocks[5];
mp_obj_t writeblocks[5];
// new protocol uses just ioctl, old uses sync (optional) and count
union {
mp_obj_t ioctl[4];
struct {
mp_obj_t sync[2];
mp_obj_t count[2];
} old;
} u;
} mp_vfs_blockdev_t;
typedef struct _mp_vfs_mount_t { typedef struct _mp_vfs_mount_t {
const char *str; // mount point with leading / const char *str; // mount point with leading /
size_t len; size_t len;
@ -57,6 +79,13 @@ typedef struct _mp_vfs_mount_t {
struct _mp_vfs_mount_t *next; struct _mp_vfs_mount_t *next;
} mp_vfs_mount_t; } mp_vfs_mount_t;
void mp_vfs_blockdev_init(mp_vfs_blockdev_t *self, mp_obj_t bdev);
int mp_vfs_blockdev_read(mp_vfs_blockdev_t *self, size_t block_num, size_t num_blocks, uint8_t *buf);
int mp_vfs_blockdev_read_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t block_off, size_t len, uint8_t *buf);
int mp_vfs_blockdev_write(mp_vfs_blockdev_t *self, size_t block_num, size_t num_blocks, const uint8_t *buf);
int mp_vfs_blockdev_write_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t block_off, size_t len, const uint8_t *buf);
mp_obj_t mp_vfs_blockdev_ioctl(mp_vfs_blockdev_t *self, uintptr_t cmd, uintptr_t arg);
mp_vfs_mount_t *mp_vfs_lookup_path(const char *path, const char **path_out); mp_vfs_mount_t *mp_vfs_lookup_path(const char *path, const char **path_out);
mp_import_stat_t mp_vfs_import_stat(const char *path); mp_import_stat_t mp_vfs_import_stat(const char *path);
mp_obj_t mp_vfs_mount(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); mp_obj_t mp_vfs_mount(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);

143
extmod/vfs_blockdev.c Normal file
View File

@ -0,0 +1,143 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013-2019 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "py/runtime.h"
#include "py/binary.h"
#include "py/objarray.h"
#include "py/mperrno.h"
#include "extmod/vfs.h"
#if MICROPY_VFS
void mp_vfs_blockdev_init(mp_vfs_blockdev_t *self, mp_obj_t bdev) {
mp_load_method(bdev, MP_QSTR_readblocks, self->readblocks);
mp_load_method_maybe(bdev, MP_QSTR_writeblocks, self->writeblocks);
mp_load_method_maybe(bdev, MP_QSTR_ioctl, self->u.ioctl);
if (self->u.ioctl[0] != MP_OBJ_NULL) {
// Device supports new block protocol, so indicate it
self->flags |= MP_BLOCKDEV_FLAG_HAVE_IOCTL;
} else {
// No ioctl method, so assume the device uses the old block protocol
mp_load_method_maybe(bdev, MP_QSTR_sync, self->u.old.sync);
mp_load_method(bdev, MP_QSTR_count, self->u.old.count);
}
}
int mp_vfs_blockdev_read(mp_vfs_blockdev_t *self, size_t block_num, size_t num_blocks, uint8_t *buf) {
if (self->flags & MP_BLOCKDEV_FLAG_NATIVE) {
mp_uint_t (*f)(uint8_t*, uint32_t, uint32_t) = (void*)(uintptr_t)self->readblocks[2];
return f(buf, block_num, num_blocks);
} else {
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, num_blocks * self->block_size, buf};
self->readblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num);
self->readblocks[3] = MP_OBJ_FROM_PTR(&ar);
mp_call_method_n_kw(2, 0, self->readblocks);
// TODO handle error return
return 0;
}
}
int mp_vfs_blockdev_read_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t block_off, size_t len, uint8_t *buf) {
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, len, buf};
self->readblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num);
self->readblocks[3] = MP_OBJ_FROM_PTR(&ar);
self->readblocks[4] = MP_OBJ_NEW_SMALL_INT(block_off);
mp_obj_t ret = mp_call_method_n_kw(3, 0, self->readblocks);
if (ret == mp_const_none) {
return 0;
} else {
return MP_OBJ_SMALL_INT_VALUE(ret);
}
}
int mp_vfs_blockdev_write(mp_vfs_blockdev_t *self, size_t block_num, size_t num_blocks, const uint8_t *buf) {
if (self->writeblocks[0] == MP_OBJ_NULL) {
// read-only block device
return -MP_EROFS;
}
if (self->flags & MP_BLOCKDEV_FLAG_NATIVE) {
mp_uint_t (*f)(const uint8_t*, uint32_t, uint32_t) = (void*)(uintptr_t)self->writeblocks[2];
return f(buf, block_num, num_blocks);
} else {
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, num_blocks * self->block_size, (void*)buf};
self->writeblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num);
self->writeblocks[3] = MP_OBJ_FROM_PTR(&ar);
mp_call_method_n_kw(2, 0, self->writeblocks);
// TODO handle error return
return 0;
}
}
int mp_vfs_blockdev_write_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t block_off, size_t len, const uint8_t *buf) {
if (self->writeblocks[0] == MP_OBJ_NULL) {
// read-only block device
return -MP_EROFS;
}
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, len, (void*)buf};
self->writeblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num);
self->writeblocks[3] = MP_OBJ_FROM_PTR(&ar);
self->writeblocks[4] = MP_OBJ_NEW_SMALL_INT(block_off);
mp_obj_t ret = mp_call_method_n_kw(3, 0, self->writeblocks);
if (ret == mp_const_none) {
return 0;
} else {
return MP_OBJ_SMALL_INT_VALUE(ret);
}
}
mp_obj_t mp_vfs_blockdev_ioctl(mp_vfs_blockdev_t *self, uintptr_t cmd, uintptr_t arg) {
if (self->flags & MP_BLOCKDEV_FLAG_HAVE_IOCTL) {
// New protocol with ioctl
self->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(cmd);
self->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(arg);
return mp_call_method_n_kw(2, 0, self->u.ioctl);
} else {
// Old protocol with sync and count
switch (cmd) {
case MP_BLOCKDEV_IOCTL_SYNC:
if (self->u.old.sync[0] != MP_OBJ_NULL) {
mp_call_method_n_kw(0, 0, self->u.old.sync);
}
break;
case MP_BLOCKDEV_IOCTL_BLOCK_COUNT:
return mp_call_method_n_kw(0, 0, self->u.old.count);
case MP_BLOCKDEV_IOCTL_BLOCK_SIZE:
// Old protocol has fixed sector size of 512 bytes
break;
case MP_BLOCKDEV_IOCTL_INIT:
// Old protocol doesn't have init
break;
}
return mp_const_none;
}
}
#endif // MICROPY_VFS

View File

@ -68,27 +68,18 @@ STATIC mp_obj_t fat_vfs_make_new(const mp_obj_type_t *type, size_t n_args, size_
// create new object // create new object
fs_user_mount_t *vfs = m_new_obj(fs_user_mount_t); fs_user_mount_t *vfs = m_new_obj(fs_user_mount_t);
vfs->base.type = type; vfs->base.type = type;
vfs->flags = FSUSER_FREE_OBJ;
vfs->fatfs.drv = vfs; vfs->fatfs.drv = vfs;
// load block protocol methods // Initialise underlying block device
mp_load_method(args[0], MP_QSTR_readblocks, vfs->readblocks); vfs->blockdev.flags = MP_BLOCKDEV_FLAG_FREE_OBJ;
mp_load_method_maybe(args[0], MP_QSTR_writeblocks, vfs->writeblocks); vfs->blockdev.block_size = FF_MIN_SS; // default, will be populated by call to MP_BLOCKDEV_IOCTL_BLOCK_SIZE
mp_load_method_maybe(args[0], MP_QSTR_ioctl, vfs->u.ioctl); mp_vfs_blockdev_init(&vfs->blockdev, args[0]);
if (vfs->u.ioctl[0] != MP_OBJ_NULL) {
// device supports new block protocol, so indicate it
vfs->flags |= FSUSER_HAVE_IOCTL;
} else {
// no ioctl method, so assume the device uses the old block protocol
mp_load_method_maybe(args[0], MP_QSTR_sync, vfs->u.old.sync);
mp_load_method(args[0], MP_QSTR_count, vfs->u.old.count);
}
// mount the block device so the VFS methods can be used // mount the block device so the VFS methods can be used
FRESULT res = f_mount(&vfs->fatfs); FRESULT res = f_mount(&vfs->fatfs);
if (res == FR_NO_FILESYSTEM) { if (res == FR_NO_FILESYSTEM) {
// don't error out if no filesystem, to let mkfs()/mount() create one if wanted // don't error out if no filesystem, to let mkfs()/mount() create one if wanted
vfs->flags |= FSUSER_NO_FILESYSTEM; vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_NO_FILESYSTEM;
} else if (res != FR_OK) { } else if (res != FR_OK) {
mp_raise_OSError(fresult_to_errno_table[res]); mp_raise_OSError(fresult_to_errno_table[res]);
} }
@ -380,11 +371,11 @@ STATIC mp_obj_t vfs_fat_mount(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs
// 1. readonly=True keyword argument // 1. readonly=True keyword argument
// 2. nonexistent writeblocks method (then writeblocks[0] == MP_OBJ_NULL already) // 2. nonexistent writeblocks method (then writeblocks[0] == MP_OBJ_NULL already)
if (mp_obj_is_true(readonly)) { if (mp_obj_is_true(readonly)) {
self->writeblocks[0] = MP_OBJ_NULL; self->blockdev.writeblocks[0] = MP_OBJ_NULL;
} }
// check if we need to make the filesystem // check if we need to make the filesystem
FRESULT res = (self->flags & FSUSER_NO_FILESYSTEM) ? FR_NO_FILESYSTEM : FR_OK; FRESULT res = (self->blockdev.flags & MP_BLOCKDEV_FLAG_NO_FILESYSTEM) ? FR_NO_FILESYSTEM : FR_OK;
if (res == FR_NO_FILESYSTEM && mp_obj_is_true(mkfs)) { if (res == FR_NO_FILESYSTEM && mp_obj_is_true(mkfs)) {
uint8_t working_buf[FF_MAX_SS]; uint8_t working_buf[FF_MAX_SS];
res = f_mkfs(&self->fatfs, FM_FAT | FM_SFD, 0, working_buf, sizeof(working_buf)); res = f_mkfs(&self->fatfs, FM_FAT | FM_SFD, 0, working_buf, sizeof(working_buf));
@ -392,7 +383,7 @@ STATIC mp_obj_t vfs_fat_mount(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs
if (res != FR_OK) { if (res != FR_OK) {
mp_raise_OSError(fresult_to_errno_table[res]); mp_raise_OSError(fresult_to_errno_table[res]);
} }
self->flags &= ~FSUSER_NO_FILESYSTEM; self->blockdev.flags &= ~MP_BLOCKDEV_FLAG_NO_FILESYSTEM;
return mp_const_none; return mp_const_none;
} }

View File

@ -26,30 +26,13 @@
#ifndef MICROPY_INCLUDED_EXTMOD_VFS_FAT_H #ifndef MICROPY_INCLUDED_EXTMOD_VFS_FAT_H
#define MICROPY_INCLUDED_EXTMOD_VFS_FAT_H #define MICROPY_INCLUDED_EXTMOD_VFS_FAT_H
#include "py/lexer.h"
#include "py/obj.h" #include "py/obj.h"
#include "lib/oofatfs/ff.h" #include "lib/oofatfs/ff.h"
#include "extmod/vfs.h" #include "extmod/vfs.h"
// these are the values for fs_user_mount_t.flags
#define FSUSER_NATIVE (0x0001) // readblocks[2]/writeblocks[2] contain native func
#define FSUSER_FREE_OBJ (0x0002) // fs_user_mount_t obj should be freed on umount
#define FSUSER_HAVE_IOCTL (0x0004) // new protocol with ioctl
#define FSUSER_NO_FILESYSTEM (0x0008) // the block device has no filesystem on it
typedef struct _fs_user_mount_t { typedef struct _fs_user_mount_t {
mp_obj_base_t base; mp_obj_base_t base;
uint16_t flags; mp_vfs_blockdev_t blockdev;
mp_obj_t readblocks[4];
mp_obj_t writeblocks[4];
// new protocol uses just ioctl, old uses sync (optional) and count
union {
mp_obj_t ioctl[4];
struct {
mp_obj_t sync[2];
mp_obj_t count[2];
} old;
} u;
FATFS fatfs; FATFS fatfs;
} fs_user_mount_t; } fs_user_mount_t;

View File

@ -38,16 +38,11 @@
#include "py/runtime.h" #include "py/runtime.h"
#include "py/binary.h" #include "py/binary.h"
#include "py/objarray.h" #include "py/objarray.h"
#include "py/mperrno.h"
#include "lib/oofatfs/ff.h" #include "lib/oofatfs/ff.h"
#include "lib/oofatfs/diskio.h" #include "lib/oofatfs/diskio.h"
#include "extmod/vfs_fat.h" #include "extmod/vfs_fat.h"
#if FF_MAX_SS == FF_MIN_SS
#define SECSIZE(fs) (FF_MIN_SS)
#else
#define SECSIZE(fs) ((fs)->ssize)
#endif
typedef void *bdev_t; typedef void *bdev_t;
STATIC fs_user_mount_t *disk_get_device(void *bdev) { STATIC fs_user_mount_t *disk_get_device(void *bdev) {
return (fs_user_mount_t*)bdev; return (fs_user_mount_t*)bdev;
@ -69,20 +64,9 @@ DRESULT disk_read (
return RES_PARERR; return RES_PARERR;
} }
if (vfs->flags & FSUSER_NATIVE) { int ret = mp_vfs_blockdev_read(&vfs->blockdev, sector, count, buff);
mp_uint_t (*f)(uint8_t*, uint32_t, uint32_t) = (void*)(uintptr_t)vfs->readblocks[2];
if (f(buff, sector, count) != 0) {
return RES_ERROR;
}
} else {
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, count * SECSIZE(&vfs->fatfs), buff};
vfs->readblocks[2] = MP_OBJ_NEW_SMALL_INT(sector);
vfs->readblocks[3] = MP_OBJ_FROM_PTR(&ar);
mp_call_method_n_kw(2, 0, vfs->readblocks);
// TODO handle error return
}
return RES_OK; return ret == 0 ? RES_OK : RES_ERROR;
} }
/*-----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/
@ -101,25 +85,14 @@ DRESULT disk_write (
return RES_PARERR; return RES_PARERR;
} }
if (vfs->writeblocks[0] == MP_OBJ_NULL) { int ret = mp_vfs_blockdev_write(&vfs->blockdev, sector, count, buff);
if (ret == -MP_EROFS) {
// read-only block device // read-only block device
return RES_WRPRT; return RES_WRPRT;
} }
if (vfs->flags & FSUSER_NATIVE) { return ret == 0 ? RES_OK : RES_ERROR;
mp_uint_t (*f)(const uint8_t*, uint32_t, uint32_t) = (void*)(uintptr_t)vfs->writeblocks[2];
if (f(buff, sector, count) != 0) {
return RES_ERROR;
}
} else {
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, count * SECSIZE(&vfs->fatfs), (void*)buff};
vfs->writeblocks[2] = MP_OBJ_NEW_SMALL_INT(sector);
vfs->writeblocks[3] = MP_OBJ_FROM_PTR(&ar);
mp_call_method_n_kw(2, 0, vfs->writeblocks);
// TODO handle error return
}
return RES_OK;
} }
@ -139,42 +112,16 @@ DRESULT disk_ioctl (
} }
// First part: call the relevant method of the underlying block device // First part: call the relevant method of the underlying block device
mp_obj_t ret = mp_const_none;
if (vfs->flags & FSUSER_HAVE_IOCTL) {
// new protocol with ioctl
static const uint8_t op_map[8] = { static const uint8_t op_map[8] = {
[CTRL_SYNC] = BP_IOCTL_SYNC, [CTRL_SYNC] = MP_BLOCKDEV_IOCTL_SYNC,
[GET_SECTOR_COUNT] = BP_IOCTL_SEC_COUNT, [GET_SECTOR_COUNT] = MP_BLOCKDEV_IOCTL_BLOCK_COUNT,
[GET_SECTOR_SIZE] = BP_IOCTL_SEC_SIZE, [GET_SECTOR_SIZE] = MP_BLOCKDEV_IOCTL_BLOCK_SIZE,
[IOCTL_INIT] = BP_IOCTL_INIT, [IOCTL_INIT] = MP_BLOCKDEV_IOCTL_INIT,
}; };
uint8_t bp_op = op_map[cmd & 7]; uint8_t bp_op = op_map[cmd & 7];
mp_obj_t ret = mp_const_none;
if (bp_op != 0) { if (bp_op != 0) {
vfs->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(bp_op); ret = mp_vfs_blockdev_ioctl(&vfs->blockdev, bp_op, 0);
vfs->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(0); // unused
ret = mp_call_method_n_kw(2, 0, vfs->u.ioctl);
}
} else {
// old protocol with sync and count
switch (cmd) {
case CTRL_SYNC:
if (vfs->u.old.sync[0] != MP_OBJ_NULL) {
mp_call_method_n_kw(0, 0, vfs->u.old.sync);
}
break;
case GET_SECTOR_COUNT:
ret = mp_call_method_n_kw(0, 0, vfs->u.old.count);
break;
case GET_SECTOR_SIZE:
// old protocol has fixed sector size of 512 bytes
break;
case IOCTL_INIT:
// old protocol doesn't have init
break;
}
} }
// Second part: convert the result for return // Second part: convert the result for return
@ -194,10 +141,8 @@ DRESULT disk_ioctl (
} else { } else {
*((WORD*)buff) = mp_obj_get_int(ret); *((WORD*)buff) = mp_obj_get_int(ret);
} }
#if FF_MAX_SS != FF_MIN_SS
// need to store ssize because we use it in disk_read/disk_write // need to store ssize because we use it in disk_read/disk_write
vfs->fatfs.ssize = *((WORD*)buff); vfs->blockdev.block_size = *((WORD*)buff);
#endif
return RES_OK; return RES_OK;
} }
@ -211,7 +156,7 @@ DRESULT disk_ioctl (
if (ret != mp_const_none && MP_OBJ_SMALL_INT_VALUE(ret) != 0) { if (ret != mp_const_none && MP_OBJ_SMALL_INT_VALUE(ret) != 0) {
// error initialising // error initialising
stat = STA_NOINIT; stat = STA_NOINIT;
} else if (vfs->writeblocks[0] == MP_OBJ_NULL) { } else if (vfs->blockdev.writeblocks[0] == MP_OBJ_NULL) {
stat = STA_PROTECT; stat = STA_PROTECT;
} else { } else {
stat = 0; stat = 0;

125
extmod/vfs_lfs.c Normal file
View File

@ -0,0 +1,125 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "py/runtime.h"
#include "extmod/vfs.h"
#include "extmod/vfs_lfs.h"
#if MICROPY_VFS && (MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2)
enum { LFS_MAKE_ARG_bdev, LFS_MAKE_ARG_readsize, LFS_MAKE_ARG_progsize, LFS_MAKE_ARG_lookahead };
static const mp_arg_t lfs_make_allowed_args[] = {
{ MP_QSTR_, MP_ARG_OBJ },
{ MP_QSTR_readsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} },
{ MP_QSTR_progsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} },
{ MP_QSTR_lookahead, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} },
};
#if MICROPY_VFS_LFS1
#include "lib/littlefs/lfs1.h"
#define LFS_BUILD_VERSION (1)
#define LFSx_MACRO(s) LFS1 ## s
#define LFSx_API(s) lfs1_ ## s
#define MP_VFS_LFSx(s) mp_vfs_lfs1_ ## s
#define MP_OBJ_VFS_LFSx mp_obj_vfs_lfs1_t
#define MP_OBJ_VFS_LFSx_FILE mp_obj_vfs_lfs1_file_t
#define MP_TYPE_VFS_LFSx mp_type_vfs_lfs1
#define MP_TYPE_VFS_LFSx_(s) mp_type_vfs_lfs1 ## s
typedef struct _mp_obj_vfs_lfs1_t {
mp_obj_base_t base;
mp_vfs_blockdev_t blockdev;
vstr_t cur_dir;
struct lfs1_config config;
lfs1_t lfs;
} mp_obj_vfs_lfs1_t;
typedef struct _mp_obj_vfs_lfs1_file_t {
mp_obj_base_t base;
mp_obj_vfs_lfs1_t *vfs;
lfs1_file_t file;
struct lfs1_file_config cfg;
uint8_t file_buffer[0];
} mp_obj_vfs_lfs1_file_t;
const char *mp_vfs_lfs1_make_path(mp_obj_vfs_lfs1_t *self, mp_obj_t path_in);
mp_obj_t mp_vfs_lfs1_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in);
#include "extmod/vfs_lfsx.c"
#include "extmod/vfs_lfsx_file.c"
#undef LFS_BUILD_VERSION
#undef LFSx_MACRO
#undef LFSx_API
#undef MP_VFS_LFSx
#undef MP_OBJ_VFS_LFSx
#undef MP_OBJ_VFS_LFSx_FILE
#undef MP_TYPE_VFS_LFSx
#undef MP_TYPE_VFS_LFSx_
#endif // MICROPY_VFS_LFS1
#if MICROPY_VFS_LFS2
#include "lib/littlefs/lfs2.h"
#define LFS_BUILD_VERSION (2)
#define LFSx_MACRO(s) LFS2 ## s
#define LFSx_API(s) lfs2_ ## s
#define MP_VFS_LFSx(s) mp_vfs_lfs2_ ## s
#define MP_OBJ_VFS_LFSx mp_obj_vfs_lfs2_t
#define MP_OBJ_VFS_LFSx_FILE mp_obj_vfs_lfs2_file_t
#define MP_TYPE_VFS_LFSx mp_type_vfs_lfs2
#define MP_TYPE_VFS_LFSx_(s) mp_type_vfs_lfs2 ## s
typedef struct _mp_obj_vfs_lfs2_t {
mp_obj_base_t base;
mp_vfs_blockdev_t blockdev;
vstr_t cur_dir;
struct lfs2_config config;
lfs2_t lfs;
} mp_obj_vfs_lfs2_t;
typedef struct _mp_obj_vfs_lfs2_file_t {
mp_obj_base_t base;
mp_obj_vfs_lfs2_t *vfs;
lfs2_file_t file;
struct lfs2_file_config cfg;
uint8_t file_buffer[0];
} mp_obj_vfs_lfs2_file_t;
const char *mp_vfs_lfs2_make_path(mp_obj_vfs_lfs2_t *self, mp_obj_t path_in);
mp_obj_t mp_vfs_lfs2_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in);
#include "extmod/vfs_lfsx.c"
#include "extmod/vfs_lfsx_file.c"
#endif // MICROPY_VFS_LFS2
#endif // MICROPY_VFS && (MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2)

39
extmod/vfs_lfs.h Normal file
View File

@ -0,0 +1,39 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_EXTMOD_VFS_LFS_H
#define MICROPY_INCLUDED_EXTMOD_VFS_LFS_H
#include "py/obj.h"
extern const mp_obj_type_t mp_type_vfs_lfs1;
extern const mp_obj_type_t mp_type_vfs_lfs1_fileio;
extern const mp_obj_type_t mp_type_vfs_lfs1_textio;
extern const mp_obj_type_t mp_type_vfs_lfs2;
extern const mp_obj_type_t mp_type_vfs_lfs2_fileio;
extern const mp_obj_type_t mp_type_vfs_lfs2_textio;
#endif // MICROPY_INCLUDED_EXTMOD_VFS_LFS_H

428
extmod/vfs_lfsx.c Normal file
View File

@ -0,0 +1,428 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdio.h>
#include <string.h>
#include "py/runtime.h"
#include "py/stream.h"
#include "py/binary.h"
#include "py/objarray.h"
#include "py/mperrno.h"
#include "extmod/vfs.h"
STATIC int MP_VFS_LFSx(dev_ioctl)(const struct LFSx_API(config) *c, int cmd, int arg, bool must_return_int) {
mp_obj_t ret = mp_vfs_blockdev_ioctl(c->context, cmd, arg);
int ret_i = 0;
if (must_return_int || ret != mp_const_none) {
ret_i = mp_obj_get_int(ret);
}
return ret_i;
}
STATIC int MP_VFS_LFSx(dev_read)(const struct LFSx_API(config) *c, LFSx_API(block_t) block, LFSx_API(off_t) off, void *buffer, LFSx_API(size_t) size) {
return mp_vfs_blockdev_read_ext(c->context, block, off, size, buffer);
}
STATIC int MP_VFS_LFSx(dev_prog)(const struct LFSx_API(config) *c, LFSx_API(block_t) block, LFSx_API(off_t) off, const void *buffer, LFSx_API(size_t) size) {
return mp_vfs_blockdev_write_ext(c->context, block, off, size, buffer);
}
STATIC int MP_VFS_LFSx(dev_erase)(const struct LFSx_API(config) *c, LFSx_API(block_t) block) {
return MP_VFS_LFSx(dev_ioctl)(c, MP_BLOCKDEV_IOCTL_BLOCK_ERASE, block, true);
}
STATIC int MP_VFS_LFSx(dev_sync)(const struct LFSx_API(config) *c) {
return MP_VFS_LFSx(dev_ioctl)(c, MP_BLOCKDEV_IOCTL_SYNC, 0, false);
}
STATIC void MP_VFS_LFSx(init_config)(MP_OBJ_VFS_LFSx *self, mp_obj_t bdev, size_t read_size, size_t prog_size, size_t lookahead) {
self->blockdev.flags = MP_BLOCKDEV_FLAG_FREE_OBJ;
mp_vfs_blockdev_init(&self->blockdev, bdev);
struct LFSx_API(config) *config = &self->config;
memset(config, 0, sizeof(*config));
config->context = &self->blockdev;
config->read = MP_VFS_LFSx(dev_read);
config->prog = MP_VFS_LFSx(dev_prog);
config->erase = MP_VFS_LFSx(dev_erase);
config->sync = MP_VFS_LFSx(dev_sync);
MP_VFS_LFSx(dev_ioctl)(config, MP_BLOCKDEV_IOCTL_INIT, 0, false); // initialise block device
int bs = MP_VFS_LFSx(dev_ioctl)(config, MP_BLOCKDEV_IOCTL_BLOCK_SIZE, 0, true); // get block size
int bc = MP_VFS_LFSx(dev_ioctl)(config, MP_BLOCKDEV_IOCTL_BLOCK_COUNT, 0, true); // get block count
self->blockdev.block_size = bs;
config->read_size = read_size;
config->prog_size = prog_size;
config->block_size = bs;
config->block_count = bc;
#if LFS_BUILD_VERSION == 1
config->lookahead = lookahead;
config->read_buffer = m_new(uint8_t, config->read_size);
config->prog_buffer = m_new(uint8_t, config->prog_size);
config->lookahead_buffer = m_new(uint8_t, config->lookahead / 8);
#else
config->block_cycles = 100;
config->cache_size = 4 * MAX(read_size, prog_size);
config->lookahead_size = lookahead;
config->read_buffer = m_new(uint8_t, config->cache_size);
config->prog_buffer = m_new(uint8_t, config->cache_size);
config->lookahead_buffer = m_new(uint8_t, config->lookahead_size);
#endif
}
const char *MP_VFS_LFSx(make_path)(MP_OBJ_VFS_LFSx *self, mp_obj_t path_in) {
const char *path = mp_obj_str_get_str(path_in);
if (path[0] != '/') {
size_t l = vstr_len(&self->cur_dir);
if (l > 0) {
vstr_add_str(&self->cur_dir, path);
path = vstr_null_terminated_str(&self->cur_dir);
self->cur_dir.len = l;
}
}
return path;
}
STATIC mp_obj_t MP_VFS_LFSx(make_new)(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
mp_arg_val_t args[MP_ARRAY_SIZE(lfs_make_allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(lfs_make_allowed_args), lfs_make_allowed_args, args);
MP_OBJ_VFS_LFSx *self = m_new0(MP_OBJ_VFS_LFSx, 1);
self->base.type = type;
vstr_init(&self->cur_dir, 16);
vstr_add_byte(&self->cur_dir, '/');
MP_VFS_LFSx(init_config)(self, args[LFS_MAKE_ARG_bdev].u_obj,
args[LFS_MAKE_ARG_readsize].u_int, args[LFS_MAKE_ARG_progsize].u_int, args[LFS_MAKE_ARG_lookahead].u_int);
int ret = LFSx_API(mount)(&self->lfs, &self->config);
if (ret < 0) {
mp_raise_OSError(-ret);
}
return MP_OBJ_FROM_PTR(self);
}
STATIC mp_obj_t MP_VFS_LFSx(mkfs)(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
mp_arg_val_t args[MP_ARRAY_SIZE(lfs_make_allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(lfs_make_allowed_args), lfs_make_allowed_args, args);
MP_OBJ_VFS_LFSx self;
MP_VFS_LFSx(init_config)(&self, args[LFS_MAKE_ARG_bdev].u_obj,
args[LFS_MAKE_ARG_readsize].u_int, args[LFS_MAKE_ARG_progsize].u_int, args[LFS_MAKE_ARG_lookahead].u_int);
int ret = LFSx_API(format)(&self.lfs, &self.config);
if (ret < 0) {
mp_raise_OSError(-ret);
}
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(MP_VFS_LFSx(mkfs_fun_obj), 0, MP_VFS_LFSx(mkfs));
STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(MP_VFS_LFSx(mkfs_obj), MP_ROM_PTR(&MP_VFS_LFSx(mkfs_fun_obj)));
// Implementation of mp_vfs_lfs_file_open is provided in vfs_lfsx_file.c
STATIC MP_DEFINE_CONST_FUN_OBJ_3(MP_VFS_LFSx(open_obj), MP_VFS_LFSx(file_open));
typedef struct MP_VFS_LFSx(_ilistdir_it_t) {
mp_obj_base_t base;
mp_fun_1_t iternext;
bool is_str;
MP_OBJ_VFS_LFSx *vfs;
LFSx_API(dir_t) dir;
} MP_VFS_LFSx(ilistdir_it_t);
STATIC mp_obj_t MP_VFS_LFSx(ilistdir_it_iternext)(mp_obj_t self_in) {
MP_VFS_LFSx(ilistdir_it_t) *self = MP_OBJ_TO_PTR(self_in);
struct LFSx_API(info) info;
for (;;) {
int ret = LFSx_API(dir_read)(&self->vfs->lfs, &self->dir, &info);
if (ret == 0) {
LFSx_API(dir_close)(&self->vfs->lfs, &self->dir);
return MP_OBJ_STOP_ITERATION;
}
if (!(info.name[0] == '.' && (info.name[1] == '\0'
|| (info.name[1] == '.' && info.name[2] == '\0')))) {
break;
}
}
// make 4-tuple with info about this entry
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(4, NULL));
if (self->is_str) {
t->items[0] = mp_obj_new_str(info.name, strlen(info.name));
} else {
t->items[0] = mp_obj_new_bytes((const byte*)info.name, strlen(info.name));
}
t->items[1] = MP_OBJ_NEW_SMALL_INT(info.type == LFSx_MACRO(_TYPE_REG) ? MP_S_IFREG : MP_S_IFDIR);
t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // no inode number
t->items[3] = MP_OBJ_NEW_SMALL_INT(info.size);
return MP_OBJ_FROM_PTR(t);
}
STATIC mp_obj_t MP_VFS_LFSx(ilistdir_func)(size_t n_args, const mp_obj_t *args) {
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(args[0]);
bool is_str_type = true;
const char *path;
if (n_args == 2) {
if (mp_obj_get_type(args[1]) == &mp_type_bytes) {
is_str_type = false;
}
path = MP_VFS_LFSx(make_path)(self, args[1]);
} else {
path = vstr_null_terminated_str(&self->cur_dir);
}
MP_VFS_LFSx(ilistdir_it_t) *iter = m_new_obj(MP_VFS_LFSx(ilistdir_it_t));
iter->base.type = &mp_type_polymorph_iter;
iter->iternext = MP_VFS_LFSx(ilistdir_it_iternext);
iter->is_str = is_str_type;
iter->vfs = self;
int ret = LFSx_API(dir_open)(&self->lfs, &iter->dir, path);
if (ret < 0) {
mp_raise_OSError(-ret);
}
return MP_OBJ_FROM_PTR(iter);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(MP_VFS_LFSx(ilistdir_obj), 1, 2, MP_VFS_LFSx(ilistdir_func));
STATIC mp_obj_t MP_VFS_LFSx(remove)(mp_obj_t self_in, mp_obj_t path_in) {
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
const char *path = MP_VFS_LFSx(make_path)(self, path_in);
int ret = LFSx_API(remove)(&self->lfs, path);
if (ret < 0) {
mp_raise_OSError(-ret);
}
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(remove_obj), MP_VFS_LFSx(remove));
STATIC mp_obj_t MP_VFS_LFSx(rmdir)(mp_obj_t self_in, mp_obj_t path_in) {
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
const char *path = MP_VFS_LFSx(make_path)(self, path_in);
int ret = LFSx_API(remove)(&self->lfs, path);
if (ret < 0) {
mp_raise_OSError(-ret);
}
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(rmdir_obj), MP_VFS_LFSx(rmdir));
STATIC mp_obj_t MP_VFS_LFSx(rename)(mp_obj_t self_in, mp_obj_t path_old_in, mp_obj_t path_new_in) {
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
const char *path_old = MP_VFS_LFSx(make_path)(self, path_old_in);
vstr_t path_new;
vstr_init(&path_new, vstr_len(&self->cur_dir));
vstr_add_strn(&path_new, vstr_str(&self->cur_dir), vstr_len(&self->cur_dir));
vstr_add_str(&path_new, mp_obj_str_get_str(path_new_in));
int ret = LFSx_API(rename)(&self->lfs, path_old, vstr_null_terminated_str(&path_new));
vstr_clear(&path_new);
if (ret < 0) {
mp_raise_OSError(-ret);
}
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(MP_VFS_LFSx(rename_obj), MP_VFS_LFSx(rename));
STATIC mp_obj_t MP_VFS_LFSx(mkdir)(mp_obj_t self_in, mp_obj_t path_o) {
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
const char *path = MP_VFS_LFSx(make_path)(self, path_o);
int ret = LFSx_API(mkdir)(&self->lfs, path);
if (ret < 0) {
mp_raise_OSError(-ret);
}
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(mkdir_obj), MP_VFS_LFSx(mkdir));
STATIC mp_obj_t MP_VFS_LFSx(chdir)(mp_obj_t self_in, mp_obj_t path_in) {
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
// Check path exists
const char *path = MP_VFS_LFSx(make_path)(self, path_in);
if (path[1] != '\0') {
// Not at root, check it exists
struct LFSx_API(info) info;
int ret = LFSx_API(stat)(&self->lfs, path, &info);
if (ret < 0 || info.type != LFSx_MACRO(_TYPE_DIR)) {
mp_raise_OSError(-MP_ENOENT);
}
}
// Update cur_dir with new path
if (path == vstr_str(&self->cur_dir)) {
self->cur_dir.len = strlen(path);
} else {
vstr_reset(&self->cur_dir);
vstr_add_str(&self->cur_dir, path);
}
// If not at root add trailing / to make it easy to build paths
if (vstr_len(&self->cur_dir) != 1) {
vstr_add_byte(&self->cur_dir, '/');
}
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(chdir_obj), MP_VFS_LFSx(chdir));
STATIC mp_obj_t MP_VFS_LFSx(getcwd)(mp_obj_t self_in) {
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
if (vstr_len(&self->cur_dir) == 1) {
return MP_OBJ_NEW_QSTR(MP_QSTR__slash_);
} else {
// don't include trailing /
return mp_obj_new_str(self->cur_dir.buf, self->cur_dir.len - 1);
}
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(MP_VFS_LFSx(getcwd_obj), MP_VFS_LFSx(getcwd));
STATIC mp_obj_t MP_VFS_LFSx(stat)(mp_obj_t self_in, mp_obj_t path_in) {
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
const char *path = mp_obj_str_get_str(path_in);
struct LFSx_API(info) info;
int ret = LFSx_API(stat)(&self->lfs, path, &info);
if (ret < 0) {
mp_raise_OSError(-ret);
}
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL));
t->items[0] = MP_OBJ_NEW_SMALL_INT(info.type == LFSx_MACRO(_TYPE_REG) ? MP_S_IFREG : MP_S_IFDIR); // st_mode
t->items[1] = MP_OBJ_NEW_SMALL_INT(0); // st_ino
t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // st_dev
t->items[3] = MP_OBJ_NEW_SMALL_INT(0); // st_nlink
t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid
t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid
t->items[6] = mp_obj_new_int_from_uint(info.size); // st_size
t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // st_atime
t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // st_mtime
t->items[9] = MP_OBJ_NEW_SMALL_INT(0); // st_ctime
return MP_OBJ_FROM_PTR(t);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(stat_obj), MP_VFS_LFSx(stat));
STATIC int LFSx_API(traverse_cb)(void *data, LFSx_API(block_t) bl) {
(void)bl;
uint32_t *n = (uint32_t*)data;
*n += 1;
return LFSx_MACRO(_ERR_OK);
}
STATIC mp_obj_t MP_VFS_LFSx(statvfs)(mp_obj_t self_in, mp_obj_t path_in) {
(void)path_in;
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
uint32_t n_used_blocks = 0;
#if LFS_BUILD_VERSION == 1
int ret = LFSx_API(traverse)(&self->lfs, LFSx_API(traverse_cb), &n_used_blocks);
#else
int ret = LFSx_API(fs_traverse)(&self->lfs, LFSx_API(traverse_cb), &n_used_blocks);
#endif
if (ret < 0) {
mp_raise_OSError(-ret);
}
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL));
t->items[0] = MP_OBJ_NEW_SMALL_INT(self->lfs.cfg->block_size); // f_bsize
t->items[1] = t->items[0]; // f_frsize
t->items[2] = MP_OBJ_NEW_SMALL_INT(self->lfs.cfg->block_count); // f_blocks
t->items[3] = MP_OBJ_NEW_SMALL_INT(self->lfs.cfg->block_count - n_used_blocks); // f_bfree
t->items[4] = t->items[3]; // f_bavail
t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // f_files
t->items[6] = MP_OBJ_NEW_SMALL_INT(0); // f_ffree
t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // f_favail
t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // f_flags
t->items[9] = MP_OBJ_NEW_SMALL_INT(LFSx_MACRO(_NAME_MAX)); // f_namemax
return MP_OBJ_FROM_PTR(t);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(statvfs_obj), MP_VFS_LFSx(statvfs));
STATIC mp_obj_t MP_VFS_LFSx(mount)(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs) {
(void)self_in;
(void)readonly;
(void)mkfs;
// already called LFSx_API(mount) in MP_VFS_LFSx(make_new)
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(MP_VFS_LFSx(mount_obj), MP_VFS_LFSx(mount));
STATIC mp_obj_t MP_VFS_LFSx(umount)(mp_obj_t self_in) {
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
// LFS unmount never fails
LFSx_API(unmount)(&self->lfs);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(MP_VFS_LFSx(umount_obj), MP_VFS_LFSx(umount));
STATIC const mp_rom_map_elem_t MP_VFS_LFSx(locals_dict_table)[] = {
{ MP_ROM_QSTR(MP_QSTR_mkfs), MP_ROM_PTR(&MP_VFS_LFSx(mkfs_obj)) },
{ MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&MP_VFS_LFSx(open_obj)) },
{ MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&MP_VFS_LFSx(ilistdir_obj)) },
{ MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&MP_VFS_LFSx(mkdir_obj)) },
{ MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&MP_VFS_LFSx(rmdir_obj)) },
{ MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&MP_VFS_LFSx(chdir_obj)) },
{ MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&MP_VFS_LFSx(getcwd_obj)) },
{ MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&MP_VFS_LFSx(remove_obj)) },
{ MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&MP_VFS_LFSx(rename_obj)) },
{ MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&MP_VFS_LFSx(stat_obj)) },
{ MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&MP_VFS_LFSx(statvfs_obj)) },
{ MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&MP_VFS_LFSx(mount_obj)) },
{ MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&MP_VFS_LFSx(umount_obj)) },
};
STATIC MP_DEFINE_CONST_DICT(MP_VFS_LFSx(locals_dict), MP_VFS_LFSx(locals_dict_table));
STATIC mp_import_stat_t MP_VFS_LFSx(import_stat)(void *self_in, const char *path) {
MP_OBJ_VFS_LFSx *self = self_in;
struct LFSx_API(info) info;
int ret = LFSx_API(stat)(&self->lfs, path, &info);
if (ret == 0) {
if (info.type == LFSx_MACRO(_TYPE_REG)) {
return MP_IMPORT_STAT_FILE;
} else {
return MP_IMPORT_STAT_DIR;
}
}
return MP_IMPORT_STAT_NO_EXIST;
}
STATIC const mp_vfs_proto_t MP_VFS_LFSx(proto) = {
.import_stat = MP_VFS_LFSx(import_stat),
};
const mp_obj_type_t MP_TYPE_VFS_LFSx = {
{ &mp_type_type },
#if LFS_BUILD_VERSION == 1
.name = MP_QSTR_VfsLfs1,
#else
.name = MP_QSTR_VfsLfs2,
#endif
.make_new = MP_VFS_LFSx(make_new),
.protocol = &MP_VFS_LFSx(proto),
.locals_dict = (mp_obj_dict_t*)&MP_VFS_LFSx(locals_dict),
};

236
extmod/vfs_lfsx_file.c Normal file
View File

@ -0,0 +1,236 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdio.h>
#include <string.h>
#include "py/runtime.h"
#include "py/stream.h"
#include "py/mperrno.h"
#include "extmod/vfs.h"
STATIC void MP_VFS_LFSx(check_open)(MP_OBJ_VFS_LFSx_FILE *self) {
if (self->vfs == NULL) {
mp_raise_ValueError(NULL);
}
}
STATIC void MP_VFS_LFSx(file_print)(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)self_in;
(void)kind;
mp_printf(print, "<io.%s>", mp_obj_get_type_str(self_in));
}
mp_obj_t MP_VFS_LFSx(file_open)(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in) {
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
int flags = 0;
const mp_obj_type_t *type = &MP_TYPE_VFS_LFSx_(_textio);
const char *mode_str = mp_obj_str_get_str(mode_in);
for (; *mode_str; ++mode_str) {
int new_flags = 0;
switch (*mode_str) {
case 'r':
new_flags = LFSx_MACRO(_O_RDONLY);
break;
case 'w':
new_flags = LFSx_MACRO(_O_WRONLY) | LFSx_MACRO(_O_CREAT) | LFSx_MACRO(_O_TRUNC);
break;
case 'x':
new_flags = LFSx_MACRO(_O_WRONLY) | LFSx_MACRO(_O_CREAT) | LFSx_MACRO(_O_EXCL);
break;
case 'a':
new_flags = LFSx_MACRO(_O_WRONLY) | LFSx_MACRO(_O_CREAT) | LFSx_MACRO(_O_APPEND);
break;
case '+':
flags |= LFSx_MACRO(_O_RDWR);
break;
#if MICROPY_PY_IO_FILEIO
case 'b':
type = &MP_TYPE_VFS_LFSx_(_fileio);
break;
#endif
case 't':
type = &MP_TYPE_VFS_LFSx_(_textio);
break;
}
if (new_flags) {
if (flags) {
mp_raise_ValueError(NULL);
}
flags = new_flags;
}
}
if (flags == 0) {
flags = LFSx_MACRO(_O_RDONLY);
}
#if LFS_BUILD_VERSION == 1
MP_OBJ_VFS_LFSx_FILE *o = m_new_obj_var_with_finaliser(MP_OBJ_VFS_LFSx_FILE, uint8_t, self->lfs.cfg->prog_size);
#else
MP_OBJ_VFS_LFSx_FILE *o = m_new_obj_var_with_finaliser(MP_OBJ_VFS_LFSx_FILE, uint8_t, self->lfs.cfg->cache_size);
#endif
o->base.type = type;
o->vfs = self;
#if !MICROPY_GC_CONSERVATIVE_CLEAR
memset(&o->file, 0, sizeof(o->file));
memset(&o->cfg, 0, sizeof(o->cfg));
#endif
o->cfg.buffer = &o->file_buffer[0];
const char *path = MP_VFS_LFSx(make_path)(self, path_in);
int ret = LFSx_API(file_opencfg)(&self->lfs, &o->file, path, flags, &o->cfg);
if (ret < 0) {
o->vfs = NULL;
mp_raise_OSError(-ret);
}
return MP_OBJ_FROM_PTR(o);
}
STATIC mp_obj_t MP_VFS_LFSx(file___exit__)(size_t n_args, const mp_obj_t *args) {
(void)n_args;
return mp_stream_close(args[0]);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(MP_VFS_LFSx(file___exit___obj), 4, 4, MP_VFS_LFSx(file___exit__));
STATIC mp_uint_t MP_VFS_LFSx(file_read)(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) {
MP_OBJ_VFS_LFSx_FILE *self = MP_OBJ_TO_PTR(self_in);
MP_VFS_LFSx(check_open)(self);
LFSx_API(ssize_t) sz = LFSx_API(file_read)(&self->vfs->lfs, &self->file, buf, size);
if (sz < 0) {
*errcode = -sz;
return MP_STREAM_ERROR;
}
return sz;
}
STATIC mp_uint_t MP_VFS_LFSx(file_write)(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) {
MP_OBJ_VFS_LFSx_FILE *self = MP_OBJ_TO_PTR(self_in);
MP_VFS_LFSx(check_open)(self);
LFSx_API(ssize_t) sz = LFSx_API(file_write)(&self->vfs->lfs, &self->file, buf, size);
if (sz < 0) {
*errcode = -sz;
return MP_STREAM_ERROR;
}
return sz;
}
STATIC mp_uint_t MP_VFS_LFSx(file_ioctl)(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) {
MP_OBJ_VFS_LFSx_FILE *self = MP_OBJ_TO_PTR(self_in);
if (request != MP_STREAM_CLOSE) {
MP_VFS_LFSx(check_open)(self);
}
if (request == MP_STREAM_SEEK) {
struct mp_stream_seek_t *s = (struct mp_stream_seek_t*)(uintptr_t)arg;
int res = LFSx_API(file_seek)(&self->vfs->lfs, &self->file, s->offset, s->whence);
if (res < 0) {
*errcode = -res;
return MP_STREAM_ERROR;
}
res = LFSx_API(file_tell)(&self->vfs->lfs, &self->file);
if (res < 0) {
*errcode = -res;
return MP_STREAM_ERROR;
}
s->offset = res;
return 0;
} else if (request == MP_STREAM_FLUSH) {
int res = LFSx_API(file_sync)(&self->vfs->lfs, &self->file);
if (res < 0) {
*errcode = -res;
return MP_STREAM_ERROR;
}
return 0;
} else if (request == MP_STREAM_CLOSE) {
if (self->vfs == NULL) {
return 0;
}
int res = LFSx_API(file_close)(&self->vfs->lfs, &self->file);
self->vfs = NULL; // indicate a closed file
if (res < 0) {
*errcode = -res;
return MP_STREAM_ERROR;
}
return 0;
} else {
*errcode = MP_EINVAL;
return MP_STREAM_ERROR;
}
}
STATIC const mp_rom_map_elem_t MP_VFS_LFSx(file_locals_dict_table)[] = {
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) },
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
{ MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) },
{ MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj) },
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
{ MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) },
{ MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) },
{ MP_ROM_QSTR(MP_QSTR_seek), MP_ROM_PTR(&mp_stream_seek_obj) },
{ MP_ROM_QSTR(MP_QSTR_tell), MP_ROM_PTR(&mp_stream_tell_obj) },
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_stream_close_obj) },
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) },
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&MP_VFS_LFSx(file___exit___obj)) },
};
STATIC MP_DEFINE_CONST_DICT(MP_VFS_LFSx(file_locals_dict), MP_VFS_LFSx(file_locals_dict_table));
#if MICROPY_PY_IO_FILEIO
STATIC const mp_stream_p_t MP_VFS_LFSx(fileio_stream_p) = {
.read = MP_VFS_LFSx(file_read),
.write = MP_VFS_LFSx(file_write),
.ioctl = MP_VFS_LFSx(file_ioctl),
};
const mp_obj_type_t MP_TYPE_VFS_LFSx_(_fileio) = {
{ &mp_type_type },
.name = MP_QSTR_FileIO,
.print = MP_VFS_LFSx(file_print),
.getiter = mp_identity_getiter,
.iternext = mp_stream_unbuffered_iter,
.protocol = &MP_VFS_LFSx(fileio_stream_p),
.locals_dict = (mp_obj_dict_t*)&MP_VFS_LFSx(file_locals_dict),
};
#endif
STATIC const mp_stream_p_t MP_VFS_LFSx(textio_stream_p) = {
.read = MP_VFS_LFSx(file_read),
.write = MP_VFS_LFSx(file_write),
.ioctl = MP_VFS_LFSx(file_ioctl),
.is_text = true,
};
const mp_obj_type_t MP_TYPE_VFS_LFSx_(_textio) = {
{ &mp_type_type },
.name = MP_QSTR_TextIOWrapper,
.print = MP_VFS_LFSx(file_print),
.getiter = mp_identity_getiter,
.iternext = mp_stream_unbuffered_iter,
.protocol = &MP_VFS_LFSx(textio_stream_p),
.locals_dict = (mp_obj_dict_t*)&MP_VFS_LFSx(file_locals_dict),
};

View File

@ -217,3 +217,19 @@ char *strstr(const char *haystack, const char *needle)
return (char *) haystack; return (char *) haystack;
return 0; return 0;
} }
size_t strspn(const char *s, const char *accept) {
const char *ss = s;
while (*s && strchr(accept, *s) != NULL) {
++s;
}
return s - ss;
}
size_t strcspn(const char *s, const char *reject) {
const char *ss = s;
while (*s && strchr(reject, *s) == NULL) {
++s;
}
return s - ss;
}

View File

@ -0,0 +1,10 @@
// an implementation of sqrt for Thumb using hardware double-precision VFP instructions
double sqrt(double x) {
double ret;
asm volatile (
"vsqrt.f64 %P0, %P1\n"
: "=w" (ret)
: "w" (x));
return ret;
}

19
lib/littlefs/README.md Normal file
View File

@ -0,0 +1,19 @@
littlefs library
================
The upstream source for the files in this directory is
https://github.com/ARMmbed/littlefs
To generate the separate files with lfs1 and lfs2 prefixes run the following
commands in the top-level directory of the littlefs repository (replace the
version tags with the latest/desired ones, and set `$MPY_DIR`):
git checkout v1.7.2
python2 ./scripts/prefix.py lfs1
cp lfs1*.[ch] $MPY_DIR/lib/littlefs
git reset --hard HEAD
git checkout v2.1.3
python2 ./scripts/prefix.py lfs2
cp lfs2*.[ch] $MPY_DIR/lib/littlefs
git reset --hard HEAD

2583
lib/littlefs/lfs1.c Normal file

File diff suppressed because it is too large Load Diff

501
lib/littlefs/lfs1.h Normal file
View File

@ -0,0 +1,501 @@
/*
* The little filesystem
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef LFS1_H
#define LFS1_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C"
{
#endif
/// Version info ///
// Software library version
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
#define LFS1_VERSION 0x00010007
#define LFS1_VERSION_MAJOR (0xffff & (LFS1_VERSION >> 16))
#define LFS1_VERSION_MINOR (0xffff & (LFS1_VERSION >> 0))
// Version of On-disk data structures
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
#define LFS1_DISK_VERSION 0x00010001
#define LFS1_DISK_VERSION_MAJOR (0xffff & (LFS1_DISK_VERSION >> 16))
#define LFS1_DISK_VERSION_MINOR (0xffff & (LFS1_DISK_VERSION >> 0))
/// Definitions ///
// Type definitions
typedef uint32_t lfs1_size_t;
typedef uint32_t lfs1_off_t;
typedef int32_t lfs1_ssize_t;
typedef int32_t lfs1_soff_t;
typedef uint32_t lfs1_block_t;
// Max name size in bytes
#ifndef LFS1_NAME_MAX
#define LFS1_NAME_MAX 255
#endif
// Max file size in bytes
#ifndef LFS1_FILE_MAX
#define LFS1_FILE_MAX 2147483647
#endif
// Possible error codes, these are negative to allow
// valid positive return values
enum lfs1_error {
LFS1_ERR_OK = 0, // No error
LFS1_ERR_IO = -5, // Error during device operation
LFS1_ERR_CORRUPT = -52, // Corrupted
LFS1_ERR_NOENT = -2, // No directory entry
LFS1_ERR_EXIST = -17, // Entry already exists
LFS1_ERR_NOTDIR = -20, // Entry is not a dir
LFS1_ERR_ISDIR = -21, // Entry is a dir
LFS1_ERR_NOTEMPTY = -39, // Dir is not empty
LFS1_ERR_BADF = -9, // Bad file number
LFS1_ERR_FBIG = -27, // File too large
LFS1_ERR_INVAL = -22, // Invalid parameter
LFS1_ERR_NOSPC = -28, // No space left on device
LFS1_ERR_NOMEM = -12, // No more memory available
};
// File types
enum lfs1_type {
LFS1_TYPE_REG = 0x11,
LFS1_TYPE_DIR = 0x22,
LFS1_TYPE_SUPERBLOCK = 0x2e,
};
// File open flags
enum lfs1_open_flags {
// open flags
LFS1_O_RDONLY = 1, // Open a file as read only
LFS1_O_WRONLY = 2, // Open a file as write only
LFS1_O_RDWR = 3, // Open a file as read and write
LFS1_O_CREAT = 0x0100, // Create a file if it does not exist
LFS1_O_EXCL = 0x0200, // Fail if a file already exists
LFS1_O_TRUNC = 0x0400, // Truncate the existing file to zero size
LFS1_O_APPEND = 0x0800, // Move to end of file on every write
// internally used flags
LFS1_F_DIRTY = 0x10000, // File does not match storage
LFS1_F_WRITING = 0x20000, // File has been written since last flush
LFS1_F_READING = 0x40000, // File has been read since last flush
LFS1_F_ERRED = 0x80000, // An error occured during write
};
// File seek flags
enum lfs1_whence_flags {
LFS1_SEEK_SET = 0, // Seek relative to an absolute position
LFS1_SEEK_CUR = 1, // Seek relative to the current file position
LFS1_SEEK_END = 2, // Seek relative to the end of the file
};
// Configuration provided during initialization of the littlefs
struct lfs1_config {
// Opaque user provided context that can be used to pass
// information to the block device operations
void *context;
// Read a region in a block. Negative error codes are propogated
// to the user.
int (*read)(const struct lfs1_config *c, lfs1_block_t block,
lfs1_off_t off, void *buffer, lfs1_size_t size);
// Program a region in a block. The block must have previously
// been erased. Negative error codes are propogated to the user.
// May return LFS1_ERR_CORRUPT if the block should be considered bad.
int (*prog)(const struct lfs1_config *c, lfs1_block_t block,
lfs1_off_t off, const void *buffer, lfs1_size_t size);
// Erase a block. A block must be erased before being programmed.
// The state of an erased block is undefined. Negative error codes
// are propogated to the user.
// May return LFS1_ERR_CORRUPT if the block should be considered bad.
int (*erase)(const struct lfs1_config *c, lfs1_block_t block);
// Sync the state of the underlying block device. Negative error codes
// are propogated to the user.
int (*sync)(const struct lfs1_config *c);
// Minimum size of a block read. This determines the size of read buffers.
// This may be larger than the physical read size to improve performance
// by caching more of the block device.
lfs1_size_t read_size;
// Minimum size of a block program. This determines the size of program
// buffers. This may be larger than the physical program size to improve
// performance by caching more of the block device.
// Must be a multiple of the read size.
lfs1_size_t prog_size;
// Size of an erasable block. This does not impact ram consumption and
// may be larger than the physical erase size. However, this should be
// kept small as each file currently takes up an entire block.
// Must be a multiple of the program size.
lfs1_size_t block_size;
// Number of erasable blocks on the device.
lfs1_size_t block_count;
// Number of blocks to lookahead during block allocation. A larger
// lookahead reduces the number of passes required to allocate a block.
// The lookahead buffer requires only 1 bit per block so it can be quite
// large with little ram impact. Should be a multiple of 32.
lfs1_size_t lookahead;
// Optional, statically allocated read buffer. Must be read sized.
void *read_buffer;
// Optional, statically allocated program buffer. Must be program sized.
void *prog_buffer;
// Optional, statically allocated lookahead buffer. Must be 1 bit per
// lookahead block.
void *lookahead_buffer;
// Optional, statically allocated buffer for files. Must be program sized.
// If enabled, only one file may be opened at a time.
void *file_buffer;
};
// Optional configuration provided during lfs1_file_opencfg
struct lfs1_file_config {
// Optional, statically allocated buffer for files. Must be program sized.
// If NULL, malloc will be used by default.
void *buffer;
};
// File info structure
struct lfs1_info {
// Type of the file, either LFS1_TYPE_REG or LFS1_TYPE_DIR
uint8_t type;
// Size of the file, only valid for REG files
lfs1_size_t size;
// Name of the file stored as a null-terminated string
char name[LFS1_NAME_MAX+1];
};
/// littlefs data structures ///
typedef struct lfs1_entry {
lfs1_off_t off;
struct lfs1_disk_entry {
uint8_t type;
uint8_t elen;
uint8_t alen;
uint8_t nlen;
union {
struct {
lfs1_block_t head;
lfs1_size_t size;
} file;
lfs1_block_t dir[2];
} u;
} d;
} lfs1_entry_t;
typedef struct lfs1_cache {
lfs1_block_t block;
lfs1_off_t off;
uint8_t *buffer;
} lfs1_cache_t;
typedef struct lfs1_file {
struct lfs1_file *next;
lfs1_block_t pair[2];
lfs1_off_t poff;
lfs1_block_t head;
lfs1_size_t size;
const struct lfs1_file_config *cfg;
uint32_t flags;
lfs1_off_t pos;
lfs1_block_t block;
lfs1_off_t off;
lfs1_cache_t cache;
} lfs1_file_t;
typedef struct lfs1_dir {
struct lfs1_dir *next;
lfs1_block_t pair[2];
lfs1_off_t off;
lfs1_block_t head[2];
lfs1_off_t pos;
struct lfs1_disk_dir {
uint32_t rev;
lfs1_size_t size;
lfs1_block_t tail[2];
} d;
} lfs1_dir_t;
typedef struct lfs1_superblock {
lfs1_off_t off;
struct lfs1_disk_superblock {
uint8_t type;
uint8_t elen;
uint8_t alen;
uint8_t nlen;
lfs1_block_t root[2];
uint32_t block_size;
uint32_t block_count;
uint32_t version;
char magic[8];
} d;
} lfs1_superblock_t;
typedef struct lfs1_free {
lfs1_block_t off;
lfs1_block_t size;
lfs1_block_t i;
lfs1_block_t ack;
uint32_t *buffer;
} lfs1_free_t;
// The littlefs type
typedef struct lfs1 {
const struct lfs1_config *cfg;
lfs1_block_t root[2];
lfs1_file_t *files;
lfs1_dir_t *dirs;
lfs1_cache_t rcache;
lfs1_cache_t pcache;
lfs1_free_t free;
bool deorphaned;
bool moving;
} lfs1_t;
/// Filesystem functions ///
// Format a block device with the littlefs
//
// Requires a littlefs object and config struct. This clobbers the littlefs
// object, and does not leave the filesystem mounted. The config struct must
// be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs1_format(lfs1_t *lfs1, const struct lfs1_config *config);
// Mounts a littlefs
//
// Requires a littlefs object and config struct. Multiple filesystems
// may be mounted simultaneously with multiple littlefs objects. Both
// lfs1 and config must be allocated while mounted. The config struct must
// be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs1_mount(lfs1_t *lfs1, const struct lfs1_config *config);
// Unmounts a littlefs
//
// Does nothing besides releasing any allocated resources.
// Returns a negative error code on failure.
int lfs1_unmount(lfs1_t *lfs1);
/// General operations ///
// Removes a file or directory
//
// If removing a directory, the directory must be empty.
// Returns a negative error code on failure.
int lfs1_remove(lfs1_t *lfs1, const char *path);
// Rename or move a file or directory
//
// If the destination exists, it must match the source in type.
// If the destination is a directory, the directory must be empty.
//
// Returns a negative error code on failure.
int lfs1_rename(lfs1_t *lfs1, const char *oldpath, const char *newpath);
// Find info about a file or directory
//
// Fills out the info structure, based on the specified file or directory.
// Returns a negative error code on failure.
int lfs1_stat(lfs1_t *lfs1, const char *path, struct lfs1_info *info);
/// File operations ///
// Open a file
//
// The mode that the file is opened in is determined by the flags, which
// are values from the enum lfs1_open_flags that are bitwise-ored together.
//
// Returns a negative error code on failure.
int lfs1_file_open(lfs1_t *lfs1, lfs1_file_t *file,
const char *path, int flags);
// Open a file with extra configuration
//
// The mode that the file is opened in is determined by the flags, which
// are values from the enum lfs1_open_flags that are bitwise-ored together.
//
// The config struct provides additional config options per file as described
// above. The config struct must be allocated while the file is open, and the
// config struct must be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs1_file_opencfg(lfs1_t *lfs1, lfs1_file_t *file,
const char *path, int flags,
const struct lfs1_file_config *config);
// Close a file
//
// Any pending writes are written out to storage as though
// sync had been called and releases any allocated resources.
//
// Returns a negative error code on failure.
int lfs1_file_close(lfs1_t *lfs1, lfs1_file_t *file);
// Synchronize a file on storage
//
// Any pending writes are written out to storage.
// Returns a negative error code on failure.
int lfs1_file_sync(lfs1_t *lfs1, lfs1_file_t *file);
// Read data from file
//
// Takes a buffer and size indicating where to store the read data.
// Returns the number of bytes read, or a negative error code on failure.
lfs1_ssize_t lfs1_file_read(lfs1_t *lfs1, lfs1_file_t *file,
void *buffer, lfs1_size_t size);
// Write data to file
//
// Takes a buffer and size indicating the data to write. The file will not
// actually be updated on the storage until either sync or close is called.
//
// Returns the number of bytes written, or a negative error code on failure.
lfs1_ssize_t lfs1_file_write(lfs1_t *lfs1, lfs1_file_t *file,
const void *buffer, lfs1_size_t size);
// Change the position of the file
//
// The change in position is determined by the offset and whence flag.
// Returns the old position of the file, or a negative error code on failure.
lfs1_soff_t lfs1_file_seek(lfs1_t *lfs1, lfs1_file_t *file,
lfs1_soff_t off, int whence);
// Truncates the size of the file to the specified size
//
// Returns a negative error code on failure.
int lfs1_file_truncate(lfs1_t *lfs1, lfs1_file_t *file, lfs1_off_t size);
// Return the position of the file
//
// Equivalent to lfs1_file_seek(lfs1, file, 0, LFS1_SEEK_CUR)
// Returns the position of the file, or a negative error code on failure.
lfs1_soff_t lfs1_file_tell(lfs1_t *lfs1, lfs1_file_t *file);
// Change the position of the file to the beginning of the file
//
// Equivalent to lfs1_file_seek(lfs1, file, 0, LFS1_SEEK_CUR)
// Returns a negative error code on failure.
int lfs1_file_rewind(lfs1_t *lfs1, lfs1_file_t *file);
// Return the size of the file
//
// Similar to lfs1_file_seek(lfs1, file, 0, LFS1_SEEK_END)
// Returns the size of the file, or a negative error code on failure.
lfs1_soff_t lfs1_file_size(lfs1_t *lfs1, lfs1_file_t *file);
/// Directory operations ///
// Create a directory
//
// Returns a negative error code on failure.
int lfs1_mkdir(lfs1_t *lfs1, const char *path);
// Open a directory
//
// Once open a directory can be used with read to iterate over files.
// Returns a negative error code on failure.
int lfs1_dir_open(lfs1_t *lfs1, lfs1_dir_t *dir, const char *path);
// Close a directory
//
// Releases any allocated resources.
// Returns a negative error code on failure.
int lfs1_dir_close(lfs1_t *lfs1, lfs1_dir_t *dir);
// Read an entry in the directory
//
// Fills out the info structure, based on the specified file or directory.
// Returns a negative error code on failure.
int lfs1_dir_read(lfs1_t *lfs1, lfs1_dir_t *dir, struct lfs1_info *info);
// Change the position of the directory
//
// The new off must be a value previous returned from tell and specifies
// an absolute offset in the directory seek.
//
// Returns a negative error code on failure.
int lfs1_dir_seek(lfs1_t *lfs1, lfs1_dir_t *dir, lfs1_off_t off);
// Return the position of the directory
//
// The returned offset is only meant to be consumed by seek and may not make
// sense, but does indicate the current position in the directory iteration.
//
// Returns the position of the directory, or a negative error code on failure.
lfs1_soff_t lfs1_dir_tell(lfs1_t *lfs1, lfs1_dir_t *dir);
// Change the position of the directory to the beginning of the directory
//
// Returns a negative error code on failure.
int lfs1_dir_rewind(lfs1_t *lfs1, lfs1_dir_t *dir);
/// Miscellaneous littlefs specific operations ///
// Traverse through all blocks in use by the filesystem
//
// The provided callback will be called with each block address that is
// currently in use by the filesystem. This can be used to determine which
// blocks are in use or how much of the storage is available.
//
// Returns a negative error code on failure.
int lfs1_traverse(lfs1_t *lfs1, int (*cb)(void*, lfs1_block_t), void *data);
// Prunes any recoverable errors that may have occured in the filesystem
//
// Not needed to be called by user unless an operation is interrupted
// but the filesystem is still mounted. This is already called on first
// allocation.
//
// Returns a negative error code on failure.
int lfs1_deorphan(lfs1_t *lfs1);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

31
lib/littlefs/lfs1_util.c Normal file
View File

@ -0,0 +1,31 @@
/*
* lfs1 util functions
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "lfs1_util.h"
// Only compile if user does not provide custom config
#ifndef LFS1_CONFIG
// Software CRC implementation with small lookup table
void lfs1_crc(uint32_t *restrict crc, const void *buffer, size_t size) {
static const uint32_t rtable[16] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
};
const uint8_t *data = buffer;
for (size_t i = 0; i < size; i++) {
*crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 0)) & 0xf];
*crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 4)) & 0xf];
}
}
#endif

186
lib/littlefs/lfs1_util.h Normal file
View File

@ -0,0 +1,186 @@
/*
* lfs1 utility functions
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef LFS1_UTIL_H
#define LFS1_UTIL_H
// Users can override lfs1_util.h with their own configuration by defining
// LFS1_CONFIG as a header file to include (-DLFS1_CONFIG=lfs1_config.h).
//
// If LFS1_CONFIG is used, none of the default utils will be emitted and must be
// provided by the config file. To start I would suggest copying lfs1_util.h and
// modifying as needed.
#ifdef LFS1_CONFIG
#define LFS1_STRINGIZE(x) LFS1_STRINGIZE2(x)
#define LFS1_STRINGIZE2(x) #x
#include LFS1_STRINGIZE(LFS1_CONFIG)
#else
// System includes
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#ifndef LFS1_NO_MALLOC
#include <stdlib.h>
#endif
#ifndef LFS1_NO_ASSERT
#include <assert.h>
#endif
#if !defined(LFS1_NO_DEBUG) || !defined(LFS1_NO_WARN) || !defined(LFS1_NO_ERROR)
#include <stdio.h>
#endif
#ifdef __cplusplus
extern "C"
{
#endif
// Macros, may be replaced by system specific wrappers. Arguments to these
// macros must not have side-effects as the macros can be removed for a smaller
// code footprint
// Logging functions
#ifndef LFS1_NO_DEBUG
#define LFS1_DEBUG(fmt, ...) \
printf("lfs1 debug:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#else
#define LFS1_DEBUG(fmt, ...)
#endif
#ifndef LFS1_NO_WARN
#define LFS1_WARN(fmt, ...) \
printf("lfs1 warn:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#else
#define LFS1_WARN(fmt, ...)
#endif
#ifndef LFS1_NO_ERROR
#define LFS1_ERROR(fmt, ...) \
printf("lfs1 error:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#else
#define LFS1_ERROR(fmt, ...)
#endif
// Runtime assertions
#ifndef LFS1_NO_ASSERT
#define LFS1_ASSERT(test) assert(test)
#else
#define LFS1_ASSERT(test)
#endif
// Builtin functions, these may be replaced by more efficient
// toolchain-specific implementations. LFS1_NO_INTRINSICS falls back to a more
// expensive basic C implementation for debugging purposes
// Min/max functions for unsigned 32-bit numbers
static inline uint32_t lfs1_max(uint32_t a, uint32_t b) {
return (a > b) ? a : b;
}
static inline uint32_t lfs1_min(uint32_t a, uint32_t b) {
return (a < b) ? a : b;
}
// Find the next smallest power of 2 less than or equal to a
static inline uint32_t lfs1_npw2(uint32_t a) {
#if !defined(LFS1_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
return 32 - __builtin_clz(a-1);
#else
uint32_t r = 0;
uint32_t s;
a -= 1;
s = (a > 0xffff) << 4; a >>= s; r |= s;
s = (a > 0xff ) << 3; a >>= s; r |= s;
s = (a > 0xf ) << 2; a >>= s; r |= s;
s = (a > 0x3 ) << 1; a >>= s; r |= s;
return (r | (a >> 1)) + 1;
#endif
}
// Count the number of trailing binary zeros in a
// lfs1_ctz(0) may be undefined
static inline uint32_t lfs1_ctz(uint32_t a) {
#if !defined(LFS1_NO_INTRINSICS) && defined(__GNUC__)
return __builtin_ctz(a);
#else
return lfs1_npw2((a & -a) + 1) - 1;
#endif
}
// Count the number of binary ones in a
static inline uint32_t lfs1_popc(uint32_t a) {
#if !defined(LFS1_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
return __builtin_popcount(a);
#else
a = a - ((a >> 1) & 0x55555555);
a = (a & 0x33333333) + ((a >> 2) & 0x33333333);
return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24;
#endif
}
// Find the sequence comparison of a and b, this is the distance
// between a and b ignoring overflow
static inline int lfs1_scmp(uint32_t a, uint32_t b) {
return (int)(unsigned)(a - b);
}
// Convert from 32-bit little-endian to native order
static inline uint32_t lfs1_fromle32(uint32_t a) {
#if !defined(LFS1_NO_INTRINSICS) && ( \
(defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
return a;
#elif !defined(LFS1_NO_INTRINSICS) && ( \
(defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
return __builtin_bswap32(a);
#else
return (((uint8_t*)&a)[0] << 0) |
(((uint8_t*)&a)[1] << 8) |
(((uint8_t*)&a)[2] << 16) |
(((uint8_t*)&a)[3] << 24);
#endif
}
// Convert to 32-bit little-endian from native order
static inline uint32_t lfs1_tole32(uint32_t a) {
return lfs1_fromle32(a);
}
// Calculate CRC-32 with polynomial = 0x04c11db7
void lfs1_crc(uint32_t *crc, const void *buffer, size_t size);
// Allocate memory, only used if buffers are not provided to littlefs
static inline void *lfs1_malloc(size_t size) {
#ifndef LFS1_NO_MALLOC
return malloc(size);
#else
(void)size;
return NULL;
#endif
}
// Deallocate memory, only used if buffers are not provided to littlefs
static inline void lfs1_free(void *p) {
#ifndef LFS1_NO_MALLOC
free(p);
#else
(void)p;
#endif
}
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif
#endif

4742
lib/littlefs/lfs2.c Normal file

File diff suppressed because it is too large Load Diff

651
lib/littlefs/lfs2.h Normal file
View File

@ -0,0 +1,651 @@
/*
* The little filesystem
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef LFS2_H
#define LFS2_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C"
{
#endif
/// Version info ///
// Software library version
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
#define LFS2_VERSION 0x00020001
#define LFS2_VERSION_MAJOR (0xffff & (LFS2_VERSION >> 16))
#define LFS2_VERSION_MINOR (0xffff & (LFS2_VERSION >> 0))
// Version of On-disk data structures
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
#define LFS2_DISK_VERSION 0x00020000
#define LFS2_DISK_VERSION_MAJOR (0xffff & (LFS2_DISK_VERSION >> 16))
#define LFS2_DISK_VERSION_MINOR (0xffff & (LFS2_DISK_VERSION >> 0))
/// Definitions ///
// Type definitions
typedef uint32_t lfs2_size_t;
typedef uint32_t lfs2_off_t;
typedef int32_t lfs2_ssize_t;
typedef int32_t lfs2_soff_t;
typedef uint32_t lfs2_block_t;
// Maximum name size in bytes, may be redefined to reduce the size of the
// info struct. Limited to <= 1022. Stored in superblock and must be
// respected by other littlefs drivers.
#ifndef LFS2_NAME_MAX
#define LFS2_NAME_MAX 255
#endif
// Maximum size of a file in bytes, may be redefined to limit to support other
// drivers. Limited on disk to <= 4294967296. However, above 2147483647 the
// functions lfs2_file_seek, lfs2_file_size, and lfs2_file_tell will return
// incorrect values due to using signed integers. Stored in superblock and
// must be respected by other littlefs drivers.
#ifndef LFS2_FILE_MAX
#define LFS2_FILE_MAX 2147483647
#endif
// Maximum size of custom attributes in bytes, may be redefined, but there is
// no real benefit to using a smaller LFS2_ATTR_MAX. Limited to <= 1022.
#ifndef LFS2_ATTR_MAX
#define LFS2_ATTR_MAX 1022
#endif
// Possible error codes, these are negative to allow
// valid positive return values
enum lfs2_error {
LFS2_ERR_OK = 0, // No error
LFS2_ERR_IO = -5, // Error during device operation
LFS2_ERR_CORRUPT = -84, // Corrupted
LFS2_ERR_NOENT = -2, // No directory entry
LFS2_ERR_EXIST = -17, // Entry already exists
LFS2_ERR_NOTDIR = -20, // Entry is not a dir
LFS2_ERR_ISDIR = -21, // Entry is a dir
LFS2_ERR_NOTEMPTY = -39, // Dir is not empty
LFS2_ERR_BADF = -9, // Bad file number
LFS2_ERR_FBIG = -27, // File too large
LFS2_ERR_INVAL = -22, // Invalid parameter
LFS2_ERR_NOSPC = -28, // No space left on device
LFS2_ERR_NOMEM = -12, // No more memory available
LFS2_ERR_NOATTR = -61, // No data/attr available
LFS2_ERR_NAMETOOLONG = -36, // File name too long
};
// File types
enum lfs2_type {
// file types
LFS2_TYPE_REG = 0x001,
LFS2_TYPE_DIR = 0x002,
// internally used types
LFS2_TYPE_SPLICE = 0x400,
LFS2_TYPE_NAME = 0x000,
LFS2_TYPE_STRUCT = 0x200,
LFS2_TYPE_USERATTR = 0x300,
LFS2_TYPE_FROM = 0x100,
LFS2_TYPE_TAIL = 0x600,
LFS2_TYPE_GLOBALS = 0x700,
LFS2_TYPE_CRC = 0x500,
// internally used type specializations
LFS2_TYPE_CREATE = 0x401,
LFS2_TYPE_DELETE = 0x4ff,
LFS2_TYPE_SUPERBLOCK = 0x0ff,
LFS2_TYPE_DIRSTRUCT = 0x200,
LFS2_TYPE_CTZSTRUCT = 0x202,
LFS2_TYPE_INLINESTRUCT = 0x201,
LFS2_TYPE_SOFTTAIL = 0x600,
LFS2_TYPE_HARDTAIL = 0x601,
LFS2_TYPE_MOVESTATE = 0x7ff,
// internal chip sources
LFS2_FROM_NOOP = 0x000,
LFS2_FROM_MOVE = 0x101,
LFS2_FROM_USERATTRS = 0x102,
};
// File open flags
enum lfs2_open_flags {
// open flags
LFS2_O_RDONLY = 1, // Open a file as read only
LFS2_O_WRONLY = 2, // Open a file as write only
LFS2_O_RDWR = 3, // Open a file as read and write
LFS2_O_CREAT = 0x0100, // Create a file if it does not exist
LFS2_O_EXCL = 0x0200, // Fail if a file already exists
LFS2_O_TRUNC = 0x0400, // Truncate the existing file to zero size
LFS2_O_APPEND = 0x0800, // Move to end of file on every write
// internally used flags
LFS2_F_DIRTY = 0x010000, // File does not match storage
LFS2_F_WRITING = 0x020000, // File has been written since last flush
LFS2_F_READING = 0x040000, // File has been read since last flush
LFS2_F_ERRED = 0x080000, // An error occured during write
LFS2_F_INLINE = 0x100000, // Currently inlined in directory entry
LFS2_F_OPENED = 0x200000, // File has been opened
};
// File seek flags
enum lfs2_whence_flags {
LFS2_SEEK_SET = 0, // Seek relative to an absolute position
LFS2_SEEK_CUR = 1, // Seek relative to the current file position
LFS2_SEEK_END = 2, // Seek relative to the end of the file
};
// Configuration provided during initialization of the littlefs
struct lfs2_config {
// Opaque user provided context that can be used to pass
// information to the block device operations
void *context;
// Read a region in a block. Negative error codes are propogated
// to the user.
int (*read)(const struct lfs2_config *c, lfs2_block_t block,
lfs2_off_t off, void *buffer, lfs2_size_t size);
// Program a region in a block. The block must have previously
// been erased. Negative error codes are propogated to the user.
// May return LFS2_ERR_CORRUPT if the block should be considered bad.
int (*prog)(const struct lfs2_config *c, lfs2_block_t block,
lfs2_off_t off, const void *buffer, lfs2_size_t size);
// Erase a block. A block must be erased before being programmed.
// The state of an erased block is undefined. Negative error codes
// are propogated to the user.
// May return LFS2_ERR_CORRUPT if the block should be considered bad.
int (*erase)(const struct lfs2_config *c, lfs2_block_t block);
// Sync the state of the underlying block device. Negative error codes
// are propogated to the user.
int (*sync)(const struct lfs2_config *c);
// Minimum size of a block read. All read operations will be a
// multiple of this value.
lfs2_size_t read_size;
// Minimum size of a block program. All program operations will be a
// multiple of this value.
lfs2_size_t prog_size;
// Size of an erasable block. This does not impact ram consumption and
// may be larger than the physical erase size. However, non-inlined files
// take up at minimum one block. Must be a multiple of the read
// and program sizes.
lfs2_size_t block_size;
// Number of erasable blocks on the device.
lfs2_size_t block_count;
// Number of erase cycles before littlefs evicts metadata logs and moves
// the metadata to another block. Suggested values are in the
// range 100-1000, with large values having better performance at the cost
// of less consistent wear distribution.
//
// Set to -1 to disable block-level wear-leveling.
int32_t block_cycles;
// Size of block caches. Each cache buffers a portion of a block in RAM.
// The littlefs needs a read cache, a program cache, and one additional
// cache per file. Larger caches can improve performance by storing more
// data and reducing the number of disk accesses. Must be a multiple of
// the read and program sizes, and a factor of the block size.
lfs2_size_t cache_size;
// Size of the lookahead buffer in bytes. A larger lookahead buffer
// increases the number of blocks found during an allocation pass. The
// lookahead buffer is stored as a compact bitmap, so each byte of RAM
// can track 8 blocks. Must be a multiple of 8.
lfs2_size_t lookahead_size;
// Optional statically allocated read buffer. Must be cache_size.
// By default lfs2_malloc is used to allocate this buffer.
void *read_buffer;
// Optional statically allocated program buffer. Must be cache_size.
// By default lfs2_malloc is used to allocate this buffer.
void *prog_buffer;
// Optional statically allocated lookahead buffer. Must be lookahead_size
// and aligned to a 32-bit boundary. By default lfs2_malloc is used to
// allocate this buffer.
void *lookahead_buffer;
// Optional upper limit on length of file names in bytes. No downside for
// larger names except the size of the info struct which is controlled by
// the LFS2_NAME_MAX define. Defaults to LFS2_NAME_MAX when zero. Stored in
// superblock and must be respected by other littlefs drivers.
lfs2_size_t name_max;
// Optional upper limit on files in bytes. No downside for larger files
// but must be <= LFS2_FILE_MAX. Defaults to LFS2_FILE_MAX when zero. Stored
// in superblock and must be respected by other littlefs drivers.
lfs2_size_t file_max;
// Optional upper limit on custom attributes in bytes. No downside for
// larger attributes size but must be <= LFS2_ATTR_MAX. Defaults to
// LFS2_ATTR_MAX when zero.
lfs2_size_t attr_max;
};
// File info structure
struct lfs2_info {
// Type of the file, either LFS2_TYPE_REG or LFS2_TYPE_DIR
uint8_t type;
// Size of the file, only valid for REG files. Limited to 32-bits.
lfs2_size_t size;
// Name of the file stored as a null-terminated string. Limited to
// LFS2_NAME_MAX+1, which can be changed by redefining LFS2_NAME_MAX to
// reduce RAM. LFS2_NAME_MAX is stored in superblock and must be
// respected by other littlefs drivers.
char name[LFS2_NAME_MAX+1];
};
// Custom attribute structure, used to describe custom attributes
// committed atomically during file writes.
struct lfs2_attr {
// 8-bit type of attribute, provided by user and used to
// identify the attribute
uint8_t type;
// Pointer to buffer containing the attribute
void *buffer;
// Size of attribute in bytes, limited to LFS2_ATTR_MAX
lfs2_size_t size;
};
// Optional configuration provided during lfs2_file_opencfg
struct lfs2_file_config {
// Optional statically allocated file buffer. Must be cache_size.
// By default lfs2_malloc is used to allocate this buffer.
void *buffer;
// Optional list of custom attributes related to the file. If the file
// is opened with read access, these attributes will be read from disk
// during the open call. If the file is opened with write access, the
// attributes will be written to disk every file sync or close. This
// write occurs atomically with update to the file's contents.
//
// Custom attributes are uniquely identified by an 8-bit type and limited
// to LFS2_ATTR_MAX bytes. When read, if the stored attribute is smaller
// than the buffer, it will be padded with zeros. If the stored attribute
// is larger, then it will be silently truncated. If the attribute is not
// found, it will be created implicitly.
struct lfs2_attr *attrs;
// Number of custom attributes in the list
lfs2_size_t attr_count;
};
/// internal littlefs data structures ///
typedef struct lfs2_cache {
lfs2_block_t block;
lfs2_off_t off;
lfs2_size_t size;
uint8_t *buffer;
} lfs2_cache_t;
typedef struct lfs2_mdir {
lfs2_block_t pair[2];
uint32_t rev;
lfs2_off_t off;
uint32_t etag;
uint16_t count;
bool erased;
bool split;
lfs2_block_t tail[2];
} lfs2_mdir_t;
// littlefs directory type
typedef struct lfs2_dir {
struct lfs2_dir *next;
uint16_t id;
uint8_t type;
lfs2_mdir_t m;
lfs2_off_t pos;
lfs2_block_t head[2];
} lfs2_dir_t;
// littlefs file type
typedef struct lfs2_file {
struct lfs2_file *next;
uint16_t id;
uint8_t type;
lfs2_mdir_t m;
struct lfs2_ctz {
lfs2_block_t head;
lfs2_size_t size;
} ctz;
uint32_t flags;
lfs2_off_t pos;
lfs2_block_t block;
lfs2_off_t off;
lfs2_cache_t cache;
const struct lfs2_file_config *cfg;
} lfs2_file_t;
typedef struct lfs2_superblock {
uint32_t version;
lfs2_size_t block_size;
lfs2_size_t block_count;
lfs2_size_t name_max;
lfs2_size_t file_max;
lfs2_size_t attr_max;
} lfs2_superblock_t;
// The littlefs filesystem type
typedef struct lfs2 {
lfs2_cache_t rcache;
lfs2_cache_t pcache;
lfs2_block_t root[2];
struct lfs2_mlist {
struct lfs2_mlist *next;
uint16_t id;
uint8_t type;
lfs2_mdir_t m;
} *mlist;
uint32_t seed;
struct lfs2_gstate {
uint32_t tag;
lfs2_block_t pair[2];
} gstate, gpending, gdelta;
struct lfs2_free {
lfs2_block_t off;
lfs2_block_t size;
lfs2_block_t i;
lfs2_block_t ack;
uint32_t *buffer;
} free;
const struct lfs2_config *cfg;
lfs2_size_t name_max;
lfs2_size_t file_max;
lfs2_size_t attr_max;
#ifdef LFS2_MIGRATE
struct lfs21 *lfs21;
#endif
} lfs2_t;
/// Filesystem functions ///
// Format a block device with the littlefs
//
// Requires a littlefs object and config struct. This clobbers the littlefs
// object, and does not leave the filesystem mounted. The config struct must
// be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs2_format(lfs2_t *lfs2, const struct lfs2_config *config);
// Mounts a littlefs
//
// Requires a littlefs object and config struct. Multiple filesystems
// may be mounted simultaneously with multiple littlefs objects. Both
// lfs2 and config must be allocated while mounted. The config struct must
// be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *config);
// Unmounts a littlefs
//
// Does nothing besides releasing any allocated resources.
// Returns a negative error code on failure.
int lfs2_unmount(lfs2_t *lfs2);
/// General operations ///
// Removes a file or directory
//
// If removing a directory, the directory must be empty.
// Returns a negative error code on failure.
int lfs2_remove(lfs2_t *lfs2, const char *path);
// Rename or move a file or directory
//
// If the destination exists, it must match the source in type.
// If the destination is a directory, the directory must be empty.
//
// Returns a negative error code on failure.
int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath);
// Find info about a file or directory
//
// Fills out the info structure, based on the specified file or directory.
// Returns a negative error code on failure.
int lfs2_stat(lfs2_t *lfs2, const char *path, struct lfs2_info *info);
// Get a custom attribute
//
// Custom attributes are uniquely identified by an 8-bit type and limited
// to LFS2_ATTR_MAX bytes. When read, if the stored attribute is smaller than
// the buffer, it will be padded with zeros. If the stored attribute is larger,
// then it will be silently truncated. If no attribute is found, the error
// LFS2_ERR_NOATTR is returned and the buffer is filled with zeros.
//
// Returns the size of the attribute, or a negative error code on failure.
// Note, the returned size is the size of the attribute on disk, irrespective
// of the size of the buffer. This can be used to dynamically allocate a buffer
// or check for existance.
lfs2_ssize_t lfs2_getattr(lfs2_t *lfs2, const char *path,
uint8_t type, void *buffer, lfs2_size_t size);
// Set custom attributes
//
// Custom attributes are uniquely identified by an 8-bit type and limited
// to LFS2_ATTR_MAX bytes. If an attribute is not found, it will be
// implicitly created.
//
// Returns a negative error code on failure.
int lfs2_setattr(lfs2_t *lfs2, const char *path,
uint8_t type, const void *buffer, lfs2_size_t size);
// Removes a custom attribute
//
// If an attribute is not found, nothing happens.
//
// Returns a negative error code on failure.
int lfs2_removeattr(lfs2_t *lfs2, const char *path, uint8_t type);
/// File operations ///
// Open a file
//
// The mode that the file is opened in is determined by the flags, which
// are values from the enum lfs2_open_flags that are bitwise-ored together.
//
// Returns a negative error code on failure.
int lfs2_file_open(lfs2_t *lfs2, lfs2_file_t *file,
const char *path, int flags);
// Open a file with extra configuration
//
// The mode that the file is opened in is determined by the flags, which
// are values from the enum lfs2_open_flags that are bitwise-ored together.
//
// The config struct provides additional config options per file as described
// above. The config struct must be allocated while the file is open, and the
// config struct must be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file,
const char *path, int flags,
const struct lfs2_file_config *config);
// Close a file
//
// Any pending writes are written out to storage as though
// sync had been called and releases any allocated resources.
//
// Returns a negative error code on failure.
int lfs2_file_close(lfs2_t *lfs2, lfs2_file_t *file);
// Synchronize a file on storage
//
// Any pending writes are written out to storage.
// Returns a negative error code on failure.
int lfs2_file_sync(lfs2_t *lfs2, lfs2_file_t *file);
// Read data from file
//
// Takes a buffer and size indicating where to store the read data.
// Returns the number of bytes read, or a negative error code on failure.
lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file,
void *buffer, lfs2_size_t size);
// Write data to file
//
// Takes a buffer and size indicating the data to write. The file will not
// actually be updated on the storage until either sync or close is called.
//
// Returns the number of bytes written, or a negative error code on failure.
lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file,
const void *buffer, lfs2_size_t size);
// Change the position of the file
//
// The change in position is determined by the offset and whence flag.
// Returns the new position of the file, or a negative error code on failure.
lfs2_soff_t lfs2_file_seek(lfs2_t *lfs2, lfs2_file_t *file,
lfs2_soff_t off, int whence);
// Truncates the size of the file to the specified size
//
// Returns a negative error code on failure.
int lfs2_file_truncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size);
// Return the position of the file
//
// Equivalent to lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_CUR)
// Returns the position of the file, or a negative error code on failure.
lfs2_soff_t lfs2_file_tell(lfs2_t *lfs2, lfs2_file_t *file);
// Change the position of the file to the beginning of the file
//
// Equivalent to lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_SET)
// Returns a negative error code on failure.
int lfs2_file_rewind(lfs2_t *lfs2, lfs2_file_t *file);
// Return the size of the file
//
// Similar to lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_END)
// Returns the size of the file, or a negative error code on failure.
lfs2_soff_t lfs2_file_size(lfs2_t *lfs2, lfs2_file_t *file);
/// Directory operations ///
// Create a directory
//
// Returns a negative error code on failure.
int lfs2_mkdir(lfs2_t *lfs2, const char *path);
// Open a directory
//
// Once open a directory can be used with read to iterate over files.
// Returns a negative error code on failure.
int lfs2_dir_open(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path);
// Close a directory
//
// Releases any allocated resources.
// Returns a negative error code on failure.
int lfs2_dir_close(lfs2_t *lfs2, lfs2_dir_t *dir);
// Read an entry in the directory
//
// Fills out the info structure, based on the specified file or directory.
// Returns a positive value on success, 0 at the end of directory,
// or a negative error code on failure.
int lfs2_dir_read(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info);
// Change the position of the directory
//
// The new off must be a value previous returned from tell and specifies
// an absolute offset in the directory seek.
//
// Returns a negative error code on failure.
int lfs2_dir_seek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off);
// Return the position of the directory
//
// The returned offset is only meant to be consumed by seek and may not make
// sense, but does indicate the current position in the directory iteration.
//
// Returns the position of the directory, or a negative error code on failure.
lfs2_soff_t lfs2_dir_tell(lfs2_t *lfs2, lfs2_dir_t *dir);
// Change the position of the directory to the beginning of the directory
//
// Returns a negative error code on failure.
int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir);
/// Filesystem-level filesystem operations
// Finds the current size of the filesystem
//
// Note: Result is best effort. If files share COW structures, the returned
// size may be larger than the filesystem actually is.
//
// Returns the number of allocated blocks, or a negative error code on failure.
lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2);
// Traverse through all blocks in use by the filesystem
//
// The provided callback will be called with each block address that is
// currently in use by the filesystem. This can be used to determine which
// blocks are in use or how much of the storage is available.
//
// Returns a negative error code on failure.
int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data);
#ifdef LFS2_MIGRATE
// Attempts to migrate a previous version of littlefs
//
// Behaves similarly to the lfs2_format function. Attempts to mount
// the previous version of littlefs and update the filesystem so it can be
// mounted with the current version of littlefs.
//
// Requires a littlefs object and config struct. This clobbers the littlefs
// object, and does not leave the filesystem mounted. The config struct must
// be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg);
#endif
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

33
lib/littlefs/lfs2_util.c Normal file
View File

@ -0,0 +1,33 @@
/*
* lfs2 util functions
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "lfs2_util.h"
// Only compile if user does not provide custom config
#ifndef LFS2_CONFIG
// Software CRC implementation with small lookup table
uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) {
static const uint32_t rtable[16] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
};
const uint8_t *data = buffer;
for (size_t i = 0; i < size; i++) {
crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf];
crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf];
}
return crc;
}
#endif

230
lib/littlefs/lfs2_util.h Normal file
View File

@ -0,0 +1,230 @@
/*
* lfs2 utility functions
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef LFS2_UTIL_H
#define LFS2_UTIL_H
// Users can override lfs2_util.h with their own configuration by defining
// LFS2_CONFIG as a header file to include (-DLFS2_CONFIG=lfs2_config.h).
//
// If LFS2_CONFIG is used, none of the default utils will be emitted and must be
// provided by the config file. To start, I would suggest copying lfs2_util.h
// and modifying as needed.
#ifdef LFS2_CONFIG
#define LFS2_STRINGIZE(x) LFS2_STRINGIZE2(x)
#define LFS2_STRINGIZE2(x) #x
#include LFS2_STRINGIZE(LFS2_CONFIG)
#else
// System includes
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
#ifndef LFS2_NO_MALLOC
#include <stdlib.h>
#endif
#ifndef LFS2_NO_ASSERT
#include <assert.h>
#endif
#if !defined(LFS2_NO_DEBUG) || \
!defined(LFS2_NO_WARN) || \
!defined(LFS2_NO_ERROR) || \
defined(LFS2_YES_TRACE)
#include <stdio.h>
#endif
#ifdef __cplusplus
extern "C"
{
#endif
// Macros, may be replaced by system specific wrappers. Arguments to these
// macros must not have side-effects as the macros can be removed for a smaller
// code footprint
// Logging functions
#ifdef LFS2_YES_TRACE
#define LFS2_TRACE(fmt, ...) \
printf("lfs2_trace:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#else
#define LFS2_TRACE(fmt, ...)
#endif
#ifndef LFS2_NO_DEBUG
#define LFS2_DEBUG(fmt, ...) \
printf("lfs2_debug:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#else
#define LFS2_DEBUG(fmt, ...)
#endif
#ifndef LFS2_NO_WARN
#define LFS2_WARN(fmt, ...) \
printf("lfs2_warn:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#else
#define LFS2_WARN(fmt, ...)
#endif
#ifndef LFS2_NO_ERROR
#define LFS2_ERROR(fmt, ...) \
printf("lfs2_error:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#else
#define LFS2_ERROR(fmt, ...)
#endif
// Runtime assertions
#ifndef LFS2_NO_ASSERT
#define LFS2_ASSERT(test) assert(test)
#else
#define LFS2_ASSERT(test)
#endif
// Builtin functions, these may be replaced by more efficient
// toolchain-specific implementations. LFS2_NO_INTRINSICS falls back to a more
// expensive basic C implementation for debugging purposes
// Min/max functions for unsigned 32-bit numbers
static inline uint32_t lfs2_max(uint32_t a, uint32_t b) {
return (a > b) ? a : b;
}
static inline uint32_t lfs2_min(uint32_t a, uint32_t b) {
return (a < b) ? a : b;
}
// Align to nearest multiple of a size
static inline uint32_t lfs2_aligndown(uint32_t a, uint32_t alignment) {
return a - (a % alignment);
}
static inline uint32_t lfs2_alignup(uint32_t a, uint32_t alignment) {
return lfs2_aligndown(a + alignment-1, alignment);
}
// Find the next smallest power of 2 less than or equal to a
static inline uint32_t lfs2_npw2(uint32_t a) {
#if !defined(LFS2_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
return 32 - __builtin_clz(a-1);
#else
uint32_t r = 0;
uint32_t s;
a -= 1;
s = (a > 0xffff) << 4; a >>= s; r |= s;
s = (a > 0xff ) << 3; a >>= s; r |= s;
s = (a > 0xf ) << 2; a >>= s; r |= s;
s = (a > 0x3 ) << 1; a >>= s; r |= s;
return (r | (a >> 1)) + 1;
#endif
}
// Count the number of trailing binary zeros in a
// lfs2_ctz(0) may be undefined
static inline uint32_t lfs2_ctz(uint32_t a) {
#if !defined(LFS2_NO_INTRINSICS) && defined(__GNUC__)
return __builtin_ctz(a);
#else
return lfs2_npw2((a & -a) + 1) - 1;
#endif
}
// Count the number of binary ones in a
static inline uint32_t lfs2_popc(uint32_t a) {
#if !defined(LFS2_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
return __builtin_popcount(a);
#else
a = a - ((a >> 1) & 0x55555555);
a = (a & 0x33333333) + ((a >> 2) & 0x33333333);
return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24;
#endif
}
// Find the sequence comparison of a and b, this is the distance
// between a and b ignoring overflow
static inline int lfs2_scmp(uint32_t a, uint32_t b) {
return (int)(unsigned)(a - b);
}
// Convert between 32-bit little-endian and native order
static inline uint32_t lfs2_fromle32(uint32_t a) {
#if !defined(LFS2_NO_INTRINSICS) && ( \
(defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
return a;
#elif !defined(LFS2_NO_INTRINSICS) && ( \
(defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
return __builtin_bswap32(a);
#else
return (((uint8_t*)&a)[0] << 0) |
(((uint8_t*)&a)[1] << 8) |
(((uint8_t*)&a)[2] << 16) |
(((uint8_t*)&a)[3] << 24);
#endif
}
static inline uint32_t lfs2_tole32(uint32_t a) {
return lfs2_fromle32(a);
}
// Convert between 32-bit big-endian and native order
static inline uint32_t lfs2_frombe32(uint32_t a) {
#if !defined(LFS2_NO_INTRINSICS) && ( \
(defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
return __builtin_bswap32(a);
#elif !defined(LFS2_NO_INTRINSICS) && ( \
(defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
return a;
#else
return (((uint8_t*)&a)[0] << 24) |
(((uint8_t*)&a)[1] << 16) |
(((uint8_t*)&a)[2] << 8) |
(((uint8_t*)&a)[3] << 0);
#endif
}
static inline uint32_t lfs2_tobe32(uint32_t a) {
return lfs2_frombe32(a);
}
// Calculate CRC-32 with polynomial = 0x04c11db7
uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size);
// Allocate memory, only used if buffers are not provided to littlefs
// Note, memory must be 64-bit aligned
static inline void *lfs2_malloc(size_t size) {
#ifndef LFS2_NO_MALLOC
return malloc(size);
#else
(void)size;
return NULL;
#endif
}
// Deallocate memory, only used if buffers are not provided to littlefs
static inline void lfs2_free(void *p) {
#ifndef LFS2_NO_MALLOC
free(p);
#else
(void)p;
#endif
}
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif
#endif

@ -1 +1 @@
Subproject commit d633ad0882a0b17c70adb1680a793ace43da7368 Subproject commit 2315fe625a4c2db133259be50b43f33f2a8eac68

1
lib/mynewt-nimble Submodule

@ -0,0 +1 @@
Subproject commit 223714cb16c255cfa701929c0de6d7579bfd2cdd

View File

@ -109,7 +109,7 @@ STATIC int usage(char **argv) {
"-msmall-int-bits=number : set the maximum bits used to encode a small-int\n" "-msmall-int-bits=number : set the maximum bits used to encode a small-int\n"
"-mno-unicode : don't support unicode in compiled strings\n" "-mno-unicode : don't support unicode in compiled strings\n"
"-mcache-lookup-bc : cache map lookups in the bytecode\n" "-mcache-lookup-bc : cache map lookups in the bytecode\n"
"-march=<arch> : set architecture for native emitter; x86, x64, armv6, armv7m, xtensa\n" "-march=<arch> : set architecture for native emitter; x86, x64, armv6, armv7m, xtensa, xtensawin\n"
"\n" "\n"
"Implementation specific options:\n", argv[0] "Implementation specific options:\n", argv[0]
); );
@ -288,6 +288,9 @@ MP_NOINLINE int main_(int argc, char **argv) {
} else if (strcmp(arch, "xtensa") == 0) { } else if (strcmp(arch, "xtensa") == 0) {
mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_XTENSA; mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_XTENSA;
mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_XTENSA; mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_XTENSA;
} else if (strcmp(arch, "xtensawin") == 0) {
mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_XTENSAWIN;
mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_XTENSAWIN;
} else { } else {
return usage(argv); return usage(argv);
} }

View File

@ -39,6 +39,7 @@
#define MICROPY_EMIT_ARM (1) #define MICROPY_EMIT_ARM (1)
#define MICROPY_EMIT_XTENSA (1) #define MICROPY_EMIT_XTENSA (1)
#define MICROPY_EMIT_INLINE_XTENSA (1) #define MICROPY_EMIT_INLINE_XTENSA (1)
#define MICROPY_EMIT_XTENSAWIN (1)
#define MICROPY_DYNAMIC_COMPILER (1) #define MICROPY_DYNAMIC_COMPILER (1)
#define MICROPY_COMP_CONST_FOLDING (1) #define MICROPY_COMP_CONST_FOLDING (1)

View File

@ -69,11 +69,11 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_3(pyb_flash_writeblocks_obj, pyb_flash_writeblock
STATIC mp_obj_t pyb_flash_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) { STATIC mp_obj_t pyb_flash_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) {
mp_int_t cmd = mp_obj_get_int(cmd_in); mp_int_t cmd = mp_obj_get_int(cmd_in);
switch (cmd) { switch (cmd) {
case BP_IOCTL_INIT: return MP_OBJ_NEW_SMALL_INT(sflash_disk_init() != RES_OK); case MP_BLOCKDEV_IOCTL_INIT: return MP_OBJ_NEW_SMALL_INT(sflash_disk_init() != RES_OK);
case BP_IOCTL_DEINIT: sflash_disk_flush(); return MP_OBJ_NEW_SMALL_INT(0); case MP_BLOCKDEV_IOCTL_DEINIT: sflash_disk_flush(); return MP_OBJ_NEW_SMALL_INT(0);
case BP_IOCTL_SYNC: sflash_disk_flush(); return MP_OBJ_NEW_SMALL_INT(0); case MP_BLOCKDEV_IOCTL_SYNC: sflash_disk_flush(); return MP_OBJ_NEW_SMALL_INT(0);
case BP_IOCTL_SEC_COUNT: return MP_OBJ_NEW_SMALL_INT(SFLASH_SECTOR_COUNT); case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: return MP_OBJ_NEW_SMALL_INT(SFLASH_SECTOR_COUNT);
case BP_IOCTL_SEC_SIZE: return MP_OBJ_NEW_SMALL_INT(SFLASH_SECTOR_SIZE); case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: return MP_OBJ_NEW_SMALL_INT(SFLASH_SECTOR_SIZE);
default: return mp_const_none; default: return mp_const_none;
} }
} }
@ -96,14 +96,14 @@ const mp_obj_type_t pyb_flash_type = {
void pyb_flash_init_vfs(fs_user_mount_t *vfs) { void pyb_flash_init_vfs(fs_user_mount_t *vfs) {
vfs->base.type = &mp_fat_vfs_type; vfs->base.type = &mp_fat_vfs_type;
vfs->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL; vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_NATIVE | MP_BLOCKDEV_FLAG_HAVE_IOCTL;
vfs->fatfs.drv = vfs; vfs->fatfs.drv = vfs;
vfs->readblocks[0] = (mp_obj_t)&pyb_flash_readblocks_obj; vfs->blockdev.readblocks[0] = (mp_obj_t)&pyb_flash_readblocks_obj;
vfs->readblocks[1] = (mp_obj_t)&pyb_flash_obj; vfs->blockdev.readblocks[1] = (mp_obj_t)&pyb_flash_obj;
vfs->readblocks[2] = (mp_obj_t)sflash_disk_read; // native version vfs->blockdev.readblocks[2] = (mp_obj_t)sflash_disk_read; // native version
vfs->writeblocks[0] = (mp_obj_t)&pyb_flash_writeblocks_obj; vfs->blockdev.writeblocks[0] = (mp_obj_t)&pyb_flash_writeblocks_obj;
vfs->writeblocks[1] = (mp_obj_t)&pyb_flash_obj; vfs->blockdev.writeblocks[1] = (mp_obj_t)&pyb_flash_obj;
vfs->writeblocks[2] = (mp_obj_t)sflash_disk_write; // native version vfs->blockdev.writeblocks[2] = (mp_obj_t)sflash_disk_write; // native version
vfs->u.ioctl[0] = (mp_obj_t)&pyb_flash_ioctl_obj; vfs->blockdev.u.ioctl[0] = (mp_obj_t)&pyb_flash_ioctl_obj;
vfs->u.ioctl[1] = (mp_obj_t)&pyb_flash_obj; vfs->blockdev.u.ioctl[1] = (mp_obj_t)&pyb_flash_obj;
} }

View File

@ -184,16 +184,16 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_3(pyb_sd_writeblocks_obj, pyb_sd_writeblocks);
STATIC mp_obj_t pyb_sd_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) { STATIC mp_obj_t pyb_sd_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) {
mp_int_t cmd = mp_obj_get_int(cmd_in); mp_int_t cmd = mp_obj_get_int(cmd_in);
switch (cmd) { switch (cmd) {
case BP_IOCTL_INIT: case MP_BLOCKDEV_IOCTL_INIT:
case BP_IOCTL_DEINIT: case MP_BLOCKDEV_IOCTL_DEINIT:
case BP_IOCTL_SYNC: case MP_BLOCKDEV_IOCTL_SYNC:
// nothing to do // nothing to do
return MP_OBJ_NEW_SMALL_INT(0); // success return MP_OBJ_NEW_SMALL_INT(0); // success
case BP_IOCTL_SEC_COUNT: case MP_BLOCKDEV_IOCTL_BLOCK_COUNT:
return MP_OBJ_NEW_SMALL_INT(sd_disk_info.ulNofBlock * (sd_disk_info.ulBlockSize / 512)); return MP_OBJ_NEW_SMALL_INT(sd_disk_info.ulNofBlock * (sd_disk_info.ulBlockSize / 512));
case BP_IOCTL_SEC_SIZE: case MP_BLOCKDEV_IOCTL_BLOCK_SIZE:
return MP_OBJ_NEW_SMALL_INT(SD_SECTOR_SIZE); return MP_OBJ_NEW_SMALL_INT(SD_SECTOR_SIZE);
default: // unknown command default: // unknown command

View File

@ -169,19 +169,6 @@ extern const struct _mp_obj_module_t mp_module_ussl;
{ MP_ROM_QSTR(MP_QSTR_ubinascii), MP_ROM_PTR(&mp_module_ubinascii) }, \ { MP_ROM_QSTR(MP_QSTR_ubinascii), MP_ROM_PTR(&mp_module_ubinascii) }, \
{ MP_ROM_QSTR(MP_QSTR_ussl), MP_ROM_PTR(&mp_module_ussl) }, \ { MP_ROM_QSTR(MP_QSTR_ussl), MP_ROM_PTR(&mp_module_ussl) }, \
#define MICROPY_PORT_BUILTIN_MODULE_WEAK_LINKS \
{ MP_ROM_QSTR(MP_QSTR_errno), MP_ROM_PTR(&mp_module_uerrno) }, \
{ MP_ROM_QSTR(MP_QSTR_struct), MP_ROM_PTR(&mp_module_ustruct) }, \
{ MP_ROM_QSTR(MP_QSTR_re), MP_ROM_PTR(&mp_module_ure) }, \
{ MP_ROM_QSTR(MP_QSTR_json), MP_ROM_PTR(&mp_module_ujson) }, \
{ MP_ROM_QSTR(MP_QSTR_os), MP_ROM_PTR(&mp_module_uos) }, \
{ MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&mp_module_utime) }, \
{ MP_ROM_QSTR(MP_QSTR_select), MP_ROM_PTR(&mp_module_uselect) }, \
{ MP_ROM_QSTR(MP_QSTR_socket), MP_ROM_PTR(&mp_module_usocket) }, \
{ MP_ROM_QSTR(MP_QSTR_binascii), MP_ROM_PTR(&mp_module_ubinascii) }, \
{ MP_ROM_QSTR(MP_QSTR_ssl), MP_ROM_PTR(&mp_module_ussl) }, \
{ MP_ROM_QSTR(MP_QSTR_machine), MP_ROM_PTR(&machine_module) }, \
// extra constants // extra constants
#define MICROPY_PORT_CONSTANTS \ #define MICROPY_PORT_CONSTANTS \
{ MP_ROM_QSTR(MP_QSTR_umachine), MP_ROM_PTR(&machine_module) }, \ { MP_ROM_QSTR(MP_QSTR_umachine), MP_ROM_PTR(&machine_module) }, \

View File

@ -300,7 +300,7 @@ STATIC void mptask_init_sflash_filesystem (void) {
// Initialise the local flash filesystem. // Initialise the local flash filesystem.
// init the vfs object // init the vfs object
fs_user_mount_t *vfs_fat = sflash_vfs_fat; fs_user_mount_t *vfs_fat = sflash_vfs_fat;
vfs_fat->flags = 0; vfs_fat->blockdev.flags = 0;
pyb_flash_init_vfs(vfs_fat); pyb_flash_init_vfs(vfs_fat);
// Create it if needed, and mount in on /flash. // Create it if needed, and mount in on /flash.

View File

@ -33,12 +33,13 @@ MICROPY_FATFS = 1
MICROPY_PY_BTREE = 1 MICROPY_PY_BTREE = 1
MICROPY_PY_ESPIFD = 1 MICROPY_PY_ESPIFD = 1
#FROZEN_DIR = scripts FROZEN_MANIFEST ?= boards/manifest.py
FROZEN_MPY_DIR = modules
# include py core make definitions # include py core make definitions
include $(TOP)/py/py.mk include $(TOP)/py/py.mk
GIT_SUBMODULES = lib/berkeley-db-1.xx
PORT ?= /dev/ttyUSB0 PORT ?= /dev/ttyUSB0
BAUD ?= 460800 BAUD ?= 460800
FLASH_MODE ?= dio FLASH_MODE ?= dio
@ -111,6 +112,16 @@ $(info Add the xtensa toolchain to your PATH. See README.md)
$(error C compiler missing) $(error C compiler missing)
endif endif
# Support BLE by default when building with IDF 4.x.
# Can be explicitly disabled on the command line or board config.
ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4))
MICROPY_PY_BLUETOOTH ?= 1
ifeq ($(MICROPY_PY_BLUETOOTH),1)
SDKCONFIG += boards/sdkconfig.ble
MICROPY_BLUETOOTH_NIMBLE = 1
endif
endif
# include sdkconfig to get needed configuration values # include sdkconfig to get needed configuration values
include $(SDKCONFIG) include $(SDKCONFIG)
@ -175,6 +186,29 @@ INC_ESPCOMP += -I$(ESPCOMP)/spi_flash/private_include
INC_ESPCOMP += -I$(ESPCOMP)/wpa_supplicant/include/esp_supplicant INC_ESPCOMP += -I$(ESPCOMP)/wpa_supplicant/include/esp_supplicant
INC_ESPCOMP += -I$(ESPCOMP)/xtensa/include INC_ESPCOMP += -I$(ESPCOMP)/xtensa/include
INC_ESPCOMP += -I$(ESPCOMP)/xtensa/esp32/include INC_ESPCOMP += -I$(ESPCOMP)/xtensa/esp32/include
ifeq ($(CONFIG_BT_NIMBLE_ENABLED),y)
INC_ESPCOMP += -I$(ESPCOMP)/bt/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/common/osi/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/common/btc/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/common/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/porting/nimble/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/port/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/ans/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/bas/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/gap/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/gatt/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/ias/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/lls/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/tps/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/util/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/store/ram/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/store/config/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/porting/npl/freertos/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/ext/tinycrypt/include
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/esp-hci/include
endif
else else
INC_ESPCOMP += -I$(ESPCOMP)/ethernet/include INC_ESPCOMP += -I$(ESPCOMP)/ethernet/include
INC_ESPCOMP += -I$(ESPCOMP)/expat/expat/expat/lib INC_ESPCOMP += -I$(ESPCOMP)/expat/expat/expat/lib
@ -186,6 +220,17 @@ INC_ESPCOMP += -I$(ESPCOMP)/nghttp/port/include
INC_ESPCOMP += -I$(ESPCOMP)/nghttp/nghttp2/lib/includes INC_ESPCOMP += -I$(ESPCOMP)/nghttp/nghttp2/lib/includes
endif endif
ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4))
ifeq ($(MICROPY_PY_BLUETOOTH),1)
CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH=1
CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE=1
ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1)
CFLAGS_MOD += -DMICROPY_BLUETOOTH_NIMBLE=1
endif
endif
endif
# these flags are common to C and C++ compilation # these flags are common to C and C++ compilation
CFLAGS_COMMON = -Os -ffunction-sections -fdata-sections -fstrict-volatile-bitfields \ CFLAGS_COMMON = -Os -ffunction-sections -fdata-sections -fstrict-volatile-bitfields \
-mlongcalls -nostdlib \ -mlongcalls -nostdlib \
@ -271,6 +316,7 @@ SRC_C = \
modnetwork.c \ modnetwork.c \
network_lan.c \ network_lan.c \
network_ppp.c \ network_ppp.c \
nimble.c \
modsocket.c \ modsocket.c \
modesp.c \ modesp.c \
esp32_partition.c \ esp32_partition.c \
@ -287,6 +333,7 @@ SRC_C = \
EXTMOD_SRC_C = $(addprefix extmod/,\ EXTMOD_SRC_C = $(addprefix extmod/,\
modonewire.c \ modonewire.c \
modbluetooth_nimble.c \
) )
LIB_SRC_C = $(addprefix lib/,\ LIB_SRC_C = $(addprefix lib/,\
@ -321,12 +368,11 @@ $(ESPIDFMOD_MODULE): $(ALL_ESPIDFMOD_SRC) $(LVGL_BINDING_DIR)/gen/gen_mpy.py
LIB_SRC_C += \ LIB_SRC_C += \
lib/lv_bindings/driver/esp32/modlvesp32.c \ lib/lv_bindings/driver/esp32/modlvesp32.c \
lib/lv_bindings/driver/esp32/modILI9341.c \
lib/lv_bindings/driver/esp32/modxpt2046.c \
lib/lv_bindings/driver/esp32/modrtch.c \ lib/lv_bindings/driver/esp32/modrtch.c \
lib/lv_bindings/driver/esp32/espidf.c \
$(ESPIDFMOD_MODULE) \ $(ESPIDFMOD_MODULE) \
lib/lv_bindings/driver/esp32/espidf.c # lib/lv_bindings/driver/esp32/modxpt2046.c \
#TODO lib/lv_bindings/driver/esp32/modxpt2046.c \ # lib/lv_bindings/driver/esp32/modILI9341.c \
endif endif
@ -488,6 +534,31 @@ ESPIDF_ESP_EVENT_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/esp_event/*.c))
ESPIDF_ESP_WIFI_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/esp_wifi/src/*.c)) ESPIDF_ESP_WIFI_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/esp_wifi/src/*.c))
ifeq ($(CONFIG_BT_NIMBLE_ENABLED),y)
ESPIDF_BT_NIMBLE_O = $(patsubst %.c,%.o,\
$(wildcard $(ESPCOMP)/bt/controller/*.c) \
$(wildcard $(ESPCOMP)/bt/common/btc/core/*.c) \
$(wildcard $(ESPCOMP)/bt/common/osi/*.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/esp-hci/src/*.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/ext/tinycrypt/src/*.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/ans/src/*.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/bas/src/*.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/gap/src/*.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/gatt/src/*.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/ias/src/*.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/lls/src/*.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/tps/src/*.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/src/*.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/store/config/src/ble_store_config.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/store/config/src/ble_store_nvs.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/store/ram/src/*.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/util/src/*.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/src/*.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/porting/nimble/src/*.c) \
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/porting/npl/freertos/src/*.c) \
)
endif
$(BUILD)/$(ESPCOMP)/esp_eth/src/esp_eth_mac_dm9051.o: CFLAGS += -fno-strict-aliasing $(BUILD)/$(ESPCOMP)/esp_eth/src/esp_eth_mac_dm9051.o: CFLAGS += -fno-strict-aliasing
ESPIDF_ESP_ETH_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/esp_eth/src/*.c)) ESPIDF_ESP_ETH_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/esp_eth/src/*.c))
@ -548,6 +619,9 @@ ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4))
$(eval $(call gen_espidf_lib_rule,esp_common,$(ESPIDF_ESP_COMMON_O))) $(eval $(call gen_espidf_lib_rule,esp_common,$(ESPIDF_ESP_COMMON_O)))
$(eval $(call gen_espidf_lib_rule,esp_event,$(ESPIDF_ESP_EVENT_O))) $(eval $(call gen_espidf_lib_rule,esp_event,$(ESPIDF_ESP_EVENT_O)))
$(eval $(call gen_espidf_lib_rule,esp_wifi,$(ESPIDF_ESP_WIFI_O))) $(eval $(call gen_espidf_lib_rule,esp_wifi,$(ESPIDF_ESP_WIFI_O)))
ifeq ($(CONFIG_BT_NIMBLE_ENABLED),y)
$(eval $(call gen_espidf_lib_rule,bt_nimble,$(ESPIDF_BT_NIMBLE_O)))
endif
$(eval $(call gen_espidf_lib_rule,esp_eth,$(ESPIDF_ESP_ETH_O))) $(eval $(call gen_espidf_lib_rule,esp_eth,$(ESPIDF_ESP_ETH_O)))
$(eval $(call gen_espidf_lib_rule,xtensa,$(ESPIDF_XTENSA_O))) $(eval $(call gen_espidf_lib_rule,xtensa,$(ESPIDF_XTENSA_O)))
else else
@ -663,6 +737,7 @@ APP_LD_ARGS += -L$(dir $(LIBSTDCXX_FILE_NAME)) -lstdc++
APP_LD_ARGS += $(LIBC_LIBM) APP_LD_ARGS += $(LIBC_LIBM)
ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4))
APP_LD_ARGS += -L$(ESPCOMP)/xtensa/esp32 -lhal APP_LD_ARGS += -L$(ESPCOMP)/xtensa/esp32 -lhal
APP_LD_ARGS += -L$(ESPCOMP)/bt/controller/lib -lbtdm_app
APP_LD_ARGS += -L$(ESPCOMP)/esp_wifi/lib_esp32 -lcore -lmesh -lnet80211 -lphy -lrtc -lpp -lsmartconfig -lcoexist APP_LD_ARGS += -L$(ESPCOMP)/esp_wifi/lib_esp32 -lcore -lmesh -lnet80211 -lphy -lrtc -lpp -lsmartconfig -lcoexist
else else
APP_LD_ARGS += $(ESPCOMP)/esp32/libhal.a APP_LD_ARGS += $(ESPCOMP)/esp32/libhal.a

View File

@ -122,17 +122,10 @@ this repository):
$ make -C mpy-cross $ make -C mpy-cross
``` ```
The ESP32 port has a dependency on Berkeley DB, which is an external
dependency (git submodule). You'll need to have git initialize that
module using the commands:
```bash
$ git submodule init lib/berkeley-db-1.xx
$ git submodule update
```
Then to build MicroPython for the ESP32 run: Then to build MicroPython for the ESP32 run:
```bash ```bash
$ cd ports/esp32 $ cd ports/esp32
$ make submodules
$ make $ make
``` ```
This will produce binary firmware images in the `build/` subdirectory This will produce binary firmware images in the `build/` subdirectory

View File

@ -1,5 +1,6 @@
FLASH_FREQ = 80m FLASH_FREQ = 80m
SDKCONFIG += boards/sdkconfig.base SDKCONFIG += boards/sdkconfig.base
SDKCONFIG += boards/sdkconfig.240mhz
SDKCONFIG += boards/sdkconfig.spiram SDKCONFIG += boards/sdkconfig.spiram
SDKCONFIG += boards/TINYPICO/sdkconfig.board SDKCONFIG += boards/TINYPICO/sdkconfig.board

View File

@ -0,0 +1,6 @@
freeze('$(PORT_DIR)/modules')
freeze('$(MPY_DIR)/tools', ('upip.py', 'upip_utarfile.py'))
freeze('$(MPY_DIR)/ports/esp8266/modules', 'ntptime.py')
freeze('$(MPY_DIR)/ports/esp8266/modules', ('webrepl.py', 'webrepl_setup.py', 'websocket_helper.py',))
freeze('$(MPY_DIR)/drivers/dht', 'dht.py')
freeze('$(MPY_DIR)/drivers/onewire')

View File

@ -0,0 +1,6 @@
include('manifest.py')
freeze('$(MPY_LIB_DIR)/upysh', 'upysh.py')
freeze('$(MPY_LIB_DIR)/urequests', 'urequests.py')
freeze('$(MPY_LIB_DIR)/umqtt.simple', 'umqtt/simple.py')
freeze('$(MPY_LIB_DIR)/umqtt.robust', 'umqtt/robust.py')

View File

@ -0,0 +1,5 @@
# MicroPython on ESP32, ESP IDF configuration with 240MHz CPU
CONFIG_ESP32_DEFAULT_CPU_FREQ_80=
CONFIG_ESP32_DEFAULT_CPU_FREQ_160=
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240

View File

@ -11,9 +11,8 @@ CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR=y
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
# ESP32-specific # ESP32-specific
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n
CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=n CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n
CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=n
CONFIG_ESP32_XTAL_FREQ_AUTO=y CONFIG_ESP32_XTAL_FREQ_AUTO=y
CONFIG_FREERTOS_INTERRUPT_BACKTRACE=n CONFIG_FREERTOS_INTERRUPT_BACKTRACE=n
CONFIG_IDLE_TASK_STACK_SIZE=4096 CONFIG_IDLE_TASK_STACK_SIZE=4096
@ -27,14 +26,19 @@ CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y
CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=y CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=y
# UDP # UDP
CONFIG_LWIP_PPP_SUPPORT=y
CONFIG_LWIP_PPP_PAP_SUPPORT=y
CONFIG_LWIP_PPP_CHAP_SUPPORT=y
# v3.3-only (renamed in 4.0)
CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y
CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=n
CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=n
CONFIG_SUPPORT_STATIC_ALLOCATION=y
CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK=y
CONFIG_PPP_SUPPORT=y CONFIG_PPP_SUPPORT=y
CONFIG_PPP_PAP_SUPPORT=y CONFIG_PPP_PAP_SUPPORT=y
CONFIG_PPP_CHAP_SUPPORT=y CONFIG_PPP_CHAP_SUPPORT=y
# v3.3-only (renamed in 4.0)
CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y
CONFIG_SUPPORT_STATIC_ALLOCATION=y
CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK=y
# Logs # Logs
CONFIG_LOG_DEFAULT_LEVEL_WARN=y CONFIG_LOG_DEFAULT_LEVEL_WARN=y

View File

@ -0,0 +1,14 @@
# Note this requires building with IDF 4.x
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=
CONFIG_BTDM_CTRL_MODE_BTDM=
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=4
# Pin to the same core as MP.
CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=n
CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=y
CONFIG_BT_NIMBLE_PINNED_TO_CORE=1

View File

@ -173,11 +173,11 @@ STATIC mp_obj_t esp32_partition_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_
esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in); esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_int_t cmd = mp_obj_get_int(cmd_in); mp_int_t cmd = mp_obj_get_int(cmd_in);
switch (cmd) { switch (cmd) {
case BP_IOCTL_INIT: return MP_OBJ_NEW_SMALL_INT(0); case MP_BLOCKDEV_IOCTL_INIT: return MP_OBJ_NEW_SMALL_INT(0);
case BP_IOCTL_DEINIT: return MP_OBJ_NEW_SMALL_INT(0); case MP_BLOCKDEV_IOCTL_DEINIT: return MP_OBJ_NEW_SMALL_INT(0);
case BP_IOCTL_SYNC: return MP_OBJ_NEW_SMALL_INT(0); case MP_BLOCKDEV_IOCTL_SYNC: return MP_OBJ_NEW_SMALL_INT(0);
case BP_IOCTL_SEC_COUNT: return MP_OBJ_NEW_SMALL_INT(self->part->size / BLOCK_SIZE_BYTES); case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: return MP_OBJ_NEW_SMALL_INT(self->part->size / BLOCK_SIZE_BYTES);
case BP_IOCTL_SEC_SIZE: return MP_OBJ_NEW_SMALL_INT(BLOCK_SIZE_BYTES); case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: return MP_OBJ_NEW_SMALL_INT(BLOCK_SIZE_BYTES);
default: return mp_const_none; default: return mp_const_none;
} }
} }

View File

@ -205,7 +205,7 @@ STATIC void machine_hw_spi_init_internal(
return; return;
case ESP_ERR_INVALID_STATE: case ESP_ERR_INVALID_STATE:
mp_raise_msg(&mp_type_OSError, "SPI device already in use"); mp_raise_msg(&mp_type_OSError, "SPI host already in use");
return; return;
} }

View File

@ -337,26 +337,26 @@ STATIC mp_obj_t machine_sdcard_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t
mp_int_t cmd = mp_obj_get_int(cmd_in); mp_int_t cmd = mp_obj_get_int(cmd_in);
switch (cmd) { switch (cmd) {
case BP_IOCTL_INIT: case MP_BLOCKDEV_IOCTL_INIT:
err = sdcard_ensure_card_init(self, false); err = sdcard_ensure_card_init(self, false);
return MP_OBJ_NEW_SMALL_INT((err == ESP_OK) ? 0 : -1); return MP_OBJ_NEW_SMALL_INT((err == ESP_OK) ? 0 : -1);
case BP_IOCTL_DEINIT: case MP_BLOCKDEV_IOCTL_DEINIT:
// Ensure that future attempts to look at info re-read the card // Ensure that future attempts to look at info re-read the card
self->flags &= ~SDCARD_CARD_FLAGS_CARD_INIT_DONE; self->flags &= ~SDCARD_CARD_FLAGS_CARD_INIT_DONE;
return MP_OBJ_NEW_SMALL_INT(0); // success return MP_OBJ_NEW_SMALL_INT(0); // success
case BP_IOCTL_SYNC: case MP_BLOCKDEV_IOCTL_SYNC:
// nothing to do // nothing to do
return MP_OBJ_NEW_SMALL_INT(0); // success return MP_OBJ_NEW_SMALL_INT(0); // success
case BP_IOCTL_SEC_COUNT: case MP_BLOCKDEV_IOCTL_BLOCK_COUNT:
err = sdcard_ensure_card_init(self, false); err = sdcard_ensure_card_init(self, false);
if (err != ESP_OK) if (err != ESP_OK)
return MP_OBJ_NEW_SMALL_INT(-1); return MP_OBJ_NEW_SMALL_INT(-1);
return MP_OBJ_NEW_SMALL_INT(self->card.csd.capacity); return MP_OBJ_NEW_SMALL_INT(self->card.csd.capacity);
case BP_IOCTL_SEC_SIZE: case MP_BLOCKDEV_IOCTL_BLOCK_SIZE:
err = sdcard_ensure_card_init(self, false); err = sdcard_ensure_card_init(self, false);
if (err != ESP_OK) if (err != ESP_OK)
return MP_OBJ_NEW_SMALL_INT(-1); return MP_OBJ_NEW_SMALL_INT(-1);

View File

@ -50,6 +50,7 @@ typedef struct _machine_uart_obj_t {
uint16_t rxbuf; uint16_t rxbuf;
uint16_t timeout; // timeout waiting for first char (in ms) uint16_t timeout; // timeout waiting for first char (in ms)
uint16_t timeout_char; // timeout waiting between chars (in ms) uint16_t timeout_char; // timeout waiting between chars (in ms)
uint32_t invert; // lines to invert
} machine_uart_obj_t; } machine_uart_obj_t;
STATIC const char *_parity_name[] = {"None", "1", "0"}; STATIC const char *_parity_name[] = {"None", "1", "0"};
@ -61,13 +62,42 @@ STATIC void machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_pri
machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in);
uint32_t baudrate; uint32_t baudrate;
uart_get_baudrate(self->uart_num, &baudrate); uart_get_baudrate(self->uart_num, &baudrate);
mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, tx=%d, rx=%d, rts=%d, cts=%d, txbuf=%u, rxbuf=%u, timeout=%u, timeout_char=%u)", mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, tx=%d, rx=%d, rts=%d, cts=%d, txbuf=%u, rxbuf=%u, timeout=%u, timeout_char=%u",
self->uart_num, baudrate, self->bits, _parity_name[self->parity], self->uart_num, baudrate, self->bits, _parity_name[self->parity],
self->stop, self->tx, self->rx, self->rts, self->cts, self->txbuf, self->rxbuf, self->timeout, self->timeout_char); self->stop, self->tx, self->rx, self->rts, self->cts, self->txbuf, self->rxbuf, self->timeout, self->timeout_char);
if (self->invert) {
mp_printf(print, ", invert=");
uint32_t invert_mask = self->invert;
if (invert_mask & UART_INVERSE_TXD) {
mp_printf(print, "INV_TX");
invert_mask &= ~UART_INVERSE_TXD;
if (invert_mask) {
mp_printf(print, "|");
}
}
if (invert_mask & UART_INVERSE_RXD) {
mp_printf(print, "INV_RX");
invert_mask &= ~UART_INVERSE_RXD;
if (invert_mask) {
mp_printf(print, "|");
}
}
if (invert_mask & UART_INVERSE_RTS) {
mp_printf(print, "INV_RTS");
invert_mask &= ~UART_INVERSE_RTS;
if (invert_mask) {
mp_printf(print, "|");
}
}
if (invert_mask & UART_INVERSE_CTS) {
mp_printf(print, "INV_CTS");
}
}
mp_printf(print, ")");
} }
STATIC void machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { STATIC void machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_tx, ARG_rx, ARG_rts, ARG_cts, ARG_txbuf, ARG_rxbuf, ARG_timeout, ARG_timeout_char }; enum { ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_tx, ARG_rx, ARG_rts, ARG_cts, ARG_txbuf, ARG_rxbuf, ARG_timeout, ARG_timeout_char, ARG_invert };
static const mp_arg_t allowed_args[] = { static const mp_arg_t allowed_args[] = {
{ MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_bits, MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_bits, MP_ARG_INT, {.u_int = 0} },
@ -81,6 +111,7 @@ STATIC void machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, co
{ MP_QSTR_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
{ MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
}; };
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
@ -205,6 +236,13 @@ STATIC void machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, co
if (self->timeout_char < min_timeout_char) { if (self->timeout_char < min_timeout_char) {
self->timeout_char = min_timeout_char; self->timeout_char = min_timeout_char;
} }
// set line inversion
if (args[ARG_invert].u_int & ~UART_LINE_INV_MASK) {
mp_raise_ValueError("invalid inversion mask");
}
self->invert = args[ARG_invert].u_int;
uart_set_line_inverse(self->uart_num, self->invert);
} }
STATIC mp_obj_t machine_uart_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { STATIC mp_obj_t machine_uart_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
@ -341,6 +379,11 @@ STATIC const mp_rom_map_elem_t machine_uart_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
{ MP_ROM_QSTR(MP_QSTR_sendbreak), MP_ROM_PTR(&machine_uart_sendbreak_obj) }, { MP_ROM_QSTR(MP_QSTR_sendbreak), MP_ROM_PTR(&machine_uart_sendbreak_obj) },
{ MP_ROM_QSTR(MP_QSTR_INV_TX), MP_ROM_INT(UART_INVERSE_TXD) },
{ MP_ROM_QSTR(MP_QSTR_INV_RX), MP_ROM_INT(UART_INVERSE_RXD) },
{ MP_ROM_QSTR(MP_QSTR_INV_RTS), MP_ROM_INT(UART_INVERSE_RTS) },
{ MP_ROM_QSTR(MP_QSTR_INV_CTS), MP_ROM_INT(UART_INVERSE_CTS) },
}; };
STATIC MP_DEFINE_CONST_DICT(machine_uart_locals_dict, machine_uart_locals_dict_table); STATIC MP_DEFINE_CONST_DICT(machine_uart_locals_dict, machine_uart_locals_dict_table);

View File

@ -172,3 +172,13 @@ void nlr_jump_fail(void *val) {
void mbedtls_debug_set_threshold(int threshold) { void mbedtls_debug_set_threshold(int threshold) {
(void)threshold; (void)threshold;
} }
void *esp_native_code_commit(void *buf, size_t len) {
len = (len + 3) & ~3;
uint32_t *p = heap_caps_malloc(len, MALLOC_CAP_EXEC);
if (p == NULL) {
m_malloc_fail(len);
}
memcpy(p, buf, len);
return p;
}

View File

@ -1 +0,0 @@
../../../drivers/dht/dht.py

View File

@ -1 +0,0 @@
../../esp8266/modules/ds18x20.py

View File

@ -0,0 +1 @@
../../../lib/lv_bindings/driver/esp32/ili9341.py

View File

@ -1 +0,0 @@
../../esp8266/modules/ntptime.py

View File

@ -1 +0,0 @@
../../esp8266/modules/onewire.py

View File

@ -1 +0,0 @@
../../../../../micropython-lib/umqtt.robust/umqtt/robust.py

View File

@ -1 +0,0 @@
../../../../../micropython-lib/umqtt.simple/umqtt/simple.py

View File

@ -1 +0,0 @@
../../../tools/upip.py

View File

@ -1 +0,0 @@
../../../tools/upip_utarfile.py

View File

@ -1 +0,0 @@
../../../../micropython-lib/upysh/upysh.py

View File

@ -1 +0,0 @@
../../../../micropython-lib/urequests/urequests.py

Some files were not shown because too many files have changed in this diff Show More