Man vs MicroSwitch: Converting a SD16234

User avatar
LessthanZero

06 Sep 2018, 02:17

Slom the Silent TI doesn't have a demultiplexor it just writes directly to the enable lines. My MicroSwitch board does have a demultiplexor on it so I had to take this into account when I did my conversion. I think DorkVader was referring to the source code for my SD board not the code that you wrote for the Silent 700.

Yes DorkVader those are the correct lines for the scanner logic in the source I will try to explain exactly whats going on but explaining this stuff is actually harder than understanding it. Its proof that to really be a master at something you must first be able to help another achieve mastery.
here is the key function

Code: Select all

static uint8_t  Direct_Read(uint8_t column)
{
  uint8_t p2;

  SC_ADDR_PORT = (SC_ADDR_PORT & ~(0x0F << SC_ADDR_SHIFT)) | (column << SC_ADDR_SHIFT);
  SC_STROBE_PORT &= ~SC_STROBE;


  _delay_us(5);


  p2 = SC_KEYS_PIN;

  SC_STROBE_PORT |= SC_STROBE;
  return p2;
}

this function takes in an unsigned 8 bit integer defined as column and then returns an unsigned 8 bit integer. It's a decimal to binary converter it takes an 8 bit number and returns a 4 bit number.

The first line

Code: Select all

    uint8_t p2;  
just creates an unsigned variable called p2 that is used to hold the result of our calculation below,

Code: Select all

 
  SC_ADDR_PORT = (SC_ADDR_PORT & ~(0x0F << SC_ADDR_SHIFT)) | (column << SC_ADDR_SHIFT);
  SC_STROBE_PORT &= ~SC_STROBE;
 
When you And a value with a Port I think of it as using the value as a mask so if you And 1111 1111 with 0000 0001 you would be left with 0000 0001 on the Port,
Or works just as it does in logic 1111 1111 with 0000 0001 becomes 1111 1111. These are used as methods for grabbing specific values from a binary string and placing them on a port or reading a certain value from a port.

~ is the inverse operator so it just takes the complement of a number ~1111 would become 0000.
0x0f which is decimal 15 or 0000 1111 in binary. We then shift the number by the size of SC_ADDR_SHIFT which is defined at the top of the file as 4. This gives us 1111 0000. If you then AND this with SC_ADDR_PORT and you are left with 0000. This operation just truncates the trailing 4 zeros.

Code: Select all

(column << SC_ADDR_SHIFT);

We then take the value we passed in, which is stored in variable Column, lets say its the number 13. We then take the binary of that number and shift it by four places, the 8 bit binary for 13 is 0000 1101 so when I shift it by 4 I get 1101 0000. So we are taking in a number between 0 and 15 and shifting it by four placees we now have the correct four bits to send to the demux to select the proper output we just need to get rid of the trailing zeros and put the bit pattern onto the port.
We do this by OR'ing the two values together
so 0000 OR 1101 0000, returns 1101

But we also need to bring the enable pin on the demux low to output any signal at all so to do this we AND the inverse of SC_STROBE(defined as 1<<0 which is another way to write 0001 or 0x01) and place the result on the SC_STROBE_PORT which is just the last four pins of port B we send the demux address on the first four pins. So we send 0000 which puts a opens a path to ground through the pin for the enable of the demux.

Code: Select all

  _delay_us(5);
this gives the enable time to get through the demux and the switch time to output, its basically just a slight delay so that the read occurs after the enable. This value could be tuned.

Code: Select all


  p2 = SC_KEYS_PIN;
 SC_STROBE_PORT |= SC_STROBE;

we enable the demux and then take in the value from the 8 pins tied to the test points this is stored as an 8 bit number so if you depressed a key on row 6 we would get back 1111 1011. We then bring the enable on the demux high.

Code: Select all

  return p2;
We send back the 8 bits to the calling function which in our case is Direct_Scan(void) which is a function that takes no value and returns no value. It gets used in the top part of that function here is the relevant code.

Code: Select all

static void Direct_Scan(void)
{
  int i;
  int j;
  
  for (i = 0; i < 16; i++)
  {
    DirectNKeyStates[] = Direct_Read(i);
  }

I and J are stored as ints which are 16 bit numbers this allows us to store values between -32768 and 32767 which is more than enough for our purposes. Then we count up from 0 to 15 each time we perform a Direct_Read(i) with the value of i as our column value and store the result in the global array named DirectNKeyStates in the index number i . Each time you do this you are getting an 8 bit number that represents the 8 row locations in that column. We do it 16 times and this gives us the entire key array.

Code: Select all

  for (i = 0; i < 16; i++)
  {
      uint8_t keys;
      uint8_t change;
      
    keys =   DirectNKeyStates[i ];
    change = keys ^ DirectKeyStates[i ];

    if (change == 0) continue;
      
We then put the value stored at DirectNKeyStates['i '] into the variable keys. Then we Exclusive-OR also called XOR the value the truth table for XOR is
00 = 0
1 1 = 0
0 1 = 1
1 0 = 1
I think of it as an either or, but not both operation, XORing these values together we will get a one as long as the two values are not both one, so in this case as long as there is a change between the two values.
This gives us an easy way to test if the value at that location has changed or not since we last checked it

Code: Select all

if (change == 0) continue;
So if there has been no change we just bail out of this function right here there is no reason to continue. If a key was down it is still down or if a key was not hit it has not been hit since the last scan. We are only concerned with new keypresses or releases.

The rest of the scan function looks like this,

Code: Select all

DirectKeyStates[i] = keys;

for (j = 0; j < 8; j++)
      {
          if (change & (1 << j))
          {
              int code = (i * 8) + j;
              if (keys & (1 << j))
              {
                  KeyUp(&Keys[code1]);
              }
              else
              {
                  KeyDown(&Keys[code1],false);
             
              }
We place the value of Keys into the global variable DirectKeyStates['i'] at location i
We then go through this loop 8 times for every time we go through the outer loop once(the outer loop is 16 iterations)
we test to see if a key was hit or released by checking it against what the location was previously, 1 in binary is 0000 0001 This is shifted by j which gives us the location in the 8 bits that we are testing. So if j was 7 we would be testing against 0100 0000. If a key in that column had been hit the test would pass and we would move into this block

Code: Select all

 int code = (i * 8) + j;
              if (keys & (1 << j))
              {
                  KeyUp(&Keys[code]);
              }
              else
              {
                  KeyDown(&Keys[code],false);
             
              }

In this section

Code: Select all

 int code = (i * 8) + j;
[/code[
we first create a variable and multiply [i] i [/i] by the length of a column and then add j this just turns a multidimensional array location into a linear array location, Our keyarray is  linear its all one big chunk but when we index through the array we do it by rows and columns. 
[code]
              if (keys & (1 << j))
              {
                  KeyUp(&Keys[code]);
              }
              else
              {
                  KeyDown(&Keys[code],false);
             
              }
we then test again to see if the vaue in Keys which is the value stored in DirectKeyStates from earlier is a 1 we know a key has been released and we then enter the upper block and call the KeyUp function sending it the code that we calculated above. It the value is a 0 we have a key depressed so we enter the else block and call the function KeyDown sending it the code we calculated above.

I'm sure I didn't do that good of a job explaining that and I apologize if you already knew alot of the information I didn't know how much you know about C and programming for a MIcro in general. If I left anything out or I was unclear just ask me about that part and I'll try to explain it better.
Bit banging is tough even the best programmers say that bit manipulation is some of the trickiest most finicky code you can write so I'm sure someone else with more experience could explain it better.

(The key to understanding all of this for me was just breaking the habit of thinking of a number as decimal. In programming binary is what's important. Hex is used becaue its easier to talk about binary numbers by saying F instead of 0000 1111. The important thing to remember is that 0x0F is 0000 1111 to the computer so we do operations on the binary not its hex representation. I hope I explained that correctly, I didn't go to college or anything I learned C from reading books when I was a teenager and a few online tutorials that I've found. So take everything I say and go check it out for yourself. Which is just good life advice in general)

I should have that TI code up by tommorow afternoon the new Dragon Quest came out so its been taking up a bit of my free time, I love keyboards and coding but I love me some Dragon Quest too.
Im reworking the keymap for my SD board I need to check out those Function key locations and make sure they are all correct. I wasn't as concerned with those when I was figuring out the keymap so its possible that alot of them could be wrong. :/
We'll get there though.
Last edited by LessthanZero on 07 Sep 2018, 00:01, edited 1 time in total.

Slom

06 Sep 2018, 19:57

LessthanZero wrote: Slom the Silent TI doesn't have a demultiplexor it just writes directly to the enable lines.
I (think I) never said it did. I certainly know that is doesn't.
Last edited by Slom on 07 Sep 2018, 07:42, edited 1 time in total.

User avatar
LessthanZero

06 Sep 2018, 21:12

I attributed this to mmCm did you even read my initial post. I never claimed this source was my own I just had to hack and slash at it until I could get it to compile and then flip some of the logic around because my boards logic was inverted to his. Its also why his name appears at the top of the source.
Last edited by LessthanZero on 07 Sep 2018, 16:32, edited 1 time in total.

Slom

07 Sep 2018, 07:42

LessthanZero wrote: I attributed this to mmCm did you even read my initial post. I never claimed this source was my own I just had to hack and slash at it until I could get it to compile and then flip some of the logic around because my boards logic was inverted to his. Its also why his name appears at the top of the source. What's your deal.
In your initial post you wrote that you used MMcM's code as a "jumping off point". To me that sounded like you made some substantial additions to it yourself.

You later clarified this, but I had not seen that post until just now. My apologies, I removed the offending part of my previous comment.
LessthanZero wrote: [...] I took this code almost directly from a Space Cadet Conversion that another guy named McMM had done a while back [...]
Just to be clear: That you made his code compile, and with the small changes you made to logic, is in my opinon a good achievement in itself. I'm not trying to take that from you.

I also have one of those SD16234 around here somewhere, so I might be able to test the driver this weekend. Waiting on a breadboard that is in the mail right now.

I'll take a few days to cool down now, take care ...

User avatar
LessthanZero

07 Sep 2018, 13:19

Its ok you know I love you. I live in the U.S in Arkansas which is pretty deep south so I don't have alot of people to talk programming or MicroSwitch with. I wrote a new Read function that should work with the Silent 700. I hooked power to the Silent last night and got the beautiful green glow of the little led on the face of the board so we at least know the board works. I'm still waiting on another Teensy though. I'm tempted to pull one off another board that I don't use very often so I might do that tonight if they don't arrive today. You guys have a good one.

Slom

12 Sep 2018, 01:35

Here is my go at explaining the output for the demultiplexer. I will use bold to write sequences of bits. I will use lowercase characters to stand for indeterminate bits. (To actually write binary literals in C, you would have to prefix them with "0b": https://en.cppreference.com/w/cpp/langu ... er_literal.)

Code: Select all

SC_ADDR_PORT = (SC_ADDR_PORT & ~(0x0F << SC_ADDR_SHIFT)) | (column << SC_ADDR_SHIFT);
In a nutshell, what this does is: take the four bits of the binary presentation of the column variable (we know it only needs four bits bc/ it is a number smaller than 16) and write them into the leftmost 4 bits of the port represented by SC_ADDR_PORT, while at the same time keeping the rightmost 4 bits of the port unchanged.

Lets brake the expression into smaller bits and handle them piecewise:
  1. Code: Select all

    ~(0x0F << SC_ADDR_SHIFT)
    With SC_ADDR_SHIFT defined as 4, this is actually a noop.
    • First 0x0F represents the following 8 bits: 00001111.
    • The << binary operator shifts the bits in its lefthand side to the left. The amount of positions to shift is determined by its righthand side. Shifting 00001111 by four positions to the left and filling with zeros from the right gives us 11110000.
    • The ~ unary operator inverts every single bit in its operand. The inverse of 11110000 is 00001111 which is 0x0F which is where we started.
  2. Code: Select all

    (SC_ADDR_PORT & ~(0x0F << SC_ADDR_SHIFT))
    • With the result of 1. above this is (SC_ADDR_PORT & 00001111)
    • SC_ADDR_PORT reads the current content of the port. The port represents 8 IO pins of the teensy, we do not know the value of those pins, so we will represent them with the characters a to h: abcdefgh.
    • The & binary operator performs a bitwise logical "and" of its operands. We know the right hand side operand. Where we have zero in the righthand side, there will be a zero in the result. Where we have a 1 in the righthand side, the result will be the corresponding bit of the lefthand side.

      So accordingly (abcdefgh & 00001111) is 0000efgh. Effectivly we have kept the four bits that we want to keep unchanged in the port, and set all the other bits to zero.
  3. Code: Select all

    (column << SC_ADDR_SHIFT)
    The value of column is the binary representation for the column that we want to activate. In the binary representation that the teensy uses, the bit with the lowest value is on the right and the bit with the highest value is on the left. As only the lowest valued for bits are enough to represent the numbers 0 to 15 (1 + 2 + 4 + 8 = 15), we know that only the four rightmost bits will be used. The four leftmost bits will be zero.

    So assume that the four bits are w,x,y and z, then column is 0000wxyz. Shifting 4 times to the left, filling again with zeros, we have wxyz0000. We have moved the binary representation of column to the leftmost four bits.
  4. Code: Select all

    (SC_ADDR_PORT & ~(0x0F << SC_ADDR_SHIFT)) | (column << SC_ADDR_SHIFT)
    We allready explained the left and the right side of this expression. We are left with (0000efgh | wxyz0000).
    The operator | performs a bitwise logical "or" on its operands. When one side has a zero in a bit position the result will be the corresponding bit of the other side. The final result of this expressing is wxyzefgh.

    This number has the leftmost for bits filled with the binary representation of column and the rightmost four bits filled with the rightmost four bits of SC_ADDR_PORT.
  5. Code: Select all

    SC_ADDR_PORT = (SC_ADDR_PORT & ~(0x0F << SC_ADDR_SHIFT)) | (column << SC_ADDR_SHIFT);
    The = binary assignment operator writes the value of its righthand side into the location represented by its lefthand side.

    So the content of SC_ADDR_PORT is abcdefgh before the assignment (see 2.) and it will contain wxyzefgh (see 4.) afterwards. You can see that the leftmost 4 bits now contain the binary representation of column and the rightmost four bits remained unchanged.

User avatar
dorkvader

23 Sep 2018, 00:41

Hi Slom and lessthanzero. Thanks for posting the explanations. I've been busy being a full time student (18 hours of classes, so I spend at least 12 hours every day including weekends on schoolwork) so I haven't had any time to test anything but I did sign up for a C class to help me understand the programming better.

I'm a visual learner so Slom's use of color really helped. I'm actually doing bitwise binary stuff in class next week. I think since everyone has the same version of this KB, then reverse-engineering the protocol that the CPU on the texscan variants will not be useful so I'll put that on hold and focus on getting the kb here working. The CPU is socketed so I can just remove it and re-add it back later)

Cheers!

User avatar
LessthanZero

24 Sep 2018, 20:22

Yeah Sloms explanation of the logic is right on. The topic of bitbanging on the teensy is hard to find good info for when you're just getting started. There are plenty of tutorials for the basics but trying to find stuff with more specfic examples can be hard. Most newbie type tutorials are geared toward the Arduino IDE. I got the C version of the Silent 700 driver going, it uses MMcMs structure but I rewrote the scanner portion so it will work without the demultiplexor. I gotta give a big thankyou to Slom for helping me out testing some stuff and reminding me to use pullups on the inputs. Now I just need some more Microswitch boards, ebays kinda dried up. I really want a 3 pin, I'm almost 100 percent sure that the driver we have now would work with those switches. But I need to test it before I can say it for sure. There's also a Wang but its a monitor combo and the price plus shipping is just too much. My wife limits my keyboard budget and I just got a GL102 so when I mentioned needing to spend 200 for a test board I got a dirty look. I'm going to get it though more than anything I want to get the 4 pin source level switches to have at least 6kro with as little board mod as possible. I've been working on my fk5001 I wrote an xt/at converter for it and I'm trying to map out the pin connector for the LCD I want to replace it with a more modern one like Engicoder did for Chyros.

MMcM

04 May 2020, 00:49

There is an eBay seller who seems to have a bunch of these SD-16234 boards for sale. Specifically, as 99SD24-3. There are a couple listings up now. They look very similar to LessthanZero's board, except that a couple of the keys (Bell Off and Tab) are white.

They come with a light-pen, which is perhaps a hint to what system they were pulled from.

They have the same bunch of hand-wiring, particular added capacitors, as noted above. And, likewise, some apparently unused but populated components like the driver for the speaker and the opto-coupled transistor, the other two sides of which are on the IDC connector.

Relative to dorkvader's schematic, a wire has been added between pins 6 (/INT) and 32 (P1<5>), so 22 and 27 on the header are correspondingly shorted.

I got one of that seller's items last summer and, as so often happens, it got set aside and I am only now having a look at it, as we all have more time for getting things organized at home.

There has been so much rework on this board already that probably a sensible thing to do to make use of it is take out the microcontroller, just like everyone did up-post, and drive the de-mux directly. But I was interested to see what the firmware in the 8748 that came with it does.

To summarize the IDC connector:
  • 1,2,3,4,5,6,33,34: ground
  • 10,8,7,9,11,13,15,17: data port
  • 12,14,16,18: jumpers 38,34,30,26 on the upper-left of the board (all of which are open)
  • 19,20: Vcc +5V
  • 21, 23: that weird transistor
  • 25: /RESET
  • 27: /INT
  • 29: /RD
  • 31: /PSEN
  • 22: P1<5>
  • 24, 26, 28, 30, 32: LEDs, the first (rightmost) of which isn't populated), active low
My first guess, based on other similar keyboards, was that 22 was an output strobe for data output as ASCII. That is more or less the case, so the existing https://github.com/MMcM/parallel-ascii-kbd mostly works.

The function keys send characters outside the normal printing range. This unfortunately includes Return and Tab. Likewise Ctrl and Rept don't seem to do anything. The number pad keys send the same code as the main number keys, but ignore Shift. Caps Lock (as opposed to Shift Lock) causes all the alphabetic to send uppercase regardless of the Shift key.

The five keys that don't have keycaps, and whose switches don't even have a way to put on a cap and so must have been activated in some different way, just send additional single byte codes. Interestingly, the one at the top-right sends 0, while most of (all?) the other function keys have the eighth bit set.

It's strange that RD and PSEN are exposed externally, because this implies that the terminal might act as external program or data memory. I don't think this is a matter of debugging, since EA is hard-wired to ground. So the program only gets there by branching outside of the on-chip addresses. A few times when fooling around I would see PSEN go low and the data bus output all kinds of random bytes. So maybe there is some way to cause this to happen. Or perhaps the old MCU has partially lost its program. I never saw an attempt at an external memory read, though.

Post Reply

Return to “Workshop”