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