Help with saving .wav file to SDCard

I am attempting to make a simple program to record sound using an ADMP401 microphone and save it to an SD card using the SdFat library. I have managed to write the analogue value in a text file so the writing to SD Card and getting a value is working.

I have tried two methods of getting this to work and ran into issues with both of them. The first is trying to use the arduino TMRpcm library (https://github.com/TMRh20/TMRpcm), however I realise this is not compatible with the Photon. In attempting to port over the library I have ran into a list of errors:
‘ICIE1’ was not declared in this scope
TMRpcm.cpp:120:30: ‘TIMSK1’ was not declared in this scope
TMRpcm.cpp:121:31: ‘TCCR1A’ was not declared in this scope
TMRpcm.cpp:122:31: ‘TCCR1B’ was not declared in this scope
TMRpcm.cpp:123:38: ‘OCR1A’ was not declared in this scope
as well as some further very similar errors.

The second method I have used is trying to adapt the brilliant @rickkas7 example found here: https://github.com/rickkas7/photonAudio

Although this seems more complicated than I need I cannot get this to work either.

The third option is of course to build it up from scratch but I don’t want to do this if I can sort this by one of the two above methods.

Does anyone have any advice for the best method to proceed with? Has anyone got any similar projects that they have got working?

Any help is much appreciated

Could you elaborate in what way it failed to work?

What sampling rate are you aiming for?
Would a raw PCM file suffice or do you need to prepare the WAV header too?
How are you currently obtaining your analog values you write to a text file?
Would the data (if formatted correctly) you are already saving in that text file suffice as is?

Hi ScruffR,
Thanks for your response.

I need a sampling rate of around 40kHz but could be dropped lower than that if necessary.

Eventually a WAV header would be preferred but I think I’d be able to do that step. For now a raw PCM file would suffice.

I am currently obtaining with a simple
analogRead()
file.print
loop.

I’m getting a sampling rate of around 8kHz so I need a bit better than this.

Thanks for your questions, they’ve helped me think this through. My next stage is to look at a buffer to try and improve sampling rate…

Rick’s audio4 sample should be suitable for you.
Instead of uploading to the TCP server you should be able to use SdFat to do the a DMA transfer of the buffer to the SD card in STATE_RUNNING.

I’m pretty sure @whg has got DMA support in his Particle SdFat library.

1 Like

Great, thanks for your help. I’ll start looking into that now and let you know if I need any further help.

Hello again,

I have tried implementing your suggestions @ScruffR but I’ve ran into some issues. Following the code through with publishes it appears to be working but no data is being saved on the
file created on the sdcard.

Will continue troubleshooting to see what I’ve done wrong, can you (or anyone else) potentially spot where I am going wrong? I have commented out the bits that I believe I don’t need from Rick’s audio4 code.

// This #include statement was automatically added by the Particle IDE.
#include <SdFat.h>

#include "Particle.h"

// particle compile photon audio4.cpp --target 0.6.3 --saveTo firmware.bin


//
// ADCDMA - Class to use Photon ADC in DMA Mode
//
#include "adc_hal.h"
#include "gpio_hal.h"
#include "pinmap_hal.h"
#include "pinmap_impl.h"

#define TEST_FILE_NAME "AudioTest4.bin"

void buttonHandler(system_event_t event, int data); // forward declaration


SdFat sd;
SdFile file;

// The buffer should be around 1024 bytes. However, it also should be a multiple of
// 2 * number of channels = 12, so 1020 is a good choice. The total memory usage is 2x this because
// of double buffers.
const size_t SAMPLE_BUF_SIZE = 1020;

// The audio sample rate. The minimum is probably 8000 for minimally acceptable audio quality.
// Not sure what the maximum rate is, but it's pretty high.
const long SAMPLE_RATE = 44100;

// If you don't hit the setup button to stop recording, this is how long to go before turning it
// off automatically. The limit really is only the disk space available to receive the file.
const unsigned long MAX_RECORDING_LENGTH_MS = 30000;

// This is the IP Address and port that the audioServer.js node server is running on.
IPAddress serverAddr = IPAddress(192,168,2,4);
int serverPort = 7123;


uint16_t samples[SAMPLE_BUF_SIZE];

TCPClient client;
unsigned long recordingStart;

enum State { STATE_WAITING, STATE_CONNECT, STATE_RUNNING, STATE_FINISH };
State state = STATE_WAITING;

//
//
//
class ADCDMA {
public:
	ADCDMA(uint16_t *buf, size_t bufSize);
	virtual ~ADCDMA();

	void start(size_t freqHZ);
	void stop();

private:
	int pin;
	uint16_t *buf;
	size_t bufSize;
};

// Helpful post:
// https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Flat.aspx?RootFolder=https%3a%2f%2fmy%2est%2ecom%2fpublic%2fSTe2ecommunities%2fmcu%2fLists%2fcortex%5fmx%5fstm32%2fstm32f207%20ADC%2bTIMER%2bDMA%20%20Poor%20Peripheral%20Library%20Examples&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B&currentviews=6249

ADCDMA::ADCDMA(uint16_t *buf, size_t bufSize) : pin(pin), buf(buf), bufSize(bufSize) {
}

ADCDMA::~ADCDMA() {

}

void ADCDMA::start(size_t freqHZ) {

    // Using Dual ADC Regular Simultaneous DMA Mode 1

	// Using Timer3. To change timers, make sure you edit all of:
	// RCC_APB1Periph_TIM3, TIM3, ADC_ExternalTrigConv_T3_TRGO

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

	// Set the pin as analog input
	// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
	// GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
	for(int pin = A0; pin <= A5; pin++) {
		HAL_Pin_Mode(pin, AN_INPUT);
	}

	// Enable the DMA Stream IRQ Channel
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	// 60000000UL = 60 MHz Timer Clock = HCLK / 2
	// Even low audio rates like 8000 Hz will fit in a 16-bit counter with no prescaler (period = 7500)
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
	TIM_TimeBaseStructure.TIM_Period = (60000000UL / freqHZ) - 1;
	TIM_TimeBaseStructure.TIM_Prescaler = 0;
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
	TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update); // ADC_ExternalTrigConv_T3_TRGO
	TIM_Cmd(TIM3, ENABLE);

	ADC_CommonInitTypeDef ADC_CommonInitStructure;
	ADC_InitTypeDef ADC_InitStructure;
	DMA_InitTypeDef DMA_InitStructure;

	// DMA2 Stream0 channel0 configuration
	DMA_InitStructure.DMA_Channel = DMA_Channel_0;
	DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buf;
	DMA_InitStructure.DMA_PeripheralBaseAddr =  0x40012308; // CDR_ADDRESS; Packed ADC1, ADC2;
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
	DMA_InitStructure.DMA_BufferSize = bufSize;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
	DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
	DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
	DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
	DMA_Init(DMA2_Stream0, &DMA_InitStructure);

	// Don't enable DMA Stream Half / Transfer Complete interrupt
	// Since we want to write out of loop anyway, there's no real advantage to using the interrupt, and as
	// far as I can tell, you can't set the interrupt handler for DMA2_Stream0 without modifying
	// system firmware because there's no built-in handler for it.
	// DMA_ITConfig(DMA2_Stream0, DMA_IT_TC | DMA_IT_HT, ENABLE);

	DMA_Cmd(DMA2_Stream0, ENABLE);

	// ADC Common Init
	ADC_CommonInitStructure.ADC_Mode = ADC_DualMode_RegSimult;
	ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
	ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1;
	ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_14Cycles;
	ADC_CommonInit(&ADC_CommonInitStructure);

	// ADC1 configuration
	ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
	ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Left;
	ADC_InitStructure.ADC_NbrOfConversion = 3;
	ADC_Init(ADC1, &ADC_InitStructure);

	ADC_RegularChannelConfig(ADC1, PIN_MAP[A0].adc_channel, 1, ADC_SampleTime_28Cycles);
	ADC_RegularChannelConfig(ADC1, PIN_MAP[A2].adc_channel, 2, ADC_SampleTime_28Cycles);
	ADC_RegularChannelConfig(ADC1, PIN_MAP[A4].adc_channel, 3, ADC_SampleTime_28Cycles);

	// ADC2 configuration - same
	ADC_Init(ADC2, &ADC_InitStructure);

	//
	ADC_RegularChannelConfig(ADC2, PIN_MAP[A1].adc_channel, 1, ADC_SampleTime_28Cycles);
    ADC_RegularChannelConfig(ADC2, PIN_MAP[A3].adc_channel, 2, ADC_SampleTime_28Cycles);
    ADC_RegularChannelConfig(ADC2, PIN_MAP[A5].adc_channel, 3, ADC_SampleTime_28Cycles);

	// Enable DMA request after last transfer (Multi-ADC mode)
	ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE);

	// Enable ADCs
	ADC_Cmd(ADC1, ENABLE);
	ADC_Cmd(ADC2, ENABLE);

	ADC_SoftwareStartConv(ADC1);
}

void ADCDMA::stop() {
	// Stop the ADC
	ADC_Cmd(ADC1, DISABLE);
	ADC_Cmd(ADC2, DISABLE);

	DMA_Cmd(DMA2_Stream0, DISABLE);

	// Stop the timer
	TIM_Cmd(TIM3, DISABLE);
}

ADCDMA adcDMA(samples, SAMPLE_BUF_SIZE);

// End ADCDMA


void setup() {
    Wire.begin();
	Serial.begin(9600);
	sd.begin(A2);
    file.open(TEST_FILE_NAME, O_APPEND | O_CREAT | O_TRUNC);   //| std::ios::binary
	// Register handler to handle clicking on the SETUP button
	System.on(button_click, buttonHandler);
	pinMode(D7, OUTPUT);

}

void loop() {
	uint16_t *sendBuf = NULL;

	switch(state) {
	case STATE_WAITING:
		// Waiting for the user to press the SETUP button. The setup button handler
		// will bump the state into STATE_CONNECT
		break;

	case STATE_CONNECT:
		// Ready to connect to the server via TCP
		//if (client.connect(serverAddr, serverPort)) {
			// Connected
			//adcDMA.start(SAMPLE_RATE);

			Particle.publish("starting", PRIVATE);

			recordingStart = millis();
			digitalWrite(D7, HIGH);

			state = STATE_RUNNING;
		//}
		/*else {
			Particle.publish("failed to connect to server", PRIVATE);
			state = STATE_WAITING;
		}*/
		break;

	case STATE_RUNNING:
	       Particle.publish("A", PRIVATE);
	       delay(2000);
	       Particle.publish("A2", PRIVATE);
	       delay(2000);
	//	if (DMA_GetFlagStatus(DMA2_Stream0, DMA_FLAG_HTIF0)) {
	//	    DMA_ClearFlag(DMA2_Stream0, DMA_FLAG_HTIF0);
		    sendBuf = samples;
		    Particle.publish("B", PRIVATE);
		    delay(2000);
	        Particle.publish("B2", PRIVATE);
	//	}
		if (DMA_GetFlagStatus(DMA2_Stream0, DMA_FLAG_TCIF0)) {
		    DMA_ClearFlag(DMA2_Stream0, DMA_FLAG_TCIF0);
		    sendBuf = &samples[SAMPLE_BUF_SIZE / 2];
		    Particle.publish("C", PRIVATE);
		}

		if (sendBuf != NULL) {
			// There is a sample buffer to send

			// Send here. We're actually sending 1/2 of the samples buffer, but the samples
			// are 16 bits and client.write() takes bytes, so there's no division
			Particle.publish("File has been succesfully opened", PRIVATE);
			int count = file.write(sendBuf,SAMPLE_BUF_SIZE);                            //My attempt at replacement  
			//file.print("test");
			Particle.publish("file.write", PRIVATE);
			file.close();
			//int count = client.write((uint8_t *)sendBuf, SAMPLE_BUF_SIZE);                //replace with sdFat DMA
			if (count == SAMPLE_BUF_SIZE) {
				// Success
			}
			else
			if (count == -16) {
				// TCP Buffer full
				Particle.publish("buffer full, discarding", PRIVATE);
			}
			//else {
				// Error
			//	Particle.publish("error writing %d", PRIVATE);
			//	state = STATE_FINISH;
			//}
		}

		if (millis() - recordingStart >= MAX_RECORDING_LENGTH_MS) {
			state = STATE_FINISH;
		}
		break;

	case STATE_FINISH:
		digitalWrite(D7, LOW);
		//adcDMA.stop();
		//client.stop();
		
		Particle.publish("stopping", PRIVATE);
		state = STATE_WAITING;
		break;
	}
}

// button handler for the SETUP button, used to toggle recording on and off
void buttonHandler(system_event_t event, int data) {
	switch(state) {
	case STATE_WAITING:
		state = STATE_CONNECT;
		break;

	case STATE_RUNNING:
		state = STATE_FINISH;
		break;
	}
}

I’ve been fairly brutal with cutting out bits of code to try and get it to run all the way through, now it is doing that but not behaving as expected (not really a surprise) so I’m trying to return functionality now…

I’m getting it looping round and succesfully creating the file but it doesn’t contain anything. Is it an issue with having it as a .txt?

I quickly adapted Rick’s demo project to create some raw PCM file on SD

code
#include "Particle.h"


// Tested with Adafruit 1713
// Electret Microphone Amplifier - MAX9814 with Auto Gain Control
// https://www.adafruit.com/products/1713
//
// AR   - No connection
// Out  - Audio out (analog) to Photon A0
// Gain - No connection
// VDD  - 3V3
// GND  - GND

//
// ADCDMA - Class to use Photon ADC in DMA Mode
//
#include "adc_hal.h"
#include "gpio_hal.h"
#include "pinmap_hal.h"
#include "pinmap_impl.h"
#include <SdFat.h>

SdFat sd;
File  myFile;
int   fileCount = 0;

void buttonHandler(system_event_t event, int data); // forward declaration

const size_t SAMPLE_BUF_SIZE = 2048;
const int SAMPLE_PIN   = A0;
const int SPI_CS       = A2;
const long SAMPLE_RATE = 44100;
const unsigned long MAX_RECORDING_LENGTH_MS = 10000;

uint16_t samples[SAMPLE_BUF_SIZE];

unsigned long recordingStart;

enum State { STATE_WAITING, STATE_CONNECT, STATE_RUNNING, STATE_FINISH };
State state = STATE_WAITING;

//
//
//
class ADCDMA {
public:
	ADCDMA(int pin, uint16_t *buf, size_t bufSize);
	virtual ~ADCDMA();

	void start(size_t freqHZ);
	void stop();

private:
	int pin;
	uint16_t *buf;
	size_t bufSize;
};

// Helpful post:
// https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Flat.aspx?RootFolder=https%3a%2f%2fmy%2est%2ecom%2fpublic%2fSTe2ecommunities%2fmcu%2fLists%2fcortex%5fmx%5fstm32%2fstm32f207%20ADC%2bTIMER%2bDMA%20%20Poor%20Peripheral%20Library%20Examples&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B&currentviews=6249

ADCDMA::ADCDMA(int pin, uint16_t *buf, size_t bufSize) : pin(pin), buf(buf), bufSize(bufSize) {
}

ADCDMA::~ADCDMA() {

}

void ADCDMA::start(size_t freqHZ) {

    // Using Dual ADC Regular Simultaneous DMA Mode 1

	// Using Timer3. To change timers, make sure you edit all of:
	// RCC_APB1Periph_TIM3, TIM3, ADC_ExternalTrigConv_T3_TRGO

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

	// Set the pin as analog input
	// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
	// GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    HAL_Pin_Mode(pin, AN_INPUT);

	// Enable the DMA Stream IRQ Channel
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	// 60000000UL = 60 MHz Timer Clock = HCLK / 2
	// Even low audio rates like 8000 Hz will fit in a 16-bit counter with no prescaler (period = 7500)
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
	TIM_TimeBaseStructure.TIM_Period = (60000000UL / freqHZ) - 1;
	TIM_TimeBaseStructure.TIM_Prescaler = 0;
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
	TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update); // ADC_ExternalTrigConv_T3_TRGO
	TIM_Cmd(TIM3, ENABLE);

	ADC_CommonInitTypeDef ADC_CommonInitStructure;
	ADC_InitTypeDef ADC_InitStructure;
	DMA_InitTypeDef DMA_InitStructure;

	// DMA2 Stream0 channel0 configuration
	DMA_InitStructure.DMA_Channel = DMA_Channel_0;
	DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buf;
	DMA_InitStructure.DMA_PeripheralBaseAddr =  0x40012308; // CDR_ADDRESS; Packed ADC1, ADC2;
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
	DMA_InitStructure.DMA_BufferSize = bufSize;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
	DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
	DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
	DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
	DMA_Init(DMA2_Stream0, &DMA_InitStructure);

	// Don't enable DMA Stream Half / Transfer Complete interrupt
	// Since we want to write out of loop anyway, there's no real advantage to using the interrupt, and as
	// far as I can tell, you can't set the interrupt handler for DMA2_Stream0 without modifying
	// system firmware because there's no built-in handler for it.
	// DMA_ITConfig(DMA2_Stream0, DMA_IT_TC | DMA_IT_HT, ENABLE);

	DMA_Cmd(DMA2_Stream0, ENABLE);

	// ADC Common Init
	ADC_CommonInitStructure.ADC_Mode = ADC_DualMode_RegSimult;
	ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
	ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1;
	ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
	ADC_CommonInit(&ADC_CommonInitStructure);

	// ADC1 configuration
	ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
	ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Left;
	ADC_InitStructure.ADC_NbrOfConversion = 1;
	ADC_Init(ADC1, &ADC_InitStructure);

	// ADC2 configuration - same
	ADC_Init(ADC2, &ADC_InitStructure);

	//
	ADC_RegularChannelConfig(ADC1, PIN_MAP[pin].adc_channel, 1, ADC_SampleTime_15Cycles);
    ADC_RegularChannelConfig(ADC2, PIN_MAP[pin].adc_channel, 1, ADC_SampleTime_15Cycles);

	// Enable DMA request after last transfer (Multi-ADC mode)
	ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE);

	// Enable ADCs
	ADC_Cmd(ADC1, ENABLE);
	ADC_Cmd(ADC2, ENABLE);

	ADC_SoftwareStartConv(ADC1);
}

void ADCDMA::stop() {
	// Stop the ADC
	ADC_Cmd(ADC1, DISABLE);
	ADC_Cmd(ADC2, DISABLE);

	DMA_Cmd(DMA2_Stream0, DISABLE);

	// Stop the timer
	TIM_Cmd(TIM3, DISABLE);
}

ADCDMA adcDMA(SAMPLE_PIN, samples, SAMPLE_BUF_SIZE);

void setup() {
	Serial.begin(9600);

	// Register handler to handle clicking on the SETUP button
	System.on(button_click, buttonHandler);
	pinMode(D7, OUTPUT);

	if (sd.begin(SPI_CS, SPI_FULL_SPEED)) 
      Serial.println("SD initialised");
	else
      Serial.println("failed to open card");
}

void loop() {
	uint16_t *sendBuf = NULL;

	switch(state) {
	case STATE_WAITING:
		// Waiting for the user to press the SETUP button. The setup button handler
		// will bump the state into STATE_CONNECT
		break;

	case STATE_CONNECT:
	  {
        char fileName[128];
        snprintf(fileName, sizeof(fileName), "rec%04d.pcm", fileCount+1);
        if (myFile.open(fileName, O_RDWR | O_CREAT | O_TRUNC)) {
          fileCount++;
          digitalWrite(D7, HIGH);
		  Serial.printlnf("Writing to %s", fileName);
		  recordingStart = millis();
          adcDMA.start(SAMPLE_RATE);
          state = STATE_RUNNING;
        }
        else {
		  Serial.printlnf("opening %s for write failed", fileName);
		  state = STATE_WAITING;
	    }
	  }
      break;

	case STATE_RUNNING:
		if (DMA_GetFlagStatus(DMA2_Stream0, DMA_FLAG_HTIF0)) {
		    DMA_ClearFlag(DMA2_Stream0, DMA_FLAG_HTIF0);
		    sendBuf = &samples[0];
		}
		if (DMA_GetFlagStatus(DMA2_Stream0, DMA_FLAG_TCIF0)) {
		    DMA_ClearFlag(DMA2_Stream0, DMA_FLAG_TCIF0);
		    sendBuf = &samples[SAMPLE_BUF_SIZE / 2];
		}

		if (sendBuf != NULL) {
			// There is a sample buffer to send

			// Average the pairs of samples and adjust to 16bit signed PCM
			for(size_t ii = 0, jj = 0; ii < SAMPLE_BUF_SIZE / 2; ii += 2, jj++) {
				int32_t sum = sendBuf[ii] + sendBuf[ii + 1];
				sendBuf[jj] = (int16_t)((sum / 2) - 32768);
			}

            int count = myFile.write((uint8_t *)sendBuf, SAMPLE_BUF_SIZE / 2);
			if (count == SAMPLE_BUF_SIZE / 2) {
				// Success
				// Serial.printlnf("%d samples written", count);
			}
			else {
				// Error
				Serial.printlnf("error writing %d", count);
				state = STATE_FINISH;
			}
		}

		if (millis() - recordingStart >= MAX_RECORDING_LENGTH_MS) {
			state = STATE_FINISH;
		}
		break;

	case STATE_FINISH:
		digitalWrite(D7, LOW);
		adcDMA.stop();
		Serial.println("stopping");
		myFile.close();
		state = STATE_WAITING;
		break;
	}
}

// button handler for the SETUP button, used to toggle recording on and off
void buttonHandler(system_event_t event, int data) {
	switch(state) {
	case STATE_WAITING:
		state = STATE_CONNECT;
		break;

	case STATE_RUNNING:
		state = STATE_FINISH;
		break;
	}
}

Hi ScruffR,

Thank you, I managed to work it through to get some very similar code to the one you posted. Thanks for posting this as mine is mixed up with some other code.

My issue now is writing the wav header, more out of best route to take rather than explicit code. I need the number of bytes in the file in order to format some of the header. What is the best way to write the header?

  1. Write two seperate files and then concatenate
  2. Pad the area needed for the header with zero’s and then seek back to the start of the file and rewrite over
  3. Fill in all of the header except for number of bytes and then read this number of bytes and the rewrite over
  4. Another smarter option I haven’t considered??

The sound files I’m recording will be of variable sizes and ideally I want a program which can automatically fill in the header according to the size of the file.

Thanks again for your response and anyone with similar issues just take a look at ScruffR’s code

I’d probably opt for 3.

Okay cool, thanks for the advice

Hey Charles,
if you haven't already found a solution for creating a proper WAV file, here is my take on the matter

1 Like

Hi ScruffR,

Great thank you for the link. We had some other more urgent issues come up and .wav was put on hold. Just using audacity for now to convert .pcm to .wav but want to have it all done on the Photon eventually. Will have a read through this when I get some time thank you! Our project is advancing very quickly so thank you again for your help in the early stages.

1 Like