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.
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
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
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?
Deja una respuesta
Lo siento, debes estar conectado para publicar un comentario.