Closed davidevertuani closed 6 years ago
I have the same problem too.
Hardware: NodeMCU V1.0 (but I chose "Generic ESP8266 Module in Arduino IDE, v1.8.5) Core: 00000000 SDK: 2.2.1(cfd48f3) CPU Freq: 160
I tested with core 2.3.0 and it works!
Hardware: NodeMCU V1.0 (but I chose "Generic ESP8266 Module in Arduino IDE, v1.8.5) Core: 2_3_0 SDK: 1.5.3(aec24ac9) CPU Freq: 160
A have the same problem too. After "tone" on one pin and then "analogWrite" on another pin, "tone" doesn't work anymore, when I use core version 2.4.1 for NodeMCU 1.0 (ESP-12 Module). When I use core version 2.3.0 the behavior is different, but also not usable: after "tone" on one pin "analogWrite" works correct at another pin, but after "noTone" "analogWrite" doesn't work anymore. I don't know, how to flash the "Generic ESP8266 Module" for my NodeMCU. All four flash modes caused an upload error.
Personally in my current project I prefer the tone()
, so I have removed all analogWrite
lines in my code.
I will wait for the solid solution then I will insert those lines again.
I don't know, how to flash the "Generic ESP8266 Module" for my NodeMCU.
I am on fairly recent Git core, and assuming you are using Arduino IDE like me, this is how I flash my NodeMCU V1.0.
I also have a NodeMCU V3 Lolin, not sure if it is a clone or genuine. I can flash it with the above settings but it won't run (won't work). But simply change the flash mode from DIO to QIO, then it works.
Thank you, may be I had forgotten the Reset Method: "nodemcu". But the behavior is the same as with Nodecmu v1 core version 2.3. After using tone and then analogWrite and tone once more, analogWrite doesn't work anymore. Best would be, if I would write an own tone and analogWrite function, which work both together. But if I therefore would use timer1 probably the serial connection wouldn't work anymore.
I wrote an own tone and noTone function with names itone and noItone. itone works with frequencies beginning with 1 Hz. But with core 2.4.1 itone has the same behavior as tone after using analogWrite. analogWrite is very aggressive. If called only once timer1 can't be used nomore for other purposes. analogWrite always grabs timer1 back. This means, if I need timer1, and I need it for precise measurements, then analogWrite mustn't be used.
These are my itone and noItone functions:
/*
ESP8266 example for tone via timer1
Hardware: NodeMCU
2018
tone using Timer
*/
#define ITONE_CYCLE 2500000 // 1 second cycle in 1/16 CPU clock (1/5 µs) for 80 MHz
// #define ITONE_CYCLE 5000000 // 1 second cycle in 1/16 CPU clock (1/5 µs) for 160 MHz
//=======================================================================
// itone, noItone
//=======================================================================
byte _itonepin = D0;
byte _itoneval = HIGH;
void ICACHE_RAM_ATTR _onItoneTimerISR(){
_itoneval ^= 1;
digitalWrite(_itonepin,_itoneval); //Toggle LED Pin
}
void itone(byte pin,unsigned long frequency) {
timer1_detachInterrupt();
timer1_attachInterrupt(_onItoneTimerISR);
timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP);
_itoneval = HIGH;
_itonepin = pin;
pinMode(pin,OUTPUT);
digitalWrite(pin,HIGH);
timer1_write(ITONE_CYCLE/frequency);
}
void noItone() {
timer1_detachInterrupt();
}
//=======================================================================
// Setup
// we test the serial connection during itone running
// result: it still works
//=======================================================================
void setup()
{
Serial.begin(115200);
itone(D0,1); // LED blinks with 1 Hz
}
//=======================================================================
// MAIN LOOP
//=======================================================================
void loop()
{
}
In the same kind also an analogWrite function could be written, which is compatible with a modified itone function.
Oh, the serial connecton still works, when timer1 is used for itone.
hi sir my linux arduino ide using to program nodemcu but port always not enable u have any solution for this problem sir
On Mar 22, 2018 6:22 PM, "AlfonsMittelmeyer" notifications@github.com wrote:
I wrote an own tone and noTone function with names itone and noItone. itone works with frequencies beginning with 1 Hz. But with core 2.4.1 itone has the same behavior as tone after using analogWrite. analogWrite is very aggressive. If called only once timer1 can't be used nomore for other purposes. analogWrite always grabs timer1 back. This means, if I need timer1, and I need it for precise measurements, then analogWrite mustn't be used.
These are my itone and noItone functions:
/ ESP8266 example for tone via timer1 Hardware: NodeMCU 2018 tone using Timer /
define ITONE_CYCLE 2500000 // 1 second in 1/16 CPU clock for 80 MHz
// #define ITONE_CYCLE 5000000 // 1 second in 1/16 CPU clock for 160 MHz
//======================================================================= // itone, noItone //======================================================================= byte _itonepin = D0; byte _itoneval = HIGH;
void ICACHE_RAM_ATTR _onItoneTimerISR(){ _itoneval ^= 1; digitalWrite(_itonepin,_itoneval); //Toggle LED Pin } void itone(byte pin,unsigned long frequency) { timer1_detachInterrupt(); timer1_attachInterrupt(_onItoneTimerISR); timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); _itoneval = HIGH; _itonepin = pin; pinMode(pin,OUTPUT); digitalWrite(pin,HIGH); timer1_write(ITONE_CYCLE/frequency); }
void noItone() { timer1_detachInterrupt(); }
//======================================================================= // Setup // we test the serial connection during itone running // result: it still works //======================================================================= void setup() { Serial.begin(115200); itone(D0,1); // LED blinks with 1 Hz } //======================================================================= // MAIN LOOP //======================================================================= void loop() { }
In the same kind also an analogWrite function could be written, which is compatible with a modified itone function.
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/esp8266/Arduino/issues/4349#issuecomment-375292756, or mute the thread https://github.com/notifications/unsubscribe-auth/Ajj29SVBP_1HaHI3hjtvH2gNU3vG69ABks5tg55zgaJpZM4SBV0J .
I use the arduino ide on my raspberry pi3 and port /dev/ttyUSB0 works without any problems. You should try this command without NodeMCU connected and after with NodeMCU connected:
ls /dev/tty*
then you may compare, whether the serial connection was detected and with which name
ls /dev/tty* am try this one in terminal but output is lot more to come like this
On Mar 23, 2018 12:26 AM, "AlfonsMittelmeyer" notifications@github.com wrote:
I use the arduino ide on my raspberry pi3 and port /dev/ttyUSB0 works without any problems. You should try this command without NodeMCU connected and after with NodeMCU connected:
ls /dev/tty*
then you may compare, whether the serial connection was detected and with which name
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/esp8266/Arduino/issues/4349#issuecomment-375420074, or mute the thread https://github.com/notifications/unsubscribe-auth/Ajj29bruoe04s6rgsQQikY4Cxb_lrh-6ks5tg_PcgaJpZM4SBV0J .
yes, you see a lot beginneng with /dev/tty and you should try this with and without the NodeCMU connected and then you can compare, whether there is one more with connected NodeCMU.
ok sir you are in whatsapp or Skype because its useful for my further doubts am more interested in using microcontroller like arduino ,nodemcu and raspberry pi
On Mar 23, 2018 3:13 PM, "AlfonsMittelmeyer" notifications@github.com wrote:
yes, you see a lot beginneng with /dev/tty and you should try this with and without the NodeCMU connected and then you can compare, whether there is one more with connected NodeCMU.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/esp8266/Arduino/issues/4349#issuecomment-375596522, or mute the thread https://github.com/notifications/unsubscribe-auth/Ajj29b5Aeuz-OvUT_lPjt0nZAl8yjOZoks5thMOxgaJpZM4SBV0J .
was there one one /dev/tty* ? The raspberry po3 is my favored desktop py and I don't like to use its GPIO ports. What if I would destroy my raspberry? But if something would happen to the NodeCMU, that's not any problem.
Please @AlfonsMittelmeyer @santhosh000 stop, this is an esp8266 issue not a Raspberry support forum.
I don't want to talk about the raspberry specially, because it's only my favored linux pc. And talked about connecting the NodeCMU with a linux pc.
great
On Mar 23, 2018 3:38 PM, "AlfonsMittelmeyer" notifications@github.com wrote:
I don't want to talk about the raspberry specially, because it's only my favored linux pc. And talked about connecting the NodeCMU with a linux pc.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/esp8266/Arduino/issues/4349#issuecomment-375603147, or mute the thread https://github.com/notifications/unsubscribe-auth/Ajj29XewF9KkMFt46ZQAkcwwAcPtCUlUks5thMmpgaJpZM4SBV0J .
@santhosh000: could your linux pc detect an additional device /dev/tty* ?
no sir what I'll do
On Mar 23, 2018 4:17 PM, "AlfonsMittelmeyer" notifications@github.com wrote:
@santhosh000 https://github.com/santhosh000: could your linux pc detect an additional device /dev/tty* ?
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/esp8266/Arduino/issues/4349#issuecomment-375620440, or mute the thread https://github.com/notifications/unsubscribe-auth/Ajj29R_IMlr0wipS0Vd5oM3bbq5vgNIUks5thNK-gaJpZM4SBV0J .
@santhosh000: what you should do is a detailed error description including pc hardware and os. For example Dell PC OPTIPLEX 760 Linux Ubuntu 16.04 LTS. I tried it with this computer and got /dev/ttyUSB0 when using ls /dev/tty*. The bug could be a defect NodeCMU, a defect USB cable or a PC or a linux, which can't recognize the connection. So try another USB cable, check whether you inserted it correct into the USB connector, try other linux systems and windows systems and other PCS. When nothing works, then it seems to be a defect NodeMCU.
see my port always disable state I'll do all ideas
On Mar 23, 2018 5:11 PM, "AlfonsMittelmeyer" notifications@github.com wrote:
@santhosh000 https://github.com/santhosh000: what you should do is a detailed error description including pc hardware and os. For example Dell PC OPTIPLEX 760 Linux Ubuntu 16.04 LTS. I tried it with this computer and got /dev/ttyUSB0 when using ls /dev/tty*. The bug could be a defect NodeCMU, a defect USB cable or a PC or a linux, which can't recognize the connection. So try another USB cable, check whether you inserted it correct into the USB connector, try other linux systems and window systems and other PCS. When nothing works, then it seems to be a defect NodeMCU.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/esp8266/Arduino/issues/4349#issuecomment-375633109, or mute the thread https://github.com/notifications/unsubscribe-auth/Ajj29dkkTuruq15lBabA3avyQEHTjADEks5thN95gaJpZM4SBV0J .
@santhosh000 : aren't you able to tell about your linux pc? The NodeMCU isn't recognized by all linux systems, especially elderly versions. So I have an old Android TV stick MK808 B, which I had flashed with an old linux picuntu. It doesn't recognize the NodeMCU.
Maybe you have to install an USB to UART Bridge driver:
https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers
read also:
https://cityos-air.readme.io/docs/1-usb-drivers-for-nodemcu-v10
@santhosh000 @AlfonsMittelmeyer This is not a support forum. This is an issue tracker for issues in the core libs. In addition, this specific issue is about tone and pwm incompatibility, and has nothing to do with usb or tty or rpi. Please discuss at a general forum like esp8266.com or stackoverflow, and stop hijacking.
latest version debian running now on my linux pc
On Mar 23, 2018 5:54 PM, "AlfonsMittelmeyer" notifications@github.com wrote:
@santhosh000 https://github.com/santhosh000 : aren't you able to tell about your linux pc? The NodeMCU isn't recognized by all linux systems, especially elderly versions. So I have an old Android TV stick MK808 B, which I had flashed with an old linux picuntu. It doesn't recognize the NodeMCU.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/esp8266/Arduino/issues/4349#issuecomment-375649045, or mute the thread https://github.com/notifications/unsubscribe-auth/Ajj29XAtHwwmOFGXFtSzGJdGCnGnWCxfks5thOmUgaJpZM4SBV0J .
@santhosh000: we should end this discussion here about your NodeMCU, because this thread is about incompatibility of PWM and tone. I don't want to install debian for further research. Because raspbian is derived from debian, I would think, that also latest versions of debian should recognize a NodeCMU. So most probably your NodeMCU is defect. You could check this by installing Ubuntu 16.04 LTS, because this linux version worked. And if not, then send back your NodeCMU and exchange it with one, which works.
Back to the problem incompatibility PMW and tone. It's clear, that there can't be a solution if PWM and tone is handled in different libraries. Tone has to be integrated in PWM, if we would like to use both.
I just began to deal with NodeMCU and ESP8266 and wonder, where I can find a good documentation. About timer1 I found only this via google: https://circuits4you.com/2018/01/02/esp8266-timer-ticker-example/
and the header file on github: https://github.com/esp8266/Arduino/blob/master/cores/esp8266/Arduino.h
Then I wanted to know about an accurate time, not in microseconss but in CPU clocks. Via google I could detect an assemler code for this: http://sub.nanona.fi/esp8266/timing-and-ticks.html
So I had enough information for writing an own PWM function, in which I could integrate a tone function in the next step. But still there was a problem. I couldn't find any information for which values timer1_write works. Seemingly for values below 28 an overflow takes place, which results in a pause of nearly one second.
Sorry that I am a new bee in connection with the NodeMCU. Is there somebody who knows, where I could find useful documentation?
I think, it's not great problem to use ananogWrite and tone.
Just write an own analogWrite function, where you may integrate an own tone function.
This is such an analogWrite function. It differs from the original analogWrite function in two ways:
1.) digitalWrite will not stop this analogWrite. It has to be stopped by analogWrite(pin,0) for LOW or analogWrite(pin,1024) for HIGH, if we don't want to write an own digitalWrite function too, which considers this itself.
2.) it doesn't grab timer1 back, if we detach this timer for other purposes
What do you think about this analogWrite:
/*
ESP8266 example for own analogWrite via timer1 and CCOUNT
Hardware: NodeMCU
2018
*/
#define CPU_CLOCK 80 // Mhz
//=======================================================================
// ianalogWrite
//=======================================================================
#define analogWrite ianalogWrite
#define PWM_CYCLE 1024
#define AWRITE_PINCOUNT 21
#define AWRITE_START_PIN 19
#define AWRITE_STOP_PIN 20
#define TIMER1_MIN_STEP 30
enum {
PWM_CYCLE_TICKS = PWM_CYCLE * CPU_CLOCK,
};
struct {
uint32_t value;
byte nextpin;
} awriteData[AWRITE_PINCOUNT];
static inline uint32_t asm_ccount(void) {
uint32_t r;
asm volatile ("rsr %0, ccount" : "=r"(r));
return r;
}
bool _iawritestate = true;
void ICACHE_RAM_ATTR _onIawriteTimerISR(){
static uint32_t startcount;
static byte current_pin = AWRITE_START_PIN;
static uint32_t current_count;
static uint32_t next_time;
while(true) {
if(_iawritestate) {
startcount = asm_ccount();
current_pin = AWRITE_START_PIN;
for(int ipin = AWRITE_START_PIN; awriteData[ipin].nextpin != AWRITE_STOP_PIN;) {
ipin = awriteData[ipin].nextpin;
digitalWrite(ipin,HIGH);
}
_iawritestate = false;
}
else {
digitalWrite(current_pin,LOW);
}
uint32_t nextduty = awriteData[awriteData[current_pin].nextpin].value;
if(nextduty == PWM_CYCLE_TICKS) {
_iawritestate = true;
}
else {
current_pin = awriteData[current_pin].nextpin;
}
current_count = asm_ccount();
next_time = startcount + nextduty - current_count;
if(!(next_time & 0x80000000 || next_time < TIMER1_MIN_STEP)) {
timer1_write(next_time);
return;
}
}
}
void init_ianalogWrite() {
awriteData[AWRITE_START_PIN].value = 0;
awriteData[AWRITE_START_PIN].nextpin = AWRITE_STOP_PIN;
awriteData[AWRITE_STOP_PIN].nextpin = AWRITE_STOP_PIN;
awriteData[AWRITE_STOP_PIN].value = PWM_CYCLE_TICKS;
_iawritestate = true;
timer1_detachInterrupt();
timer1_attachInterrupt(_onIawriteTimerISR);
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
timer1_write(TIMER1_MIN_STEP);
}
void ianalogWrite(byte pin,unsigned int value) {
int ipin = AWRITE_START_PIN;
byte nextpin;
pinMode(pin,OUTPUT);
// if the pin is aleady in PWM, then unregister
while(awriteData[ipin].nextpin != pin && awriteData[ipin].nextpin != AWRITE_STOP_PIN)
ipin = awriteData[ipin].nextpin;
if(awriteData[ipin].nextpin == pin) {
noInterrupts();
awriteData[ipin].nextpin = awriteData[pin].nextpin;
interrupts();
}
// if value not 0 and not 1024, then register the pin
if(value & 0x3FF /* 1023 */ ) {
ipin = AWRITE_START_PIN;
nextpin = awriteData[ipin].nextpin;
value *= CPU_CLOCK;
while(value > awriteData[nextpin].value) {
ipin = nextpin;
nextpin = awriteData[nextpin].nextpin;
}
noInterrupts();
awriteData[pin].value = value;
awriteData[pin].nextpin = nextpin;
awriteData[ipin].nextpin = pin;
interrupts();
}
else {
digitalWrite(pin,value ? HIGH : LOW);
}
}
//=======================================================================
// Setup
//=======================================================================
int startval = 0;
void setup()
{
Serial.begin(115200);
while(!Serial);
init_ianalogWrite();
}
//=======================================================================
// MAIN LOOP
//=======================================================================
void loop()
{
analogWrite(D2,1024-startval);
analogWrite(D1,startval);
digitalWrite(D1,LOW); // to test, whether it's the own analogWrite
delay(20);
startval = (startval + 8) & 0x3FF;
}
Oh, I forgot to tell, that I used a two color LED connected with D1 and D2. Normal LEDs also would be fine.
I recognized a small bug in my algorithm when testing with this code:
void loop()
{
for(float i = 0.5; i < 1024; i *= 1.03 ) {
analogWrite(D2,i);
delay(10);
}
for(float i = 1024; i >= 0.5; i /= 1.03 ) {
analogWrite(D2,i);
delay(10);
}
}
I saw a short flicker, when 1024 was reached. It's clear, that changes shouldn't be made during a cycle. Changes should be prepared which take effect exactly at the beginning of the next cycle. So I should change my algorithm.
Now this algorithm seems to work perfect. Does anybody like to integrate tone?
/*
ESP8266 example for own analogWrite via timer1 and CCOUNT
Hardware: NodeMCU
2018
*/
#define CPU_CLOCK 80 // Mhz
#define analogWrite ianalogWrite
//=======================================================================
// CCOUNT
//=======================================================================
static inline uint32_t asm_ccount(void) {
uint32_t r;
asm volatile ("rsr %0, ccount" : "=r"(r));
return r;
}
//=======================================================================
// ianalogWrite
//=======================================================================
#define TIMER1_MIN_STEP 30
#define AWRITELEN 11
#define PWM_CYCLE 1024
enum {
PWM_CYCLE_TICKS = PWM_CYCLE * CPU_CLOCK,
};
typedef struct {
uint32_t value;
byte pin;
} awriteElement;
typedef struct {
awriteElement data[AWRITELEN];
int len = 0;
} awriteData;
awriteData awriteDataX[2];
int awriteDataIndex = 0;
bool _iawritestate = true;
volatile bool awriteDataChanged = false;
void ICACHE_RAM_ATTR _onIawriteTimerISR(){
static int datalen;
static awriteElement * data;
static int index;
static uint32_t startcount;
static uint32_t current_count;
static uint32_t next_duty;
static int32_t next_time;
while(true) {
if(_iawritestate) {
_iawritestate = false;
if(awriteDataChanged) {
awriteDataChanged = false;
datalen = awriteDataX[awriteDataIndex].len;
data = awriteDataX[awriteDataIndex].data;
}
startcount = asm_ccount();
for(int i = 0; i < datalen; i++) {
digitalWrite(data[i].pin,HIGH);
}
index = 0;
}
else {
digitalWrite(data[index++].pin,LOW);
}
if(index == datalen) {
_iawritestate = true;
next_duty = PWM_CYCLE_TICKS;
}
else {
next_duty = data[index].value;
}
current_count = asm_ccount();
next_time = startcount + next_duty - current_count;
if(next_time >= TIMER1_MIN_STEP) {
timer1_write(next_time);
return;
}
}
}
void init_ianalogWrite() {
awriteDataIndex = 0;
awriteDataX[0].len = 0;
awriteDataX[1].len = 0;
_iawritestate = true;
awriteDataChanged = true;
timer1_detachInterrupt();
timer1_attachInterrupt(_onIawriteTimerISR);
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
timer1_write(TIMER1_MIN_STEP);
while(awriteDataChanged);
}
void ianalogWrite(byte pin,unsigned int value) {
pinMode(pin,OUTPUT);
int len0 = awriteDataX[awriteDataIndex].len;
awriteElement * data0 = awriteDataX[awriteDataIndex].data;
if(!(value & 0x3FF))
{
if(len0) {
bool contains = false;
for(int i = 0; i < len0; ++i) {
if(data0[i].pin == pin) {
contains = true;
break;
}
}
if(contains) {
awriteDataIndex ^= 1;
awriteElement * data1 = awriteDataX[awriteDataIndex].data;
for(int i = 0; i < len0; ++i) {
if(data0[i].pin != pin)
*data1++ = data0[i];
}
awriteDataX[awriteDataIndex].len = --len0;
awriteDataChanged = true;
while(awriteDataChanged);
}
}
digitalWrite(pin,value ? HIGH : LOW);
}
else {
awriteDataIndex ^= 1;
awriteElement * data1 = awriteDataX[awriteDataIndex].data;
int len1 = 0;
int i = 0;
value *= CPU_CLOCK;
for(;i < len0; ++i) {
if(data0[i].pin != pin) {
if(data0[i].value < value)
data1[len1++] = data0[i];
else
break;
}
}
data1[len1].pin=pin;
data1[len1++].value=value;
for(;i < len0; ++i) {
if(data0[i].pin != pin)
data1[len1++] = data0[i];
}
awriteDataX[awriteDataIndex].len = len1;
awriteDataChanged = true;
while(awriteDataChanged);
}
}
//=======================================================================
// Setup
//=======================================================================
void setup()
{
Serial.begin(115200);
while(!Serial);
init_ianalogWrite();
}
//=======================================================================
// MAIN LOOP
//=======================================================================
void loop()
{
for(float i = 1; i < 1024; i *= 1.05 ) {
analogWrite(D2,i);
delay(10);
}
for(float i = 1024; i >= 1; i /= 1.05 ) {
analogWrite(D2,i);
delay(10);
}
}
@minida28 : hi I did it. PWM at pin D2 (take a LED) and sound at pin D3 (oh susanna). Here is the solution:
/*
ESP8266 example for own analogWrite and own tone via timer1 and CCOUNT
PWM at D2, tone at D3
Hardware: NodeMCU
2018 by Alfons Mittelmeyer
*/
#include <Ticker.h> //Ticker Library
#define CPU_CLOCK 80 // Mhz
#define analogWrite ianalogWrite
#define tone itone
#define noTone noItone
//=======================================================================
// oh susanna
//=======================================================================
char * keys = "cdefgah ";
int frequencies[] = {262,294,330,349,392,440,494,0};
char * ohsusanna = "cdeggagecdeedcd cdeggagecdeeddc ffaa aggecd cdeggagecdeeddc ";
Ticker playticker;
void pause_susannah(int index) {
noTone(D3);
playticker.once_ms(100,play_ohsusanna,index);
}
void play_ohsusanna(int index) {
char tchar = ohsusanna[index];
int frequency = 0;
for(int i = 0;;i++) {
if(keys[i] == tchar) {
frequency = frequencies[i];
break;
}
}
if(frequency)
tone(D3,frequency);
if(!ohsusanna[++index])
index = 0;
playticker.once_ms(400,pause_susannah,index);
}
//=======================================================================
// CCOUNT
//=======================================================================
static inline uint32_t asm_ccount(void) {
uint32_t r;
asm volatile ("rsr %0, ccount" : "=r"(r));
return r;
}
//=======================================================================
// itone
//=======================================================================
struct {
bool on = false;
byte pin;
byte value;
uint32_t half_periode;
uint32_t next_ccount;
} _itone;
void itone(byte pin, unsigned int frequency) {
uint32_t half_periode = (CPU_CLOCK * 500000) / frequency;
noInterrupts();
_itone.pin = pin;
_itone.value = HIGH;
_itone.half_periode = half_periode;
_itone.next_ccount = asm_ccount() + CPU_CLOCK;
_itone.on = true;
interrupts();
}
void noItone(byte pin) {
noInterrupts();
_itone.on = false;
interrupts();
}
//=======================================================================
// ianalogWrite
//=======================================================================
#define TIMER1_MIN_STEP 30
#define AWRITELEN 11
#define PWM_CYCLE 1024
enum {
PWM_CYCLE_TICKS = PWM_CYCLE * CPU_CLOCK,
};
typedef struct {
uint32_t value;
byte pin;
} awriteElement;
typedef struct {
awriteElement data[AWRITELEN];
int len = 0;
} awriteData;
awriteData awriteDataX[2];
int awriteDataIndex = 0;
bool _iawritestate = true;
volatile bool awriteDataChanged = false;
void ICACHE_RAM_ATTR _onIawriteTimerISR(){
static int datalen;
static awriteElement * data;
static int index;
static uint32_t startcount;
static uint32_t current_count;
static uint32_t next_duty;
static int32_t next_time;
static int32_t itone_time;
static bool isTone = false;
while(true) {
if(_iawritestate) {
_iawritestate = false;
if(awriteDataChanged) {
awriteDataChanged = false;
datalen = awriteDataX[awriteDataIndex].len;
data = awriteDataX[awriteDataIndex].data;
}
startcount = asm_ccount();
for(int i = 0; i < datalen; i++) {
digitalWrite(data[i].pin,HIGH);
}
index = 0;
}
else {
if(isTone) {
isTone = false;
digitalWrite(_itone.pin,_itone.value);
_itone.next_ccount += _itone.half_periode;
_itone.value ^= 1;
}
else
digitalWrite(data[index++].pin,LOW);
}
if(index == datalen) {
_iawritestate = true;
next_duty = PWM_CYCLE_TICKS;
}
else {
next_duty = data[index].value;
}
current_count = asm_ccount();
next_time = startcount + next_duty - current_count;
if(_itone.on) {
itone_time = _itone.next_ccount - current_count;
if(itone_time < next_time) {
_iawritestate = false;
next_time = itone_time;
isTone = true;
}
}
if(next_time >= TIMER1_MIN_STEP) {
timer1_write(next_time);
return;
}
}
}
void init_ianalogWrite() {
awriteDataIndex = 0;
awriteDataX[0].len = 0;
awriteDataX[1].len = 0;
_iawritestate = true;
awriteDataChanged = true;
timer1_detachInterrupt();
timer1_attachInterrupt(_onIawriteTimerISR);
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
timer1_write(TIMER1_MIN_STEP);
while(awriteDataChanged);
}
void ianalogWrite(byte pin,unsigned int value) {
pinMode(pin,OUTPUT);
int len0 = awriteDataX[awriteDataIndex].len;
awriteElement * data0 = awriteDataX[awriteDataIndex].data;
if(!(value & 0x3FF))
{
if(len0) {
bool contains = false;
for(int i = 0; i < len0; ++i) {
if(data0[i].pin == pin) {
contains = true;
break;
}
}
if(contains) {
awriteDataIndex ^= 1;
awriteElement * data1 = awriteDataX[awriteDataIndex].data;
for(int i = 0; i < len0; ++i) {
if(data0[i].pin != pin)
*data1++ = data0[i];
}
awriteDataX[awriteDataIndex].len = --len0;
awriteDataChanged = true;
while(awriteDataChanged);
}
}
digitalWrite(pin,value ? HIGH : LOW);
}
else {
awriteDataIndex ^= 1;
awriteElement * data1 = awriteDataX[awriteDataIndex].data;
int len1 = 0;
int i = 0;
value *= CPU_CLOCK;
for(;i < len0; ++i) {
if(data0[i].pin != pin) {
if(data0[i].value < value)
data1[len1++] = data0[i];
else
break;
}
}
data1[len1].pin=pin;
data1[len1++].value=value;
for(;i < len0; ++i) {
if(data0[i].pin != pin)
data1[len1++] = data0[i];
}
awriteDataX[awriteDataIndex].len = len1;
awriteDataChanged = true;
while(awriteDataChanged);
}
}
//=======================================================================
// Setup
//=======================================================================
void setup()
{
Serial.begin(115200);
while(!Serial);
init_ianalogWrite();
pinMode(D3,OUTPUT);
play_ohsusanna(0);
}
//=======================================================================
// MAIN LOOP
//=======================================================================
void loop()
{
for(float i = 1; i < 1024; i *= 1.05 ) {
analogWrite(D2,i);
delay(10);
}
for(float i = 1024; i >= 1; i /= 1.05 ) {
analogWrite(D2,i);
delay(10);
}
}
Of course, also duo-tones, triads and chords would be possible.
Oh, I forgot the duration, which I also should implement. And about music for several voices. It's clear that this cannot be done by only one pin for tone, because we have only HIGH or LOW. But If we would use several tone channels, the channels at different pins and if we would connect these pins by resistors, then this should work. Wouldn't you think so?
Oh PWM and tone work very fine. Is there no further interest? I tested PWM with music for several voices. It works very well. Of cource, square waves are not the nicest sound.
Here an excerpt of my program:
//=======================================================================
// Oh Susanna
//=======================================================================
// melody
float voice1[] = {
d4,0.125,E4,0.125,SOUND_CLOCK,Fis4,0.25,A4,0.25,A4,0.375,H4,0.125,SOUND_CLOCK,A4,0.25,Fis4,0.25,d4,0.375,E4,0.125,SOUND_CLOCK,Fis4,0.25,Fis4,0.25,E4,0.25,d4,0.25,SOUND_CLOCK,E4,0.75,
d4,0.125,E4,0.125,SOUND_CLOCK,Fis4,0.25,A4,0.25,A4,0.375,H4,0.125,SOUND_CLOCK,A4,0.25,Fis4,0.25,d4,0.375,E4,0.125,SOUND_CLOCK,Fis4,0.25,Fis4,0.25,E4,0.25,E4,0.25,SOUND_CLOCK,d4,1.0,SOUND_CLOCK,
G4,0.5,G4,0.5,SOUND_CLOCK,H4,0.25,H4,0.5,H4,0.25,SOUND_CLOCK,A4,0.25,A4,0.25,Fis4,0.25,d4,0.25,SOUND_CLOCK,E4,0.75,
d4,0.125,E4,0.125,SOUND_CLOCK,Fis4,0.25,A4,0.25,A4,0.375,H4,0.125,SOUND_CLOCK,A4,0.25,Fis4,0.25,d4,0.375,E4,0.125,SOUND_CLOCK,Fis4,0.25,Fis4,0.25,E4,0.25,E4,0.25,SOUND_CLOCK,d4,1.0,SOUND_CLOCK,
SOUND_END
};
// accompaniment 1
float voice2[] = {
PAUSE,0.25,SOUND_CLOCK,d3,0.25,PAUSE,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,d3,0.25,PAUSE,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,d3,0.25,PAUSE,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,Cis3,0.25,A3,0.25,A3,0.25,
PAUSE,0.25,SOUND_CLOCK,d3,0.25,PAUSE,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,d3,0.25,PAUSE,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,d3,0.25,A3,0.25,Cis3,0.25,A3,0.25,SOUND_CLOCK,d3,0.25,A3,0.25,A3,0.25,
PAUSE,0.25,SOUND_CLOCK,H3,0.5,H3,0.5,SOUND_CLOCK,d4,0.25,d4,0.5,d4,0.25,SOUND_CLOCK,d4,1.0,SOUND_CLOCK,PAUSE,0.25,A3,0.25,A3,0.25,
PAUSE,0.25,SOUND_CLOCK,d3,0.25,PAUSE,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,d3,0.25,PAUSE,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,d3,0.25,A3,0.25,Cis3,0.25,A3,0.25,SOUND_CLOCK,d3,0.25,A3,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,
SOUND_END
};
// accompaniment 2
float voice3[] = {
PAUSE,0.25,SOUND_CLOCK,PAUSE,0.5,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.5,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.5,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.25,G3,0.25,G3,0.25,
PAUSE,0.25,SOUND_CLOCK,PAUSE,0.5,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.5,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.25,Fis3,0.25,PAUSE,0.25,G3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.25,Fis3,0.25,Fis3,0.25,
PAUSE,0.25,SOUND_CLOCK,G3,0.5,G3,0.5,SOUND_CLOCK,G3,0.25,G3,0.5,G3,0.25,SOUND_CLOCK,Fis3,1.0,SOUND_CLOCK,PAUSE,0.25,G3,0.25,G3,0.25,
PAUSE,0.25,SOUND_CLOCK,PAUSE,0.5,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.5,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.25,Fis3,0.25,PAUSE,0.25,G3,0.25,SOUND_CLOCK,PAUSE,0.25,Fis3,0.25,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,
SOUND_END
};
//=======================================================================
// Setup
//=======================================================================
void setup()
{
Serial.begin(115200);
while(!Serial);
pinMode(D5,INPUT_PULLUP); // starting with unconnected head phones, because the NodeCMU doesn't boot with too much pins connected with ground (inspite connected resistors)
while(digitalRead(D5)) // waiting for connection of headphones
delay(10);
delay(2000); // then 2 seconds pause
init_ianalogWrite(); // initalizing the PWM and tone extensions
tonechannels(D5,D6,D7); // registering pins D5, D6 and D7 as tone channels 0, 1 and 2
sound_voice(0,voice1); // registerung melody for tone channel 0
sound_voice(1,voice2); // registerung melody for tone channel 1
sound_voice(2,voice3); // registerung melody for tone channel 2
sound_start(); // start playing music at pins D5, D6 and D7
}
//=======================================================================
// MAIN LOOP
//=======================================================================
// testing PWM with music playing
void loop()
{
for(float i = 1; i < 1024; i *= 1.05 ) {
analogWrite(D2,i);
delay(10);
}
for(float i = 1024; i >= 1; i /= 1.05 ) {
analogWrite(D2,i);
delay(10);
}
}
@AlfonsMittelmeyer I have interest, not because I want to use it, but because I want to improve the core implementation. Your example code is essentially a custom implementation. The question for you is: can you come up with a replacement of the current code in the core libs, without changing the exposed arduino api?
I could come up with a replacement of the current core libs if I would invest maybe a lot of time. The question is also, would this be only for arduino or arwe behind arduino deeper layers involved like the ESP8266 Non-OS SDK of Espressif. There is a Hardware timer API and there are PMW-related APIs. Normally I would think that these two APIS must be integrated. If there would be only an integration at Arduino core level and somebody would use such deeper layers, then the problem exists further, that these layers don't work together. But it wouldn't be a problem to offer such APIS at arduino level. I think, that these Hardware timer API has also a poor accuracy. When I would integrate a hardware timer API in my PMW ISR then a hardwaretimer could have a response time of only two micro seconds for CPU with 80 MHz or 1 micro second for CPU with 160 MHz.
The problem is, that I don't know yet, which dependencies exist in the Arduino core libraries, because I just began to explore about the ESP8266, Arduino and the NodeCMU. When there are different APIS and libraries for tone, hardware timers and PWM, then these APIS would have to be integrated and new mixed, so that all works together. I doubt, that some replacements in some core libs, as they exist yet, would be sufficient.
Later I can say more about this, when I have got a better overview.
But now first I present the new version of the code. With PWM at pin D2 and sound at pins D5, D6 and D7. I connected these pins over 1 kOhm resistors with a headphone. The NodeMCU didn't boot, when the Headphone was connected. So i booted first and then connected the headphone. Now the music is Oh Susanna correct with melody and accompaniment.
/*
ESP8266 example for own analogWrite and own tone via timer1 and CCOUNT
PWM at D2, tone at D3
Hardware: NodeMCU
2018 by Alfons Mittelmeyer
*/
#include <Ticker.h> //Ticker Library
#define CPU_CLOCK 80 // Mhz
#define analogWrite ianalogWrite
#define tone itone
#define noTone noItone
#define TONEMAXCHANNELS 4
//=======================================================================
// CCOUNT
//=======================================================================
static inline uint32_t asm_ccount(void) {
uint32_t r;
asm volatile ("rsr %0, ccount" : "=r"(r));
return r;
}
//=======================================================================
// tonechannels
//=======================================================================
Ticker _itoneTickers[TONEMAXCHANNELS];
int _itonescount = 0;
struct _itone {
bool on = false;
bool start = true;
byte pin;
byte value;
uint32_t half_periode;
uint32_t next_ccount;
};
struct _itone _itones[TONEMAXCHANNELS];
void _itoneChannelDisable(int channel) {
noInterrupts();
_itones[channel].on = false;
interrupts();
}
void tonechannels() {
_itonescount = 0;
}
void tonechannels(byte pin) {
pinMode(pin,OUTPUT);
_itones[0].on = false;
_itones[0].pin = pin;
_itonescount = 1;
}
void tonechannels(byte pin1, byte pin2) {
pinMode(pin2,OUTPUT);
tonechannels(pin1);
_itones[1].on = false;
_itones[1].pin = pin2;
_itonescount = 2;
}
void tonechannels(byte pin1, byte pin2, byte pin3) {
pinMode(pin3,OUTPUT);
tonechannels(pin1,pin2);
_itones[2].on = false;
_itones[2].pin = pin3;
_itonescount = 3;
}
void tonechannels(byte pin1,byte pin2,byte pin3,byte pin4) {
pinMode(pin4,OUTPUT);
tonechannels(pin1,pin2,pin3);
_itones[3].on = false;
_itones[3].pin = pin4;
_itonescount = 4;
}
void channelTone(byte id, float frequency, unsigned int duration) {
uint32_t half_periode = (CPU_CLOCK * 500000) / frequency + 0.5;
_itoneTickers[id].detach();
noInterrupts();
_itones[id].start = true;
_itones[id].value = HIGH;
_itones[id].half_periode = half_periode;
_itones[id].next_ccount = asm_ccount() + CPU_CLOCK;
_itones[id].on = true;
interrupts();
if(duration)
_itoneTickers[id].once_ms(duration,_itoneChannelDisable,(int) id);
}
void channelTone(byte id, float frequency) {
channelTone(id,frequency,0);
}
void channelNoTone(byte id) {
noInterrupts();
_itones[id].on = false;
interrupts();
_itoneTickers[id].detach();
}
//=======================================================================
// itone
//=======================================================================
void _itoneDisable(int channel) {
noInterrupts();
_itones[channel].on = false;
_itonescount = 0 ? _itonescount < 2 : _itonescount;
interrupts();
}
void itone(byte pin, unsigned int frequency,unsigned int duration) {
pinMode(pin,OUTPUT);
uint32_t half_periode = (CPU_CLOCK * 500000) / frequency;
_itoneTickers[0].detach();
noInterrupts();
_itones[0].pin = pin;
_itones[0].start = true;
_itones[0].value = HIGH;
_itones[0].half_periode = half_periode;
_itones[0].next_ccount = asm_ccount() + CPU_CLOCK;
_itones[0].on = true;
_itonescount = _itonescount ? _itonescount : 1;
interrupts();
if(duration)
_itoneTickers[0].once_ms(duration,_itoneDisable,0);
}
void itone(byte pin, unsigned int frequency) {
itone(pin,frequency,0);
}
void noItone(byte pin) {
noInterrupts();
_itones[0].on = false;
_itonescount = 0 ? _itonescount < 2 : _itonescount;
interrupts();
_itoneTickers[0].detach();
}
//=======================================================================
// ianalogWrite
//=======================================================================
#define TIMER1_MIN_STEP 30
#define AWRITELEN 11
#define PWM_CYCLE 1024
enum {
PWM_CYCLE_TICKS = PWM_CYCLE * CPU_CLOCK,
};
typedef struct {
uint32_t value;
byte pin;
} awriteElement;
typedef struct {
awriteElement data[AWRITELEN];
int len = 0;
} awriteData;
awriteData awriteDataX[2];
int awriteDataIndex = 0;
bool _iawritestate = true;
volatile bool awriteDataChanged = false;
void ICACHE_RAM_ATTR _onIawriteTimerISR(){
static int datalen;
static awriteElement * data;
static int index;
static uint32_t startcount;
static uint32_t current_count;
static uint32_t next_duty;
static int32_t next_time;
static int32_t itone_time;
static bool isTone = false;
static int tonechannel = 0;
int itime;
while(true) {
if(_iawritestate) {
_iawritestate = false;
if(awriteDataChanged) {
awriteDataChanged = false;
datalen = awriteDataX[awriteDataIndex].len;
data = awriteDataX[awriteDataIndex].data;
}
startcount = asm_ccount();
for(int i = 0; i < datalen; i++) {
digitalWrite(data[i].pin,HIGH);
}
index = 0;
}
else {
if(isTone) {
isTone = false;
digitalWrite(_itones[tonechannel].pin,_itones[tonechannel].value);
if(_itones[tonechannel].start) {
_itones[tonechannel].start = false;
_itones[tonechannel].next_ccount = asm_ccount();
}
_itones[tonechannel].next_ccount += _itones[tonechannel].half_periode;
_itones[tonechannel].value ^= 1;
}
else
digitalWrite(data[index++].pin,LOW);
}
if(index == datalen) {
_iawritestate = true;
next_duty = PWM_CYCLE_TICKS;
}
else {
next_duty = data[index].value;
}
current_count = asm_ccount();
next_time = startcount + next_duty - current_count;
itone_time = 0x7FFFFFFF;
if(_itonescount) {
for(int i = 0; i < _itonescount; ++i) {
if(_itones[i].on) {
itime = _itones[i].next_ccount - current_count;
if(itime <= itone_time) {
itone_time = itime;
tonechannel = i;
}
}
}
if(itone_time < next_time) {
_iawritestate = false;
next_time = itone_time;
isTone = true;
}
}
next_time -= asm_ccount() - current_count;
if(next_time >= TIMER1_MIN_STEP) {
timer1_write(next_time);
return;
}
}
}
void init_ianalogWrite() {
awriteDataIndex = 0;
awriteDataX[0].len = 0;
awriteDataX[1].len = 0;
_iawritestate = true;
awriteDataChanged = true;
timer1_detachInterrupt();
timer1_attachInterrupt(_onIawriteTimerISR);
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
timer1_write(TIMER1_MIN_STEP);
while(awriteDataChanged);
}
void ianalogWrite(byte pin,unsigned int value) {
pinMode(pin,OUTPUT);
int len0 = awriteDataX[awriteDataIndex].len;
awriteElement * data0 = awriteDataX[awriteDataIndex].data;
if(!(value & 0x3FF))
{
if(len0) {
bool contains = false;
for(int i = 0; i < len0; ++i) {
if(data0[i].pin == pin) {
contains = true;
break;
}
}
if(contains) {
awriteDataIndex ^= 1;
awriteElement * data1 = awriteDataX[awriteDataIndex].data;
for(int i = 0; i < len0; ++i) {
if(data0[i].pin != pin)
*data1++ = data0[i];
}
awriteDataX[awriteDataIndex].len = --len0;
awriteDataChanged = true;
while(awriteDataChanged);
}
}
digitalWrite(pin,value ? HIGH : LOW);
}
else {
awriteDataIndex ^= 1;
awriteElement * data1 = awriteDataX[awriteDataIndex].data;
int len1 = 0;
int i = 0;
value *= CPU_CLOCK;
for(;i < len0; ++i) {
if(data0[i].pin != pin) {
if(data0[i].value < value)
data1[len1++] = data0[i];
else
break;
}
}
data1[len1].pin=pin;
data1[len1++].value=value;
for(;i < len0; ++i) {
if(data0[i].pin != pin)
data1[len1++] = data0[i];
}
awriteDataX[awriteDataIndex].len = len1;
awriteDataChanged = true;
while(awriteDataChanged);
}
}
//=======================================================================
// Sound Machine
//=======================================================================
#define SOUND_CLOCK_CYCLE 2000
#define C3 130.813
#define Cis3 138.591
#define d3 146.832
#define Dis3 155.563
#define E3 164.814
#define F3 174.614
#define Fis3 184.997
#define G3 195.998
#define Gis3 207.652
#define A3 220.0
#define B3 233.082
#define H3 246.942
#define C4 261.626
#define Cis4 277.183
#define d4 293.665
#define Dis4 311.127
#define E4 329.628
#define F4 349.228
#define Fis4 369.994
#define G4 391.995
#define Gis4 415.305
#define A4 440.0
#define B4 466.164
#define H4 493.883
#define C5 523.251
#define Cis5 554.365
#define d5 587.330
#define Dis5 622.254
#define E5 659.255
#define PAUSE 0
#define SOUND_CLOCK -1
#define SOUND_END -2
float * sound_voices[TONEMAXCHANNELS];
Ticker sound_tickers[TONEMAXCHANNELS];
int sound_indices[TONEMAXCHANNELS];
int sound_voicecount = 0;
void play_voice(int channel) {
float note;
float nextnote;
unsigned int duration;
bool isClock = false;
float * data = sound_voices[channel];
note = data[sound_indices[channel]++];
if(note == SOUND_CLOCK) {
note = data[sound_indices[channel]++];
if(!channel)
isClock = true;
}
if(note == SOUND_END) {
sound_tickers[0].once_ms(2000,sound_start);
}
else {
duration = data[sound_indices[channel]++] * SOUND_CLOCK_CYCLE;
nextnote = data[sound_indices[channel]];
if(!channel || nextnote >= 0)
sound_tickers[channel].once_ms(duration,play_voice,channel);
if(note > 0)
channelTone(channel,note,duration-20);
if(isClock) {
for(int i = 1; i < sound_voicecount; ++i)
play_voice(i);
}
}
}
void sound_start() {
for(int i = 0; i < sound_voicecount; ++i) {
sound_indices[i] = 0;
play_voice(i);
}
}
void sound_voice(int id, float * voicedata) {
sound_voicecount = max(sound_voicecount,id+1);
sound_voices[id] = voicedata;
}
//=======================================================================
// Oh Susanna
//=======================================================================
// melody
float voice1[] = {
d4,0.125,E4,0.125,SOUND_CLOCK,Fis4,0.25,A4,0.25,A4,0.375,H4,0.125,SOUND_CLOCK,A4,0.25,Fis4,0.25,d4,0.375,E4,0.125,SOUND_CLOCK,Fis4,0.25,Fis4,0.25,E4,0.25,d4,0.25,SOUND_CLOCK,E4,0.75,
d4,0.125,E4,0.125,SOUND_CLOCK,Fis4,0.25,A4,0.25,A4,0.375,H4,0.125,SOUND_CLOCK,A4,0.25,Fis4,0.25,d4,0.375,E4,0.125,SOUND_CLOCK,Fis4,0.25,Fis4,0.25,E4,0.25,E4,0.25,SOUND_CLOCK,d4,1.0,SOUND_CLOCK,
G4,0.5,G4,0.5,SOUND_CLOCK,H4,0.25,H4,0.5,H4,0.25,SOUND_CLOCK,A4,0.25,A4,0.25,Fis4,0.25,d4,0.25,SOUND_CLOCK,E4,0.75,
d4,0.125,E4,0.125,SOUND_CLOCK,Fis4,0.25,A4,0.25,A4,0.375,H4,0.125,SOUND_CLOCK,A4,0.25,Fis4,0.25,d4,0.375,E4,0.125,SOUND_CLOCK,Fis4,0.25,Fis4,0.25,E4,0.25,E4,0.25,SOUND_CLOCK,d4,1.0,SOUND_CLOCK,
SOUND_END
};
// accompaniment 1
float voice2[] = {
PAUSE,0.25,SOUND_CLOCK,d3,0.25,PAUSE,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,d3,0.25,PAUSE,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,d3,0.25,PAUSE,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,Cis3,0.25,A3,0.25,A3,0.25,
PAUSE,0.25,SOUND_CLOCK,d3,0.25,PAUSE,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,d3,0.25,PAUSE,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,d3,0.25,A3,0.25,Cis3,0.25,A3,0.25,SOUND_CLOCK,d3,0.25,A3,0.25,A3,0.25,
PAUSE,0.25,SOUND_CLOCK,H3,0.5,H3,0.5,SOUND_CLOCK,d4,0.25,d4,0.5,d4,0.25,SOUND_CLOCK,d4,1.0,SOUND_CLOCK,PAUSE,0.25,A3,0.25,A3,0.25,
PAUSE,0.25,SOUND_CLOCK,d3,0.25,PAUSE,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,d3,0.25,PAUSE,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,d3,0.25,A3,0.25,Cis3,0.25,A3,0.25,SOUND_CLOCK,d3,0.25,A3,0.25,A3,0.25,PAUSE,0.25,SOUND_CLOCK,
SOUND_END
};
// accompaniment 2
float voice3[] = {
PAUSE,0.25,SOUND_CLOCK,PAUSE,0.5,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.5,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.5,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.25,G3,0.25,G3,0.25,
PAUSE,0.25,SOUND_CLOCK,PAUSE,0.5,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.5,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.25,Fis3,0.25,PAUSE,0.25,G3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.25,Fis3,0.25,Fis3,0.25,
PAUSE,0.25,SOUND_CLOCK,G3,0.5,G3,0.5,SOUND_CLOCK,G3,0.25,G3,0.5,G3,0.25,SOUND_CLOCK,Fis3,1.0,SOUND_CLOCK,PAUSE,0.25,G3,0.25,G3,0.25,
PAUSE,0.25,SOUND_CLOCK,PAUSE,0.5,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.5,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,PAUSE,0.25,Fis3,0.25,PAUSE,0.25,G3,0.25,SOUND_CLOCK,PAUSE,0.25,Fis3,0.25,Fis3,0.25,PAUSE,0.25,SOUND_CLOCK,
SOUND_END
};
//=======================================================================
// Setup
//=======================================================================++++++++++++++++++++++++++++++++++++++++--
void setup()
{
Serial.begin(115200);
while(!Serial);
pinMode(D5,INPUT_PULLUP); // starting with unconnected head phones, because the NodeCMU doesn't boot with too much pins connected with ground (inspite connected resistors)
while(digitalRead(D5)) // waiting for connection of headphones
delay(10);
delay(2000); // then 2 seconds pause
init_ianalogWrite(); // initalizing the PWM and tone extensions
tonechannels(D5,D6,D7); // registering pins D5, D6 and D7 as tone channels 0, 1 and 2
sound_voice(0,voice1); // registerung melody for tone channel 0
sound_voice(1,voice2); // registerung melody for tone channel 1
sound_voice(2,voice3); // registerung melody for tone channel 2
sound_start(); // start playing music at pins D5, D6 and D7
}
//=======================================================================
// MAIN LOOP
//=======================================================================
// testing PWM with music playing
void loop()
{
for(float i = 1; i < 1024; i *= 1.05 ) {
analogWrite(D2,i);
delay(10);
}
for(float i = 1024; i >= 1; i /= 1.05 ) {
analogWrite(D2,i);
delay(10);
}
}
@minida28 : I think, it's simpler to do, than I thought yesterday. We need not think of all other posibilities, like fast read ADC now. We could just integrate tone and PWM now and later more functionality. Could you tell me, where I can find these core libraries?
Tone should be easy to integrate, because tone is very simple. Not so simple for me was to write an own PWM function without having seen the original in one of the core libraries.
The other question is a question about security. What for is PWM used? Is it used for steering model aircrafts or other very critical devices. If there is an orchestra playing, maybe the PWM pulse could come one ot two micro seconds late and then a very expensive device is destroyed. Could be this a case, which has to be considered?
If there would be a requirement for absolute exactness of PWM, nothing should be integrated into the current PWM. We could only offer a second PWM without such strict requirements.
Hi @AlfonsMittelmeyer
Great work there, I am sorry I have not have a chance to study and test your code yet.
@minida28 : I think, it's simpler to do, than I thought yesterday. We need not think of all other posibilities, like fast read ADC now. We could just integrate tone and PWM now and later more functionality. Could you tell me, where I can find these core libraries?
I have a very limited knowledge about the core libraries and programming, I am afraid I will point you to a wrong direction. Maybe you mean to mention @devyte ?
What for is PWM used? Is it used for steering model aircrafts
Could be, I believe I have read somewhere someone did that using PWM. But personally in my current project (just a hobby project, a digital clock using 16x128 Led matrix), I want to use PWM to control its brightness. It will also have alarm, so I need tone() to drive the buzzer.
@Juppit : thank you. I thought about implementing the same behaviour. But my idea, that the structure of the core libraries would have to be changed, was wrong. If tone and PWM would use another shared source file, this would be sufficient. I saw now also, that seemingly a case like mine was foreseen in tone.cpp. Here you see a currently not supported case 0. So we could implement this case 0.
// set up the interrupt frequency
switch (tone_timers[_index]) {
case 0:
// Not currently supported
break;
case 1:
timer1_disable();
timer1_isr_init();
timer1_attachInterrupt(t1IntHandler);
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP);
timer1_write((clockCyclesPerMicrosecond() * 500000) / frequency);
break;
}
}
}
void disableTimer(uint8_t _index) {
tone_pins[_index] = 255;
switch (tone_timers[_index]) {
case 0:
// Not currently supported
break;
case 1:
timer1_disable();
break;
}
}
I got the idea now, for making PWM and tone compatible. We can do this with no change for PWM and only a very small change for tone. PWM uses timer1. We needn't change this. Tone also uses timer1. We change this by using timer2 instead. How can we do this? For timer1 we don't use the hardware timer1, but a virtual timer1 und for timer2 we do the same. More hardware timers than one dont bring any advantage, because we can't use additional time from a parallel universe. Because there is only one time dimension, which we can use, one hardware timer is fully sufficient.
The question is, whether the arduini pwm only uses timer1 or also functions of the pmw API of espressif or functions of ETS. The answer is, it does so:
void pwm_start_timer()
{
timer1_disable();
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
ETS_FRC_TIMER1_NMI_INTR_ATTACH(pwm_timer_isr);
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
timer1_write(1);
}
This means, changing timer.c isn't a solution. The interrupt service routine of timer.c and of pwm must be the same, if tone and pwm shall work both.
What would this mean? The timer1 functions would have to be integrated into pwm instead being an own library. I don't know, whether others would like this idea.
Faced with the same problem. Trying to make an adaptive lcd backlight brightness with making some sound using analogWrite() and tone() functions. The analogWrite() function controls brightness well, until the tone() function is used. Then analogWrite() resets to 0 and doesn't work anymore. tone() continue working as usual. I hope that respected AlfonsMittelmeyer will find simple and beautiful solution. Thanks pal for the good job!
@X-Stas-EEE : then you use core version 2.3.0. Core version 2.4 kills the tone instead and also the normal timer1 interrupt.
I would like to know, why on earth a timer1 NMI interrupt is now used since core version 2.4.0 for PWM. A NMI interrupt sounds to be a good idea, because it can't be masked. But I tested this yesterday. A NMI interrupt isn't a good idea for small PWM values. A timer1 NMI interrupt for the ESP8266 has a poor accuracy for short times and doesn't work proper for time spans below 10 µs. With normal timer1 interrupts also time spans of 4 µs work proper if only one timer is used.
Yesterday I implemented a module for four shared timers. One timer is for PWM, one timer for tone, one timer could replace timer1 of the timer1 module and for a further timer I would like to implement new features. These four timers are enough because I have a further idea. Why shouldn't these timers have children? Without the implementation of children, I could present a solution maybe tomorrow for PWM and tone working together. Oh not tomorrrow, because tomorrow I don't have time, then today or after tomorrow.
With children also multi channel tone could be used and there would be an unlimited number of further timer interrupts. But this would take about two or three days more.
Maybe multi channel tone and an unlimited number of timer interrupts needn't be implemented as a core module. Should these features be implemted as a library?
@AlfonsMittelmeyer Good stuff! Will wait for the solution. You're awesome! Thanks!
@AlfonsMittelmeyer
Should these features be implemted as a library?
Yes, please!
OK, then the core module for tone will have only one tone channel by using TIMER_SHARED_TONE. But an additional library will offer multi channel tone by using the same timer for children timers. And the same library will also offer children timers for each of the four primary shared timers and maybe some other basic functionalities for creating and measuring time dependent signals.
Sounds good!
Is there something special to know about compiling the core? The core files are on my Raspberry pi3 in this directory:
/home/pi/.arduino15/packages/esp8266/hardware/esp8266/2.4.1/cores/esp8266
Therein i had put my module "timer_shared.cpp" and the header file "timer_shared.h"
This is the content of my header file:
#include "c_types.h"
//=======================================================================
// defines
//=======================================================================
#define TIMER_SHARED_PWM 0
#define TIMER_SHARED_TONE 1
#define TIMER_SHARED_TIMER1 2
#define TIMER_SHARED_USR 3
//=======================================================================
// function prototypes: interface functions
//=======================================================================
void shared_timer_isr_init(void);
void shared_timer_nmi_isr_init(void);
void timer_shared_attachParamInterrupt(uint8_t shared_timer, void (* userFunc)(void *), void * parameters );
void inline timer_shared_attachInterrupt(uint8_t shared_timer, void (* userFunc)(void));
void timer_shared_enable(uint8_t shared_timer, uint8_t divider, uint8_t int_type, uint8_t reload);
void ICACHE_RAM_ATTR timer_shared_stop_inisr(uint8_t shared_timer);
void timer_shared_stop_nonisr(uint8_t shared_timer);
void timer_shared_disable_nonisr(uint8_t shared_timer);
#define timer_shared_detachInterrupt timer_shared_disable_nonisr
void ICACHE_RAM_ATTR timer_shared_write_inisr(uint8_t shared_timer , uint32_t ticks);
void timer_shared_write_nonisr(uint8_t shared_timer , uint32_t ticks);
void timer_shared_reload(uint8_t shared_timer,uint32_t ticks);
void timer_shared_write_reload_nonisr(uint8_t shared_timer , uint32_t ticks, uint32_t reload_ticks);
uint32_t timer_shared_read(uint8_t shared_timer);
uint8_t timer_shared_enabled(uint8_t shared_timer);
My module compiles without problem. But I would like to call functions of my module from the module "core_esp8266_wiring_pwm.c". Of course I included my header file. But when I compile I get such error messages:
arduino.ar(core_esp8266_wiring_pwm.c.o):(.iram.text+0x14): undefined reference to timer_shared_write_inisr'
What must I do, that the references to my functions are found?
I compiled via Arduino IDE.
@AlfonsMittelmeyer you forgot about C++ mangling :) your new core files are .cpp, but you're trying to call your new functions from a .c file. You have to guard your new functions with
extern "C" {
...
}
I got it, it compiled. I should use extern "C" and I shouldn't use inline in a declararion for external use.
OK, I should also use #ifdef __cplusplus. In Arduino IDE this isn't neccessary, because cpp Files are compiled by c++ compiler and c files by c compiler. I will do this later after testing.
Or because I don't use classes, I also could make a c file after some work, like put local variables at the beginning of a function. Don't initialize structures at declaration time, maybe don't use line comment and don't use inline.
This with extern "C" was a bit tricky for me. The changes for tone and pwm, so that they use my shared timers were very simple. Now tone and pwm work both. But I go to bed now and wish a good night.
@AlfonsMittelmeyer Good job! Looking forward to try your lib!
@X-Stas-EEE this wasn't a lib but a core module!
I should also make measurements about performance and maybe I should disable other interrupts in non NMI mode, because the normal timer1 ISR does this also.
But not today.
Basic Infos
Hardware
Hardware: NodeMCU ESP8266 Core Version: 2.4.0
Description
Hi. I'm having trouble using tone() and analogWrite() on the NodeMCU ESP8266. Calling only tone() works fine, however calling analogWrite() on another pin, setting it to LOW and then calling tone() (on a different pin) only produces an annoying noise.
Sketch