diff --git a/README.md b/README.md index 5f99e3a..3e0fb33 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,29 @@ The standard I2C bus address for the Si5351 is 0x60, however there are other ICs Si5351 si5351(0x61); +Selecting an I2C Bus +-------------------- +By default the library uses the standard _Wire_ bus. On boards with more than one I2C peripheral (ESP32, RP2040, SAMD, Teensy, STM32 and others), pass a pointer to the _TwoWire_ instance the Si5351 is connected to, optionally followed by the bus address: + + #include "Wire.h" + #include "si5351.h" + + Si5351 si5351(&Wire1); // second I2C bus, address 0x60 + // Si5351 si5351(&Wire1, 0x61); // second I2C bus, alternate address + + void setup() + { + #if defined(ARDUINO_ARCH_ESP32) + Wire1.setPins(13, 15); // ESP32 only: choose SDA/SCL before init() + #endif + si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0); + // Wire1.setClock(400000); // optional: set the bus speed after init() + } + +A _TwoWire_ object you create yourself (for example `TwoWire wire1(1);` on ESP32) works the same way: `Si5351 si5351(&wire1);`. + +_init()_ calls _begin()_ on the selected bus with no arguments, as earlier versions did on _Wire_. That keeps pins set beforehand with _setPins()_ (ESP32) or _setSDA()/setSCL()_ (RP2040). If your board needs pins passed to _begin()_ itself, call _begin(...)_ on the bus yourself before _init()_. On ESP32 a bus that is already started is left as it is; the core logs the warning "Bus already started in Master Mode", which is harmless and the same as with earlier versions of this library. On most other cores (AVR, SAMD, RP2040, STM32 and others) _begin()_ runs again inside _init()_ and may reset a bus speed chosen earlier with _setClock()_, so call _setClock()_ after _init()_. To get the default _Wire_ bus through the bus constructor, pass `nullptr` or `(TwoWire *)0`; a plain `NULL` or `0` is treated as I2C address 0, as in earlier versions. The existing address-only constructor, `Si5351 si5351(0x61);`, still selects _Wire_. + Startup Conditions ------------------ This library initializes the Si5351 parameters to the following values upon startup and on reset: @@ -711,6 +734,13 @@ This library does not currently support the spread spectrum function of the Si53 Changelog --------- +* v2.4.0 + + * Add I2C bus selection: `Si5351(TwoWire *wire = &Wire, uint8_t i2c_addr = SI5351_BUS_BASE_ADDR)` so the Si5351 can be on Wire1 or any other TwoWire bus (closes #103, #106). `Si5351()` and `Si5351(addr)` (any integer type, including a literal 0 or NULL) behave as before. No new C++11 features are required + * Passing `nullptr` or `(TwoWire *)0` to the bus constructor selects `Wire`; `NULL` or `0` still mean I2C address 0 + * Cost on the Uno basic example: +138 B flash, +2 B RAM + * Fix compile error in the si5351_vcxo example (`get_correction()` now called with `SI5351_PLL_INPUT_XO`) + * v2.3.0 * Reduce flash usage by roughly 0.6 to 3 KB on AVR (depending on which features a sketch uses) with no change to the public API diff --git a/examples/si5351_vcxo/si5351_vcxo.ino b/examples/si5351_vcxo/si5351_vcxo.ino index afc7ffe..03f3ee7 100644 --- a/examples/si5351_vcxo/si5351_vcxo.ino +++ b/examples/si5351_vcxo/si5351_vcxo.ino @@ -49,7 +49,7 @@ void loop() // Read the Status Register and print it every 10 seconds si5351.update_status(); Serial.print("corr: "); - Serial.print(si5351.get_correction()); + Serial.print(si5351.get_correction(SI5351_PLL_INPUT_XO)); Serial.print(" SYS_INIT: "); Serial.print(si5351.dev_status.SYS_INIT); Serial.print(" LOL_A: "); diff --git a/library.json b/library.json index bb2faa2..8bedf55 100644 --- a/library.json +++ b/library.json @@ -16,7 +16,7 @@ "maintainer": true } ], - "version": "2.3.0", + "version": "2.4.0", "frameworks": "arduino", "platforms": "*" } \ No newline at end of file diff --git a/library.properties b/library.properties index b9e8eeb..3b45d97 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Etherkit Si5351 -version=2.3.0 +version=2.4.0 author=Jason Milldrum maintainer=Jason Milldrum sentence=A full-featured library for the Si5351 series of clock generator ICs from Silicon Labs diff --git a/src/si5351.cpp b/src/si5351.cpp index a6bfcf7..deaa147 100644 --- a/src/si5351.cpp +++ b/src/si5351.cpp @@ -67,9 +67,16 @@ static void frac_to_reg(uint32_t a, uint32_t b, uint32_t c, struct Si5351RegSet /* Public functions */ /********************/ -Si5351::Si5351(uint8_t i2c_addr): - i2c_bus_addr(i2c_addr) +Si5351::Si5351(TwoWire *wire, uint8_t i2c_addr) { + setup_obj(wire, i2c_addr); +} + +// Shared constructor body (no C++11 delegating constructor needed) +void Si5351::setup_obj(TwoWire *wire, uint8_t i2c_addr) +{ + i2c_bus_addr = i2c_addr; + _wire = wire ? wire : &Wire; xtal_freq[0] = SI5351_XTAL_FREQ; // Start by using XO ref osc as default for each PLL @@ -96,13 +103,15 @@ Si5351::Si5351(uint8_t i2c_addr): */ bool Si5351::init(uint8_t xtal_load_c, uint32_t xo_freq, int32_t corr) { - // Start I2C comms - Wire.begin(); + // Start I2C comms on the selected bus. Called with no arguments so that + // pins chosen beforehand (e.g. ESP32 setPins()) are kept; on ESP32 a bus + // already started by the sketch with begin(sda, scl, freq) is left as is. + _wire->begin(); // Check for a device on the bus, bail out if it is not there - Wire.beginTransmission(i2c_bus_addr); + _wire->beginTransmission(i2c_bus_addr); uint8_t reg_val; - reg_val = Wire.endTransmission(); + reg_val = _wire->endTransmission(); if(reg_val == 0) { @@ -1200,37 +1209,37 @@ void Si5351::set_ref_freq(uint32_t ref_freq, enum si5351_pll_input ref_osc) uint8_t Si5351::si5351_write_bulk(uint8_t addr, uint8_t bytes, uint8_t *data) { - Wire.beginTransmission(i2c_bus_addr); - Wire.write(addr); + _wire->beginTransmission(i2c_bus_addr); + _wire->write(addr); for(int i = 0; i < bytes; i++) { - Wire.write(data[i]); + _wire->write(data[i]); } - return Wire.endTransmission(); + return _wire->endTransmission(); } uint8_t Si5351::si5351_write(uint8_t addr, uint8_t data) { - Wire.beginTransmission(i2c_bus_addr); - Wire.write(addr); - Wire.write(data); - return Wire.endTransmission(); + _wire->beginTransmission(i2c_bus_addr); + _wire->write(addr); + _wire->write(data); + return _wire->endTransmission(); } uint8_t Si5351::si5351_read(uint8_t addr) { uint8_t reg_val = 0; - Wire.beginTransmission(i2c_bus_addr); - Wire.write(addr); - Wire.endTransmission(); + _wire->beginTransmission(i2c_bus_addr); + _wire->write(addr); + _wire->endTransmission(); - Wire.requestFrom(i2c_bus_addr, (uint8_t)1); + _wire->requestFrom(i2c_bus_addr, (uint8_t)1); - while(Wire.available()) + while(_wire->available()) { - reg_val = Wire.read(); + reg_val = _wire->read(); } return reg_val; diff --git a/src/si5351.h b/src/si5351.h index 132a522..a04c8c0 100644 --- a/src/si5351.h +++ b/src/si5351.h @@ -279,7 +279,20 @@ struct Si5351IntStatus class Si5351 { public: - Si5351(uint8_t i2c_addr = SI5351_BUS_BASE_ADDR); + // Select the I2C bus (TwoWire instance) and address, e.g. Si5351 si5351(&Wire1); + // A null bus pointer falls back to Wire. + Si5351(TwoWire *wire = &Wire, uint8_t i2c_addr = SI5351_BUS_BASE_ADDR); + // Address only, on the default Wire bus (kept for existing sketches). + // One overload per integer type so that any integer argument, including a + // literal 0 or NULL, selects the address path exactly as before instead of + // being ambiguous with the TwoWire * constructor. + Si5351(uint8_t i2c_addr) { setup_obj(&Wire, i2c_addr); } + Si5351(int i2c_addr) { setup_obj(&Wire, (uint8_t)i2c_addr); } + Si5351(unsigned int i2c_addr) { setup_obj(&Wire, (uint8_t)i2c_addr); } + Si5351(long i2c_addr) { setup_obj(&Wire, (uint8_t)i2c_addr); } + Si5351(unsigned long i2c_addr) { setup_obj(&Wire, (uint8_t)i2c_addr); } + Si5351(long long i2c_addr) { setup_obj(&Wire, (uint8_t)i2c_addr); } + Si5351(unsigned long long i2c_addr) { setup_obj(&Wire, (uint8_t)i2c_addr); } bool init(uint8_t, uint32_t, int32_t); void reset(void); uint8_t set_freq(uint64_t, enum si5351_clock); @@ -318,6 +331,7 @@ class Si5351 enum si5351_pll_input pllb_ref_osc; uint32_t xtal_freq[2]; private: + void setup_obj(TwoWire *, uint8_t); uint64_t pll_calc(enum si5351_pll, uint64_t, struct Si5351RegSet *, int32_t, uint8_t); uint64_t multisynth_calc(uint64_t, uint64_t, struct Si5351RegSet *); uint64_t multisynth67_calc(uint64_t, uint64_t, struct Si5351RegSet *); @@ -328,6 +342,7 @@ class Si5351 int32_t ref_correction[2]; uint8_t clkin_div; uint8_t i2c_bus_addr; + TwoWire *_wire; bool clk_first_set[8]; };