- // ===================================================================================
- // Project: TinyPocketRadio - FM Tuner based on ATtiny13A
- // Version: v1.0
- // Year: 2020
- // Author: Stefan Wagner
- // Github: https://github.com/wagiminator
- // EasyEDA: https://easyeda.com/wagiminator
- // License: http://creativecommons.org/licenses/by-sa/3.0/
- // ===================================================================================
- //
- // Description:
- // ------------
- // This code implements a simple Pocket Radio with three control buttons
- // (Vol+/- and Channel Seek). The FM tuner IC RDA5807MP is controled via
- // I²C by the ATtiny.
- //
- // The I²C protocol implementation is based on a crude bitbanging method.
- // It was specifically designed for the limited resources of ATtiny10 and
- // ATtiny13, but should work with some other AVRs as well. Due to the low
- // clock frequency of the CPU, it does not require any delays for correct
- // timing. In order to save resources, only the basic functionalities which
- // are needed for this application are implemented. For a detailed
- // information on the working principle of the I²C implementation visit
- // https://github.com/wagiminator/attiny13-tinyoleddemo
- //
- // The code utilizes the sleep mode power down function to save power.
- // The CPU wakes up on every button press by pin change interrupt, transmits
- // the appropriate command via I²C to the RDA5807 and falls asleep again.
- //
- // Wiring:
- // -------
- // +-\/-+
- // --- RST ADC0 PB5 1|° |8 Vcc
- // I2C SDA ------- ADC3 PB3 2| |7 PB2 ADC1 -------- VOL+ BUTTON
- // I2C SCL ------- ADC2 PB4 3| |6 PB1 AIN1 OC0B --- VOL- BUTTON
- // GND 4| |5 PB0 AIN0 OC0A --- SEEK BUTTON
- // +----+
- //
- // Compilation Settings:
- // ---------------------
- // Controller: ATtiny13A
- // Core: MicroCore (https://github.com/MCUdude/MicroCore)
- // Clockspeed: 1.2 MHz internal
- // BOD: BOD disabled
- // Timing: Micros 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:0x2a:m -U hfuse:w:0xff:m
- // ===================================================================================
- // Libraries and Definitions
- // ===================================================================================
- // Libraries
- #include <avr/io.h> // for GPIO
- #include <avr/sleep.h> // for sleep functions
- #include <avr/interrupt.h> // for interrupts
- #include <util/delay.h> // for delays
- // Pin definitions
- #define BT_SEEK PB0 // CH+ button
- #define BT_VOLM PB1 // VOL- button
- #define BT_VOLP PB2 // VOL+ button
- #define I2C_SDA PB3 // I2C serial data pin
- #define I2C_SCL PB4 // I2C serial clock pin
- #define BT_MASK (1<<BT_SEEK)|(1<<BT_VOLM)|(1<<BT_VOLP)
- // ===================================================================================
- // I2C Implementation
- // ===================================================================================
- // I2C macros
- #define I2C_SDA_HIGH() DDRB &= ~(1<<I2C_SDA) // release SDA -> pulled HIGH by resistor
- #define I2C_SDA_LOW() DDRB |= (1<<I2C_SDA) // SDA as output -> pulled LOW by MCU
- #define I2C_SCL_HIGH() DDRB &= ~(1<<I2C_SCL) // release SCL -> pulled HIGH by resistor
- #define I2C_SCL_LOW() DDRB |= (1<<I2C_SCL) // SCL as output -> pulled LOW by MCU
- // I2C init function
- void I2C_init(void) {
- DDRB &= ~((1<<I2C_SDA)|(1<<I2C_SCL)); // pins as input (HIGH-Z) -> lines released
- PORTB &= ~((1<<I2C_SDA)|(1<<I2C_SCL)); // should be LOW when as ouput
- }
- // 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
- I2C_SDA_LOW(); // SDA LOW for now (saves some flash this way)
- if(data & 0x80) I2C_SDA_HIGH(); // SDA HIGH if bit is 1
- I2C_SCL_HIGH(); // clock HIGH -> slave reads the bit
- I2C_SCL_LOW(); // clock LOW again
- }
- I2C_SDA_HIGH(); // release SDA for ACK bit of slave
- I2C_SCL_HIGH(); // 9th clock pulse is for the ACK bit
- I2C_SCL_LOW(); // but ACK bit is ignored
- }
- // 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 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
- }
- // ===================================================================================
- // 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 R2_SEEK_ENABLE 0x0100 // RDA seek enable bit
- #define R2_SOFT_RESET 0x0002 // RDA soft reset bit
- #define R5_VOLUME 0x000F // RDA volume mask
- #define RDA_VOL 5 // start volume
- // RDA write registers
- uint16_t RDA_regs[6] = {
- 0b1101001000000101, // RDA register 0x02
- 0b0001010111000000, // RDA register 0x03
- 0b0000101000000000, // RDA register 0x04
- 0b1000100010000000, // RDA register 0x05
- 0b0000000000000000, // RDA register 0x06
- 0b0000000000000000 // RDA register 0x07
- };
- // 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_regs[reg] >> 8); // send high byte
- I2C_write(RDA_regs[reg] & 0xFF); // 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_regs[i] >> 8); // send high byte
- I2C_write(RDA_regs[i] & 0xFF); // send low byte
- }
- I2C_stop(); // stop I2C
- }
- // RDA initialize tuner
- void RDA_init(void) {
- I2C_init(); // init I2C
- RDA_regs[0] |= R2_SOFT_RESET; // set soft reset
- RDA_regs[3] |= RDA_VOL; // set start volume
- RDA_writeAllRegs(); // write all registers
- RDA_regs[0] &= 0xFFFD; // clear soft reset
- RDA_writeReg(0); // write to register 0x02
- }
- // RDA set volume
- void RDA_setVolume(uint8_t vol) {
- RDA_regs[3] &= 0xFFF0; // clear volume bits
- RDA_regs[3] |= vol; // set volume
- RDA_writeReg(3); // write to register 0x05
- }
- // RDA seek next channel
- void RDA_seekUp(void) {
- RDA_regs[0] |= R2_SEEK_ENABLE; // set seek enable bit
- RDA_writeReg(0); // write to register 0x02
- }
- // ===================================================================================
- // Main Function
- // ===================================================================================
- int main(void) {
- // Setup pins
- PORTB |= (BT_MASK); // pull-ups for button pins
-
- // Setup pin change interrupt
- GIMSK = (1<<PCIE); // turn on pin change interrupts
- PCMSK = (BT_MASK); // turn on interrupt on button pins
- sei(); // enable global interrupts
- // Disable unused peripherals and set sleep mode to save power
- ADCSRA = 0; // disable ADC
- ACSR = (1<<ACD); // disable analog comperator
- PRR = (1<<PRTIM0) | (1<<PRADC); // shut down ADC and timer0
- set_sleep_mode(SLEEP_MODE_PWR_DOWN); // set sleep mode to power down
- // Setup radio
- uint8_t volume = RDA_VOL; // set start volume
- RDA_init(); // initialize RDA
- RDA_seekUp(); // seek a channel
- // Loop
- while(1) {
- sleep_mode(); // sleep until button is pressed
- _delay_ms(1); // debounce
- uint8_t buttons = ~PINB & (BT_MASK); // read button pins
- switch (buttons) { // send corresponding command to RDA
- case (1<<BT_SEEK): RDA_seekUp(); break;
- case (1<<BT_VOLM): if(volume) RDA_setVolume(--volume); break;
- case (1<<BT_VOLP): if(volume < 15) RDA_setVolume(++volume); break;
- default: break;
- }
- }
- }
- // Pin change interrupt service routine
- EMPTY_INTERRUPT(PCINT0_vect); // nothing to be done here, just wake up from sleep
|