This is an automated email from the ASF dual-hosted git repository. jerpelea pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit f1e7b143d9400a32041e2495e4b9cfef69067ab5 Author: raiden00pl <[email protected]> AuthorDate: Wed May 20 10:07:29 2026 +0200 !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 <[email protected]> --- .../applications/examples/pulsecount/index.rst | 21 ++ Documentation/applications/examples/pwm/index.rst | 8 +- .../components/drivers/character/timers/index.rst | 1 + .../drivers/character/timers/pulsecount.rst | 97 ++++++ .../components/drivers/character/timers/pwm.rst | 8 +- drivers/timers/CMakeLists.txt | 4 + drivers/timers/Kconfig | 12 +- drivers/timers/Make.defs | 6 + drivers/timers/pulsecount.c | 361 +++++++++++++++++++++ drivers/timers/pwm.c | 163 +--------- include/nuttx/fs/ioctl.h | 6 + include/nuttx/timers/pulsecount.h | 189 +++++++++++ include/nuttx/timers/pwm.h | 71 +--- 13 files changed, 704 insertions(+), 243 deletions(-) diff --git a/Documentation/applications/examples/pulsecount/index.rst b/Documentation/applications/examples/pulsecount/index.rst new file mode 100644 index 00000000000..b9b34e18d4a --- /dev/null +++ b/Documentation/applications/examples/pulsecount/index.rst @@ -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. diff --git a/Documentation/applications/examples/pwm/index.rst b/Documentation/applications/examples/pwm/index.rst index 33519d722f1..90c0002929e 100644 --- a/Documentation/applications/examples/pwm/index.rst +++ b/Documentation/applications/examples/pwm/index.rst @@ -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. diff --git a/Documentation/components/drivers/character/timers/index.rst b/Documentation/components/drivers/character/timers/index.rst index b615da0cbef..51ce8415fda 100644 --- a/Documentation/components/drivers/character/timers/index.rst +++ b/Documentation/components/drivers/character/timers/index.rst @@ -8,6 +8,7 @@ Timers Drivers timer.rst oneshot.rst pwm.rst + pulsecount.rst watchdog.rst rtc.rst capture.rst diff --git a/Documentation/components/drivers/character/timers/pulsecount.rst b/Documentation/components/drivers/character/timers/pulsecount.rst new file mode 100644 index 00000000000..627be45a165 --- /dev/null +++ b/Documentation/components/drivers/character/timers/pulsecount.rst @@ -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``. diff --git a/Documentation/components/drivers/character/timers/pwm.rst b/Documentation/components/drivers/character/timers/pwm.rst index 61bb6dca340..59169ba92e4 100644 --- a/Documentation/components/drivers/character/timers/pwm.rst +++ b/Documentation/components/drivers/character/timers/pwm.rst @@ -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. diff --git a/drivers/timers/CMakeLists.txt b/drivers/timers/CMakeLists.txt index 1107f1139af..da67a9cabd1 100644 --- a/drivers/timers/CMakeLists.txt +++ b/drivers/timers/CMakeLists.txt @@ -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() diff --git a/drivers/timers/Kconfig b/drivers/timers/Kconfig index b34721657af..143446ceb8e 100644 --- a/drivers/timers/Kconfig +++ b/drivers/timers/Kconfig @@ -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 diff --git a/drivers/timers/Make.defs b/drivers/timers/Make.defs index 6e671ebdbd1..51debd2db42 100644 --- a/drivers/timers/Make.defs +++ b/drivers/timers/Make.defs @@ -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 diff --git a/drivers/timers/pulsecount.c b/drivers/timers/pulsecount.c new file mode 100644 index 00000000000..d2c1677efc7 --- /dev/null +++ b/drivers/timers/pulsecount.c @@ -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 */ diff --git a/drivers/timers/pwm.c b/drivers/timers/pwm.c index cd583de509e..8714abbc745 100644 --- a/drivers/timers/pwm.c +++ b/drivers/timers/pwm.c @@ -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,79 +280,12 @@ 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; @@ -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 */ diff --git a/include/nuttx/fs/ioctl.h b/include/nuttx/fs/ioctl.h index 6a520f24a41..f6ec263ff3d 100644 --- a/include/nuttx/fs/ioctl.h +++ b/include/nuttx/fs/ioctl.h @@ -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 ****************************************************************************/ diff --git a/include/nuttx/timers/pulsecount.h b/include/nuttx/timers/pulsecount.h new file mode 100644 index 00000000000..a6d0ec3c4aa --- /dev/null +++ b/include/nuttx/timers/pulsecount.h @@ -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 */ diff --git a/include/nuttx/timers/pwm.h b/include/nuttx/timers/pwm.h index d0c2a2f9efa..86c8f80c60c 100644 --- a/include/nuttx/timers/pwm.h +++ b/include/nuttx/timers/pwm.h @@ -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 ****************************************************************************/
