[SOLVED] Interface with I2C-bus controlled synthesizer (TSA5511)

Hi fellow Particles,

My current project (to be shared soon when finished) is a PLL FM transmitter based on the RDVV NoTune design.
The transmitter uses a Philips TSA5511 I2C-bus controlled synthesizer. The synth is used to set the frequency in combination with the VCO.

Normally these circuits have a PIC integrated (e.g. pic16f628a) to interface with the tsa5511 over I2C and to provide input and outputs for selecting frequency and reading PLL lock state…

A PIC is way to oldschool for me, this thing must have a Photon included!

My challenge is to get the I2C interface working. I am not able to read the table describing what can be written and what can be read. The testing I did with interfacing over I2C with the tsa5511 resulted in gibberish.

My goals;

  1. read the “In-Lock” bit (page 8 of the datasheet)
  2. write the frequency (page 7 of the datasheet)

Any help is more than welcome!

Thanks!

INO code as inspiration: INO
tsa5511 datasheet: PDF
PS. I am not an FM design guru


Using the common I2C scanning script;

Scanning...
I2C device found at address 0x60  !
I2C device found at address 0x61  !
done

Reading i2c on address 0x60;

void loop() {
  Wire.requestFrom(0x60, 4);    // request 4 bytes from slave device #8

  while (Wire.available()) { // slave may send less than requested
    char c = Wire.read(); // receive a byte as character
    Serial.print(c);         // print the character
    //Serial.print(Wire.read());
  }
  Serial.println("#");
  delay(1000);
}

Serial output:

Opening serial monitor for com port: "COM3"
 ����#

You may rather want to print the bytes as Serial.println(Wire.read(), BIN)

Result:

Then try

Serial.println((int)Wire.read(), BIN);

But since I prefer the more compact HEX representation of bytes anyway, you could also do this

  Wire.requestFrom(0x60, 4);    
  while (Wire.available() < 4);  // wait for all data to arrive
  for (int i=0; i<4; i++) { 
    Serial.printf("%02x ", Wire.read());        
  }
  Serial.println("#");

The TSA5511 datasheet:

So there are only 8 byte to read? Or am I misreading the table here?
The goal is the be able to read the FL flag bit.

Code used:

     Wire.requestFrom(0x60, 2);    
      while (Wire.available() < 2);  // wait for all data to arrive
      for (int i=0; i<4; i++) { 
        Serial.printf("%02x ", Wire.read());        
    }

Output:

Opening serial monitor for com port: "COM4"
10 10 10 10 #

That looks more like only one byte (8bit) for the status.

0x10 = 0b0001000
So the only bit set is the I1 bit.

If you request 2 bytes, you also need to reduce the for() loop to <2

2 Likes

I’m still puzzeling…

For programming, there is one module address (7 bits) and the R/W bit for selecting READ or WRITE mode.

READ mode : R/W = 1
WRITE mode : R/W = 0

Based on the “Table 2” image above the device address = 1100010 or in HEX 0x62 Which is a 7 bit address missing the last R/W bit. The R/W low bit is added by the Wire library based on the Read or Write action in the library.

There is only 1 full byte to read.
The code for reading the status should then be:

  Wire.requestFrom(0x62, 1);    
  while (Wire.available() < 1);  // wait for all data to arrive
  for (int i=0; i<1; i++) { 
    Serial.printf("%02x ", Wire.read());        
  }
  Serial.println("#");

Where I struggle is to “address” the part I want to receive, which is FL status, the second position in the table.

When you get a byte (8 bits) and you want bit number 6 of these (which is the FL flag) you’ll just do that

  int bitNumber = 6;
  myBit = (readByte >> bitNumber) & 0x01;
1 Like

Thanks @ScruffR, I think this seems to get working, thanks for the support so far!

Moving further to the writing part of the TSA5511.
The crystal used as a reference for the TSA5511 is a 3.2Mhz crystal. The manual describes it like this: The 7.8125 kHz reference frequency is obtained by dividing the output of the 4 MHz crystal oscillator by 512. Because the input of UHF/VHF signal is first divided by 8 the step size is 62.5 kHz. A 3.2 MHz crystal can offer step sizes of 50 kHz.
I am using a 3.2Mhz crystal, so it is correct because 3200000Hz / 512 = 6250Hz

What I know now is:

  • 3200000Hz is the crystal
  • 6250Hz is the reference frequency obtained by dividing the crystal with 512
  • 92000000Hz is the desired FM frequency (92.00Mhz)
  • The input RF must be divide by 8 (92000000/8=11500000)
  • to be send to TSA5511: 11500000 / 6259 = 1840

The TSA5511 can also be partially programmed on the condition that the first data byte following the address is byte 2 or byte 4. The meaning of the bits in the data bytes is given in Table 1. The first bit of the first data byte transmitted indicates whether frequency data (first bit = 0) or charge pump and port information (first bit = 1) will follow. Until an I2C-bus STOP condition is sent by the controller, additional data bytes can be entered without the need to re-address the device. This allows a smooth frequency sweep for fine tuning or AFC purposes. At power-on the ports are set to the high
impedance state.

Screenshot od table 1 out of the manual:

What I know from this now is:

  • Need to send 5 bytes starting with the address, the other 4 are the real data
  • byte 2 needs to start with a 0 (how will i do that??)

To send:
Address: 11000010 (0xC2) = 1 byte
programmable divider: ‭011100110000 (decimal 1840) = 2 bytes
‬Charge pump and test bits: 10001110 = 1 byte
Port config: 00000000 = 1 byte

My challange: how do I correctly format the programmable divder and write it to the TSA5511?

The function to set the frequency:

long freq = 92000000; // is 93.00Mhz FM frequency

void pll_set_frequency(long freq)
{
	char data[5];
	long Xtal = 3200000; // 3.2Mhz crystal
	long fRef = Xtal / 512; //Reference devider = 6250Mhz
	long fDiv = freq / 8; // RF input devider = 11500000
	long div = fDiv / fRef; // 11500000 / 6250 = 1840

//byte1
	data[0] = 0xC2; // the address
//byte2
	data[1] = (div & 0xFF00) >> 8; // should begin with a 0, shifting bits
//byte3
	data[2] = div & 0x00FF; // everything that follows
//byte4
	data[3] = 0x8E; // 10001110, to set charge pump and test bits
//byte5
	data[4] = 0x00; // 00000000, All zeros

	Wire.beginTransmission(0x61);
	Wire.write(data); //write everything to the TSA5511
	Wire.endTransmission();
}

The code you use does set the bytes correct already.
If you have a number in the range of 0~32767 (0b0000 0000 0000 0000 ~ 0b0111 1111 1111 1111) bit 15 will always be 0 already.

That’s only required if you want to do a full setup, but you could also only send 3 bytes where the 2nd byte tells the device how to treat the next byte and if you end the transmission after the 3rd byte the device will still be happy with that.

You just need to write out all bytes.
https://docs.particle.io/reference/firmware/photon/#write--2

In your case it could be

  Wire.beginTransmission(0x61); 
  Wire.write(data, 5); // if the device actually needs the address sent again (which I doubt)
                       // I'd rather think data[0] is exactly the same as your 0x61 << 1
  Wire.endTransmission();

but as said in the comment, I’d rather think it should be this

  Wire.beginTransmission(data[0] >> 1); 
  Wire.write(&data[1], 4); // only send the data bytes, since the address was already sent above
  Wire.endTransmission();

BTW:
An integer division by a power of 2 is the same as a right shift (>>) by the power.

	char data[5];
	long Xtal = 3200000;     // 3.2Mhz crystal
	long fRef = Xtal >> 9;   // x / 512 Reference devider = 6250Mhz
	long fDiv = freq >> 3;   // x / 8   RF input devider = 11500000
	long div  = fDiv / fRef; // 11500000 / 6250 = 1840

@bartjenniskens suggest that you get a logic analyser like this https://www.saleae.com/ that can analyse the I2C bus traffic. It is really handy for hardware debug.

1 Like

Many thanks @ScruffR! No luck yet even when its done by the book.

Decided to go for full reverse engineering of the PIC that controls the TSA5511, a logic analyzer is ordered on its way to my workbench :sweat_smile:.

The software of the PIC is kept very closed-source by the ones making the board, I only have a compiled assembly HEX file.

Hopefully the logic analyzer can give a clear understanding of what the original PIC is sending and receiving from the TSA5511. I’ll keep you posted! :particle:

OK! This Saleae thing rocks!

Samples I’ve grabbed on the i2c between the PIC and TSA5511:
Initiate TSA5511:

Write frequency 92.00Mhz to TSA5511 with help of the formula. The two bytes are 0x70 and 0x30 when I run it through the formula and the image below proofs that it is correct:

Read in-lock flag state from TSA5511

Write frequency 92.05Mhz to TSA5511 with help of the formula. The two bytes are 0x07 and 0x31

Set charge pump and ports (called after frequency set/change):

The 2e byte, 0x20 = 0010 0000, in the image below sets the P5 port. When set to 1 the open-collector output is active. On my multimeter the voltage drops when set to 1, it turns on a LED on the board that indicates a “in-lock” state.

Now I have enough to fiddle with the code and to get it fully working. When the code is ready I will share it!

Hi

I was struggling with the same classic PLL last month.
I have working code for Arduino but I suppose it will work for you as well.
The program isn’t using “wire” but something custom make.
I was wondering…did you make it work?

Cheers,

Yes I did, it all works fine! Currently working on a custom PCB to control the FM board.
You could just use the Wire library for all i2c communications, no need for custom coding there.

ok, very good. Last weekend I made “Wire” working as well. Keep us informed when your PCB is finished. It’s always interresting to see how other people do similar jobs. My goal is to build some kind of universal PLL for RX/TX in UHF ham radio.

1 Like