h4yn0nnym0u5e
Well-known member
Excellent news, glad we got it sorted out. It’s prompted me to improve the documentation for the PR branch (it shows up in the Design Tool), so once it’s in, things should be a bit easier for everyone.
I was thinking in terms of having the option to use different pins as chip select. D2-4 are also I2S2, so might well be wanted for an audio project. There aren’t many pins on a Teensy 4.0 that can’t be used for some audio function or another, but a 4.1 gives a few more options.Can you please clarify "links to change the device selection lines." Is this just a naming thing, like I should use C0/C1/C2 instead of DO2/DO3/DO4? If so, I agree. Or are you talking about moving to different pins altogether?
Once this comes together, I will post on github and run a few copies with Osh Park.
Great, thanks. One point that’s just occurred to me … as it is there’s always one PSRAM enabled, so the SPI bus can’t be used for anything else. That wasn’t an issue with only 6 of them, of course, as there are a couple of unused addresses.
why Frank put the 74LCX126 on memoryboard4. Can/should it be removed to allow two more memory chips on a new board?
I did wonder about that part…The 74LCX126 is for level shifting, because Teensy3 is 5V, the memory chips are not. It's not needed for Teensy4.
I am building a memory expansion board that's shorter so it fits on Teensy 4.0 footprint (also 4.1 of course) and has (6) 23LC1024 chips. It uses the same memory addressing scheme as the original Teensy 3 Memoryboard so hopefully it'll be recognized by the libraries.
#include "Arduino.h"
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#define DUMMY_TIME_MS 92700.0f
// GUItool: begin automatically generated code
AudioSynthWaveformModulated waveformMod; //xy=183,174
// One PSRAM 8MB chip is 95108.93ms of delay
// With a SPI bus of 31.41MHz, and 3 delay objects with 3, 3, and 1 tap, we
// need to do 3 writes and 7 reads of 128*2*8 = 2048 bits, so 20480 bits,
// which will take about 0.68ms. This happes every 2.9ms, so expect
// a CPU load of 22.4%. We get about 19%, so fairly close.
AudioEffectDelayExternal delayExt(AUDIO_MEMORY_PSRAM64_X8, 1400.0f); //xy=322,396
AudioEffectDelayExternal dummyDelay(AUDIO_MEMORY_PSRAM64_X8,DUMMY_TIME_MS); // make next delay cross chip boundary
AudioEffectDelayExternal delayExt1(AUDIO_MEMORY_PSRAM64_X8,2000.0f); //xy=329,641
AudioMixer4 mixer4; //xy=604,213
AudioMixer4 mixer5; //xy=611,458
AudioOutputI2S i2s; //xy=811,148
AudioConnection patchCord1(waveformMod, 0, mixer4, 0);
AudioConnection patchCord2(delayExt, 0, mixer4, 1);
AudioConnection patchCord3(delayExt, 1, mixer4, 2);
AudioConnection patchCord4(delayExt, 1, mixer5, 0);
AudioConnection patchCord5(delayExt, 2, mixer4, 3);
AudioConnection patchCord6(delayExt1, 0, mixer5, 1);
AudioConnection patchCord7(delayExt1, 1, mixer5, 2);
AudioConnection patchCord8(delayExt1, 2, mixer5, 3);
AudioConnection patchCord9(mixer4, delayExt);
AudioConnection patchCord10(mixer4, 0, i2s, 1);
AudioConnection patchCord11(mixer5, delayExt1);
AudioConnection patchCord12(mixer5, 0, i2s, 0);
AudioControlSGTL5000 sgtl5000_1; //xy=811,195
// GUItool: end automatically generated code
void setup() {
Serial.begin(115200);
Serial.println("Starting audio...");
AudioMemory(40);
mixer4.gain(0,0.71f);
mixer4.gain(1,0.05f);
mixer4.gain(2,0.20f);
mixer4.gain(3,0.71f);
Serial.println("Set up delayExt object");
delayExt.delay(0,23.0);
delayExt.delay(1,137.0);
delayExt.delay(2,911.0);
mixer5.gain(0,0.71f);
mixer5.gain(1,0.05f);
mixer5.gain(2,0.20f);
mixer5.gain(3,0.71f);
Serial.println("Set up delayExt1 object");
delayExt1.delay(0,29.0);
delayExt1.delay(1,241.0);
delayExt1.delay(2,1297.0);
Serial.println("Set up dummyDelay object");
uint32_t now = micros();
dummyDelay.delay(0,29.0);
now = micros() - now;
float SPIrate = DUMMY_TIME_MS / 1000.0f * AUDIO_SAMPLE_RATE * 2 * 8 / now;
Serial.printf("SPI bus speed = %.2fMHz\n",SPIrate);
Serial.printf("Maximum delay is %.3fms\n",delayExt.getMaxDelay());
int x = sizeof(((audio_block_t*) 0)->data[0]);
Serial.printf("Sample size is %d bytes\n",x);
sgtl5000_1.enable();
sgtl5000_1.volume(0.2);
sgtl5000_1.lineOutLevel(14); // 2.98V pk-pk
}
uint32_t next;
void loop() {
if (millis() > next)
{
next += 5000;
waveformMod.begin(1.0f,220.0f,WAVEFORM_SINE);
delay(10);
Serial.printf("Usage %.2f, max %.2f\n",AudioProcessorUsage(),AudioProcessorUsageMax());
AudioProcessorUsageMaxReset();
waveformMod.amplitude(0.0f);
}
}
#include <SPI.h>
#include <Arduino.h>
#define SPISETTING_PS SPISettings(31'000'000, MSBFIRST, SPI_MODE0) // Adjusted speed for PSRAM
#define MEMBRD8M_CS0_PIN 2
#define MEMBRD8M_CS1_PIN 3
#define MEMBRD8M_CS2_PIN 4
#define MEMBRD8M_ENL_PIN 5 // Enable pin, active low
const unsigned long MAX_ADDRESS = 0x3FFFFF; // Maximum address for 64MB chips in 16-bit mode
void setup() {
Serial.begin(9600);
SPI.begin();
pinMode(MEMBRD8M_CS0_PIN, OUTPUT);
pinMode(MEMBRD8M_CS1_PIN, OUTPUT);
pinMode(MEMBRD8M_CS2_PIN, OUTPUT);
pinMode(MEMBRD8M_ENL_PIN, OUTPUT);
initPSRAM();
unsigned long startTime = micros();
// Sequentially write to and read from each chip
for (int chip = 0; chip < 8; chip++) {
for (unsigned long addr = 0; addr <= MAX_ADDRESS; addr += 1024) {
writePSRAM(chip, addr, 0xA5A5);
uint16_t data = readPSRAM(chip, addr);
}
}
unsigned long endTime = micros();
// Output result
Serial.print("Total operation time (microseconds): ");
Serial.println(endTime - startTime);
}
void loop() {
}
void setMuxPSRAMx8(int chip) {
digitalWrite(MEMBRD8M_CS0_PIN, ~chip & 2);
digitalWrite(MEMBRD8M_CS1_PIN, ~chip & 4);
digitalWrite(MEMBRD8M_CS2_PIN, chip & 1);
digitalWrite(MEMBRD8M_ENL_PIN, LOW);
}
void initPSRAM() {
SPI.beginTransaction(SPISETTING_PS);
for (int i = 0; i < 8; i++) {
setMuxPSRAMx8(i);
}
setMuxPSRAMx8(-1);
SPI.endTransaction();
}
void writePSRAM(int chip, uint32_t address, uint16_t data) {
SPI.beginTransaction(SPISETTING_PS);
setMuxPSRAMx8(chip);
SPI.transfer(0x02); // Command for write
SPI.transfer((address >> 16) & 0xFF); // High byte
SPI.transfer((address >> 8) & 0xFF); // Middle byte
SPI.transfer(address & 0xFF); // Low byte
SPI.transfer16(data); // Data to write
setMuxPSRAMx8(-1); // Deselect the chip
SPI.endTransaction();
}
uint16_t readPSRAM(int chip, uint32_t address) {
uint16_t data;
SPI.beginTransaction(SPISETTING_PS);
setMuxPSRAMx8(chip);
SPI.transfer(0x03); // Command for read
SPI.transfer((address >> 16) & 0xFF); // High byte
SPI.transfer((address >> 8) & 0xFF); // Middle byte
SPI.transfer(address & 0xFF); // Low byte
data = SPI.transfer16(0xFFFF); // Dummy data to read
setMuxPSRAMx8(-1); // Deselect the chip
SPI.endTransaction();
return data;
}
setMuxPSRAMx8()
, so selecting chip -1 doesn't disable the mux by setting MEMBRD8M_ENL_PIN HIGH
.ah apologies!, i was on the go....ill deep dive into thisI never said that! I think you meant to use the </> code tag, which would make things more readable, too... Though bits do resemble my updated delay code - you missed a bit when you copiedsetMuxPSRAMx8()
, so selecting chip -1 doesn't disable the mux by settingMEMBRD8M_ENL_PIN HIGH
.
Doesn't make much sense as-is. All you seem to be doing is displaying the time taken to write 1k uint16_t values to each of 8 possible PSRAM chips, and then read them back. You haven't checked to see if the read-back value matches what was written.
In this post from 2023 I demonstrated that very long delays are possible using a buffer on SD card on a T4.1.Could someone tell me roughly how long a delay is possible with the delay effect in the audio library? I'm building a hardware delay line for music synthesis and wondering whether the Teensy 3.1 is appropriate for longer delays (2000ms). I realize that other code will also consume memory. For this application, the only extra code would be instructions necessary for reading controls for real-time parameter changes.
Thanks very much for your help,
Michael
AudioExtMem
class isn't exactly well-documented ... sorry. The idea is that it's used as a base class for any derived (probably but not necessarily audio) class to abstract the interface to large buffer memories, either SPI-based or memory mapped. At instantiation you define the memory type as one of the options in AudioEffectDelayMemoryType_t
, along with the desired size, then just use the (protected) read and write member functions to access the memory, without having to write yet another copy of the code that can do it. For loops and delays, the wrap versions of the access functions are to my mind particularly snazzy!thats good to know, im going to pick it apart and pull something together, it looks goodOK ... theAudioExtMem
class isn't exactly well-documented ... sorry. The idea is that it's used as a base class for any derived (probably but not necessarily audio) class to abstract the interface to large buffer memories, either SPI-based or memory mapped. At instantiation you define the memory type as one of the options inAudioEffectDelayMemoryType_t
, along with the desired size, then just use the (protected) read and write member functions to access the memory, without having to write yet another copy of the code that can do it. For loops and delays, the wrap versions of the access functions are to my mind particularly snazzy!
Obviously SD cards are the way to go for humongous buffers, and will also be faster than SPI-based RAM if you use the built-in socket on a Teensy 4.1. Card wear could be a long-term issue, but probably not worth worrying about as it's so easy to pop in a known-fresh card if you have a gig you can't afford to lose.