En el post anterior, pudimos comprobar lo facil que nos va a resultar usar al Stellaris Launchpad con el IDE Energía, como reemplazo del Arduino.

En este post vamos a revisar como usar las interrupciones del timer, para poder ejecutar eventos periódicamente. El Launchpad que estamos usando (LM4F120XL) tiene 27 timers que pueden ser activados independientemente.

Para poder utilizar el timer, en este caso el Timer 0 del Launchpad, necesitamos implementar 2 rutinas: 1 de inicialización y otra de interrupción. Para nuestro ejemplo, las rutinas son las siguientes:

Inicialización:

void initTimer(unsigned Hz)
{
  SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
  TimerConfigure(TIMER0_BASE, TIMER_CFG_PERIODIC);
  unsigned long ulPeriod = (SysCtlClockGet() / Hz) / 2;
  TimerLoadSet(TIMER0_BASE, TIMER_A, ulPeriod -1);
  IntEnable(INT_TIMER0A);
  TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
  TimerIntRegister(TIMER0_BASE, TIMER_A, Timer0IntHandler);
  TimerEnable(TIMER0_BASE, TIMER_A);
  
}

Interrupción:

void Timer0IntHandler()
{
  
  TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
  digitalWrite(RED_LED, j&0x01);  
  j++;

}

Como podemos observar, la rutina de inicialización recibe como parámetro la frecuencia de activación del timer en Hz. Y la rutina de interrupción, simplemente activa o desactiva el led Rojo, para comprobar la funcionalidad del timer.

El sketch completo sería el siguiente:

#include "Energia.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_ints.h"
#include "driverlib/debug.h"
#include "driverlib/interrupt.h"
#include "driverlib/sysctl.h"
#include "driverlib/timer.h"

int j=0;

void setup()
{
  pinMode(RED_LED, OUTPUT);
  initTimer(1); // timer a 1 hz
  
}

void loop()
{
  // put your main code here, to run repeatedly:
  while(1) {} 
}

void initTimer(unsigned Hz)
{
  SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
  //TimerConfigure(TIMER0_BASE, TIMER_CFG_32_BIT_PER);
  TimerConfigure(TIMER0_BASE, TIMER_CFG_PERIODIC);
  unsigned long ulPeriod = (SysCtlClockGet() / Hz) / 2;
  TimerLoadSet(TIMER0_BASE, TIMER_A, ulPeriod -1);
  IntEnable(INT_TIMER0A);
  TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
  TimerIntRegister(TIMER0_BASE, TIMER_A, Timer0IntHandler);
  TimerEnable(TIMER0_BASE, TIMER_A);
  
}

void Timer0IntHandler()
{
  
  TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
  digitalWrite(RED_LED, j&0x01);  
  j++;

}

He decidido escribir una serie de posts, acerca del uso del Stellaris Launchpad. Este mini tutorial, vamos a analizar la primera versión del Stellaris Launchpad, que veía con el microcontrolador LM4F120H5QR, pero aplica a las nuevas versiones del board, que tienen el microcontrolador Tiva-C.

Este development board de Texas Instruments, sobresale de otros similares, por que su microcontrolador LM4F120XL tiene las siguientes características:

  • Microcontrolador Stellaris LM4F120H5QR con núcleo ARM® Cortex™-M4 con las siguientes características:
    • Operaciones de punto flotante
    • Velocidad de operación máxima de 80 MHz
    • 256 KB de memoria flash
    • EEPROM interna
    • SRAM de 32 KB
    • 8 UARTs
    • 4 I2C
    • 4 SPI
    • 27 Temporizadores
  • Interfaz ICDI (In-Circuit Debug Interface) USB incluida en la tarjeta.
  • Conexión micro USB-B para depuración.
  • Conexión micro USB-B de dispositivo para aplicaciones de usuario.
  • Switch selector de alimentación de dispositivo/depuración.
  • Pines BoosterPack XL, compatible con muchas tarjetas BoosterPack existentes.
  • 2 botones para aplicaciones de usuario
  • Botón de reset
  • 1 LED RGB
  • Puente (jumper) para medición de corriente
  • Cristal principal de 16 MHz
  • Cristal de 32.768 KHz para hibernación y reloj en tiempo real (RTC – Real Time Clock)

Si lo comparamos con el cásico y conocido Arduino, vemos que tenemos un procesador mucho más poderoso, con muchos mas pines de Entrada y Salida y mas periféficos, lo que permitirá que podamos diseñar aplicaciones embebidas con mayor facilidad. Además de esto, el Stellaris Launchpad, tiene un precio aproximado de USD.12,00  (en su nueva versión, que lleva un microcontrolador TIVA C) lo que lo coloca al nivel de los clones del Arduino Uno, por lo que vale la pena el intento.

Texas Instruments, con el fin de promover el uso de sus microcontroladores, permite descargar de forma gratuita (luego de un pequeño registro) sus herramientas de desarrollo, y son bastante completas. Pero al ser un entorno de desarrollo basado en Eclipse, es bastante lento y pesado, si no tienes un computador bastante potente.

Es aquí cuando aparece Energia. Este entorno de desarrollo, ha sido pensado para facilitar el desarrollo de aplicaciones usando la linea Stellaris de TI, presentando la posibilidad de usar el IDE de Arduino, para los procesadores de TI. Con este entorno, podemos programar fácil y rápidamente aplicaciones con el Launchpad, de la misma manera que lo hacemos con el Arduino, traduciéndose esto en velocidad de desarrollo, y facilidad de uso. Además, Energía es multiplataforma, existiendo versiones para Windows, MacOS y Linux.

En primer lugar, y si estamos trabajando bajo Windows, debemos descargar los Drivers Stellaris ICDI  para USB. esto lo podemos descargar desde este enlace. Si estamos usando Linux o MacOS, podemos saltarnos este paso sin problema.

Una vez descargados los drivers, los descomprimimos en alguna carpeta facil de ubicar, y procedemos a conectar el Launchpad a nuestro puerto USB. Se iniciará el proceso de detección del dispositivo, y deberemos buscar la carpeta donde se descomprimió nuestro Driver, para que windows lo detecte e instale los dispositivos. Si todo sale bien, debemos tener 3 dispositivos nuevos en nuestro computador.


stellaris1

 

Luego descargamos el IDE  Energia desde este enlace, escogiendo la versión que corresponda a nuestro sistema operativo. La descarga es algo grande (alrededor de 200MB) así que tomará algunos minutos. Una vez concluida la descarga, lo descomprimimos en una carpeta (puede ser en Mis Documentos) y listo, podemos empezar.

Al ejecutar Energia, a primera vista, observamos un clon del IDE de Arduino (algo bastante obvio, porque es un fork) pero en color rojo. Además del cambio de color, todas las barras de herramientas y menús, son identicos al IDE de Arduino.

 

Antes de empezar a programar, debemos cerciorarnos de que estamos utilizando la configuración correcta para nuestro Launchpad. Para esto, debemos seleccionar el puerto serie, y tipo de board correctos en el menu Herramientas. En mi caso estoy usando el puerto COM7 y el board LM4F120, que es de la primera serie de los Launchpad con Cortex M4.

stellaris3

Ahora si tenemos todo listo para empezar a programar, y vamos a empezar con el “Hello World!” de los microcontroladores: El blink. Vamos a crear un pequeño sketch que encienda el led Rojo del Launchpad, espere 1 segundo, lo apague, espere 1 segundo, y lo vuelva a encender. Para esto, pegamos el siguiente código en la ventana del editor Energia.

#define LED RED_LED
void setup() {                
  pinMode(LED, OUTPUT);     
}

void loop() {
  digitalWrite(LED, HIGH);   
  delay(1000);               
  digitalWrite(LED, LOW);    
  delay(1000);              
}

Una vez copiado el código, presionamos en boton de descarga, y listo! El Led rojo de nuestro Launchpad empieza a parpadear.

Si queremos jugar un poco, podemos cambiar el color del Led. El launchpad incluye un led RGB, que podemos hacerlo parpadear cambiando esta línea:

#define LED GREEN_LED

O por

#define LED BLUE_LED

En los próximos días, continuaré con la serie, explicando como usar los distintos periféricos, y algunas funciones avanzadas del Launchpad.

Todo el mundo quiere un osciloscopio! (bueno, todos los que trabajan en electrónica jejeje), y para no quitarme el gustito de construir un osciloscopio digital, desde cero, hice algunas pruebas con mi stellaris launchpad, que como comentaba en un post anterior, logré que muestree a casi 1Msps. Modificando un poco el código del stellaris, bajé la velocidad de muestreo a 125Ksps, e hice algunas capturas de la señal PPM que genera mi transmisor de RC basado en Arduino.

Para visualizar la señal armé una pequeña app en Visual Basic 2010, que recibe los datos del puerto serie del stellaris, y los grafica sobre un Chart Control, que resulta que es más facil de usar de lo que han pensado.

Les dejo un par de fotos, y el código fuente del Stellaris y de  la Aplicación de Visual Basic

Captura PPM a 125Ksps, 2048 muestras

dso

 

Captura PPM a 125Ksps, 4096 muestras

dso1

 

 

 

Luego de tenerlo algún tiempo empolvándose, decidí darle una prueba al Stellaris Launchpad que compré  (en $5.00) cuando lo lanzaron hace más de 6 meses, y la verdad que el precio de $12.00 actual, sigue siendo una ganga, para toda la potencia de este microcontrolador.

StellarisLaunchPad_estore

Entre los periféricos importantes de este micro, podemos comentar:

  • Reloj principal de 80Mhz y 32khz para modo de bajo consumo
  • 256kb de memoria flash
  • 32kb de memoria ram
  • 2kb de memoria EEPROM
  • Controlador uDMA de 32 bits
  • 2 ADC de 12 bits – 1MSPS
  • Comparadores Analógicos
  • 8 puertos UART
  • 4 puertos SPI
  • Puerto USB Host/Device/OTG
  • 12 Timers
  • 16 Salidas PWM
  • Puertos GPIO
  • Debugger integrado en la tarjeta
  • 2 pulsantes, y 1 led RGB para pruebas

stellaris_perifericos

 

Como vemos, este micro, tiene mucha potencia en ese pequeño paquete, pero tanta potencia tiene un precio. Desarrollar para este micro puede convertirse en un dolor de cabeza, ya que es necesario utilizar Code Composer Studio de TI. Este IDE basado en Eclipse, nos permite programar y depurar al stellaris, pero su gran tamaño (la descarga es de 1GB aproximadamente) y lo lento de Eclipse, puede hacernos despechar de cualquier proyecto pequeño que tengamos.

Afortunadamente, existe el Proyecto Energia, que nos permite programar al Stellaris Launchpad con una interfaz de programación similar a la de nuestro conocido Arduino, y nos brinda facilidad de programación, usando toda la potencia del Stellaris.

Al descargar y abrir Energia, vamos a tener una interfaz idéntica a la del IDE de Arduino, salvo por el color rojo de la misma (Rojo Stellaris XD), y podemos compilar algunos de los ejemplos, como el típico Blink.ino incluido dentro del IDE

stellarisblink

Ahora, vamos a hacer algo más útil. Aprovechando los ADC de alta velocidad, vamos a intentar capturar datos con el ADC a la máxima velocidad posible (tomando en cuenta que lso ADC son de 1MSPS).

Por ventaja para nosotros, Energía trae precompiladas todas las librerías Stellarisware,  que nos brindan rutinas de configuración y acceso a todos los periféricos de nuestro micro. Con estas librerías, programamos un sketch que va a hacer 3 cosas:

  • Habilitar el puerto serial para envío y recepción de datos
  • Capturar datos con el ADC, usando interrupciones, iniciando las interrupciones del ADC al recibir un dato desde el puerto serial. Se capturará 2048 datos en ráfaga, sin usar DMA.
  • Generar un timer de 1hz para referencia

Nuestro sketch para realizar esto es el siguiente:

#include "Energia.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_ints.h"
#include "driverlib/debug.h"
#include "driverlib/interrupt.h"
#include "driverlib/sysctl.h"
#include "driverlib/adc.h"
#include "driverlib/timer.h"

int i=0;
int j=1;
int dato0=0;
int adc[1024];
unsigned char dataReady=0;
unsigned long ulADC0Value[1];
unsigned long time1;
unsigned long time2;
unsigned long ttotal;

void setup() {
  Serial.begin(115200);
  pinMode(RED_LED, OUTPUT);
  pinMode(BLUE_LED, OUTPUT);
  pinMode(GREEN_LED, OUTPUT);

  initADC();  
  initTimer(2);

  Serial.println("Inicio OK. ");
  Serial.print(SysCtlClockGet());
  Serial.println(" hz");

}

void loop() {
  unsigned char serialIn;
  int i;
   digitalWrite(GREEN_LED,1); 

   if (Serial.available() > 0) {
     serialIn=Serial.read();
     ADCIntEnable(ADC0_BASE, 3);
   } 

   if (dataReady!=0) {
     Serial.println("Datos ADC");
     for (i=0;i<=1023;i++) {
         Serial.println(adc[i]);
     }

     dataReady=0;
   }
}

 void Timer0IntHandler()
{

  // Clear the timer interrupt
  TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
  //leeADC();
  digitalWrite(RED_LED, j&0x01);  
  j++;

}

void ADC0IntHandler() {
   ADCIntClear(ADC0_BASE,3);
   ADCSequenceDataGet(ADC0_BASE, 3, ulADC0Value);
   adc[i]=(int)ulADC0Value[0];  
   i++;
   if (i&0b10000000000) { i=0; ADCIntDisable(ADC0_BASE, 3); dataReady=1;}
   //ADCProcessorTrigger(ADC0_BASE, 3);
   //digitalWrite(RED_LED, i&0x01);
}

void initTimer(unsigned Hz)
{
  SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
  //TimerConfigure(TIMER0_BASE, TIMER_CFG_32_BIT_PER);
  TimerConfigure(TIMER0_BASE, TIMER_CFG_PERIODIC);
  unsigned long ulPeriod = (SysCtlClockGet() / Hz) / 2;
  TimerLoadSet(TIMER0_BASE, TIMER_A, ulPeriod -1);
  IntEnable(INT_TIMER0A);
  TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
  TimerIntRegister(TIMER0_BASE, TIMER_A, Timer0IntHandler);
  TimerEnable(TIMER0_BASE, TIMER_A);

}

void initADC(void) {
	SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
	SysCtlADCSpeedSet(SYSCTL_ADCSPEED_1MSPS);
        //SysCtlADCSpeedSet(SYSCTL_ADCSPEED_500KSPS);
	ADCSequenceDisable(ADC0_BASE, 3);
	//ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_PROCESSOR, 0);
        ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_ALWAYS, 0);
	ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_CH0 | ADC_CTL_IE | ADC_CTL_END); //Sequencer 3 Step 0: Samples Channel PE3
	//ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_TS | ADC_CTL_IE | ADC_CTL_END); //Sequencer 3 Step 0: Samples TS
        ADCIntRegister(ADC0_BASE, 3, ADC0IntHandler);
	ADCSequenceEnable(ADC0_BASE, 3);
        ADCIntEnable(ADC0_BASE, 3);
        //ADCProcessorTrigger(ADC0_BASE, 3);
}

Como vemos en el código, tenemos 2 rutinas de inicialización de periféricos, 2 rutinas de interrupción, y las cásicas setup() y loop() del programa principal. Facil no?

Ahora, debemos tener una consideración adicional. El compilador para este procesador Cortex, necesita un archivo llamado startup.gcc (que se encuentra dentro del IDE Energia) que define las subrutinas de interrupción de cada uno de los periféricos. Es por esto, que debemos modificar el nuestro, para que quede algo así:

/*
 * create some overridable default signal handlers
 */
__attribute__((weak)) void UARTIntHandler(void) {}
__attribute__((weak)) void ToneIntHandler(void) {}
__attribute__((weak)) void I2CIntHandler(void) {}
__attribute__((weak)) extern void Timer0IntHandler(void) {}
__attribute__((weak)) extern void ADC0IntHandler(void) {}

y un poco mas abajo

__attribute__ ((section(".isr_vector")))
void (* const g_pfnVectors[])(void) =
{
   (void *)&_estack,                        // The initial stack pointer, 0x20008000 32K
    ResetISR,                               // The reset handler
    NmiSR,                                  // The NMI handler
    FaultISR,                               // The hard fault handler
    IntDefaultHandler,                      // The MPU fault handler
    IntDefaultHandler,                      // The bus fault handler
    IntDefaultHandler,                      // The usage fault handler
    0,                                      // Reserved
    0,                                      // Reserved
    0,                                      // Reserved
    0,                                      // Reserved
    IntDefaultHandler,                      // SVCall handler
    IntDefaultHandler,                      // Debug monitor handler
    0,                                      // Reserved
    IntDefaultHandler,                      // The PendSV handler
    IntDefaultHandler,                      // The SysTick handler
    GPIOAIntHandler,                        // GPIO Port A
    GPIOBIntHandler,                        // GPIO Port B
    GPIOCIntHandler,                        // GPIO Port C
    GPIODIntHandler,                        // GPIO Port D
    GPIOEIntHandler,                        // GPIO Port E
    UARTIntHandler,                         // UART0 Rx and Tx
    UARTIntHandler,                         // UART1 Rx and Tx
    IntDefaultHandler,                      // SSI0 Rx and Tx
    I2CIntHandler,                          // I2C0 Master and Slave
    IntDefaultHandler,                      // PWM Fault
    IntDefaultHandler,                      // PWM Generator 0
    IntDefaultHandler,                      // PWM Generator 1
    IntDefaultHandler,                      // PWM Generator 2
    IntDefaultHandler,                      // Quadrature Encoder 0
    IntDefaultHandler,                      // ADC Sequence 0
    IntDefaultHandler,                      // ADC Sequence 1
    IntDefaultHandler,                      // ADC Sequence 2
    ADC0IntHandler,                      // ADC Sequence 3
    IntDefaultHandler,                      // Watchdog timer
    Timer0IntHandler,                      // Timer 0 subtimer A

podemos ver que el ADC sequece 3, y el Timer 0 tienen aquí definidas sus subrutinas en este archivo. Si quieren descargar el archivo completo, pueden usar este link.

Y que tal funciona? pues midiendo una señal de referencia de 8khz, vemos que muestrea eficientemente a 950Ksps, que es mucho más alto de los 10Khz que se obtiene con el Arduino… hay mejora cierto?