/ 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  namespace {
 13  
 14  PIO pio_open_check() {
 15      if (pio_init()) {
 16          throw std::runtime_error("PIO not available");
 17      }
 18  
 19      PIO pio = pio_open(0);
 20      if(PIO_IS_ERR(pio)) {
 21          throw std::runtime_error(
 22              py::str("Failed to open PIO device (error {})").attr("format")(PIO_ERR_VAL(pio)).cast<std::string>());
 23      }
 24      return pio;
 25  }
 26  
 27  int pio_sm_claim_unused_sm_check(PIO pio) {
 28      int sm = pio_claim_unused_sm(pio, false);
 29      if (sm < 0) {
 30          throw std::runtime_error("No state machine available");
 31      }
 32      return sm;
 33  }
 34  
 35  int pio_add_program_check(PIO pio, const struct pio_program *program) {
 36      int offset = pio_add_program(pio, program);
 37      if (offset < 0) {
 38          throw std::runtime_error("Could not load program");
 39      }
 40      return offset;
 41  }
 42  
 43  int get_pin_number(py::object gpio_obj) {
 44      return py::getattr(gpio_obj, "_pin", gpio_obj).attr("id").cast<int>();
 45  }
 46  
 47  template<class T>
 48  int get_default(py::object o, T default_value) {
 49      if (o.is_none()) { return default_value; }
 50      return o.cast<T>();
 51  }
 52  
 53  class StateMachine {
 54      PIO pio{};
 55      int sm{-1};
 56      int offset{-1};
 57  
 58  public:
 59      StateMachine(py::buffer assembled,
 60              double frequency,
 61              py::object first_sideset_pin,
 62              int sideset_pin_count,
 63              bool auto_pull,
 64              bool out_shift_right,
 65              int pull_threshold) {
 66          pio = pio_open_check();
 67          sm = pio_sm_claim_unused_sm_check(pio);
 68          py::buffer_info info = assembled.request();
 69          if (info.itemsize != 2) {
 70              throw py::value_error("assembled: Expected array of type `h`");
 71          }
 72          if (info.size >= 32) {
 73              throw py::value_error("assembled: Exceeds maximum program length (32)");
 74          }
 75  
 76          struct pio_program program = {
 77              .instructions = reinterpret_cast<uint16_t*>(info.ptr),
 78              .length = static_cast<uint8_t>(info.size),
 79              .origin = -1,
 80          };
 81          offset = pio_add_program_check(pio, &program);
 82  
 83          pio_sm_config c = {0, 0, 0};
 84          sm_config_set_wrap(&c, offset, offset + info.size - 1);
 85  
 86          if (!first_sideset_pin.is_none()) {
 87              if (sideset_pin_count < 1 || sideset_pin_count > 5) {
 88                  throw py::value_error("sideset_pin_count out of range (must be 1 to 5)");
 89              }
 90              auto first_sideset_pin_number = get_pin_number(first_sideset_pin);
 91              for(int i=0; i<sideset_pin_count; i++) {
 92                  pio_gpio_init(pio, first_sideset_pin_number + i);
 93              }
 94              sm_config_set_sideset(&c, sideset_pin_count, /* optional */ false, /* pindirs */ false);
 95              sm_config_set_sideset_pins(&c, first_sideset_pin_number);
 96          }
 97  
 98          sm_config_set_out_shift(&c, out_shift_right, auto_pull, pull_threshold);
 99  
100          double div = clock_get_hz(clk_sys) / frequency;
101          sm_config_set_clkdiv(&c, div);
102  
103          pio_sm_init(pio, sm, offset, &c);
104          pio_sm_set_enabled(pio, sm, true);
105  
106      }
107  
108      ~StateMachine() {
109          if(!PIO_IS_ERR(pio)) pio_close(pio);
110      }
111  
112      void write(py::buffer b) {
113          py::buffer_info info = b.request();
114          uint32_t *ptr = reinterpret_cast<uint32_t*>(info.ptr);
115          std::vector<uint32_t> vec;
116          // the DMA controller doesn't replicate 8- and 16-bit values like on rp2, so we have to do it ourselves
117          if (info.itemsize != 4) {
118              vec.reserve(info.size);
119              switch(info.itemsize) {
120                  case 1:
121                  {
122                      auto *buf = reinterpret_cast<uint8_t*>(info.ptr);
123                      for(pybind11::ssize_t i=0; i<info.size; i++) {
124                          vec.push_back(buf[i] * 0x01010101);
125                      }
126                      break;
127                  }
128                  case 2:
129                  {
130                      auto *buf = reinterpret_cast<uint16_t*>(info.ptr);
131                      for(pybind11::ssize_t i=0; i<info.size; i++) {
132                          vec.push_back(buf[i] * 0x00010001);
133                      }
134                  }
135                      break;
136                  default:
137                      throw py::value_error("buffer must contain items of 1, 2, or 4 bytes");
138              }
139              ptr = &vec[0];
140          }
141          size_t size = info.size * sizeof(uint32_t);
142          if (pio_sm_config_xfer(pio, sm, PIO_DIR_TO_SM, size, 1)) {
143              throw std::runtime_error("pio_sm_config_xfer() failed");
144          }
145          if (pio_sm_xfer_data(pio, sm, PIO_DIR_TO_SM, size, ptr)) {
146              throw std::runtime_error("pio_sm_xfer_data() failed");
147          }
148      }
149  };
150  
151  PYBIND11_MODULE(adafruit_rp1pio, m) {
152      m.doc() = R"pbdoc(
153          Hardware interface to RP1 series’ programmable IO (PIO) peripheral.
154          -------------------------------------------------------------------
155  
156          Except as noted, this is intended to be a subset of the functionality
157          in CircuitPython's ``rp2pio`` module.
158  
159          .. currentmodule:: adafruit_rp1pio
160  
161          .. autosummary::
162             :toctree: _generate
163  
164             StateMachine
165      )pbdoc";
166  
167  
168      py::class_<StateMachine>(m, "StateMachine", "A single PIO StateMachine")
169          .def(py::init<py::buffer /* assembled */,
170                  double /* frequency */,
171                  py::object /* first_sideset_pin */,
172                  int /* sideset_pin_count */,
173                  bool /* auto_pull */,
174                  bool /* out_shift_right */,
175                  int /* pull_threshold */>(),
176              "Construct a StateMachine",
177              py::arg("assembled"),
178              py::arg("frequency"),
179              py::kw_only(),
180              py::arg("first_sideset_pin") = py::none(),
181              py::arg("sideset_pin_count") = 1,
182              py::arg("auto_pull") = false,
183              py::arg("out_shift_right") = true,
184              py::arg("pull_threshold") = 32
185              )
186          .def("write", &StateMachine::write, "Write the data contained in buffer to the state machine", py::arg("buffer"));
187  
188  #ifdef VERSION_INFO
189      m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);
190  #else
191      m.attr("__version__") = "dev";
192  #endif
193  }
194  }