Tuesday, January 12, 2021

ESP32 (NodeMCU-32S) capture Analog input, display on SPI ILI9341 screen in waveform, base on Timer Interrupt.

The code run on NodeMCU-32S (ESP32 in Arduino framework) capture analog input in background base on timer interrupt, display on SPI ILI9341 screen graphically.


The timer interrupt is prepared in setup(), refer to Arduino IDE Examples > ESP32 > Timer > RepeatTimer.

bfr[][] is a 2x300 double buffer to store captured analog data. When one page (300 elements) is filled in interrupt service routine, it will switch to fill the another page and set bfr_filled flag to display the filled page in main loop(). Such that the main loop have to finish display within one page filled, 300 x sampling interval.

ESP32_AIN_TmrInt_20210112b.ino
#include "SPI.h"

#include "TFT_eSPI.h"
#define TFT_GREY 0x7BEF

TFT_eSPI myGLCD = TFT_eSPI();

#define FRAME_TOPX    10
#define FRAME_TOPY    5
#define FRAME_WIDTH   300
#define FRAME_HEIGHT  200
#define FRAME_BOTTOMY FRAME_TOPY + FRAME_HEIGHT

#define LAMP_PLOTTING_X FRAME_TOPX+290
#define LAMP_PLOTTING_Y FRAME_BOTTOMY+10
#define LAMP_PLOTTING_R 5

#define NO_OF_BFR_PAGE  2
#define NO_OF_BFR_ELE   300
unsigned int bfr[NO_OF_BFR_PAGE][NO_OF_BFR_ELE];
volatile int   bfr_page;
volatile int   bfr_idx;
volatile bool  bfr_filled;
volatile int   bfr_filled_page;
volatile bool  bfr_run = false;

volatile int   updateDot = -1;
volatile unsigned long prvSamplingTime;
#define PrgressDot_X  FRAME_TOPX
#define PrgressDot_Y  FRAME_BOTTOMY+4

#define PinAnalogIn 36

unsigned long previousTime = 0;

hw_timer_t * timer = NULL;
volatile SemaphoreHandle_t timerSemaphore;

void IRAM_ATTR onTimer(){
  toFillBfr();
  // Give a semaphore that we can check in the loop
  xSemaphoreGiveFromISR(timerSemaphore, NULL);
}

void setup() {
  
  Serial.begin(115200);
  myGLCD.init();
  myGLCD.setRotation(1);
  myGLCD.fillScreen(TFT_BLACK);
  delay(500);
  myGLCD.fillRect(FRAME_TOPX-1, FRAME_TOPY-1, 
                  FRAME_WIDTH+2, FRAME_HEIGHT+2, TFT_WHITE);
  myGLCD.fillRect(FRAME_TOPX, FRAME_TOPY, 
                  FRAME_WIDTH, FRAME_HEIGHT, TFT_GREY);

  myGLCD.setTextColor(TFT_WHITE,TFT_BLACK);
  myGLCD.drawString("Double Buffer", FRAME_TOPX, FRAME_BOTTOMY+20);

  // prepare bfr
  bfr_run = false;
  bfr_filled = false;
  bfr_page = 0;
  bfr_idx = 0;
  bfr_run = true;

  // --- Prepare Timer Intrrupt ---
  // Create semaphore to inform us when the timer has fired
  timerSemaphore = xSemaphoreCreateBinary();

  // Use 1st timer of 4 (counted from zero).
  // Set 80 divider for prescaler (see ESP32 Technical Reference Manual for more
  // info).
  timer = timerBegin(0, 80, true);

  // Attach onTimer function to our timer.
  timerAttachInterrupt(timer, &onTimer, true);

  // Set alarm to call onTimer function every second (value in microseconds).
  // Repeat the alarm (third parameter)
  timerAlarmWrite(timer, 10000, true);  //10ms
  //timerAlarmWrite(timer, 1000, true);   //1ms

  // Start an alarm
  timerAlarmEnable(timer);
  prvSamplingTime = micros();
  // --- End of Prepare Timer Intrrupt ---

  Serial.println("\n --- Start ---\n");
  Serial.println("CPU Frequency (Mhz): " + String(getCpuFrequencyMhz()));
}

void handle_screen(){
  if(bfr_filled){
    //unsigned long beforeScreen = micros();
    
    bfr_filled = false;
    myGLCD.fillCircle(LAMP_PLOTTING_X, LAMP_PLOTTING_Y, LAMP_PLOTTING_R, TFT_RED);
    
    myGLCD.fillRect(FRAME_TOPX, FRAME_TOPY, 
                  FRAME_WIDTH, FRAME_HEIGHT, TFT_GREY);
    int yPrv = bfr[bfr_filled_page][0];
    for(int i=1; i<NO_OF_BFR_ELE; i++){
      int yCur = bfr[bfr_filled_page][i];
      myGLCD.drawLine(FRAME_TOPX+i-1, FRAME_BOTTOMY-yPrv, 
                      FRAME_TOPX+i, FRAME_BOTTOMY-yCur, 
                      TFT_WHITE);
      yPrv = yCur;
        
    }

    myGLCD.fillCircle(LAMP_PLOTTING_X, LAMP_PLOTTING_Y, LAMP_PLOTTING_R, TFT_GREEN);
    myGLCD.drawLine(PrgressDot_X, PrgressDot_Y, PrgressDot_X+299, PrgressDot_Y, TFT_BLACK);

    //Serial.println("Fil duration: " + String(micros()-beforeScreen) + " (microsecond)");
  }

  //to draw alblue line under the graph for indicate the sample progress
  if(int x=updateDot){
    updateDot = -1;
    myGLCD.drawPixel(PrgressDot_X+x, PrgressDot_Y, TFT_BLUE);
  }
  
}

void loop() {

  if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE){
    handle_screen();
  }
}

void toFillBfr(){
  //unsigned long beforeFill = micros();

  unsigned long int curSamplingTime = micros();
  
  if(bfr_run){
    bfr[bfr_page][bfr_idx] = map(analogRead(PinAnalogIn),0, 4096, 0, 200);

    updateDot = bfr_idx;
    bfr_idx++;
    
    if(bfr_idx >= NO_OF_BFR_ELE){
      bfr_filled_page = bfr_page;
      bfr_idx=0;
      if(bfr_page==0){
        bfr_page = 1;
      }else{
        bfr_page = 0;
      }
      bfr_filled = true;
    }
  }

  //Send sampling interval to Serial Monitor for evaluation only,
  //remove it before finalize.
  Serial.println(curSamplingTime - prvSamplingTime);
  prvSamplingTime = curSamplingTime;

  //Serial.println("Fil duration: " + String(micros()-beforeFill) + " (microsecond)");
}

To setup TFT_eSPI using SPI ILI9342 for ESP32, read the post "ESP32 + 2.4" 320X240 Display (SPI ILI9341), using TFT_eSPI, prepare user setup file".


No comments:

Post a Comment