打印
[PIC®/AVR®/dsPIC®产品]

ATtiny85驱动RDA5807的收音机应用

[复制链接]
41|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
游客,如果您要查看本帖隐藏内容请回复



// ===================================================================================
// Project:   tinyFMradio - FM Tuner with RDS based on ATtiny45/85 and RDA5807
// Version:   v1.0
// Year:      2019 - 2021
// Author:    Stefan Wagner
// Github:    https://github.com/wagiminator
// EasyEDA:   https://easyeda.com/wagiminator
// License:   http://creativecommons.org/licenses/by-sa/3.0/
// ===================================================================================
//
// Description:
// ------------
// This is just a demo sketch that implements basic functionality. By pressing
// the rotary encoder button the RDA5807 seeks the next radio station. Turning
// the rotary encoder increases/decreases the volume. Selected frequency and
// volume are stored in the EEPROM. Station name, frequency, signal strength,
// volume and battery state of charge are shown on an OLED display.
//
// References:
// -----------
// RDA5807 datasheet:
// https://datasheet.lcsc.com/szlcsc/1806121226_RDA-Microelectronics-RDA5807MP_C167245.pdf
//
// SSD1306 OLED datasheet:
// https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
//
// The OLED font was adapted from Neven Boyanov and Stephen Denne:
// https://github.com/datacute/Tiny4kOLED
//
// The RDA8507 implementation was adapted from Maarten Janssen:
// https://hackaday.io/project/9009-arduino-radio-with-rds
//
// Wiring:
// -------
//                              +-\/-+
//           --- RST ADC0 PB5  1|°   |8  Vcc
// Encoder A ------- ADC3 PB3  2|    |7  PB2 ADC1 -------- OLED/RDA SCK
// Encoder B ------- ADC2 PB4  3|    |6  PB1 AIN1 OC0B --- Encoder SW
//                        GND  4|    |5  PB0 AIN0 OC0A --- OLED/RDA SDA
//                              +----+
//
// Compilation Settings:
// ---------------------
// Core:    ATtinyCore (https://github.com/SpenceKonde/ATTinyCore)
// Board:   ATtiny25/45/85 (No bootloader)
// Chip:    ATtiny 45 or ATtiny85 (depending on your chip)
// Clock:   1 MHz (internal)
// B.O.D:   disabled
//
// Leave the rest on default settings. Don't forget to "Burn bootloader"!
// No Arduino core functions or libraries are used. Use the makefile if
// you want to compile without Arduino IDE.
//
// Fuse settings: -U lfuse:w:0x62:m -U hfuse:w:0xd7:m -U efuse:w:0xff:m


// ===================================================================================
// Libraries, Definitions and Macros
// ===================================================================================

// Libraries
#include <avr/io.h>                 // for GPIO
#include <avr/eeprom.h>             // for storing user settings into EEPROM
#include <avr/pgmspace.h>           // for reading data from program memory
#include <avr/interrupt.h>          // for interrupt functions
#include <util/delay.h>             // for delays

// Pin assignments
#define PIN_SDA       PB0           // I2C Serial Data,  connect to OLED/RDA
#define PIN_SCL       PB2           // I2C Serial Clock, connect to OLED/RDA
#define PIN_ENC_SW    PB1           // pin connected to rotary encoder switch
#define PIN_ENC_A     PB3           // pin connected to rotary encoder A
#define PIN_ENC_B     PB4           // pin connected to rotary encoder B

// EEPROM identifier
#define EEPROM_IDENT  0x6CE7        // to identify if EEPROM was written by this program

// Text strings
const char HEADER[] PROGMEM = "Tiny FM Radio v1.0";

// Variables
uint16_t  channel;                  // 0 .. 1023
uint8_t   volume = 1;               // 0 .. 15

// Pin manipulation macros
#define pinInput(x)   DDRB  &= ~(1<<(x))        // set pin to INPUT
#define pinOutput(x)  DDRB  |=  (1<<(x))        // set pin to OUTPUT
#define pinLow(x)     PORTB &= ~(1<<(x))        // set pin to LOW
#define pinHigh(x)    PORTB |=  (1<<(x))        // set pin to HIGH
#define pinPullup(x)  PORTB |=  (1<<(x))        // enable PULLUP resistor
#define pinIntEn(x)   PCMSK |=  (1<<(x))        // enable pin change interrupt
#define pinIntDis(x)  PCMSK &= ~(1<<(x))        // disable pin change interrupt
#define pinRead(x)    (PINB &   (1<<(x)))       // READ pin

// ===================================================================================
// I2C Master Implementation
// ===================================================================================

// I2C macros
#define I2C_SDA_HIGH()  pinInput(PIN_SDA)       // release SDA   -> pulled HIGH by resistor
#define I2C_SDA_LOW()   pinOutput(PIN_SDA)      // SDA as output -> pulled LOW  by MCU
#define I2C_SCL_HIGH()  pinInput(PIN_SCL)       // release SCL   -> pulled HIGH by resistor
#define I2C_SCL_LOW()   pinOutput(PIN_SCL)      // SCL as output -> pulled LOW  by MCU
#define I2C_SDA_READ()  pinRead(PIN_SDA)        // read SDA line
#define I2C_CLOCKOUT()  I2C_SCL_HIGH();I2C_SCL_LOW()  // clock out

// I2C transmit one data byte to the slave, ignore ACK bit, no clock stretching allowed
void I2C_write(uint8_t data) {
  for(uint8_t i=8; i; i--, data<<=1) {          // transmit 8 bits, MSB first
    (data&0x80)?I2C_SDA_HIGH():I2C_SDA_LOW();   // SDA depending on bit
    I2C_CLOCKOUT();                             // clock out -> slave reads the bit
  }
  I2C_SDA_HIGH();                               // release SDA for ACK bit of slave
  I2C_CLOCKOUT();                               // 9th clock pulse is for the ignored ACK bit
}

// I2C start transmission
void I2C_start(uint8_t addr) {
  I2C_SDA_LOW();                                // start condition: SDA goes LOW first
  I2C_SCL_LOW();                                // start condition: SCL goes LOW second
  I2C_write(addr);                              // send slave address
}

// I2C restart transmission
void I2C_restart(uint8_t addr) {
  I2C_SDA_HIGH();                               // prepare SDA for HIGH to LOW transition
  I2C_SCL_HIGH();                               // restart condition: clock HIGH
  I2C_start(addr);                              // start again
}

// I2C stop transmission
void I2C_stop(void) {
  I2C_SDA_LOW();                                // prepare SDA for LOW to HIGH transition
  I2C_SCL_HIGH();                               // stop condition: SCL goes HIGH first
  I2C_SDA_HIGH();                               // stop condition: SDA goes HIGH second
}

// I2C receive one data byte from the slave (ack=0 for last byte, ack>0 if more bytes to follow)
uint8_t I2C_read(uint8_t ack) {
  uint8_t data = 0;                             // variable for the received byte
  I2C_SDA_HIGH();                               // release SDA -> will be toggled by slave
  for(uint8_t i=8; i; i--) {                    // receive 8 bits
    data <<= 1;                                 // bits shifted in right (MSB first)
    I2C_SCL_HIGH();                             // clock HIGH
    if(I2C_SDA_READ()) data |= 1;               // read bit
    I2C_SCL_LOW();                              // clock LOW -> slave prepares next bit
  }
  if(ack) I2C_SDA_LOW();                        // pull SDA LOW to acknowledge (ACK)
  I2C_CLOCKOUT();                               // clock out -> slave reads ACK bit
  return data;                                  // return the received byte
}

// ===================================================================================
// OLED Implementation
// ===================================================================================

// OLED definitions
#define OLED_ADDR       0x78                    // OLED write address
#define OLED_CMD_MODE   0x00                    // set command mode
#define OLED_DAT_MODE   0x40                    // set data mode
#define OLED_INIT_LEN   9                       // length of init command array

// OLED init settings
const uint8_t OLED_INIT_CMD[] PROGMEM = {
  0xC8, 0xA1,                                   // flip screen
  0xA8, 0x1F,                                   // set multiplex ratio
  0xDA, 0x02,                                   // set com pins hardware configuration
  0x8D, 0x14,                                   // set DC-DC enable
  0xAF                                          // display on
};

// Standard ASCII 5x8 font (adapted from Neven Boyanov and Stephen Denne)
const uint8_t OLED_FONT[] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00,
  0x14, 0x7F, 0x14, 0x7F, 0x14, 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x23, 0x13, 0x08, 0x64, 0x62,
  0x36, 0x49, 0x55, 0x22, 0x50, 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, 0x1C, 0x22, 0x41, 0x00,
  0x00, 0x41, 0x22, 0x1C, 0x00, 0x14, 0x08, 0x3E, 0x08, 0x14, 0x08, 0x08, 0x3E, 0x08, 0x08,
  0x00, 0x00, 0xA0, 0x60, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x60, 0x60, 0x00, 0x00,
  0x20, 0x10, 0x08, 0x04, 0x02, 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x42, 0x7F, 0x40, 0x00,
  0x42, 0x61, 0x51, 0x49, 0x46, 0x21, 0x41, 0x45, 0x4B, 0x31, 0x18, 0x14, 0x12, 0x7F, 0x10,
  0x27, 0x45, 0x45, 0x45, 0x39, 0x3C, 0x4A, 0x49, 0x49, 0x30, 0x01, 0x71, 0x09, 0x05, 0x03,
  0x36, 0x49, 0x49, 0x49, 0x36, 0x06, 0x49, 0x49, 0x29, 0x1E, 0x00, 0x36, 0x36, 0x00, 0x00,
  0x00, 0x56, 0x36, 0x00, 0x00, 0x08, 0x14, 0x22, 0x41, 0x00, 0x14, 0x14, 0x14, 0x14, 0x14,
  0x00, 0x41, 0x22, 0x14, 0x08, 0x02, 0x01, 0x51, 0x09, 0x06, 0x32, 0x49, 0x59, 0x51, 0x3E,
  0x7C, 0x12, 0x11, 0x12, 0x7C, 0x7F, 0x49, 0x49, 0x49, 0x36, 0x3E, 0x41, 0x41, 0x41, 0x22,
  0x7F, 0x41, 0x41, 0x22, 0x1C, 0x7F, 0x49, 0x49, 0x49, 0x41, 0x7F, 0x09, 0x09, 0x09, 0x01,
  0x3E, 0x41, 0x49, 0x49, 0x7A, 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x41, 0x7F, 0x41, 0x00,
  0x20, 0x40, 0x41, 0x3F, 0x01, 0x7F, 0x08, 0x14, 0x22, 0x41, 0x7F, 0x40, 0x40, 0x40, 0x40,
  0x7F, 0x02, 0x0C, 0x02, 0x7F, 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x3E, 0x41, 0x41, 0x41, 0x3E,
  0x7F, 0x09, 0x09, 0x09, 0x06, 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x7F, 0x09, 0x19, 0x29, 0x46,
  0x46, 0x49, 0x49, 0x49, 0x31, 0x01, 0x01, 0x7F, 0x01, 0x01, 0x3F, 0x40, 0x40, 0x40, 0x3F,
  0x1F, 0x20, 0x40, 0x20, 0x1F, 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x63, 0x14, 0x08, 0x14, 0x63,
  0x07, 0x08, 0x70, 0x08, 0x07, 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, 0x7F, 0x41, 0x41, 0x00,
  0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x41, 0x41, 0x7F, 0x00, 0x04, 0x02, 0x01, 0x02, 0x04,
  0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x01, 0x02, 0x04, 0x00, 0x20, 0x54, 0x54, 0x54, 0x78,
  0x7F, 0x48, 0x44, 0x44, 0x38, 0x38, 0x44, 0x44, 0x44, 0x20, 0x38, 0x44, 0x44, 0x48, 0x7F,
  0x38, 0x54, 0x54, 0x54, 0x18, 0x08, 0x7E, 0x09, 0x01, 0x02, 0x18, 0xA4, 0xA4, 0xA4, 0x7C,
  0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, 0x44, 0x7D, 0x40, 0x00, 0x40, 0x80, 0x84, 0x7D, 0x00,
  0x7F, 0x10, 0x28, 0x44, 0x00, 0x00, 0x41, 0x7F, 0x40, 0x00, 0x7C, 0x04, 0x18, 0x04, 0x78,
  0x7C, 0x08, 0x04, 0x04, 0x78, 0x38, 0x44, 0x44, 0x44, 0x38, 0xFC, 0x24, 0x24, 0x24, 0x18,
  0x18, 0x24, 0x24, 0x18, 0xFC, 0x7C, 0x08, 0x04, 0x04, 0x08, 0x48, 0x54, 0x54, 0x54, 0x20,
  0x04, 0x3F, 0x44, 0x40, 0x20, 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x1C, 0x20, 0x40, 0x20, 0x1C,
  0x3C, 0x40, 0x30, 0x40, 0x3C, 0x44, 0x28, 0x10, 0x28, 0x44, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C,
  0x44, 0x64, 0x54, 0x4C, 0x44, 0x08, 0x36, 0x41, 0x41, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00,
  0x00, 0x41, 0x41, 0x36, 0x08, 0x08, 0x04, 0x08, 0x10, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

// OLED variables
uint8_t OLED_x, OLED_y;                         // current cursor position
const uint16_t DIVIDER[] PROGMEM = {10000, 1000, 100, 10, 1}; // BCD conversion array

// OLED init function
void OLED_init(void) {
  I2C_start(OLED_ADDR);                         // start transmission to OLED
  I2C_write(OLED_CMD_MODE);                     // set command mode
  for (uint8_t i = 0; i < OLED_INIT_LEN; i++)
    I2C_write(pgm_read_byte(&OLED_INIT_CMD[i]));// send the command bytes
  I2C_stop();                                   // stop transmission
}

// OLED set the cursor
void OLED_setCursor(uint8_t xpos, uint8_t ypos) {
  I2C_start(OLED_ADDR);                         // start transmission to OLED
  I2C_write(OLED_CMD_MODE);                     // set command mode
  I2C_write(xpos & 0x0F);                       // set low nibble of start column
  I2C_write(0x10 | (xpos >> 4));                // set high nibble of start column
  I2C_write(0xB0 | (ypos & 0x07));              // set start page
  I2C_stop();                                   // stop transmission
  OLED_x = xpos; OLED_y = ypos;                 // set the cursor variables
}

// OLED clear rest of the current line
void OLED_clearLine(void) {
  I2C_start(OLED_ADDR);                         // start transmission to OLED
  I2C_write(OLED_DAT_MODE);                     // set data mode
  while(OLED_x++ < 128) I2C_write(0);           // clear rest of the line
  I2C_stop();                                   // stop transmission
  if(++OLED_y > 3) OLED_y = 0;                  // calculate next line
  OLED_setCursor(0, OLED_y);                    // set cursor to start of next line
}

// OLED clear screen
void OLED_clearScreen(void) {
  OLED_setCursor(0, 0);                         // set cursor to home position
  for(uint8_t i=4; i; i--) OLED_clearLine();    // clear all 4 lines
}

// OLED plot a single character
void OLED_plotChar(char c) {
  uint16_t ptr = c - 32;                        // character pointer
  ptr += ptr << 2;                              // -> ptr = (ch - 32) * 5;
  I2C_write(0x00);                              // write space between characters
  for (uint8_t i=5 ; i; i--) I2C_write(pgm_read_byte(&OLED_FONT[ptr++]));
  OLED_x += 6;                                  // update cursor
  if (OLED_x > 122) {                           // line end ?
    I2C_stop();                                 // stop data transmission
    OLED_setCursor(0,++OLED_y);                 // set next line start
    I2C_start(OLED_ADDR);                       // start transmission to OLED
    I2C_write(OLED_DAT_MODE);                   // set data mode
  }
}

// OLED print a string
void OLED_printStr(uint8_t* str) {
  I2C_start(OLED_ADDR);                         // start transmission to OLED
  I2C_write(OLED_DAT_MODE);                     // set data mode
  while(*str) OLED_plotChar(*str++);            // plot each character
  I2C_stop();                                   // stop transmission
}

// OLED print a string from program memory
void OLED_print(const char* p) {
  I2C_start(OLED_ADDR);                         // start transmission to OLED
  I2C_write(OLED_DAT_MODE);                     // set data mode
  char ch = pgm_read_byte(p);                   // read first character from program memory
  while (ch) {                                  // repeat until string terminator
    OLED_plotChar(ch);                          // print character on OLED
    ch = pgm_read_byte(++p);                    // read next character
  }
  I2C_stop();                                   // stop transmission
}

// OLED print a string from program memory with new line
void OLED_println(const char* p) {
  OLED_print(p);
  OLED_clearLine();
}

// OLED print 8-bit value as 2-digit decimal (BCD conversion by substraction method)
void OLED_printVal(uint8_t value) {
  if(value > 99) value = 99;                    // limit 2-digit value
  I2C_start(OLED_ADDR);                         // start transmission to OLED
  I2C_write(OLED_DAT_MODE);                     // set data mode
  uint8_t digitval = 0;                         // start with digit value 0
  while(value >= 10) {                          // if current divider fits into the value
    digitval++;                                 // increase digit value
    value -= 10;                                // decrease value by divider
  }
  if(digitval) OLED_plotChar(digitval + '0');   // print first digit
  else OLED_plotChar(' ');                      // leading space if zero
  OLED_plotChar(value + '0');                   // print second digit
  I2C_stop();                                   // stop transmission
}

// OLED print frequency (BCD conversion by substraction method)
void OLED_printFrequency(uint16_t value) {
  uint8_t leadflag = 0;                         // flag for leading spaces
  I2C_start(OLED_ADDR);                         // start transmission to OLED
  I2C_write(OLED_DAT_MODE);                     // set data mode
  for(uint8_t digit = 0; digit < 5; digit++) {  // 5 digits
    uint8_t digitval = 0;                       // start with digit value 0
    uint16_t divider = pgm_read_word(&DIVIDER[digit]); // current divider
    while(value >= divider) {                   // if current divider fits into the value
      leadflag = 1;                             // end of leading spaces
      digitval++;                               // increase digit value
      value -= divider;                         // decrease value by divider
    }
    if(leadflag || (digit > 1)) OLED_plotChar(digitval + '0'); // print the digit
    else OLED_plotChar(' ');                    // or print leading space
    if(digit == 2) OLED_plotChar('.');          // print decimal after 3rd digit
  }
  I2C_stop();                                   // stop transmission
}

// ===================================================================================
// RDA5807 Implementation
// ===================================================================================

// RDA definitions
#define RDA_ADDR_SEQ    0x20                    // RDA I2C write address for sequential access
#define RDA_ADDR_INDEX  0x22                    // RDA I2C write address for indexed access
#define RDA_VOL         1                       // start volume

// RDA register definitions
enum{ RDA_REG_2, RDA_REG_3, RDA_REG_4, RDA_REG_5, RDA_REG_6, RDA_REG_7 };
enum{ RDA_REG_A, RDA_REG_B, RDA_REG_C, RDA_REG_D, RDA_REG_E, RDA_REG_F };
uint16_t RDA_read_regs[6];                      // RDA registers for reading
uint16_t RDA_write_regs[6] = {                  // RDA registers for writing:
  0b1101001000001101,                           // RDA register 0x02 preset
  0b0001010111000000,                           // RDA register 0x03 preset
  0b0000101000000000,                           // RDA register 0x04 preset
  0b1000100010000000,                           // RDA register 0x05 preset
  0b0000000000000000,                           // RDA register 0x06 preset
  0b0000000000000000                            // RDA register 0x07 preset
};

// RDA state macros
#define RDA_hasRdsData        ( RDA_read_regs[RDA_REG_A] & 0x8000 )
#define RDA_isTuning          (~RDA_read_regs[RDA_REG_A] & 0x4000 )
#define RDA_tuningError       ( RDA_read_regs[RDA_REG_A] & 0x2000 )
#define RDA_hasRdsBlockE      ( RDA_read_regs[RDA_REG_A] & 0x0800 )
#define RDA_isStereo          ( RDA_read_regs[RDA_REG_A] & 0x0400 )
#define RDA_channel           ( RDA_read_regs[RDA_REG_A] & 0x03FF )
#define RDA_isTunedToChannel  ( RDA_read_regs[RDA_REG_B] & 0x0100 )
#define RDA_rdsBlockE         ( RDA_read_regs[RDA_REG_B] & 0x0010 )
#define RDA_rdsBlockErrors    ( RDA_read_regs[RDA_REG_B] & 0x000F )
#define RDA_signalStrength    ((RDA_read_regs[RDA_REG_B] & 0xFE00 ) >> 9 )

// RDA variables
uint8_t RDA_stationName[9];                     // string for the station name
uint8_t RDA_rdsStationName[8];                  // just for internal use

// RDA write specified register
void RDA_writeReg(uint8_t reg) {
  I2C_start(RDA_ADDR_INDEX);                    // start I2C for index write to RDA
  I2C_write(0x02 + reg);                        // set the register to write
  I2C_write(RDA_write_regs[reg] >> 8);          // send high byte
  I2C_write(RDA_write_regs[reg]);               // send low byte
  I2C_stop();                                   // stop I2C
}

// RDA write all registers
void RDA_writeAllRegs(void) {
  I2C_start(RDA_ADDR_SEQ);                      // start I2C for sequential write to RDA
  for(uint8_t i=0; i<6; i++) {                  // write to 6 registers
    I2C_write(RDA_write_regs[i] >> 8);          // send high byte
    I2C_write(RDA_write_regs[i]);               // send low byte
  }
  I2C_stop();                                   // stop I2C
}

// RDA read all registers
void RDA_readAllRegs(void) {
  I2C_start(RDA_ADDR_SEQ | 1);                  // start I2C for sequential read from RDA
  for(uint8_t i=0; i<6; i++)                    // read 6 registers
    RDA_read_regs[i] = (uint16_t)(I2C_read(1) << 8) | I2C_read(5-i);
  I2C_stop();                                   // stop I2C
}

// RDA clear station
void RDA_resetStation(void) {
  for(uint8_t i=0; i<8; i++) RDA_stationName[i] = ' ';
}

// RDA initialize tuner
void RDA_init(void) {
  RDA_resetStation();                           // reset station available
  RDA_stationName[8] = 0;                       // set string terminator
  RDA_write_regs[RDA_REG_2] |=  0x0002;         // set soft reset
  RDA_write_regs[RDA_REG_5] |=  RDA_VOL;        // set start volume
  RDA_writeAllRegs();                           // write all registers
  RDA_write_regs[RDA_REG_2] &= ~0x0002;         // clear soft reset
  RDA_writeReg(RDA_REG_2);                      // write to register 0x02
}

// RDA set volume
void RDA_setVolume(uint8_t vol) {
  RDA_write_regs[RDA_REG_5] &= ~0x000F;         // clear volume bits
  RDA_write_regs[RDA_REG_5] |=  vol;            // set volume
  RDA_writeReg(RDA_REG_5);                      // write to register 0x05
}

// RDA tune to a specified channel
void RDA_setChannel(uint16_t chan) {
  RDA_resetStation();
  RDA_write_regs[RDA_REG_3] &= ~0xFFC0;         // clear channel
  RDA_write_regs[RDA_REG_3] |= (chan << 6) | 0x0010;  // set channel and tune enable
  RDA_writeReg(RDA_REG_3);                      // write register
}

// RDA seek next channel
void RDA_seekUp(void) {
  RDA_resetStation();
  RDA_write_regs[RDA_REG_2] |=  0x0100;         // set seek enable bit
  RDA_writeReg(RDA_REG_2);                      // write to register 0x02
}

// RDA update status and handle RDS
void RDA_updateStatus(void) {
  RDA_readAllRegs();

  // When tuned disable tuning and stop seeking
  if (!RDA_isTuning) {
    RDA_write_regs[RDA_REG_3] &= ~0x0010;       // clear tune enable flag
    RDA_writeReg(RDA_REG_3);
    RDA_write_regs[RDA_REG_2] &= ~0x0100;       // clear seek enable flag
    RDA_writeReg(RDA_REG_2);
  }

  // Check for RDS data
  if(RDA_hasRdsData) {                          // RDS ready?
    // Toggle RDS flag to request new data
    RDA_write_regs[RDA_REG_2] &= ~0x0008;       // clear RDS flag
    RDA_writeReg(RDA_REG_2);                    // write to register 0x02
    RDA_write_regs[RDA_REG_2] |=  0x0008;       // set RDS flag
    RDA_writeReg(RDA_REG_2);                    // write to register 0x02

    // Decode RDS message (station name)
    if(!RDA_rdsBlockE) {                                         // REG_B..F carrying blocks A-D?
      if( (RDA_read_regs[RDA_REG_D] & 0xF800) == 0x0000) {       // is it station name?
        uint8_t offset = (RDA_read_regs[RDA_REG_D] & 0x03) << 1; // get character position
        uint8_t c1 = RDA_read_regs[RDA_REG_F] >> 8;              // get character 1
        uint8_t c2 = RDA_read_regs[RDA_REG_F];                   // get character 2

        // Copy station name characters only if received twice in a row...
        if(RDA_rdsStationName[offset] == c1)                     // 1st char received twice?
             RDA_stationName[offset] = c1;                       // copy to station name
        else RDA_rdsStationName[offset] = c1;                    // save for next test
        if(RDA_rdsStationName[offset + 1] == c2)                 // 2nd char received twice?
             RDA_stationName[offset + 1] = c2;                   // copy to station name
        else RDA_rdsStationName[offset + 1] = c2;                // save for next test
      }
    }
  }
}

// Calculate frequency in 10kHz
uint16_t RDA_getFrequency(void) {
  return(8700 + (RDA_channel << 3) + (RDA_channel << 1));
}

// Waits until tuning completed
void RDA_waitTuning(void) {
  do {
    _delay_ms(100);
    RDA_updateStatus();
  } while(RDA_isTuning);
}

// ===================================================================================
// ADC Implementation for Supply Voltage Measurement
// ===================================================================================

// Init ADC
void ADC_init(void) {
  ADCSRA = (1<<ADPS1) | (1<<ADPS0);             // set ADC clock prescaler to 8
  ADMUX  = (1<<MUX3)  | (1<<MUX2);              // set 1.1V Vref against Vcc
}

// Read Vcc voltage in dV by measuring 1.1V reference against Vcc
uint8_t ADC_readVcc(void) {
  PRR    &= ~(1<<PRADC);                        // power on ADC
  ADCSRA |=  (1<<ADEN);                         // enable ADC
  _delay_ms(2);                                 // wait for vref to settle
  ADCSRA |= (1<<ADSC);                          // start sampling
  while(ADCSRA & (1<<ADSC));                    // wait for sampling to complete
  uint16_t vcc = ADC;                           // read sampling result
  ADCSRA &= ~(1<<ADEN);                         // disable ADC
  PRR    |=  (1<<PRADC);                        // power off ADC
  vcc = 11253 / vcc;                            // calculate Vcc in dV; 11253 = 1.1*1023*10
  return vcc;                                   // divide by 8 and return result
}

// ===================================================================================
// Rotary Encoder Implementation using Pin Change Interrupt
// ===================================================================================

// Global variables
volatile uint8_t  ENC_a0, ENC_b0, ENC_ab0;
volatile int16_t  ENC_count, ENC_countMin, ENC_countMax, ENC_countStep;

// Init rotary encoder
void ENC_init(void) {
  pinPullup(PIN_ENC_A);                         // enable pullup on encoder pins ...
  pinPullup(PIN_ENC_B);
  pinPullup(PIN_ENC_SW);
  pinIntEn(PIN_ENC_A);                          // enable pin change interrupt on ENC A
  ENC_a0  = !pinRead(PIN_ENC_A);                // set initial values ...
  ENC_b0  = !pinRead(PIN_ENC_B);
  ENC_ab0 = (ENC_a0 == ENC_b0);
  GIMSK  |= (1<<PCIE);                          // enable pin change interrupts
}

// Set parameters for rotary encoder
void ENC_set(int16_t rmin, int16_t rmax, int16_t rstep, int16_t rvalue) {
  ENC_countMin  = rmin << 1;                    // min value
  ENC_countMax  = rmax << 1;                    // max value
  ENC_countStep = rstep;                        // count steps (negative if CCW)
  ENC_count     = rvalue << 1;                  // actual count value
}

// reads current rotary encoder value
int ENC_get(void) {
  return(ENC_count >> 1);
}

// Pin change interrupt service routine for rotary encoder
ISR(PCINT0_vect) {
  uint8_t a = !pinRead(PIN_ENC_A);
  uint8_t b = !pinRead(PIN_ENC_B);
  if(a != ENC_a0) {                             // A changed?
    ENC_a0 = a;
    if(b != ENC_b0) {                           // B changed?
      ENC_b0 = b;
      ENC_count += (a == b) ? -ENC_countStep : ENC_countStep;
      if((a == b) != ENC_ab0) ENC_count += (a == b) ? -ENC_countStep : ENC_countStep;
      if(ENC_count < ENC_countMin) ENC_count = ENC_countMin;
      if(ENC_count > ENC_countMax) ENC_count = ENC_countMax;
      ENC_ab0 = (a == b);
    }
  }
}

// ===================================================================================
// EEPROM Functions
// ===================================================================================

// updates frequency and volume stored in EEPROM
void EEPROM_update() {
  eeprom_update_word((uint16_t*)0, EEPROM_IDENT);
  eeprom_update_word((uint16_t*)2, RDA_channel);
  eeprom_update_byte((uint8_t*)4, volume);
}

// reads frequency and volume stored in EEPROM
uint8_t EEPROM_get() {
  uint16_t identifier = eeprom_read_word((const uint16_t*)0);
  if (identifier == EEPROM_IDENT) {
    channel = eeprom_read_word((const uint16_t*)2);
    volume  = eeprom_read_byte((const uint8_t*)4);
    return 1;
  }
  return 0;
}

// ===================================================================================
// Main Function
// ===================================================================================

int main(void) {
  // Setup  
  ADC_init();                                   // setup ADC
  ENC_init();                                   // setup rotary encoder
  sei();                                        // enable global interrupts

  // Disable unused peripherals to save power
  ACSR =  (1<<ACD);                             // disable analog comperator
  PRR  =  (1<<PRADC)                            // shut down ADC
       |  (1<<PRUSI)                            // shut down USI
       |  (1<<PRTIM0)                           // shut down timer0
       |  (1<<PRTIM1);                          // shut down timer1

  // Prepare and start OLED
  OLED_init();
  OLED_clearScreen();
  OLED_println(HEADER);
  OLED_print(PSTR("Starting ..."));

  // Start the tuner
  RDA_init();
  if(EEPROM_get()) RDA_setChannel(channel);
  else RDA_seekUp();
  ENC_set(0, 15, 1, volume);
  RDA_setVolume(volume);
  RDA_waitTuning();

  // Loop
  while(1) {
    // Update information on OLED
    RDA_updateStatus();
    uint8_t vcc = ADC_readVcc();
    OLED_setCursor(0, 1);
    OLED_print(PSTR("Station:  "));
    OLED_printStr(RDA_stationName);
    OLED_clearLine();
    OLED_print(PSTR("Vol: "));
    OLED_printVal(volume);
    OLED_print(PSTR("   Frq: "));
    OLED_printFrequency(RDA_getFrequency());
    OLED_print(PSTR("Sig: "));
    OLED_printVal(RDA_signalStrength);
    OLED_print(PSTR("   Bat: "));
    if(vcc < 32) OLED_println(PSTR("weak"));
    else OLED_println(PSTR("OK"));

    // Check rotary encoder switch for channel seek
    if (!pinRead(PIN_ENC_SW)) {                 // seek up if encoder button is pressed
      OLED_setCursor(0, 1);
      OLED_println(PSTR("Tuning ..."));
      OLED_clearLine(); OLED_clearLine();
      RDA_seekUp();
      RDA_waitTuning();
      while (!pinRead(PIN_ENC_SW));
      EEPROM_update();
    }

    // Check rotary encoder for volume change
    if (volume != ENC_get()) {                  // change volume if encoder was turned
      volume = ENC_get();
      RDA_setVolume(volume);
      EEPROM_update();
    }
  }
}



使用特权

评论回复
沙发
598330983|  楼主 | 2024-9-28 13:40 | 只看该作者
低成本RDA5807MP是一款单芯片广播 FM 立体声收音机调谐器,具有完全集成的合成器、IF 选择性、RDS/RBDS 和 MPX 解码器。调谐器采用 CMOS 工艺,支持多接口,需要最少的外部元件。所有这些都使其非常适合便携式设备。

FM 调谐器 IC RDA5807MP 由 ATtiny 通过 I²C 控制。它有 6 个可写的 16 位寄存器(地址 0x02 - 0x07)和 6 个可读的 16 位寄存器(地址 0x0A - 0x0F)。RDA5807 有两种写入访问方法,一种是顺序方法,其中寄存器总是从地址 0x02 开始写入,另一种是索引方法,其中首先传输寄存器地址,然后传输内容。两种方法都由不同的 I²C 地址决定。要传输 16 位寄存器内容,首先发送高字节。RDA5807 是通过设置或清除相应 registers 中的某些 bits 来控制的。各个寄存器的含义的详细信息可以在数据表中找到。当前 register 内容保存在 RDA_regs 数组中。

使用特权

评论回复
板凳
734774645| | 2024-9-28 17:11 | 只看该作者
IO模拟的,比较通用,学习一下。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

245

主题

5376

帖子

22

粉丝