Category: "General"

Design of a switching motor controller

02/08/13 | by Jfkfhhfj | Categories: General, Code, Projects

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.

Motivation

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

The Basics

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.

The Design

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

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.

Sauce

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.

Files:

Controller Board:
Shield:
 

Intro to finite state machines (FSM)

07/04/11 | by Jfkfhhfj | Categories: Informative, General, Code

I've been out of the game of writing blog posts for a while now, but i promise its only because I've been working on some cool stuff. I'm going to be making a few posts on simple concepts before i roll out some larger projects I've been working on.

Finite State Machines:
A very important part of digital/software design is being able to control how a piece of software "flows", how the user interacts the software and how it behaves to certain stimuli. Finite State Machines associate actions (things your software/hardware does) with a current state and an input combination (Mealy State Machine) or just the current state alone (Moore Machine).

Finite state machines involve breaking your problem down into states, in which each state; the possible inputs to your code produce different results. Its a very simple paradigm, but its often hard to abstract, so we'll start with an example.

An Example:
Lets say we have a digital clock, and we have 3 buttons, there are several features we'd like to implement. Lets say for now, we can only set the time, the alarm time and turn on and off the alarm. If we list all the possible actions we find we can:

-Turn on and off the alarm
-Change the hours of the alarm
-Change the minutes of the alarm
-Change the hours of the actual time
-Change the minutes of the actual time
-So and and so forth.

So now we can do several things; one thing we can do is to have a button for every action. This really tends to eat up hardware and makes the end result sloppy and very user unfriendly (imagine having a button for every aspect of the operation of a more complicated device) Plus we stated above, we only have 3 buttons!

A much better example is to use a finite state machine, in which each button will have a different function dependent on the state that it is in.

For simplicity sake, we will be implementing the design as a Mealy machine (where the action is determined by the inputs and current state.

State Diagrams:
A state diagram is a way of representing the states that a system can take, for our clock we have Three:

State 1: The idle state, in this state we simply keep time, the user can enter programming mode from here or turn on and off the alarm

Button 1 - Will turn on or off the alarm
Button 2 - Enters programming mode (goes to state 2)
Button 3 - Does nothing

State 2: Programming mode for clock/alarm HOURS setting

Button 1 - Does nothing
Button 2 - Sets the current HOURS setting (goes to state 3)
Button 2 - Increments the set value

State 3: Programming mode for clock/alarm MINUTES Setting

Button 1 - Does nothing
Button 2 - Sets the current MINUTES setting (goes back into run mode after)
Button 3 - Increments the set values

So here, we implemented all the functions we wanted to do, with the buttons we had, and we even had ability to add extra functionality to each state!

Here is the state diagram for such a system:

Why FSM:
Besides for making hardware much more simple, FSM also has some major code benefits. The most major is now we can abstract our design into actions. We assign actions to a state/input combination and call this action when the criteria is met, with this we can break down out code and avoid "spaghetti code." This allows us to segment our code into different parts, making things easier to work on in the long run. FSM also allows us to avoid duplicate code, for example, with our clock, we have to increment the minutes and the hours, with this FSM we can write one routine to increment a register and have the current state determine what register to increment. Here we use the current state as a parameter to avoid having two routines for one thing(a simple example). There are many benefits to using FSM in your code, below i included a FSM routine for PIC microcontrollers using lookup tables. The same routine can be written with ROM tables as well.

Some Code:
Below I've written very simple code for a PIC18F micro which allows you to implement a Mealy FSM into your design. It's very simple code and is completely stand-alone, so you can implement it into any project you wish!

cblock 0x00
	INPUT
	CURRENTSTATE
	NEXTSTATE
	TEMP
endc

org 0x00
 
PORTS:
;;;;;;;;;;;;;;;;;;;;;;;
;SETUP STUFF GOES HERE;
;;;;;;;;;;;;;;;;;;;;;;;
	

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;MAIN                             ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
INIT:
	movlw		0x00
	movwf		CURRENTSTATE
	movwf		NEXTSTATE      ;Initialize States to idle

	
MAIN_LOOP:
	call		FSM
        goto        MAIN_LOOP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;This is the main FSM Code           ;
;INPUT must be from 0-256            ;               
;Each state calls the INPUT numbered ;
;Action from the lookup table        ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
FSM:
	rlncf 		CURRENTSTATE,w
	call		GETSTATE
	movff		NEXTSTATE,CURRENTSTATE
	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;Lookup Tables
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
GETSTATE:
	movff		PCL,TEMP
	addwf 		PCL
	goto		STATE0
	goto		STATE1
	goto		STATE2
	return
STATE0:
	rlncf       INPUT,w
	movff		PCL,TEMP	;Pics are stupid
	addwf		PCL
	goto		ACTION0
	goto		ACTION1
	goto		ACTION2
	goto		ACTION3
	return
STATE1:
	rlncf       INPUT,w
	movff		PCL,TEMP	;Pics are stupid
	addwf		PCL
	goto		ACTION0
	goto		ACTION1
	goto		ACTION2
	goto		ACTION3
	return
STATE2:
	rlncf       INPUT,w
	movff		PCL,TEMP	;Pics are stupid
	addwf		PCL
	goto		ACTION0
	goto		ACTION1
	goto		ACTION2
	goto		ACTION3
	return

;State actions
ACTION0:
	movlw 0x01
	movwf NEXTSTATE
	return
ACTION1:
	movlw 0x02
	movwf NEXTSTATE
	return
ACTION2:
	movlw 0x03
	movwf NEXTSTATE
	return
ACTION3:
	movlw 0x00
	movwf NEXTSTATE
	return
end

Code Explanation:
Essentially, we have three system variables, NEXTSTATE, PRESENTSTATE and INPUT, when we call FSM the program uses PRESENTSTATE as an offset for a look-up table, calling the current set state. Once a state is loaded, we use INPUT as a offset for the action associated with a state. Thus we get an action that is associated with an input/state combination.

The major downside to this code is that STATE and INPUT MUST BE FORMATTED! If they are not, you will get erratic program behavior. (I.e. if there is 3 states, the first must be equivalent to 0x00, second, 0x01, and third 0x02, same for input)

This code can also be used with interrupts as well.

 

Introducing the PIC18

12/22/09 | by Jfkfhhfj | Categories: Informative, General

I figured it'd be a good time to move to a more capable microcontroller as the projects on the blog seems to be getting more and more complicated and lets face it, keeping up with our PIC16 and their limitations can get a little annoying when you're moving lots of important data. We need more pins, we need more features on the chip and we need better instructions.

Enter the PIC18 series from microchip.

http://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=2696&param=en537796

The PIC18 is the most high end 8-bit microcontroller that microchip offers. It has many advantages over the lower end PIC16s the biggest being a new and improved instruction-set and memory management system.

I suggest you take a look at a datasheet for a PIC, everything in this article will reference:

http://ww1.microchip.com/downloads/en/DeviceDoc/39605F.pdf

A new memory system:

The coolest thing with the PIC18s is an Access Bank system. In this system the MCUs RAM is divided up into 16 banks (0-15)> Each bank has however many memory addresses(Depending on PIC, for example the PIC18F1220 has 256 byte banks) and you select which bank you want with the BSR (Bank Select Register). This sounds awfully similar to ram-pages of the PIC16F however its the access bank delimiter in the opcode that makes life so much easier. The access bank is the first 128 Bytes of bank 0 (Our General purpose registers) and the last 128 of bank 15 (Our Special Function registers) by default all operations are done in the access bank (a=0)This allows us to access a good amount of GPR and all of our SFR without ever changing what bank were working in. No more STATUS,RPx calls. However if we want to access ram outside the access bank we have to set the access bank delimiter call in the instruction to 1 and then by default you will be working in whatever bank happens to be set in the BSR.

Software accessible Stack:

I'm not too familiar with this feature, but the PIC18 allows you to control the stack from software, you also have complete control over the stack pointer, this is extremely powerful and not offered on the PIC16

New and improved modules:

The PIC18 series features an 8-bit hardware multiplier. Microchip provides sample code for doing all sorts of fun arithmetic. This is good if you want to crunch numbers but don't want to go into a full DSP. Also some PIC18s feature on chip USB and other goodies.

Priority interrupts:

This is another really cool feature. The PIC18 features two interrupt vectors, one high priority and one low priority. This allows you to have a really complex interrupt scheme allowing really sophisticated user interaction. For every interrupt you can choose what priority it is. An example of this is having a sample system for a sensor have highest priority while the user interface is low priority, that way even if the user is interacting the device to cause an interrupt, you don't interrupt the sample of the sensor if it is time sensitive.

NEW INSTRUCTIONS!:

This feature is going to take me the longest to get used to, but the PIC18 features a plethora of new instructions to make our lives easier. One nice and needed one is a MOVFF instruction (move from one register to another) These instructions are meant to speed up your code and make execution much faster. All new instructions follow a similar style to the traditional ones, they just do more for the clock. You can take a look at them on page 193 of the datasheet.

Free C compiler:

Microchip offers a free c-compiler for the PIC18 series for MPLAB, i haven't used it but its free and its a c compiler.

Tons of other stuff i still have to figure out:
I'm working on it :P

The PIC18 series is a really powerful line and there isn't much that they cant do. The first project i will be posting with them is a Digitial Audio Processor, we'll be taking advantage of the priority interrupts to make really fast and efficient code.

Hope to see you soon, happy programming!

 

The Fundamentals of Digital Audio

12/08/09 | by Jfkfhhfj | Categories: Informative, General

Digital audio is a very powerful thing, we listen to our iPods every day, we stream music from websites using our computers and we work with many algorithms derived from techniques in digital audio. Digital audio has its roots in wave mechanics and is a very fundamental process for modern computation. We will be discussing sound as a wave, some basic properties of waves and how to convert a wave into something which you can store in memory, and techniques to play that wave back. We’ll start with an introduction to sound and how to represent sound as a wave, and a mathematical function.

An introduction to sound:

Sound is a wave, and like all waves it transfers energy through a medium, in this case it is usually air. Whenever something emits a “sound” it compresses air molecules, these compressed air molecules exert a force on uncompressed ones, this starts a chain reaction and sound propagates; this is the essence of a sound wave. For sound to exist you need a medium, be it air, a solid, liquid or plasma. The sound wave will travel through that medium until it dissipates completely and is undetectable. Sound is usually the result of a force or stress on an object, you hit a tuning fork on a table, and it will vibrate at a particular frequency. To better visualize a sound wave mathematically, we will look at strign vibrations.

String Vibrations:

Like a turning fork, a string when plucked vibrates a particular frequency. The frequency at which a string vibrates at is due to its mass, length and tension on that string. The frequency which is most prevalent is called the fundamental frequency, when you play middle c on an instrument the fundamental frequency is 261 Hz. Now if middle c is a set frequency, why does middle c sound different on different instruments? The answer is because the sound from an instrument inst pure, each instrument has a unique harmonic structure which delivers a unique sound. To understand this more let’s look at the math of a wave.

Mathematical representation of a sound wave:

The nice thing about waves, is we can represent them mathematically, the simplest being that of a sine wave. Sine is a natural function which oscillates between crest to crest at a particular frequency. We can represent a pure oscillating sound by the following function.

Where A is the amplitude (sine is a function which oscillates between 1 and -1, multiplying it by A will result in A as the amplitude). The Greek letter omega is the angular frequency which is denoted in radians/second and the Greek letter phi denotes the angular offset (for all cases in this article we can consider it zeros). A “Pure” sound is that which is sinusoidal, we here these all the time on cheap synthesizers, when a sound is sinusoidal, it can be represented by the above formula alone. If a synthesizer were to play middle c, it would produce a sine wave of frequency 261Hz (omega = 640) and what you would notice is that it sounds like the right pitch, but no natural instrument sounds like a pure tone. When an instrument makes a tone, it is usually comprised of its fundamental frequency and higher order oscillations called harmonics. Harmonics are usually either ½(Strings) or ¾(Closed tube) integer multiples of the fundamental frequency of a lesser amplitude. Because of this we can consider and natural tone a superposition of fundamental sine waves and harmonic sine waves. Most tones in nature are comprised of a superposition of sine waves. We can use this fact to break down any complex sound into a series of many simple ones governed by the very simple formula above.

Synthesizer theorem:

Breaking a complex sound into a series of sine waves can be done using a Fourier series. A Fourier series simply is a mathematical series which can break apart any repeating oscillations into a series of simpler sine waves. We use a Fourier transform to translate an arbitrary function into a series of sine functions. For the purpose of this article we’ll use a Fourier series as a concept and we won’t be exploring the math behind it (Maybe one day).

To give a quick example of this we can consult Wikipedia. We all know and love our humble saw tooth wave; If we find the Fourier series of a saw tooth wave we get the following series as a result.

If we take n=1 we get the fundamental frequency of the series:

If we take the first 5 terms we get:

As we take n to infinity, we get a better and better approximation of our function. This is the fundamental way in which analog synthesizers would generate more complicated sounds, they would take several sine wave oscillators which they would combine in ways to reproduce a wave, and they wouldn’t need many because after a while the approximation was good enough to replicate the sound. This is fine and dandy, but in digital circuits we do not use sine waves as our building blocks, instead we use impulses.

The Impulse wave:

An impulse wave is completely different from a sine wave, instead of taking a wave as an infinite series of sine waves; we take the function and evaluate its value at a specific time t. We break the function up into many steps and by taking the value at every step we can reconstruct the wave. In this scenario as step size goes to infinity, we get a better reconstruction of the wave.

In this graph we have 5 impulses (red) that make up the waveform (green). Each impulse exists at time t and has a definite height to it. We break up a wave into specific impulses with definite height, we do this every sample. If the red waveform were 10KHz and we had 5 impulses per period, our sample frequency would be 50KHz. Each sample all we do is record the height of the waveform at that time.

="http://mcuplace.com/mcu/media/blogs/blog/impulse.jpg" alt="" title="" width="400" height="280" />

If we were to reconstruct the wave using our impulses we would get the following:

="http://mcuplace.com/mcu/media/blogs/blog/step.jpg" alt="" title="" width="400" height="280" />

As you can see, its not a pretty picture, but if you were to filter the signal, you would get a pretty good reconstruction of the original. To increase reconstruction we increase the sample rate and get more samples of the waveform per period. A wave with 100 samples/wave looks much more accurate than one with 10 samples/wave.

One golden rule is that your sampling frequency should always be above that of your highest recorded sound (Or at least the ones you would like to capture)

Digital Audio:

The heart of digital audio is taking an analog sound signal in the form of voltage over time and doing periodic analog to digital conversions and then storing these results in memory for later use.

Lets go back to our graph with the impulses. Lets say the vertical axis is voltage and the horizontal is time. Every impulse we sample a wave:

IMPULSE # -- VOLTAGE -- A2D(Int)(VDD=5V) -- T(uS)(@50KHz/Sample)
1         --  .1     --  5               -- 20
2         --  .2     --  10              -- 40
3         --  .3     --  15              -- 60
4         --  .4     --  21              -- 80
5         --  .5     --  26              -- 100
6         --  .1     --  5               -- 120
7         --  .2     --  10              -- 140
8         --  .3     --  15              -- 160
9         --  .4     --  21              -- 180
10        --  .5     --  26              -- 200
etc...

In all digital audio systems there are two things which determine your bandwidth.
The first is bit-rate which is how many bits you use to convert the signal. A bit-rate of 8-bits can break a signal up into 256 parts, thus a 5V signal has an ideal resolution of (5/256) 19mV(not bad!).

The second is sampling-rate, this is how often you sample a waveform, typical MP3 sample rates are about 44.1KHZ or one sample every 22uS.

The bandwidth of a signal is simply the sample-rate times the bit-rate.

The advantage of a digital system is that 22uS is rather slow, we can do a lot of work between sampling and storing a waveform, and with some good code we can produce high-quality powerful audio systems which are easy and cheap to make.

What now?
So now that we have an introduction to basic properties of sound, what can we do now? In upcoming posts i will be discussing the PIC18 series of micro-controllers, and some sample code to do audio pass-through and eventually storing and playing back a waveform.

 

Weak Pull up Resistors.

09/21/09 | by Jfkfhhfj | Categories: Informative, General

I haven't posted in a while so i figured i'd get back into the swing of things by posting a short post on the pic's internal weak pull up resistors.

What are pull up resistors?

A pull up resistor is a fairly high resistance (usually 1K for TTL and 10K for CMOS) resistor to pull a pin to a high logic level. This is very useful as this prevents your logic levels from floating between 1 and 0. This also lets to prime a pin to a certain logic level and have an event (such as a button press) be used to then pull your pin to another logic level.

You can also pull pins down to ground if you desire using the same method (except hooking the resistor between the pin and ground instead of the pin and vdd)

For the pic16f690 you can enable WPA by clearing bit 7 of OPTION_REG (cleared by default) and setting the appropriate bits in the WPUA(bank 1) and WPUB(bank 2) registers.

This feature is extremely useful in keeping more hardware inside the pic and using as little external hardware as possible.

Often you will pull up a pin to vdd and have a button press short the pin to ground (making the pin a 0) instead of having an external resistor you can just use the on-chip WPA resistors.

So, just a short write up for now, but i have plenty of stuff coming in the next coming weeks.

I would like to introduce the PIC18 line of microcontrollers and begin posing some more sophisticated tutorials.

I will also be updating the circuit blog to include some soldering advice.

Most importantly, i need suggestions and comments, if there is a guide you would like to see, please don't hesitate to ask!

I'm looking forward to the next year to bring even more exciting content to my blog!

 

PIC Memory Organization & EEPROM Control

04/01/09 | by Jfkfhhfj | Categories: Informative, General

One of the most key features of any electronic system is how memory is organized. This includes how much Ram and Rom, how they are subdivided and how to access the memory. Every series of PIC is different and every PIC has different amounts of memory. It is your job to understand how memory is mapped in your system and to use if efficiently.

A memory system defines a system, it is the one thing that makes it unique and when you master it, you master a system. This has two effects, it makes a system hard to master, and it makes other systems easier to use. Every system from X86 to PowerPC to our PICs are essentially the same thing, ROM to store program data, RAM to store variables and running code and a processor to crunch data. No matter what, a processor might have different instructions, but it still is a beast that doesn’t change too much. ROM and RAM define a system; every system is going to use it differently. The logic rarely changes system to system however the code can do a complete 360 depending on what system you use just because of how memory is mapped.

First let’s define a few things:

RAM (Random Access Memory): This is the place where your variables reside (General Purpose Registers), and how you access special function registers. When you setup a Cblock or set a variable to an address, this is where it’s going. RAM is super fast but very limited, and everything is stored temporarily. RAM is merely to hold values that are important to current operation.

ROM (Read-Only Memory): ROM is like your hard disk, its where all permanent data is stored, like your program. This is marginally slower but not too slow. Unlike your hard disk, this ROM is super fast since it’s on die. When you program your code onto a PIC this is where it goes.

Since a lot of this blog focuses on the PIC16F690 I am going to be going over the memory architecture of it.

Everything on memory organization can be found in the section 2.0 Memory Organization:
http://ww1.microchip.com/downloads/en/DeviceDoc/41262E.pdf

The first section goes over a few things you should know:

The RAM is partitioned into 4 banks (0-3). Depending on the data you’re addressing depends on which bank you need to access. In each bank you’ll find a mirage of General Purpose Registers, used for storing program variables, and Special Function Registers, used for controlling the microcontroller. The next few tables’ show where these SFR reside and what banks they’re located in. The rest is just trivial stuff we’ve gone over before.

Likewise how the RAM is partitioned so is the ROM. The ROM is compromised by Program Memory and Data Memory. Program memory is reserved for your program (however on PIC18’s and above you can write to it and use it for data). Data memory is used exclusively for storing data. On the PIC16F690 it is 256 Bytes. Writing and controlling this DATA ROM is not as straight forward as controlling a GPR.

The PIC has a built in EEPROM control module which you use to read and write to EEPROM.

The EEPROM Control Module:

This module is fairly simple and allows you to store program variables in hard data for later use. 256 Bytes doesn’t sound like much but it does come in handy.

The Registers:

EECON1: Control register.
EECON2: Non implemented register used in writing data (see below)
EEDAT: This is the register that holds the data to be written or has been read.
EEDATH: Some memory is larger than 8 bits so this is the High portion of the register that contains the MSB of a non 8-bit number.
EEADR: This is the register that holds the memory address of the a data to be read or written
EEADRH: The high register for EEADR for non 8-bit addresses.

The Sequence:

Write (Data memory only):
Configure EEPROM for write
Set the address to be written to EEADR and EEADRH
Set the data in the data registers
Initiate a write sequence and wait.

Read (Data and program memory):
Configure EEPROM for read
Set the address to be read to EEADR and EEADRH
Initiate a read sequence and wait.
Data will be in the data register.

The sequence is pretty straightforward, the only tough part is the control so lets go over EECON1.

EECON1: EEPROM control register
Bit 7: EEPGD – 1: Access Program Memory 0: Acess Data memory
Bit 6-4: Unimplemented
Bit 3: WRERR – 1: There was a write error 0: Write operation completed
Bit 2: WREN – 1: Allows write to EEPROM 0: Write is not allowed
Bit 1: WR – 1: Initiates a write 0: Write is complete
Bit 0: RD – 1: Initiates a Read 0: Read is complete.

As you can see, this module is pretty simple a sample read code will be something of the sort:

clrf  EECON1 ;Read Data memory, do not allow writes
movlw 0x00
movwf EEADR
movwf EEADRH   ;Clear address location start at 00
bsf   EECON1,0  ;Initiate read
btfsc EECON1,0  ;Wait till clear
goto  $-1
movfw EEDAT     ;Move read data to where-ever
movwf DAT

Reading from EEPROM will be very similar to the above routine just make sure you're in the right banks for each register (EECON1 and EEDAT/EEADR aren't in the same bank)

Writing to the EEPROM isn't exactly as intuitive as reading but is still pretty easy:

Just like above, you set up the EECON1 register for whatever you're doing and move the data you want to write into EEDAT and the address you want to in EEADR and EEADRH. After you do that, you initiate a write sequence such as the following:

WRITE:
	bsf      STATUS,RP0
	movlw    0x55      
	movwf    EECON2
	movlw    0xAA
	movwf    EECON2
	bsf      EECON1,WR   
	btfsc    EECON1,WR
	goto     $-1
	bcf      STATUS,RP0
	return

Now, to write to data EEPROM you must write 0x55 then 0xAA to EECON2 (one after another) and then set the WR bit on EECON1.

This sequence is what is required by Microchip in their data sheet.

Other thank that, EEPROM control is fairly straight forward and is very powerful.

Good luck and any questions/concerns please feel free to send me an EMAIL.

 

Pointers

11/05/08 | by Jfkfhhfj | Categories: Informative, General, Code

Pointers are a very fundamental part of programming, often you need to reference a memory location without directly addressing or calling it, pointers help us do that. With c/c++ pointers can be a little daunting because how they work isn't directly apparent. Luckily for us, assembly works directly with the ram of the MCU and using pointers is extremely easy and almost effortless.

The use of pointers with a PIC mcu is called indirect addressing. We use the FSR(File Select register) and INDF. Essentially we load the value we want to point to into FSR and we address it using INDF. Fairly simple, lets look at an example:

VALUE equ 0x20

movlw   0x03
movwf   VALUE
movlw   0x20
movwf   FSR
movf    INDF,w
movwf   PORTC

The value on port c will be 0x03

First, we set up our variable VALUE to the general purpose address of 0x20.
Then, we set it equal to 0x03
We move the value of 0x20 to FSR (the location of VALUE)
Then we move INDF (The value of the address we loaded into FSR in this case 0x03) to w
Then w to PORTC

Very simple, with this in place you can save memory and make your programs extremely efficient and quick. Using the FSR and INDF will be essential to any remotely complicated code such as communications and robotics and other advance topics.

 

Time

11/04/08 | by Jfkfhhfj | Categories: Informative, General, Code

As your code gets more complex and so does the tasks you wish to perform its often important to take advantage of the build in hardware and gain the ability to multitask. One of the last modules to go over is the timer module. There are several timers build in but we will be focusing on the TMR0 module.

Using the module is fairly straight forward you can turn it “on” with about 3 lines of code. The TMR0 module is a 8 bit timer, thus after 256 clocks it carries over. We can also set a prescaler that sets what the ratio of the timer to the clock is. This prescale value has a max of 256 as well. With max prescaler our timer will reach a max time before rollover of 65536 instructions or .06 seconds. If you need a timer that lasts longer, the other modules hold larger values and thus rollover at a later time.

The Registers:

OPTION_REG: (bank 1) Holds timer configuration information.

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
PORTS:
	bsf       STATUS,RP1
	clrf      ANSEL          ; All pins are digital I/O
 	clrf 	  ANSELH         ; All pins are digital I/O
	bcf       STATUS,RP1	
	bsf       STATUS,RP0
	movlw     b'0111'    	 ; Turn on 256 prescaler
	movwf     OPTION_REG
	bcf          OPTION_REG,3   ; Prescaler set to timer0
	bcf       OPTION_REG,5	 ; set timer for internal osc
	movlw     0x00           ; Move the hex value of 0 to multipurpose register W
              movwf     TRISC          ; make IO PortC all output
	bcf       STATUS,RP0
	clrf      PORTC
MAIN:
	movlw     0x01
	xorwf     PORTC
	btfss     INTCON,2  ; Check if timer overflow flag is set
	goto      $-1
	bcf       INTCON,2
	goto      MAIN
end


This code enables the timer and uses it to flash an LED when it rolls over.

Breaking it down:


movlw b'0111' ; Turn on 256 prescaler
movwf OPTION_REG
bcf OPTION_REG,3 ; Prescaler set to timer0
bcf OPTION_REG,5 ; set timer for internal osc

This is all the code we need for the timer module. It’s fairly straight forward. We mostly change OPTION_REG to set the timer for what we want.

OPTION_REG:

bit 7 RABPU: PORTA/PORTB Pull-up Enable bit

1 = PORTA/PORTB pull-ups are disabled
0 = PORTA/PORTB pull-ups are enabled by individual PORT latch values

bit 6 INTEDG: Interrupt Edge Select bit
1 = Interrupt on rising edge of RA2/INT pin
0 = Interrupt on falling edge of RA2/INT pin

bit 5 T0CS: Timer0 Clock Source Select bit
1 = Transition on RA2/T0CKI pin
0 = Internal instruction cycle clock (FOSC/4)

bit 4 T0SE: Timer0 Source Edge Select bit
1 = Increment on high-to-low transition on RA2/T0CKI pin
0 = Increment on low-to-high transition on RA2/T0CKI pin

bit 3 PSA: Prescaler Assignment bit
1 = Prescaler is assigned to the WDT
0 = Prescaler is assigned to the Timer0 module

bit 2-0 PS<2:0>: Prescaler Rate Select bits
(table in datasheet)

For the TMR0 module the bits we care about are 0,1,2,3 and 5. For basic operation we set bit 5 low, using the internal oscillator for the clock source. We assign the prescaler to the timer module and not the Watch Dog Timer. We also assign 111 to the prescaler which is the max value.

With that set, our basic code does a XOR operation with the current contents of PORTC, waits until the timer overflows by polling the TMR0 overflow flag bit (INTCON,2) and then returning to main and doing it all over again. In the end we get a blinking LED using highly efficient code.

There we have the TMR0 module in a nutshell. Most of the other timers work in a similar fashion. With this hardware under your belt the possibilities are endless.

Assembly Source

HEX

 

A2D

11/04/08 | by Jfkfhhfj | Categories: Informative, General, Code

It is often very useful to create a digital representation of an analog signal. The primary way we do this is through a analog to digital conversion. This is done by comparing Vin with a reference voltage (Vref) and converting their ratios to a binary number.

Fortunately for us the PIC microcontroller has analog to digital converters built into the hardware. Doing an analog to digital conversion does take time however, but it is an extremely valuable resource. We often use analog to digital conversions for sensor applications, or anything where voltage varies with conditions.
Doing an A2D conversion

As with everything in the PIC, the first thing we have to do is enable the hardware and then set up the port to do an A2D conversion.

The registers:

ADCON0: (bank 0) Stores the config information for the A2D module
ADCON1: (bank 1) Sets the clock for the A2D conversion
ANSEL: (bank 2) Determines whether a port is digital or analog, for A2D we obviously want whatever port were using to be analog
ADRESH: (bank 0 ) Results of conversion are here.

The Code:

    #include 
     __config (_INTRC_OSC_NOCLKOUT & _WDT_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOR_OFF & _IESO_OFF & _FCMEN_OFF)
org 0                                               ; Beginning of code
DISPLAY equ 0x22                        ; DISPLAY var stored at General purpose register 0x22
PORTS:
	bsf       STATUS,RP0        ; select BANK 1
	movlw     0x00               ; Move the hex value of 0 to multipurpose register W
        movwf     TRISC              ; make IO PortC all output
	movlw     0xff                 ; Move the hex value of FF(255) to multipurpose register W
	movwf     TRISA              ; Make  PortA all inputs
	movlw     0x10               
        movwf     ADCON1         ;set A2d Clock to FOSC/32
	bcf       STATUS,RP0       ;Exit bank 1
	bsf       STATUS,RP1       ; BANK 2
	movlw     0xff     
	movwf     ANSEL            ; Set PortA for all analog 
	bcf       STATUS,RP1      ; Exit bank 2, Enter bank 0
	movlw     0x01            
	movwf     ADCON0      ; Configure ADCON and turn it on {Left just, VDD=VREF, RA0,A2D ON}
	clrf      DISPLAY             ; Clear Display Var
MAIN:
	call      A2D                    ; Call subroutine
	movf      DISPLAY,w     ; Move Display to w	
	movwf     PORTC         ; Move W to portC
	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 until conversion is complete
	goto      $-1                 ; Until then keep going back
        movf      ADRESH,w  ; Move result to w
	movwf     DISPLAY    ;Then w to Display
	return
 end

The Breakdown:

Most of the code is fairly straight forward, we set up the A2D module in the ports routine and then we do a conversion on RA0 and display the results on PORTC.

movlw     0x10               
movwf     ADCON1         ;set A2d Clock to FOSC/32

These lines move a literal into ADCON1, ADCON1 is the register for the clock of the conversion module, this one sets the time to the oscillator by 32, which is fairly slow. The higher the impedance of your input, the longer you should let the conversion take. The longer the clock, the more data you acquire and the more accurate your results are. However set this to what best fits your application


movlw     0xff     
movwf     ANSEL            ; Set PortA for all analog 

This merely tells the PIC to set porta up for analog signals, if you’re doing a purely digital circuit it’s wise to set these low

movlw     0x01            
movwf     ADCON0      ; Configure ADCON and turn it on {Left just, VDD=VREF, RA0,A2D ON}

ADCON0 is the configuration register of the A2D module, here you set the VREF source, how the data is outputted, and what port to run the conversion on and weather to start or stop the conversion. The datasheet explains what each bit does. This setup, we have our data left justified (A2D is a 10 bit operation, bit we only want 8, so we have the data start with the first 8 bits rather than the first 2), Our VREF is VDD, channel 000 is set (ra0) and we turned the module on.

A2D:
	nop              
	nop
	nop
	nop
	nop                               ; wait 5 uS
	bsf       ADCON0,GO  ; Start the conversion 
	btfss     ADCON0,GO ; Bit will stay high until conversion is complete
	goto      $-1                 ; Until then keep going back
        movf      ADRESH,w  ; Move result to w
	movwf     DISPLAY    ;Then w to Display

This is the main A2D subroutine. The module works by charging an internal capacitor; this takes time to charge so it’s always good practice to kill some time before the conversion starts. Once you set the ADCON0 GO bit high the conversion starts. After the conversion finishes the bit will go low, so we continue to poll that bit until it does so, and then we move the contents of ADRESH to wherever we want. In this case we move it to DISPLAY.
There we have it, a basic Analog to digital conversion. It is worth your time to check out the chapter on A2D conversion in the PIC hand guide. Good luck on your future code, the source and compiled hex are located below

Assembly Source A2D.asm

HEX

 

Hello world!

08/19/08 | by Jfkfhhfj | Categories: General

Welcome to the awesome world of microcontrollers! Lets start things off by talking a little about myself. I am a Freshman at Stony Brook University pursuing a degree in Physics and Computer Engineering. I am mostly self taught on my knowledge on circuitry and MCUs. I have done tons of research over the past few months as well as faced my own share of buggy code and non compliant hardware.

I mostly work with the PIC series of MCUs and i do all my work in assembly. I have experience in C but i prefer assembly as it gets you much closer to the device and the inner workings of the hardware. I am working on several projects including robotics, simple displays and other digital systems. Over the next few days i will be posting my writings on how to get into working with microcontrollers and my code and resources.

I hope you enjoy as i defiantly will writing and researching ^.^

 
February 2017
Sun Mon Tue Wed Thu Fri Sat
 << <   > >>
      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        

Ads by Google

Mcuplace.com Store!

This blog is dedicated to working with digital circuitry and the use of microcontrollers, small compact computers on a chip. I will be encompassing many techniques to develop projects, tools to use to write and assemble code and i will be sharing any projects i am currently working on. User feedback is a must! I do not know it all, hell im not even that experienced, but without a general place to get all the info needed i find it very hard to get into the world of microcontrollers without pursing a CE degree. So come one come all and enter the world of mystery and creativity!

Search

  XML Feeds

powered by b2evolution