diff --git a/arch/arm/src/stm32h7/stm32_adc.c b/arch/arm/src/stm32h7/stm32_adc.c index 4609111eba..e2169846cd 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; - - /* Verify that the upper-half driver has bound its callback functions */ - - if (priv->cb != NULL) + do { - /* 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. + /* Read the converted value and clear EOC bit + * (It is cleared by reading the ADC_DR) */ - 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 - */ - - priv->current++; - - if (priv->current >= priv->nchannels) - { - /* Restart the conversion sequence from the beginning */ - - priv->current = 0; + value = adc_getreg(priv, STM32_ADC_DR_OFFSET); + value &= ADC_DR_MASK; + + /* Verify that the upper-half driver has bound its + * callback functions + */ + + 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 + */ + + priv->current++; + + if (priv->current >= priv->nchannels) + { + /* Restart the conversion sequence from the beginning */ + + 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 7bc90cb41c..c956f9e79f 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) @@ -420,6 +435,23 @@ static int adc_ioctl(FAR struct file *filep, int cmd, unsigned long arg) return ret; } +/**************************************************************************** + * 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 0fb99c199b..aa09485571 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