Pages

Digital Stethoscope

Introduction

Our project is a digital stethoscope that displays your heartbeat on any television.  It also calculates beats per minute and alerts you if your rate falls out of a specified range.

At the highest level, the design of our project centers around an acquisition circuit, data processing in two MCUs, and the output on a TV screen.  The first part of the stethoscope is the acquisition unit, which consists of an actual stethoscope mated with a microphone, and an amplifier circuit.  The microphone captures the audible signal from the body that is acoustically amplified by the stethoscope.  After that, we bias and set the gain of the signal using an operational amplifier so that the ADC on the MCU will be able to pick up the signal.  The analog data will be independently sampled by the two MCUs at a rate appropriate for display on the TV (CPU1) and a rate sufficient to capture the appropriate characteristics of the signal for beat detection (CPU2).  CPU2, uses a moving threshold scheme to detect the actual heartbeats, and from that derive the heart rate.  Then the signal is blasted to the TV, which also displays pertinent data, such as beats per minute.  Additional information is displayed on the HyperTerm.  If applicable, a buzzer will sound if your heart rate falls out of a specified range.   

High Level Design

The Idea and Requisite Knowledge

The idea for our project is pretty much our own, and is really just a proof of concept and a good demonstration of the capabilities of the Mega32.  There are many instruments commercially available to measure your heart rate, even with far greater accuracy than we obtain.  We thought it would be interesting to implement a usable piece of equipment that puts the heart rate on the TV and gets the correct beats per minute (BPM) reasonably well.

There is no requisite knowledge to understand our project beyond the content of ECE 476, except maybe that of low-pass filters.  Our acquisition circuit contains an analog low-pass filter and the second MCU which calculates the actual BPM uses a digital low-pass filter also.  The characteristics for the analog filter and the math for the digital filter appear in the program/hardware design section, where they can be viewed in context.  It is also good to know that there is a major spike for each heartbeat.

General Design

In our final project we implemented a digital stethoscope.  At the highest level, the design of our project centers around an acquisition circuit, data processing in two MCUs, and the output on a TV screen.  The stethoscope detects and displays the heart beat and BPM.  The first part of the stethoscope is the acquisition unit, which basically consists of an actual stethoscope mated with a microphone, and an amplifier circuit.  The microphone captures the audible signal from the body that is acoustically amplified by the stethoscope.  After that, we set the gain of the signal using an operational amplifier so that the ADC on the MCU will be able to pick up the signal.  This is necessary because the amount of signal you get from the heartbeat in your neck is probably much more than the heartbeat in your wrist, for example.

The analog data will be independently sampled by the two MCUs at a rate appropriate for display on the TV (CPU1) and a rate sufficient to capture the appropriate characteristics of the signal for beat detection
(CPU2).  With uniform data to work with in CPU2, we then wrote a scheme to detect the actual heartbeat, and from that derive the heart rate.  There are many methods we could use to implement this including absolute minima/maxima, average min/max, and Fourier (frequency) representation of the signal.  Our basic approach uses a moving threshold approach that is outlined in detail in the software section below.  The heartbeat signal and rate (beats per minute) will then be output to the TV screen, similar to ECE476 lab 4.  Figure 1 shows the basic setup of the entire project.


Figure 1

Hardware/Software Tradeoffs

One example of a hardware/software tradeoff in our project was our decision to eliminate automatic gain control.  We included this feature in our proposal because it would improve the robustness of the digital stethoscope by increasing the number of areas on your body that you could take a reading.  (ie. The signal from your neck is different from your wrist.)  Basically, when we deemed hardware AGC too difficult, we decided to implement it in software.  However, it turned out that we really didn’t need it at all, since the output from the acquisition circuit on the neck was relatively stable and consistent, even with different users.  We could have just blown the output of the microphone up in software, but we decided to use an op-amp to obtain resolution on the analog to digital conversion.  Also, the beats from weaker points on the body, like the wrist, just did not come out looking like beats at all.

Another tradeoff was the way that we implemented the mute function for the alert buzzer.  Instead of having a few logic gates hanging off of the high and low violation control lines between the two CPUs, we decided to implement the logic inside the second MCU and just have an extra output for the buzzer.  This saved extra external hardware, but added a level of complexity to the code.  In general, we tried to implement as much as possible in software, when appropriate.

Another limiting factor of the Mega32 CPUs is that they have a finite number of I/O ports.  Since we have 2 CPUs (one for displaying information, one for computing information), there is a limit on the amount of information that we could output, on the TV especially.  Because we essentially had more numbers to put on the TV than we had I/O ports to send it between the CPUs, we decided to implement the RS232 interface to HyperTerminal on the PC.  A picture of the output on the HyperTerminal can be found in the appendix.

Recognized Standards

The first standard in use for our project is the same we used in ECE476 lab 4, the RS170 composite video standard and the NTSC frame rate.  Our TV signal is non-interlaced, black and white video just as in lab 4.  The RS170 standard dictates the 3 voltages for black, white and sync, as well as the 4:3 aspect ratio, and the widths of the horizontal and vertical sync pulses and their front and back porches.  Additionally, the RS170 standard dictates that there are 525 lines per frame, and 60 frames per second.  As described above, our implementation uses a 30fps, non-interlaced version.

The second standard that we used was RS232 serial interface with the PC.  RS232 is a common serial interface for digital data communications.  It specifies signal voltages, timing, and protocols for information exchange and mechanical connectors.  Because our RS232 was implemented on the STK500, we did not have to touch any of the specifics of the protocol as the Mega32 implements it for us.

Patents and Intellectual Property

There are already many methods of detecting heart beat patterns and pulse rates.  Undoubtedly many of these methods are protected under patent law, but the simplicity of our design will not encroach on any of those complex designs.  There are many patents on stethoscopes.  While we are using one, however, we are not designing it.  This will probably limit the chances of our device receiving a patent.  Really, the only patent potential our device has is the fact that it plugs into your own TV.

We also feel that credit is due to others for part of the code used in our CPUs.  This will also limit our patent potential.  Professor Bruce Land of Cornell University supplied the basic code for NTSC video output on the Mega32 in the ECE476 lab 4.  We use an altered version of this code in CPU1 which outputs the heartbeat signal to the TV. Also, the majority of the button debouncing state machine in CPU2 was also supplied by Dr. Land in the ECE476 lectures.  A general diagram of the state machine can be found in the Appendix.

Program/Hardware Design

Hardware Details

The first part of our circuit was the stethoscope itself.  Aaron’s father is a cardiologist and we were able to obtain a used stethoscope from him free of charge.  Originally the stethoscope had very flexible medical latex tubing which mated the end piece with the metal frame and the earpieces.  The problem with the tubing is that we could not find a microphone small enough to fit inside it.  Our solution was to make a trip to the hardware store and pick up some vinyl tubing.  We used 3/16” inside diameter tubing for the main length and then 2 fitted larger sizes to telescope up to the size of our microphone.  After we worked the microphone down into the largest piece, we secured it with a metal hose clamp, as shown in figure 2.  We initially figured that the tube needed to be sealed so that acoustic loss would be minimized.  We attempted to melt the pieces of tubing together, but that was quite disastrous and we ended up burning holes in the tubing.  We eventually decided (after reassurance from Professor Land) that it was adequate to just jam the pieces together.

Figure 2

The WM-034DHB microphone we used is omnidirectional with -42dB sensitivity, >60dB SNR, and a 20-16kHz response range.  This particular microphone requires a 1.5V bias to operate properly.  We built a bias circuit using a 10kΩ trimpot, a 2N3904 NPN transistor, and a few resistors.  The diagram for the microphone bias circuit is shown in figure 3.  The gain portion of our circuit consists of an LMC7111 op-amp set up with a gain of 7, in a low-pass configuration, as shown in figure 4.  For this circuit, the equations for the low-pass time constant and amplifier gain are as follows:



We used an actual time constant of 0.5 and a gain of 7, as shown in figure 4.  The input of the amplifier was coupled to a DC voltage of around 2.5V.  This was necessary to center the amplified signal in the A/D range for the MCU.  We implemented this with a voltage divider with 2 x 1MΩ resistors, as shown in figure 4.

Figure 3                                                                                                  Figure 4


Since we use two MCUs (as detailed in the software details section), we had to build one MCU setup on a white board.  Figure 5 is a photograph of CPU1 and the TV output circuit on the white board (The MCU and TV circuit are on the lower of the two whiteboards shown; the upper is the microphone bias and amplifier circuit, along with the buzzer).  The TV uses pin D.5 as the sync signal, and D.6 as the video input.  HyperTerm was set up by connections from the STK500 to the PC, D.0 was the receive bit and D.1 was the transmit bit.  Please refer to the appendix for a picture of the actual Hyperterm output.  The TV output DAC, shown in figure 1, was provided to us in ECE 476 lab 4.  The appendix contains a detailed diagram of how the Mega32 is implemented on the whiteboard.

Figure 5

The final piece of hardware we used was a CT-1205C buzzer, which is shown on the upper board in figure 5.  This is used to indicate a violation of the high or low BPM violations.  We bought this particular buzzer because it was loud, annoying, inexpensive, and could be driven off of a single output pin in terms of current and voltage.

Software Details

Initially we planned on using only one MCU, but we quickly realized that it just was not possible to do all the necessary calculations and blast output to the screen on one MCU.  The 2-CPU solution we came up with uses CPU1 for the TV output, and CPU2 as a processing unit.  Both CPUs sample the same heart beat signal independently of each other and at different rates.

Our CPU1 code was a modified version of our ECE476 lab 4 code, which in turn was a modified version of Professor Land’s code for NTSC video output from the Mega32. Similar to lab 4, the TV (CPU1) has different timing modes, and can be set to display either one or two seconds of trace across the screen.  We decided to go with the one/two second division because at 60BPM, a typical heart rate, there is exactly one heartbeat on a one second width.  With a two second width, of course, you can see at least two complete beats.

CPU1 also takes several control lines that are generated by CPU2, including high and low violation lines, run/stop and one/two second screen widths from the buttons attached to CPU2.  Finally, there are 8 parallel bits which transmit the beats per minute as they are calculated.  Figure 1 shows how the two CPUs fit together and interface, along with outside connections.  The following table (figure 6) outlines the various definitions for the interface and control lines.

Figure 6

CPU2 has three basic tasks.  First, it is responsible for debouncing the 7 buttons attached on port B.  We used a state machine structure that was similar to the one presented in ECE 476 lecture for debouncing buttons.  The button functions are outlined in the following table, and a general diagram for a debouncing state machine can be found in the Appendix.

Figure 7

Second, CPU2 is responsible for calculating the BPM of the sampled signal.  We chose to go with a windowed sampling scheme, instead of a real-time approach.  This basically means we take a string of samples over a certain time period, process the data, determine the BPM, and start the next sample string.  In order to cover the reasonable BPM ranges of the human cardiovascular system, we were aiming for reliable performance between 60 and 180BPM.  The low end is where we really had to worry since at 60BPM, there is one beat each second.

Our detection scheme basically attempts to find pulse peaks in the data, determines the time between the peaks, and then converts that to BPM.  The sample window had to be long enough to get at least two beats in one window for the lowest range.  At 60BPM, a 2 second window gives this, and this is the window we chose.  The downside to the long sample window is that we can only provide a new BPM update every 2 seconds.  This decision between sample window length and the rate at with the BPM is output is a tradeoff that we made.  Heart rates lower than 60BPM can be detected if two pulses happen to fall inside the 2 second window, but since the phase of the beats is relatively random, there is no guarantee this will happen.  We modified our code such that if we only identified one peak, we would leave BPM unchanged. If we didn’t identify any peaks, then BPM was set to 0.  In the lab, we were able to reliably detect heart rates of around 50BPM.  The high end accuracy was limited by how much time we waited after identifying the first peak before looking for the second peak.  Our code waits 150 samples at 2ms each, or a total of 300ms, which equates to a maximum of 200BPM.

The beat detection algorithm is actually quite simple.  We took 1000 data points over 2 seconds, giving a sampling rate of 2ms.  This corresponds to a sampling frequency of 500Hz.  The Nyquist Sampling Theorem states then that the largest frequency component present in our sampled signal is 250Hz.  This in effect is a low pass filter.  This is not a problem, however, since we are only interested in the low frequencies anyway.  A second digital low-pass filter was implemented as a weighted rolling average.  The filter looks like this:


The alpha value in the equation dictates the amount of “smudging” that occurs.  This in effect smoothes the data and lowers the noise floor.  We experimentally determined the best alpha was 0.8.  The resulting 1000 data points in CPU2 then would look something like figure 8, which graphs actual data we collected by printing it to theHyperTerm.  Before sampling and the digital low-pass filter, the SNR of the heartbeat signal was about 5, but you can see from figure 8 that the post-filtered SNR is much higher.  The detection uses a moving threshold algorithm.  The first step is to determine the maximum value of data in the sequence.  Then, we look for successive peaks with increasing slopes within 80% of the maximum value.  This inherently assumes that the peaks in the input sequence are relatively stable, and within a reasonable range above the secondary peaks and noise.

Figure 8

The third function of CPU2 is to send data to CPU1 and to control the alarm buzzer.  CPU2 sends BPM through PORTC and information about the modes of operation and violations through PORTA to CPU1.  CPU2 sends one bit to the buzzer for violations through PORTA.  Figure 1 shows the specific connections and figure 6 outlines the control lines.  In addition to the data sent to CPU1, the second CPU also outputs other (excess) data to the HyperTerm.  This includes the maximum value (out of 255) received over the last sample window, the indexes of the two beats used to determine the BPM, the actual BPM, and the high and low threshold values which are settable by the buttons attached to PORTB.  The data is updated every 2 seconds, just like the BPM value.  Typical HyperTerminal output is shown in the Appendix.

Things We Tried That Didn’t Work

Our project was very successful as a whole, but there were a few things that did not work out as we had originally planned.  First, we planned to display the high/low violation values on the TV.  The problem with this is that the buttons to set the high/low values are attached to CPU2 on the STK500.  We did not have enough output ports left to send two 8bit numbers to CPU1.  Because there was no easy way to synchronize the CPUs we settled on using HyperTerm to display the high/low violation values. We did, however, print “HIGH” or “LOW” on the screen if the respective violations occurred.

Second, we wanted to blast the digitally filtered heartbeat signal to the television.  The filtering would have to be done on CPU1 since there is too much data to transfer it from CPU2 to CPU1.  Since the trace on the TV “rolls” across the screen, a significant amount of logic had to be inserted to keep track of the “edge” of the data trace.  The filter would need to start on the edge, wrap around the screen and continue back to the edge so that the data is filtered in order.  When we implemented this, the code was just too bulky, and interfered with the timing of the TV output.  We decided to settle for displaying the heartbeat signal that was amplified and filtered in an analog fashion on the television. 

Lastly, we wanted to implement automatic gain control (AGC), but realized that it was not very useful or necessary.  In addition, it is nice to see the relative strength of different heartbeat signals.  This was explained in the above section, Hardware/Software Tradeoffs.

Results

Speed of Execution

In CPU1, our digital stethoscope draws the heartbeat trace to the television with a delay imperceptible to humans.  Since CPU2 uses a sampling window to determine the BPM, there is an inherent 2 second delay between updates of the heart rate.   The HyperTerm is updated every 2 seconds as well.  All other components of our project execute with no perceptible delay and no concurrency issues ever arose.

Accuracy

Our design was able to achieve a good level of accuracy, considering the cheap parts used and simple structure of design.  The signal to noise ratio for the amplified input (which was then sampled), was around 5:1, so the heartbeat displayed on the screen looks good.  Figure 9 shows a typical trace on the TV screen.  On occasion, some stray pixels were left on the screen, probably due to the timing of our CPU1 code.  We were not able to entirely eliminate this, but it does not happen very often and we modified the code to solve this problem and make it happen as seldom as possible.  When the digital stethoscope is put into stop mode and then started again these imperfections are cleared when the screen is initialized.  On the whole, though, our trace is of very good quality, as is demonstrated below. 

Figure 9

Accuracy for our BPM calculation was discussed in the software design section.  In general, we found that our digital stethoscope was accurate in the range of around 50-180 BPM.  We used a function generator to help determine these bounds.  In addition, BPM is calculated every 2 second from the time between two successive peaks and then that time per beat is converted into BPM.  Consequently, the actual BPM is only updated every 2 seconds.

Our digital stethoscope works most reliably with a strong heartbeat signal.  If a signal is not very strong, it may be hard to detect peaks because of inherent noise.  Thus, on a few occasions, we found it necessary to get out of our chairs in the lab and run up and down the stairs a few times to keep our blood moving.  It’s quite amazing how much this makes a difference.  Finding the right place for the end piece of stethoscope is also very important.  The strongest and most reliable place on the body that we found was the neck.  We had limited success on the heart when blood flow was good.  The wrist rarely worked at all.  Additionally, you must be very still and quiet to use our device.  Talking or movement of the stethoscope produces output far above the voltage rail of the amplifier, and the resulting heartbeat trace and BPM will not be displayed accurately.  This is however inherent to any stethoscope and not unique to our project; if an old-fashioned stethoscope moves around or the patient talks when it is on his/her neck there will be mostly noise.

Safety Enforcement and Interference

Our design is inherently safe for several reasons.  First, the only contact our device has with the user is the end piece of the stethoscope; it is placed against his/her neck or chest.  This is as safe as using a normal stethoscope.  Additionally, we use no high voltages or currents in any of our circuits.  Humans are protected from this anyway since the plastic tubing connecting the microphone and the stethoscope end piece are insulators and do not conduct any current.

Our chip to chip communication is purely wired, so our design does not interfere in any way with other groups’ designs.

Usability

Our digital stethoscope is easy to use.  The most difficult part of using the digital stethoscope is to find the best place to measure your heartbeat.  This is typically on your neck, but the chest as also well has worked in the lab.  Additionally, people with heartbeats that are not very strong will have a harder time finding the right place and getting a good display of their heartbeat on the television.  If you are having trouble getting a good display of your heartbeat, you can always run up and down the stairs or do something equivalent to raise your heart rate and blood flow.  The increase in heart rate and stronger beats will make a better display. 

Others ECE 476 students have had no problems working our project after a simple explanation; our design is user friendly.   

Conclusions

Analysis of Results

We were very happy with the final results of our project.  The finished product met our expectations and worked as well as we could have hoped.  Our project demonstrates what can be done using Mega32 MCUs with some additional circuitry and components.  The display of the heartbeat is very good and the BPM calculations are accurate for a wide range of heart rates.  The proper placement of the end piece on the body—in order to get a good signal—can be finicky at times, but with some practice, you can find a good spot easily, and it will give excellent results.

If we had more time, there are some additions to the project that we would attempt.  First, we would try to improve the low end BPM calculation accuracy, without delaying the BPM displayed on the television when unnecessary.  We would try to go about this by altering when we stored ADC values in the array, so that the peak of the heartbeat would be near the beginning of the array and the beat would be properly detected.  Second, we would try to make our digital stethoscope work from more points on the body.  This would likely entail implementing additional amplification for certain parts of the body and possibly additional filters for different sources for noise.  Third, we would try to output a smoother signal on the television, similar to the signal we used calculated BPM.  It was not implemented in our project because it screwed up the timing, but we may be able to implement it, although it would be difficult, if we spent significant time and modified some of the structure of our CPU1 code. 

Standards & Intellectual Property

As discussed above, much of the TV output code inside CPU1 was provided for us in the ECE476 lab 4 by Cornell Professor Bruce Land.  Additionally, the debounce state machine for the buttons attached to CPU2 was derived from Professor Land’s lectures.  Other than that, the code used in this project is of our own design and is not part of the public domain.  Our design is a simple and interesting implementation of a digital stethoscope, but we do not feel that there are any patent opportunities because commercial solutions are much more specialized and specific, and patents are very expensive to obtain.  Because of the general nature of our project, we do not feel that we infringed on any other existing intellectual property.

Ethical Considerations

In all stages of design, implementation and testing, we complied with the IEEE code of ethics.  Five points from the IEEE code of ethics are referenced below, followed by an explanation of how we complied with them.

Point one: to accept responsibility in making engineering decisions consistent with the safety, health and welfare of the public, and to disclose promptly factors that might endanger the public or the environment

Our project is safe and users are not at risk when using our digital stethoscope.  The digital stethoscope can alert the user through a buzzer if his/her heart rate falls outside of a safety zone that the user can define.  Also, a trained professional may be able to identify problems with heartbeats by viewing the display from our project onto the television.  Our project works to promote health and welfare of the public and does nothing to harm people.

Point three: to be honest and realistic in stating claims or estimates based on available data

From the beginning, we had a realistic view of the project in mind and set goals accordingly.  We made every possible effort to obtain reliable data and report it in an objective fashion.  We stand behind all claims in this report, as they are honest and reliable. 

Point seven: to seek, accept, and offer honest criticism of technical work, to acknowledge and correct errors, and to credit properly the contributions of others

On this website, we have properly credited the contributions of others.  At several points in the project, we asked Professor Land his opinion about what characteristics are most important for our microphone or how to make a circuit better when we wanted the opinion of someone with more experience and expertise.  This advice and criticism helped to make our project better, and we are grateful for it.  There were also errors made in our project, such as incorrect connections or flawed code, but all the errors we encountered were acknowledged, debugged and fixed.

Point nine: to avoid injuring others, their property, reputation, or employment by false or malicious action

No one was injured in any way during the completion of the project.  We were respectful of other’s property, space, and need to use lab benches.

Point ten: to assist colleagues and co-workers in their professional development and to support them in following this code of ethics

During this project, our colleagues and co-workers were fellow ECE 476 students and our partners.  We tried to be as helpful as possible when other groups asked us questions or showed us their projects.  The project was completed earlier and the end result was better because we worked together as a group and had high standards for each other’s work.  In addition to assisting in professional development, we also supported each other in following the IEEE code of ethics.  For example, we made a conscious effort to both make sure we presented our project honestly and objectively and cited sources properly.

Appendix

Other figures and diagrams

Below is a general state machine diagram for a button debouncer.  This is implemented in code for each of the buttons used.  There are slight modifications to increment/toggle variables on a button push, and also to make sure the action does not happen more than once per push.


Below is a general diagram of how the Mega32 was implemented on the whiteboard.  This diagram can be seen in the photo in figure 5.


The following are some additional pictures that we took of our project.  They include Aaron demonstrating how to hold the stethoscope end piece to his neck, typical HyperTerminal output, and an overall view of our two whiteboards and the STK500.

Aaron doing a demonstration

Typical Hyperterm output


Overall Project View

References

Datasheet for the Atmel Mega32.  Available:  http://instruct1.cit.cornell.edu/courses/ee476/AtmelStuff/full32.pdf




Code

CPU1
//************************************************************
// EE 476 FINAL PROJECT CPU1 CODE
// Aaron Davis (amd43) and Brandon Richter (bcr5)
//************************************************************


#pragma regalloc-    //I allocate the registers myself
#pragma optsize-     //optimize for speed

//include necessary files                   
#include <Mega32.h>  
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <delay.h>  

//cycles = 63.625 * 16 Note NTSC is 63.55
//but this line duration makes each frame exactly 1/60 sec
//which is nice for keeping a realtime clock
#define lineTime 1018

#define begin {
#define end   }

// define top and bottom of screen
#define ScreenTop 30
#define ScreenBot 230

//NOTE that v1 to v8 and i must be in registers! 
register char v1 @4;
register char v2 @5;
register char v3 @6;
register char v4 @7;
register char v5 @8;
register char v6 @9;
register char v7 @10;
register char v8 @11;
register int i @12;

#pragma regalloc+

char syncONsyncOFF;
int LineCount;
int time;     
char bpm//beats per minute from CPU2

// ADC
char time_offsettime_countertime_index;
char values[128], values_old[128];
char run_state;     

//loop vars
unsigned char j;
int sum;

char screen[1600], t, ts[10];

// strings we print to the screen
char cu1[]="DIGITAL";
char cu2[]="STETHOSCOPE";   
char cu3[]="BPM";
char hl1[]="OK  ";
char hl2[]="HIGH";
char hl3[]="LOW ";
char hl4[]="ERR ";
char sec[]=" SEC";
                                     
//Point plot lookup table  
//One bit masks
flash char pos[8]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};                         

//define some character bitmaps
//5x7 characters
flash char bitmap[38][7]={
          //0
          0b01110000,
          0b10001000,
          0b10011000,
          0b10101000,
          0b11001000,
          0b10001000,
          0b01110000,
          //1
          0b00100000,
          0b01100000,
          0b00100000,
          0b00100000,
          0b00100000,
          0b00100000,
          0b01110000, 
          //2
          0b01110000,
          0b10001000,
          0b00001000,
          0b00010000,
          0b00100000,
          0b01000000,
          0b11111000,
        //3
          0b11111000,
          0b00010000,
          0b00100000,
          0b00010000,
          0b00001000,
          0b10001000,
          0b01110000,
          //4
          0b00010000,
          0b00110000,
          0b01010000,
          0b10010000,
          0b11111000,
          0b00010000,
          0b00010000,
          //5
          0b11111000,
          0b10000000,
          0b11110000,
          0b00001000,
          0b00001000,
          0b10001000,
          0b01110000,
          //6
          0b01000000,
          0b10000000,
          0b10000000,
          0b11110000,
          0b10001000,
          0b10001000,
          0b01110000,
          //7
          0b11111000,
          0b00001000,
          0b00010000,
          0b00100000,
          0b01000000,
          0b10000000,
          0b10000000,
          //8
          0b01110000,
          0b10001000,
          0b10001000,
          0b01110000,
          0b10001000,
          0b10001000,
          0b01110000,
          //9
          0b01110000,
          0b10001000,
          0b10001000,
          0b01111000,
          0b00001000,
          0b00001000,
          0b00010000, 
          //A
          0b01110000,
          0b10001000,
          0b10001000,
          0b10001000,
          0b11111000,
          0b10001000,
          0b10001000,
          //B
          0b11110000,
          0b10001000,
          0b10001000,
          0b11110000,
          0b10001000,
          0b10001000,
          0b11110000,
          //C
          0b01110000,
          0b10001000,
          0b10000000,
          0b10000000,
          0b10000000,
          0b10001000,
          0b01110000,
          //D
          0b11110000,
          0b10001000,
          0b10001000,
          0b10001000,
          0b10001000,
          0b10001000,
          0b11110000,
          //E
          0b11111000,
          0b10000000,
          0b10000000,
          0b11111000,
          0b10000000,
          0b10000000,
          0b11111000,
          //F
          0b11111000,
          0b10000000,
          0b10000000,
          0b11111000,
          0b10000000,
          0b10000000,
          0b10000000,
          //G
          0b01110000,
          0b10001000,
          0b10000000,
          0b10011000,
          0b10001000,
          0b10001000,
          0b01110000,
          //H
          0b10001000,
          0b10001000,
          0b10001000,
          0b11111000,
          0b10001000,
          0b10001000,
          0b10001000,
          //I
          0b01110000,
          0b00100000,
          0b00100000,
          0b00100000,
          0b00100000,
          0b00100000,
          0b01110000,
          //J
          0b00111000,
          0b00010000,
          0b00010000,
          0b00010000,
          0b00010000,
          0b10010000,
          0b01100000,
          //K
          0b10001000,
          0b10010000,
          0b10100000,
          0b11000000,
          0b10100000,
          0b10010000,
          0b10001000,
          //L
          0b10000000,
          0b10000000,
          0b10000000,
          0b10000000,
          0b10000000,
          0b10000000,
          0b11111000,
          //M
          0b10001000,
          0b11011000,
          0b10101000,
          0b10101000,
          0b10001000,
          0b10001000,
          0b10001000,
          //N
          0b10001000,
          0b10001000,
          0b11001000,
          0b10101000,
          0b10011000,
          0b10001000,
          0b10001000,
          //O
          0b01110000,
          0b10001000,
          0b10001000,
          0b10001000,
          0b10001000,
          0b10001000,
          0b01110000,
          //P
          0b11110000,
          0b10001000,
          0b10001000,
          0b11110000,
          0b10000000,
          0b10000000,
          0b10000000,
          //Q
          0b01110000,
          0b10001000,
          0b10001000,
          0b10001000,
          0b10101000,
          0b10010000,
          0b01101000,
          //R
          0b11110000,
          0b10001000,
          0b10001000,
          0b11110000,
          0b10100000,
          0b10010000,
          0b10001000,
          //S
          0b01111000,
          0b10000000,
          0b10000000,
          0b01110000,
          0b00001000,
          0b00001000,
          0b11110000,
          //T
          0b11111000,
          0b00100000,
          0b00100000,
          0b00100000,
          0b00100000,
          0b00100000,
          0b00100000,
          //U
          0b10001000,
          0b10001000,
          0b10001000,
          0b10001000,
          0b10001000,
          0b10001000,
          0b01110000,
          //V
          0b10001000,
          0b10001000,
          0b10001000,
          0b10001000,
          0b10001000,
          0b01010000,
          0b00100000,
          //W
          0b10001000,
          0b10001000,
          0b10001000,
          0b10101000,
          0b10101000,
          0b10101000,
          0b01010000,
          //X
          0b10001000,
          0b10001000,
          0b01010000,
          0b00100000,
          0b01010000,
          0b10001000,
          0b10001000,
          //Y
          0b10001000,
          0b10001000,
          0b10001000,
          0b01010000,
          0b00100000,
          0b00100000,
          0b00100000,
          //Z
          0b11111000,
          0b00001000,
          0b00010000,
          0b00100000,
          0b01000000,
          0b10000000,
          0b11111000,
          //figure1
          0b01110000,
          0b00100000,
          0b01110000,
          0b10101000,
          0b00100000,
          0b01010000,
          0b10001000,
          //figure2
          0b01110000,
          0b10101000,
          0b01110000,
          0b00100000,
          0b00100000,
          0b01010000,
          0b10001000};


//================================
//3x5 font numbers, then letters
//packed two per definition for fast
//copy to the screen at x-position divisible by 4
flash char smallbitmap[41][5]={
          //0
        0b11101110,
          0b10101010,
          0b10101010,
          0b10101010,
          0b11101110,
          //1
          0b01000100,
          0b11001100,
          0b01000100,
          0b01000100,
          0b11101110,
          //2
          0b11101110,
          0b00100010,
          0b11101110,
          0b10001000,
          0b11101110,
          //3
          0b11101110,
          0b00100010,
          0b11101110,
          0b00100010,
          0b11101110,
          //4
          0b10101010,
          0b10101010,
          0b11101110,
          0b00100010,
          0b00100010,
          //5
          0b11101110,
          0b10001000,
          0b11101110,
          0b00100010,
          0b11101110,
          //6
          0b11001100,
          0b10001000,
          0b11101110,
          0b10101010,
          0b11101110,
          //7
          0b11101110,
          0b00100010,
          0b01000100,
          0b10001000,
          0b10001000,
          //8
          0b11101110,
          0b10101010,
          0b11101110,
          0b10101010,
          0b11101110,
          //9
          0b11101110,
          0b10101010,
          0b11101110,
          0b00100010,
          0b01100110,
          //:
          0b00000000,
          0b01000100,
          0b00000000,
          0b01000100,
          0b00000000,
          //=
          0b00000000,
          0b11101110,
          0b00000000,
          0b11101110,
          0b00000000,
          //blank
          0b00000000,
          0b00000000,
          0b00000000,
          0b00000000,
          0b00000000,
          //A
          0b11101110,
          0b10101010,
          0b11101110,
          0b10101010,
          0b10101010,
          //B
          0b11001100,
          0b10101010,
          0b11101110,
          0b10101010,
          0b11001100,
          //C
          0b11101110,
          0b10001000,
          0b10001000,
          0b10001000,
          0b11101110,
          //D
          0b11001100,
          0b10101010,
          0b10101010,
          0b10101010,
          0b11001100,
          //E
          0b11101110,
          0b10001000,
          0b11101110,
          0b10001000,
          0b11101110,
          //F
          0b11101110,
          0b10001000,
          0b11101110,
          0b10001000,
          0b10001000,
          //G
          0b11101110,
          0b10001000,
          0b10001000,
          0b10101010,
          0b11101110,
          //H
          0b10101010,
          0b10101010,
          0b11101110,
          0b10101010,
          0b10101010,
          //I
          0b11101110,
          0b01000100,
          0b01000100,
          0b01000100,
          0b11101110,
          //J
          0b00100010,
          0b00100010,
          0b00100010,
          0b10101010,
          0b11101110,
          //K
          0b10001000,
          0b10101010,
          0b11001100,
          0b11001100,
          0b10101010,
          //L
          0b10001000,
          0b10001000,
          0b10001000,
          0b10001000,
          0b11101110,
          //M
          0b10101010,
          0b11101110,
          0b11101110,
          0b10101010,
          0b10101010,
          //N
          0b00000000,
          0b11001100,
          0b10101010,
          0b10101010,
          0b10101010,
          //O
          0b01000100,
          0b10101010,
          0b10101010,
          0b10101010,
          0b01000100,
          //P
          0b11101110,
          0b10101010,
          0b11101110,
          0b10001000,
          0b10001000,
          //Q
          0b01000100,
          0b10101010,
          0b10101010,
          0b11101110,
          0b01100110,
          //R
          0b11101110,
          0b10101010,
          0b11001100,
          0b11101110,
          0b10101010,
          //S
          0b11101110,
          0b10001000,
          0b11101110,
          0b00100010,
          0b11101110,
          //T
          0b11101110,
          0b01000100,
          0b01000100,
          0b01000100,
          0b01000100,
          //U
          0b10101010,
          0b10101010,
          0b10101010,
          0b10101010,
          0b11101110,
          //V
          0b10101010,
          0b10101010,
          0b10101010,
          0b10101010,
          0b01000100,
          //W
          0b10101010,
          0b10101010,
          0b11101110,
          0b11101110,
          0b10101010,
          //X
          0b00000000,
          0b10101010,
          0b01000100,
          0b01000100,
          0b10101010,
          //Y
          0b10101010,
          0b10101010,
          0b01000100,
          0b01000100,
          0b01000100,
          //Z
          0b11101110,
          0b00100010,
          0b01000100,
          0b10001000,
          0b11101110,
        // characters we defined       
        // -
        0b00000000,
        0b00000000,
        0b11101110,
        0b00000000,
        0b00000000,
        // .
        0b00000000,
        0b00000000,
        0b00000000,
        0b01100110,
        0b01100110
          };

//==================================
//plot one point
//at x,y with color 1=white 0=black 2=invert
#pragma warn-
void video_pt(char x, char y, char c)
begin  
         
          #asm
          ;  i=(x>>3) + ((int)y<<4) ;   the byte with the pixel in it

          push r16
          ldd r30,y+2                  ;get x
          lsr r30
          lsr r30
          lsr r30                      ;divide x by 8
          ldd r12,y+1                  ;get y
          lsl r12                      ;mult y by 16
          clr r13
          lsl r12
          rol r13
          lsl r12
          rol r13
          lsl r12
          rol r13
          add r12, r30       ;add in x/8
         
          ;v2 = screen[i];   r5
        ;v3 = pos[x & 7];  r6
          ;v4 = c            r7
          ldi r30,low(_screen)
          ldi r31,high(_screen)
          add r30, r12
          adc r31, r13
          ld r5,Z            ;get screen byte
          ldd r26,y+2                  ;get x
          ldi r27,0
          andi r26,0x07           ;form x & 7
          ldi r30,low(_pos*2) 
          ldi r31,high(_pos*2)
          add r30,r26
          adc r31,r27
          lpm r6,Z
          ld r16,y           ;get c
      
       ;if (v4==1) screen[i] = v2 | v3 ;
       ;if (v4==0) screen[i] = v2 & ~v3;
       ;if (v4==2) screen[i] = v2 ^ v3 ;
      
       cpi r16,1
       brne tst0
       or  r5,r6
       tst0:
       cpi r16,0
       brne tst2
       com r6
       and r5,r6
       tst2:
       cpi r16,2
       brne writescrn
       eor r5,r6
       writescrn:
          ldi r30,low(_screen)
          ldi r31,high(_screen)
          add r30, r12
          adc r31, r13
          st Z, r5           ;write the byte back to the screen
         
          pop r16
          #endasm
      
end
#pragma warn+

//==================================
// put a big character on the screen
// c is index into bitmap
void video_putchar(char x, char y, char c) 
begin
    v7 = x;
    for (v6=0;v6<7;v6++)
    begin
        v1 = bitmap[c][v6];
        v8 = y+v6;
        video_pt(v7,   v8, (v1 & 0x80)==0x80); 
        video_pt(v7+1, v8, (v1 & 0x40)==0x40);
        video_pt(v7+2, v8, (v1 & 0x20)==0x20);
        video_pt(v7+3, v8, (v1 & 0x10)==0x10);
        video_pt(v7+4, v8, (v1 & 0x08)==0x08);
    end
end

//==================================
// put a string of big characters on the screen
void video_puts(char x, char y, char *str)
begin
          char i ;
          for (i=0; str[i]!=0; i++)
          begin 
                   if (str[i]>=0x30 && str[i]<=0x3a)
                             video_putchar(x,y,str[i]-0x30);
                   else video_putchar(x,y,str[i]-0x40+9);
                   x = x+6; 
          end
end
     
//==================================
// put a small character on the screen
// x-cood must be on divisible by 4
// c is index into bitmap
void video_smallchar(char x, char y, char c) 
begin
          char mask;
          i=((int)x>>3) + ((int)y<<4) ;
          if (x == (x & 0xf8)) mask = 0x0f;     //f8
          else mask = 0xf0;
         
          screen[i] =    (screen[i] & mask) | (smallbitmap[c][0] & ~mask);
          screen[i+16] = (screen[i+16] & mask) | (smallbitmap[c][1] & ~mask);
        screen[i+32] = (screen[i+32] & mask) | (smallbitmap[c][2] & ~mask);
        screen[i+48] = (screen[i+48] & mask) | (smallbitmap[c][3] & ~mask);
          screen[i+64] = (screen[i+64] & mask) | (smallbitmap[c][4] & ~mask);
end 

//==================================
// put a string of small characters on the screen
// x-cood must be on divisible by 4
void video_putsmalls(char x, char y, char *str)
begin
          char i ;
          for (i=0; str[i]!=0; i++)
          begin 
                   if (str[i]>=0x30 && str[i]<=0x3a && str[i]!=0x20)
                             video_smallchar(x,y,str[i]-0x30);
                // these else if statements were added for the characters we defined
                   else if (str[i]==0x2d) video_smallchar(x,y,str[i]-6);
                   else if (str[i]==0x2e) video_smallchar(x,y,str[i]-6);
                   else if (str[i]==0x20) video_smallchar(x,y,str[i]-20); 
              else video_smallchar(x,y,str[i]-0x40+12);
                   x = x+4; 
          end
end
      
//==================================
//plot a line
//at x1,y1 to x2,y2 with color 1=white 0=black 2=invert
//NOTE: this function requires signed chars  
//Code is from David Rodgers,
//"Procedural Elements of Computer Graphics",1985
void video_line(char x1, char y1, char x2, char y2, char c)
begin  
          int e;
          signed char dx,dy,j2, temp;
          signed char s1,s2, xchange;
        signed char x,y;
       
          x = x1;
          y = y1;
          dx = cabs(x2-x1);
          dy = cabs(y2-y1);
          s1 = csign(x2-x1);
          s2 = csign(y2-y1);
          xchange = 0;  
          if (dy>dx)
          begin
                   temp = dx;
                   dx = dy;
                   dy = temp;
                   xchange = 1;
          end
          e = ((int)dy<<1) - dx;  
          for (j2=0; j2<=dx; j2++)
          begin
                   video_pt(x,y,c) ;
                   if (e>=0)
                   begin
                             if (xchange==1) x = x + s1;
                             else y = y + s2;
                             e = e - ((int)dx<<1);
                   end
                   if (xchange==1) y = y + s2;
                   else x = x + s1;
                   e = e + ((int)dy<<1);
          end
end

//==================================
//return the value of one point
//at x,y with color 1=white 0=black 2=invert
char video_set(char x, char y)
begin
          //The following construction
          //detects exactly one bit at the x,y location
          i=((int)x>>3) + ((int)y<<4) ; 
    return ( screen[i] & 1<<(7-(x & 0x7)));    
end

//==================================
//This is the sync generator and raster generator. It MUST be entered from
//sleep mode to get accurate timing of the sync pulses
#pragma warn-
interrupt [TIM1_COMPA] void t1_cmpA(void) 
begin
  //start the Horizontal sync pulse   
  PORTD = syncON;    
  //update the curent scanline number
  LineCount ++ ;  
  //begin inverted (Vertical) synch after line 247
  if (LineCount==248)
  begin
    syncON = 0b00100000;
    syncOFF = 0;
  end
  //back to regular sync after line 250
  if (LineCount==251)       
  begin
    syncON = 0;
    syncOFF = 0b00100000;
  end 
  //start new frame after line 262
  if (LineCount==263)
  begin
     LineCount = 1;
  end 

  delay_us(2); //adjust to make 5 us pulses
  //end sync pulse
  PORTD = syncOFF;  

  if (LineCount<ScreenBot && LineCount>=ScreenTop)
    begin
      
       //compute byte index for beginning of the next line
       //left-shift 4 would be individual lines
       // <<3 means line-double the pixels
       //The 0xfff8 truncates the odd line bit
       //i=(LineCount-ScreenTop)<<3 & 0xfff8; //
      
       #asm
       push r16
       lds   r12, _LineCount
       lds   r13, _Linecount+1
       ldi   r16, 30
       sub  r12, r16
       ldi  r16,0
       sbc  r13, r16
       lsl  r12
       rol  r13
       lsl  r12
       rol  r13
       lsl  r12   
       rol  r13
       mov  r16,r12
       andi r16,0xf0
       mov  r12,r16
       pop r16
       #endasm
       
       //load 16 registers with screen info
       #asm
       push r14
       push r15
       push r16
       push r17
       push r18
       push r19
       push r26
       push r27
      
       ldi  r26,low(_screen)   ;base address of screen
       ldi  r27,high(_screen)  
       add  r26,r12            ;offset into screen (add i)
       adc  r27,r13
       ld   r4,x+                   ;load 16 registers and inc pointer
       ld   r5,x+
       ld   r6,x+ 
       ld   r7,x+
       ld   r8,x+
       ld   r9,x+
       ld   r10,x+ 
       ld   r11,x+
       ld   r12,x+
       ld   r13,x+
       ld   r14,x+ 
       ld   r15,x+
       ld   r16,x+  
       ld   r17,x+ 
       ld   r18,x+
       ld   r19,x
      
       pop  r27
       pop  r26
       #endasm 

       delay_us(3);  //adjust to center image on screen
      
       //blast 16 bytes to the screen  
       #asm
       ;but first a macro to make the code shorter 
       ;the macro takes a register number as a parameter
       ;and dumps its bits serially to portD.6  
       ;the nop can be eliminated to make the display narrower
       .macro videobits ;regnum
        BST  @0,7
          IN   R30,0x12
          BLD  R30,6
          nop
          OUT  0x12,R30 
         
          BST  @0,6
          IN   R30,0x12
          BLD  R30,6
          nop
          OUT  0x12,R30
         
          BST  @0,5
          IN   R30,0x12
          BLD  R30,6
          nop
          OUT  0x12,R30
         
          BST  @0,4
          IN   R30,0x12
          BLD  R30,6
          nop
          OUT  0x12,R30
         
          BST  @0,3
          IN   R30,0x12
          BLD  R30,6
          nop
          OUT  0x12,R30
         
          BST  @0,2
          IN   R30,0x12
          BLD  R30,6
          nop
          OUT  0x12,R30
         
          BST  @0,1
          IN   R30,0x12
          BLD  R30,6
          nop
          OUT  0x12,R30
         
          BST  @0,0
          IN   R30,0x12
          BLD  R30,6
          nop
          OUT  0x12,R30
       .endm    
       
          videobits r4 ;video line -- byte 1
        videobits r5 ;byte 2 
        videobits r6 ;byte 3
        videobits r7 ;byte 4
        videobits r8 ;byte 5
        videobits r9 ;byte 6
        videobits r10 ;byte 7
        videobits r11 ;byte 8
        videobits r12 ;byte 9
        videobits r13 ;byte 10 
        videobits r14 ;byte 11
        videobits r15 ;byte 12
        videobits r16 ;byte 13
        videobits r17 ;byte 14
        videobits r18 ;byte 15
        videobits r19 ;byte 16
          clt   ;clear video after the last pixel on the line
          IN   R30,0x12
          BLD  R30,6
          OUT  0x12,R30
                                                                            
       pop r19
       pop r18
       pop r17
       pop r16 
       pop r15
       pop r14
       #endasm
             
    end                     

  if ((time_counter == time_offset) && (run_state < 3))  //ready for sample
  begin
    values[time_index++] = (ADCH>>2) + 20;  // store the (adjusted) value      
    if (time_index == 127) time_index = 0; // start over
    time_counter = 0; // start over
  end                 
  time_counter++; //increment
  ADCSR.6 = 1; //starts adc

end 
#pragma warn+

/********************************************/
void init_screen(void)
begin 
  TIMSK = 0x00; // turns off timers
  for (sum=0; sum<1600; sum++)
    screen[sum] = 0; // blank screen
  sum = 0;

  for (j=0; j<=127; j++)
  begin
    values[j] = 150;
    values_old[j] = 150;
    video_pt(j, values_old[j], 1);   // draw the horizontal line at 0 to start with so we can invert later
  end 
  video_pt(127,150,0);       
   
  //Print "DIGITAL"
  video_puts(5,3,cu1);
  //Print "STETHOSCOPE"
  video_puts(57,3,cu2);      
  //Print "BPM"
  video_putsmalls(4,93,cu3);

  //side lines
  #define tv_width 126
  video_line(0,0,0,99,1);
  video_line(tv_width,0,tv_width,99,1);

  //top line & bottom lines
  video_line(0,0,tv_width,0,1);
  video_line(0,99,tv_width,99,1);
  video_line(0,11,tv_width,11,1);
  video_line(0,89,tv_width,89,1);    

  //Division line on bottom bar
  video_line(40,89,40,99,1);
  video_line(68,89,68,99,1);
  video_line(95,89,95,99,1);
         
  TIMSK = 0x10; // turns on timer1    
end

/*********************************************/
void initialize(void)
begin
  //init timer 1 to generate sync
  OCR1A = lineTime         //One NTSC line
  TCCR1B = 9;                //full speed; clear-on-match
  TCCR1A = 0x00;   //turn off pwm and oc lines
  TIMSK = 0x10;              //enable interrupt T1 cmp

  //init ports
  DDRD = 0xf0;               //video out and switches
  //D.5 is sync:1000 ohm + diode to 75 ohm resistor
  //D.6 is video:330 ohm + diode to 75 ohm resistor
  DDRA = 0x00;            // input from output of op-amp on A.0, A3,4 are run/stop and 1/2 sec from CPU2, A5,6 are high/low condition from CPU2
  DDRC = 0x00;            // bpm input from CPU2


  //init ADC stuff
  ADMUX = 0b01100000;     // ADLAR = 1, read only from ADCH (low 2 bits in ADCL)
                          // read from channel 0, single ended
  ADCSR = 0b10000110;     // 7: ADenable, 6: ADstart, 5: freerun, 4: ADdone INT flag
                          // 3: AD INT enable, 2-0: clk
                          // (110 -> 250KHz AD Clk; 19.2KHz effective sample rate) 

  time_counter = 1;
  time_offset = 120;        // start at 1 sec timing
  time_index = 0;  
  run_state = 1;          // start on 1 sec
   /*  run_state:
       5:  off 1 sec
       6:  off 2 sec
       1:  run 1 sec
       2:  run 2 sec  */

  //init the scren
  init_screen();                     


  //initialize synch constants
  LineCount = 1;
  syncON = 0b00000000;
  syncOFF = 0b00100000; 
   
  //init software timer
  t=0;
  time=0;  
      
  //enable sleep mode
  MCUCR = 0b10000000;
  #asm ("sei");
end

//==================================
// set up the ports and timers
void main(void)
begin

  initialize();
  ADCSR.6 = 1; // turn on ADC

  //The following loop executes once/video line during lines
  //1-230, then does all of the frame-end processing
  while(1)
  begin      
    #asm ("sleep");
    //a total of 60 lines x 63.5 uSec/line x 16 cycles/uSec    
   
    if (LineCount == 231)
    begin

      if (run_state < 3)  // running
      begin              

        // blast the values
        for (j=1; j<126; j++)
        begin
          video_pt(j,values_old[j],2);       //clear the old points
          video_pt(j,values[j],2);          // write the new points       
          values_old[j] = values[j];     // save the values to be cleared next frame
        end                                                                        

        if (PINA.3 == 0)    // stop button pressed
        begin                               
          if (run_state == 1) run_state = 5;   // if stop is pressed then stop (in 1sec)
            else run_state = 6;               // stop in 2 sec mode
        end  // stop test    
      end // if running

      else  // stopped
      begin            
        if (PINA.3 == 1)    // if turned off and run button pressed, start
        begin
          if (run_state == 5) run_state = 1;      // run in 1sec
            else run_state = 2;                  //run in 2sec
          init_screen();                        // to clear screen
        end

        if (PINA.4 == 0) // put in 2 sec mode
        begin              
          time_offset = 240;    // set the time offset for timing width
          // print 2sec on bottom of screen
          video_smallchar(72,93,2);
          video_putsmalls(76,93,sec);
        end

        if (PINA.4 == 1) // put in 1 sec mode     
        begin                     
          time_offset = 120;     // set the time offset for timing width
          //print 1sec on bottom of screen
          video_smallchar(72,93,1);
          video_putsmalls(76,93,sec);
        end
      end    // if turned off

      //update the second clock
      if (++t>59)
      begin       
     
        //indicate high/low condition on screen
        if (PINA.5 == 1)  video_putsmalls(44,93,hl3); // "LOW"
          else if (PINA.6 == 1) video_putsmalls(44,93,hl2);  // "HIGH"
          else if (PINC == 0x00) video_putsmalls(44,93,hl4); // "ERR"
          else video_putsmalls(44,93,hl1); // "OK"

        //put the bpm (portC) on the screen
        bpm = PINC;
        sprintf(ts,"%03d",bpm);
        video_putsmalls(20,93,ts);
       
        // update timer on screen
        t=0;
        time = time + 1;         
        sprintf(ts,"%05d",time);
        video_putsmalls(100,93,ts);
      end
    end  //line 231
  end  //while
end  //main
CPU2
//************************************************************
// EE 476 FINAL PROJECT CPU2 CODE
// Aaron Davis (amd43) and Brandon Richter (bcr5)
//************************************************************

//include necessary files                  
#include <Mega32.h>
#include <stdio.h>
#include <delay.h>
#include <stdlib.h>

//timeout values for each task
#define t1 50
#define t2 2

#define begin {
#define end }

//define pushstates
#define NoPush 1
#define MaybePush 2
#define Pushed 3
#define MaybeNoPush 4

//define modes
#define mode1 1
#define mode2 2

//define # samples for bpm calculation
#define numSamps 1000


char b0_PushState, b1_PushState, b2_PushState, b3_PushState,b5_PushState,b6_PushState,b7_PushState;
char b5_PushFlag,b6_PushFlag,b7_PushFlag;


//the task subroutines
void initialize(void);
void debounce(void);
void detect(void);

unsigned char time1,time2;

char mode,mute,stopgo//variables for modes in statemachine
unsigned char Ain//voltage from output of op-amp
unsigned char max_value;  //used for peak detection
int start, stop; //variables for bpm calculation
unsigned int i//loop variable
unsigned char bpm, low, high;  //bpm, low violation value, high violation value        
int arr_index//array index for values array
float alpha; //digital lpf parameter
char values[numSamps]; // store 1000 adc values taken every 2ms for 2 sec of data
      
//**********************************************************
//timer 0 compare ISR
interrupt [TIM0_COMP] void timer0_compare(void)
begin     
  //Decrement the times if it is not already zero
  if (time1>0)     --time1;   
  if (time2>0) --time2;
end 

//**********************************************************      
//Entry point and task scheduler loop
void main(void)                                             
begin 
  initialize(); 

  while (1)
  begin            

    if (time1==0)  debounce();   // run the state machine

    if(time2==0) 
    begin
      time2 = t2;                 // update t2
      values[arr_index++]= ADCH; //get value 
      ADCSR.6 = 1;               //start next adc           
    end                                     
   
    if (arr_index-1 == numSamps//values array is full
    begin
      detect();      // calculate bpm
      arr_index = 0; // start at beginning of values array
    end     
   
   
    //send messages to cpu1 to be displayed on screen if there is a low or high violation    
    if((bpm<low) && (bpm != 0))  PORTA.5 = 1;       //low violation
    else PORTA.5 = 0;                               // no low violation
   
    if(bpm>high)  PORTA.6 = 1;                      // high violation
    else PORTA.6 = 0;                               // no high violation
   
    //turn on buzzer when appropriate
    if (  ((bpm<low)||(bpm>high)) && (bpm != 0) && (mute==mode2)    )
      PORTA.7=1;        //buzzer on
    else PORTA.7=0;     //buzzer off
   
   
    //output BPM
    PORTC = bpm;
  end
end       

//**********************************************************
//calculate bpm
void detect(void)
begin                     
  TCCR0=0;     //turn off timer0 interrupt
  alpha = 0.8; // set LPF parameter
  for (i=1;i<numSamps;i++)
  begin
    values[i] = (char)(255- (alpha*values[i] + (1-alpha)*values[i-1]));  //flip and digital LPF
  end        

  // find max in values
  max_value = 0;
  for(i=0;i<numSamps;i++)
  begin
    if(values[i]>max_valuemax_value=values[i];         
  end

  //find 1st point where values becomes > (0.8)*max_value
  start=0;
  for(i=7;i<numSamps;i++)
  begin
    if(start!=0) break;
    if(values[i]>values[i-6]) //increasing
    begin
      if(values[i] > 128 + (max_value-128)*.8)   start=i// >(0.8)*max_value
    end
  end

  //find next point where values crosses (0.8)*max_values
  // note:skip 150 values, which corresponds to 0.3 sec of data, before staring to look for stop in case of noise
  stop=0;
  for(i=start+150;i<numSamps;i++)
  begin
    if(stop!=0) break;
    if(values[i]>values[i-6]) //increasing
    begin
      if((values[i]) > 128 + (max_value-128)*.8)  stop=i// <(0.8*max_value
    end
  end            

  if (max_value<135)   // no beat threshold
  begin
    start = 0;
    stop = 0;
  end
      
  //calculate bpm
  if((start!=0) && (stop!=0))
    bpm = (char)(60/(.002*(stop-start)));     // calculation for bpm
  else if ((start==0) && (stop==0)) bpm = 0;  // no peak detected      

  //put the info to the hyperterm
  printf("Max: %d     Start: %d    Start: %d\r\n", max_value, start, stop);
  if (mute==mode2) printf("Buzzer on\r\n"); else printf("Buzzer Muted\r\n");
  if (stopgo==mode1) printf("Monitor Running"); else printf("Monitor off");
  if (mode==mode1) printf("  1 second\r\n"); else printf("   2 second\r\n");
  printf("Low Value:  %d         High Value:  %d \r\n", low,high);                                               
  printf("Current BPM:  %d", bpm);
  printf("\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n");

  TCCR0=0b00001011;        
end 
//**********************************************************
//debounce state machine
void debounce(void)

begin 
  time1 = t1;
  switch (b0_PushState)      // decrease low
  begin
      case NoPush:
        if (~PINB == 0b00000001)
          b0_PushState = MaybePush;
        else
          b0_PushState = NoPush;
      break;

      case MaybePush:
        if (~PINB == 0b00000001)
          b0_PushState = Pushed;
        else
          b0_PushState = NoPush;
      break;

      case Pushed:
        if (~PINB == 0b00000001)
        begin
          b0_PushState = Pushed;
          low--;
        end
        else
          b0_PushState = MaybeNoPush;
      break;
    
      case MaybeNoPush:
        if (~PINB == 0b00000001)
          b0_PushState = Pushed;
        else
          b0_PushState = NoPush;
      break;
  end

  switch (b1_PushState)      // increase low
  begin
      case NoPush:
        if (~PINB == 0b00000010)
          b1_PushState = MaybePush;
        else
          b1_PushState = NoPush;
      break;

      case MaybePush:
        if (~PINB == 0b00000010)
          b1_PushState = Pushed;
        else
          b1_PushState = NoPush;
      break;

      case Pushed:
        if (~PINB == 0b00000010)
        begin
          b1_PushState = Pushed;
          low++;
        end
        else
          b1_PushState = MaybeNoPush;
      break;
    
      case MaybeNoPush:
        if (~PINB == 0b00000010)
          b1_PushState = Pushed;
        else
          b1_PushState = NoPush;
      break;
  end

  switch (b2_PushState)      // decrease high
  begin
      case NoPush:
        if (~PINB == 0b00000100)
          b2_PushState = MaybePush;
        else
          b2_PushState = NoPush;
      break;

      case MaybePush:
        if (~PINB == 0b00000100)
          b2_PushState = Pushed;
        else
          b2_PushState = NoPush;
      break;

      case Pushed:
        if (~PINB == 0b00000100)
        begin
          b2_PushState = Pushed;
          high--;
        end
        else
          b2_PushState = MaybeNoPush;
      break;
    
      case MaybeNoPush:
        if (~PINB == 0b00000100)
          b2_PushState = Pushed;
        else
          b2_PushState = NoPush;
      break;
  end

  switch (b3_PushState)      // increase high
  begin
      case NoPush:
        if (~PINB == 0b00001000)
          b3_PushState = MaybePush;
        else
          b3_PushState = NoPush;
      break;

      case MaybePush:
        if (~PINB == 0b00001000)
          b3_PushState = Pushed;
        else
          b3_PushState = NoPush;
      break;

      case Pushed:
        if (~PINB == 0b00001000)
        begin
          b3_PushState = Pushed;
          high++;
        end
        else
          b3_PushState = MaybeNoPush;
      break;
    
      case MaybeNoPush:
        if (~PINB == 0b00001000)
          b3_PushState = Pushed;
        else
          b3_PushState = NoPush;
      break;
  end         

  switch (b5_PushState)      // mute for buzzer: mode1=mute, mode2=buzzer on
  begin
      case NoPush:
        if (~PINB == 0b00100000)
          b5_PushState = MaybePush;
        else
          b5_PushState = NoPush;
      break;

      case MaybePush:
        if (~PINB == 0b00100000)
          b5_PushState = Pushed;
        else
          b5_PushState = NoPush;
      break;

      case Pushed:
        if (~PINB == 0b00100000)
        begin
          b5_PushState = Pushed;  
          if (b5_PushFlag == 0) 
          begin
            if (mute == mode1)
              mute = mode2;
            else
              mute = mode1;
            b5_PushFlag = 1;
          end
        end
        else
          b5_PushState = MaybeNoPush;
      break;
    
      case MaybeNoPush:
        if (~PINB == 0b00100000)
          b5_PushState = Pushed;
        else
        begin
          b5_PushState = NoPush;
          b5_PushFlag = 0;
        end
      break;
  end


  switch (b6_PushState)      // toggle 1 or 2 sec mode: mode1=1sec, mode2=2sec
  begin
      case NoPush:
        if (~PINB == 0b01000000)
          b6_PushState = MaybePush;
        else
          b6_PushState = NoPush;
      break;

      case MaybePush:
        if (~PINB == 0b01000000)
          b6_PushState = Pushed;
        else
          b6_PushState = NoPush;
      break;

      case Pushed:
        if (~PINB == 0b01000000)
        begin
          b6_PushState = Pushed;
          if (b6_PushFlag==0)
          begin
            if (mode == mode1)
              mode = mode2;
            else
              mode = mode1;
            b6_PushFlag = 1;
          end
        end
        else
          b6_PushState = MaybeNoPush;
      break;
    
      case MaybeNoPush:
        if (~PINB == 0b01000000)
          b6_PushState = Pushed;
        else      
        begin
          b6_PushState = NoPush;
          b6_PushFlag = 0;
        end
      break;
  end

  switch (b7_PushState)      // toggle on/off: mode1=run, mode2=stop
  begin
      case NoPush:
        if (~PINB == 0b10000000)
          b7_PushState = MaybePush;
        else
          b7_PushState = NoPush;
      break;

      case MaybePush:
        if (~PINB == 0b10000000)
          b7_PushState = Pushed;
        else
          b7_PushState = NoPush;
      break;

      case Pushed:
        if (~PINB == 0b10000000)
        begin
          b7_PushState = Pushed;
          if (b7_PushFlag == 0)
          begin
            if(stopgo == mode1)
              stopgo = mode2;
            else
              stopgo = mode1;
            b7_PushFlag = 1;
          end
        end
        else
          b7_PushState = MaybeNoPush;
      break;
    
      case MaybeNoPush:
        if (~PINB == 0b10000000)
          b7_PushState = Pushed;
        else
        begin
          b7_PushState = NoPush;
          b7_PushFlag = 0;
        end
      break;
  end

  //send info to CPU1
  if (stopgo == mode1)  PORTA.3 = 1; //run
    else PORTA.3 = 0;                //stop
  if (mode == mode1) PORTA.4 = 1;    //1sec
    else PORTA.4 = 0;                //2sec

end


//**********************************************************
//Set it all up
void initialize(void)
begin
  // set up the ports
  DDRB=0x00;      // PORT B is button inputs
  DDRA=0b11111110;      // PORT A.0 is input from stethoscope, A1-7 are output
  DDRC=0xff;      // PORT C is output to cpu1              

           
  // set up timer 0
  TIMSK=2;                   //turn on timer 0 cmp match ISR, and T1 cmp match A ISR
  OCR0 = 250;      //set the compare reg to 250 time ticks, giving a 1ms time interval for interrupts
  //prescalar to 64 and turn on clear-on-match
  TCCR0=0b00001011;         


  // init ADC stuff
  ADMUX = 0b11100000;     // ADLAR = 1, read only from ADCH (low 2 bits in ADCL)
                          // read from channel 0, single ended
  ADCSR = 0b10000111;     // 7: ADenable, 6: ADstart, 5: freerun, 4: ADdone INT flag
                          // 3: AD INT enable, 2-0: clk
                          // (110 -> 250KHz AD Clk; 19.2KHz effective sample rate) 
                          // (111 -> 125KHz AD Clk; 9.6KHz effective sample rate) 

  // init the task timer
  time1=t1;
  time2=0;

  // init mode
  mode = mode1;   //1sec mode
  stopgo = mode1; //run    
  mute = mode1;  //muted

  // init the state variables
  b0_PushState = NoPush;  b1_PushState = NoPush;  b2_PushState = NoPush;  b3_PushState = NoPush;   b5_PushState = NoPush;  b6_PushState = NoPush;  b7_PushState = NoPush;
  b5_PushFlag = 0;  b6_PushFlag = 0; b7_PushFlag = 0;

  //initialize high/low violation values
  low = 50;
  high = 100;

  //initialize bpm
  bpm=0;

  // initialize these variables to 0
  Ain =0;
  max_value=0;
  start=0;
  stop=0;
  i=0;        
  arr_index = 0;

  //init the UART
  UCSRB = 0b00011000;      // enable Tx and Rx (no ints)
  UBRRL = 103;            // using a 16 MHz crystal (9600 baud)
  putsf("Program Starting!!!\r\n\r\n");  // print to hyperterm when starting
               
    //crank up the ISRs
  #asm
     sei
  #endasm

end


0 comments:

Post a Comment

Share your knowledge

Related Posts Plugin for WordPress, Blogger...

Popular Projects

program for Dual DAC 8051 Microcontroller Based DC Motor Control A Microcontroller Based Turbidity Meter A m -Controller Based Thermostat ASCII to BCD conversion in 8051 AT90LS8515 Digital Message Machine Audio Frequency Response Analyzer Audio Homing Robot Automated Juice Mixer Automated Pet Feeder Autonomous Car Autonomous Parallel Parking RC Car Autonomous Search Robot Autonomous Tank Autonomous Vehicle Contrast Following Rover Autonomous navigating robot BCD number to ASCII in 8051 Balance Bot Blind Bot Blood Pressure Monitor Bloodshed Dev-C++ 5 Compiler/IDE Breath Alcohol Tester Converters on TI MSP430 CrossStudio MSP430 IDE Design of a Real-Time Digital Guitar Tuner Digital Oscilloscope Digital Stethoscope Digital clock project using PIC16C54A microcontroller Digital thermometer ECG monitoring system GPS Data Logger with Wireless Trigger Handwriting Recognition Systm Home Security System Home energy managment IAR Embedded Workbench IDE INFRARED TRACKING SYSTEM IntelliBOT Laser Communications System Line following van MSP-EXP430FG4618 Development Tool and the eZ430 kits MSP430FG4618 device implement a Buzzer tone generator MSP430FG4618 device implement a Real Time Clock MSP430FG4618 device implement a voltage ramp generator MSP430FG4618 device present a message on the LCD Basic Microcontroller(8051) Lab Mivo- RFID based mobile payment system Multi-Zone Fire Alarm System PC based temperature control PIC 16f877 RPM Meter PIC16C54 dual dice electronic project circuit PIC16F84A digital thermometer microcontroller project PIC16F886 horn driver PWM motor contoller with MSP430 Program Block data transfer in 8051 Program to add two BCD numbers in 8051 Program to check whether a 4th bit of a byte is 1 Program to convert ASCII to hex in 8051 Program to count from 0-9 in 8051 Program to count number of 1's in a given data byte in 8051 Program to divide an 8 bit no by another 8 bit number in 8051 Program to find largest of n numbers in 8051 Program to find the LCM of two numbers in 8051 Program to find the square of an 8 bit number in 8051 Program to generate 50msec delay in 8051 Program to implement BCD counter to count from 0-99 in 8051 Program to implement BCD counter to count from 99-0 in 8051 Program to interchange two blocks of data in 8051 Program to multiply 16 bit number by 8 bit number in 8051 Program to search an element in an array in 8051 Program to sort an array of 10 elements in 8051 Programming the ez430 Proximity Security System RAMP wave in 8051 RC Car Controller RObo Dog Radio-controlled Truck Retina color tracker Robotic Arm Controller with GUI Robotic Car Traction Control Safety-sensor vehicle Security Entrance System Self-Powered Solar Data Logger Snake Arm Ultrasonic Positioning Control System Store FFh if 1 Super Train Controller TI MSP430 Microcontrollers Timers on the MSP430 TouchPad Drawing Board Ultra-Sonic Parking Assistant Ultrasonic Parking Controller Ultrasonic Range finder Voice Activated Alarm Clock Voice Recognition Robotic Car Voting Machine Weather Station Web-Monitored Thermostat Wireless Drawing Device Wireless Telemetry Wireless message Communicator Write a C program to display the code of the key pressed in 8051 Zigbee Wireless Relay Control and Power Monitoring System add two multibyte numbers in 8051 convert a decimal number to hex number in 8051 convert an 8bit Hex number to decimal number in 8051 convert hex number to ASCII number in 8051 eZ430-F2013 Development Tool use SD16_A ADC eZ430-RF2500 Development Tool use ADC10 else store 00 in the same location in 8051 find the GCF of two numbers in 8051 find the average of 10 numbers in 8051 generate Fibonacci series in 8051 metal detector project microcontroller using IAR Embedded Workbench program for Elevator Interface in 8051 program for Stepper motor interface in 8051 spectrum analyser square wave in 8051 triangle wave in 8051 voice recognition security system

Sites U missed

Hint

Open Pictures in new page by right click on it, if it is not shown full image.