portapack-mayhem/firmware/application/hw/si5351.hpp

412 lines
9.5 KiB
C++
Raw Normal View History

2015-07-08 11:39:24 -04:00
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __SI5351_H__
#define __SI5351_H__
#include <cstdint>
#include <array>
#include <algorithm>
#include "ch.h"
#include "hal.h"
#include "i2c_pp.hpp"
namespace si5351 {
namespace Register {
enum {
DeviceStatus = 0,
InterruptStatusSticky = 1,
InterruptStatusMask = 2,
OutputEnableControl = 3,
OEBPinEnableControlMask = 9,
PLLInputSource = 15,
CLKControl_Base = 16,
CLKControl0 = 16,
CLKControl1 = 17,
CLKControl2 = 18,
CLKControl3 = 19,
CLKControl4 = 20,
CLKControl5 = 21,
CLKControl6 = 22,
CLKControl7 = 23,
CLK3_0DisableState = 24,
CLK7_4DisableState = 25,
MultisynthNAParameters_Base = 26,
MultisynthNBParameters_Base = 34,
Multisynth0Parameters_Base = 42,
Multisynth1Parameters_Base = 50,
Multisynth2Parameters_Base = 58,
Multisynth3Parameters_Base = 66,
Multisynth4Parameters_Base = 74,
Multisynth5Parameters_Base = 82,
Multisynth6Parameters = 90,
Multisynth7Parameters = 91,
Clock6And7OutputDivider = 92,
SpreadSpectrumParameters_Base = 149,
VCXOParameters_Base = 162,
CLKInitialPhaseOffset_Base = 165,
PLLReset = 177,
CrystalInternalLoadCapacitance = 183,
FanoutEnable = 187,
};
}
namespace DeviceStatus {
using Type = uint8_t;
enum {
REVID_Mask = (0b11 << 0),
LOS_Mask = (1 << 4),
LOS_ValidClockAtCLKIN = (0 << 4),
LOS_LossOfSignalAtCLKIN = (1 << 4),
LOL_A_Mask = (1 << 5),
LOL_A_PLLALocked = (0 << 5),
LOL_A_PLLAUnlocked = (1 << 5),
LOL_B_Mask = (1 << 6),
LOL_B_PLLBLocked = (0 << 6),
LOL_B_PLLBUnlocked = (1 << 6),
SYS_INIT_Mask = (1 << 7),
SYS_INIT_Complete = (0 << 7),
SYS_INIT_Initializing = (1 << 7),
};
}
namespace ClockControl {
using Type = uint8_t;
enum {
CLK_IDRV_Mask = (0b11 << 0),
CLK_IDRV_2mA = (0b00 << 0),
CLK_IDRV_4mA = (0b01 << 0),
CLK_IDRV_6mA = (0b10 << 0),
CLK_IDRV_8mA = (0b11 << 0),
CLK_SRC_Mask = (0b11 << 2),
CLK_SRC_XTAL = (0b00 << 2),
CLK_SRC_CLKIN = (0b01 << 2),
CLK_SRC_MS_Group = (0b10 << 2),
CLK_SRC_MS_Self = (0b11 << 2),
CLK_INV_Mask = (1 << 4),
CLK_INV_Normal = (0 << 4),
CLK_INV_Invert = (1 << 4),
MS_SRC_Mask = (1 << 5),
MS_SRC_PLLA = (0 << 5),
MS_SRC_PLLB = (1 << 5),
MS_INT_Mask = (1 << 6),
MS_INT_Fractional = (0 << 6),
MS_INT_Integer = (1 << 6),
CLK_PDN_Mask = (1 << 7),
CLK_PDN_Power_On = (0 << 7),
CLK_PDN_Power_Off = (1 << 7),
};
}
using ClockControls = std::array<ClockControl::Type, 8>;
namespace CrystalInternalLoadCapacitance {
using Type = uint8_t;
enum {
XTAL_CL_Mask = (0b11 << 6),
XTAL_CL_6pF = (0b01 << 6),
XTAL_CL_8pF = (0b10 << 6),
XTAL_CL_10pF = (0b11 << 6),
};
}
namespace PLLInputSource {
using Type = uint8_t;
enum {
PLLA_Source_Mask = (1 << 2),
PLLA_Source_XTAL = (0 << 2),
PLLA_Source_CLKIN = (1 << 2),
PLLB_Source_Mask = (1 << 3),
PLLB_Source_XTAL = (0 << 3),
PLLB_Source_CLKIN = (1 << 3),
CLKIN_Div_Mask = (0b11 << 6),
CLKIN_Div1 = (0b00 << 6),
CLKIN_Div2 = (0b01 << 6),
CLKIN_Div4 = (0b10 << 6),
CLKIN_Div8 = (0b11 << 6),
};
}
struct Inputs {
const uint32_t f_xtal;
const uint32_t f_clkin;
const uint32_t clkin_div;
2017-01-05 20:06:44 -05:00
constexpr uint32_t f_clkin_out() const {
2015-07-08 11:39:24 -04:00
return f_clkin / clkin_div;
}
};
using PLLReg = std::array<uint8_t, 9>;
struct PLL {
const uint32_t f_in;
const uint32_t a;
const uint32_t b;
const uint32_t c;
2017-01-05 20:06:44 -05:00
constexpr uint32_t f_vco() const {
2015-07-08 11:39:24 -04:00
return f_in * (a + (float)b / (float)c);
}
2017-01-05 20:06:44 -05:00
constexpr uint32_t p1() const {
2015-07-08 11:39:24 -04:00
return 128 * a + (uint32_t)(128 * (float)b / (float)c) - 512;
}
2017-01-05 20:06:44 -05:00
constexpr uint32_t p2() const {
2015-07-08 11:39:24 -04:00
return 128 * b - c * (uint32_t)(128 * (float)b / (float)c);
}
2017-01-05 20:06:44 -05:00
constexpr uint32_t p3() const {
2015-07-08 11:39:24 -04:00
return c;
}
2017-01-05 20:06:44 -05:00
constexpr PLLReg reg(const uint8_t pll_n) const {
2015-07-08 11:39:24 -04:00
return {
uint8_t(26 + (pll_n * 8)),
uint8_t((p3() >> 8) & 0xff),
uint8_t((p3() >> 0) & 0xff),
uint8_t((p1() >> 16) & 0x03),
uint8_t((p1() >> 8) & 0xff),
uint8_t((p1() >> 0) & 0xff),
uint8_t(
(((p3() >> 16) & 0x0f) << 4)
| ((p2() >> 16) & 0x0f)
),
uint8_t((p2() >> 8) & 0xff),
uint8_t((p2() >> 0) & 0xff),
};
}
};
using MultisynthFractionalReg = std::array<uint8_t, 9>;
struct MultisynthFractional {
const uint32_t f_src;
const uint32_t a;
const uint32_t b;
const uint32_t c;
const uint32_t r_div;
2017-01-05 20:06:44 -05:00
constexpr uint32_t p1() const {
2015-07-08 11:39:24 -04:00
return 128 * a + (uint32_t)(128 * (float)b / (float)c) - 512;
}
2017-01-05 20:06:44 -05:00
constexpr uint32_t p2() const {
2015-07-08 11:39:24 -04:00
return 128 * b - c * (uint32_t)(128 * (float)b / (float)c);
}
2017-01-05 20:06:44 -05:00
constexpr uint32_t p3() const {
2015-07-08 11:39:24 -04:00
return c;
}
2017-01-05 20:06:44 -05:00
constexpr uint32_t f_out() const {
2015-07-08 11:39:24 -04:00
return f_src / (a + (float)b / (float)c) / (1 << r_div);
}
2017-01-05 20:06:44 -05:00
constexpr MultisynthFractionalReg reg(const uint8_t multisynth_n) const {
2015-07-08 11:39:24 -04:00
return {
uint8_t(42 + (multisynth_n * 8)),
uint8_t((p3() >> 8) & 0xFF),
uint8_t((p3() >> 0) & 0xFF),
uint8_t((r_div << 4) | (0 << 2) | ((p1() >> 16) & 0x3)),
uint8_t((p1() >> 8) & 0xFF),
uint8_t((p1() >> 0) & 0xFF),
uint8_t((((p3() >> 16) & 0xF) << 4) | (((p2() >> 16) & 0xF) << 0)),
uint8_t((p2() >> 8) & 0xFF),
uint8_t((p2() >> 0) & 0xFF)
};
}
};
struct MultisynthInteger {
const uint32_t f_src;
const uint32_t a;
const uint32_t r_div;
2017-01-05 20:06:44 -05:00
constexpr uint8_t p1() const {
2015-07-08 11:39:24 -04:00
return a;
}
2017-01-05 20:06:44 -05:00
constexpr uint32_t f_out() const {
2015-07-08 11:39:24 -04:00
return f_src / a / (1 << r_div);
}
};
using Multisynth6And7Reg = std::array<uint8_t, 4>;
constexpr Multisynth6And7Reg ms6_7_reg(
const MultisynthInteger& ms6,
const MultisynthInteger& ms7
) {
return {
Register::Multisynth6Parameters,
uint8_t(ms6.p1() & 0xff),
uint8_t(ms7.p1() & 0xff),
uint8_t(((ms7.r_div & 7) << 4) | ((ms6.r_div & 7) << 0)),
};
}
class Si5351 {
public:
using regvalue_t = uint8_t;
constexpr Si5351(I2C& bus, I2C::address_t address) :
_clock_control({
ClockControl::CLK_PDN_Power_Off, ClockControl::CLK_PDN_Power_Off,
ClockControl::CLK_PDN_Power_Off, ClockControl::CLK_PDN_Power_Off,
ClockControl::CLK_PDN_Power_Off, ClockControl::CLK_PDN_Power_Off,
ClockControl::CLK_PDN_Power_Off, ClockControl::CLK_PDN_Power_Off
}),
_bus(bus),
_address(address),
_output_enable(0x00)
{
}
void reset();
uint8_t device_status() {
return read_register(Register::DeviceStatus);
}
void wait_for_device_ready() {
while(device_status() & 0x80);
}
void enable_fanout() {
write_register(Register::FanoutEnable, 0b11010000);
}
void reset_plls() {
write_register(Register::PLLReset, 0xa0);
}
regvalue_t read_register(const uint8_t reg);
template<size_t N>
void write(const std::array<uint8_t, N>& values) {
_bus.transmit(_address, values.data(), values.size());
}
void write_register(const uint8_t reg, const regvalue_t value) {
write(std::array<uint8_t, 2>{
reg, value
});
}
void write(const size_t ms_number, const MultisynthFractional& config) {
write(config.reg(ms_number));
}
void set_ms_frequency(
const size_t ms_number,
const uint32_t frequency,
const uint32_t vco_frequency,
const size_t r_div
);
void set_crystal_internal_load_capacitance(const CrystalInternalLoadCapacitance::Type xtal_cl) {
write_register(Register::CrystalInternalLoadCapacitance, xtal_cl);
}
void set_pll_input_sources(const PLLInputSource::Type value) {
write_register(Register::PLLInputSource, value);
}
void enable_output_mask(const uint8_t mask) {
_output_enable |= mask;
update_output_enable_control();
}
void enable_output(const size_t n) {
enable_output_mask(1 << n);
}
void disable_output_mask(const uint8_t mask) {
_output_enable &= ~mask;
update_output_enable_control();
}
void disable_output(const size_t n) {
disable_output_mask(1 << n);
}
void set_clock_control(const ClockControls& clock_control) {
_clock_control = clock_control;
update_all_clock_control();
}
void enable_clock(const size_t n) {
_clock_control[n] &= ~ClockControl::CLK_PDN_Mask;
write_register(Register::CLKControl_Base + n, _clock_control[n]);
}
void disable_clock(const size_t n) {
_clock_control[n] |= ClockControl::CLK_PDN_Mask;
write_register(Register::CLKControl_Base + n, _clock_control[n]);
}
template<size_t N>
void write_registers(const uint8_t reg, const std::array<uint8_t, N>& values) {
std::array<uint8_t, N + 1> data;
data[0] = reg;
std::copy(values.cbegin(), values.cend(), data.begin() + 1);
write(data);
}
private:
std::array<uint8_t, 8> _clock_control;
I2C& _bus;
const I2C::address_t _address;
uint8_t _output_enable;
void update_output_enable_control() {
write_register(Register::OutputEnableControl, ~_output_enable);
}
void update_all_clock_control() {
write_registers(Register::CLKControl_Base, _clock_control);
}
};
}
#endif/*__SI5351_H__*/