Programming the original JP1 remote controls with Arduino

The early JP1 remote controls (universal device IR remote controls for your audio-visual equipment - TVs, DVDs, Tivos, Cable boxes, etc.) were programmable through the parallel port on PCs. These days, parallel ports -as well as the original JP1 remotes- are becoming increasingly rare (both being replaced by the USB kind). Since at the moment I have a few more JP1 remotes (a handful) than parallel ports in operation (zero), I had to find a way to program updates for my audio-visual equipment lineup.

The Arduino can be accessed via USB and can also easily talk to the EEPROM that contains the program for the JP1 remote. The software for JP1/JP1.X programming does communicate via USB to a JP1 EEPROM programmer firmware in order to program the original JP1 remotes. It appears that the Arduino can easily be enlisted to cover the combo of USB serial driver and JP1 EEPROM programmer. Since Kevin Timmerman's open-sourced his JP1 EEPROM Programmer software (thanks, Kevin!), here we are!

Below are instructions and the Arduino sketch on how you can share the fun.

/***********************************************************************
Arduino sketch for programming of the original JP1 remotes


14 Mar 2012 by Tim6502

This is for the original JP1 remote control interface ONLY.
Not intended for JP1.1, 1.2, 1.3, etc. remotes!

See JP1 - Just How Easy Is It? for background on
remote controls that are programmable via JP1.

(1) Make yourself a JP1 cable - for example by cutting up an IDE cable.
View from the front of the female plug on the cable:
+----------+ JP1 Wire pinout on remote:
/ 1 3 5 | 1 - Vdd U1 2 - Vdd U2
| 2 4 6 | 3 - Ground 4 - Serial Data (SDA)
+-----------+ 5 - Reset 6 - Serial Clock (SCL)

(2) Connect the wires from this cable as follows:

  • connect 1 and 2 together
  • connect 3 to Arduino Ground
  • connect 4 (SDA) to Arduino Analog Pin 4
  • connect 5 (Reset) to Arduino Ground
    NOTE: you will need to unplug the remote from the cable to use it.
  • connect 6 (SCL) to Arduino Analog Pin 5

If the battery voltage in your remote is higher than the voltage
in the Arduino, you also need to pull up the following JP1 lines
via a 10k Ohm resistor each, connected to the JP1 Vdd line 1 (or 2):
4 (SDA) and 6 (SCL).

(3) Attach the Arduino to your PC and upload this sketch.
When you run the RemoteMasterIR or IR programs, select
JP1.X Serial... interface.
Yes, even though this is for a non JP1.X remote!
Now you should be able to download and/or upload the EEPROM
content from or to your remote.

Note that after an upload, the Arduino will be restarted by the
RM / IR program. If you want to avoid this, you would need to
pull RESET high with an adequate resistor value, or -if using
an external USB-Serial TTL converter device, make connections
except for the line that will pull on RESET.

This sketch is directly based on assembler code from:

JP1 EEPROM Programming Adapter Firmware
Copyright (C) 2010 Kevin Timmerman
jp1epa [@t] compendiumarcana [d0t] com
JP1 EEPROM Programming Adapter

For EEPROM communication this sketch uses example code by
davekw7x, March, 2011, which in turn was
derived from sketch for 24C64 devices posted on
Arduino Playground - I2CEEPROM
from hkhijhe Date: 01/10/2010.

While this worked for me with a *duino Duemillanova clone and with a
URC 6131 remote using 2k 24C32 EEPROM chips (the default size this
sketch selects for EEPROMs), it has not been tested with other EEPROM
chips or other sizes.
More importantly, you should be aware that you are using these
instructions and code at your own risk. There are no guarantees
that this will work, or that it will not damage or blow up your
equipment (or yourself). You have been warned!

  • Good luck!

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

#include <Wire.h>

// The seven-bit device address for EEPROMs
const byte DEVADDR = 0x50;

const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

byte ee_size;
byte ee_fill;
byte jp1_cmd;
byte jp1_len;
byte jp1_chk;
byte addrl;
byte addrh;

byte countl;

byte data_buf[40];

boolean fTwoByteAddress;
boolean fFillTestPattern;
boolean fShowAckWait;

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

fTwoByteAddress = false;
fFillTestPattern = false;
fShowAckWait = false;

C16();
}

void loop() {
jp1_cmd = (byte) SerRx();
jp1_chk = jp1_cmd;
fShowAckWait = false;

cmd_lookup();
}

Part 2:

void cmd_lookup() {
switch ((char) jp1_cmd) {
case 'R': R(); break;
case 'S': S(); break;
case 'C': C(); break;
case 'E': E(); break;
case 'I': I(); break;
case 'V': V(); break;
case 'd': d(); break;
case 'f': f(); break;
case 'z': z(); break;
case 't': t(); break;
case '1': _1(); break;
case '2': _2(); break;
case '4': C04(); break;
case '8': C08(); break;
case '6': C16(); break;
case '3': C32(); break;
case 'x': x(); break;
case '?': info(); break;
default: break;
};
};

void send_msg(char s) {
int i = 0;
while (s
) {*
jp1_chk ^= s*;
_ SerTx(s);
i++;
}
}
void ACK() {
SerTx(6);
}
// --- Ping*
// -> E
// <-
void E() {
* ACK();
}
// --- Identity*
// -> I
// <- [data]
void I() {
* jp1_chk = 0;
send_msg("eeprom");
SerTx(jp1_chk);*

}
// --- Version
// -> V
// <- [data]
//
// - Flash size is in units of 128 bytes. Get max MSB of EE address * 2_

// - EEPROM base address MSB
// - EEPROM base address LSB
void V() {
jp1_chk = 2 * ee_size;
* SerTx(jp1_chk);
_ SerTx(0);
SerTx(0);_
SerTx(jp1_chk);
_}
// --- Read*
// -> R
// <- r [data]
void R() {
* jp1_chk=(byte)'R';
get_addr_len();*

* byte chk = SerRx();_
if ( chk ^ jp1_chk ) {
// bad_checksum*

* return;*
* }*
* jp1_chk = (byte)'r';
SerTx(jp1_chk);
for (int i=0; i<jp1_len; i++) {
byte b = eeprom_read_byte(DEVADDR, i + addrl + 256addrh);

* jp1_chk ^= b;
_ SerTx(b);
}_
SerTx(jp1_chk);
_}
// --- Write*
// -> S [data]
// <-
void S() {
* jp1_chk=(byte)'S';*
* byte b;_
get_addr_len();*

* for (int i=0; i<jp1_len; i++) {
_ b = SerRxChk();_
data_buf=b;
_ }
b = SerRx();_

if (b ^ jp1_chk) {
// bad_checksum*

* return;*
* }*
* ACK();*
eeprom_write_pages(DEVADDR, addrl + addrh * 256, jp1_len, data_buf);
}
// --- Erase
// -> C
// <-
void C() {
* get_addr_len();
_ countl = SerRx();_
jp1_chk ^= countl;
_ byte b = SerRx();_
if (b ^ jp1_chk) {
// bad_checksum*

* } else {*
* // add code to clear EEPROM here*
* ACK();*
* }*
}
// --- d - Display EEPROM contents
// code for content dump taken from davekw7x (not from K. Timmerman code) ;
void d() {
* SerCRLF();*

unsigned numPages = (ee_size * 2 * 256) / 16;

* for (unsigned i = 0; i < numPages; i++) {*
* byte buffer[16]; // Hold a page of EEPROM*
* char outbuf[6]; //Room for three hex digits and ':' and ' ' and '\0'*
* sprintf(outbuf, "%03x: ", i);*
_ Serial.print(outbuf);
* for (int j = 0; j < 16; j++) {_
buffer[j]=eeprom_read_byte(DEVADDR, j+i16);

* if (j == 8) {*
_ Serial.print(" ");
* }
sprintf(outbuf, "%02x ", buffer[j]);
Serial.print(outbuf);
}
Serial.print(" |");
for (int j = 0; j < 16; j++) {
if (isprint(buffer[j])) {
Serial.print(buffer[j]);
}
else {
Serial.print('.');
}
}
Serial.println("|");
}
SerCRLF();
}
// 1 - One byte addressing*

void _1() {
* fTwoByteAddress = false;
ACK();
}
// 2 - Two byte addressing*

void _2() {
* fTwoByteAddress=true;
ACK();
}
// 4 - 24C04 - 512 byte EEPROM*

void C04() {
* ee_size = 0x02;
1();*
}

// 8 - 24C08 - 1K byte EEPROM
void C08() {
* ee_size = 0x04;
1();*
}
// 6 - 24C16 - 2K byte EEPROM
void C16() {
* ee_size = 0x08;
1();*
}

// 3 - 24C32 - 4K byte EEPROM
void C32() {
* ee_size = 0x10;
2();*
}
// f - Fill EEPROM with 0xFF
void f() {
* ee_fill = 0xFF;*
* fFillTestPattern = false;
fill();
}
// z - Fill EEPROM with 0x00*

void z() {
* ee_fill = 0;*
* fFillTestPattern = false;
fill();
}
// t - Fill EEPROM with test pattern*

void t() {
* fFillTestPattern = true;
fill();
}
// x - Release remote reset*

void x() {
}
[/quote]_

Part 3 (last part):

// ? - Product information
void info() {
SerCRLF();
send_msg("JP1 EEPROM Programming Adapter\r\n");
send_msg("Copyright 2010 Kevin Timmerman\r\n");
send_msg("Ported to Arduino 2012 Tim6502\r\n");
send_msg("Rev 003 Config ");
tx_hex_byte(ee_size);
SerTx(' ');
SerTx( (fTwoByteAddress) ? '2' : '1' );
SerCRLF();
SerCRLF();
send_msg("d Display EEPROM contents\r\n");
send_msg("f Fill with FF\r\n");
send_msg("z Fill with 00\r\n");
send_msg("t Fill with test pattern\r\n");
send_msg("4 24C04\r\n");
send_msg("8 24C08\r\n");
send_msg("6 24C16\r\n");
send_msg("3 24C32");
SerCRLF();
}

// --- Fill EEPROM with ee_fill
void fill() {
send_msg("Fill EEPROM with ");
if (fFillTestPattern) {
send_msg("test pattern");
} else {
tx_hex_byte(ee_fill);
}
SerTx(' ');
SerTx('?');
SerCRLF();

byte b = SerRx();
if (b != (byte)'Y') {
fill_abort();
return;
}

addrl = 0;
addrh = 0;
fShowAckWait = true;

SerCRLF();
tx_hex_byte(addrh);
tx_hex_byte(addrl);

unsigned address=addrl + 256*addrh;

while (address < ee_size*256) {
if (fFillTestPattern) {
eeprom_write_byte(DEVADDR, address, ee_fill);
} else {
eeprom_write_byte(DEVADDR, address, addrl+addrh);
addrl++;
if (addrl==0) addrh++;
}
}
SerCRLF();
}

// - Abort fill
void fill_abort() {
send_msg("Fill Canceled");
SerCRLF();
}

// --- Get address and length from host, update checksum
byte get_addr_len() {
addrh = SerRxChk();
addrl = SerRxChk();
jp1_len = SerRxChk();
}

// - CR LF
void SerCRLF() {
SerTx(13);
SerTx(10);
}

// - Send byte as ASCII hex
void tx_hex_byte(byte b) {
byte nib = b / 16;
SerTx(hex[nib]);
nib = b % 16;
SerTx(hex[nib]);
}

void SerTx(byte b) {
Serial.write(b);
}

byte SerRx() {
int b = -1;
while (b < 0) {
while (Serial.available() < 0) {
// wait here
};
b = Serial.read();
}
return (byte) b;
}

byte SerRxChk() {
byte b = SerRx();
jp1_chk = (b ^ jp1_chk);
return b;
}

void eeprom_write_byte(byte deviceaddress, int eeaddress, byte data)
{
// Three lsb of Device address byte are bits 8-10 of eeaddress
byte devaddr = deviceaddress | ((eeaddress >> 8) & 0x07);
byte addr = eeaddress;
Wire.beginTransmission(devaddr);
Wire.write(int(addr));
Wire.write(int(data));
Wire.endTransmission();
delay(10);
}

int eeprom_read_byte(byte deviceaddress, unsigned eeaddr)
{
byte rdata = -1;

// Three lsb of Device address byte are bits 8-10 of eeaddress
byte devaddr = deviceaddress | ((eeaddr >> 8) & 0x07);
byte addr = eeaddr;

Wire.beginTransmission(devaddr);
Wire.write(int(addr));
Wire.endTransmission();
Wire.requestFrom(int(devaddr), 1);
if (Wire.available()) {
rdata = Wire.read();
}
return rdata;
}

// Pages are blocks of 16 bytes, starting at 0x000.
// That is, pages start at 0x000, 0x010, 0x020, ...
// For a device "page write", the last byte must be
// on the same page as the first byte.
void eeprom_write_pages(byte deviceaddress, unsigned eeaddr, unsigned length, const byte * data)
{
unsigned count=0;
while (count < length) {
// Three lsb of Device address byte are bits 8-10 of eeaddress
byte devaddr = deviceaddress | ((eeaddr >> 8) & 0x07);
byte addr = eeaddr;
byte nibble = addr & 0x07;
byte i = 0;

Wire.beginTransmission(devaddr);
Wire.write(int(addr));
do {
Wire.write(data[count]);
count++; i++;
} while (count < length && (((nibble + i) & 0x07) != 0));
eeaddr += i;
Wire.endTransmission();
delay(10);
}

}

Moderator edit: smiles turned off.

This sketch fails to compile for me. I cut and paste the 3 parts from the original email, but get the following errors:

JP1_RemoteCotrol.cpp: In function ‘void send_msg(char*)’:
JP1_RemoteCotrol:73: error: invalid operands of types ‘byte’ and ‘char*’ to binary ‘operator^’
JP1_RemoteCotrol:73: error: in evaluation of ‘operator^=(byte, char*)’
JP1_RemoteCotrol:74: error: invalid conversion from ‘char*’ to ‘byte’
JP1_RemoteCotrol:74: error: initialising argument 1 of ‘void SerTx(byte)’
JP1_RemoteCotrol.cpp: In function ‘void S()’:
JP1_RemoteCotrol:153: error: incompatible types in assignment of ‘byte’ to ‘byte [40]’

Have I missed something, or is there a typo in the original post??

It appears that:
open-square-bracket i close-square-bracket
got transmogrified into something else (start italics??)

Correct code:

(1)
void send_msg(char *s) {
int i = 0;
while ( s [ i ] ) {
jp1_chk ^= s [ i ] ;
SerTx( s [ i ] );
i++;
}
}

(2)
for (int i=0; i<jp1_len; i++) {
b = SerRxChk();
data_buf [ i ] =b;
}

I have attached the Arduino source file for convenience.

Happy JP1-ing!

JP1_Arduino.ino (12.9 KB)

One more comment:

If you have a JP1.2 or JP1.3 remote and also a USB to TTL breakout adapter
/ cable to talk to a 6-pin connector on a *duino, then you should not need
any additional hardware to program your JP1.2/1.3 remote. You just have to
connect pins from the adapter to appropriate pins on the 6-pin JP1.x cable.
Unfortunately, I did not fully realize this until after I had built, debugged, and
used serial-to-JP1.2/1.3 hardware and thus have not tried out this route.
I suggest that you check the JP1 sites for details.

Programming the old JP1 remotes would still require an Arduino loaded with the
sketch above in order to directly program the EEPROM chip. In the newer JP1.x
remotes, this is accomplished via talking through the serial (TTL) interface to the
processor in the remote.

Hi Tim,

thanks a lot for you work! I've uploaded your sketch to the JP1 file area, so it doesn't get lost.

Michael