Skip to content

Commit 641c18a

Browse files
authored
Merge pull request #2 from pazzarpj/feature/1-readme
#1 Updating readme with some usage examples
2 parents 0854bf5 + a7db12b commit 641c18a

File tree

2 files changed

+343
-11
lines changed

2 files changed

+343
-11
lines changed

README.md

Lines changed: 339 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22

33
uStubby is a library for generating micropython c extension stubs from type annotated python.
44

5-
Fixate is a Python library for testing real stuff.
6-
Fixate provides a framework for writing scripts in Python to test hardware.
7-
Providing drivers for equipment, support for switching, logging and a basic test runner UI.
8-
While Fixate is fairly simple, it is already being used to test real electronics products in real factories.
5+
According to [Link](https://micropython.org/)
6+
7+
"MicroPython is a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimised to run on microcontrollers and in constrained environments."
8+
9+
Sometimes, pure python performance isn't enough.
10+
C extensions are a way to improve performace whilst still having the bulk of your code in micropython.
11+
12+
Unfortunately there is a lot of boilerplate code needed to build these extensions.
13+
14+
uStubby is designed to make this process as easy as writing python.
915

1016
## Getting Started
1117

@@ -17,7 +23,329 @@ Currently, there are no external dependencies for running uStubby.
1723
Clone the repository using git and just put it on the path to install.
1824
Alternatively, install from PyPI with
1925
```bash
20-
'pip install ustubby'
26+
pip install ustubby
27+
```
28+
29+
## Usage
30+
This example follows generating the template as shown [here](http://docs.micropython.org/en/latest/develop/cmodules.html#basic-example)
31+
32+
Create a python file with the module as you intend to use it from micropython.
33+
34+
eg. example.py
35+
```python
36+
def add_ints(a: int, b: int) -> int:
37+
"""Adds two integers
38+
:param a:
39+
:param b:
40+
:return:a + b"""
41+
```
42+
We can then convert this into the appropriate c stub by running
43+
```python
44+
import ustubby
45+
import example
46+
47+
print(ustubby.stub_module(example))
48+
```
49+
<details><summary>Output</summary><p>
50+
51+
```c
52+
// Include required definitions first.
53+
#include "py/obj.h"
54+
#include "py/runtime.h"
55+
#include "py/builtin.h"
56+
57+
//Adds two integers
58+
//:param a:
59+
//:param b:
60+
//:return:a + b
61+
STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) {
62+
mp_int_t a = mp_obj_get_int(a_obj);
63+
mp_int_t b = mp_obj_get_int(b_obj);
64+
mp_int_t ret_val;
65+
66+
//Your code here
67+
68+
return mp_obj_new_int(ret_val);
69+
}
70+
MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints);
71+
72+
STATIC const mp_rom_map_elem_t example_module_globals_table[] = {
73+
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example) },
74+
{ MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) },
75+
};
76+
77+
STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table);
78+
const mp_obj_module_t example_user_cmodule = {
79+
.base = {&mp_type_module},
80+
.globals = (mp_obj_dict_t*)&example_module_globals,
81+
};
82+
83+
MP_REGISTER_MODULE(MP_QSTR_example, example_user_cmodule, MODULE_EXAMPLE_ENABLED);
84+
```
85+
</p></details>
86+
87+
This will parse all the functions in the module and attach them to the same namespace in micropython.
88+
##### Note: It will only generate the boilerplate code and not the actual code that does the work such as a + b
89+
After editing the code in the template at the place marked //Code goes here you can follow the instructions
90+
[here](http://docs.micropython.org/en/latest/develop/cmodules.html#basic-example) for modifying
91+
the Make File and building the module into your micro python deployment.
92+
93+
You should then be able to use the module in micro python by typing
94+
```python
95+
import example # from example.c compiled into micropython
96+
example.add_ints(1, 2)
97+
# prints 3
98+
```
99+
##### Note: This example.py is the one compiled into the micropython source and not the file we created earlier
100+
101+
### Advanced usage
102+
If you added two more functions to the original example.py
103+
```python
104+
def lots_of_parameters(a: int, b: float, c: tuple, d: object, e: str) -> None:
105+
"""
106+
:param a:
107+
:param b:
108+
:param c:
109+
:param d:
110+
:return:
111+
"""
112+
113+
def readfrom_mem(addr: int = 0, memaddr: int = 0, arg: object = None, *, addrsize: int = 8) -> str:
114+
"""
115+
:param addr:
116+
:param memaddr:
117+
:param arg:
118+
:param addrsize: Keyword only arg
119+
:return:
120+
"""
121+
```
122+
123+
logs_of_parameters shows the types of types you can parse in. You always need to annotate each parameter and the return.
124+
readfrom_mem shows that you can set default values for certain parameters and specify that addrsize is a keyword only
125+
argument.
126+
127+
At the c level in micropython, there is only three ways of implementing a function.
128+
##### Basic Case
129+
```python
130+
def foo(a, b, c): # 0 to 3 args
131+
pass
132+
```
133+
```c
134+
MP_DEFINE_CONST_FUN_OBJ_X // Where x is 0 to 3 args
135+
```
136+
##### Greater than three positional args
137+
```python
138+
def foo(*args):
139+
pass
140+
```
141+
```c
142+
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN
143+
```
144+
##### Arbitary args
145+
```python
146+
def foo(*args, **kwargs):
147+
pass
148+
```
149+
```c
150+
MP_DEFINE_CONST_FUN_OBJ_KW
151+
```
152+
Each successively increasing the boiler plate to conveniently accessing the variables.
153+
Using the same code to parse it
154+
```python
155+
import ustubby
156+
import example
157+
158+
print(ustubby.stub_module(example))
159+
```
160+
<details><summary>Output</summary><p>
161+
162+
```c
163+
// Include required definitions first.
164+
#include "py/obj.h"
165+
#include "py/runtime.h"
166+
#include "py/builtin.h"
167+
168+
//Adds two integers
169+
//:param a:
170+
//:param b:
171+
//:return:a + b
172+
STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) {
173+
mp_int_t a = mp_obj_get_int(a_obj);
174+
mp_int_t b = mp_obj_get_int(b_obj);
175+
mp_int_t ret_val;
176+
177+
//Your code here
178+
179+
return mp_obj_new_int(ret_val);
180+
}
181+
MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints);
182+
//
183+
//:param a:
184+
//:param b:
185+
//:param c:
186+
//:param d:
187+
//:return:
188+
//
189+
STATIC mp_obj_t example_lots_of_parameters(size_t n_args, const mp_obj_t *args) {
190+
mp_int_t a = mp_obj_get_int(a_obj);
191+
mp_float_t b = mp_obj_get_float(b_obj);
192+
mp_obj_t *c = NULL;
193+
size_t c_len = 0;
194+
mp_obj_get_array(c_arg, &c_len, &c);
195+
mp_obj_t d args[ARG_d].u_obj;
196+
197+
//Your code here
198+
199+
return mp_const_none;
200+
}
201+
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(example_lots_of_parameters_obj, 4, 4, example_lots_of_parameters);
202+
//
203+
//:param addr:
204+
//:param memaddr:
205+
//:param arg:
206+
//:param addrsize: Keyword only arg
207+
//:return:
208+
//
209+
STATIC mp_obj_t example_readfrom_mem(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
210+
enum { ARG_addr, ARG_memaddr, ARG_arg, ARG_addrsize };
211+
STATIC const mp_arg_t example_readfrom_mem_allowed_args[] = {
212+
{ MP_QSTR_addr, MP_ARG_REQUIRED | MP_ARG_INT, { .u_int = 0 } },
213+
{ MP_QSTR_memaddr, MP_ARG_REQUIRED | MP_ARG_INT, { .u_int = 0 } },
214+
{ MP_QSTR_arg, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } },
215+
{ MP_QSTR_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 8 } },
216+
};
217+
218+
mp_arg_val_t args[MP_ARRAY_SIZE(example_readfrom_mem_allowed_args)];
219+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args,
220+
MP_ARRAY_SIZE(example_readfrom_mem_allowed_args), example_readfrom_mem_allowed_args, args);
221+
222+
mp_int_t addr = args[ARG_addr].u_int;
223+
mp_int_t memaddr = args[ARG_memaddr].u_int;
224+
mp_obj_t arg = args[ARG_arg].u_obj;
225+
mp_int_t addrsize = args[ARG_addrsize].u_int;
226+
227+
//Your code here
228+
229+
return mp_obj_new_str(<ret_val_ptr>, <ret_val_len>);
230+
}
231+
MP_DEFINE_CONST_FUN_OBJ_KW(example_readfrom_mem_obj, 1, example_readfrom_mem);
232+
233+
STATIC const mp_rom_map_elem_t example_module_globals_table[] = {
234+
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example) },
235+
{ MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) },
236+
{ MP_ROM_QSTR(MP_QSTR_lots_of_parameters), MP_ROM_PTR(&example_lots_of_parameters_obj) },
237+
{ MP_ROM_QSTR(MP_QSTR_readfrom_mem), MP_ROM_PTR(&example_readfrom_mem_obj) },
238+
};
239+
240+
STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table);
241+
const mp_obj_module_t example_user_cmodule = {
242+
.base = {&mp_type_module},
243+
.globals = (mp_obj_dict_t*)&example_module_globals,
244+
};
245+
246+
MP_REGISTER_MODULE(MP_QSTR_example, example_user_cmodule, MODULE_EXAMPLE_ENABLED);
247+
```
248+
</p></details>
249+
250+
#### Adding fully implemented c functions
251+
Going one step further you can directly add c code to be substituted into the c generated code where the
252+
"//Your code here comment" is.
253+
254+
For example, starting with a fresh example.py you could define it as.
255+
256+
```python
257+
def add_ints(a: int, b: int) -> int:
258+
"""Adds two integers
259+
:param a:
260+
:param b:
261+
:return:a + b"""
262+
add_ints.code = " ret_val = a + b;"
263+
```
264+
to get a fully defined function in c
265+
266+
<details><summary>Output</summary><p>
267+
268+
```c
269+
// Include required definitions first.
270+
#include "py/obj.h"
271+
#include "py/runtime.h"
272+
#include "py/builtin.h"
273+
274+
//Adds two integers
275+
//:param a:
276+
//:param b:
277+
//:return:a + b
278+
STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) {
279+
mp_int_t a = mp_obj_get_int(a_obj);
280+
mp_int_t b = mp_obj_get_int(b_obj);
281+
mp_int_t ret_val;
282+
283+
ret_val = a + b;
284+
285+
return mp_obj_new_int(ret_val);
286+
}
287+
MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints);
288+
289+
STATIC const mp_rom_map_elem_t example_module_globals_table[] = {
290+
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example) },
291+
{ MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) },
292+
};
293+
294+
STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table);
295+
const mp_obj_module_t example_user_cmodule = {
296+
.base = {&mp_type_module},
297+
.globals = (mp_obj_dict_t*)&example_module_globals,
298+
};
299+
300+
MP_REGISTER_MODULE(MP_QSTR_example, example_user_cmodule, MODULE_EXAMPLE_ENABLED);
301+
```
302+
</p></details>
303+
304+
#### Using functions without a module definition
305+
If you don't need the fully module boiler plate, you can generate individual functions with
306+
```python
307+
import ustubby
308+
def add_ints(a: int, b: int) -> int:
309+
"""add two ints"""
310+
add_ints.code = " ret_val = a + b;"
311+
add_ints.__module__ = "new_module"
312+
313+
print(ustubby.stub_function(add_ints))
314+
```
315+
316+
```c
317+
//add two ints
318+
STATIC mp_obj_t new_module_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) {
319+
mp_int_t a = mp_obj_get_int(a_obj);
320+
mp_int_t b = mp_obj_get_int(b_obj);
321+
mp_int_t ret_val;
322+
323+
ret_val = a + b;
324+
325+
return mp_obj_new_int(ret_val);
326+
}
327+
MP_DEFINE_CONST_FUN_OBJ_2(new_module_add_ints_obj, new_module_add_ints);
328+
```
329+
330+
#### Parsing Litex Files
331+
uStubby is also trying to support c code generation from Litex files such as
332+
```csv
333+
#--------------------------------------------------------------------------------
334+
# Auto-generated by Migen (5585912) & LiteX (e637aa65) on 2019-08-04 03:04:29
335+
#--------------------------------------------------------------------------------
336+
csr_register,cas_leds_out,0x82000800,1,rw
337+
csr_register,cas_buttons_ev_status,0x82000804,1,rw
338+
csr_register,cas_buttons_ev_pending,0x82000808,1,rw
339+
csr_register,cas_buttons_ev_enable,0x8200080c,1,rw
340+
csr_register,ctrl_reset,0x82001000,1,rw
341+
csr_register,ctrl_scratch,0x82001004,4,rw
342+
csr_register,ctrl_bus_errors,0x82001014,4,ro
343+
```
344+
Currently only csr_register is supported. Please raise issues if you need to expand this feature.
345+
```python
346+
import ustubby
347+
mod = ustubby.parse_csv("csr.csv")
348+
print(ustubby.stub_module(mod))
21349
```
22350

23351
## Running the tests
@@ -35,7 +363,12 @@ TBD
35363
Contributions are welcome. Get in touch or create a new pull request.
36364

37365
## Credits
38-
Inspired by https://gitlab.com/oliver.robson/mpy-c-stub-gen and PyCon AU 2019 Sprints
366+
Inspired by
367+
- [Extending MicroPython: Using C for Good](https://youtu.be/fUb3Urw4H-E)
368+
- [Online C Stub Generator](https://gitlab.com/oliver.robson/mpy-c-stub-gen)
369+
- [Micropython](https://micropython.org)
370+
371+
PyCon AU 2019 Sprints
39372

40373
## Authors
41374

0 commit comments

Comments
 (0)