/*
Version: V2.0
Author: Vincent
Create Date: 2021/7/21
Note:
2021/7/23 V2.0:Add PIO document.
*/

[toc]
Pico mechanical hand driver is a platform based on raspberry pi pico, which is designed for driving and controlling servos. The board comes with an 8-bit level translator and 8 channels GPIO headers that can connect the servo directly. Now you can build your new project about servo by trying out the Raspberry PI Pico.

[toc]
Most of the MCU have some kind of PWM outputs, for example the analogWrite() in Arduino, it output PWM to related pins, with UNO, these pins can be 3/5/6/9/10 and 11, to control the connected LED brightness , or motor speed.
But if these hardware pins already used by some other functions, such as the D11 in UNO(MOSI in SPI communication), most users simulate the PWM in firmware, which need hardware times/interrupts, which take big burden to the MCU. and seriously, when the PWM frequency get high, or multiple PWM are needed, the frequency get unstable, as the MCU maybe too busy to deal with them all, on time.
And more, for servo control, it may needs signal with 20ms frequency, with 0.1%~99.9% logic high duty, hardware PWM can not output such a signal, while with firmware simulating, it will take a lot of timer/interrupt and MCU ability, especially when it comes to multiple servo simultaneously. Here we introduce a simple way , based on the hardware PIO in RP2040(Raspberry Pi PICO), to control multiple servo without the MCU engaging in much.
The programmable input/output block (PIO) is a versatile hardware interface. It can support a variety of IO standards, including: - 8080 and 6800 parallel bus - I2C - 3-pin I2S - SDIO - SPI, DSPI, QSPI - UART - DPI or VGA (via resistor DAC)
There are 2 identical PIO blocks in RP2040. Each PIO block has dedicated connections to the bus fabric, GPIO and interrupt controller.

PIO is programmable in the same sense as a processor. There are two PIO blocks with four state machines each, that can independently execute sequential programs to manipulate GPIOs and transfer data. Unlike a general purpose processor, PIO state machines are highly specialised for IO, with a focus on determinism, precise timing, and close integration with fixed-function hardware. Each state machine is equipped with: - Two 32-bit shift registers – either direction, any shift count - Two 32-bit scratch registers - 4×32-bit bus FIFO in each direction (TX/RX), reconfigurable as 8×32 in a single direction - Fractional clock divider (16 integer, 8 fractional bits) - Flexible GPIO mapping - DMA interface, sustained throughput up to 1 word per clock from system DMA - IRQ flag set/clear/status
Each state machine, along with its supporting hardware, occupies approximately the same silicon area as a standard serial interface block, such as an SPI or I2C controller. However, PIO state machines can be configured and reconfigured dynamically to implement numerous different interfaces.
Making state machines programmable in a software-like manner allows more hardware interfaces to be offered in the same cost and power envelope and also presents a more familiar programming model, and simpler tool flow, to those who wish to exploit PIO’s full flexibility by programming it directly, rather than using a premade interface from the PIO library.
State machines' inputs and outputs are mapped to up to 32 GPIOs (limited to 30 GPIOs for RP2040), and all state machines have independent, simultaneous access to any GPIO. For example, the standard UART code allows TX, RX, CTS and RTS to be any four arbitrary GPIOs, and I2C permits the same for SDA and SCL. The amount of freedom available depends on how exactly a given PIO program chooses to use PIO’s pin mapping resources, but at the minimum, an interface can be freely shifted up or down by some number of GPIOs.
The four state machines execute from a shared instruction memory. System software loads programs into this memory, configures the state machines and IO mapping, and then sets the state machines running. PIO programs come from various sources:
From this point on, state machines are generally autonomous, and system software interacts through DMA, interrupts and control registers, as with other peripherals on RP2040. For more complex interfaces, PIO provides a small but flexible set of primitives which allow system software to be more hands-on with state machine control flow.

PIO state machines execute short, binary programs.
Programs for common interfaces, such as UART, SPI, or I2C, are available in the PIO library, so in many cases, it is not necessary to write PIO programs. However, the PIO is much more flexible when programmed directly, supporting a wide variety of interfaces which may not have been foreseen by its designers.
Output 2 PWM with diff frequency, with Thonny IDE MicroPyhon
Full code is in : PICO_Merchanical_Hand_Driver/Arm6axis/
Open Thonny, Create a new Python file, inputs the follows code and excute:
from rp2 import PIO, StateMachine, asm_pio
from machine import Pin
import time
@asm_pio(set_init=PIO.OUT_LOW)
def pwm_1():
set(pins, 1)
set(pins, 0)
def test():
sm1 = StateMachine(1, pwm_1, freq=1000000, set_base=Pin(0))
sm1.active(1)
while 1:
print("Run....")
time.sleep(5)
test()
Test the GPIO0 with Oscilloscope:

PWM with frequency 500K Hz:

@asm_pio(set_init=PIO.OUT_LOW)
def pwm_1():
set(pins, 1)
set(pins, 0)
@asm_pio defines this function a PIO assembly function
set(pins, 1) is not a python funtion, but a PIO assembly function(we will explain it some later,together with other PIO assembly functions),it sets the logitic 1 and 0. each PIO assebly function take 1 PIO cycle, so there 2 assembly funtions here , wit 2 PIO cycles.
sm1 = StateMachine(1, pwm_1, freq=1000000, set_base=Pin(0))
sm1.active(1)
initiate a state machine object , and set the machine state code, assembly function, frequency and the related pins. There totally 2 PIO in RP2040, and 4 state machine each, so the machine state code 0~7
freq sets the PIO frequency,1MHz,so the cycle 1us。
set_base sets PIO GPIO to 0
sm1.active(1) to open the state machine.
The PIO outputs PWM with 2 us cycle(500 KHz), as the Oscilloscope, the advantagtes are:
a. Much precisly than simulation with software
b. Easy to change the frequency
c. Do not take hardware ablity , timers/ interrupts/ MCU.
Open Thonny, Create a new Python file, inputs the follows code and excute:
from rp2 import PIO, StateMachine, asm_pio
from machine import Pin
import time
@asm_pio(set_init=PIO.OUT_LOW)
def pwm_1():
set(pins, 1)
set(pins, 0)
@asm_pio(set_init=PIO.OUT_LOW)
def pwm_2():
set(pins, 1)[2]
set(pins, 0)[6]
def test():
sm1 = StateMachine(1, pwm_1, freq=1000000, set_base=Pin(0))
sm1.active(1)
sm2 = StateMachine(2, pwm_2, freq=5000000, set_base=Pin(15))
sm2.active(1)
while 1:
print("Run....")
time.sleep(5)
test()
The outputs:

2 PWM with same frequency, GPIO1 duty 50% and GPIO15 duty 30%
@asm_pio(set_init=PIO.OUT_LOW)
def pwm_2():
set(pins, 1)[2]
set(pins, 0)[6]
In the set(pins, 1)[2], it means 2 cycle delays after the commaand set, equivalent to NOP:
@asm_pio(set_init=PIO.OUT_LOW)
def pwm_2():
set(pins, 1)
nop()
nop()
set(pins, 0)
nop()
nop()
nop()
nop()
nop()
nop()
10 commands totally.
sm2 = StateMachine(2, pwm_2, freq=5000000, set_base=Pin(15))
sm2.active(1)
Here the state machine #2 was used, and frequency set to 5MHz. As simple caculation, the logic 10.6us, logic 0 1.4us, that is , PWM with 2 us cyle, 30% duty, same as the outputs from the oscilloscope.
There 9 commands in PIO:

Set program counter to Address if Condition is true, otherwise no operation.
Condition:
Stall until some condition is met.
Polarity:
Source: what to wait on. Values are:
Index: which pin or bit to check.
Shift Bit count bits from Source into the Input Shift Register (ISR). Shift direction is configured for each state machine by SHIFTCTRL_IN_SHIFTDIR. Additionally, increase the input shift count by Bit count, saturating at 32.
Source:
Bit count: How many bits to shift into the ISR. 1…32 bits, 32 is encoded as 00000.
If automatic push is enabled, IN will also push the ISR contents to the RX FIFO if the push threshold is reached (SHIFTCTRL_PUSH_THRESH). IN still executes in one cycle, whether an automatic push takes place or not. The state machine will stall if the RX FIFO is full when an automatic push occurs. An automatic push clears the ISR contents to all-zeroes, and clears the input shift count.
IN always uses the least significant Bit count bits of the source data. For example, if PINCTRL_IN_BASE is set to 5, the instruction IN PINS, 3 will take the values of pins 5, 6 and 7, and shift these into the ISR. First the ISR is shifted to the left or right to make room for the new input data, then the input data is copied into the gap this leaves. The bit order of the input data is not dependent on the shift direction.
NULL can be used for shifting the ISR’s contents. For example, UARTs receive the LSB first, so must shift to the right. After 8 IN PINS, 1 instructions, the input serial data will occupy bits 31…24 of the ISR. An IN NULL, 24 instruction will shift in 24 zero bits, aligning the input data at ISR bits 7…0. Alternatively, the processor or DMA could perform a byte read from FIFO address + 3, which would take bits 31…24 of the FIFO contents.
Shift Bit count bits out of the Output Shift Register (OSR), and write those bits to Destination. Additionally, increase the output shift count by Bit count, saturating at 32.
Destination:
Bit count: how many bits to shift out of the OSR. 1…32 bits, 32 is encoded as 00000.
A 32-bit value is written to Destination: the lower Bit count bits come from the OSR, and the remainder are zeroes. This value is the least significant Bit count bits of the OSR if SHIFTCTRL_OUT_SHIFTDIR is to the right, otherwise it is the most significant bits.
PINS and PINDIRS use the OUT pin mapping.
If automatic pull is enabled, the OSR is automatically refilled from the TX FIFO if the pull threshold, SHIFTCTRL_PULL_THRESH, is reached. The output shift count is simultaneously cleared to 0. In this case, the OUT will stall if the TX FIFO is empty, but otherwise still executes in one cycle.
OUT EXEC allows instructions to be included inline in the FIFO datastream. The OUT itself executes on one cycle, and the instruction from the OSR is executed on the next cycle. There are no restrictions on the types of instructions which can be executed by this mechanism. Delay cycles on the initial OUT are ignored, but the executee may insert delay cycles as normal.
OUT PC behaves as an unconditional jump to an address shifted out from the OSR.
Push the contents of the ISR into the RX FIFO, as a single 32-bit word. Clear ISR to all-zeroes.
PUSH IFFULL helps to make programs more compact, like autopush. It is useful in cases where the IN would stall at an inappropriate time if autopush were enabled, e.g. if the state machine is asserting some external control signal at this point.
The PIO assembler sets the Block bit by default.
$ claude mcp add PICO_Merchanical_Hand_Driver \
-- python -m otcore.mcp_server <graph>