Файл: 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].