Файл: Programming Microcontrollers in C, 2-nd edit (Ted Van Sickle, 2001).pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 15.06.2025
Просмотров: 4069
Скачиваний: 1
320 Chapter 6 Large Microcontrollers
Result = 15 +(50 – 15) (67 – 40) = 38.625 80 – 40
Notice that there is a product and a division that is the same for all input values between 40 and 80 in this case. This calculation is the slope of the line between the two points being interpolated. Usu ally table look-up operations are used where time constraints on the program are great so that these operations, multiply and divide (es pecially divide), are unwelcome in these programs. There is a way to avoid the divide operation. Note that between each set of points the slope is a constant. Therefore, if the slope of the line is built into the table, the calculation would involve one subtraction, one multiply, and one addition. Let us modify the table above to contain the slopes of the lines between each point.
20 |
5 |
0.5 |
40 |
15 |
0.875 |
80 |
50 |
1.75 |
120 |
120 |
2 |
180 |
240 |
|
Table 6-4: Look-Up Table With Slopes
Note that the slope of the line runs from the lower point to the next point. Therefore, there is no slope for the last point. When this table is built in memory, it consists of a header followed by a fourbyte entry for each entry in the table. The header consists of a single byte that indicates the length of the table. In this case, since the table starts with an independent variable value of 20, there are expected to be no input values less than 20. If there are, however, the value of 5 will be used for these output values. Also, if the value of the indepen dent variable is greater than the maximum of 180, a value of 240 will be returned from the routine. With data from the above table, the calculation above would reduce to
Result = 15 + 0.875(67 - 40) = 38.625
which requires a multiply, a subtract and one add. Of course, the routine will truncate the fractional part of the result so that the value returned will be 38.
Table Look-Up 321
Each table entry contains four bytes. The first byte is the inde pendent variable value, often designated as x. The second byte contains the dependent variable value, usually called y, and the final two bytes contain a floating point format of the slope. The slope must be floating point. The third byte contains the integer position of the slope, and the fourth byte contains the fractional value of the slope. With the binary point placed between the third and fourth bytes of the table entry, we can accomplish some interesting multiply op erations. Prior to looking at the coding of this problem, let us complete the table and convert the slopes into hexadecimal format.
5 |
|||
20 |
5 |
0x00 |
0x80 |
40 |
15 |
0x00 |
0xe0 |
80 |
50 |
0x01 |
0xc1 |
120 |
120 |
0x02 |
0x00 |
180 |
240 |
||
Table 6-5: Look-Up Table with Hexadecimal Slopes
How does one build a table of this nature in C? Of course, it is an array of structures, or it could be a structure that contains an array of structures. The latter approach would be
struct entry
{
char x; char y; int slope; };
struct lut
{
char entries;
struct entry data[5]; } _lut = {
5,
20, 5, 0x0080, 40, 15, 0x00e0,
322Chapter 6 Large Microcontrollers
80, 50, 0x01c0, 120, 120, 0x0200, 80, 240 };
Note that in the table above, the slopes are entered in the table as two-byte integers. There is no binary point in the slope. The conver sion of these integers to floating-point numbers is accomplished easily. We must merely recognize that these numbers are a factor of 128 too large, so after the slope is used as a multiplier, the result must be divided by 128 to get the correct answer. Of course, division by 128 can be accomplished by a shift right by 8, or more practically merely choosing the left byte of the product.
A function that will make use of this table is
char table_look_up( char x_in)
{
int i;
for(i=0;x_in>_lut.data[i].x && i<=_lut.entries; i++); if(i>=_lut.entries)
return _lut.data[_lut.entries-1].y; else if (i==0)
return _lut.data[0].y; else
return _lut.data[i-1].y+(((x_in-_lut.data[i-1].x)* _lut.data[i-1].slope)>>8);
}
Listing 6-6: Table Look-Up Routine, Version 1
The compiler optimizer will recognize the shift right by 8 in the above function and will merely select the upper byte of the result rather than executing the shift operation indicated.
This function was checked on an evaluation system for the single input value of 67 and the result was the expected value of 38. Of course, that did not check the function over its full range of opera tion. The check over the full range was accomplished on a host machine. It is a simple matter to include the above function and table in the following program.
#include “tlu.h” void main(void)
Table Look-Up 323
{
int i;
for(i=0;i< 256; i++)
printf(“ i = %d r = %d\n”,i,table_look_up(i));
}
where tlu.h contains the function and the table above. This code compiles and runs on a MS-DOC PC, and the result is as expected. Here, the program scans the entire input range and prints out the resultant value at each point. A check of the outputs will show that the function hits the break value at each break, and creates a linear interpolation between all points. For values above or below the range, the proper output is observed.
In a normal control system, there will often be need for several table look-up operations. The above code is not too good in this case because the code to do the look-up must be repeated with each table. This extra code can be eliminated if the function table_look_up( ) is passed two parameters: the first parameter is the interpolation value, and the second value is a pointer to the look-up table as is shown below:
char table_look_up( char x_in, struct lut* table)
{
int i;
for(i=0;x_in>table->data[i].x && i<=table->entries; i++) ; if(i>=table->entries)
return table->data[table->entries-1].y; else if (i==0)
return table->data[0].y; else
return table->data[i-1].y+
(((x_in-table->data[i-1].x)*table->data[i- 1].slope)>>8);
}
Listing 6-7: Table Look-Up, Version 2
This form of the table look-up should probably be used in all cases. Here is another example of where it is wise to examine the code generated by a compiler. It will not be listed here, but the as sembly code required for Listing 6-6 is 182 bytes long, and that for listing 6-7 is but 142 bytes. This greatly improved utility automati cally gives a substantial savings in code.
It was pointed out that the compiler optimizer will sometimes change the basic code dictated by the programmer. Perhaps, it should
324 Chapter 6 Large Microcontrollers
be stated that the optimizer changes the code suggested by the pro grammer. Often when you examine the code generated by the compiler, the operations that take place will not even resemble those that the programmer had in mind. This alteration of the code will show up with shifts, and divides or products of powers of two. A proper optimizer should determine if a multiply or a shift is better and provide the best code. Often you will find that the compiler optimizer will provide either fastest execution or minimum code. Usually the two will be different. Faster code will almost always take more memory.
It may seem that the lost time is small, but you will always find that if you want fast code, you will not use any looping constructs. The code that increments a counter and tests the counter must be executed each loop. Without the looping construct, this code is completely elimi nated, and its execution each loop will be completely eliminated. Therefore, if you want fast code, you should eliminate looping con structs and repeat the code within the loop the desired number of times. The cost of this move is more code. We have also seen that recursive code can require an inordinant amount of time. Again, recursive code merely creates hidden looping constructs that require frequent genera tion of stack frames prior to new function calls. The construction of these stack frames and return from functions require time which is often masked by the elegant appearance of the code. Usually you will produce faster code if you figure a way to accomplish the same end operation without calls to the executing function.
Today, we are at the very beginning of a completely new set of microcontrollers. These machines are based on RISC (reduced instruc tion set computer) techniques. Do not be misled. RISC does not really mean reduced instruction set. The instruction sets are complete and extensive. However, the basic architecture of a RISC machine is quite different from the older machines like those we have been working with. One of the main differences is that the design of the machine is to provide for the execution of one or more instructions with each clock cycle. A standard RISC will allow almost one instruction per clock cycle, and a super scaler architecture will allow two or more instructions per clock cycle. This operation is enabled by the use of instruction pipelines and multiple arithmetic logic units (ALUs). Some times, an instruction must use more than one clock to complete an
Table Look-Up 325
instruction. For example, an integer multiply instruction might require five clock cycles. In that case, an instruction pipe five instructions long would be implemented within the integer ALU. Therefore, a multiply instruction would be launched into the instruction pipe for execution. In the meantime, other instructions could execute while the multiply is progressing through the pipe.
Compiler optimization for these machines must take into account all of the pipes, the separate ALUs, and other unique operations when creating the required assembly code. Clearly, a straightforward cre ation of code in the order that might seem to be natural to a programmer will not necessarily create the quickest code for a RISC machine. Here, speed optimization consists of arranging the code so that, as nearly as possible, the maximum number of instructions are launched each clock cycle. This rearrangement of your code will make it extremely difficult to examine the assembly code version of the program and even make sense of it. So long as the results are not altered, the optimizer for a RISC machine will move instructions around in the code stream to accomplish this end. Therefore, from a practical sense, any debugging on a RISC machine will probably be done with a source level debugger, and not an assembly language version of the program.
The chip used in Chapter 8 is of the MCORE family of RISC microcontrollers. We will see more of the above comments in that chapter.
EXERCISE
1.Sometimes two input values are needed to specify a parameter. For example, if you recall from Chapter 5, the change in pulse on time to properly control the motor speed was given by
∆pc = –3809500 ∆p p2
create a sparse two-dimensional look-up table whose inputs are p and ∆p and which provides ∆pc as the result of three interpolations. Is the look-up table better than the calculation in any way—less code, quicker, etc.? Why would you use a lookup table in a problem like this one?
326 Chapter 6 Large Microcontrollers
Digital Signal Processor Operations
Most microcontrollers, regardless of their basic speed, are not really able to process signals in real time. The basic speed of the processors is fast enough to accomplish most signal processing; how ever, the set of things needed to do digital signal processing is not usually available in the regular microcontroller. The basic digital sig nal processor (DSP) function that is required is summarized by three actions: 1) the processor must multiply two values, 2) the processor must add the product into a value, and 3) it must prepare for the next multiply. This set of operations must execute quickly enough that the computer can keep ahead of the real-time input of data being pro cessed. Almost all signal processing operations are based on the multiply-accumulate sequence. Filtering, correlation operations, sca lar products of vectors, Fourier and other transformations, and convolutions are but a few of the operations that are built around the DSP multiply and accumulate sequence above. The MC68HC16 fam ily has an extension to its core that can execute basic DSP operations fast enough to support real-time filtering, correlation, and so forth.
Unfortunately, C compilers do not know of these added func tions, so the C programmer would seem to be unable to include DSP operations in programs. We will see here that, while it is rather in convenient, it is possible to write assembly functions that will permit the programmer access to the complete DSP capabilities found in this family of devices.
One of the good features of C is that it is not necessary for the programmer to have detailed knowledge of the nature of the basic computer being programmed. So far, we have had little to say about accumulators or index registers or the like. No more! We must now get inside of the computer to create functions that will accomplish our DSP needs. When these functions are complete, we should be able to treat the DSP operations in much the same manner that we would any other function call. The DSP contains four registers and controls three bits in the condition code register (CCR). The first two registers are called the MAC multiplier input registers H and I, re spectively. These registers must be loaded with the multiplier and the multiplicand to be executed. Data stored in these registers are signed fractional binary numbers with the radix point between bits 15 and 14. The product will be accumulated into the MAC accumu
Digital Signal Processor Operations |
327 |
lator M register. This register is 36 bits long. For convenience, the register is broken into two portions, bits 35 through 16 and bits 15 through 0. We will see later that different portions of this register are moved by different instructions, so that breaking this register into the two parts is logical.
The last register in the DSP register model is the MAC XY mask register. Automatic selection of the addresses for the next multiply needs modulo arithmetic. We will see more modulo arithmetic later. The mask register contains the modulo base for both the x and y index registers which will allow fixed coefficient tables, that can be manipulated as either single-or two-dimensional arrays, to be tra versed automatically during successive multiply and accumulate instructions. The DSP register model is shown in Figure 6-2 below.
20 |
16 |
15 |
8 |
7 |
0 |
Bit Position |
MAC Multiplier Register |
||||||
H |
R |
|||||
I |
R |
MAC Multplican Register |
||||
A |
M |
MAC Accumulator MSB[35-16] |
||||
MAC Accumulator LSB[15-0] |
||||||
A |
M |
|||||
MAC XY Mask Register |
||||||
XMSK |
YMSK |
|||||
Figure 6-2: MC68HC16 DSP Register Model
The CCR contains three bits that are associated with the DSP. Bits 14 and 12 are DSP overflow flags. These flags will be discussed in detail later. Bit 4 is called the SM bit and is the DSP saturation mode control bit.
15 |
3 |
0 |
|||||||||||||
S |
MV |
H |
EV |
N |
Z |
V |
C |
l1 l2 l3 |
SM |
PK |
CCR |
||||
DSP Control Bits
Figure 6-3: DSP Control Bits In The CCR
The accumulator is 36 bits with the radix point between bits 31 and 30. When accumulating into the MAC accumulator AM, there are