The sketch
The sketch listens to commands both on USB and on the HC-05, at 9600 baud. It works on Quasar Basic and on Arduino Uno.
/*
Quasar Console 2.0 - Starlyware
https://www.starlyware.com/products/quasar/console
Lets the Quasar web console control the board over USB or over a
Bluetooth HC-05 module. Works on Quasar Basic and on Arduino Uno
(any ATmega328P board at 16 MHz). In the Arduino IDE choose "Arduino Uno".
HC-05 wiring:
HC-05 VCC -> 5V
HC-05 GND -> GND
HC-05 TXD -> D2 (board RX for Bluetooth)
HC-05 RXD <- D3 through a voltage divider (1 kOhm from D3, 2 kOhm to GND):
the HC-05 RX pin works at 3.3 V.
Commands (one per line, upper or lower case), pins 4-13:
PING -> PONG QUASAR
HELP -> list of commands
ON 13 / OFF 13 -> digital output HIGH or LOW
TOGGLE 13 -> invert a digital output
PWM 9 128 -> PWM 0-255 on pins 5, 6, 9, 10, 11
MODE 7 IN -> pin mode: OUT, IN or PULLUP
SERVO 9 90 -> servo angle 0-180 (SERVO 9 OFF to release it)
READ A0 / READ D7 -> read an analog input (0-1023) or a digital pin
STREAM 50 A0 A1 -> send the analog inputs every 50 ms (20-5000)
STREAM OFF -> stop the stream
STATUS -> state of pins 4-13 and A0-A5
Answers use KEY=VALUE pairs: D = digital output, P = PWM, I = input,
U = input with pull-up, S = servo angle, A = analog input, T = time in ms.
Digital inputs report their changes automatically.
Note: while a servo is attached, the Servo library uses Timer1 and
PWM on pins 9 and 10 stops working.
Serial speed: 9600 baud on both USB and Bluetooth (HC-05 default).
If your HC-05 is configured at 38400, change BT_BAUD below.
*/
#include <SoftwareSerial.h>
#include <Servo.h>
const uint8_t BT_RX_PIN = 2; // connected to HC-05 TXD
const uint8_t BT_TX_PIN = 3; // connected to HC-05 RXD (through the divider)
const long USB_BAUD = 9600;
const long BT_BAUD = 9600;
const uint8_t FIRST_PIN = 4;
const uint8_t LAST_PIN = 13;
const uint8_t LINE_MAX = 40;
const uint8_t MAX_ARGS = 8;
enum QMode : uint8_t { M_OUT, M_PWM, M_IN, M_PULLUP, M_SERVO };
SoftwareSerial bt(BT_RX_PIN, BT_TX_PIN);
Servo servos[LAST_PIN - FIRST_PIN + 1]; // one per pin 4-13 (10 of the 12 available)
uint8_t pinModeOf[LAST_PIN + 1];
int pinValue[LAST_PIN + 1]; // output value, PWM value, input level or servo angle
Stream *lastOut = &Serial; // where spontaneous messages go
uint8_t streamMask = 0; // bit n = stream analog input An
unsigned long streamInterval = 0;
unsigned long lastStream = 0;
unsigned long lastInputPoll = 0;
char usbLine[LINE_MAX];
uint8_t usbLen = 0;
char btLine[LINE_MAX];
uint8_t btLen = 0;
bool isPwmPin(int p) {
return p == 5 || p == 6 || p == 9 || p == 10 || p == 11;
}
// Accepts "13" or "D13". Returns -1 if not a usable pin.
int parseDigital(const char *t) {
if (!t) return -1;
if (*t == 'D') t++;
if (*t < '0' || *t > '9') return -1;
int p = atoi(t);
return (p >= FIRST_PIN && p <= LAST_PIN) ? p : -1;
}
// Accepts "A0".."A5". Returns -1 if not valid.
int parseAnalog(const char *t) {
if (!t || t[0] != 'A' || t[1] < '0' || t[1] > '5' || t[2] != 0) return -1;
return t[1] - '0';
}
char modeLetter(uint8_t m) {
switch (m) {
case M_PWM: return 'P';
case M_IN: return 'I';
case M_PULLUP: return 'U';
case M_SERVO: return 'S';
default: return 'D';
}
}
void printPin(Stream &out, int p) {
out.print(modeLetter(pinModeOf[p]));
out.print(p);
out.print('=');
out.print(pinValue[p]);
}
void releaseServo(int p) {
if (pinModeOf[p] == M_SERVO) servos[p - FIRST_PIN].detach();
}
void makeOutput(int p) {
releaseServo(p);
if (pinModeOf[p] == M_IN || pinModeOf[p] == M_PULLUP || pinModeOf[p] == M_SERVO) {
pinMode(p, OUTPUT);
digitalWrite(p, LOW);
pinValue[p] = 0;
}
pinModeOf[p] = M_OUT;
}
void printStatus(Stream &out) {
for (int p = FIRST_PIN; p <= LAST_PIN; p++) {
printPin(out, p);
out.print(' ');
}
for (int a = 0; a <= 5; a++) {
out.print('A'); out.print(a); out.print('='); out.print(analogRead(A0 + a));
if (a < 5) out.print(' ');
}
out.println();
}
void handle(char *line, Stream &out) {
lastOut = &out;
for (char *c = line; *c; c++) *c = toupper(*c);
char *arg[MAX_ARGS];
uint8_t n = 0;
for (char *t = strtok(line, " "); t && n < MAX_ARGS; t = strtok(NULL, " ")) arg[n++] = t;
if (n == 0) return;
const char *cmd = arg[0];
if (!strcmp(cmd, "PING")) {
out.println(F("PONG QUASAR"));
} else if (!strcmp(cmd, "HELP")) {
out.println(F("OK PING HELP ON OFF TOGGLE PWM MODE SERVO READ STREAM STATUS"));
} else if (!strcmp(cmd, "STATUS")) {
out.print(F("STATUS "));
printStatus(out);
} else if (!strcmp(cmd, "ON") || !strcmp(cmd, "OFF") || !strcmp(cmd, "TOGGLE")) {
int p = parseDigital(n > 1 ? arg[1] : NULL);
if (p < 0) { out.println(F("ERR PIN 4-13")); return; }
bool wasOn = pinModeOf[p] == M_OUT || pinModeOf[p] == M_PWM ? pinValue[p] > 0 : false;
makeOutput(p);
int v = !strcmp(cmd, "ON") ? 1 : !strcmp(cmd, "OFF") ? 0 : (wasOn ? 0 : 1);
digitalWrite(p, v ? HIGH : LOW);
pinValue[p] = v;
out.print(F("OK ")); printPin(out, p); out.println();
} else if (!strcmp(cmd, "PWM")) {
int p = parseDigital(n > 1 ? arg[1] : NULL);
if (p < 0 || !isPwmPin(p)) { out.println(F("ERR PWM PIN 5 6 9 10 11")); return; }
if (n < 3) { out.println(F("ERR VALUE 0-255")); return; }
makeOutput(p);
int v = constrain(atoi(arg[2]), 0, 255);
analogWrite(p, v);
pinValue[p] = v;
pinModeOf[p] = M_PWM;
out.print(F("OK ")); printPin(out, p); out.println();
} else if (!strcmp(cmd, "MODE")) {
int p = parseDigital(n > 1 ? arg[1] : NULL);
if (p < 0 || n < 3) { out.println(F("ERR MODE PIN OUT|IN|PULLUP")); return; }
if (!strcmp(arg[2], "OUT")) {
makeOutput(p);
digitalWrite(p, LOW);
pinValue[p] = 0;
} else if (!strcmp(arg[2], "IN") || !strcmp(arg[2], "PULLUP")) {
releaseServo(p);
bool pullup = !strcmp(arg[2], "PULLUP");
pinMode(p, pullup ? INPUT_PULLUP : INPUT);
pinModeOf[p] = pullup ? M_PULLUP : M_IN;
pinValue[p] = digitalRead(p);
} else {
out.println(F("ERR MODE OUT|IN|PULLUP")); return;
}
out.print(F("OK ")); printPin(out, p); out.println();
} else if (!strcmp(cmd, "SERVO")) {
int p = parseDigital(n > 1 ? arg[1] : NULL);
if (p < 0 || n < 3) { out.println(F("ERR SERVO PIN 0-180|OFF")); return; }
if (!strcmp(arg[2], "OFF")) {
makeOutput(p);
out.print(F("OK ")); printPin(out, p); out.println();
return;
}
int angle = constrain(atoi(arg[2]), 0, 180);
if (pinModeOf[p] != M_SERVO) {
servos[p - FIRST_PIN].attach(p);
pinModeOf[p] = M_SERVO;
}
servos[p - FIRST_PIN].write(angle);
pinValue[p] = angle;
out.print(F("OK ")); printPin(out, p); out.println();
} else if (!strcmp(cmd, "READ")) {
int a = parseAnalog(n > 1 ? arg[1] : NULL);
if (a >= 0) {
out.print('A'); out.print(a); out.print('='); out.println(analogRead(A0 + a));
return;
}
int p = parseDigital(n > 1 ? arg[1] : NULL);
if (p < 0) { out.println(F("ERR READ A0-A5|D4-D13")); return; }
if (pinModeOf[p] == M_IN || pinModeOf[p] == M_PULLUP) pinValue[p] = digitalRead(p);
printPin(out, p); out.println();
} else if (!strcmp(cmd, "STREAM")) {
if (n > 1 && !strcmp(arg[1], "OFF")) {
streamMask = 0;
out.println(F("OK STREAM OFF"));
return;
}
long ms = n > 1 ? atol(arg[1]) : 0;
uint8_t mask = 0;
for (uint8_t i = 2; i < n; i++) {
int a = parseAnalog(arg[i]);
if (a >= 0) mask |= (1 << a);
}
if (ms < 20 || ms > 5000 || mask == 0) { out.println(F("ERR STREAM 20-5000 A0..A5")); return; }
streamInterval = ms;
streamMask = mask;
lastStream = 0;
out.println(F("OK STREAM ON"));
} else {
out.println(F("ERR UNKNOWN COMMAND"));
}
}
// Collects characters until a newline, then runs the command.
void readLine(Stream &in, char *buf, uint8_t &len) {
while (in.available()) {
char c = in.read();
if (c == '\r') continue;
if (c == '\n') {
buf[len] = 0;
if (len > 0) handle(buf, in);
len = 0;
} else if (len < LINE_MAX - 1) {
buf[len++] = c;
}
}
}
// Reports the inputs that changed since the last check.
void pollInputs() {
for (int p = FIRST_PIN; p <= LAST_PIN; p++) {
if (pinModeOf[p] != M_IN && pinModeOf[p] != M_PULLUP) continue;
int v = digitalRead(p);
if (v != pinValue[p]) {
pinValue[p] = v;
printPin(*lastOut, p);
lastOut->println();
}
}
}
void sendStream() {
lastOut->print(F("STREAM T="));
lastOut->print(millis());
for (uint8_t a = 0; a <= 5; a++) {
if (!(streamMask & (1 << a))) continue;
lastOut->print(F(" A")); lastOut->print(a); lastOut->print('=');
lastOut->print(analogRead(A0 + a));
}
lastOut->println();
}
void setup() {
for (int p = FIRST_PIN; p <= LAST_PIN; p++) {
pinMode(p, OUTPUT);
digitalWrite(p, LOW);
pinModeOf[p] = M_OUT;
pinValue[p] = 0;
}
Serial.begin(USB_BAUD);
bt.begin(BT_BAUD);
Serial.println(F("READY QUASAR CONSOLE 2"));
bt.println(F("READY QUASAR CONSOLE 2"));
}
void loop() {
readLine(Serial, usbLine, usbLen);
readLine(bt, btLine, btLen);
unsigned long now = millis();
if (now - lastInputPoll >= 20) {
lastInputPoll = now;
pollInputs();
}
if (streamMask && now - lastStream >= streamInterval) {
lastStream = now;
sendStream();
}
}