I was recently asked to document the system that I use to control stepper motors (or any motor, for that matter) by sending serial commands with a computer. I realize the concept can be intimidating, but is really not that complex. It involves three components which are very easy to find (or already have) today.
The first component is a computer with USB (if you don’t have access to such a computer, you may be reading this posting in 2027 when such contraptions will be obsolete), the second item is an USB to Serial chip such as those commercially available from FTDI or TI. The third and last component is a microcontroller of your choice, but with an UART port. Figure 1 shows the block diagram for this typical implementation.
If you have a computer with a serial port, then all you need is the RS232 to TTL converter, but this implementation is so Flintstone technology I am not even going to bother to document such an oldie.
The computer job is to send serial commands, which consists of data packets the microcontroller will decode. That’s it! It really is not so complex, although the truth is there is some coding you will need to take care of, and this is what intimidates most people. Luckily, I will detail all the aspects of this useful control methodology and you should be able to create your own controls in no time. Have in mind that this is like the first million: the first one is the hardest to make. The subsequent modules, just follow easily.
An introduction To This Mess
In order to make this work, you need to be both a firmware and a software programmer. Sounds like a nightmare, but chances are that if you are one, you are the other. When I started doing this I chose VB6 as my windows software to send the data packets. Yes, I am that old… Today, however, VB6 is beyond obsolete and my recommendation is to pick up one of the .NET framework compilers. At the moment I am using VB2010, although I hear that the best platform out there is C#. I am not certain what the differences are so I will let it up to you to choose one or the other.
That being said, as I utilized VB6 to code my applications, a few dozen people approached me with their intention to reutilize the hardware but by using their own coding platform. As of today I know of people successfully interfacing to my hardware platform using Lab View, Borland’s C++ Builder and MatLab. If you can send data packets, that’s it.
I did hear about a poor soul some time ago who wanted to use the Windows terminal to communicate with one of my boards. Luckily it was easy to dissuade him from pursuing this abomination of a technique. So even if you know how to make it work, I recommend you to try and stay away from that last one as I think it will be more troublesome than helpful.
All we need the Computer software to do is packetize a series of bytes into a data stream. There is some science to all of this matter, but we do not need to riddle our minds with all these details at the moment. If you want to be continuously sending packets to control some real time function, you may then need to resort to doing the math on serial bus utilization and such. I, however, prefer to let the microcontroller do all the dirty work, so the computer’s task is to send a few bytes every now and then.
At the moment, the most typical protocol I use is a 5 byte data packet where the first byte is an opcode and the other four bytes are parameters. Notice there is no particular reason to make the packet size 5 bytes wide other than I found this to be more than enough for the great majority of applications I work with. Sometimes I have found that 9 bytes would have been better as I can write four words instead of two. However, I can always send consecutive packets and fill a table of words if need be.
If you are going to be continuously sending numerous data packets to the microcontroller, I suggest you increase the packet size to whatever suites you best. The reasoning is that there is no need to send the same opcode a gazillion times if you are just going to be doing the same thing over and over. It makes more sense to minimize the number of useless bytes and optimize the serial port utilization. Now, we could end up having a religious discussion on how to size the data packet according to utilization, and since the amount of data storage on the internet is not infinite, I might as well move on.
Every now and then I have seen serial data protocols where a check byte is added at the end. It is often a CRC code manufactured from the first data bytes and to tell the system whether one of the bits was severely affected during the transmission. I think for this application this checkup is not necessary because the USB chip is so close to the MCU, there is not much chance for noise to couple in. If you have a long line distance, then you might as well add the check or you will be risking data integrity. Do note that adding a CRC check would require the system to analyze every data packet and react accordingly so this is an extra level of complexity you better be ready to deal with. I would only use it if utterly necessary and since I have never experienced communication disruption issues, I decided not to use it.
Do note that the great majority of my applications are more play time than serious applications. I think that for applications in which the life of a user is at stake, a CRC is a must even if you can insure zero noise (which you truly cannot ensure anyway). You can decide whether your application will need to add this feature or not.
Henceforth, figure 2 shows my typical data packet structure:
Like I had specified, the first byte is called the opcode. The subsequent bytes are the parameters which will tailor the operational code accordingly. Note the parameters do not need to be all filled. It is possible for commands to only consist of the opcode in which case the parameters are junk. It is also possible to only need one parameter byte, with the other three bytes being completely ignored. However, once you set on a static byte count protocol, the specified number of bytes must always be sent, whether they contain useful data or not. In other words, even if all that matters is the opcode, you still need to send the five bytes, and in this case the parameters bytes can be padded with zeroes or just left blanck. But they must all be sent!
The reason is we will code the firmware to react to a number of bytes being received over the UART port. Once the right amount of bytes are received, we will call this event a Message Complete event. If more or less bytes than needed are received, then communication havoc ensues. I will document later how to clean up this mess, in case it happens.
Going back to the opcode, it is easy to see that with a byte we have up to 256 different commands available to use. I can assure you this is more than enough. As of today, I have not been able to code an application using even half of that. And some of my applications are plain old massive!
So what does all of this mean?
As an example, you can have a command, say opcode 0x01, to start a motor and yet another command, say opcode 0x02, to stop the motor. You could then use the parameters to specify at what speed you want to start the motor and what direction you want the current to flow. An implementation such as the one depicted in the figure 3 below would work perfectly, but is not the only way to do it.
Another option would be to have a command, say opcode 0x03, to control the motor and then use one of the parameter bytes to determine whether the command is to turn the motor ON or OFF. In figure 4, I am using the first parameter byte to contain the 8 bit PWM command and the second byte parameter as a collection of bits from which one of them is to be decoded as the Direction, and the other as the Enable/disable. Is up to you and there is no rule as to which way is correct or incorrect.
As I will detail in the firmware section, the microcontroller code will use the opcode to execute code from something like a lookup table. It is in reality a SWITCH CASE Statement where the opcode byte is the switch. So as you can see, only the code of that command is executed.
Notice that there are also no rules as to what the data bytes are. You configure them to be whatever you want them to be. They can be GPIO levels you will want to output, special configuration parameters for control signals, or any other parameter you want to convey to your state machine. For example, the four bytes could be a motor PWM output, or a stepper stepping rate. Heck, it can be a collection of 8 bits which you will use to decode different aspects of the engine on a one by one basis. It is all code, so the sky is the limit.
Once the opcode is decoded and executed, I like to have the serial port return some kind of message. There are a few reasons why this is useful. First, it can be used as an acknowledgement that communications are working. If the microcontroller was programmed to return nothing, it would be impossible to know if the command even made it in. Sure, if the command is to turn the motor on and the motor does turn on, then it is obvious the command made it through. But I also like to have commands in which I just write bytes to a register. Unless I am on debug mode, how could I know that this happened?
Getting a message back is a clear indicator that the routine executed completely and that the message was accepted. As I will explain on the firmware section, an acknowledge can only happen if the message was executed, so this works nicely.
The other reason why having a message back from the microcontroller is a good idea is to actually get information back from the system. This could be any data, such as a reading from an ADC channel, the state of a variable, input levels from GPIO pins configured as inputs, etc. With this read back you can now make the packetizing application work interactively with the system. Otherwise, the operation would be in essence open loop. Although the great majority of my applications do work in the open loop fashion, I like the ability to read back every now and then.
To read back, I have selected a data packet size of three bytes. Why? Because this is enough. Again, this is up to you and there is not much science other than that as defined by your application. If you need lots of data back with high frequency, it may make sense to make this data packet larger than three bytes. If all you want is an acknowledge, then even a single byte will do. The reason why I chose three is because the first byte was meant to be the firmware revision (which I hardly use) and the other two bytes are a word I may want to read every now and then.
I have found that the most times I use the read back function is when I am using a subsequent form of communications. Per example (and this is going to sound preposterous, but it happens to be quite useful) you may want the microcontroller to actually be an I2C or an SPI port to yet another device. Let me offer the big picture in case you are confused. The PC uses USB to talk to the USB2UART chip. The USB chip talks to the microcontroller through the UART and then the microcontroller talks to the other chip through the I2C or USB port. And then all the way back into the PC…
You may be asking, why on Earth not just use an USB to I2C or an I2C to SPI? Sure, this is definitely an option and if that is you need, then it should be the technique you should pursue. On the great majority of my applications, however, it is far much more useful to have a microcontroller to take care of all the other tasks in question. And believe it or not, the great majority of users of my communication boards are to do I2C or SPI. I guess it just works so well…
The reason why I like to use a computer to talk to a microcontroller through an USB2UART port is because microcontrollers nowadays are like a lab in a chip. We are dealing here with superbly economical chips with the horse power of a computer from a few decades ago, and some more. For example, the microcontroller I use the most runs at 16 MHz, has 116 KB of Flash ROM and 8 KB of RAM, but then packs 2 12 bit DAC outputs, 8 12 bit ADC inputs, 8 16 bit timer functions which can be used as PWM’s, Input Captures or Output compares, numerous serial communication ports (I2C, SPI, UART ,etc), as well as 48 GPIO pins. What 1980’s personal computer packed so much power?
What you can then is have the microcontroller (or a multitude of these) take care of the real time functions such as motor speed control, sensor information gathering, actuator control, etc, and then have the computer schedule commands to coordinate all of these efforts. At the end this results in a better utilization of the resources as it is a form of parallel processing. If you only had a computer taking care of every single application aspect, as fast as the computer is, some tasks may lack real time support during moments of heavy traffic.
OK, but enough introduction. It is now time to get our hands DIRTY!