1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/liblpfk.c Tue Aug 26 12:45:33 2008 +0100 1.3 @@ -0,0 +1,399 @@ 1.4 +/**************************************************************************** 1.5 + * Project: liblpfk 1.6 + * Purpose: Driver library for the IBM 6094-020 Lighted Program Function 1.7 + * Keyboard. 1.8 + * Version: 1.0 1.9 + * Author: Philip Pemberton <philpem@philpem.me.uk> 1.10 + * 1.11 + * The latest version of this library is available from 1.12 + * <http://www.philpem.me.uk/code/liblpfk/>. 1.13 + * 1.14 + * Copyright (c) 2008, Philip Pemberton 1.15 + * All rights reserved. 1.16 + * 1.17 + * Redistribution and use in source and binary forms, with or without 1.18 + * modification, are permitted provided that the following conditions 1.19 + * are met: 1.20 + * 1.21 + * * Redistributions of source code must retain the above copyright 1.22 + * notice, this list of conditions and the following disclaimer. 1.23 + * * Redistributions in binary form must reproduce the above copyright 1.24 + * notice, this list of conditions and the following disclaimer in 1.25 + * the documentation and/or other materials provided with the 1.26 + * distribution. 1.27 + * * Neither the name of the project nor the names of its 1.28 + * contributors may be used to endorse or promote products derived 1.29 + * from this software without specific prior written permission. 1.30 + * 1.31 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 1.32 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 1.33 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 1.34 + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 1.35 + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 1.36 + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 1.37 + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 1.38 + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 1.39 + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 1.40 + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 1.41 + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 1.42 + ****************************************************************************/ 1.43 + 1.44 +/** 1.45 + * @file liblpfk.c 1.46 + * @brief liblpfk library, main source 1.47 + */ 1.48 + 1.49 +#include <sys/types.h> 1.50 +#include <sys/stat.h> 1.51 +#include <fcntl.h> 1.52 +#include <unistd.h> 1.53 +#include <termios.h> 1.54 +#include <sys/ioctl.h> 1.55 +#include <time.h> 1.56 +#include <string.h> 1.57 +#include <stdbool.h> 1.58 +#include <stdio.h> 1.59 + 1.60 +#include "liblpfk.h" 1.61 + 1.62 +int lpfk_open(const char *port, LPFK_CTX *ctx) 1.63 +{ 1.64 + struct termios newtio; 1.65 + int status; 1.66 + int fd; 1.67 + int i; 1.68 + 1.69 + // open the serial port 1.70 + fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY); 1.71 + if (fd < 0) return LPFK_E_PORT_OPEN; 1.72 + 1.73 + // save current port settings 1.74 + tcgetattr(fd, &ctx->oldtio); 1.75 + 1.76 + // set up new parameters 1.77 + memset(&newtio, 0, sizeof(newtio)); 1.78 + // 9600 baud, 8 bits, parity enabled, odd parity 1.79 + newtio.c_cflag = B9600 | CS8 | PARENB | PARODD | CLOCAL | CREAD; 1.80 + newtio.c_iflag = 0; 1.81 + newtio.c_oflag = 0; 1.82 + 1.83 + // set input mode -- non canonical, no echo 1.84 + newtio.c_lflag = 0; 1.85 + 1.86 + // inter-character timer unused 1.87 + newtio.c_cc[VTIME] = 0; 1.88 + // read does not block waiting for characters if there are none in the buffer 1.89 + newtio.c_cc[VMIN] = 0; 1.90 + 1.91 + // flush input buffer 1.92 + tcflush(fd, TCIFLUSH); 1.93 + 1.94 + // set new port config 1.95 + tcsetattr(fd, TCSANOW, &newtio); 1.96 + 1.97 + // set RTS true to pull the LPFK out of reset 1.98 + ioctl(fd, TIOCMGET, &status); 1.99 + status |= TIOCM_RTS; 1.100 + ioctl(fd, TIOCMSET, &status); 1.101 + 1.102 + // wait a few seconds for the LPFK to become ready 1.103 + sleep(2); 1.104 + 1.105 + // 0x06: READ CONFIGURATION. LPFK sends 0x03 in response. 1.106 + // Try five times to wake it up. 1.107 + status = false; 1.108 + for (i=0; i<5; i++) { 1.109 + unsigned char buf; 1.110 + time_t tm; 1.111 + 1.112 + // Send 0x06: READ CONFIGURATION, loop on failure 1.113 + if (write(fd, "\x06", 1) < 1) { 1.114 + continue; 1.115 + } 1.116 + 1.117 + // save current time (in seconds) 1.118 + tm = time(NULL); 1.119 + 1.120 + // loop until 2 seconds have passed, or LPFK responds 1.121 + status = false; 1.122 + do { 1.123 + // read data, loop if not successful 1.124 + if (read(fd, &buf, 1) < 1) { 1.125 + continue; 1.126 + } 1.127 + 1.128 + // we got some data, what is it? 1.129 + if (buf == 0x03) { 1.130 + // 0x03 -- correct response. we're done. 1.131 + status = true; 1.132 + } 1.133 + } while (((time(NULL) - tm) < 2) && (!status)); 1.134 + 1.135 + // exit loop if we got the LPFK to talk 1.136 + if (status) { 1.137 + break; 1.138 + } 1.139 + } 1.140 + 1.141 + // Did the LPFK respond? 1.142 + if (!status) { 1.143 + // LPFK isn't talking. Restore serial port state and exit. 1.144 + tcsetattr(fd, TCSANOW, &ctx->oldtio); 1.145 + close(fd); 1.146 + 1.147 + return LPFK_E_NOT_PRESENT; 1.148 + } else { 1.149 + // Initialise LPFK context 1.150 + ctx->led_mask = 0; 1.151 + ctx->fd = fd; 1.152 + 1.153 + // Enable the LPFK 1.154 + write(fd, "\x08", 1); 1.155 + ctx->enabled = true; 1.156 + 1.157 + // Return OK status 1.158 + return LPFK_E_OK; 1.159 + } 1.160 +} 1.161 + 1.162 +int lpfk_close(LPFK_CTX *ctx) 1.163 +{ 1.164 + int status; 1.165 + 1.166 + // 0x09: DISABLE. Stop the LPFK responding to keystrokes. 1.167 + write(ctx->fd, "\x09", 1); 1.168 + 1.169 + // turn all the LEDs off 1.170 + lpfk_set_leds(ctx, false); 1.171 + 1.172 + // set RTS false to put the LPFK into reset 1.173 + ioctl(ctx->fd, TIOCMGET, &status); 1.174 + status &= ~TIOCM_RTS; 1.175 + ioctl(ctx->fd, TIOCMSET, &status); 1.176 + 1.177 + // Restore the port state and close the serial port. 1.178 + tcsetattr(ctx->fd, TCSANOW, &ctx->oldtio); 1.179 + close(ctx->fd); 1.180 + 1.181 + // Done! 1.182 + return LPFK_E_OK; 1.183 +} 1.184 + 1.185 +int lpfk_enable(LPFK_CTX *ctx, int val) 1.186 +{ 1.187 + if (val) { 1.188 + // val == true, enable the LPFK 1.189 + if (write(ctx->fd, "\x08", 1) != 1) { 1.190 + ctx->enabled = true; 1.191 + return LPFK_E_COMMS; 1.192 + } 1.193 + } else { 1.194 + // val == false, disable the LPFK 1.195 + if (write(ctx->fd, "\x09", 1) != 1) { 1.196 + return LPFK_E_COMMS; 1.197 + } 1.198 + } 1.199 + 1.200 + // update the context, return success 1.201 + ctx->enabled = val; 1.202 + return LPFK_E_OK; 1.203 +} 1.204 + 1.205 +/** 1.206 + * @brief Set or clear an LED on the LPFK. 1.207 + * @param ctx Pointer to an LPFK_CTX struct initialised by lpfk_open(). 1.208 + * @param num LED/key number, from 0 to 31. 1.209 + * @param state State, true for on, false for off. 1.210 + * @return LPFK_E_OK on success, LPFK_E_PARAM on bad parameter, LPFK_E_COMMS 1.211 + * on comms error. 1.212 + */ 1.213 +int lpfk_set_led(LPFK_CTX *ctx, const int num, const int state) 1.214 +{ 1.215 + int i; 1.216 + time_t tm; 1.217 + unsigned long mask, leds; 1.218 + unsigned char buf[5]; 1.219 + unsigned char status; 1.220 + 1.221 + // check parameters 1.222 + if ((num < 0) || (num > 31)) { 1.223 + return LPFK_E_PARAM; 1.224 + } 1.225 + 1.226 + // parameters OK, now build the LED mask 1.227 + mask = (0x80 >> (num % 8)) << ((num / 8) * 8); 1.228 + 1.229 + leds = ctx->led_mask; 1.230 + 1.231 + // mask the specified bit 1.232 + if (state) { 1.233 + leds |= mask; 1.234 + } else { 1.235 + leds &= ~mask; 1.236 + } 1.237 + 1.238 + // send new LED mask to the LPFK 1.239 + buf[0] = 0x94; 1.240 + buf[1] = leds & 0xff; 1.241 + buf[2] = leds >> 8; 1.242 + buf[3] = leds >> 16; 1.243 + buf[4] = leds >> 24; 1.244 + 1.245 + // make 5 attempts to set the LEDs 1.246 + for (i=0; i<5; i++) { 1.247 + if (write(ctx->fd, &buf, 5) < 5) { 1.248 + continue; 1.249 + } 1.250 + 1.251 + // check for response -- 0x81 = OK, 0x80 = retransmit 1.252 + // save current time (in seconds) 1.253 + tm = time(NULL); 1.254 + 1.255 + // loop until 2 seconds have passed, or LPFK responds 1.256 + status = 0x00; 1.257 + do { 1.258 + // read data, loop if not successful 1.259 + if (read(ctx->fd, &status, 1) < 1) { 1.260 + continue; 1.261 + } 1.262 + 1.263 + // we got some data, what is it? 1.264 + if (status == 0x81) { 1.265 + // 0x81 -- received successfully 1.266 + break; 1.267 + } 1.268 + } while ((time(NULL) - tm) < 2); 1.269 + 1.270 + // status OK? 1.271 + if (status == 0x81) { 1.272 + // 0x81: OK 1.273 + break; 1.274 + } else if (status == 0x80) { 1.275 + // 0x80: Retransmit request 1.276 + continue; 1.277 + } 1.278 + } 1.279 + 1.280 + // update the context 1.281 + ctx->led_mask = leds; 1.282 + 1.283 + return LPFK_E_OK; 1.284 +} 1.285 + 1.286 +/** 1.287 + * @brief Set or clear all the LEDs on the LPFK. 1.288 + * @param ctx Pointer to an LPFK_CTX struct initialised by lpfk_open(). 1.289 + * @param state State, true for on, false for off. 1.290 + * @return LPFK_E_OK on success, LPFK_E_PARAM on bad parameter, LPFK_E_COMMS 1.291 + * on comms error. 1.292 + */ 1.293 +int lpfk_set_leds(LPFK_CTX *ctx, const int state) 1.294 +{ 1.295 + int i; 1.296 + time_t tm; 1.297 + unsigned long leds; 1.298 + unsigned char buf[5]; 1.299 + unsigned char status; 1.300 + 1.301 + if (state) { 1.302 + // all LEDs on 1.303 + leds = 0xFFFFFFFF; 1.304 + } else { 1.305 + // all LEDs off 1.306 + leds = 0x00000000; 1.307 + } 1.308 + 1.309 + // send new LED mask to the LPFK 1.310 + buf[0] = 0x94; 1.311 + buf[1] = leds & 0xff; 1.312 + buf[2] = leds >> 8; 1.313 + buf[3] = leds >> 16; 1.314 + buf[4] = leds >> 24; 1.315 + 1.316 + // make 5 attempts to set the LEDs 1.317 + for (i=0; i<5; i++) { 1.318 + if (write(ctx->fd, &buf, 5) < 5) { 1.319 + continue; 1.320 + } 1.321 + 1.322 + // check for response -- 0x81 = OK, 0x80 = retransmit 1.323 + // save current time (in seconds) 1.324 + tm = time(NULL); 1.325 + 1.326 + // loop until 2 seconds have passed, or LPFK responds 1.327 + status = 0x00; 1.328 + do { 1.329 + // read data, loop if not successful 1.330 + if (read(ctx->fd, &status, 1) < 1) { 1.331 + continue; 1.332 + } 1.333 + 1.334 + // we got some data, what is it? 1.335 + if (status == 0x81) { 1.336 + // 0x81 -- received successfully 1.337 + break; 1.338 + } 1.339 + } while ((time(NULL) - tm) < 2); 1.340 + 1.341 + // status OK? 1.342 + if (status == 0x81) { 1.343 + // 0x81: OK 1.344 + break; 1.345 + } else if (status == 0x80) { 1.346 + // 0x80: Retransmit request 1.347 + continue; 1.348 + } 1.349 + } 1.350 + 1.351 + // update the context 1.352 + ctx->led_mask = leds; 1.353 + 1.354 + return LPFK_E_OK; 1.355 +} 1.356 + 1.357 +/** 1.358 + * @brief Get the status of an LED on the LPFK. 1.359 + * @param ctx Pointer to an LPFK_CTX struct initialised by lpfk_open(). 1.360 + * @param num LED/key number, from 0 to 31. 1.361 + * @return true if LED is on, false otherwise. 1.362 + */ 1.363 +int lpfk_get_led(LPFK_CTX *ctx, const int num) 1.364 +{ 1.365 + unsigned long mask; 1.366 + 1.367 + // check parameters 1.368 + if ((num < 0) || (num > 31)) { 1.369 + return false; 1.370 + } 1.371 + 1.372 + // parameters OK, now build the LED mask 1.373 + mask = (0x80 >> (num % 8)) << ((num / 8) * 8); 1.374 + if (ctx->led_mask & mask) { 1.375 + return true; 1.376 + } else { 1.377 + return false; 1.378 + } 1.379 +} 1.380 + 1.381 +/** 1.382 + * @brief Read a key from the LPFK 1.383 + * @param ctx Pointer to an LPFK_CTX struct initialised by lpfk_open(). 1.384 + * @return -1 if no keys in buffer, 0-31 for key 1-32 down. 1.385 + */ 1.386 +int lpfk_read(LPFK_CTX *ctx) 1.387 +{ 1.388 + int nbytes; 1.389 + unsigned char key; 1.390 + 1.391 + // try and read a byte (keycode) from the LPFK 1.392 + nbytes = read(ctx->fd, &key, 1); 1.393 + 1.394 + if ((nbytes < 1) || (key > 31)) { 1.395 + // no keys buffered, or keycode invalid. 1.396 + return -1; 1.397 + } else { 1.398 + // key buffered, pass it along. 1.399 + return key; 1.400 + } 1.401 +} 1.402 +