Jump to content
43oh

Stellaris fast analog reads


Recommended Posts

Hi folks,

 

I would like to do some pretty fast analog reads with the stellaris board. Preferably just from one pin.

 

I loaded a snippet of code using 100 sequential reads

....
Sample[0]=analogRead(PE_3);
Sample[1]=analogRead(PE_3);
Sample[2]=analogRead(PE_3);
Sample[3]=analogRead(PE_3);
Sample[4]=analogRead(PE_3);
Sample[5]=analogRead(PE_3);
Sample[6]=analogRead(PE_3);
....
etc.

And measured the time in microseconds at the beginning and end => and got 1434 microseconds to reel in 100 samples

Assuming my maths is right, that's ~69,400 samples per second or one sample every 14.4 microseconds.

 

I would love to get this down to pretty close to 1 sample every microsecond or as close as possible to it.

 

I am doing the code in energia as this is what I am most comfortable with. I had a play with Code Composer but I have decided that is for smarter folks than me. ;)

 

Have found some very interesting links for the arduino for speeding up analog reads

http://forum.arduino.cc/index.php/topic,6549.0.html

 

Plus some other links using external ADC chips (doesn't suit me as I don't have enough spare pins, Though I could change a few things to get them if it was my only option)

http://bobdavis321.blogspot.com.au/2013/06/arduino-powered-3-million-samples-per.html

 

Soooo..... Are there any tricks (Energia style) you folks know of where I could squeeze a bit higher sampling rate?

 

Its all very early days at the moment and I am just trying to sort out my plan of attack.

Any advice or suggestions greatly appreciated.

 

Thanks

 

PTB (The perpetual Noob)

Link to post
Share on other sites
  • Replies 46
  • Created
  • Last Reply

Top Posters In This Topic

Top Posters In This Topic

Popular Posts

Well, here is my 2 cents.   Im working on a kind of DSO using the stellaris launchpad, and visual basic 2010.   Right now, the stellaris can sample in 1 channel (pin PA7) at different speeds, sele

This is my results: Number of Samples : 255                                                Sample Starttime : 378625230                                           Sample Endtime : 378625913          

And Here's the Code // // LM4F_ADC // // Description of the project // Developed with [embedXcode](http://embedXcode.weebly.com) // // Author Rei VILO // Rei Vilo // // Date 21/07/13 09:4

Posted Images

As @JKabat rightly said, 

 

 

 

Remember, HWREG is your friend!

 

Please refer to this thread where I've explored the fast read/write operation with Energia.

 

 

As rightly pointed by @@chicken, the following pre-processing statement is required:

#include "inc/lm4f120h5qr.h"

Without going as deep as recommended by @@jkabat, the functions mentioned by @@igor are available in Energia in the gpio.c library:

GPIODirModeSet(unsigned long ulPort, unsigned char ucPins, unsigned long ulPinIO)

GPIOPinWrite(unsigned long ulPort, unsigned char ucPins, unsigned char ucVal)

GPIOPinRead(unsigned long ulPort, unsigned char ucPins)

Yes, this is for a 8-bit parallel screen  :)

Link to post
Share on other sites

@Rei Vilo
 
You need to look at the source for wiring_analog.c and driverlib\adc.c./ There is a lot of overhead that can be removed. Most routines in adc.c boild down to 1 line that can be inserted into a copy of analogRead. this will run faster and eliminate the ROM_ calls.
 
If you are really ambitious you can move the ADC code to use sequencer 0 rather than 3. Sequencer 0 has a 16 entry fifo for readings. you can interrupt at half full to read the values. 
As the next step use DMA instead of interrupts to directly place the values in memory.
 
This is all off the top of my head after a quick peek at the source for the analog stuff.
 
Have fun!
 
John

Link to post
Share on other sites

Ok,

 

Have read through (and watched) a lot of that stuff and my head is spinning, but I think I am getting close.

 

I added these lines.....

#define SENSOR_INT_BASE           GPIO_PORTE_BASE
#define SENSOR_INT_PIN            GPIO_PIN_3

then in main program added

for (uint16_t i=0;i<SampleQty;i++)
{
//  Sample[i]=analogRead(PE_3);
Sample[i] = GPIOPinRead(SENSOR_INT_BASE, SENSOR_INT_PIN);
} 

This compiles and runs ok. I am using SampleQty = 255 and this is giving me 3673 microseconds to get 255 samples => Or 1 sample every 14.4 microseconds for the old analogread.

When using the new HWREG version which I understand is buried within GPIOPinRead. I am getting 255 samples in 71 microseconds.

 

That can't be right can it? I know its supposed to be fast but...... yikes.

 

I have a basic voltage divider hooked up with one resistor going from Pin1 to pin29 and another resistor going from Pin 22 to Pin 29.

 

The analog read version returns values of around the 4000 mark and the HWREG version returns all zeros.

 

Analogread strikes me as being wrong as I thought it was supposed to return between o and 1023. (Or is that an arduino thing).

HWREG strikes me as wrong as it is returning zeros which it definitely isn't.

 

Is there something else I need to set ?

 

Thanks in advance

 

PTB

Link to post
Share on other sites

PTB,

 

There are many things wrong here. 

 

GPIOPIN read does a digital read of the pin not an analog read!  Take a look at the LM4F version of wiring_analog.c.

Your best bet is to to duplicate analog_read and remove all the calls to ROM_ and place the code that was called inline. See driverlib/adc.c for the code being called.

 

In addition there is code in analog_read that initializes everything each time it is called.  You can split this out and put the guts of the routine in a loop. Should run faster.

 

Regards

John

Link to post
Share on other sites

PTB,

 

Your problem intrigued me so i did some thinking. (In my day job optimization and speed is a way of life!)

 

I have split out into some semi-optimized routines below. This is more or less a direct translation of analogRead in wiring.c.  IT HAS NOT BEEN TESTED but should work. (I think so)

 

 

your routine should be:

 

fast_analogInit(PE_3);

for (uint16_t i=0;i<SampleQty;i++)
{
// Sample=analogRead(PE_3);
//Sample[i] = GPIOPinRead(SENSOR_INT_BASE, SENSOR_INT_PIN);

Sample = fast_analogRead();
}

 

for further optimization you could have the sequencer run continuously and/or place the result directly in your table.   Be careful Sequencer 3 has a FIFO depth of only one entry . Change to sequencer 0 to get a deptf of 16.

 

 

Here is the my code:

unsigned long faBase;uint16_t faMask;#define SEQUENCER   3uint16_t fast_analogInit(uint8_t pin) {    uint8_t port = digitalPinToPort(pin);    uint16_t value[1];    uint32_t channel = digitalPinToADCIn(pin);    if (pin == NOT_ON_ADC) {                    //invalid ADC pin        return(0);    }    faBase = (uint32_t)portBASERegister(port);    faMask = digitalPinToBitMask(pin);        ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);    ROM_GPIOPinTypeADC((uint32_t)portBASERegister(port), digitalPinToBitMask(pin));    ROM_ADCSequenceConfigure(ADC0_BASE, SEQUENCER, ADC_TRIGGER_PROCESSOR, 0);    ROM_ADCSequenceStepConfigure(ADC0_BASE, SEQUENCER, 0, channel | ADC_CTL_IE | ADC_CTL_END);    ROM_ADCSequenceEnable(ADC0_BASE, SEQUENCER);            return(1);}uint16_t fast_analogRead(void) {    uint16_t value[1];        unsigned long *pulBuffer = &value[0];    unsigned long ulCount;    unsigned long ulBase = faBase;//    ROM_ADCIntClear(ADC0_BASE, 3);//// Clear the interrupt.//    HWREG(ADC0_BASE + ADC_O_ISC) = 1 << SEQUENCER;    //    ROM_ADCProcessorTrigger(ADC0_BASE, 3);//// Generate a processor trigger for this sample sequence.//    HWREG(ADC0_BASE + ADC_O_PSSI) |= ((SEQUENCER & 0xffff0000) |                                          (1 << (SEQUENCER & 0xf)));    //    while (!ROM_ADCIntStatus(ADC0_BASE, 3, false)) {}    while (!(HWREG(ADC0_BASE + ADC_O_ISC) & (0x10001 << SEQUENCER))) {}//    ROM_ADCIntClear(ADC0_BASE, 3);//// Clear the interrupt.//    HWREG(ADC0_BASE + ADC_O_ISC) = 1 << SEQUENCER;//    ROM_ADCSequenceDataGet(ADC0_BASE, 3, (unsigned long *)value);        //    // Get the offset of the sequence to be read.    //    ulBase += ADC_SEQ + (ADC_SEQ_STEP * SEQUENCER);        //    // Read samples from the FIFO until it is empty.    //    ulCount = 0;    while (!(HWREG(ulBase + ADC_SSFSTAT) & ADC_SSFSTAT0_EMPTY) && (ulCount < 8)) {        //        // Read the FIFO and copy it to the destination.        //        *pulBuffer++ = HWREG(ulBase + ADC_SSFIFO);                //        // Increment the count of samples read.        //        ulCount++;    }        return value[0];}    
Link to post
Share on other sites

JKabat,

 

Thanks for the seriously value added answers. Awesome. I will have a play with this as soon as I can. Hopefully tonight.

In my earlier haste I didnt realise I was looking at the digital pin read command GPIOPinRead instead of an analog. So another rookie mistake there on my part.

 

Appreciate the feedback.

 

Cheers

 

PTB

Link to post
Share on other sites

@@jkabat @@Rei Vilo

 

Ok,

 

I have had a plough through this and I appear to be stuck.

 

I had a look in wiring_analog.c and I can see the overall structure of what you have done with the 2 routines "fast_analogRead" and "fast_analogInit" being born from the standard "analogRead".

As far as what each new equivalent line means exactly is a struggle for me to fully comprehend at the moment.

 

I have spliced your code into my basic sketch. Posted in complete form below.

 

I currently can't get the routine to compile at the moment and I think the root cause is I am missing some kind of #include. I had a swim through the lm4f core subdirectories trying to find which ".h" may be being referenced but are not currently included. Couldn't see them.

 

The errors I am getting on compile are

Fast_Analog_Read.cpp: In function 'uint16_t fast_analogRead()':
Fast_Analog_Read.cpp:134:40: error: cannot convert 'uint16_t* {aka short unsigned int*}' to 'long unsigned int*' in initialization
Fast_Analog_Read.cpp:162:15: error: 'ADC_SEQ' was not declared in this scope
Fast_Analog_Read.cpp:162:26: error: 'ADC_SEQ_STEP' was not declared in this scope
Fast_Analog_Read.cpp:168:14: error: 'ADC_SSFSTAT' was not declared in this scope
Fast_Analog_Read.cpp:172:24: error: 'ADC_SSFIFO' was not declared in this scope

The complete code as it stands at the moment is

///
/// @file	Fast_Analog_Read.ino
/// @brief	Main sketch
///
/// @details	Fast Analog Read Investigations for Stellaris LM4F
///

//
// Stellaris Pin Assignments
// =========================
//PE_3    //Sensor Analog Read
//PA_5    //Trigger Analog Signal Pulse
//PF_4    //Push Button to Start

//
// Libraries
// =========
#include "Energia.h"
#include "SPI.h"
#include "inc/lm4f120h5qr.h"


#include "inc/hw_adc.h"
#include "inc/hw_gpio.h"
#include "inc/hw_memmap.h"
#include "inc/hw_sysctl.h"

#include "driverlib/adc.h"
#include "driverlib/gpio.h"
#include "driverlib/sysctl.h"
#include "driverlib/rom.h"





#define SENSOR_INT_BASE           GPIO_PORTE_BASE
#define SENSOR_INT_PIN            GPIO_PIN_3



//
// Global Constants
// ================
const byte SampleQty = 255;               // number of samples  
const uint16_t buttonPin = PUSH1;         // the number of the pushbutton pin
const uint16_t ledPin =  GREEN_LED;       // the number of the LED pin
const uint16_t TriggerPin = PA_5;         // Analog Signal Trigger connected to digital pin PA_5


//
// Global Variables
// ================

uint16_t SampleTest;
uint16_t buttonState = 0;         // variable for reading the pushbutton status
uint16_t Sample[SampleQty];       // Array variable to store the value coming from the sensor
unsigned long SampleStartTime;
unsigned long SampleEndTime;
unsigned long SampleDuration;


//
// JKabat
// ================
unsigned long faBase;
uint16_t faMask;
#define SEQUENCER   3

///**************************************************************************
///
///                                   Setup code
///
///**************************************************************************
void setup() {
Serial.begin(9600) ;               //Debugging initialize the serial communication:
pinMode(ledPin, OUTPUT);           // initialize the LED pin as an output:    
pinMode(buttonPin, INPUT_PULLUP);  // initialize the pushbutton pin as an input:
pinMode(TriggerPin, OUTPUT);       // initialize the Trigger pin as an output:    
}//end setup

///**************************************************************************
///
///                                   Loop code
///
///**************************************************************************
void loop() {
buttonState = digitalRead(buttonPin);  // read the state of the pushbutton value:
if (buttonState == LOW) {     
digitalWrite(ledPin, HIGH);            // turn LED on:
fast_analogInit(PE_3);                 // JKabat
SampleStartTime = micros();
digitalWrite(TriggerPin, HIGH);        // Start the Analog Signal 
for (uint16_t i=0;i<SampleQty;i++)
 {
//  Sample[i]=analogRead(PE_3);                                  // Original Analog Read
//  Sample[i] = GPIOPinRead(SENSOR_INT_BASE, SENSOR_INT_PIN);    // 2nd failed attempt
Sample[i] = fast_analogRead();                                   // JKabat
 } 
SampleEndTime = micros();
//Dump results to serial monitor
for (uint16_t i=0;i<SampleQty;i++)
{
  Serial.println(Sample[i]);
} 
Serial.print("Number of Samples : ");
Serial.println(SampleQty);
Serial.print("Sample Starttime : ");
Serial.println(SampleStartTime);
Serial.print("Sample Endtime : ");
Serial.println(SampleEndTime);
SampleDuration = SampleEndTime - SampleStartTime;
Serial.print("Sample Array Set Duration time in microseconds  : ");
Serial.println(SampleDuration);
}// end if 
delay(1000);
digitalWrite(ledPin, LOW);  // turn LED off:
}//end loop


///**************************************************************************
///
///                                   Fast Analog Read Routine
///
///**************************************************************************
uint16_t fast_analogRead(void) {
    uint16_t value[1];
    
    unsigned long *pulBuffer = &value[0];
    unsigned long ulCount;
    unsigned long ulBase = faBase;
//    ROM_ADCIntClear(ADC0_BASE, 3);
//
// Clear the interrupt.
//
    HWREG(ADC0_BASE + ADC_O_ISC) = 1 << SEQUENCER;
    
//    ROM_ADCProcessorTrigger(ADC0_BASE, 3);
//
// Generate a processor trigger for this sample sequence.
//
    HWREG(ADC0_BASE + ADC_O_PSSI) |= ((SEQUENCER & 0xffff0000) | 
                                         (1 << (SEQUENCER & 0xf)));
    
//    while (!ROM_ADCIntStatus(ADC0_BASE, 3, false)) {}
    while (!(HWREG(ADC0_BASE + ADC_O_ISC) & (0x10001 << SEQUENCER))) {}
//    ROM_ADCIntClear(ADC0_BASE, 3);
//
// Clear the interrupt.
//
    HWREG(ADC0_BASE + ADC_O_ISC) = 1 << SEQUENCER;
//    ROM_ADCSequenceDataGet(ADC0_BASE, 3, (unsigned long *)value);
    
    //
    // Get the offset of the sequence to be read.
    //
    ulBase += ADC_SEQ + (ADC_SEQ_STEP * SEQUENCER);
    
    //
    // Read samples from the FIFO until it is empty.
    //
    ulCount = 0;
    while (!(HWREG(ulBase + ADC_SSFSTAT) & ADC_SSFSTAT0_EMPTY) && (ulCount < 8)) {
        //
        // Read the FIFO and copy it to the destination.
        //
        *pulBuffer++ = HWREG(ulBase + ADC_SSFIFO);
        
        //
        // Increment the count of samples read.
        //
        ulCount++;
    }
    
    return value[0];
}    

///**************************************************************************
///
///                                   Fast Analog Init Routine
///
///**************************************************************************
  uint16_t fast_analogInit(uint8_t pin) {
    uint8_t port = digitalPinToPort(pin);
    uint16_t value[1];
    uint32_t channel = digitalPinToADCIn(pin);
    if (pin == NOT_ON_ADC) {                    //invalid ADC pin
        return(0);
    }
    faBase = (uint32_t)portBASERegister(port);
    faMask = digitalPinToBitMask(pin);
    
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
    ROM_GPIOPinTypeADC((uint32_t)portBASERegister(port), digitalPinToBitMask(pin));
    ROM_ADCSequenceConfigure(ADC0_BASE, SEQUENCER, ADC_TRIGGER_PROCESSOR, 0);
    ROM_ADCSequenceStepConfigure(ADC0_BASE, SEQUENCER, 0, channel | ADC_CTL_IE | ADC_CTL_END);
    ROM_ADCSequenceEnable(ADC0_BASE, SEQUENCER);
    
    
    return(1);
}


I suspect with the proper includes, it will be all sytems go.

 

Thanks

PTB

Link to post
Share on other sites
Fast_Analog_Read.cpp: In function 'uint16_t fast_analogRead()':
Fast_Analog_Read.cpp:134:40: error: cannot convert 'uint16_t* {aka short unsigned int*}' to 'long unsigned int*' in initialisation

@@PTB

 

You're doing right with using the C99 types instead of the literal ones, impossible to read and use...

 

Unfortunately, unsigned long means uint32_t and not uint16_t. Just change the value[1definition for uint32_t value[1];

Fast_Analog_Read.cpp:162:15: error: 'ADC_SEQ' was not declared in this scope
Fast_Analog_Read.cpp:162:26: error: 'ADC_SEQ_STEP' was not declared in this scope
Fast_Analog_Read.cpp:168:14: error: 'ADC_SSFSTAT' was not declared in this scope
Fast_Analog_Read.cpp:172:24: error: 'ADC_SSFIFO' was not declared in this scope

Those constants are in adc.c but not in adc.h, so they aren't included with the #include "driverlib/adc.h"

 

As a solution, add those line to your sketch:

// Constants from adc.c
#define ADC_SEQ                 (ADC_O_SSMUX0)
#define ADC_SEQ_STEP            (ADC_O_SSMUX1 - ADC_O_SSMUX0)
#define ADC_SSMUX               (ADC_O_SSMUX0 - ADC_O_SSMUX0)
#define ADC_SSEMUX              (ADC_O_SSEMUX0 - ADC_O_SSMUX0)
#define ADC_SSCTL               (ADC_O_SSCTL0 - ADC_O_SSMUX0)
#define ADC_SSFIFO              (ADC_O_SSFIFO0 - ADC_O_SSMUX0)
#define ADC_SSFSTAT             (ADC_O_SSFSTAT0 - ADC_O_SSMUX0)
#define ADC_SSOP                (ADC_O_SSOP0 - ADC_O_SSMUX0)
#define ADC_SSDC                (ADC_O_SSDC0 - ADC_O_SSMUX0)

Please find enclosed the sketch I've compiled with embedXcode and Energia successfully. 

 

post-389-1426460529384_thumb.png

 

I couldn't test it as I don't have a StellarPad board at hand right now.

 

LM4F_ADC.ino.zip

Link to post
Share on other sites

@JKabat @Rei Vilo

 

Ok folks,

 

Now it (LM4F_ADC.ino) compiles ok, but execution hangs.

 

It appears to get stuck on this line within "fast_analogRead" routine.

    while (!(HWREG(ADC0_BASE + ADC_O_ISC) & (0x10001 << SEQUENCER))) {}

I'm having a bit of a play to see if I can figure what is going on. This is quite the learning experience ;)

 

Cheers

 

PTB

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...