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
This commit is contained in:
GAEHWILER Reto 2021-06-15 13:08:48 +02:00 committed by David Sidrane
parent 82887c10a7
commit b9fba3edae
3 changed files with 120 additions and 33 deletions

View File

@ -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;

View File

@ -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
****************************************************************************/

View File

@ -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