Занятие 5. Технология создания собственных функций для приложений
При программировании на языке С следует создавать новые функции по единой технологии. Ниже будет рассмотрен пример создания процедуры генерации пилообразного сигнала с заданным шагом до заданного значения. Данный пример строится на базе предыдущего проекта, в котором имеется сконфигурированная ранее операционная система.
Так как предполагается, что функции и данные будут объединены в структуру с одним именем, то такая структура будет носить название «объект».
Для начала определим данные, которые будут использоваться объектом. Определение необходимых ресурсов происходит посредством введения заголовочного файла (название имени кроме расширения совпадает с именем файла исходного текста используемых функций, предполагается что текст функции будет содержаться в отдельном файле).
Заголовочный файл состоит из 4-х частей:
1. объявление структуры как типа данных; в структуру входят все переменные функции, а также указатели на адреса процедур, применяющихся при исполнении функций, обычно это процедуры инициализации (init) и расчета (update или calc).
2. Определение типа данных для указателя на структуру п.1
3. Объявление прототипов процедур, применяемых при обслуживании и расчете функции.
4. Задание начальных констант в структуру.
Подобный подход позволяет запускать несколько копий объектов, каждый использует свой набор данных, равный структуре, при этом не происходит тиражирования кода в памяти программ, так как используется для всех копий объектов один код.
Сохраните следующий файл как заголовочный (с расширением *.h) в директорию «include»:
#ifndef __RAMP_H__
#define __RAMP_H__
typedef struct
{
int step;
int ramp_out;
int max_out;
int (*init)();
int (*update)();
} RAMP;
typedef RAMP *RAMP_handle;
void RAMP_init(RAMP_handle);
void RAMP_update(RAMP_handle);
#define RAMP_DEFAULTS { 1,\
0,\
100,\
(int (*)(int))RAMP_init,\
(int (*)(int))RAMP_update}
#endif
Примечание. При задании параметров по умолчанию следует четко соблюдать чередование символов «переброс каретки» и «\», в противном случае возможны ошибки компиляции. См.пример.
Для самих процедур функции необходимо создать отдельный файл, например следующего содержания:
#include "..\include\ramp.h"
/* процедура инициализации */
void RAMP_init(RAMP *v)
{
v->step=3;
}
/* процедура расчета */
void RAMP_update(RAMP *v)
{
v->ramp_out=v->ramp_out+v->step;
if (v->ramp_out>v->max_out) v->ramp_out=0;
}
В основном файле программы следует объявить экземпляры объекта.
RAMP ramp1=RAMP_DEFAULTS;
RAMP ramp2=RAMP_DEFAULTS;
В основной файл исходного текста проекта необходимо добавить вызовы функций объектов, например:
int a=0;
void main()
{
RAMP_init(&ramp1);
RAMP_init(&ramp2);
while (1)
{
a++;
RAMP_update(&ramp1);
RAMP_update(&ramp2);
}
}
В результате будут генерироваться 2 пилообразных сигнала, шаг и максимальное значение которых можно задавать индивидуально.
В ходе разработки функций полезно пользоваться директивами компилятора, которые позволяют настроить ход компиляции. Рассмотрим на примере технологию применения директив.
Для того чтобы избежать предупреждения о переопределении константы, необходимо в начале каждого заголовочного файла вводить следующее:
#ifndef __LES1_H__
#define __LES1_H__
Для определения константы используем директиву #define. Допустим, необходимо, чтобы при компиляции происходило генерирование кода на сложение или вычитание. Для этого вводим
#define ADD 0
#define SUB 1
В дальнейшем ход компиляции будет определяться в зависимости от значений констант ADD или SUB.
#if ADD
my1.c = my1.a + my1.b;
#endif
#if SUB
my1.c = my1.a - my1.b;
#endif
Определять можно не только константы, но и макросы. Например, макрос возведения в квадрат будет выглядеть как
#define quad(x) ((x)*(x))
Вызов макроса возможен следующим образом:
float temp=0;
temp = quad(3.5);
При компиляции все строки, определенные через #define, будут подменены на заданные в директиве значения.
Директива #pragma позволяет определить область памяти, куда будут размещены данные или код (например, в командном файле должна быть задана секция sect):
#pragma CODE_SECTION(www, "sect");
void www(void);
В данном случае при объявлении прототипа функции указывается ее размещение в памяти.
Полный пример заголовочного файла les1.h:
#ifndef __LES1_H__
#define __LES1_H__
#define ADD 0
#define SUB 1
#define Uint16 unsigned int
#define quad(x) ((x)*(x))
typedef struct
{
float a;
float b;
float c;
} MY_STRUCT;
#define MY_STRUCT_DEFAULTS {2,\
3,\
0}
struct ABCD_bits{
Uint16 a:4;
Uint16 b:4;
Uint16 c:4;
Uint16 d:4;
};
typedef union ABCD_union {
Uint16 all;
struct ABCD_bits bit;
} ABCD_union;
#endif
Полный пример файла программы les1.c:
#include "les1.h"
#include <float.h>
MY_STRUCT my1=MY_STRUCT_DEFAULTS;
#pragma CODE_SECTION(www, "sect");
void www(void);
ABCD_union ax;
float temp=0;
void main()
{
while(1)
{
temp = quad(3.5);
#if ADD
my1.c = my1.a + my1.b;
#endif
#if SUB
my1.c = my1.a - my1.b;
#endif
www();
}
}
void www()
{
ax.bit.c=1;
}
В данном примере также показан механизм использования объединений (union), который позволяет использовать одну и ту же область памяти для различных типов данных, в данном случае – это одновременно и беззнаковое целое, и группы битов по 4 бита в каждой. Такой механизм удобно использовать, например, для конфигурирования регистров либо побитно, либо целиком загрузкой целого известного значения.
Собственно разработанные процедуры полезно объединять в библиотеки, объединяющие процедуры пользователя по функциональному принципу (библиотека регуляторов, драйверов, сигналов и т.д.). Библиотека будет содержать объектный код, который будет подключен в основной проект при выполнении компоновки (выполняется при подаче команды project-build).
Для этого необходимо создать проект библиотеки Project-New, в появившемся окне указать имя библиотеки (автоматически будет создан каталог библиотеки, куда необходимо сохранять все ее исходные файлы), в поле типа проекта (Project type) выбрать Library (.lib). В окне настройки опции компиляции изменятся названия закладок (появится Archiver), см.рис.1.
Рис.1
В проект необходимо добавить файл процедуры. При компиляции появится файл с расширением lib, имя которого будет одинаково с именем проекта.