Rotary encoder and digital potentiometer

Posted on
  • Our kitchen radio recently died, so I bought a si4703 i2c FM receiver. In about two years time, we'll be able to get some tunes in our kitchen again ;) I don't know a thing or two about bitwise JS, so reading the registers from the si4703 and overwriting them will be a pain... Reading tips are much appreciated!

    However, the good news is that I got the rotary encoder and digital potentiometers for tuning and volume working! Both needed a new module: the existing Encoder-module doesn't cover the push-button my encoders have built into the shaft, and there was no existing module for my potmeters (X9C103).

    I'm leaving them here in case anyone finds it useful. Also, remarks about coding is welcome, since I'm not a pro coder. Full modules are attached, here is a usage example:

    	var pot = new X9C103( { cs:PIN_CS, ud: PIN_UD, inc: PIN_INC } );
    
    	var volume = new Rotary( PIN_A, PIN_B, PIN_SW );
    	volume.rotateCallback = function () {
    		if( this.position < 0 )
    			this.position = 0;
    
    		if( this.position > 100 )
    			this.position = 100;
    		
    		pot.jumpTo( this.position );
    	};
    

    2 Attachments

  • Thanks! Those X9C103 look really interesting - a very neat way to control volume!

    The code you've got there looks good - one thing to watch out for is that digitalPulse is asyncronous - so:

    digitalWrite(LED1, 1);
    digitalPulse(LED2, 1,100);
    digitalWrite(LED1, 0);
    

    Will turn LED1 on, then LED2 on, then LED1 off, then LED2 off 100ms later. I think for your X9C103 it might mean that .up().up() will fail - so I'd consider just using two digitalWrites to do the pulses to make things easier.

    Let us know if you have any questions about interfacing to the si4703 - I'm more than happy to help - it'd be really fun to see an Espruino powered radio :)

  • Yes I'm aware of that. I thought the datasheet stated an input rise and fall time of 10ms. That's why I chose the digitalPulse anyway - creating a cueue would be too much of a hassle. However, reading the datasheet again, I see it says 10 ns :) I guess a double-digitalWrite should be fine.

    I guess the first step for interfacing with the si4703 is getting the registers out one by one, using the subaddress-examples found here. I'll let you know when I get lost...

  • Okay, I've been breaking my head over this. There is a datasheet and an extensive programming guide available, but both assume you are able to interface with the si4703 :/

    I'm using the Arduino-sketch as a guide line:

    	function si4703 ( option ) {
    		this.addr  = 0x10;
    		this.reset_pin = option.res;
    		this.data_pin  = option.dat;
    		this.clock_pin = option.clk;
    		this.sen_pin   = option.sen;
    		this.registers = new Uint16Array(16);
    	}
    
    	si4703.prototype.init = function () {
    		pinMode(this.reset_pin, 'output');
    		digitalWrite( this.reset_pin, false );
    
    		pinMode(this.data_pin, 'output');
    		digitalWrite( this.data_pin, false );
    
    		digitalWrite( this.reset_pin, true );
    
    		this.i2c = I2C1;
    		this.i2c.setup( { scl: this.clock_pin, sda: this.data_pin } );
    
    		this.readReg();
    
    		this.registers[ 0x02 ] = 0x4001; // enable IC
    		this.registers[ 0x07 ] = 0x8100; // enable oscillator
    
    		this.writeReg();
    
    		this.readReg();
    	};
    
    	si4703.prototype.readReg = function () {
    		this.i2c.writeTo( this.addr, 0 );
    		data = this.i2c.readFrom( this.addr, 32 );
    
    		for( var x=0x0A; ; x++ )
    		{
    			if( x == 0x10 ) x=0;
    			this.registers[x] = data[x] << 8;
    			this.registers[x] |= data[x];
    			if( x == 0x09 ) break;
    		}
    
    		console.log( this.registers );
    	};
    
    	si4703.prototype.writeReg = function () {
    		for( var i in this.registers )
    		{
    			if( i > 0x02 )
    				this.i2c.writeTo( this.addr, i, this.registers[i] );
    		}
    	};
    

    So,the init-method should read the registers, update a few things (mainly the ENABLE-bit), and read the registers again. However, there is no change in the actual registers (first line is the output at startup, second line is after attempting to write the new values):

    new Uint16Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4626, 16962, 2056, 0])
    new Uint16Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4626, 16962, 2056, 0])
    

    What am I missing?

  • Pulse the pin with the value for the given time in milliseconds. It uses a hardware timer to produce accurate pulses, and returns immediately (before the pulse has finished). Use digitalPulse(A0,1,0) to wait until a previous pulse has finished. [...digitalPulse() reference]

    The immediate return mays cause your problem: 'things' start to overlap. I do not know how exactly your code works, most of the time this asynchronousity things - in this case ending the pulse(s) -happens while continuing code already executes (when the pulse(s) are not extremely short - which means, that the code execution is so much slower that the pulse completed before return processing and going on to the next statement happens). Therefore, 1st, all pulses should be in one array, and after firing it, one has to wait for the time the pulses - all ons and offs - together take to complete. Since 'waiting in Espruino' has to be with setTimeout(...), it is asynchronous and you have to work with callbacks: your pulses function has to accept a callback as parameter in addition to the pulse array which you invoke after firing off the pulses array in a setTimeout(...).

  • Is there a reason you couldn't just go from 0 to 15 for the FOR loop in readReg? Currently you do 10 to 15, then 0 to 9 - but the array is sorted so I don't think it makes much difference? :)

    I think there might be some issue with how you're handling 16 bits numbers... i2c.readFrom and i2c.writeTo will only be expecting and producing arrays of 8 bit numbers, so while you have:

                this.registers[x] = data[x] << 8;
                this.registers[x] |= data[x];
    

    You'd usually want to do something like:

                this.registers[x] = data[2*x] << 8;
                this.registers[x] |= data[(2*x) + 1];
    

    Same thing with this.i2c.writeTo( this.addr, i, this.registers[i] ); in writeReg - you probably want this.i2c.writeTo( this.addr, i, this.registers[i]>>8, this.registers[i]&255 ); or something like that?

    Also, you have if( i > 0x02 ) in writeReg so I guess this.registers[ 0x02 ] = 0x4001; won't do anything, then the one command that might have worked:

    this.registers[ 0x07 ] = 0x8100;
    

    Has the bottom 8 bits (which would be all that gets set currently) as 0 - which is what you're reading.

    You could take a look at DataView which has a nicer way of handling 16 bit to 8 bit number conversion in an array - but honestly it may just complicate matters.

  • @allObjects: Perhaps I should have mentioned that I left the volume control out for the moment, since there is no sound to control yet :)

    @Gordon: you make some excellent points on 8 vs 16-bits. I'll have a look at DataView, and some YouTube tutorials on bitwise operators ;-)

  • @Thijsmans, I obviously chase a different hog... I assumed 'correct' code / logic - what ever correct means - no offense... - You breaking your head over this, I was thinking of the Espruino hidden (gem) things... ;-) ...that make coding Espruino special... and fun...

  • Fri 2018.08.17

    re: I was thinking of the Espruino hidden (gem) things... ;-)

    I couldn't remember who in fact Ack'd that comment so took a peek:

    ref 2nd pp: 'I really like your Gem comment . . . '

    http://forum.espruino.com/comments/13433­001/



    Ahhhh, yes . . . . who was that now? . . . . @allObjects

    quoted from #1 and #5:
    http://forum.espruino.com/conversations/­299035/#13433001

    Double checked and was from my very first forum post. Hard to believe that was eighteen months ago.

    Interesting how one word remains as a 'thought worm' . . . .

  • I got past the 8-bits vs 16-bits issue and I was just listening to my first song on the si4703; Phil Collings I believe...

    
    	/*
    		si4703 begins reading from register upper register of 0x0A and reads 
    		to 0x0F, then loops to 0x00. Since register 0x0A comes in first 
    		we have to shuffle the array around a bit.
    	*/
    
    	si4703.prototype.readReg = function ( verb ) {
    		//this.i2c.writeTo( this.addr, 0 );
    		data = this.i2c.readFrom( this.addr, 32 );
    
    		for( var i=0; i<data.length; i+=2 )
    		{
    			bits = data [ i ] << 8;
    			bits |= data[ i+1 ];
    
    			reg = ( i < 12 ? i+(10-i/2) : (i/2)-6 );
    			this.registers[reg] = bits;
    		}
    
    		if( verb !== false )
    			this.printReg();
    	};
    
    	/*
    		Write the current 9 control registers (0x02 to 0x07) to the Si4703. 
    		It's a little weird, you don't write an I2C addres, The Si4703 assumes 
    		you are writing to 0x02 first, then increments.
    	*/
    	si4703.prototype.writeReg = function () {
    		bytes = [];
    		for( i=0x02; i<0x08; i++ )
    		{
    			var high = this.registers[ i ] >> 8;
    			var low  = this.registers[ i ] & 0x00FF;
    			bytes.push(high, low);
    		}
    		this.i2c.writeTo( this.addr, bytes );
    	};
    
    	si4703.prototype.printReg = function () {
    		console.log( 'Register    | Dec     | Hex    | Bits');
    		fill   = String('.');
    
    		for( var i in this.r )
    		{
    			label = i;
    			dec = this.registers[ this.r[i] ];
    			hex = dec.toString(16);
    			bits = dec.toString(2);
    
    			console.log( label + fill.repeat(10-label.length ) 		+ '  | ' +
    						 dec   + fill.repeat(6-String(dec).length)  + '  | ' +
    						 hex   + fill.repeat(5-hex.length)  		+ '  | ' + 
    						 fill.repeat(18-bits.length) + bits
    				);
    		}
    		console.log(" ");
    	};
    

    This is working just fine:

    Register    | Dec     | Hex    | Bits
    DEVIDEID..  | 4674..  | 1242.  | .....1001001000010
    CHIPID....  | 2640..  | a50..  | ......101001010000
    POWERCFG..  | 50433.  | c501.  | ..1100010100000001
    CHANNEL...  | 0.....  | 0....  | .................0
    SYSCONFIG1  | 6144..  | 1800.  | .....1100000000000
    SYSCONFIG2  | 21....  | 15...  | .............10101
    SYSCONFIG3  | 0.....  | 0....  | .................0
    TEST1.....  | 48132.  | bc04.  | ..1011110000000100
    TEST2.....  | 6.....  | 6....  | ...............110
    BOOTCONFIG  | 1.....  | 1....  | .................1
    STATUSRSSI  | 28693.  | 7015.  | ...111000000010101
    READCHAN..  | 205...  | cd...  | ..........11001101
    RDSA......  | 255...  | ff...  | ..........11111111
    RDSB......  | 0.....  | 0....  | .................0
    RDSC......  | 0.....  | 0....  | .................0
    RDSD......  | 0.....  | 0....  | .................0
    

    So now onwards to an Espruino-port of the Arduino sketch!

  • That's great news! If you're ok sharing it, it'd be great to turn your si4703 object into a module that others could use.

  • Hi!
    Any example code? I need fm radio to working, please.

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Rotary encoder and digital potentiometer

Posted by Avatar for Thijsmans @Thijsmans

Actions