STM32: Warum kann ich den Scan-Modus im Interrupt-gesteuerten ADC nicht verwenden?

Ich verwende einen STM32F103C8 zum Lesen von 3 ADC-Kanälen und habe CubeMX + HAL verwendet, um den ADC so zu konfigurieren, dass die ADC-Werte in einen Puffer verschoben werden.

Ich konnte dies mit DMA und Polling erreichen: Ich habe beide als allgemein akzeptable Möglichkeiten gesehen, dies online zu tun. Ich konnte jedoch keine Konfiguration finden, mit der ich die Kanäle scannen kann, indem ich Konvertierungen im EOC-Interrupt manuell starte. Bei all meinen Versuchen werden entweder keine Kanäle vorgerückt oder der Interrupt wird überhaupt nicht ausgelöst.

Ich würde es vorziehen, Interrupts zu verwenden, da mein einziger DMA-Kanal auf dem Gerät zum Puffern einiger ziemlich hochfrequenter Audiodaten verwendet wird, aber ich bin auch nur beunruhigt, dass ich nicht verstehen kann, wie die ADC-Interrupts in Verbindung mit Scan funktionieren Modus. Ich habe die folgenden Ansätze verwendet:

  • DMA : Dies scheint die maßgebliche Methode zu sein, um mehrere Kanäle zu scannen und ihre jeweiligen Ergebnisse zu speichern. Insbesondere sagt das Benutzerhandbuch in §11.3.8 ¶3:

    Bei Verwendung des Scan-Modus muss das DMA-Bit gesetzt werden und der Direct Memory Access Controller wird verwendet, um die konvertierten Daten der regulären Gruppenkanäle nach jeder Aktualisierung des ADC_DR-Registers an den SRAM zu übertragen.

    Ich konnte es mit den intuitiven Einstellungen in CubeMX zum Laufen bringen:

    • ADC_Einstellungen:
      • Scan-Konvertierungsmodus: Aktiviert
      • Kontinuierlicher Modus: Aktiviert
      • Diskontinuierlicher Modus: Deaktiviert
    • ADC_Regular_Conversion_Mode
      • Reguläre Konvertierungen aktivieren: Aktivieren
      • Anzahl der Konvertierungen: 3
      • Externe Trigger-Conversion-Quelle: Regelmäßige Conversion, die von Software gestartet wird
      • <Kanalkonfigurationen und Ränge...>

    plus ein kreisförmiges halbwortausgerichtetes DMA und ein direkter Aufruf von HAL_ADC_Start_DMA()in der Quelle.

  • Abfrage : Ich habe versucht, dieser Antwort zu folgen , die sowohl den kontinuierlichen als auch den diskontinuierlichen Modus deaktiviert und in der Lage ist, mit aufeinanderfolgenden Anrufen allein durch die Kanäle zu gehen HAL_ADC_PollForConversion. Ich fand, dass ich den diskontinuierlichen Modus mit Gruppengrößen von 1 aktivieren musste, dh:

    hadc1.Init.DiscontinuousConvMode = ENABLE;
    hadc1.Init.NbrOfDiscConversion = 1;
    

    Das Steppen durch die Kanäle HAL_ADC_PollForConversionfunktionierte dann reibungslos.

  • Interrupts: Ich habe jede Permutation von Scan-Modus, diskontinuierlichem Modus und Anzahl diskontinuierlicher Konvertierungen ausprobiert, und keine davon lässt mich durch die Kanäle in der HAL_ADC_ConvCpltCallbackInterrupt-Routine gehen. Hier ist die Routine, die ich verwende:

    #define NUM_ADC_BUF 8
    #define NUM_ADC_CH 3
    volatile uint16_t adc_buf[NUM_ADC_BUF][NUM_ADC_CH];
    void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
      if(hadc->Instance == ADC1) {
        adc_buf[adc_buf_idx & (NUM_ADC_BUF - 1)][adc_ch++] = HAL_ADC_GetValue(hadc);
        if(adc_ch == NUM_ADC_CH) {
          adc_ch = 0;
          adc_buf_idx++;
        }
    
        HAL_ADC_Start_IT(hadc);
      }
    }
    

    Innerhalb der ADC_Settings in CubeMX sind hier meine Versuche und ihre Ergebnisse:

    +-------------------+-----------+--------------------+---------------------+------------------------------------------------------+
    | (Continuous mode) | Scan mode | Discontinuous mode | Number of           |                      Outcome                         |
    |                   |           |                    | discontinuous conv. |                                                      |
    +-------------------+-----------+--------------------+---------------------+------------------------------------------------------+
    | DISABLED          | ENABLED   | ENABLED            | 3                   | Only highest-rank-# (rank 3) channel result is given |
    | DISABLED          | ENABLED   | ENABLED            | 1                   | Interrupt never fires: EOC never set                 |
    | DISABLED          | ENABLED   | DISABLED           | N/A                 | Only highest-rank-# (rank 3) channel result is given |
    | DISABLED          | DISABLED  | ENABLED            | 3                   | Only lowest-rank-# (rank 1) channel result is given  |
    | DISABLED          | DISABLED  | ENABLED            | 1                   | Only lowest-rank-# (rank 1) channel result is given  |
    | DISABLED          | DISABLED  | DISABLED           | N/A                 | Only lowest-rank-# (rank 1) channel result is given  |
    +-------------------+-----------+--------------------+---------------------+------------------------------------------------------+
    

    Wie Sie sehen können, funktionieren keine Kombinationen richtig. Ist das einfach unmöglich? Ich nehme an, ich kann den Auszug, den ich aus dem Benutzerhandbuch zitiert habe, so verstehen, dass die einzige Möglichkeit, Scans zu verwenden, DMA ist und dass die Abfrage als eine Funktion funktioniert, die formal nicht unterstützt wird. Ist das wahr?

    Als Referenz ist hier mein unberührtes automatisch generiertes adc.cvon CubeMX:

    /* Includes ------------------------------------------------------------------*/
    #include "adc.h"
    
    /* USER CODE BEGIN 0 */
    
    /* USER CODE END 0 */
    
    ADC_HandleTypeDef hadc1;
    
    /* ADC1 init function */
    void MX_ADC1_Init(void)
    {
      ADC_ChannelConfTypeDef sConfig = {0};
    
      /** Common config 
      */
      hadc1.Instance = ADC1;
      hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
      hadc1.Init.ContinuousConvMode = DISABLE;
      hadc1.Init.DiscontinuousConvMode = ENABLE;
      hadc1.Init.NbrOfDiscConversion = 3;
      hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
      hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
      hadc1.Init.NbrOfConversion = 3;
      if (HAL_ADC_Init(&hadc1) != HAL_OK)
      {
        Error_Handler();
      }
      /** Configure Regular Channel 
      */
      sConfig.Channel = ADC_CHANNEL_4;
      sConfig.Rank = ADC_REGULAR_RANK_1;
      sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
      if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
      {
        Error_Handler();
      }
      /** Configure Regular Channel 
      */
      sConfig.Channel = ADC_CHANNEL_8;
      sConfig.Rank = ADC_REGULAR_RANK_2;
      if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
      {
        Error_Handler();
      }
      /** Configure Regular Channel 
      */
      sConfig.Channel = ADC_CHANNEL_9;
      sConfig.Rank = ADC_REGULAR_RANK_3;
      if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
      {
        Error_Handler();
      }
    
    }
    
    void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
    {
    
      GPIO_InitTypeDef GPIO_InitStruct = {0};
      if(adcHandle->Instance==ADC1)
      {
      /* USER CODE BEGIN ADC1_MspInit 0 */
    
      /* USER CODE END ADC1_MspInit 0 */
     /* ADC1 clock enable */
     __HAL_RCC_ADC1_CLK_ENABLE();
    
     __HAL_RCC_GPIOA_CLK_ENABLE();
     __HAL_RCC_GPIOB_CLK_ENABLE();
     /**ADC1 GPIO Configuration    
     PA4     ------> ADC1_IN4
     PB0     ------> ADC1_IN8
     PB1     ------> ADC1_IN9 
     */
     GPIO_InitStruct.Pin = Xin_Pin;
     GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
     HAL_GPIO_Init(Xin_GPIO_Port, &GPIO_InitStruct);
    
     GPIO_InitStruct.Pin = Zin_Pin|Yin_Pin;
     GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    
     /* ADC1 interrupt Init */
     HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 0);
     HAL_NVIC_EnableIRQ(ADC1_2_IRQn);
      /* USER CODE BEGIN ADC1_MspInit 1 */
    
      /* USER CODE END ADC1_MspInit 1 */
      }
    }
    
    void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
    {
    
      if(adcHandle->Instance==ADC1)
      {
      /* USER CODE BEGIN ADC1_MspDeInit 0 */
    
      /* USER CODE END ADC1_MspDeInit 0 */
     /* Peripheral clock disable */
     __HAL_RCC_ADC1_CLK_DISABLE();
    
     /**ADC1 GPIO Configuration    
     PA4     ------> ADC1_IN4
     PB0     ------> ADC1_IN8
     PB1     ------> ADC1_IN9 
     */
     HAL_GPIO_DeInit(Xin_GPIO_Port, Xin_Pin);
    
     HAL_GPIO_DeInit(GPIOB, Zin_Pin|Yin_Pin);
    
     /* ADC1 interrupt Deinit */
     HAL_NVIC_DisableIRQ(ADC1_2_IRQn);
      /* USER CODE BEGIN ADC1_MspDeInit 1 */
    
      /* USER CODE END ADC1_MspDeInit 1 */
      }
    } 
    
    /* USER CODE BEGIN 1 */
    
    /* USER CODE END 1 */
    
Was sagt das Referenzhandbuch?
@P__J__ Abgesehen von dem Zitat, das ich erwähnt habe, konnte ich keine Details darüber finden, wie sich aufeinanderfolgende Conversions auf die Gruppen von Kanälen beziehen. Ich hätte die Tatsache akzeptiert, dass Scan- und diskontinuierliche Modi nur mit einem DMA sinnvoll sind, wenn es keine Möglichkeit gäbe, auch im Polling-Modus durch die Kanäle zu gehen, was nicht im Referenzhandbuch dokumentiert ist.
Dort ist alles dokumentiert. Aber sicher nicht in der HAL-Dokumentation oder Infile-Hilfe (Sie haben das Zitat von dort übernommen). Verwenden Sie Register und alles wird "magisch" wie beabsichtigt funktionieren. Verzichten Sie auf HAL für so einfache Peripherie!!
@P__J__ Wollen Sie damit sagen, dass Interrupt-basiertes Scannen möglich sein sollte? Ich würde mich gerne darüber ärgern, was im Referenzhandbuch darüber steht, aber nach mehrfachem Lesen sagt das Handbuch wirklich nichts außer "Set Scan Mode = 1", um Kanäle zum Inkrementieren zu bringen.
Was ist der Wert von ADC->CR2: EOCS- Bit?
@Tagli Ich sehe kein EOCS-Bit, beziehst du dich auf EOCIE?
Entschuldigung, ich habe mir den falschen RM angesehen. EOCS scheint auf F4 vorhanden zu sein, aber nicht auf F1.
@Tagli ah, ich sehe es im F4xx RM, ich verstehe, warum Sie fragen würden.

Antworten (1)

Im Gegensatz zur F4-Serie kann der ADC in der F1-Serie nur einen (einzelnen) Interrupt am Ende der gesamten Scansequenz erzeugen. Aus diesem Grund ist DMA ein Muss, wenn Sie den Scanmodus verwenden.

Ich denke jedoch, dass es immer noch möglich ist, ADC für Interrupt-basiertes Scannen zu konfigurieren, aber Sie müssen dies manuell tun:

  1. Deaktivieren Sie das Scannen und den kontinuierlichen Modus. Konfigurieren Sie ADC für Einzelkonvertierung.
  2. Wählen Sie den ADC-Kanal mit ADC->SQR3 : SQ1- Bits (1. Wandlung in regulärer Reihenfolge)
  3. Aktivieren Sie den EOC-Interrupt mit ADC->CR1: EOCIE- Bit
  4. Sammeln Sie in der Interrupt-Service-Routine (ISR) das Ergebnis von ADC->DR .
  5. Wiederholen Sie in ISR Schritt 2, um ADC für den nächsten Kanal zu konfigurieren.
  6. ADC muss manuell oder über ein externes Ereignis ausgelöst werden.

Für die periodische Abtastung mehrerer Kanäle benötigen Sie wahrscheinlich 2 TIM-Module und einige TIM-Interrupts. Ein Timer (Slave) steuert das Timing zwischen jedem Kanal einer Scanning-Gruppe und triggert das ADC-Modul. Dieser Timer muss sich seiner Anzahl von Überläufen bewusst sein (unter Verwendung eines Interrupts), damit er sich selbst deaktivieren kann, nachdem alle Kanäle in der Sequenz abgetastet und konvertiert wurden. Ein weiterer Timer (Master) steuert das Timing zwischen den Scansequenzen und aktiviert den Slave-Timer (und wahrscheinlich den ersten Scan in der Sequenz).

Ich weiß nicht, wie ausgelastet Ihr DMA ist, aber ich würde auf jeden Fall versuchen, DMA zu verwenden, bevor ich die oben beschriebene Methode ausprobiere.

Zahlen, das würde erklären, warum ich im Scan-Modus immer nur den letzten Kanal in der Sequenz bekomme. In meinem Fall konvertiere ich die Kanäle kontinuierlich (dh keine Verzögerung zwischen Scansequenzen), also denke ich, dass ich immer nur den "Slave"-Timer brauchen sollte, aber ich stimme zu, dass dies wahrscheinlich das Beste ist, was es auf F1 bekommt. Gut zu wissen, dass es das in F4 gibt!