I also want to know how can we make it without functions like digitalwrite
-
1read this arduino.cc/en/Reference/PortManipulation– jsotolaJun 7, 2019 at 3:01
-
but then it is not Arduino, if you don't use Arduino functions and the question is off-topic– Juraj ♦Jun 7, 2019 at 4:06
-
1I don't see what is off-topic about it, if he has an Arduino and wants to make it do stuff. Surely he doesn't necessarily have to use the Tone library, or necessarily do digitalWrite? Ultimately every library will eventually use the hardware registers, and you wouldn't say that the libraries are off-topic, would you?– Nick Gammon ♦Jun 7, 2019 at 6:32
-
@NickGammon, OP wants an AVR C program with main() (embedded C language code) and it looks like an assignment from school. If I write the low level part of an Arduino library I use the underlying technology which is not Arduino and if it is AVR and I have a question about it, I go to avrfreaks forum– Juraj ♦Jun 7, 2019 at 9:11
-
Perhaps you are right about the assignment, but there is nothing inherently bad about helping people to enlightenment. :)– Nick Gammon ♦Jun 7, 2019 at 23:13
2 Answers
Yes, that's easy enough to do. I wrote a library to do that, as described here.
Basically you could make a class like this:
class TonePlayer
{
// addresses of output ports - NULL if not applicable
volatile byte * const timerRegA_;
volatile byte * const timerRegB_;
volatile byte * const timerOCRH_;
volatile byte * const timerOCRL_;
volatile byte * const timerTCNTH_;
volatile byte * const timerTCNTL_;
public:
// constructor
TonePlayer (
// ports
volatile byte & timerRegA,
volatile byte & timerRegB,
volatile byte & timerOCRH,
volatile byte & timerOCRL,
volatile byte & timerTCNTH,
volatile byte & timerTCNTL)
:
timerRegA_ (&timerRegA),
timerRegB_ (&timerRegB),
timerOCRH_ (&timerOCRH),
timerOCRL_ (&timerOCRL),
timerTCNTH_ (&timerTCNTH),
timerTCNTL_ (&timerTCNTH)
{ }
void tone (const unsigned int Hz);
void noTone ();
}; // end of TonePlayer
Then implement the two functions like this:
void TonePlayer::tone (const unsigned int Hz)
{
// it takes two toggles for one "cycle"
unsigned long ocr = F_CPU / Hz / 2;
byte prescaler = _BV (CS10); // start with prescaler of 1 (bits are the same for all timers)
// too large? prescale it
if (ocr > 0xFFFF)
{
prescaler |= _BV (CS11); // now prescaler of 64
ocr /= 64;
}
// stop timer
*timerRegA_ = 0;
*timerRegB_ = 0;
// reset counter
*timerTCNTH_ = 0;
*timerTCNTL_ = 0;
// what to count up to
*timerOCRH_ = highByte (ocr);
*timerOCRL_ = lowByte (ocr);
*timerRegA_ = _BV (COM1A0); // toggle output pin
*timerRegB_ = _BV (WGM12) | prescaler; // CTC
} // end of TonePlayer::tone
void TonePlayer::noTone ()
{
// stop timer
*timerRegA_ = 0;
*timerRegB_ = 0;
} // end of TonePlayer::noTone
And now call it:
TonePlayer tone1 (TCCR1A, TCCR1B, OCR1AH, OCR1AL, TCNT1H, TCNT1L); // pin D9 (Uno), D11 (Mega)
void setup()
{
pinMode (9, OUTPUT); // output pin is fixed (OC1A)
tone1.tone (220); // 220 Hz
delay (500);
tone1.noTone ();
tone1.tone (440);
delay (500);
tone1.noTone ();
tone1.tone (880);
delay (500);
tone1.noTone ();
}
void loop() { }
In this example you plug your speaker into D9 (digital pin 9) on your Uno, with the other pin going to ground. Since this plays tones using the hardware timers you can do other things in your loop without affecting the tones.
-
from Atmega328 datasheet
TCNT1 = 0x1FF;
" Note that when using “C”, the compiler handles the 16-bit access"– Juraj ♦Jun 7, 2019 at 10:38 -
I made a 13 note piano-like (well, more organ-like) tones instrument here
https://forum.arduino.cc/index.php?topic=179761.0
Youtube clip of it in action
https://www.youtube.com/watch?v=4c8idXN4Pg0
Only 8 notes in the clip, that was all the buttons I had at the time.
I used a '1284P processor instead of '328P so it could have 13 inputs & 13 outputs, without jumping up to a 2560.
There is nothing wrong with using Direct Port Manipulation in the Arduino IDE environment. Arduino is just C++ with some things made easier for the user with functions, macros, etc.
Here's the code.
/* Crossroads, 26 July 2013
test for creating multiple simultaneous notes using micros()
press a key, get a note. Use external analog mixer (summing op amp) for sound mixdown to single output to a speaker
need a higher pin count device - this code is for '1284P - with specific pin mappings, not written for uC portability
play 13 notes for now, C4 up to C5
note info from http://www.sengpielaudio.com/calculator-notenames.htm
Input keys: pin2=C4, 3=C#4, 30=D4, 8=D#4, 9=E5, 31=F5, 4=F5#, 5=G5, 6=G5#, 7=A5, 10=A5#, 11=B5, 12=C5
Output notes: pin 16=C4, 17=C#4, 18=D4, 19=D#4, 20=E5, 21=F5, 22=F5#, 23=G5, 24=G5#, 25=A5, 26=A5#, 27=B5, 28=C5
Ordering is funny due to Bobuino mapping, and having PortA output bits follow portD input bits, portC output bits follow portB input bits
0,1 reserved for serial
13,14,15,29, unused, maybe make 1 of 9 octave select? Piano has 7 full C to B octaves, 2 partials
*/
// array of 1/2 period of the notes in microseconds
// so a C8 note would be 119uS High, 119uS low for example
unsigned long noteArray[] = {
119, // C8, highest note - note 0
127,134,142,150,159,169,179,190,201,213,225,239, // B7, A#7,A7, G#7, G7, F#7, F7, E7, D#7, D7, C#7, C7 - notes 1-12
253,268,284,301,318,338,356,379,402,426,451,478, // B6 down to C6 - notes 13-24
506,536,568,602,638,676,716,758,804,851,902,965, // B5 down to C5 - notes 25-36
1012,1073,1136,1204,1276,1351,1432,1517,1607,1703,1804,1911, // B4 down to C4 - notes 37-48
2025,2145,2272,2408,2551,2703,2863,3034,3214,3405,3607,3822, // B3 down to C3 - notes 49-60
4050,4290,4545,4816,5102,5405,5727,6067,6428,6810,7215,7645, // B2 down to C2 - notes 61-72
8099,8581,9091,9631,10204,10811,11454,12135,12856,13621,14431,15289, // B1 down to C1 - notes -73-84
16198,17161,18182, // B0, A#0, A0 - 85-86-87
}; // really need to flip this whole thing over so that 0 is lowest note & 87 is highest
byte keyArray[]={
2,3,30,8,9,31,4,5,6,7,10,11,12,13,}; //Ports D2,D3,D4,D5,D6,D7,B0,B1,B2,B3,B4,B5,B6, added b7 - notes 48, 47,46,45,44,43,42,41,40,39,38,37,36
byte outputArray[] = {
14,15,16,17,18,19,22,23,24,25,26,27,28, 21,22,29}; // Ports A0,A1,A2,A3,A4,A5,C0,C1,C2,C3,C4,C5,C6, added a6, a7, c7
// ports selected for consistency for bits used from input to output from D in to A out, and B in to C out
byte keyActive[]= {
0,0,0,0,0,0,0,0,0,0,0,0,0,}; // 1 indicates key is pressed
//byte octaveSelect[] = {
// 13,21,22,29}; // b7,a6,a7,c7 - 0 to 9 - future use
// ATMEL ATMEGA1284P on Bobuino
//
// +---\/---+
// (D 4) PB0 1 | | 40 PA0 (D 21) AI 7
// (D 5) PB1 2 | | 39 PA1 (D 20) AI 6
// INT2 (D 6) PB2 3 | | 38 PA2 (D 19) AI 5
// PWM (D 7) PB3 4 | | 37 PA3 (D 18) AI 4
// PWM/SS (D 10) PB4 5 | | 36 PA4 (D 17) AI 3
// MOSI (D 11) PB5 6 | | 35 PA5 (D 16) AI 2
// PWM/MISO (D 12) PB6 7 | | 34 PA6 (D 15) AI 1
// PWM/SCK (D 13) PB7 8 | | 33 PA7 (D 14) AI 0
// RST 9 | | 32 AREF
// VCC 10 | | 31 GND
// GND 11 | | 30 AVCC
// XTAL2 12 | | 29 PC7 (D 29)
// XTAL1 13 | | 28 PC6 (D 28)
// RX0 (D 0) PD0 14 | | 27 PC5 (D 27) TDI
// TX0 (D 1) PD1 15 | | 26 PC4 (D 26) TDO
// INT0 RX1 (D 2) PD2 16 | | 25 PC3 (D 25) TMS
// INT1 TX1 (D 3) PD3 17 | | 24 PC2 (D 24) TCK
// PWM (D 30) PD4 18 | | 23 PC1 (D 23) SDA
// PWM (D 8) PD5 19 | | 22 PC0 (D 22) SCL
// PWM (D 9) PD6 20 | | 21 PD7 (D 31) PWM
// +--------+
//
byte x;
byte portDkeys;
byte portBkeys;
byte portAnotes;
byte portCnotes;
unsigned long currentTime;
unsigned long changeTime[]= {
0,0,0,0,0,0,0,0,0,0,0,0,0,}; // track when to flip output
unsigned long duration;
/* consistency check
INDEX, NOTE, port In, port Out
0, C4, D2, A2 read 04 write 04 FB 1111 1011 Middle C
1, C#4, D3, A3 read 08 write 08 F7 1111 0111
2, D4, D4, A4 read 10 write 10 EF 1110 1111
3, D#4, D5, A5 read 20 write 20 DF 1101 1111
4, E4, D6, A6 read 40 write 40 BF 1011 1111
5, F4, D7, A7 read 80 write 80 7F 0111 1111
6, F#4, B0, C0 read 01 write 01 FE 1111 1110
7, G4, B1, C1 read 02 write 02 FD 1111 1101
8, G#4, B2, C2 read 04 write 04 FB 1111 1011
9, A4, B3, C3 read 08 write 08 F7 1111 0111 Concert A
10, A#4, B4, C4 read 10 write 10 EF 1110 1111
11, B4, B5, C5 read 20 write 20 DF 1101 1111
12, C5, B6, C6 read 40 write 40 BF 1011 1111
*/
/*************/
void setup(){
//Serial.begin (115200); // for debug testing
for (x=0; x<14; x=x+1){ // input pins with pullup
pinMode(keyArray[x], INPUT);
digitalWrite (keyArray[x], HIGH);
}
for (x=0; x<17; x=x+1){
pinMode(outputArray[x], OUTPUT); // output pins
}
// for (x=0; x<4; x=x+1){ // future use maybe
// pinMode(octaveSelect[x], INPUT); // input pins with pullups
// digitalWrite (octaveSelect[x], HIGH);
// }
} // end setup
/***********/
void loop(){
//Serial.print (" ");
// if key is pressed, determine time for next transition
// if key is not pressed, no transition, use AC coupling into summing op-amp for no output
// idea is to allow multiple notes to be created simultaneoulsy. Can the reading & if's all be done fast enough?
portDkeys = PIND; // Read the input keys - using direct port manipulation for speed
portBkeys = PINB;
currentTime = micros();
// Didn't make this part of a larger loop/array lookup because that adds 12-15uS for each pass thru the loop
/* Check if key for note C4 is pressed, key0 */
if ((portDkeys & 0x04) == 0){ // key is pressed D2 -> A2
if (keyActive[0]==0){
keyActive[0] = 1;
changeTime[0] = currentTime;
}
// see if time to change hi to lo, or lo to hi
if ( (currentTime - changeTime[0])>=noteArray[48]){ // time for a period toggle?
changeTime[0] = changeTime[0] + noteArray[48]; // setup time for the next toggle
PINA=0x04; // write a 1 to PINx toggles an output bit - NickGammon to the rescue again!
}
}
else{keyActive[0] = 0;}
/* Check if key for note C#4 is pressed, key1 */
if ((portDkeys & 0x08) == 0){ // key is pressed D3 -> A3
if (keyActive[1]==0){
keyActive[1] = 1;
changeTime[1] = currentTime;
}
// see if time to change hi to lo, or lo to hi
if ( (currentTime - changeTime[1])>=noteArray[47]){
changeTime[1] = changeTime[1] + noteArray[47];
PINA = 0x08;
}
}
else{keyActive[1] = 0;}
/* Check if key for note D4 is pressed, key2 */
if ((portDkeys & 0x10) == 0){ // key is pressed D4 -> A4
if (keyActive[2]==0){
keyActive[2] = 1;
changeTime[2] = currentTime;
}
// see if time to change hi to lo, or lo to hi
if ( (currentTime - changeTime[2])>=noteArray[46]){
changeTime[2] = changeTime[2] + noteArray[46];
PINA = 0x10;
}
}
else{keyActive[2] = 0;}
/* Check if key for note D#4 is pressed, key3 */
if ((portDkeys & 0x20) == 0){ // key is pressed D5 -> A5
if (keyActive[3]==0){
keyActive[3] = 1;
changeTime[3] = currentTime;
}
// see if time to change hi to lo, or lo to hi
if ( (currentTime - changeTime[3])>=noteArray[45]){
changeTime[3] = changeTime[3] + noteArray[45];
PINA = 0x20;
}
}
else{keyActive[3] = 0;}
/* Check if key for note E4 is pressed, key4 */
if ((portDkeys & 0x40) == 0){ // key is pressed D6 -> A6
if (keyActive[4]==0){
keyActive[4] = 1;
changeTime[4] = currentTime;
}
// see if time to change hi to lo, or lo to hi
if ( (currentTime - changeTime[4])>=noteArray[44]){
changeTime[4] = changeTime[4] + noteArray[44];
PINA = 0x40;
}
}
else{keyActive[4] = 0;}
/* Check if key for note F4 is pressed, key5 */
if ((portDkeys & 0x80) == 0){ // key is pressed D7 -> A7
if (keyActive[5]==0){
keyActive[5] = 1;
changeTime[5] = currentTime;
}
// see if time to change hi to lo, or lo to hi
if ( (currentTime - changeTime[5])>=noteArray[43]){
changeTime[5] = changeTime[5] + noteArray[43];
PINA = 0x80;
}
}
else{keyActive[5] = 0;}
/* Now Port B & Port C */
/* Check if key for note F#4 is pressed, key6 */
if ((portBkeys & 0x01) == 0){ // key is pressed B0 -> C0
if (keyActive[6]==0){
keyActive[6] = 1;
changeTime[6] = currentTime;
}
// see if time to change hi to lo, or lo to hi
if ( (currentTime - changeTime[6])>=noteArray[42]){
changeTime[6] = changeTime[6] + noteArray[42];
PINC = 0x01;
}
}
else{keyActive[6] = 0;}
/* Check if key for note G4 is pressed, key7 */
if ((portBkeys & 0x02) == 0){ // key is pressed B1 -> C1
if (keyActive[7]==0){
keyActive[7] = 1;
changeTime[7] = currentTime;
}
// see if time to change hi to lo, or lo to hi
if ( (currentTime - changeTime[7])>=noteArray[41]){
changeTime[7] = changeTime[7] + noteArray[41];
PINC = 0x02;
}
}
else{keyActive[7] = 0;}
/* Check if key for note G#4 is pressed, key8 */
if ((portBkeys & 0x04) == 0){ // key is pressed B2 -> C2
if (keyActive[8]==0){
keyActive[8] = 1;
changeTime[8] = currentTime;
}
// see if time to change hi to lo, or lo to hi
if ( (currentTime - changeTime[8])>=noteArray[40]){
changeTime[8] = changeTime[8] + noteArray[40];
PINC = 0x04;
}
}
else{keyActive[8] = 0;}
/* Check if key for note A4 is pressed, key9 */
if ((portBkeys & 0x08) == 0){ // key is pressed B3 -> C3
if (keyActive[9]==0){
keyActive[9] = 1;
changeTime[9] = currentTime;
}
// see if time to change hi to lo, or lo to hi
if ( (currentTime - changeTime[9])>=noteArray[39]){
changeTime[9] = changeTime[9] + noteArray[39];
PINC = 0x08;
}
}
else{keyActive[9] = 0;}
/* Check if key for note A#4 is pressed, key10 */
if ((portBkeys & 0x10) == 0){ // key is pressed B4 -> C4
if (keyActive[10]==0){
keyActive[10] = 1;
changeTime[10] = currentTime;
}
// see if time to change hi to lo, or lo to hi
if ( (currentTime - changeTime[10])>=noteArray[38]){
changeTime[10] = changeTime[10] + noteArray[38];
PINC = 0x10;
}
}
else{keyActive[10] = 0;}
/* Check if key for note B4 is pressed, key11 */
if ((portBkeys & 0x20) == 0){ // key is pressed B5 -> C5
if (keyActive[11]==0){
keyActive[11] = 1;
changeTime[11] = currentTime;
}
// see if time to change hi to lo, or lo to hi
if ( (currentTime - changeTime[11])>=noteArray[37]){
changeTime[11] = changeTime[11] + noteArray[37];
PINC = 0x20;
}
}
else{keyActive[11] = 0;}
/* Check if key for note C5 is pressed, key12 */
if ((portBkeys & 0x40) == 0){ // key is pressed B6 -> C6
if (keyActive[12]==0){
keyActive[12] = 1;
changeTime[12] = currentTime;
}
// see if time to change hi to lo, or lo to hi
if ( (currentTime - changeTime[12])>=noteArray[36]){
changeTime[12] = changeTime[12] + noteArray[36];
PINC = 0x40;
}
}
else{keyActive[12] = 0;}
} // end loop
-
Nice video! You could post your rather short code into your answer so that it doesn't totally rely on links. :) This is, after all, a completely alternative way of doing it to the one I showed (which doesn't allow multiple notes at once).– Nick Gammon ♦Jun 7, 2019 at 23:15
-
Just select the code, hit Ctrl+K (K for Kode). That indents it 4 spaces and formats it for you. I did it for you. :)– Nick Gammon ♦Jun 10, 2019 at 4:21
-