From c796052a90e22d15c6c233c5396843493ed0ea5d Mon Sep 17 00:00:00 2001 From: andryblack Date: Wed, 23 Oct 2024 20:25:54 +0300 Subject: [PATCH] [rp] PIO with compile-time assembler --- examples/rp_pico/pio_ws2812/main.cpp | 150 ++++++ examples/rp_pico/pio_ws2812/project.xml | 10 + src/modm/platform/pio/rp/module.lb | 61 +++ src/modm/platform/pio/rp/pio.cpp.in | 39 ++ src/modm/platform/pio/rp/pio.hpp.in | 98 ++++ src/modm/platform/pio/rp/pio_asm.hpp | 564 +++++++++++++++++++++++ src/modm/platform/pio/rp/pio_program.hpp | 33 ++ src/modm/platform/pio/rp/pio_sm.hpp | 233 ++++++++++ 8 files changed, 1188 insertions(+) create mode 100644 examples/rp_pico/pio_ws2812/main.cpp create mode 100644 examples/rp_pico/pio_ws2812/project.xml create mode 100644 src/modm/platform/pio/rp/module.lb create mode 100644 src/modm/platform/pio/rp/pio.cpp.in create mode 100644 src/modm/platform/pio/rp/pio.hpp.in create mode 100644 src/modm/platform/pio/rp/pio_asm.hpp create mode 100644 src/modm/platform/pio/rp/pio_program.hpp create mode 100644 src/modm/platform/pio/rp/pio_sm.hpp diff --git a/examples/rp_pico/pio_ws2812/main.cpp b/examples/rp_pico/pio_ws2812/main.cpp new file mode 100644 index 0000000000..a037541f67 --- /dev/null +++ b/examples/rp_pico/pio_ws2812/main.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2016, Sascha Schade + * Copyright (c) 2017, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include +using namespace Board; + +/* from https://github.com/raspberrypi/pico-examples/blob/master/pio/ws2812/ws2812.pio */ + +// constants WS2812 +// static constexpr uint32_t T0H = 350; // ns +// static constexpr uint32_t T0L = 800; +// static constexpr uint32_t T1H = 700; +// static constexpr uint32_t T1L = 600; + +// constants WS2812B +static constexpr uint32_t T0H = 400; // ns +static constexpr uint32_t T0L = 850; +static constexpr uint32_t T1H = 850; +static constexpr uint32_t T1L = 400; + +static constexpr uint32_t TimeScale = 50; + +static constexpr uint32_t TH_Common = T0H/TimeScale; +static constexpr uint32_t TH_Add = (T1H-T0H)/TimeScale; +static constexpr uint32_t TL_Common = T1L/TimeScale; +static constexpr uint32_t TL_Add = (T0L-T1L)/TimeScale; + +static constexpr uint32_t T3 = 12;// + +// labels +struct bitloop {}; +struct do_one {}; +struct do_zero {}; + + +/* +.wrap_target +bitloop: + out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls + jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse +do_one: + jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse +do_zero: + nop side 0 [T2 - 1] ; Or drive low, for a short pulse +.wrap +*/ +// PIO program +static constexpr auto pio_prog = modm::platform::PIOProgram::begin() + .sideset<1>() + .wrapTarget() + .label() + .instr(pio::Out().x<1>() .side<0>().delay()) + .instr(pio::Jmp().not_x().to() .side<1>().delay()) + .label() + .instr(pio::Jmp().to() .side<1>().delay()) + .label() + .instr(pio::Nop() .side<0>().delay()) + .wrap() + .end(); + + +struct HW +{ + using PIO = modm::platform::Pio0; + using PIO_SM = PIO::StateMachine<0>; + using DataGpio = modm::platform::GpioOutput23; +}; + +static inline void write(uint32_t d) { + while (HW::PIO_SM::txFifoFull()) {__NOP();} + HW::PIO_SM::write(d << 8); +} + +int +main() +{ + + + Board::initialize(); + + + HW::DataGpio::setOutput(Gpio::OutputType::PushPull, Gpio::SlewRate::Fast); + HW::DataGpio::setDriveStrength(Gpio::DriveStrength::mA_12); + + auto pio_prog_offset = HW::PIO::addProgram(pio_prog); + + HW::PIO::connect(); + + HW::PIO_SM::addOutput(); + + + HW::PIO_SM::config() + .setup(pio_prog_offset,pio_prog) + .setSidesetPins() + .setFifoJoinTx() + .setOutShift() + .setFrequency() + .init(pio_prog_offset+pio_prog.getOffset()); + + HW::PIO_SM::setEnabled(true); + + constexpr auto delay_val = 5ms; + + while (true) + { + uint32_t clr = 0; + while (clr!=0xff0000) { + clr = clr + 0x010000; + write(clr); + modm::delay(delay_val); + } + while (clr!=0x000000) { + clr = clr - 0x010000; + write(clr); + modm::delay(delay_val); + } + while (clr!=0x00ff00) { + clr = clr + 0x000100; + write(clr); + modm::delay(delay_val); + } + while (clr!=0x000000) { + clr = clr - 0x000100; + write(clr); + modm::delay(delay_val); + } + while (clr!=0x0000ff) { + clr = clr + 0x000001; + write(clr); + modm::delay(delay_val); + } + while (clr!=0x000000) { + clr = clr - 0x000001; + write(clr); + modm::delay(delay_val); + } + + } + + return 0; +} diff --git a/examples/rp_pico/pio_ws2812/project.xml b/examples/rp_pico/pio_ws2812/project.xml new file mode 100644 index 0000000000..c932d97d93 --- /dev/null +++ b/examples/rp_pico/pio_ws2812/project.xml @@ -0,0 +1,10 @@ + + modm:rp-pico + + + + + modm:build:scons + modm:platform:pio:0 + + diff --git a/src/modm/platform/pio/rp/module.lb b/src/modm/platform/pio/rp/module.lb new file mode 100644 index 0000000000..22ff18f0c2 --- /dev/null +++ b/src/modm/platform/pio/rp/module.lb @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2024, Andrey Kunitsyn +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + + +class Instance(Module): + def __init__(self, instance): + self.instance = instance + + def init(self, module): + module.name = str(self.instance) + module.description = "PIO {} instance".format(self.instance) + + def prepare(self, module, options): + return True + + def build(self, env): + properties = { + "id": self.instance, + } + env.substitutions = properties + env.outbasepath = "modm/src/modm/platform/pio" + + env.template("pio.hpp.in", "pio_{}.hpp".format(self.instance)) + env.template("pio.cpp.in", "pio_{}.cpp".format(self.instance)) + + + +def init(module): + module.name = ":platform:pio" + module.description = "Programmable IO block (PIO)" + +def prepare(module, options): + device = options[":target"] + if not device.has_driver("pio:rp*"): + return False + + module.depends( + ":platform:gpio", + ":platform:clockgen", + ":architecture:interrupt") + + for instance in listify(device.get_driver("pio")["instance"]): + module.add_submodule(Instance(instance)) + + return True + +def build(env): + env.outbasepath = "modm/src/modm/platform/pio" + env.copy("pio_asm.hpp") + env.copy("pio_program.hpp") + env.copy("pio_sm.hpp") + pass diff --git a/src/modm/platform/pio/rp/pio.cpp.in b/src/modm/platform/pio/rp/pio.cpp.in new file mode 100644 index 0000000000..6113a4ba54 --- /dev/null +++ b/src/modm/platform/pio/rp/pio.cpp.in @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include +#include +#include +#include + +#include "../device.hpp" + +#include "pio_{{ id }}.hpp" + +// ---------------------------------------------------------------------------- + +namespace modm::platform +{ + uint32_t Pio{{ id }}::used_instruction_space = 0; +} + + + +// ---------------------------------------------------------------------------- +MODM_ISR(PIO{{ id }}_IRQ_0) +{ + +} + +MODM_ISR(PIO{{ id }}_IRQ_1) +{ + +} diff --git a/src/modm/platform/pio/rp/pio.hpp.in b/src/modm/platform/pio/rp/pio.hpp.in new file mode 100644 index 0000000000..7bffc1924c --- /dev/null +++ b/src/modm/platform/pio/rp/pio.hpp.in @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include +#include + +#include "pio_program.hpp" +#include "pio_sm.hpp" +#include + +namespace modm::platform +{ + + + /** + * Programmable IO block (PIO) + * + * @ingroup modm_platform_pio + * @author Andrey Kunitsyn + */ + class Pio{{ id }} : public ::modm::PeripheralDriver + { + static inline pio_hw_t& pio() { return *pio{{ id }}_hw; } + + static uint32_t used_instruction_space; + static int findOffset(const PIOProgram& prg) { + uint32_t program_mask = (1u << prg.length) - 1; + if (prg.origin >= 0) { + if (prg.origin > 32 - prg.length) return -1; + return used_instruction_space & (program_mask << prg.origin) ? -1 : prg.origin; + } else { + // work down from the top always + for (int i = 32 - prg.length; i >= 0; i--) { + if ((used_instruction_space & (program_mask << static_cast(i)))==0) { + return i; + } + } + return -1; + } + } + static void addProgramAtOffset(size_t offset,const PIOProgram& prg) { + for (uint8_t i = 0; i < prg.length; ++i) { + auto& instr = prg.instructions[i]; + pio().instr_mem[offset + i] = instr.store(offset); + } + uint32_t program_mask = (1u << prg.length) - 1; + used_instruction_space |= program_mask << offset; + } + + template + friend class pio::StateMachine; + + public: + static constexpr Peripheral peripherial = Peripheral::Pio{{ id }}; + template + class StateMachine : public modm::platform::pio::StateMachine {}; + + template< class... Signals > + static void + connect() + { + using Connector = GpioConnector; + Connector::connect(); + } + + template< class Pin > + static void connectPin() { + using Connector = GpioConnector; + Connector::connect(); + } + + template + static size_t addProgram(const Program& prg_data) { + auto prg = PIOProgram::get(prg_data); + auto offset = findOffset(prg); + if (offset < 0) { + return offset; + } + auto res = static_cast(offset); + addProgramAtOffset(res,prg); + return res; + } + }; + +} // namespace modm::platform + diff --git a/src/modm/platform/pio/rp/pio_asm.hpp b/src/modm/platform/pio/rp/pio_asm.hpp new file mode 100644 index 0000000000..51410566bd --- /dev/null +++ b/src/modm/platform/pio/rp/pio_asm.hpp @@ -0,0 +1,564 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +namespace modm::platform::pio +{ + + struct Instruction { + uint16_t value; + constexpr Instruction(uint16_t val) : value(val) {} + constexpr uint16_t store(size_t offset) const { + return ((value & 0xe000) == 0x0000) ? (value + offset) : value; + } + constexpr Instruction operator | (uint16_t params) const { + return Instruction(value | params); + } + constexpr bool operator == (uint16_t val) const { + return value == val; + } + }; + + enum class Opcode : uint16_t { + JMP = 0x0000 << 13, + WAIT = 0x0001 << 13, + IN = 0x0002 << 13, + OUT = 0x0003 << 13, + PUSH = 0x0004 << 13, + PULL = (0x0004 << 13) | (0x0001 << 7), + MOV = 0x0005 << 13, + IRQ = 0x0006 << 13, + SET = 0x0007 << 13, + }; + + struct OptionalValue { + uint16_t value{0}; + bool is_set{false}; + }; + + static constexpr uint16_t NO_SIDE_VALUE = 0xffff; + + template + struct SideDelay { + static constexpr const uint16_t side_value = (side == NO_SIDE_VALUE) ? 0 : side; + static constexpr const bool side_value_present = side != NO_SIDE_VALUE; + static constexpr const uint16_t delay_value = delay; + }; + + template + struct InstructionEncoding { + static constexpr const Opcode opcode = Res::opcode; + static constexpr const uint16_t DELAY_SIDESET_SHIFT = 8; + static constexpr const uint16_t DELAY_SIDESET_MASK = 0x1f << DELAY_SIDESET_SHIFT; + static constexpr const uint16_t ARG1_SHIFT = 5; + + using SideDelayConfig = SideDelay<>; + + template + struct SideValue : Res { + using SideDelayConfig = SideDelay; + constexpr SideValue() {} + template + struct DelayValue : Res { + constexpr DelayValue() {} + using SideDelayConfig = SideDelay; + template + constexpr Instruction encode(const Encoder& coder) const { + return Res::encode(coder) | (coder.template encodeSideDelay() << DELAY_SIDESET_SHIFT); + } + }; + template + constexpr auto delay() { + return DelayValue(); + } + template + constexpr Instruction encode(const Encoder& coder) const { + return Res::encode(coder) | (coder.template encodeSideDelay() << DELAY_SIDESET_SHIFT); + } + }; + template + struct DelayValue : Res { + constexpr DelayValue() {} + using SideDelayConfig = SideDelay; + template + constexpr Instruction encode(const Encoder& coder) const { + return Res::encode(coder) | (coder.template encodeSideDelay() << DELAY_SIDESET_SHIFT); + } + }; + + constexpr InstructionEncoding() {} + template + constexpr auto side() const { + return SideValue(); + } + template + constexpr auto delay() { + return DelayValue(); + } + template + constexpr Instruction encode(const Encoder& ) const { + return Instruction(uint16_t(opcode)); + } + }; + + enum class JmpCondition : uint16_t { + ALWAYS = 0x00, + NOT_X = 0x01, + X_DEC = 0x02, + NOT_Y = 0x03, + Y_DEC = 0x04, + X_NE_Y = 0x05, + PIN = 0x06, + NOT_ORSE = 0x07, + }; + template + struct Jmp : InstructionEncoding> { + static constexpr const Opcode opcode = Opcode::JMP; + static constexpr const JmpCondition condition_value = condition; + static constexpr const uint16_t addr_value = addr; + static_assert(addr_value < 0x0f, "Invalid addr"); + + using LabelName = Label; + using Base = InstructionEncoding>; + + explicit constexpr Jmp() {} + static constexpr auto not_x() { return Jmp(); } + static constexpr auto x_dec() { return Jmp(); } + static constexpr auto not_y() { return Jmp(); } + static constexpr auto y_dec() { return Jmp(); } + static constexpr auto x_ne_y() { return Jmp(); } + static constexpr auto pin() { return Jmp(); } + static constexpr auto not_osre() { return Jmp(); } + + template + static constexpr auto to() { return Jmp(); } + + template + static constexpr uint16_t getAddr(const Encoder& coder) { + if constexpr (std::is_same_v) { + return addr_value; + } else { + return coder.template getOffset