24 de enero de 2016

ADC STM32F4 Discovery - Parte 1

Anteriormente veíamos lo importante que resultaban los temporizadores en los sistemas electrónicos actuales. Al igual que estos, existen otros periféricos, que debido a su interacción con el medio, también resultan de gran interés para el desarrollo de aplicaciones.
Por ello, el siguiente periférico con el que trabajaremos será el convertidor analógico a digital (ADC, del inglés, Analog to Digital Converter), que se encarga de convertir una señal en tensión de naturaleza analógica a un valor digital con el que es posible realizar el procesamiento de dicha señal.

Antes de mostrar ejemplos sencillos de las distintas configuraciones del ADC de nuestra placa, veremos una pequeña introducción (mas información en Analog to digital converter, Wikipedia) a los convertidores analógicos a digital para tratar unos aspectos interesantes.
Cuando realizamos la conversión de analógico a digital, debemos tener en cuenta dos aspectos principales, el muestreo y la cuantificación y codificación de dicha señal, lo cual, en simples palabras, significa que la conversión de analógico a digital produce una discretización de la señal en tiempo y amplitud.
El muestreo consiste en tomar una muestra (un valor) de la señal monitorizada en un instante de tiempo. Dado que el sistema electrónico que se encargue de esto no tiene la capacidad de capturar los infinitos valores que toma la señal en el tiempo, estaremos discretizando la señal en el tiempo. De aquí se deriva el término frecuencia de muestreo, que indica el intervalo de tiempo entre cada muestra de la señal analógica. Entrando aun mas en teoría, conviene saber que para muestrear "correctamente" una señal analógica, la frecuencia escogida ha de ser mayor que el doble de la frecuencia máxima de dicha señal. Dicho valor es conocido como frecuencia de Nyquist (Teorema de Nyquist, Wikipedia).
Una vez obtenidas las muestras necesarias de la señal, se procede a cuantificar y codificar dichos valores. Aquí entra en juego la resolución, en bits, del conversor que se este utilizando. En nuestro caso, como se verá a continuación, disponemos de un ADC de 12 bits de resolución, lo que nos permite codificar una medida desde 0 hasta 4095 (cuantificación de 4096 valores codificados desde 0 a 4095). Aquí nuevamente volvemos a ver el efecto de la discretización, en este caso en amplitud, y al igual que la discretización en el tiempo, es muy importante, ya que una elección incorrecta de la resolución del convertidor derivará en una reducción de la relación señal ruido (SNR, del inglés, Signal Noise Ratio).

Visto esto, pasamos a ver detalles específicos del ADC (en realidad dispone de 3, según podemos leer en su datasheet) disponible en la placa STM32F4 Discovery.
Sus características mas interesantes son (ver RM0090 Reference Manual):
  • Resolución configurable: 6, 8, 10 y 12 bits.
  • 19 canales multiplexados: 16 de ellos conectados a los puertos de entrada/salida, 2 canales internos conectados a un sensor de temperatura y para medir la tensión de referencia del ADC y un tercero para el canal de la batería para RTC (del inglés, Real Time Clock).
  • Configuración de lectura: conversión única o continua sobre uno o varios canales y modos scan para conversión automática desde el canal 0 al canal 'n' o discontinuo.
  • Alineación a izquierda o derecha en registro de 16 bits del valor convertido.
  • Almacenamiento a través de DMA (del inglés, Direct Memory Access).
A continuación, y como es de costumbre, vemos el diagrama de bloques del microcontrolador para identificar el bus de conexión del ADC.


Como se puede apreciar, se encuentra conectado al APB2 y también podemos identificar los 3 ADC a los que se hacia referencia mas arriba.

Y en la siguiente imagen podemos ver el diagrama de bloques del ADC.


Tras esto veremos una tabla resumen de los canales del ADC para conocer su asignación a cada pin de entrada/salida de la placa y conexiones internas.

ADC Channel
I/O Pin
Internal
ADC123_IN0
PA0
-
ADC123_IN1
PA1
-
ADC123_IN2
PA2
-
ADC123_IN3
PA3
-
ADC12_IN4
PA4
-
ADC12_IN5
PA5
-
ADC12_IN6
PA6
-
ADC12_IN7
PA7
-
ADC12_IN8
PB0
-
ADC12_IN9
PB1
-
ADC123_IN10
PC10
-
ADC123_IN11
PC11
-
ADC123_IN12
PC12
-
ADC123_IN13
PC13
-
ADC12_IN14
PC14
-
ADC12_IN15
PC15
-
ADC1_IN16
-
Temperature sensor
ADC1_IN17
-
Vref
ADC1_IN18
-
Vbat

Como se aprecia, no todos los canales de los ADC disponibles están asignados, en concreto algunos del ADC3. Para seleccionar un puerto de entrada/salida antes debemos cerciorarnos de que este no esta siendo usado por otro periférico, lo cual podemos comprobar en UM1472 User Manual.
Visto esto, pasaremos a realizar algunos ejemplos de programación del ADC.

Inicialmente configuraremos un solo canal y realizaremos la lectura, primero por software, y a continuación mediante trigger (pin de entrada/salida o timer).
Lo primero que haremos, como siempre, será configurar el pin de entrada/salida. En este caso elegiremos el pin PA1, pues el PA0 esta conectado al botón de usuario. También haremos lo mismo con los pines de los leds para generar una visualización que varie con el valor medido.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void GPIO_Configure (void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
//    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
//    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init (GPIOA, &GPIO_InitStructure);

    /* Leds */
    RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOD, ENABLE);

                                /* LED4 Green | LED3 Orange |  LED5 Red   | LED6  Blue */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init (GPIOD, &GPIO_InitStructure);
}

La configuración del ADC es algo mas larga pero igualmente sencilla. A continuación la vemos y se detallan algunos parámetros.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void ADC_Configure (void)
{
    ADC_InitTypeDef ADC_InitStructure;
    ADC_CommonInitTypeDef ADC_CommonInitStructure;

    RCC_APB2PeriphClockCmd (RCC_APB2Periph_ADC1, ENABLE);

    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConvEdge_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfConversion = 1;
    ADC_Init (ADC1, &ADC_InitStructure);

    ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
    ADC_CommonInit (&ADC_CommonInitStructure);

    ADC_RegularChannelConfig (ADC1, ADC_Channel_1, 1, ADC_SampleTime_3Cycles);

    ADC_Cmd (ADC1, ENABLE);
}

Seleccionamos la máxima resolución del ADC, puesto que no tenemos ningún tipo de restricción temporal que nos obligue a reducir esta y desactivamos los modos scan y continuo, ya que vamos a usar un único canal (ADC_ScanConvMode = DISABLE) y el bucle principal será quien haga la petición de conversión (ADC_ContinuousConvMode = DISABLE). Como vamos a realizar la conversion por software, no estableceremos el flanco del trgigger (ADC_ExternalTrigConvEdge = AD_ExternalTrigConvEdge_None), de forma que el siguiente valor no tiene efecto. Los datos estarán justificados a la derecha (el bit menos significativo del valor obtenido coincide con el bit menos significativo del registro que lo contenga). Para que el ADC pueda realizar una única conversión deberá estar en modo independiente. Con intención de obtener una alta tasa de muestreo, se ha divido el reloj del APB2 por 2 para que el reloj del ADC sea de 42 MHz. Se ha desactivado el modo DMA e igualmente se ha escogido el valor mas pequeño para el intervalo de tiempo entre conversiones del ADC.
El cálculo de la tasa de muestreo del ADC en este caso es muy sencilla. Tan solo se ha de conocer el numero de ciclos de reloj que requiere la conversión con una resolución de 12 bits, siendo este igual a 12. A esto hay que añadirle ADC_SampleTime_3Cycles de la configuración del canal y la mitad de los ciclos entre conversiones, es decir, 2.5 ciclos (no tiene sentido hablar de medio ciclo). Con esto nos queda lo siguiente:

$$\frac { 12+3+2.5 }{ 42 } =0.416667us\rightarrow \frac { 1 }{ 0.416667 } =2.4Msps$$

Se obtiene una tasa de muestreo de 2.4 Msps, que coincide con el valor dado en el datasheet de este.

Por último, la función que se encarga de realizar la conversión es la siguiente.

1
2
3
4
5
6
uint16_t ADC_Read (void)
{
    ADC_SoftwareStartConv (ADC1);
    while (ADC_GetFlagStatus (ADC1, ADC_FLAG_EOC) == RESET);
    return ADC_GetConversionValue (ADC1);
}

Simplemente llama a la función de librería ADC_SoftwareStartConv y espera a que termine la conversión para devolver el valor obtenido, en este caso a una variable local del main como se ve a continuación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int main (void)
{
    uint16_t ADC_ConvertedValue = 0;

    GPIO_Configure ();
    ADC_Configure ();

    while (1)
    {
        ADC_ConvertedValue = ADC_Read ();

        if (ADC_ConvertedValue == 0)
        {
            GPIO_ResetBits (GPIOD, GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
        }
        else if (ADC_ConvertedValue > 0 && ADC_ConvertedValue < 1024)
        {
            GPIO_SetBits (GPIOD, GPIO_Pin_13);
            GPIO_ResetBits (GPIOD, GPIO_Pin_12 | GPIO_Pin_14 | GPIO_Pin_15);
        }
        else if (ADC_ConvertedValue >= 1024 && ADC_ConvertedValue < 2048)
        {
            GPIO_Pin_13 | GPIO_Pin_14);
            GPIO_ResetBits (GPIOD, GPIO_Pin_12 | GPIO_Pin_15);
        }
        else if (ADC_ConvertedValue >= 2048 && ADC_ConvertedValue < 3072)
        {
            GPIO_SetBits (GPIOD, GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
            GPIO_ResetBits (GPIOD, GPIO_Pin_12);
        }
        else if (ADC_ConvertedValue >= 3072 && ADC_ConvertedValue < 4096)
        {
            GPIO_SetBits (GPIOD, GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
        }
    }
}

Aquí, a parte de la lectura del valor convertido, se hace una pequeña decodificación, de forma que mientras mayor sea el valor leído, mas leds se encenderán. Si intentamos sacar mayor provecho a esta programación, podemos hacer que la función de lectura ADC_Read () tenga un parámetro que sea el canal a leer. Asi pues, si además del canal ya configurado, queremos también poder leer los canales internos (sensor de temperatura, tensión de referencia y monitorización de batería), procederíamos de la siguiente forma. En ADC_Config () añadiríamos la activación de dichos canales con las siguientes lineas

1
2
ADC_TempSensorVrefintCmd (ENABLE);
ADC_VBATCmd (ENABLE);

y eliminaríamos la linea de configuración del canal regular, que pasaría a la función de lectura de la siguiente forma:

1
2
3
4
5
6
7
uint16_t ADC_Read (uint8_t ADC_Channel)
{
    ADC_RegularChannelConfig (ADC1, ADC_Channel, 1, ADC_SampleTime_3Cycles);
    ADC_SoftwareStartConv (ADC1);
    while (ADC_GetFlagStatus (ADC1, ADC_FLAG_EOC) == RESET);
    return ADC_GetConversionValue (ADC1);
}

Para leer los valores modificaremos de la siguiente nuestro main:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
int main (void)
{
    uint16_t ADC_ConvertedValue = 0, TempSensor_ConvertedValue = 0, Vref_ConvertedValue = 0, Vbat_ConvertedValue = 0;
    float vTempSensor = 0, temp = 0, Vbat = 0;

    GPIO_Configure ();
    ADC_Configure ();

    while (1)
    {
        ADC_ConvertedValue = ADC_Read (ADC_Channel_1);

        if (ADC_ConvertedValue == 0)
        {
            GPIO_ResetBits (GPIOD, GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
        }
        else if (ADC_ConvertedValue > 0 && ADC_ConvertedValue < 1024)
        {
            GPIO_SetBits (GPIOD, GPIO_Pin_13);
            GPIO_ResetBits (GPIOD, GPIO_Pin_12 | GPIO_Pin_14 | GPIO_Pin_15);
        }
        else if (ADC_ConvertedValue >= 1024 && ADC_ConvertedValue < 2048)
        {
            GPIO_SetBits (GPIOD, GPIO_Pin_13 | GPIO_Pin_14);
            GPIO_ResetBits (GPIOD, GPIO_Pin_12 | GPIO_Pin_15);
        }
        else if (ADC_ConvertedValue >= 2048 && ADC_ConvertedValue < 3072)
        {
            GPIO_SetBits (GPIOD, GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
            GPIO_ResetBits (GPIOD, GPIO_Pin_12);
        }
        else if (ADC_ConvertedValue >= 3072 && ADC_ConvertedValue < 4096)
        {
            GPIO_SetBits (GPIOD, GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
        }

        TempSensor_ConvertedValue = ADC_Read (ADC_Channel_16);
        vTempSensor = (float) TempSensor_ConvertedValue / 4095;    // vTempSensor (mV) = 3300 * (TempSensor_ConvertedValue / 4095)
        vTempSensor *= 3300;
        temp = vTempSensor - 760;    // temp (ºC) = ((Vsense - V25) / Average_slope) + 25
        temp /= 2.5;
        temp += 25;

        Vref_ConvertedValue = ADC_Read (ADC_Channel_17);

        Vbat_ConvertedValue = ADC_Read (ADC_Channel_18);
        Vbat = (float) Vbat_ConvertedValue * 2;    // Internal bridge divider by 2
        Vbat /= 4095;    // Vbat (mV) = 3300 * ((Vbat_ConvertedValue * 2) / 4095)
        Vbat *= 3300;
    }
}

La información de las expresiones mostradas se puede ver en UM1472 User Manual.
En el siguiente vídeo se puede ver una demostración de esto.


En el siguiente enlace a dropbox se encuentra la programación de este ultimo ejemplo: ADC SoftConv STM32F4 Discovery Dropbox

Para finalizar esta primera parte de la entrada, veremos como activar la conversión del ADC mediante un trigger (pin de entrada/salida o timer). En este caso nos decantaremos por la opción de la interrupción externa, de modo que procederemos a configurar el puerto PD11 (podría ser cualquier otro puerto pero siempre el pin 11) como entrada digital y seguidamente su interrupción, como se ve a continuación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
void EXTI_Configure (void)
{
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB2PeriphClockCmd (RCC_APB2Periph_SYSCFG, ENABLE);

    SYSCFG_EXTILineConfig (EXTI_PortSourceGPIOD, EXTI_PinSource11);

    EXTI_InitStructure.EXTI_Line = EXTI_Line11;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_Init (&EXTI_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x2;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init (&NVIC_InitStructure);
}

En mi caso puenteé el pin PA0 conectado al botón de usuario con el usado aquí para facilitar la generación del pulso para la interrupción.
También tendremos que modificar la configuración del ADC para que se active la conversión a partir de la interrupción programada.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void ADC_Configure (void)
{
    ADC_InitTypeDef ADC_InitStructure;
    ADC_CommonInitTypeDef ADC_CommonInitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB2PeriphClockCmd (RCC_APB2Periph_ADC1, ENABLE);

    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_Ext_IT11;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfConversion = 4;
    ADC_Init (ADC1, &ADC_InitStructure);

    ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
    ADC_CommonInit (&ADC_CommonInitStructure);

    ADC_TempSensorVrefintCmd (ENABLE);
    ADC_VBATCmd (ENABLE);

    ADC_RegularChannelConfig (ADC1, ADC_Channel_1, 1, ADC_SampleTime_3Cycles);
    ADC_RegularChannelConfig (ADC1, ADC_Channel_16, 2, ADC_SampleTime_3Cycles);
    ADC_RegularChannelConfig (ADC1, ADC_Channel_17, 3, ADC_SampleTime_3Cycles);
    ADC_RegularChannelConfig (ADC1, ADC_Channel_18, 4, ADC_SampleTime_3Cycles);

    ADC_EOCOnEachRegularChannelCmd (ADC1, ENABLE);

    ADC_ITConfig (ADC1, ADC_IT_OVR, ENABLE);
    ADC_ITConfig (ADC1, ADC_IT_EOC, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
    NVIC_Init (&NVIC_InitStructure);

    ADC_Cmd (ADC1, ENABLE);
}

El funcionamiento esperado de este código es que tras la interrupción generada en el pin PD11 se active la conversión de los canales del ADC, de forma que con cada conversión, se genera una interrupción de fin de conversión (función ADC_EOCOnEachRegularChannelCmd), y tener un contador en la interrupción que controle cual es el canal convertido.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void ADC_IRQHandler (void)
{
    if (ADC_GetITStatus (ADC1, ADC_IT_EOC) != RESET)
    {
        static int i = 0;

        if (i == 0)
        {
            ADC_ConvertedValue = ADC_GetConversionValue (ADC1);
            i++;
        }
        else if (i == 1)
        {
            TempSensor_ConvertedValue = ADC_GetConversionValue (ADC1);
            i++;
        }
        else if (i == 2)
        {
            Vref_ConvertedValue = ADC_GetConversionValue (ADC1);
            i++;
        }
        else if (i == 3)
        {
            Vbat_ConvertedValue = ADC_GetConversionValue (ADC1);
            i = 0;
        }

        ADC_ClearITPendingBit (ADC1, ADC_IT_EOC);
    }
    else if (ADC_GetITStatus (ADC1, ADC_IT_OVR) != RESET)
    {
        ADC_ClearITPendingBit (ADC1, ADC_IT_OVR);
    }
}

void EXTI15_10_IRQHandler (void)
{
    if (EXTI_GetITStatus (EXTI_Line11) != RESET)
    {
        EXTI_ClearITPendingBit (EXTI_Line11);
    }
}

Ademas, para que el ADC sepa que tiene que realizar mas de una conversión, además de modificar el valor de ADC_NbrOfConversion se deberá activar el modo Scan
Como he dicho en el parrafo anterior, el funcionamiento esperado es el comentado. Pero al momento de probarlo no es así. El fallo (no he conseguido determinarlo) es que al inicio de la conversión, tras la detección de la interrupción, se activa el flag de Overrun (pérdida de datos de las conversiones), de forma que impide que la conversión de canales continúe. He intentado solventarlo, activando dicha interrupción y limpiando su flag, pero con la siguiente interrupción externa se vuelve a generar la comentada y no permite finalizar todas las conversiones. Si consigo dar con la solución o alguien tuviera idea de como resolverlo, bienvenida sea la ayuda para poderlo corregir.
He comentado todo esto debido a que, como expondré en la segunda parte, esta forma de convertir varios canales se prefiere usarla junto con el DMA, de forma que no se interrumpe al microcontrolador y se realiza mas rápido la transferencia de los valores convertidos a memoria. En cualquier caso, vuelvo a dejar un archivo de dropbox con esta configuración: ADC ExtTrig STM32F4 Discovery Dropbox

3 comentarios:

  1. Muchisimas gracias por el artículo, me ha servido de muchísima ayuda. Estoy son un STM32F429 haciendo varias lecturas de ADC y por ello estoy cacharreando con él para ver si consigo entender todo su funcionamiento y asi aplicar la mejor manera de realizar las conversiones, así que, gracias a tu artículo, me va quedando bastante mas claro todo. Paso a leer la parte 2 ;)

    ResponderEliminar
    Respuestas
    1. Hola, muchas gracias por tu comentario. Me alegro que te haya servido de ayuda. Espero retomar pronto el hilo y subir algunos ejemplo sencillos de otras funcionalidades. Que vaya bien los proyectos.
      Saludos.

      Eliminar
  2. Hola, agradecido por el material, me sirve en gran manera, ya que recién estoy comenzando en éste mundo de las tarjetas de desarrollo.

    ResponderEliminar