mirror of
https://github.com/espruino/Espruino.git
synced 2025-12-08 19:06:15 +00:00
337 lines
10 KiB
C
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) {
|
|
}
|
|
|