Author Topic: QMK-ifying a Leopold FC980M  (Read 5495 times)

0 Members and 1 Guest are viewing this topic.

Offline LANS

  • Thread Starter
  • Posts: 4
QMK-ifying a Leopold FC980M
« on: Wed, 03 June 2020, 00:36:48 »
I have a Leopold FC980M that I love, but I want to add QMK to it. I'm going about this in a little different of a way than usually expected for this kind of thing - I want to avoid recreating a whole keyboard PCB.

Instead I'm attempting to make a QMK mod board that will fit in the case and connect to a bunch of bodge wires. Maybe this is more work than reproducing the entire PCB, but I want to do it.

First I buzzed out all the rows and columns - the FC980 matrix is not anything that makes the slightest bit of sense to me.





The numbers refer to the pin numbers on the cypress chip that is the stock keyboard controller.

Then I soldered on a bunch of bodge wires for rows and columns, all labeled:



Attaching it to the Salae logic analyzer, and checking how the default matrix is scanned reveals this:



Columns (channel 0, 2) are always 5V unless being pulled down by a key.
Rows (channels 1) are strobed by switching between input and I think high-impedance mode? Might be 5V output, either is equivalent for my purpose.



Next I will write some code for a 5V Arduino to decode the matrix into keys by reading the signals put out by the original chip. I don't want to desolder it quite yet, at least not until I'm confident that I have decoded the switch matrix into what key is where.

I don't want to just connect the lines to another microcontroller and use it to strobe the matrix while the Cypress chip is unpowered to avoid accidentally blowing up the internal protection diodes.

Offline suicidal_orange

  • * Global Moderator
  • Posts: 4771
  • Location: England
Re: QMK-ifying a Leopold FC980M
« Reply #1 on: Thu, 04 June 2020, 03:34:25 »
Be warned - Atmel chips commonly used on Arduinos aren't fast enough to do this.  Someone was trying to do something vaguely similar not long ago, I'd post a link but need to remember some details to have any hope of searching for it.  Edit:  Found it, they landed up using an STM32 but no details were given.

Option 2 is to take a blue flame lighter and go round the original controller chip a few times then knock it off.  That's pretty much non reversible though as you'll probably break a trace or two.

Option 3 would be a USB to USB converter - gives you TMK progammability on all but the FN key as that's handled on the board.
« Last Edit: Thu, 04 June 2020, 03:40:48 by suicidal_orange »
120/100g linear Zealio R1  
GMK Hyperfuse
'Split everything' perfection  
MX Clear
SA Hack'd by Geeks     
EasyAVR mod

Offline LANS

  • Thread Starter
  • Posts: 4
Re: QMK-ifying a Leopold FC980M
« Reply #2 on: Fri, 05 June 2020, 17:32:45 »
Well, I did the following before reading suicidal_orange's post. Turns out it worked!

@suicidal_orange: Arduinos are fast enough to do this if you do it right, as I describe below. Also I have a hot air reflow iron, I'm not taking a flame to this board when I eventually take the cypress chip off.

STM32 boards won't usually work for this as they aren't 5V tolerant.

I did originally want to do this with my Arduino mega 2560, but something's gone funky with it and I don't feel like wasting time figuring it out. I can program it with Atmel Studio and an Atmel ICE programmer through ICSP and/or JTAG, and I tested loopback on the onboard usb-serial chip and that works. Basic IO stuff like setting pins high and low works. However, whenever I try to set up any of the USART peripherals, it doesn't work. Setting one of the USART settings registers makes it hardfault, and I can't figure out why. Maybe the guy who had this thing before me managed to blow up only a tiny part of the chip without frying the rest of it. Instead I did this in two stages with an ordinary arduino.


First, I connected the row bodge wires to the arduino and ran this program I wrote, to report through serial which row was pushed when I held down a key:

Code: [Select]
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(13, OUTPUT);
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  pinMode(4, INPUT);
  pinMode(5, INPUT);
  pinMode(6, INPUT);
  pinMode(7, INPUT);
  pinMode(8, INPUT);
  pinMode(9, INPUT);
  pinMode(10, INPUT);
  pinMode(11, INPUT);
  pinMode(12, INPUT);
  pinMode(A0, INPUT);
  pinMode(A1, INPUT);
  pinMode(A2, INPUT);
  pinMode(A3, INPUT);
  pinMode(A4, INPUT);
  pinMode(A5, INPUT);

 
}
bool keypushed = false;
void loop() {
  //rows - 5V normally, go down to 0V when triggered
  if(!digitalRead(4)){
    Serial.println("8");
    keypushed = true;
  }
  else if(!digitalRead(5)){
    Serial.println("9");
    keypushed = true;
  }
  else if(!digitalRead(6)){
    Serial.println("10");
    keypushed = true;
  }
  else if(!digitalRead(8)){
    Serial.println("39");
    keypushed = true;
  }
  else if(!digitalRead(9)){
    Serial.println("40");
    keypushed = true;
  }
  else if(!digitalRead(10)){
    Serial.println("41");
    keypushed = true;
  }
  else if(!digitalRead(11)){
    Serial.println("42");
    keypushed = true;
  }
  else if(!digitalRead(3)){
    Serial.println("7");
    keypushed = true;
  }
  if(keypushed == true){
    digitalWrite(13, HIGH);
    delay(250);
    digitalWrite(13, LOW);
    keypushed = false;
  }
}

Using python, I generated a list of keys, and using the serial interface, recorded which key corresponded to which row. The program prompts me to push a key (eg: "KC_ESC") then listens for which row pin is triggered on the arduino, and records the reported row.

Code: [Select]
keydf = pd.read_csv('keylist.csv')
coltemp = []
for index, value in keydf['key list'].items():
    print(value)
    ser.flushInput()
    val = int(ser.readline().rstrip())
    print(val)
    coltemp.append(val)

keydf['column'] = coltemp
keydf.to_csv('keylist_columns.csv', index=False)


Then I disconnected the row lines, connected the column lines, uploaded the following code to the arduino, and ran the following python bit to figure out the column numbers:

Code: [Select]
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  //Serial.println("hi");
  pinMode(13, OUTPUT);
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  pinMode(4, INPUT);
  pinMode(5, INPUT);
  pinMode(6, INPUT);
  pinMode(7, INPUT);
  pinMode(8, INPUT);
  pinMode(9, INPUT);
  pinMode(10, INPUT);
  pinMode(11, INPUT);
  pinMode(12, INPUT);
  pinMode(A0, INPUT);
  pinMode(A1, INPUT);
  pinMode(A2, INPUT);
  pinMode(A3, INPUT);
  pinMode(A4, INPUT);
  pinMode(A5, INPUT);

 
}
bool keypushed = false;
void loop() {
  //columns - 5V normally, go down to 0V when triggered
  if(digitalRead(2)){
    Serial.println("35");
    keypushed = true;
  }
  else if(digitalRead(4)){
    Serial.println("12");
    keypushed = true;
  }
  else if(digitalRead(5)){
    Serial.println("37");
    keypushed = true;
  }
  else if(digitalRead(6)){
    Serial.println("18");
    keypushed = true;
  }
  else if(digitalRead(7)){
    Serial.println("17");
    keypushed = true;
  }
  else if(digitalRead(8)){
    Serial.println("29");
    keypushed = true;
  }
  else if(digitalRead(9)){
    Serial.println("31");
    keypushed = true;
  }
  else if(digitalRead(10)){
    Serial.println("19");
    keypushed = true;
  }
  else if(digitalRead(11)){
    Serial.println("30");
    keypushed = true;
  }
  else if(digitalRead(12)){
    Serial.println("20");
    keypushed = true;
  }
  else if(digitalRead(A0)){
    Serial.println("14");
    keypushed = true;
  }
  else if(digitalRead(A1)){
    Serial.println("32");
    keypushed = true;
  }
  else if(digitalRead(A2)){
    Serial.println("38");
    keypushed = true;
  }
  else if(digitalRead(A3)){
    Serial.println("36");
    keypushed = true;
  }
  else if(digitalRead(A4)){
    Serial.println("13");
    keypushed = true;
  }
  else if(digitalRead(A5)){
    Serial.println("11");
    keypushed = true;
  }
 
  if(keypushed == true){
    digitalWrite(13, HIGH);
    delay(250);
    digitalWrite(13, LOW);
    keypushed = false;
  }
}

Code: [Select]
keydf = pd.read_csv('keylist_columns.csv')
rowtemp = []
for index, value in keydf['key list'].items():
    print(value)
    ser.flushInput();
    val = int(ser.readline().rstrip())
    print(val)
    rowtemp.append(val)

keydf['row'] = rowtemp
keydf.to_csv('keylist_both.csv', index=False)


Now I have a list of what key codes correspond to what row and column lines (in terms of pin numbers on the Cypress chip). See: https://pastebin.com/raw/k45zsZxT

This needs to be translated to 0->7 rows and 0->15 columns.

Code: [Select]
keydf = pd.read_csv('keylist_both.csv')

rows = [7,8,9,10,39,40,41,42]
for i,elem in enumerate(rows):
    keydf['row'].replace(elem, i, inplace=True)

columns = [11,12,13,14,17,18,19,20,29,30,31,32,35,36,37,38]
for i, elem in enumerate(columns):
    keydf['column'].replace(elem, i, inplace=True)

keydf.to_csv('keylist_renumbered.csv', index=False)


This can be converted to a "key grid" that can be easily formed into a QMK-compatible keyboard format:

Code: [Select]
#convert from key list to key grid
keydf = pd.read_csv('keylist_renumbered.csv')
matrix = np.empty((8,16), dtype=object)

for index, elem in keydf.iterrows():
    i = elem['row']
    j = elem['column']
    matrix[i][j] = elem['key list']

#add KC_NO
matrix[matrix == None] = 'KC_NO'

#export
a = pd.DataFrame(matrix)
a.to_csv('qmk_matrix.csv', index=False, header=False)


Now we have a QMK-style key matrix that works with the ridiculously convoluted routing on the stock PCB. You can find it at:
https://pastebin.com/raw/1mWe4GfA

Offline LANS

  • Thread Starter
  • Posts: 4
Re: QMK-ifying a Leopold FC980M
« Reply #3 on: Sat, 06 June 2020, 22:26:12 »
Some more progress to report:


https://github.com/lanslans/qmk_firmware/tree/master/keyboards/fc980_bluepill


Haven't tested it with the keyboard plugged in, but I have a blue pill now programmed with QMK set up for what I think is the right settings to hook up to the keyboard matrix.

Connections are as follows:
Code: [Select]
Rows (Cypress) QMK Row Index STM32 Blue Pill Pin
7 0 A4
8 1 A5
9 2 A6
10 3 A7
39 4 B1
40 5 B1
41 6 B10
42 7 B11
Columns (Cypress) QMK Column Index
11 0 B12
12 1 B13
13 2 B14
14 3 B15
17 4 A8
18 5 B3
19 6 B4
20 7 B5
29 8 B6
30 9 B7
31 10 B8
32 11 B9
35 12 A0
36 13 A1
37 14 A2
38 15 A3


Offline LANS

  • Thread Starter
  • Posts: 4
Re: QMK-ifying a Leopold FC980M
« Reply #4 on: Sun, 07 June 2020, 19:36:54 »
It's done!







Yes, it's an ugly mess, and I left on some bits of tape that used to be tags numbering which line was what, but it's all hidden inside the case so that doesn't really matter.



Some time in the future if I have time I'll replace that small board where the USB lines are soldered on with a custom board carrying an nrf52 or something, and then I can put a battery where the blue pill is. That's far off, this is good enough for now.


 ;D