/** * ECM Standalone FM Transmitter * * A lot of code and circuit was guided by http://www.km5z.us/FM-Stereo-Broadcaster.php * */ #include #include #define topFM 107900000 // top of the FM range #define botFM 87500000 // bottom of the FM range #define incrFM 200000 // FM channel increment #define encoderPinA 3 #define encoderPinB 2 #define encoderSwitch 4 int encoderPos = 0; // initial encoder position int lastEncoderSwitchState = LOW; // initial button state int encoderSwitchState; // current button state long lastDebounceTime = 0; // temporary debounce time storage long debounceDelay = 50; // debounce time byte number[4]; // number to be displayed on the 7 segment display byte ls247pins[] = { 12, 10, 8, 6}; byte anodepins[] = { 13, 9, 11, 7}; byte dp = 5; // decimal point // for parsing incoming serial messages int serialArray[2]; int serialCount = 0; long initialFrequency = 99300000; long frequency = initialFrequency; // the default initial frequency in Hz long newFrequency = 0; boolean gOnAir = false; // initially NOT on air ////////// // MAIN // ////////// void setup() { Serial.begin(9600); initRadio(); } void loop() { readSwitch(); // check if the switch on the encoder has been pressed setDisplayFrequency(frequency); // prepare current frequency for 7 segment display for (int i = 0; i < 4; i++) { // display the frequency on the 7 segment display led7segWriteDigit(i, number[i]); } checkSerial(); // check for serial info from Max to set a frequency that way } /////////////////// // OUR FUNCTIONS // /////////////////// void initRadio() { // attempt to read the last saved frequency from EEPROM newFrequency = loadFrequency(); // test if outside the FM Range if (newFrequency < botFM || newFrequency > topFM) frequency = initialFrequency; // haven't saved before, use the default. else frequency = newFrequency; // we have a valid frequency! // set pin types pinMode(encoderPinA, INPUT_PULLUP); pinMode(encoderPinB, INPUT_PULLUP); pinMode(encoderSwitch, INPUT_PULLUP); for (int i = 0; i < 4; i++) { pinMode(ls247pins[i], OUTPUT); pinMode(anodepins[i], OUTPUT); } pinMode(dp, OUTPUT); digitalWrite(dp, HIGH); attachInterrupt(0, doEncoder, CHANGE); // encoder pin on interrupt 0 - pin 2 Wire.begin(); // join i2c bus as master transmitter_standby(frequency); delay(2000); transmitter_setup(frequency); } void checkSerial() { if (Serial.available() > 0) { int inByte = Serial.read(); // read incoming message if (inByte == 254) transmitter_standby(frequency); // set transmitter into standby mode if (inByte == 255) serialCount = 0; // change the transmission frequency // set incoming byte into a temporary array and move through it // these values will be reassigned else { serialArray[serialCount] = inByte; serialCount++; if (serialCount == 2) { transmitter_standby(frequency); // parse incoming numbers from Max and set that as the new frequency int left = serialArray[0] * 100; long tempFrequency = left + (long) serialArray[1]; frequency = tempFrequency * 10000; set_freq(frequency); saveFrequency(frequency); // save the frequency to EEPROM // write the incoming messages for debugging in Max // Serial.write(serialArray[0]); // Serial.write(serialArray[1]); // reset the serial count to receive the next message serialCount = 0; } } } } void readSwitch() { int reading = digitalRead(encoderSwitch); // read encoder switch if (reading != lastEncoderSwitchState) { lastDebounceTime = millis(); // start debouncing } if ((millis() - lastDebounceTime) > debounceDelay) { encoderSwitchState = reading; // we're past the debounce period, so read the button press } // when the button is pressed if (encoderSwitchState == LOW) { // go into standby if (gOnAir) { transmitter_standby(frequency); encoderSwitchState = HIGH; } // then set the new frequency else { set_freq(frequency); saveFrequency(frequency); // save the Frequency to EEPROM Memory delay(1000); encoderSwitchState = HIGH; } } lastEncoderSwitchState = reading; } // display frequency on 7 segment display void setDisplayFrequency(long input) { // int freq; int freq = (int) (input / 100000); number[0] = freq / 1000; // should be 0 or 1 if (number[0] == 0) number[0] = 15; // display blank LCD character freq = freq % 1000; number[1] = freq / 100; freq = freq % 100; number[2] = freq / 10; freq = freq % 10; number[3] = freq; } void led7segWriteDigit(int digit, int value) { // turn off the previous led if (digit == 0) digitalWrite(anodepins[3], HIGH); else digitalWrite(anodepins[digit-1], HIGH); // set decimal point if (digit == 3) (digitalWrite(dp, LOW)); else (digitalWrite(dp, HIGH)); // write incoming integer value to 7 segment display for (int i = 0; i < 4; i++) { digitalWrite(ls247pins[i], (bitRead(value, i))); } // turn on the appropriate digit digitalWrite(anodepins[digit], LOW); delay(5); } /////////////// // INTERRUPT // /////////////// void doEncoder() { // If pinA and pinB are both high or both low, the encoder is spinning forward. // If they're different, it's going backward. if (digitalRead(encoderPinA) == digitalRead(encoderPinB)) { frequency -= incrFM; frequency = constrain(frequency, botFM, topFM); // Keeps us in range } else { frequency += incrFM; frequency = constrain(frequency, botFM, topFM); // Keeps us in range } } ///////////////////////////// // RADIO CONTROL FUNCTIONS // ///////////////////////////// void transmitter_setup(long initFrequency) { i2c_send(0x0E, B00000101); // Software reset i2c_send(0x01, B10110100); // Register 1: forced subcarrier, pilot tone on i2c_send(0x02, B00000011); // Register 2: Unlock detect off, 2mW Tx Power set_freq(initFrequency); // set the frequency // i2c_send(0x00, B10100001); // Register 0: 200mV audio input, 75us pre-emphasis on, crystal off, power on i2c_send(0x00, B00100001); // Register 0: 100mV audio input, 75us pre-emphasis on, crystal off, power on i2c_send(0x0E, B00000101); // Software reset i2c_send(0x06, B00011110); // Register 6: charge pumps at 320uA and 80 uA } void transmitter_standby(long aFrequency) { // i2c_send(0x00, B10100000); // Register 0: 200mV audio input, 75us pre-emphasis on, crystal off, power OFF i2c_send(0x00, B00100000); // Register 0: 100mV audio input, 75us pre-emphasis on, crystal off, power OFF delay(100); gOnAir = false; } void set_freq(long aFrequency) { int new_frequency; // new range checking; implement the (experimentally determined) VFO bands: if (aFrequency < 88500000) i2c_send(0x08, B00011011); // band 3 else if (aFrequency < 97900000) i2c_send(0x08, B00011010); // band 2 else if (aFrequency < 103000000) i2c_send(0x08, B00011001); // band 1 else i2c_send(0x08, B00011000); // band 0 new_frequency = (aFrequency + 304000) / 8192; byte reg3 = new_frequency & 255; // extract low byte of frequency register byte reg4 = new_frequency >> 8; // extract high byte of frequency register i2c_send(0x03, reg3); // send low byte i2c_send(0x04, reg4); // send high byte i2c_send(0x0E, B00000101); // software reset // i2c_send(0x00, B10100001); // Register 0: 200mV audio input, 75us pre-emphasis on, crystal off, power ON i2c_send(0x00, B00100001); // Register 0: 100mV audio input, 75us pre-emphasis on, crystal off, power ON gOnAir = true; } void i2c_send(byte reg, byte data) { Wire.beginTransmission(B1100111); // transmit to device 1100111 Wire.write(reg); // sends register address Wire.write(data); // sends register data Wire.endTransmission(); // stop transmitting delay(5); // allow register to set } ////////////////////// // EEPROM FUNCTIONS // ////////////////////// void saveFrequency(long aFrequency) { long memFrequency = 0; // for use in read/write to EEPROM memFrequency = aFrequency / 10000; EEPROM.write(0, memFrequency / 256); // right-most byte EEPROM.write(1, memFrequency - (memFrequency / 256) * 256); // next to right-most byte } long loadFrequency() { long memFrequency = 0; // for use in read/write to EEPROM memFrequency = EEPROM.read(0) * 256 + EEPROM.read(1); memFrequency *= 10000; return memFrequency; }