I gave up on the build-in I2C and used the following code to bit bang the I2C instead in JavaScript (code almost 1 to 1 from I2C article on wikipedia):
function jsI2C(p_Info){
function pinHigh(pin){ pin.mode("input"); }
function pinLow(pin){ pin.mode("opendrain"); }
function I2C_delay(){ wait(1); }
function read_SCL(){ return p_Info.scl.read(); } // Set SCL as input and return current level of line, 0 or 1
function read_SDA(){ return p_Info.sda.read(); } // Set SDA as input and return current level of line, 0 or 1
function set_SCL(){ pinHigh(p_Info.scl); } // Actively drive SCL signal high
function clear_SCL(){ pinLow(p_Info.scl); } // Actively drive SCL signal low
function set_SDA(){ pinHigh(p_Info.sda); } // Actively drive SDA signal high
function clear_SDA(){ pinLow(p_Info.sda); } // Actively drive SDA signal low
function arbitration_lost(){}
function wait(n){ for (var index = 0; index < n; index++){} }
function waitForClockStretch(){ while (!read_SCL()){ wait(1); } }
var started = false; // global data
function i2c_start_cond(){
if (started)
{
// if started, do a restart cond
// set SDA to 1
set_SDA();
I2C_delay();
waitForClockStretch();
// Repeated start setup time, minimum 4.7us
I2C_delay();
}
if (!read_SDA())
{
arbitration_lost();
}
// SCL is high, set SDA from 1 to 0.
clear_SDA();
I2C_delay();
clear_SCL();
started = true;
}
function i2c_stop_cond(){
// set SDA to 0
set_SCL();
clear_SDA();
I2C_delay();
waitForClockStretch();
// Stop bit setup time, minimum 4us
I2C_delay();
// SCL is high, set SDA from 0 to 1
set_SDA();
I2C_delay();
if (!read_SDA()){
arbitration_lost();
}
I2C_delay();
started = false;
}
// Write a bit to I2C bus
function i2c_write_bit(bit){
if (bit)
{
set_SDA();
}
else
{
clear_SDA();
}
// SDA change propagation delay
I2C_delay();
// Set SCL high to indicate a new valid SDA value is available
set_SCL();
// Wait for SDA value to be read by slave, minimum of 4us for standard mode
I2C_delay();
waitForClockStretch();
// SCL is high, now data is valid
// If SDA is high, check that nobody else is driving SDA
if (bit && !read_SDA()){
arbitration_lost();
}
// Clear the SCL to low in preparation for next change
clear_SCL();
}
// Read a bit from I2C bus
function i2c_read_bit(){
var bit;
// Let the slave drive data
set_SDA();
// Wait for SDA value to be written by slave, minimum of 4us for standard mode
I2C_delay();
// Set SCL high to indicate a new valid SDA value is available
set_SCL();
waitForClockStretch();
// Wait for SDA value to be read by slave, minimum of 4us for standard mode
I2C_delay();
// SCL is high, read out bit
bit = read_SDA();
// Set SCL low in preparation for next operation
clear_SCL();
return bit;
}
// Write a byte to I2C bus. Return 0 if ack by the slave.
function i2c_write_byte(send_start, send_stop, byte){
var nack = false;
if (send_start){
i2c_start_cond();
}
for (var bit = 0; bit < 8; bit++){
i2c_write_bit((byte & 0x80) !== 0);
byte <<= 1;
}
nack = i2c_read_bit();
if (send_stop){
i2c_stop_cond();
}
return nack;
}
// Read a byte from I2C bus
function i2c_read_byte(nack, send_stop){
var byte = 0;
for (var bit = 0; bit < 8; bit++ )
{
byte = ( byte << 1 ) | i2c_read_bit();
}
i2c_write_bit(nack);
if (send_stop){
i2c_stop_cond();
}
return byte;
}
return {
readRegister: function(address, register, bytes){
var result = [];
// write address and write mode
if (!i2c_write_byte(true, false, (address << 1) | 0)){
// write the data to write in this case the register and write a stop
if (!i2c_write_byte(false, true, register)){
// write the address and read mode
if (!i2c_write_byte(true, false, (address << 1) | 1)){
// read the bytes
while (bytes){
bytes--;
// if no bytes remaining send nack followed by stop
result.push(i2c_read_byte(bytes === 0, bytes === 0));
}
}
}
}
return result;
},
write: function(address, p_Bytes){
if (!i2c_write_byte(true, false, (address << 1) | 0)){
// write the data to write in this case the register and write a stop
for (var index = 0; index < p_Bytes.length; index++){
if (i2c_write_byte(false, index == p_Bytes.length - 1, p_Bytes[index])){
break;
}
}
}
}
};
}
This code actually works with the radio module and I managed to tune the FM station and got it to play :-).
Here is the radio code that uses my bit-banging I2C for anyone who is interested :
// create the radio interface
// specify SDA and SCL pins to use, will default to 4 and 5 if not set, neither pin can be 0
function Radio(p_SDA, p_SCL){
"use strict";
var i2c = jsI2C({ sda: p_SDA || D4, scl: p_SCL || D5});
var i2c_write_address = 0x10;
var i2c_read_address = 0x11;
var out_buffer = [];
// constants :
var rda_CHIP_ID = 0x58, // CHIP ID
// Tuning Band //
rda_87_108MHz = 0b0000000000000000, // us band
rda_76_91MHz = 0b0000000000000100, // japan band
rda_76_108MHz = 0b0000000000001000, // world band we use this
rda_65_76MHz = 0b0000000000001100, // east europe
// Tuning Steps //
rda_100kHz = 0b0000000000000000,
rda_200kHz = 0b0000000000000001, // not US band compatible **//
rda_50kHz = 0b0000000000000010,
rda_25kHz = 0b0000000000000011, // we use this
// ** 200kHz spacing works but frequencies are always out by 100kHz from US frequency slots.
// It's not possible to tune to a US station with 200kHz spacing you must use 100kHz instead.
// De-emphasis //
rda_50us = 0b0000100000000000, // eu standard
rda_75us = 0b0000000000000000, // us standard
// REG 0x02
rda_DHIZ = 0b1000000000000000,
rda_DMUTE = 0b0100000000000000,
rda_MONO = 0b0010000000000000,
rda_BASS = 0b0001000000000000,
rda_RCLK = 0b0000100000000000,
rda_RCKL_DIM = 0b0000010000000000,
rda_SEEKUP = 0b0000001000000000,
rda_SEEK = 0b0000000100000000,
rda_SKMODE = 0b0000000010000000, // default is to wrap around
// Timing XTAL = rda_CLK_MODE //
// we are not changing the defaults so remark
rda_32_768kHz = 0b0000000000000000,
rda_12MHz = 0b0000000000010000,
rda_24MHz = 0b0000000001010000,
rda_13MHz = 0b0000000000100000,
rda_26MHz = 0b0000000001100000,
rda_19_2MHz = 0b0000000000110000,
rda_38_4MHz = 0b0000000001110000,
rda_CLK_MODE = 0b0000000001110000, // default is 32.768kHz which is the build in crystal
rda_RDS_EN = 0b0000000000001000,
rda_NEW_METHOD = 0b0000000000000100,
rda_SOFT_RESET = 0b0000000000000010,
rda_ENABLE = 0b0000000000000001,
// REG 0x03
rda_CHAN = 0b1111111111000000,
rda_DIRECT_MODE = 0b0000000000100000, // only used with test
rda_TUNE = 0b0000000000010000,
rda_BAND = 0b0000000000001100,
rda_SPACE = 0b0000000000000011,
// REG 0x04
rda_DE = 0b0000100000000000,
rda_SOFTMUTE_EN = 0b0000001000000000,
rda_AFCD = 0b0000000100000000,
// REG 0x05
rda_INT_MODE = 0b1000000000000000,
rda_SEEKTH = 0b0000111100000000,
rda_VOLUME = 0b0000000000001111,
// REG 0x06
rda_OPEN_MODE = 0b0110000000000000,
// REG 0x07
rda_BLEND_TH = 0b0111110000000000,
rda_65_50M_MODE = 0b0000001000000000,
rda_SEEK_TH_OLD = 0b0000000011111100,
rda_BLEND_EN = 0b0000000000000010,
rda_FREQ_MODE = 0b0000000000000001,
// REG 0x0A
rda_RDSR = 0b1000000000000000,
rda_STC = 0b0100000000000000,
rda_SF = 0b0010000000000000,
rda_RDSS = 0b0001000000000000,
rda_BLK_E = 0b0000100000000000,
rda_ST = 0b0000010000000000,
rda_READCHAN = 0b0000001111111111,
// REG 0x0B
rda_RSSI = 0b1111110000000000,
rda_FM_TRUE = 0b0000001000000000,
rda_FM_READY = 0b0000000100000000,
rda_ABCD_E = 0b0000000000010000,
rda_BLERA = 0b0000000000001100,
rda_BLERB = 0b0000000000000011;
//
function read_chip(offset) {
var data = i2c.readRegister(i2c_read_address, offset, 2);
return ((data[0] << 8) & 0xff00) | (data[1] & 0x00ff);
}
function write_chip(size) {
i2c.write(i2c_write_address, out_buffer.slice(0, size));
}
function tune(p_On){
var data = ((out_buffer[2]<<8)|out_buffer[3])|rda_TUNE;
if (!p_On) { data = data^rda_TUNE; }
out_buffer[2] = (data >> 8) & 0xff;
out_buffer[3] = data & 0xff;
}
function readLoopChip(){
for (var loop = 2; loop < 8; loop++) {
var data = read_chip(loop);
out_buffer[(loop*2)-4] = (data >> 8) & 0xff;
out_buffer[(loop*2)-3] = data & 0xff;
}
tune(false); // disable tuning
}
function init_chip() {
var data = read_chip(0);
var found = 0;
var result;
if ((data&0xff) == rda_CHIP_ID) {
found = 1;
}
data = (data >> 8)&0xff;
if (data == rda_CHIP_ID) {
found = 1;
}
if (found === 0) {
}
else if ((read_chip(13) == 0x5804) && (read_chip(15) == 0x5804)) {
result = 0;
} // not set up
else {
result = 1;
} // already used
return result;
}
function isInitialized(){
var state = init_chip();
return state !== 0;
}
// radio on
function on(p_Callback){
// REG 02
// normal output, enable mute, stereo, no bass boost, clock = 32.768kHz, RDS enabled, new demod method, power on
var data = rda_DHIZ|(rda_DMUTE&0)|(rda_MONO&0)|(rda_BASS&0)|rda_RDS_EN|rda_NEW_METHOD|rda_ENABLE;
out_buffer[0] = (data >> 8) & 0xff;
out_buffer[1] = data & 0xff;
// REG 03 - no auto tune, 76-108 band, 0.1 spacing
data = (rda_TUNE&0)|rda_76_108MHz|rda_100kHz;
out_buffer[2] = (data >> 8) & 0xff;
out_buffer[3] = data & 0xff;
// REG 04 - audio 50us, no soft mute, disable AFC
data = rda_50us|(rda_SOFTMUTE_EN&0)|rda_AFCD;
out_buffer[4] = (data >> 8) & 0xff;
out_buffer[5] = data & 0xff;
// REG 05 - max volume
data = rda_INT_MODE|0x0480|rda_VOLUME;
out_buffer[6] = (data >> 8) & 0xff;
out_buffer[7] = data & 0xff;
// REG 06 - reserved
out_buffer[8] = 0;
out_buffer[9] = 0;
// REG 07
var blend_threshold = 0b0011110000000000; // mix L+R with falling signal strength
data = blend_threshold|rda_65_50M_MODE|0x80|0x40|rda_BLEND_EN|(rda_FREQ_MODE&0);
out_buffer[10] = (data >> 8) & 0xff;
out_buffer[11] = data &0xff;
write_chip(12);
setTimeout(p_Callback, 200);
}
function off(){
if (isInitialized()){
var data = (read_chip(2)|rda_ENABLE)^rda_ENABLE;
out_buffer[0] = (data >> 8) & 0xff;
out_buffer[1] = data &0xff;
write_chip(2);
}
}
function forceMono(p_On){
if (isInitialized()){
var data = (read_chip(2)|rda_MONO);
if (!p_On) { data = data^rda_MONO; }
out_buffer[0] = (data >> 8) & 0xff;
out_buffer[1] = data & 0xff;
write_chip(2);
}
}
function bassBoost(p_On){
if (isInitialized()){
var data = (read_chip(2)|rda_BASS);
if (!p_On) { data = data^rda_BASS; }
out_buffer[0] = (data >> 8) & 0xff;
out_buffer[1] = data & 0xff;
write_chip(2);
}
}
function mute(p_On){
if (isInitialized()){
var data = (read_chip(2)|rda_DMUTE);
if (p_On) { data = data^rda_DMUTE; }
out_buffer[0] = (data >> 8) & 0xff;
out_buffer[1] = data & 0xff;
write_chip(2);
}
}
function checkSeek(p_Callback){
var regA = read_chip(0x0A);
if (!!(regA & rda_STC)){
p_Callback(!(regA & rda_SF));
}
else {
setTimeout(function(){ checkSeek(p_Callback); }, 500);
}
}
function seekUp(p_Callback){
if (isInitialized()){
var data = (read_chip(2)|rda_SEEKUP|rda_SEEK);
out_buffer[0] = (data >> 8) & 0xff;
out_buffer[1] = data & 0xff;
tune(true);
write_chip(2);
checkSeek(p_Callback);
}
}
function seekDown(p_Callback){
if (isInitialized()){
var data = (read_chip(2)|rda_SEEKUP|rda_SEEK)^rda_SEEKUP;
out_buffer[0] = (data >> 8) & 0xff;
out_buffer[1] = data & 0xff;
tune(true);
write_chip(2);
checkSeek(p_Callback);
}
}
function deemphasis75(p_On){
if (isInitialized()){
readLoopChip();
var data = (read_chip(4)|rda_DE)^rda_DE;
data = (p_On) ? (data | rda_75us) : (data | rda_50us);
out_buffer[4] = (data >> 8) & 0xff;
out_buffer[5] = data & 0xff;
write_chip(6);
}
}
function volume(p_Val){
if (isInitialized()){
readLoopChip();
var data = (read_chip(5)|rda_VOLUME)^rda_VOLUME;
if (p_Val > rda_VOLUME) { p_Val = rda_VOLUME; }
data = data|p_Val;
out_buffer[6] = (data >> 8) & 0xff;
out_buffer[7] = data & 0xff;
write_chip(8);
}
}
function setFrequency(p_Mhz){
if (isInitialized()){
mute(false);
var channel = ((((p_Mhz - 76)/0.1) | 0) & 0x3FF) << 6;
var data = (read_chip(3)|rda_CHAN)^rda_CHAN;
data = data|channel;
out_buffer[2] = (data >> 8) & 0xff;
out_buffer[3] = data & 0xff;
tune(true);
write_chip(4);
}
}
function getRDS() {
// check register A
var data = read_chip(0x0A);
var rds = [0,0,0,0, false, false];
// block E type
rds[4] = !!(data & rda_BLK_E);
// check for new RDS data available
if (rds[4] || data & rda_RDSR) {
rds[0] = read_chip(0x0C) & 0xffff;
rds[1] = read_chip(0x0D) & 0xffff;
rds[2] = read_chip(0x0E) & 0xffff;
rds[3] = read_chip(0x0F) & 0xffff;
rds[5] = true;
}
return rds;
}
function getState(){
var reg2 = read_chip(2);
var regA = read_chip(0x0A);
var regB = read_chip(0x0B);
return {
isInitialized: isInitialized(),
muteOn: !(reg2 & rda_DMUTE),
forceMonoOn: !!(reg2 & rda_MONO),
bassBoostOn: !!(reg2 & rda_BASS),
rdsOn: !!(reg2 & rda_RDS_EN),
de: !read_chip(4) & rda_DE,
volume: read_chip(5) & rda_VOLUME,
stereo: !!(regA & rda_ST),
signalStrength: ((regB & rda_RSSI)>>10),
channel: ((regA & 0x03ff) + 760) / 10,
isStation: !!(regB & rda_FM_TRUE),
isReady: !!(regB & rda_FM_READY),
seekComplete: !!(regA & rda_STC),
seekFail: !!(regA & rda_SF),
hasRDS: !!(regA & rda_BLK_E) || !!(regA & rda_RDSR)
};
}
return {
read_chip: read_chip,
write_chip: write_chip,
on: on,
off: off,
forceMono: forceMono,
bassBoost: bassBoost,
mute: mute,
deemphasis75: deemphasis75,
volume: volume,
setFrequency: setFrequency,
getState: getState,
getRDS: getRDS,
seekUp: seekUp,
seekDown: seekDown
};
}
var radio = Radio(D4, D5);
radio.on(function(){
radio.setFrequency(104.5);
});
I gave up on the build-in I2C and used the following code to bit bang the I2C instead in JavaScript (code almost 1 to 1 from I2C article on wikipedia):
This code actually works with the radio module and I managed to tune the FM station and got it to play :-).
Here is the radio code that uses my bit-banging I2C for anyone who is interested :