I2C Analog TV Modulator controlled by Arduino

 Author:   Posted on:   Updated on:  2018-01-24T10:52:45Z
Analog video is getting replaced by digital signals which provide better resolution and picture without noise or interference. But, analog video signal is easy to generate with simple hardware and then it can be FM modulated for broadcasting over a wire. I2C controlled RF modulators are common modules in obsolete VCRs and set top boxes. Most of them cover the entire UHF band and support multistandard sound carrier frequencies. Once taken out of its device, the modulator needs a microcontroller to set up its frequency and other parameters.

Using an Arduino board with LCD and keypad shield a full featured modulator can be built. Arduino can be used to generate video too, but a single board can't use I2C and generate video in the same sketch. You'll need different boards if that's what you want to do.

I used for this project a Samsung RMUP74055AD modulator with MBS74T1AEF controller. Some searching reveals the same IC is also used by Tena TNF0170U722 modulator. Some datasheets will come up too, if you search for them. Anyway, these modulators are 5V devices.

RMUP74055AD UHF RF modulator
RMUP74055AD UHF RF modulator
Finding the pinout of these devices is possible even without the datasheet. They have one or two power supply pins, I2C Clock and Data pins and AV pins. Look carefully on the board you get the modulator from and you should be able to identify the pins.

RMUP74055AD and TNF0170U722 pinout
RMUP74055AD and TNF0170U722 pinout
Above is the pinout of the modulator I will use. Both MB+ and LOP are power pins. Ground connects to the metal case. Hooking up this to Arduino becomes very easy now. The parameters of the modulator are changed using the analog keypad provided by the shield.

I2C Analog TV Modulator controlled by Arduino
RF Modulator connected to Arduino and SDR dongle
I hooked up the modulator output to a SDR dongle to monitor its output using TVSharp application. You may see in the photo, on the breadboard below the wires, there are pull-up resistors on I2C lines. I had a lot of issues with them because it seemed the MCU wouldn't send I2C commands all the time. Removing those resistors fixed the protocol transmission.

LCD shield is placed over Arduino UNO board. The modulator connects directly to A4 and A5 pins of the Arduino (for I2C) and to the power pins. There are the only connections you need.

To make this work, we need some software. The modulator has 4 write registers and 1 read register, each of 1 byte.

I2C registers of MBS74T1AEF
I2C registers of MBS74T1AEF
OSC, SO and ATT bits control sleep mode (OSC=0, SO=1 and ATT=1 turn off the device). TPEN enables test pattern output (two vertical white lines and beep sound). SFD bits control sound carrier frequency and OOR is the read bit that indicates an out of range error.

To set frequency, its value in kHz must be divided by 250 and the result written in the 12 frequency bits. The analog video frequency of an UHF channel is 8 * channel + 303.25. Therefore:

Frequency (kHz) = 8 * channel * 1000 + 303250
Frequency (bits) = Frequency (kHz) / 250
Frequency (bits) = 8 * channel * 4 + 1213
Frequency (bits) = 32 * channel + 1213

Then, upper sixth bits go to FM register: Frequency (bits) >> 6
Lower sixth bits go to left of FL register: [Frequency (bits) & 0x3F] << 2

An I2C transmission may contain, after the device address, either C1-C0 bytes, either FM-FL bytes, or all of them (C1-C0-FM-FL or FM-FL-C1-C0). Here is the code:
#include <LiquidCrystal.h>
#include <Wire.h>
#include <EEPROM.h>

#define MOD_ADDRESS (0xCA >> 1)

// 4.5MHz, 5.5MHz, 6MHz, 6.5MHz
const byte mod_sound[4] = { 0x00, 0x08, 0x10, 0x18 };
byte eChannel, eSoundIdx, eOn, eTest;

#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5

unsigned long keyTime = 0;

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

/****** Keypad functions *****/
byte readKeypad() {
  int adc = analogRead(A0);

  if (adc < 50) return btnRIGHT;
  if (adc < 250) return btnUP;
  if (adc < 450) return btnDOWN;
  if (adc < 650) return btnLEFT;
  if (adc < 850) return btnSELECT;

  return btnNONE;
}

void keypadAction() {
  unsigned long currentTime = millis();
  if (currentTime - keyTime < 300) return; // less than 100 ms passed; quit

  byte key = readKeypad();
  keyTime = currentTime; // store last call time

  // perform actions
  if (key == btnNONE) return; // no key is pressed; quit
  switch (key)
  {
    case btnRIGHT:
      {
        if (eChannel < 69) eChannel++;
        EEPROM.write(0, eChannel);

        if (eOn) mod_changeChannel(eChannel, eTest);
        break;
      }
    case btnLEFT:
      {
        if (eChannel > 21) eChannel--;
        EEPROM.write(0, eChannel);

        if (eOn) mod_changeChannel(eChannel, eTest);
        break;
      }
    case btnUP:
      {
        if (eOn) eOn = 0;
        else eOn = 1;
        EEPROM.write(2, eOn);

        if (eOn) {
          mod_start(eChannel, eSoundIdx, eTest);
          lcd.setCursor(0, 0);
          lcd.print("  << ON AIR >>  ");
        }
        else {
          mod_sleep();
          lcd.setCursor(0, 0);
          lcd.print("   << OFF >>    ");
        }
        break;
      }
    case btnDOWN:
      {
        if (eTest) eTest = 0;
        else eTest = 1;

        if (eOn) mod_changeChannel(eChannel, eTest);
        break;
      }
    case btnSELECT:
      {
        eSoundIdx++;
        eSoundIdx &= 3;
        EEPROM.write(1, eSoundIdx);

        if (eOn) mod_setSoundCarrier(eSoundIdx);
        break;
      }
  }
  lcdUpdate(eChannel, eSoundIdx, eTest);

  delay(100);
  mod_getStatus();
}

/***** Modulator functions *****/
void mod_setFreqBytes(byte &fm, byte &fl, byte channel, boolean testMode) {
  if ((channel < 21) || (channel > 69)) channel = 21;

  uint16_t freq = 32 * channel + 1213;
  fm = freq >> 6;
  if (testMode) fm |= 0x40;
  fl = (freq & 0x3F) << 2;
}

void mod_start(byte channel, byte soundIdx, boolean testMode) {
  byte fm, fl;

  mod_setFreqBytes(fm, fl, channel, testMode);

  if (soundIdx > 3) soundIdx = 1;

  Wire.beginTransmission(MOD_ADDRESS);
  Wire.write(0x88);
  Wire.write(0x40 | mod_sound[soundIdx]);
  Wire.write(fm);
  Wire.write(fl);
  Wire.endTransmission();
}

void mod_sleep() {
  Wire.beginTransmission(MOD_ADDRESS);
  Wire.write(0xA8);
  Wire.write(0x20);
  Wire.endTransmission();
}

void mod_changeChannel(byte channel, boolean testMode) {
  byte fm, fl;

  mod_setFreqBytes(fm, fl, channel, testMode);

  Wire.beginTransmission(MOD_ADDRESS);
  Wire.write(fm);
  Wire.write(fl);
  Wire.endTransmission();
}

void mod_setSoundCarrier(byte idx) {
  if (idx > 3) idx = 1;

  Wire.beginTransmission(MOD_ADDRESS);
  Wire.write(0x88);
  Wire.write(0x40 | mod_sound[idx]);
  Wire.endTransmission();
}

void mod_getStatus() {
  if (!eOn) return;
  byte s = 1;
  int c = Wire.requestFrom(MOD_ADDRESS, 1);
  if (c == 1) s = Wire.read();

  if (s & 0x01) {
    lcd.setCursor(0, 0);
    lcd.print("  << ERROR !! >>  ");
  }
}

void lcdUpdate(byte channel, byte soundIdx, boolean testMode) {
  lcd.setCursor(0, 1);
  lcd.print("CH");
  lcd.print(channel);

  lcd.setCursor(6, 1);
  if (testMode) lcd.print("TEST");
  else lcd.print("    ");

  lcd.setCursor(12, 1);
  switch (soundIdx) {
    case 0: lcd.print("4.5M"); break;
    case 1: lcd.print("5.5M"); break;
    case 2: lcd.print("6.0M"); break;
    case 3: lcd.print("6.5M"); break;
  }
}

void setup() {
  pinMode(10, INPUT); // for LCD shield

  Wire.begin();
  lcd.begin(16, 2);

  lcd.print("  TV Modulator  ");
  delay(1000);
  lcd.clear();

  // get/set initial EEPROM parameters
  eChannel = EEPROM.read(0);
  if ((eChannel < 21) || (eChannel > 69)) eChannel = 21;
  EEPROM.write(0, eChannel);

  eSoundIdx = EEPROM.read(1);
  if (eSoundIdx > 3) eSoundIdx = 1;
  EEPROM.write(1, eSoundIdx);

  eTest = 0;

  eOn = EEPROM.read(2);
  if (eOn) {
    lcd.setCursor(0, 0);
    lcd.print("  << ON AIR >>  ");

    mod_start(eChannel, eSoundIdx, eTest);
  }
  else {
    lcd.setCursor(0, 0);
    lcd.print("   << OFF >>    ");
  }

  lcdUpdate(eChannel, eSoundIdx, eTest);
}

void loop() {
  keypadAction();
}
The software makes use of integrated EEPROM of ATmega MCU to store modulator parameters like on/off state, frequency and sound information. These are restored after reset. Using LEFT/RIGHT keys, output channel can be changed from 21 to 69 UHF. SELECT key cycles through sound carrier frequencies which are displayed on the bottom line, in the right of LCD screen. UP key turns modulator on and off and DOWN key enables/disables test pattern. Enabling the test pattern is not stored in EEPROM, therefor after reset, modulator will start, but not in test mode.

In a future post I will build an analog TV receiver using a tuner with included demodulator, an Arduino board to control it and an USB video capture card to send video to PC.

11 comments :

  1. Great project! Thank you man!

    Have you some example of demodulator digital TV using Realtek RTL2832U + Arduino?

    ReplyDelete
    Replies
    1. I guess an Arduino doesn't have enough processing power for capturing and performing demodulation on samples from RTL2832U. If the dongle is used for digital TV, a transport stream is sent over USB port. This is a high bitrate stream which can't be decoded by a small microcontroller.

      Delete
  2. Thanks. Also very nice worked with fm-mc016a4f5g modulator with ase74t1aef chip inside.

    ReplyDelete
  3. Thanks. But i want to ask something. If we only write this commands (i want to see only test pattern) it doesnt work. Can you help?

    #include


    void setup() {
    Wire.begin();
    Serial.begin(9600);

    }

    void loop() {
    Wire.beginTransmission(0xCA);
    Wire.write(B10001000);
    Wire.write(B01001000);
    Wire.write(B01100100);
    Wire.write(B11110100);
    Wire.endTransmission();


    }

    ReplyDelete
    Replies
    1. Your commands are in loop. It is enough to send them to the modulator only once after power up, not continusously, without any delay. You can move them into setup function. If it still doesn't work double check the validity of the bits you sent.

      Delete
    2. Thansk for your answer. I'm trying it now

      Delete
    3. #include

      void setup() {
      Wire.begin();
      Wire.beginTransmission(0xCA);
      Wire.write(0x84);
      Wire.write(0x48);
      Wire.write(0x64);
      Wire.write(0xF4);
      Wire.endTransmission();

      }
      void loop() {
      while(1);
      }

      I changed my codes as you said. But it still not worked. I try a new modulator one. Its same too. Where is my mistakes? I dont know :(

      ATT=0 , OSC=1, SFD1=0, SFD0=1, PS=0, SO=0, PWC=0, TPEN=1,
      Frequency bits are, N11 ==> 100100111101 CH36 = 591,25Mhz

      I checked Arduino's sda, scl pins a basic code. It's not damaged. It still work.

      At hardware,

      MB+ and B+ are 5 volts

      Please help..

      Delete
    4. Hi, again. A little time ago. I tried Nick Gammon's "i2c scanner" program.

      I2C scanner. Scanning ...
      Found address: 101 (0x65)
      Done.
      Found 1 device(s).

      This program said that my chip adress is 0x65, but our chip adress is 0xCA isnt it?

      Delete
  4. Yes, finally I finished. I see two vertical lines on tv. But how was it?

    My chip adress is not 0xCA. Chip adress is 0x65.

    ReplyDelete
    Replies
    1. 0xCA is full chip address, with R/W bit included. 0x65 is actually 0xCA >> 1. This is the 7-bit address without R/W bit and this is what you need to specify to Wire.beginTransmission. See the definition #define MOD_ADDRESS (0xCA >> 1) which means MOD_ADDRESS is 0x65.

      Delete

Please read the comments policy before publishing your comment.