Let me start by telling a little about myself. Ever since I arrived at Stony Brook I've been involved in the Solar Boat club. Were basically an organization that designs, builds and races solar powered boats. Now, when I tell people we build solar powered boats, they think RC race boats; no, we build typically around 14ft vessels meant for speed (At least we hope). Also by build, I mean we build pretty much everything from scratch including the hull, drive train and other systems. Anyway, not too long into when i joined the club i toyed with making our own motor controllers. Little did i know this design would take almost 2 years. What i want to now share with the everyone are the lessons that i have learned while working on this design. It began as something that i would expect to be done with quickly, but has turned into something which i still work on.
So why build a motor controller? There are companies that already offer plenty of solutions for electric vehicles that will work in a variety of voltage ranges and currents right? Well the truth is, not really. At our competition all the controller are either.. AllTrax, Curtis or 4QD. And all of them either are: Stuck in the 70's in terms of logic and control signals or have a micro but don't actually allow you to usefully use the information they spit out. This being a custom race boat, we want specific information so knowing how the controller is operating at all times is crucial for our boat. That is, if we design the micro system that controls the controller, we can get whatever information we want and use it however we want. We would have digital freedom, which doesn't limit our design(Hello iPhone controlled solar boat?). Secondly, if you actually design a motor controller you know the specifications on the controller are completely artificial. Can a Curtis 1205 actually only source 400 amps? Well... no, the mosfet bank is rated for quite a bit more than that. But due to thermal limitations, they manufacturer sets these limits. Same goes for voltage, most controller have over-voltage ratings to limit the input voltage, despite the fact all the components on the inside are rated for much higher voltages. When you actually design a system from the ground up, you can set those limits and maximize performance of your system. You aren't limited to designing a product specifically for consumer use. You can test the limits of your hardware and learn more about your system in the long run.(for real world design, i suggest against this, but we are racing, so we can take some things to their limits in a blaze of glory :P) These two reasons were the main reasons why i decided to begin this design. Now i would like to go over a little bit about the basics of the design of a switching motor controller
A switching motor controller, at its heart is super simple; We have a battery source, motor and mosfet bank like such:
When we apply a positive voltage on the gate(with respect to the source) The mosfets are fully conducting and the motor turns on. When we apply 0 or a negative voltage, the motors turn off. Yay! we finished our design and we can all go home, the controller works perfectly! Well... not really, we have 0 RPM and Max RPM, when the hell did you ever use a vehicle where you could only go full speed and 0 speed? The technology that we use is called pulse width modulation. I am not going to bore everyone with what it is, it's fairly simple. We have a square wave of frequency x and we vary the time that is is on, versus off. this is called the Duty Cycle and varies from 0 to 1. We can roughly estimate that Iavg = DC * Imax. Since we know P = t*w = I*v, with a constant supply voltage by changing the average current, we change the average power, and therefor for a constant torque load, we change the RPM.
To do this with any mircocontroller is fairly trivial, all decent micro's have some way of outputting a PWM wave at a specified frequency. For a PIC microcontroller, we use a single timer and we compare TMRx to the CCPRxH register, when they are equal, we have a transition from HI/LOW or LOW/HI (settable in the control register). When the TMRx register equals the PRx register we have another transitional and a new period starts. Operation is pretty simple, we have two registers, one for the Period of the wave PR2 and one for the Duty Cycle of the wave CCPRxH. The same is true for the AVR, its just different register names. So with this in mind our updated schematic is now: With this design, we have exactly what we want, we have a micro-controller that can adjust the average power going through a DC motor from a full stop to maximum RPMs. We can add various sensors to adjust the duty cycle and we can chose our mosfets however big to pass as much current we want! There is one small problem..... This setup would NEVER work.
Models and why they are important
A model is a way of representing a very complicated system with one which is much more manageable and well defined. An extreme example of a model is the law of gravitational forces:
This model allows us to tell what two forces an object will see on each other separated by a distance R. As far as well can tell, this is a very accurate model which works for most cases. However, it doesn't represent reality perfectly. We know that the law of gravity doesn't hold for the quantum scale and it especially doesn't hold true for black holes and, even when we calculate a force using this law, we only know this force to a certain amount of uncertainty. How fitting an equation the law of gravitational forces to nature is also a matter of interpretation. for a MUCH better explanation o this subject I recommend those to Richard Feynman's lecture on the character of physical law: http://research.microsoft.com/apps/tools/tuva/ The one thing that annoys me about many engineers is that many of them take a schematic as a golden rule. It exactly shows how a circuit is to operate and if you follow a schematic you will get precisely the operation you desire. This is simply not true. Lets take a simple system of a 9 volt battery with a resistor attached to it:
Now, we want to find the current so we use , great. For most applications this works great, we find what we need and now we can move on with our design. However, lets think about this, what if R is 0? Well then , which is undefined, so that means every time you short your common 9v battery, universe should explode, no? How come it doesnt? Ok, R=0 is a little extreme, but you'll also find, even with small resistances where R!=0, the system wont behave as expected. The above schematic, is only a model to a certain degree, it doesn't tell us everything about the system. For most batteries, there is an internal resistance. This internal resistance, is in series with our load resistance and limits the total output current. We can add this resistance to our schematic and improve our model by a degree:
If we keep digging we can find more and more parameters we can add to our model. One important thing is to realize when we work with REAL components vs ideal components. What makes an inductor? an inductor is a coil of wire...a coil.. of WIRE. Every piece of wire in our circuit has an inductance. What makes a capacitor? two of anything with area, separated by a distance with a potential between them, everything has capacitance, and unless you're working with superconductors, everything has a resistance. These are called parasitic components and for the most part, they have such low values, we arent concerned since they don't affect our circuit in a region we are operating it. Lets add some of these components to our schematic and we get (and yes i know i may be "missing" a few):
Whats great about this model, is that well know exactly the frequency of oscillation when an incident photon strikes the body of the resistor creating a mobile electron.We can even go further! If we add quantum mechanics we'll have a decent idea of what any individuality electron might do. Do we need this? When making a model, you have to define what you NEED to know and you design your model accordingly, for 99% of us, our simple schematic will do. When your designs start failing, and you don't know why, chances are, its because of the parasitics. For high power electronics, the parasitics own your design and your job is to constantly fight them and try to tame them.
Where we stand
Our job is to take a look at our design and find where the parasitics are, the most obvious place to start looking is the electric motor. For our motor we use either a Lynch permanent magnet dc motor or a mars e-tek electric motor. If you're curious about motors check out:
its a good video with some well made diagrams and doesn't move at too fast a pace. At the end of the day, the simplest model we can apply to our motor is an inductor in series with the armature resistance. Four our cabling we use 00 gauge welding wire, if you haven't worked with large gauge welding cable, its the most fantastic cable you can ever hope to work with, its awesome stuff, but i digress. Our wire has both inductance and resistance to it. Adding to original schematic we get:
Now to get back to the "switching" part of this design. We are using a bank of NMOS transistors to turn on and off the motor. When the bank is on, it has a resistance of RDSon/nmosfets . When it is off, the resistance is a few mega-ohms. When the mosfet is going from off to on, it is a resistance between those two. To minimize the power dissipated in the mosfets, we want to switch them as fast as possible. Once again we must look at the inside of a mosfet and understand what makes it tick. A Metal Oxide Field Effect Transistor works by using an electric field to change the concentration of carriers in a semiconductor bulk. On the gate pin, there is no physical connection to the body of the device, it is separated by a later of oxide. This gate, is analogous to a capacitor, and to apply any voltage, it must be charged. Every mosfet has a gate capacitance, and to turn on the device, we have to charge this capacitor.
We know to charge a capacitor C to a voltage of V through a resistor of R is:
where is the RC time constant. Here we can find the time it takes to charge the gate capacitor to a voltage V. To charge this capacitor we generally use an IC called a mosfet driver, we do this simple because the pins of a microcontroller can only source 20mA. At that rate it would simply take too long to turn on/off and we would waste alot of energy in our fets switching. Ok, so that problem is solved, whats next? well, lets get back to that schematic of the motor controller with the inductors, once equation you may recall is the following:
this is the equation for voltage across an inductor, is the same as saying how fast the current changes over time, now we have a mosfet that's trying its absolute hardest to turn on and off as fast as possible, di is the same as the magnitude of on current and off current and dt is how fast it takes to go from on to off. well crap.. it doesn't matter how small L is, is HUGE, it dominates that term. and everywhere in that schematic where we have an inductor, we have a very large spike in voltage. Below is an old capture of a probe across the mosfet bank. I would see spikes well past 100V on a 24V system! I essentially made a really nice boost converter! (Add output caps in the right place, and this IS how you make a boost converter)
Crap, what do we do
Because i didn't set out to design a boost converter, those spikes are unpleasant, plus they'll destroy about every component in the controller if i don't control them. Now, if we have a large positive voltage across the mosfet bank, and the input to the motor is a smaller positive voltage, we have a negative voltage across the motor (with respect to current flow). If anyone has ever worked with inductive loads before, you know the answer to this problem, we need a flyback diode. A very large bank of flyback diodes. A flyback diode does miracles to take care of the motor but we also need something to take care of the inductors in the wires, the short answer is we use a bank of capacitors on the input of the controller. The bank of capacitors act as a low impedance short to "ground" for any spikes in the wires, keeping the voltage across the controller very stable. It isn't perfect, since we have to deal with alot of energy, but it makes things safe enough that we wont unnecessarily kill anything.
This schematic represents closely what I did on the actual controller, replace the diodes, capacitors and mosfets with many in parallel and you got the basic picture of the real thing.
Now, flyback diodes are only one possible solution, another is making a synchronous half bridge controller, This is something that i am also working on, but i wanted to perfect the passive system before i moved onto an active one.
So lets dive into what the motor controller has evolved into, this is the 3(or fourth) major revision of my design and so far it is the best operating version I have.
Below is a picture of the PCB mostly populated and the mosfet bank:
Here is a picture of it connected and ready to be used!:
Picture of the watercooling:
It has the following features:
-pic18f26K22 runs the show, all pins and power are broken out to a set of headers which allow the use of "shields"
-TX Shield has filtered and buffered inputs, 3.3v supply for xbee
-Inputs for various current, temperature, tachometer, battery voltage and other sensors
-Dual independent logic and drive power supplies, coupled optically
-4 layer PCB
-Large bank of schottky high current diodes for flyback
-Bank of 12 high capacitance electrolytic capacitors, Hexagonal closed packed D.
-Breakout for 10 high power mosfets each with own gate resistor connected to two mosfet drivers in parallel
-Watercooled Mosfet Drain bus bar
I used the following components:
-PIC18F26K22 Microcontroller : http://ww1.microchip.com/downloads/en/DeviceDoc/41412F.pdf
-FOD220 optocoupler: http://ww1.microchip.com/downloads/en/DeviceDoc/41412F.pdf
- UCC27424 Low side mosfet driver: http://www.ti.com/lit/ds/symlink/ucc27424.pdf
-LM2796 Simple switcher: http://www.ti.com/lit/ds/symlink/lm2596.pdf
-SBR60a200ct rectifier: http://www.diodes.com/datasheets/sbr/SBR60A200CT.pdf
-FDP036n10a N channel mosfets: http://www.fairchildsemi.com/ds/FD/FDP036N10A.pdf
Two logic signals from the microare sent to the mosfet drivers, the PWM and Enable signal. everything has external pull down resistors to ensure reliable operation in case something goes wrong with the micro. Like i mentioned above, the pins of the microcontroller are broken out into two 14 pin headers, +5V and +12V are available to any header accessories. There is also a 5 pin programming port for the micro.
The header "shield" board looks like this:
Each sensor input goes to a low pass filter and an op amp buffer. This keeps the signal low noise ans allows me to temporarily ignore any real need for DSP at the moment.
Finally, the mosfet bank is watercooled using PC watercooling supplies i had lying around. This allows me to push them a little further than what you could normally get from just air cooling, ans allows me to reach the datasheet speced currents a little easier.
The code is written entirely in assembly. As of now it is 6 asm files:
main.asm - Just a file which includes all the other .ASM files.
Setup.asm - Includes the setup routine and configures on board peripherals.
FSM.asm - Contains the code for the Finite State machine.
int.asm - Contains interrupt code.
subs.asm - Contains all the subroutines.
A finite state machine was chosen because it allows very robust control loop. As long as next state/current state are in bounds, you wont have your code running out of boundaries. There is no checking to see if the next state variable is in range, but this can be easily added.
The state machine handles all the motor controller functions, including ensuring safe operation. Some of the safety features includes are to limit the change in throttle, disable the throttle when first turning on, and use of a key/killswitch. Any hazardous operation return to an idle default state which waits for key input. The code is incredibly modular and adding/removing states is relatively simple. The state diagram is shown below:
The interrupt routine simply polls all the analog inputs, stores each 10 bit a2d in two 8 bit registers and then transmits the data wirelesly to be processed off board. Also existing in the interrupt routine is limit code which defaults the controller to safe operation if it exceed software set limits.
Some features i plan to include are addition of code to read a tachometer output, and the ability to receive commands.
As of now, don't trust my comments too much, always best to look in the data sheet or ask me a question if you are confused.
Below is a video of the controller running a MARS permamemnt magnet DC motor connected by chain to a lynch motor with it's terminals shorted by a piece of welding cable. I only go to about 50% duty cycle. I am still in the process of testing and verifying the current output of the controller. I have been using the Allegro acs758 hall effect current sensor, but i haven't been getting reliable data from them. I hope that i can refine my current measurement procedure to get a better estimate of the current through the controller. Regardless, I have verified peak currents of 250 amps and continuous current of around 100-150 amps. The main limitation is cooling of the flyback diodes as they get very hot. In the video they are passively cooled with a failed waterblock, in the next revision they will be actively cooled and should allow me to push thing a little harder.
Also a little note about the setup, We made a simple dynometer to put load on our controller, its pretty simple, Motor controller drives the Mars motor, lynch motor provides load for it. By varying the restive load on the Lynch, we can vary the load on the Mars. So far we have two settings, no load and full load :P seems makng a many-KW power resistor is not simple...
Conclusion and things to improve
I am far from done with this project, but this is the first major milestone and i think most of the hard work is done. The micro code is very mature and very adaptable, the TX shield does what is suppose to, the XBEE sends data effortlessly. The Mosfet bank remains very cool and i feel safe pushing it further, the biggest limitation right now are my diodes and keeping them cool. I have reduced the switching frequency to around 250hz and this seems to be working. I will be pushing another revision of the PCB to make mounting the lugs easier, i want to also look into diodes that can be mounted on the bus bar directly with the mosfets (they are hard to find, most diodes are wired backwards of what i want).Another alternative is to make a full half bridge controller and using a back of mosfets for the back emf, this is much better than using a bank of diodes as power dissipated in a bank of mosfets is proportional to Rdson and not a constant voltage drop. However, this adds much complexity to design as I now have to make the two banks of mosfets synchronous. Even worse, you cannot find PMOS transistors of these current ranges (In a cheap, useful package) so its best to use NMOS transistors on the high side with a boost cap mosfet driver. I've used them in the past and they can be unreliable, but i will be looking into the possibility of using them in my next design.
As with all my project, files are below, if you take from my design, give me credit. I dont mind you taking and improving where I leave off.
I play Dungeons and Dragons, and like any geek who plays DND i felt like making electronic dice (how clever and original?) with a catch, it has to be ultra portable and has to last at least the length of a standard gaming session on a single charge, it also has to be relatively cheap. I started this build last year around April time, and finished around May last year, i haven't really had much time to write lately so i've been putting off this post. Finally got some free time today, so here it is, my D20 Roller.
One resource i took well advantage of was Dorkbot's PCB service. After three separate revisions i settled on my final design, essentially its a micro (pic18f13k50) 22 SMD LEDs, two push buttons, and a super cap with all the appropriate passives on a 2x1" PCB.
So for this design i wanted to try and make everything as small as possible, which meant surface mount... everything. Personally, i prefer soldering surface mount, once you get used to is its pretty easy, just sucks when you lose stuff under the bench.
Laying things out when using surface mount is pretty trivial, put the stuff that's important for operation on one side, the junk that runs the show on the other side. Making it portable on the other hand, well that's another story. If you want portability, whats the most obvious choice for power? Battery of course, well aa's and aaa's are bulky and don't provide enough voltage for our LEDs, we could add a boost converter or some sort of power system, but that adds cost and space. We could also use coin cell batteries, these too however can be bulky and the holders can take quite a bit of space. My solution was to use a 1F 5.5v super capacitor.
The biggest advantage is they require no charge circuitry (a current limit resistor is plenty sufficient) They hold whatever voltage they're charged to (in this case 5 volts) and they pack plenty of juice to run a micro and some LEDs for a few hours (if you manage your power well). I provide a mini-usb b type receptacle for plugging into a USB port and siphoning off 5V, a current limit resistor limits the current so smart sources (like a PC) don't freak out when there's this huge current draw on the USB port. A smarter design would of been to add a LDO regulator, since then you can accept a wider range of voltages, this adds costs and takes up space.
Now, as for the actual operation of the die roller itself; there are two thing i'd like the roller to accomplish:
Firstly, you need to be able to tell it when to roll a die, so we need a button for rolling dice. Secondly, we would like to be able to select the die type we would like to roll, this is our second button. Operation is simple, hit select until the the die type you want is highlighted, hit roll and you'll get a value within the range of what you want. Simple, and effective.
A note about random numbers:
Well first off, there is absolutely nothing random about this die roller, it is completely synchronous, if you could time yourself perfectly, you'll get the same value every time. But, if you do the statistics (i have) with this roller and an actual d20, you'll find they both have the same statistical distribution. So how do I do it?
1) Humans are slow and all push-buttons suck.
That is the key to this algorithm, in the background of the program I have timer0 cycling as fast as possible. When a random number is requested, I multiply timer0 with the range, i take the higher byte of the result and that is the "random" value which gets displayed.
The trick is timer0 is a 8 bit counter, with a range of 0-255, at 1MHz it overflows approximately 3900 times a second. The fastest human reaction time is around ~100 milliseconds, average is about 200. So before you can even react to your brain saying "hey i wanna push that button, timer0 has already cycled a few hundred times.
In essence you cant time yourself to get the same value twice, its humanly impossible, the error to your reaction time is much greater than the refresh rate of the timer.
Secondly buttons suck. Anyone who has used a pushbutton knows about a problem called switch de-bouncing. When a switches mechanical contacts come together, they dont make a perfect connection, they bounce against each other untill actually settling into a set state. If you have a scope, set it to single trigger and hook it across a switch, you'll notice have many times the switch "bounces" until it settles into a value. This article explains switch debouncing better than I could:
The result of this, even if you could perfectly time yourself down to the microsecond, the time it takes the switch to settle, is random. This is actually the source for many random number generators, but I mostly use the first phenomenon, my switch denouncing algorithm is rather simple, I just kill some time before looking at the switch again, its simple and it works!
So yea, my algorithm is't truly random, but it's incredibly efficient (takes 4 instructions) and it generates results indistinguishable from a real die.
Dice in action
The software is pretty simple, and it's mostly incomplete. For the final iteration i wanted to run a finite state machine and work hard on implementing sleep features, but i never got around to it. The current code is pretty basic, but it works pretty well. Here is the state diagram for how i wanted to implement the code :P
Below i've attached a few things, the source code, the schematic, and a sample excel file where I've done some stats.
If anyone is interested in buying one to play with, I have plenty that need a good home and i'd love to have some funding for a better revision!
You can contact me by email at: email@example.com
I've written a brief manual that those who bought either a kit or the dice themselves will find useful!
Also, I've written another firmware revision, which i will be updating and i'm in the process of writing one more!
Finally, i have another revision of the board, which i am getting printed, i will have these available as kits and assembled versions.
I have two new firmware revisions:
First up is version 4 of the firmware, if i remember correctly, this version puts the display as interrupts making the display a little cleaner and smoother.
This revision focuses on power saving and improving how inputs are handled. Inputs are now handled by interrupts and the main routine is completely freed up. Sleep is enabled and clock speed is reduced to 500khz.
Any questions! Email me!
It's been a while since i posted a project but i think its about time we make use of the tools we have.
I've always wanted to make a simple bot and it should be no surprise that i'm in the solar boat club, so i figured i would combine the two.
I have a small 5W panel in my lab; its too small to directly power any significant equipment and too small to let set on a desk and do nothing.
Usually panels like these are used to passively charge lead acid/whatever batteries. They provide around 12V and usually more than enough current to charge a decently sized battery in a few hours.
However, i wanted to do something different, i wanted to use the panel to power a small robot that had a backup power source. If i made the systems efficient, i wouldn't need that much power to make it move about.
The obvious choice for a backup power source is a battery, although this is true, batteries get a little pesky because you need charge controllers for them. I could design one, but they're not the most thrilling things in the world to build and design.
A better alternative is a capacitor bank. If i stick to my rule of only using minimal power to get about, a capacitor bank makes sense (especially considering how easy they are to charge)
The biggest problem with capacitor banks are the less than spectacular energy density they offer. Energy density is how much energy a medium stores per unit volume. A battery will give you much more energy per unit volume than a capacitor, thus for equal amounts of energy, you need a larger bank (significantly larger). Enter the super-capacitor
Super-capacitors offer much superior energy density compared to an electrolytic capacitor. The capacitors I am using for this project are 5.5V 1Farad capacitors from: http://www.all-battery.com/. Which you can find here: http://www.all-battery.com/55v1capacitancecoinsupercapacitor-1-1.aspx
Props to all-battery.com for sourcing such cheap and powerful capacitors.
Lets compare the energy densities of our capacitors:
We recall that energy stored in a capacitor is given by the following formula:
We can see that the super-cap has a 100 fold increase in energy density over the electrolytic cap.
How does it do it?
Super-caps are really a hybrid between a traditional capacitor (Separation of plates) and a chemical battery. The key is with super-caps, to use nano-scale barriers to separate our charge, increasing surface area. We also use the space for the dielectric to hold charge (where the icky chemical part comes in). These two things combined help increase our energy density. There is one downside, super-caps sacrifice the ability to store large voltages (because of the nano-sized gaps), have increased leakage currents, and take longer to charge than conventional capacitors. However the ability to store much more energy is worth it.
So why super caps again?
Even at 100 joules per cubic centimeter, a typical alkaline battery dwarfs it at 1100 joules per cubic centimeter. However, the ease of charge and use of a super capacitor bank makes them very attractive. Also another thing to note. Capacitors deliver instantaneous current at rates much higher than a battery. Although a capacitor has lower energy density, the power density is much greater for a capacitor, it can discharge its entire energy contents in a fraction of a second while batteries take seconds to discharge completely. (This of an explosion versus a slow burning match)
Each super-cap holds 12 Joules of energy at 5 volts. A typical AA holds about 1kj of energy. So to match the capacity of a AA battery i'd need on the order of 100 super-caps. (Not on my budget) Luckily, we don't need 1kj of energy for our robot, we only need enough energy to move the robot (not far).
For this bot we have 30 super-caps:
They came packed in a tray very well via first class mail. My order got here in a few business days (not bad for free shipping)
Just like electrolytic capacitors, super-caps are polarized. a word of caution with these however. Because of how thin the are, the manufacturer didn't leave enough of the marking band on to show the polarity of the caps (and the leads are equal length). However, it should be noticed that the side with the dimple is + and the flat side is -. Thus in my picture above, the capacitor is facing with the + terminal upwards.
For this project we are putting our 30 1Farad capacitors in parallel for a 30F pack, this yields us 375 joules. A little more than 1/3 of a AA battery. Still quite a capacitor pack.
In this bank i have the capacitors in an alternating pattern to put them all in parallel like so:
Once you hook all the + together and all the - together, the pack is in parallel.
The backside of the proto-board looks like this: (to give you an idea of how i wired it)
Although 5V isn't a huge system voltage, its convenient for our micro's and motors. at 5v our motors are self-current limiting. They don't draw as much power at 5V as they do at say 12V, this is nice for current limiting. The biggest reason is that these caps are rated at 5V and i'd prefer one large parallel pack rather than a combination of series and parallel.
So, our source of energy is our solar panel:
Solar panels provide power by converting photons into electrons (kinda) Each panel has a few ratings. The open circuit voltage (Voltage between bare terminals) the Short circuit current (Current through a short) and the maximum power point.
For maximum power you want to keep the load of the solar panel at this voltage current point. Since i don't have money for a maximum power point system ill do one better, a switching power supply. Although not a MPPT system, with my switching power supply i can adjust the load on the capacitor bank to adjust the load on the panels, thus with some math and some good logic i can easily implement a sudo-MPPT system using a cheap little switching power supply chip.
I will be using the LM2674 buck converter IC to convert the panel voltage(anywhere from 0 to 18V) into 5V. The advantage of a switching power supply is that it works for a wide range of input voltages and is very efficient.
(Thanks to nat semi for the samples!)
So that covers the power system, i'll be using a 5 watt panel to charge a 30F capacitor bank via a switching power supply.
For the logic on this project i will be using a PIC18F. What model i don't know, but what i would like to do:
-Analog input for sensors (LDRs, color sensors etc)
-Serial In/Out for radio/infra red communications
-PWM out for motor drive
A potential candidate is the PIC18F26J50 i recently sampled a few and after checking them out i'll make my decision.
I also might be introducing the use of C to program. For robots C is ideal because we have to use complex data structures but we don't care how long it takes to process them.
I have to begin fabricating the robot's chassis and i will begin testing the power systems. I have to implement a charge controller on the Cap pack and begin designing the mechanics. So stay tuned!
Also any feedback is appreciated!
This project is a simple but fun one. We are going to be using a tri-axis accelerometer to collect analog data from and then display it on the LCD. Our output on the LCD will look like:
The X:, Y:, and Z: are all static and only have to be sent once. (once you update the DDRAM address it stays there until power is cut) The digits however, have to be updated as we get new values, thus it is dynamically updated in our code and sent periodically.
This algorithm is fairly simple and works automatically. The procedure goes:
-Initialize the MCU
-Initialize the LCD
-Draw the static information
-Collect the analog information
-Convert the raw data into digits
-Display the data on the LCD.
The Code: #include p16F690.inc __config (_INTRC_OSC_NOCLKOUT & _WDT_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOR_OFF & _IESO_OFF & _FCMEN_OFF) org 0 ; Begginning of code cblock 0x20 CHARLOCATION RESULT ONES TENS HUNDREDS TEMP DELAY1 DELAY2 COUNT endc PORTS: bsf STATUS,RP1 ; Bank 2 movlw 0x14 ; RA2 & 4 Configured for analog AN2,3 movwf ANSEL ; movlw 0x08 ; RB5 configured for analog (AN11) movwf ANSELH bcf STATUS,RP1 bsf STATUS,RP0 ; select Register Page 1 movlw 0x00 ; Move the hex value of 0 to multi purpose register W movwf TRISC ; make IO PortC all output movlw 0x16 ; Make Ra4,2 and 1 Input movwf TRISA movlw 0x20 ;RB5 Input movwf TRISB movlw 0x10 ;set A2d Clock movwf ADCON1 bcf STATUS,RP0 ;Exit bank 1 ;initialize clrf PORTC clrf PORTA clrf PORTB clrf COUNT clrf TEMP call INITIALIZELCD call DRAWLCD MAIN: ; Our MAIN routine gets the analog value off a port, and updates the LCD with the info movlw 0x41 movwf ADCON0 ;Channel 2 call A2D ;Get A2D value movwf RESULT movlw 0x83 ;Char position 03 call UPDATELCD ;Call Update LCD, having the char position in W movlw 0x4D movwf ADCON0 ;Channel 3 call A2D movwf RESULT movlw 0xC3 ;Char Position 43 call UPDATELCD movlw 0x6D movwf ADCON0 ;Channel 11 call A2D movwf RESULT movlw 0x97 ;Char position 17 call UPDATELCD call DELAY goto MAIN ; Return to main A2D: nop nop nop nop nop ; wait 5 uS bsf ADCON0,GO ; Start the conversion btfss ADCON0,GO ; Bit will stay high untill conversion is complete goto $-1 ; Untill then keep going back movf ADRESH,w return INITIALIZELCD: ;Initialization Routine for the LCD bsf PORTC,0 ;Put CMD 0 on port C call SEND ;Call Send routine, Sends command and waits for busy flag movlw 0x38 ; Set Function, 8 Bit, 5X10 Dots, Two line movwf PORTC call SEND movlw b'00001100' ; Turn on the Display (0x0B movwf PORTC call SEND movlw 0x06 ; Set entry mode to: Increment Adress counter, no shift. movwf PORTC call SEND return DRAWLCD: ; Routine which draws static display movlw 0x80 ; Start with the DDRAM address 0x00 call WRITELOC ; Call Function to write the DDRAM address movlw 'X' ;Move the ASCII value of 'X' to W call WRITECHAR ; Call function to write the Char to DDRAM address movlw 0x3A call WRITECHAR movlw 0xC0 call WRITELOC movlw 'Y' call WRITECHAR movlw 0x3A call WRITECHAR movlw 0x94 call WRITELOC movlw 'Z' call WRITECHAR movlw 0x3A call WRITECHAR ; Continue untill all static elements drawm return WRITECHAR: ; Function to write a characture movwf TEMP ; Move value to temp register call BUSYCHK ; Check Busy flag bsf PORTA,0 ; Turn on Data movfw TEMP ; Move from Temp to W movwf PORTC ; Then to PORTC call SEND ; Send bcf PORTA,0 ; Turn off Data return WRITELOC: movwf TEMP ;Move value to temp call BUSYCHK ;Check busy flag movfw TEMP ;Temp -> W movwf PORTC ;W -> Portc call SEND ;SEND return UPDATELCD: ; Routine to update the dynamic elements of the LCD call WRITELOC ; Init the LCD at the proper DDRAM location clrf ONES ; Clear Vars clrf TENS clrf HUNDREDS call CALCULATE ; Call calculate routine which takes raw A2D value and separates them into digits movfw HUNDREDS ; Write the Hundreds digit to the LCD call TABLE1 call WRITECHAR movfw TENS ;Write Tens call TABLE1 call WRITECHAR movfw ONES ;Write Ones call TABLE1 call WRITECHAR return CALCULATE: ; Basic math routine, takes Result from A2D and separates into 1's 10's and 100's movlw 0x00 bcf STATUS,2 xorwf RESULT,w btfsc STATUS,2 return movlw 0x0A bcf STATUS,2 subwf ONES,w btfsc STATUS,2 goto UPDATETENS decf RESULT,f incf ONES,f goto CALCULATE UPDATETENS: clrf ONES incf TENS,f movlw 0x09 bcf STATUS,2 subwf TENS,w btfsc STATUS,2 call UPDATEHUNS decf RESULT,f goto CALCULATE UPDATEHUNS: clrf TENS incf HUNDREDS,f movlw 0x09 bcf STATUS,2 subwf HUNDREDS,w btfsc STATUS,2 incf HUNDREDS,f return SEND: ;Use this routine to send a command to the LCD. Placement doesnt matter as it does two BSYFLG checks ;Send bsf PORTB,4 ; Set E line High nop nop nop nop bcf PORTB,4 ;Wait a bit then set it low, sending command to LCD BUSYCHK: bsf STATUS,RP0 ; Port C, all input movlw 0xff movwf TRISC bcf STATUS,RP0 bsf PORTA,5 ;Set Read nop nop bsf PORTB,4 ;E Line high nop nop nop nop bcf PORTB,4 ;Wait, Now low, Polling busy flag btfsc PORTC,7 ; Wait untill busy flag is clear. goto $-7 bcf PORTA,5 ;Turn off Read bsf STATUS,RP0 movlw 0x00 movwf TRISC bcf STATUS,RP0 ;Port c all output return TABLE1: ;Generic Table: Decimal -> ASCII addwf PCL; retlw 0x30 retlw 0x31 retlw 0x32 retlw 0x33 retlw 0x34 retlw 0x35 retlw 0x36 retlw 0x37 retlw 0x38 retlw 0x39 return DELAY: ;Generic Delay decfsz DELAY1 goto DELAY decfsz DELAY2 goto DELAY return end
This code is fairly simple, there are no interrupts to worry about. We set up our variables in available registers as always. Our ports routine is much of the same, we set the appropriate ports for analog function and then set the appropriate I/O
Initialize the LCD:
INITIALIZELCD: ;Initialization Routine for the LCD bsf PORTC,0 ;Put CMD 0 on port C call SEND ;Call Send routine, Sends command and waits for busy flag movlw 0x38 ; Set Function, 8 Bit, 5X10 Dots, Two line movwf PORTC call SEND movlw b'00001100' ; Turn on the Display (0x0B movwf PORTC call SEND movlw 0x06 ; Set entry mode to: Increment Adress counter, no shift. movwf PORTC call SEND return
Remember all those commands? We have to first tell the LCD what we want it to do before we start writing to the DDRAM. We set our LCD up for how were going to use it. This a fairly common initialization routine. We turn on the display, set 8 bit data control, two line, 5x10 font, and we have the address counter increment on write to DDRAM.
This routine we load the command we want into PORTC and then call our Send routine
The Send Routine:
SEND: ;Use this routine to send a command to the LCD. Placement doesnt matter as it does two BSYFLG checks ;Send bsf PORTB,4 ; Set E line High nop nop nop nop bcf PORTB,4 ;Wait a bit then set it low, sending command to LCD BUSYCHK: bsf STATUS,RP0 ; Port C, all input movlw 0xff movwf TRISC bcf STATUS,RP0 bsf PORTA,5 ;Set Read nop nop bsf PORTB,4 ;E Line high nop nop nop nop bcf PORTB,4 ;Wait, Now low, Polling busy flag btfsc PORTC,7 ; Wait untill busy flag is clear. goto $-7 bcf PORTA,5 ;Turn off Read bsf STATUS,RP0 movlw 0x00 movwf TRISC bcf STATUS,RP0 ;Port c all output return
To send data the the LCD we have to toggle the E line. We set it high momentarily then we set it low. Once we do that the LCD reads the command and performs an internal operation. As this happens the BSY Flag is set on the LCD and we poll this until it is clear. After which we continue with normal operation by exiting the routine
Drawing the Static Display:
We draw all the static information by first setting the DDRAM address position and then putting the value we wish to send into w and then calling a routine which sends the value to the LCD
WRITELOC: movwf TEMP ;Move value to temp call BUSYCHK ;Check busy flag movfw TEMP ;Temp -> W movwf PORTC ;W -> Portc call SEND ;SEND return
Basic routine that makes it a little more mechanical to write data to the LCD. The value in W is sent to the LCD
WRITECHAR: ; Function to write a characture movwf TEMP ; Move value to temp register call BUSYCHK ; Check Busy flag bsf PORTA,0 ; Turn on Data movfw TEMP ; Move from Temp to W movwf PORTC ; Then to PORTC call SEND ; Send bcf PORTA,0 ; Turn off Data return
Another routine specially designed for writing a character to a DDRAM address. This sets our PORTA,0 (Instruction/Data) high as we send Data to the LCD.
The MAIN routine:
The main routine is fairly simple, we generate an A2D result and then call the routine which does the dynamic updating of the LCD (we do this 3 times for each of the 3 axis (xyz)
Updating the LCD:
UPDATELCD: ; Routine to update the dynamic elements of the LCD call WRITELOC ; Init the LCD at the proper DDRAM location clrf ONES ; Clear Vars clrf TENS clrf HUNDREDS call CALCULATE ; Call calculate routine which takes raw A2D value and separates them into digits movfw HUNDREDS ; Write the Hundreds digit to the LCD call TABLE1 call WRITECHAR movfw TENS ;Write Tens call TABLE1 call WRITECHAR movfw ONES ;Write Ones call TABLE1 call WRITECHAR return
This is another simple routine, we take the A2D result, break it down into its individual digits and send it to the LCD. We provide the DDRAM address in w before we call the function, thus this can be used for more than 1 axis making our code much more efficient.
Overall this is a fairly easy project that goes over the basics of controlling an HD44780 character LCD using a tri axis accelerometer as an analog input.
Ill leave you with a picture of the completed project as well the source and hex. Enjoy!
Whats next: Making it display proper units
The basis of this project is 5 7 segment displays, One of which the user controls and uses to select a number 0-9, and 4 of which store previously received values. There are 3 buttons; Increment decrement and send, all of do their respectable functions.
This project takes advantage of the following procedures:
Asynchronous Serial Communication – We use the UART module on the PIC16F690 to transmit data from one PIC to another. We use a 9600 baud with the transmitter and receiver set asynchronously. RB5 is RX and RB7 is TX
Multiplexed 7-segment Displays – One problem with running 5 segment displays is the sheer number of pins we’d need to run each one individually. Each display has 7 segments and a common cathode. For all of the displays that’s 40 pins we’d need to run all those displays. Multiplexing is where we only run one display at an instantaneous moment. We do this but loading the data for one display onto PORTC, then using another port to switch the CC connection using a bipolar transistor. We keep rotating the output on PORTC and the CC we are switching until we’ve gotten to the last display. This happens so fast all the displays seem like they’re on at one given time. One thing to consider when using a multiplexed display is that the outputs must be turned off when you rotate the display, otherwise you will get ghosting of the previous value.
Interrupts – We use the interrupt on change and the receive interrupt to handle user input.
#include p16F690.inc __config (_INTRC_OSC_NOCLKOUT & _WDT_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOR_OFF & _IESO_OFF & _FCMEN_OFF) org 0 ;Declare Variables cblock 0x20 DISPLAY FIRSTDISP SECONDDISP THIRDDISP FOURTHDISP CURRENTDISP STATUS_TEMP TEMP DELAY1 DELAY2 GROUND W_TEMP endc goto PORTS ;Skip interrupt ;;;;;;;;;;;;;; ; INTERUPT ; ;;;;;;;;;;;;;; org 0x04 INTERUPT: bcf INTCON,7 ;Turn off interrupts movwf W_TEMP ; Move W to temp bcf STATUS,RP0 ; Goto bank 0 bcf STATUS,RP1 ; Goto bank 0 btfsc INTCON,0 ; Check for IOCA/B int goto BUTTONPRESS ; Yes, goto button press routine btfss PIR1,RCIF ; No, Check if value received goto OUT ; No, leave interrupt call RX ; Yes , receive the value OUT: movf W_TEMP,w ; Restore W bcf PIR1,RCIF ; Clear flag bcf INTCON,0 ; Clear flags bsf INTCON,7 ; Restart interrupts retfie ; Return from interrupt BUTTONPRESS: btfss PORTA,3 ;Check first button call INCREASE ;Goto said function btfss PORTB,4 ;Check Decrement button pressed, call DECREASE ;Goto Said function btfss PORTB,6 ;Check if TX button pressed call TX ; Goto TX routine call DELAY ; Button Debounce goto OUT INCREASE: incf DISPLAY ;Increment display movlw 0x0A ; Move 10d to W bcf STATUS,2 ;Clear Z bit subwf DISPLAY,w ;Subtract w from display btfsc STATUS,2 ;Check if z bit set clrf DISPLAY ; Yes, clear display return ; no, return DECREASE: incf DISPLAY ;Increment display (incase 0) bcf STATUS,2 ;Z bit decf DISPLAY ;Decrement display btfsc STATUS,2 ; Check z bit goto INITIALIZE ;Yes, reinitialize the display decf DISPLAY ;No, Decrement display return INITIALIZE: movlw 0x09 ;Move 9d to w movwf DISPLAY ; Move it to display return TX: btfss PIR1 , TXIF ; TXIF is set if TXREG has no character goto $-1 ; Loop until TXIF is cleared movf DISPLAY,w ; Data to be transmitted over serial port movwf TXREG ; Copy data to transmit register bsf STATUS,RP0 ; RAM PAGE 1 btfss TXSTA , TRMT ; Wait until transmission is completed goto $-1 ; Loop back until transmission completed bcf STATUS,RP0 ; Bank 0 return RX: btfss PIR1,RCIF ; Wait for value to be recieved goto $-1 ; loop bcf PIR1,RCIF ; Clear flag movf RCREG,w ; Move recieved word to w movwf TEMP ; then to var temp clrf FOURTHDISP ;Clear 4th display <- 3rd Disp <- Second Display <- First Display <- Temp movfw THIRDDISP movwf FOURTHDISP movfw SECONDDISP movwf THIRDDISP movfw FIRSTDISP movwf SECONDDISP movfw TEMP movwf FIRSTDISP return DELAY: ; Generic Delay routine decfsz DELAY1 goto DELAY decfsz DELAY2 goto DELAY return PORTS: ; Analog bsf STATUS,RP1 ; Bank 2 clrf ANSEL ; All pins are digital I/O clrf ANSELH ; All pins are digital I/O bcf STATUS,RP1 ; Bank 0 ; Tris bsf STATUS,RP0 ; goto bank 1 movlw 0x00 ; Move the hex value of 0 to multi purpose register W movwf TRISC ; make IO PortC all output movlw b'01010000' ; Move move b'01010000' to w movwf TRISB ; move to tris movlw b'00001000' ;Make porta.3 input rest output movwf TRISA bcf STATUS,RP0 ; goto bank 0 ; Interrupt bsf INTCON,7 ;Set up global interrupts bsf INTCON,6 ;Peripferal Interupts bsf INTCON,3 ; IOCA/B interupt bsf STATUS,RP0 ; Bank 1 bsf PIE1,5 ; Enable Interupt on Recieve bcf STATUS,RP0 ; Bank 0 bcf PIR1,RCIF ; Clear Flag bcf INTCON,0 ; IOCA/B change flag ; IOCB bsf STATUS,RP1 ;Bank2 movlw b'01010000' ; Make ports 4 and 6 IOC movwf IOCB bcf STATUS,RP1 bsf STATUS,RP0 ;Bank1 ; IOCA movlw b'00001000' ;Make porta.3 IOC movwf IOCA ;serial comm transmit movlw d'25' ; Move the value of 25 or 0x19, to the baud rate genrator movwf SPBRG ; this will make the baud rate = to 9600 at a 4mhz internal clock in high speed mode bsf TXSTA,BRGH ;baud rate high bsf TXSTA,TXEN ;Enable Transmission bcf TXSTA,SYNC ;asynchronous bcf STATUS,RP0 ;Exit bank 1 ;Serial comm Receive bsf RCSTA,SPEN ;Set serial port enable high bsf RCSTA,CREN ; Enable receiver ; Initialize clrf DISPLAY clrf FIRSTDISP clrf SECONDDISP clrf THIRDDISP clrf FOURTHDISP movlw 0x01 ;Move 0x01 to ground movwf GROUND movlw 0x20 ;Start pointer at DISLAY location: 0x20 movwf FSR ;;;;;;;;;;;;;;;;;; ; MAIN ROUTINE ; ;;;;;;;;;;;;;;;;;; MAIN: bsf STATUS,RP0 ;goto bank 1 and turn off display movlw 0xff movwf TRISA movlw 0xff movwf TRISC bcf STATUS,RP0 ;bank 0 ;Begin rotate movfw INDF call TABLE movwf PORTC movfw GROUND movwf PORTA ;Turn on Displays bsf STATUS,RP0 movlw 0x00 movwf TRISC movlw b'00001000' movwf TRISA bcf STATUS,RP0 ;Finish rotating rlf GROUND call RA3 incf FSR movlw 0x25 bcf STATUS,2 subwf FSR,w btfsc STATUS,2 call CLEAR goto MAIN RA3: btfss GROUND,3 return rlf GROUND return CLEAR: movlw 0x20 movwf FSR movlw 0x01 movwf GROUND return TABLE: ;Generic lookup table addwf PCL ; 1; retlw b'00111111';0 ;;;;;;; retlw b'00000110';1 6 ; ; 2 retlw b'01011011';2 ; 7 ; retlw b'01001111';3 ;;;;;;; retlw b'01100110';4 5 ; ; 3 retlw b'01101101';5 ; ; retlw b'01111100';6 ;;;;;;; retlw b'00000111';7 4 retlw b'01111111';8 retlw b'01100111';9 return end ;end
We initialize our variables to hold temporary values as well as the current values in each display.
We set all digital I/O, Port C all output, Port A 0,1,2,4,5 as output 3 as input, Port B 4,6 as input, 7 and 5 as TX and RX respectively. The I/O Structure for this code is:
PORTC – Primary 7-Segment driver
PORTA - Drives the line for the ground for the common cathode as well as the send input
PORTB – Handles serial comm., as well as increment and decrement inputs.
We use the interrupt on change for Port A and B; we set up that interrupt for its respectful inputs
We set up the UART module for asynchronous communication, high speed baud and we turn on TX and RX.
We finally clear the display variables, set the ground to the first position and set the pointer to look at the first digit’s memory location (0x20)
We turn off the outputs to prevent ghosting. Since we can only change one port at one time and the display uses to ports to display data we shut off the ports, change the value and then update the ports. Thus we prevent the previous value from being displayed on top of the current one. We display the current value by using the value in that digit’s block of memory into a lookup table and then outputting to PORTC, along with the respectable ground for that digit’s display being HIGH.
After that we update the pointer reference to the next digit and rotate the ground position. We must note however, when the ground is at RA3 (input only) we rotate it once more since were using it as input.
Once we reach the end of the rotation, we reinitialize the displays.
The interrupt routine is fairly simple it handles 4 conditions.
The first two conditions are fairly simple, the interrupt happens we pool for a button press and then we update the first digit’s value.
The send routine, pools for a button press, if it was the first digit is loaded into the TX reg and is sent out.
When a value is received, we enter the RX routine, we store the value into a temp register and we begin updating the displays.
We do this by rotating the display into the next one. I.e. the third display into the fourth, etc.
If no conditions are met, we just exit the interrupt.
This is a fairly simple project but it takes advantage of the PIC’s resources and on board hardware, we end up using 19 of the 20 pins of the PIC MCU. This is a great demo for serial communication between two PIC MCUs. I’ve expanded this project to include two Zigbee RF modules in which the two modules talk to each other wirelessly.
Where to go from here:
There is one major downside to this project, and that is the displays. The 7-segments are horribly inefficient and lack much flexibility. One way I’d like to expand this project is to add a LCD screen and a keypad matrix to expand the user input and possible output.
I went through the explanation of the code very quickly in this article. As the code gets more complicated I won’t be able to keep revisiting basic techniques and explaining everything. That is what the previous posts are for, to lay the basic ground work down. If you need further explanation feel free to email me, I’m glad to break it down if you need it.
Thanks once again for taking the time to read this, Source and hex as always, is below.
Matrices are somewhat interesting. They present a way to organize large and similar data sets. And because of the similarities you can do some interesting things with the numbers. In an electronic sense matrices are grids of components that can simplify i/o over a large amount of components of a similar function. The two most widely known are keypads and displays. Keypad uses a matrix of switches while a display uses a matrix of LEDs or liquid crystals. I'll be demonstrating the schematics and code to operate a simple static 8X4 LED matrix.
This project relies heavily on a correct circuit layout. Matrices are special because of how they're hooked up. Essentially, you provide power to one column and ground one row where they intersect, that LED lights.
The program used for drawing the schematics and making the board is called EAGLE. It is free to download and evaluate at their site
The basic schematic is as follows:
PORTC controls power and PORTB controls ground. Where PORTC = 1 and PORTB = 0 we get an illuminated LED
The objective: To implement a 8X4 LED matrix in a way that is useful for displaying useful information.
1 - Set up the variables
2 - Set up the port
3 - call a table to display the pattern for the first column
4 - Rotate to the next column
5 - Increment count bit to keep track of bit location
6 - check if overflowed
a - if overflowed, clear count
7 - goto beginning
Essentially, Portc cycles so rapidly it seams that all the columns are on at one even though only one is on at a time.
The the "0"s in the look up table also essentially represent which LEDs are going to be lit.
What can be improved: The code can be improved by removing the COUNT variable and just observing and rotating PORTC, the current configuration however is more flexibly and allows expandability.
This version is also STATIC, there is no user implementation if the user wants a different design he has to go into the code and modify the look up table. Future versions will be dynamic.
As always, this is just a short description of the code and its functions. Full code is available below and discussion is welcome!
The board layout was designed is EAGLE as well and is meant to be as compact as possible. No voltage regulation features or external oscillators have been implemented yet. For troubleshooting purposes, this is fine.
Now that we have most of the fundamentals down and you understand the basics behind microcontrollers, lets have a little fun.
Today's project is a 3 speed PWM(Pulse Width Modified) fan controller.
In this project the user selects the fan level (low, medium, and high) and the program adjusts the duty cycle (%on vs %off) of a 20KHZ signal accordingly. By adjusting the duty cycle we can control how fast the fan runs by limiting how long it is on vs how long it is off.
The code is fairly simple and very inefficient, but we will be working on it time to time to improve its efficiency and not waste clock cycles or memory.
The general flow of the program is like so:
1 - Set up environmental variables
2 - Set up ports
3 - Goto main loop
4 - Check for button press, increment/decrement COUNT variable depending on button press
5 - Check the count variable to see what "level" it is set at (High, Medium, Low)
6 - Adjust the duty cycle based on the level
7 - Return to main loop
8 - Call the table and output the level to 7 segment display
9 - Reset loop
Its fairly simple and works pretty well. However i think i should break down each part to help you better understand the garble of letters. The source is free to grab and modify at the end of the post.
We set up environmental variables by using the equ function, we set labels to places in the memory that are unoccupied. Thus, they will store a value like a variable and have convenient names. The three main variables are DELAY, COUNT and TEMP. DELAY is used for the delay loop, TEMP is used for temporary storage and COUNT is the variable we use to set the speed level.
We set up the ports by first switching to bank 1. After we do that we send values to the TRISA, TRISB, and TRISC registers which control ports a,b, and c. A 1 makes it an input and a 0 makes it an output. We send vales buy moving a literal value to multipurpose register w and then moving the contents of w to the appropriate register.
We use a simple goto statement to go to the main LOOP
We use the function BTFSS (Check if bit is SET) and BTFSC (Check if bit is CLEAR). We provide it a register location as well as a bit. btfsc PORTA,0 means check PORTA bit 0 if it is clear. If it is, jump over the next line. If not, continue as normal.
If port a bit 0 is set, the program goes into the increment routine. This routine increments count by 1 and checks to make sure it didn't go past 2. It does this by doing an arithmetic operation with w, in this case, subtraction. If a zero is returned the Z flag of the status register is set. We can use this to advance further into our program. If it is not set (thus it is under 2) we return back to the main loop with count only incremented. If it returns a zero, we clear count, and then return back to the loop.
Same as the increment routine except we add zero and decrement count.
We call the check level routine. In this routine we do several arithmetic operations to count to see what value it has in it. Its value send it to the proper routine to adjust the duty cycle.
High: Set the fan constantly on, call no delay (100% duty cycle)
Medium: Make sure fan is off, call a delay, turn fan on, call another delay, turn it off. (50% duty cycle)
Low: turn off fan, call 3 delays, turn fan on, call one delay, turn fan off. (approx 25% duty cycle)
The Delay routine decrements the variable DELAY1 by 1 until it reaches 0, once it does it resets DELAY1 to 100 and exits the loop, this eats up 100 program cycles or roughly 100(micro)s
RETURN brings us back from the subroutines back to the main loop
We move COUNT to w, call the TABLE sub routine and add W to the program counter, this makes the program counter advance over the other values not desired and return a value to w back to the main loop. We send this value to PORTC which our 7 segment display is located.
We use a goto function to begin again at the front of the loop
GOTO VS CALL
Goto: Simply GOES TO the part of the code, does not return a value
Call: Goes to a section of code and will return to where it was called from once a value was returned with it.
|<< <||> >>|