Файл: Programming Microcontrollers in C, 2-nd edit (Ted Van Sickle, 2001).pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 15.06.2025
Просмотров: 4076
Скачиваний: 1
192 Chapter 4 Small 8-Bit Systems
there was a function called delay. A delay function can be integrated into a time interrupt service routine easily. The interrupt service rou tine above is entered every millisecond. We specified a function delay that had an unsigned long argument that corresponds to the required delay in milliseconds. Such a function could be implemented as:
void delay(unsigned long);
unsigned long time=0;
void delay(unsigned long del)
{
time = del; /* place the delay time in the global memory */
while(time>0);
}
Now we must add one code sequence to the interrupt service routine above:
if(--count==0)
{
if(time>0) --time; return ;
}
The function delay() places the value of the required delay in the unsigned long location time. The program then hangs on the while statement so long as time is greater than 0. Every time the ISR is entered—which is every millisecond— the long unsigned value time will be decremented. When the proper time has passed, the delay() function will return to the calling program.
The function delay() does something that many programmers do not like: the program hangs in a loop until a specified time has passed. This operation may seem to waste the microcontroller re source. Note, however, that the program does not spend all of the time in the loop; interrupts are being serviced during this time. We will see that the timer interrupt is only one of many potential inter rupts that will be working on the part at all times. Placing the device into a wait loop for a few milliseconds may stop the main program in
Timers 193
its tracks, but the background operations being serviced by the inter rupts will continue to be processed unhindered. In this case, a global variable has been used to transfer data between an interrupt service routine and the applications portion of the program. The variable time is set in the applications program, and the completion of the time delay is evaluated in the applications portion of the program, and time itself is decremented in the interrupt service routine.
It is possible to create errors in operation with this type of proce dure. One place where a nasty bug can creep into your code is when you are dealing with bit manipulations in both the application por tion of the program and the interrupt service routine. Suppose that you want to toggle a bit when an event was detected in the applica tion, and simultaneously you need a periodic bit toggle that is controlled by code in the interrupt service routine. The code sequence might appear as follows:
.
.
PORTA.BITAPP = !PORTA.BITAPP;
.
.
In the interrupt service routine, the code could be
.
.
PORTA.BITINT = !PORTA.BITINT;
In each case, this code will compile into
lda PORTA
eor 2^BITNUMBER sta PORTA
The same code sequence will appear in both the application and the interrupt service routine with the BITNUMBER appropriately cho sen. Suppose that we are in the application, and have just executed the lda PORTA instruction when the interrupt occurs. In the inter rupt service routine, the above code will be executed properly, and when control is returned to the program main line, it will continue with the eor instruction. However, the contents of PORTA will have
194 Chapter 4 Small 8-Bit Systems
been changed by the interrupt service routine, so the value that was loaded by the lda instruction prior to the interrupt is no longer valid. The earlier loaded value of PORTA will be restored by the above sequence in the application code, and the change made in the inter rupt service routine will be undone.
This bit of interplay between the applications program and the interrupt service routine is a type of software race. It can be easily avoided. Whenever a variable is changed in both the applications code and in an ISR, the erroneous set/reset can be avoided if the program disables all interrupts before the offending instruction in the applications code. The interrupts must then be re-enabled after the code execution. In this case, the code will be
·
·
SEI (); PORTA.BITABB=!PORTA.BITAPP; CLI();
·
·
in the applications code. In this case, there can be no interrupt to interfere with the proper handling of PORTA in the application.
In the timer routine above, there is what some might call a glaring oversight. The timer was not initialized in the initializing routine of the program. The first lines of code merely enabled the timer interrupt and then proceeded into the FOREVER loop, which is the applications portion of the program. The initial state of the TCR and the OCR regis ters is not established at reset. There is no way of knowing when the first output compare will take place. If the initial value of the OCR happens to be one less than that of the TCR, there will be an output compare about 0.13 seconds after the interrupts are enabled, and from then on the interrupt period will be accurate. If it is imperative that the first output compare occur at exactly the specified time, then the code for initialization of the output compare registers must be included in the initialization routine. Otherwise, if this error in the initial timing can be tolerated, it will save bytes of code space that might be desper ately needed for another portion of the program.
Analog-to-Digital Converter Operation |
195 |
EXERCISES
1.Write a program that uses an output compare system to generate an accurate waveform with a 1000.0 second period.
2.Devise a convenient method to test the performance of the pro gram in Exercise 1 above.
3.Write a program to generate two waveforms. One output is to be at twice the frequency of the other. The duty factors of the two sig nals are to be equal to 50%. The frequency of the slowest wave is to be 1000 Hz. The phase of the higher frequency signal is to be such that its rising edge is to occur 260 microseconds following the rising edge of the first signal.
4.Two DC motors are running. Each motor has an optical interrupter on its shaft with 15 interrupts per revolution of the shaft. All but one of the interrupts occupy one-sixteenth of the circumference of the rotation. The fifteenth interrupt occupies one-eighth of the circum ference. Using input capture registers, measure the speed of the two motors, and provide a slow down or speed up signal that can be used on either motor to synchronize the rotation of the two motors with the wide interrupter positions on the shafts being in lock-step.
5.What microcontroller characteristics will control the maximum speed at which the motors in Exercise 4 can run? The minimum speed?
Analog-to-Digital Converter Operation
The analog-to-digital converter (ADC) found on the M68HC05 family is moderately simple in its operation. There are a few impor tant items that must be remembered when dealing with the ADC. Most important is that the ADC must be turned on for at least 100 microseconds prior to reading a value. If 100 microseconds has not elapsed, it is guaranteed that the value read will be in error. The ADC is turned on by setting the ADON bit in the ADC control/status regis ter. This register is referred to as AD_CTST. The following code sequence will turn the ADC on:
AD_CTST=0;
AD_CTST.ADON=1;
196 Chapter 4 Small 8-Bit Systems
The following function will provide a reading of a single channel of the ADC input:
unsigned int read_adc(int k)
{
AD_CTST &=~0X7; AD_CTST |=k; while(AD_CTST.COCO==0)
; /* wait here til COCO is set */ return AD_DATA;
}
The argument k is the channel that is to be read, and k can have a value of 0 to 7 to read the external channels.
The first two lines of code in the above function will place the channel number to be read in the channel bits of AD_CTST. These bits must be cleared by an instruction sequence that will not alter the upper bits of AD_CTST because the ADON bit is in the upper portion of AD_CTST. This bit cannot be reset while the ADC operation is continuing. The first line of code clears the least significant three bits, and the second line places the channel number in these bits.
Writing to AD_CTST will cause the ADC conversion to start. There fore, all that must be done is to wait until the conversion is completed to read the data into the program. The code
while(AD_CTST.COCO==0)
; /* wait here til COCO is set */
will keep control of the microcontroller in that instruction sequence until the COCO bit, which is the conversion completion bit, is set. At that time the value found in AD_DATA will be the result of the latest conversion.
Often, the ADC results must be subjected to some processing to remove unwanted characteristics of the signal being measured. Here is a case where careful use of assembly language procedures can make a big difference in the execution speed as well as the amount of code needed. An example that is often used is to average the past values of the data. A reasonably simple approach is to allow the lat est ADC reading to have a 50% weight and all of the past readings to have a 50% weight. The following example code will accomplish this task in three different ways:
Analog-to-Digital Converter Operation |
197 |
#include “hc05b6.h”
unsigned adc_data[8]; unsigned read_adc(int);
main()
{
unsigned int j;
AD_CTST=0;
AD_CTST.ADON=1;
for(j=0;j<8;j++)
{
adc_data[j]=(read_adc(j)+adc_data[j])/2; adc_data[j] >>= 1;
adc_data[j] += read_adc(j)>>1;
#asm ldx j lda j
jsr read_adc add adc_data,x rora
sta adc_data,x #endasm
}
}
Listing 4-5: Three Different ADC Averaging Routines
The first attempt to read and average the data approaches the problem as simple as practical. The data are read in from the ADC, added to the corresponding stored data in the array adc_data, the result is divided by two, and the final average is put back into the proper location in the array. The following line of code is all that is necessary to accomplish this task:
198 Chapter 4 Small 8-Bit Systems
adc_data[j]=(read_adc(j)+adc_data[j])/2;
Hidden in this code is the fact that both read_adc and adc_data are unsigned results. When two unsigned numbers are added together, the most significant bit of each can be 1 so there can be a carry or overflow when the addition takes place. Problems from this carry can be avoided in this case by merely using a long or double precision add routine in adding the two numbers. Then, when the result is divided by 2, if a bit is carried into the upper byte of the result it will be shifted back into the lower byte. The result of this operation is correct and will fit into a single unsigned int.
The compiled version of the above program with read_adc() merged into it follows:
#include “hc05b6.h”
0050 0008 unsigned adc_data[8]; unsigned read_adc(int);
void main(void) |
||||
{ |
||||
0058 |
unsigned int j; |
|||
0100 |
3F |
09 |
CLR $09 |
AD_CTST=0; |
0102 |
1A |
09 |
BSET 5,$09 |
AD_CTST.ADON=1; |
0104 |
3F |
58 |
CLR $58 |
for(j=0;j<8;j++) |
0106 |
B6 |
58 |
LDA $58 |
|
0108 |
A1 |
08 |
CMP #$08 |
|
010A |
24 |
36 |
BCC $0142 |
|
{ |
||||
010C |
CD 01 |
43 JSR $0143 |
adc_data[j]=(read_adc(j)+ |
|
adc_data[j])/2; |
||||
010F |
BE 58 |
LDX $58 |
||
0111 |
EB 50 |
ADD $50,X |
||
0113 |
AE 02 |
LDX #$02 |
||
0115 |
CD 01 |
57 JSR $0157 |
||
0118 |
9F |
TXA |
||
0119 |
BE 58 |
LDX $58 |
||
011B |
E7 |
50 |
STA $50,X |
|
Analog-to-Digital Converter Operation 199 |
||||
011D |
BE 58 LDX $58 |
adc_data[j]>>= 1; |
||
011F |
E6 |
50 |
LDA $50,X |
|
0121 |
44 |
LSRA |
||
153 |
||||
0122 |
BE 58 LDX $58 |
|||
0124 |
E7 |
50 |
STA $50,X |
|
0126 |
B6 |
58 |
LDA $58 |
adc_data[j]+= |
read_adc(j)>>1; |
||||
0128 |
CD 01 43 JSR $0143 |
|||
012B |
44 |
LSRA |
||
012C |
BE 58 LDX $58 |
|||
012E |
EB 50 ADD $50,X |
|||
0130 |
E7 |
50 |
STA $50,X |
|
#asm |
||||
0132 |
BE 58 ldx j |
|||
0134 |
B6 |
58 |
lda j |
|
0136 |
CD 01 43 jsr read_adc |
|||
0139 |
EB 50 add adc_data,x |
|||
013B |
46 |
rora |
||
013C |
E7 |
50 |
sta adc_data,x |
|
#endasm |
||||
} |
||||
013E |
3C |
58 |
INC $58 |
|
0140 |
20 |
C4 |
BRA $0106 |
|
0142 |
81 |
RTS } |
||
unsigned int read_adc(int k) |
||||
0059 |
{ |
|||
0143 |
B7 |
59 |
STA $59 |
|
0145 |
B6 |
09 |
LDA $09 |
AD_CTST&=~0X7; |
0147 |
A4 |
F8 |
AND #$F8 |
|
0149 |
B7 |
09 |
STA $09 |
|
014B |
B6 |
09 |
LDA $09 |
AD_CTST |=k; |
014D |
BA 59 ORA $59 |
|||
014F |
B7 |
09 |
STA $09 |
|
200Chapter 4 Small 8-Bit Systems
0151 0F 09 FD BRCLR 7,$09,$0151
while(AD_CTST.COCO==0);
0154 |
B6 |
08 |
LDA $08 return AD_DATA; |
0156 |
81 |
RTS |
|
} |
|||
0157 |
BF 5A STX $5A |
||
0159 |
B7 |
5B |
STA $5B |
015B |
4F |
CLRA |
|
015C |
5F |
CLRX |
|
015D |
5C |
INCX |
|
015E |
38 |
5B |
LSL $5B |
0160 |
49 |
ROLA |
|
0161 |
B0 |
5A |
SUB $5A |
0163 |
24 |
03 |
BCC $0168 |
0165 |
BB 5A ADD $5A |
||
0167 |
99 |
SEC |
|
0168 |
59 |
ROLX |
|
0169 |
24 |
F3 |
BCC $015E |
016B |
53 |
COMX |
|
016C |
81 |
RTS |
|
1FFE 01 00
The assembly code to execute this single line of C code is found in the address range $10c to $11b, or 17 bytes of code. However, there is a call to the double precision add routine at address $115. This routine occupies the address range $157 to $16c, or 22 addi tional bytes of code hidden from the main routine. Therefore, this requires 37 bytes of code for its execution.
The next approach is to avoid the overflow problem by dividing by 2 each of the terms to be added prior to the addition. Division by 2 is accomplished by shifting each of the terms to the right by one bit. This approach guarantees that there will be no overflow into the higher byte because the most significant bit of each number will be 0 after the shift, and no binary addition can cause more than a 1-bit overflow. Therefore, the sum will at most have its most significant bit turned on. This number is then stored in the location adc_data[j].