atomvm / AtomVM

Tiny Erlang VM
https://www.atomvm.net
Apache License 2.0
1.45k stars 96 forks source link

Align Peripheral call semantics between platforms. #1122

Open DawDavis opened 5 months ago

DawDavis commented 5 months ago

This is not my idea - just breaking out an issue from #894

This should have an associated target milestone and individual issues for each peripheral and each platform.

I think the goal is to remove the need for code like this:

start() ->
    platform_gpio_setup(atomvm:platform()),
    loop(pin(), low).

loop(Pin, Level) ->
    io:format("Setting pin ~p ~p~n", [Pin, Level]),
    gpio:digital_write(Pin, Level),
    timer:sleep(1000),
    loop(Pin, toggle(Level)).

toggle(high) ->
    low;
toggle(low) ->
    high.

pin() ->
    case atomvm:platform() of
        esp32 ->
            2;
        pico ->
            ?PIN;
        stm32 ->
            {b, 0};
        Platform ->
            erlang:exit({unsupported_platform, Platform})
    end.

platform_gpio_setup(esp32) ->
    gpio:set_pin_mode(pin(), output);
platform_gpio_setup(stm32) ->
    gpio:set_pin_mode(pin(), output);
platform_gpio_setup(pico) ->
    case ?PIN of
        {wl, 0} ->
            % Pico-W needs no setup for extra "WL" pins
            ok;
        Pin ->
            % Setup for Pico GPIO pins
            gpio:init(Pin),
            gpio:set_pin_mode(Pin, output)
    end;
platform_gpio_setup(Platform) ->
    io:format("Platform ~p is not supported.~n", [Platform]),
    erlang:exit({error, {unsupported_platform, Platform}}).

In favor of something closer to this:

start() ->
    Pin = ?PIN,
    gpio:init(Pin),
    gpio:set_pin_mode(Pin, output),
    loop(Pin, low).

loop(Pin, Level) ->
    io:format("Setting pin ~p ~p~n", [Pin, Level]),
    gpio:digital_write(Pin, Level),
    timer:sleep(1000),
    loop(Pin, toggle(Level)).

toggle(high) ->
    low;
toggle(low) ->
    high.

I specifically included the gpio:init() call - I know STM32 doesn't need it, but it is still important to keep unity between all devices.

This is a VM after all - there should not be a code-visible difference between running on the RP2040 or the STM32.

UncleGrumpy commented 4 months ago

I absolutely agree, the unique peculiarities of each platform and development environment should be handled internally in the VM, and users should be able to use a consistent set of functions to setup and use devices like GPIO.

We already have fixing the error returns from the GPIO driver to be consistent across platforms planned for a future release, and I believe adding an single setup function that works for all supported platforms should also be added.

There are some unavoidable differences across platforms, like stm32 gpio pins must be a tuple, while other platforms use an atom. I believe a setup function that takes a map would be convenient for users, and the easiest way to handle the setup input internally. The map could be a single pin, or many pins, so that a single function call could be used to configure all of the pins used for a given application. We should still continue to support the lower level configuration functions, so that users have the flexibility to work with the pins using the underlying platform functionality directly when needed.

UncleGrumpy commented 4 months ago

We can't use gpio:init/1 on any platform except rp2040, so this is an example of something that should (at least optionally) be handled internally by the driver when a pin is configured on the rp2040 platform.

DawDavis commented 4 months ago

Yeah, after reading the documentation around the RP2040, it looks like just doing the init any time a configuration is set is A-okay. The call is idempotent afaik.

I also think a key idea would be to consider a software layer on non-MCU systems, so that you can run a blackbox test of the code on a linux machine and really test the crap out of production code. I'm really thinking that Erlang on an MCU could offer a really good environment for hobby aerospace stuff. (Model rocketry and drones mostly)

A framework around I2C, SPI, CAN, and UART would also be useful. Because of the non-standard nature of it though, I'm not sure that non-master/primary control needs to be a priority. Unless you're trying to network nodes together?

I guess having a sort of OTP-alike networking layer over UART or I2C would be neat.

UncleGrumpy commented 4 months ago

I also think a key idea would be to consider a software layer on non-MCU systems, so that you can run a blackbox test of the code on a linux machine and really test the crap out of production code.

This is a great idea, but not a small amount of work. A gpio emulator for the generic_unix port would be fairly straightforward. To do this for I2C or SPI, and have it be truly useful, you would also need to emulate all of the various sensors and output devices you want to test as well. This would be an amazing feature, but something I would not expect until after a 1.0 release. …but if this is something you want to work on I’m sure it would be more than welcome!

Personally I would rather see GPIO, SPI and I2C support for the generic_unix port first. There are plenty of supported devices with these built in and exposed to users (any single board computer), and there are USB breakout boards that can add support for most other systems.