Arduino and MIDI

Thread Starter

DerStrom8

Joined Feb 20, 2011
2,390
Using the arduino compiler libraries for timers will not give the precision frequencies that my code will do using a timer interrupt.

Those libraries wont be free running, so there will be overhead after each timed period.

It may be usable with their interrupt functions provided there is no attached latency especially variable latency.

Do you have a freq counter? If so you could write a quick test in code setting the timer to 50uS and using the timer interrupt toggle an output pin. Toggling at 50uS is 100uS period = 10 kHz output freq from the pin. Then the frequency counter will tell you if it is 10000 Hz or there is latency.



That sounds much better! If you can just set an interrupt to occur after every 512 instructions there will be no variable latency. Then my code will work fine and you will get very precise frequencies.

Just change this section of code to work with 31.25 kHz interrupt freq;
Rich (BB code):
  if(bres1 >= 2000000)     // using 31.25 kHz * 64
  {
    bres1 -= 2000000;
    note1_duty = 10;
  }
Then all the notes will be exact again.

Regarding the note ON period and variable name you are correct, I just rushed that code example. :) A better name would be note1_onperiod or something like that.

It's your baby so just change var names to whatever suits your code and your preferences.
Great, sounds like I'm on the right track. I already made the changes you suggested.

I do not have a dedicated frequency meter, but I do have a DMM with that functionality, as well as several scopes. I don't anticipate that being an issue, as I can always adjust things in code.

It had not occurred to me that there might be some latency due to using the timer library. I guess I just assumed it accessed the timers on the AVR directly, and that it wouldn't pose a problem. I guess I'll test it out when I return to my home in Vermont, which is where I have my scopes. I'll check how accurate it is and we can go from there.

I'll let you know what happens.

Thanks,
Matt
 

Thread Starter

DerStrom8

Joined Feb 20, 2011
2,390
Hi The_RB,

Sorry to bring up an old (ish) topic, but I'm still not quite following this part of the code:


Rich (BB code):
// code below is in the interrupt
if(note1on==1)            // this flag turns sound on/off
{
  bres1 += note1freq;    
  if(bres1 >= 1250000)     // using 19531.25Hz * 64
  {
    bres1 -= 1250000;
    note1_duty = 10;
  }
}
I think I understand the on-time part, but I'm not understanding what exactly the bres is doing, and why you are adding/subtracting the interrupt frequency * 64. Is there any chance you could explain how this is supposed to work?

Thanks,
Matt
 

THE_RB

Joined Feb 11, 2008
5,438
The Bresenham accumulator allows you to generate an average freq at the ratio of two numbers.

The main benefit is that it doesn't matter what freq the interrupt is at, you can generate any perfect note freq (or a number of perfect freq at the same time) from any freq interrupt.

If the freq you want to generate exactly is 440 Hz, and the interrupt occurs at (in your case) exactly 31250 Hz, the ratio of these two frequencies is 440:31250.

So this code generates an event at that ratio, so the event occurs at exactly 440 Hz;
Rich (BB code):
  bres += 440;
  if(bres >= 31250)    
  {
    bres1 -= 31250;
    (440 Hz / edge event occurs here)
  }
Operation; The interrupt occurs at 31250 Hz, and in every interrupt the exact value of 440 is added to the accumulator. When the accumulator >=31250, the exact value of 31250 is subtracted but the error remainder is left in the accumulator for next time.

Because only an exact value of 440 is added, and only an exact value of 31250 is subtracted, the "event" occurs at a perfect mathematical ratio of 440:31250.

Because the operation is performed at 31250 Hz (in interrupt), the perfect ratio of 440:31250 is applied to that, so the "event" frequency must occur at exactly 440 Hz. (An exact average frequency, because any / edge can have some small variance).

It's a very powerful way of generating an exact frequency from any other frequency. And very fast math; one addition, one test, sometimes one subtraction.

Bresenham's original algorithm was to draw a diagonal line on a XY matrix as in plotting, where the ratio needed to be exact so the line was at an exact angle and ended up on an exact pixel.

Like if a diagonal line moves across 57 pixels and up 14 pixels, the ratio is 14:57.

That same concept of X to Y ratio can be used for A freq to B freq. :)

The only thing different was to increase the constant to 31250*64, which is done simply to allow finer adjustment resolution of the note frequency. (64 times finer resolution, obviously).
:)
 

Thread Starter

DerStrom8

Joined Feb 20, 2011
2,390
THE_RB, that post is extraordinarily helpful. It makes much more sense now, thank you!

Brilliant method for playing the notes, too. Very clever!

Thanks once again. I moved back to Vermont today, so I now have my equipment to work with. I think I might get started on testing it right now.

Many thanks, you've answered a lot of questions that have been going through my mind!

Regards,
Matt
 

Thread Starter

DerStrom8

Joined Feb 20, 2011
2,390
Just thought I'd mention that the Timer1 interrupt (when set for 51uS) is very accurate, and is producing a 19.53kHz signal. So we shouldn't have to worry much about overhead, except for the math functions. I don't think it'll affect it too much, but I'll keep testing it as I go on.

As for this:

Rich (BB code):
if (bres1 >= (31250 * 64)) {
      bres1 -= (31250 * 64);
      note_on_time = 10;
    }
Am I mistaken, or does the code within that if statement be executed 440 times per second (if bres1 is for an A4 note)?

Also, I just want to clarify some things:

-noteOn is the MIDI command, which tells the note itself to start.
-note_on_time (was note1_duty) is the high-time of a single pulse that makes up the note. It is not how long the note is playing.

Since noteOn is the MIDI command, then I want to say that the duty cycle portion should be INSIDE the "if(noteON)" statement, am I correct?

Regards,
Matt

EDIT: This is the code I currently have (adapted for the Arduino):

Rich (BB code):
#include <TimerOne.h>

#define TIMER_OVF_PERIOD 32  //period in uS before Timer1 triggers the interrupt
#define MAX_ON_TIME 10

int tone1pin = 3;            //tone output 1
boolean note1on = false;     //note1 status
unsigned long bres1 = 0;     //32-bit accumulator for note1
unsigned long note1freq;     //32-bit frequency of note1
int note_on_time = MAX_ON_TIME;

void setup() {
  pinMode(tone1pin, OUTPUT);
  Timer1.initialize(TIMER_OVF_PERIOD);    //trigger the interrupt every 32 microseconds
  Timer1.attachInterrupt(noteISR);        //attach interrupt service routine to Timer1
  interrupts();                           //start interrupts
  Serial.begin(9600);
}

void loop() {
  note1freq = 440 * 64;             //assign A4 note to note1
  note1on = true;                   //turn on note 1
}

/*****************************
 * Interrupt Service Routine *
 *****************************/
 
void noteISR() {
  if (note1on) {
    bres1 += note1freq;
    if (bres1 >= (31250 * 64)) {
      bres1 -= (31250 * 64);
      note_on_time = MAX_ON_TIME;
    }
    if (note_on_time > 0) {
      note_on_time--;
      //Serial.println(note_on_time);
      digitalWrite(tone1pin, HIGH);
    } else {
      digitalWrite(tone1pin, LOW);
    }
  }
}
Unfortunately, this ends up sending out a 500uS pulse (good) but a frequency in the millihertz range. It seems the frequency is off by a power of 10^4 (if I did my math right). Curious how I'm getting such a significant error....
 
Last edited:

THE_RB

Joined Feb 11, 2008
5,438
Yes the test;
if (bres1 >= (31250 * 64))
will be true 440 times a second, and the code inside the {}will run at 440 Hz and generate the freq at 440 Hz.

note1_on will allow the note to play, or disable it.

note1_on_time sets the HI period of the note. (You should have the '1' as part of the variable name, ready for wwhen you add note2 and note3 etc.)

DerStrom8 said:
... Since noteOn is the MIDI command, then I want to say that the duty cycle portion should be INSIDE the "if(noteON)" statement, am I correct?
...
The problem with doing that (as you have done) is that the countdown for the note Hi period; note_on_time--; will not execute if you turn the note off by clearing note1_on.

The way you have it now, if you turn note1 off at the wrong time it can get stuck with the output pin HI, which is a danger situation for your hardware.

The way I wrote it;
Rich (BB code):
if(note1on==1)            // this flag turns sound on/off
{
  bres1 += note1freq;   
  if(bres1 >= 1250000)     // using 19531.25Hz * 64
  {
    bres1 -= 1250000;
    note1_duty = 10;
  }
}
if(note1_duty)
{
  note1_duty--;
  PORTB.F1 = 1;      // note HI
}
else PORTB.F1 = 0;   // note LO again
Means that even after you turn the note off, the code still runs to finish the HI period, and ensure it finally comes to rest with the pin forced LO.

Having that code outside the brackets means it has to run all the time even when the note is OFF.

But the cost of that is very low, because when the note is OFF it executes very quickly, basically 3 assembler instructions;
* tests that note1_on_time == 0 (2 asm instr)
* clr port pin to LO (1 asm instr)

Basically it's a "failsafe".
:)
 

Thread Starter

DerStrom8

Joined Feb 20, 2011
2,390
Yes the test;
if (bres1 >= (31250 * 64))
will be true 440 times a second, and the code inside the {}will run at 440 Hz and generate the freq at 440 Hz.

note1_on will allow the note to play, or disable it.

note1_on_time sets the HI period of the note. (You should have the '1' as part of the variable name, ready for wwhen you add note2 and note3 etc.)



The problem with doing that (as you have done) is that the countdown for the note Hi period; note_on_time--; will not execute if you turn the note off by clearing note1_on.

The way you have it now, if you turn note1 off at the wrong time it can get stuck with the output pin HI, which is a danger situation for your hardware.

The way I wrote it;
Rich (BB code):
if(note1on==1)            // this flag turns sound on/off
{
  bres1 += note1freq;   
  if(bres1 >= 1250000)     // using 19531.25Hz * 64
  {
    bres1 -= 1250000;
    note1_duty = 10;
  }
}
if(note1_duty)
{
  note1_duty--;
  PORTB.F1 = 1;      // note HI
}
else PORTB.F1 = 0;   // note LO again
Means that even after you turn the note off, the code still runs to finish the HI period, and ensure it finally comes to rest with the pin forced LO.

Having that code outside the brackets means it has to run all the time even when the note is OFF.

But the cost of that is very low, because when the note is OFF it executes very quickly, basically 3 assembler instructions;
* tests that note1_on_time == 0 (2 asm instr)
* clr port pin to LO (1 asm instr)

Basically it's a "failsafe".
:)
See, now that you explain it it seems very obvious :p

For some reason I was thinking that it was toggling the tone pin even when the note was off, but I see now that the note1_on_time (meant to put in a 1, but since I"m testing with just one tone, I left it out) is only set if the note is on, so the tone pin only toggles when it receives the note on command.

Half past 1am here though, I suppose I should get some sleep and continue this in the morning. I get the week off before starting at my new job :)

Cheers,
Matt
 

Thread Starter

DerStrom8

Joined Feb 20, 2011
2,390
Just thought I'd post this here:

View attachment NewFile0.bmp

As you can see, I got it working this morning :)
The frequency jumps around between 438.6 and 446.4 Hz.

Time to experiment with multiple tones.... :D

Regards,
Matt

UPDATE: I successfully played a nice-sounding C chord using the three output pins on the Arduino. I tried applying the MIDI using loopMIDI, Hairless MIDI-Serial Bridge, and the VMPK, but after playing one note it locks on that note. It continues playing until I reset the Arduino, and I can't do anything in the mean time (can't play any other notes, I mean).

At least the polyphonic part is working! I need to make sure the separate notex_on_time periods don't overlap, otherwise I could (potentially) get an on-time of 500uS * 3 tones = 1.5mS if the outputs were triggered one right after another.
 
Last edited:

THE_RB

Joined Feb 11, 2008
5,438
Excellent! Nice to see a 500uS pin HI period.

Re the 438.6 and 446.4 Hz variance it would be good to get that sorted out.

That could be that Arduino code library 52uS delay latency we talked about. You could try instead using the timer interrupt at 31250 Hz?

Or maybe it's just the way the 'scope reads the frequency, because the code will generate fractional differences in the freq of each cycle, so the AVERAGE is a perfect 440 Hz.

On my scope it reads freq by number of trigger events over a gated second, so it would read 440 Hz. Maybe your scope is calculating freq based on just the 2 complete cycles seen on screen? Maybe change your H timebase so it shows 50+ cycles on screen, and see what the freq reads then?

Re the stuck on notes, MIDI is notorious for that! If it fails to send a note-off command or you fail to receive it you get a stuck note... You might want to put some type of time-out feature in your note allocating engine in your code, so any note that runs for more than a couple of seconds is shutdown?

:D:D Congrats on getting the polyphonics playing 3 nice sounding notes at once!

Re the noteX_on_time's overlapping, I don't think you have a hope in hell of stopping that. Notes in music are only slightly different frequencies. Like the difference between a C and a D is only 12% or so. If both those are playing at the same time they will overlap or "beat" every 6 or 7 wavelengths. It is just going to constantly happen as polyphonic music plays.

I don't have an easy solution. I think the best way is to just make sure the hardware can handle 3 notes each with 500uS HI periods, and just tolerate the constant overlaps.

Anyway congrats again! Beer time. :D
 

Thread Starter

DerStrom8

Joined Feb 20, 2011
2,390
So now something else has come up that should be addressed. After doing even further research, it looks like my on-time can be no greater than 48uS (Huge difference, I know--I must have left out something important). So I will definitely need to use a higher frequency, for sure. Also, to avoid issues with overlapping notes, I think I'll just shorten the on-time for each one to 1/3 of the maximum. That way even when they're all added together they still do not exceed the hardware's capabilities. I won't be able to prevent "loops" from happening though (that is one plays right after another, and when the first one ends, it starts all over again).

When increasing the timebase to 5ms the frequency still jumps, but now it shows between 416.7 and 434.8 Hz. The pulse width also starts displaying higher numbers though, which is definitely a scope bug. lowering the timebase gives me a more accurate pulse width and frequency measurement, so I expect the jitter is due to the way the scope handles things. I guess that's what I get for buying a cheap scope :p

Anyway, I'll need to be able to generate pulses that are only 16uS long (1/3 of the max), which means my timer would have to interrupt at 62.5kHz or higher. I guess that's my next task....

Thanks,
Matt

UPDATE: I measured the signal with my DMM set to frequency mode and it measures a steady 441Hz and 22% duty cycle, which gives me an on-time of 498.87uS. This is the value I will need to tweak.
 
Last edited:

THE_RB

Joined Feb 11, 2008
5,438
What is the reason your ON period is limited to 48uS? That sounds extremely short, you said before the tesla coil osc freq was 217 kHz?

That is 4.6uS per cycle, so your max ON period is only 10 cycles of the tesla coil? Does it even build up to resonant full power with 10 cycles?

And then if you divide that by 3 you have about 3 tesla osc cycles? Something sounds wrong there.

If you are limited to ON period of 48uS, what amount of OFF period is required for recovery before you can enable the next ON period?

Sorry that I don't understand the need for the limitation but it does sound like this limitation is going to cause a heap of hassle.

Maybe fixing the limitation first is a good option?
 

Thread Starter

DerStrom8

Joined Feb 20, 2011
2,390
What is the reason your ON period is limited to 48uS? That sounds extremely short, you said before the tesla coil osc freq was 217 kHz?

That is 4.6uS per cycle, so your max ON period is only 10 cycles of the tesla coil? Does it even build up to resonant full power with 10 cycles?

And then if you divide that by 3 you have about 3 tesla osc cycles? Something sounds wrong there.

If you are limited to ON period of 48uS, what amount of OFF period is required for recovery before you can enable the next ON period?

Sorry that I don't understand the need for the limitation but it does sound like this limitation is going to cause a heap of hassle.

Maybe fixing the limitation first is a good option?
You bring up an excellent point. The 46uS limit is based on the voltage derating of the tank capacitor. I had originally just used 30%. The MMC I am using, however, has a voltage rating of 12000v, which is 2-3 times the absolute maximum voltage I would expect to see. Therefore, I guess I could assume 0%, which brings my allowed on-time up to 70uS.

It is very common for DRSSTCs to run with extremely low duty cycles. Based on my calculations, I should have full energy transfer after 3.81 cycles, which means it would take about 17.7uS. So you're right, it wouldn't work very well if I only allowed 16uS (48uS / 3) for each note.

The reason for the limitation is primarily to protect the capacitors and H-bridge from over-current. Pulsing current through the capacitors and transistors is much easier on the components and reduces the chance of failure. Generally it is recommended that the duty cycle not exceed 10%, so hopefully that'll give you an idea of the off-time necessary in order for the driver and primary tank circuit to recover.

Unfortunately there's not really anything I can do to eliminate or change the constraints. They're limitations of the hardware itself. Other Tesla Coilers have used similar hardware in their designs, so really the trick is just to figure out how they kept everything within the bounds of the hardware's capabilities.

Hopefully this clears some things up. Thanks again for pointing out that other issue regarding the time it takes for complete energy transfer.

Matt
 

THE_RB

Joined Feb 11, 2008
5,438
... Pulsing current through the capacitors and transistors is much easier on the components and reduces the chance of failure. Generally it is recommended that the duty cycle not exceed 10%, so hopefully that'll give you an idea of the off-time necessary in order for the driver and primary tank circuit to recover.
Ahhh. Your problem just got 10 times worse. :(

So now you are limited to 46uS ON period and a forced rest of 46uS * 9 = 414uS. That is going to mess with your polyphony.

...
Unfortunately there's not really anything I can do to eliminate or change the constraints. They're limitations of the hardware itself. Other Tesla Coilers have used similar hardware in their designs, so really the trick is just to figure out how they kept everything within the bounds of the hardware's capabilities. ...
So these videos that show the tesla coil making a huge continuous arc for many seconds are not continuous? It's just a pulsing arc with 10% duty?

If so, maybe you could take that approach? It would solve your problem.

Imagine if you generated the note with a long ON period, say 50:50 duty. So a 440Hz note has periods of 1.14mS ON and 1.14mS OFF.

Then during the 1.14mS ON period you modulate the tesla coil at 10% duty.

So in effect the note ON period is "continuous" and can be as long as you like, because the "continuously ON" arc is actually modulated at 10% duty.

BUT, my personal preference would just be to stick with a short fixed note on period (like 46uS etc) and just make sure the hardware can tolerate your worst case scenario of 3 notes on, at higher note frequencies, with overlaps. I'm sure that's how other people are playing music.
 

Thread Starter

DerStrom8

Joined Feb 20, 2011
2,390
So these videos that show the tesla coil making a huge continuous arc for many seconds are not continuous? It's just a pulsing arc with 10% duty?
Pretty much, yes, except it is often far less than 10%. If it was continuous (and continuous-wave Tesla coils do exist), you would most likely not here the arc--it would just be a faint hum or hiss because it's oscillating at a frequency much higher than humans can hear. If you can hear the arc, then that means the coil is being turned on and off by the interrupter generally with less than a 10% duty cycle.

If so, maybe you could take that approach? It would solve your problem.
That's exactly what I'm trying to do. I have a simple interrupter that uses a 555 timer to generate the pulses at the right duty cycle, but I am trying to create something that allows me to do this through software, and have it be controlled by a MIDI interface.

Imagine if you generated the note with a long ON period, say 50:50 duty. So a 440Hz note has periods of 1.14mS ON and 1.14mS OFF.

Then during the 1.14mS ON period you modulate the tesla coil at 10% duty.

So in effect the note ON period is "continuous" and can be as long as you like, because the "continuously ON" arc is actually modulated at 10% duty.
That's exactly what I've been trying to do, and yes, the "continuously ON" arc is actually modulated. There are several layers that need to be considered here:


  • We have the Tesla coil oscillating at its resonant frequency of ~200kHz. We can't hear this. This is just the frequency of the original arc. Each time an arc is made, there's a small "pop" sound.
  • We then have the interrupter that turns the entire coil on and off at a frequency we can hear. So to play an A4 note, we would set the interrupter at 440Hz. This gives us 440 "pops" per second, and that is how we hear the note.
  • We then have to make sure that the coil is only on for only ~50uS at a time. Since the interrupter is sending 440 pulses per second, we need to change the length of each of those pulses so that it doesn't exceed 50uS. The signal will still be 440Hz, and thus we will still hear an A4 note, but the duty cycle is extremely low, which will protect the circuitry from overheating. Each time one of these pulses is sent to the Tesla coil, it will create an arc (oscillating at ~200kHz) and a "pop".



BUT, my personal preference would just be to stick with a short fixed note on period (like 46uS etc) and just make sure the hardware can tolerate your worst case scenario of 3 notes on, at higher note frequencies, with overlaps. I'm sure that's how other people are playing music.
That's actually what I'm trying to do. An occasional overlap shouldn't hurt anything, so I'm thinking ~50uS for any given note would be acceptable.
 

THE_RB

Joined Feb 11, 2008
5,438
Well if it can tolerate the occasional overlap I think you have it solved. :)

Assuming notes like 440 Hz that have a total period of 2280uS, if you run a 50uS ON period the overall duty is very low.

So even with some overlaps from 3 voice polyphony it will still be a very low average ON duty.

You will have to double check with higher note frequencies.
 

Thread Starter

DerStrom8

Joined Feb 20, 2011
2,390
Well if it can tolerate the occasional overlap I think you have it solved. :)

Assuming notes like 440 Hz that have a total period of 2280uS, if you run a 50uS ON period the overall duty is very low.

So even with some overlaps from 3 voice polyphony it will still be a very low average ON duty.

You will have to double check with higher note frequencies.
I don't think I will need to worry about higher frequencies. With a maximum of 10% duty cycle, 50uS gives me a max frequency of 20kHz. There is almost no way I'll ever go that high, but at least I have plenty of overhead there. That should allow for overlapping notes if necessary. I will also (hopefully) have over-current detection, which will sense if the current exceeds a certain threshhold it'll turn off the coil for a short period of time to cool down (short as in a couple milliseconds, perhaps).

The main problem at this point, I think, is the stuck notes. I'm not sure if it's a problem with the MIDI library or what. Maybe it's as simple as increasing the baud rate of the serial connection. I've been running at 57600, but I might up that to 115200 because otherwise I'm only getting one sample per 17uS or so. The timers run faster than that, which means the interrupters do as well. Thinking that might be the cause of the MIDI glitches. Going to experiment a bit with this tomorrow. If you have further ideas though of what could be causing the issues, they'd definitely be appreciated.

Regards,
Matt
 

THE_RB

Joined Feb 11, 2008
5,438
It's hard to say with serial unless you can scope it or monitor it somehow.

Either the MIDI note off command is not being transmitted, or it is being transmitted and your arduino is losing the occasional command.

I think the second one is more likely. :)

If the arduino loses a note-on command you likely would never notice it. But the note-off command is very easy to notice!

So I think the most likely probability is that the arduino is losing or corrupting the occasional command. You could try speeding up the serial baudrate, or slowing it down might be a better option if the arduino serial library is too slow.

Another good option is to set the serial transmitter to 2 stop bits. That gives the arduino more breathing space and better ability to resync each byte.
 
Last edited:

Thread Starter

DerStrom8

Joined Feb 20, 2011
2,390
Hi THE_RB--

It's hard to say with serial unless you can scope it or monitor it somehow.

Either the MIDI note off command is not being transmitted, or it is being transmitted and your arduino is losing the occasional command.

I think the second one is more likely. :)
I agree with you there. That does seem the most likely. I'll explore it a bit more to see what I can find. :)

If the arduino loses a note-on command you likely would never notice it. But the note-off command is very easy to notice!

So I think the most likely probability is that the arduino is losing or corrupting the occasional command. You could try speeding up the serial baudrate, or slowing it down might be a better option if the arduino serial library is too slow.

Another good option is to set the serial transmitter to 2 stop bits. That gives the arduino more breathing space and better ability to resync each byte.
That sounds good. I'll give that a shot next chance I get! Right now I'm finalizing the overall schematic so I can start soldering the driver bits together. The interrupter is on a separate board (Arduino ProtoShield), so I'll be saving that for a little later.

Thanks for the suggestions. I'll let you know how they turn out!

Regards,
Matt
 

Thread Starter

DerStrom8

Joined Feb 20, 2011
2,390
Hi TheRB, just thought I'd give you a few updates:

I got really annoyed by the Arduino--it is extremely inefficient, inaccurate, and severely limits what can be done with it. Therefore I have decided to go with the PIC instead, the 18F4550 to be exact.

I have set up the timers to work with the bresenham timing system, but am getting some major issues (very nasty waveform, for one thing). Here is the code:


Code:
/* Main.c */

#include <xc.h>
#include <pic18.h>
#include <main.h>

/*
* CONFIG1H @ 0x300001
*
* Standard Crystal Oscillator (8MHz)
*/

#pragma config FOSC = INTOSC_EC

/*
* CONFIG2L @ 0x300002
*
* Brown Out Reset disabled
* Power Up Timer enabled
*/

#pragma config BOR = OFF, PWRT = ON

/*
* CONFIG2H @ 0x300003
*
* Watchdog Timer disabled
*/

#pragma config WDT = OFF

/*
* CONFIG4L @ 0x300006
*
* Low Voltage Programming disabled
* ICPRT disabled (only available on 44-pin packages)
* Reset on stack over/underflow disabled
*/

#pragma config LVP = OFF, ICPRT = OFF, STVREN = OFF

unsigned long bres1 = 0;    //bresenham accumulator
unsigned long note1freq;    //output frequency
unsigned int note1_duty;    //output duty cycle
unsigned char note1on = 0;    //note state (0 = off, 1 = on)
unsigned int period = 0;    //note period

void timer0_init(void);                //Timer0 init prototype
void interrupt Timer0_ISR(void);    //Timer0 interrupt prototype

void main(void)
{
    PORTA = 0x00;            //Clear latches of PORTA-E
    PORTB = 0x00;
    PORTC = 0x00;
    PORTD = 0x00;
    PORTE = 0x00;

    ADCON1 = 0b00001101;    //Use AN0 and AN1, set rest to digital
    OSCCON = 0b01110000;    //Set internal oscillator to 8MHz

    TRISB &= 0b11111110;    //Set PORTB BIT0 to output

    timer0_init();

    while(1) {
      
        note1freq = 440;    //set frequency to 440Hz
        note1on = 1;        //turn on note
    }
}

void timer0_init(void) {

    T0CON = 0b01001000;
    /*
    T0CONbits.TMR0ON = 0;    //0: Timer0 off, 1: Timer0 on
    T0CONbits.TO8BIT = 1;    //0: 16-bit mode, 1: 8-bit mode
    T0CONbits.T0CS = 0;        //Timer clock = main clock
    T0CONbits.T0SE = 0;        //Rising edge
    T0CONbits.PSA = 1;        //Bypass prescaler
    T0CONbits.PS2 = 0;        //We're skipping these, but
    T0CONbits.PS1 = 0;        // 010 = 8, 011 = 16, 100 = 32,
    T0CONbits.PS1 = 0;        // 101 = 64, 110 = 128, 111 = 256
    */

    INTCONbits.TMR0IE = 1;    //enable Timer0 interrupt
    INTCONbits.GIE = 1;        //enable global interrupts

    TMR0 = 256 - 64;        //overflow every 64 cycles (31250 Hz)

    INTCONbits.TMR0IF = 0;    //Clear Timer0 interrupt flag

    T0CON |= 0b10000000;    //Start Timer 0
}

void interrupt Timer0_ISR() {

    if (INTCONbits.TMR0IE && INTCONbits.TMR0IF) {    //Check that it's the Timer0 interrupt
        if (note1on == 1) {
            bres1 += note1freq * 64 * 2;//increment bresenham accumulator
            if (bres1 >= 2000000) {        //31250Hz interrupt freq * 64
                bres1 -= 2000000;
                note1_duty = 10;
            }
        }
        if (note1_duty > 0) {            //handle on/off-time
            note1_duty--;
            PORTBbits.RB0 = 1;  
        } else {
            PORTBbits.RB0 = 0;
        }
    }
    TMR0 = 256 - 64;        //overflow every 64 cycles (31250 Hz)
    INTCONbits.TMR0IF = 0;        //clear interrupt flag
}
Here is the waveform produced on PORTB BIT0:

NewFile13.png

As you can see, it's far from being an actual square wave, and the frequency is off significantly.

I'm wondering if part of the problem is this:

Code:
if (note1_duty > 0) {
            note1_duty--;
            PORTBbits.RB0 = 1;   
        } else {
            PORTBbits.RB0 = 0;
        }
I'm not sure how the PIC handles that internally, when it's told to turn on a pin that is already turned on.

Any ideas?

Regards,
Matt
 
Last edited:
Top