!drivers: separate pulse count feature from PWM driver

BREAKING CHANGE: separate pulse count feature from PWM driver.

Coupling PWM driver with pulse count feature was bad decision from the beginning,
these are two different things:

- PWM is a modulation scheme: it continuously represents a value by varying duty
cycle, usually at a fixed frequency.
- Pulse train generation is a finite waveform transaction: generate N edges/pulses
with selected timing, then complete.

This change introduce a new pulse count driver with new API.
Now user can generate pulse train by providing:

- high pulse length in ns
- low pulse length in ns
- pulse count

All architectures supporting pulse count have been adapted in subsequent commits.
Users must migrate their code to use the new driver with new API.

Signed-off-by: raiden00pl <raiden00@railab.me>
This commit is contained in:
raiden00pl 2026-05-20 10:07:29 +02:00 committed by Alin Jerpelea
parent 08a2de27cf
commit f1e7b143d9
13 changed files with 705 additions and 244 deletions

View file

@ -0,0 +1,21 @@
====================================
``pulsecount`` Pulse Count Example
====================================
This example starts a finite pulse train on a pulsecount device and
waits for completion when the device is opened in blocking mode.
Required configuration:
- ``CONFIG_PULSECOUNT`` Enables pulsecount support.
- ``CONFIG_NSH_BUILTIN_APPS`` Builds the example as an NSH built-in.
Specific configuration options:
- ``CONFIG_EXAMPLES_PULSECOUNT_DEVPATH`` The default pulsecount device.
Default: ``/dev/pulsecount0``.
- ``CONFIG_EXAMPLES_PULSECOUNT_HIGH_NS`` The pulse high time. Default:
``5000000`` ns.
- ``CONFIG_EXAMPLES_PULSECOUNT_LOW_NS`` The pulse low time. Default:
``5000000`` ns.
- ``CONFIG_EXAMPLES_PULSECOUNT_COUNT`` The finite pulse count.

View file

@ -10,8 +10,6 @@ This test depends on these specific PWM/NSH configurations settings (your
specific PWM settings might require additional settings).
- ``CONFIG_PWM`` Enables PWM support.
- ``CONFIG_PWM_PULSECOUNT`` Enables PWM pulse count support (if the hardware
supports it).
- ``CONFIG_NSH_BUILTIN_APPS`` Build the PWM test as an NSH built-in function.
Specific configuration options for this example include:
@ -22,8 +20,4 @@ Specific configuration options for this example include:
- ``CONFIG_EXAMPLES_PWM_DUTYPCT`` The initial PWM duty as a percentage. Default:
``50%``.
- ``CONFIG_EXAMPLES_PWM_DURATION`` The initial PWM pulse train duration in
seconds. Used only if the current pulse count is zero (pulse count is only
supported if ``CONFIG_PWM_PULSECOUNT`` is defined). Default: ``5`` seconds.
- ``CONFIG_EXAMPLES_PWM_PULSECOUNT`` The initial PWM pulse count. This option is
only available if ``CONFIG_PWM_PULSECOUNT`` is non-zero. Default: ``0`` (i.e., use
the duration, not the count).
seconds. Default: ``5`` seconds.

View file

@ -8,6 +8,7 @@ Timers Drivers
timer.rst
oneshot.rst
pwm.rst
pulsecount.rst
watchdog.rst
rtc.rst
capture.rst

View file

@ -0,0 +1,97 @@
===================
Pulsecount Drivers
===================
The pulsecount driver generates a finite pulse train and reports completion
after the requested number of pulses has been produced. It is intended for
hardware that can generate pulse output with a fixed repetition count, often
using PWM/timer peripherals internally.
This interface is separate from the PWM driver. PWM describes a continuous
periodic output using frequency and duty cycle. Pulsecount describes a finite
waveform using explicit high time, low time, and pulse count.
Driver Model
============
The NuttX pulsecount driver is split into two parts:
#. An upper-half character driver that provides the common application
interface.
#. A lower-half platform driver that programs the hardware timer/PWM
peripheral and calls ``pulsecount_expired()`` when the finite pulse train
completes.
Files supporting pulsecount can be found in the following locations:
- ``include/nuttx/timers/pulsecount.h`` - public interface and lower-half
callbacks.
- ``drivers/timers/pulsecount.c`` - generic upper-half driver.
- ``arch/<architecture>/src/<chip>/*pulsecount*.c`` - platform lower-half
drivers.
Application Interface
=====================
Applications use the pulsecount driver through a character device such as
``/dev/pulsecount0``. Include the pulsecount header:
.. code-block:: c
#include <nuttx/timers/pulsecount.h>
The driver is controlled through ``ioctl`` commands:
- ``PULSECOUNTIOC_SETCHARACTERISTICS``
- ``PULSECOUNTIOC_GETCHARACTERISTICS``
- ``PULSECOUNTIOC_START``
- ``PULSECOUNTIOC_STOP``
Pulse Characteristics
=====================
``PULSECOUNTIOC_SETCHARACTERISTICS`` takes a pointer to
``struct pulsecount_info_s``:
.. code-block:: c
struct pulsecount_info_s
{
uint32_t high_ns; /* Pulse high time in nanoseconds */
uint32_t low_ns; /* Pulse low time in nanoseconds */
uint32_t count; /* Number of pulses to generate */
};
``high_ns`` and ``low_ns`` must both be non-zero. Their sum is the pulse
period. ``count`` is the number of complete high/low pulses to generate.
This API previously followed the PWM-style ``frequency`` plus ``duty``
model. It now uses explicit high/low nanosecond timing because that is more
user-friendly for finite pulse trains: applications can describe the waveform
directly and do not need to convert timing requirements into fixed-point duty
cycle values.
The lower-half driver may quantize the requested timings to the nearest values
that the hardware timer can represent. Very long periods or very short
pulses can be rejected if they are outside the timer's clock and counter
range.
Starting And Stopping
=====================
After setting the pulse characteristics, start the pulse train with
``PULSECOUNTIOC_START``. By default, this call blocks until the requested
pulse count completes. Open the device with ``O_NONBLOCK`` to start the
pulse train and return immediately.
``PULSECOUNTIOC_STOP`` stops pulse generation before the count completes.
TODO: support cancelling a blocking ``PULSECOUNTIOC_START``.
Example
=======
The ``apps/examples/pulsecount`` example starts a finite pulse train from
NSH. It can be built with ``CONFIG_EXAMPLES_PULSECOUNT`` and configured with
``CONFIG_EXAMPLES_PULSECOUNT_HIGH_NS``,
``CONFIG_EXAMPLES_PULSECOUNT_LOW_NS``, and
``CONFIG_EXAMPLES_PULSECOUNT_COUNT``.

View file

@ -5,8 +5,8 @@ PWM Drivers
For the purposes of this driver, a PWM device is any device that
generates periodic output pulses of controlled frequency and pulse
width. Such a device might be used, for example, to perform
pulse-width modulated output or frequency/pulse-count modulated
output (such as might be needed to control a stepper motor).
pulse-width modulated output. Finite pulse-count output is provided
through the separate pulsecount driver interface.
The NuttX PWM driver is split into two parts:
@ -209,3 +209,7 @@ wait for an end of cycle.
The ``CONFIG_PWM_DEADTIME`` option brings the possibility to introduce
dead time values between complementary PWM outputs.
Finite pulse trains are not configured through ``pwm_info_s``. Use the
:doc:`pulsecount <pulsecount>` driver for hardware that supports a fixed pulse
count.

View file

@ -86,6 +86,10 @@ if(CONFIG_PWM)
list(APPEND SRCS pwm.c)
endif()
if(CONFIG_PULSECOUNT)
list(APPEND SRCS pulsecount.c)
endif()
if(CONFIG_DSHOT)
list(APPEND SRCS dshot.c)
endif()

View file

@ -5,7 +5,7 @@
menu "Timer Driver Support"
config ARCH_HAVE_PWM_PULSECOUNT
config ARCH_HAVE_PULSECOUNT
bool
default n
@ -24,18 +24,18 @@ config PWM
This selection enables building of the "upper-half" PWM driver.
See include/nuttx/timers/pwm.h for further PWM driver information.
if PWM
config PWM_PULSECOUNT
bool "PWM Pulse Count Support"
config PULSECOUNT
bool "Pulse Count Support"
default n
depends on ARCH_HAVE_PWM_PULSECOUNT
depends on ARCH_HAVE_PULSECOUNT
---help---
Some hardware will support generation of a fixed number of pulses.
This might be used, for example to support a stepper motor. If the
hardware will support a fixed pulse count, then this configuration
should be set to enable the capability.
if PWM
config PWM_OVERWRITE
bool "PWM Overwrite Support"
default n

View file

@ -118,6 +118,12 @@ ifeq ($(CONFIG_PWM),y)
TMRVPATH = :timers
endif
ifeq ($(CONFIG_PULSECOUNT),y)
CSRCS += pulsecount.c
TMRDEPPATH = --dep-path timers
TMRVPATH = :timers
endif
ifeq ($(CONFIG_DSHOT),y)
CSRCS += dshot.c
TMRDEPPATH = --dep-path timers

361
drivers/timers/pulsecount.c Normal file
View file

@ -0,0 +1,361 @@
/****************************************************************************
* drivers/timers/pulsecount.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/debug.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>
#include <nuttx/fs/fs.h>
#include <nuttx/timers/pulsecount.h>
#ifdef CONFIG_PULSECOUNT
/****************************************************************************
* Private Types
****************************************************************************/
struct pulsecount_upperhalf_s
{
uint8_t crefs; /* The number of times the device has
* been opened */
volatile bool started; /* True: pulse output is active */
volatile bool waiting; /* True: a thread is waiting for expiry */
mutex_t lock; /* Supports mutual exclusion */
sem_t waitsem; /* Waits for finite pulse completion */
struct pulsecount_info_s info; /* Pulse output characteristics */
/* Lower half state */
FAR struct pulsecount_lowerhalf_s *dev;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int pulsecount_open(FAR struct file *filep);
static int pulsecount_close(FAR struct file *filep);
static ssize_t pulsecount_read(FAR struct file *filep,
FAR char *buffer, size_t buflen);
static ssize_t pulsecount_write(FAR struct file *filep,
FAR const char *buffer, size_t buflen);
static int pulsecount_start(FAR struct pulsecount_upperhalf_s *upper,
unsigned int oflags);
static int pulsecount_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct file_operations g_pulsecount_fops =
{
pulsecount_open, /* open */
pulsecount_close, /* close */
pulsecount_read, /* read */
pulsecount_write, /* write */
NULL, /* seek */
pulsecount_ioctl, /* ioctl */
};
/****************************************************************************
* Private Functions
****************************************************************************/
static void pulsecount_dump(FAR const char *msg,
FAR const struct pulsecount_info_s *info,
bool started)
{
_info("%s: high: %" PRIu32 " ns low: %" PRIu32
" ns count: %" PRIu32 " started: %d\n",
msg, info->high_ns, info->low_ns, info->count, started);
}
static int pulsecount_open(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct pulsecount_upperhalf_s *upper = inode->i_private;
FAR struct pulsecount_lowerhalf_s *lower;
uint8_t tmp;
int ret;
ret = nxmutex_lock(&upper->lock);
if (ret < 0)
{
return ret;
}
tmp = upper->crefs + 1;
if (tmp == 0)
{
ret = -EMFILE;
goto errout_with_lock;
}
if (tmp == 1)
{
lower = upper->dev;
DEBUGASSERT(lower->ops->setup != NULL);
ret = lower->ops->setup(lower);
if (ret < 0)
{
goto errout_with_lock;
}
}
upper->crefs = tmp;
ret = OK;
errout_with_lock:
nxmutex_unlock(&upper->lock);
return ret;
}
static int pulsecount_close(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct pulsecount_upperhalf_s *upper = inode->i_private;
FAR struct pulsecount_lowerhalf_s *lower;
int ret;
ret = nxmutex_lock(&upper->lock);
if (ret < 0)
{
return ret;
}
if (upper->crefs > 1)
{
upper->crefs--;
}
else
{
lower = upper->dev;
upper->crefs = 0;
DEBUGASSERT(lower->ops->shutdown != NULL);
lower->ops->shutdown(lower);
}
nxmutex_unlock(&upper->lock);
return OK;
}
static ssize_t pulsecount_read(FAR struct file *filep,
FAR char *buffer, size_t buflen)
{
return 0;
}
static ssize_t pulsecount_write(FAR struct file *filep,
FAR const char *buffer, size_t buflen)
{
return 0;
}
static int pulsecount_start(FAR struct pulsecount_upperhalf_s *upper,
unsigned int oflags)
{
FAR struct pulsecount_lowerhalf_s *lower;
int ret = OK;
DEBUGASSERT(upper != NULL);
lower = upper->dev;
DEBUGASSERT(lower != NULL && lower->ops->start != NULL);
if (upper->info.count == 0)
{
return -EINVAL;
}
if (!upper->started)
{
upper->waiting = (oflags & O_NONBLOCK) == 0;
upper->started = true;
ret = lower->ops->start(lower, &upper->info, upper);
if (ret == OK)
{
while (upper->waiting)
{
ret = nxsem_wait_uninterruptible(&upper->waitsem);
if (ret < 0)
{
upper->started = false;
upper->waiting = false;
}
}
}
else
{
upper->started = false;
upper->waiting = false;
}
}
return ret;
}
static int pulsecount_ioctl(FAR struct file *filep, int cmd,
unsigned long arg)
{
FAR struct inode *inode = filep->f_inode;
FAR struct pulsecount_upperhalf_s *upper = inode->i_private;
FAR struct pulsecount_lowerhalf_s *lower = upper->dev;
int ret;
ret = nxmutex_lock(&upper->lock);
if (ret < 0)
{
return ret;
}
switch (cmd)
{
case PULSECOUNTIOC_SETCHARACTERISTICS:
{
FAR const struct pulsecount_info_s *info =
(FAR const struct pulsecount_info_s *)((uintptr_t)arg);
DEBUGASSERT(info != NULL);
if (info->high_ns == 0 || info->low_ns == 0 ||
pulsecount_frequency(info) == 0 || info->count == 0)
{
ret = -EINVAL;
break;
}
pulsecount_dump("PULSECOUNTIOC_SETCHARACTERISTICS", info,
upper->started);
memcpy(&upper->info, info, sizeof(struct pulsecount_info_s));
if (upper->started)
{
ret = lower->ops->start(lower, &upper->info, upper);
}
}
break;
case PULSECOUNTIOC_GETCHARACTERISTICS:
{
FAR struct pulsecount_info_s *info =
(FAR struct pulsecount_info_s *)((uintptr_t)arg);
DEBUGASSERT(info != NULL);
memcpy(info, &upper->info, sizeof(struct pulsecount_info_s));
pulsecount_dump("PULSECOUNTIOC_GETCHARACTERISTICS", info,
upper->started);
}
break;
case PULSECOUNTIOC_START:
{
pulsecount_dump("PULSECOUNTIOC_START", &upper->info,
upper->started);
ret = pulsecount_start(upper, filep->f_oflags);
}
break;
case PULSECOUNTIOC_STOP:
{
if (upper->started)
{
ret = lower->ops->stop(lower);
upper->started = false;
upper->waiting = false;
}
}
break;
default:
{
DEBUGASSERT(lower->ops->ioctl != NULL);
ret = lower->ops->ioctl(lower, cmd, arg);
}
break;
}
nxmutex_unlock(&upper->lock);
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
int pulsecount_register(FAR const char *path,
FAR struct pulsecount_lowerhalf_s *dev)
{
FAR struct pulsecount_upperhalf_s *upper;
upper = kmm_zalloc(sizeof(struct pulsecount_upperhalf_s));
if (upper == NULL)
{
_err("ERROR: Allocation failed\n");
return -ENOMEM;
}
nxmutex_init(&upper->lock);
nxsem_init(&upper->waitsem, 0, 0);
upper->dev = dev;
_info("Registering %s\n", path);
return register_driver(path, &g_pulsecount_fops, 0666, upper);
}
void pulsecount_expired(FAR void *handle)
{
FAR struct pulsecount_upperhalf_s *upper =
(FAR struct pulsecount_upperhalf_s *)handle;
if (upper->started)
{
if (upper->waiting)
{
upper->waiting = false;
nxsem_post(&upper->waitsem);
}
upper->started = false;
}
}
#endif /* CONFIG_PULSECOUNT */

View file

@ -41,12 +41,9 @@
#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>
#include <nuttx/fs/fs.h>
#include <nuttx/timers/pwm.h>
#include <nuttx/irq.h>
#ifdef CONFIG_PWM
/****************************************************************************
@ -61,15 +58,7 @@ struct pwm_upperhalf_s
* been opened */
volatile bool started; /* True: pulsed output is being
* generated */
#ifdef CONFIG_PWM_PULSECOUNT
volatile bool waiting; /* True: Caller is waiting for the pulse
* count to expire */
#endif
mutex_t lock; /* Supports mutual exclusion */
#ifdef CONFIG_PWM_PULSECOUNT
sem_t waitsem; /* Used to wait for the pulse count to
* expire */
#endif
struct pwm_info_s info; /* Pulsed output characteristics */
FAR struct pwm_lowerhalf_s *dev; /* lower-half state */
};
@ -126,10 +115,6 @@ static void pwm_dump(FAR const char *msg, FAR const struct pwm_info_s *info,
info->channels[i].channel, info->channels[i].duty);
}
#ifdef CONFIG_PWM_PULSECOUNT
pwminfo(" count: %" PRIx32 "\n", info->channels[0].count);
#endif
pwminfo(" started: %d\n", started);
}
@ -295,80 +280,13 @@ static ssize_t pwm_write(FAR struct file *filep, FAR const char *buffer,
*
****************************************************************************/
#ifdef CONFIG_PWM_PULSECOUNT
static int pwm_start(FAR struct pwm_upperhalf_s *upper, unsigned int oflags)
{
FAR struct pwm_lowerhalf_s *lower;
irqstate_t flags;
int ret = OK;
DEBUGASSERT(upper != NULL);
lower = upper->dev;
DEBUGASSERT(lower != NULL && lower->ops->start != NULL);
/* Verify that the PWM is not already running */
if (!upper->started)
{
/* Indicate that if will be waiting for the pulse count to complete.
* Note that we will only wait if a non-zero pulse count is specified
* and if the PWM driver was opened in normal, blocking mode. Also
* assume for now that the pulse train will be successfully started.
*
* We do these things before starting the PWM to avoid race conditions.
*/
upper->waiting = (upper->info.channels[0].count > 0) &&
((oflags & O_NONBLOCK) == 0);
upper->started = true;
/* Invoke the bottom half method to start the pulse train */
ret = lower->ops->start(lower, &upper->info, upper);
/* A return value of zero means that the pulse train was started
* successfully.
*/
if (ret == OK)
{
/* Should we wait for the pulse output to complete? Loop in
* in case the wakeup form nxsem_wait() is a false alarm.
*/
while (upper->waiting)
{
/* Wait until we are awakened by pwm_expired(). When
* pwm_expired is called, it will post the waitsem and
* clear the waiting flag.
*/
ret = nxsem_wait_uninterruptible(&upper->waitsem);
if (ret < 0)
{
upper->started = false;
upper->waiting = false;
}
}
}
else
{
/* Looks like we won't be waiting after all */
pwminfo("start failed: %d\n", ret);
upper->started = false;
upper->waiting = false;
}
}
return ret;
}
#else
static int pwm_start(FAR struct pwm_upperhalf_s *upper, unsigned int oflags)
{
FAR struct pwm_lowerhalf_s *lower;
int ret = OK;
UNUSED(oflags);
DEBUGASSERT(upper != NULL);
lower = upper->dev;
DEBUGASSERT(lower != NULL && lower->ops->start != NULL);
@ -395,7 +313,6 @@ static int pwm_start(FAR struct pwm_upperhalf_s *upper, unsigned int oflags)
return ret;
}
#endif
/****************************************************************************
* Name: pwm_ioctl
@ -453,11 +370,7 @@ static int pwm_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
if (upper->started)
{
#ifdef CONFIG_PWM_PULSECOUNT
ret = lower->ops->start(lower, &upper->info, upper);
#else
ret = lower->ops->start(lower, &upper->info);
#endif
}
}
break;
@ -513,12 +426,6 @@ static int pwm_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
ret = lower->ops->stop(lower);
upper->started = false;
#ifdef CONFIG_PWM_PULSECOUNT
if (upper->waiting)
{
upper->waiting = false;
}
#endif
}
}
break;
@ -588,9 +495,6 @@ int pwm_register(FAR const char *path, FAR struct pwm_lowerhalf_s *dev)
*/
nxmutex_init(&upper->lock);
#ifdef CONFIG_PWM_PULSECOUNT
nxsem_init(&upper->waitsem, 0, 0);
#endif
upper->dev = dev;
@ -600,69 +504,4 @@ int pwm_register(FAR const char *path, FAR struct pwm_lowerhalf_s *dev)
return register_driver(path, &g_pwmops, 0666, upper);
}
/****************************************************************************
* Name: pwm_expired
*
* Description:
* If CONFIG_PWM_PULSECOUNT is defined and the pulse count was configured
* to a non-zero value, then the "upper half" driver will wait for the
* pulse count to expire. The sequence of expected events is as follows:
*
* 1. The upper half driver calls the start method, providing the lower
* half driver with the pulse train characteristics. If a fixed
* number of pulses is required, the 'count' value will be nonzero.
* 2. The lower half driver's start() method must verify that it can
* support the request pulse train (frequency, duty, AND pulse count).
* If it cannot, it should return an error. If the pulse count is
* non-zero, it should set up the hardware for that number of pulses
* and return success. NOTE: That is CONFIG_PWM_PULSECOUNT is
* defined, the start() method receives an additional parameter
* that must be used in this callback.
* 3. When the start() method returns success, the upper half driver
* will "sleep" until the pwm_expired method is called.
* 4. When the lower half detects that the pulse count has expired
* (probably through an interrupt), it must call the pwm_expired
* interface using the handle that was previously passed to the
* start() method
*
* Input Parameters:
* handle - This is the handle that was provided to the lower-half
* start() method.
*
* Returned Value:
* None
*
* Assumptions:
* This function may be called from an interrupt handler.
*
****************************************************************************/
#ifdef CONFIG_PWM_PULSECOUNT
void pwm_expired(FAR void *handle)
{
FAR struct pwm_upperhalf_s *upper = (FAR struct pwm_upperhalf_s *)handle;
pwminfo("started: %d waiting: %d\n", upper->started, upper->waiting);
/* Make sure that the PWM is started */
if (upper->started)
{
/* Is there a thread waiting for the pulse train to complete? */
if (upper->waiting)
{
/* Yes.. clear the waiting flag and awakened the waiting thread */
upper->waiting = false;
nxsem_post(&upper->waitsem);
}
/* The PWM is now stopped */
upper->started = false;
}
}
#endif
#endif /* CONFIG_PWM */

View file

@ -114,6 +114,7 @@
#define _EEPIOCBASE (0x4600) /* EEPROM driver ioctl commands */
#define _PTPBASE (0x4700) /* PTP ioctl commands */
#define _DSHOTIOCBASE (0x4800) /* Dshot device ioctl commands */
#define _PULSECOUNTBASE (0x4900) /* Pulse count driver ioctl commands */
#define _WLIOCBASE (0x8b00) /* Wireless modules ioctl network commands */
/* boardctl() commands share the same number space */
@ -815,6 +816,11 @@
#define _DSHOTIOCVALID(c) (_IOC_TYPE(c)==_DSHOTIOCBASE)
#define _DSHOTIOC(nr) _IOC(_DSHOTIOCBASE,nr)
/* Pulse count driver ioctl definitions *************************************/
#define _PULSECOUNTIOCVALID(c) (_IOC_TYPE(c)==_PULSECOUNTBASE)
#define _PULSECOUNTIOC(nr) _IOC(_PULSECOUNTBASE,nr)
/****************************************************************************
* Public Type Definitions
****************************************************************************/

View file

@ -0,0 +1,189 @@
/****************************************************************************
* include/nuttx/timers/pulsecount.h
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#ifndef __INCLUDE_NUTTX_TIMERS_PULSECOUNT_H
#define __INCLUDE_NUTTX_TIMERS_PULSECOUNT_H
/* A pulsecount device generates a finite pulse train with controlled high
* time, low time, and pulse count. The driver is split into two parts:
*
* 1. An "upper half", generic character driver that provides the common
* pulsecount interface to application code.
* 2. A "lower half", platform-specific driver that implements the timer
* programming needed to generate the pulse train.
*/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/compiler.h>
#include <nuttx/clock.h>
#include <stdint.h>
#include <fixedmath.h>
#include <nuttx/fs/ioctl.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* IOCTL Commands ***********************************************************/
/* PULSECOUNTIOC_SETCHARACTERISTICS - Set the pulse train characteristics.
*
* ioctl argument: A read-only reference to struct pulsecount_info_s.
*
* PULSECOUNTIOC_GETCHARACTERISTICS - Get the currently selected pulse train
* characteristics.
*
* ioctl argument: A writable reference to struct pulsecount_info_s.
*
* PULSECOUNTIOC_START - Start the configured pulse train. The
* PULSECOUNTIOC_SETCHARACTERISTICS command must have previously been sent.
* By default this command blocks until the configured pulse count
* completes. That blocking behavior can be overridden by opening the
* device with O_NONBLOCK.
*
* ioctl argument: None.
*
* PULSECOUNTIOC_STOP - Stop pulse generation and return immediately.
*
* TODO: Support cancelling a blocking PULSECOUNTIOC_START.
*
* ioctl argument: None.
*/
#define PULSECOUNTIOC_SETCHARACTERISTICS _PULSECOUNTIOC(1)
#define PULSECOUNTIOC_GETCHARACTERISTICS _PULSECOUNTIOC(2)
#define PULSECOUNTIOC_START _PULSECOUNTIOC(3)
#define PULSECOUNTIOC_STOP _PULSECOUNTIOC(4)
/****************************************************************************
* Public Types
****************************************************************************/
struct pulsecount_info_s
{
uint32_t high_ns; /* Pulse high time in nanoseconds */
uint32_t low_ns; /* Pulse low time in nanoseconds */
uint32_t count; /* Number of pulses to generate */
};
/****************************************************************************
* Public Inline Functions
****************************************************************************/
static inline uint64_t
pulsecount_period_ns(FAR const struct pulsecount_info_s *info)
{
return (uint64_t)info->high_ns + info->low_ns;
}
static inline uint32_t
pulsecount_frequency(FAR const struct pulsecount_info_s *info)
{
return NSEC_PER_SEC / pulsecount_period_ns(info);
}
static inline ub16_t
pulsecount_duty(FAR const struct pulsecount_info_s *info)
{
return (ub16_t)(((uint64_t)info->high_ns << 16) /
pulsecount_period_ns(info));
}
struct pulsecount_lowerhalf_s;
struct pulsecount_ops_s
{
/* This method is called when the driver is opened. The lower half driver
* should configure and initialize the device so that it is ready for use.
* It should not generate pulses until the start method is called.
*/
CODE int (*setup)(FAR struct pulsecount_lowerhalf_s *dev);
/* This method is called when the driver is closed. The lower half driver
* should stop pulse output, free resources, disable the timer, and put the
* system into the lowest possible power usage state.
*/
CODE int (*shutdown)(FAR struct pulsecount_lowerhalf_s *dev);
/* Configure the timer and start the finite pulse train. The lower half
* must call pulsecount_expired() with the provided handle after the
* programmed pulse count completes.
*/
CODE int (*start)(FAR struct pulsecount_lowerhalf_s *dev,
FAR const struct pulsecount_info_s *info,
FAR void *handle);
/* Stop the pulse train and reset the timer resources. */
CODE int (*stop)(FAR struct pulsecount_lowerhalf_s *dev);
/* Lower-half logic may support platform-specific ioctl commands. */
CODE int (*ioctl)(FAR struct pulsecount_lowerhalf_s *dev,
int cmd, unsigned long arg);
};
struct pulsecount_lowerhalf_s
{
/* The first field of this state structure must be a pointer to the
* pulsecount callback structure.
*/
FAR const struct pulsecount_ops_s *ops;
};
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
#ifdef __cplusplus
#define EXTERN extern "C"
extern "C"
{
#else
#define EXTERN extern
#endif
int pulsecount_register(FAR const char *path,
FAR struct pulsecount_lowerhalf_s *dev);
/* This callback is called by the lower half after a finite pulse train has
* completed. The handle must be the value passed to the lower half start()
* method.
*/
void pulsecount_expired(FAR void *handle);
#undef EXTERN
#ifdef __cplusplus
}
#endif
#endif /* __INCLUDE_NUTTX_TIMERS_PULSECOUNT_H */

View file

@ -24,10 +24,9 @@
#define __INCLUDE_NUTTX_TIMERS_PWM_H
/* For the purposes of this driver, a PWM device is any device that generates
* periodic output pulses s of controlled frequency and pulse width. Such a
* device might be used, for example, to perform pulse-width modulated output
* or frequency/pulse-count modulated output (such as might be needed to
* control a stepper motor).
* periodic output pulses of controlled frequency and pulse width. Such a
* device might be used, for example, to perform pulse-width modulated
* output.
*
* The PWM driver is split into two parts:
*
@ -59,10 +58,6 @@
/* Configuration ************************************************************/
/* CONFIG_PWM - Enables because PWM driver support
* CONFIG_PWM_PULSECOUNT - Some hardware will support generation of a fixed
* number of pulses. This might be used, for example to support a stepper
* motor. If the hardware will support a fixed pulse count, then this
* configuration should be set to enable the capability.
* CONFIG_DEBUG_PWM_INFO - This will generate output that can be use to
* debug the PWM driver.
*/
@ -98,11 +93,7 @@
* characteristics of the pulsed output.
*
* PWMIOC_START - Start the pulsed output. The PWMIOC_SETCHARACTERISTICS
* command must have previously been sent. If CONFIG_PWM_PULSECOUNT is
* defined and the pulse count was configured to a non-zero value, then
* this ioctl call will, by default, block until the programmed pulse count
* completes. That default blocking behavior can be overridden by using
* the O_NONBLOCK flag when the PWM driver is opened.
* command must have previously been sent.
*
* ioctl argument: None
*
@ -174,11 +165,6 @@ struct pwm_chan_s
uint8_t cpol;
uint8_t dcpol;
int8_t channel;
#ifdef CONFIG_PWM_PULSECOUNT
uint32_t count; /* The number of pulse to generate. 0 means to
* generate an indefinite number of pulses */
#endif
};
/* This structure describes the characteristics of the pulsed output */
@ -219,17 +205,11 @@ struct pwm_ops_s
/* (Re-)initialize the timer resources and start the pulsed output. The
* start method should return an error if it cannot start the timer with
* the given parameter (frequency, duty, or optionally pulse count)
* the given parameter (frequency or duty)
*/
#ifdef CONFIG_PWM_PULSECOUNT
CODE int (*start)(FAR struct pwm_lowerhalf_s *dev,
FAR const struct pwm_info_s *info,
FAR void *handle);
#else
CODE int (*start)(FAR struct pwm_lowerhalf_s *dev,
FAR const struct pwm_info_s *info);
#endif
/* Stop the pulsed output and reset the timer resources */
@ -313,47 +293,6 @@ extern "C"
int pwm_register(FAR const char *path, FAR struct pwm_lowerhalf_s *dev);
/****************************************************************************
* Name: pwm_expired
*
* Description:
* If CONFIG_PWM_PULSECOUNT is defined and the pulse count was configured
* to a non-zero value, then the "upper half" driver will wait for the
* pulse count to expire. The sequence of expected events is as follows:
*
* 1. The upper half driver calls the start method, providing the lower
* half driver with the pulse train characteristics. If a fixed
* number of pulses is required, the 'count' value will be nonzero.
* 2. The lower half driver's start() method must verify that it can
* support the request pulse train (frequency, duty, AND pulse count).
* If it cannot, it should return an error. If the pulse count is
* non-zero, it should set up the hardware for that number of pulses
* and return success. NOTE: That is CONFIG_PWM_PULSECOUNT is
* defined, the start() method receives an additional parameter
* that must be used in this callback.
* 3. When the start() method returns success, the upper half driver
* will "sleep" until the pwm_expired method is called.
* 4. When the lower half detects that the pulse count has expired
* (probably through an interrupt), it must call the pwm_expired
* interface using the handle that was previously passed to the
* start() method
*
* Input Parameters:
* handle - This is the handle that was provided to the lower-half
* start() method.
*
* Returned Value:
* None
*
* Assumptions:
* This function may be called from an interrupt handler.
*
****************************************************************************/
#ifdef CONFIG_PWM_PULSECOUNT
void pwm_expired(FAR void *handle);
#endif
/****************************************************************************
* Platform-Independent "Lower-Half" PWM Driver Interfaces
****************************************************************************/