I found an old beaten-up CD player a while back, and decided to take out the human interface card, with the LCD and buttons on it.
In a previous post, I found out where all the connections on the PCB go and how they connect the buttons, LCD and chips together and what they do. Now, I will try to see whether I can control them.
I found an old CD player and am slowly turning it into a kitchen clock/timer, and teaching myself hacking along the way. Instances of this series are:
The backlight
I did not really understand the control of the backlight, but with help from [Ruben], I found out how to drive it.
I still don’t fully understand nor how this works nor why this is necessary, but (ignoring the capacitor) as the clock line is pulled low, the transistor is on some kind of voltage divider with 3.2kΩ (1.5kΩ+820Ω) to the ground and 1.0kΩ to Vcc. With Vcc at 5V, this sets the base at (3.2kΩ/(3.2kΩ+1.0kΩ))*5V = 3.8V. This is 1.2V below the collector voltage, more than the 0.7V needed to open a silicon PNP transistor. I sound very knowledgeable now, but this was all theoretical knowledge stuck somewhere at the back of my head since high school before I started looking at this card.
The function of the capacitor still evades me, and I would think that having a capacitor between the clock line and positive rail would damp the clock pulses — apparently the 1.5kΩ resistor prevents this and/or the clock pulse sensing in he chips is sensitive enough to prevent any problems.
Anyway: hooking Vcc and ground up to my power supply did not light the backlight. Then when I connected the clock pin 3 to ground … the backlight lit up!
This backlight draws 0.21A or over a watt@5V — so I can’t run it directly from an Arduino data pin which is rated at 20mA (less than one tenth of what is needed).
Driving the LCD
The datasheet for the LCD is quite easy to read in how to program it, but even easier, I found an example in a thread(🇳🇱) from [DimitriK] . His example is flawed but helped me along.
[DimitriK] seems to use one COP472N-3 (a variant of the WM-3) with probably 4 segments. To neatly send data to a chip you need to pull low the CS (Chip Select) line, then clock in 5*8 bits of data, then turn CS high. I had no idea how to do this clock in, but with [DimitriK]’s example I found out that it’s just a case of
shiftOut(data_pin, clock_pin, LSBFIRST, byte_toSend);
.
This shifts out (= clocking in, just from a different perspective) 8 bits of data, cycling the clock pin high, then low, after each bit.
According to the datasheet, when using only one COP472 chip, you can stop after each byte of sending data. The flaw in DimitriK’s code was that he used MSBFIRST
, then quit sending after 4 bytes. This works if, like DimitriK, you are using only one chip, but you drive the wrong segments — which is no problem if you have to find out yourself what segment corresponds to what bit.
When using 2 (or more) chips together, you first have to synchronize the chips (remember that one of the backplane lines is connected to chip 2 and the other two to chip 1), and you have to supply the full 5 byte to each chip on every update, because you need to give some control data to chips in the 4 most significant (or last sent) bits of data byte 5.
This all sounds complicated, but is clearly explained in page 4-6 of the datasheet.
The control data is quite simple: First you need to select both LCD driver chips (drive down the CS lines) and send them 1110
as control data (after the 36 bit of segment data). Then deselect both chips, select only the master chip, and send 36 bit + 0001
, deselect. The chips are now synchronized, and after this you can theoretically drive each chip independently, but have to add the correct control bits (0001
for the master and 0110
for slave).
My code (I’m programming for Arduino) is as follows: first some definitions, which I hope are self-evident.
/* COP472WM-3 test program
Original by Dimitri at 02-07-2014 found at https://forum.arduino.cc/index.php?topic=252851
Adapted by IIVQ at 02-05-2020
*/
int data_pin = 6; // set data pin
int chip_1_select_pin = 3; // set chip select pin for master chip
int chip_2_select_pin = 2; // set chip select pin for slave chip
int clock_pin = 5; // set clock pulse pin
int a_Sequence[8] = {1,2,4,8,16,32,64,128}; // list of all 8 bytes with exactly 1 bit set.
int byteToSend;
Then the Arduino setup routine, in which I set up both chips with the control bits, but also switch all segments on.
One interesting thing I found was that if you do not start out with the chip select lines high before pulling them low, the LCD seems to be less crispy, with some segments less clear on or off than others.
void setup()
{
// setup all input pins
pinMode(data_pin, OUTPUT);
pinMode(chip_1_select_pin, OUTPUT);
pinMode(chip_2_select_pin, OUTPUT);
pinMode(clock_pin, OUTPUT);
digitalWrite(clock_pin, LOW);
// start chip select pins on high. Without this, not all segments are visible clearly
digitalWrite(chip_1_select_pin, HIGH);
digitalWrite(chip_2_select_pin, HIGH);
// setup synchronisation, send the control bits 1110 to both chips
digitalWrite(chip_1_select_pin, LOW);
digitalWrite(chip_2_select_pin, LOW);
shiftOut(data_pin, clock_pin, LSBFIRST, 255); // all ones
shiftOut(data_pin, clock_pin, LSBFIRST, 255);
shiftOut(data_pin, clock_pin, LSBFIRST, 255);
shiftOut(data_pin, clock_pin, LSBFIRST, 255);
shiftOut(data_pin, clock_pin, LSBFIRST, 224+15); // 224 = 1110xxxx, 15 = xxxx1111
digitalWrite(chip_1_select_pin, HIGH);
digitalWrite(chip_2_select_pin, HIGH);
// Send control bits 0001 to only the master chip
digitalWrite(chip_1_select_pin, LOW);
shiftOut(data_pin, clock_pin, LSBFIRST, 255); // all ones
shiftOut(data_pin, clock_pin, LSBFIRST, 255);
shiftOut(data_pin, clock_pin, LSBFIRST, 255);
shiftOut(data_pin, clock_pin, LSBFIRST, 255);
shiftOut(data_pin, clock_pin, LSBFIRST, 16+15); // 16 = 0001xxxx, 15 = xxxx1111
digitalWrite(chip_1_select_pin, HIGH);
// setup complete
delay(2000);
}
In the Arduino loop, I first cycle trough all 8 bits for the first 4 groups of segments connected to the master chip, then do the same for the slave chip. Note that in this example I never drive the segments for the last four segments (in the byte 5, the same byte that holds the control data) as I’m lazy.
void loop()
{
// drive master chip
for (int i=0; i < 8; i++)
{
byteToSend = a_Sequence[i];
digitalWrite(chip_1_select_pin, LOW);
shiftOut(data_pin, clock_pin, LSBFIRST, byteToSend); //sequence for segment 1
shiftOut(data_pin, clock_pin, LSBFIRST, byteToSend); //sequence for segment 2
shiftOut(data_pin, clock_pin, LSBFIRST, byteToSend); //sequence for segment 3
shiftOut(data_pin, clock_pin, LSBFIRST, byteToSend); //sequence for segment 4
shiftOut(data_pin, clock_pin, LSBFIRST, 16); //control data 0001 + segment data 0000
digitalWrite(chip_1_select_pin, HIGH);
delay(500);
}
// drive slave chip
for (int i=0; i < 8; i++)
{
byteToSend = a_Sequence[i];
digitalWrite(chip_2_select_pin, LOW);
shiftOut(data_pin, clock_pin, LSBFIRST, byteToSend); //sequence for segment 1
shiftOut(data_pin, clock_pin, LSBFIRST, byteToSend); //sequence for segment 2
shiftOut(data_pin, clock_pin, LSBFIRST, byteToSend); //sequence for segment 3
shiftOut(data_pin, clock_pin, LSBFIRST, byteToSend); //sequence for segment 4
shiftOut(data_pin, clock_pin, LSBFIRST, 96); //control data 0110 + segment data 0000
digitalWrite(chip_2_select_pin, HIGH);
delay(500);
}
}
This code is quite crude and can be optimized enormously, but I find that when testing something new, it’s often easier to just bodge something together, then later try to optimize things. That will be something for another post though.
When I hooked this up to my Arduino and a power source … lo and behold: it worked!
I got a real Yes, I am invincible!-feeling when I saw those digits light up and move. For an experienced tinkerer, this might be a real piece of cake, but for a beginner like me, it really feels like I have advanced one rank in the army of hackers — even if it is only to have passed the selection day and being accepted into the training.
This was the first time I connected to some technology that I didn’t understand beforehand and connect to with an Arduino without just following a tutorial.
The next steps are to find out what segment is driven by what bit and doing something useful with this. That is a lot of work, but relative peanuts compared to the first driving. I just hope I don’t meet my James Bond moment and get frozen with some liquid nitrogen on the way.
Scanning the buttons
There are 13 buttons on the card and only 8 lines that connect to the shift register, and 2 towards the ribbon cable.
In the most simple electronics examples, each button gets it’s own pin on a microcontroller, but with more than a few buttons, this will take a lot of connections. That is why on situations with more buttons, something I now know is called a keyboard matrix scan is used. In this case, 13 buttons would require something like 4 row and 4 column wires, but instead, a shift register is used to control 8 row lines, and the 2 column (or sense) lines are connected to the controller on the motherboard. This makes that this sense circuit takes only 6 lines: the two sense lines, and data, power, ground and clock for the shift register. But as this interface card already had data, power, ground and clock, it requires only 2 additional lines.
There is a drawback: the keyboard can’t detect each button press instantaneously. It has to scan the keyboard. But by doing that fast enough (say: 20 times per second) it can feel near-instantaneous.
The method of scanning is that the shift register is fed data such that exactly one of it’s outputs is made high. This output connects to two buttons and if either is pressed, a connection is made to its corresponding sense line. That sense line connects to (at most) 8 buttons, but the microcontroller knows that it has set only the third bit high, and it detects a high at the second sense line, then the button at the junction betweeen the third column and second sense line is pressed.
Or at least, that’s the general theory. The setup for this board is a little different and feels more complex to me. The shift register is connected to the buttons via diodes (one for each sense line) such that current can only flow towards the shift register. So I use the inverse of what I described below: I set up the shift register to only have one column pin set low. Then the inputs on the two sense lines are pulled high by the Arduino. If a button is pressed, there is a connection from the high on the sense line, via the button and the diode to the low on the shift register, making the sense pin on the microcontroller low as well. So a low on one of the sense shows a button is pressed.
The code to achieve that is quite simple:
/* key matrix example
by IIVQ */
int data_pin = 6;
int clock_pin = 5;
int key_sense_pin_1=8;
int key_sense_pin_2=9;
void setup()
{
Serial.begin(9600); // start Serial output (just to give data back to the computer)
pinMode(data_pin, OUTPUT);
pinMode(clock_pin, OUTPUT);
pinMode(key_sense_pin_1, INPUT_PULLUP); // set the sense pin 1 as input, pulling it high
pinMode(key_sense_pin_2, INPUT_PULLUP); // set the sense pin 2 as input, pulling it high
digitalWrite(clock_pin, LOW); // make the clock pin low once
}
void loop()
{
for (int i=0; i<8; i++) {
int bitToSend=!!i; // "not not" converts int i to boolean: send a 0 when i=0, send a 1 otherwise
shiftBit(bitToSend); // send one bit to the shift register
int a = digitalRead(key_sense_pin_1);
int b = digitalRead(key_sense_pin_2);
Serial.println((String)i + " " + bitToSend + " " + !a + !b);
}
Serial.println("---------------------------");
delay(1000);
}
void shiftBit(bool data) {
digitalWrite(data_pin, data); // write one bit of data
digitalWrite(clock_pin, HIGH); // cycle the clock line high ...
digitalWrite(clock_pin, LOW); // ... then low
}
When I ran this program, opened the serial monitor and pushed some buttons, I got the following result:
One word of caution: In this example the (8-bit) shift register is the only thing I send data to and in each loop, I will first send a 0 and then 7 1’s. This means the shift register always contains exactly one 0. But on this board the same data line is shared with the two LCD driver chips. If I have just sent data to the LCD, then the shift register will contain the last 8 bits I’ve set to the LCD driver chips. So I need to first need to clear the shift register.
The Master Reset pin is tied to Vcc so can’t be used. But I can “clear” the shift register it by sending 7 1’s, before sending a 0. In fact, I can do even better by sending 7 1’s and a 0 in one go:
shiftOut(data_pin, clock_pin, LSBFIRST, 127);
The way this works is 127 is 01111111 on binary, but because the least significant bit is sent out first, in fact 11111110 is sent. Because there is already one zero in the shift register, I can immediately start scanning the first column, and then have to shift only 1’s to the shift register afterward, meaning I can omit the variable to the shiftBit()
function.
Next steps
I already thought of a good use for this card: I’m going to make a kitchen clock+timer out of it. That will take a lot of hard work, but mostly in software (I need to add a speaker and real time clock to the Arduino, both things I have done before).
To to that, I first have to set up a foundation which I can build upon: easy interfacing with the display and buttons, and then a basic clock and timer function, and then the whole idea I have in my head. But that is all time for a next post…
Leave a comment