Friday, August 31, 2012

Volvo head units (radios...)

Intro


I decided, that I'm tired of using CDs in my car. However it's got a quite old unit (2002 I think) and it cannot play mp3 and it doesn't have any aux or line-in input. And no, I don't want to replace it as it has astonishing sound quality (6 speakers + "central" sub-woofer) and great design and radio works in an unbelievable way. And integrates with the rest of the car nicely.
So I decided to make line input or a player that can connect to HU. 

HU story

I have HU-605 which has cassette player, AM/FM radio with all what can radio have and a CD. There are number of HU-xxx units and they all share the (more or less) same design. All parts are modules, and (I suppose) communicate with master controller through some sort of a bus. How do I know? Well, most units have DIN-8 connector a the back that allows you to connect something. And that something usually meant CD-changer. But if you look closely to the LCD (in test mode you can light-up all all segments) you will notice that there is a number of input sources possible:
(note, this is HU-650 display, note the lack of MD-CHGR channel)
And my HU has FM, AM, TAPE and CD channels "lit" during the normal operation. When I disassemble it (it's quite modular) and I remove tape deck and power it on - TAPE disappears! That's why I think all the modules share same bus and controller piece only, surprisingly, controls them. 
So we have a HU unit supporting many external (and internal) devices  - and a 8-pin DIN connector on the back to connect something.
Ready made devices
In general we have two classes of devices:
  • iMIV - created by smart guy called mrg_Ed from swedespeed.com forums - this adapter has... all (iPOD support, video support, song names etc., all); and is costly ;)
  • chinese/other adapters emulating CD-changer from folders on SD card and are not that cheap (300PLN)
So I decided I'll try to make my own. Starting from simple "line in". 

HU protocol

is not public. There are not that many people around the globe that know it or have some documentation. It's known as "melbus". 

Hardware layer

There are 3 lines involved - data, clock and busy. Signal level is 0-5V, so it can be almost directly connected to microcontroller. Note that inside HU itself there are specialized drivers based on complementary pair of transistors (push-pull). But I guess if you don't want lines having long meters you can safely use typical cmos microcontroller pin. As far as I managed to discover clock is generated by master (HU) and both data and busy is bi-directional. Busy is mainly pulled low by devices to inform master of presence. Data is bi-directional data bus.
DIN8 pinout:

  1. Clock 
  2.  Signal ground
  3.  Run - +12 when radio is on? Can be used to power-up accessory?
  4.  Data
  5.  Busy
  6.  Left analog audio
  7.  Right analog audio
  8. Mono audio (navigation, phone etc.)
This will be continued...









Thursday, August 16, 2012

LaserJet 4 panel

I recently (huh 2009) got my hands on LJ4 control panel. As electonic engineer I decided to work out how it works and connect it to some microcontroller and make it work for yet-to-be-defined MY purpose :)

Hardware

It consists of keyboard (8 keys), 3 LEDs and nice looking a little bit retro alphanumeric VFD display (1 line by 16 characters ). Glance at the board reveals that it has voltage converter for VFD and 3 ICs. One is M66004FP - mitsubishi VFD controller, HC595 serial in-parallel out shift register and HC165 parallel in serial out shift register. Board is connected with the rest of the world with 10 wire ribbon cable. not saying more I've discovered pinout to be:
Pin#description(M66004/HC595/HC165)
1GND
2GND
3GND
4+5V
5+5V
6CSK/SRCLK/CLK
7SDATA/SER/-
8~CS/-/-
9-/RCLK/~LD
10-/-/QH
I've used TI's datasheets for HCxxx and pin names are taken from them, for M66004FP I've used original mitsubishi datasheet.

LEDs are connected to bits F, G, H (three MSB) of HC595 (active low - 0 in register makes led light up) and keys are connected to inputs of HC165 (active high - i.e. pressed key is loaded as hi to register).
(taken with shitty phone and after double re-compression, sorry for the quality)
VFD driving
The most easy task, first - grab datasheet, M66004FP is very easy to drive, and datasheet is concise and useful. Most (well, all except custom character definition) commands are one byte long and "use" empty space in ascii character map. In a nutshell to send byte to M66004FP you need to:
  1. pull ~CS low (this enables M66004FP) [pin 8]
  2. clock data using CSK [pin 6] and SDATA [pin 7] lines (bits are latched on rising CSK edge)
  3. pull ~CS high [pin 8]
That's it. One interesting note - VFD has special cursor/undeline segment under each digit and they can be driven independently (vide video).

Leds and keyboard

And now for something completely different - reading keyboard and driving LEDs. Reminder: LEDs are connected to 3 MSB of serial-to-parallel shift register HC595, keyboard is connected (8 keys - 8 bits) to parallel in-serial out shift register HC165. LEDs ar light up by 0 in register. Pressing a key forces hi (1) state on register input. And now the tricky part - both registers share clock signal (SRCLK/CLK), and load line (RCLK/~LD). Unfortunately according to me it's impossible to independently read keys and drive LEDS. Which means that in your program you'll need ugly global variable to store LED state.
Procedure is as follows:
  1. clock in 8 bits (they go to HC595) - 3 MSB define LED state
  2. drive RCLK/~LD high,low,high (which clocks data to HC595 outputs - setting LEDs and loads keyboard state to HC165)
  3. clock 8 bits of data from HC165 register, data is available on pin 10 of connector (QH)

Prototype

I used one of the most popular microcontrollers - PIC16F84 (or F628). And very nice programming language - similar to nothing but very well fitting 16F84 - JAL.
Pins:
A0 - serial input (uC input)
B0 - sclk (uC output)
B1 - sdata (uC output)
B2 - ~LD (uC output)
B3 - M66004FP CS (uC output)
It looks like this:


Source code:

--
-- name : hp_panel.jal
-- author : misio
-- date : 2009
-- purpose : poc driving hp lj panel
-- author : misio
-- public domain

-- target configuration: 16f84 with 4 Mhz Xtal
include 16f84_4

-- standard library
include jlib

-- two LEDs that are "on board"
var volatile bit led1 is pin_b7
var volatile bit led2 is pin_b6

-- panel connection
var volatile bit sclk is pin_b0
var volatile bit sdata is pin_b1
var volatile bit data_ld is pin_b2
var volatile bit m6600fp_cs is pin_b3
var volatile bit serin is pin_a0

var byte i

procedure clock_byte( byte in b ) is
 var bit x at b : 7
 for 8 loop
  delay_1us( 100 )
  sclk = off
  sdata = x
  delay_1us( 100 )
  sclk = on
  b = b << 1 

 end loop 
 sclk = off 
end procedure 

procedure send_m6600fp( byte in b ) is 
 data_ld = on 
 m6600fp_cs = off 
 clock_byte( b ) 
 delay_1us( 20 ) 
end procedure 

procedure write_m6600fp( byte in b ) is 
 send_m6600fp( b ) 
 m6600fp_cs = on 
end procedure 

procedure goto_m6600fp( byte in b ) is 
 send_m6600fp ( 0x_E0 | (b & 0x_0F) ) 
 m6600fp_cs = on 
end procedure 

procedure show_byte( byte in b ) is 
 var bit v at b : 0 
 goto_m6600fp(0) 
 write_m6600fp("[") 
 for 8 loop 
  if ( v == on ) then 
   write_m6600fp(0x_7F) 
 else 
   write_m6600fp(" ") 
 end 
 if b = b >> 1
   end loop
 write_m6600fp("]")
end procedure

procedure send_hc595( byte in b ) is
 m6600fp_cs = on
 data_ld = off
 clock_byte( b )
 data_ld = on
 delay_1us( 100 )
 data_ld = off
end procedure


function get_kbd_set_leds ( byte in m ) return byte is
var byte n
var byte b

m6600fp_cs = on
data_ld = off
clock_byte( m )
data_ld = on
delay_1us( 100 )
n = 7
b = 0
for 8 loop
 sclk = off
 delay_1us ( 100 )
 if serin == on then
  b = b | ( 1 << n ) 

 end if 
 sclk = on 
 delay_1us( 100 ) 
 n = n - 1 
end loop 
return b 
end function 

-- ------------------ -- MAIN PROGRAM -- ------------------ 
var byte led_status 

assembler 
 bsf 03, 5
 bcf 01, 7 
 bcf 03, 5 
end assembler
-- pin directions init 
 pin_b7_direction = output 
 pin_b6_direction = output 
 pin_b0_direction = output 
 pin_b1_direction = output 
 pin_b2_direction = output 
 pin_b3_direction = output 
 pin_a0_direction = input 

delay_1ms( 300 ) 
send_hc595( 0x_FF ) 
write_m6600fp( 0x_F3 ) -- test mode - all on 
write_m6600fp( 0x_07 ) -- 16 digits 
write_m6600fp( 0x_0F ) -- dim max 
write_m6600fp( 0x_F6 ) -- 128/fosc 
-- delay_1s( 1 ) 
-- write_m6600fp( 0x_F7 ) -- 128/fosc 
-- write_m6600fp( 0x_08 ) 
write_m6600fp( 0x_F1 ) -- test mode - normal 
write_m6600fp( 0x_80 ) -- cursor off @ 0 
write_m6600fp( 0x_11 ) -- cursor on @ 1 

write_m6600fp( "-" ) 
write_m6600fp( "=" ) 
write_m6600fp( " " ) 
write_m6600fp( "D" ) 
write_m6600fp( "u" ) 
write_m6600fp( "p" ) 
write_m6600fp( "a" ) 
write_m6600fp( " " ) 
write_m6600fp( "b" ) 
write_m6600fp( "l" ) 
write_m6600fp( "a" ) 
write_m6600fp( "d" ) 
write_m6600fp( "a" ) 
write_m6600fp( " " ) 
write_m6600fp( "=" ) 
write_m6600fp( "-" ) 

-- endless loop 
-- i = 0 
-- for 32 loop 

-- send_hc595( ! (1 << i) ) 
-- i = i + 1 
-- if i > 7 then
-- i = 0
-- end if
-- delay_1ms( 200 )
-- end loop
-- send_hc595 ( 0x_FF )


led_status = 1
forever loop
 i = get_kbd_set_leds ( led_status )
 if (i & 1) == 1 then
  while ( i & 1 ) == 1 loop
   i = get_kbd_set_leds ( led_status )
  end loop
  led_status = led_status << 1 

  if led_status == 0 then 
   led_status = 1 
  end if 
 end if 
-- i = read_hc165 
 show_byte( i ) 
 led1 = on delay_1ms( 50 ) 
 led1 = off
end loop -- final, never goes below 

forever loop 
 i = 0 
 for 8 loop 
  write_m6600fp( 0x_10 + i ) 
  write_m6600fp( 0x_10 + 15 - i ) 
  delay_1ms(100) 
  write_m6600fp( 0x80 + i ) 
  write_m6600fp( 0x80 + 15 - i ) 
  i = i + 1 
 end loop 
 i = i - 1 
 for 7 loop 
  write_m6600fp( 0x_10 + i ) 
  write_m6600fp( 0x_10 + 15 - i ) 
  delay_1ms(100) 
  write_m6600fp( 0x80 + i ) 
  write_m6600fp( 0x80 + 15 - i ) 
  i = i - 1 
 end loop 
-- delay_1s 
-- led1 = on 
-- delay_1s 
-- led2 = on 
-- delay_1s 
-- led1 = off 
-- delay_1s 
-- led2 = off 
end loop

Wednesday, August 15, 2012

Raspberry pi and i2c gpios

Intro

As Pi arrived I started playing with it.  I know I have plenty of GPIO pins on it's connector, however I was interested in using either SPI or I2C interface to connect "something". I quickly hacked old, lying around, board with pcf8574 i2c expander (connected to 8 LEDs) and connected it to Pi I2C lines (and ground and +3.3V). And the fun started... I had no previous experience in I2C and GPIOs on linux. 

Making I2C work

First thing I've learned is that kernel needs to be replaced, as wheezy one does not support I2C. I used Chris Boot's one: http://www.bootc.net/projects/raspberry-pi-kernel/
(procedure is there requires firmware update, is rather easy and automated).
After booting to new kernel I expected to have /dev/i2c-0 and /dev/i2c-1 devices (the BCM chip raspberry bases upon has two I2C interfaces). Well... no, devices were not created, creating them by hand (mknod /dev/i2c-0 c 89 0) did not help much - which means there is no driver in kernel is to service those. After digging and reading parts of kernel source I finally knew what is going on.
to have i2c devices visible in userspace (/dev/*) you need the following:

  • hardware I2C module (i2c_bcm2708, this exposes hardware in a standard way to kernel internals)
  • i2c-dev module (exposig /dev/i2c-x entries)

So after doing:
modprobe i2c_bcm2708
modprobe i2c-dev
I had desired device entries in /dev/

Using I2C directly

As I already had pcf8574 connected I wanted to try it, the key to success was i2c-tools package that contains (among others) i2cdetect and i2cset programs.
As I was too lazy to check what address is set on pcf and what datasheet says about it's bus address so I run i2cdetect (warning - this tool reads (and writes?) whole i2c bus which can harm some hardware (older thinkpads for example...), see man) as follows:

root@raspberrypi:~# i2cdetect -a 0
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-0.
I will probe address range 0x00-0x7f.
Continue? [Y/n] y
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- 27 -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
It was clearly visible that my pcf8574 had address of 0x27. From now on I was able to write it like that:
i2cset -f -y 0 0x27 1 <value>
So to have blinking LEDs I used the following one-liner:
while : ; do i2cset -f -y 0 0x27 1 0xaa && sleep 0.3 &&  i2cset -f -y 0 0x27 1 0x55 && sleep 0.3; done
(as you can see raspberry wheezy has sleep with fraction support!)

Dedicated module

So far so good, but this is still direct I2C data banging with i2cset (which is sort of acceptable), but we have a nice module gpio_pcf857x for pcf8574 expanders - as they are quite popular. 
Loading the module directly did not work as expected - you need to provide bus address of your pcf8574 to the module, easiest way I found is:
echo pcf8574 0x27 > /sys/class/i2c-adapter/i2c-0/new_device
which tells I2C kernel subsystem that there is a pfc8574 @ 0x27. This causes auto-loading of gpio_pfc857x module into kernel. 

/sys/class/gpio

Loading this module also creates entry in /sys/class/gpio:
root@raspberrypi:/sys/class/gpio# ls -l
total 0
--w------- 1 root root 4096 Aug 15 18:19 export
lrwxrwxrwx 1 root root    0 Aug 15 17:54 gpiochip0 -> ../../devices/virtual/gpio/gpiochip0
lrwxrwxrwx 1 root root    0 Aug 15 18:09 gpiochip248 -> ../../devices/platform/bcm2708_i2c.0/i2c-0/0-0027/gpio/gpiochip248
--w------- 1 root root 4096 Aug 15 18:01 unexport
It's clear that gpiochip248 is our expander, the 248 is the base (also can be read from base file from within gpiochip248 directory. 
And that's pretty it - now we can make specific gpios visible in /sys/class/gpio, to do this use export and unexport entries. 8 gpios on pcf have numbers: 248...255 (base+8 to base+7)
to export a gpio do:
echo 248 > /sys/class/gpio/export
this causes :
lrwxrwxrwx 1 root root    0 Aug 15 18:18 gpio248 -> ../../devices/platform/bcm2708_i2c.0/i2c-0/0-0027/gpio/gpio248
to appear
inside we find:
root@raspberrypi:/sys/class/gpio/gpio248# ls -l
total 0
-rw-r--r-- 1 root root 4096 Aug 15 19:21 active_low
lrwxrwxrwx 1 root root    0 Aug 15 19:21 device -> ../../../0-0027
-rw-r--r-- 1 root root 4096 Aug 15 18:19 direction
-rw-r--r-- 1 root root 4096 Aug 15 19:21 edge
drwxr-xr-x 2 root root    0 Aug 15 19:21 power
lrwxrwxrwx 1 root root    0 Aug 15 18:18 subsystem -> ../../../../../../../class/gpio
-rw-r--r-- 1 root root 4096 Aug 15 18:18 uevent
-rw-r--r-- 1 root root 4096 Aug 15 18:19 value
in this very simple example (pcf8574 is quite simple) we are interested in direction and value files. Direction controls, well, direction of a pin, and value file is used to read or write value (to write gpio needs to be an output...). So to write a 1 to pin do the following:
echo "out" > /sys/class/gpio/gpio248/direction
echo 1 > /sys/class/gpio/gpio248/value
then to set it to 0 do:
echo 0 > /sys/class/gpio/gpio248/value

More info on gpio on linux can be found here: http://www.kernel.org/doc/Documentation/gpio.txt