Keil C51. Ручное использование флага переноса в арифметических операциях
Александр Бельченко
17 ноября 2004
Этот небольшой практический совет показывает как вручную манипулировать флагом переноса в арифметических операциях. Такая «мануальная терапия» может быть полезна при работе с двухбайтными регистрами таймеров 0 и 1.
В стандартной архитектуре MCS-51 два таймера 0 и 1 могут работать в режиме таймера с перезагрузкой, но только в однобайтном режиме. Если необходимо запустить таймер в 16-битном режиме, то при каждом переполнении необходимо будет перезагружать таймер вручную.
Например, для получения прерываний от таймера 0 каждые 10 мс, при каждом переполнении таймера (т.е. в прерывании) его нужно перегружать значением (65536 - 9216) = 0xDC00. Дополнительно нужно учесть сколько времени будет затрачено на саму операцию перезагрузки, а также на вход в прерывание. В типичном случае код для перезагрузки значения таймера будет выглядеть так (пример взят из Application Note 105):
#include <reg51.h>
#define TIMER0_COUNT 0xDC11 /* 10000h - ((11,059,200 Hz / (12 * FREQ)) - 17) */
static void timer0_isr (void) interrupt 1 using 1
{
unsigned i;
/*------------------------------------------------
Stop Timer 0, adjust the timer 0 counter so that
we get another interrupt in 10ms, and restart the
------------------------------------------------*/
TR0 = 0; /* stop timer 0 */
i = TIMER0_COUNT + TL0 + (TH0<<8);
TL0 = i;
TH0 = i >> 8;
TR0 = 1;
...
}
Как видно, для учитывания времени, прошедшего с момента возникновения прерывания и до того момента, как значения таймера будет перезагружено новой величиной, применяется простой прием: значение для перезагрузки прибавляется к текущему значению таймера. Время, которое тратится на арифметические операции, учитывается в константе перезагрузки в вышеприведенном примере на это отведено 17 машинных тактов. Если взглянуть на тот ассемблерный код, который будет сгенерирован для этого примера, то можно увидеть примерно такое:
0000 C28C CLR TR0 ; TR0 = 0; /* stop timer 0 */
0002 AF8C MOV R7,TH0 ; i = TIMER0_COUNT + TL0 + (TH0<<8);
0004 EF MOV A,R7
0005 FE MOV R6,A
0006 AD8A MOV R5,TL0
0008 ED MOV A,R5
0009 2411 ADD A,#011H
000B FD MOV R5,A
000C E4 CLR A
000D 34DC ADDC A,#0DCH
000F CD XCH A,R5
0010 2400 ADD A,#00H
0012 FF MOV R7,A
0013 ED MOV A,R5
0014 3E ADDC A,R6
;---- Variable 'i' assigned to Register 'R6/R7' ----
0015 8F8A MOV TL0,R7 ; TL0 = i;
0017 F58C MOV TH0,A ; TH0 = i >> 8;
0019 D28C SETB TR0 ; TR0 = 1;
Бегло подсчитав количество тактов, за которое будет выполнен этот код, у меня получился 21 такт. В кейловском примере стоит цифра 17 возможно, что компилятор в версии 5 был поумнее.
Для тех, кто писал подобный код на ассемблере, получаемый код может показаться весьма неоптимальным по крайней мере я так считаю. На чистом ассемблере я бы записал эту операцию так:
CLR TR0
MOV A, #0x08
ADD A, TL0
MOV TL0, A
MOV A, #0xDC
ADDC A, TH0
MOV TH0, A
SETB TR0
Если захотите, то подобный кусок кода можете вставить в свой обработчик прерывания в качестве ассемблерной вставки. Но целью этой статьи было показать, как максимально приблизиться к желаемому результату средствами языка C51.
Самый существенный момент здесь это то, что необходимо произвести сложение двухбайтовых чисел на 8-битном контроллере. Т.е. при сложении старших байт нужно учитывать флаг переноса. Для обращения к флагу переноса Carry из си-кода можно использовать имя CY (бит с таким именем определяется в заголовочном файле reg51.h или специфичном для конкретного микроконтроллера).
Для ручной реализации двухбайтового сложения можно использовать такой код:
TR0 = 0;
TL0 += 0x0A;
TH0 += 0xDC + (unsigned char)CY;
TR0 = 1;
Обратите внимание на явное приведение бита CY к типу unsigned char. Посмотрим на ассемблерный эквивалент:
0004 C28C CLR TR0
0006 740A MOV A,#00AH
0008 258A ADD A,TL0
000A F58A MOV TL0,A
000C E4 CLR A
000D 33 RLC A
000E 24DC ADD A,#0DCH
0010 258C ADD A,TH0
0012 F58C MOV TH0,A
0014 D28C SETB TR0
Как видно лишний код (оверхэд) составил всего 2 команды или 2 такта, что уже можно считать приемлемым результатом. По сравнению с первым примером время выполнения этого кода уменьшилось в 2 раза, а объем кода в полтора раза.