tmk / tmk_keyboard

Keyboard firmwares for Atmel AVR and Cortex-M
3.98k stars 1.7k forks source link

Incoherent behaviour with two M0110s and a M0110A #6

Closed skagon closed 12 years ago

skagon commented 12 years ago

I've got three early Mac keyboards here. As title states, two M0110 (plain vanilla Mac) and one M0110A (Mac Plus). The problem is, apart from the M0110A not working properly, which is to be expected, that one of the 0110s and specifically the (sort of) newer one works perfectly, while the second, older, one is exhibiting delays, i.e. keypresses arrive slowly. The 0110A is even worse in that respect. I'm using plain vanilla code with Teensy 2.0. Let me explain in greater detail. Using HID_listen, I tried to analyse the behaviour of each of the keyboards. M0110 new:

M0110 old:

M0110A:

I have to add that the M0110A keyboard has been thoroughly cleaned (after first connecting it and seeing the huge lag), all keys have been checked with a continuity metre and are working perfectly (and switch instantly), all lines from the keyboard microcontroller have been checked and double-checked for shorts and there's no problem there either. On the other hand, the working M0110 hasn't been cleaned nor checked in any way, apart from replacing a couple of keys, and it was working perfectly even before that (well, apart from the aforementioned couple of problematic keys). The cables all work perfectly. Yes, I checked them.

I'm wondering whether it's a problem with the timings, but from what I can see, all data 'events' in the code are not reliant on timing, timing is there just as a time-out, not a latch trigger. I was also wondering whether it could be a problem with the matrix overloading, but again, I can't see the connection in the code. I'll try to relax the timings and see if that helps any. In the meantime, please, don't hesitate to offer advice! Thanks!

tmk commented 12 years ago

I think you are right and problem is clock timing. Changing delay time of WAIT macro in m0110_send and m0110_recv may helps.

From curiosity I'd like to ask some. I wonder how you can distinguish old and new M0110. Is there some difference in appearance? Your M0110s looks like this? http://www.lowpoly.com/keyboard/m0110/m0110_110.jpg And which country of origin does your M0110A has? I know there are many for M0110A like USA, Japan, Taiwan and Malaysia at least.

skagon commented 12 years ago

Thanks for the input. That was exactly what I was going to do. I'll let you know how it goes.

Now, on to the question. The picture you linked shows exactly my point. The old one is on top, with two lines of ventilation holes, the newer one is on bottom, with just holes at the bottom/front. I can verify the "age" because of the markings on the PCB as well as the production dates on the microcontrollers. Of course, there's also the serial numbers. My M0110A came from the UK, but it was made in Japan. However, I have verified the scan codes and it's in agreement with the Apple documentation for the 0110 plus keypad, with a few minor differences, since the 0110A has a few extra keys. Both the M0110s were made in the USA. Another point of interest is the microcontroller itself, which in the old 0110 is an Intel (8052 or something), a NEC 48H517 (from what I could find, a derivative of the Intel 8048) in 0110A's case, and I can't remember what the newer 0110 had, but it was not Intel. Just because I looked closer, I know the 0110A's microcontroller is clocked at 6MHz. I guess I have to check out the frequencies of the 0110s, since that could be an important reason for the errors. From what I know so far, I am forced to assume that the newer 0110 has a faster microcontroller, or it's working closer to the original Apple specs for the communication protocol between keyboard and host.

The other assumption I have is that the presence of the keypad on the 0110A is slowing down the communication. If you read the specs, the original 0110 plus keypad was daisy-chained. The "correct" way was that the Mac would query the keypad, then the keypad would query the keyboard, the keyboard would reply to the keypad and then the keypad would respond to the Mac. Since each query would incur a 1/4 second delay, the combination of keypad and keyboard would effectively reply to the Mac in 1/2 second, just in time before the Mac would time out and reset the keypad/keyboard. Since the 0110A keyboard is essentially emulating the keypad+keyboard combination, maybe it's also replicating the delay? I don't know, it seems a long shot, but without an oscilloscope or a logic analyser, I can't prove that theory. Unless I can activate some sort of "super-debug" mode in the driver and have it report the delays back to me.

Incidentally, I noticed that the firmware doesn't use the model number returned by the keyboard. Maybe that could be the way to adjust the timings and differentiate between a plain 0110 and an 0110A.

tmk commented 12 years ago

Thanks for the detailed info! My M0110 seems to be new one and has GI PIC1657-824. http://i.imgur.com/VGZVg.jpg

After some wandering I found scan of Tech Info for the Mac at: ftp://ftp.apple.asimov.net/pub/apple_II/documentation/macintosh/Mac%20Hardware%20Info%20-%20Mac%20128K.pdf ftp://ftp.apple.asimov.net/pub/apple_II/documentation/macintosh/Mac%20Hardware%20Info%20-%20Mac%20Plus.pdf This FTP site has great archive for of Apple and Mac!

And after read the Tech Info again I found my code doesn't comply with the communication protocol spec. Unless I read it wrongly we must read data line at falling edge while the code does at rising edge. Looks like I didn't read the spec thoroughly and might look only signals on my scope. This may cause your M0110 problem. Try following patch and let me know the result.

The spec says like this:

When sending data to the Macintosh, the keyboard clock transmits eight 330-usec cycles (160 usec low, 170 usec high) on the normally high clock line. It places the data bit on the data line 40 usec before the falling edge of the clock line and maintains it for 330 usec.

@@ -205,10 +212,10 @@ uint8_t m0110_recv(void)
     for (uint8_t i = 0; i < 8; i++) {
         data <<= 1;
         WAIT(clock_lo, 200, 2);
-        WAIT(clock_hi, 200, 3);
         if (data_in()) {
             data |= 1;
         }
+        WAIT(clock_hi, 200, 3);
     }
     idle();
     if (data != M0110_NULL) {
skagon commented 12 years ago

Taking it from the bottom... it doesn't work. The idea is sound, however it looks like that's not the problem.

On a tangent now, unsurprisingly, my new 0110 is using the same microcontroller as yours. Same model, even same firmware 342-0308-A, mine's only newer (8638). Which pretty much explains why that keyboard is working flawlessly. What the hell have you done to yours, mate? Where are the keys?

Anyway, back to the issue. I'm beginning to think that it's more of a systemic problem rather than a simple matter of timings. I tried the "patch", which, as I said, makes sense. I've also tried extending the waits to rather big numbers. However, even though the 0110 that worked (I'll call this from here on, 6b) still works, neither the 0110 that sort of worked (hereafter called 6a) works better, nor the 0110A (called A from now on) works any better. I've tried a few things, even inserting "prints" to see what's going on (by the way, that wreaked havoc with the timings and even the 6b stopped working). The strange thing is that under normal operation, I can't even see the 6b reporting its model code. Don't know why. It just doesn't. Sometimes, I can see the 0x7D it reports after the self-test. Usually, not. And it's damn strange that the 6b doesn't report any errors at all on connection or disconnection, while the other two throw errors. I even added a tiny bit of code to show in which bit the error occurs in reception. Usually, the receive errors are in either the 7th or 8th bit. Even funnier is, the A, even though it's slow as hell, doesn't report ANY errors at all. It just delays a whole damn lot. The worst are a few buttons on the keypad, that transmit three bytes per action: 0x71 (shift down), 0x79 (keypad) and then, say, 0x0D (keypad plus down) and then 0xF1 (shift up), 0x79 (keypad) and then 0x8D (keypad plus up). The whole process can (and usually does) take more than 6 seconds. Given that the keypad and arrows return the 0x79 code, I think that's the first indication of a systemic problem in the code. According to the documentation, when using a keypad, the Mac is connected to the keypad and the keyboard is connected on a secondary port on the keypad. Therefore, the keypad acts as a keyboard to the Mac and as the Mac to the normal keyboard. So, when the Mac asks the keypad for any pressed keys (that happens every 1/4 of a second), then the keypad, if there are no keys pressed on it, asks the keyboard. Then, the keyboard replies to the keypad and the keypad replies to the Mac. However, the keyboard has the bad habit of waiting for 1/4 of a second if there are no keys pressed. However, if the keypad does have an event to report on itself, it just sends the 0x79 code (unless it's one of the four exceptions), upon which the Mac should immediately send back an "instant" command, and then the keypad will return the scan code for the key pressed or released. That design alone makes those few keys on the keypad that first report 0x71 (shift pressed) and then the 0x79 code. That means that when the host (Mac or Teensy) asks, it will get the reply 0x71 and the 0x79 will be reported to it 1/4sec later, and the actual keypress a few msecs later. Thankfully, those keys are only 4: "=", "/", "*" and "+", but they should by design be somewhat slower.

Anyway. I don't know what to do next, since I haven't examined all the code. I still haven't figured out how it all functions. I guess the next place I'll be looking is the actual command sequence that regulates the "asking the keyboard every 1/4 sec for new key events" and maybe adding the necessary code handling the 0x79 and the prerequisite "immediate" command that should follow it. But I have a feeling this will require some redesigning of some routines. You see, the documentation I have, shows that the keyboard is connected to a chip called "VIA" (Versatile Interface Adapter), which throws an interrupt when it detects a clock low from the keyboard. From what I can see, right now the implementation in the code here is "send command and wait for clock low on pin #whatever". I don't know... I'll have to check the code and see if there are any other parts which don't exactly conform with Apple's directives.

And as you might have noticed by now, I have a tendency to write loooooooooong posts... :D

skagon commented 12 years ago

Oh, you might want to check this out, if you haven't already: http://www.pagetable.com/?p=50

tmk commented 12 years ago

I did misread the spec, ignore the patch in my previous post. BTW, do you have pull-up resistors on data and clock lines? Without these you may have bad communication between Teensy and keyboard in particular when you use long or coiled cable. I think it is safe to have two pull-up resistors on both lines.

What the hell have you done to yours, mate? Where are the keys?

Hehehe, I desoldered all switches to lubricate old ALPS and do silencing mod. http://i.imgur.com/PeUqa.jpg

The strange thing is that under normal operation, I can't even see the 6b reporting its model code. Don't know why. It just doesn't. Sometimes, I can see the 0x7D it reports after the self-test. Usually, not.

Looks like it needs some delay before these commands.

@@ -158,12 +165,13 @@ void m0110_init(void)
 {
     uint8_t data;
     idle();
-    _delay_ms(255);

+    _delay_ms(1000);
     m0110_send(M0110_MODLE);
     data = m0110_recv();
     print("m0110_init model: "); phex(data); print("\n");

+    _delay_ms(1000);
     m0110_send(M0110_TEST);
     data = m0110_recv();
     print("m0110_init test: "); phex(data); print("\n");

I don't have M0110A and I can't try anything about keypad part :( I read the spec about keypad communication protocol but I don't understand it so clearly. I tried INSTANT command instead of INQUIRY this just work fine with my M0110. I wonder if this INSTANT command mitigates the problem of M0110A.

diff --git a/m0110.c b/m0110.c
index f4f5223..c1ffcb8 100644
--- a/m0110.c
+++ b/m0110.c
@@ -226,7 +236,7 @@ ERROR:
 uint8_t m0110_recv_key(void)
 {
     uint8_t key;
-    m0110_send(M0110_INQUIRY);
+    m0110_send(M0110_INSTANT);
     key = m0110_recv();
     if (key == 0xFF || key == M0110_NULL)
         return M0110_NULL;

diff --git a/m0110.h b/m0110.h
index f5c1e21..7242eb9 100644
--- a/m0110.h
+++ b/m0110.h
@@ -55,7 +55,7 @@ POSSIBILITY OF SUCH DAMAGE.
 #endif

 #define M0110_INQUIRY     0x10
-#define M0110_INSTNAT     0x14
+#define M0110_INSTANT     0x14
 #define M0110_MODLE       0x16
 #define M0110_TEST        0x36
skagon commented 12 years ago

Actually, I have some news. It still doesn't work, after all the changes. Theoretically, latching the data on clock low is a bit risky, since the data should have just come out on the data line. From my interpretation of the data, the Macs would latch the data on the low-to-high. In any case, it still works just as well (or just as bad) in either case. The "instant" command should work just as well, the only difference is that it instructs the keyboard to answer back right away, instead of waiting for 1/4 sec, if there's no events to report. If there are events to report, "instant" and "inquiry" should have the same effect.

Another thing is puzzling, though. I did add the pull-up resistors, like you said. Nothing happened. I am using the original Apple coiled cable, by the way. I found this: http://museum.dyne.org/gallery/apple/stuff/mac/andreas.kann/MacPlus2.GIF where you will notice that the Mac motherboard only had the clock pulled up; the data line was left alone. I used the same 47kΩ resistor on the clock. No change on either keyboard. Then, I changed the port from F to B, since Atmel's datasheet says that port B "has better driving capabilities than the other ports". I also re-did the clock-hi/lo/in and data-hi/lo/in functions, because -- in my opinion -- there were some small errors. I have to say, that did help a bit, since it made all the keyboards work a bit better. Then, I added a proper check in the m0110_init function, where it won't move until a proper model number is returned from the "model" command, and a 0x7D is returned from the "test" command. At least, that way we can be sure that the keyboard has initialised properly. Unfortunately, I still haven't had the time to examine in detail the keymap functions and how they're used, so that I can add the extra keys from the A keyboard. I still don't understand how that works.

Anyway, then I thought I'd add a couple of LEDs on both the clock and the data lines, just to see what's going on. Don't worry, I didn't short-circuit anything, I know what I'm doing. So... I connected both anodes to Vcc through a limiting resistor and the cathodes to clock and data lines, so that the LEDs stay off when the buses are idle (i.e. high) but blink whenever one line goes low. So, here's the weird part. When I connect either 6a or A keyboards (the two that don't work correctly), I can see the LEDs blinking, sort of slowly. Like, when there's no key pressed on the keyboard, I can see the data line LED blinking twice as fast as the clock line. Which is rather puzzling, since you'd expect both to be flashing more or less simultaneously. But here's the interesting part: the 6b, which is working nicely (still), will blink its LEDs frantically when there's key activity. However, both the A and the 6a seem to be taking their sweet time. Especially the A, when I press one of the keys that return 3+3 bytes, seem to be doing that at their leisure. Same thing goes, of course, between key press and release. It's like, the keyboard is "stalling" between sending data. Or, you could say it's stalling when clocking out. Actually, I was even suspicious of the clock itself, maybe being too weak or noisy, so I added a schmitt buffer on a breadboard with the Teensy, so that the keyboard's clock would go through the buffer and then into the port. That didn't help at all. After that, looking at the code I realised that maybe, just maybe this damn microcontroller has a different routine (I mean the NEC of the A keyboard and to a lesser extent the Intel of the 6a), maybe they're slower, I don't know, and take longer to start clocking after the data line goes low (I'm talking about the send function). So, I increased the wait for the clock from 1000 to 3000. Well, essentially, 2500 works just as well. Actually, that removed quite some of the lag. I'd say, it cut back around 50%.

So... that's all for now. Actually, I've been trying to write this post for the past four days, and every time I start, I get a new idea on something else to try. Actually, I think at some point I should post the changes I've made so far to the code. :) Any more ideas?

skagon commented 12 years ago

Oh yeah, I forgot to mention, at some point I got desperate and thought that maybe there's some problem with the microcontroller of the A keyboard itself. So I... ehm... desoldered the damn thing off the keyboard PCB, cleaned the PCB and soldered a DIP socket in its place. Then, of course, I put the microcontroller back on the new socket. Yeah, I know... a bit excessive. But I did it nonetheless.

I've also tried a lot of other things. Like, add a few delays here, remove a few delays there... things like that. But, like I said, the only things that helped were two (that I could tell): re-working the clock and data ins and outs and adding a lot more delay to the wait for low clock in the read function. Well, in any case, the clock high and low are never used; the only thing that's used is the clock in, as you obviously know. Just saying.

void m0110_init(void)
{
    uint8_t data;
    idle();

      _delay_ms(1000);

    do
    {
      m0110_send(M0110_MODEL);
      data = m0110_recv();
     } while (data == 0xFF);

    print("m0110_init model: "); phex(data); print("\n");

    _delay_ms(100);
    do
    {
      m0110_send(M0110_TEST);
      data = m0110_recv();
    } while (data != 0x7D);

    print("m0110_init test: "); phex(data); print("\n");
}

static inline void clock_lo()
{
    M0110_CLOCK_DDR  |= 0x01;   //direction: output, set to 1
    M0110_CLOCK_PORT &= 0xFE;   //set bit to 0
}
static inline void clock_hi()
{
    M0110_CLOCK_DDR  |= 0x01;   //direction: output, set to 1
    M0110_CLOCK_PORT |= 0x01;   //set bit to 1
}
static inline bool clock_in()
{
    M0110_CLOCK_PORT |= 0x01; //set bit to 1
    M0110_CLOCK_DDR  &= 0xFE;   //direction: input, set to 0
    _delay_us(1);
    return M0110_CLOCK_PIN & 0x01;
}
static inline void data_lo()
{
    M0110_DATA_DDR  |= 0x0A;    //direction: output, set to 1 plus bit 4 for LED
    M0110_DATA_PORT &= 0xF5;    //set bit to 0
}
static inline void data_hi()
{
    M0110_DATA_DDR  |= 0x0A;    //direction: output, set to 1 plus bit 4 for LED
    M0110_DATA_PORT |= 0x0A;    //set bit to 1
}
static inline bool data_in()
{
    M0110_DATA_PORT |= 0x0A;    //set bit to 1 plus bit 4 for LED
    M0110_DATA_DDR  &= 0xFD;    //direction: input, set to 0
    _delay_us(1);
    return M0110_DATA_PIN & 0x02;
}
tmk commented 12 years ago

Ummmm, it is a tough problem...

You still see errors in hid_listen console like this, right? You see these errors even when no keys pressed?

m0110_recv err: 03 m0110_send err: 03

If so I'd suspect dull signal edge again. The dull edge will stay in threshold voltage region long time. This may screw up WAIT(clock_lo/hi). I wonder stronger pull up resistors might cure this problem. I think 47Kohm is some weak for pull-up, 1K-10K is enough strong, though I'm not sure.

I can't come up with others to try so far. Now you may need an oscilloscope or logic analyzer to debug complicated situation like this. I want to buy a M0110A to see this problem but the board is not cheap here in Japan unfortunately :(

skagon commented 12 years ago

Actually, no, there are no errors under normal operation. The only errors I see are when I'm hot-plugging either the 6a or the A keyboards. If I disconnect the USB first, and then change keyboards, there's almost no errors at all. OK, once in a while, I may get an error, but VERY rarely, and that could even happen with the 6b that's working perfectly. One other thing: the clock signal is routed to the Teensy through a high-speed Schmitt-triggered buffer, so I seriously doubt there's any problem with the clock signal. Unfortunately, I can't do the same with the data line, since I don't have any bidirectional Schmitt buffers. I don't even have any plain tri-state Schmitt buffers either, in case you were thinking I can solve this. However, I don't think the data line has any problem, since there's no errors and all keys pressed report correct.

That's why I'm saying I think the problem is not pull-ups or coiled cables; I think there's an inherent logical flaw in the structure of the routine. Maybe there information on the Apple documents is not complete. Or they're making assumptions that we don't know about. Which is understandable, since that data was provided for completeness' sake; I doubt anyone ever went so low-level on the keyboard interface of a Mac, as we're doing now.

In any case, I've started writing the keymap code for the A. I finally understood what the f&ck the KEYMAP macro does. After that, I'll be adding a couple of routines in the m0110.c file, to handle the keypad 0x79 code, initiate an immediate 'instant' command, get the subsequent key code, add 0x20 to it and return the value to the recv_key. The only problem is that 4 keys of the keypad first return a shift-down and then the 0x79 code. So... I guess I'll have to capture the shift-down key and issue an 'instant' command after each shift-down. If the reply is null, then it's a plain shift event. If the reply is 0x79 then we return to the earlier 'instant' again. The same goes for key-up where it first sends a shift-release and then 0x79. I don't like capturing all shift events very much, but what can you do. I will also see if it's possible to change keymaps, depending on the model number reply. Normal 0110 keyboards return 0x09 (00001001) which is model number 4 (100), while 0110A returns 0x0B (00001011) which is model number 5 (101). So I think it'd be a good idea to change layouts depending on the keyboard. Finally, I'm looking at a way to actually throw an error if the keyboard doesn't respond after 500msec, like the Apple documentation says, in which case the code should re-initialise the keyboard. That would also enable hot-plugging keyboards and changing keymaps accordingly. When I reach that point, I'll look at the structure of the existing code and see the best way to actually implement the change. Something else that worries me is this loop:

    while (1) {
       keyboard_proc(); 
    }

That means that Teensy is 'instructed' to query the keyboard as quickly as possible. Now, remember that we're dealing with -- well, I am dealing with -- three different devices here: two different 0110s and one 0110A, and three different microcontrollers: an Intel 8021 or whatever, a PIC and a NEC replica of an 8048. Obviously, the firmwares of these devices were written by different people, perhaps even on different continents, and God knows what data they had. One of them might have thought "ok, it's a keyboard so any time it's being asked, it should reply". Someone else might have thought "according to Apple, the Mac will be querying the keyboard once every 250msec, so immediately after it replies, it will have 250msec to scan the matrix and do other stuff, so I'll wait 50msec after a query has been replied and hard-code a timeout of 150msec at which time I'll be scanning the matrix and any queries during that time will be stalled". I hope you see what I'm getting at here. What I'm getting at is that maybe querying the keyboard "as fast as possible" works for one model, but not for another. So, we'll have to ditch that "while(1)" and use a timer in there, to throw queries every 250msec. I would assume that Teensy runs the entire keyboard_proc function at around 20-50msec at most, which would explain a few things. Finally, the documentation says nothing about... well... multiple data. If we assume that the keyboard has registered two (or more) key events during its scan... will it send both bytes one after the other? If so, we're only capturing one and then retiring the function. But there's no documentation saying that the keyboard returns anything more than one byte at a time. And it's almost impossible to type so quickly, as to see if you're missing key events. What if we are missing keystrokes when it happens too quickly, and when it does, we're just thinking "oh, I was typing too fast so I made a mistake"? Everything seems ok... but what if it's not?

Anyway, these are my thoughts for now. Cheers, mate!

skagon commented 12 years ago

Heh... I am thrilled to report... I am typing this at full speed on the A keyboard. Yes, that means I solved the problem! :D

YES, YES, YES!!! I did it. Now, it's down to simply fine-tuning.

I've also added the keymap, and I'm happy to report, it's working nicely. Let's see... --1234567890.=/*+ yup, that's from the keypad.

So, you will be asking, so what was the problem? Well, you might remember that, throughout the entire descriptions, I was convinced that the problem appeared to be the A keyboard being too slow? Weeeeeeell... that is true, to some extent, but as always, you can look at the problem from the opposite angle: the keyboard is too slow, but FOR WHOM? Weeeell... there you have your answer: it's too slow for what the teensy is trying to do. So, it's not the keyboard that's slow, it's the teensy that's too fast! If you look at what I was saying in the previous post, it appears I'm correct, to a certain extent: nobody knows what the controllers of these keyboards are doing inside. So, knowing the speed of the Mac's communication, they were free to make some assumptions, which would never show on a Mac, because of how the Mac communicates with the keyboard. But it had a huge impact on the way we communicate with the keyboard from the teensy.

Let me narrate how I discovered what was going on:

Like I said I would, I implemented the keypad keys in the keymap. So, it was a simple matter of adding the appropriate routines in the recv_key function, so that the routine asks the keyboard again, and appropriate offsets are added. Like I said, the keyboard first answers with a 0x79 if the key pressed (or released) comes from the keypad (or arrows) and then the Mac (or Teensy) will have to ask again with an 'instant' command, to get the appropriate data, and the offset has to be added manually -- otherwise, keypad 0 will be registered as the top row 1, keypad 1 as top row 2, keypad + as Z etc. The same goes for the four "special" keys, where the keyboard first reports a shift-down, then you have to ask again, then it returns 0x79, then you have to ask yet again, and only then will it report the scan code -- to which you again have to add the proper offset! Anyway, as you will see in the changed code, I added another function, called m0110_inst_recv, which simply sends an 'instant' command, reads the reply and returns it. That was the first major clue: the returned data was always 0xFF! So, I thought, wait a minute here... what the f&ck? I'm doing as the Apple datasheet instructs! I get 0x79, then I issue an 'instant'. Why do I get 0xFF? In the beginning I thought it could be a fluke from the keyboard controller. (Poor controller. I was always putting the blame on it! There, there.) So, I thought that maybe, just maybe, the keyboard needs two 'instant' commands. So I added them. That sometimes worked, sometimes not. ... Then, first lightning hit me: maybe it doesn't need two writes, maybe it just needs TIME to respond to the second command!!! So, I went in the new inst_recv function and added a _delay_us, and I started with 100 and started to increase the number until the returned data made sense. 100 didn't work, nor did 500, then I went to _delay_ms and 1msec, then 2. At 2msec delay, it sometimes worked. At 5msec it mostly worked. I wanted to be on the safe side, so I settled for 9msec. ... That fixed the keypad responses. But remember: the damn thing was still delaying a lot when it came to any key, and even between key press and key release. Like that, it was unusable. ... Then, lightning stroke again!!! What if the delays in replying aren't delays at all, but instead they're 0xFF replies from the keyboard that don't get registered? And what if the keyboard is throwing those 0xFF replies, because it is being issued an "inquiry" command TOO QUICKLY after the last one????!!! ... So... I went ahead and added another _delay_ms(10) at the beginning of the m0110_recv_key() function. And... VOILA!!!

IT'S WORKING PERFECTLY !!! :D

Well, like I said, it needs quite some fine-tuning, because there's still the occasional glitch here and there. But the main issue is that I typed all this, and I think I'm a fairly fast typist, on the M0110A keyboard, with just a few glitches.

WHO'S THE MAN!!! :D Yaaaaaaaaaaaaay!!!

tmk commented 12 years ago

Great work! Nicely done in the end :D

If you post your patch or source repository at some point in future it will be very helpful for me and others. Thanks.

skagon commented 12 years ago

Hey, thanks mate. :)

Now, one thing I realised after my last post -- well, I had all the pieces staring me in the face, it just hit me after I had made that last post: even the "good" 6b keyboard has the same problem. The only difference is, the 6b doesn't return 0xFF when queried too quickly. It's just the A keyboard that's a bit less tolerant -- or should I say, more strictly adhering to the protocol -- that's why one appears to be working normally while the other doesn't.

Actually, that realisation came to me when I realised that we don't need two keymaps for the two keyboards. Actually, the M0110 and the M0110A have only one key different! Well, of course the A's got the numpad (18 keys) and it's got extra arrow keys (4 more), but... it's also got every single key that the 'plain' M0110 has, except this one key: the 'Enter' key, which is located directly to the right of the space bar (and usually it's used as a right Alt). The best thing is that the keys the two keyboards have in common, which is to say, all the keys of the M0110 except that 'enter' key, also have the same scan codes! So, there's no need at all for a separate keymap! Connecting the M0110 and typing on it, is exactly the same as having an A keyboard and never touching the numpad or arrows. :) And actually adding that 'enter' key isn't a problem, since that 'enter' key is using its own scan code, which does not conflict with anything on the A. Oh, yeah. The A also does not have a right Option (Ctrl) key, but that's no big difference, since the two keys on the plain M0110 were using the same lines, so they were essentially the same button, only using two physical switches.

Finally... of course I will post the code changes, but I'd like to be certain of a few things first. I'll see if all the waits are really necessary. Well, it's no big deal, since it's just "maximum waiting times", it's not like they're actually delaying anything, it's just me wanting to be 'precise'. I will also see if I can shave off some of the delay milliseconds. But here's the deal: since the keyboard is working full speed anyhow, I don't know if it's better to play it safe and leave those delays there, or go for the "lean and mean" approach and try to shave off as much as possible. After all, there might be other keyboards out there that need even more delay time. I don't know. I'd also like to see if I can write some code to reinitialise the keyboard if it doesn't reply within 500msec. That's what the official spec says anyway.

And one thing before I go: the A keyboard has better sounding switches. :D Typing on it sounds and feels better than the plain 0110s. They also are structurally different (I opened two to clean them) and they look different on the outside too. The bottom plastic part is white! And the internal mechanism is light blue! And also, the locking Caps Lock does not have an external spring, and so it's much lighter and nice. ;)

tmk commented 12 years ago

Finally... of course I will post the code changes, but I'd like to be certain of a few things first. I'll see if all the waits are really necessary.

Great. I can wait for it.

I'd also like to see if I can write some code to reinitialise the keyboard if it doesn't reply within 500msec. That's what the official spec says anyway.

I will be able to do this after you finished other changes. You can leave it to me.

And one thing before I go: the A keyboard has better sounding switches. :D Typing on it sounds and feels better than the plain 0110s.

Oh really? :) Interesting. I want to try M0110A too. M0110A has some variants of switch: ALPS, Mitsumi and SMK at least. Seems like your board is Mitsumi. I guess it is "Made in Japan". http://deskthority.net/keyboards-f2/macintosh-plus-keyboard-t472.html

tmk commented 12 years ago

Though I'm not sure exactly where you need to delay. I guess first WAIT in m0110_send() or/and m0110_recv() is problematic. These WAIT's might be too short and time out before keyboard reply. WAIT takes a 16bit time argument in micro second, so you can wait up to 0xFFFF(65535)us(65ms). In my code these are 1000 and -1(0xFFFF) on each function now but it seems to be not enough.

Maybe we need WAIT_MS macro?

skagon commented 12 years ago

Heh... just hold your horses, mate. :)

Since you can't wait to find out, it's not the WAIT that's the problem. There has to be a _delay_ms before an "inquiry" command can be issued. Otherwise, the A keyboard will return 0xFF and stall. The only difference with the 6b is that it just won't stall after returning 0xFF. That's why it appears to work normally. Actually, 0xFF is not a proper keyboard response, and it's not equivalent to 0x7B, which is the "null" character. Actually, according to the documentation, the keyboard should not return 0xFF under any circumstances.

By the way, yes, my A is exactly like the one in your link. Exactly like that. It's a Mitsumi, and it sounds and feels excellent.

Now, just be patient for a couple of days, until I properly debug and fine-tune the code. ;)

tmk commented 12 years ago

Since you can't wait to find out, it's not the WAIT that's the problem. There has to be a _delay_ms before an "inquiry" command can be issued. Otherwise, the A keyboard will return 0xFF and stall.

0xFF is produced by code in ERROR: section, keyboard actually does not reply with it, right? This means an error occurs in any WAIT and I thought first WAIT in the functions is probably problematic as I told. Just FYI: Patch which changes code according to my this guess is below.

Long delay means MCU can't communicate with keyboard during that time even though keyboard is ready while WAIT can. So I prefer use of WAIT than delay if it is possible.

Now, just be patient for a couple of days, until I properly debug and fine-tune the code. ;)

OK. Anyway I'll try to be :)

-#define WAIT(stat, us, err) do { \
+#define WAIT_US(stat, us, err) do { \
     if (!wait_##stat(us)) { \
         m0110_error = err; \
         goto ERROR; \
     } \
 } while (0)
+#define WAIT WAIT_US
+
+#define WAIT_MS(stat, ms, err) do { \
+    uint16_t _ms = ms; \
+    while (_ms) { \
+        if (wait_##stat(1000)) { \
+            break; \
+        } \
+        _ms--; \
+    } \
+    if (_ms == 0) { \
+        m0110_error = err; \
+        goto ERROR; \
+    } \
+} while (0)

@@ -174,10 +196,11 @@ uint8_t m0110_send(uint8_t data)
     m0110_error = 0;

     request();
-    WAIT(clock_lo, 1000, 0);
+    //WAIT_US(clock_lo, 0xFFFF, 1);
+    WAIT_MS(clock_lo, 250, 1);
     for (uint8_t bit = 0x80; bit; bit >>= 1) {
         WAIT(clock_lo, 250, 3);
-        _delay_us(15);
+        //_delay_us(15);
         if (data&bit) {
             data_hi();
         } else {
@@ -189,9 +212,8 @@ uint8_t m0110_send(uint8_t data)
     idle();
     return 1;
 ERROR:
-    if (m0110_error) {
-        print("m0110_send err: "); phex(m0110_error); print("\n");
-    }
+    print("m0110_send err: "); phex(m0110_error); print("\n");
+    _delay_ms(500);
     idle();
     return 0;
 }
@@ -201,7 +223,8 @@ uint8_t m0110_recv(void)
     uint8_t data = 0;
     m0110_error = 0;

-    WAIT(clock_lo, -1, 0); // need 250ms? insted 0xffff(16bit max)us
+    //WAIT_US(clock_lo, 0xffff, 1);
+    WAIT_MS(clock_lo, 250, 1);
     for (uint8_t i = 0; i < 8; i++) {
         data <<= 1;
         WAIT(clock_lo, 200, 2);
@@ -211,14 +234,10 @@ uint8_t m0110_recv(void)
         }
     }
     idle();
-    if (data != M0110_NULL) {
-        print("m0110_recv data: "); phex(data); print("\n");
-    }
     return data;
 ERROR:
-    if (m0110_error) {
-        print("m0110_recv err: "); phex(m0110_error); print("\n");
-    }
+    print("m0110_recv err: "); phex(m0110_error); print("\n");
+    _delay_ms(500);
     idle();
     return 0xFF;
 }
skagon commented 12 years ago

Hey mate! I'm back from a short long weekend. :D

Anyway, I tried it your way and yes, it's working as well. I guess the WAITs are the proper way to do it, so let's keep your code there. Of course, I kept some of my own changes in there too, otherwise the extra keys wouldn't work. Also, the previous changes I posted with the data_lo/hi/in and clock_in are recommended, otherwise communication gets unstable. Previously, in some cases you had the port bit set to input.

However, there's another small-ish problem, which I'm not sure yet how to resolve. I'll have to post my code somewhere first, to explain. It's some kind of incompatibility between how the keyboard transmits data and how your code receives them. The good thing is that it's rarely noticeable. It's a bug, though.

So, I should be posting my code, as well as the new keymap for both M0110 an M0110A. How can I add changes here? Also, how do I get diff to produce git code? Note, I'm on Windows.

tmk commented 12 years ago

If you can have repository on github it is preferable. Or you post diff here.

skagon commented 12 years ago

Ok mate. I've got some good news and some bad news.

I guess it's good news first: I've got the keyboard working in all aspects. It took some rather large redesign, but it's working, almost perfectly.

Now for the bad news: The keyboard is working almost perfectly. Yeah, the problem is the "almost", and it's not my fault. Actually, I don't know how Apple did it, but I think it's a bug on their behalf, or the original Macs didn't work a certain way that modern computers do. Let me explain:

I've already mentioned that there are 4 keys on the keypad, which send out three bytes. Those keys are the keypad =, /, * and +. Let's see the codes they send out in detail:

[=]   71 79 11   press
      F1 79 91   release

[/]   71 79 1B   press
      F1 79 9B   release

[*]   71 79 05   press
      F1 79 85   release

[+]   71 79 0D   press
      F1 79 8D   release

Now, as you can see, the keyboard is sending out the shift-down code first (from a virtual shift key), then the 0x79 numpad code, and finally the key code. Why is that? Well, it's because the M0110A keyboard is supposed to be functioning exactly like the M0110 plus numeric keypad combination. So, that older numeric keypad had the arrow keys on the top-right positions, and they doubled as /, =, * and + by pressing the shift key! Actually, let's see the arrow key return codes:

[left]   79 0D   press
         79 8D   release
[right]  79 05   press
         79 85   release
[up]     79 1B   press
         79 9B   release
[down]   79 11   press
         79 91   release

I guess that there was no provision for selecting text by using shift+arrow, so they didn't care. However, we do care, and there lies our problem. Since there's four keys that send out shift press/release codes, I had to capture the shift key and immediately send an "instant" request. If the reply from the "instant" is null, then the shift key registers as usual. However... if the reply from the "instant" is not null, then I added another check: if the reply is 0x79, then we need yet another "instant" command, to get the keypad key code, whereas if the reply is not 0x79 then I had to register two keys, the shift and the other key, which was obviously very quickly pressed. To cut a long story short, the bug in the code (of the Apple keyboard) is when someone presses or releases the shift key and either the arrow keys or those four keypad keys (equal, slash, asterisk, plus). I have seen two things happening:

  1. our code registers a key press but a different key release (i.e. left arrow pressed, keypad + released)
  2. the keyboard transmits e.g. 71 79 11 for key press, followed by 79 91 for release (doesn't transmit virtual shift up) In both cases, the result is that one key is stuck to 'pressed' state, or if you prefer, never transmits the released code. For the first case, I cannot see a solution, because it's just impossible for our code to differentiate a key event from one of these four keys, and a very fast shift plus arrow event. For the second, there can be no cure, since it's most likely a bug of the keyboard firmware.

In any case, I can only say it very rarely happens, and only if you're using shift plus arrows very fast, or shift plus those-four-damned-keypad-keys, again very fast.

I'm just finalising the code now. I will be posting it soon -- I hope. Sorry for the delay, but I was trying to fix the problem and debugging takes a LOT of time.

skagon commented 12 years ago

For a better understanding, check out this image: http://www.auctionzealot.com/members/danf1970/dscn4577.jpg

skagon commented 12 years ago

Finally, I forked the project, like you said. At least I hope you meant that. I don't know if you can move the changes into the main branch, I hope you can.

Also, if you have any good ideas about the handling of the known issues, please, let me know.

Finally, I am now using the original Apple coiled cable, on port B, without any pull-up resistors. It's working perfectly. Well, also there are two LEDs on those same pins hooked to Vcc through a limiting resistor, so I can look at them blink and be all happy. No problems at all.

Cheers mate!

tmk commented 12 years ago

Great! I'll look into your repository and described problem later. Thank you.

skagon commented 12 years ago

Check out my repo again, mate. I've added some code to reduce the problem as much as possible. Take care!

skagon commented 12 years ago

Hey, have you checked out my code yet? It's been working without an issue for 5 days now over here!

tmk commented 12 years ago

Sorry for snail-slow move.

I looked into you repository and I think I understood it now. I'm working on introducing your M0110A support into my repository now. I decided to write codes a little myself to examine my understanding instead of just merging your codes into master branch. I'll create new branch for M0110A support to show my codes and examine in a few days.

tmk commented 12 years ago

I created new branch for M0110A support. I'll test my code and merge into master branch once I get M0110A near future. But I appreciate it if you could try my code before that.

https://github.com/tmk/tmk_keyboard/tree/m0110a

skagon commented 12 years ago

Actually, don't merge that in your master branch. It's not working at all. Actually, I could tell it wouldn't work, even before I tried it. You've got some serious logic errors in there. Every shift plus keypad or arrow combination is problematic, the keypad keys don't work at all, the calc keys and enter key repeat themselves until kingdom come. I suggest you keep my code as it is. I've worked very hard to get it working properly, and there's no bugs in there at all. I see no point in trying to rewrite it all differently, from scratch.

Actually, from the changes you made, I realise that you haven't really understood the real cause of the problem that my code is trying to address. For example, it can't be done just from m0110.c; you have to add code into matrix.c otherwise it won't work. The main issue is to be able to understand when a shift event is user-generated or keyboard-generated. If it's user-generated, you have to issue a shift-event as well as a whatever key event. You've mixed that up, so key-press events are being sent but the corresponding key-release events are never issued. The shift events are also mixed, the arrow keys never release and so on. Also, the data and clock hi/lo/in functions are still wrong.

Sorry, mate.

Oh, by the way, you too might want to try the port B and a better (not phone) cord. It might save you the pull-up resistors. I don't use any.

tmk commented 12 years ago

Thanks for reporting. I could found some bugs in my code. OK. At this time my code may has more bugs than this and I need to get M0110A to debug myself now.

Your code will work well actually but it has some magic numbers and unclear tricks and they are not understandable to me in a way. I don't want to bring code that I can't maintain myself into master branch. So I need to understand intention of your code at least or rewrite it possibly. To understand it looks like I need to see your code in action using the real keyboard.

Until I get a M0110A for test I'll suspend this issue but this won't be urgent problem in fact. Now I can point to your repository for someone who want M0110A support.

Yeah, actually in most case you need no pull-up resistors, they are just insurance against some unfortunate case. In fact I don't need them even with phone code and Port F. You had any problem with Port F? ,though Port B seems to be better choice as you said. Only one case I need resistors is when I used an old, long, coiled and thick ADB cable. I think capacitance of the cable is essential. Capacitance will make signal edge dull.

I appreciate your help. Thanks again.

skagon commented 12 years ago

Actually, I completely understand your need to control your own code. As I do understand that some of the things that I have done, will not make sense to you, because you don't have an M0110A keyboard and therefore, haven't seen it in action. However, I do want people to understand what I did and why, and maybe suggest better ways of doing them. So, I will start adding comments into the code, my code as well as yours, so that it will be easier for someone else to understand what's going on in there. I will also explain all the "magic numbers" and why they are there and what they do.

I don't know when I'll finish that, though, since for the time being, I have the other problem of trying to create a firmware and a Windows driver for Teensy, possibly from scratch, because the damn HID class is limited to 64kB/sec and I'm pretty swamped there. I have never done it before and Windows programming isn't exactly my forte.

Finally, to answer your questions: no, I didn't have any problem with the F port, I just changed it at a time when I couldn't understand what was wrong with the M0110A and I was grasping at straws, so I noticed the recommendation of the Atmel datasheet and kept it ever since. Besides, it's just as easy to use, since it's the two opposite pins to the F port. Also, have in mind that the usual effect of coiled cables is inductance actually, since a long coil is basically an inductor. The effect on the signal quality is essentially the same, though. :) Oh, you also might want to do what I did and add LEDs to the ports. That way, you can see how the usual bus behaviour is, for a keyboard working without problems, and then you can immediately tell that something's wrong with some code change. Actually, I was able to tell that there's a problem with the communication in general, because the data LED would light up twice as fast as the clock LED, which made me realise that half the requests from the Teensy were not answered by the keyboard.

Anyway, that will be all. I'll let you know when I've added the commentary in the code. Cheers mate!

skagon commented 12 years ago

OK mate, I've added quite a lot of commenting in the code, in my fork. I also changed the code slightly, to give priority (or bypass) to the code route for the most common key events, rather than delaying unnecessarily in matrix checks. It should be a bit faster, even though it will be evident only to really fast typists... maybe. Anyway, I hope you can now understand what I'm doing and why. Even though I can't think of any way to make it faster, and I don't think replacing the single else-if with switch is going to help at all.

Anyhow, feel free to check the code and let me know.

tmk commented 12 years ago

Thanks, I could pull your new commits to local and will look into it later. And good news, my M0110A is being in the mail now.

tmk commented 12 years ago

I finally merged m0110a branch into master to make public this M0110A support. Thank you for your support, skagon!

cuavas commented 4 years ago

Sorry for bumping a dead issue, but @skagon would you be so kind as to post good quality photos of the PCB from M0110A keyboard? I recently added emulation of this keyboard to MAME, guessing all the details from the microcontroller program. I’d like to know what the exact microcontroller model is, what kind of clock source it uses, and whether is it has watchdog circuitry.