Wednesday, 30 July 2014

The PLL and various Clock sources of a LPC810


Previously we found out that using the default CMSIS startup routines that  the LPC810's System core clock is 12 MHZ upon entry to main() and that the MAINCLKSEL is 0x00 meaning that the PLL is not engaged so the LPC810 is being driven directly by the IRC Oscillator which is actually the default starting condition of the LPC810 anyway.

Since saving bytes is clearly important on a device like this the call to the CMSIS SystemInit() can be removed from cr_startup_lpc8xx.c entirely. Find the following lines of code and remove them


#if defined (__USE_CMSIS) || defined (__USE_LPCOPEN)
    SystemInit();
#endif

Since we know the SystemCoreClock is 12 MHZ we can also remove the call to the CMSIS SystemCoreClockUpdate() function in main.c and replace it simply with:


uint32_t SystemCoreClock = 12000000;


By my calculations this will save a whopping 268 bytes which might just save the day.


Diagram from lpc8xx pll calculator.xlsx

From the diagram above the SystemCoreClock (Just System Clock in the diagram) is the Main Clock divided by SYSAHBCLKDIV. The SystemCoreClock is also drives the peripheral clocks, the exception here is the UART's that have there own divider UARTCLKDIV and are clocked from the Main Clock divided by UART(CLKDIV and also IOCON is clocked from the Main Clock divided by IOCONCLKDIV. The mistake in the first version of the Uart init routine was to assume it was driven from the SystemCoreClock like most of the other peripherals.

The LM810's phase locked loop

What if we would like the SystemCoreClock to be more that 12MHZ? The LPC810 is specified to run up to 30MHZ in this case we can use the PLL to increase the IRC frequency.With a PLL the input frequency is multiplied to a higher frequency and then divided down to provide the actual clock frequency used by the CPU .

On reset the Internal RC oscillator is selected as input to the PLL.The value of SYSPLLCTRL will control the multiplication factor. The lower 4 bits of this register (MSEL) plus 1 contain the PLL multiplier.

MSELPLL out clock MHZ
012
124
236
348
460
572
684
796
8108
9120

The PLL output clock must be less than or equal to 100MHZ hence the last two MSEL values are out of specification.

Were not quite there yet, the bits D5 and D6 together make the  PSEL value that we can use to calculate the VCO frequency.
The value of PSEL signifies how many bits we shift 0x1 left by to use as a multiplier for the PLL output clock.

VCO FREQ = 2 *  (1 << PSEL ) * (PLL out clock)

The VCO frequency must be between 156MHZ and 320MHZ so we have to select a PSEL that constrains the VCO frequency within this range.The table below shows the values of SYSPLLCTRL needed for the corresponding PLL output clock.

MSELPSELPLL out clock MHZSYSPLLCTRL
0312 0x60
12240x41
22360x42
31480x23
41600x24
51720x25
60840x06
70960x07
801080x08
901200x09

You will then need to use  SYSAHBCLKDIV to divide the output of the PLL down in order to get the required SystemCoreClock

Finally we set  MAINCLKSEL to 0x03 to select the PLL output as the Main Clock rather than the IRC Clock.

We should also note that we must wait for the PLL to lock and also wait for the PLL output to be selected as the Main Clock see the code for further details. The code will also allow you to select the Watchdog Clock if speed is not necessary with substantial power savings.


#include "LPC8xx.h"
#include "xprintf.h"

#define BAUDRATE 57600
#define UART_CFG_DATALEN_8      (0x01 << 2)  /* UART 8 bit length mode */
#define UART_CFG_PARITY_NONE    (0x00 << 4)  /* No parity */
#define UART_CFG_STOPLEN_1      (0x00 << 6)  /* UART One Stop Bit Select */
#define UART_STAT_RXRDY         (0x01 << 0)  /* Receiver ready */
#define UART_STAT_TXRDY         (0x01 << 2)  /* Transmitter ready for data */
#define UART_CFG_ENABLE         (0x01 << 0)

/*Which Clock source ?, if both are 0 then Internal RC Oscillator is used */
#define USE_PLL  (1)
#define USE_WATCHDOG  (0)

#define WATCHDOG_300KHZ ((1 << 5) | (0))
#define WATCHDOG_9375HZ ((1 << 5) | (0x1f))
#define WATCHDOG_2300KHZ ((0xf << 5) | (0))
#define PLL_60MHZ (0x24)
#define PLL_24MHZ (0x41)
#define PLL_96MHZ (0x07)

uint32_t SystemCoreClock=12000000; /* Value @ Reset */
uint32_t SystemMainClock=12000000; /* Value @ Reset */

void uart0putc(unsigned char c){
 /* Wait until we're ready to send */
  while (!(LPC_USART0->STAT & UART_STAT_TXRDY));
   LPC_USART0->TXDATA = c;
}

unsigned char uart0getc(){
 /* Wait until we're ready to receive */
 while (!(LPC_USART0->STAT & UART_STAT_RXRDY));
  return LPC_USART0->RXDATA;
}

short uart0PollChar() {
 if (!(LPC_USART0->STAT & UART_STAT_RXRDY))
  return -1;
 return LPC_USART0->RXDATA;
}


int main(void) {

 /* Enable AHB clock to the Switch Matrix , UART0 , IOCON */
 LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 7) | (1 << 14) | (1 << 18);

#if (USE_PLL)
 /* Enable the PLL Clock for a maximum 30MHZ SystemCoreClock */
 LPC_SYSCON->SYSPLLCTRL    = PLL_60MHZ; /* 60 MHZ pll output clock*/
 LPC_SYSCON->PDRUNCFG     &= ~(0x1 << 7);        /* Power-up SYSPLL          */
 while (!(LPC_SYSCON->SYSPLLSTAT & 0x01));       /* Wait Until PLL Locked    */

 LPC_SYSCON->MAINCLKSEL    = 3;     /* Select PLL Clock Output  */
 LPC_SYSCON->MAINCLKUEN    = 0x01;      /* Update MCLK Clock Source */
 while (!(LPC_SYSCON->MAINCLKUEN & 0x01));       /* Wait Until Updated       */

 LPC_SYSCON->SYSAHBCLKDIV  =  2; /* Divide by 2 to get the maximum 30MHZ SystemCoreClock */

 SystemMainClock = 12000000 * ((LPC_SYSCON->SYSPLLCTRL & 0x01F) + 1); /* Calculate SystemMainClock for IRC directed to the PLL*/
 SystemCoreClock = SystemMainClock / LPC_SYSCON->SYSAHBCLKDIV; /*Calculate SystemCoreClock */

#elif (USE_WATCHDOG)
 /* Enable the Watchdog Oscillator for minimum power consumption*/
 LPC_SYSCON->WDTOSCCTRL = WATCHDOG_2300KHZ;
 LPC_SYSCON->PDRUNCFG &= ~(0x1 << 6); /* Power-up Watchdog          */
 LPC_SYSCON->MAINCLKSEL = 2; /* Select Watchdog Clock Output  */
 LPC_SYSCON->MAINCLKUEN = 0x01; /* Update MCLK Clock Source */
 while (!(LPC_SYSCON->MAINCLKUEN & 0x01)); /* Wait Until Updated       */

 if ((LPC_SYSCON->WDTOSCCTRL & (0x0f << 5)) == (0x01 << 5))
 SystemMainClock = 600000 / (2 * (1 + (LPC_SYSCON->WDTOSCCTRL & 0x1f)));
 else
 SystemMainClock = 4600000 / (2 * (1 + (LPC_SYSCON->WDTOSCCTRL & 0x1f)));

 SystemCoreClock = SystemMainClock / LPC_SYSCON->SYSAHBCLKDIV; /* Calculate SystemCoreClock */
 LPC_SYSCON->PDRUNCFG |= (1 << 7) | (1 << 1) | (1 << 0); /* Power down IRC & PLL as not needed now */
#endif

 /* Switch matrix , select uart */
 LPC_SWM->PINASSIGN0 = 0xffff0004UL;
 
 /* Set the UART clock divisor to 1  */
 LPC_SYSCON->UARTCLKDIV = 1; // note on reset the value is 0. i.e clock is disabled
 /* Configure UART0 baud rate */
 LPC_USART0->BRG = ((SystemMainClock / LPC_SYSCON->UARTCLKDIV) >> 4) / BAUDRATE - 1;
 LPC_SYSCON->UARTFRGMULT = ((((SystemMainClock / LPC_SYSCON->UARTCLKDIV) >> 4) * (0x100)) / (BAUDRATE * (LPC_USART0->BRG + 1))) - (0x100);
 LPC_SYSCON->UARTFRGDIV = 0xFF; //note on reset the value is 0

 /* Set UART0 to 8N1 and finally enable */
 LPC_USART0->CFG = UART_CFG_DATALEN_8 | UART_CFG_PARITY_NONE | UART_CFG_STOPLEN_1 | UART_CFG_ENABLE;

 /* Init Chan's Embedded String Functions (xprintf ect) */
 xdev_out(uart0putc);
 xdev_in(uart0getc);

    // Enter an infinite loop testing for the '0' and '1' keys
    while(1) {
     switch( uart0PollChar() ){
      case '0':
          xprintf("System Core Clock is %d hz\n\rSystem Main Clock is %d\n\r",SystemCoreClock,SystemMainClock);
      break;
      case '1':
          xprintf("Main clock select is %0x\n\rSYSAHBCLKDIV is %d\n\r",LPC_SYSCON->MAINCLKSEL,LPC_SYSCON->SYSAHBCLKDIV);
         break;
     }
    }
    return 0 ;
}

No comments:

Post a Comment