Espruino/src/jsdevices.c
2013-11-04 17:48:34 +00:00

337 lines
10 KiB
C

/*
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
*
* Copyright (C) 2013 Gordon Williams <gw@pur3.co.uk>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* ----------------------------------------------------------------------------
* Common low-level device handling (Events, IO buffers)
* ----------------------------------------------------------------------------
*/
#include "jsdevices.h"
#include "jsparse.h"
#include "jsinteractive.h"
#ifdef LINUX
#include <signal.h>
#endif//LINUX
#ifdef USE_TRIGGER
#include "trigger.h"
#endif
#ifdef USE_CC3000
#include "board_spi.h"
#endif
// ----------------------------------------------------------------------------
// BUFFERS
// ----------------------------------------------------------------------------
// DATA TRANSMIT BUFFER
typedef struct {
IOEventFlags flags; // Where this data should be transmitted
unsigned char data; // data to transmit
} PACKED_FLAGS TxBufferItem;
TxBufferItem txBuffer[TXBUFFERMASK+1];
volatile unsigned char txHead=0, txTail=0;
// ----------------------------------------------------------------------------
// Queue a character for transmission
void jshTransmit(IOEventFlags device, unsigned char data) {
#ifndef LINUX
#ifdef USB
if (device==EV_USBSERIAL && !jshIsUSBSERIALConnected()) {
jshTransmitClearDevice(EV_USBSERIAL); // clear out stuff already waiting
return;
}
#endif
if (device==EV_NONE) return;
unsigned char txHeadNext = (txHead+1)&TXBUFFERMASK;
if (txHeadNext==txTail) {
jsiSetBusy(BUSY_TRANSMIT, true);
while (txHeadNext==txTail) {
// wait for send to finish as buffer is about to overflow
#ifdef USB
// just in case USB was unplugged while we were waiting!
if (!jshIsUSBSERIALConnected()) jshTransmitClearDevice(EV_USBSERIAL);
#endif
}
jsiSetBusy(BUSY_TRANSMIT, false);
}
txBuffer[txHead].flags = device;
txBuffer[txHead].data = (char)data;
txHead = txHeadNext;
jshUSARTKick(device); // set up interrupts if required
#else // if PC, just put to stdout
if (device==DEFAULT_CONSOLE_DEVICE) {
fputc(data, stdout);
fflush(stdout);
}
#endif
}
// Try and get a character for transmission - could just return -1 if nothing
int jshGetCharToTransmit(IOEventFlags device) {
unsigned char ptr = txTail;
while (txHead != ptr) {
if (IOEVENTFLAGS_GETTYPE(txBuffer[ptr].flags) == device) {
unsigned char data = txBuffer[ptr].data;
if (ptr != txTail) { // so we weren't right at the back of the queue
// we need to work back from ptr (until we hit tail), shifting everything forwards
unsigned char this = ptr;
unsigned char last = (unsigned char)((this+TXBUFFERMASK)&TXBUFFERMASK);
while (this!=txTail) { // if this==txTail, then last is before it, so stop here
txBuffer[this] = txBuffer[last];
this = last;
last = (unsigned char)((this+TXBUFFERMASK)&TXBUFFERMASK);
}
}
txTail = (unsigned char)((txTail+1)&TXBUFFERMASK); // advance the tail
return data; // return data
}
ptr = (unsigned char)((ptr+1)&TXBUFFERMASK);
}
return -1; // no data :(
}
void jshTransmitFlush() {
jsiSetBusy(BUSY_TRANSMIT, true);
while (jshHasTransmitData()) ; // wait for send to finish
jsiSetBusy(BUSY_TRANSMIT, false);
}
// Clear everything from a device
void jshTransmitClearDevice(IOEventFlags device) {
while (jshGetCharToTransmit(device)>=0);
}
bool jshHasTransmitData() {
#ifndef LINUX
return txHead != txTail;
#else
return false;
#endif
}
// ----------------------------------------------------------------------------
// IO EVENT BUFFER
IOEvent ioBuffer[IOBUFFERMASK+1];
volatile unsigned char ioHead=0, ioTail=0;
// ----------------------------------------------------------------------------
void jshIOEventOverflowed() {
// Error here - just light LED as we can't do much else right now
#ifdef LED1_PININDEX
jshPinOutput(LED1_PININDEX, 1);
#endif
}
void jshPushIOCharEvent(IOEventFlags channel, char charData) {
if (charData==3 && channel==jsiGetConsoleDevice()) {
// Ctrl-C - force interrupt
#ifdef LINUX
raise(SIGINT);
#endif
jspSetInterrupted(true);
return;
}
if (DEVICE_IS_USART(channel) && jshGetEventsUsed() > IOBUFFER_XOFF)
jshSetFlowControlXON(channel, false);
// Check for existing buffer (we must have at least 2 in the queue to avoid dropping chars though!)
unsigned char nextTail = (unsigned char)((ioTail+1) & IOBUFFERMASK);
if (ioHead!=ioTail && ioHead!=nextTail) {
// we can do this because we only read in main loop, and we're in an interrupt here
unsigned char lastHead = (unsigned char)((ioHead+IOBUFFERMASK) & IOBUFFERMASK); // one behind head
if (IOEVENTFLAGS_GETTYPE(ioBuffer[lastHead].flags) == channel &&
IOEVENTFLAGS_GETCHARS(ioBuffer[lastHead].flags) < IOEVENT_MAXCHARS) {
// last event was for this event type, and it has chars left
unsigned char c = (unsigned char)IOEVENTFLAGS_GETCHARS(ioBuffer[lastHead].flags);
ioBuffer[lastHead].data.chars[c] = charData;
IOEVENTFLAGS_SETCHARS(ioBuffer[lastHead].flags, c+1);
return;
}
}
// Make new buffer
unsigned char nextHead = (unsigned char)((ioHead+1) & IOBUFFERMASK);
if (ioTail == nextHead) {
jshIOEventOverflowed();
return; // queue full - dump this event!
}
ioBuffer[ioHead].flags = channel;
IOEVENTFLAGS_SETCHARS(ioBuffer[ioHead].flags, 1);
ioBuffer[ioHead].data.chars[0] = charData;
ioHead = nextHead;
}
void jshPushIOWatchEvent(IOEventFlags channel) {
JsSysTime time = jshGetSystemTime();
bool state = jshGetWatchedPinState(channel);
/* // This is some simple debounce code - however it requires that the event
// is not taken out of ioBuffer by the main thread, which will require
// a bit of fiddling in jsinteractive.c. In fact it might be worth just
// doing debounce outside of the interrupt
if (true) { // debounce
// scan back and see if we have an event for this pin
unsigned char prevHead = ioHead;
while (prevHead!=ioTail && (IOEVENTFLAGS_GETTYPE(ioBuffer[prevHead].flags)!=channel))
prevHead = (unsigned char)((prevHead+IOBUFFERMASK) & IOBUFFERMASK); // step back
// if we have an event
if (prevHead!=ioTail && (IOEVENTFLAGS_GETTYPE(ioBuffer[prevHead].flags)==channel)) {
// just use it (with the same timestamp)...
ioBuffer[prevHead].flags = channel | (state?EV_EXTI_IS_HIGH:0);
return;
}
}*/
#ifdef USE_TRIGGER
if (trigHandleEXTI(channel | (state?EV_EXTI_IS_HIGH:0), time))
return;
#endif
#ifdef USE_CC3000
IOEvent event;
event.flags = channel;
if (!state && jshIsEventForPin(&event, WLAN_IRQ_PIN)) {
SpiIntGPIOHandler();
}
#endif
// Otherwise add this event
jshPushIOEvent(channel | (state?EV_EXTI_IS_HIGH:0), time);
}
void jshPushIOEvent(IOEventFlags channel, JsSysTime time) {
unsigned char nextHead = (unsigned char)((ioHead+1) & IOBUFFERMASK);
if (ioTail == nextHead) {
jshIOEventOverflowed();
return; // queue full - dump this event!
}
ioBuffer[ioHead].flags = channel;
ioBuffer[ioHead].data.time = time;
ioHead = nextHead;
}
// returns true on success
bool jshPopIOEvent(IOEvent *result) {
if (ioHead==ioTail) return false;
*result = ioBuffer[ioTail];
ioTail = (unsigned char)((ioTail+1) & IOBUFFERMASK);
return true;
}
bool jshHasEvents() {
return ioHead!=ioTail;
}
int jshGetEventsUsed() {
int spaceUsed = (ioHead >= ioTail) ? ((int)ioHead-(int)ioTail) : /*or rolled*/((int)ioHead+IOBUFFERMASK+1-(int)ioTail);
return spaceUsed;
}
bool jshHasEventSpaceForChars(int n) {
int spacesNeeded = 4 + (n/IOEVENT_MAXCHARS); // be sensible - leave a little spare
int spaceUsed = jshGetEventsUsed();
int spaceLeft = IOBUFFERMASK+1-spaceUsed;
return spaceLeft > spacesNeeded;
}
// ----------------------------------------------------------------------------
// DEVICES
const char *jshGetDeviceString(IOEventFlags device) {
switch (device) {
#ifdef USB
case EV_USBSERIAL: return "USB";
#endif
case EV_SERIAL1: return "Serial1";
case EV_SERIAL2: return "Serial2";
case EV_SERIAL3: return "Serial3";
#if USARTS>=4
case EV_SERIAL4: return "Serial4";
#endif
#if USARTS>=5
case EV_SERIAL5: return "Serial5";
#endif
#if USARTS>=6
case EV_SERIAL6: return "Serial6";
#endif
#if SPIS>=1
case EV_SPI1: return "SPI1";
#endif
#if SPIS>=2
case EV_SPI2: return "SPI2";
#endif
#if SPIS>=3
case EV_SPI3: return "SPI3";
#endif
#if I2CS>=1
case EV_I2C1: return "I2C1";
#endif
#if I2CS>=2
case EV_I2C2: return "I2C2";
#endif
#if I2CS>=3
case EV_I2C3: return "I2C3";
#endif
default: return "";
}
}
IOEventFlags jshFromDeviceString(const char *device) {
if (device[0]=='U') {
#ifdef USB
if (strcmp(device, "USB")==0) return EV_USBSERIAL;
#endif
}
else if (device[0]=='S') {
if (device[1]=='e' && device[2]=='r' && device[3]=='i' && device[4]=='a' && device[5]=='l') {
if (strcmp(device, "Serial1")==0) return EV_SERIAL1;
if (strcmp(device, "Serial2")==0) return EV_SERIAL2;
if (strcmp(device, "Serial3")==0) return EV_SERIAL3;
#if USARTS>=4
if (strcmp(device, "Serial4")==0) return EV_SERIAL4;
#endif
#if USARTS>=5
if (strcmp(device, "Serial5")==0) return EV_SERIAL5;
#endif
#if USARTS>=6
if (strcmp(device, "Serial6")==0) return EV_SERIAL6;
#endif
}
if (device[1]=='P' && device[2]=='I') {
#if SPIS>=1
if (strcmp(device, "SPI1")==0) return EV_SPI1;
#endif
#if SPIS>=2
if (strcmp(device, "SPI2")==0) return EV_SPI2;
#endif
#if SPIS>=3
if (strcmp(device, "SPI3")==0) return EV_SPI3;
#endif
}
}
else if (device[0]=='I' && device[1]=='2' && device[2]=='C') {
#if I2CS>=1
if (strcmp(device, "I2C1")==0) return EV_I2C1;
#endif
#if I2CS>=2
if (strcmp(device, "I2C2")==0) return EV_I2C2;
#endif
#if I2CS>=3
if (strcmp(device, "I2C3")==0) return EV_I2C3;
#endif
}
return EV_NONE;
}
/// Set whether the host should transmit or not
void jshSetFlowControlXON(IOEventFlags device, bool hostShouldTransmit) {
}