Kade Cox Email: Phone: (435) 232-5504
Kade Cox
Email: [email protected]
Phone: (435) 232-5504
Cole Palmer
Email: [email protected]
Phone: (801) 589-3581
Garrett Rasmussen
Email: [email protected]
Phone: (801) 529-3944
Anthony Thomas
Email: [email protected]
Phone: (435) 787-8723
29 April 2010
Don Cripps
Department of Electrical and Computer Engineering
Utah State University
Dr. Cripps,
We are submitting to you our final design report entitled Improved Bike Trainer. This
report is being sent to you to fulfill our requirements for our senior or capstone project.
The report documents our approach, design, and testing of our modified bike trainer
project. It includes design analysis, specifications, optimizations as well as our
implementation. If you have any questions, comments or concerns, feel free to contact
any of us. Thank you for your time and consideration of our project.
Sincerely,
Kade Cox
Cole Palmer
Garrett Rasmussen
Anthony Thomas
Improved Bike Trainer
A project for ECE5770
By Kade Cox, Cole Palmer, Garrett Rasmussen, and Anthony Thomas
X
Dr. Paul Wheeler
Department of Electrical and Computer Engineering
Utah State University
X
Donald Cripps
Department of Electrical and Computer Engineering
Utah State University
Date
Date
Contents
List of Tables .......................................................................................................................................... iv
List of Figures......................................................................................................................................... iv
Introduction............................................................................................................................................ 1
Problem Statement and Design Objectives............................................................................................ 3
Cycling Resistances ................................................................................................................................. 3
Air Resistance ..................................................................................................................................... 3
Rolling Resistance ............................................................................................................................... 5
Gravitational Resistance ..................................................................................................................... 5
Total Resistance and Power Output ................................................................................................... 6
Speed Sensor .......................................................................................................................................... 6
Circuit Design ...................................................................................................................................... 6
Physical Connections .......................................................................................................................... 8
Resistance Measurement Unit ............................................................................................................... 9
Resistance Measurement Circuit........................................................................................................ 9
Physical Setup ................................................................................................................................... 11
Calibration ........................................................................................................................................ 12
Resistance Unit ..................................................................................................................................... 13
Performance Optimization and Design of Resistance Unit (Eddy-Current Brake) ........................... 13
Implementation/Operation and Assessment of Resistance Unit..................................................... 16
Mechanical considerations ............................................................................................................... 17
ii
Micro-Controller ................................................................................................................................... 18
C8051F020 Features ......................................................................................................................... 19
Programming .................................................................................................................................... 20
Micro-Controller Testing .................................................................................................................. 21
User Interfaces ..................................................................................................................................... 22
Cost Estimation..................................................................................................................................... 23
Project Management ............................................................................................................................ 24
Team Members ................................................................................................................................ 24
Tasks completed ............................................................................................................................... 25
Tasks to be completed...................................................................................................................... 25
Gantt chart .......................................................................................................................................... 26
Conclusion ............................................................................................................................................ 26
Works Cited .......................................................................................................................................... 28
Appendix A – ImprovedBicycleTrainer.C .............................................................................................. 29
Appendix – GUI Code............................................................................................................................ 43
iii
List of Tables
Table 1: Croll for different surfaces cyclists often travel on..................................................................... 5
Table 2: Strain Gauge Calibration Points .............................................................................................. 12
Table 3 : Project Costs .......................................................................................................................... 23
List of Figures
Figure 1 : Heart Rate Zones by Age (Cardio Tracker) ............................................................................. 2
Figure 2: EE-SB5 Sensor (Omron) ........................................................................................................... 6
Figure 3: Phototransistor Circuit (Sharp)................................................................................................ 7
Figure 4: Resistance Monitoring Circuit ................................................................................................. 9
Figure 5: Precision Amplifier Configuration ......................................................................................... 10
Figure 6: Flex arm and strain gauge for measuring resistance............................................................. 11
Figure 7: Resistance Force Vs. Force Sensor ........................................................................................ 12
Figure 8: Schematic of amplifier circuit for the coils. ........................................................................... 17
Figure 9: Trainer mounted on plywood base ....................................................................................... 18
Figure 10: C8051F02x Block Diagram ................................................................................................... 19
Figure 11: Gantt chart .......................................................................................................................... 25
iv
Introduction
In many climates it is not possible for cyclists to train outdoors year round. For these cyclists they
have a variety of options available to them for training when they cannot go outside. None of them are
pleasant however, and their effectiveness depends upon the individual. The most common methods of
training when the weather is bad are to ride a stationary bicycle or to mount a bicycle to a trainer and
ride indoors.
A stationary bicycle (also known as a spin bike) is an exercise device that usually consists of a frame
with a large flywheel attached. The flywheel is then attached to the pedal system by a belt or chain.
The resistance is generally created by felt pads clamped onto the flywheel. One disadvantage to spin
bikes is that a cyclist must adjust them so that they fit in a similar manner to their regular bicycle.
A bicycle trainer is a frame that attaches to the rear axle of a standard bicycle. When the bicycle is
attached to the trainer, the bicycles rear wheel is in contact with a drum that can rotate. The drum is
attached to a resistance unit. This setup allows a cyclist to use their regular bicycle for both indoor and
outdoor training.
Both stationary bicycles and bike trainers suffer from the same basic problems. The resistance that
they present is not an accurate representation of the resistance of riding outdoors. On most stationary
bicycles it is not possible coast, when attempted, stationary bicycles have been known to hurl their user
across the room. Coasting is possible on bicycle trainers, however it is more comparable to coasting
uphill than on a horizontal surface. Stationary bikes do not have a speed sensor, and on trainers the
speed reported from the cyclist’s bike computer is not an accurate representation of what they have
accomplished. Training on a stationary bike or bicycle trainer is meant to be done with a heart rate
monitor. There are five zones that indicate what kind of exercise that the cyclist is performing, see
Figure 1. These zones are based on age and weight. However, these zones do not take into account the
1
fitness level of the individual, and heart rate has
been shown to fluctuate from day to day. As an
alternative to training exclusively with heart rate
data, many cyclists have began to equip their
bicycles with power meters. The advantage to
training with a power meter is that it removes
Figure 1 : Heart Rate Zones by Age (Cardio Tracker)
many variables for the cyclist. When cyclists base
their performance off of speed and heart rate, they do not take into account environmental factors such
as terrain variation and wind speed. By measuring their power, cyclists get a value that does not
fluctuate dramatically as conditions change.
The resistance units on available bike trainers are not sufficient for indoor training. The resistances
that a cyclist experiences when riding are determined largely by the cyclist’s weight and cross sectional
area. Trainers and stationary bicycles take neither of these into account. Stationary bicycles and trainers
try to produce a normalized resistance curve that is close to what most cyclists’ experience. While this
creates an effective resistance for working out, it does not accurately recreate the resistances that a
cyclist would experience outdoors.
It is also not possible for cyclists to compare indoor to outdoor workouts. Outdoors the terrain is
changing constantly, creating a near infinite amount of resistance values for the cyclist to experience
which are always changing. Indoor trainer have relatively few resistance settings, most only have one,
though a few trainers have as many as nine individual resistance settings. However no matter which
trainer or stationary bicycle you use, it is not the same as riding outdoors.
2
Problem Statement and Design Objectives
Cyclists need a better way to compare their indoor and outdoor workouts. The objective of this
project is to create an indoor bicycle trainer that will better replicate the experience of riding outdoors.
The trainer will have the fallowing traits:
•
Coasting will be possible
•
Resistance will be based on individual cyclists
•
Power and speed data will be logged
•
Resistance will mimic outdoor riding resistances
By creating a trainer that will do the above items, we will enable cyclists to train more effectively in
the off season. They will come into the spring races with strong legs and plenty of power.
Cycling Resistances
In order to accurately recreate the resistances that a cyclist experiences outdoors, we must be able
to calculate what those values must be. There are three main sources of resistance a cyclist
experiences, these are: Air Resistance, Rolling Resistance, and Gravitational resistance.
Air Resistance
The air resistance (Fair) is largely a function of the air speed and the cyclist’s frontal area. The
formula for the air resistance is given below.
1
𝐹𝑎𝑖𝑟 = 𝐴𝐶𝑑𝑟𝑎𝑔 𝐷𝑎𝑖𝑟 𝑉𝑎𝑖𝑟 2
2
Where A is the frontal area of the cyclist and the bicycle(m2), Cdrag is the coefficient of drag(unit less), Dair
is the density of the air (kg/m3), and Vair is the velocity of the air(m/s). The area we are going to have to
3
determine, the coefficient of drag is typically 0.9 for a cyclist and bicycle, Vair will be the bicycle speed as
in this project we are not simulating wind, though this could be implemented latter with simple vector
addition. The density of air is given by the equation:
𝐷𝑎𝑖𝑟 = 𝑃/(𝑅𝑇)
Where P is the Air Pressure in Pascals, R is the gas constant in J/(kg °K), and T is the temperature in °K. R
turns out to be 287.058 for dry air. T is given by the equation:
𝑇 = 273.15 + 𝐶
Where C is the temperature in °C. The pressure P is given by the equation:
𝑃 = 101325(1 − 2.25577 10−5 ℎ)5.2588
Where h is the elevation above sea level in meters. We are using the elevation of Logan, UT for our
project which is approximately 4,534 ft which is approximately 1381.963 meters.
A is approximated using the equation:
2
𝐴 = ℎ𝑠𝑤
3
Where h is the height of the cyclist, and sw is the shoulder width of the cyclist. This is approximating the
area of the torso of the cyclist to be a rectangle and computing a rectangular area. This approximation
will not hold in the real world and further study must be perused to find a more accurate representation
of a cyclists frontal area.
4
Rolling Resistance
The rolling resistance (Froll ) is proportional to the weight of the cyclist and the bike and the surface the
bike is being ridden on. The rolling resistance is given by the following formula:
𝐹𝑟𝑜𝑙𝑙 = 9.8𝑊𝐶𝑟𝑜𝑙𝑙
Where 9.8 is the acceleration due to gravity (m/s2), W is the combines weight of the cyclist and
bicycle (kg), and Croll is the coefficient of rolling resistance(unit less). Typical values for the coefficient of
rolling resistance are listed on the table below.
Croll
.001
.002
.004
.008
Surface
Wooden Track
Smooth Concrete
Asphalt Road
Rough Paved Road
Table 1: Croll for different surfaces cyclists often travel on
For our design we always assume the course is over an asphalt road, so Croll is .004.
Gravitational Resistance
Gravitational resistance is the force a rider experiences when climbing hills. It is also the force that aids
the cyclist when going downhill. The gravitational resistance is the only resistance in our system that can
have a negative value. The gravitational resistance is given by the equation:
𝐹𝑔𝑟𝑎𝑑 = 9.8𝑊𝐺
Where 9.8 is the acceleration due to gravity(m/s2), W is the combined weight of the cyclist and
bicycle(kg), and G is the grade of the hill(unit less). The grade of the hill is the change in vertical distance
divided by the change in horizontal distance. The grade can be a negative value which will result in a
negative gravitational resistance. When this occurs we are simulating a cyclist riding downhill.
5
Total Resistance and Power Output
From the Gravitational, Rolling, and Air resistances the total resistance R is calculated using the
equation:
𝑅 = 𝐹𝑎𝑖𝑟 + 𝐹𝑟𝑜𝑙𝑙 + 𝐹𝑔𝑟𝑎𝑑
Using R we can now find the power output of the cyclist using the equation:
𝑃𝑐𝑦𝑐 = 𝑉𝑅
Where Pcyc is the power output of the cyclist(w), V is the velocity of the cyclist (m/s) and R is the
total resistance calculated above.
Speed Sensor
A speed sensor is necessary for our design. For our system to work properly we must know how far
we traveled so that we know when to change the resistance on the resistance unit. . To find the
distance traveled Kade designed a speed sensor. Knowing the speed for a given amount of time, we can
calculate the distance.
Circuit Design
An EE-SB5 optical phototransistor sensor was used for the
design of the speed sensor. The SB5 sensor works by reflecting
infrared light off of a surface onto the phototransistor. In order for
this reflective manner to work, the sensor must be within 5mm of
the reflective object. The SB5 sensor consists of an infrared LED
6
Figure 2: EE-SB5 Sensor
(Omron)
and a phototransistor as shown in the figure to the right. A and K junctions represent the anode and the
cathode of the LED respectively. The C and the E junctions represent the collector and the emitter of
the transistor respectively.
The phototransistor portion of the speed sensor circuit is
set up in the manner shown to the right. In order for the
transistor to operate in switching mode, I(Rc)> Vcc. Vcc is 5v
and a 50 kOhm potentiometer is used for Rc so that Rc may be
adjusted for different light conditions. Vout is connected to
one of the interrupt ports of the microcontroller. In this
circuit, when light is on the transistor it is on and Vout is pulled
to ground. When light is not on the transistor, the transistor is
Figure 3: Phototransistor Circuit
(Sharp)
not on and Vout is pulled up to Vcc.
The LED portion of the circuit was also straightforward. A 5 volt voltage source is connected to a
440 Ohm resistor. This resistor was then connected to the anode of the LED, and the cathode of the LED
was connected to ground. Verification of LED was obtained by looking at the LED while it was on with a
digital camera.
The Sensor was connected to the rest of the circuit via a Cat-4 cable. The sensor was mounted to
the trainer within 5 mm of the flywheel. A black stripe is painted onto the flywheel as to interrupt the
surface.
7
Figure 4: Total Speed Sensor Circuit
Physical Connections
The SB5 sensor is connected to a PVC arm that is bolted to the
frame of the trainer. The sensor sits inside a hollow out portion of the
arm to limit the sensors field of view. This will limit the amount of noise
detected from ambient light sources. The cable is connected to the rest
of the circuit through [name] connectors. The output of the speed
sensor circuit is connected directly to one of the microcontroller’s
external interrupt ports. A strip of black masking tape is placed on the
flywheel to created a variation in the flywheel surface.
8
Resistance Measurement Unit
In order to verify the cyclist is experiencing the proper amount of resistance at any given time, we
need a method to measure the amount of resistance the cyclist is experiencing . in order to do this we
designed a resistance monitoring subsystem utilizing a strain gauge.
Resistance Measurement Circuit
The resistance measurements are made by measuring the voltage across a wheatstone bridge.
Because these changes in voltage are so small, it is necessary to amplify this voltage difference. Kade
designed the circuit below to measure the strain on the gauge
Figure 4: Resistance Monitoring Circuit
The strain gauge provides a resistance of 350 Ohms when it is experiencing zero deflection. This
strain gauge is used in one o f the upper branches of the wheatstone bridge circuit. For each of the
other three branches of the wheatstone bridge circuit, a 1 kOhm resistor and a 1 kOhm potentiometer
are placed in parallel to each other. By placing the resistor and potentiometer in parallel with each
9
other, we are able to fine tune the wheatstone bridge circuit to where we have zero volts across the
bridge when there is no load on the bridge.
The voltage on the bridge is connected to a LH0044 precision amplifier. This amplifier is set up in an
instrumentation amplifier configuration as shown in the figure below.
Figure 5: Precision Amplifier Configuration
I used two 2.5 MOhm resistors connected in the manner shown in the figure above. By using two
2.5 MOhm resistors, the amplifier provided a gain of 1000. A 3.3 zener diode is also connected between
the output of the amplifier and ground to prevent too much voltage being supplied to the
microcontroller.
The power for this circuit had to be set up in an interesting manner due to the precision amplifiers
need of both rails to function properly. As such, 2 volts was treated as ground when designing this
circuit. By using 2 volts as the ground this enabled Kade to use a 4 volt power supply where 4 volts
acted like a +2 Volt source and the ground acted like a -2 volt source. Because of this power supply
arrangement the output of the circuit when there is no strain on the gauge is 2 volts. Care had to be
taken to ensure that as the load is placed on the strain gauge, the voltage on the output would go down.
To get this to occur we merely had to make sure the strain gauge deflected in the proper direction.
10
Physical Setup
The resistance measurement unit relies on two basic physical principals. First, that a length of material
firmly connected at one end and lose at the other will experience internal strain as a force is applied to
the material, also called the flex arm. The second principal is Newton's Third Law, which states that for
any action there is an equal but opposite reaction.
Newton's Third law is applied between the flywheel and the eddy current brake. Since the eddy current
brake creates a resistance to the motion of the flywheel, the flywheel in turn creates a force on the eddy
current brake. Our eddy current brake was mounted so that it could rock forward when experiencing
this force. As it rocks forward it lifts off of the flex arm and reduces the stress in the arm which is
measured by the strain gauge. Thus, if the magnet is applying no resistance to the flywheel the stress in
the flex arm will be at its maximum and as the resistance is increased the stress in the flex arm
decreases.
Figure 6: Flex arm and strain gauge for measuring resistance.
11
Calibration
The strain gauge was calibrated using a series of weights placed on the edge of the magnet. By
using known weights we are able to calculate the amount of force that is required for a given output on
the strain gauge. Several weights were used and then fitted to a linear curve in Excel. The data can be
seen in the table below and the curve in the following figure.
Voltage From
Strain Gauge
circuit (mV)
1441
1456
1523
1515
1485
Force on
Resistance
Unit (N)
0
4.3365
13.328
10.682
1.764
Table 2: Strain Gauge Calibration Points
Forec on Unit (N)
Resistance Force Vs. Force Sensor
Ouput
16
14
12
10
8
6
4
2
0
-21420
Data Set
Linear (Data Set)
y = 0.1429x - 206.1
R² = 0.7899
1440 1460 1480 1500 1520
Strain Circuit Output (mV)
1540
Figure 7: Resistance Force Vs. Force Sensor
12
Resistance Unit
The resistance unit on the original trainer was a permanent magnet with little adjustment. Other
bike trainers employ things such as fans or drums of viscous fluid in order to provide resistance to the
rider. However, in order to enable the resistance to be adjustable we had several options such as:
•
Adjust the permanent magnet with an electrical motor
•
Create a mechanical resistance unit that could be adjusted by a motor
•
Use a motor as a generator and adjust the load to increase the resistance
•
Create an eddy-current brake using an electromagnet rather than a permanent magnet
Our decision to use the electromagnet for the eddy-current brake was based on two things: first,
because it would be similar to the original trainer and second, because we thought it would be easy to
implement the electromagnet.
Performance Optimization and Design of Resistance Unit (Eddy-Current
Brake)
The eddy-current brake is based on the physical principal that eddy-currents are induced in a
conducting material when a magnetic field, or B-field, is applied to that material. The eddy-currents in
turn produce a B-field that is equal to, but opposite in direction of the first B-field. These two B-fields
then create an attractive force between the two materials. This attractive force also attempts to keep a
flywheel from spinning according to equation 1.
→
→
→
F = q v× B
Equation 1: Lorentz Force
13
This equation states that a force is exerted on a charge moving through a B-field in the direction of
the cross-product of the velocity of the charge and the B-field. Since the charge is negative it uses the
left-hand rule.
Eddy-current brakes have not been well studied and therefore it was difficult finding information on
their operation and characteristics. We did find one study from J.H. Wouterse [1] which gives an
equation for a magnetic induction brake (Eddy-current bake) relating the power dissipated by the brake
to other physical and electrical properties of the brake.
π 2 2 2
P=
D dB0 v
4ρ
Equation 2: Power Dissipation in Eddy Brake
In equation 2, P is the power dissipated by the brake, D is the diameter of the soft-iron pole, d is the
thickness of the flywheel, B0 is the strength of the magnetic field, v is the linear velocity of the flywheel,
and rho (ρ) is the permeability of the specific resistance of the flywheel material.
Several of these factors were set by the choice we made to alter the specific trainer that we decided
to use, namely the thickness of the flywheel (16mm) which we increased slightly by adding an additional
aluminum plate to one side, and the material it was made from (Aluminum) which we would have
chosen anyway because of its’ properties. The velocity will be constantly changing due to the biker, but
a top speed was calculated to be around 200 rotations/sec. That speed translates into about 10.3 m/s
as a linear speed. The power dissipation was decided by the fact that most bikers can only sustain about
100 W of power and Tour de France riders can only sustain about 500 W of power over a distance. We
decided to shoot for the Tour de France rider, setting our power dissipation at a little above 500W. This
14
left two variables to be decided, the diameter of the soft-iron pole and the magnetic field strength.
Because we had a desire to make the field as small as possible, thus minimizing the current needed, we
made the diameter of the soft-iron pole as large as possible for our flywheel, which turned out to be
about 30.4mm. After doing some calculations it was determined that our field needed to be about 0.4
Teslas.
Designing the magnet required that the soft-iron core itself would not be saturated before reaching
the desired value. If it was the case that the iron core would become saturated by the magnetic field
before reaching this value it would be necessary to use two magnets or use a laminated stack of cores
such as the ones that are used in most transformers. After doing research it was discovered that softiron saturates at slightly less than 2 T and we should therefore not need two magnets.
A laminated stack is also used in transformers to keep the core cool. When running an alternating
signal through the wires of a transformer it causes eddy-currents in the core of the transformer. Those
eddy-currents cause a heating effect and can damage the transformer. Since we decided on using a
pulse-width modulation signal we did think about possibly using a laminated stack to minimize heating
effects and if this device were going to be produced commercially it would probably be a good idea.
However, with the mass of metal used for the magnet and the limited time it will be used we decided
that it would be OK to use a solid core.
A magnetic field in an electromagnet is produced by a current running through the windings of the
magnet and is proportional to both the number of windings and the current through the windings. After
using excel to do some calculations it was found that we could achieve the desired magnetic field using
about 0.5 km (0.33 miles) of wire wound around our core in about 3600 turns and a 12 VDC supply to
power the magnet. This would give us less than 0.5 A of current through the wire and still produce a
strong enough B-field.
15
Implementation/Operation and Assessment of Resistance Unit
The soft-iron core of the electromagnet was built from a slice of an iron pipe. The pipe had
dimensions of 146 mm inner diameter and 101 mm outer diameter. A 38.1 mm long slice was cut from
the pipe. The given dimensions gave us the approximate area for the pole that we need.
A grove was milled out that would accommodate our flywheel. The grove unfortunately had to be
milled out several times and increased past the gap size that we wanted. This was mainly due to the
fact that the bike trainer we decided to use did not have a very stable base and the flywheel was both
uneven and capable of shifting from side to side as the rider pedaled. We ended up with a gap size of
around 20 mm.
The core of the magnet was wound in electrical tape to keep it insulated from the magnet wire
wrappings. Additional magnetic tape was used between different layers of wire but was not necessary.
(We had thought of the possibility of having parallel coils). An original wrap of 200 turns was made for
testing. On top of that the main coil was wound. The winding of the main coil took approximately 9
hours to complete.
An amplifier circuit was then built for the coil (see Figure 8). The circuit is controlled by a PWM
signal from the microcontroller board.
16
Figure 8: Schematic of amplifier circuit for the coils.
Mechanical considerations
Due to the fact that the electromagnet is the size that it is, and the weight that it is, it became
necessary to build a base for the trainer to support the magnet and to keep the trainer from moving and
damaging either the flywheel or the magnet. This base was constructed from plywood, aluminum, and
steel, and the magnet holder was made from rock maple. Two mounts were made for the force sensor
from PVC, one attached to each steel tube leg of the magnet support. A photo of the base can be seen
in Figure 9.
17
Figure 9: Trainer mounted on plywood base
Micro-Controller
The micro-controller we used was the Silicon Labs, Inc. C8051F020. Some of its features are listed
below. The micro-controller is used to capture information from the trainer speed sensor and strain
gauge. It then communicates this data to the computer. The computer calculates an updated resistance
level based on this information which is then sent to the micro-controller. This level is used to output a
PWM signal to the electromagnet.
18
C8051F020 Features
•
2 ADC sub-systems (12/8-bit)
•
16-bit Programmable Counter Array, capable of 16 or 8-bit PWM output
•
64 port digital I/O
•
24.1184 MHz system clock (using external oscillator)
•
22 vectored interrupt sources
•
Two UART serial ports
•
5 general purpose 16-bit timers
Figure 10: C8051F02x Block Diagram
http://www.silabs.com/products/mcu/mixed-signalmcu/Pages/C8051F02x.aspx
19
Programming
The micro-controller was programmed in C using Silicon Labs IDE. The programs were written to the
board using the JTAG port. The entire code can be seen in the Appendix A – ImprovedBicycleTrainer.C.
The main function calls all of the initializations for the board, and then continually calculates the
measured analog input. After the initializations are complete, the micro-controller is almost purely
interrupt driven. The main interrupt service routines are INT6, ADC0, Timer0 and UART1.
INT6
Int6 is external driven interrupt and is connected to the optical speed sensor (See Appendix A, line
603 for INT6 code). Every time the speed sensor fires, a speed sensor counter is incremented and the
external interrupt flag is cleared. Every second, the speed sensor counter value is sent to the computer
and the counter is reset. Since only one byte of data is sent to the computer, the counter is limited to a
maximum value of 256.
ADC0
This interrupt keeps a running total of the analog input from the power meter until an internal
counter reaches 0 (See Appendix A, Line 568 for ADC0 code). It then posts the results in a global
variable. The main function then converts that result into mV every 50ms.
Timer0
Timer0 is set up to execute every 100ms based on the 24.1184 MHz system clock (See Appendix A,
Line 628 for Timer0 code). A counter is used to calculate when 1 second has been executed. Every
second, the counter is reset to 0, the speed sensor count and analog input from the power meter
(calculated in main) are written to a character array, UART_Buffer, and the UART1 transmit flag is set.
This causes the UART1 interrupt service routine to fire.
20
UART1
Every time this interrupt is called, it checks to see if a byte has been received or needs to be
transmitted (See Appendix A, Line 668 for UART1 interrupt code). If a byte has been received, it
immediately writes it to the Programmable Counter Array with outputs a PWM signal based on the
received byte. It clears the UART received flag. If data needs to be sent, it first clears the transmit
complete flag then outputs one byte of UART_Buffer to the computer. After each byte is sent, the
transmit complete flag is set so the interrupt service routine is called again. This routine is called until all
of the data from the UART_Buffer is sent to the computer.
Micro-Controller Testing
Testing for the micro-controller was done for each function, then with combined methods and
finally the entire system was tested as a whole. The first function tested was the speed sensor. To test
the speed sensor, a digital external interrupt had to be enabled on the board. The interrupt was set to
toggle a LED on the micro-controller every time it was called. A function generator was then connected
to the pin associated with the external interrupt. When configured correctly, the LED toggled at the
frequency input to the pin. Later, after UART serial communication was added, the interrupt was set to
increment a counter and the counter was sent to the PC. The data was then displayed in a terminal
program.
To test the Analog input, example code was used from Silicon Labs. The code was modified as
needed to changed ports and voltage reference levels. Voltage from the function generator was
connected to an Analog Input port on the board along with ground. The voltage reference was modified
to be 3.3V instead of the default 2.45V. The result from the port read on the board was output on the
PC via a terminal.
21
To test the UART serial communication, the PC opened a serial connection on the COM port
connected to the micro-controller. A byte was then sent to the computer and verified on the terminal.
A character was entered into the terminal. The micro-controller was set to store any received bytes in a
byte. After the character was sent, the byte was checked in the debugger to verify that character was
received correctly.
To test the PWM output, the PC opened a serial connection on the COM port connected to the
micro-controller. An oscilloscope was connected to the output pin of the PWM signal. The PWM used
was 8-bit so a character was sent from the PC via a terminal and written to the register used for the
PWM signal. The signal generated was verified on the oscilloscope.
The system as a whole was used with all of the above methods with slight modifications. With all
functions running, each was tested to verify accuracy and desired results.
User Interfaces
The Graphical User Interface first has the rider enter their biometric data and their bikes weight.
Once the user has confirmed their biometric data, the user can select an .xml or .tcx file containing
prerecorded data from a Garmin cycling computer. This data file is parsed to extract the elevation and
distance data for input into the system to replicate the course run on the system. The user is then able
to start running the course. Once the user starts the course, the micro-controller begins sending the
input sensor data to the computer and the computer sends the micro-controller to current resistance
value. When the computer receives data from the micro-controller it changes the speed and power
input data to Miles per Hour and Watts, respectively. After calculating the speed and power values,
these values are then saved to a data log. The computer then calculates the new resistance and if it is
different it sends this new resistance to the microcontroller. The computer can also receive commands
22
from the GUI and LCD display. The commands that can be issued by the GUI and LCD will start or stop
the course, or increase or decrease the adjusted resistance value. This adjusted resistance value is
added to the base resistance and the speed caused resistance.
The LCD displays identical data as the GUI on the computer. This data is updated every second.
Cost Estimation
The goal of this project was to produce an improved bike trainer that was competitive in cost to
lower end bike trainers. The total budget for this project was not to exceed $800 (or $200 per group
member). The project received partial funding from Ab Clements of the SMART Fellowship and from his
advisor, Dr. Chen. The cost of parts is listed in Table 3.
Part
Micro-Controller (C8051F020)
LCD Display (GLK19264-7T-1U-YG-LV)
XBee DTE Serial Board (990.006)
XBee USB Board (990.002)
Xbee Module (XB24-Z7CIT-004)
Trainer Base Parts (Wood, Screws, etc.)
Wire for electromagnet
Misc. (Capicitors, Power Regulators, etc.)
Total Cost
Table 3 : Project Costs
23
Quantity
Cost
1
1
2
1
3
1
-
$40
$86.75
$25.00
$29.50
$24.15
$60.00
$54.00
$80
Line Total
$40.00
$86.75
$50.00
$29.50
$72.45
$60.00
$54.00
$80.00
$472.70
Project Management
The project was managed by each individual team member. Each team member was given specific
responsibilities dealing with the projects and its paperwork. The work was distributed as evenly as
possible between the team members and according to their interests and expertise.
Team Members
Anthony Thomas is an Electrical Engineering major graduating in May 2010. He works for the
Engineering and Technology Education department. He has access to a machine shop and various
materials making assembly of the electromagnet and mounting frame possible. His has experience in
Garrett Rasmussen is a Computer Engineering major graduating in May 2010. He took on the role of
the microcontroller programmer and provided help with the GUI programming as needed. He has
experience in various programming languages including C, C++, C# and will be graduating with a minor in
computer science making him well qualified for this role.
Kade Cox is an Electrical Engineering major graduating in December 2010. He took on the role of the
sensor and circuit engineer as well as cycling reference. Kade has taken various Electrical Engineering
classes and was taking an Electro Optics class at the time of this class qualifying him for his role. Kade is
also an avid cyclist who has competed in the Logan to Jackson (LOTOJA) twice, is on the USU Cycling
team and the Joyride team.
Cole Palmer is a Computer Engineering major graduating in December 2010. He has taken on the
role of GUI programmer. Cole has experience in various programming languages including C, C++, C# and
will be graduating with a minor in computer science making him well qualified for his role.
24
Tasks completed
•
Research and Design
o
Resistance units
o
Speed sensors
o
GUI/LCD Interface
o
Microcontroller
o
Power measurement\
•
Assembly of Design Parts
•
Testing
o
Eddy Current Break
o
Optical Speed Sensor
o
Strain Gauge
o
MicroController
o
GUI
o
LCD
o
Miscellaneous Circuitry
o
Whole System
Tasks to be completed
•
Wireless communication with LCD and
microcontroller.
•
Graphical interpretation of data log on GUI
•
Complete manual control of resistance unit via
LCD or GUI.
25
Figure 11: Gantt chart
Gantt chart
The Gantt chart, Figure 11, shows the actual workflow of the project. An initial chart was designed
at the start of the project but it was poorly designed. Therefore, the initial design was discarded and
replaced with this Gantt chart.
Conclusion
The objective of this project was to build an improved trainer. This trainer better simulates real
world resistances experienced by cyclists. It also provides cyclists with a means of comparing their
outdoor workouts to their indoor workouts. The following paragraphs sum up some of our favorite
parts of the project.
Kade’s favorite part of this project was over ten hours spent wrapping an electromagnet, during
which time he managed to watch most of the Peter Jacksons Lord of the Rings Trilogy, and bruise the
palm of one of his hands.
Anthony’s favorite part of the project was being able to put holes in things when he was frustrated.
Garrett’s favorite part was figuring out, completely by himself, how to set pin 3.6 as an external
interrupt based solely on reading the micro-controller data sheet. He also enjoyed, probably too much,
researching unnecessary, novel parts that might be used.
Cole’s favorite part of the project was playing around with all the hardware that he has not used
before. Even though his focus was on the software for the GUI and LCD, the hardware was still quite
interesting.
26
Overall, we were pleased with how the project turned out, we had to learn about electro-magnets,
strain gauges, precision amplifiers, the 8051 micro-controller, C# GUI’s, machining, prony brakes, lasers,
and optical sensors.
27
Works Cited
28 April 2010 <http://www.google.com/imgres?imgurl=http://bp3.blogger.com/_Sxu5lwo_eA/RzaMx6gcCJI/AAAAAAAAACc/MzpGnPoAUkE/S660/Heart%2BRate%2BZone.JPG&imgrefurl=http:/
/cardiotracker.blogspot.com/&usg=__HBauPvKoDy6Ik575ugtthgStQlQ=&h=275&w=413&sz=28&hl=en&
start=4&um=1&i>.
Omron. ECE Store. 28 April 2010 <http://www.ece.usu.edu/ece_store/spec/OR501.pdf>.
Sharp. 28 April 2010
<http://www.physics.csbsju.edu/~awhitten/reference/Sharp_photodevices.pdf>.
Wouterse, J.H. "Critical torque and speed of eddy current brake with widely separated soft iron
poles." IEE Procedings-B 138.No.4 (1991): 153-158.
28
Appendix A – ImprovedBicycleTrainer.C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//---------------------------------------------------------------------------// ImprovedBikeTrainer.c
//---------------------------------------------------------------------------//
// References
//
// Example code from Silicon Laboratories, Inc. was used as a base for this
program
// Most of the code was modified to fit this particular program
// Some of the initializations were left unchanged
//
// http://www.silabs.com
//
// Program Description:
//
// This program controls a modified bicycle trainer. The trainer is equipped
with
// an electro magnetic eddy current brake (modified to also add friction via
felt),
// an optical phototransitor sensor (measures # of revolutions of
flywheel/drum),
// and a strain gauge (measures power output of bicyclist).
//
// The program sends the revolutions per second (RPS) and power (in mV) to
the computer.
// Based off of the RPS and power, the computer sends a resistance level back
to the
// microcontroller. The microcontroller uses this resistance level to
generate a
// PWM signal. This signal controls the eddy current brake. The
electromagnet uses
// 12V which is unsafe for the board so the board signal is sent to an
optoisolator.
//
// How To Test w/o the modified Bicycle Trainer:
//
// 1) Download code to a 'F02x device via the JTAG
// 2) Verify jumpers J6 and J9 are populated on the 'F02x TB.
// 3) Connect serial cable from the transceiver to a PC
// 4) On the PC, open HyperTerminal (or any other terminal program) and
connect
//
to the COM port at <BAUDRATE> and 8-N-1
// 5) Connect pin 3.6 (Port 3, Pin 6) to a function generator power
connector.
//
The frequency should be less than 256 Hz. The Max voltage should
be less than 5V and
29
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//
the min voltage should be greater than 0V to prevent damage to
the board.
// 6) Connect pin 0.2 (Port 0, Pin 2) to an oscilliscope power connector.
// 7) Connect grounds from function generator and oscilliscope to either pin
0.9 or 3.9
// 8) On the expansion I/O connector, connect pin A1 to A29.
//
Sets analog voltage input reference 0 (VREF0) to 3.3V instead of
2.45V.
// 9) Connect power from power supply to J20, pin 5 (AIN0.0).
// 10)Connect ground from power supply to J20, pin 8 (Analog Ground).
// 11) Download and execute code on an 'F02x target board.
//
// Target:
C8051F020
// Tool chain:
Silicon Labs IDE
// Command Line:
None
//
// Release 0.9
//
- Intial Release 06 May 2010
//
//
//---------------------------------------------------------------------------// Includes
//---------------------------------------------------------------------------#include <c8051f020.h>
#include <stdio.h>
// SFR declarations
//---------------------------------------------------------------------------// 16-bit SFR Definitions for 'F02x
//---------------------------------------------------------------------------sfr16
sfr16
sfr16
sfr16
sfr16
ADC0
RCAP2
RCAP3
TMR2
TMR3
=
=
=
=
=
0xbe;
0xca;
0x92;
0xcc;
0x94;
//
//
//
//
//
ADC0 data
Timer2 capture/reload
Timer3 capture/reload
Timer2
Timer3
//---------------------------------------------------------------------------// Global Constants
//---------------------------------------------------------------------------#define BAUDRATE
#define SYSCLK
frequency
#define SAMPLE_RATE
#define INT_DEC
#define SAR_CLK
115200
22118400
// Baud rate of UART in bps
// External crystal oscillator
50000
256
2500000
// Sample frequency in Hz
// Integrate and decimate ratio
// Desired SAR clock speed
#define SAMPLE_DELAY 500
sbit LED = P1^6;
// Delay in ms before taking sample
// LED='1' means ON
30
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
sbit SW1 = P3^7;
// SW1='0' means switch pressed
#define TIMER_PRESCALER
12
// Based on Timer CKCON settings
#define LED_TOGGLE_RATE
100 // LED toggle rate in milliseconds
// if LED_TOGGLE_RATE = 1, the LED
will
// be on for 1 millisecond and off for
// 1 millisecond
// There are SYSCLK/TIMER_PRESCALER timer ticks per second, so
// SYSCLK/TIMER_PRESCALER/1000 timer ticks per millisecond.
#define TIMER_TICKS_PER_MS SYSCLK/TIMER_PRESCALER/10000
// Note: LED_TOGGLE_RATE*TIMER_TICKS_PER_MS should not exceed 65535 (0xFFFF)
// for the 16-bit timer
#define
#define
#define
#define
AUX1
AUX2
AUX3
AUX4
TIMER_TICKS_PER_MS*LED_TOGGLE_RATE
-AUX1
AUX2&0x00FF
((AUX2&0xFF00)>>8)
#define TIMER0_RELOAD_HIGH
#define TIMER0_RELOAD_LOW
AUX4
AUX3
// Reload value for Timer0 high byte0
// Reload value for Timer0 low byte
#define TEN_SECONDS
1000
the 100ms interrupts needed for 10 seconds
#define THREE_SECONDS
300
100ms interrupts needed for 3 seconds
#define ONE_SECOND
100
the 100ms interrupts needed for 1 second
//The number of times
//The number of times the
//The number of times
//---------------------------------------------------------------------------// Function Prototypes
//---------------------------------------------------------------------------void OSCILLATOR_Init (void);
(22.1184 MHz)
void PORT_Init (void);
UART ports
void UART1_Init (void);
void ADC0_Init (void);
void TIMER3_Init (int counts);
Input
void EXT_INTERRUPT_INIT (void);
void TIMER0_Init(void);
void PCA0_Init (void);
void Wait_MS (unsigned int ms);
//Sets clock to external oscillator
//Sets up all necessary digital and
//Sets up UART1
//Sets up Analog Input AIN0.0
//Sets up Timer3, used for Analog
//Sets up
//---------------------------------------------------------------------------// Global Variables
//---------------------------------------------------------------------------#define UART_BUFFERSIZE 3
31
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
unsigned long Result = 0;
unsigned int measurement = 0;
input in mV
unsigned long SensorCount = 0;
interrupt count
unsigned long AVG_RPS = 0;
unsigned long RPM = 0;
unsigned char TX_Ready = 0;
unsigned char Byte = 0;
unsigned char UART_Buffer[UART_BUFFERSIZE];
// ADC0 decimated value
// Actual Voltage
// External
//---------------------------------------------------------------------------// main() Routine
//---------------------------------------------------------------------------void main (void)
{
//long measurement;
WDTCN = 0xde;
WDTCN = 0xad;
OSCILLATOR_Init ();
PORT_Init ();
UART1_Init ();
TIMER0_Init ();
TIMER3_Init (SYSCLK/SAMPLE_RATE);
// Measured voltage in mV
// Disable watchdog timer
// Initialize crossbar and GPIO
// Initialize Timer3 to overflow at
// sample rate
EXT_INTERRUPT_INIT ();
PCA0_Init ();
ADC0_Init ();
// Init ADC
AD0EN = 1;
// Enable ADC
EA = 1;
// Enable global interrupts
while (1)
{
EA = 0;
// Disable interrupts
// The 12-bit ADC value is averaged across INT_DEC measurements.
// The result is then stored in Result, and is right-justified
// The measured voltage applied to AIN 0.1 is then:
//
//
Vref (mV)
//
measurement (mV) =
--------------- * Result (bits)
//
(2^12)-1 (bits)
//
// Vref = 3300 but was yielding measurements that were off by a
constant proportion
// Vref was tweeked to 3115 to yield measurements accurates to .01V
32
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
measurement =
Result * 3115 / 4095;
EA = 1;
// Re-enable interrupts
Wait_MS(SAMPLE_DELAY);
// Wait 50 milliseconds before taking
// another sample
}
}
//---------------------------------------------------------------------------// Initialization Subroutines
//---------------------------------------------------------------------------//---------------------------------------------------------------------------// OSCILLATOR_Init
//---------------------------------------------------------------------------//
// Return Value : None
// Parameters
: None
//
// This routine initializes the system clock to use an 22.1184MHz crystal
// as its clock source.
//
//
//---------------------------------------------------------------------------void OSCILLATOR_Init (void)
{
int i;
// delay counter
OSCXCN = 0x67;
// start external oscillator with
// 22.1184MHz crystal
for (i=0; i < 256; i++) ;
// wait for oscillator to start
while (!(OSCXCN & 0x80)) ;
// Wait for crystal osc. to settle
OSCICN = 0x88;
SYSCLK
// select external oscillator as
// source and enable missing clock
// detector
}
//---------------------------------------------------------------------------// PORT_Init
//---------------------------------------------------------------------------//
// Return Value : None
// Parameters
: None
//
33
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
// This function configures the crossbar and GPIO ports.
//
// P0.0
digital
push-pull
UART TX
// P0.1
digital
open-drain
UART RX
// P0.2
digital
push-pull
Pulse Width Modulation Output
// P1.6
digital
push-pull
LED
// P3.6
digital
push-pull
External input interrupt (Speed Sensor)
// AIN0.1 analog
Analog input (no configuration necessary)
//---------------------------------------------------------------------------void PORT_Init (void)
{
XBR0
= 0x08;
// Route CEX0 to P0.0 for
PWM
XBR1
= 0x04;
// No peripherals selected
XBR2
|= 0x44;
// Enable crossbar, weak pull-ups,
enable UART1
P0MDOUT |= 0x05;
// enable P0.0(TX0) and P0.2(CEX0) as
a push-pull output
P1MDOUT |= 0x40;
// enable P1.6(LED) as push-pull
output
}
//---------------------------------------------------------------------------// UART1_Init
//---------------------------------------------------------------------------//
// Return Value : None
// Parameters
: None
//
// Configure the UART1 using Timer1, for <baudrate> and 8-N-1.
// This routine configures the UART1 based on the following equation:
//
// Baud = (2^SMOD1/32)*(SYSCLK*12^(T1M-1))/(256-TH1)
//
// This equation can be found in the datasheet, Mode1 baud rate using timer1.
// The function select the proper values of the SMOD1 and T1M bits to allow
// for the proper baud rate to be reached.
//---------------------------------------------------------------------------void UART1_Init (void)
{
SCON1
= 0x50;
// SCON1: mode 1, 8-bit UART, enable
RX
TMOD
TMOD
&= ~0xF0;
|= 0x20;
// TMOD: timer 1, mode 2, 8-bit reload
if ( (((SYSCLK/BAUDRATE)/256)/16) < 1 )
{
PCON |= 0x10;
// SMOD1 (PCON.4) = 1 --> UART1
baudrate
// divide-by-two disabled
CKCON |= 0x10;
// Timer1 uses the SYSCLK
34
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
TH1 = - ((SYSCLK/BAUDRATE)/16);
}
else if ( (((SYSCLK/BAUDRATE)/256)/32) < 1 )
{
PCON &= ~0x10;
// SMOD1 (PCON.4) = 0 --> UART1
baudrate
// divide-by-two enabled
CKCON |= 0x10;
// Timer1 uses the SYSCLK
TH1 = - ((SYSCLK/BAUDRATE)/32);
}
else if ( ((((SYSCLK/BAUDRATE)/256)/16)/12) < 1 )
{
PCON |= 0x10;
// SMOD1 (PCON.4) = 1 --> UART1
baudrate
// divide-by-two disabled
CKCON &= ~0x10;
// Timer1 uses the SYSCLK/12
TH1 = - (((SYSCLK/BAUDRATE)/16)/12);
}
else if ( ((((SYSCLK/BAUDRATE)/256)/32)/12) < 1 )
{
PCON &= ~0x10;
// SMOD1 (PCON.4) = 0 --> UART1
baudrate
// divide-by-two enabled
CKCON &= ~0x10;
// Timer1 uses the SYSCLK/12
TH1 = - (((SYSCLK/BAUDRATE)/32)/12);
}
TL1 = TH1;
TR1 = 1;
EIE2
|= 0x40;
EIP2
|= 0x40;
// init Timer1
// START Timer1
// Enable UART1 interrupts
// Make UART high priority
}
//---------------------------------------------------------------------------// ADC0_Init
//---------------------------------------------------------------------------//
// Return Value : None
// Parameters
: None
//
// Configure ADC0 to use Timer3 overflows as conversion source, to
// generate an interrupt on conversion complete, and to use right-justified
// output mode. Enables ADC end of conversion interrupt. Leaves ADC
disabled.
// VREF0 (connected to board 3.3V) is used in place of standard VREF (2.43V).
//
//---------------------------------------------------------------------------void ADC0_Init (void)
{
ADC0CN = 0x04;
// ADC0 disabled; normal tracking
35
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
// mode; ADC0 conversions are
initiated
// on overflow of Timer3; ADC0 data is
// right-justified
REF0CN = 0x07;
AMX0CF = 0x00;
(default)
// Enable temp sensor, on-chip VREF0,
// and VREF output buffer
// AIN inputs are single-ended
AMX0SL = 0x00;
// Select AIN0.1 pin as ADC mux input
ADC0CF = (SYSCLK/SAR_CLK) << 3;
ADC0CF |= 0x00;
// ADC conversion clock = 2.5MHz
// PGA gain = 1 (default)
EIE2 |= 0x02;
// enable ADC interrupts
}
//---------------------------------------------------------------------------// Timer0_Init
//---------------------------------------------------------------------------//
// Return Value : None
// Parameters
: None
//
// Timer0 as a 8-bit auto-reload timer, interrupt enabled.
// Using the SYSCLK and reloading the T0L register.
//
// Note: The Timer0 uses a 1:12 prescaler. If this setting changes, the
// TIMER_PRESCALER constant must also be changed.
//---------------------------------------------------------------------------void TIMER0_Init(void)
{
TH0 = TIMER0_RELOAD_HIGH;
// Reinit Timer0 High register
ET0 |= 1;
// Timer0 interrupt enabled
TMOD |= 0x01;
// 16-bit Mode Timer0
TCON |= 0x10;
// Timer0 ON
}
//---------------------------------------------------------------------------// TIMER3_Init
//---------------------------------------------------------------------------//
// Return Value : None
// Parameters
:
//
1) int counts - calculated Timer overflow rate
//
range is postive range of integer: 0 to 32767
//
// Configure Timer3 to auto-reload at interval specified by <counts> (no
// interrupt generated) using SYSCLK as its time base.
36
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
//
//---------------------------------------------------------------------------void TIMER3_Init (int counts)
{
TMR3CN = 0x02;
// Stop Timer3; Clear TF3; set sysclk
// as timebase
RCAP3
= -counts;
TMR3
= RCAP3;
EIE2
&= ~0x01;
TMR3CN |= 0x04;
//
//
//
//
Init reload values
Set to reload immediately
Disable Timer3 interrupts
start Timer3
}
//---------------------------------------------------------------------------// Ext_Interrupt_Init
//---------------------------------------------------------------------------//
// Return Value : None
// Parameters
: None
//
// This function configures and enables External Interrupt 6 on P3.6 as
negative edge-triggered.
//
//---------------------------------------------------------------------------void EXT_INTERRUPT_INIT (void)
{
EIE2 |= 0x10;
// Enable External
Interrupt 6 (interrupt 18)
}
//---------------------------------------------------------------------------// PCA0_Init
//---------------------------------------------------------------------------//
// Return Value : None
// Parameters
: None
//
// This function configures the PCA time base, and sets up 8-bit PWM output
// mode for Module 0 (CEX0 pin).
//
// The frequency of the PWM signal generated at the CEX0 pin is equal to the
// PCA main timebase frequency divided by 256.
//
// The PCA time base in this example is configured to use SYSCLK, and SYSCLK
// is set up to use an external crystal running at 22.1184 MHz. Therefore,
// the frequency of the PWM signal will be 22.1184 MHz / 256 = 86.4 kHz.
// Using different PCA clock sources or a different processor clock will
// result in a different frequency for the PWM signal.
//
37
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
//
-----------------------------------------------------------------------//
How "8-Bit PWM Mode" Works:
//
//
The PCA's 8-bit PWM Mode works by setting an output pin low every
//
time the main PCA counter low byte (PCA0L) overflows, and then setting
//
the pin high whenever a specific match condition is met.
//
//
Upon a PCA0L overflow (PCA0L incrementing from 0xFF to 0x00), two
things
//
happen:
//
//
1) The CEXn pin will be set low.
//
2) The contents of the PCA0CPHn register for the module are copied into
//
the PCA0CPLn register for the module.
//
//
When the PCA0L register increments and matches the PCA0CPLn register
for
//
the selected module, the CEXn pin will be set high, except when the
//
ECOMn bit in PCA0CPMn is cleared to '0'. By varying the value of the
//
PCA0CPHn register, the duty cycle of the waveform can also be varied.
//
//
When ECOMn = '1', the duty cycle of the PWM waveform is:
//
//
8-bit PWM Duty Cycle = (256 - PCA0CPLn) / 256
//
//
To set the duty cycle to 100%, a value of 0x00 should be loaded into
the
//
PCA0CPHn register for the module being used (with ECOMn set to '1').
//
When the value of PCA0CPLn is equal to 0x00, the pin will never be
//
set low.
//
//
To set the duty cycle to 0%, the ECOMn bit in the PCA0CPMn register
//
should be cleared to 0. This prevents the PCA0CPLn match from
occuring,
//
which results in the pin never being set high.
//
-----------------------------------------------------------------------//
//---------------------------------------------------------------------------void PCA0_Init (void)
{
// configure PCA time base; overflow interrupt disabled
PCA0CN = 0x00;
// Stop counter; clear all flags
PCA0MD = 0x08;
// Use SYSCLK as time base
PCA0CPM0 = 0x42;
// Module 0 = 8-bit PWM mode
// Configure initial PWM duty cycle = 50%
PCA0CPH0 = 256 - (256 * 0.5);
// Start PCA counter
CR = 1;
}
38
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
//---------------------------------------------------------------------------// Interrupt Service Routines
//---------------------------------------------------------------------------//---------------------------------------------------------------------------// ADC0_ISR
//---------------------------------------------------------------------------//
// Here we take the ADC0 sample, add it to a running total <accumulator>, and
// decrement our local decimation counter <int_dec>. When <int_dec> reaches
// zero, we post the decimated result in the global variable <result>.
//
//---------------------------------------------------------------------------void ADC0_ISR (void) interrupt 15
{
static unsigned int_dec=INT_DEC;
// Integrate/decimate counter
// we post a new result when
// int_dec = 0
static long accumulator=0L;
// Here's where we integrate the
// ADC samples
AD0INT = 0;
// Clear ADC conversion complete
// indicator
accumulator += ADC0;
// Read ADC value and add to running
// total
// Update decimation counter
int_dec--;
if (int_dec == 0)
{
int_dec = INT_DEC;
Result = accumulator >> 8;
accumulator = 0L;
}
// If zero, then post result
// Reset counter
// Reset accumulator
}
//---------------------------------------------------------------------------// /INT6 ISR
//---------------------------------------------------------------------------//
// Whenever a negative edge appears on P3.6, SensorCount is incremented.
// The interrupt pending flag is cleared manually and the LED is toggled.
//
// NOTE: The SensorCount sent to the PC is sent in one byte.
//
This requires the input frequency to be less than 256 Hz
//
Frequencies greater than 256 Hz will be clipped to 256 Hz.
//
//---------------------------------------------------------------------------void INT6_ISR (void) interrupt 18
39
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
{
LED = ~LED;
//Toggle the LED
P3IF = 0;
//Clear the interrupt pending flag
if(SensorCount <= 256) //Clip frequencies greater than 256 Hz.
{
SensorCount++;
//Increment SensorCount
}
}
//---------------------------------------------------------------------------// Timer0_ISR
//---------------------------------------------------------------------------//
// Here we process the Timer0 interrupt and toggle the LED
//
//---------------------------------------------------------------------------void Timer0_ISR (void) interrupt 1
{
static long counter = 0;
//static counter to count
number of interrupts
TH0 = TIMER0_RELOAD_HIGH;
TL0 = TIMER0_RELOAD_LOW;
// Reinit Timer0 register
if(counter == ONE_SECOND)
//When counter reaches value of
ONE_SECOND, execute this code
{
counter = 0;
//Reset counter
UART_Buffer[0] = SensorCount;
//Add
SensorCount data to UART_Buffer
UART_Buffer[1] = (char)measurement;
//Add Least
Significant byte of measurement to UART_Buffer
UART_Buffer[2] = (char)(measurement >> 8);
//Add Most Significatn
byte of measurement to UART_Buffer
SensorCount = 0;
//Reset SensorCount
SCON1 = (SCON1 | 0x02);
//Set UART1
transmit flag (forces UART1_Interrupt)
}
else
{
counter++; //Otherwise, Increment Counter
}
}
//---------------------------------------------------------------------------// UART1_Interrupt
//---------------------------------------------------------------------------//
// This routine is invoked whenever a character is entered or displayed on
the
40
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
// Hyperterminal.
//
//---------------------------------------------------------------------------void UART1_Interrupt (void) interrupt 20
{
char temp = 0;
static char counter = 0;
//counter for byte
transmission
if ((SCON1 & 0x01) == 0x01)
//Check if received flag is
set (RI1 = 1)
{
SCON1 = (SCON1 & ~0x01);
//Clear received flag (RI1 = 0)
PCA0CPH0 = SBUF1;
//Set PWM duty cycle
according to recieved byte
//temp = SBUF1;
//PCA0CPH0 = temp;
}
if ((SCON1 & 0x02) == 0x02)
//Check if transmit flag is set
(TI1 = 1)
{
SCON1 = (SCON1 & ~0x02);
//Clear transmit flag (TI1 =
0)
if(counter < UART_BUFFERSIZE)
//If counter < 3, transmit
byte
{
SBUF1 = UART_Buffer[counter++];
//Send byte from UART_Buffer
based on counter
}
else
{
counter = 0;
//Once counter == 3,
reset counter
}
}
}
//---------------------------------------------------------------------------// Support Subroutines
//---------------------------------------------------------------------------//---------------------------------------------------------------------------// Wait_MS
//---------------------------------------------------------------------------//
// Return Value : None
// Parameters:
//
1) unsigned int ms - number of milliseconds of delay
//
range is full range of integer: 0 to 65335
//
// This routine inserts a delay of <ms> milliseconds.
41
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
//
//---------------------------------------------------------------------------void Wait_MS(unsigned int ms)
{
763
-----
CKCON &= ~0x20;
// use SYSCLK/12 as timebase
RCAP2 = -(SYSCLK/1000/12);
TMR2 = RCAP2;
// Timer 2 overflows at 1 kHz
ET2 = 0;
// Disable Timer 2 interrupts
TR2 = 1;
// Start Timer 2
while(ms)
{
TF2 = 0;
while(!TF2);
ms--;
}
TR2 = 0;
// Clear flag to initialize
// Wait until timer overflows
// Decrement ms
// Stop Timer 2
}
//---------------------------------------------------------------------------// End Of File
//------------------------------------------------------------------------
42
Appendix – GUI Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//Mainform.cs
using
using
using
using
using
using
using
using
System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
namespace gui
{
public partial class Mainform : Form
{
private CourseData myCourse = new CourseData();
public Mainform()
{
InitializeComponent();
showBMI();
Course_start_stop_button.Enabled = false;
User_Course_Select_Button.Enabled = false;
LCD_Port.Open();//open port here so that we can send the
display data and can receive a start command from the LCD
}
private void Mainform_FormClosing(object sender,
FormClosingEventArgs e)
{
LCD_Port.Close();
}
private void User_Data_setup_finished_button_Click(object sender,
EventArgs e)
{
if (!User_Set)
{
User_Height_Feet.Enabled = false;
User_Height_Inches.Enabled = false;
User_Weight.Enabled = false;
shoulder_Width_Numeric_Up_Down.Enabled = false;
Bike_Weight_Numeric_Up_Down.Enabled = false;
User_Course_Select_Button.Enabled = true;
43
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
User_Data_setup_finished_button.Text = "Change";
User_Set = true;
}
else
{
User_Height_Feet.Enabled = true;
User_Height_Inches.Enabled = true;
User_Weight.Enabled = true;
shoulder_Width_Numeric_Up_Down.Enabled = true;
Bike_Weight_Numeric_Up_Down.Enabled = true;
User_Course_Select_Button.Enabled = false;
Course_start_stop_button.Enabled = false;
User_Data_setup_finished_button.Text = "Finalize";
User_Set = false;
}
}
}
}
71
//BMI_calculators.cs
72
//Calculate the BMI for the rider
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
using
using
using
using
using
using
using
using
System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
namespace gui
{
public partial class Mainform : Form
{
private double User_BMI = 0.0;
public double calc_BMI(double feet, double inches, double pounds)
{
double height = (feet * 12) + inches;
double weight = pounds;
return ((weight * 703) / (height * height));
}
public double calc_BMI(double meters, double kilos)
{
44
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
return ((kilos) / (meters * meters));
}
private void showBMI()
{
User_BMI =
calc_BMI(System.Convert.ToDouble(User_Height_Feet.Value),
System.Convert.ToDouble(User_Height_Inches.Value),
System.Convert.ToDouble(User_Weight.Value));
User_BMI_show_label.Text = User_BMI.ToString();
}
private void User_Height_Feet_ValueChanged(object sender,
EventArgs e)
{
showBMI();
}
private void User_Height_Inches_ValueChanged(object sender,
EventArgs e)
{
showBMI();
}
private void User_Weight_ValueChanged(object sender, EventArgs e)
{
showBMI();
}
}
}
127
//Course_Run_data.cs
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
using
using
using
using
using
using
using
using
System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
namespace gui
{
public partial class Mainform : Form
{
//Some global variables to coordinate data on the form
private double course_distance_left = 0;
private double course_distance_traveled = 0;
private int course_current_resistance = 0;
private int course_base_resistance = 0;
private int course_adjust_resistance = 0;
private DateTime Course_start_time = new DateTime();
45
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
private
private
private
private
private
private
DateTime Course_previous_time = new DateTime();
DateTime Course_current_time = new DateTime();
int cur_point = 0;
double cur_point_distance_left = 0;
bool started = false;
bool User_Set = false;
//get the course data ready to be used
private void setupCourse()
{
calc_resistances();
course_distance_left =
myCourse.myResistances[myCourse.myResistances.Count - 1].MyDistance;
cur_point_distance_left =
myCourse.myResistances[0].MyDistance;
updateCourseDisplay();
Course_start_stop_button.Enabled = true;
}
//show the data on the GUI display
private void updateCourseDisplay()
{
Course_distance_left_show_label.Text =
course_distance_left.ToString() + " meters";
Course_distance_traveled_show_label.Text =
course_distance_traveled.ToString() + " meters";
if (myCourse.myDataLog.Count > 0)
{
Course_current_speed_show_label.Text =
myCourse.myDataLog[myCourse.myDataLog.Count - 1].Speed.ToString("00") + "
MPH";
Course_current_power_show_label.Text =
myCourse.myDataLog[myCourse.myDataLog.Count - 1].Power.ToString("000") + "
Watts";
}
else
{
Course_current_speed_show_label.Text = "00" + " MPH";
Course_current_power_show_label.Text = "000" + " Watts";
}
Actual_resistance_display_label.Text = "Resistance: " +
course_current_resistance.ToString();
TimeSpan timeLapsed = Course_current_time - Course_start_time;
Course_time_lapsed_show_label.Text =
timeLapsed.Hours.ToString("00") + ":" + timeLapsed.Minutes.ToString("00") +
":" + timeLapsed.Seconds.ToString("00");
updateLCD();
}
private void Course_resistance_up_down_ValueChanged(object sender,
EventArgs e)
{
course_adjust_resistance =
Convert.ToInt32(Course_resistance_up_down.Value);
calc_and_update_resistance();
updateLCD();
46
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
}
//update all the distances for the current run of the course
private void update_distances(double length)
{
course_distance_left -= length;
course_distance_traveled += length;
while (course_distance_traveled >
myCourse.myResistances[cur_point].MyDistance)
{
Course_resistance_up_down.Value = 0;
increment_cur_point();
}
}
//calculate the base resistances for the entire course
private void calc_resistances()
{
double resistance = 0;
foreach (slope myslope in myCourse.mySlopes)
{
resistance = (9.8 * ((Convert.ToDouble(User_Weight.Value +
Bike_Weight_Numeric_Up_Down.Value))/2.2) * ((.004) + myslope.Slope));
myCourse.myResistances.Add(new
Resistance(Convert.ToInt16(System.Math.Min(255.0, System.Math.Max(0.0,
resistance))),myslope.Distance));
}
}
//calculate the current resistance based on the base resistance,
adjustment value and speed caused resistance
private void calc_and_update_resistance()
{
course_base_resistance =
Math.Min(Math.Max((myCourse.myResistances[cur_point].MyResistance)+course_adj
ust_resistance, 0), 255);
if (myCourse.myDataLog.Count > 0)
{
course_current_resistance =
Math.Min(Math.Max((course_base_resistance +
speed_resistance(myCourse.myDataLog[myCourse.myDataLog.Count - 1].Speed)),
0), 255);
}
sendResistanceData(Convert.ToByte(course_current_resistance));
}
//calculate the resistance caused by the speed of the cyclist
private int speed_resistance(double speed)
{
double speed_meters_per_second = ((speed * 1609.344)/3600);
double user_height = Convert.ToDouble((User_Height_Feet.Value
* 12) + (User_Height_Inches.Value)) / 39.3700787;//convert to meters
double shoulderwidth =
(Convert.ToDouble(shoulder_Width_Numeric_Up_Down.Value) / 39.3700787);
double resistance = ((.304654) * user_height * shoulderwidth *
speed_meters_per_second * speed_meters_per_second);
47
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
return Convert.ToInt32(resistance);
}
//move the course to the next data point, without running past the end of
the course data
private void increment_cur_point()
{
cur_point = System.Math.Min(cur_point + 1,
myCourse.myResistances.Count - 1);
calc_and_update_resistance();
}
//Update the course data using the current speed and power output data
private void updateCourseData()
{
Course_previous_time = Course_current_time;
Course_current_time = DateTime.Now;
TimeSpan timeStep = Course_current_time Course_previous_time;
//Convert MPH to meters per second
//multiply meters per hour by the time difference
//this gives meters traveled, store in length
double MySpeed = (myCourse.myDataLog[myCourse.myDataLog.Count
- 1].Speed * 1609.344)/3600;
double length = ((timeStep.Seconds * MySpeed)
+(timeStep.Milliseconds*MySpeed/1000/60));
update_distances(length);
calc_and_update_resistance();
updateCourseDisplay();
}
private void Course_start_stop_button_Click(object sender,
EventArgs e)
{
start_stop_script();
}
public void start_stop_script()
{
if (Course_start_stop_button.Text == "Start")
{
start_script();
}
else
{
stop_script();
}
}
public delegate void script_delegate();
public delegate void process_buffer_delegate(List<byte> buffer);
private void resistance_up_pushed()
48
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
{
Course_resistance_up_down.UpButton();
}
private void resistance_down_pushed()
{
Course_resistance_up_down.DownButton();
}
private void start_stop_pushed()
{
start_stop_script();
}
private void start_script()
{
if (!started)
{
Course_start_time = DateTime.Now;
Course_current_time = DateTime.Now;
Course_start_stop_button.Text = "Stop";
started = true;
Microcontroller_Port.Open();
calc_and_update_resistance();
}
updateLCD();
}
private void stop_script()
{
if (started)
{
started = false;
Course_start_stop_button.Text = "Start";
Microcontroller_Port.Close();
}
updateLCD();
}
}
}
366
//File_processing.cs
367
368
369
370
using
using
using
using
System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
49
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
using
using
using
using
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
namespace gui
{
public partial class Mainform : Form
{
private void User_Course_Select_Button_Click(object sender,
EventArgs e)
{
readXML();
processPointData();
setupCourse();
}
private void readXML()
{
OpenFileDialog myDialog = new OpenFileDialog() { Filter = "Xml
File (*.xml)| *.xml;|Garmin Trainer File (*.tcx)|*.tcx;" };
if (myDialog.ShowDialog() == DialogResult.OK)
{
LoadedCourseData.Clear();
LoadedCourseData.ReadXml(myDialog.FileName.ToString());
LoadedCourseData.AcceptChanges();
}
}
private void processPointData()
{
double current_elevation = 0;
double current_distance = 0;
double last_elevation = 0;
double last_distance = 0;
int count = 0;
foreach (DataRow thisdata in
LoadedCourseData.Tables["Trackpoint"].Rows)
{
//if first data point
if (System.Convert.ToInt16(thisdata[1]) == 0)
{
if ((thisdata[2] == DBNull.Value) || (thisdata[3] ==
DBNull.Value))
{
}
else
{
current_elevation =
System.Convert.ToDouble(thisdata[2]);
current_distance =
System.Convert.ToDouble(thisdata[3]);
}
}
//otherwise
else
{
//check to see if we have changed elevation
50
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
if ((thisdata[2] == DBNull.Value) || (thisdata[3] ==
DBNull.Value))
{
}
else
{
if ((System.Convert.ToDouble(thisdata[2]) !=
current_elevation))// && (System.Convert.ToDouble(thisdata[2]) != null))
{
if (count > 0)
{
addSlope(0.0, current_distance);
}
last_elevation = current_elevation;
last_distance = current_distance;
current_elevation =
System.Convert.ToDouble(thisdata[2]);
current_distance =
System.Convert.ToDouble(thisdata[3]);
addSlope(((current_elevationlast_elevation)/(current_distancelast_distance)),current_distance);//(current_distance-last_distance));
count = 0;
}
else
{
current_distance =
System.Convert.ToDouble(thisdata[3]);
count++;
}
}
}
}
}
private void addSlope(double myslope, double mydistance)
{
myCourse.mySlopes.Add(new slope(myslope, mydistance));
}
}
}
475
//LCD_communications
476
477
478
479
using
using
using
using
System;
System.IO;
System.Collections.Generic;
System.ComponentModel;
51
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
using
using
using
using
using
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
namespace gui
{
public partial class Mainform : Form
{
public void updateLCD()
{
if (LCD_Port.IsOpen)
{
DateTime temp = DateTime.Now;
byte[] clearScreenMessage = new byte[2];
clearScreenMessage[0] = Convert.ToByte(254);
clearScreenMessage[1] = Convert.ToByte(88);
TimeSpan timeLapsed = Course_current_time Course_start_time;
//LCD_Port.Open();
LCD_Port.Write(new byte[] { 0xfe, 0x58 }, 0, 2);
LCD_Port.WriteLine(temp.Hour.ToString("00") + ":" +
temp.Minute.ToString("00") + "
Course:" + timeLapsed.Hours.ToString("00")
+ ":" + timeLapsed.Minutes.ToString("00") + ":"
+timeLapsed.Seconds.ToString("00"));
LCD_Port.WriteLine("Distance - Traveled:" +
course_distance_traveled.ToString("000000"));
LCD_Port.WriteLine("Distance - Left:" +
course_distance_left.ToString("000000"));
if (myCourse.myDataLog.Count > 0)
{
LCD_Port.WriteLine("Speed:" +
myCourse.myDataLog[myCourse.myDataLog.Count - 1].Speed.ToString("00") +
"MPH");
LCD_Port.WriteLine("Power:" +
myCourse.myDataLog[myCourse.myDataLog.Count - 1].Power.ToString("000") + "
Watts");
}
else
{
LCD_Port.WriteLine("Speed:" + "00" + "MPH");
LCD_Port.WriteLine("Power:" + "000" + " Watts");
}
LCD_Port.WriteLine("Resistance Level:" +
course_current_resistance.ToString("000"));
//LCD_Port.Close();
}
}
52
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
private void LCD_Port_DataReceived(object sender,
System.IO.Ports.SerialDataReceivedEventArgs e)
{
List<byte> myRecieveBuffer= new List<byte>();
while (LCD_Port.BytesToRead > 0)
{
myRecieveBuffer.Add((byte)LCD_Port.ReadByte());
}
ProcessLCDRecieveBuffer(myRecieveBuffer);
}
private void ProcessLCDRecieveBuffer(List<byte> buffer)
{
//Up arrow=66, down arrow=72, top left=65, bottom left=71
foreach (byte command in buffer)
{
switch (Convert.ToInt32(command))
{
case
case
case
case
66:
72:
65:
71:
increase_Resistance_button(); break;
decrease_resistance_button(); break;
start_button(); break;
stop_button(); break;
}
}
}
private void increase_Resistance_button()
{
if (this.Course_resistance_up_down.InvokeRequired)
{
this.Course_resistance_up_down.Invoke(new
script_delegate(resistance_up_pushed));
return;
}
this.resistance_up_pushed();
}
private void decrease_resistance_button()
{
if (this.Course_resistance_up_down.InvokeRequired)
{
this.Course_resistance_up_down.Invoke(new
script_delegate(resistance_down_pushed));
return;
}
this.resistance_down_pushed();
}
private void start_button()
{
if (this.InvokeRequired)
{
this.Invoke(new script_delegate(start_stop_pushed));
return;
53
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
}
this.start_stop_script();
}
private void stop_button()
{
if (this.InvokeRequired)
{
this.Invoke(new script_delegate(start_stop_pushed));
return;
}
this.start_stop_script();
}
private void pause_button()
{
}
}
}
//Microcontroller_communications.cs
using
using
using
using
using
using
using
using
System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
namespace gui
{
public partial class Mainform : Form
{
bool receiving_power_data1 = false;
bool receiving_power_data2 = false;
double speed_temp = 0.0;
byte power_temp1 = 0;
byte power_temp2 = 0;
double MyRecievedSpeed = 0;
double MyRecievedPower = 0;
private void Microcontroller_Port_DataReceived(object sender,
System.IO.Ports.SerialDataReceivedEventArgs e)
{
List<byte> myRecieveBuffer = new List<byte>();
while (Microcontroller_Port.BytesToRead > 0)
{
if (Microcontroller_Port.IsOpen)
{
54
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
myRecieveBuffer.Add((byte)Microcontroller_Port.ReadByte());
}
}
if (this.InvokeRequired)
{
this.Invoke(new
process_buffer_delegate(ProcessDataRecieveBuffer), myRecieveBuffer);
}
else
{
ProcessDataRecieveBuffer(myRecieveBuffer);
}
}
private void ProcessDataRecieveBuffer(List<byte> buffer)
{
foreach (byte data in buffer)
{
if ((!receiving_power_data1)&&(!receiving_power_data2))
{
speed_temp = Convert.ToDouble(data);
receiving_power_data1 = true;
}
else if(receiving_power_data1)
{
power_temp1 = data;
receiving_power_data1 = false;
receiving_power_data2 = true;
}
else if (receiving_power_data2)
{
power_temp2 = data;
receiving_power_data1 = false;
receiving_power_data2 = false;
}
}
if ((!receiving_power_data1) && (!receiving_power_data2))
{
addDataLogData();
}
}
private void addDataLogData()
{
if (started)
{
MyRecievedSpeed = ((3600 * speed_temp * 1.39 * Math.PI) /
(5280 * 12*2));
MyRecievedPower = (((((0.1429) *
(Convert.ToDouble(Convert.ToInt16(power_temp1) +
(Convert.ToInt16(power_temp2) << 8)))) - 206.1)) * (((MyRecievedSpeed *
1609.344) / 3600)));//power in Watts
addDataToLog();
}
55
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
}
private void addDataToLog()
{
myCourse.myDataLog.Add(new
DataLog((MyRecievedSpeed),(MyRecievedPower)));
this.updateCourseData();
}
private byte current_resistance_byte=255;
private void sendResistanceData(byte resistance)
{
byte myByte = 255;
resistance = Convert.ToByte(myByte - resistance);
if (Microcontroller_Port.IsOpen)
{
if (resistance.CompareTo(current_resistance_byte)!=0)
{
current_resistance_byte = resistance;
Microcontroller_Port.Write(new byte[] {
current_resistance_byte }, 0, 1);
}
}
}
}
}
734
//CourseData.cs
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
using
using
using
using
751
//DataLog.cs
752
using System;
(A data class)
System;
System.Collections.Generic;
System.Linq;
System.Text;
namespace gui
{
class CourseData
{
public List<Point> myPoints = new List<Point>();
public List<slope> mySlopes = new List<slope>();
public List<Resistance> myResistances = new List<Resistance>();
public List<DataLog> myDataLog = new List<DataLog>();
}
}
(A data class)
56
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
using System.Collections.Generic;
using System.Linq;
using System.Text;
782
//slope.cs
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
using
using
using
using
namespace gui
{
class DataLog
{
private double mySpeed;
private double myPower;
public DataLog(double speed, double power)
{
mySpeed = speed;
myPower = power;
}
public double Speed
{
get { return mySpeed; }
}
public double Power
{
get { return myPower; }
}
}
}
(A data class)
System;
System.Collections.Generic;
System.Linq;
System.Text;
namespace gui
{
class slope
{
private double mySlope = 0;
private double myDistance = 0;
public slope(double Slope, double Distance)
{
mySlope = Slope;
myDistance = Distance;
}
public double Slope
{
get { return mySlope; }
}
57
805
806
807
808
809
810
811
812
public double Distance
{
get { return myDistance; }
}
}
}
813
//Resistance.cs
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
using
using
using
using
846
847
848
849
850
851
852
853
(A data class)
System;
System.Collections.Generic;
System.Linq;
System.Text;
namespace gui
{
class Resistance
{
private int resistance;
private double distance;
public Resistance(int newresistance, double newdistance)
{
resistance = newresistance;
distance = newdistance;
}
public int MyResistance
{
get { return resistance; }
}
public double MyDistance
{
get { return distance; }
}
}
}
//Point.cs
using
using
using
using
(A data class)
System;
System.Collections.Generic;
System.Linq;
System.Text;
namespace gui
{
58
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
class Point
{
private double elevation;
private double distance;
public Point(double Elevation, double Distance)
{
elevation = Elevation;
distance = Distance;
}
public double Elevation
{
get { return elevation; }
}
public double Distance
{
get { return distance; }
}
}
}
877
59
Was this manual useful for you? yes no
Thank you for your participation!

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project

Download PDF

advertisement