/ src / main.cpp
main.cpp
  1  #include <iostream>
  2  #include <pybind11/pybind11.h>
  3  
  4  #include "piolib.h"
  5  #include "utils/piolib/examples/ws2812.pio.h"
  6  
  7  #define STRINGIFY(x) #x
  8  #define MACRO_STRINGIFY(x) STRINGIFY(x)
  9  
 10  namespace py = pybind11;
 11  
 12  static PIO pio{};
 13  static int sm{-1};
 14  static int offset{-1};
 15  static int last_gpio{-1};
 16  static size_t last_size{};
 17  
 18  static void neopixel_write(py::object gpio_obj, py::buffer buf) {
 19      int gpio = py::getattr(gpio_obj, "_pin", gpio_obj).attr("id").cast<int>();
 20      py::buffer_info info = buf.request();
 21  
 22      if (!pio || sm < 0) {
 23          // (is safe to call twice)
 24          if (pio_init()) {
 25              throw std::runtime_error("pio_init() failed");
 26          }
 27  
 28          // can't use `pio0` macro as it will call exit() on failure!
 29          pio = pio_open(0);
 30          if (PIO_IS_ERR(pio)) {
 31              throw std::runtime_error(
 32                  py::str("Failed to open PIO device (error {})")
 33                      .attr("format")(PIO_ERR_VAL(pio))
 34                      .cast<std::string>());
 35          }
 36  
 37          sm = pio_claim_unused_sm(pio, true);
 38  
 39          offset = pio_add_program(pio, &ws2812_program);
 40  
 41          pio_sm_clear_fifos(pio, sm);
 42          pio_sm_set_clkdiv(pio, sm, 1.0);
 43  
 44          last_gpio = -1;
 45      }
 46  
 47      if (gpio != last_gpio) {
 48          ws2812_program_init(pio, sm, offset, gpio, 800000.0, true);
 49          last_gpio = gpio;
 50      }
 51  
 52      size_t size = info.size * info.itemsize;
 53  
 54      // rp1pio can only DMA in 32-bit blocks.
 55      // Copy the data into a temporary vector, with redundant zeros at the end
 56      // then byteswap it so that the data comes out in the right order.
 57      uint8_t *data = reinterpret_cast<uint8_t *>(info.ptr);
 58      std::vector<uint32_t> vec;
 59      vec.resize((size + 3) / 4);
 60      size_t data_size = vec.size() * 4;
 61      memcpy(&vec[0], data, size);
 62      for (auto &i : vec)
 63          i = __builtin_bswap32(i);
 64  
 65      if (data_size > UINT16_MAX) {
 66          throw py::value_error("Too much data");
 67      }
 68  
 69      if (data_size != last_size) {
 70          if (pio_sm_config_xfer(pio, sm, PIO_DIR_TO_SM, data_size, 1)) {
 71              throw std::runtime_error("pio_sm_config_xfer() failed");
 72          }
 73          last_size = data_size;
 74      }
 75      if (pio_sm_xfer_data(pio, sm, PIO_DIR_TO_SM, data_size, &vec[0])) {
 76          throw std::runtime_error("pio_sm_xfer_data() failed");
 77      }
 78  }
 79  
 80  static void free_pio(void) {
 81      if (!pio) {
 82          return;
 83      }
 84      if (offset <= 0) {
 85          pio_remove_program(pio, &ws2812_program, offset);
 86      };
 87      offset = -1;
 88      if (sm >= 0) {
 89          pio_sm_unclaim(pio, sm);
 90      }
 91      sm = -1;
 92      pio_close(pio);
 93      pio = nullptr;
 94  }
 95  
 96  PYBIND11_MODULE(adafruit_raspberry_pi5_neopixel_write, m) {
 97      m.doc() = R"pbdoc(
 98          neopixel_write for pi5
 99          ----------------------
100  
101          .. currentmodule:: adafruit_raspberry_pi5_neopixel_write
102  
103          .. autosummary::
104             :toctree: _generate
105  
106             neopixel_write
107             free_pio
108      )pbdoc";
109  
110      m.def("neopixel_write", &neopixel_write, py::arg("gpio"), py::arg("buf"),
111            R"pbdoc(NeoPixel writing function)pbdoc");
112  
113      m.def("free_pio", &free_pio, R"pbdoc(Release any held PIO resource)pbdoc");
114  
115  #ifdef VERSION_INFO
116      m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);
117  #else
118      m.attr("__version__") = "dev";
119  #endif
120  }