home.social

Search

406 results for “circuitpython”

  1. A Friendly Reminder That You Might Be In Danger - Product recalls are one of those things that most people don’t pay attention to until things get rea... more: hackaday.com/2019/08/19/a-frie #microcontrollers #circuitpython #productrecall #livefeed #pyportal #parts

  2. The PewPew Console Is Coming To EuroPython 2019 - EuroPython is a community-run developer conference, which began back in Belgium in 2002. In 2019, it... more: hackaday.com/2019/06/21/the-pe #circuitpython #classichacks #europython #trinketm0 #python

  3. Kodlama ile oynamak istiyorum ve mikrodenetleyici tarafında yeni ürünler var mı diye baktım ve içlerinden bir tek bu ilgimi çekti

    #RaspberryPiPico için tasarlanmış, ama MicroPython ile çok basit, Atari seviyesinde oyun yapılmayacaksa #C /C++ yâni #Arduino istiyor!

    Ne anladım ben bundan?

    O zaman Arduino ile çalışacak şekilde tasarlasaydınız

    Python ile bir Led Matrix ekranı bile sürülemiyorsa yuh artık!

    Sanırım @adafruit 'in CircuitPython'u ile ancak basit Atari oyunları herhâlde yapılabilir!

  4. Kodlama ile oynamak istiyorum ve mikrodenetleyici tarafında yeni ürünler var mı diye baktım ve içlerinden bir tek bu ilgimi çekti

    #RaspberryPiPico için tasarlanmış, ama MicroPython ile çok basit, Atari seviyesinde oyun yapılmayacaksa #C /C++ yâni #Arduino istiyor!

    Ne anladım ben bundan?

    O zaman Arduino ile çalışacak şekilde tasarlasaydınız

    Python ile bir Led Matrix ekranı bile sürülemiyorsa yuh artık!

    Sanırım @adafruit 'in CircuitPython'u ile ancak basit Atari oyunları herhâlde yapılabilir!

  5. Kodlama ile oynamak istiyorum ve mikrodenetleyici tarafında yeni ürünler var mı diye baktım ve içlerinden bir tek bu ilgimi çekti

    #RaspberryPiPico için tasarlanmış, ama MicroPython ile çok basit, Atari seviyesinde oyun yapılmayacaksa #C /C++ yâni #Arduino istiyor!

    Ne anladım ben bundan?

    O zaman Arduino ile çalışacak şekilde tasarlasaydınız

    Python ile bir Led Matrix ekranı bile sürülemiyorsa yuh artık!

    Sanırım @adafruit 'in CircuitPython'u ile ancak basit Atari oyunları herhâlde yapılabilir!

  6. Kodlama ile oynamak istiyorum ve mikrodenetleyici tarafında yeni ürünler var mı diye baktım ve içlerinden bir tek bu ilgimi çekti

    #RaspberryPiPico için tasarlanmış, ama MicroPython ile çok basit, Atari seviyesinde oyun yapılmayacaksa #C /C++ yâni #Arduino istiyor!

    Ne anladım ben bundan?

    O zaman Arduino ile çalışacak şekilde tasarlasaydınız

    Python ile bir Led Matrix ekranı bile sürülemiyorsa yuh artık!

    Sanırım @adafruit 'in CircuitPython'u ile ancak basit Atari oyunları herhâlde yapılabilir!

  7. Kodlama ile oynamak istiyorum ve mikrodenetleyici tarafında yeni ürünler var mı diye baktım ve içlerinden bir tek bu ilgimi çekti

    #RaspberryPiPico için tasarlanmış, ama MicroPython ile çok basit, Atari seviyesinde oyun yapılmayacaksa #C /C++ yâni #Arduino istiyor!

    Ne anladım ben bundan?

    O zaman Arduino ile çalışacak şekilde tasarlasaydınız

    Python ile bir Led Matrix ekranı bile sürülemiyorsa yuh artık!

    Sanırım @adafruit 'in CircuitPython'u ile ancak basit Atari oyunları herhâlde yapılabilir!

  8. PIO on the Raspberry Pi Pico

    Every time I’ve started to approach the use of the programmable IO (PIO) subsystem on the RP2040 or RP2350 (as used on the Raspberry Pi Pico), I’ve found myself essentially starting from scratch again and the examples quite opaque to me.

    So this time as I’ve worked through it yet again, I’ve decided to write it all down 🙂

    Here are some existing tutorials and projects that talk about getting going with the PIO:

    Assembling PIO Code

    The PIO has its own bespoke micro-instruction set that is very similar to many types of assembly language and it requires its own pio assembler to process it. The basic sequence is as follows:

    • PIO -> pioasm -> C header file -> include in C/C++ project and build

    There are options for writing PIO in both Micropython and Circuitpython, which I have done in the past, but I’m sticking with the C route here. This requires the pioasm to take PIO code and produce a C header file that can then be included in a C project.

    To use the RP2040/2350 Arduino environment, it is necessary to process PIO independently and then add the C file to the Arduino project. The Raspberry Pi C/C++ SDK can process PIO files directly as part of the main build.

    There is also an option to use hardware SDK functions for dynamic creation of PIO code at runtime. The functions are a series of pio_encode_XX() functions representing the different PIO instructions as listed here: https://www.raspberrypi.com/documentation/pico-sdk/hardware.html#group_pio_instructions

    There are two other novel approaches I found so far too:

    The first is an online editing environment that creates the required processed PIO related code for the C/C++ SDK or Python which can then be included in your build environment as required.

    The second is an alternative run-time approach that uses a range of C macros to allow the “assembling” of PIO code as part of the run-time execution. It does this by directly creating the HEX equivalents of PIO instructions, thereby effectively assembling in the fly. This means that the PIO code can be customised to the specific run-time situation.

    At this stage I’m not sure what it gives over using the pio_encode_ SDK functions directly. I do note however there is an equivalent PIO emulator which means this approach will run equally well on real hardware or in emulation. I’ve bookmarked this to come back to at some point.

    Running PIO Code

    Regardless of how the PIO instructions become code, to use them requires setting up and configuring the PIO state machines at run time as part of a project. A common approach is to include an initialisation function within the PIO code itself that is destined for passing straight through to the C/C++ SDK. This will have access to all definitions used within the PIO code and also allows the appropriate configuration information to remain encapsulated with the code.

    But I have to admit I find there is an awful lot of assumed “magic” going on when configuring and getting running PIO programs and state machines. And whilst there are plenty of examples to study, I don’t find that they are written so as to teach. Consequently, I’ve noted the following as “reminders to self” on how to read some of the examples. It doesn’t help that the SDK function list is very long and there are several ways to achieve the same things.

    Taking the PIO PWM code from the pico_examples as a starting point (https://github.com/raspberrypi/pico-examples/tree/master/pio/pwm), I’ve added in some comments containing the full function prototypes for some of the calls to make them a bit easier to walk through.

    pwm.pio:

    ;
    ; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
    ;
    ; SPDX-License-Identifier: BSD-3-Clause
    ;

    ; Side-set pin 0 is used for PWM output
    .pio_version 0 // only requires PIO version 0

    .program pwm
    .side_set 1 opt

    pull noblock side 0 ; Pull from FIFO to OSR if available, else copy X to OSR.
    mov x, osr ; Copy most-recently-pulled value back to scratch X
    mov y, isr ; ISR contains PWM period. Y used as counter.
    countloop:
    jmp x!=y noset ; Set pin high if X == Y, keep the two paths length matched
    jmp skip side 1
    noset:
    nop ; Single dummy cycle to keep the two paths the same length
    skip:
    jmp y-- countloop ; Loop until Y hits 0, then pull a fresh PWM value from FIFO

    % c-sdk {
    static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) {

    // static void pio_gpio_init (PIO pio, uint pin)
    pio_gpio_init(pio, pin);

    // int pio_sm_set_consecutive_pindirs (PIO pio, uint sm, uint pins_base, uint pin_count, bool is_out)
    pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);

    // A piece of pioasm "magic" based on .program pwm (see following notes)
    pio_sm_config c = pwm_program_get_default_config(offset);

    // static void sm_config_set_sideset_pins (pio_sm_config *c, uint sideset_base)
    sm_config_set_sideset_pins(&c, pin);

    // int pio_sm_init (PIO pio, uint sm, uint initial_pc, const pio_sm_config *config)
    pio_sm_init(pio, sm, offset, &c);
    }
    %}

    And its associated C code pwm.c:

    /**
    * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
    *
    * SPDX-License-Identifier: BSD-3-Clause
    */

    #include <stdio.h>

    #include "pico/stdlib.h"
    #include "hardware/pio.h"
    #include "pwm.pio.h"

    // Write `period` to the input shift register
    void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) {
    pio_sm_set_enabled(pio, sm, false);
    pio_sm_put_blocking(pio, sm, period);
    pio_sm_exec(pio, sm, pio_encode_pull(false, false));
    pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
    pio_sm_set_enabled(pio, sm, true);
    }

    // Write `level` to TX FIFO. State machine will copy this into X.
    void pio_pwm_set_level(PIO pio, uint sm, uint32_t level) {
    pio_sm_put_blocking(pio, sm, level);
    }

    int main() {
    stdio_init_all();
    #ifndef PICO_DEFAULT_LED_PIN
    #warning pio/pwm example requires a board with a regular LED
    puts("Default LED pin was not defined");
    #else

    // todo get free sm
    PIO pio = pio0;
    int sm = 0;
    uint offset = pio_add_program(pio, &pwm_program);
    printf("Loaded program at %d\n", offset);

    pwm_program_init(pio, sm, offset, PICO_DEFAULT_LED_PIN);
    pio_pwm_set_period(pio, sm, (1u << 16) - 1);

    int level = 0;
    while (true) {
    printf("Level = %d\n", level);
    pio_pwm_set_level(pio, sm, level * level);
    level = (level + 1) % 256;
    sleep_ms(10);
    }
    #endif
    }

    There are a few key things to remember to make sense of these examples:

    • The offset that is talked about is (I believe) the location within the shared 32 instruction program area and is used to refer back to the installed PIO program. It is returned from pio_add_program().
    • A PIO .program directive becomes a default C like directive on processing by pioasm. This results in two obscure bits of “magic” coding going on meaning in this case that “.program pwm” in the PIO file becomes “pwm_program” in C/C++:
      • pio_program_t pwm_program is a C structure which can then be referenced from the C code as shown in the line pio_add_program(pio, &pwm_program).
      • static inline pio_sm_config pwm_program_get_default_config(uint offset) is a C function based on pio_get_default_sm_config() that returns the PIO configuration for the specific PIO program in question – in this case of course the pwm program.
    • The use of .side_step opt means that not every PIO instruction has to have a side step instruction too.
    • The PIO refers to an abstract group of pins, but it is the configuration which is part of the C/C++ SDK that determines which pins are used.
    • The %c-sdk { … %} pairing signifies that this part of the PIO code will be passed straight onto the C/C++ SDK.
    • There are multiple ways of initialising GPIO pins and directions. In this example it doesn’t use pindirs in the PIO code but uses pio_sm_set_consecutive_pindirs() in the C code.
    • This example uses hardcoded references to PIO 0 and SM 0, but in many cases the PIO and SM would be chosen dynamically using API calls such as the following:
      • pio_claim_free_sm_and_add_program ()
      • pio_claim_free_sm_and_add_program_for_gpio_range()
      • pio_claim_unused_sm()
    • Each PIO program has a default configuration associated with it which can be updated. A typical pattern is shown here where the default configuration is grabbed using (in this case) pwm_program_get_default_config() and then updated by passing into following SDK calls.
    • The state machine is finally set running using pio_sm_init();

    There is one additional mix of techniques that is worth pulling out here. In the C code the function pio_pwm_set_period() is used to update the PWM period which it has to do by passing it into the SM via the FIFO. It is using some SM manipulation routines and then some inline, run-time PIO code, to achieve this.

    void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) {
    pio_sm_set_enabled(pio, sm, false);
    pio_sm_put_blocking(pio, sm, period);
    pio_sm_exec(pio, sm, pio_encode_pull(false, false));
    pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
    pio_sm_set_enabled(pio, sm, true);
    }

    Again some pretty confusing API calls, especially giving this is meant to be an example, but essentially what is going on (I think) is:

    Disable the statemachine by using pio_sm_set_enabled(... false).
    Push the period value into the TX FIFO, blocking if full to wait for it to be empty.

    Execute two direct PIO instructions using pio_sm_exec():
    This uses pio_encode_pull and pio_encode_out to run the following PIO code:
    pull noblock ; non-blocking pull
    out isr, 32 ; out 32 bits to the interrupt shift register

    Re-enable he state machine using pio_sm_set_enabled(... true).

    By default anything sent to the FIFO is written to the X register and used to set the duty cycle of the PWM. But this code creates some temporary PIO code to receive the contents of the FIFO and put it into ISR instead. Of course it has to temporarily suspend the execution of the stored PIO code in order to do this.

    I really dislike the nomenclature of “set enabled (false)” as an API approach. I’d much prefer to see something like pio_sm_enable() and pio_sm_disable() myself. I suppose they haven’t done this due to the large increase in API functions it creates.

    I guess this is personal preference, but I do find that it adds to the opaqueness of much of the example code when it doesn’t read naturally.

    So To Recap…

    Writing PIO code can be done at build time (from Python or C/C++ using pioasm or an online assembler) or run time (using pio_encode_ functions or maybe APIO).

    pioasm bridges the gap between PIO code and C/C++ including creating two magic C/C++ constructs: pwm_program for the code and pwm_program_get_default_config() to return the created PIO configuration.

    PIO and SMs can be allocated by the system using a range of “claim” functions. There are 2 PIOs on the RP2040 and 3 on the RP2350, each with its own 32 instruction program memory and each with four state machines.

    It can be useful to include an initialisation routine, that configures and starts the PIO program, within the PIO code for use from the C/C++ code using % c-sdk { … %}.

    The PIO program is added into the system and given an offset in instruction memory using pio_add_program.

    PIO code is very dense and often the functionality cannot be seen from the PIO code itself as it is defined by the PIO configuration – e.g. pins to use, frequency of execution, direction of shifts and so on.

    I’ve not touched on it here, but the use of PIO and DMA (direct memory access) often go hand in hand to create completely CPU-free means of getting data in and out of a RP2040/RP2350 system. A really good example of this is Piers Rocks’ OneROM (see this video for a brilliant summary of how this works: https://www.youtube.com/watch?v=Y8RODQZM2HY).

    Finally I need to remember that ISR stands for Input Shift Register and not Interrupt Service Routine…

    Kevin

    #pio #raspberryPiPico #rp2040 #rp2350
  9. PIO on the Raspberry Pi Pico

    Every time I’ve started to approach the use of the programmable IO (PIO) subsystem on the RP2040 or RP2350 (as used on the Raspberry Pi Pico), I’ve found myself essentially starting from scratch again and the examples quite opaque to me.

    So this time as I’ve worked through it yet again, I’ve decided to write it all down 🙂

    Here are some existing tutorials and projects that talk about getting going with the PIO:

    Assembling PIO Code

    The PIO has its own bespoke micro-instruction set that is very similar to many types of assembly language and it requires its own pio assembler to process it. The basic sequence is as follows:

    • PIO -> pioasm -> C header file -> include in C/C++ project and build

    There are options for writing PIO in both Micropython and Circuitpython, which I have done in the past, but I’m sticking with the C route here. This requires the pioasm to take PIO code and produce a C header file that can then be included in a C project.

    To use the RP2040/2350 Arduino environment, it is necessary to process PIO independently and then add the C file to the Arduino project. The Raspberry Pi C/C++ SDK can process PIO files directly as part of the main build.

    There is also an option to use hardware SDK functions for dynamic creation of PIO code at runtime. The functions are a series of pio_encode_XX() functions representing the different PIO instructions as listed here: https://www.raspberrypi.com/documentation/pico-sdk/hardware.html#group_pio_instructions

    There are two other novel approaches I found so far too:

    The first is an online editing environment that creates the required processed PIO related code for the C/C++ SDK or Python which can then be included in your build environment as required.

    The second is an alternative run-time approach that uses a range of C macros to allow the “assembling” of PIO code as part of the run-time execution. It does this by directly creating the HEX equivalents of PIO instructions, thereby effectively assembling in the fly. This means that the PIO code can be customised to the specific run-time situation.

    At this stage I’m not sure what it gives over using the pio_encode_ SDK functions directly. I do note however there is an equivalent PIO emulator which means this approach will run equally well on real hardware or in emulation. I’ve bookmarked this to come back to at some point.

    Running PIO Code

    Regardless of how the PIO instructions become code, to use them requires setting up and configuring the PIO state machines at run time as part of a project. A common approach is to include an initialisation function within the PIO code itself that is destined for passing straight through to the C/C++ SDK. This will have access to all definitions used within the PIO code and also allows the appropriate configuration information to remain encapsulated with the code.

    But I have to admit I find there is an awful lot of assumed “magic” going on when configuring and getting running PIO programs and state machines. And whilst there are plenty of examples to study, I don’t find that they are written so as to teach. Consequently, I’ve noted the following as “reminders to self” on how to read some of the examples. It doesn’t help that the SDK function list is very long and there are several ways to achieve the same things.

    Taking the PIO PWM code from the pico_examples as a starting point (https://github.com/raspberrypi/pico-examples/tree/master/pio/pwm), I’ve added in some comments containing the full function prototypes for some of the calls to make them a bit easier to walk through.

    pwm.pio:

    ;
    ; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
    ;
    ; SPDX-License-Identifier: BSD-3-Clause
    ;

    ; Side-set pin 0 is used for PWM output

    .program pwm
    .side_set 1 opt

    pull noblock side 0 ; Pull from FIFO to OSR if available,
    ; else copy X to OSR.
    mov x, osr ; Copy most-recently-pulled value
    ; back to scratch X
    mov y, isr ; ISR contains PWM period.
    ; Y used as counter.
    countloop:
    jmp x!=y noset ; Set pin high if X == Y,
    ; keep the two paths length matched
    jmp skip side 1
    noset:
    nop ; Single dummy cycle to keep
    ; the two paths the same length
    skip:
    jmp y-- countloop ; Loop until Y hits 0,
    ; then pull a fresh PWM value from FIFO

    % c-sdk {
    static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) {

    // static void pio_gpio_init (PIO pio, uint pin)
    pio_gpio_init(pio, pin);

    // int pio_sm_set_consecutive_pindirs
    // (PIO pio, uint sm, uint pins_base, uint pin_count, bool is_out)
    pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);

    // A piece of pioasm "magic" based on .program pwm
    // (see following notes)
    pio_sm_config c = pwm_program_get_default_config(offset);

    // static void sm_config_set_sideset_pins
    // (pio_sm_config *c, uint sideset_base)
    sm_config_set_sideset_pins(&c, pin);

    // int pio_sm_init (PIO pio, uint sm,
    // uint initial_pc, const pio_sm_config *config)
    pio_sm_init(pio, sm, offset, &c);
    }
    %}

    The above PIO code assembles down into the following (this is using the online PIO assembler):

    // -------------------------------------------------- //
    // This file is autogenerated by pioasm; do not edit! //
    // -------------------------------------------------- //

    #pragma once

    #if !PICO_NO_HARDWARE
    #include "hardware/pio.h"
    #endif

    // --- //
    // pwm //
    // --- //

    #define pwm_wrap_target 0
    #define pwm_wrap 6

    static const uint16_t pwm_program_instructions[] = {
    // .wrap_target
    0x9080, // 0: pull noblock side 0
    0xa027, // 1: mov x, osr
    0xa046, // 2: mov y, isr
    0x00a5, // 3: jmp x != y, 5
    0x1806, // 4: jmp 6 side 1
    0xa042, // 5: nop
    0x0083, // 6: jmp y--, 3
    // .wrap
    };

    #if !PICO_NO_HARDWARE
    static const struct pio_program pwm_program = {
    .instructions = pwm_program_instructions,
    .length = 7,
    .origin = -1,
    };

    static inline pio_sm_config pwm_program_get_default_config(uint offset) {
    pio_sm_config c = pio_get_default_sm_config();
    sm_config_set_wrap(&c, offset + pwm_wrap_target,
    offset + pwm_wrap);
    sm_config_set_sideset(&c, 2, true, false);
    return c;
    }

    static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) {
    // static void pio_gpio_init (PIO pio, uint pin)
    pio_gpio_init(pio, pin);
    // int pio_sm_set_consecutive_pindirs
    // (PIO pio, uint sm, uint pins_base, uint pin_count, bool is_out)
    pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
    // A piece of pioasm "magic" based on .program pwm
    // (see following notes)
    pio_sm_config c = pwm_program_get_default_config(offset);
    // static void sm_config_set_sideset_pins
    // (pio_sm_config *c, uint sideset_base)
    sm_config_set_sideset_pins(&c, pin);
    // int pio_sm_init (PIO pio, uint sm,
    // uint initial_pc, const pio_sm_config *config)
    pio_sm_init(pio, sm, offset, &c);
    }

    #endif

    Some key points to note about this “assembled” code:

    • Notice the appearance of pwm_program_get_default_config() which was already referenced in the passed-through C code.
    • Notice also that the “get” function has the side effect of actually setting some state machine configuration parameters in the returned configuration too! Worse for me, it isn’t actually part of the configuration at this stage – it is just in the configuration that is returned by the function…
      • The engineer in me hates this… I hate seeing a function that implies a benign function (“get”) that actually changes something critical… by I digress.
    • A key parameter that gets set are the wrap parameters via sm_config_set_wrap(). This has to be updated once the actual code offset in the PIO instruction memory is known, hence I guess why this is set at run time.
    • The patched in parameters (wrap and sideset in this case) won’t actually take effect until the configuration is finally set in the provided pass-through C code via the call to pio_sm_init().

    Here is its associated C code pwm.c:

    /**
    * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
    *
    * SPDX-License-Identifier: BSD-3-Clause
    */

    #include <stdio.h>

    #include "pico/stdlib.h"
    #include "hardware/pio.h"
    #include "pwm.pio.h"

    // Write `period` to the input shift register
    void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) {
    pio_sm_set_enabled(pio, sm, false);
    pio_sm_put_blocking(pio, sm, period);
    pio_sm_exec(pio, sm, pio_encode_pull(false, false));
    pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
    pio_sm_set_enabled(pio, sm, true);
    }

    // Write `level` to TX FIFO. State machine will copy this into X.
    void pio_pwm_set_level(PIO pio, uint sm, uint32_t level) {
    pio_sm_put_blocking(pio, sm, level);
    }

    int main() {
    stdio_init_all();
    #ifndef PICO_DEFAULT_LED_PIN
    #warning pio/pwm example requires a board with a regular LED
    puts("Default LED pin was not defined");
    #else

    // todo get free sm
    PIO pio = pio0;
    int sm = 0;
    uint offset = pio_add_program(pio, &pwm_program);
    printf("Loaded program at %d\n", offset);

    pwm_program_init(pio, sm, offset, PICO_DEFAULT_LED_PIN);
    pio_pwm_set_period(pio, sm, (1u << 16) - 1);

    int level = 0;
    while (true) {
    printf("Level = %d\n", level);
    pio_pwm_set_level(pio, sm, level * level);
    level = (level + 1) % 256;
    sleep_ms(10);
    }
    #endif
    }

    There are a few key things to remember to make sense of these examples:

    • The offset that is talked about is the location within the shared 32 instruction program area and is used to refer back to the installed PIO program. It is returned from pio_add_program().
    • A PIO .program directive becomes a default C like directive on processing by pioasm. This results in two obscure bits of “magic” coding going on meaning in this case that “.program pwm” in the PIO file becomes “pwm_program” in C/C++:
      • pio_program_t pwm_program is a C structure which can then be referenced from the C code as shown in the line pio_add_program(pio, &pwm_program).
      • static inline pio_sm_config pwm_program_get_default_config(uint offset) is a C function based on pio_get_default_sm_config() that returns the PIO configuration for the specific PIO program in question – in this case of course the pwm program (see previous notes).
    • The use of .side_step opt means that not every PIO instruction has to have a side step instruction too.
    • The PIO refers to an abstract group of pins, but it is the configuration which is part of the C/C++ SDK that determines which pins are used.
    • The %c-sdk { … %} pairing signifies that this part of the PIO code will be passed straight onto the C/C++ SDK.
    • There are multiple ways of initialising GPIO pins and directions. In this example it doesn’t use pindirs in the PIO code but uses pio_sm_set_consecutive_pindirs() in the C code.
    • This example uses hardcoded references to PIO 0 and SM 0, but in many cases the PIO and SM would be chosen dynamically using API calls such as the following:
      • pio_claim_free_sm_and_add_program ()
      • pio_claim_free_sm_and_add_program_for_gpio_range()
      • pio_claim_unused_sm()
    • Each PIO program has a default configuration associated with it which can be updated. A typical pattern is shown here where the default configuration is grabbed using (in this case) pwm_program_get_default_config() and then updated by passing into SDK calls that follow.
    • The state machine is finally set running with the required configuration using pio_sm_init();

    There is one additional mix of techniques that is worth pulling out here. In the C code the function pio_pwm_set_period() is used to update the PWM period which it has to do by passing it into the SM via the FIFO. It is using some SM manipulation routines and then some inline, run-time PIO code, to achieve this.

    void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) {
    pio_sm_set_enabled(pio, sm, false);
    pio_sm_put_blocking(pio, sm, period);
    pio_sm_exec(pio, sm, pio_encode_pull(false, false));
    pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
    pio_sm_set_enabled(pio, sm, true);
    }

    Again some pretty confusing API calls, especially giving this is meant to be an example, but essentially what is going on (I think) is:

    Disable the statemachine by using pio_sm_set_enabled(... false).
    Push the period value into the TX FIFO, blocking if full to wait for it to be empty.

    Execute two direct PIO instructions using pio_sm_exec():
    This uses pio_encode_pull and pio_encode_out to run the following PIO code:
    pull noblock ; non-blocking pull
    out isr, 32 ; out 32 bits to the interrupt shift register

    Re-enable he state machine using pio_sm_set_enabled(... true).

    By default anything sent to the FIFO is written to the X register and used to set the duty cycle of the PWM. But this code creates some temporary PIO code to receive the contents of the FIFO and put it into ISR instead. Of course it has to temporarily suspend the execution of the stored PIO code in order to do this.

    I really dislike the nomenclature of “set enabled (false)” as an API approach. I’d much prefer to see something like pio_sm_enable() and pio_sm_disable() myself. I suppose they haven’t done this due to the large increase in API functions it creates.

    I guess this is personal preference, but I do find that it adds to the opaqueness of much of the example code when it doesn’t read naturally.

    So To Recap…

    Writing PIO code can be done at build time (from Python or C/C++ using pioasm or an online assembler) or run time (using pio_encode_ functions or maybe APIO).

    pioasm bridges the gap between PIO code and C/C++ including creating two magic C/C++ constructs: pwm_program for the code and pwm_program_get_default_config() to return the created PIO configuration.

    PIO and SMs can be allocated by the system using a range of “claim” functions. There are 2 PIOs on the RP2040 and 3 on the RP2350, each with its own 32 instruction program memory and each with four state machines.

    It can be useful to include an initialisation routine, that configures and starts the PIO program, within the PIO code for use from the C/C++ code using % c-sdk { … %}.

    The PIO program is added into the system and given an offset in instruction memory using pio_add_program.

    PIO code is very dense and often the functionality cannot be seen from the PIO code itself as it is defined by the PIO configuration – e.g. pins to use, frequency of execution, direction of shifts and so on.

    I’ve not touched on it here, but the use of PIO and DMA (direct memory access) often go hand in hand to create completely CPU-free means of getting data in and out of a RP2040/RP2350 system. A really good example of this is Piers Rocks’ OneROM (see this video for a brilliant summary of how this works: https://www.youtube.com/watch?v=Y8RODQZM2HY).

    Finally I need to remember that ISR stands for Input Shift Register and not Interrupt Service Routine…

    Kevin

    #pio #raspberryPiPico #rp2040 #rp2350
  10. PIO on the Raspberry Pi Pico

    Every time I’ve started to approach the use of the programmable IO (PIO) subsystem on the RP2040 or RP2350 (as used on the Raspberry Pi Pico), I’ve found myself essentially starting from scratch again and the examples quite opaque to me.

    So this time as I’ve worked through it yet again, I’ve decided to write it all down 🙂

    Here are some existing tutorials and projects that talk about getting going with the PIO:

    Assembling PIO Code

    The PIO has its own bespoke micro-instruction set that is very similar to many types of assembly language and it requires its own pio assembler to process it. The basic sequence is as follows:

    • PIO -> pioasm -> C header file -> include in C/C++ project and build

    There are options for writing PIO in both Micropython and Circuitpython, which I have done in the past, but I’m sticking with the C route here. This requires the pioasm to take PIO code and produce a C header file that can then be included in a C project.

    To use the RP2040/2350 Arduino environment, it is necessary to process PIO independently and then add the C file to the Arduino project. The Raspberry Pi C/C++ SDK can process PIO files directly as part of the main build.

    There is also an option to use hardware SDK functions for dynamic creation of PIO code at runtime. The functions are a series of pio_encode_XX() functions representing the different PIO instructions as listed here: https://www.raspberrypi.com/documentation/pico-sdk/hardware.html#group_pio_instructions

    There are two other novel approaches I found so far too:

    The first is an online editing environment that creates the required processed PIO related code for the C/C++ SDK or Python which can then be included in your build environment as required.

    The second is an alternative run-time approach that uses a range of C macros to allow the “assembling” of PIO code as part of the run-time execution. It does this by directly creating the HEX equivalents of PIO instructions, thereby effectively assembling in the fly. This means that the PIO code can be customised to the specific run-time situation.

    At this stage I’m not sure what it gives over using the pio_encode_ SDK functions directly. I do note however there is an equivalent PIO emulator which means this approach will run equally well on real hardware or in emulation. I’ve bookmarked this to come back to at some point.

    Running PIO Code

    Regardless of how the PIO instructions become code, to use them requires setting up and configuring the PIO state machines at run time as part of a project. A common approach is to include an initialisation function within the PIO code itself that is destined for passing straight through to the C/C++ SDK. This will have access to all definitions used within the PIO code and also allows the appropriate configuration information to remain encapsulated with the code.

    But I have to admit I find there is an awful lot of assumed “magic” going on when configuring and getting running PIO programs and state machines. And whilst there are plenty of examples to study, I don’t find that they are written so as to teach. Consequently, I’ve noted the following as “reminders to self” on how to read some of the examples. It doesn’t help that the SDK function list is very long and there are several ways to achieve the same things.

    Taking the PIO PWM code from the pico_examples as a starting point (https://github.com/raspberrypi/pico-examples/tree/master/pio/pwm), I’ve added in some comments containing the full function prototypes for some of the calls to make them a bit easier to walk through.

    pwm.pio:

    ;
    ; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
    ;
    ; SPDX-License-Identifier: BSD-3-Clause
    ;

    ; Side-set pin 0 is used for PWM output

    .program pwm
    .side_set 1 opt

    pull noblock side 0 ; Pull from FIFO to OSR if available,
    ; else copy X to OSR.
    mov x, osr ; Copy most-recently-pulled value
    ; back to scratch X
    mov y, isr ; ISR contains PWM period.
    ; Y used as counter.
    countloop:
    jmp x!=y noset ; Set pin high if X == Y,
    ; keep the two paths length matched
    jmp skip side 1
    noset:
    nop ; Single dummy cycle to keep
    ; the two paths the same length
    skip:
    jmp y-- countloop ; Loop until Y hits 0,
    ; then pull a fresh PWM value from FIFO

    % c-sdk {
    static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) {

    // static void pio_gpio_init (PIO pio, uint pin)
    pio_gpio_init(pio, pin);

    // int pio_sm_set_consecutive_pindirs
    // (PIO pio, uint sm, uint pins_base, uint pin_count, bool is_out)
    pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);

    // A piece of pioasm "magic" based on .program pwm
    // (see following notes)
    pio_sm_config c = pwm_program_get_default_config(offset);

    // static void sm_config_set_sideset_pins
    // (pio_sm_config *c, uint sideset_base)
    sm_config_set_sideset_pins(&c, pin);

    // int pio_sm_init (PIO pio, uint sm,
    // uint initial_pc, const pio_sm_config *config)
    pio_sm_init(pio, sm, offset, &c);
    }
    %}

    The above PIO code assembles down into the following (this is using the online PIO assembler):

    // -------------------------------------------------- //
    // This file is autogenerated by pioasm; do not edit! //
    // -------------------------------------------------- //

    #pragma once

    #if !PICO_NO_HARDWARE
    #include "hardware/pio.h"
    #endif

    // --- //
    // pwm //
    // --- //

    #define pwm_wrap_target 0
    #define pwm_wrap 6

    static const uint16_t pwm_program_instructions[] = {
    // .wrap_target
    0x9080, // 0: pull noblock side 0
    0xa027, // 1: mov x, osr
    0xa046, // 2: mov y, isr
    0x00a5, // 3: jmp x != y, 5
    0x1806, // 4: jmp 6 side 1
    0xa042, // 5: nop
    0x0083, // 6: jmp y--, 3
    // .wrap
    };

    #if !PICO_NO_HARDWARE
    static const struct pio_program pwm_program = {
    .instructions = pwm_program_instructions,
    .length = 7,
    .origin = -1,
    };

    static inline pio_sm_config pwm_program_get_default_config(uint offset) {
    pio_sm_config c = pio_get_default_sm_config();
    sm_config_set_wrap(&c, offset + pwm_wrap_target,
    offset + pwm_wrap);
    sm_config_set_sideset(&c, 2, true, false);
    return c;
    }

    static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) {
    // static void pio_gpio_init (PIO pio, uint pin)
    pio_gpio_init(pio, pin);
    // int pio_sm_set_consecutive_pindirs
    // (PIO pio, uint sm, uint pins_base, uint pin_count, bool is_out)
    pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
    // A piece of pioasm "magic" based on .program pwm
    // (see following notes)
    pio_sm_config c = pwm_program_get_default_config(offset);
    // static void sm_config_set_sideset_pins
    // (pio_sm_config *c, uint sideset_base)
    sm_config_set_sideset_pins(&c, pin);
    // int pio_sm_init (PIO pio, uint sm,
    // uint initial_pc, const pio_sm_config *config)
    pio_sm_init(pio, sm, offset, &c);
    }

    #endif

    Some key points to note about this “assembled” code:

    • Notice the appearance of pwm_program_get_default_config() which was already referenced in the passed-through C code.
    • Notice also that the “get” function has the side effect of actually setting some state machine configuration parameters in the returned configuration too! Worse for me, it isn’t actually part of the configuration at this stage – it is just in the configuration that is returned by the function…
      • The engineer in me hates this… I hate seeing a function that implies a benign function (“get”) that actually changes something critical… by I digress.
    • A key parameter that gets set are the wrap parameters via sm_config_set_wrap(). This has to be updated once the actual code offset in the PIO instruction memory is known, hence I guess why this is set at run time.
    • The patched in parameters (wrap and sideset in this case) won’t actually take effect until the configuration is finally set in the provided pass-through C code via the call to pio_sm_init().

    Here is its associated C code pwm.c:

    /**
    * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
    *
    * SPDX-License-Identifier: BSD-3-Clause
    */

    #include <stdio.h>

    #include "pico/stdlib.h"
    #include "hardware/pio.h"
    #include "pwm.pio.h"

    // Write `period` to the input shift register
    void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) {
    pio_sm_set_enabled(pio, sm, false);
    pio_sm_put_blocking(pio, sm, period);
    pio_sm_exec(pio, sm, pio_encode_pull(false, false));
    pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
    pio_sm_set_enabled(pio, sm, true);
    }

    // Write `level` to TX FIFO. State machine will copy this into X.
    void pio_pwm_set_level(PIO pio, uint sm, uint32_t level) {
    pio_sm_put_blocking(pio, sm, level);
    }

    int main() {
    stdio_init_all();
    #ifndef PICO_DEFAULT_LED_PIN
    #warning pio/pwm example requires a board with a regular LED
    puts("Default LED pin was not defined");
    #else

    // todo get free sm
    PIO pio = pio0;
    int sm = 0;
    uint offset = pio_add_program(pio, &pwm_program);
    printf("Loaded program at %d\n", offset);

    pwm_program_init(pio, sm, offset, PICO_DEFAULT_LED_PIN);
    pio_pwm_set_period(pio, sm, (1u << 16) - 1);

    int level = 0;
    while (true) {
    printf("Level = %d\n", level);
    pio_pwm_set_level(pio, sm, level * level);
    level = (level + 1) % 256;
    sleep_ms(10);
    }
    #endif
    }

    There are a few key things to remember to make sense of these examples:

    • The offset that is talked about is the location within the shared 32 instruction program area and is used to refer back to the installed PIO program. It is returned from pio_add_program().
    • A PIO .program directive becomes a default C like directive on processing by pioasm. This results in two obscure bits of “magic” coding going on meaning in this case that “.program pwm” in the PIO file becomes “pwm_program” in C/C++:
      • pio_program_t pwm_program is a C structure which can then be referenced from the C code as shown in the line pio_add_program(pio, &pwm_program).
      • static inline pio_sm_config pwm_program_get_default_config(uint offset) is a C function based on pio_get_default_sm_config() that returns the PIO configuration for the specific PIO program in question – in this case of course the pwm program (see previous notes).
    • The use of .side_step opt means that not every PIO instruction has to have a side step instruction too.
    • The PIO refers to an abstract group of pins, but it is the configuration which is part of the C/C++ SDK that determines which pins are used.
    • The %c-sdk { … %} pairing signifies that this part of the PIO code will be passed straight onto the C/C++ SDK.
    • There are multiple ways of initialising GPIO pins and directions. In this example it doesn’t use pindirs in the PIO code but uses pio_sm_set_consecutive_pindirs() in the C code.
    • This example uses hardcoded references to PIO 0 and SM 0, but in many cases the PIO and SM would be chosen dynamically using API calls such as the following:
      • pio_claim_free_sm_and_add_program ()
      • pio_claim_free_sm_and_add_program_for_gpio_range()
      • pio_claim_unused_sm()
    • Each PIO program has a default configuration associated with it which can be updated. A typical pattern is shown here where the default configuration is grabbed using (in this case) pwm_program_get_default_config() and then updated by passing into SDK calls that follow.
    • The state machine is finally set running with the required configuration using pio_sm_init();

    There is one additional mix of techniques that is worth pulling out here. In the C code the function pio_pwm_set_period() is used to update the PWM period which it has to do by passing it into the SM via the FIFO. It is using some SM manipulation routines and then some inline, run-time PIO code, to achieve this.

    void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) {
    pio_sm_set_enabled(pio, sm, false);
    pio_sm_put_blocking(pio, sm, period);
    pio_sm_exec(pio, sm, pio_encode_pull(false, false));
    pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
    pio_sm_set_enabled(pio, sm, true);
    }

    Again some pretty confusing API calls, especially giving this is meant to be an example, but essentially what is going on (I think) is:

    Disable the statemachine by using pio_sm_set_enabled(... false).
    Push the period value into the TX FIFO, blocking if full to wait for it to be empty.

    Execute two direct PIO instructions using pio_sm_exec():
    This uses pio_encode_pull and pio_encode_out to run the following PIO code:
    pull noblock ; non-blocking pull
    out isr, 32 ; out 32 bits to the interrupt shift register

    Re-enable he state machine using pio_sm_set_enabled(... true).

    By default anything sent to the FIFO is written to the X register and used to set the duty cycle of the PWM. But this code creates some temporary PIO code to receive the contents of the FIFO and put it into ISR instead. Of course it has to temporarily suspend the execution of the stored PIO code in order to do this.

    I really dislike the nomenclature of “set enabled (false)” as an API approach. I’d much prefer to see something like pio_sm_enable() and pio_sm_disable() myself. I suppose they haven’t done this due to the large increase in API functions it creates.

    I guess this is personal preference, but I do find that it adds to the opaqueness of much of the example code when it doesn’t read naturally.

    So To Recap…

    Writing PIO code can be done at build time (from Python or C/C++ using pioasm or an online assembler) or run time (using pio_encode_ functions or maybe APIO).

    pioasm bridges the gap between PIO code and C/C++ including creating two magic C/C++ constructs: pwm_program for the code and pwm_program_get_default_config() to return the created PIO configuration.

    PIO and SMs can be allocated by the system using a range of “claim” functions. There are 2 PIOs on the RP2040 and 3 on the RP2350, each with its own 32 instruction program memory and each with four state machines.

    It can be useful to include an initialisation routine, that configures and starts the PIO program, within the PIO code for use from the C/C++ code using % c-sdk { … %}.

    The PIO program is added into the system and given an offset in instruction memory using pio_add_program.

    PIO code is very dense and often the functionality cannot be seen from the PIO code itself as it is defined by the PIO configuration – e.g. pins to use, frequency of execution, direction of shifts and so on.

    I’ve not touched on it here, but the use of PIO and DMA (direct memory access) often go hand in hand to create completely CPU-free means of getting data in and out of a RP2040/RP2350 system. A really good example of this is Piers Rocks’ OneROM (see this video for a brilliant summary of how this works: https://www.youtube.com/watch?v=Y8RODQZM2HY).

    Finally I need to remember that ISR stands for Input Shift Register and not Interrupt Service Routine…

    Kevin

    #pio #raspberryPiPico #rp2040 #rp2350
  11. @Edent ah. Fair warning this is a rabbit hole.

    There is some stuff on Mastodon, lots on Reddit or Discord.

    Specifically on firmware, #MechanicalKeyboard smaller volume productions and DIY creations often use #QMK (typically wired, sadly not all follow the GPL requirements) or #ZMK (typically with Bluetooth) under the hood. There are more like KMK or CircuitPython.

    Often there’s a GUI for changing the keymap, as a native app or web based (Chrome). See eg Via, Vial, or the currently less capable but catching up ZMK Studio.

  12. I realized I had an unused PyPortal around, so yesterday evening I quickly hacked together a (solar, battery, house) power display. Pushing the data to it using MQTT.

    Obviously want to make it prettier and include some more values on it, but quite functional as a first test.

    The circuitpython doc is still as hard to search as before, it could use a browsable list of libraries
    #iot #solar #powermonitoring

  13. Finally calling this done! Say hello to the HellSplit a hand-wired, asymmetric 40% split #ortholinear keyboard with vertical column stagger and two rotary encoders. Powered by a pair of Raspberry Pi Pico's #RP2040 and #KMK firmware

    #mechanicalkeyboards #splitkeyboard #ergo #otho #mechkb #splitkb #pipico #raspberypipico #40percent #handwired #circuitpython @circuitpython @RaspberryPi @rpimag

  14. Finally calling this done! Say hello to the HellSplit a hand-wired, asymmetric 40% split #ortholinear keyboard with vertical column stagger and two rotary encoders. Powered by a pair of Raspberry Pi Pico's #RP2040 and #KMK firmware

    #mechanicalkeyboards #splitkeyboard #ergo #otho #mechkb #splitkb #pipico #raspberypipico #40percent #handwired #circuitpython @circuitpython @RaspberryPi @rpimag

  15. Finally calling this done! Say hello to the HellSplit a hand-wired, asymmetric 40% split #ortholinear keyboard with vertical column stagger and two rotary encoders. Powered by a pair of Raspberry Pi Pico's #RP2040 and #KMK firmware

    #mechanicalkeyboards #splitkeyboard #ergo #otho #mechkb #splitkb #pipico #raspberypipico #40percent #handwired #circuitpython @circuitpython @RaspberryPi @rpimag

  16. Finally calling this done! Say hello to the HellSplit a hand-wired, asymmetric 40% split #ortholinear keyboard with vertical column stagger and two rotary encoders. Powered by a pair of Raspberry Pi Pico's #RP2040 and #KMK firmware

    #mechanicalkeyboards #splitkeyboard #ergo #otho #mechkb #splitkb #pipico #raspberypipico #40percent #handwired #circuitpython @circuitpython @RaspberryPi @rpimag

  17. XIAO ESP32-C3 MIDI Synthesizer – Part 3

    So, now that the basics are out of the way I’m going to dive into the specifics of the Dream SAM2695 chip itself.

    • Part 1 – Getting started and getting code running.
    • Part 2 – Swapping the ESP32-C3 for a SAMD21 to get USB MIDI.
    • Part 3 – Taking a deeper look at the SAM2695 itself.
    • Part 4 – A USB MIDI Synth Module using the SAMD21 again as a USB MIDI Host.
    • Part 5 – A Serial MIDI Synth Module using the original ESP32-C3.
    • Part 6 – Pairs the Synth with a XIAO Expansion board to add display and potentiometers.

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    These are the key tutorials for the main concepts used in this project:

    If you are new to microcontrollers, see the Getting Started pages.

    The Dream SAM2695

    I first encountered this device on a breakout board several years ago, called a “GM Mini Module”. Various tutorials at the time showed how to hook this up to an Arduino. For example, my specific module had the following suggested application:

    The breakout was essentially just the SAM2695 with an additional MIDI IN circuit and DIN socket.

    At the time it was a component of the “fluxamasynth” – an Arduino shield containing the chip. Whilst the shield is now discontinued, a number of resources still remain online about it: https://github.com/fluxly/Fluxamasynth

    Since then there have been a number of modules released that use this neat synth chip, probably the most recognisable of which is perhaps the M5 MDI Synth: https://shop.m5stack.com/products/midi-synthesizer-unit-sam2695

    There is also a M5 module with built-in MIDI DIN socket too.

    This latest XIAO device follows in the footsteps of all these other variations but adds the microcontroller, and ESP32-C3, to make a complete “system”.

    I happen to have three of these devices – the original Dream “GM mini module”, M5 synth and the XIAO, so I’ll do a post about them together at some point, but the all work in essentially the same way.

    This series of posts is mostly taking a detailed look at the XIAO MIDI Synthesizer, but the following post shows how to use a XIAO SAMD21 to talk to the M5 Stack Synth: XIAO USB Device to Serial MIDI Converter.

    The datasheet for the SAM2695 describes its capabilities:

    • MIDI control processor, serial and parallel interface
    • Synthesis, General MIDI wavetable implementation
    • General MIDI compatible effects: reverb + chorus
    • Spatial Effect
    • 4-band stereo equalizer
    • Stereo DAC. DR: 86dB typ, THD+N: -75dB typ
    • Mike input. DR: 86dB typ, THD+N: -75dB typ
    • Mike echo
    • 64-voice polyphony (without effects)
    • 38-voice polyphony + effects
    • On-chip CleanWave™ wavetable data, firmware, RAM delay lines
    • Audio stereo line output

    Interestingly it supports a parallel or serial MIDI access. But using serial is by far the easiest as it can link up directly to another microcontroller’s UART or an opto-isolator of a standard MIDI IN circuit.

    The GM Sound Engine

    The SAM2695 has two complete instrument banks and a drum set. The banks are as follows:

    • CH1-9,11-16: BANKSEL 0: Full General MIDI Sound set
    • CH1-9,11-16: BANKSEL 127: MT-32 Compatible Sound set
    • CH10: General MIDI Drum Sound set:
      • Program 1: Normal kit, notes 35-81
      • Program 17: “Power Set”, Normal set + note 38 only
      • Program 41: “Brush”, Normal set + notes 35-40 changed
      • Program 49: “Orchestra”, Normal set + notes 27-30, 38-53, 88 changed
      • Program 128: “CM 64/32 Partial”, range of effects and things – MT-32 like

    The device also supports effects and an equaliser. All additional controls are configured using either MIDI NRPN or SysEx message. And there are a lot of messages defined in the datasheet!

    There are also a number of control messages which apparently have to be sent over the parallel data interface.

    But what I am most interested in is the MIDI implementation for the common range of MIDI messages, the most useful of which (to me) are listed below.

    MIDI MessageHEXNotesCompatibilityNote On9n kk vvMIDINote Off8n kk vvMIDIPitch BendEn ll hh14-bit pitch bend data hhllGMProgram ChangeCn ppGM/GSChannel AftertouchDn vvMIDIControl ChangeBn cc ddSee table below for specific control change messagesRPNBn 65 …Registered parameter numbers (see datasheet)MIDI/GMNRPNBn 63 …Non-registered parameter numbers (see datasheet)GS/DREAMSysExF0 7E 7F 09 01 F7MIDI ResetGMSysExF0 7F 7F 04 01 00 vv F7Master volumeGMSysExF0 41 00 42 12 … F7Range of GS specific SysEx messages (see datasheet)GSSysExF0 00 20 00 00 … F7Dream specific “port write” commandDREAM

    MIDI Control Change Messages

    CommandHEXDefaults / NotesBank SelectBn 00 cc0ModulationBn 01 cc0 (Rate/Depth set via SysEx)Portamento TimeBn 05 ccChannel VolumeBn 07 cc100PanBn 0A cc64ExpressionBn 0B cc127Sustain PedalBn 40 cc0 (>63 ON)PortamentoBn 41 cc0 (>63 ON)Sostenuto PedalBn 42 cc0 (>63 ON)Soft PedalBn 43 cc0 (>63 ON)ReverbBn 50 vv4 (0..7 = reverb effects)ChorusBn 51 vv2 (0..7 = chorus effects)Reverb SendBn 5B vvChorus SendBn 5D vvAll Sound OffBn 78 00Reset All ControllersBn 79 00All Notes OffBn 7B 00Mono OnBn 7E 00Poly OnBn 7F 00Defaults to poly on power upCC1Bn cc vvcc=00..5F, Fn set by SysExCC2Bn cc vvcc=00..5F, Fn set by SysEx

    To be honest, I’m not sure I quite understand those last two, but that seems to be what it is saying in the datasheet…

    There are a /lot/ of parameters accessible over NRPN or SysEx relating to the routing of signals in the device, the effects and the equaliser. At this point I’m just experimenting with the basics above.

    Any of the MIDI interfaces allows me to test it out, but using a XIAO SAMD21 in USB MIDI device mode is the easiest as I can just plug it into a computer and fire up MIDIOx and start messing around with some of the above.

    Closing Thoughts

    There is a lot to the SAM2695, so I can see why it has captured interest once again after a few years seeming almost forgotten.

    It would be nice to build some kind of knob-based interface that supports many of the parameters in the synth, but I guess the major application is meant to be for when some kind of “compatible” sound card is required “off the shelf”. For that, it would appear that the SAM2695 can be a General MIDI Synth, a Roland “General Sound” (GS) Synth or even a Roland MT-32.

    Not bad for a small, relatively easily integrated component!

    Kevin

    #generalMidi #midi #mt32 #rolandGs #SAM2695

  18. XIAO ESP32-C3 MIDI Synthesizer – Part 3

    So, now that the basics are out of the way I’m going to dive into the specifics of the Dream SAM2695 chip itself.

    • Part 1 – Getting started and getting code running.
    • Part 2 – Swapping the ESP32-C3 for a SAMD21 to get USB MIDI.
    • Part 3 – Taking a deeper look at the SAM2695 itself.
    • Part 4 – A USB MIDI Synth Module using the SAMD21 again as a USB MIDI Host.
    • Part 5 – A Serial MIDI Synth Module using the original ESP32-C3.
    • Part 6 – Pairs the Synth with a XIAO Expansion board to add display and potentiometers.

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    These are the key tutorials for the main concepts used in this project:

    If you are new to microcontrollers, see the Getting Started pages.

    The Dream SAM2695

    I first encountered this device on a breakout board several years ago, called a “GM Mini Module”. Various tutorials at the time showed how to hook this up to an Arduino. For example, my specific module had the following suggested application:

    The breakout was essentially just the SAM2695 with an additional MIDI IN circuit and DIN socket.

    At the time it was a component of the “fluxamasynth” – an Arduino shield containing the chip. Whilst the shield is now discontinued, a number of resources still remain online about it: https://github.com/fluxly/Fluxamasynth

    Since then there have been a number of modules released that use this neat synth chip, probably the most recognisable of which is perhaps the M5 MDI Synth: https://shop.m5stack.com/products/midi-synthesizer-unit-sam2695

    There is also a M5 module with built-in MIDI DIN socket too.

    This latest XIAO device follows in the footsteps of all these other variations but adds the microcontroller, and ESP32-C3, to make a complete “system”.

    I happen to have three of these devices – the original Dream “GM mini module”, M5 synth and the XIAO, so I’ll do a post about them together at some point, but the all work in essentially the same way.

    This series of posts is mostly taking a detailed look at the XIAO MIDI Synthesizer, but the following post shows how to use a XIAO SAMD21 to talk to the M5 Stack Synth: XIAO USB Device to Serial MIDI Converter.

    The datasheet for the SAM2695 describes its capabilities:

    • MIDI control processor, serial and parallel interface
    • Synthesis, General MIDI wavetable implementation
    • General MIDI compatible effects: reverb + chorus
    • Spatial Effect
    • 4-band stereo equalizer
    • Stereo DAC. DR: 86dB typ, THD+N: -75dB typ
    • Mike input. DR: 86dB typ, THD+N: -75dB typ
    • Mike echo
    • 64-voice polyphony (without effects)
    • 38-voice polyphony + effects
    • On-chip CleanWave™ wavetable data, firmware, RAM delay lines
    • Audio stereo line output

    Interestingly it supports a parallel or serial MIDI access. But using serial is by far the easiest as it can link up directly to another microcontroller’s UART or an opto-isolator of a standard MIDI IN circuit.

    The GM Sound Engine

    The SAM2695 has two complete instrument banks and a drum set. The banks are as follows:

    • CH1-9,11-16: BANKSEL 0: Full General MIDI Sound set
    • CH1-9,11-16: BANKSEL 127: MT-32 Compatible Sound set
    • CH10: General MIDI Drum Sound set:
      • Program 1: Normal kit, notes 35-81
      • Program 17: “Power Set”, Normal set + note 38 only
      • Program 41: “Brush”, Normal set + notes 35-40 changed
      • Program 49: “Orchestra”, Normal set + notes 27-30, 38-53, 88 changed
      • Program 128: “CM 64/32 Partial”, range of effects and things – MT-32 like

    The device also supports effects and an equaliser. All additional controls are configured using either MIDI NRPN or SysEx message. And there are a lot of messages defined in the datasheet!

    There are also a number of control messages which apparently have to be sent over the parallel data interface.

    But what I am most interested in is the MIDI implementation for the common range of MIDI messages, the most useful of which (to me) are listed below.

    MIDI MessageHEXNotesCompatibilityNote On9n kk vvMIDINote Off8n kk vvMIDIPitch BendEn ll hh14-bit pitch bend data hhllGMProgram ChangeCn ppGM/GSChannel AftertouchDn vvMIDIControl ChangeBn cc ddSee table below for specific control change messagesRPNBn 65 …Registered parameter numbers (see datasheet)MIDI/GMNRPNBn 63 …Non-registered parameter numbers (see datasheet)GS/DREAMSysExF0 7E 7F 09 01 F7MIDI ResetGMSysExF0 7F 7F 04 01 00 vv F7Master volumeGMSysExF0 41 00 42 12 … F7Range of GS specific SysEx messages (see datasheet)GSSysExF0 00 20 00 00 … F7Dream specific “port write” commandDREAM

    MIDI Control Change Messages

    CommandHEXDefaults / NotesBank SelectBn 00 cc0ModulationBn 01 cc0 (Rate/Depth set via SysEx)Portamento TimeBn 05 ccChannel VolumeBn 07 cc100PanBn 0A cc64ExpressionBn 0B cc127Sustain PedalBn 40 cc0 (>63 ON)PortamentoBn 41 cc0 (>63 ON)Sostenuto PedalBn 42 cc0 (>63 ON)Soft PedalBn 43 cc0 (>63 ON)ReverbBn 50 vv4 (0..7 = reverb effects)ChorusBn 51 vv2 (0..7 = chorus effects)Reverb SendBn 5B vvChorus SendBn 5D vvAll Sound OffBn 78 00Reset All ControllersBn 79 00All Notes OffBn 7B 00Mono OnBn 7E 00Poly OnBn 7F 00Defaults to poly on power upCC1Bn cc vvcc=00..5F, Fn set by SysExCC2Bn cc vvcc=00..5F, Fn set by SysEx

    To be honest, I’m not sure I quite understand those last two, but that seems to be what it is saying in the datasheet…

    There are a /lot/ of parameters accessible over NRPN or SysEx relating to the routing of signals in the device, the effects and the equaliser. At this point I’m just experimenting with the basics above.

    Any of the MIDI interfaces allows me to test it out, but using a XIAO SAMD21 in USB MIDI device mode is the easiest as I can just plug it into a computer and fire up MIDIOx and start messing around with some of the above.

    Closing Thoughts

    There is a lot to the SAM2695, so I can see why it has captured interest once again after a few years seeming almost forgotten.

    It would be nice to build some kind of knob-based interface that supports many of the parameters in the synth, but I guess the major application is meant to be for when some kind of “compatible” sound card is required “off the shelf”. For that, it would appear that the SAM2695 can be a General MIDI Synth, a Roland “General Sound” (GS) Synth or even a Roland MT-32.

    Not bad for a small, relatively easily integrated component!

    Kevin

    #generalMidi #midi #mt32 #rolandGs #SAM2695

  19. Learn to send keyboard presses & Strings using a microcontroller. And as a challenge, learn how to create a game control pad using vegetables. youtu.be/5VkYCk3spv8?si=GTBwTE #STEMeducators, dig in. A free semester of beginner-friendly university-lessons for all! bit.ly/circuitpython-school

  20. Wow. Hadn’t expected a random unsolicited $100 thanks for my YouTube content today. I don’t solicit donations but it was super nice to receive. Will of course apply to my own learning. Am thinking more seriously about creating a summer bootcamp for #STEMeducators who want to teach iOS app building and/or maker programming with CircuitPython. Daunted by the startup of marketing, securing venue and tech backup, but definitely leaning toward this.

  21. More stickers going out today. If you build something from the build videos on my YouTube channel & tag me plus #BuiltWithProfG with a pic/vid of your goodness, you might be selected for weekly stickers drawing. And teachers, if you use my CircuitPython and/or Swift lessons I can send a stack to your class. #Makers #STEMeducators YouTube.com/profgallaugher

  22. #PyConUS Sprints for Tuesday:

    Room 308: #RouteE
    Route 309: #GnuMailman
    Room 310/311: #PiecesOS (#GenAI, #LLM tool), Strawberry GraphQL
    Room 315: #PyPA Packaging, Cloud Custodian
    Room 316: @beeware, #PyScript, #PalletsProject
    Room 317: aio-libs, @ppb, @takahe, @micropython, @circuitpython, Robots
    Room 318: Accelerated Python on GPU, Mesa (agent-based modeling + GIS)
    Room 319: #GDSFactory
    Room 320: #Python Core
    Room 321: @pydantic, #LogFire, #PyO3

    #PyCon #PyConUS2024

  23. I had to cut it short and didn’t have time for more new #robot pictures/video (so heres a silly older one with my #fpv headset on).

    I expect to resume this #wip tomorrow.

    #ALICEfpv is JUST about ready for her big show at #OpenSauce


    #robots #maker #makers #3Dprinting #electronics #circuitPython #kinect

  24. Hi all!

    Time for a re-#introduction.

    My name’s Scott & I make lovable #Robots.

    Current #WIP : prep to exhibit my bots at OpenSauce.Live #makers con.

    I’ll be showing off:

    #RoBud —chatty lovable, acessible, autonomous companion #robot
    &
    -#ALICEfpv —little #fpv, body-controlled avatar bot

    Follow me/tags for bot-centric #diy #3dprinting #maker baloney.

    Non-elephant links: scottsrobots.com

    #introductions #newhere #micropython #circuitpython #OpenSauce #RaspberryPi

  25. Hi all!

    Time for a re-#introduction.

    My name’s Scott & I make lovable #Robots.

    Current #WIP : prep to exhibit my bots at OpenSauce.Live #makers con.

    I’ll be showing off:

    #RoBud —chatty lovable, acessible, autonomous companion #robot
    &
    -#ALICEfpv —little #fpv, body-controlled avatar bot

    Follow me/tags for bot-centric #diy #3dprinting #maker baloney.

    Non-elephant links: scottsrobots.com

    #introductions #newhere #micropython #circuitpython #OpenSauce #RaspberryPi

  26. Hi all!

    Time for a re-#introduction.

    My name’s Scott & I make lovable #Robots.

    Current #WIP : prep to exhibit my bots at OpenSauce.Live #makers con.

    I’ll be showing off:

    #RoBud —chatty lovable, acessible, autonomous companion #robot
    &
    -#ALICEfpv —little #fpv, body-controlled avatar bot

    Follow me/tags for bot-centric #diy #3dprinting #maker baloney.

    Non-elephant links: scottsrobots.com

    #introductions #newhere #micropython #circuitpython #OpenSauce #RaspberryPi

  27. Hi all!

    Time for a re-#introduction.

    My name’s Scott & I make lovable #Robots.

    Current #WIP : prep to exhibit my bots at OpenSauce.Live #makers con.

    I’ll be showing off:

    #RoBud —chatty lovable, acessible, autonomous companion #robot
    &
    -#ALICEfpv —little #fpv, body-controlled avatar bot

    Follow me/tags for bot-centric #diy #3dprinting #maker baloney.

    Non-elephant links: scottsrobots.com

    #introductions #newhere #micropython #circuitpython #OpenSauce #RaspberryPi