Blue Pill - ADC

Table des matières

TL;DR

Conversions multiples et DMA.

Le matériel

Je continue de recycler le montage feu rouge avec bouton auquel j'ajoute 2 potentiomètres (10K dans mon cas) et un capteur de lumière. Le projet consiste à faire clignoter la LED de la carte en fonction d'un capteur. Les capteurs sont associées à une LED, le bouton sert à changer de capteur (et donc de LED). La connection des LED aux ports (B12, B13, B14) n'est pas représentée sur le montage pour faciliter sa lecture.

le principe

L'ADC gère plusieurs capteurs (ou plusieurs canaux), il faut dans ce cas utiliser le DMA qui se chargera, après chaque conversion, de copier la valeur du registre DR vers un tableau. Pour simplifier le code, l'accès aux LEDs se fait à l'aide de 2 tableaux (un pour le GPIO, un autre pour la PIN).

Le code

La fonction suivante configure et ajoute un canal de type "regular":
     1	void
     2	adc_regular_add(ADC_TypeDef *adc, uint8_t channel, enum ADC_CYCLES cycles, uint8_t idx) {
     3	    volatile uint32_t *sqr;
     4	    uint8_t sqr_pos;
     5	    
     6	    adc_init_channel(adc, channel, cycles);
     7	#define BITS_PER_SEQUENCE  5
     8	#define RESET_5BITS        0x1F
     9	    if (idx < 7) {
    10	        sqr     = &adc->SQR3; 
    11	        sqr_pos = idx * BITS_PER_SEQUENCE;
    12	    }
    13	    else if (idx < 13) {
    14	        sqr     = &adc->SQR2;
    15	        sqr_pos = (idx - 7) * BITS_PER_SEQUENCE;
    16	    }
    17	    else {
    18	        sqr     = &adc->SQR1;
    19	        sqr_pos = (idx - 13) * BITS_PER_SEQUENCE;
    20	    }
    21	    *sqr &= ~(RESET_5BITS << sqr_pos);
    22	    *sqr |= channel << sqr_pos;
    23	}
La séquence commence par le registre SQR3 et se termine par le SQR1. L'index passé en paramètre (idx) permet de calculer la position où placer le numéro de canal dans le registre. Les lignes 9 à 20 calculent le registre et la position, la ligne 22 place la valeur du canal au bon endroit.
...
uint16_t adc_values[] = { SENSORS_CHANNELS }; // channel number as fake value
uint8_t i, current_sensor;
GPIO_TypeDef *sensors_led_gpio[] = { RED_GPIO, ORANGE_GPIO, GREEN_GPIO };
uint8_t sensors_led_pin[]        = { RED_PIN,  ORANGE_PIN,  GREEN_PIN  };

int
main (void) {
    int8_t sensors_channels[] = { SENSORS_CHANNELS, -1 };
...
On trouve ici le tableau servant à stocker les conversions (adc_values), 2 variables pour naviguer dans les tableaux et les 2 tableaux d'accès aux LEDs. Petite astuce: j'utilise la liste de canaux pour initialiser le tableau des valeurs afin qu'il ait la bonne taille (mais des valeurs "fantaisistes"). Le tableau sensors_channels liste les canaux, le -1 est une autre astuce pour terminer le parcours du tableau (et éviter d'utiliser sizeof).
...
    adc_init(SENSORS_ADC, SENSORS_CR2);
    adc_init_calibration(SENSORS_ADC);
    for (i = 0; sensors_channels[i] != -1; i++) {
        adc_regular_add(SENSORS_ADC, sensors_channels[i], ADC_239_5, i);
        adc_values[i] = ADC_MAX_VALUE / 2; // reset initial values
    }
    SENSORS_ADC->SQR1 |= (i - 1) << ADC_SQR1_L_Pos;  // sequence length (3 < i)
    SENSORS_ADC->CR1  |= ADC_CR1_SCAN;               // scan mode
    SENSORS_ADC->CR2  |= ADC_CR2_DMA | ADC_CR2_CONT; // enable DMA & CONTinous mode
...
Initialisation de l'ADC, ajout des capteurs et configuration de l'ADC (longueur de la séquence, mode scan, activation du DMA et conversion en continu).
void
SysTick_Handler(void) {
    ONBOARD_LED_GPIO->ODR ^= 1 << ONBOARD_LED_PIN; // toggle state
    SysTick->LOAD = SYSTICK_LOAD_MIN + (adc_values[current_sensor] * SYSTICK_LOAD_STEP);
}
C'est bien parce que le gestion d'interruption SysTick calcule la prochaine valeur à charger qu'il faut veiller à ce qu'elle ne soit pas trop petite au risque de subir une tempête d'interruption.
void
BUTTON_HANDLER(void) {
    EXTI->PR |= BUTTON_EXTI_PR; // Reset irq
    sensors_led_gpio[current_sensor]->BSRR = 1 << sensors_led_pin[current_sensor]; // PIN high, LED off
    current_sensor++;
    if (current_sensor == i) { current_sensor = 0; }
    sensors_led_gpio[current_sensor]->BRR = 1 << sensors_led_pin[current_sensor]; // PIN low, LED on
}
Le gestionnaire d'interruption du bouton acquitte l'interruption, éteint la LED courante, passe à la LED suivante et l'allume.

Le code complet: https://gitlab.com/dsx/blue-pill/-/tree/master/stm32f103c8t6/04_adc_sensors

A suivre

Un peu de matériel avec les afficheurs 7 segments