I2C Analog TV Modulator controlled by Arduino

 Posted by:   Posted on:   Updated on:  2018-01-24T10:52:45Z

Using a RMUP74055AD or TNF0170U722 device to build an analog UHF TV modulator with Arduino and LCD display.

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.

36 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
  5. Great work. Can you do something similar with Satellite / DVB-S or DVB-S2 tuner. There is very little information about it.

    ReplyDelete
    Replies
    1. I controlled satellite tuners too: https://www.onetransistor.eu/2016/05/control-s7vz6306-tuner-ix2470.html. This kind of tuners can be used to build ADS-B receivers.

      Delete
  6. Hi, Could You provide me any assistance? I have almost the same RF Modulator (RMUP74055DD) but I cannot accomplish what You did. I'm unable to see the address of the modulator in I2C Scanner and while using Your code it is unable to go "on air". It freezes ... and when I disconnect modulator then I see error instead. I'm unable to make I2C connection with the rf modulator What can I check ? I've tried with pullup resistors but no change.

    ReplyDelete
    Replies
    1. Make sure the modulator is powered (there are two power pins for +5V) and try again with I2C scanner sketch. Do not connect pull up resistors. Make sure ground is connected to metal case. Try to switch SDA and SCL, maybe you got them wrong. If it still does not work, maybe there is something wrong with your modulator.

      Delete
    2. I did all of that. I'm also powering the modulator from external 5V source of course connecting all grounds. While powering from Arduino 5v pin voltage was to much below 5V. Modulator is ok for sure. It was working fine just a few hours ago in VCR :) Moreover this is the second one I'm trying. I'm powering module as You showed . I have no idea what to check more.

      Delete
    3. It could be different pin configuration between 74055AD and 74055DD. If it draws so much current that Arduino can't supply 5V, then something seems to be wrong.

      Delete
    4. When it was still in VCR I checked all pins. +5V were in the same place as in AD

      Delete
    5. I have one more (and last) question. Are You familiar with python or perl? RF modulator will be used with Raspberry Pi and I don't want to add any extra devices (I'm trying to make it as small as possible) so I'd like to turn the modulator on while booting raspberry. That is why I need to transform arduino code to perl/python. I just need to send a command to switch on, on one of the channels and then switch off. For test purpose I would also need to run test signal on modulator. Can You help me with that? I just need to know what to sent? Thanks in advance

      Delete
    6. I'm not familiar with specific Raspberry Pi programming. I don't know whether you can send I2C data while Linux kernel is booting. I guess you can use bash scripting too and make a script that will be launched after startup. Have a look at mod_start function that sends all bytes required to configure and start modulator. You'll have to write your code based on it.

      Delete
    7. Please explain me a bit "i2c bit map" in modulator.
      Wire.beginTransmission(MOD_ADDRESS);
      Wire.write(0x88);
      Wire.write(0x40 | mod_sound[soundIdx]);
      Wire.write(fm);
      Wire.write(fl);
      Wire.endTransmission();
      Are there any registers and every time I'm sending data I need to set register or offset or You just send bytes one by one to 0x65 address?

      Delete
    8. Have a look at the registers table above. After the device address you need to send 2 or 4 bytes of data. These bytes are either (C1, C0) or (FM, FL) or (C1, C0, FM, FL) or (FM, FL, C1, C0).

      Delete
  7. Found the proper pinout for RMUP74055DD
    pin1 -> 5V
    pin2 -> Audio in
    pin3 -> SDA
    pin4 -> 5V
    pin5 -> SCL
    pin6 -> Video in

    Now everything works great!

    ReplyDelete
    Replies
    1. That's great! Only voltage supply pins are the same. A/V and I2C are totally different.

      Delete
  8. Hi
    Thank you for your post.
    Is it possible to run the modulator without the micro-controller on fixed settings for example?
    Regards.

    ReplyDelete
    Replies
    1. Unfortunately this type of modulator needs a microcontroller to set it up after power on.

      Delete
  9. This comment has been removed by a blog administrator.

    ReplyDelete
  10. RMUP74055AD is it the same as with V5151MUP????

    ReplyDelete
    Replies
    1. Open the metal case and see if it contains MBS74T1AEF IC. If yes, then they are the same.

      Delete
  11. Hello, congratulations! I have a question about the same modulator, RMUP74055AD. I have my father's old CRT TV which has no scart or rca input, that's why found this module and try to make "DIY Scart/RCA to RF Project". However I couldn't determine whether SDA and SCL pins are used. Which pins should I use? Best regards

    ReplyDelete
    Replies
    1. Yes, SCL and SDA pins are used. Both must be wired to a microcontroller which has to set the frequency and other parameters of the modulator. If you are using Arduino, connect SDA to A4 and SCL to A5 pin, then use my sketch to control the modulator.

      Delete
  12. Bonjour.Votre montage fonctionne très bien.Merci pour votre description

    ReplyDelete
  13. I tried to do it on raspberry pi 4 but it doesn't work (

    ReplyDelete
    Replies
    1. don't think that I used the Arduino code, I translated it for Raspberry, but I always got the answer 200 255 255 255

      Delete
  14. It seems to me that the addressing table for the pictures is mixed up? I studied your code and implemented mine on putana via a USB-CH341A programmer! It allows you to use I2C! The table shows that the addressing table is CA,C0,C1,FL,FM ! But in your code, the package goes like this CA,C1,C0,FM,FL ! Anyway, everything worked for me as in your code! Do you have documentation for the block or chip? Where did you get the information on him? I will be very grateful for the original documentation!

    ReplyDelete

Please read the comments policy before publishing your comment.