Skip to content

Commit 9217573

Browse files
Merge pull request #55 from wnienhaus/fixes-for-v1
Updated docs, less ugly imports and a useful example
2 parents 037bc58 + afe0517 commit 9217573

File tree

8 files changed

+193
-40
lines changed

8 files changed

+193
-40
lines changed

Diff for: README.rst

+9-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ development machine using the binutils-esp32ulp toolchain from Espressif.
1515
Status
1616
------
1717

18-
The most commonly used simple stuff should work.
18+
The most commonly used stuff should work. Many ULP code examples found on
19+
the web will work unmodified. Notably, assembler macros and #include processing
20+
are not supported.
1921

2022
Expressions in assembly source code are supported and get evaluated during
2123
assembling. Only expressions evaluating to a single integer are supported.
@@ -29,7 +31,12 @@ ULP source files containing convenience macros such as WRITE_RTC_REG. The
2931
preprocessor and how to use it is documented here:
3032
`Preprocessor support <docs/preprocess.rst>`_.
3133

32-
There might be some stuff missing, some bugs and other symptoms of alpha
34+
The minimum supported version of MicroPython is v1.12. py-esp32-ulp has been
35+
tested with MicroPython v1.12 and v1.17. It has been tested on real ESP32
36+
devices with the chip type ESP32D0WDQ6 (revision 1) without SPIRAM. It has
37+
also been tested on the Unix port.
38+
39+
There might be some stuff missing, some bugs and other symptoms of beta
3340
software. Also, error and exception handling is rather rough yet.
3441

3542
Please be patient or contribute missing parts or fixes.

Diff for: esp32_ulp/__init__.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from .util import garbage_collect
2+
3+
from .preprocess import preprocess
4+
from .assemble import Assembler
5+
from .link import make_binary
6+
garbage_collect('after import')
7+
8+
9+
def src_to_binary(src):
10+
assembler = Assembler()
11+
src = preprocess(src)
12+
assembler.assemble(src, remove_comments=False) # comments already removed by preprocessor
13+
garbage_collect('before symbols export')
14+
addrs_syms = assembler.symbols.export()
15+
for addr, sym in addrs_syms:
16+
print('%04d %s' % (addr, sym))
17+
18+
text, data, bss_len = assembler.fetch()
19+
return make_binary(text, data, bss_len)
20+
21+
22+
def assemble_file(filename):
23+
with open(filename) as f:
24+
src = f.read()
25+
26+
binary = src_to_binary(src)
27+
28+
if filename.endswith('.s') or filename.endswith('.S'):
29+
filename = filename[:-2]
30+
with open(filename + '.ulp', 'wb') as f:
31+
f.write(binary)
32+

Diff for: esp32_ulp/__main__.py

+2-29
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,9 @@
11
import sys
2-
3-
from .util import garbage_collect
4-
5-
from .preprocess import preprocess
6-
from .assemble import Assembler
7-
from .link import make_binary
8-
garbage_collect('after import')
9-
10-
11-
def src_to_binary(src):
12-
assembler = Assembler()
13-
src = preprocess(src)
14-
assembler.assemble(src, remove_comments=False) # comments already removed by preprocessor
15-
garbage_collect('before symbols export')
16-
addrs_syms = assembler.symbols.export()
17-
for addr, sym in addrs_syms:
18-
print('%04d %s' % (addr, sym))
19-
20-
text, data, bss_len = assembler.fetch()
21-
return make_binary(text, data, bss_len)
2+
from . import assemble_file
223

234

245
def main(fn):
25-
with open(fn) as f:
26-
src = f.read()
27-
28-
binary = src_to_binary(src)
29-
30-
if fn.endswith('.s') or fn.endswith('.S'):
31-
fn = fn[:-2]
32-
with open(fn + '.ulp', 'wb') as f:
33-
f.write(binary)
6+
assemble_file(fn)
347

358

369
if __name__ == '__main__':

Diff for: examples/blink.py

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"""
2+
Simple example showing how to control a GPIO pin from the ULP coprocessor.
3+
4+
The GPIO port is configured to be attached to the RTC module, and then set
5+
to OUTPUT mode. To avoid re-initializing the GPIO on every wakeup, a magic
6+
token gets set in memory.
7+
8+
After every change of state, the ULP is put back to sleep again until the
9+
next wakeup. The ULP wakes up every 500ms to change the state of the GPIO
10+
pin. An LED attached to the GPIO pin would toggle on and off every 500ms.
11+
12+
The end of the python script has a loop to show the value of the magic token
13+
and the current state, so you can confirm the magic token gets set and watch
14+
the state value changing. If the loop is stopped (Ctrl-C), the LED attached
15+
to the GPIO pin continues to blink, because the ULP runs independently from
16+
the main processor.
17+
"""
18+
19+
from esp32 import ULP
20+
from machine import mem32
21+
from esp32_ulp import src_to_binary
22+
23+
source = """\
24+
# constants from:
25+
# https://github.com/espressif/esp-idf/blob/1cb31e5/components/soc/esp32/include/soc/soc.h
26+
#define DR_REG_RTCIO_BASE 0x3ff48400
27+
28+
# constants from:
29+
# https://github.com/espressif/esp-idf/blob/1cb31e5/components/soc/esp32/include/soc/rtc_io_reg.h
30+
#define RTC_IO_TOUCH_PAD2_REG (DR_REG_RTCIO_BASE + 0x9c)
31+
#define RTC_IO_TOUCH_PAD2_MUX_SEL_M (BIT(19))
32+
#define RTC_GPIO_OUT_REG (DR_REG_RTCIO_BASE + 0x0)
33+
#define RTC_GPIO_ENABLE_W1TS_REG (DR_REG_RTCIO_BASE + 0x10)
34+
#define RTC_GPIO_ENABLE_W1TC_REG (DR_REG_RTCIO_BASE + 0x14)
35+
#define RTC_GPIO_ENABLE_W1TS_S 14
36+
#define RTC_GPIO_ENABLE_W1TC_S 14
37+
#define RTC_GPIO_OUT_DATA_S 14
38+
39+
# constants from:
40+
# https://github.com/espressif/esp-idf/blob/1cb31e5/components/soc/esp32/include/soc/rtc_io_channel.h
41+
#define RTCIO_GPIO2_CHANNEL 12
42+
43+
# When accessed from the RTC module (ULP) GPIOs need to be addressed by their channel number
44+
.set gpio, RTCIO_GPIO2_CHANNEL
45+
.set token, 0xcafe # magic token
46+
47+
.text
48+
magic: .long 0
49+
state: .long 0
50+
51+
.global entry
52+
entry:
53+
# load magic flag
54+
move r0, magic
55+
ld r1, r0, 0
56+
57+
# test if we have initialised already
58+
sub r1, r1, token
59+
jump after_init, eq # jump if magic == token (note: "eq" means the last instruction (sub) resulted in 0)
60+
61+
init:
62+
# connect GPIO to ULP (0: GPIO connected to digital GPIO module, 1: GPIO connected to analog RTC module)
63+
WRITE_RTC_REG(RTC_IO_TOUCH_PAD2_REG, RTC_IO_TOUCH_PAD2_MUX_SEL_M, 1, 1);
64+
65+
# GPIO shall be output, not input
66+
WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + gpio, 1, 1);
67+
68+
# store that we're done with initialisation
69+
move r0, magic
70+
move r1, token
71+
st r1, r0, 0
72+
73+
after_init:
74+
move r1, state
75+
ld r0, r1, 0
76+
77+
move r2, 1
78+
sub r0, r2, r0 # toggle state
79+
st r0, r1, 0 # store updated state
80+
81+
jumpr on, 0, gt # if r0 (state) > 0, jump to 'on'
82+
jump off # else jump to 'off'
83+
84+
on:
85+
# turn on led (set GPIO)
86+
WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TS_REG, RTC_GPIO_ENABLE_W1TS_S + gpio, 1, 1)
87+
jump exit
88+
89+
off:
90+
# turn off led (clear GPIO)
91+
WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TC_REG, RTC_GPIO_ENABLE_W1TC_S + gpio, 1, 1)
92+
jump exit
93+
94+
exit:
95+
halt # go back to sleep until next wakeup period
96+
"""
97+
98+
binary = src_to_binary(source)
99+
100+
load_addr, entry_addr = 0, 8
101+
102+
ULP_MEM_BASE = 0x50000000
103+
ULP_DATA_MASK = 0xffff # ULP data is only in lower 16 bits
104+
105+
ulp = ULP()
106+
ulp.set_wakeup_period(0, 500000) # use timer0, wakeup after 500000usec (0.5s)
107+
ulp.load_binary(load_addr, binary)
108+
109+
ulp.run(entry_addr)
110+
111+
while True:
112+
print(hex(mem32[ULP_MEM_BASE + load_addr] & ULP_DATA_MASK), # magic token
113+
hex(mem32[ULP_MEM_BASE + load_addr + 4] & ULP_DATA_MASK) # current state
114+
)

Diff for: examples/counter.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from esp32 import ULP
1313
from machine import mem32
1414

15-
from esp32_ulp.__main__ import src_to_binary
15+
from esp32_ulp import src_to_binary
1616

1717
source = """\
1818
data: .long 0

Diff for: tests/01_compat_tests.sh

+7-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
set -e
66

7+
calc_file_hash() {
8+
local filename=$1
9+
10+
shasum < $1 | cut -d' ' -f1
11+
}
12+
713
for src_file in $(ls -1 compat/*.S); do
814
src_name="${src_file%.S}"
915

@@ -36,6 +42,6 @@ for src_file in $(ls -1 compat/*.S); do
3642
xxd $bin_file
3743
exit 1
3844
else
39-
echo -e "\tBuild outputs match"
45+
echo -e "\tBuild outputs match (sha1: $(calc_file_hash $ulp_file))"
4046
fi
4147
done

Diff for: tests/02_compat_rtc_tests.sh

+7-1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ build_defines_db() {
5151
esp-idf/components/esp_common/include/*.h 1>$log_file
5252
}
5353

54+
calc_file_hash() {
55+
local filename=$1
56+
57+
shasum < $1 | cut -d' ' -f1
58+
}
59+
5460
patch_test() {
5561
local test_name=$1
5662
local out_file="${test_name}.tmp"
@@ -150,6 +156,6 @@ for src_file in ulptool/src/ulp_examples/*/*.s binutils-esp32ulp/gas/testsuite/g
150156
xxd $bin_file
151157
exit 1
152158
else
153-
echo -e "\tBuild outputs match"
159+
echo -e "\tBuild outputs match (sha1: $(calc_file_hash $ulp_file))"
154160
fi
155161
done

Diff for: tests/preprocess.py

+21-6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,21 @@ def test(param):
1111
tests.append(param)
1212

1313

14+
def resolve_relative_path(filename):
15+
"""
16+
Returns the full path to the filename provided, taken relative to the current file
17+
e.g.
18+
if this file was file.py at /path/to/file.py
19+
and the provided relative filename was tests/unit.py
20+
then the resulting path would be /path/to/tests/unit.py
21+
"""
22+
r = __file__.rsplit("/", 1) # poor man's os.path.dirname(__file__)
23+
head = r[0]
24+
if len(r) == 1 or not head:
25+
return filename
26+
return "%s/%s" % (head, filename)
27+
28+
1429
@test
1530
def test_replace_defines_should_return_empty_line_given_empty_string():
1631
p = Preprocessor()
@@ -204,7 +219,7 @@ def preprocess_should_replace_BIT_with_empty_string_unless_defined():
204219
def test_process_include_file():
205220
p = Preprocessor()
206221

207-
defines = p.process_include_file('fixtures/incl.h')
222+
defines = p.process_include_file(resolve_relative_path('fixtures/incl.h'))
208223

209224
assert defines['CONST1'] == '42'
210225
assert defines['CONST2'] == '99'
@@ -216,8 +231,8 @@ def test_process_include_file():
216231
def test_process_include_file_with_multiple_files():
217232
p = Preprocessor()
218233

219-
defines = p.process_include_file('fixtures/incl.h')
220-
defines = p.process_include_file('fixtures/incl2.h')
234+
defines = p.process_include_file(resolve_relative_path('fixtures/incl.h'))
235+
defines = p.process_include_file(resolve_relative_path('fixtures/incl2.h'))
221236

222237
assert defines['CONST1'] == '42', "constant from incl.h"
223238
assert defines['CONST2'] == '123', "constant overridden by incl2.h"
@@ -232,8 +247,8 @@ def test_process_include_file_using_database():
232247
p = Preprocessor()
233248
p.use_db(db)
234249

235-
p.process_include_file('fixtures/incl.h')
236-
p.process_include_file('fixtures/incl2.h')
250+
p.process_include_file(resolve_relative_path('fixtures/incl.h'))
251+
p.process_include_file(resolve_relative_path('fixtures/incl2.h'))
237252

238253
assert db['CONST1'] == '42', "constant from incl.h"
239254
assert db['CONST2'] == '123', "constant overridden by incl2.h"
@@ -250,7 +265,7 @@ def test_process_include_file_should_not_load_database_keys_into_instance_define
250265
p = Preprocessor()
251266
p.use_db(db)
252267

253-
p.process_include_file('fixtures/incl.h')
268+
p.process_include_file(resolve_relative_path('fixtures/incl.h'))
254269

255270
# a bit hackish to reference instance-internal state
256271
# but it's important to verify this, as we otherwise run out of memory on device

0 commit comments

Comments
 (0)