You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by da...@apache.org on 2021/06/16 11:58:13 UTC

[incubator-nuttx] 01/03: stm32h7 fix adc port to handle overrun and the DR's fifo, adapt adc driver

This is an automated email from the ASF dual-hosted git repository.

davids5 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nuttx.git

commit b9fba3edae59bc9bd0b38b3411947b8cf480f3da
Author: GAEHWILER Reto <re...@hexagon.com>
AuthorDate: Tue Jun 15 13:08:48 2021 +0200

    stm32h7 fix adc port to handle overrun and the DR's fifo, adapt adc driver
    
    * port didn't know about data-register fifo
    * port didn't handle overrun condition
    * driver could get stuck if interrupts were skipped due to saturation
---
 arch/arm/src/stm32h7/stm32_adc.c | 95 ++++++++++++++++++++++++++++------------
 drivers/analog/adc.c             | 34 +++++++++++++-
 include/nuttx/analog/adc.h       | 14 ++++++
 3 files changed, 115 insertions(+), 28 deletions(-)

diff --git a/arch/arm/src/stm32h7/stm32_adc.c b/arch/arm/src/stm32h7/stm32_adc.c
index 4609111..e216984 100644
--- a/arch/arm/src/stm32h7/stm32_adc.c
+++ b/arch/arm/src/stm32h7/stm32_adc.c
@@ -1389,6 +1389,10 @@ static int adc_setup(FAR struct adc_dev_s *dev)
   clrbits |= ADC_CFGR_EXTEN_MASK;
   setbits |= ADC_CFGR_EXTEN_NONE;
 
+  /* Set overrun mode to preserve the data register */
+
+  clrbits |= ADC_CFGR_OVRMOD;
+
   /* Set CFGR configuration */
 
   adc_modifyreg(priv, STM32_ADC_CFGR_OFFSET, clrbits, setbits);
@@ -1551,9 +1555,9 @@ static void adc_rxint(FAR struct adc_dev_s *dev, bool enable)
   regval = adc_getreg(priv, STM32_ADC_IER_OFFSET);
   if (enable)
     {
-      /* Enable end of conversion interrupt */
+      /* Enable end of conversion and overrun interrupts */
 
-      regval |= ADC_INT_EOC;
+      regval |= ADC_INT_EOC | ADC_INT_OVR;
     }
   else
     {
@@ -1833,7 +1837,7 @@ static int adc_interrupt(FAR struct adc_dev_s *dev, uint32_t adcisr)
   FAR struct stm32_dev_s *priv = (FAR struct stm32_dev_s *)dev->ad_priv;
   int32_t value;
 
-  /* Identifies the interruption AWD or OVR */
+  /* Identifies the interruption AWD */
 
   if ((adcisr & ADC_INT_AWD1) != 0)
     {
@@ -1848,50 +1852,87 @@ static int adc_interrupt(FAR struct adc_dev_s *dev, uint32_t adcisr)
       adc_startconv(priv, false);
     }
 
+  /* OVR: Overrun */
+
   if ((adcisr & ADC_INT_OVR) != 0)
     {
+      /* In case of a missed ISR - due to interrupt saturation -
+       * the upper half needs to be informed to terminate properly.
+       */
+
       awarn("WARNING: Overrun has occurred!\n");
+
+      /* To make use of already sampled data the conversion needs to be
+       * stopped first before reading out the data register.
+       */
+
+      adc_startconv(priv, false);
+
+      while ((adc_getreg(priv, STM32_ADC_CR_OFFSET) & ADC_CR_ADSTART) != 0);
+
+      /* Verify that the upper-half driver has bound its callback functions */
+
+      if ((priv->cb != NULL) && (priv->cb->au_reset != NULL))
+        {
+          /* Notify upper-half driver about the overrun */
+
+          priv->cb->au_reset(dev);
+        }
+
+      adc_putreg(priv, STM32_ADC_ISR_OFFSET, ADC_INT_OVR);
     }
 
   /* EOC: End of conversion */
 
   if ((adcisr & ADC_INT_EOC) != 0)
     {
-      /* Read the converted value and clear EOC bit
-       * (It is cleared by reading the ADC_DR)
+      /* Read from the ADC_DR register until 8 stage FIFO is empty.
+       * The FIFO is first mentioned in STM32H7 Reference Manual
+       * rev. 7, though, not yet indicated in the block diagram!
        */
 
-      value  = adc_getreg(priv, STM32_ADC_DR_OFFSET);
-      value &= ADC_DR_MASK;
+      do
+        {
+          /* Read the converted value and clear EOC bit
+           * (It is cleared by reading the ADC_DR)
+           */
 
-      /* Verify that the upper-half driver has bound its callback functions */
+          value  = adc_getreg(priv, STM32_ADC_DR_OFFSET);
+          value &= ADC_DR_MASK;
 
-      if (priv->cb != NULL)
-        {
-          /* Give the ADC data to the ADC driver.  The ADC receive() method
-           * accepts 3 parameters:
-           *
-           * 1) The first is the ADC device instance for this ADC block.
-           * 2) The second is the channel number for the data, and
-           * 3) The third is the converted data for the channel.
+          /* Verify that the upper-half driver has bound its
+           * callback functions
            */
 
-          DEBUGASSERT(priv->cb->au_receive != NULL);
-          priv->cb->au_receive(dev, priv->chanlist[priv->current], value);
-        }
+          if (priv->cb != NULL)
+            {
+              /* Hand the ADC data to the ADC driver.  The ADC receive()
+               * method accepts 3 parameters:
+               *
+               * 1) The first is the ADC device instance for this ADC block.
+               * 2) The second is the channel number for the data, and
+               * 3) The third is the converted data for the channel.
+               */
+
+              DEBUGASSERT(priv->cb->au_receive != NULL);
+              priv->cb->au_receive(dev, priv->chanlist[priv->current],
+                                   value);
+            }
 
-      /* Set the channel number of the next channel that will complete
-       * conversion
-       */
+          /* Set the channel number of the next channel that will
+           * complete conversion
+           */
 
-      priv->current++;
+          priv->current++;
 
-      if (priv->current >= priv->nchannels)
-        {
-          /* Restart the conversion sequence from the beginning */
+          if (priv->current >= priv->nchannels)
+            {
+              /* Restart the conversion sequence from the beginning */
 
-          priv->current = 0;
+              priv->current = 0;
+            }
         }
+      while ((adc_getreg(priv, STM32_ADC_ISR_OFFSET) & ADC_INT_EOC) != 0);
     }
 
   return OK;
diff --git a/drivers/analog/adc.c b/drivers/analog/adc.c
index 7bc90cb..c956f9e 100644
--- a/drivers/analog/adc.c
+++ b/drivers/analog/adc.c
@@ -72,6 +72,7 @@ static int     adc_close(FAR struct file *filep);
 static ssize_t adc_read(FAR struct file *fielp, FAR char *buffer,
                         size_t buflen);
 static int     adc_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
+static int     adc_reset(FAR struct adc_dev_s *dev);
 static int     adc_receive(FAR struct adc_dev_s *dev, uint8_t ch,
                            int32_t data);
 static void    adc_notify(FAR struct adc_dev_s *dev);
@@ -98,7 +99,8 @@ static const struct file_operations g_adc_fops =
 
 static const struct adc_callback_s g_adc_callback =
 {
-  adc_receive   /* au_receive */
+  adc_receive,    /* au_receive */
+  adc_reset       /* au_reset */
 };
 
 /****************************************************************************
@@ -158,6 +160,10 @@ static int adc_open(FAR struct file *filep)
                   dev->ad_recv.af_head = 0;
                   dev->ad_recv.af_tail = 0;
 
+                  /* Clear overrun indicator */
+
+                  dev->ad_isovr = false;
+
                   /* Finally, Enable the ADC RX interrupt */
 
                   dev->ad_ops->ao_rxint(dev, true);
@@ -280,6 +286,15 @@ static ssize_t adc_read(FAR struct file *filep, FAR char *buffer,
       flags = enter_critical_section();
       while (dev->ad_recv.af_head == dev->ad_recv.af_tail)
         {
+          /* Check if there was an overrun, if set we need to return EIO */
+
+          if (dev->ad_isovr)
+            {
+              dev->ad_isovr = false;
+              ret = -EIO;
+              goto return_with_irqdisabled;
+            }
+
           /* The receive FIFO is empty -- was non-blocking mode selected? */
 
           if (filep->f_oflags & O_NONBLOCK)
@@ -421,6 +436,23 @@ static int adc_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
 }
 
 /****************************************************************************
+ * Name: adc_reset
+ ****************************************************************************/
+
+static int adc_reset(FAR struct adc_dev_s *dev)
+{
+  /* Set overrun flag to give read a chance to recover */
+
+  dev->ad_isovr = true;
+
+  /* No need to notify here. The adc_receive callback will be called next.
+   * If an adc overrun occurs then there must be at least one conversion.
+   */
+
+  return OK;
+}
+
+/****************************************************************************
  * Name: adc_receive
  ****************************************************************************/
 
diff --git a/include/nuttx/analog/adc.h b/include/nuttx/analog/adc.h
index 0fb99c1..aa09485 100644
--- a/include/nuttx/analog/adc.h
+++ b/include/nuttx/analog/adc.h
@@ -110,6 +110,19 @@ struct adc_callback_s
 
   CODE int (*au_receive)(FAR struct adc_dev_s *dev, uint8_t ch,
                          int32_t data);
+
+  /* This method is called from the lower half, platform-specific ADC logic
+   * when an overrun appeared to free / reset upper half.
+   *
+   * Input Parameters:
+   *   dev  - The ADC device structure that was previously registered by
+   *          adc_register()
+   *
+   * Returned Value:
+   *   Zero on success; a negated errno value on failure.
+   */
+
+  CODE int (*au_reset)(FAR struct adc_dev_s *dev);
 };
 
 /* This describes on ADC message */
@@ -195,6 +208,7 @@ struct adc_dev_s
   sem_t                       ad_closesem;   /* Locks out new opens while close is in progress */
   sem_t                       ad_recvsem;    /* Used to wakeup user waiting for space in ad_recv.buffer */
   struct adc_fifo_s           ad_recv;       /* Describes receive FIFO */
+  bool                        ad_isovr;      /* Flag to indicate an ADC overrun */
 
   /* The following is a list of poll structures of threads waiting for
    * driver events.  The 'struct pollfd' reference for each open is also