Maryam Shojaei
Published © GPL3+

Equisense

Horses can't speak. But they show everything. Equisense helps you see it.

IntermediateShowcase (no instructions)Over 4 days27
Equisense

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
×1
2.8 TFT Touch Shield V2.0
Seeed Studio 2.8 TFT Touch Shield V2.0
×1
Flash Memory Card, SD Card
Flash Memory Card, SD Card
×1
Tactile Switch, Top Actuated
Tactile Switch, Top Actuated
×6

Software apps and online services

Arduino IDE
Arduino IDE
Adobe illustrator

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Schematics

equisense_circuit_diagram.jpg

Code

Coding

Arduino
When a human senses the horse’s movements and scores them based on buttons and horse body language, then, according to the code and calculations, the Arduino displays one of the images as the result and actually tells us the horse’s mood on a daily basis.
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SdFat.h>
#include <Adafruit_ImageReader.h>

// ---- TFT Pins ----
#define TFT_CS   10
#define TFT_RST   4
#define TFT_DC    5
#define TFT_MOSI 11
#define TFT_SCK  12
#define TFT_MISO 13

// ---- SD Card Pins ----
#define SD_CS    15
#define SD_MOSI   1
#define SD_MISO   2
#define SD_SCK   18

// ---- Button Pins ----
#define B1      6
#define B3      7
#define B5      8
#define B7      9
#define B10    14  // Pin 14 (5th Button)
#define B_BACK 16

SPIClass tftSPI(FSPI);
SPIClass sdSPI(HSPI);

Adafruit_ILI9341 tft(&tftSPI, TFT_DC, TFT_CS, TFT_RST);
SdFat SD;
Adafruit_ImageReader reader(SD);

// ---- img00-img17 ----
const char* pageFiles[18] = {
  "/img00.bmp",
  "/img01.bmp", "/img02.bmp", "/img03.bmp", "/img04.bmp", "/img05.bmp",
  "/img06.bmp", "/img07.bmp", "/img08.bmp", "/img09.bmp", "/img10.bmp",
  "/img11.bmp",
  "/img12.bmp", "/img13.bmp", "/img14.bmp", "/img15.bmp", "/img16.bmp", "/img17.bmp"
};

// ---- 20 result images ----
const char* resultFiles[20] = {
  "/res01.bmp", "/res02.bmp", "/res03.bmp", "/res04.bmp", "/res05.bmp", // Row 0: Calm & Responsive
  "/res06.bmp", "/res07.bmp", "/res08.bmp", "/res09.bmp", "/res10.bmp", // Row 1: Subdued State
  "/res11.bmp", "/res12.bmp", "/res13.bmp", "/res14.bmp", "/res15.bmp", // Row 2: Distressed Signals
  "/res16.bmp", "/res17.bmp", "/res18.bmp", "/res19.bmp", "/res20.bmp"  // Row 3: Acute Critical Expression
};

// ---- State Parameters ----
int state  = 0;
int qIndex = 0;
int hIndex = 0;
int scores[10]; 
int hgs[6];     
int lastBtn = 0;

// ============================================================
//  CORE BITMAP RENDERER
// ============================================================
void drawBitmap(const char* filename) {
  ImageReturnCode stat = reader.drawBMP(filename, tft, 0, 0);
  if (stat != IMAGE_SUCCESS) {
    tft.fillScreen(0xF800);
    tft.setCursor(10, 80);
    tft.setTextColor(0xFFFF);
    tft.setTextSize(2);
    tft.print("Err:");
    tft.println((int)stat);
  }
}

void drawSplash()   { drawBitmap(pageFiles[0]); }
void drawQuestion() { drawBitmap(pageFiles[1 + qIndex]); } 
void drawHGSIntro() { drawBitmap(pageFiles[11]); }          
void drawHGS()      { drawBitmap(pageFiles[12 + hIndex]); } 

void drawResult() {
  int bodySum = 0;
  for (int i = 0; i < 10; i++) bodySum += scores[i];

  int hgsSum = 0;
  for (int i = 0; i < 6; i++) hgsSum += hgs[i];

  // ---- Adjusted Physical Level Assignment ----
  // Widened thresholds to account for button contact anomalies
  int physicalLevel;
  if      (bodySum <= 6)  physicalLevel = 0;  // Comfortable State
  else if (bodySum <= 14) physicalLevel = 1;  // Mild Discomfort
  else if (bodySum <= 22) physicalLevel = 2;  // Moderate Pain
  else if (bodySum <= 30) physicalLevel = 3;  // Severe Stress
  else                    physicalLevel = 4;  // Critical (Scores 31-40 trigger Critical)

  // ---- Adjusted Emotional Level Assignment ----
  int emotionalLevel;
  if      (hgsSum <= 4)  emotionalLevel = 0;  // Calm & Responsive
  else if (hgsSum <= 10) emotionalLevel = 1;  // Subdued State
  else if (hgsSum <= 16) emotionalLevel = 2;  // Distressed Signals
  else                   emotionalLevel = 3;  // Acute Critical Expression (Scores 17-24 trigger Acute)

  // Calculate final lookup array slot (0 to 19)
  int imageIndex = (emotionalLevel * 5) + physicalLevel;

  // Safety hardcap protection
  if (imageIndex > 19) imageIndex = 19;
  if (imageIndex < 0)  imageIndex = 0;

  Serial.print("Scores - Body: "); Serial.print(bodySum);
  Serial.print(" | HGS: "); Serial.println(hgsSum);
  Serial.print("Displaying Image Index Slot: "); Serial.println(imageIndex);

  drawBitmap(resultFiles[imageIndex]);
}

// ============================================================
//  BUTTON READER
// ============================================================
int getBtn() {
  if (digitalRead(B1)    == LOW) return 1;
  if (digitalRead(B3)    == LOW) return 2;
  if (digitalRead(B5)    == LOW) return 3;
  if (digitalRead(B7)    == LOW) return 4;
  if (digitalRead(B10)   == LOW) return 5; 
  if (digitalRead(B_BACK)== LOW) return -1;
  return 0;
}

// ============================================================
//  SETUP
// ============================================================
void setup() {
  Serial.begin(115200);

  pinMode(B1,     INPUT_PULLUP);
  pinMode(B3,     INPUT_PULLUP);
  pinMode(B5,     INPUT_PULLUP);
  pinMode(B7,     INPUT_PULLUP);
  pinMode(B10,    INPUT_PULLUP);
  pinMode(B_BACK, INPUT_PULLUP);

  tftSPI.begin(TFT_SCK, TFT_MISO, TFT_MOSI, TFT_CS);
  tft.begin();
  tft.setRotation(1);

  sdSPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);

  SdSpiConfig sdConfig(SD_CS, DEDICATED_SPI, SD_SCK_MHZ(25), &sdSPI);
  if (!SD.begin(sdConfig)) {
    tft.fillScreen(0xF800);
    tft.setCursor(10, 90);
    tft.setTextColor(0xFFFF);
    tft.setTextSize(2);
    tft.print("SD Failed!");
    while(1);
  }

  state = 0;
  drawSplash();
}

// ============================================================
//  MAIN CONTROL LOOP
// ============================================================
void loop() {
  int  btn   = getBtn();
  bool press = (btn != 0 && lastBtn == 0);
  lastBtn    = btn;
  if (!press) return;

  // Enforce a small mechanical debounce protection window
  delay(250); 

  if (btn == -1) {
    if (state == 1) {
      if (qIndex > 0) { qIndex--; drawQuestion(); }
      else            { state = 0; drawSplash(); }
    }
    else if (state == 2) { state = 1; qIndex = 9; drawQuestion(); }
    else if (state == 3) {
      if (hIndex > 0) { hIndex--; drawHGS(); }
      else            { state = 2; drawHGSIntro(); }
    }
    else if (state == 4) { state = 3; hIndex = 5; drawHGS(); }
    return;
  }

  if (state == 0) {
    state = 1; qIndex = 0; drawQuestion();
  }
  else if (state == 1) {
    scores[qIndex++] = btn - 1; 
    if (qIndex >= 10) { state = 2; drawHGSIntro(); }
    else              drawQuestion();
  }
  else if (state == 2) {
    state = 3; hIndex = 0; drawHGS();
  }
  else if (state == 3) {
    hgs[hIndex++] = btn - 1;
    if (hIndex >= 6) { state = 4; drawResult(); }
    else             drawHGS();
  }
  else if (state == 4) {
    state = 0; qIndex = 0; hIndex = 0; drawSplash();
  }
}

Credits

Maryam Shojaei
4 projects • 1 follower

Comments