I am encountering problems regarding mesage communication between ESP8266 and arduino due.
The following link contains my codes:
The webpage folder contains the code for esp8266 (webpage.ino and webpage.h)
The stimuli folder contains the code for arduino due (stimuli.ino)
I am trying to create a WiFi-controlled fear conditioning device, which uses sound and shock to test animals. I am using the ESP8266 to control the arduino due by the webpage i created.
I have tested that the esp8266 can send message to the arduino due but the variables that i have put into the esp8266 appears as 0.00 on the arduino due serial monitor and i don't seem to find where the problem is.
The esp8266 code for sending variables to the arduino due looks like this:
/*
* Núcleo de Neurociências - UFMG/Brazil (Universidade Federal de Minas Gerais)
*
* Custom Conditioning Chamber for Classical Fear Conditioning - ESP Control
*
*
* Authors: Paulo Aparecido Amaral Júnior - amaraljr.paulo@gmail.com
* Flávio Afonso Gonçalves Mourão - mourao.fg@gmail.com
* Márcio Flávio Dutra Moraes - mfdm@icb.ufmg.br
*
* ESP8266 libraries: https://github.com/esp8266/Arduino
* The code relies on the following library boards: Aug 03, 2018 / version: 2.4.2 -> https://github.com/esp8266/Arduino/releases?page=2
*
* Last code update: August/2022 - Flávio Mourao
*
*/
/*############################################################################################################
Includes.
############################################################################################################*/
#include <ESP8266WiFi.h> //
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <SPI.h>
#include <EEPROM.h>
//#include "user_interface.h"
#include "Web_Page.h"
ControleDueSkinner *ArduinoDueSkinner = new ControleDueSkinner(); // Initialize the pointer here
/*############################################################################################################
Global variables.
############################################################################################################*/
const char *ssid = "ConditioningCage";
const char *password = "0112358132"; // Fibonacci
IPAddress myIP;
ESP8266WebServer server ( 80 );
/*############################################################################################################
Function declarations.
############################################################################################################*/
void handleRoot();
void Programar();
void handleNotFound();
/*############################################################################################################
Setup.
############################################################################################################*/
void setup ( void )
{
// Uncommenting Serial.begin() and all Serial.println() lines may help troubleshooting this sketch.
// Configure UART communication
//Serial.begin ( 115200 );
pinMode ( ArduinoDueSkinner->pinABORT, OUTPUT );
digitalWrite ( ArduinoDueSkinner->pinABORT, LOW );
pinMode ( ArduinoDueSkinner->pinSOUND, OUTPUT );
digitalWrite ( ArduinoDueSkinner->pinSOUND, LOW );
//pinMode ( ArduinoDueSkinner->pinSOUNDnot, OUTPUT );
//digitalWrite ( ArduinoDueSkinner->pinSOUNDnot, HIGH );
pinMode ( ArduinoDueSkinner->pinSHOCK, OUTPUT );
digitalWrite ( ArduinoDueSkinner->pinSHOCK, LOW );
// Waiting 1 second before reset Arduino
delay(1000);
pinMode (ArduinoDueSkinner->pinRSTarduino,OUTPUT);
digitalWrite ( ArduinoDueSkinner->pinRSTarduino, LOW );
delay(100);
digitalWrite ( ArduinoDueSkinner->pinRSTarduino, HIGH );
// Configure SPI communication.
SPI.begin ();
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE0);
SPI.setClockDivider(SPI_CLOCK_DIV16);
//SPI.setClockDivider(SPI_CLOCK_DIV2);
/* SPI_CLOCK_DIV2 0x00101001 //8 MHz
SPI_CLOCK_DIV4 0x00241001 //4 MHz
SPI_CLOCK_DIV8 0x004c1001 //2 MHz
SPI_CLOCK_DIV16 0x009c1001 //1 MHz
SPI_CLOCK_DIV32 0x013c1001 //500 KHz
SPI_CLOCK_DIV64 0x027c1001 //250 KHz
SPI_CLOCK_DIV128 0x04fc1001 //125 KHz */
SPI.setHwCs(true); // setHwCs(true) sets ESP8266 as MASTER. setHwCs(false) sets ESP8266 as SLAVE.
// Configure WiFI
WiFi.mode(WIFI_AP);
WiFi.softAP(ssid, password);
myIP = WiFi.softAPIP();
//Domain Name Server.
if ( MDNS.begin ( "esp8266" ) )
{
// Serial.println ( "MDNS responder started" );
}
/*
Serial.println("Configuring access point...");
Serial.println();
Serial.print("Access point name is: ");
Serial.println(ssid);
Serial.print("Access Point IP address: ");
Serial.println(myIP);
// Print the IP address
Serial.print("Use this URL to connect: ");
Serial.print("http://");
Serial.print(myIP);
Serial.println("/");
*/
server.on ( "/", handleRoot );
server.on ( "/Programar", Programar );
server.on ( "/inline", []()
{
server.send ( 200, "text/plain", "this works as well" );
} );
server.onNotFound ( handleNotFound );
server.begin();
// Serial.println ( "HTTP server started" );
}
/*############################################################################################################
loop.
############################################################################################################*/
void loop ( void )
{
server.handleClient();
}
/*############################################################################################################
handleRoot.
############################################################################################################*/
void handleRoot()
{
// Publish webpage.
server.send ( 200, "text/html", ArduinoDueSkinner->PaginaHTML );
}
/*############################################################################################################
Experiment code.
############################################################################################################*/
void Programar()
{
String argname,argvalue,strResponse;
uint8_t numofargs = (uint8_t) (server.args());
Serial.print("numofargs: ");
Serial.println(numofargs);
bool bSenha=false;
strResponse="";
// For all the arguments...
for (int i = 0; i < numofargs; i++ )
{
// Get argument name, which is a String, and turn it into an array of chars.
argname = server.argName(i);
//Serial.print(argname);
//Serial.print(": ");
// Get argument value, which is a String, and turn it into an array of chars.
argvalue = server.arg(i);
//Serial.println(argvalue);
// switch structure on the first character of parameter name (indicates the type)
if (argname=="Comando1")
{
strResponse+="O comando1 =" + argvalue + "; ";
}
if (argname=="Comando2")
{
strResponse+="O comando2 =" + argvalue + "; ";
}
if (argname=="Comando3")
{
strResponse+="O comando3 =" + argvalue + "; ";
}
if (argname=="Comando4")
{
uint16_t uDataIn;
uDataIn=SPI.transfer16(0xA0A0); //Pode processar
char *chTemp; chTemp=(char *) &uDataIn;
strResponse+=String("O comando4 =") + uDataIn + "; ";
}
if (argname=="EscrevaBytes")
{
char *c = (char *) ArduinoDueSkinner->BufferFloat;
c[0]='A';
c[1]='B';
c[2]='C';
c[3]='D';
uint16_t *varX;
strResponse+="Request sending (" + argvalue + ") bytes on SPI bus; ";
for (int zxc=0;zxc<argvalue.toInt();zxc+=2)
{
varX=(uint16_t *)(c +zxc);
while (SPI1CMD & SPIBUSY) {};
SPI.write16(*varX,true);
}
}
if (argname=="Senha")
{
if (argvalue=="12345")
{
strResponse+="** CORRECT PASSWORD ** \n";
bSenha=true;
} else strResponse+="** INCORRECT PASSWORD ** \n";
}
if (argname=="ProgramarTrials")
{
if (i==(numofargs-1)) strResponse+="SOME PARAMETERS ARE MISSING";
else if ((server.argName(i+1)=="strTrials")&&(bSenha))
{
i++;
ArduinoDueSkinner->ProgramaVariaveis(argvalue.toInt(),server.arg(i));
} else strResponse+="STRING PARAMETERS IS MISSING";
}
if ((argname=="START")&&bSenha)
{
ArduinoDueSkinner->StartTrials();
strResponse+="\nSTART COMMAND\n";
}
if ((argname=="ABORT")&&bSenha)
{
ArduinoDueSkinner->Abort();
strResponse+="\nABORT COMMAND\n";
}
} // end for structure (end sending parameters)
//Serial.println("end variables");
server.send ( 200, "application/x-www-form-urlencoded; charset=UTF-8",strResponse);
}
/*############################################################################################################
handleNotFound.
############################################################################################################*/
void handleNotFound()
{
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += ( server.method() == HTTP_GET ) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for ( uint8_t i = 0; i < server.args(); i++ )
{
message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n";
}
server.send ( 404, "text/plain", message );
}
// Global functions.
int parseStringToFloat(String str,char delimiter);
int parseStringToFloat(String str,char delimiter, float *floatArray);
// Timers and their interrupt service routines.
os_timer_t trial_timer, sound_timer, shock_timer;
void isr_trial(void *pArg);
void isr_sound(void *pArg);
void isr_shock(void *pArg);
/* Function parseStringToFloat, used in 2 different ways (2 implementations below)
*
*
*/
int parseStringToFloat(String str,char delimiter)
{
int floatsEscritos=0;
for (int k=0;k<str.length();k++) if (str[k]==delimiter) floatsEscritos++;
return (floatsEscritos+1);
}
int parseStringToFloat(String str,char delimiter, float *floatArray)
{
int floatsEscritos=0;
int lastPos=0;
for (int i=0;i<str.length();i++)
if (str[i]==delimiter)
{
floatArray[floatsEscritos]=str.substring(lastPos,i).toFloat();
lastPos=i+1;
floatsEscritos++;
}
floatArray[floatsEscritos]=str.substring(lastPos).toFloat();
return (floatsEscritos+1);
}
class ControleDueSkinner
{
public:
//Construtor
ControleDueSkinner();
//Class functions
void StartTrials();
void Abort();
bool ProgramaArduinoSPI(int iQualTrial);
bool ProgramaVariaveis(int QuantosTrials, String strVar);
bool bReady=0;
//Control GPIOs
const int pinABORT = 4; // Pin used to tell Arduino to abort experiment (Abort).
const int pinSOUND = 0; // Pin used to tell Arduino that the sound (modulator + carrier) must be played.
const int pinSOUNDnot = 5; // PinSOUNDnot = not(pinSOUND)
const int pinSHOCK = 2; // Pin used to tell Arduino to shock rat's paw.
const int pinRSTarduino = 16; // Pin used to reset Arduino.
// Variables related to experiment parameters. Data comes from WEBPAGE.
float BufferFloat[1800]; // a buffer to hold the values configured by user via webpage. Values are copied from strVar.
float *silence; // in seconds.
float current_trial_length; // in seconds
float *onset_shock; // in seconds.
float *onset_sound; // in seconds.
float *sound_duration; // in seconds.
float *shock_duration; // in seconds.
float *carrier_freq; // in Hertz.
float *modulator_freq; // in Hertz.
float *pulse_shockHigh; // in ms.
float *pulse_shockLow; // in ms.
float *volume_sound; // in percent (%).
int iCountTrials=0;
int iNumberOfTrials=0;
/*############################################################################################################
// Webpage content. -> Need to convert html to string and then past here below
############################################################################################################*/
String PaginaHTML =
"<!DOCTYPE html>\n\
<html>\n\
<body>\n\
\n\
<body style=\"background-color:powderblue;\">\n\
\n\
<center><h2>Conditioning Cage V1.0</h2></center>\n\
<!-- \n\
<button type=\"button\" onclick=\"loadDoc()\">Request data</button></br>\n\
-->\n\
\n\
Cage Control Password: <input id=\"senha\" type=\"password\" value=\"\" />\n\
<button type=\"button\" onclick=\"enviarESP()\">Send Parameters</button>\n\
<button type=\"button\" onclick=\"enviarESPSTART()\">START</button>\n\
<button type=\"button\" onclick=\"enviarESPABORT()\">ABORT</button></br>\n\
<!-- \n\
<button type=\"button\" onclick=\"testeESP()\">TESTANDO COISAS</button></br>\n\
-->\n\
<div id=\"entrada\">\n\
<p>TRIAL CONFIGURATION</p></br>\n\
Inter-trial silence or initial silence (seconds)<input id=\"Silence\" type=\"text\" value=\"\" /><br>\n\
Sound onset (seconds)<input id=\"SoundOnset\" type=\"text\" value=\"\" /> \n\
Sound duration (seconds)<input id=\"SoundDuration\" type=\"text\" value=\"\" /><br>\n\
Shock onset (seconds)<input id=\"ShockOnset\" type=\"text\" value=\"\" /> \n\
Shock duration (seconds)<input id=\"ShockDuration\" type=\"text\" value=\"\" /><br>\n\
</br>\n\
Carrier Frequency (Hz)<input id=\"CarrierFreq\" type=\"text\" value=\"\" /> \n\
Modulator Frequency (Hz)<input id=\"ModFreq\" type=\"text\" value=\"\" /><br>\n\
ShockPulse High (ms)<input id=\"ShockPulseHigh\" type=\"text\" value=\"\" /> \n\
ShockPulse Low (ms)<input id=\"ShockPulseLow\" type=\"text\" value=\"\" /><br>\n\
Sound Volume (%)<input id=\"SoundVolume\" type=\"text\" value=\"\" /><br>\n\
</br>\n\
<input id=\"SubmitTrial\" type=\"button\" onclick=\"adicionarTrial()\" value=\"Add Trial\" /><br>\n\
</div>\n\
<hr>\n\
<textarea id=\"expTexto\" rows=\"4\" cols=\"50\"></textarea>\n\
<button type=\"button\" onclick=\"programaExperimento()\">LOAD TRIAL(S)</button></br>\n\
<hr><br>\n\
<div id=\"saidaHTML\">\n\
</div>\n\
\n\
<p id=\"demo\"></p>\n\
\n\
<script>\n\
//Variáveis Globais\n\
xhr=new XMLHttpRequest();\n\
\n\
var Trials = [];\n\
var varSet = [];\n\
\n\
varSet[0]=\"Silence\";\n\
varSet[1]=\"SoundOnset\";\n\
varSet[2]=\"SoundDuration\";\n\
varSet[3]=\"ShockOnset\";\n\
varSet[4]=\"ShockDuration\";\n\
varSet[5]=\"CarrierFreq\";\n\
varSet[6]=\"ModFreq\";\n\
varSet[7]=\"ShockPulseHigh\";\n\
varSet[8]=\"ShockPulseLow\";\n\
varSet[9]=\"SoundVolume\";\n\
for (var i=0;i<varSet.length;i++) Trials[i]=[];\n\
\n\
function adicionarTrial()\n\
{\n\
var indice=0;\n\
if (typeof Trials[0] != 'undefined') indice=Trials[0].length;\n\
for (var i=0;i<varSet.length;i++) Trials[i][indice]=document.getElementById(varSet[i]).value;\n\
ShowRecordedTrials();\n\
};\n\
\n\
function ShowRecordedTrials()\n\
{\n\
var strTemp=\"<center><p> ALL RECORDED TRIALS </p></center>\";\n\
\n\
for (var i=0;i<Trials[0].length;i++)\n\
{\n\
strTemp+=\"<hr>\";\n\
strTemp+=\"<p>RECORDED TRIAL - \" + i + \" Start at (seg): \" + Trials[0][i] + \"s. \";\n\
strTemp+=\"<br> To delete this trial click ====> <button id=\\\"reg\" + i + \"\\\" type=\\\"button\\\" onclick=\\\"ClickDeleteReg(this)\\\">Delete</button></p>\";\n\
\n\
strTemp+=varSet[1] + \" = \" + Trials[1][i] + \" e \";\n\
strTemp+=varSet[2] + \" = \" + Trials[2][i];\n\
strTemp+=\"<br>\" + varSet[3] + \" = \" + Trials[3][i] + \" e \";\n\
strTemp+=varSet[4] + \" = \" + Trials[4][i];\n\
strTemp+=\"<br>\" + varSet[5] + \" = \" + Trials[5][i] + \" e \";\n\
strTemp+=varSet[6] + \" = \" + Trials[6][i];\n\
strTemp+=\"<br>\" + varSet[7] + \" = \" + Trials[7][i] + \" e \";\n\
strTemp+=varSet[8] + \" = \" + Trials[8][i];\n\
strTemp+=\"<br>\" + varSet[9] + \" = \" + Trials[9][i]; \n\
strTemp+=\"<hr>\";\n\
}\n\
//strTemp+=\"Sort = \" + Trials[0].sort(); ele faz o sort e fode tudo \n\
document.getElementById(\"saidaHTML\").innerHTML = strTemp;\n\
\n\
strTemp=\"\";\n\
for (var j=0;j<varSet.length;j++)\n\
{\n\
//strTemp+=\"|\";\n\
for (var i=0;i<((Trials[0].length)-1);i++)\n\
strTemp+=Trials[j][i] + \";\";\n\
strTemp+=Trials[j][i] + \"|\";\n\
}\n\
strTemp=strTemp.substring(0, strTemp.length-1); \n\
document.getElementById(\"expTexto\").value=strTemp;\n\
}\n\
\n\
function programaExperimento()\n\
{\n\
//Trials = [];\n\
\n\
var linha=document.getElementById(\"expTexto\").value;\n\
var linha2=linha.split(\"|\");\n\
var linha3;\n\
\n\
for (var i=0; i<linha2.length ;i++)\n\
{\n\
linha3=linha2[i].split(\";\");\n\
for (var j=0; j<linha3.length ;j++)\n\
Trials[i][j]=linha3[j];\n\
}\n\
\n\
ShowRecordedTrials();\n\
}\n\
\n\
\n\
function ClickDeleteReg(ele)\n\
{\n\
var iQual=ele.id.substring(3);\n\
\n\
if (confirm(\"Tem certeza? Voce vai deletar o TRIAL: \" + iQual))\n\
for (var i=0;i<varSet.length;i++) Trials[i].splice(iQual,1);\n\
\n\
\n\
ShowRecordedTrials();\n\
}\n\
\n\
function trataResposta()\n\
{\n\
if(xhr.readyState == 4 && xhr.status == 200) \n\
{\n\
alert(xhr.responseText);\n\
}\n\
};\n\
\n\
\n\
function enviarESP() \n\
{\n\
//MUITO CUIDADO, FALTA SORT E OUTRAS COISITAS MAS\n\
var strTemp=\"\";\n\
\n\
\n\
strTemp+=\"ProgramarTrials=\" + Trials[0].length + \"&\";\n\
strTemp+=\"strTrials=\";\n\
\n\
for (var j=0;j<varSet.length;j++)\n\
for (var i=0;i<Trials[0].length;i++)\n\
strTemp+=Trials[j][i] + \";\";\n\
\n\
strTemp=strTemp.substring(0,strTemp.length-1);\n\
\n\
var strTemp2=\"Senha=\" + document.getElementById(\"senha\").value + \"&\";\n\
\n\
xhr.open(\"POST\", \"http://192.168.4.1/Programar\", true);\n\
xhr.setRequestHeader(\"Content-Type\", \"application/x-www-form-urlencoded; charset=UTF-8\");\n\
xhr.onreadystatechange = trataResposta;\n\
xhr.send(strTemp2+strTemp);\n\
\n\
};\n\
\n\
function enviarESPSTART() \n\
{\n\
//MUITO CUIDADO, FALTA SORT E OUTRAS COISITAS MAS\n\
//var strTemp=\"EscrevaBytes=32\";\n\
var strTemp=\"Senha=\" + document.getElementById(\"senha\").value + \"&START=1\";\n\
xhr.open(\"POST\", \"http://192.168.4.1/Programar\", true);\n\
xhr.setRequestHeader(\"Content-Type\", \"application/x-www-form-urlencoded; charset=UTF-8\");\n\
xhr.onreadystatechange = trataResposta;\n\
xhr.send(strTemp);\n\
};\n\
\n\
function enviarESPABORT() \n\
{\n\
//MUITO CUIDADO, FALTA SORT E OUTRAS COISITAS MAS\n\
//var strTemp=\"EscrevaBytes=32\";\n\
var strTemp=\"Senha=\" + document.getElementById(\"senha\").value + \"&ABORT=1\";\n\
xhr.open(\"POST\", \"http://192.168.4.1/Programar\", true);\n\
xhr.setRequestHeader(\"Content-Type\", \"application/x-www-form-urlencoded; charset=UTF-8\");\n\
xhr.onreadystatechange = trataResposta;\n\
xhr.send(strTemp);\n\
};\n\
\n\
function testeESP() \n\
{\n\
//MUITO CUIDADO, FALTA SORT E OUTRAS COISITAS MAS\n\
//var strTemp=\"EscrevaBytes=32\";\n\
var strTemp=\"Comando4=1\";\n\
\n\
\n\
\n\
\n\
xhr.open(\"POST\", \"http://192.168.4.1/Programar\", true);\n\
xhr.setRequestHeader(\"Content-Type\", \"application/x-www-form-urlencoded; charset=UTF-8\");\n\
xhr.onreadystatechange = trataResposta;\n\
xhr.send(strTemp);\n\
\n\
};\n\
function loadDoc() \n\
{\n\
xhr.open(\"POST\", \"http://192.168.4.1/Programar\", true);\n\
xhr.setRequestHeader(\"Content-Type\", \"application/x-www-form-urlencoded; charset=UTF-8\");\n\
xhr.onreadystatechange = trataResposta;\n\
xhr.send(\"Comando4=Este foi o Primeiro&teste1=Depois este&teste2=LIGA ESTA PORRA\");\n\
\n\
//ProgramarTrials=5&strTrials=2.14.314;2342342342;2423423;234234;...\n\
//\n\
};\n\
\n\
</script>\n\
\n\
</body>\n\
</html>";
// End Webpage.
private:
protected:
};
extern ControleDueSkinner *ArduinoDueSkinner;
/*############################################################################################################
Funções da CLASSE
############################################################################################################*/
ControleDueSkinner::ControleDueSkinner()
{
os_timer_setfn(&trial_timer, isr_trial, NULL); // sets callback function for sound_timer.
os_timer_setfn(&sound_timer, isr_sound, NULL); // sets callback function for sound_timer.
os_timer_setfn(&shock_timer, isr_shock, NULL); // sets callback function for shock_timer.
}
/*############################################################################################################
SPI COMUNICATION FROM/TO MASTER: Our protocol.
0xA0A0 is the command word sent by ESP8266 that indicates SPI data should be processed.
0x0010 is the command word sent by ESP8266 that indicates control variables should be programmed.
(values for carrier_freq, modulator_freq, pulse_shockHigh and pulse_shockLow should be sent by ESP8266 after 0x0010 command)
0x10FF is the command word sent by Arduino indicating that the values for the control variables were all received.
0x1000 is the command word sent by Arduino indicating that the values for the control variables were not received.
0x1A00 is the command word sent by ESP8266 indicating to ABORT experiment.
############################################################################################################*/
bool ControleDueSkinner::ProgramaArduinoSPI(int iQualTrial)
{
uint16_t *uTemp;
SPI.write16(0x0010,true); // Command sent to Arduino indicating that parameters are goind to be sent through SPI.
uTemp=(uint16_t *)(carrier_freq+iQualTrial);
SPI.write16(*uTemp,true); SPI.write16(*(uTemp+1),true); // sending first float variable (every 2 bytes)
uTemp=(uint16_t *)(modulator_freq+iQualTrial);
SPI.write16(*uTemp,true); SPI.write16(*(uTemp+1),true); // sending second float variable (every 2 bytes)
uTemp=(uint16_t *)(pulse_shockHigh+iQualTrial);
SPI.write16(*uTemp,true); SPI.write16(*(uTemp+1),true); // sending third float variable (every 2 bytes)
uTemp=(uint16_t *)(pulse_shockLow+iQualTrial);
SPI.write16(*uTemp,true); SPI.write16(*(uTemp+1),true); // sending fourth float variable (every 2 bytes)
uTemp=(uint16_t *)(volume_sound+iQualTrial);
SPI.write16(*uTemp,true); SPI.write16(*(uTemp+1),true); // sending fifth float variable (every 2 bytes)
SPI.write16(0xA0A0,true); // Telling Arduino to process data.
return true;
}
void ControleDueSkinner::Abort()
{
// Disarm timers and put GPIOs in LOW state (except pinRSTarduino and pinSOUNDnot, which goes HIGH), indicating not to play sound and not to shock anymore.
os_timer_disarm(&trial_timer);
os_timer_disarm(&sound_timer);
os_timer_disarm(&shock_timer);
digitalWrite(pinSOUND, LOW);
digitalWrite(pinSOUNDnot, HIGH);
digitalWrite(pinSHOCK, LOW);
SPI.write16(0x001A,true); //Comando para programar parametros
SPI.write16(0xA0A0,true); //Pode processar
// Web page response.
// server.send ( 200, "text/html", temp );
}
void ControleDueSkinner::StartTrials()
{
//Vamos zerar o contador
if (bReady==0) return;
//Colocar os pinos de controle em LOW
digitalWrite ( pinSOUND, LOW );
digitalWrite ( pinSOUNDnot, HIGH );
digitalWrite ( pinSHOCK, LOW );
//Vamos desarmar os interrupts:
os_timer_disarm(&trial_timer);
os_timer_disarm(&sound_timer);
os_timer_disarm(&shock_timer);
void *nada;
iCountTrials=-1; //Vai acrescentar assim que entrar no interrupt de timer
if (silence[0]==0) isr_trial(nada); //Começar direto
else os_timer_arm(&(trial_timer), (uint32_t)(silence[0]*1000), 0); //Ligar o timer
}
bool ControleDueSkinner::ProgramaVariaveis(int QuantosTrials, String strVar)
{
//Formato strVar: iNumberOfTrials;todos os silence; todos os onset_sound ...
iNumberOfTrials=QuantosTrials;
parseStringToFloat(strVar,';',BufferFloat);
silence = BufferFloat;
onset_sound = BufferFloat+iNumberOfTrials*1;
sound_duration = BufferFloat+iNumberOfTrials*2;
onset_shock = BufferFloat+iNumberOfTrials*3;
shock_duration = BufferFloat+iNumberOfTrials*4;
carrier_freq = BufferFloat+iNumberOfTrials*5;
modulator_freq = BufferFloat+iNumberOfTrials*6;
pulse_shockHigh = BufferFloat+iNumberOfTrials*7;
pulse_shockLow = BufferFloat+iNumberOfTrials*8;
volume_sound = BufferFloat+iNumberOfTrials*9;
bReady=1;
return true;
}
/*############################################################################################################
Funções de Timmer Interrupt - FORA DA CLASSE
############################################################################################################*/
//extern void ControleDueSkinner::isr_trial(void *pArg)
void isr_trial(void *pArg)
{
ArduinoDueSkinner->iCountTrials++; //Vamos acrscentar o trial para o Próximo, este já deu início e foi programado.
//Colocar os pinos de controle em LOW
digitalWrite ( ArduinoDueSkinner->pinSOUND, LOW );
digitalWrite ( ArduinoDueSkinner->pinSOUNDnot, HIGH );
digitalWrite ( ArduinoDueSkinner->pinSHOCK, LOW );
//Vamos desarmar os interrupts:
os_timer_disarm(&(sound_timer));
os_timer_disarm(&(shock_timer));
ArduinoDueSkinner->ProgramaArduinoSPI(ArduinoDueSkinner->iCountTrials); //Programar o que tiver que ser programado
delay(10); //Esperar um pouco antes de começar
//Vamos programar os sons e choques deste trial
if (ArduinoDueSkinner->onset_shock[ArduinoDueSkinner->iCountTrials]==0)
{
digitalWrite ( ArduinoDueSkinner->pinSHOCK, HIGH );
os_timer_arm(&(shock_timer), (uint32_t)(ArduinoDueSkinner->shock_duration[ArduinoDueSkinner->iCountTrials]*1000), 0);
} else if (ArduinoDueSkinner->onset_shock[ArduinoDueSkinner->iCountTrials]>0) os_timer_arm(&(shock_timer), (uint32_t)(ArduinoDueSkinner->onset_shock[ArduinoDueSkinner->iCountTrials]*1000), 0);
if (ArduinoDueSkinner->onset_sound[ArduinoDueSkinner->iCountTrials]==0)
{
digitalWrite ( ArduinoDueSkinner->pinSOUND, HIGH );
digitalWrite ( ArduinoDueSkinner->pinSOUNDnot, LOW );
os_timer_arm(&(sound_timer), (uint32_t)(ArduinoDueSkinner->sound_duration[ArduinoDueSkinner->iCountTrials]*1000), 0);
} else if (ArduinoDueSkinner->onset_sound[ArduinoDueSkinner->iCountTrials]>0) os_timer_arm(&(sound_timer), (uint32_t)(ArduinoDueSkinner->onset_sound[ArduinoDueSkinner->iCountTrials]*1000), 0);
/* Let's program next trial (if there is one). Note that the trial_timer is programmed for the next trial
* just after starting the current trial (programming SOUND and SHOCK timers for the current timer).
* First, we have to calculate the current trial length in order to program the next trial.
*/
if ( (ArduinoDueSkinner->onset_sound[ArduinoDueSkinner->iCountTrials] + ArduinoDueSkinner->sound_duration[ArduinoDueSkinner->iCountTrials])
>=
(ArduinoDueSkinner->onset_shock[ArduinoDueSkinner->iCountTrials] + ArduinoDueSkinner->shock_duration[ArduinoDueSkinner->iCountTrials]) )
ArduinoDueSkinner->current_trial_length = ArduinoDueSkinner->onset_sound[ArduinoDueSkinner->iCountTrials] + ArduinoDueSkinner->sound_duration[ArduinoDueSkinner->iCountTrials];
else
ArduinoDueSkinner->current_trial_length = ArduinoDueSkinner->onset_shock[ArduinoDueSkinner->iCountTrials] + ArduinoDueSkinner->shock_duration[ArduinoDueSkinner->iCountTrials];
if ( ArduinoDueSkinner->iCountTrials<(ArduinoDueSkinner->iNumberOfTrials-1) ) // Checking if there are more trials to program.
os_timer_arm( &(trial_timer), (uint32_t)((ArduinoDueSkinner->silence[ArduinoDueSkinner->iCountTrials+1] + ArduinoDueSkinner->current_trial_length) * 1000), 0 ); //Ligar o timer
else os_timer_disarm(&(trial_timer)); //Na verdade não precisaria desta linha, mas, em todo caso ....
}
//extern void ControleDueSkinner::isr_sound(void *pArg)
void isr_sound(void *pArg)
{
if (digitalRead (ArduinoDueSkinner->pinSOUND))
{
digitalWrite ( ArduinoDueSkinner->pinSOUND, LOW );
digitalWrite ( ArduinoDueSkinner->pinSOUNDnot, HIGH );
}
else
{
digitalWrite ( ArduinoDueSkinner->pinSOUND, HIGH );
digitalWrite ( ArduinoDueSkinner->pinSOUNDnot, LOW );
os_timer_arm(&(sound_timer), (uint32_t)(ArduinoDueSkinner->sound_duration[ArduinoDueSkinner->iCountTrials]*1000), 0);
}
}
//extern void ControleDueSkinner::isr_shock(void *pArg)
void isr_shock(void *pArg)
{
if (digitalRead (ArduinoDueSkinner->pinSHOCK)) digitalWrite ( ArduinoDueSkinner->pinSHOCK, LOW );
else
{
digitalWrite ( ArduinoDueSkinner->pinSHOCK, HIGH );
os_timer_arm(&(shock_timer), (uint32_t)(ArduinoDueSkinner->shock_duration[ArduinoDueSkinner->iCountTrials]*1000), 0);
}
}
And the arduino code to receive messages from the esp8266 looks like this:
/*
* Núcleo de Neurociências - UFMG/Brazil (Universidade Federal de Minas Gerais)
*
* Custom Conditioning Chamber for Classical Fear Conditioning
*
* Sketch for Arduino Due (slave SPI). It receives parameters from ESP8266 (master SPI) and generates configurable sound and shock stimuli patterns.
* Timer4 and Timer5 are used to control sound and shock temporal patterns.
* Sound signal is produced by DAC1.
* Shock signals are produced in 8 digital pins (configurable via code).
*
* Authors: Paulo Aparecido Amaral Júnior - amaraljr.paulo@gmail.com
* Flávio Afonso Gonçalves Mourão - mourao.fg@gmail.com
* Márcio Flávio Dutra Moraes - mfdm@icb.ufmg.br
*
* 2019
*/
/*############################################################################################################
includes.
############################################################################################################*/
#include "Waveforms.h"
#include "DueTimer.h"
#include <SPI.h>
#include <avr/interrupt.h>
/*############################################################################################################
Defines.
############################################################################################################*/
static BitOrder bitOrder = MSBFIRST;
static int pinSS = 10; // Slave Select pin for Arduino.
static int pinABORT = 48; // Pin used to Abort experiment (Abort).
static int pinSOUND = 50; // Input pin by which ESP8266 controls SOUND.
static int pinSHOCK = 52; // Input pin by which ESP8266 controls SHOCK.
static int pinTrigger = 53; // Output pin used to generate a reference signal (a square wave) that represents sound modulator.
bool bpinSOUND = false; // variable used to determine if it's a rising or a falling edge on pinSOUND.
bool bpinSHOCK = false; // variable used to determine if it's a rising or a falling edge on pinSHOCK.
bool bBarStatus = false; // variable used to track the status of SHOCK (if shock is on or if it's off).
/* ATENTION!!
freqModulating must be less than (clock freqModulating)/maxSampleNumModulating
*/
/*############################################################################################################
Global variables.
############################################################################################################*/
// Variables that ESP8266 will send to ArduinoDue via SPI bus.
/* ATENTION!!
carrier_freq must be less than (clock carrier_freq)/maxSampleNumCarrier
REMEMBER! : maximum feasible frequency is given by the conversor limit: 125kHz
*/
float carrier_freq = 1000; // defauilt value for carrier frequency (in Hertz). This variable can be programmed by ESP8266 via webpage -> SPI.
float modulator_freq = 53.71; // default value for modulator frequency (in Hertz). This variable can be programmed by ESP8266 via webpage -> SPI.
float volume_sound = 100; // default value for volume sound (in percent). This variable can be programmed by ESP8266 via webpage -> SPI.
int iC = 0; // Carrier wave counter.
int iM = 0; // Modulating wave counter
uint32_t DACbuffer[maxSamplesNumCarrier * maxSamplesNumModulating]; // Template of the signal with modulator already calculated.
// SHOCK variables.
float pulse_shockHigh = 0; // default value for the time the bar is tied to GND (in ms). This variable can be programmed by ESP8266 via webpage -> SPI.
float pulse_shockLow = 0; // default value for the time that should be waited until reach next bar (in ms). This variable can be programmed by ESP8266 via webpage -> SPI.
float fDeltaT;
int ipulse_shockHigh; // in ms.
int ipulse_shockLow; // in ms.
int iDeltaT;
int iBarPin = 22;
static int iInitialBarPin = 23; // Initial pin for the first bar.
static int MaxNumBarPin = 8; // Number of bars.
static int iBarPinStep = 2; // Step for bar pins (so we can choose only odd or even pins, for example).
int iCountShockIntervals;
bool bSPIprocessdataOUT = false;
bool bSPIprocessdataIn = false;
int iSPIdataCountIN = 0;
int iSPIdataCountOUT = 0;
int iSPIdataMaxOUT = 0;
byte byteBufferDataIN[500];
byte byteBufferDataOUT[500];
uint32_t uData; // A variable where we copy the content of the SPI Receive Data Register when we have an SPI interrupt.
uint32_t *pointerData;
String strTEMP;
/*############################################################################################################
Function declarations.
############################################################################################################*/
void slaveBegin(uint8_t _pin);
void FUNCAO_INTERRUPT();
void ProcessIncomingData();
void bars(bool bStatusiBarPin);
void carrier();
void modulating();
void pinSOUND_rising();
void pinSOUND_falling();
void pinSHOCK_rising();
void pinSHOCK_falling();
void ProgramSound();
void ProgramShock();
void Abort();
/*############################################################################################################
Setup
############################################################################################################*/
void setup()
{
// Configure SPI communication.
slaveBegin(pinSS);
pinMode(pinABORT, INPUT);
pinMode(pinSOUND, INPUT);
pinMode(pinSHOCK, INPUT);
pinMode(pinTrigger, OUTPUT);
digitalWrite(pinTrigger, LOW);
// Programming pins that control bars potentials
for (int i = 0; i < MaxNumBarPin; i++)
{
pinMode((iInitialBarPin + i * iBarPinStep), OUTPUT);
digitalWrite((iInitialBarPin + i * iBarPinStep), LOW);
}
// Timer4 produces carrier signal and timer5 modulates carrier signal
Timer4.attachInterrupt(carrier);
Timer5.attachInterrupt(modulating);
analogWriteResolution(12);
analogWrite(DAC1, 0); // Configure DAC communication with analogWrite's built-in parameters
ProgramSound(); //Program template signal for SOUND.
}
/*############################################################################################################
Loop
############################################################################################################*/
void loop()
{
if (bpinSOUND != digitalRead(pinSOUND)) // check if pinSOUND has changed state.
{
if (bpinSOUND) pinSOUND_falling(); // check if it was a falling edge.
else pinSOUND_rising();
bpinSOUND = !bpinSOUND; // toggle bpinsSOUND, since pinSOUND changed state.
}
if (bpinSHOCK != digitalRead(pinSHOCK)) // check if pinSHOCK has changed state.
{
if (bpinSHOCK) pinSHOCK_falling(); // check if it was a falling edge.
else pinSHOCK_rising();
if (ipulse_shockHigh > 0) bpinSHOCK = !bpinSHOCK; // we check if pulse_shockHigh > 0 before writing TRUE/FALSE to bpinSHOCK. Else, bpinSHOCK will always be false.
}
if (bpinSHOCK) bars(bBarStatus); // if bpinSHOCK is TRUE, shock must be turned on (this is made in bars() function).
//SerialComandCheck();
if (bSPIprocessdataIn) ProcessIncomingData();
}
/*############################################################################################################
slaveBegin. Programm Arduino Due as a slave SPI.
############################################################################################################*/
void slaveBegin(uint8_t _pin)
{
SPI.begin(_pin);
REG_SPI0_CR = SPI_CR_SWRST; // reset SPI
REG_SPI0_CR = SPI_CR_SPIEN; // enable SPI
REG_SPI0_MR = SPI_MR_MODFDIS; // slave and no modefault
REG_SPI0_CSR = SPI_MODE0|(0x80); // DLYBCT=0, DLYBS=0, SCBR=0, 16 bit transfer
//REG_SPI0_IER=SPI_IER_RDRF; // Setting Interrupt Enable Flag Register to 1???
delay(1000);
// Configures interruption whenever master SPI sends a message.
int intNum = digitalPinToInterrupt(_pin); // _pin (SPI slave select) is used to generate interrupts.
if (intNum != NOT_AN_INTERRUPT)
{
SPI.usingInterrupt(intNum); // uses slave select pin to trigger an interrupt service routine (FUNCAO_INTERRUPT).
attachInterrupt(intNum, FUNCAO_INTERRUPT, RISING); // Slave select rising indicates that SPI transaction in done and data can be processed.
}
}
/*############################################################################################################
SPI Transmission/reception complete ISR
SPI COMUNICATION FROM/TO MASTER: Our protocol.
0xA0A0 is the command word sent by ESP8266 that indicates SPI data should be processed.
0x0010 is the command word sent by ESP8266 that indicates control variables should be programmed.
(values for carrier_freq, modulator_freq, pulse_shockHigh and pulse_shockLow should be sent by ESP8266 after 0x0010 command)
0x10FF is the command word sent by Arduino indicating that the values for the control variables were all received.
0x1000 is the command word sent by Arduino indicating that the values for the control variables were not received.
0x1A00 is the command word sent by ESP8266 indicating to ABORT experiment.
SPI Registers and Commands:
REG_SPI0_SR - SPI Status Register
REG_SPI0_RDR - SPI Receive Data Register
SPI_SR_RDRF - Receive Data Register Full
SPI_SR_TDRE - Transmit Data Register Empty
############################################################################################################*/
void FUNCAO_INTERRUPT()
{
while ((REG_SPI0_SR & SPI_SR_RDRF) != 0) // wait for SPI data is available
{
uData = REG_SPI0_RDR; // The registry is a uint32_t 32bit word
if (bitOrder == LSBFIRST) uData = __REV(__RBIT(uData));
byteBufferDataIN[iSPIdataCountIN]= (uData & 0xFF); // copy the first byte received to byteBufferDataIN.
byteBufferDataIN[iSPIdataCountIN+1]= (uData & 0xFF00)>>8; // copy the second byte received to byteBufferDataIN.
iSPIdataCountIN+=2;
if (uData==0xA0A0) bSPIprocessdataIn=true; // status of bSPIprocessdataIn variable is checked in main loop. Fim de comando.
if (((REG_SPI0_SR & SPI_SR_TDRE) != 0) && bSPIprocessdataOUT) // checking if we have to send data
{
pointerData= (uint32_t *) (byteBufferDataOUT +iSPIdataCountOUT);
REG_SPI0_TDR = *pointerData;
iSPIdataCountOUT+=2;
if (iSPIdataCountOUT>=iSPIdataMaxOUT) // If at the end, finished reading.
{
iSPIdataCountOUT=0;
bSPIprocessdataOUT=false;
iSPIdataMaxOUT=0;
}
}
}
}
void ProcessIncomingData()
{
//Serial.println(iSPIdataCountIN);
if ((byteBufferDataIN[0]==0x10)&&((byteBufferDataIN[1]==0x00))) // Command 0x0010
// This is the command that tells Arduino to program experiment control variables.
{
float *fTemp= (float *)(byteBufferDataIN+2);
carrier_freq=fTemp[0]; //+ 4bytes = 6
modulator_freq=fTemp[1]; //+ 4bytes = 10
pulse_shockHigh=fTemp[2]; //+ 4bytes = 14
pulse_shockLow=fTemp[3]; //+ 4bytes = 18
volume_sound=fTemp[4]; //+ 4bytes = 22
//+ 2bytes = 24. This is the process command: 0xA0A0;
if (iSPIdataCountIN==24) // Succeeded in SPI communication (received expected number of bytes). Don't forget 0xA0A0 in order to process SPI data.
{
byteBufferDataOUT[iSPIdataCountOUT]=0x10; // 0x10FF indicates success in receiving data.
byteBufferDataOUT[iSPIdataCountOUT+1]=0xFF; // 0x10FF indicates success in receiving data.
ProgramSound();
//ProgramShock();
//Serial.println(carrier_freq);
//Serial.println(modulator_freq);
//Serial.println(pulse_shockHigh);
//Serial.println(pulse_shockLow);
//Serial.println(volume_sound);
} else
{
byteBufferDataOUT[iSPIdataCountOUT]=0x10; // 0x1000 indicates fail in receiving data.
byteBufferDataOUT[iSPIdataCountOUT+1]=0x00; // 0x1000 indicates fail in receiving data.
}
// Anyway,
iSPIdataMaxOUT+=2;
bSPIprocessdataOUT=true;
//Serial.println(String("Mandando Sinal de Volta: ") + iSPIdataMaxOUT);
}
if ((byteBufferDataIN[0]==0x1A)&&((byteBufferDataIN[1]==0x00))) // ABORT Command 0x1A00.
{
Abort();
}
//Independent of what was processed, erase and restart.
iSPIdataCountIN=0;
bSPIprocessdataIn=false;
}
/*############################################################################################################
Functions for PIN control
############################################################################################################*/
void pinSOUND_rising() // pinSOUND rises when ESP8266 indicates Arduino Due should start producing SOUND.
{
iC = 0; iM = 0;
}
void pinSOUND_falling() // pinSOUND falls when ESP8266 indicates Arduino Due should stop producing SOUND.
{
analogWrite(DAC1, 0);
iC = 0; iM = 0;
PIO_SetOutput( g_APinDescription[pinTrigger].pPort, g_APinDescription[pinTrigger].ulPin, LOW, 0, PIO_DEFAULT );
}
void pinSHOCK_rising() // pinSHOCK rises when ESP8266 indicates Arduino Due should start producing shock patterns (control signals).
{
iBarPin = iInitialBarPin;
iCountShockIntervals = 0;
}
void pinSHOCK_falling() // pinSHOCK rises when ESP8266 indicates Arduino Due should stop producing shock patterns (control signals).
{
PIO_SetOutput(g_APinDescription[iBarPin].pPort, g_APinDescription[iBarPin].ulPin, LOW, 0, PIO_DEFAULT) ;
iBarPin = iInitialBarPin;
bBarStatus = false;
iCountShockIntervals = 0;
}
/*############################################################################################################
Function: bars
############################################################################################################*/
void bars(bool bStatusiBarPin) //When the interrupt is called those sequence of commands are executed
{
// This function will execute when the processor is idle ... within the loop ... the priority FOR TIMING is with the wave generation.
// If Count is between zero and ipulse_shockHigh, SHOCK is suposed to be on.
// If Count is between ipulse_shockHigh and ipulse_shockLow, SHOCK is suposed to be off.
// If Count is greater than ipulse_shockHigh + ipulse_shockLow, should SHOCK next bar.
if (bStatusiBarPin) // SHOCK is on => let's see if we have to turn it off.
{
if (iCountShockIntervals >= ipulse_shockHigh)
{
PIO_SetOutput( g_APinDescription[iBarPin].pPort, g_APinDescription[iBarPin].ulPin, LOW, 0, PIO_DEFAULT );
bBarStatus = false;
}
} else // SHOCK is off => let's see if we have to turn it on.
{
if ((iCountShockIntervals >= 0) && (iCountShockIntervals<ipulse_shockHigh))
{
PIO_SetOutput( g_APinDescription[iBarPin].pPort, g_APinDescription[iBarPin].ulPin, HIGH, 0, PIO_DEFAULT );
bBarStatus = true;
}
// SHOCK is off => let's see if we have to change to next bar.
if (iCountShockIntervals >= (ipulse_shockHigh + ipulse_shockLow))
{
iCountShockIntervals = 0;
iBarPin += iBarPinStep;
// If we were in the last bar, return to the first one.
if (iBarPin == (iInitialBarPin + MaxNumBarPin * iBarPinStep)) iBarPin = iInitialBarPin;
}
}
}
/*############################################################################################################
Function: carrier
############################################################################################################*/
void carrier()
{
// note that the DACbuffer was already calculated in ProgramSound(). Here, we only check which value we have to write on DAC.
if (bpinSOUND) dacc_write_conversion_data(DACC_INTERFACE, DACbuffer[iC + iM * maxSamplesNumCarrier]); // writes data directly on the pin (analogWrite is too slow); with analogWrite() the maximum frequency achieved was 28kHz, whereas that implementation can reach up to 125kHz
iC++;
// check if carrier completed one period.
if (iC == maxSamplesNumCarrier) iC = 0;
//
if (bpinSHOCK) iCountShockIntervals += iDeltaT; // We are using the same clock to count shock and sound intervals.
//IFs must be only bools or INTs ... working with floats delays things
}
/*############################################################################################################
Function: modulating
############################################################################################################*/
void modulating()
{
// The next 2 if's below are used to generate a trigger sinal that will be used as a reference for the modulation phase.
if ((iM == 0) && (bpinSOUND)) PIO_SetOutput( g_APinDescription[pinTrigger].pPort, g_APinDescription[pinTrigger].ulPin, LOW, 0, PIO_DEFAULT );
if ((iM == (maxSamplesNumModulating/2)) && (bpinSOUND)) PIO_SetOutput( g_APinDescription[pinTrigger].pPort, g_APinDescription[pinTrigger].ulPin, HIGH, 0, PIO_DEFAULT );
iM++;
if (iM == maxSamplesNumModulating) iM = 0;// Modulator completes one period
}
/*############################################################################################################
Function: ProgramSound
This is where we program a sound buffer that will be played during experiment.
Timer4 is used to control the carrier wave and the shock.
Timer5 is used to control trigger wave.
############################################################################################################*/
void ProgramSound()
{
//Stop Everything
Timer4.stop();
Timer5.stop();
bpinSOUND = false;
// First program sound buffer with new parameters.
for (int i = 0; i < maxSamplesNumModulating; i++)
for (int j = 0; j < maxSamplesNumCarrier; j++)
// check if sound is modulated of if it's a pure tune.
if (modulator_freq > 0) DACbuffer[j + (i * maxSamplesNumCarrier)] = ((1 + waveformsTableCarrier[j] * waveformsTableModulating[i]) * 4095 * volume_sound/100) / 2;
else DACbuffer[j + (i * maxSamplesNumCarrier)] = ((1 + waveformsTableCarrier[j]) * 4095 * volume_sound/100) / 2;
iC = 0;
iM = 0;
fDeltaT = (1 / (carrier_freq * maxSamplesNumCarrier)) * 1000; // Pacote de cada Delta T do interrupt de SOM ... Em millisegundos.
float fTemp;
fTemp = fDeltaT * 1000; // In multiples of 10 microsseconds;
iDeltaT = floor(fTemp);
iCountShockIntervals = 0;
fTemp = pulse_shockHigh * 1000; // In multiples of 10 microsseconds;
ipulse_shockHigh = floor(fTemp);
fTemp = pulse_shockLow * 1000; // In multiples of 10 microsseconds;
ipulse_shockLow = floor(fTemp);
// The line below can be a problem if carrier_freq is zero. Shock will have no clock to control its temporal patterns.
Timer4.setFrequency(carrier_freq * maxSamplesNumCarrier);
if (modulator_freq > 0)
{
Timer5.setFrequency(modulator_freq * maxSamplesNumModulating);
Timer5.start();
}
Timer4.start();
}
/*############################################################################################################
Function: Abort
############################################################################################################*/
void Abort()
{
// Disable all timers.
}
Any thoughts will be helpful, thank you!