src/liblpfk.c

Tue, 26 Aug 2008 13:18:18 +0100

author
Philip Pemberton <philpem@philpem.me.uk>
date
Tue, 26 Aug 2008 13:18:18 +0100
changeset 1
3e775df109e6
parent 0
745037d69d81
child 2
071056e12321
permissions
-rw-r--r--

added: Vim folding markers to all functions
added: Cached LED write (TODO: add to test app)
added: LPFK keys no longer active by default
TODO: build Doxyfile for Doxygen documentation generation

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@0 60 int lpfk_open(const char *port, LPFK_CTX *ctx)
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@0 151 // Enable the LPFK
philpem@0 152 write(fd, "\x08", 1);
philpem@0 153 ctx->enabled = true;
philpem@0 154
philpem@0 155 // Return OK status
philpem@0 156 return LPFK_E_OK;
philpem@0 157 }
philpem@0 158 }
philpem@0 159
philpem@1 160 /* }}} */
philpem@1 161
philpem@1 162 /* lpfk_close {{{ */
philpem@0 163 int lpfk_close(LPFK_CTX *ctx)
philpem@0 164 {
philpem@0 165 int status;
philpem@0 166
philpem@0 167 // 0x09: DISABLE. Stop the LPFK responding to keystrokes.
philpem@0 168 write(ctx->fd, "\x09", 1);
philpem@0 169
philpem@0 170 // turn all the LEDs off
philpem@0 171 lpfk_set_leds(ctx, false);
philpem@0 172
philpem@0 173 // set RTS false to put the LPFK into reset
philpem@0 174 ioctl(ctx->fd, TIOCMGET, &status);
philpem@0 175 status &= ~TIOCM_RTS;
philpem@0 176 ioctl(ctx->fd, TIOCMSET, &status);
philpem@0 177
philpem@0 178 // Restore the port state and close the serial port.
philpem@0 179 tcsetattr(ctx->fd, TCSANOW, &ctx->oldtio);
philpem@0 180 close(ctx->fd);
philpem@0 181
philpem@0 182 // Done!
philpem@0 183 return LPFK_E_OK;
philpem@0 184 }
philpem@1 185 /* }}} */
philpem@0 186
philpem@1 187 /* lpfk_enable {{{ */
philpem@0 188 int lpfk_enable(LPFK_CTX *ctx, int val)
philpem@0 189 {
philpem@0 190 if (val) {
philpem@0 191 // val == true, enable the LPFK
philpem@0 192 if (write(ctx->fd, "\x08", 1) != 1) {
philpem@0 193 ctx->enabled = true;
philpem@0 194 return LPFK_E_COMMS;
philpem@0 195 }
philpem@0 196 } else {
philpem@0 197 // val == false, disable the LPFK
philpem@0 198 if (write(ctx->fd, "\x09", 1) != 1) {
philpem@0 199 return LPFK_E_COMMS;
philpem@0 200 }
philpem@0 201 }
philpem@0 202
philpem@0 203 // update the context, return success
philpem@0 204 ctx->enabled = val;
philpem@0 205 return LPFK_E_OK;
philpem@0 206 }
philpem@0 207
philpem@1 208 /* }}} */
philpem@1 209
philpem@1 210 /* lpfk_set_led_cached {{{ */
philpem@1 211 int lpfk_set_led_cached(LPFK_CTX *ctx, const int num, const int state)
philpem@0 212 {
philpem@0 213 int i;
philpem@0 214 time_t tm;
philpem@0 215 unsigned long mask, leds;
philpem@0 216 unsigned char buf[5];
philpem@0 217 unsigned char status;
philpem@0 218
philpem@0 219 // check parameters
philpem@0 220 if ((num < 0) || (num > 31)) {
philpem@0 221 return LPFK_E_PARAM;
philpem@0 222 }
philpem@0 223
philpem@0 224 // parameters OK, now build the LED mask
philpem@1 225 mask = (0x80 >> (num % 8)) << ((3 - (num / 8)) * 8);
philpem@0 226
philpem@0 227 // mask the specified bit
philpem@0 228 if (state) {
philpem@1 229 ctx->led_mask |= mask;
philpem@0 230 } else {
philpem@1 231 ctx->led_mask &= ~mask;
philpem@0 232 }
philpem@0 233
philpem@1 234 return LPFK_E_OK;
philpem@1 235 }
philpem@1 236 /* }}} */
philpem@1 237
philpem@1 238 /* lpfk_set_leds_cached {{{ */
philpem@1 239 int lpfk_set_leds_cached(LPFK_CTX *ctx, const int state)
philpem@1 240 {
philpem@1 241 int i;
philpem@1 242 time_t tm;
philpem@1 243 unsigned long leds;
philpem@1 244 unsigned char buf[5];
philpem@1 245 unsigned char status;
philpem@1 246
philpem@1 247 if (state) {
philpem@1 248 // all LEDs on
philpem@1 249 ctx->led_mask = 0xFFFFFFFF;
philpem@1 250 } else {
philpem@1 251 // all LEDs off
philpem@1 252 ctx->led_mask = 0x00000000;
philpem@1 253 }
philpem@1 254
philpem@1 255 return LPFK_E_OK;
philpem@1 256 }
philpem@1 257 /* }}} */
philpem@1 258
philpem@1 259 /* lpfk_update_leds {{{ */
philpem@1 260 int lpfk_update_leds(LPFK_CTX *ctx)
philpem@1 261 {
philpem@1 262 int i;
philpem@1 263 time_t tm;
philpem@1 264 unsigned char buf[5];
philpem@1 265 unsigned char status;
philpem@1 266
philpem@0 267 // send new LED mask to the LPFK
philpem@0 268 buf[0] = 0x94;
philpem@1 269 buf[1] = ctx->led_mask >> 24;
philpem@1 270 buf[2] = ctx->led_mask >> 16;
philpem@1 271 buf[3] = ctx->led_mask >> 8;
philpem@1 272 buf[4] = ctx->led_mask & 0xff;
philpem@0 273
philpem@0 274 // make 5 attempts to set the LEDs
philpem@0 275 for (i=0; i<5; i++) {
philpem@0 276 if (write(ctx->fd, &buf, 5) < 5) {
philpem@0 277 continue;
philpem@0 278 }
philpem@0 279
philpem@0 280 // check for response -- 0x81 = OK, 0x80 = retransmit
philpem@0 281 // save current time (in seconds)
philpem@0 282 tm = time(NULL);
philpem@0 283
philpem@0 284 // loop until 2 seconds have passed, or LPFK responds
philpem@0 285 status = 0x00;
philpem@0 286 do {
philpem@0 287 // read data, loop if not successful
philpem@0 288 if (read(ctx->fd, &status, 1) < 1) {
philpem@0 289 continue;
philpem@0 290 }
philpem@0 291
philpem@0 292 // we got some data, what is it?
philpem@0 293 if (status == 0x81) {
philpem@0 294 // 0x81 -- received successfully
philpem@0 295 break;
philpem@0 296 }
philpem@0 297 } while ((time(NULL) - tm) < 2);
philpem@0 298
philpem@0 299 // status OK?
philpem@0 300 if (status == 0x81) {
philpem@0 301 // 0x81: OK
philpem@0 302 break;
philpem@0 303 } else if (status == 0x80) {
philpem@0 304 // 0x80: Retransmit request
philpem@0 305 continue;
philpem@0 306 }
philpem@0 307 }
philpem@0 308
philpem@0 309 return LPFK_E_OK;
philpem@0 310 }
philpem@1 311 /* }}} */
philpem@0 312
philpem@1 313 /* lpfk_set_led {{{ */
philpem@1 314 int lpfk_set_led(LPFK_CTX *ctx, const int num, const int state)
philpem@1 315 {
philpem@1 316 lpfk_set_led_cached(ctx, num, state);
philpem@1 317 return lpfk_update_leds(ctx);
philpem@1 318 }
philpem@1 319 /* }}} */
philpem@1 320
philpem@1 321 /* lpfk_set_leds {{{ */
philpem@0 322 int lpfk_set_leds(LPFK_CTX *ctx, const int state)
philpem@0 323 {
philpem@1 324 lpfk_set_leds_cached(ctx, state);
philpem@1 325 return lpfk_update_leds(ctx);
philpem@1 326 }
philpem@1 327 /* }}} */
philpem@0 328
philpem@1 329 /* lpfk_get_led {{{ */
philpem@0 330 int lpfk_get_led(LPFK_CTX *ctx, const int num)
philpem@0 331 {
philpem@0 332 unsigned long mask;
philpem@0 333
philpem@0 334 // check parameters
philpem@0 335 if ((num < 0) || (num > 31)) {
philpem@0 336 return false;
philpem@0 337 }
philpem@0 338
philpem@0 339 // parameters OK, now build the LED mask
philpem@1 340 mask = (0x80 >> (num % 8)) << ((3 - (num / 8)) * 8);
philpem@0 341 if (ctx->led_mask & mask) {
philpem@0 342 return true;
philpem@0 343 } else {
philpem@0 344 return false;
philpem@0 345 }
philpem@0 346 }
philpem@1 347 /* }}} */
philpem@0 348
philpem@1 349 /* lpfk_read {{{ */
philpem@0 350 int lpfk_read(LPFK_CTX *ctx)
philpem@0 351 {
philpem@0 352 int nbytes;
philpem@0 353 unsigned char key;
philpem@0 354
philpem@1 355 // make sure the LPFK is enabled before trying to read a scancode
philpem@1 356 if (!ctx->enabled) {
philpem@1 357 return LPFK_E_NOT_ENABLED;
philpem@1 358 }
philpem@1 359
philpem@0 360 // try and read a byte (keycode) from the LPFK
philpem@0 361 nbytes = read(ctx->fd, &key, 1);
philpem@0 362
philpem@0 363 if ((nbytes < 1) || (key > 31)) {
philpem@0 364 // no keys buffered, or keycode invalid.
philpem@0 365 return -1;
philpem@0 366 } else {
philpem@0 367 // key buffered, pass it along.
philpem@0 368 return key;
philpem@0 369 }
philpem@0 370 }
philpem@1 371 /* }}} */
philpem@0 372