Zephyr-style approach to custom driver using PPI, GPIOTE and TIMER

I have a driver for a Piezo buzzer running on bare metal that I would like to port to the nRF connect SDK. The existing PWM-based driver in Zephyr does not fulfill my requirements. My driver uses two PPI channels, two GPIOTE channels and one timer instance and I would like to implement it the Zephyr way, i.e. with a proper driver and a device tree overlay defining the hardware.

I've attached the overlay for my custom board (custom_board.dts), the device tree bindings for my piezo buffer driver (buzzer-piezo.yaml) and the prototype driver implementation (piezo.c).

With this initial attempt, I get all sorts of cryptic errors, mainly about missing specifier cell sizes. Unfortunately, the documentation on this matter is thin and I don't find relevant sample code. The few samples using the PPI seem to just rely on the nrfx APIs and hard-code the channels etc. into the application code. I would really prefer to do it the Zephyr way, using device tree definitions.

Why are there no drivers for PPI and GPIOTE in the connect SDK? These seem to be essential peripherals that I use in almost all my nRF-based projects.

Is my approach possible at all without writing full drivers for PPI and GPIOTE?

Can you help me to complete/fix my code or tell me a different approach?

7433.custom_board.dts

7851.buzzer-piezo.yaml

#define DT_DRV_COMPAT buzzer_piezo

#include <zephyr/device.h>

#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>

#include <app/drivers/buzzer.h>

LOG_MODULE_REGISTER(buzzer_piezo, CONFIG_BUZZER_LOG_LEVEL);

struct buzzer_piezo_data {
  bool state;
};

struct buzzer_piezo_config {
  struct gpio_dt_spec pin1, pin2;
  unsigned int ppi_channel1, ppi_channel2;
  unsigned int gpiote_channel1, gpiote_channel2;
  struct device *timer_dev;
  struct device *ppi;
  struct device *gpiote;
};

static int buzzer_piezo_set(const struct device *dev, bool state) {
  const struct buzzer_piezo_config *config = dev->config;
  struct buzzer_piezo_data *data = dev->data;

  return 0;
}

static const struct buzzer_driver_api buzzer_piezo_api = {
    .set = &buzzer_piezo_set,
};

static int buzzer_piezo_init(const struct device *dev) {
  const struct buzzer_piezo_config *config = dev->config;
  struct buzzer_piezo_data *data = dev->data;

  return 0;
}

#define BUZZER_PIEZO_DEFINE(inst)                                              \
  static struct buzzer_piezo_data data##inst;                                  \
                                                                               \
  static const struct buzzer_piezo_config config##inst = {                     \
      .pin1 = GPIO_DT_SPEC_INST_GET_BY_IDX(inst, gpios, 0),                    \
      .pin2 = GPIO_DT_SPEC_INST_GET_BY_IDX(inst, gpios, 1),                    \
      .gpiote_channel1 = ?,                                                    \
      .gpiote_channel2 = ?,                                                    \
      .ppi_channel1 =    ?,                                                    \
      .ppi_channel2 =    ?,                                                    \
      .ppi =             ?,                                                    \
      .gpiote =          ? };                                                  \
                                                                               \
  DEVICE_DT_INST_DEFINE(inst, buzzer_piezo_init, NULL, &data##inst,            \
                        &config##inst, POST_KERNEL,                            \
                        CONFIG_BUZZER_INIT_PRIORITY, &buzzer_piezo_api);

DT_INST_FOREACH_STATUS_OKAY(BUZZER_PIEZO_DEFINE)

  • Hello,

    There is no zephyr api for configuring ppi or gpiote directly no, so you will need to use nrfx directly for this if you want to set this up. I guess you will have to look at other drivers or implementation on example on how to do that.

    That said, if you want to have PWM, why not just use the PWM peripheral?

    Kenneth

  • Hi, it's not that I wouldn't know how to implement this via nrfx or direct register access. In fact I have that implementation already. I just don't like the idea of mixing Zephyr's approach, where hardware description is carefully separated from application logic with the conventional way of defining hardware right in the application. It feels wrong and nullifies one of the greatest advantages of using such a sophisticated OS - having truly portable application code. But well I guess we're not quite there, yet.

    Thanks for mentioning the PWM, though. I'm very familiar with the GPIOTE and PPI so I just used those to solve my problem of generating an inverted PWM signal on two pins. But it looks like the PWM peripheral can do that, too. I'll give it a shot.

Related