300x250 AD TOP

Search This Blog

Pages

Paling Dilihat

Powered by Blogger.

Wednesday, November 30, 2022

Generic Gamepad for Toy Cars

Some kids love motorized toys, cars, trucks and basically anything that makes a noise or have a motor can be a child's toy.

I've been searching for a quick, simple, cheap, generic option to replace the remote controls with something I can easily source without building a specialized PCB or costing too much and I think I've found that option.

This is the battlebot I've been using it for, the original controller stopped working after 3 minutes.

Wemos D1 R32

The Wemos D1 R32 is ESP32 in Arduino Uno form factor. The pinout is standard while still allowing access to all Arduino Uno standard pins and GPIO2 for onboard led. The schematics are available.




L293D Motor Control Shield

The motor shield was originally created by Adafruit but has since become ubiquitous through other online shops.



But it was designed to work with Arduino Uno so I had to go through the schematic to understand how the ESP32 should access it.


  DIR_SER - GPIO12
  PWM1A - GPIO13 - servo2
  PWM1B - GPIO5 - servo1
  PWM2A - GPIO23 - dc1
  DIR_LATCH - GPIO19
  DIR_EN - GPIO14
  PWM0A - GPIO27 - dc4
  PWM0B - GPIO16 - dc3
  DIR_CLK - GPIO17
  PWM2B - GPIO25 - dc2

Bluepad32

Bluepad32 was created by Ricardo Quesada, it was designed to to allow using newer gamepad controllers with retro gaming consoles.


While it has a long list of supported controllers, I've found that the DualShock 4 works best for me.

To pair the DualShock 4 to ESP32 you'll need to turn it on while pressing the "SHARE" button and PS Button.

Firmware

After mapping the pins between the ESP32 and the Motor Shield, I needed to write a new Bluepad32 platform, I've called mine uni_platform_motor. The new platform uses Adafruit's AFMotor to control the motors, but you can use anything else you'd like to control.

The uni platform has the following important events:
- on_init_complete - which fires when the platform is initialized
- on_device_connected - where you can do motor arming 
- on_device_disconnected - where you can do motor disarming, stopping the engines etc'
on_gamepad_data - where you can process incoming joysticks and buttons processing and convert it into motors commands.

Porting AFMotor

The AFMotor was not designed for ESP32 and I did a very crude job of porting it since it was just to see if they can work together and it was good enough.
Please note that the Motor Driver uses a few pins that might not be mapped to GPIO, (for example: pin 14), to use these pins its not enough to use the gpio_set_direction, but rather you should use the more generic gpio_config.

Motor Direction Algorithm


Summary

The proposed solution enables relatively cheap components to be bound to a single remote and so I don't have to disassemble the controller or build my own. 
Also, thinking about the future, it can be used for scratch or Arduino development with the same hardware so another one for the pros list.

Lastly, The DualShock4 can be used with other game consoles, PCs, TV Stocks etc' therefore future proofing the whole expense.

I would like to express my gratitude again to Ricardo Quesada for making the Bluepad32.

As always you can find the fruits of my labor at my GitHub account.









Tags: , , ,

Wednesday, November 23, 2022

Embedding Lua in your Projects

Lua is lighweight programing language designed for embedding, it was created in 1993 by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, and Waldemar Celes.

Lua even has a very fast LuaJIT which sadly I cannot use since I'm aiming for embedded MCU.



Fortunately, Lua has great documentation and huge community, while the language needs some getting used it, it can do everything I need and more and embedding it was by far the fastest from previous MicroPython and QuickJS.

Installation of the Lua library was very simple, download the release, extract it in the lib folder and add library.json.

To gain the performance I needed, I've modified luaconf.h

1
2
3
/* Default configuration ('long long' and 'double', for 64-bit Lua) */
#define LUA_INT_DEFAULT		LUA_INT_INT
#define LUA_FLOAT_DEFAULT	LUA_FLOAT_FLOAT

To initialize Lua a new state is created and the default libraries added to it

1
2
3
4
5
6
7
8
9
lua_State *L = luaL_newstate(); /* create state */
if (L == NULL)
{
    l_message(argv[0], "cannot create state: not enough memory");
    return EXIT_FAILURE;
}

printf("setting libs\r\n");
luaL_openlibs(L);

Next is injecting the demo function into the state

1
2
3
function transform(a,b)
    return (a^2/math.sin(2*math.pi/b))-(a/2)
end

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
char *code = "function transform(a,b)\r\n \
return (a^2/math.sin(2*math.pi/b))-(a/2)\r\n \
end\r\n";

if (luaL_loadstring(L, code) == LUA_OK)
{
    if (lua_pcall(L, 0, 0, 0) == LUA_OK)
    {
        // If it was executed successfully we remove the code from the stack
        lua_pop(L, lua_gettop(L));
    }
}

And to run the transform function, the function name is pushed along with its arguments, the function is called and the result is pulled back from the stack

1
2
3
4
5
6
lua_getglobal(L, "transform");
lua_pushnumber(L, i);
lua_pushnumber(L, i);
lua_call(L, 2, 1);

double res = lua_tonumber(L, -1);

Summary

Lua was the easiest to integrate, it is the fastest scripting engine and if speed is your primary concern it should be one of the top candidates.

In my particular use case:
  • x64 native vs Lua slow down is about 4x times
  • ESP32 native vs Lua slow down is about 20x times


As always you can find the fruits of my labor at my GitHub account.



Tags: ,

Embedding QuickJS in your Projects

QuickJS is small and embeddable Javascript engine made by the famous Fabrice Bellard, it supports ES2020 specifications and made available under MIT license.

You might have read about my porting of his TinyEMU to ESP32 which could boot linux.



During my hunt for scripting engine that can be integrated into a new embedded product I've encountered MicroPython and QuickJS, while the documentation is a bit lacking, the code was much easier to read and compile, there are no complicated build scripts and it was basically dropping the last version in the lib folder and selecting which files should be compiled with library.json

Once the library was compiled, integrating it into my demo was very easy and supporting multiple execution contexts is very easy since its not sharing context and variables.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
JSRuntime *rt = JS_NewRuntime();
if (!rt)
{
    fprintf(stderr, "qjs: cannot allocate JS runtime\n");
}

JS_SetMemoryLimit(rt, 80 * 1024);
JS_SetMaxStackSize(rt, 10 * 1024);

JSContext *ctx = JS_NewContext(rt);
if (!ctx)
{
    fprintf(stderr, "qjs: cannot allocate JS context\n");
}

The JSRuntime object represents the JavaScript engine, its responsible for memory allocation and C function calls. 

The JSContext object respresents the execution context where JavaScript functions and variables live.

Our demo workload

1
2
3
function transform(a, b) { 
    return (a ^ 2 / Math.sin(2 * Math.PI / b)) - a / 2; 
}

We'll eval(uate) the function to get it into the context

1
2
3
4
5
6
const char *expr = "function transform(a,b){return  (a^2/Math.sin(2*Math.PI/b))-a/2;}";
JSValue r = JS_Eval(ctx, expr, strlen(expr), "", 0);
if (JS_IsException(r))
{
    printf("Error evaluating script\r\n");
}

And once we have the function in the context, we can get it by name and execute it with the a and b arguments

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
JSValue args[2];
args[0] = JS_NewFloat64(ctx, (double)i);
args[1] = JS_NewFloat64(ctx, (double)i);
JSValue res = JS_Call(ctx, func, global, 2, args);
if (JS_IsException(res))
{
    printf("Error Executing transform\r\n");
}

if (!JS_IsNumber(res))
{
    printf("is not number!\r\n");
}

double result;
if (JS_ToFloat64(ctx, &result, res))
{
    printf("error parsing number\r\n");
}

Summary

QuickJS looks very interesting as a JavaScript runtime engine, its relatively fast and the code seems self explanatory. It has been embedded in rust, as an isolated VM in Node JS and more.

While QuickJS is different from MicroPython, its faster to execute the same workload, it was more readable for me and faster to setup and compile, one of the major drawbacks is that if you want to use it as an alternative to MicroPython, you'll need to implement your own hardware drivers for SPI, I2C etc'.

You may find Carlos Alberto's Writing native modules in C for QuickJS engine useful.

Lastly, I've attempted to modify QuickJS to use floats instead of double since ESP32 FPU is single precision only, it will probably make it non-standard and fail many JavaScript standard tests but I've included it here anyway.

In my particular use case:

  • x64 native vs QuickJS slowdown is about x9 times
  • ESP32 native vs QuickJS slowdown is about x63 times
  • ESP32 native vs QuickJS using float slowdown is about x25 times

You may view the official benchmarks here.

As always you can find the fruits of my labor at my GitHub account.







Tags: , , , ,

Thursday, November 17, 2022

Embedding Micropython in your Projects

MicroPython is a python implementation for microcontrollers and other low resource requirement implementation which can make it perfect for embedding without significantly increasing your delivered executable or firmware.

The project was created by Damien George at 2013 and has since grown and improved and even earned its place at OBCPs and is planned to reach space on board Euclid at 2023 as well as integrating into RODOS.



If it can work for spacecrafts, why shouldn't it work for you?

My original goal was to use MicroPython as an expression evaluation and enrichment engine for embedded data acquisition, however, I've found that the complexity of actually integrating it into a PlatformIO project was too big to miss on an opportunity to make it an easier task.

MicroPython Project Structure

After downloading the release (in my case 1.19), the archive has the following interesting folders:

py - contains MicroPython VM

ports - contains platform specific implementation and support functionality

extmod - extra modules

I was a bit naïve, so I've decided to build all files in py and windows port but there were so many errors I've realized its probably the wrong approach.

I've started digging in the makefiles since I wasn't sure what to look for, the instructions were pretty standard. but once you start looking in the makefiles, there's plenty going on there.

But there's no easy way to use that in PlatformIO as far as I know, so I went ahead and checked what was executing when and why, there's some documentation about qstr but there are also modules and version info, some of it to optimize the build and prevent a rebuild of the whole qstr header.

I wanted to avoid precompiling the headers and keeping them in git, since it will complicate version updates, build flags changes and built-in module changes, there have been similar approached with NXP port but I wanted it to be more robust and developer friendly.

I've decided to keep it short and just write a script that executes all the scripts on the appropriate files and folders and after some fiddling with the list of sources that needed to be compiled, the project compiled on both Windows and ESP32.

Building MicroPython on PlatformIO

PlatformIO uses scons to build, scons keeps a series of flags and dictionaries of the files to build and uses the compiler so actually generate the executable and firmware. Though in ESP32 case it uses cmake as well to build esp-idf.

PlatformIO also added hooks before and after building the project, allowing extensibility through scripting, in this case I've chosen to execute a script in the library so its always executing before the build.

I went ahead and made the build system a bit more flexible by allowing different sources and includes to be used for different platforms. You may want to look in build_settings.py to see how.

In short, the script determines the framework and platform and reads the library.json->build->environments, it appends all the sources and flags from common and then looks for a specific platform and appends the sources and flags from there as well.

I then read list of file selectors (SRC_FILTER) and build a list of source files so it can be used to generate the headers with generate_strings.py

  • makeversionhdr - generates the mpversion.h
  • makeqstrdefs pp - generates qstr.i.last from a batch of source files and headers
  • makeqstrdefs split qstr - extracts a list of qstr from the last qstr.i.last, this may run a few times with a few different qstr.i.last
  • makeqstrdefs split module - extracts a list of modules from the last qstr.i.last, this may run a few times with a few different qstr.i.last
  • makeqstrdefs cat module - generates a collected file from the splitted modle files
  • makeqstrdefs cat qstr - generates a collected file from the splitted qstr files
  • makemoduledefs - generates moduledefs.h from the collected module file
  • qstrdefs_preprocessed - precompiles strings into preprocessed.h
  • qstrdefs_generated - generates generated.h from preprocessed.h
Once this process is done, the qstr and modules dictionaries are ready to be compiled into the library.

Generic Port

MicroPython uses a set of configuration header files and functions so it will be portable and work across multiple environments but to run it without any special hardware modules requires very little configuration.

I've created a "generic" port:
  • mimimal gc
  • unhandled exceptions are sent to stderr
  • all filesystem operations are throwing errors
  • all python's print are sent to stdout, stdin does nothing

Initialization

To initialize micropython we need a stack and heap and then initialize the stack, the stack limit, the garbage collector which also handles allocations and finally MicroPython.

Please note there are two stack limits, one for python (pystack) and one for the OS limit, which is used to limit the recursion the MicroPython engine uses - it should be less than the OS stack size, looking at MicroPython's code, its about 2k less than the OS stack.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
instance->stack = (uint8_t *)malloc(instance->stack_size);
instance->heap = (uint8_t *)malloc(instance->heap_size);

mp_stack_ctrl_init();
mp_stack_set_limit(instance->stack_size);

// Initialize heap
gc_init(instance->heap, instance->heap + instance->heap_size);
mp_pystack_init(instance->stack, instance->stack + instance->stack_size);

// Initialize interpreter
mp_init();


NLR

NLR stands for non-local return, which is how MicroPython handles exceptions in C, using a stack of jumps.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
nlr_buf_t nlr;
if (nlr_push(&nlr) == 0)
{
    mp_obj_t retval = mp_call_function_n_kw(func, argc, 0, argv);
    nlr_pop();
    return retval;
}
else
{
    mp_obj_print_exception(&mp_stderr_print, MP_OBJ_FROM_PTR(nlr.ret_val));
    return (mp_obj_t)nlr.ret_val;
}

1-2 define a temporary nlr buffer and push the current context.
4 execute a MicroPython function
5 pop for successful execution
8 in case there's an error between lines 3-7 the IP will jump to line 9.
10 display the error

Executing Scripts and Calling Functions

While it seems very similar in python, there is a difference, executing a script might have an output, it is not captured, so there is no way to use that output unless you plan on capturing and parsing Python's print.

This is why I chose to split that functionality into two parts, the first part executes a script, creating functions / variables on the local/global scope and the second part executes a function and uses its return value.

For executing scripts, MicroPython needs to parse the string and compile it into MicroPython bytecode and execute it. So once the script is in the context, we can use the module for looking up the function name and execute it.

We'll pass the following script to the compilation code

1
2
3
import math;
def transform(a,b):
    return (a**2/math.sin(2*math.pi/b))-a/2


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
nlr_buf_t nlr;
if (nlr_push(&nlr) == 0)
{
    qstr src_name = 1 /*MP_QSTR_*/;
    mp_lexer_t *lex = mp_lexer_new_from_str_len(src_name, fragment, strlen(fragment), false);
    qstr source_name = lex->source_name;
    mp_parse_tree_t pt = mp_parse(lex, MP_PARSE_FILE_INPUT);
    mp_obj_t module_fun = mp_compile(&pt, source_name, false);
    mp_call_function_0(module_fun);

    nlr_pop();
    return NULL;
}
else
{
    mp_obj_print_exception(&mp_stderr_print, MP_OBJ_FROM_PTR(nlr.ret_val));
    return (mp_obj_t)nlr.ret_val;
}

And now we can lookup transform (line 1) function and call it (line 10)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
mp_obj_t transform_function = mp_obj_dict_get(mp_locals_get(), mp_obj_new_str("transform", strlen("transform")));

mp_obj_t args[2];
args[0] = mp_obj_new_float(1);
args[1] = mp_obj_new_float(2);

nlr_buf_t nlr;
if (nlr_push(&nlr) == 0)
{
    mp_obj_t retval = mp_call_function_n_kw(transform_function, argc, 0, argv);
    nlr_pop();
    return retval;
}
else
{
    mp_obj_print_exception(&mp_stderr_print, MP_OBJ_FROM_PTR(nlr.ret_val));
    return (mp_obj_t)nlr.ret_val;
}


Summary

MicroPython has advanced light years since its first inception and it is very capable and enables rapid prototyping and even usable for space applications. That being said, its still a bit slow to function as a generic programming language and more appropriate for coordinating calls to C functions.

The other thing I've found lacking is documentation, to write this demo I needed to go over multiple sources and demos, look for forks, pull requests and read a lot of source code, its still not 100% clear to me what all the defines are doing exactly and what to expect in terms of performance impact for each of them.

In my particular use case:
  • x64 native vs MicroPython slowdown is about x16 times.
  • ESP32 native vs MicroPython slowdown is about x100 times.

For the curious minds of where did the CPU spent its time, I'm very happy to have found Very Sleepy




As always you can find the fruits of my labor at my GitHub account.


Further reading:


Tags: , , ,