22
33uStubby 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.
1723Clone the repository using git and just put it on the path to install.
1824Alternatively, 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
35363Contributions 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