[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!
}
}

Technologie End of Life i bezpieczeństwo sieci – wyzwania Europy związane z tzw. długiem technologicznym
Najczęstsze błędy firm przy wyborze dostawcy energii i jak ich uniknąć
Fotorezystor, czyli czujnik światła dwojakiego działania. Przykład innowacji w automatyce i elektronice możliwej dzięki technologii fotooporników 



