Teensy 4.0 - Audio Library screen oscilloscope

fmenes

Member
Hello,

I am working on a FM synthesizer that uses the Audio library on a Teensy 4.0 and the audio shield rev D. I am currently working on a basic algorithm with two mixed waveforms that are being modulated by two other basic waveforms.
I would like to show the different waves that create the complex sound on a SSD13151 OLED screen that has a resolution of 128*128 pixels. The idea is to get something similar to the Korg Minilogue's oscilloscope. I would like to represent separated waves next to one another in real time. I have had a look at different oscilloscopes projects with Teensys but they are all working with external signals. I would like to avoid resampling the audio signal output if possible, as this would be using more resources, more memory and only output the end signal.
Would there be a way for me to output all the waveforms separately to an array of buffers and then draw it on the screen ? I am not really familiar with the screen library or buffers yet and don't really know what signal sampling rate to use for this application. Also I don't know how much memory this would take. The goal for now is to get this working for one voice (2 carriers + 2 operators) but I'm aiming at having multiple voices at once and have a visualizer that displays the end output of every voice. If anyone has examples I could take a look at to figure the process for one wave that would be amazing.

Thank you guys and thanks to everyone involved in this forum and Teensy, I'm having a lot of fun with amazing tool.
 
I have the Adafruit SSD1306 display which is 128x64. I've made a working sketch which is a mashup of the example code for that + the demo code for the Teensy Audio Shield with 3x encoders and 3x buttons. I doubt the SSD1306 library will work out of the box with your display but the part numbers seem similar enough that it might be close.

This is not quite an oscilloscope display but displaying an FFT analysis of the entire audio output afaik.

By the way the Minilogue is definitely something I also wanted to emulate so I'm interested in how this goes.

I think overlaying both waves might be as simple as calling something like display.drawlines (possibly in a loop) for each waveform, and only clearing the display after both lines are drawn.

Code:
// Advanced Microcontroller-based Audio Workshop
//
// http://www.pjrc.com/store/audio_tutorial_kit.html
// https://hackaday.io/project/8292-microcontroller-audio-workshop-had-supercon-2015
// 
// Part 2-8: Oscillators

#include <Bounce.h>

Bounce button0 = Bounce(0, 15);
Bounce button1 = Bounce(1, 15);  // 15 = 15 ms debounce time
Bounce button2 = Bounce(2, 15);


#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
//#include <SD.h>
#include <SerialFlash.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 5 
Adafruit_SSD1306 display(OLED_RESET);

#define LOGO16_GLCD_HEIGHT 16 
#define LOGO16_GLCD_WIDTH  16 
static const unsigned char PROGMEM logo16_glcd_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };

// GUItool: begin automatically generated code
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
//#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioSynthWaveform       waveform1;      //xy=740,490
AudioSynthWaveformSine   sine1;          //xy=761,618
AudioSynthNoisePink      pink1;          //xy=764,692
AudioSynthWaveformSineModulated sine_fm1;       //xy=805,562
AudioMixer4              mixer1;         //xy=974,580
AudioEffectEnvelope      envelope1;      //xy=1003.7500190734863,731.7500114440918
AudioMixer4              mixer2;         //xy=1151.500015258789,653.2500076293945
AudioAnalyzeFFT1024      LeftFFT;      //xy=1355.0000190734863,682.5000114440918
AudioAnalyzeFFT1024      RightFFT;      //xy=1358,744
AudioOutputI2S           i2s1;           //xy=1393,540
AudioConnection          patchCord1(waveform1, 0, mixer1, 0);
AudioConnection          patchCord2(waveform1, sine_fm1);
AudioConnection          patchCord3(sine1, 0, mixer1, 2);
AudioConnection          patchCord4(pink1, 0, mixer1, 3);
AudioConnection          patchCord5(sine_fm1, 0, mixer1, 1);
AudioConnection          patchCord6(mixer1, 0, mixer2, 0);
AudioConnection          patchCord7(mixer1, envelope1);
AudioConnection          patchCord8(envelope1, 0, mixer2, 1);
AudioConnection          patchCord9(mixer2, 0, i2s1, 0);
AudioConnection          patchCord10(mixer2, 0, i2s1, 1);
AudioConnection          patchCord11(mixer2, LeftFFT);
AudioConnection          patchCord12(mixer2, RightFFT);
AudioControlSGTL5000     sgtl5000_1;     //xy=953,944
// GUItool: end automatically generated code



void setup() {
  Serial.begin(9600);
  AudioMemory(20);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.32);

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  display.display();
  delay(2000);

  display.clearDisplay();

  display.setCursor(0, 0);
  
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  mixer1.gain(0, 0.75);
  mixer1.gain(1, 0.0);
  mixer1.gain(2, 0.0);
  mixer1.gain(3, 0.0);
  mixer2.gain(0, 0.15);
  mixer2.gain(1, 0.0);
  mixer2.gain(2, 0.0);
  mixer2.gain(3, 0.0);
  waveform1.begin(WAVEFORM_SAWTOOTH);
  waveform1.amplitude(0.75);
  waveform1.frequency(50);
  waveform1.pulseWidth(0.15);
  sine_fm1.frequency(440);
  sine_fm1.amplitude(0.75);
  sine1.frequency(200);
  sine1.amplitude(0.75);
  pink1.amplitude(0.75);
  envelope1.attack(10);
  envelope1.hold(10);
  envelope1.decay(25);
  envelope1.sustain(0.4);
  envelope1.release(70);
}

int waveform_type = WAVEFORM_SAWTOOTH;
int mixer1_setting = 0;
int mixer2_setting = 0;
elapsedMillis timeout = 0;
bool mixer2_envelope = false;

unsigned long last_time = millis();
uint8_t overlayCounter = 0;
float lastLoopTime = 0;
uint16_t lastCPU = 0;
uint16_t lastMem = 0;

float leftBands[40] = {
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

float RightBands[40] = {
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};


void loop() {
  button0.update();
  button1.update();
  button2.update();

  display.setCursor(0, 0);

  // Left changes the type of control waveform
  if (button0.fallingEdge()) {
    Serial.print("Control waveform: ");
    if (waveform_type == WAVEFORM_SAWTOOTH) {
      waveform_type = WAVEFORM_SINE;
      Serial.println("Sine");
    } else if (waveform_type == WAVEFORM_SINE) {
      waveform_type = WAVEFORM_SQUARE;
      Serial.println("Square");
    } else if (waveform_type == WAVEFORM_SQUARE) {
      waveform_type = WAVEFORM_TRIANGLE;
      Serial.println("Triangle");
    } else if (waveform_type == WAVEFORM_TRIANGLE) {
      waveform_type = WAVEFORM_PULSE;
      Serial.println("Pulse");
    } else if (waveform_type == WAVEFORM_PULSE) {
      waveform_type = WAVEFORM_SAWTOOTH;
      Serial.println("Sawtooth");
    }
    waveform1.begin(waveform_type);
  }

  // middle button switch which source we hear from mixer1
  if (button1.fallingEdge()) {
    if (mixer1_setting == 0) {
      mixer1.gain(0, 0.75);
      mixer1.gain(1, 0.0);
      mixer1.gain(2, 0.0);
      mixer1.gain(3, 0.0);
      Serial.println("Mixer1: Control oscillator");
      mixer1_setting = 1;
    } else if (mixer1_setting == 1) {
      mixer1.gain(0, 0.0);
      mixer1.gain(1, 0.75);
      mixer1.gain(2, 0.0);
      mixer1.gain(3, 0.0);
      Serial.println("Mixer1: Frequency Modulated Oscillator");
      mixer1_setting = 2;
    } else if (mixer1_setting == 2) {
      mixer1.gain(0, 0.0);
      mixer1.gain(1, 0.0);
      mixer1.gain(2, 0.75);
      mixer1.gain(3, 0.0);
      Serial.println("Mixer1: Regular Sine Wave Oscillator");
      mixer1_setting = 3;
    } else if (mixer1_setting == 3) {
      mixer1.gain(0, 0.0);
      mixer1.gain(1, 0.0);
      mixer1.gain(2, 0.0);
      mixer1.gain(3, 0.75);
      Serial.println("Mixer1: Pink Noise");
      mixer1_setting = 0;
    }
  }

  // Right button activates the envelope
  if (button2.fallingEdge()) {
    mixer2.gain(0, 0.0);
    mixer2.gain(1, 1.0);
    mixer2_envelope = true;
    timeout = 0;
    envelope1.noteOn();
  }
  if (button2.risingEdge()) {
    envelope1.noteOff();
    timeout = 0;
  }

  // after 4 seconds of inactivity, go back to
  // steady listening intead of the envelope
  if (mixer2_envelope == true && timeout > 4000) {
    mixer2.gain(0, 0.15);
    mixer2.gain(1, 0.0);
    mixer2_envelope = false;
  }

  // use the knobs to adjust parameters
  float knob1 = (float)analogRead(A1) / 1023.0;
  float knob2 = (float)analogRead(A2) / 1023.0;
  float knob3 = (float)analogRead(A3) / 1023.0;
  waveform1.frequency(360 * knob2 + 0.25);
  sine_fm1.frequency(knob3 * 1500 + 50);
  sine1.frequency(knob3 * 1500 + 50);
//  Serial.println("knob1 = " + knob1);
  sgtl5000_1.volume(knob1); 


  float loopTime;
  int i;

  //calc loopTime
  unsigned long this_time = millis();
  if (this_time > last_time)
  {
    loopTime = (this_time - last_time);
  }
  last_time = this_time;

  //Update data every 20 frames for readability
  overlayCounter++;
  if (overlayCounter > 20)
  {
    lastLoopTime = loopTime;
    lastCPU = AudioProcessorUsageMax();
    AudioProcessorUsageMaxReset();
    lastMem = AudioMemoryUsageMax();
    AudioMemoryUsageMaxReset();

    overlayCounter = 0;
  }

  //Draw a frame
  display.clearDisplay();

  //Draw left bands
  for (i = 0; i < 40; i++)
  {
    if (leftBands[i] > 0.5) leftBands[i] = 0.25;
    display.drawLine(62 - i, 31, 62 - i, 31 - (int) (leftBands[i] * 1023), WHITE);
  }

  //Draw Right bands
  for (i = 0; i < 40; i++)
  {
    if (RightBands[i] > 0.5) RightBands[i] = 0.25;
    display.drawLine(65 + i, 31, 65 + i, 31 - (int) (RightBands[i] * 1023), WHITE);
  }

    // text display tests
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
//  display.println("Hello, world!");

  //Overlay info
  //  loop time
  display.setCursor(0, 0);
  display.print("Loop=");
  display.print((uint8_t)lastLoopTime);
  display.print("ms");
  //  Teensy Audio info
  display.setCursor(83, 0);
  display.print("cpu=");
  display.print(lastCPU);
  display.setCursor(91, 8);
  display.print("mem=");
  display.print(lastMem);
  //  L/R letters
  display.setCursor(15, 24);
  display.print("L");
  display.setCursor(108, 24);
  display.print("R");

  if (LeftFFT.available()) {
    // each time new FFT data is available
    for (i = 0; i < 40; i++) {
      leftBands[i] = LeftFFT.read(i);
    }
  }
  if (RightFFT.available()) {
    // each time new FFT data is available
    for (i = 0; i < 40; i++) {
      RightBands[i] = RightFFT.read(i);
    }
  }

  display.display();

  
}
 
Hi Rozling,

Thanks for your input ! I will dive a bit more into that. These screens are definitely similar and use GFX so porting the code should be easy.
I'm looking at the possibility of writing a class for an Oscilloscope object with four inputs. I'm still learning about libraries at the moment so I will see how far I manage to push this with these informationss. From what I understand, it's just about finding the right way to map the AudioStream or a array of parallel audio buffers to a cursor on the screen and shift the columns of pixels at a certain rate. I have only tested the screen with the basic test code on the teensy 4.0 but the refresh rate was great and I believe should be comfortable enough for a small oscilloscope.
I will post any of the advancements and if I eventually manage to write a small library I think you'll be able to do it easily with the SSD1306. Cheers !
 
Hey everyone,

I have had good progress on the rest of my work so I could start working on this yesterday. This is part of a bigger synthesizer project on which I want to work on for a while but I think I'll share more about this on another post if anyone is interested.
I'm glad to share that I have managed to write a small class that does the trick for me right now with the SSD1351. It is very one of my first "libraries" so I would really appreciate if I could get any advice on how to optimize this. Some aspects are still not perfect but I have some optimisations in mind I would like to share and would like to know if these would be efficient.

If haven't succeeded in using both the Audio Shield and the display on the same SPI ports. When using the test code from the Adafruit SSD1351 with the screen directly plugged on the audio board it runs perfectly. With the Audio Library on it didn't work I get because it's conflicting with some of the signals of the shield. I also tried with KurtE's adaptation of the alternative SSD1351 library for teensy 4.0 but couldn't manage to get it working with my setup.
I finally got the display working smoothly using SPI1 (if anyone is looking for that I used the RST pin on 3V3, DC on 24, CS on 25, MOSI on 26 and SCK on 27).

The class I wrote uses a pointer to an object of the Adafruit_SSD1351 but I think it could be easily adapted for any type of screen that uses the GFX library. This is still an early code but I think it could be useful for a lot of people.
Right now it displays an audio block on the screen every time a new block arrives and I think that is a bit too much for the screen. I am not using fillscreen otherwise I can't use the rest of my interface because of the disable IRQ but keeping the lit pixels in memory to turn them off at the next block. I can edit sound live and see how it comes through on the oscilloscope. Right now the shapes aren't perfect but I do love their graphical aspect ! I'm going to try adding a drawline for a certain threshold distance between two pixels in the y axis so the lines are perfectly continuous. Also to reduce the refresh rate I think I will align blocks on different zones of the screen eg : new block arrives on the right half of the screen, when new block right half copied to left and new block is written on the right side,etc.

Here's a preview of the oscilloscope. Sorry for the sound from phone and not so melodic but I'm still setting up the interface. Everything is one voice made out of 2 waveforms modulated by one operator each and filtered. Showing the true power of the teensy 4.0 ! Thanks again everyone involved in making Teensy and helping around it. I never thought I would be able to achieve something even close from this.

[video]https://drive.google.com/file/d/1t-R4PeVz0ZBsiagizBsYZme8Re_QYNij/view?usp=sharing[/video]

Here's the library, I'll be posting more when this gets updated:

Code:
#ifndef oscilloscope_h_
#define oscilloscope_h_

#include "Arduino.h"
#include "AudioStream.h"
#include "Adafruit_SSD1351.h"

#define CHANNELS 5
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 128
#define SCREEN_MIDDLE 63
#define SCREEN_WHITE 65535
#define SCREEN_GREEN 5553
#define SCREEN_BLACK 0

class Oscilloscope : public AudioStream
{
public:
  Oscilloscope(void) : AudioStream(5, inputQueueArray){
  }
  virtual void update(void);
  void ScreenSetup(Adafruit_SSD1351*);
  void AudioToPixel(int16_t*);
  void SetWaveColor(int16_t);
  int16_t color;
  int16_t backgroundcolor;


private:
  audio_block_t *inputQueueArray[5];;
  volatile bool new_output;
  Adafruit_SSD1351 *display;
  int16_t old_val[AUDIO_BLOCK_SAMPLES];
};
#endif


#include <Arduino.h>
#include "Oscilloscope.h"


void Oscilloscope::ScreenSetup(Adafruit_SSD1351 *screen){
  __disable_irq();
display = screen;
display->fillScreen(0);
__enable_irq();
return;
}

void Oscilloscope::SetWaveColor(int16_t colour){
color = colour;
}


void Oscilloscope::AudioToPixel(int16_t *audio){

const int16_t *begin = audio;
const int16_t *end = audio + AUDIO_BLOCK_SAMPLES;
__disable_irq();
do {
  int16_t wave_data = *audio;
  int16_t pixel_y = map(wave_data, -32768, 32767, -63, 63) +64;
  int16_t pixel_x = audio - begin;
  display->drawPixel(pixel_x, old_val[pixel_x], backgroundcolor);
  display->drawPixel(pixel_x, pixel_y, color);
  old_val[pixel_x] = {pixel_y};
  audio++;
} while (audio < end);
__enable_irq();
return;
}

void Oscilloscope::update(void)
{
if (!display){
  return;
}
unsigned int channel;
audio_block_t *block=NULL;

for (channel = 0; channel < 5; channel++){
  if (!block){
      block = receiveReadOnly(channel);
	     if (block){
          AudioToPixel(block->data);
        }
  }
}
  if (block) {
    release(block);
  }
}
 
Last edited:
Hi Rolf,

Thanks for your input, the oscilloscope looks great. Will have a better look at the code in the coming days. Cheers !
 
Hi friend

Yes do that. I still have a problem with the sample rate and trigger function.

The sample rate is too high and low frequencies are not displayed completely.

The trigger only works once when the audiostream is written to the buffer.

Greetings Rolf
 
Last edited:
Hey there,

Just had a quick look, I think the best way to palliate the sample rate and display the lowest frequencies would be to downsample the x axis, to eventually scale the signal to the screen. Would need to see how it looks. I think a ratio of 3/4 of the sampling rate would be better already so it would be interesting to see how to adapt the refresh process of the screen to that.

For the trigger problem, I doubt it would be fixable as the buffers are the main data used and processed by the audio library, so the closest thing to the sound wave so to speak. It can only display what it is happening and not what will happen, otherwise you would need to generate the oscilloscope wave with code rather than displaying the actual sound which is far more work.
I do think some optimisation of the oscilloscope refresh process could reduce lags between the trigger and the display. We'll see what can help. I will start working on this project soon again and will keep you posted.

Have a nice evening
 
Hallo..

I change the trigger function in the Oscilloscope. Now I test 8 samples in the audio stream for triggering. Redrawing rate for screen its better.

If no triggering then draw old buffer on screen. Is new triggering write samples in the buffer and draw screen.


Video: https://youtu.be/SLWPrdfzuvs


Now I just have to think about how I can decrease the sample rate for the oscilloscope :confused:


Code:
void Oscilloscope::AddtoBuffer(int16_t *audio) {
	const int16_t *begin = audio;
	const int16_t *end = audio + AUDIO_BLOCK_SAMPLES;
	int sample1 = 0;
	int sample2 = 0;
	uint8_t trigger_flag = false;
	__disable_irq();
	
	// cross trigger	
	for (uint8_t i = 0; i < 8; i++ ) {
		sample1 = *audio;
		audio++;
		sample2 = *audio;
		if (sample1 > 0 && sample2 < 0)
		{
			trigger_flag = true;
			break;
		}
	}
	
	if (trigger_flag == true){
		do {
			buffer[count++] = *audio;
			audio++;
		} while (audio < end);
		if(count > (AUDIO_BLOCK_SAMPLES * NO_OF_BLOCKS) -1){
			count = 0;
		}	
	}
	Display();
	__enable_irq();
}

Greetings Rolf
 
Here is my complete code change for oscilloscopes.

Oscilloscope.h
Code:
#ifndef Oscilloscope_h_
#define Oscilloscope_h_
#include "AudioStream.h"
#include "ST7735_t3.h"
#include <Adafruit_GFX.h>

#define NO_OF_BLOCKS 1


class Oscilloscope : public AudioStream {
	public:
	Oscilloscope(void) : AudioStream(1, inputQueueArray) {
	}
	virtual void update(void);
	void ScreenSetup(ST7735_t3*);
	void Display(void);
	void AddtoBuffer(int16_t*);

	private:
	audio_block_t *inputQueueArray[1];
	ST7735_t3 *display;
	int16_t buffer[AUDIO_BLOCK_SAMPLES * NO_OF_BLOCKS];
	int16_t old_val[AUDIO_BLOCK_SAMPLES +1];
	uint32_t count = 0;
};
#endif


void Oscilloscope::ScreenSetup(ST7735_t3 *screen) {
	__disable_irq();
	display = screen;
	__enable_irq();
}

void Oscilloscope::Display() {
	__disable_irq();
	uint16_t pixel_x = 0;
	uint16_t i = 0;
	old_val[0] = 96;
	do {
		int16_t wave_data = buffer[i];
		int16_t pixel_y = map(wave_data, 32767, -32768, -100, 100) + 96;
		if (pixel_y < 65) {
			pixel_y = 65;
			display->drawLine(pixel_x + 19, old_val[pixel_x], pixel_x + 19, pixel_y, ST7735_BLACK);//New pixel
		}
		else if (pixel_y > 125) {
			pixel_y = 125;
			display->drawLine(pixel_x + 19, old_val[pixel_x], pixel_x + 19, pixel_y, ST7735_BLACK);//New pixel
		}
		else {
			display->drawLine(pixel_x + 19, old_val[pixel_x], pixel_x + 19, pixel_y, ST7735_BLUE);//New pixel
		}
		old_val[pixel_x + 1] = pixel_y;
		pixel_x++;
		i = i + NO_OF_BLOCKS;
	} while (i < ((AUDIO_BLOCK_SAMPLES * NO_OF_BLOCKS) -8));  // -8 trigger samples
	__enable_irq();
}

void Oscilloscope::AddtoBuffer(int16_t *audio) {
	const int16_t *begin = audio;
	const int16_t *end = audio + AUDIO_BLOCK_SAMPLES;
	int sample1 = 0;
	int sample2 = 0;
	uint8_t trigger_flag = false;
	__disable_irq();
	
	// cross trigger	
	for (uint8_t i = 0; i < 8; i++ ) {
		sample1 = *audio;
		audio++;
		sample2 = *audio;
		if (sample1 > 0 && sample2 < 0)
		{
			trigger_flag = true;
			break;
		}
	}
	
	if (trigger_flag == true){
		do {
			buffer[count++] = *audio;
			audio++;
		} while (audio < end);
		if(count > (AUDIO_BLOCK_SAMPLES * NO_OF_BLOCKS) -1){
			count = 0;
		}	
	}
	Display();
	__enable_irq();
}

void Oscilloscope::update(void) {
	if (!display) return;
	audio_block_t *block;
	block = receiveReadOnly(0);
	if (block) {
		AddtoBuffer(block->data);
		release(block);
	}
}
 
Wow, thanks for this thread - I was just contemplating how to do an Oscilloscope display for a Teensy Audio synth, in particular, how to get access to the raw audio samples. I sort of figured that you'd need to derive from the AudioStream class and catch it in Update - this seems to confirm that is the way to do it, so thanks for this.

PS Rolf I seem to remember you used to do some amazing synth things with Xmega on the AVR Freaks forum!
 
@Rolfdegen (or anyone else who has already succeeded in adding an oscilloscope display to your TFT project display):

I would like to add an oscilloscope display of the (digital) audio stream created by the Teensy Audio library to my personal TeensyMIDIPolySynth project. I am using an 800x480 TFT display based upon the RA8875 display controller chip. I am not a C++ programmer, so I am stumbling a little with exactly how to integrate the Oscilloscope class code that you provided in <this> post.

I have successfully created my own customized "Oscilloscope.h" file (placed in the same folder as my TeensyMIDIPolySynth source) with changes that I believe will convert from your "ST7735_t3" display controller to my RA8875 display controller.

Code:
#ifndef Oscope_h_
#define Oscope_h_
#include "AudioStream.h"
#include "RA8875.h"
#define NO_OF_BLOCKS 2

class Oscope : public AudioStream {
   public:
      Oscope(void) : AudioStream(1, inputQueueArray) {
      }
      virtual void update(void);
      void screenSetup(RA8875*);
      void doDisplay(void);
      void addtoBuffer(int16_t*);

   private:
      audio_block_t *inputQueueArray[1];
      RA8875 *display;
      int16_t buffer[AUDIO_BLOCK_SAMPLES * NO_OF_BLOCKS];
      int16_t old_val[AUDIO_BLOCK_SAMPLES];
      uint32_t count = 0;
};
#endif

void Oscope::screenSetup(RA8875 *screen) {
   __disable_irq();
   display = screen;
   __enable_irq();
}

void Oscope::doDisplay() {
   __disable_irq();
   boolean crossing = false;
   int16_t pixel_x = 0;
   uint32_t i = 0;

   do {
      int16_t wave_data = buffer[i];
      if (crossing || wave_data > 0) {
         i++;
         wave_data = buffer[i];
         if (crossing || wave_data < 0 ) {
            crossing = true;
            //int16_t pixel_y = map(wave_data, 32767, -32768, -100, 100) + 63;
            int16_t pixel_y = map(wave_data, 32767, -32768, -64, 64) + 96;

            // limit sample size
            if (pixel_y >= 66 && pixel_y <= 126)
            {
               display->drawPixel(pixel_x + 15, old_val[pixel_x], RA8875_BLACK);//Remove previous pixel
               display->drawPixel(pixel_x + 15, pixel_y, RA8875_WHITE);//New pixel
               old_val[pixel_x] = {pixel_y};
               pixel_x++;
            }
            else {
               old_val[pixel_x] = 96;
               pixel_x++;
            }
         }
      }
      i++;
   } while (i < AUDIO_BLOCK_SAMPLES * NO_OF_BLOCKS);

   __enable_irq();
}

void Oscope::addtoBuffer(int16_t *audio) {
//   const int16_t *begin = audio;
   const int16_t *end = audio + AUDIO_BLOCK_SAMPLES;
   __disable_irq();
   do {
      buffer[count++] = *audio;
      audio++;
   } while (audio < end);
   if (count > (AUDIO_BLOCK_SAMPLES * NO_OF_BLOCKS) - 1) {
      count = 0;
      doDisplay();
   }
   __enable_irq();
}

void Oscope::update(void) {
   if (!display) return;
   audio_block_t *block;
   block = receiveReadOnly(0);
   if (block) {
      addtoBuffer(block->data);
      release(block);
   }
}


I have added these lines to include the new file as follows:

Code:
#include <SPI.h>    // to avoid unnecessary compile errors, this *must* appear in the list of includes *before* RA8875
#include <RA8875.h>
#include "Oscope.h"
#include <USBHost_t36.h>
#include <MIDI.h>
#include <EEPROM.h>
#include <Audio.h>


I have added these lines to (as my best guess) to declare the "Oscilloscope" object (which I will call "scope"), placed after the RA8875 display is declared as follows:

Code:
RA8875 tft = RA8875(RA8875_CHIP_SELECT, RA8875_RESET, RA8875_MOSI, RA8875_SCLK, RA8875_MISO);

Oscope scope = Oscope();


I have added these lines in "setup()" to initialize the Oscilliscope "scope" as follows:

Code:
   tft.begin(RA8875_800x480);
   [[ other tft initialization occurs here - snipped for brevity ]]
   scope.screenSetup(&tft);


Now, the part that I really don't understand is exactly what to call (in "loop()" ??) to get the display to show. I would appreciate it if you could provide any pointers on what I need to do to take this to completion.

Thanks in advance !!

Mark J Culross
KD5RXT
 
Last edited:
Hello Mark

I am sorry. But unfortunately I can't help you much.

Unfortunately, I don't know much about C-classes. I copied the scope code from another open source project.


For display output I use a frame buffer and for drawing to the display I use the ST7735.h and Adafruit_Gfx.h library.


Normally you can draw a line directly on the display with tft.drawLine() without a frame buffer.


For drawing a scope I would use an interval timer in loop(). I don't know how to call the Scope class in loop() :confused:


Greetings Rolf
 
Back
Top