-
Home
-
doc
-
support
-
driver
-
sysclock
sysclock_pic24_dspic33.c
View on Github
/**
* @file sysclock_pic24_dspic33.c
* @author Sebastien CAUX (sebcaux)
* @copyright Robotips 2016-2017
* @copyright UniSwarm 2018-2023
*
* @date April 11, 2016, 05:12 PM
*
* @brief System clock support for udevkit for dsPIC33FJ, dsPIC33EP, dsPIC33EV,
* PIC24F, PIC24FJ, PIC24EP and PIC24HJ
*
* Implementation based on Microchip documents DS70186D, DS70580C, DS70005131A,
* DS70307E and DS70216D :
* http://ww1.microchip.com/downloads/en/DeviceDoc/70216D.pdf
* http://ww1.microchip.com/downloads/en/DeviceDoc/70580C.pdf
* http://ww1.microchip.com/downloads/en/DeviceDoc/70005131a.pdf
* http://ww1.microchip.com/downloads/en/DeviceDoc/70307E.pdf
* http://ww1.microchip.com/downloads/en/DeviceDoc/70216D.pdf
*
* Simple PLL devices: DS70644A
* http://ww1.microchip.com/downloads/en/DeviceDoc/S52.pdf
*/
#include "sysclock.h"
#include "board.h"
#include <archi.h>
static uint32_t _sysclock_sysfreq = 0;
static uint32_t _sysclock_sosc = 0;
static uint32_t _sysclock_posc = 0;
static uint32_t _sysclock_pll = 0;
/**
* @brief Gets the actual frequency on a particular peripheral bus clock
* @param busClock id of the bus clock (1 periph bus clock), 0 for sysclock
* @return bus frequency in Hz, 1 in case of error to not cause divide by 0
*/
uint32_t sysclock_periphFreq(SYSCLOCK_CLOCK busClock)
{
if (_sysclock_sysfreq == 0)
{
_sysclock_sysfreq = sysclock_sourceFreq(sysclock_source());
}
if (busClock == SYSCLOCK_CLOCK_SYSCLK)
{
return _sysclock_sysfreq >> 1;
}
if (busClock == SYSCLOCK_CLOCK_PBCLK)
{
uint16_t div = 1;
if (CLKDIVbits.DOZEN == 1)
{
div = 1 << (CLKDIVbits.DOZE);
}
return (_sysclock_sysfreq >> 1) / div;
}
return 1;
}
/**
* @brief Change the divisor of the busClock given as argument.
* @param busClock id of the bus clock or ref clock (SYSCLOCK_CLOCK_REFCLK or
* SYSCLOCK_CLOCK_PBCLK, SYSCLOCK_CLOCK_FRC)
* @param div divisor to set
* @return 0 if ok, -1 in case of error
*/
int sysclock_setClockDiv(SYSCLOCK_CLOCK busClock, uint16_t div)
{
uint16_t udiv;
#ifndef SYSCLOCK_HAVENO_FRCDIV
if (busClock == SYSCLOCK_CLOCK_FRC)
{
if (div > 256)
{
return -1;
}
if (div == 256)
{
udiv = 8;
}
else
{
for (udiv = 0; div != 0; udiv++)
{
div >>= 1;
}
}
udiv -= 1;
CLKDIVbits.FRCDIV = udiv;
return 0;
}
#endif
#ifdef SYSCLOCK_HAVE_REFCLOCKO
if (busClock == SYSCLOCK_CLOCK_REFCLK)
{
if (div > 32768)
{
return -1;
}
else
{
for (udiv = 0; div != 0; udiv++)
{
div >>= 1;
}
}
udiv -= 1;
REFOCONbits.RODIV = udiv;
return 0;
}
#endif
if (busClock == SYSCLOCK_CLOCK_PBCLK)
{
if (div == 1)
{
CLKDIVbits.DOZEN = 0;
}
else
{
if (div > 128)
{
return -1;
}
for (udiv = 0; div != 0; udiv++)
{
div >>= 1;
}
udiv -= 1;
CLKDIVbits.DOZE = udiv;
CLKDIVbits.DOZEN = 1;
}
return 0;
}
return -1; // bad index
}
/**
* @brief Return the actual frequency of the clock source
* @param source clock id to request
* @return frequency of 'source' clock, 0 in case of disabled clock, -1 in case of error
*/
int32_t sysclock_sourceFreq(SYSCLOCK_SOURCE source)
{
int32_t freq = -1;
uint16_t div;
int32_t osctune;
switch (source)
{
case SYSCLOCK_SRC_LPRC:
freq = 32000; // 32kHz LPRC
break;
case SYSCLOCK_SRC_SOSC:
freq = _sysclock_sosc; // external secondary oscillator
break;
case SYSCLOCK_SRC_POSC:
freq = _sysclock_posc; // external primary oscillator
break;
case SYSCLOCK_SRC_PPLL:
freq = _sysclock_pll; // primary oscillator with PLL
break;
case SYSCLOCK_SRC_FRC:
case SYSCLOCK_SRC_FRC16:
case SYSCLOCK_SRC_FRCDIV:
osctune = OSCTUN;
if (osctune >= 32)
{
osctune = (osctune | 0xFFFFFFE0);
}
freq = FRC_BASE_FREQ + osctune * OSCTUN_D; // FRC
if (source == SYSCLOCK_SRC_FRC16)
{
freq = freq / 16; // FRC /16
}
if (source == SYSCLOCK_SRC_FRCDIV)
{
#ifndef SYSCLOCK_HAVENO_FRCDIV
div = CLKDIVbits.FRCDIV;
#else
div = 1 << CLKDIVbits.RCDIV;
#endif
if (div != 0b111)
{
div = 1 << div;
}
else
{
div = 256;
}
freq = freq / div; // FRC / div
}
break;
case SYSCLOCK_SRC_FRCPLL:
freq = _sysclock_pll; // FRC with PLL
break;
}
return freq;
}
/**
* @brief Change a frequency of a source if it can be modified
* @param source clock id to change
* @return 0 in case of success, -1 in case of error
*/
int sysclock_setSourceFreq(SYSCLOCK_SOURCE source, uint32_t freq)
{
if (source == SYSCLOCK_SRC_SOSC)
{
_sysclock_sosc = freq;
return 0;
}
if (source == SYSCLOCK_SRC_POSC)
{
_sysclock_posc = freq;
return 0;
}
return -1;
}
/**
* @brief Return the actual clock source for system clock
* @return SYSCLOCK_SOURCE enum corresponding to actual clock source
*/
SYSCLOCK_SOURCE sysclock_source(void)
{
SYSCLOCK_SOURCE source = (SYSCLOCK_SOURCE)OSCCONbits.COSC;
return source;
}
/**
* @brief Switch the source clock of sysclock to another one and wait for the change effective
* @param source id to switch to
* @return 0 if ok, -1 in case of error
*/
int sysclock_switchSourceTo(SYSCLOCK_SOURCE source)
{
if (OSCCONbits.CLKLOCK == 1)
{
return -1; // Clocks and PLL are locked, source cannot be changed
}
// disable interrupts
disable_interrupt();
// unlock clock config (OSCCON is write protected)
// unlockClockConfig();
// select the new source
__builtin_write_OSCCONH(source); // primariry osc input
// trigger change
__builtin_write_OSCCONL(OSCCON | 0x01);
nop();
nop();
// relock clock config
// lockClockConfig();
while (OSCCONbits.OSWEN == 1)
{
nop();
}
// enable interrupts
enable_interrupt();
if (sysclock_source() != source)
{
return -3; // Error when switch clock source
}
_sysclock_sysfreq = sysclock_sourceFreq(sysclock_source());
return 0;
}
/**
* @brief Sets the system clock of the CPU, the system clock may be different of CPU
* clock and peripheral clock
* @param fosc desirate system frequency in Hz
* @return 0 if ok, -1 in case of error
*/
int sysclock_setClock(uint32_t fosc)
{
#ifndef SYSCLOCK_XTAL
OSCTUN = 21; // ==> Fin = 8 MHz Internal clock
return sysclock_setPLLClock(fosc, SYSCLOCK_SRC_FRC);
#else
return sysclock_setPLLClock(fosc, SYSCLOCK_SRC_POSC);
#endif
}
/**
* @brief Internal function to set clock with PLL from XTAL or FRC
* @param fosc desirate system frequency in Hz
* @param src input source clock of PLL (SYSCLOCK_SRC_FRC or SYSCLOCK_SRC_POSC)
* @return 0 if ok, -1 in case of error
*/
int sysclock_setPLLClock(uint32_t fosc, uint8_t src)
{
uint32_t fin, fplli, fsys;
uint16_t multiplier;
uint16_t prediv, postdiv;
if (src != SYSCLOCK_SRC_FRC && src != SYSCLOCK_SRC_POSC)
{
return -4;
}
if (fosc > SYSCLOCK_FOSC_MAX)
{
return -1; // cannot generate fosc > SYSCLOCK_FOSC_MAX
}
if (fosc < SYSCLOCK_FSYS_MIN / 8)
{
return -1; // cannot generate fosc < SYSCLOCK_FSYS_MIN / 8
}
fin = sysclock_sourceFreq(src);
if (fin == 0)
{
return -1;
}
// calculate post-diviser and fsys
postdiv = 2;
fsys = fosc << 1;
if (fsys < SYSCLOCK_FSYS_MIN)
{
postdiv = 4;
fsys = fosc << 2;
}
if (fsys < SYSCLOCK_FSYS_MIN)
{
postdiv = 8;
fsys = fosc << 3;
}
#if defined(SYSCLOCK_PLL468)
prediv = 1;
fplli = fin;
switch ((_CONFIG4 & 0x003C00) >> 10)
{
case 0b1110:
multiplier = 8;
break;
case 0b1101:
multiplier = 6;
break;
default:
multiplier = 4;
break;
}
postdiv = 1;
#elif defined(SYSCLOCK_PLL4)
prediv = 1;
fplli = fin;
multiplier = 4;
postdiv = 1;
#else
// calculate pre-diviser to ensure Fplli < SYSCLOCK_FPLLI_MAX
prediv = (fin / (SYSCLOCK_FPLLI_MAX + 1)) + 1;
if (prediv < SYSCLOCK_N1_MIN)
{
prediv = SYSCLOCK_N1_MIN;
}
fplli = fin / prediv;
// calculate multiplier
multiplier = fsys / fplli;
# ifdef ARCHI_pic24fj
// set post-diviser
if (postdiv == 2)
{
CLKDIVbits.PLLPOST = 0b00; // PLL post div = 2
}
if (postdiv == 4)
{
CLKDIVbits.PLLPOST = 0b01; // PLL post div = 4
}
if (postdiv == 8)
{
CLKDIVbits.PLLPOST = 0b11; // PLL post div = 8
}
// set pre-diviser
CLKDIVbits.PLLPRE = prediv - 2; // PLL pre div = 1
# endif
// (PLLFBD + 2)
// Fosc = Fin * ----------------------------------
// ((PLLPRE + 2) * 2 * (PLLPOST + 1))
PLLFBD = multiplier - 2;
if (src == SYSCLOCK_SRC_FRC)
{
__builtin_write_OSCCONH(SYSCLOCK_SRC_FRCPLL); // frc input
__builtin_write_OSCCONL(OSCCON | 0x01);
// Wait for Clock switch to occur
while (OSCCONbits.COSC != SYSCLOCK_SRC_FRCPLL)
{
;
}
}
else
{
__builtin_write_OSCCONH(SYSCLOCK_SRC_PPLL); // primary osc input
__builtin_write_OSCCONL(OSCCON | 0x01);
// Wait for Clock switch to occur
while (OSCCONbits.COSC != SYSCLOCK_SRC_PPLL)
{
;
}
}
#endif
// Wait for PLL to lock
while (OSCCONbits.LOCK != 1)
{
;
}
_sysclock_pll = fplli * multiplier / postdiv;
_sysclock_sysfreq = _sysclock_pll; // Complete this
return 0;
}