src/liblpfk.c

Tue, 26 Aug 2008 12:45:33 +0100

author
Philip Pemberton <philpem@philpem.me.uk>
date
Tue, 26 Aug 2008 12:45:33 +0100
changeset 0
745037d69d81
child 1
3e775df109e6
permissions
-rw-r--r--

initial commit

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@0 59 int lpfk_open(const char *port, LPFK_CTX *ctx)
philpem@0 60 {
philpem@0 61 struct termios newtio;
philpem@0 62 int status;
philpem@0 63 int fd;
philpem@0 64 int i;
philpem@0 65
philpem@0 66 // open the serial port
philpem@0 67 fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
philpem@0 68 if (fd < 0) return LPFK_E_PORT_OPEN;
philpem@0 69
philpem@0 70 // save current port settings
philpem@0 71 tcgetattr(fd, &ctx->oldtio);
philpem@0 72
philpem@0 73 // set up new parameters
philpem@0 74 memset(&newtio, 0, sizeof(newtio));
philpem@0 75 // 9600 baud, 8 bits, parity enabled, odd parity
philpem@0 76 newtio.c_cflag = B9600 | CS8 | PARENB | PARODD | CLOCAL | CREAD;
philpem@0 77 newtio.c_iflag = 0;
philpem@0 78 newtio.c_oflag = 0;
philpem@0 79
philpem@0 80 // set input mode -- non canonical, no echo
philpem@0 81 newtio.c_lflag = 0;
philpem@0 82
philpem@0 83 // inter-character timer unused
philpem@0 84 newtio.c_cc[VTIME] = 0;
philpem@0 85 // read does not block waiting for characters if there are none in the buffer
philpem@0 86 newtio.c_cc[VMIN] = 0;
philpem@0 87
philpem@0 88 // flush input buffer
philpem@0 89 tcflush(fd, TCIFLUSH);
philpem@0 90
philpem@0 91 // set new port config
philpem@0 92 tcsetattr(fd, TCSANOW, &newtio);
philpem@0 93
philpem@0 94 // set RTS true to pull the LPFK out of reset
philpem@0 95 ioctl(fd, TIOCMGET, &status);
philpem@0 96 status |= TIOCM_RTS;
philpem@0 97 ioctl(fd, TIOCMSET, &status);
philpem@0 98
philpem@0 99 // wait a few seconds for the LPFK to become ready
philpem@0 100 sleep(2);
philpem@0 101
philpem@0 102 // 0x06: READ CONFIGURATION. LPFK sends 0x03 in response.
philpem@0 103 // Try five times to wake it up.
philpem@0 104 status = false;
philpem@0 105 for (i=0; i<5; i++) {
philpem@0 106 unsigned char buf;
philpem@0 107 time_t tm;
philpem@0 108
philpem@0 109 // Send 0x06: READ CONFIGURATION, loop on failure
philpem@0 110 if (write(fd, "\x06", 1) < 1) {
philpem@0 111 continue;
philpem@0 112 }
philpem@0 113
philpem@0 114 // save current time (in seconds)
philpem@0 115 tm = time(NULL);
philpem@0 116
philpem@0 117 // loop until 2 seconds have passed, or LPFK responds
philpem@0 118 status = false;
philpem@0 119 do {
philpem@0 120 // read data, loop if not successful
philpem@0 121 if (read(fd, &buf, 1) < 1) {
philpem@0 122 continue;
philpem@0 123 }
philpem@0 124
philpem@0 125 // we got some data, what is it?
philpem@0 126 if (buf == 0x03) {
philpem@0 127 // 0x03 -- correct response. we're done.
philpem@0 128 status = true;
philpem@0 129 }
philpem@0 130 } while (((time(NULL) - tm) < 2) && (!status));
philpem@0 131
philpem@0 132 // exit loop if we got the LPFK to talk
philpem@0 133 if (status) {
philpem@0 134 break;
philpem@0 135 }
philpem@0 136 }
philpem@0 137
philpem@0 138 // Did the LPFK respond?
philpem@0 139 if (!status) {
philpem@0 140 // LPFK isn't talking. Restore serial port state and exit.
philpem@0 141 tcsetattr(fd, TCSANOW, &ctx->oldtio);
philpem@0 142 close(fd);
philpem@0 143
philpem@0 144 return LPFK_E_NOT_PRESENT;
philpem@0 145 } else {
philpem@0 146 // Initialise LPFK context
philpem@0 147 ctx->led_mask = 0;
philpem@0 148 ctx->fd = fd;
philpem@0 149
philpem@0 150 // Enable the LPFK
philpem@0 151 write(fd, "\x08", 1);
philpem@0 152 ctx->enabled = true;
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@0 159 int lpfk_close(LPFK_CTX *ctx)
philpem@0 160 {
philpem@0 161 int status;
philpem@0 162
philpem@0 163 // 0x09: DISABLE. Stop the LPFK responding to keystrokes.
philpem@0 164 write(ctx->fd, "\x09", 1);
philpem@0 165
philpem@0 166 // turn all the LEDs off
philpem@0 167 lpfk_set_leds(ctx, false);
philpem@0 168
philpem@0 169 // set RTS false to put the LPFK into reset
philpem@0 170 ioctl(ctx->fd, TIOCMGET, &status);
philpem@0 171 status &= ~TIOCM_RTS;
philpem@0 172 ioctl(ctx->fd, TIOCMSET, &status);
philpem@0 173
philpem@0 174 // Restore the port state and close the serial port.
philpem@0 175 tcsetattr(ctx->fd, TCSANOW, &ctx->oldtio);
philpem@0 176 close(ctx->fd);
philpem@0 177
philpem@0 178 // Done!
philpem@0 179 return LPFK_E_OK;
philpem@0 180 }
philpem@0 181
philpem@0 182 int lpfk_enable(LPFK_CTX *ctx, int val)
philpem@0 183 {
philpem@0 184 if (val) {
philpem@0 185 // val == true, enable the LPFK
philpem@0 186 if (write(ctx->fd, "\x08", 1) != 1) {
philpem@0 187 ctx->enabled = true;
philpem@0 188 return LPFK_E_COMMS;
philpem@0 189 }
philpem@0 190 } else {
philpem@0 191 // val == false, disable the LPFK
philpem@0 192 if (write(ctx->fd, "\x09", 1) != 1) {
philpem@0 193 return LPFK_E_COMMS;
philpem@0 194 }
philpem@0 195 }
philpem@0 196
philpem@0 197 // update the context, return success
philpem@0 198 ctx->enabled = val;
philpem@0 199 return LPFK_E_OK;
philpem@0 200 }
philpem@0 201
philpem@0 202 /**
philpem@0 203 * @brief Set or clear an LED on the LPFK.
philpem@0 204 * @param ctx Pointer to an LPFK_CTX struct initialised by lpfk_open().
philpem@0 205 * @param num LED/key number, from 0 to 31.
philpem@0 206 * @param state State, true for on, false for off.
philpem@0 207 * @return LPFK_E_OK on success, LPFK_E_PARAM on bad parameter, LPFK_E_COMMS
philpem@0 208 * on comms error.
philpem@0 209 */
philpem@0 210 int lpfk_set_led(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@0 224 mask = (0x80 >> (num % 8)) << ((num / 8) * 8);
philpem@0 225
philpem@0 226 leds = ctx->led_mask;
philpem@0 227
philpem@0 228 // mask the specified bit
philpem@0 229 if (state) {
philpem@0 230 leds |= mask;
philpem@0 231 } else {
philpem@0 232 leds &= ~mask;
philpem@0 233 }
philpem@0 234
philpem@0 235 // send new LED mask to the LPFK
philpem@0 236 buf[0] = 0x94;
philpem@0 237 buf[1] = leds & 0xff;
philpem@0 238 buf[2] = leds >> 8;
philpem@0 239 buf[3] = leds >> 16;
philpem@0 240 buf[4] = leds >> 24;
philpem@0 241
philpem@0 242 // make 5 attempts to set the LEDs
philpem@0 243 for (i=0; i<5; i++) {
philpem@0 244 if (write(ctx->fd, &buf, 5) < 5) {
philpem@0 245 continue;
philpem@0 246 }
philpem@0 247
philpem@0 248 // check for response -- 0x81 = OK, 0x80 = retransmit
philpem@0 249 // save current time (in seconds)
philpem@0 250 tm = time(NULL);
philpem@0 251
philpem@0 252 // loop until 2 seconds have passed, or LPFK responds
philpem@0 253 status = 0x00;
philpem@0 254 do {
philpem@0 255 // read data, loop if not successful
philpem@0 256 if (read(ctx->fd, &status, 1) < 1) {
philpem@0 257 continue;
philpem@0 258 }
philpem@0 259
philpem@0 260 // we got some data, what is it?
philpem@0 261 if (status == 0x81) {
philpem@0 262 // 0x81 -- received successfully
philpem@0 263 break;
philpem@0 264 }
philpem@0 265 } while ((time(NULL) - tm) < 2);
philpem@0 266
philpem@0 267 // status OK?
philpem@0 268 if (status == 0x81) {
philpem@0 269 // 0x81: OK
philpem@0 270 break;
philpem@0 271 } else if (status == 0x80) {
philpem@0 272 // 0x80: Retransmit request
philpem@0 273 continue;
philpem@0 274 }
philpem@0 275 }
philpem@0 276
philpem@0 277 // update the context
philpem@0 278 ctx->led_mask = leds;
philpem@0 279
philpem@0 280 return LPFK_E_OK;
philpem@0 281 }
philpem@0 282
philpem@0 283 /**
philpem@0 284 * @brief Set or clear all the LEDs on the LPFK.
philpem@0 285 * @param ctx Pointer to an LPFK_CTX struct initialised by lpfk_open().
philpem@0 286 * @param state State, true for on, false for off.
philpem@0 287 * @return LPFK_E_OK on success, LPFK_E_PARAM on bad parameter, LPFK_E_COMMS
philpem@0 288 * on comms error.
philpem@0 289 */
philpem@0 290 int lpfk_set_leds(LPFK_CTX *ctx, const int state)
philpem@0 291 {
philpem@0 292 int i;
philpem@0 293 time_t tm;
philpem@0 294 unsigned long leds;
philpem@0 295 unsigned char buf[5];
philpem@0 296 unsigned char status;
philpem@0 297
philpem@0 298 if (state) {
philpem@0 299 // all LEDs on
philpem@0 300 leds = 0xFFFFFFFF;
philpem@0 301 } else {
philpem@0 302 // all LEDs off
philpem@0 303 leds = 0x00000000;
philpem@0 304 }
philpem@0 305
philpem@0 306 // send new LED mask to the LPFK
philpem@0 307 buf[0] = 0x94;
philpem@0 308 buf[1] = leds & 0xff;
philpem@0 309 buf[2] = leds >> 8;
philpem@0 310 buf[3] = leds >> 16;
philpem@0 311 buf[4] = leds >> 24;
philpem@0 312
philpem@0 313 // make 5 attempts to set the LEDs
philpem@0 314 for (i=0; i<5; i++) {
philpem@0 315 if (write(ctx->fd, &buf, 5) < 5) {
philpem@0 316 continue;
philpem@0 317 }
philpem@0 318
philpem@0 319 // check for response -- 0x81 = OK, 0x80 = retransmit
philpem@0 320 // save current time (in seconds)
philpem@0 321 tm = time(NULL);
philpem@0 322
philpem@0 323 // loop until 2 seconds have passed, or LPFK responds
philpem@0 324 status = 0x00;
philpem@0 325 do {
philpem@0 326 // read data, loop if not successful
philpem@0 327 if (read(ctx->fd, &status, 1) < 1) {
philpem@0 328 continue;
philpem@0 329 }
philpem@0 330
philpem@0 331 // we got some data, what is it?
philpem@0 332 if (status == 0x81) {
philpem@0 333 // 0x81 -- received successfully
philpem@0 334 break;
philpem@0 335 }
philpem@0 336 } while ((time(NULL) - tm) < 2);
philpem@0 337
philpem@0 338 // status OK?
philpem@0 339 if (status == 0x81) {
philpem@0 340 // 0x81: OK
philpem@0 341 break;
philpem@0 342 } else if (status == 0x80) {
philpem@0 343 // 0x80: Retransmit request
philpem@0 344 continue;
philpem@0 345 }
philpem@0 346 }
philpem@0 347
philpem@0 348 // update the context
philpem@0 349 ctx->led_mask = leds;
philpem@0 350
philpem@0 351 return LPFK_E_OK;
philpem@0 352 }
philpem@0 353
philpem@0 354 /**
philpem@0 355 * @brief Get the status of an LED on the LPFK.
philpem@0 356 * @param ctx Pointer to an LPFK_CTX struct initialised by lpfk_open().
philpem@0 357 * @param num LED/key number, from 0 to 31.
philpem@0 358 * @return true if LED is on, false otherwise.
philpem@0 359 */
philpem@0 360 int lpfk_get_led(LPFK_CTX *ctx, const int num)
philpem@0 361 {
philpem@0 362 unsigned long mask;
philpem@0 363
philpem@0 364 // check parameters
philpem@0 365 if ((num < 0) || (num > 31)) {
philpem@0 366 return false;
philpem@0 367 }
philpem@0 368
philpem@0 369 // parameters OK, now build the LED mask
philpem@0 370 mask = (0x80 >> (num % 8)) << ((num / 8) * 8);
philpem@0 371 if (ctx->led_mask & mask) {
philpem@0 372 return true;
philpem@0 373 } else {
philpem@0 374 return false;
philpem@0 375 }
philpem@0 376 }
philpem@0 377
philpem@0 378 /**
philpem@0 379 * @brief Read a key from the LPFK
philpem@0 380 * @param ctx Pointer to an LPFK_CTX struct initialised by lpfk_open().
philpem@0 381 * @return -1 if no keys in buffer, 0-31 for key 1-32 down.
philpem@0 382 */
philpem@0 383 int lpfk_read(LPFK_CTX *ctx)
philpem@0 384 {
philpem@0 385 int nbytes;
philpem@0 386 unsigned char key;
philpem@0 387
philpem@0 388 // try and read a byte (keycode) from the LPFK
philpem@0 389 nbytes = read(ctx->fd, &key, 1);
philpem@0 390
philpem@0 391 if ((nbytes < 1) || (key > 31)) {
philpem@0 392 // no keys buffered, or keycode invalid.
philpem@0 393 return -1;
philpem@0 394 } else {
philpem@0 395 // key buffered, pass it along.
philpem@0 396 return key;
philpem@0 397 }
philpem@0 398 }
philpem@0 399