Trigger ADC from Timer Match

08 Mar 2011

Hi,

I'm puzzled about how to hardware trigger the ADC (in burst mode) from a Timer1 Match.

I tried Andy's DMA which worked like charm but has drawbacks on getting nice intervals as the conversion time is 65 cycles and the clock is derived from 96Mhz you cannot get an exact int number of ms for timing.

So my next bet is either Timer1 or the Repetitive Timer.

So the question: how do i connect this code to trigger the ADC in burst mode (need to convert two channels). I know I can software trigger from inside the timer interrupt routine but I would like to remove the timer interrupt handler al together and have the adc triggered by hardware.

Got the following code (ripped from a larger program) that I wrote to verify timer1 timing. I verified that it does have a nearly exact 10ms interval I want.

//------------------------------------------------------------------------------
//----TIMER1.
//------------------------------------------------------------------------------

volatile unsigned int timer1hits = 0;   //timer1 stops when timer1hits==imer1loop
unsigned long match = 10000-1;          //10ms (1Mhz/10000)
unsigned int timer1loop = 1000;         //nosamples
unsigned long prescaler =  96-1;        //96Mhz/96 = 1Mhz

//Resulting timing 1000 hits 9999.99900 ms.
//Ideal                     10000.00000 ms  ie a -0,001 % deviation

Timer match_timing;

//DigitalOut cap_led(LED2);

void Timer1_IRQHandler(void) {
    LPC_TIM1->MR0 = match;
    LPC_TIM1->IR = 1;

    //cap_led=!cap_led;

    timer1hits++;

    //TODO Debug Code (stop after timer1loop samples)...
    if (timer1hits==timer1loop) {
        match_timing.stop();
        LPC_TIM1->TCR = 0;           // Disable Timer
    }
}

This is called from a modified piece of code found on this forum:

            // Enable the ISR vector
            NVIC_SetVector (TIMER1_IRQn, (uint32_t)&Timer1_IRQHandler);
            NVIC_EnableIRQ(TIMER1_IRQn);

            // Set PCLK_TIMER1
            //                    PCLK_TIMER1 = CCLK/1 96M/1 = 96MHz
            LPC_SC->PCLKSEL0 &= ~(3UL << 4);   //clear bits
            LPC_SC->PCLKSEL0 |=  (1UL << 4);   //set bit

            //Reset Test Variable.
            timer1hits = 0;

            LPC_SC->PCONP |= 1 << 2;     // Power on Timer 1

            LPC_TIM1->TCR = 0x2UL;       // Reset and set to timer mode

            LPC_TIM1->CTCR = 0x0;        // Connect to prescaler
            LPC_TIM1->PR = prescaler;    // Prescale -> 1Mhz
            LPC_TIM1->MR0 = match;       // Match count for 10mS
            LPC_TIM1->MCR = 3;           // Interrupt and Reset on Match

            LPC_TIM1->TCR = 0x1UL;       // Enable Timer

            match_timing.start();

            //Pause 10 seconds.
            while (LPC_TIM1->TCR==1) {
                // wait(0.1);
            }

            //Moved code to interrupt routine.
            //LPC_TIM1->TCR = 0;           // Disable Timer
            //match_timing.stop();

            printf("%d hits\r\n", timer1hits);
            printf("%f ms\r\n", 0.001*match_timing.read_us());

            match_timing.reset();
        }
13 Mar 2011

Wim, here's an example (without DMA which has problems with an ADC conversion rate > 8MHz) but using interrupts. In this example we back-to-back read three AD channels, AD0.0 (p15), AD0.1 (p16) and AD0.2 (p17). Once every second and display the results to the serial port.

If you want to read the 3 channels back-to-back once every millisecond change SAMPLE_PERIOD to 1000

#include "mbed.h"

// How long between grabbing samples on all channels.
// Value is in microseconds. 
#define SAMPLE_PERIOD   1000000

#define START_COVERSION_NOW(ch) \
    LPC_ADC->ADCR=(0x1<<24)|(1UL<<21)|(1UL<<8)|ch;\
    NVIC_EnableIRQ(ADC_IRQn);\
    LPC_ADC->ADINTEN = 0x7
    
#define START_COVERSION_TIMED(ch) \
    adBufIn[0]=adBufIn[1]=adBufIn[2]=0;\
    LPC_ADC->ADCR=(0x6<<24)|(1UL<<21)|(1UL<<8)|ch;\
    NVIC_EnableIRQ(ADC_IRQn);\
    LPC_ADC->ADINTEN = 0x7;\
    LPC_TIM1->TCR=0;\
    LPC_TIM1->TCR=1

#define NUM_OF_SAMPLES  1

Serial pc(USBTX, USBRX);
DigitalOut led1(LED1);

uint32_t adBuffer[3][NUM_OF_SAMPLES];
int      adBufIn[3];

extern "C" void ADC_IRQHandler(void) __irq {
    uint32_t trash __attribute__((unused));
    switch(LPC_ADC->ADSTAT & 0xFF) {
        case 0x01: 
            adBuffer[0][adBufIn[0]++] = LPC_ADC->ADDR0; 
            START_COVERSION_NOW(0x2);
            break;
        case 0x02: 
            adBuffer[1][adBufIn[1]++] = LPC_ADC->ADDR1; 
            START_COVERSION_NOW(0x4);
            break;
        case 0x04: 
            adBuffer[2][adBufIn[2]++] = LPC_ADC->ADDR2; 
            if (adBufIn[2] != NUM_OF_SAMPLES) {
                START_COVERSION_NOW(0x1);
            }
            else {
                NVIC_DisableIRQ(ADC_IRQn);
                LPC_TIM1->TCR = 2;
            }
            break;
        default: // Shouldn't happen but just in case handle unrecognised channel
            trash = LPC_ADC->ADDR3;
            trash = LPC_ADC->ADDR4;
            trash = LPC_ADC->ADDR5;
            trash = LPC_ADC->ADDR6;
            trash = LPC_ADC->ADDR7;
            break;            
    }
    
    if (adBufIn[0] == NUM_OF_SAMPLES) {
        LPC_ADC->ADINTEN &= ~(1);
    }
    
    if (adBufIn[1] == NUM_OF_SAMPLES) {
        LPC_ADC->ADINTEN &= ~(2);
    }
    
    if (adBufIn[2] == NUM_OF_SAMPLES) {
        LPC_ADC->ADINTEN &= ~(4);        
    }
}

void printValue(int channel, int index) {
    int value = (adBuffer[channel][index] >> 4) & 0xFFF;
    double fVal = 3.3 * (double)((double)value) / ((double)0x1000); // scale to 0v to 3.3v 
    pc.printf("ADC input (channel=%d) = 0x%03x %01.3f volts\n", channel, value, fVal);
}

int main() {
  
    // Setup the serial port to print out results.
    pc.baud(115200);
    pc.printf("Starting up...\n");
    
    // Power up the ADC and set PCLK
    LPC_SC->PCONP    |=  (1UL << 12);
    LPC_SC->PCLKSEL0 &= ~(3UL << 24); // PCLK = CCLK/4 96M/4 = 24MHz
    
    // Set the pin functions to ADC
    LPC_PINCON->PINSEL1 &= ~(3UL << 14);  /* P0.23, Mbed p15 AD0.0 */
    LPC_PINCON->PINSEL1 |=  (1UL << 14);
    LPC_PINCON->PINSEL1 &= ~(3UL << 16);  /* P0.24, Mbed p16 AD0.1 */
    LPC_PINCON->PINSEL1 |=  (1UL << 16);
    LPC_PINCON->PINSEL1 &= ~(3UL << 18);  /* P0.25, Mbed p17 AD0.2 */
    LPC_PINCON->PINSEL1 |=  (1UL << 18);

    // Ready interrupts for ADC.
    NVIC_SetVector(ADC_IRQn, (uint32_t)&ADC_IRQHandler);
   
    // Note we use 2MHz -> TC. This is because the ADC is rising
    // edge triggered and MAT1.0 is set to toggle. This toggling
    // effectively halves the sample frenquency, so we double the
    // clocking speed to compensate.
    LPC_SC->PCONP    |= (1UL << 2); // TIM1 On
    LPC_SC->PCLKSEL0 |= (3UL << 4); // CCLK/8 = 12MHz
    LPC_TIM1->PR      = 5;          // TC clocks at 2MHz.
    LPC_TIM1->MR0     = SAMPLE_PERIOD;
    LPC_TIM1->MCR     = 2;          // Reset TCR to zero on match
    LPC_TIM1->EMR     = (3UL<<4)|1; // Make MAT1.0 toggle.        
    
    START_COVERSION_TIMED(0x1);
       
    while (1) {  
        if (adBufIn[0] == NUM_OF_SAMPLES && adBufIn[1] == NUM_OF_SAMPLES && adBufIn[2] == NUM_OF_SAMPLES) {
            for (int i = 0; i < NUM_OF_SAMPLES; i++) {
                printValue(0, i);
                printValue(1, i);
                printValue(2, i);                
            }
            pc.printf("\n");
            START_COVERSION_TIMED(0x1);            
        }
        
        // Show some life.
        led1 = !led1;
    }       
}
13 Mar 2011

And here finally is the above but using GPDMA. Note, it's fast, but there's still a margin of time latency in the TIMER1 "setup the capture sequence" you need to take into account. But once it starts converting, it goes as fast as you are going to get.

Note, I used MODSERIAL to ensure printf() didn't "bite me".

It's over to you now I gues to mold it into a real application ;)

#include "mbed.h"
#include "MODDMA.h"
#include "MODSERIAL.h"

// How long between grabbing samples on all channels.
// Value is in microseconds. 
#define SAMPLE_PERIOD   1000000

#define NUM_OF_SAMPLES  2

MODSERIAL pc(USBTX, USBRX);
DigitalOut led1(LED1);
DigitalOut led2(LED2);
DigitalOut led3(LED3);
DigitalOut led4(LED4);

uint32_t adBuffer[NUM_OF_SAMPLES * 3];

bool dmaTransferComplete;

MODDMA dma;
MODDMA_Config *conf;

void TC0_callback(void);
void ERR0_callback(void);

extern "C" void TIMER1_handler(void) __irq {
    led2 = !led2; // Show life
    dma.Setup( conf ); // Pre-prep a transfer
    dma.Enable( conf );
    LPC_ADC->ADCR |= (1UL << 16); // ADC burst mode
    LPC_ADC->ADINTEN = 0x100;     // Do all channels.
    LPC_TIM1->IR = 1; // Clr timer1 irq.
}

int main() {
    
    memset(adBuffer, 0, sizeof(adBuffer));
    
    // Setup the serial port to print out results.
    pc.baud(115200);
    pc.printf("Starting up...\n");
    
    // Power up the ADC and set PCLK
    LPC_SC->PCONP    |=  (1UL << 12);
    LPC_SC->PCLKSEL0 &= ~(3UL << 24); // PCLK = CCLK/4 96M/4 = 24MHz
    NVIC_DisableIRQ(ADC_IRQn); 
    
    // Set the pin functions to ADC
    LPC_PINCON->PINSEL1 &= ~(3UL << 14);  /* P0.23, Mbed p15 AD0.0 */
    LPC_PINCON->PINSEL1 |=  (1UL << 14);
    LPC_PINCON->PINSEL1 &= ~(3UL << 16);  /* P0.24, Mbed p16 AD0.1 */
    LPC_PINCON->PINSEL1 |=  (1UL << 16);
    LPC_PINCON->PINSEL1 &= ~(3UL << 18);  /* P0.25, Mbed p17 AD0.2 */
    LPC_PINCON->PINSEL1 |=  (1UL << 18);
    LPC_ADC->ADINTEN = 0x100;
    LPC_ADC->ADCR = (1UL << 21) | (1UL << 8) | (7UL << 0);
    
    LPC_SC->PCONP    |= (1UL << 2); // TIM1 On
    LPC_SC->PCLKSEL0 |= (3UL << 4); // CCLK/8 = 12MHz
    LPC_TIM1->PR      = 11;          // TC clocks at 1MHz.
    LPC_TIM1->MR0     = SAMPLE_PERIOD;
    LPC_TIM1->MCR     = 3;          // Reset TCR to zero on match and irq.
    NVIC_SetVector(TIMER1_IRQn, (uint32_t)TIMER1_handler);
    NVIC_EnableIRQ(TIMER1_IRQn);
    
    // Prepare the GPDMA system.
    conf = new MODDMA_Config;
    conf
     ->channelNum    ( MODDMA::Channel_0 )
     ->dstMemAddr    ( (uint32_t)adBuffer )
     ->transferSize  ( 3 )
     ->transferType  ( MODDMA::p2m )
     ->transferWidth ( MODDMA::word )
     ->srcConn       ( MODDMA::ADC )
     ->attach_tc     ( &TC0_callback )
     ->attach_err    ( &ERR0_callback )
    ; // end conf.
    
    // Prepare configuration.
    if (!dma.Setup( conf )) {
        error("Doh!");
    }
    
    // Begin.
    LPC_TIM1->TCR = 1;
       
    while (1) {  
        // Show some life.
        led1 = !led1;
        
        if (dmaTransferComplete) {
            int i, value, channel;
            for (i = 0; i < 3; i++) {
                value = (adBuffer[i] >> 4) & 0xFFF;
                channel = (adBuffer[i] >> 24) & 0x3;
                channel--; if (channel == -1) channel = 2; // Workaround ch num problem.
                double fVal = 3.3 * (double)((double)value) / ((double)0x1000); // scale to 0v to 3.3v 
                pc.printf("ADC input (channel=%d) = 0x%03x %01.3f volts\n", channel, value, fVal);                
            }
            dmaTransferComplete = false;
        }
    }       
}

// Configuration callback on TC
void TC0_callback(void) {
    
    MODDMA_Config *config = dma.getConfig();
    
    // Disbale burst mode and switch off the IRQ flag.
    LPC_ADC->ADCR &= ~(1UL << 16);
    LPC_ADC->ADINTEN = 0;    
    
    // Finish the DMA cycle by shutting down the channel.
    dma.haltAndWaitChannelComplete( (MODDMA::CHANNELS)config->channelNum());
    dma.Disable( (MODDMA::CHANNELS)config->channelNum() );
    
    // Tell main() while(1) loop to print the results.
    dmaTransferComplete = true;            
    
    // Clear DMA IRQ flags.
    if (dma.irqType() == MODDMA::TcIrq) dma.clearTcIrq();    
    if (dma.irqType() == MODDMA::ErrIrq) dma.clearErrIrq();
}

// Configuration callback on Error
void ERR0_callback(void) {
    // Switch off burst conversions.
    LPC_ADC->ADCR |= ~(1UL << 16);
    LPC_ADC->ADINTEN = 0;
    error("Oh no! My Mbed EXPLODED! :( Only kidding, go find the problem");
}

13 Mar 2011

I'll study the code carefully. Thanks a lot for sharing!

26 Mar 2011

Hi Andy

Finally had the courage to start on the dma/cap timer code. I found a minor timing bug.

The line:

LPC_TIM1->MR0     = SAMPLE_PERIOD;

should read:

LPC_TIM1->MR0     = SAMPLE_PERIOD-1;

then timing is exact.

I now increment the dstMemAddr inside the DMA callback like:

    config->dstMemAddr(config->dstMemAddr()+3*sizeof(uint32_t));

Works like charm..

I added three arrays to store Timer.read_us() values to examine timing. The demo starts having channelnumber slipping above 3MHz.

Acccording to my measurements at a 3Mhz clock the timing is:

  • 01 us at the start of the timer interrupt routine
  • 10 us after the DMA reconfiguration & ADC start inside the timer interrupt.
  • 75 us at the start of the DMA callback (exactly 65us later which corresponds with 65 cycli per conversion at 3Mhz).

It nicely reproduces for each set of samples taken. The interval is exact and does not slip.

Only thing I would like to try is use LL and fill it with the channel registers. But at the moment it's not that important.

So thanks again.

Wim.

26 Mar 2011

Hi Andy

I added dummy read of the ADC Global Data Register to the interrupt handler of your 2nd code sample (Timer1 Capture based conversion with DMA transport).

So i added a line:

    uint32_t dummy4 = LPC_ADC->ADGDR;

just before starting the next conversion. It clears the overrun bit probably caused by the DMA finishing an already started conversion after it completes a burst. Perhaps not usefull but if you get an overrun with this code in place you sure have timing problems.

See also topic http://mbed.org/forum/mbed/topic/2064/

27 Mar 2011

Hi

Changed the above code a bit to:

        if (sample_index!=0) {
            uint32_t dummy4 = LPC_ADC->ADGDR;
        }

As i noticed that the first sample in the array was incorrect.

04 Apr 2011

[quote]...the conversion time is 65 cycles and the clock is derived from 96Mhz you cannot get an exact int number of ms for timing.[/quote]

So why use 96 MHz? Step the clock up to 100 MHz, and you will have your integer milliseconds.

Regards,

- Gary

05 Apr 2011

Hi Gary,

I thought of that but the a) 100Mhz is out of spec for the mBed (not for the cpu) and does not produce the desired clock. Your math is not correct, the ADC needs a clock 65 times higher than for example 1KHz. You just cannot derive that from either 100 or 96MHz.

65Mhz would be a candidate clock but that is a 30% loss of CPU power (you can divide it by 1000 and the ADC conversion will add an extra divide by 65 resulting in an exact 1ms sample time). 78Mhz (65+13) & 91Mhz (65+26) would be next up I think.

Furthermore changing the clock produces undocumented effects in the libraries and finally 100Mhz isn't dividable by 65 either. There is also the USB clock of 48Mhz to be taken into account.

05 Apr 2011

The mBed works just fine at 100 MHz, and I find that the libraries have enough "undocumented effects" that I have to diddle the CPU registers to get what I want anyways. I check all timing with a scope, and commonly have to make adjustments to the libraries' choices. Setting SystemCoreClock to the new value helps with some of that when you change the clock.

As for the 48 MHz USB clock, that only applies to pins 31 and 32, not the main USB connector at the top of the board. Further, the CPU has a second PLL just for USB, and the mBed libraries don't bother to use it. You can, though, if you need to use the second USB and also use a different clock rate.

The great thing about 100 MHz is the 10 nanosecond clock. I can do the math in my head and know how long it takes for 65 clock cycles. Besides, it's a 4% improvement in throughput for free. If I wanted to guarantee a specific sample rate, I'd use scmRTOS and run a task at that rate.

Regards,

- Gary

05 Apr 2011

Hi,

How would you divide 100Mhz to get a exact multiple of 65? A 10ns clock is not what I need, I need an ADC clock of 1/65 ms in order to obtain 1 sample per ms. Or am I making a huge mistake here?

Btw I agree with effects and that it might run on 100MHz but I'm just not comfortable with it.

I though of the second PLL for the USB Host but I'm not sure what can be changed after a boot (the articles on the mBed site are not all writing the same).

Wim

05 Apr 2011

Wim -

I think you are looking at it wrong. You don't want to consume 100% of your CPU just taking 1mS samples. Try scmRTOS (search the forums). Make a task to sample your data, then send that task a semaphore from the OS::SystemTimerUserHook() function. By default, the system timer in scmRTOS runs at 1000 Hz.

Note that you need to check the clock parameters in scmRTOS to make sure they are set to your clock speed.

The end result of doing it this way is that you *will* get 1mS samples without much effort at all, and you will have plenty of CPU time left to do lots of other things, too. Since the clock rate doesn't matter, you might as well step it up to 100 MHz and do that much more.

typedef OS::process<OS::pr0, 1000> TProc1;
typedef OS::process<OS::pr1, 1000> TProc2;
TProc1 Proc1;
TProc2 Proc2;
OS::TEventFlag T1_Sem;
OS::TEventFlag T2_Sem;

int main() {
    ... Initialize, etc. here (including changing clock rate if you want to)
    OS::Run();
}

template<> OS_PROCESS void TProc1::Exec() {
    for (;;) {
        T1_Sem.Wait (0);        // Wait forever for Semaphore
        ... Sample data
        if (I_got_enough_data) {
            T2_Sem.Signal();    // Tell Task 2 to wake and process the data
        }
    }
}

template<> OS_PROCESS void TProc2::Exec() {
    for (;;) {
        T2_Sem.Wait (0);
        ... Process data
    }
}

void OS::SystemTimerUserHook() {
        T1_Sem.SignalISR();     // Signal Task 1 (1000 HZ) to wake and process one time
}

Regards,

- Gary

06 Apr 2011

Hi,

My first intention was to clock the adc in such way that it has a sample every 1ms. If it where possible i could have the dma controller handle the whole data aquisition without having to look at it. and not waste cpu at all.

Due to the factor 65 (clock cycles/conversion) using clocking the adc isn't an option as you cannot get a 65fold by dividing the cpu clock of 96MHz or 100Mhz.

So I resorted to a timer interrupt which periodically sets up a single dma transfer for a number of adc channels. The result is near xtal timing (inter sample timing is nearly perfect). This way i only have a single interrupt to handle (the dma handles the adc interrupt for me).

By decimating the data adaptively I can also vary the sample time during the data aquisition as needed (our measurement consists of two periods each of which must have a certain amout of samples at regular intervals so we can integrate the signal later).

Switching to a real-time OS is a big step for this project (however tempting) and my experiences with it (and solving/preventing deadlocks) date back to my university years some 30 years back now. Furthermore, as the mBed is tighly controller by a PC app, there is not much need for concurreny of tasks.

BUT i will download scmRTOS and play with it (for old time sake ;-), so thanks for mentioning it.

regards Wim

06 Apr 2011

Wim -

96 MHz or 100 MHz won't divide cleanly by 65, but 97.5 MHz will. Try this at the very beginning of your code:

int main() {
    LPC_SC->PLL0CON   = 0x00;               // PLL0 Disable
    LPC_SC->PLL0FEED  = 0xAA;
    LPC_SC->PLL0FEED  = 0x55;

    LPC_SC->CCLKCFG   = 0x00000003;         // Select Clock Divisor = 4
    LPC_SC->PLL0CFG   = 0x00070103;         // configure PLL0 (*2*260/8) for 97,500,000
    LPC_SC->PLL0FEED  = 0xAA;               // divide by 8 then multiply by 260 
    LPC_SC->PLL0FEED  = 0x55;               // PLL0 frequency = 390,000,000 

    LPC_SC->PLL0CON   = 0x01;               // PLL0 Enable 
    LPC_SC->PLL0FEED  = 0xAA;
    LPC_SC->PLL0FEED  = 0x55;
    while (!(LPC_SC->PLL0STAT & (1<<26)));  // Wait for PLOCK0 

    LPC_SC->PLL0CON   = 0x03;               // PLL0 Enable & Connect
    LPC_SC->PLL0FEED  = 0xAA;
    LPC_SC->PLL0FEED  = 0x55;

// Wait for PLLC0_STAT & PLLE0_STAT
    while (!(LPC_SC->PLL0STAT & ((1<<25) | (1<<24))));  

    SystemCoreClock = (12000000 * 2 /
                      (((LPC_SC->PLL0STAT >> 16) & 0xFF) + 1) *
                      ((LPC_SC->PLL0STAT & 0x7FFF) + 1)  /
                      ((LPC_SC->CCLKCFG & 0xFF)+ 1));
    printf ("  ... System clock = %d Hz\r\n", SystemCoreClock);

   ... More main() code here ...
}

Your mBed should now be running at 97.5 Mhz, which is 150,000 * 65 clocks. Please note that I didn't test it, but I have used several different clocks other than 96 MHz, and the code looks correct.

Even if you do it this way, combining it with an RTOS will help. My example of scmRTOS above had everything you need except the library itself and the #include <scmRTOS.h> line at the top. Just be sure to tell the RTOS what your clock rate is so that it can play along. You have to change it in two places for scmRTOS (scmRTOS_TARGET_CFG.h and OS_Target_asm.s).

Regards,

- Gary

06 Apr 2011

Please note, after updating the SystemCoreClock global you should call the CMSIS function void SystemCoreClockUpdate (void);

CMSIS wrote:

void SystemCoreClockUpdate(void)Updates the variable SystemCoreClock and must be called whenever the core clock is changed during program execution. SystemCoreClockUpdate() evaluates the clock register settings and calculates the current core clock.

06 Apr 2011

Andy -

I don't remember what went wrong when I called SystemCoreClockUpdate(), but after looking at the code I saw that its intent was to set SystemCoreClock to the proper value (just like its description says). I have not seen any adverse effects caused by just setting the variable myself.

As I recall, the call to SystemCoreClockUpdate() didn't always set the variable correctly, so the mBed library calculated timer values, etc. wrong. Now I just set the value and then double-check the mBed timers and baud rates as a matter of practice.

Here's the code from CMSIS. Why do I need to call it as long as SystemCoreClock has the correct value?

void SystemCoreClockUpdate (void)            /* Get Core Clock Frequency      */
{
  /* Determine clock frequency according to clock register values             */
  if (((LPC_SC->PLL0STAT >> 24) & 3) == 3) { /* If PLL0 enabled and connected */
    switch (LPC_SC->CLKSRCSEL & 0x03) {
      case 0:                                /* Int. RC oscillator => PLL0    */
      case 3:                                /* Reserved, default to Int. RC  */
        SystemCoreClock = (IRC_OSC *
                          ((2 * ((LPC_SC->PLL0STAT & 0x7FFF) + 1)))  /
                          (((LPC_SC->PLL0STAT >> 16) & 0xFF) + 1)    /
                          ((LPC_SC->CCLKCFG & 0xFF)+ 1));
        break;
      case 1:                                /* Main oscillator => PLL0       */
        SystemCoreClock = (OSC_CLK *
                          ((2 * ((LPC_SC->PLL0STAT & 0x7FFF) + 1)))  /
                          (((LPC_SC->PLL0STAT >> 16) & 0xFF) + 1)    /
                          ((LPC_SC->CCLKCFG & 0xFF)+ 1));
        break;
      case 2:                                /* RTC oscillator => PLL0        */
        SystemCoreClock = (RTC_CLK *
                          ((2 * ((LPC_SC->PLL0STAT & 0x7FFF) + 1)))  /
                          (((LPC_SC->PLL0STAT >> 16) & 0xFF) + 1)    /
                          ((LPC_SC->CCLKCFG & 0xFF)+ 1));
        break;
    }
  } else {
    switch (LPC_SC->CLKSRCSEL & 0x03) {
      case 0:                                /* Int. RC oscillator => PLL0    */
      case 3:                                /* Reserved, default to Int. RC  */
        SystemCoreClock = IRC_OSC / ((LPC_SC->CCLKCFG & 0xFF)+ 1);
        break;
      case 1:                                /* Main oscillator => PLL0       */
        SystemCoreClock = OSC_CLK / ((LPC_SC->CCLKCFG & 0xFF)+ 1);
        break;
      case 2:                                /* RTC oscillator => PLL0        */
        SystemCoreClock = RTC_CLK / ((LPC_SC->CCLKCFG & 0xFF)+ 1);
        break;
    }
  }

}
06 Apr 2011

Quote:

Why do I need to call it as long as SystemCoreClock has the correct value?

Maybe if you have found a bug you should let the CMSIS devs know? Where possible I don't bother looking at library code unless it seems not to work. Since I've never needed to change SystemCoreClock I've never needed any of this. But when I do find library errors I feed them back to the devs.

I have found a number of bugs in the NXP Driver Library, all of which I have emailed back to NXP. However, they appear deaf, no fixes have made into the latest downloadable versions. We can but try... ;)

06 Apr 2011

Andy -

It's starting to come back to me now. Depending on the multiplier and divisors, the order they use for computation can overflow when starting with a 12 MHz oscillator. I'm not going to take the time to prove that statement, so don't shoot me if it's not quite right. All I did was juggle the math a little and now my mBed works like I want it to.

I agree that reporting it helps the process, but I tend to think of the CMSIS code as a "serving suggestion" rather than a complete recipe. If I need to hack their code to make it work for me, I hack away. Maybe NXP sees it the same way.

Regards,

- Gary

06 Apr 2011

Hi Gary

Are you sure your code is correct. I pasted it into the top of my main(). But after flashing I do not get any serial output anymore.

Btw do you have a sample (hello world alike) program using scmRTOS that i cna compile on this website?

Wim

06 Apr 2011

Wim -

Sorry. Never do math in public. Use these two for the divisors/multiplier (they work for me):

    LPC_SC->CCLKCFG   = 0x00000001;       // Select Clock Divisor = 2 
    LPC_SC->PLL0CFG   = 0x001F0103;       // configure PLL0 (*2*260/16) for 97,500,000

And after you set the SystemCoreClock variable, it's probably a good idea to recalculate the baud rate divisor for the serial port:

    pc.baud(921600);                      // Or your favorite baud rate

As for the other question, Igor Skochinsky posted his port of scmRTOS last summer, and it included a "hello world." Search the forum for it (or his notebook). I think that someone else has posted another version of it since he did, but I have not looked at that one.

The code I posted is really close to all you need to compile. I probably forgot just one function, which you could tack at the bottom of the file:

void OS::IdleProcessUserHook() {
    __WFI();
}

Regards,

- Gary

06 Apr 2011

See here and here.

07 Apr 2011

Hi Igor

What is the difference between the scmRTOS insize the demo and the library. I noticed at least one extra file in the test program called OS_Target_asm.s.

Can i replace the directory in the demo by the lib?

Wim

07 Apr 2011

Hi Gary

 LPC_SC->CCLKCFG   = 0x00000001; LPC_SC->PLL0CFG   = 0x001F0103;

Works much better. Thanks.

07 Apr 2011

Hi Gary,

The comment is a bit confusing in your sample:

LPC_SC->CCLKCFG   = 0x00000001;       // Select Clock Divisor = 2 
LPC_SC->PLL0CFG   = 0x001F0103;       // configure PLL0 (*2*260/16) for 97,500,000

Should read:

LPC_SC->CCLKCFG   = 0x00000001;       // Select Clock Divisor = 2 -> 12 Mhz/2 -> 6 Mhz
LPC_SC->PLL0CFG   = 0x001F0103;       // configure PLL0 (6 Mhz*2*260/32) for 97,500,000 Hz

The 0x1F is 32-1 The 0x0103 is 260-1

Wim

07 Apr 2011

Wim -

Like I said, never do math in public.

Regards,

- Gary

07 Apr 2011

Hi,

I found the cause why it did not seem to work. I passed a Serial object to a function in order to recompute the baudrate. I seem to have passed it by value and not by reference with an &. So probably the baudrate was very upset.

Wim

31 Jan 2013

Andy Wim, could you give me a hand with the channel number?

Reading the manual it seems like it should be "channel = (adBuffer[i] >> 24) & 0x7;" but even if I do that I can't get this working to sample all 6 ADC channels correctly it only seems to work for the first 2 channels, I do select all 6 on PINSEL1 but there must be something more to it I can't find now.

Thank you!

31 Jan 2013

Ok, re-read the manual ADCR had to be uptated!

LPC_ADC->ADCR = (1UL << 21) | (11UL << 8) | (63UL << 0);

Thank you both anyway for this awesome piece of shared code!