src/liblpfk.c

Thu, 25 Sep 2008 15:29:35 +0100

author
Philip Pemberton <philpem@philpem.me.uk>
date
Thu, 25 Sep 2008 15:29:35 +0100
changeset 11
bfaaf539bc4d
parent 4
81d49d42ad6a
permissions
-rw-r--r--

Added lpfkbinclock test program (from Ethan Dicks, ethan.dicks usap.gov)

philpem@0 1 /****************************************************************************
philpem@0 2 * Project: liblpfk
philpem@0 3 * Purpose: Driver library for the IBM 6094-020 Lighted Program Function
philpem@0 4 * Keyboard.
philpem@0 5 * Version: 1.0
philpem@0 6 * Author: Philip Pemberton <philpem@philpem.me.uk>
philpem@0 7 *
philpem@0 8 * The latest version of this library is available from
philpem@0 9 * <http://www.philpem.me.uk/code/liblpfk/>.
philpem@0 10 *
philpem@0 11 * Copyright (c) 2008, Philip Pemberton
philpem@0 12 * All rights reserved.
philpem@0 13 *
philpem@0 14 * Redistribution and use in source and binary forms, with or without
philpem@0 15 * modification, are permitted provided that the following conditions
philpem@0 16 * are met:
philpem@0 17 *
philpem@0 18 * * Redistributions of source code must retain the above copyright
philpem@0 19 * notice, this list of conditions and the following disclaimer.
philpem@0 20 * * Redistributions in binary form must reproduce the above copyright
philpem@0 21 * notice, this list of conditions and the following disclaimer in
philpem@0 22 * the documentation and/or other materials provided with the
philpem@0 23 * distribution.
philpem@0 24 * * Neither the name of the project nor the names of its
philpem@0 25 * contributors may be used to endorse or promote products derived
philpem@0 26 * from this software without specific prior written permission.
philpem@0 27 *
philpem@0 28 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
philpem@0 29 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
philpem@0 30 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
philpem@0 31 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
philpem@0 32 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
philpem@0 33 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
philpem@0 34 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
philpem@0 35 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
philpem@0 36 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
philpem@0 37 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
philpem@0 38 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
philpem@0 39 ****************************************************************************/
philpem@0 40
philpem@0 41 /**
philpem@0 42 * @file liblpfk.c
philpem@0 43 * @brief liblpfk library, main source
philpem@0 44 */
philpem@0 45
philpem@0 46 #include <sys/types.h>
philpem@0 47 #include <sys/stat.h>
philpem@0 48 #include <fcntl.h>
philpem@0 49 #include <unistd.h>
philpem@0 50 #include <termios.h>
philpem@0 51 #include <sys/ioctl.h>
philpem@0 52 #include <time.h>
philpem@0 53 #include <string.h>
philpem@0 54 #include <stdbool.h>
philpem@0 55 #include <stdio.h>
philpem@0 56
philpem@0 57 #include "liblpfk.h"
philpem@0 58
philpem@1 59 /* lpfk_open {{{ */
philpem@2 60 int lpfk_open(LPFK_CTX *ctx, const char *port)
philpem@0 61 {
philpem@0 62 struct termios newtio;
philpem@0 63 int status;
philpem@0 64 int fd;
philpem@0 65 int i;
philpem@0 66
philpem@0 67 // open the serial port
philpem@0 68 fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
philpem@0 69 if (fd < 0) return LPFK_E_PORT_OPEN;
philpem@0 70
philpem@0 71 // save current port settings
philpem@0 72 tcgetattr(fd, &ctx->oldtio);
philpem@0 73
philpem@0 74 // set up new parameters
philpem@0 75 memset(&newtio, 0, sizeof(newtio));
philpem@0 76 // 9600 baud, 8 bits, parity enabled, odd parity
philpem@0 77 newtio.c_cflag = B9600 | CS8 | PARENB | PARODD | CLOCAL | CREAD;
philpem@0 78 newtio.c_iflag = 0;
philpem@0 79 newtio.c_oflag = 0;
philpem@0 80
philpem@0 81 // set input mode -- non canonical, no echo
philpem@0 82 newtio.c_lflag = 0;
philpem@0 83
philpem@0 84 // inter-character timer unused
philpem@0 85 newtio.c_cc[VTIME] = 0;
philpem@0 86 // read does not block waiting for characters if there are none in the buffer
philpem@0 87 newtio.c_cc[VMIN] = 0;
philpem@0 88
philpem@0 89 // flush input buffer
philpem@0 90 tcflush(fd, TCIFLUSH);
philpem@0 91
philpem@0 92 // set new port config
philpem@0 93 tcsetattr(fd, TCSANOW, &newtio);
philpem@0 94
philpem@0 95 // set RTS true to pull the LPFK out of reset
philpem@0 96 ioctl(fd, TIOCMGET, &status);
philpem@0 97 status |= TIOCM_RTS;
philpem@0 98 ioctl(fd, TIOCMSET, &status);
philpem@0 99
philpem@0 100 // wait a few seconds for the LPFK to become ready
philpem@0 101 sleep(2);
philpem@0 102
philpem@0 103 // 0x06: READ CONFIGURATION. LPFK sends 0x03 in response.
philpem@0 104 // Try five times to wake it up.
philpem@0 105 status = false;
philpem@0 106 for (i=0; i<5; i++) {
philpem@0 107 unsigned char buf;
philpem@0 108 time_t tm;
philpem@0 109
philpem@0 110 // Send 0x06: READ CONFIGURATION, loop on failure
philpem@0 111 if (write(fd, "\x06", 1) < 1) {
philpem@0 112 continue;
philpem@0 113 }
philpem@0 114
philpem@0 115 // save current time (in seconds)
philpem@0 116 tm = time(NULL);
philpem@0 117
philpem@0 118 // loop until 2 seconds have passed, or LPFK responds
philpem@0 119 status = false;
philpem@0 120 do {
philpem@0 121 // read data, loop if not successful
philpem@0 122 if (read(fd, &buf, 1) < 1) {
philpem@0 123 continue;
philpem@0 124 }
philpem@0 125
philpem@0 126 // we got some data, what is it?
philpem@0 127 if (buf == 0x03) {
philpem@0 128 // 0x03 -- correct response. we're done.
philpem@0 129 status = true;
philpem@0 130 }
philpem@0 131 } while (((time(NULL) - tm) < 2) && (!status));
philpem@0 132
philpem@0 133 // exit loop if we got the LPFK to talk
philpem@0 134 if (status) {
philpem@0 135 break;
philpem@0 136 }
philpem@0 137 }
philpem@0 138
philpem@0 139 // Did the LPFK respond?
philpem@0 140 if (!status) {
philpem@0 141 // LPFK isn't talking. Restore serial port state and exit.
philpem@0 142 tcsetattr(fd, TCSANOW, &ctx->oldtio);
philpem@0 143 close(fd);
philpem@0 144
philpem@0 145 return LPFK_E_NOT_PRESENT;
philpem@0 146 } else {
philpem@0 147 // Initialise LPFK context
philpem@0 148 ctx->led_mask = 0;
philpem@0 149 ctx->fd = fd;
philpem@0 150
philpem@2 151 // Disable LPFK keyboard scanning
philpem@2 152 lpfk_enable(ctx, false);
philpem@0 153
philpem@0 154 // Return OK status
philpem@0 155 return LPFK_E_OK;
philpem@0 156 }
philpem@0 157 }
philpem@0 158
philpem@1 159 /* }}} */
philpem@1 160
philpem@1 161 /* lpfk_close {{{ */
philpem@0 162 int lpfk_close(LPFK_CTX *ctx)
philpem@0 163 {
philpem@0 164 int status;
philpem@0 165
philpem@0 166 // 0x09: DISABLE. Stop the LPFK responding to keystrokes.
philpem@0 167 write(ctx->fd, "\x09", 1);
philpem@0 168
philpem@0 169 // turn all the LEDs off
philpem@0 170 lpfk_set_leds(ctx, false);
philpem@0 171
philpem@0 172 // set RTS false to put the LPFK into reset
philpem@0 173 ioctl(ctx->fd, TIOCMGET, &status);
philpem@0 174 status &= ~TIOCM_RTS;
philpem@0 175 ioctl(ctx->fd, TIOCMSET, &status);
philpem@0 176
philpem@0 177 // Restore the port state and close the serial port.
philpem@0 178 tcsetattr(ctx->fd, TCSANOW, &ctx->oldtio);
philpem@0 179 close(ctx->fd);
philpem@0 180
philpem@0 181 // Done!
philpem@0 182 return LPFK_E_OK;
philpem@0 183 }
philpem@1 184 /* }}} */
philpem@0 185
philpem@1 186 /* lpfk_enable {{{ */
philpem@3 187 int lpfk_enable(LPFK_CTX *ctx, const int val)
philpem@0 188 {
philpem@0 189 if (val) {
philpem@0 190 // val == true, enable the LPFK
philpem@0 191 if (write(ctx->fd, "\x08", 1) != 1) {
philpem@0 192 ctx->enabled = true;
philpem@0 193 return LPFK_E_COMMS;
philpem@0 194 }
philpem@0 195 } else {
philpem@0 196 // val == false, disable the LPFK
philpem@0 197 if (write(ctx->fd, "\x09", 1) != 1) {
philpem@0 198 return LPFK_E_COMMS;
philpem@0 199 }
philpem@0 200 }
philpem@0 201
philpem@0 202 // update the context, return success
philpem@0 203 ctx->enabled = val;
philpem@0 204 return LPFK_E_OK;
philpem@0 205 }
philpem@0 206
philpem@1 207 /* }}} */
philpem@1 208
philpem@1 209 /* lpfk_set_led_cached {{{ */
philpem@1 210 int lpfk_set_led_cached(LPFK_CTX *ctx, const int num, const int state)
philpem@0 211 {
philpem@0 212 int i;
philpem@0 213 time_t tm;
philpem@0 214 unsigned long mask, leds;
philpem@0 215 unsigned char buf[5];
philpem@0 216 unsigned char status;
philpem@0 217
philpem@0 218 // check parameters
philpem@0 219 if ((num < 0) || (num > 31)) {
philpem@0 220 return LPFK_E_PARAM;
philpem@0 221 }
philpem@0 222
philpem@0 223 // parameters OK, now build the LED mask
philpem@1 224 mask = (0x80 >> (num % 8)) << ((3 - (num / 8)) * 8);
philpem@0 225
philpem@0 226 // mask the specified bit
philpem@0 227 if (state) {
philpem@1 228 ctx->led_mask |= mask;
philpem@0 229 } else {
philpem@1 230 ctx->led_mask &= ~mask;
philpem@0 231 }
philpem@0 232
philpem@1 233 return LPFK_E_OK;
philpem@1 234 }
philpem@1 235 /* }}} */
philpem@1 236
philpem@1 237 /* lpfk_set_leds_cached {{{ */
philpem@1 238 int lpfk_set_leds_cached(LPFK_CTX *ctx, const int state)
philpem@1 239 {
philpem@1 240 int i;
philpem@1 241 time_t tm;
philpem@1 242 unsigned long leds;
philpem@1 243 unsigned char buf[5];
philpem@1 244 unsigned char status;
philpem@1 245
philpem@1 246 if (state) {
philpem@1 247 // all LEDs on
philpem@1 248 ctx->led_mask = 0xFFFFFFFF;
philpem@1 249 } else {
philpem@1 250 // all LEDs off
philpem@1 251 ctx->led_mask = 0x00000000;
philpem@1 252 }
philpem@1 253
philpem@1 254 return LPFK_E_OK;
philpem@1 255 }
philpem@1 256 /* }}} */
philpem@1 257
philpem@1 258 /* lpfk_update_leds {{{ */
philpem@1 259 int lpfk_update_leds(LPFK_CTX *ctx)
philpem@1 260 {
philpem@1 261 int i;
philpem@1 262 time_t tm;
philpem@1 263 unsigned char buf[5];
philpem@1 264 unsigned char status;
philpem@1 265
philpem@0 266 // send new LED mask to the LPFK
philpem@0 267 buf[0] = 0x94;
philpem@1 268 buf[1] = ctx->led_mask >> 24;
philpem@1 269 buf[2] = ctx->led_mask >> 16;
philpem@1 270 buf[3] = ctx->led_mask >> 8;
philpem@1 271 buf[4] = ctx->led_mask & 0xff;
philpem@0 272
philpem@0 273 // make 5 attempts to set the LEDs
philpem@0 274 for (i=0; i<5; i++) {
philpem@0 275 if (write(ctx->fd, &buf, 5) < 5) {
philpem@0 276 continue;
philpem@0 277 }
philpem@0 278
philpem@0 279 // check for response -- 0x81 = OK, 0x80 = retransmit
philpem@0 280 // save current time (in seconds)
philpem@0 281 tm = time(NULL);
philpem@0 282
philpem@0 283 // loop until 2 seconds have passed, or LPFK responds
philpem@0 284 status = 0x00;
philpem@0 285 do {
philpem@0 286 // read data, loop if not successful
philpem@0 287 if (read(ctx->fd, &status, 1) < 1) {
philpem@0 288 continue;
philpem@0 289 }
philpem@0 290
philpem@0 291 // we got some data, what is it?
philpem@0 292 if (status == 0x81) {
philpem@0 293 // 0x81 -- received successfully
philpem@0 294 break;
philpem@0 295 }
philpem@0 296 } while ((time(NULL) - tm) < 2);
philpem@0 297
philpem@0 298 // status OK?
philpem@0 299 if (status == 0x81) {
philpem@0 300 // 0x81: OK
philpem@0 301 break;
philpem@0 302 } else if (status == 0x80) {
philpem@0 303 // 0x80: Retransmit request
philpem@0 304 continue;
philpem@0 305 }
philpem@0 306 }
philpem@0 307
philpem@0 308 return LPFK_E_OK;
philpem@0 309 }
philpem@1 310 /* }}} */
philpem@0 311
philpem@1 312 /* lpfk_set_led {{{ */
philpem@1 313 int lpfk_set_led(LPFK_CTX *ctx, const int num, const int state)
philpem@1 314 {
philpem@1 315 lpfk_set_led_cached(ctx, num, state);
philpem@1 316 return lpfk_update_leds(ctx);
philpem@1 317 }
philpem@1 318 /* }}} */
philpem@1 319
philpem@1 320 /* lpfk_set_leds {{{ */
philpem@0 321 int lpfk_set_leds(LPFK_CTX *ctx, const int state)
philpem@0 322 {
philpem@1 323 lpfk_set_leds_cached(ctx, state);
philpem@1 324 return lpfk_update_leds(ctx);
philpem@1 325 }
philpem@1 326 /* }}} */
philpem@0 327
philpem@1 328 /* lpfk_get_led {{{ */
philpem@0 329 int lpfk_get_led(LPFK_CTX *ctx, const int num)
philpem@0 330 {
philpem@0 331 unsigned long mask;
philpem@0 332
philpem@0 333 // check parameters
philpem@0 334 if ((num < 0) || (num > 31)) {
philpem@0 335 return false;
philpem@0 336 }
philpem@0 337
philpem@0 338 // parameters OK, now build the LED mask
philpem@1 339 mask = (0x80 >> (num % 8)) << ((3 - (num / 8)) * 8);
philpem@0 340 if (ctx->led_mask & mask) {
philpem@0 341 return true;
philpem@0 342 } else {
philpem@0 343 return false;
philpem@0 344 }
philpem@0 345 }
philpem@1 346 /* }}} */
philpem@0 347
philpem@1 348 /* lpfk_read {{{ */
philpem@0 349 int lpfk_read(LPFK_CTX *ctx)
philpem@0 350 {
philpem@0 351 int nbytes;
philpem@0 352 unsigned char key;
philpem@0 353
philpem@1 354 // make sure the LPFK is enabled before trying to read a scancode
philpem@1 355 if (!ctx->enabled) {
philpem@1 356 return LPFK_E_NOT_ENABLED;
philpem@1 357 }
philpem@1 358
philpem@0 359 // try and read a byte (keycode) from the LPFK
philpem@0 360 nbytes = read(ctx->fd, &key, 1);
philpem@0 361
philpem@0 362 if ((nbytes < 1) || (key > 31)) {
philpem@0 363 // no keys buffered, or keycode invalid.
philpem@4 364 return LPFK_E_NO_KEYS;
philpem@0 365 } else {
philpem@0 366 // key buffered, pass it along.
philpem@0 367 return key;
philpem@0 368 }
philpem@0 369 }
philpem@1 370 /* }}} */
philpem@0 371