Compare commits

..

15 Commits
host ... main

Author SHA1 Message Date
matthias@arch
3a29af1a3d updated readme 2023-08-30 17:55:32 +02:00
matthias@arch
88b66b228e fix link 2023-08-17 01:56:46 +02:00
Matthias@Dell
dc399fbc93 removed old code 2023-06-26 18:59:29 +02:00
Matthias@Dell
22f56baf2f fix link 2023-06-26 18:53:31 +02:00
Matthias@Dell
6a7f9ac74f fixed errors 2023-06-26 18:52:32 +02:00
Matthias@Dell
29d08921f1 added gatt service info 2023-06-26 18:50:49 +02:00
Matthias@Dell
4e295fc66a added ArduinoBLE 2023-06-26 18:50:25 +02:00
Matthias@Dell
0460b79db4 set read resolution to 12bit 2023-06-24 12:13:44 +02:00
matthias@arch
07eeac0c9f moved measure to separate file 2023-06-24 02:59:40 +02:00
matthias@arch
73ccb41dfb added count and interval settings 2023-06-24 02:59:22 +02:00
Matthias@Dell
d70797bd92 delete old comments 2023-06-19 15:55:21 +02:00
Matthias@Dell
a0538dc4c3 connect to arduino 2023-06-16 18:01:54 +02:00
Matthias@Dell
16a43a5b3d host script with bleak 2023-06-16 18:01:54 +02:00
matthias@arch
8484546773 add ble 2023-06-16 17:54:06 +02:00
matthias@arch
0b141290b4 BLE connection works 2023-06-14 19:22:11 +02:00
11 changed files with 254 additions and 394 deletions

View File

@ -1,3 +1,42 @@
# teng-arduino # arduino-blume
Measure teng output with an arduino **Me**asure analog signal using an Arduino and **Blu**etooth communication.
This project was written for my bachelor's thesis.
It mesaures voltage one one of arduino's analog pins and transmits it via Bluetooth LE. It was written for (and only tested with) the **Arduino RP2040 Connect**.
[m-teng](https://github.com/MatthiasQuintern/m-teng) has a backend that can connect to the Arduino to receive the data.
## Configuration
See `settings.hpp` for which pin needs to be connected.
The program uses 3 leds for displaying device status, however these are optional.
## Installation
```
git clone https://github.com/MatthiasQuintern/arduino-blume
arduino-cli compile --profile nanorp
arduino-cli upload --profile nanorp
```
## Communication via Bluetooth
The Arduino will advertise a Bluetooth Low Energy service which can be used to interact with the device.
Service UUID: `00010000-9a74-4b30-9361-4a16ec09930f`
0. status (`uint8_t`, `rn`): `00010001-9a74-4b30-9361-4a16ec09930f`
- `0`: **ERROR**: error occured: invalid command received, can not advertise Bluetooth service, etc.
- `1`: **BUSY**
- `2`: **WAIT_CONNECT**: waiting for Bluetooth connection
- `3`: **CONNECTED**: connected, waiting for command
- `4`: **MEASURING**: connected, measurement running
1. command (`uint8_t`, `rw`): `00010002-9a74-4b30-9361-4a16ec09930f`
send a command to the arduino:
- `0` - **STOP**: stop active measurements
- `1` - **MEASURE_COUNT**: measure *count* measurements with *interval* *see `count` and `interva` below*
- `2` - **MEASURE**: measure with *interval* until interrupted *see `count` below*
2. reading (`uint16_t` `rn`): `00010003-9a74-4b30-9361-4a16ec09930f`
the reading from the Arduinos analog pin
3. count: (`uint16_t`, `rw`): `00010004-9a74-4b30-9361-4a16ec09930f`
set the number of measurements to take with `MEASURE_COUNT`
4. interval: (`uint16_t`, `rw`): `00010005-9a74-4b30-9361-4a16ec09930f`
set the interval between measurements in ms

View File

@ -1,104 +0,0 @@
#pragma once
#include <Arduino.h>
class Counter {
public:
const unsigned MAX_NUMBER = 4096; // 12-bit
const int RES = 18;
const int CB = 19;
void reset() {
digitalWrite(CB, HIGH);
digitalWrite(RES, HIGH);
delay(1);
digitalWrite(RES, LOW);
currentValue = 0;
}
/**
* @brief Increment the counter value
* @returns true if overflown to zero
*/
bool increment() {
digitalWrite(CB, LOW);
delay(1);
digitalWrite(CB, HIGH);
currentValue++;
if (currentValue >= MAX_NUMBER) {
currentValue = 0;
return true;
}
return false;
}
unsigned get() { return currentValue; }
private:
unsigned currentValue = 0;
};
class AddressState {
public:
AddressState(Counter counter_, int addressPinCount_, const int* addressPins_)
: counter(counter_), addressPinCount(addressPinCount_), addressPins(addressPins_) {
counter.reset();
}
void set(unsigned targetAddress) {
unsigned targetCounterValue = targetAddress % counter.MAX_NUMBER;
if (counter.get() > targetCounterValue) {
counter.reset();
}
for (unsigned i = 0; i < (targetCounterValue - counter.get()); i++) {
counter.increment();
}
unsigned pinValues = targetAddress / counter.MAX_NUMBER;
addressPinStateMask = 0;
for (unsigned i = 0; i < addressPinCount; i++) {
unsigned bitSet = pinValues & (1 << i);
if (bitSet > 0) {
digitalWrite(addressPins[i], HIGH);
addressPinStateMask |= (1 << (addressPins[i] - 1));
}
else {
digitalWrite(addressPins[i], LOW);
}
}
address = targetAddress;
}
void increment() {
if (counter.increment()) { // if overflow
// add 1 to the other pins
for (unsigned i = 0; i < addressPinCount; i++) {
if ((addressPinStateMask & (addressPins[i] - 1)) == 0) {
addressPinStateMask |= (1 << (addressPins[i] - 1));
digitalWrite(addressPins[i], HIGH);
break;
}
else {
addressPinStateMask ^= (1 << (addressPins[i] - 1));
digitalWrite(addressPins[i], LOW);
}
}
}
address++;
}
unsigned getAddress() { return address; }
unsigned reset() {
counter.reset();
for (unsigned i = 0; i < addressPinCount; i++) {
digitalWrite(addressPins[i], LOW);
}
}
private:
// if counter is 12 bit and address is 16 bit, this is the value of the top 4 bits
// i-th bit represents i-th pin
unsigned addressPinStateMask = 0;
unsigned address = 0;
Counter counter;
const int* addressPins;
int addressPinCount;
};

View File

@ -1,7 +0,0 @@
#pragma once
const char CTRL_WRITE = 1;
const char CTRL_READ = 2;
const char CTRL_256KB = 11;
const char CTRL_2M = 14;

View File

@ -1,70 +0,0 @@
#pragma once
#include <Arduino.h>
#include "address.hpp"
class EEPROM {
public:
void setup() {
for (unsigned i = 0; i < addressPinCount; i++) {
pinMode(addressPins[i], OUTPUT);
}
for (unsigned i = 0; i < 8; i++) {
pinMode(dataPins[i], INPUT);
}
pinMode(OEb, OUTPUT);
pinMode(WEb, OUTPUT);
}
/**
* @brief Read the data pins.
*/
uint8_t readDataPins() {
uint8_t b = 0;
for (unsigned i = 0; i < 8; i++) {
b |= (digitalRead(dataPins[i]) << i);
}
return b;
}
// set start address in via AddressState
void read(uint8_t* data, unsigned dataSize, AddressState& address) {
unsigned i = 0;
for (unsigned i = 0; i < dataSize; i++) {
digitalWrite(OEb, LOW);
delay(t_OUTPUT_DELAY);
data[i] = readDataPins();
digitalWrite(OEb, HIGH);
delay(t_OUTPUT_FLOAT);
address.increment();
}
}
/* private: */
int OEb;
int WEb;
int addressPins[6];
unsigned addressPinCount;
int dataPins[8] = { 2, 3, 4, 5, 6, 7, 8, 9 };
// in ms
unsigned t_OUTPUT_DELAY = 1;
unsigned t_MIN_WRITE_PULSE_WIDTH = 1;
unsigned t_OUTPUT_FLOAT = 1;
unsigned t_DATA_HOLD_TIME = 0;
};
constexpr EEPROM AT28C256 {
.OEb = 17,
.WEb = 16,
.addressPins = {10, 15, 11},
.addressPinCount = 3
};
constexpr EEPROM SST39SF02A {
.OEb = 17,
.WEb = 16,
.addressPins = {10, 15, 16, 11, 12, 14},
.addressPinCount = 6
};

36
teng/measure.hpp Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#include <ArduinoBLE.h>
#include "services.hpp"
#include "status.hpp"
enum MeasurementTask { STOP_MEASURE, START_MEASURE, START_MEASURE_COUNT };
MeasurementTask measurementTask = STOP_MEASURE;
void measure(BLEDevice central, bool limitToCount=false) {
uint16_t interval = tengInterval.value();
uint16_t count = tengCount.value();
Serial.print("Starting measurement with interval=");
Serial.print(interval, DEC);
if (limitToCount) {
Serial.print(", count=");
Serial.print(count, DEC);
}
Serial.println("");
uint16_t i = 0;
setStatus(DeviceStatus::MEASURING);
while (central.connected() && (!limitToCount || i < count) && measurementTask != STOP_MEASURE) {
uint16_t val = static_cast<uint16_t>(analogRead(PIN_TENG));
Serial.println(val, DEC);
tengReading.writeValue(val);
/* Serial.println(val, DEC); */
delay(interval);
i++;
central.poll();
}
measurementTask = STOP_MEASURE;
setStatus(DeviceStatus::CONNECTED);
}

View File

@ -1,23 +0,0 @@
#pragma once
#include <vector>
#include <cstdint>
enum PacketType : uint8_t {
MESSAGE, STATUS, COMMAND
};
struct Packet {
public:
using size_type = uint16_t;
Packet(PacketType type, size_type size)
: type(type), size(size) {
payload = new uint8_t[size];
}
~Packet() {
delete[] payload;
}
uint8_t type;
size_type size;
uint8_t* payload;
};

32
teng/services.hpp Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#include <ArduinoBLE.h>
const char* BASE_UUID = "00000000-9a74-4b30-9361-4a16ec09930f";
// Service 1: TENG
const char* TENG_SUUID = "00010000-9a74-4b30-9361-4a16ec09930f"; // Service UUID
const char* TENG_STATUS_CUUID = "00010001-9a74-4b30-9361-4a16ec09930f"; // Characteristic UUID
const char* TENG_COMMAND_CUUID = "00010002-9a74-4b30-9361-4a16ec09930f";
const char* TENG_READING_CUUID = "00010003-9a74-4b30-9361-4a16ec09930f";
const char* TENG_COUNT_CUUID = "00010004-9a74-4b30-9361-4a16ec09930f";
const char* TENG_INTERVAL_CUUID = "00010005-9a74-4b30-9361-4a16ec09930f";
/* const char* TENG_BASELINE_CUUID = "00010004-9a74-4b30-9361-4a16ec09930f"; // Characteristic UUID */
BLEService tengService(TENG_SUUID);
BLEByteCharacteristic tengStatus(TENG_STATUS_CUUID, BLERead | BLENotify);
BLEByteCharacteristic tengCommand(TENG_COMMAND_CUUID, BLEWrite);
BLEUnsignedShortCharacteristic tengReading(TENG_READING_CUUID, BLERead | BLENotify);
BLEUnsignedShortCharacteristic tengInterval(TENG_INTERVAL_CUUID, BLERead | BLEWrite);
BLEUnsignedShortCharacteristic tengCount(TENG_COUNT_CUUID, BLERead | BLEWrite);
/* BLEUnsignedShortCharacteristic tengBaseline(TENG_BASELINE_CUUID, BLERead); */
enum Command : byte { STOP = 0, MEASURE_COUNT, MEASURE, };
void initServices() {
tengService.addCharacteristic(tengStatus);
tengService.addCharacteristic(tengCommand);
tengService.addCharacteristic(tengReading);
tengService.addCharacteristic(tengCount);
tengService.addCharacteristic(tengInterval);
/* tengService.addCharacteristic(tengBaseline); */
}

6
teng/settings.hpp Normal file
View File

@ -0,0 +1,6 @@
#pragma once
constexpr int LED_RED = 2;
constexpr int LED_YELLOW = 3;
constexpr int LED_GREEN = 4;
constexpr int PIN_TENG = 0;

View File

@ -4,3 +4,4 @@ profiles:
platforms: platforms:
- platform: arduino:mbed_nano (4.0.2) - platform: arduino:mbed_nano (4.0.2)
libraries: libraries:
- ArduinoBLE (1.3.4)

43
teng/status.hpp Normal file
View File

@ -0,0 +1,43 @@
#pragma once
#include <Arduino.h>
#include "settings.hpp"
#include "services.hpp"
// needs to be in separate file because sketch preprocessor reorders stuff
enum DeviceStatus : byte { ERROR = 0, BUSY, WAIT_CONNECT, CONNECTED, MEASURING };
void setStatusLED(DeviceStatus s, bool value) {
switch (s) {
case ERROR:
digitalWrite(LED_RED, value);
break;
case BUSY:
digitalWrite(LED_YELLOW, value);
digitalWrite(LED_RED, value);
break;
case WAIT_CONNECT:
digitalWrite(LED_YELLOW, value);
break;
case CONNECTED:
digitalWrite(LED_GREEN, value);
break;
/* case MEASURING_BASELINE: */
/* digitalWrite(LED_RED, value); */
/* digitalWrite(LED_GREEN, value); */
/* break; */
case MEASURING:
digitalWrite(LED_YELLOW, value);
digitalWrite(LED_GREEN, value);
break;
}
}
DeviceStatus deviceStatus = WAIT_CONNECT;
void setStatus(DeviceStatus s) {
setStatusLED(deviceStatus, LOW);
setStatusLED(s, HIGH);
deviceStatus = s;
tengStatus.writeValue(deviceStatus);
}

View File

@ -1,31 +1,21 @@
/* /*
* EEPROM PROGRAMMER
* 15-18 Address Pins
* 8 Data Pins
* 2 Control Pins (OEb, WEb)
* *
* CEb must tied to ground
*/ */
#include <Arduino.h> #include <Arduino.h>
#include <ArduinoBLE.h> #include <ArduinoBLE.h>
/* #include "eeprom.hpp" */
/* #include "address.hpp" */
/* #include "control_bytes.hpp" */ #include "settings.hpp"
#include "status.hpp"
#include "services.hpp"
#include "measure.hpp"
// TODO
/* std::array<uint16_t, 10000> valueBuffer; // 20kB buffer for readings */
/* auto valueBufferWriteIter = valueBuffer.begin(); // points past the element that was last written */
constexpr int LED_RED = 2;
constexpr int LED_YELLOW = 3;
constexpr int LED_GREEN = 4;
constexpr int PIN_TENG = 0;
/* void maskedWrite(uint32_t mask, uint32_t bits) { */
/* for (uint32_t i = 0; i < 32; i++) { */
/* if (mask & (1 << i)) { */
/* digitalWrite(i, bits & (1 << i)); */
/* } */
/* } */
/* } */
void blinkLED(unsigned n=5, unsigned delay_=200) { void blinkLED(unsigned n=5, unsigned delay_=200) {
for (unsigned i = 0; i < n; i++) { for (unsigned i = 0; i < n; i++) {
digitalWrite(LED_BUILTIN, HIGH); digitalWrite(LED_BUILTIN, HIGH);
@ -36,196 +26,113 @@ void blinkLED(unsigned n=5, unsigned delay_=200) {
} }
unsigned MAX_DEVIATION = 0;
unsigned BASELINE = 0;
void measureBaseline(unsigned nMeas, unsigned interval=50) {
uint64_t value = 0;
unsigned minVal = 1023;
unsigned maxVal = 0;
for (unsigned i = 0; i < nMeas; i++) {
unsigned reading = analogRead(PIN_TENG);
value += reading;
delay(interval);
if (reading > maxVal) { maxVal = reading; }
if (reading < minVal) { minVal = reading; }
}
BASELINE = value / nMeas;
if (BASELINE - minVal > maxVal - BASELINE) {
MAX_DEVIATION = BASELINE - minVal;
}
else {
MAX_DEVIATION = maxVal - BASELINE;
}
}
void setup() { void setup() {
/* pinMode(LED_BUILTIN, OUTPUT); */ setStatus(DeviceStatus::BUSY);
/* digitalWrite(LED_BUILTIN, HIGH); */
/* delay(200); */
Serial.begin(9600); Serial.begin(9600);
/* // wait until available */ /* // wait until available */
while (!Serial); /* while (!Serial); */
digitalWrite(LED_BUILTIN, LOW);
delay(200); // empty buffer
while (Serial.read() != -1);
/* digitalWrite(LED_BUILTIN, HIGH); */ blinkLED(2);
/* delay(5000); */
/* // reset counter */ // TODO: test if works
/* blinkLED(); */ analogReadResolution(12); // set analog input to 12 bit resolution (0-4095)
/* uint8_t payload[] = { 1, 2, 3, 4, 5, 69 }; */
/* uint8_t type = '#'; */
/* static_assert(sizeof(payload) == 6); */
/* uint16_t size = sizeof(payload); */
/* Serial.write(type); */
/* Serial.write(lowByte(size)); */
/* Serial.write(highByte(size));u */
/* Serial.write(payload, size); */
/* delay(5000); */
blinkLED(4);
if (!BLE.begin()) { if (!BLE.begin()) {
Serial.println("starting Bluetooth® Low Energy module failed!"); Serial.println("starting Bluetooth® Low Energy module failed!");
setStatus(DeviceStatus::ERROR);
while(true);
} }
BLE.setDeviceName("ArduinoTeng"); /* BLE.setDeviceName("ArduinoTENG"); */
BLE.setLocalName("ArduinoTENG");
byte data[10] = "\xFF\xFFMQU@TUM"; // 0xFFFF manufacturer id for testing
BLE.setManufacturerData(data, 10);
initServices();
BLE.addService(tengService);
BLE.setAdvertisedService(tengService);
// defaults
tengCount.writeValue(100);
tengInterval.writeValue(100);
BLE.setConnectable(true);
blinkLED(3);
BLE.advertise(); BLE.advertise();
measureBaseline(100); setStatus(DeviceStatus::WAIT_CONNECT);
digitalWrite(LED_RED, HIGH);
digitalWrite(LED_YELLOW, HIGH);
digitalWrite(LED_GREEN, HIGH);
delay(500);
digitalWrite(LED_RED, LOW);
digitalWrite(LED_YELLOW, LOW);
digitalWrite(LED_GREEN, LOW);
} }
/* constexpr int max_val = 1023 / 5 * 3.5; // 1023 max val for 5V */ /* constexpr int max_val = 1023 / 5 * 3.5; // 1023 max val for 5V */
// it seems this function must return immediately, otherwise the connection will time out
void commandWrittenHandler(BLEDevice central, BLECharacteristic characteristic) {
switch (tengCommand.value()) {
case Command::STOP:
measurementTask = STOP_MEASURE;
break;
case Command::MEASURE:
if (measurementTask == STOP_MEASURE) {
measurementTask = START_MEASURE;
}
else {
Serial.println("ERROR: Command 'MEASURE' received while measurement is active");
}
break;
case Command::MEASURE_COUNT:
if (measurementTask == STOP_MEASURE) {
measurementTask = START_MEASURE_COUNT;
}
else {
Serial.println("ERROR: Command 'MEASURE_COUNT' received while measurement is active");
}
break;
/* case Command::MEASURE_BASELINE: */
/* setStatus(DeviceStatus::MEASURING_BASELINE); */
/* measureBaseline(100); */
/* break; */
default:
setStatus(DeviceStatus::ERROR);
Serial.print("ERROR: Unkown command: ");
Serial.println(tengCommand.value(), HEX);
delay(1000);
break;
}
}
// the loop function runs over and over again forever // the loop function runs over and over again forever
void loop() { void loop() {
int led_red = LOW; blinkLED(3, 300);
int led_yellow = LOW; // listen for Bluetooth® Low Energy peripherals to connect:
int led_green = LOW; BLEDevice central = BLE.central();
int val = analogRead(PIN_TENG); // if a central is connected to peripheral:
Serial.print(val, DEC); if (central) {
val -= BASELINE; setStatus(DeviceStatus::CONNECTED);
if (val < 0) { val = -val; } tengCommand.setEventHandler(BLEWritten, commandWrittenHandler);
Serial.print("/");
Serial.println(val, DEC);
if (val >= MAX_DEVIATION * 1) { Serial.print("Connected to central: ");
led_green = HIGH; Serial.println(central.address());
while (central.connected()) {
blinkLED(1, 200);
if (measurementTask == START_MEASURE) {
measure(central, false);
} }
if (val >= MAX_DEVIATION * 2) { else if (measurementTask == START_MEASURE_COUNT) {
led_yellow = HIGH; measure(central, true);
} }
if (val >= MAX_DEVIATION * 3) {
led_red = HIGH;
} }
digitalWrite(LED_RED, led_red);
digitalWrite(LED_YELLOW, led_yellow);
digitalWrite(LED_GREEN, led_green);
delay(100); setStatus(DeviceStatus::WAIT_CONNECT);
// when the central disconnects, notify the user
Serial.print("Disconnected from central MAC: ");
Serial.println(central.address());
/* // init address */ }
/* const int* addressPins = nullptr; */ else {
/* unsigned addressPinCount = 0; */ BLE.advertise();
/* // TODO pass eeprom via serial */ }
/* EEPROM eeprom = AT28C256; */
/* if (mem_size = CTRL_2M) { */
/* eeprom = SST39SF02A; */
/* } */
/* AddressState address(Counter(), eeprom.addressPinCount, eeprom.addressPins); */
/* const int dataSize = 0x8000; */
/* uint8_t data[dataSize]; */
/* while (true) { */
/* } */
/* if (!Serial.available()) { continue; } */
/* // read packet */
/* uint8_t type = Serial.read(); */
/* uint16_t size = Serial.read(); // low byte */
/* size |= (Serial.read() << 8); // high byte */
/* Packet packet(type, size); */
/* Serial.readBytes(packet.payload, size); */
/* switch(type) { */
/* case PacketType::WRITE: { */
/* break; */
/* } */
/* case PacketType::READ: { */
/* break; */
/* } */
/* case PacketType::SET_ADDRESS { */
/* } */
/* default: { */
/* break; */
/* } */
/* } */
/* byte mem_size = 0; */
/* byte action = 0; */
/* byte x = 0; */
/* while (action == 0 || mem_size == 0) { */
/* digitalWrite(LED_BUILTIN, HIGH); */
/* if (Serial.available() <= 0) { */
/* blinkLED(3); */
/* } */
/* else { */
/* /1* while (Serial.available() > 0) { *1/ */
/* /1* Serial.write(Serial.read()); *1/ */
/* /1* } *1/ */
/* /1* Serial.write('\n'); *1/ */
/* Serial.write(static_cast<uint8_t>(Serial.available())); */
/* int cmd = Serial.read(); */
/* switch (cmd) { */
/* case CTRL_WRITE: */
/* case CTRL_READ: */
/* action = cmd; */
/* Serial.write('\0'); */
/* Serial.write('\0'); */
/* break; */
/* default: */
/* mem_size = cmd; */
/* Serial.write('\1'); */
/* Serial.write('\1'); */
/* } */
/* Serial.write(static_cast<uint8_t>(cmd)); */
/* Serial.write('\2'); */
/* Serial.write('\2'); */
/* } */
/* Serial.write(x); */
/* Serial.print('\n'); */
/* x++; */
/* delay(1000); */
/* digitalWrite(LED_BUILTIN, LOW); */
/* delay(1000); */
/* } */
/* if (action == CTRL_WRITE) { */
/* Serial.println("Write"); */
/* } */
/* else if (action == CTRL_READ) { */
/* Serial.println("Read"); */
/* eeprom.read(data, dataSize, address); */
/* Serial.write(data, dataSize); */
/* blinkLED(10); */
/* } */
} }