LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

[PROJEKT] Tuner FM na układzie Si4703 firmy Silicon Labs i LPC1768

Sklep KAMAMI.pl wprowadził do oferty moduł z układem Si4703 (Silicon Labs). Układ Si4703 to kompletny odbiornik radiowy FM z wieloma zaawansowanymi funkcjami, m.in.:

  • odbiór fal o częstotliwościach w zakresie 76…108 MHz,
  • automatyczne wyszukiwanie stacji (seek),
  • pomiar poiozmu sygnału radiowego,
  • interfejsy SPI i I2C,
  • odbiór danych RDS.

 

 

 

Płytka modułu oprócz samego układu odbiornika zawiera m.in. wzmacniacz audio, gniazdo mini-jack stereo służące do dołączenia słuchawek oraz pole lutownicze dla 8-szpilkowego złącza goldpin. W celu zademonstrowania działania modułu przygotowaliśmy prosty przykład dla płytki startowej miniLPC1768 z mikrokontrolerem LPC1768. Sposób dołączenia modułu do płytki miniLPC1768 przedstawiono na rysunku 1.


Rys. 1. Sposób dołączenia modułu z układem Si4703 do płytki miniLPC1768

Komunikacja z układem Si4703

Nie będziemy się szczegółowo zajmować implementacją obsługi interfejsu I2C oraz linii GPIO, ponieważ biblioteki udostępnione przez firmę NXP są łatwe w użyciu, rzut oka na kod źródłowy powinien rozwiać wszelkie wątpliwości. Konfiguracja linii GPIO odbywa się w funkcji InitGPIO:

void InitGPIO()
{
  PINSEL_CFG_Type PinCfg;
  /* Initialize GPIO
  * P1.24 - GPIO (/SEN)
  * P1.25 - GPIO (/RST)
  * P0.19 - GPIO (SDA)
  */
  
  PinCfg.Funcnum = 0;
  PinCfg.Pinmode = PINSEL_PINMODE_PULLUP;
  PinCfg.Portnum = nRSTPort;
  PinCfg.Pinnum = nRSTPin;
  PINSEL_ConfigPin(&PinCfg);
  
  PinCfg.Portnum = nSENPort;
  PinCfg.Pinmode = PINSEL_PINMODE_PULLUP;
  PinCfg.Pinnum = nSENPin;
  PINSEL_ConfigPin(&PinCfg);
  
  PinCfg.Portnum = 0;
  PinCfg.Pinmode = PINSEL_PINMODE_PULLUP;
  PinCfg.Pinnum = 19;
  PINSEL_ConfigPin(&PinCfg);
  
  GPIO_SetDir(nRSTPort, ( (uint32_t) 1 << nRSTPin ), 1);
  ResetnRST();  
  GPIO_SetDir(nSENPort, ( (uint32_t) 1 << nSENPin ), 1);
  ResetnSEN();
  GPIO_SetDir(0, ( (uint32_t) 1 << 19 ), 1);
  ResetnSEN();
}

Funkcja konfiguruje linie P1.24, P1.25 oraz P0.19, będą one potrzebne podczas inicjalizacji układu Si4703. Interfejs I2C konfigurowany jest w funkcji InitI2C:

void InitI2C()
{
   PINSEL_CFG_Type PinCfg;
  /*
   * Initialize I2C pins
   * P0.19 - SDA
   * P0.20 - SCL
   */
  PinCfg.Funcnum = 3;
  PinCfg.OpenDrain = 1;
  PinCfg.Pinmode = PINSEL_PINMODE_PULLUP;
  PinCfg.Portnum = 0;
  PinCfg.Pinnum = 19;
  PINSEL_ConfigPin(&PinCfg);
  PinCfg.Pinnum = 20;
  PINSEL_ConfigPin(&PinCfg);

  // Enable I2C1 with 100 kHz speed
  I2C_Init(LPC_I2C1, 100000);
  I2C_Cmd(LPC_I2C1, ENABLE);  
}

Do współpracy z modułem potrzebujemy jeszcze dwóch funkcji, które umożliwią zapis i odczyt rejestrów układu:

void si4703_readRegisters(void){
  uint8_t rbuffer[32], i = 0;
  int x;
  
  I2C_M_SETUP_Type i2cst;
  i2cst.sl_addr7bit = 0x10;
  i2cst.tx_data = NULL;
  i2cst.tx_length = 0;
  i2cst.rx_data = &rbuffer[0];
  i2cst.rx_length = 32;
  i2cst.retransmissions_max=5;
  I2C_MasterTransferData(LPC_I2C1, &i2cst, I2C_TRANSFER_POLLING);


  //Remember, register 0x0A comes in first so we have to shuffle the array around a bit
  for(x = 0x0A ; ; x++) { //Read in these 32 bytes
    if(x == 0x10) x = 0; //Loop back to zero
    si4703_registers[x] = rbuffer[i++] << 8;
    si4703_registers[x] |= rbuffer[i++];
    if(x == 0x09) break; //We're done!
  }
} 


uint8_t si4703_updateRegisters(void) {
  char tbuffer[32], i = 0;
  int regSpot;
  uint8_t high_byte, low_byte; 
  
  I2C_M_SETUP_Type i2cst;
  i2cst.sl_addr7bit = 0x10;
  i2cst.tx_data = &tbuffer[0];
  i2cst.tx_length = 12;
  i2cst.rx_data = NULL;
  i2cst.rx_length = 0;
  i2cst.retransmissions_max=5;
  
  
  for(regSpot = 0x02 ; regSpot < 0x08 ; regSpot++) { high_byte = si4703_registers[regSpot] >> 8;
    low_byte = si4703_registers[regSpot] & 0x00FF;

    tbuffer[i++] = high_byte; //Upper 8 bits
    tbuffer[i++] = low_byte; //Lower 8 bits
  }
  I2C_MasterTransferData(LPC_I2C1, &i2cst, I2C_TRANSFER_POLLING);

  return(SUCCESS); 
}

Na pierwszy rzut oka sposób zapisu i odczytu rejestrów może wydać się dziwny, ale zaraz wszystko stanie się jasne. Zwykle chcąc odczytać bądź zapisać rejestr musimy wysłać do układu najpierw jego adres w szynie I2C, następnie adres rejestru, w tym momencie możemy odbierać lub wysyłać dane. W przypadku układu Si4703 sprawa wygląda nieco inaczej: wysyłamy tylko adres układu, ale nie wysyłamy adresów rejestrów, tylko od razu odczytujemy lub zapisujemy je sekwencyjnie. Co więcej kolejność rejestrów przy zapisie i odczycie jest inna, przy zapisie ładowane są po kolei rejestry o adresach 0x2..0x7, przy odczycie najpierw odczytywane są rejestry 0xA..0xF, następnie 0x0..0x9.

Inicjalizacja układu Si4703

Układ Si4703 może być kontrolowany przez interfejsy SPI lub I2C zależnie sposobu jego inicjalizacji, w przykładzie posłużymy się interfejsem I2C. Aby uruchomić interfejs I2C układu Si4703 musimy wykonać odpowiednią sekwencję startową:

    • dołączyć zasilanie
    • ustawić stan niski na liniach SDIO i /RST oraz wysoki na /SEN (nasz moduł ma tę linię dołączoną do plusa zasilania, więc możemy nie dołączać jej do mikrokontrolera)
    • ustawić stan wysoki na linii /RST

Teraz możemy uruchomić odbiornik, aby to zrobić należy:

  • ustawić bit XOSCEN w rejestrze 0x7
  • odczekać ok. pół sekundy
  • ustawić bit ENABLE w rejestrze 0x2
  • skonfigurować odbiornik do pracy Europie, czyli ustawić bity DE w rejestrze SYSCONFIG1 i SPACE0 w SYSCONFIG2
void Si4703_Configuration(void)
{
  InitDelay();
  
  InitGPIO();
  GPIO_ClearValue(0, ( (uint32_t) 1 << 19 ));
  ResetnRST();
  Delay(2);
  SetnRST();
  Delay(2);

  
  InitI2C();
  
  si4703_readRegisters();   //Read the current register set
  
  si4703_registers[0x07] = 0x8100; //Enable the oscillator, from AN230 page 9, rev 0.61 (works)
  si4703_updateRegisters(); //Update

  Delay(500); //Wait for clock to settle - from AN230 page 9

  si4703_readRegisters(); //Read the current register set
  si4703_registers[POWERCFG] = 0x4001; //Enable the IC
  si4703_registers[SYSCONFIG1] |= (1<<RDS); //Enable RDS


  si4703_registers[SYSCONFIG1] |= (1<<DE); //50us Europe setup
  si4703_registers[SYSCONFIG2] |= (1<<SPACE0); //100kHz channel spacing for Europe


  si4703_registers[SYSCONFIG2] &= 0xFFF0; //Clear volume bits
  si4703_registers[SYSCONFIG2] |= 0x0001; //Set volume to lowest
  si4703_updateRegisters(); //Update

  Delay(110); //Max powerup time, from datasheet page 13
}

Podstawowe funkcje

W przykładzie umożliwiono regulację głośności oraz częstotliwości odbieranej stacji korzystając z ekranu dotykowego. Regulacja głośności polega na zapisaniu wartości głośności (0..15) do odpowiedniego rejestru, to zadanie realizuje funkcja setVolume:

void setVolume(uint8_t vol) {
  si4703_readRegisters(); //Read the current register set
  si4703_registers[SYSCONFIG2] &= 0xFFF0; //Clear volume bits
  si4703_registers[SYSCONFIG2] |= vol; //Set new volume
  si4703_updateRegisters(); //Update
}

Aby ustawić częstotliwość stacji należy:

    • wpisać nową częstotliwość do rejestru CHANNEL
    • ustawić bit TUNE w rejestrze CHANNEL
    • poczekać aż bit STC w rejestrze STATUSRSSI zostanie ustawiony
    • wyzerować bit TUNE w rejestrze CHANNEL
    • poczekać aż bit STC w rejestrze STATUSRSSI zostanie wyzerowany

Powyższe czynności wykonuje funkcja gotoChannel:

void gotoChannel(int newChannel){
  newChannel *= 10; 
  newChannel -= 8750; 
  newChannel /= 10;

  //These steps come from AN230 page 20 rev 0.5
  si4703_readRegisters();
  si4703_registers[CHANNEL] &= 0xFE00; //Clear out the channel bits
  si4703_registers[CHANNEL] |= newChannel; //Mask in the new channel
  si4703_registers[CHANNEL] |= (1<<TUNE); //Set the TUNE bit to start
  si4703_updateRegisters();


  //Poll to see if STC is set
  while(1) {
    si4703_readRegisters();
    if( (si4703_registers[STATUSRSSI] & (1<<STC)) != 0) break; //Tuning complete!
  }

  si4703_readRegisters();
  si4703_registers[CHANNEL] &= ~(1<<TUNE); //Clear the tune after a tune has completed
  si4703_updateRegisters();

  while(1) {
    si4703_readRegisters();
    if( (si4703_registers[STATUSRSSI] & (1<<STC)) == 0) break; //Tuning complete!
  }
}