Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ИУС_РК2

.pdf
Скачиваний:
30
Добавлен:
14.04.2015
Размер:
3.29 Mб
Скачать

Таблица 4. Сравнение память-зависимых и нетипизированных указателей

Описание

Указатель

Указатель Xdata

Нетипизированный

 

 

 

 

Idata

 

указатель

 

char idata *ip;

char xdata *xp; char

char *p; char val; val =

Пример программы

char val; val =

val; val = *xp;

*p;

 

 

*ip;

 

 

 

 

 

 

 

MOV R0,ip MOV

MOV DPL,xp +1 MOV

MOV R1,p + 2 MOV

Код,

val,@R0

DPH,xp MOV

R2,p + 1 MOV R3,p

 

сгенерированный

 

 

 

компилятором 8051

 

A,@DPTR MOV val,A

CALL CLDPTR

 

 

 

 

Размер указателя

1 байт

2 байта

3 байта

Размер кода

4 байта

9 байт

11 байт кода + библиот.

 

 

 

 

 

Время выполнения

4 цикла

7 циклов

13 циклов

Реентерабельные функции

Реентерабельные функции могут быть вызваны одновременно разными процессами. При выполнении такой функции другой процесс может прервать ее выполнение и начать выполнять ту же функцию. В большинстве случае в функции С51 не могут быть вызваны рекурсивно или повторно до окончания выполнения. Причина этого в том, что

аргументы функций и локальные переменные хранятся в строго определенных областях памяти. Однако атрибут reentrant позволяет объявить функцию как реентерабельную, которая может быть вызвана рекурсивно, например:

int calc (char i, int b) reentrant { int x;

x = table [ i ] ; return (x * b);

}

Реентерабельные функции могут вызываться рекурсивно, а также одновременно двумя и более процессами. Подобные функции часто используются в приложениях, работающих в режиме реального времени, или в тех случаях, когда функция вызывается как из кода, так и по прерыванию.

Стек каждой реентерабельной функции размещается во внутренней или внешней памяти в зависимости от модели памяти.

Примечание: добавление атрибута reentrant при объявлении функции позволит сделать реентерабельными только те участки программ, которые могут быть вызваны повторно или рекурсивно. При этом вся программа не становится реентерабельной. Если вы объявите реентерабельной целую программу, она займет больше места в памяти.

Функции - обработчики прерываний

Компилятор С51 предоставляет возможность вызова функций при возникновении прерываний. Это дает возможность написания на языке С собственных обработчиков прерываний. Однако следует соблюдать осторожность в выборе номера прерывания и банка регистров. Компилятор автоматически генерирует вектор прерывания, а также код входа в обработчик и выхода из него. Атрибут interrupt, включенный в объявление функции, указывает на то, что данная функция обрабатывает прерывание. Кроме того, можно указать банк регистров для данного прерывания с помощью атрибута using.

unsigned int interruptcnt; unsigned char second;

void timer0 (void) interrupt 1 using 2 {

if (++interruptcnt == 4000)

{

second++;

interruptcnt = 0;

}

}

Передача параметров

Компилятор С51 размещает в регистрах до 3 аргументов функций. Это значительно повышает системную производительность, поскольку аргументы

не требуется сохранять в памяти и считывать оттуда. Передачей параметров можно управлять с помощью специальных директив - REGPARMS и NOREGPARMS.

Ниже приведен список регистров, используемых для размещения аргументов разных типов.

Таблица 5. Регистры, используемые для размещения аргументов разных типов

Количество

char, 1-байтный

int, 2-байтный

long,

Нетипизированный

аргументов

указатель

указатель

float

указатель

 

 

 

 

 

 

 

 

R4 -

 

1

R7

R6 & R7

R7

R1 - R3

 

 

 

 

 

 

 

 

 

2

R5

R4 & R5

 

 

 

 

 

 

 

3

R3

R2 & R3

 

 

 

 

 

 

 

Есливданныймоментнетсвободныхрегистровилиаргументовслишком много,тодляэтихаргументовиспользуютсяфиксированныеобластипамяти.

Значения, возвращаемые функцией

Регистры микроконтроллера всегда используются для значений, возвращаемых функциями. Ниже приведена

таблица типов возвращаемых данных и соответствующих им регистров.

Таблица 6. Типы возвращаемых данных и регистры

Тип возвращаемого значения

Регистры

Описание

bit

Флаг переноса

 

char, unsigned char, 1-byte pointer

R7

 

int, unsigned int, 2-byte pointer

R6 & R7

MSB в R6, LSB в R7

long, unsigned long

R4 - R7

MSB в R4, LSB в R7

float

R4 - R7

32-битный формат IEEE

 

 

 

generic pointer

R1 - R3

Тип памяти в R3, MSB R2, LSB, R1

Оптимальное выделение регистров

Компилятор С51 выделяет для регистровых переменных до 7 регистров процессора (в зависимости от контекста программы). Компилятору известны все регистры, содержимое которых изменилось в процессе выполнения

функции. Компоновщик / загрузчик генерирует один файл на весь проект, содержащий информацию обо всех регистрах, измененных внешними функциями. В результате компилятор знает об изменениях в каждом регистре и может оптимально распределить регистры среди всех функций.

Добавление ассемблерного кода

К процедурам, написанным на ассемблере, можно легко обращаться из кода на языке С (и наоборот). Параметры функций передаются через регистры контроллера или, в случае использования директивы NOREGPARMS, путем загрузки

их в фиксированные области памяти. Значения, возвращаемые функциями, всегда передаются через регистры.

Чтобы сгенерировать не объектный файл, а код, который может быть обработан ассемблером А51, нужно использовать директиву компилятора С51 SRC. Например, приведенный ниже текст на С:

unsigned int asmfunc1 (unsigned int arg)

{

return (1 + arg);

}

Он откомпилирован с использованием директивы SRC и создает следующий код на ассемблере:

?PR?_asmfunc1?ASM1 SEGMENT CODE

PUBLIC _asmfunc1

RSEG ?PR?_asmfunc1?ASM1

USING 0

_asmfunc1:

;

---------------Variable 'arg?00' assigned to Register 'R6/R7' ------------------------------------------------------------------------------------------

MOV A,R7 ; load LSB of the int ADD A,#01H ; add 1

MOV R7,A ; put it back into R7 CLR A

ADDC A,R6 ; add carry & R6

MOV R6,A

?C0001:

RET ; return result in R6/R7

Чтобы включить инструкции ассемблера в программу на С, следует использовать директивы препроцессора

#pragma asm и #pragma endasm.

Расширения языка Си для компилятора SDCC

Модели памяти

В компиляторе языка Си SDCC для MCS51 существуют модели small, medium и large. В моделях medium и large все переменные без класса памяти попадают по умолчанию во внешнее ОЗУ (XRAM). В модели памяти small все переменные

без класса памяти попадают во внутреннюю память данных.

Стек во внешней памяти

Опция --xstack позволяет располагать стек во внешней памяти, в сегменте pdata.

Абсолютная адресация

Переменным можно присваивать не только тип памяти (data, xdata, code), но и абсолютный адрес расположения. xdata at ( 0 x 7 f f e ) unsigned int chksum;

В примере, приведенном выше, переменная chksum будет размещена по адресу 0x7ffe во внешней памяти

XDATA. Необходимо заметить, что компилятор не резервирует места под переменные, определенные таким образом, и

проверка на отсутствие пересечений с другими данными полностью лежит на программисте. Для проверки можно использовать файлы с расширениями .lst, .rst и .map.

Если вы произведете инициализацию памяти, линкер сможет обнаружить пересечение данных. code at ( 0 x 7 f f 0 ) char Id.[5] = ' ' S D C C ' ' ;

В случае использования устройств ввода-вывода, расположенных в адресном пространстве внешней памяти, необходимо использовать ключевое слово volatile, для того, чтобы оптимизатор компилятора не заменил обращение к

устройству обращением к регистру общего назначения. volatile xdata at (0x8000) unsigned char PORTA_8255;

В SDCC допускается указание абсолютного адреса расположения бита для битовых переменных. bit at (0x02) bvar;

Использование абсолютной адресации битовой памяти может вызвать путаницу и оправда но только если вам хочется написать универсальную программу (см. пример ниже) для нескольких комплектов аппаратных средств.

extern volatile bit

MOSI;

/*

master out, slave in

*/

extern volatile bit

MISO;

/*

master in, slave out

*/

extern volatile bit

MCLK;

/*

master clock */

 

unsigned char spi_io(unsigned char out_byte)

{

unsigned char i=8; do {

MOSI = out_byte & 0x80; out_byte <<= 1; MCLK = 1; i f ( M I S O )

out_byte += 1; MCLK = 0;

} while(-i); return out_byte;

}

Далее мы видим варианты определения битов для разных аппаратных средств.

// вариант 1

 

 

 

 

 

 

 

 

bit at

(0x80)

MOSI;

/*

I/O port

0,

bit

0

*/

bit at

(0x81)

MISO;

/*

I/O port

0,

bit

1

*/

bit at

(0x82)

MCLK;

/*

I/O port

0,

bit

2

*/

// вариант 2

 

 

 

 

 

 

 

 

bit at

(0x83)

MOSI;

/*

I/O port

0,

bit

3

*/

bit at

(0x91)

MISO;

/*

I/O port

1,

bit

1

*/

bit at

(0x92)

MCLK;

/*

I/O port

1,

bit

2

*/

Нестандартные типы данных bit

Битовый тип данных. Возможные значения 0 и 1.

bit test_bit;

sfr / sfr16 / sfr32 / sbit

Ключевые слова для определения регистров специального назначения. sbit - битовый регистр, sfr - 8 разрядов, sfr16 - 16 разрядов, sfr32 - 32 разряда. Эти ключевые слова используются для создания заголовочных файлов,

позволяющих обращаться к регистрам специального назначения по именам.

sfr at (0x80) P0;

/*

регистр

специального

назначения

P0 по адресу 0x80

*/

 

 

 

 

 

/* 16 разрядный регистр общего назначения для timer 0

Старший байт значения находится по адресу 0x8C, младший по адресу 0x8A

*/

sfr16 at (0x8C8A) TMR0;

 

 

sbit at (0xd7) CY; /* CY (Carry Flag, флаг переноса)

*/

Фрагмент заголовочного файла для ADuC 812

 

 

/* Регистры с байтовым доступом */ sfr at ( 0x80 ) P0 ;

 

 

sfr at (

0x81

SP

;

 

sfr at

 

0x82

DPL

 

;

 

sfr at

 

0x83 )

DPH

 

;

 

sfr at

0x84

DPP

;

 

sfr at

 

0xF2

ADCOFSH

 

 

sfr at

 

0xF3

ADCGAINL

;

 

sfr at

 

0xF4

ADCGAINH

;

 

sfr at

 

0xF5

ADCCON3

;

 

sfr at

0xF7

SPIDAT

;

 

sfr at

0xF8

SPICON

;

 

 

sfr at (

0xF9

DAC0L

;

 

 

 

 

 

 

 

 

/* Битовые регистры */

 

 

 

 

/* TCON */

 

 

 

 

 

 

sbit at

( 0x8F

) TF1

;

 

 

sbit at

( 0x8E

) TR1

;

 

 

sbit at

( 0x8D

) TF0

;

 

 

sbit at

( 0x8C

) TR0

;

 

 

sbit at

( 0x8B

) IE1

;

 

/* Номера битов для байтовых регистров */

 

 

/* PCON bits

*/

 

 

 

 

#define IDL

 

 

0x01

 

 

#define PD

 

 

0x02

 

 

#define GF0

 

 

0x04

 

 

#define GF1

 

 

0x08

 

 

#define SMOD

 

 

0x80

 

 

 

/* Номер прерывания: адрес = (номер * 8) + 3 */

 

 

 

#define

IE0

VECTOR

0

/*

0x03

Внешнее прерывание

0

*/

#define

TF0_

VECTOR

1

/*

0x0b

Таймер 0 */

 

 

 

 

 

 

 

 

 

 

#define

IE1

VECTOR

2

/*

0x13

Внешнее прерывание

1

*/

 

 

 

 

 

 

 

#define

TF1_

VECTOR

3

/*

0x1b

Таймер 1 */

 

 

#define

SI0

VECTOR

4

/*

0x23

Последовательный канал 0 */

 

 

Модификаторы памяти

/* указатель находится во внутренней памяти и ссылается на объект во внешней памяти XDATA */

xdata unsigned char *

data p;

 

 

 

 

/* указатель находится во внешней памяти и ссылаетсмя на объект во внутренней */

 

 

 

data unsigned char * xdata p;

 

 

 

 

 

/* указатель в пространстве

памяти команд(CODE)

ссылается

на

объект

во

внешней памяти XDATA */

 

 

 

 

 

xdata unsigned char *

code p;

 

 

 

 

/* указатель в пространстве

памяти команд(CODE)

ссылается

на

объект

в

пространстве памяти команд (CODE) */

 

 

 

 

 

__code unsigned char * __code p;

/* указатель на функцию, находящийся во внутренней памяти */

char (* data fp)(void.);

Универсальный (generic) указатель в SDCC содержит дополнительную информацию о типе адресного

пространства. В целях оптимизации объема кода лучше явно указывать тип адресного пространства.

/* универсальный указатель (generic) находится во внешней памяти XDATA */

unsigned char * xdata p;

/* универсальный указатель (generic) находится в пространстве памяти по умолчанию */

unsigned char * p;

Реентерабельность

Реентерабельность (Reentrant, повторное вхождение) - свойство

императивной программы, позволяющее одновременное использование нескольких одинаковых программ в

многозадачной среде. Для того, чтобы программа стала нереентерабельной, достаточно разместить переменные в глобальной области памяти в единственном экземпляре. Тогда несколько копий программ будет использовать один и тот же набор переменных, что приведет к непредсказуемым последствиям. Для обеспечения реентерабельности необходимо снабдить каждый экземпляр кода своим собственным объемом пам яти для хранения данных. Как правило, для этих целей используют регистровые файлы и стек.

В зависимости от модели памяти и количества свободного места, автоматические переменные и параметры функции могут быть помещены в стек, или в пространство внешней или внутренней памяти. Последний вариант делает

функцию нереентерабельной. Для того, чтобы разместить автоматические переменные в стеке, можно воспользоваться опцией компилятора --stack-auto (или в тексте программы #pragma stack-auto), или ключевым словом reentrant при

определении функции.

unsigned char foo(char i) reentrant

{

}

Необходимо помнить, что в архитектуре MCS51 стек имеет очень небольшой объем. Поэтому опцией --stack-auto необходимо пользоваться экономно и с осторожностью. Для решения проблемы с размером стека можно явно указывать тип памяти для автоматической переменной и её абсолютный адрес.

unsigned char f o o ( )

{

xdata unsigned char i; bit bvar;

data at (0x31) unsigned char j;

}

Оверлеи

Оверлей - механизм управления распределением ресурсов, при котором допускается их совместное использование. Как правило, под термином оверлей понимается механизм повторного использования блоков памяти данных и кода в вычислительных системах, работающих в рамках модели вычислений ФонНеймана.

В нереентерабельных функциях одна и таже область памяти может быть использована повторно. SDCC использует оверлеи по умолчанию. Для отключения режима работы с оверлеями необходимо использовать #pragma nooverlay.

#pragma save

#pragma nooverlay

void set_error(unsigned char errcd)

{

P3 = errcd;

}

#pragma. restore

void some_isr () interrupt (2)

{

set_error(10);

}

В приведенном примере, использование errcd без #pragma nooverlay привело бы к непредсказуемым

последствиям.

Обработчики прерываний

Обработчик прерывания в SDCC имеет следующий вид:

void timer_isr (void) interrupt (1) using (1)

{

}

Ключевое слово interrupt определяет номер вектора прерываний, а слово

using - номер используемого регистрового банка. Явное указание номера

регистрового банка позволяет уменьшить объем данных, сохраняемых в стеке при вызове обработчика.

Предполагается, естественно, что этот регистровый банк кроме обработчика никто не будет использовать.

Если обработчик прерывания изменяет какие либо глобальные переменные, они должны быть определены с использованием ключевого слова volatile.

/* Номера обработчиков прерываний: адрес = (номер * 8) + 3 */

 

 

#define IE0 VECTOR

0

/*

0x03

external interrupt 0

*/

#define TF0 VECTOR

1

/*

0x0b

timer 0 */

 

#define IE1 VECTOR

2

/*

0x13

external interrupt 1

*/

#define TF1 VECTOR

3

/*

0x1b

timer 1 */

 

#define SI0 VECTOR

4

/*

0x23

serial port 0 */

 

Критические секции

Внутри критической секции SDCC генерирует код, который запрещает (в начале секции) и восстанавливает в исходное состояние (в конце) все прерывания. Необходимо помнить, что в большинстве случаев запрещать все

прерывания слишком накладно.

int foo () __critical {

}

Ключевое слово __critical может использоваться совместно с ключевым словом reentrant.

Ключевое слово __critical может использоваться для защиты отдельных переменных.

critical{ i++; }

Семафоры

Архитектура MCS51 позволяет проводить атомарные действия над битовыми переменными, что позволяет успешно реализовать простой бинарный семафор. SDCC генерирует такой код, если используется приведенный ниже

шаблон исходного текста.

volatile bit resource_is_free;

if (resource_is_free)

{

resource_is_free=0;

resource_is_free=1;

}

Пример использования бинарного семафора.

char x = 0;

volatile bit resource_is_free;

void sem( void )

{

if (resource_is_free)

{

resource_is_free = 0; x = 10; resource_is_free = 1;

}

}

Генерируемый SDCC код.

jbc _resource_is_free,00106$ ret 00106$: mov _x,#0x0A

setb _resource_is_free ret

Ассемблерные вставки

Компилятор SDCC позволяет использовать ассемблерные вставки. Попробуем оптимизировать программу,

представленную ниже.

unsigned char far _______________________________________________a t ( 0 x 7 f 0 0 ) b u f [ 0 x 1 0 0 ] ; unsigned char head, tail;

/* if interrupts are involved see section 3 . 8 . 1 . 1

about

 

 

 

 

volatile */

 

 

 

 

 

void to_buffer( unsigned char c )

 

 

 

 

 

{

 

 

 

 

 

if( head != (unsigned c h a r ) ( t a i l - 1 ) ) / * cast needed to avoid promotion to integer */

 

 

 

b u f [ head++ ] = c;

/*

access to a

256

byte

aligned

array */

 

 

 

 

 

}

 

 

 

 

 

В примере оптимизированной функции хорошо видно, что для прямого включения ассемблерного кода необходимо использовать директивы _asm и endasm.

void to_buffer_asm ( unsigned char c )

{

_asm

mov r2,dpl

; b u f f e r . c i f ( head != (unsigned c h a r ) ( t a i l - 1 ) ) / * cast need.ed to avoid promotion to integer */ mov a,_tail dec a mov r 3 , a mov a,_head cjne a,ar3,00106$ ret 00106$:

; b u f f e r . c b u f [ head++ ] = c; /* access to a 256 byte aligned array */ mov r3,_head inc _head mov dpl,r3 mov dph,#(_buf >>8) mov a,r2 movx @dptr,a 00103$: ret

_endasm;

}

Внутри ассемблерной вставки возможно использование любых директив, понятных ассемблеру.

Использование меток

Внутри функции можно определять метки вида nnnn$, где n - число от 0 до 100. Метки, используемые в языке Си, не видны внутри ассемблерных вставок и наоборот. Метки в ассемблерных вставках внутри разных функций

также не видны друг для друга.

f o o ( ) {

/ * Некоторый код на Си */

_asm

; Некоторый ассемблерный код ljmp 0003$

_endasm;

/* Еще код на Си */ clabel: /* Встроенный ассемблер не видит эту метку */ 3.6

_asm

0003$: ; Эта метка доступна только из встроенного ассемблера _endasm ;

/* Еще код на Си */

}

Директива naked

Директива __naked позволяет исключить генерацию вводной части функции. Предполагается, что за

сохранение контекста отвечает программист.

volatile data unsigned char counter;

 

 

 

 

void simpleInterrupt(void.) interrupt (1)

 

 

 

{

 

 

 

 

counter++;

 

 

 

 

}

 

 

 

 

void naked.Interrupt(void.) interrupt (2) naked

 

 

 

{

 

 

 

 

_asm

 

 

 

 

inc

_counter ; Инкремент не меняет флагов,

 

 

 

; нет необходимости сохранять psw reti

;

 

Неоходимо

явно указывать

reti

 

_endasm;

 

 

 

 

}

 

 

 

 

 

Без __naked получается такой код:

 

 

push

acc

 

 

 

push

b

 

 

 

push

dpl

 

 

 

push

dph

 

 

 

push

psw

 

 

 

mov

psw,#0x00

 

 

 

inc

counter

 

 

 

pop

psw

 

 

 

pop

dph

 

 

 

pop

dpl

 

 

 

pop

b

 

 

 

pop

acc

 

 

 

reti

 

 

 

С naked код выглядит так:

_nakedInterrupt:

inc _counter ; Инкремент не меняет флагов,

; нет необходимости сохранять psw reti ;Неоходимо

явно

указывать

reti