Основная информация
Макросы – это программы на языке Си, используемые для создания алгоритмов управления и обработки данных. Термин «макрос» для обозначения подобных программ в панелях оператора используется по исторически сложившимся причинам и следует отделять от макросов препроцессора, описанных в стандарте языка Си.
Основной задачей панели оператора является сбор данных с подключенных к ней устройств и предоставление графического интерфейса для их отображения, изменения и архивирования. Тем не менее, в некоторых проектах может потребоваться решать более специфические задачи, например:
обрабатывать полученные данные – активировать на экране индикатор тревоги в случае превышения контролируемым параметром заданного значения и т. п.;
иметь полный контроль (на уровне отдельных запросов) над обменом данными с другими устройствами;
реализовывать нестандартные протоколы обмена для специфических приборов;
сохранять данные в файл в произвольном формате или вычитывать данные из таких файлов;
отображать на экране панели геометрические объекты динамической формы и цвета;
и т.д.
Именно для этого нужны макросы.
Следует учесть, что выполнение макросов является дополнительным функционалом панели, но не основным. Код макросов должен быть как можно более компактным и решать лишь те связанные с работой панели задачи, выполнение которых не удаётся реализовать в ПЛК, ПР или другом подключенном устройстве. В случае необходимости выполнения больших и сложных программ рекомендуется использовать вместо панели оператора линейку панельных контролеров СПК210.
Работа с макросами в интерфейсе конфигуратора
Для работы с макросами используется одноименный узел дерева проекта. Он содержит две вкладки – Заголовочные файлы и Файлы исходного кода. Для создания нового файла следует нажать на соответствующую вкладку ПКМ, использовать команду Создать и указать:
имя файла (должно быть уникальным в рамках каждой из папок);
автора;
описание;
дату создания.

С помощью команды Вставить можно вставить во вкладку файл, ранее скопированный из дерева проекта в буфер обмена.
При нажатии ПКМ на файл доступны следующие команды контекстного меню:
Вырезать (копировать файл в буфер обмена и удалить его из дерева проекта);
Копировать в буфер обмена;
Удалить;
Зашифровать;
Расшифровать;
Свойства (переход в редактор кода).

Функционал шифрования позволяет установить пароль, который потребуется ввести для открытия файла. Расшифровка позволяет отключить требование ввода пароля.
Двойное нажатие ЛКМ на файл приводит к открытию этого файла в редакторе кода. Редактор состоит из следующих областей:
панель команд (сверху);
панель навигации (слева);
кнопки переключения содержимого панели навигации (слева снизу);
текстовый редактор (по центру);
панель сообщений компиляции (снизу).

В панели навигации в зависимости от выбранной вкладки отображается дерево макросов проекта или список встроенных функций и макросов.
Ярлыки панели команд позволяют:
создать новый файл;
сохранить файл;
вырезать файл;
копировать файл;
вставить файл;
удалить файл;
отменить последнее изменение в выбранном файле (список изменений очищается при открытии другого файла);
восстановить последнее изменение в выбранном файле (список изменений очищается при открытии другого файла);
компилировать все файлы;
осуществить поиск или поиск с заменой по выбранному файлу;
закомментировать в выбранном файле выделенные строки;
раскомментировать в выбранном файле выделенные строки.
Компиляция – это процесс трансляции кода, написанном на языке высокого уровня, в объектный код. Объектный код обрабатывается линкером (компоновщиком), который превращает его в машинный код, исполняемый процессором панели.
Для компиляции макросов используется описанная выше команда. В результате компиляции в панели сообщений выводится информация об успешном завершении компиляции или описания возникших в её процессе ошибок.
Кроме того, компиляция макросов происходит в процессе компиляции проекта. В этом случае информация выводится в панель сообщений.
Вызов макросов
Вызов макросов возможен с помощью следующих элементов:
- Функциональная область
(действие Вызов макроса);
- Функциональная кнопка
(действие Вызов макроса);
- Переключатель
(вкладка Макрос);
- Многопозиционный переключатель
(вкладка Макрос).
Функциональная область позволяет выполнять макросы циклически или однократно при наступлении определённых событий (например, при переходе на экран, на котором расположена функциональная область). Остальные элементы позволяют однократно выполнить макрос при нажатии и/или отпускании.
В настройках вызова указывается имя файла исходного кода данного макроса и режим выполнения (синхронно или асинхронно).
Особенности поддержки языка Си в панелях СП210
Особенности поддержки языка Си в панелях СП210
Параметр | Значение |
|---|---|
Версия стандарта языка Си | C99 |
Компилятор, используемый в панели | arm-linux-gnueabihf-gcc version 4.9.4 (32-bit) |
Компилятор, используемый в режиме эмуляции | MinGW (32-bit) with gcc version 4.5.2 |
Флаги компиляции | O0 -std=c99 |
Поддержка стандартной библиотеки языка Си | Полная |
Директория монтирования USB-накопителя | /mnt/udisk/ В режиме эмуляции можно работать с файлами на ПК (при условии, что эти файлы доступны для пользователя, от имени которого запускается конфигуратор). В этом случае пути к файлам должны экранироваться: C:\\test_folder\\test_file.txt |
Базовая информация о языке C99
Цель пункта – познакомить читателя с основными элементами языка и привести примеры их использования; но он не является полноценным справочным руководством по языку. В данном пункте приведена базовая информация о языке C99 применительно к его реализации в панелях оператора СП210. В пункте не рассмотрены некоторые возможности языка, которые в большинстве случаев не потребуется применять при создании макросов для панелей оператора:
рекурсивный вызов функций;
работа с комплексными числами;
функционал препроцессора (кроме самых базовых команд);
создание функций с переменным количеством параметров;
динамическое выделение памяти;
и т. д.
Если вы не знакомы с языком Си, то рекомендуется использовать дополнительные справочные материалы – книги (например, Стивен Прата. Язык программирования C. Лекции и упражнения), интернет-ресурсы и т. п.
Последняя черновая версия стандарта C99 (отличающаяся от релизной только незначительными изменениями в оформлении) доступна по ссылке. По ней же доступен документ C99 Rationale, в котором объясняются причины, мотивы и обоснования особенностей реализации различных элементов языка (в том числе, изменений и нововведений, добавленных в версии C99).
Список специфических функций и макросов, доступных для панелей СП210, приведен в пункте.
Файлы исходного кода
В языке Си единицей трансляции является файл исходного кода, который имеет расширение .c. Созданный в конфигураторе файл выглядит по умолчанию следующим образом:
Пример
/**************************************************
Имя : Func0.c
Автор :
Дата : 11/17/2025
Описание:
**************************************************/
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
}
Файл включает в себя:
заголовок, сформированный на основе информации, указанной при создании файла;
директиву #include, подключающую системный заголовочный файл macro.h;
директиву #define, определяющую макрос препроцессора __SYS_DEFINE__ (не представляет интереса для пользователя);
функцию __MAIN__, по умолчанию не имеющую реализации (аналог функции main, описанной в стандарте языка Си). Её код создается пользователем.
При вызове макроса фактически происходит выполнение функции __MAIN__, являющейся точкой входа в программу. Таким образом, каждый файл исходного кода в дереве проекта соответствует одной программе. Помимо директив и функции __MAIN__ файл исходного кода может содержать:
объявления и определения других функций;
объявления и определения глобальных переменных;
определения структур, объединений, перечислений и псевдонимов.
Таким образом, в рамках функции __MAIN__ могут вызываться другие функции, созданные пользователем, или размещённые в подключаемых через директиву #include библиотеках.
Заголовочные файлы
Объявление и определение – это две важные концепции языка Си. Объявление – это способ сообщить компилятору о существовании объекта, а определение – способ создать объект и выделить память под его хранение.
Заголовочные файлы в основном содержат объявления (интерфейс), а файлы исходного кода – определения (реализацию).
Расширение заголовочных файлов - .h.
При создании языка Си введение концепции заголовочных файлов позволило обеспечить возможность раздельной компиляции файлов исходного кода; кроме того, их использование позволяет повысить степень модульности проекта.
Заголовочные файлы могут включать:
директивы препроцессора;
объявления внешних (extern) переменных;
объявление и определение структур, объединений, перечислений и псевдонимов;
объявление (прототипы) функций.
При создании макросов вы можете обойтись без использования заголовочных файлов. В случае их использования – предпримите усилия, чтобы в рамках файла исходного кода один и тот же заголовочный файл подключался не более одного раза (c помощью директив #ifndef или #pragma once).
Типы данных
Типы данных языка Си можно разделить на следующие категории:
тип bool;
целочисленные;
значения с плавающей точкой;
указатели;
массивы (и их частный вид – строки);
структуры;
объединения;
перечисления;
псевдонимы;
тип void.
В следующих подпунктах рассмотрены основные свойства этих типов. Для наглядности их описание сопровождается синтетическими примерами работы с переменными данных типов. Различные аспекты этих примеров (инициализация переменных, операторы, функции и т. д.) будут рассмотрены в следующих пунктах. В комментариях иногда размещен псевдокод – неформальный язык, применяемый для пояснения выполняемых операций и использующий ключевые слова популярных языков программирования.
1. Тип bool
Логический тип данных используется для представления значения, которое имеет только два возможных состояния – true (1) или false (0). Этот тип применяется для описания бинарных событий и команд (например, отсутствие/наличие тревоги, отключение/включение насоса и т. д.). Значения данного типа поддерживают логические операции.
Переменная типа bool занимает 1 байт памяти.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdbool.h"
__MAIN__()
{
bool BoolVar;
BoolVar = GetPSB(0) && GetPSB(1);
SetPSB(2, BoolVar);
// Эквивалентный псевдокод: PSB2 = PSB0 AND PSB1
SetPSB(3, true);
// Эквивалентный псевдокод: PSB3 = true
}
2. Целочисленные типы
Целочисленные типы отличаются друг от друга наличием/отсутствием знака и диапазоном значений. Значения этих типов поддерживают арифметические и побитовые операции, а также операции сравнения.
В таблице указаны размеры типов применительно к панели и её эмулятору; на других платформах размер может отличаться. Наличие нескольких вариантов именования для каждого типа связано с некоторыми историческими особенностями языка Си и наличием в панели псевдонимов для этих типов.
При выполнении арифметических операций возможен эффект переполнения – например, если переменная типа uint16_t имеет значение 65535 (это верхний предел ее диапазона) и мы прибавим к ней 1, то переменная получит значение 0 (т. е. ее значение станет равным нижней границе диапазона ее значений). Для панелей оператора СП210 переполнение и беззнаковых, и знаковых типов обрабатывается одним и тем же способом.
Деление на 0 приведет к исключению, в результате чего выполнение всех макросов прекращается до перезагрузки панели или перезапуска эмулятора.
Типы с постфиксом _t является типами фиксированного размера, для которых гарантируется совпадение размера на всех платформах. Для их использования требуется подключить заголовочный файл stdint.h.
Целочисленные типы данных
Тип | Диапазон | Размер в байтах |
|---|---|---|
char INT8 int8_t | -128…127 | 1 |
unsigned char BYTE UINT8 uint8_t | 0…255 | |
short INT16 int16_t | -32768… 32767 | 2 |
unsigned short WORD UINT16 uint16_t | 0…65535 | |
int long INT32 int32_t | -231…231-1 | 4 |
unsigned int unsigned long DWORD UINT32 uint32_t | 0…232 | |
long long INT64 int64_t | -263…263-1 | 8 |
unsigned long long DDWORD UINT64 uint64_t | 0…264 |
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
__MAIN__()
{
uint16_t u16_var1;
uint16_t u16_var2 = 65535;
int16_t i16_var;
uint32_t u32_Var1;
uint32_t u32_Var2 = 123456;
u16_var1 = PSW[0] + PSW[1];
PSW[2] = u16_var1;
// Можно сократить до PSW[2] = PSW[0] + PSW[1];
PSW[3] = u16_var2 + 1;
// Беззнаковое переполнение: в PSW[3] будет записано значение 0
i16_var = 32767 + 1;
PSW[4] = i16_var;
// Знаковое переполнение: в PSW[3] будет записано значение -32768
// (нижняя граница типа int16_t)
u32_Var1 = *(uint32_t*)(PSW + 10);
*(uint32_t*)(PSW + 12) = u32_Var2;
// Чтение и запись параметра типа DWord
}
3. Типы данных с плавающей точкой
Значения этих типов поддерживают арифметические операции и операции сравнения. В реализации панелей оператора тип long double по диапазону и точности соответствует типу double, так что его использование не имеет смысла.
Значение с плавающей точкой является лишь приближением к реальному значению с ограниченной точностью. Поэтому такие значения нет смысла проверять на строгое равенство – вместо этого следует вычислять их разность, и если в рамках конкретной задачи она пренебрежимо мала, то считать их «равными».
Значения с плавающей точкой могут иметь особые состояния – такие, как NaN и бесконечность. Для проверки, является ли значение особым, используются макросы заголовочного файла math.h
Целочисленные типы данных
Тип | Диапазон | Точность | Размер в байтах |
|---|---|---|---|
float | 10-38…1038 | 7-8 знаков после точки | 4 |
double | 10-308…10308 | 15-17 знаков после точки | 8 |
long double | 12 |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
float FloatVar1;
float FloatVar2;
// Чтение и запись параметра формата Float
FloatVar1 = *(float*)(PSW + 20);
FloatVar2 = FloatVar1 + 0.0001;
*(float*)(PSW + 22) = FloatVar2;
// Начиная с FloatVar1 = 2048 приращение 0.0001...
// ... не будет происходить из-за ограничения точности.
// Это называется «эффект расходимости мантиссы».
// Существуют различные алгоритмы для его компенсации.
// См., например, алгоритм Кэхэна
}
4. Указатели
Переменная типа «указатель» содержит адрес другой переменной (или функции – но об этом лучше прочитать в учебной литературе). Указатели обеспечивают гибкую и эффективную работу с памятью, и поэтому активно используются в коде стандартной библиотеки Си (и других библиотек). Они позволяют создавать функции, которые способны обрабатывать и модифицировать наборы данных произвольного типа и размера.
Для панелей оператора СП210 размер указателей составляет 4 байта, так как аппаратная платформа является 32-битной.
Указатели поддерживают арифметические операции и специфические операции получения адреса (&) и разыменования – доступа к значению, расположенному по адресу (*). Частой ошибкой является разыменование неинициализированного указателя («нулевого» или имеющего некорректное значение, не совпадающее с доступным адресом памяти). Результатом подобной операции является возникновение исключения, которое делает невозможным продолжение нормальной работы панели до её перезагрузки.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
__MAIN__()
{
// Синтетический пример использования указателя
uint16_t value = 123;
// Указатель на тип uint16_t
uint16_t *pointer;
// Получение адреса переменной value
pointer = &value;
// Доступ к значению, размещенному под указателем
// В PSW[30] будет записано значение переменной value
PSW[30] = *pointer;
// Можно сделать и наоборот – записать в переменную value значение регистра PSW[30]
*pointer = PSW[30];
}
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
__MAIN__()
{
uint16_t ui16_var;
// Функция Read читает значение из памяти панели или slave-устройства...
// ...и размещает его в переменной
// Так как функция должна поддерживать чтение значения произвольного типа и размера...
// ...то на её последний вход следует передавать адрес переменной...
// ...в которую это значение будет записано
Read(_T("Внутренние регистры"), 0, TP_PFW, DT_Word, 30, 0, &ui16_var);
}
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
float FloatVar1;
// Для доступа к параметру типа Float, размещенного в регистрах панели...
// ...используется каламбур типизации
FloatVar1 = *(float*)(PSW + 20);
// PSW + 20 – это адрес регистра панели PSW20 (фактически – указатель)
// (float*) заставляет компилятор считать, что это указатель на значение типа Float
// (тот же синтаксис, что при объявлении переменной типа указатель)
//
// *(float*) – разыменовывает это указатель, чтобы получить лежащее в регистрах панели...
// ... значение типа Float
}
5. Массивы
Массив объединяет набор объектов одного и того же типа. Данные массива размещаются в памяти последовательно. Для доступа к элементу массива используется оператор []. Индексация массивов ведется с 0.
Массивы часто обрабатываются в цикле for:
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
__MAIN__()
{
uint16_t Data[4];
// Размещаем в массиве значения регистров панели PSW100, PSW200, PSW300 и PSW400...
// ...для какой-то последующей обработки
for (int i = 0; i < 4; i++)
{
Data[i] = PSW[100 * (i + 1)];
}
}
Размеры массивов удобно задавать через макроопределения или константы – иначе в случае их изменения легко забыть внести исправление в какой-нибудь фрагмент кода, где они используются.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
// Макроопределение
#define ARR_SIZE 4
__MAIN__()
{
uint16_t Data[ARR_SIZE];
// Размещаем в массиве значения регистров панели PSW100, PSW200, PSW300 и PSW400...
// ...для какой-то последующей обработки
for (int i = 0; i < ARR_SIZEi++)
{
Data[i] = PSW[100 * (i + 1)];
}
}
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
// Константа
const int16_t ARR_SIZE = 4;
__MAIN__()
{
uint16_t Data[ArrSize];
// Размещаем в массиве значения регистров панели PSW100, PSW200, PSW300 и PSW400...
// ...для какой-то последующей обработки
for (int i = 0; i < ARR_SIZE; i++)
{
Data[i] = PSW[100 * (i + 1)];
}
}
Если при инициализации массива не указать его размер – то компилятор определит его автоматически на основании числа инициализаторов.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
__MAIN__()
{
// Будет выделена память под 3 значения
uint16_t Data[] = {1, 2, 3};
}
Можно инициализировать лишь нужные элементы массива – остальные автоматически будут инициализированы нулями.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
// Макроопределение
#define ARR_SIZE 4
__MAIN__()
{
// После инициализации массив примет вид {1, 0, 0, 4}
uint16_t Data[ARR_SIZE] = {[0] = 1, [3] = 4};
}
Доступны массивы переменной длины (variable-length array; VLA) – их размер может меняться в процессе работы программы. В этом случае при доступе к элементу массива следует контролировать, что индекс соответствует текущему размеру массива. Для VLA не поддерживается инициализация и использование модификатора static.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
__MAIN__()
{
uint16_t ArrSize;
uint16_t VLA_Data[ArrSize];
uint16_t CurrentIndex;
ArrSize = PSW[51];
CurrentIndex = PSW[52];
if (ArrSize != 0 && CurrentIndex <= ArrSize)
{
VLA_Data[CurrentIndex] = 42;
}
}
Массивы могут быть многомерными:
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
__MAIN__()
{
// массив 2x3x4
uint16_t arr3d[2][3][4] =
{
{ // Первый «блок»
{1, 2, 3, 4}, // 3 «строки» по 4 значения
{5, 6, 7, 8},
{9, 10, 11, 12}
},
{ // Второй «блок»
{13, 14, 15, 16},
{17, 18, 19, 20},
{21, 22, 23, 24}
}
};
// В регистр PSW53 будет записано значение 7
// (напомним, нумерация массивов ведется с 0)
PSW[53] = arr3d[0][1][2];
}
Имя массива является указателем на его начальный элемент. Поэтому при использовании функций Reads/Writes (и других функций, принимающих на вход указатель) необязательно использовать оператор получения адреса (&).
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
#define ARR_SIZE 4
__MAIN__()
{
uint16_t ui16_var;
uint16_t Data[ARR_SIZE];
// Для обычной переменной оператор & необходим
Read(_T("Local Device"), 0, TP_PFW, DT_Word, 30, 0, &ui16_var);
// А вот для массива подойдет любой из вариантов:
Reads(_T("Local Device"), 0, TP_PSW, DT_Word, 0, 0, ARR_SIZE, &Data);
Reads(_T("Local Device"), 0, TP_PSW, DT_Word, 0, 0, ARR_SIZE, Data);
}
Зная о родстве массивов и указателей – можно оценить, как в языке Си реализованы арифметические операции над последними.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
#define ARR_SIZE 4
__MAIN__()
{
uint16_t arr[ARR_SIZE] = {10, 20, 30, 40};
uint16_t *ptr = arr;
// ptr указывает на arr[0]
PSW[54] = *ptr;
ptr = ptr + 1;
// ptr указывает на arr[1]
PSW[55] = *ptr;
}
То есть увеличение значения указателя на 1 «смещает» его на количество байт, равное типу указателя: «ptr + n» эквивалентно «ptr + n * sizeof(*ptr)».
Кроме того, указатели, как и массивы, поддерживают индексный доступ.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
#define ARR_SIZE 4
__MAIN__()
{
uint16_t arr[ARR_SIZE] = {10, 20, 30, 40};
uint16_t *ptr = arr;
// ptr[0] соответствует arr[0]
PSW[54] = ptr[0];
// ptr[1] соответствует arr[1]
PSW[55] = ptr[1];
}
6. Строки
В языке Си нет отдельного типа данных для строк. Вместо этого строки реализованы как массивы символов. В стандарте C99 доступны два вида символов:
Виды символов в C99
Тип | Источник | Кодировка | Размер символа |
|---|---|---|---|
сhar | Встроенный тип | ASCII | 1 байт |
wchar_t | Заголовочный файл wchar.h | UTF-16 | 2 байта |
Строка в кодировке ASCII представляет собой массив char, а строка в кодировке UTF-16 – массив wchar_t. Строки в языке Си являются нуль-терминированными – каждая строка должна заканчиваться символом, имеющим код «0» (NULL-терминатором). При инициализации строки этот символ добавляется автоматически. Строковые литералы обрамляются двойными кавычками (в отличие от отдельных символов, обрамляемых одинарными кавычками). Для UTF-16 значение литерала предваряется префиксом L.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "wchar.h"
__MAIN__()
{
// Выделенный размер – 1 байт
char AsciiChar = 'v';
// Выделенный размер – 5 байт (4 байтовых символа + NULL-терминатор)
char AsciiStr[] = "test";
// Выделенный размер – 2 байта
wchar_t UnicodeChar = L'я';
// Выделенный размер – 10 байт (четыре 2-байтовых символа + 2-байтовый NULL-терминатор)
wchar_t UnicodeStr[] = L"тест";
}
С помощью символа «\» можно экранировать управляющие последовательности и HEX-коды символов. Это, в частности, позволяет инициализировать строки c использованием кодировки Win-1251.
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Строка «тест» (кодировка Win-1251)
char AsciiStr[] = "\xF2\xE5\xF1\xF2";
// Перенос строки
char AsciiStr[] = "line 1\nline 2";
}
Для работы с символами и строками используются следующие заголовочные файлы:
Заголовочные файлы для работы с символами и строками
Категория | Тип | Заголовочные файлы |
|---|---|---|
Символ | char | ctype.h |
char[] | string.h stdlib.h | |
Строка | wchar_t | wctype.h |
wchar_t[] | wchar.h |
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
#include "string.h"
__MAIN__()
{
char FullStr[20] = "";
char str1[10] = "Hello, ";
char str2[10] = "World!";
uint16_t FullStrLen;
// Записываем в FullStr результат объединения FullStr и str1
// Из str1 используется не более 10 символов
strncat(FullStr, str1, 10);
// Записываем в FullStr результат объединения FullStr и str2
// Из str2 используется не более 10 символов
strncat(FullStr, str2, 10);
// Вычисляем количество символов строки FullStr
FullStrLen = strlen(FullStr);
// Записывем строку FullStr в регистры панели начиная с PSW60
Writes(_T("Внутренние регистры"), 0, TP_PSW, DT_Word, 60, 0, 1 + FullStrLen / 2, FullStr);
}
В заголовочном файле string.h также объявлены функции для работы с памятью, которые часто используются применительно к массивам – memcpy, memmove, memcmp, memset и др.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "string.h"
__MAIN__()
{
// Записываем в регистры PSW56-59 значение 123
// Регистр занимает 2 байта, поэтому 2 * 4
memset(&PSW[56], 123, 2 * 4);
}
7. Структуры
Структура объединяет набор объектов разных типов. Каждый объект размещается в отдельных ячейках памяти.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
// Объявление структуры
struct MessageBox
{
char title[30];
char message[200];
uint8_t IconCode;
uint16_t x;
uint16_t y;
};
__MAIN__()
{
// Определение структуры c полной инициализацией полей
struct MessageBox ErrorMessageBox1 = {"error", "Some error text", 0, 100, 100};
// Определение структуры c частичной инициализацией полей
// Остальные поля будут инициализированы нулями
struct MessageBox ErrorMessageBox2 = {.title = "Error",
.message = "Some error text"};
// Доступ к полям структуры в коде
ErrorMessageBox2.x = 100;
ErrorMessageBox2.y = 100;
}
Можно совместить объявление структуры с её определением:
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
// Объявление структуры
struct MessageBox
{
char title[30];
char message[200];
uint8_t IconCode;
uint16_t x;
uint16_t y;
} ErrorMessageBox2;
__MAIN__()
{
// Доступ к полям структуры в коде
ErrorMessageBox2.title = "Error";
ErrorMessageBox2.message = "Some error text";
ErrorMessageBox2.x = 100;
ErrorMessageBox2.y = 100;
}
Для доступа к полю структуры через указатель на структуру используется специфический оператор «->»:
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
// Объявление структуры
struct MessageBox
{
char title[30];
char message[200];
uint8_t IconCode;
uint16_t x;
uint16_t y;
} ErrorMessageBox2;
__MAIN__()
{
struct MessageBox *PtrToMessageBox = &ErrorMessageBox2;
// Доступ к полям структуры через указатель
// Для полей-массивов использование оператора «->» не требуется...
// ...потому что имя массива - указатель на его начальный элемент
PtrToMessageBox.title = "Error";
PtrToMessageBox.message = "Some error text";
PtrToMessageBox->x = 100;
PtrToMessageBox->y = 100;
}
В языке Си присутствует поддержка битовых полей (bitfield) – разновидностей структур, содержащих только целочисленные поля, для каждого из которых указано количество занимаемых бит.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
// Объявление структуры с битовыми полями
struct MERCURY23x_MODEL_PARAMS_2
{
uint8_t CounterConstant : 4;
uint8_t PhaseCount : 1;
uint8_t IsAveragePowerProfile : 1;
uint8_t TempRange : 1;
uint8_t DirectionCount : 1;
};

При использовании структур можно столкнуться с выравниванием памяти (memory aligment). Рассмотрим влияние выравнивания на простом примере. Пусть у нас определена структура с двумя полями, одно из которых имеет тип uint8_t, а второе – uint32_t.
Пример
struct TestStuct
{
uint8_t var1;
uint32_t var2;
} ts
Первое поле занимает 1 байт, второе 4 – и логично предположить, что вся структура будет занимать 5 байт. Но с помощью оператора sizeof можно убедиться, что в реальности в панели структура занимает 8 байт (для других платформ результат может быть иным):
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
struct TestStuct
{
uint8_t var1;
uint32_t var2;
} ts
__MAIN__()
{
// В PSW80 будет записано значение 8
PSW[80] = sizeof(ts);
}
Наблюдаемый эффект связан с выравниванием памяти. В статье на Википедии приведен необходимый минимум для понимания этой концепции (а в англоязычной версии статьи есть конкретные примеры):
«Центральные процессоры в качестве основной единицы при работе с памятью используют машинное слово, размер которого может быть различным. Однако размер слова всегда равен нескольким байтам (байт является наименьшей единицей, в которой отсчитываются адреса). Как правило, машинное слово равно 2k байтам, то есть состоит из одного, двух, четырёх, восьми и т. д. байтов.
При сохранении какого-то объекта в памяти может случиться, что некое поле, состоящее из нескольких байтов, пересечёт “естественную границу” слов в памяти. Некоторые модели процессоров не могут обращаться к данным в памяти, нарушающим границы машинных слов. Некоторые могут обращаться к невыровненным данным дольше, нежели к данным, находящимся внутри целого “машинного слова” в памяти».
В некоторых ситуациях требуется управлять выравниванием памяти (например – отключить его); это, например, может быть удобным при реализации протоколов, когда структуру требуется «наложить» на байтовый буфер запроса или ответа.
Для этого существуют различные средства, но проще всего воспользоваться директивой pack:
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
// Сохраняем текущее выравнивание и устанавливаем 1-байтовое
#pragma pack(push, 1)
struct TestStuct
{
uint8_t var1;
uint32_t var2;
} ts
// Восстанавливаем сохраненное ранее выравнивание
#pragma pack(pop)
// На эту структуру директива pack уже не действует
struct TestStuct2
{
uint8_t var1;
uint32_t var2;
} ts2;
__MAIN__()
{
// В PSW80 будет записано значение 5
PSW[80] = sizeof(ts);
// В PSW81 будет записано значение 8
PSW[81] = sizeof(ts2);
}
Структуры могут иметь произвольную вложенность и свободно сочетаются с массивами – например, можно объявить массив структур, одним из полей которой является структура, одним из полей которой является массив и т. д.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
struct TestStuct
{
uint8_t var1;
uint32_t var2;
};
__MAIN__()
{
struct TestStuct ArrTs[4];
ArrTs[0].var1 = 42;
}
8. Объединения
Объединения очень похожи на структуры, за одним существенным отличием – все их объекты размещаются в одних и тех же ячейках памяти. Это обеспечивает разные варианты представления одних и тех же данных.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
union IPAddress {
uint32_t HexIp;
uint8_t octets[4];
};
__MAIN__()
{
union IPAddress ip;
// Соответствует 192.168.0.10
ip.HexIp = 0xC0A8000A;
// Старший октет фактически хранится в младшем байте
// В регистр PSW90 будет записано значение 192
PSW[90] = ip.octets[3];
// В регистр PSW91 будет записано значение 168
PSW[91] = ip.octets[2];
// В регистр PSW92 будет записано значение 0
PSW[92] = ip.octets[1];
// В регистр PSW93 будет записано значение 10
PSW[93] = ip.octets[0];
}
9. Перечисления
Перечисление позволяет указать для конкретных значений целочисленной переменной символьные названия, что повышает читабельность кода. В языке Си все перечисления трактуются как значения типа int и имеют соответствующий диапазон доступных значений. Часто перечисления используются в качестве меток оператора switch.
Пример
#include "macro.h"
#define __SYS_DEFINE__
enum Days
{
NONE, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
};
__MAIN__()
{
enum Days WeekDay;
// Чтение системного регистра с номером дня недели
WeekDay = SPSW[22];
switch (WeekDay)
{
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
// разместите здесь код управления системой в будние дни
break;
case SATURDAY:
case SUNDAY:
// разместите здесь код управления системой в выходные дни
break;
default:
// разместите здесь код вывода сообщения об ошибке
}
}
Значения элементов перечислений могут быть указаны в явном виде:
Пример
#include "macro.h"
#define __SYS_DEFINE__
enum Days
{
NONE = 0,
MONDAY = 1,
TUESDAY = 2,
WEDNESDAY = 3,
THURSDAY = 4,
FRIDAY = 5,
SATURDAY = 6,
SUNDAY = 7
};
При этом для некоторых элементов значения могут быть опущены:
Пример
#include "macro.h"
#define __SYS_DEFINE__
enum Colors
{
RED = 1,
GREEN, // 2
BLUE, // 3
WHITE = 7,
BLACK // 8
};
Значения элементов перечисления могут быть «значимыми»:
Пример
#include "macro.h"
#define __SYS_DEFINE__
// Использутся цветовая модель BGR, применяемая в функциях работы с элементом Холст
enum Colors
{
RED = 0x0000FF,
GREEN = 0x00FF00,
BLUE = 0xFF0000
};
10. Псевдонимы (typedef)
Ключевое слово typedef создает псевдоним для типа данных, что повышает читабельность кода.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
typedef struct
{
uint16_t x;
uint16_t y;
} Point;
__MAIN__()
{
// Использование typedef позволило обойтись без ключевого слова struct
Point Line[2] = {10, 10, 100, 100};
}
11. Тип void
Void – это специфический тип данных. Он является неполным – создать переменную данного типа нельзя. Но void можно использовать:
в качестве типа, возвращаемого функцией – это будет означать, что данная функция не возвращает никакого значения (см., например, Beep);
при создании функций, которые не принимают никаких параметров (см., например, Beep);
при указании параметров функции, которые могут иметь произвольный тип (см., например, функцию WriteF);
указатель на void соответствует указателю на произвольный тип данных. Это может использоваться при создании функций, которые должны работать с данными произвольного типа (см., например, функции семейства Read/Write).
Переменные
Переменная – это именованный участок памяти, в котором хранится значение. Для использования переменной её необходимо определить в файле исходного кода, указав тип. Определение может быть проведено в любом месте файла, что влияет на область видимости и существования переменной.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
// Глобальная переменная
// Сохраняет свое значение между вызовами программы
// Может использоваться в пределах файла исходного кода
uint16_t global_var1;
// Пример одновременного определения нескольких переменных общего типа
uint16_t global_var2, global_var3;
__MAIN__()
{
// Локальная переменная
// Не сохраняет свое значение между вызовами программы
// (переинициализурется при каждом вызове)
// Может использоваться только в пределах блока
// (в данном случае – в пределах функции __MAIN__)
uint16_t local_function_var;
// какой-то код
{
// Локальная переменная
// Не сохраняет свое значение между вызовами программы
// (переинициализурется при каждом вызове)
// Может использоваться только в пределах блока
// (в данном случае – до символа закрывающей фигурной скобки)
uint16_t local_scope_var;
// какой-то код
}
// использовать здесь local_scope_var не получится
}
Определение переменной должно быть сделано перед её использованием (т. е. в расположенных выше строках файла).
Переменные могут быть глобальными и локальными.
Глобальные переменные определяются вне функций. Они доступны для использования в рамках всего файла исходного кода. Кроме того, их можно использовать в других файлах (см. пункт). Если для глобальной переменной не указано значение инициализации – то она инициализируется значением 0. Значение глобальной переменной сохраняется между вызовами программы.
Локальные переменные объявляются внутри функций. Они доступны для использования только в рамках блочного оператора, в котором определены (блочный оператор представлен парой фигурных скобок). Если для локальной переменной не указано значение инициализации – то при старте программы она будет содержать произвольное значение («мусор»). Значение локальной переменной не сохраняется между вызовами программы. Частным случаем локальных переменных являются параметры функции.
Инициализация – это процесс однократной записи в переменную предустановленного значения при запуске программы.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdbool.h"
#include "stdint.h"
__MAIN__()
{
bool BoolVar = true;
uint16_t u16_var = 123;
char str[] = "test";
}
Способы инициализации для других типов данных приведены в примерах пункт.
Спецификаторы и квалификаторы
При объявлении и определении переменных и функций могут использоваться спецификаторы. Основными из них являются static и extern. Спецификатор размещается перед типом переменной или перед типом значения, возвращаемого функций.
Локальные переменные со спецификатором static сохраняют свои значения между вызовами программы. Кроме того, если для них не указаны значения инициализации – то они инициализируются значением 0.
Для глобальных переменных и функций спецификатор static ограничивает область видимости файлом исходного кода, в котором они определены.
Пример
// Func0.c
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
// Эта глобальная переменная доступна в других файлах
uint16_t global_var;
// Эта глобальная переменная недоступна в других файлах
static uint16_t static_global_var;
__MAIN__()
{
// Эта локальная переменная не сохранит свое значение между вызовами программы...
// ...и будет инициализироваться начальным значением при каждом вызове
uint16_t local_var = 0;
// Эта локальная переменная сохранит свое значение между вызовами программы
static uint16_t static_local_var = 0;
}
// Func1.c
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
// Импорт глобальной переменной из файла Func0.c
// Импортировать static_global_var не получится...
// ...так как она помечена модификатором static
extern uint16_t global_var;
__MAIN__()
{
global_var = 42;
}
Как показано в примере выше, спецификатор extern используется для объявления (импорта) переменных, которые определены в другом файле.
Переменные с квалификатором const расцениваются как константы, значения которых не должны изменяться в процессе работы программы. Попытки каким-либо образом изменить эти значения (например, с помощью использования указателей) приводятся к неопределённому поведению (undefined behavior) программы.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
__MAIN__()
{
// Переменная (может быть массивом, структурой, полем структуры и т. д.)
float val = 11.22;
// Константное значение, не может быть изменено
const float readonly_val = 22.33;
// Указатель на константу. Значение адреса может быть изменено...
// ...а вот расположенные по адресу данные - нет
const float *ptr1 = &val
// Эквивалент варианта выше
float const *ptr2 = &val
// Константный указатель. Значение адреса не может быть изменено...
// ...а вот расположенные по адресу данные - могут
float *const ptr3 = &val
// Константый указатель на константу. Ни адрес, ни расположенные по нему данные...
// ...не могут быть изменены
const float *const ptr4 = &val
// Эквивалент варианта выше
float const *const ptr5 = &val
}
Литералы
Литерал – это элемент исходного кода, который соответствует фиксированному значению. Например, в строке
Пример
float val = 11.22;значение 11.22 является литералом.
Для литералов определённых категорий доступны префиксы и постфиксы, влияющие на их формат и тип.
Префиксы для целочисленных типов:
0x (шестнадцатеричная система счисления; HEX);
0b (двоичная система счисления).
Префиксы для строк:
префикс L указывает на кодировку UTF-16.
Постфиксы для целочисленных типов:
u (U) – признак «беззнаковости» литерала. Может сочетаться с другими постфиксами (например, ul – unsigned long);
l (L) – литерал является значением типа long;
ll (LL) – литерал является значением типа long long.
По умолчанию (без постфикса) целочисленный литерал интерпретируется компилятором как значение типа int.
Постфиксы для типов с плавающей точкой:
f (f) – литерал является значением типа float;
L (L) – литерал является значением типа long double.
По умолчанию (без постфикса) литерал с плавающей точкой интерпретируется компилятором как значение типа double.
Постфиксы позволяют:
добиться нужной интерпретации литерала, что в некоторых случаях может быть очень важно (см. пример с делением ниже);
сэкономить память;
повысить читабельность кода.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
#include "wchar.h"
__MAIN__()
{
// DEC
uint16_t val1 = 16;
// HEX
uint16_t val2 = 0x10;
// Двоичная систем, разделитель ' является опциональным
uint16_t val3 = 0b0001'0000;
// Результат: 3 (так как выполняется целочисленное деление)
float val4 = 10 / 3;
// Результат: 3.333333
float val5 = 10 / 3f; // или 3.0
wchar_t UnicodeStr[] = L"тест";
}
Идентификаторы
Идентификатор – это имя, указываемое при объявлении/определении переменных, функций, псевдонимов типов данных, макросов и других сущностей.
В языке Си идентификаторы являются регистрозависимыми, т. е. var и Var – это два разных идентификатора.
На идентификатор накладываются следующие ограничения:
может включать в себя буквы английского языка, цифры и символы нижнего подчеркивания (_);
первый символ не может быть цифрой;
не может совпадать с ключевым словом языка Си или идентификатором, используемым в стандартной библиотеке языка Си;
длина идентификатора не может превышать 256 символов.
Комментарии
В языке Си поддержаны однострочные и многострочные комментарии:
Пример
// однострочный комментарий
/* многострочный
комментарий */
Кнопки редактора макросов позволяют закомментировать/раскомментировать выделенные строки с помощью однострочных комментариев.
Операторы и выражения
Выражение — это упорядоченный набор операций над переменными, литералами и функциями.
Таблица основных операторов языка Си
Оператор | Описание |
|---|---|
Оператор присваивания | |
= |
|
Арифметические операторы
| |
+ - * / % | Cложение Вычитание Умножение Деление Остаток от деления Применимы к арифметическим типам (оператор % применим только к целочисленным типам) |
Логические операторы | |
&& || ! | Логическое И (AND) Логическое ИЛИ (OR) Логическое отрицание (NOT) Применимы к логическому типу. Заголовочный файл iso646.h содержит макроопределения-псевдонимы (например, and для &&) |
Побитовые операторы | |
& | ^ ~ | Побитовое И Побитовое ИЛИ Побитовое исключающее ИЛИ (XOR) Побитовая инверсия Применимы к целочисленным типам |
Операторы сравнения | |
< > <= >=
== != | «Меньше» «Больше» «Меньше или равно» «Больше или равно»
Применимы к арифметическими логическим типам Проверка на равенство Проверка на неравенство Применимы к арифметическим (включая перечисления) и логическим типам |
Операторы битового сдвига | |
<< >> | Битовый сдвиг влево Битовый сдвиг вправо Применимы к арифметическим типам. Первый аргумент оператора – это значение, в котором выполняется сдвиг, второй – количество сдвигаемых бит |
Остальные операторы | |
[] . -> & * , sizeof | Доступ к объекту массива или указателя Доступ к полю структуры Доступ к полю структуры через указатель на структуру Получение адреса объекта Разыменование указателя Разделитель аргументов функции Получение адреса объекта или типа (возвращаемое значение имеет тип size_t, который определен в заголовочном файле stddef.h) |
Для преобразования типов используется специфический оператор:
Пример
int ivar;
float fvar = 11.22;
// float to int
// ivar получит значение 11
ivar = (int)fvar;
В выражении могут одновременно использоваться несколько операторов:
Пример
uint16_t var;
// Что выполнится сначала – сложение 5 и 1, или умножение 10 на 5?
var = 10 * 5 + 1;
Порядок их вычисления зависит от приоритета. Для управления приоритетом используются скобки – они имеют высший приоритет:
Пример
uint16_t var;
// Теперь мы точно знаем, что сначала выполнится сложение
var = 10 * (5 + 1);
В языке Си поддерживается множественное присваивание:
Пример
uint16_t var1, var2, var3;
var1 = var2 = var3 = 42;
Арифметические, логические и «сдвиговые» операторы могут комбинироваться с оператором присваивания:
Пример
var1 += 5; // эквивалентно var = var + 5;Характерными для языка Си являются операторы инкремента и декремента, которые могут быть префиксными и постфиксными:
Пример
var2 = ++var1; // увеличивает var1 на +1, после чего записывает результат в var2
var3 = var2++; // записывает var2 в var3, после чего увеличивает var2 на +1
Некоторые характерные для других языков операторы в Си представлены в виде функций заголовочного файла math.h:
abs (получение абсолютной величины числа);
pow (возведение в степень);
sin, cos;
и др.
Функции
При создании простых макросов можно расположить весь их код в функции __MAIN__. Но при решении более сложных задач некоторые действия будут повторяться многократно, и разумно реализовать их в виде отдельных функций.
Ниже приведен пример определения и вызова функции, которая имеет 2 параметра:
T типа float;
IsAirSaturation типа bool.
Эти параметры принимают значения аргументов функции, переданные ей при вызове.
В результате выполнения функция возвращает значение типа float.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "math.h"
#include "stdbool.h"
#define a0 999.83952
#define a1 16.952577
#define a2 -7.9905127E-3
#define a3 -4.6241757E-5
#define a4 1.0584601E-7
#define a5 -2.8103006E-10
#define b 0.0168872
// Уравнение Келла для определения плотности воды при температуре T
// Параметр IsAirSaturation определяет необходимость учета влияния растворенного...
// ...в воде воздуха
// http://www.bioconsult.ch/Inovatech/W-Lehre/J%20Che%20Eng%20Dat20,%2097.pdf
// Размерность: г/мл
// Точность: 4 знака после запятой
float WaterDensity(float T, bool IsAirSaturation)
{
float wd;
wd = (a0 + a1*T + a2*pow(T,2) + a3*pow(T,3) + a4*pow(T,4) + a5*pow(T,5) ) /
(1.0 + b*T);
// http://www.bioconsult.ch/Inovatech/W-Lehre/J%20Che%20Eng%20Dat20,%2097.pdf
// Учет влияния растворенного воздуха
if (IsAirSaturation)
{
wd = wd - 0.004612 + 0.000106 * T
}
// Перевод из кг/м3 в г/мл
wd = 0.001 * wd;
return wd;
}
__MAIN__()
{
*(float*)(PSW + 20) = WaterDensity(*(float*)(PSW + 10), false);
*(float*)(PSW + 22) = WaterDensity(*(float*)(PSW + 12), false);
// и т. д.
// чтобы избежать копипасты – можно разместить вызов функции в цикле for
}
Оператор return традиционно размещается в конце функции, но может располагаться в любых ее фрагментах (в т. ч. таких операторов в функции может быть несколько, чтобы организовать выход из функции при различных условиях).
В некоторых ситуациях в качестве аргументов функции используются указатели – см., в частности, примеры использования функций Reads/Writes.
Если требуется указать, что функция не имеет явного возвращаемого значения (например, она как раз манипулирует данными, полученными по указателю), то в качестве типа функции следует использовать void.
Если функция не имеет параметров, то вместо них указывается void.
Характерный пример – функция Beep, которая используется для активации зуммера панели. Она не принимает и не возвращает никаких значений, и объявлена следующим образом:
Пример
void Beep(void);Препроцессор
Препроцессор – это программа, подготавливающая код на языке Си к компиляции. К основным задачам препроцессора относятся:
вставка в обрабатываемый файл содержимого других заголовочных файлов с помощью директивы #include;
выполнение макроподстановок для директив #define;
выполнение директив условной компиляции (не рассматриваются в рамках данного документа);
выполнение других директив.
Директива #include используется для подключения заголовочных файлов – в частности, заголовочных файлов стандартной библиотеки языка Си. Это требуется для возможности вызова функций этой библиотеки. Например, для использования функций заголовочного файла math.h требуется добавить такую строку:
Пример
#include "math.h"или такую:
Пример
#include <math.h>Разница между этими двумя формами заключается только в принципе поиска файлов, что не имеет значения при работе с панелями оператора.
Директива #define используется для определения констант и макросов. Препроцессор заменяет первый аргумент директивы вторым, выполняя макроподстановку.
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
// Макроопределение
#define ARR_SIZE 4
__MAIN__()
{
uint16_t Data[ARR_SIZE];
// Размещаем в массиве значения регистров панели PSW100, PSW200, PSW300 и PSW400...
// ...для какой-то последующей обработки
for (int i = 0; i < ARR_SIZE; i++)
{
Data[i] = PSW[100 * (i + 1)];
}
}
См. также список предопределённых констант #define, автоматически создаваемых препроцессором.
В качестве констант также можно использовать переменные с квалификатором const – по сравнению с макроконстантами этот подход уменьшает вероятность ошибки, так как для таких переменных компилятор сможет проверить соответствие типов.
Пример макроса c параметрами:
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
// Ограничиваем числовое значение в диапазоне [min, max]
#define LIMIT(min, x, max) (((x) < (min)) ? (min) : (((x) > (max)) ? (max) : (x)))
__MAIN__()
{
uint16_t var1 = 20;
uint16_t var2 = 40;
uint16_t var3 = 60;
// В регистр PSW100 будет записано значение 30
PSW[100] = LIMIT(30, var1, 50);
// В регистр PSW101 будет записано значение 40
PSW[101] = LIMIT(30, var2, 50);
// В регистр PSW102 будет записано значение 50
PSW[102] = LIMIT(30, var3, 50);
}
Такой макрос можно заменить функцией. Но подобные макросы полезны тем, что могут быть добавлены на вкладку Встроенные функции редактора макросов.
Если требуется, чтобы макрос выполнял несколько отдельных выражений, то используется идиома do {…} while(0):
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "stdint.h"
// Макрос меняет между собой значения аргументов типа int
// Символ «\» используется для переноса строк внутри кода макроса
#define SWAP(a, b) \
do { \
int temp = (a); \
(a) = (b); \
(b) = temp; \
} while(0)
__MAIN__()
{
int x = 10, y = 20;
SWAP(x, y);
// Теперь x = 20, y = 10
}
В макросах с параметрами могут использоваться специфические операторы # (обрамление параметра макроса двойными кавычками) и ## (объединение двух лексем). См. примеры использования этих операторов в статье на Википедии.
Макросы с параметрами требуют тщательной расстановки скобок, поэтому использовать их следует с осторожностью.
Директивы #warning и #error позволяют вывести в лог компилятора предупреждение или сообщение об ошибке. Это, например, позволяет расставлять в исходном коде различного рода напоминания.
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
#warning Добавить проверку деления на 0
PSW[106] = PSW[105] / PSW[104];
}
Директива #pragma позволяет выполнить указанное после имени директивы действие. Список поддерживаемых действий зависит от компилятора. Например, в пункте 12.5.4 использовалась #pragma pack для управления выравниванием в структуре.
Стандартная библиотека языка Си
Стандартная библиотека языка Си позволяет использовать в программе широкий набор функций, определённых в стандарте языка. Для использования конкретной функции требуется с помощью директивы #include подключить к программе заголовочный файл, в котором объявлена данная функция.
Ниже приведен список заголовочных файлов, которые могут потребоваться при создании программ для панелей оператора.
Список некоторых заголовочных файлов стандартной библиотеки языка Си
Заголовочный файл | Что содержит |
|---|---|
Функции для обработки символов кодировки ASCII | |
Коды ошибок, возвращаемых функциями стандартной библиотеки, и глобальную переменную errno, в которую они записываются | |
Функции для работы с целочисленными переменными фиксированного размера | |
Макроопределения-псевдонимы для логических операторов (например, and для &&) | |
Арифметические и тригонометрические функции | |
Макросы true и false | |
Целочисленные типы данных фиксированного размера (uint16_t и т. д.) | |
Функции для работы с файлами | |
Функции общего назначения (выделение и освобождение памяти, генерация псевдослучайных чисел, сортировка и поиск, преобразование чисел в строки и строк в числа и т. д.) | |
Функции для работы с ASCII-строками и памятью (memcpy, memmove, memcmp, memset) | |
Функции конвертации различных форматов даты и времени | |
Функции для работы со строками в кодировке UTF-16 (UCS-2) | |
Функции для обработки символов кодировки UTF-16 (UCS-2) |
Встроенные функции и макросы
В редакторе кода на вкладке Встроенные функции расположен список встроенных функций и макросов.

Добавление пользовательских макросов на эту вкладку рассмотрено в пункте.
Работа с внутренними битами и регистрами панели
Регистры области памяти PSW и SPSW представлены как массивы данных:
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Запись значения в регистр PSW0
PSW[0] = 123;
// Чтение значения из регистра PSW1 в переменную
WORD wVar;
wVar = PSW[1];
// Для работы с различными типами данных нужно использовать операторы конверсии:
float rValue1;
rValue1 = *(float*)(PSW + 10);
float rValue2 = 11.22;
*(float*)(PSW + 12) = rValue2;
DWORD dwValue1;
dwValue1 = *(DWORD*)(PSW + 14);
DWORD dwValue2 = 123456;
*(DWORD*)(PSW + 16) = dwValue2;
}
Для работы с другими областями памяти используются специальные функции. Некоторые из этих функций могут использоваться и для работы с PSW и SPSW:
Функции для работы с памятью панели и slave-устройств
В функциях Read/Reads/Write/WriteF/Writes в случае использования протокола Modbus для параметра DataArea доступны следующие значения:
Значения параметра DataArea для slave-устройств с протоколом Modbus
Modbus RTU | Modbus ASCII | Modbus TCP | Описание |
|---|---|---|---|
TP7_0x | TP8_0x | TP9_0x | Область coils (биты, доступные для чтения и записи) |
TP7_1x | TP8_1x | TP9_1x | Область discrete inputs (биты, доступные только для чтения) |
TP7_2x | TP8_2x | TP9_2x | Область input registers (регистры, доступные только для чтения) |
TP7_3x | TP8_3x | TP9_3x | Область holding registers (регистры, доступные для чтения и записи) |
Эти наборы эквивалентны, т. е. можно, например, использовать TP7_4x для протоколов Modbus ASCII и Modbus TCP.
В случае использования других протоколов введите в редакторе кода имя функции (например, Reads) и поставьте открывающую скобку для вызова окна настройки аргументов функции. Выберите в нём нужное устройство и его область памяти, чтобы автоматически сформировать значение аргумента.

В функциях Read/Reads/Write/WriteF/Writes в случае доступа к внутренней памяти панели для параметра DataArea доступны следующие значения:
TP_PSB;
TP_SPSB;
TP_PSW;
TP_PFW;
TP_SPSW;
TP_SPFW.
В функциях Read/Reads/Write/WriteF/Writes для параметра DataType доступны следующие значения:
DT_Bit;
DT_Byte (доступен только для некоторых протоколов обмена);
DT_WORD;
DT_DWORD;
DT_DDWORD;
DT_Word_String;
DT_DWord_String;
DT_DDWord_String.
Функции GetPSB и GetSPSB используются для получения значения бита области PSB (GetPSB) или SPSB (GetSPSB).
Описание функции GetPSB (GetSPSB)
Определение | BOOL GetPSB(DWORD Addr); |
Addr | Адрес бита |
Возвращаемое значение | Значение бита – 1 (true) или 0 (false) |
Примечание | Для использования макросов true и false следует подключить заголовочный файл stdbool.h |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Приведенный ниже фрагмент кода выполнится только в том случае, если PSB10 имеет...
// ...значение true
if (GetPSB(10) )
{
// ваш код
}
}
Функции SetPSB и SetSPSB используются для установки значения бита области PSB (SetPSB) или SPSB (SetSPSB).
Описание функции SetPSB (SetSPSB)
Определение | void SetPSB(DWORD Addr, BOOL Value); |
Addr | Адрес бита |
Value | Устанавливаемое значение бита – 1 (true) или 0 (false) |
Возвращаемое значение | отсутствует |
Примечание | Для использования макросов true и false следует подключить заголовочный файл stdbool.h |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Установка в PSB10 значения true
SetPSB(10, 1);
}
Функция Read используется для чтения одного параметра из памяти панели или slave-устройства в переменную макроса. Для настройки параметров функции с помощью вспомогательного меню введите в редакторе кода Read и поставьте открывающую скобку.
Описание функции Read
Определение | BOOL Read (int Device, int SlaveId, int DataArea, int DataType, int RegAddr, int BitAddr, void* pValue); |
Device | ID устройства. Используйте оператор _T для конвертации символьного имени устройства в его ID |
SlaveId | Адрес устройства (в случае чтения из памяти панели следует использовать адрес 0) |
DataArea | Область памяти |
DataType | Тип данных |
RegAddr | Адрес регистра |
BitAddr | Номер бита (для чтения бита регистра, в остальных случаях равен 0) |
pValue | Указатель на буфер, в котором будет размещено считанное значение |
Возвращаемое значение | TRUE – успешное выполнение операции, FALSE – ошибка (например, не был получен ответ от slave-устройства) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Чтение holding-регистра 20 из Modbus RTU Slave-устройства с адресом 5
WORD wValue1;
Read(_T("Modbus RTU Master"), 5, TP7_4x, DT_WORD, 20, 0, &wValue1);
// Чтение внутреннего регистра PFW300
WORD wValue2;
Read(_T("Внутренние регистры"), 0, TP_PFW, DT_Word, 300, 0, &wValue2);
// Чтение бита PSB100
bool xValue1;
Read(_T("Внутренние регистры"), 0, TP_PSB, DT_Bit, 100, 0, &xValue1);
// Чтение бита PSW200.8
bool xValue2;
Read(_T("Внутренние регистры"), 0, TP_PSW, DT_Bit, 200, 8, &xValue2);
}
Функция Write используется для записи одного значения макроса в память панели или slave-устройство. Для настройки параметров функции с помощью вспомогательного меню введите в редакторе кода Write и поставьте открывающую скобку.
Описание функции Write
Определение | BOOL Write(int Device, int SlaveId, int DataArea, int DataType, int RegAddr, int BitAddr, void* pValue); |
Device | ID устройства. Используйте оператор _T для конвертации символьного имени устройства в его ID |
SlaveId | Адрес устройства (в случае записи в память панели следует использовать адрес 0) |
DataArea | Область памяти |
DataType | Тип данных |
RegAddr | Адрес регистра |
BitAddr | Номер бита (для записи бита регистра, в остальных случаях равен 0) |
pValue | Указатель на буфер, в котором размещается записываемое значение |
Возвращаемое значение | TRUE – успешное выполнение операции, FALSE – ошибка (например, не был получен ответ от slave-устройства) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Запись holding-регистра 20 в Modbus RTU Slave-устройстве с адресом 5
WORD wValue1 = 123;
Write(_T("Modbus RTU Master"), 5, TP7_4x, DT_WORD, 20, 0, wValue1);
// Запись во внутренний регистр PFW300
WORD wValue2 = 123;
Write(_T("Внутренние регистры"), 0, TP_PFW, DT_Word, 300, 0, wValue2);
// Запись бита PSB100
bool xValue1 = 1;
Write(_T("Внутренние регистры"), 0, TP_PSB, DT_Bit, 100, 0, xValue1);
// Запись бита PSW200.8
bool xValue2 = 1;
Write(_T("Внутренние регистры"), 0, TP_PSW, DT_Bit, 200, 8, xValue2);
}
Функция Reads используется для чтения одного или нескольких параметров из памяти панели или slave-устройства в переменные макроса. Для настройки параметров функции с помощью вспомогательного меню введите в редакторе кода Reads и поставьте открывающую скобку.
Описание функции Reads
Определение | BOOL Reads(int Device, int SlaveId, int DataArea, int DataType, int RegAddr, int BitAddr, int Count, void* pValue); |
Device | ID устройства. Используйте оператор _T для конвертации символьного имени устройства в его ID |
SlaveId | Адрес устройства (в случае чтения из памяти панели следует использовать адрес 0) |
DataArea | Область памяти |
DataType | Тип данных |
RegAddr | Адрес начального регистра |
BitAddr | Номер бита (для чтения битов регистров, в остальных случаях равен 0) |
Count | Количество объектов |
pValue | Указатель на буфер, в котором будут размещены считанные значения |
Возвращаемое значение | TRUE – успешное выполнение операции, FALSE – ошибка (например, не был получен ответ от slave-устройства) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Чтение holding-регистров 20...29 из Modbus RTU Slave-устройства с адресом 5
WORD awData1[10];
Reads(_T("Modbus RTU Master"), 5, TP7_4x, DT_WORD, 20, 0, 10, awData1);
// Чтение внутренних регистров PFW300...329
WORD awData2[30];
Reads(_T("Внутренние регистры"), 0, TP_PFW, DT_Word, 300, 0, 30, awData2);
// Чтение битов PSB100...PSB115
WORD wBitMask;
Reads(_T("Внутренние регистры"), 0, TP_PSB, DT_Bit, 100, 0, 16, &wBitMask);
// Чтение битов PSW200.8...PSW200.15
BYTE byBitMask;
Reads(_T("Внутренние регистры"), 0, TP_PSW, DT_Bit, 200, 8, 8, &byBitMask);
}
Функция Writes используется для записи одного или нескольких значений макроса в память панели или slave-устройство. Для настройки параметров функции с помощью вспомогательного меню введите в редакторе кода Writes и поставьте открывающую скобку.
Описание функции Writes
Определение | BOOL Writes(int Device, int SlaveId, int DataArea, int DataType, int RegAddr, int BitAddr, int Count, void* pValue); |
Device | ID устройства. Используйте оператор _T для конвертации символьного имени устройства в его ID |
SlaveId | Адрес устройства (в случае чтения из памяти панели следует использовать адрес 0) |
DataArea | Область памяти |
DataType | Тип данных |
RegAddr | Адрес начального регистра |
BitAddr | Номер бита (для записи битов регистров, в остальных случаях равен 0) |
Count | Количество объектов |
pValue | Указатель на буфер, в котором размещаются записываемые значения |
Возвращаемое значение | TRUE – успешное выполнение операции, FALSE – ошибка (например, не был получен ответ от slave-устройства) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Запись holding-регистров 20...29 в Modbus RTU Slave-устройстве с адресом 5
WORD awData1[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
Writes(_T("Modbus RTU Master"), 5, TP7_4x, DT_WORD, 20, 0, 10, awData1);
// Запись во внутренние регистры PFW300...329
// PWF300 получит значение 1, PFW310 получит значение 2, PFW320 получит значение 3,
// PFW329 получит значение 4
// Остальные регистры получат значение 0
WORD awData2[30] = { [0] = 1, [10] = 2, [20] = 3, [29] = 4 };
Writes(_T("Внутренние регистры"), 0, TP_PFW, DT_Word, 300, 0, 30, awData2);
// Запись битов PSB100...PSB115
WORD wBitMask = 39321;
Writes(_T("Внутренние регистры"), 0, TP_PSB, DT_Bit, 100, 0, 16, &wBitMask);
}
Функция WriteF используется для записи одного значения с плавающей точкой в память панели или slave-устройство. Для настройки параметров функции с помощью вспомогательного меню введите в редакторе кода WriteF и поставьте открывающую скобку.
Описание функции WriteF
Определение | BOOL WriteF(int Device, int SlaveId, int DataArea, int DataType, int RegAddr, int BitAddr, void Value); |
Device | ID устройства. Используйте оператор _T для конвертации символьного имени устройства в его ID |
SlaveId | Адрес устройства (в случае чтения из памяти панели следует использовать адрес 0) |
DataArea | Область памяти |
DataType | Тип данных |
RegAddr | Адрес начального регистра |
BitAddr | Номер бита (не используется, равно 0) |
Count | Количество объектов |
Value | Записываемое значение типа Float или Double (в зависимости от DataType) |
Возвращаемое значение | TRUE – успешное выполнение операции, FALSE – ошибка (например, не был получен ответ от slave-устройства) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Запись float-значения в holding-регистры 20-21 в Modbus RTU Slave-устройстве с
// адресом 5
float rValue = 11.22;
WriteF(_T("Modbus RTU Master"), 5, TP7_4x, DT_DWord, 300, 0, rValue);
// Запись float-значения во внутренние регистры PFW300-PFW301
float rValue = 11.22;
WriteF(_T("Внутренние регистры"), 0, TP_PFW, DT_DWord, 300, 0, rValue);
}
8. Функции ReadPFW и ReadSPFW
Функции ReadPFW и ReadSPFW используются для чтения одного или нескольких регистров области PFW (ReadPFW) или SPFW (ReadSPFW) в переменные макроса.
Описание функции ReadPFW (ReadSPFW)
Определение | void ReadPFW(int RegAddr, int ByteCount, void* pData); |
RegAddr | Адрес регистра |
ByteCount | Количество байт (1 регистр = 2 байта) |
pData | Указатель на буфер, в котором будут размещены считанные значения |
Возвращаемое значение | Отсутствует |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Чтение внутренних регистров PFW300...305
WORD awValue[6];
ReadPFW(300, 6 * sizeof(WORD), awValue);
}
Функции WritePFW и WriteSPFW используются для записи значений макроса в один или несколько регистров области PFW (WritePFW) или SPFW (WriteSPFW).
Описание функции WritePFW (WriteSPFW)
Определение | void WritePFW(int RegAddr, int ByteCount, void* pData); |
RegAddr | Адрес регистра |
ByteCount | Количество байт (1 регистр = 2 байта) |
pData | Указатель на буфер, в котором размещаются записываемые значения |
Возвращаемое значение | Отсутствует |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Запись внутренних регистров PFW300...305
WORD awValue[6] = {1, 2, 3, 4, 5, 6};
WritePFW(300, 6 * sizeof(WORD), awValue);
}
Функции для обмена данными с ПЛК CODESYS V3.5
Данные функции используются для работы с переменными вкладки С устройства, импортированными из ПЛК с системой исполнения CODESYS V3.5 и опрашиваемыми по протоколу CODESYS V3 (PLC Handler) или OPC UA Client.;
Функция CodeSysRead используется для чтения значения переменной из вкладки Библиотека переменных – С устройства в переменную макроса. Для настройки параметров функции с помощью вспомогательного меню введите в редакторе кода CodeSysRead и поставьте открывающую скобку.

Описание функции CodeSysRead
Определение | BOOL CodeSysRead(int Device, char* LabelName, int Сount, int LabelType, void* pData); |
Device | ID устройства. Используйте оператор _T для конвертации символьного имени устройства в его ID |
LabelName | Путь к переменной, разделитель – «/» |
Сount | Количество элементов массива (для переменных элементарного типа устанавливается значение 1) |
LabelType | Тип переменной (BOOL = 0, BYTE = 1, WORD = 2, DWORD = 3, LWORD = 4, SINT = 5, USINT = 6, INT = 7, UINT = 8, DINT = 9, UDINT = 10, LINT = 11, REAL = 12, LREAL = 13, STRING = 14, TIME = 15, TIME_OF_DAY = 16, DATE = 17, DATE_AND_TIME = 18, WSTRING = 21) |
pValue | Указатель на буфер, в котором будет размещено считанное значение (размер буфера должен соответствовать типу данных) |
Возвращаемое значение | TRUE – успешное выполнение операции, FALSE – ошибка (например, не был получен ответ от slave-устройства) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
WORD wVar;
CodeSysRead(_T("OPC UA Client"), "Application/PLC_PRG/wTestVar", 1, 2, &wVar);
PSW[10] = wVar;
}
Функция CodeSysWrite используется для записи значения переменной макроса в переменную вкладки Библиотека переменных – С устройства. Для настройки параметров функции с помощью вспомогательного меню введите в редакторе кода CodeSysWrite и поставьте открывающую скобку.
Описание функции CodeSysWrite
Определение | BOOL CodeSysWrite (int Device, char* LabelName, int Сount, int LabelType, void* pData); |
Device | ID устройства. Используйте оператор _T для конвертации символьного имени устройства в его ID |
LabelName | Путь к переменной, разделитель – «/» |
Сount | Количество элементов массива (для переменных элементарного типа устанавливается значение 1) |
LabelType | Тип переменной (BOOL = 0, BYTE = 1, WORD = 2, DWORD = 3, LWORD = 4, SINT = 5, USINT = 6, INT = 7, UINT = 8, DINT = 9, UDINT = 10, LINT = 11, REAL = 12, LREAL = 13, STRING = 14, TIME = 15, TIME_OF_DAY = 16, DATE = 17, DATE_AND_TIME = 18, WSTRING = 21) |
pValue | Указатель на буфер, в котором размещается записываемое значение (размер буфера должен соответствовать типу данных) |
Возвращаемое значение | TRUE – успешное выполнение операции, FALSE – ошибка (например, не был получен ответ от slave-устройства) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
WORD wVar = 123;
CodeSysWrite(_T("OPC UA Client"), "Application/PLC_PRG/wTestVar", 1, 2, &wVar);
}
Функция CodeSysReadString используется для чтения значения строковой переменной из вкладки Библиотека переменных – С устройства в переменную макроса. Для строк типа WSTRING выполняется автоматическая конвертация в ASCII. Для настройки параметров функции с помощью вспомогательного меню введите в редакторе кода CodeSysReadString и поставьте открывающую скобку.
Описание функции CodeSysReadString
Определение | CodeSysReadString(int Device, char* LabelName, int Сount, int Len, void* pData); |
Device | ID устройства. Используйте оператор _T для конвертации символьного имени устройства в его ID |
LabelName | Путь к переменной, разделитель – «/» |
Len | Количество считываемых символов строки |
pData | Указатель на буфер, в котором будет размещено считанное значение (размер буфера должен соответствовать Len) |
Возвращаемое значение | TRUE – успешное выполнение операции, FALSE – ошибка (например, не был получен ответ от slave-устройства) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
char sVar[81] = {0};
// sTestVar имеет тип STRING(80)
CodeSysReadString(_T("OPC UA Client"), "Application/PLC_PRG/sTestVar", 1, 80, sVar);
// Значение переменной будет записано в регистры панели PSW100...PSW139
// Каждый символ строки типа STRING занимает 1 байт, размер регистра - 2 байта
// Writes(_T("Внутренние регистры"), 0, TP_PSW, DT_Word, 100, 0, 40, sVar);
}
Функция CodeSysWriteString используется для записи строковой переменной макроса в переменную вкладки Библиотека переменных – С устройства. Для строк типа WSTRING должна использоваться кодировка ASCII (конвертация будет выполнена автоматически). Строка не должна содержать символы кириллицы. Для настройки параметров функции с помощью вспомогательного меню введите в редакторе кода CodeSysWriteString и поставьте открывающую скобку.
Описание функции CodeSysWriteString
Определение | CodeSysWriteString(int Device, char* LabelName, int Сount, int Len,void* pData); |
Device | ID устройства. Используйте оператор _T для конвертации символьного имени устройства в его ID |
LabelName | Путь к переменной, разделитель – «/» |
Len | Количество символов записываемой строки |
pData | Указатель на буфер, в котором размещается записываемое значение (размер буфера должен соответствовать Len) |
Возвращаемое значение | TRUE – успешное выполнение операции, FALSE – ошибка (например, не был получен ответ от slave-устройства) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
char sVar[12] = "hello, world";
CodeSysWriteString(_T("OPC UA Client"), "Application/PLC_PRG/sTestVar", 1, 12, sVar);
}
Функции для реализации пользовательского протокола
Данные функции используются совместно с протоколом Пользовательский и позволяют реализовать отправку и получение через заданный COM-порт произвольного набора байт.
1. Функция COMSend
Функция COMSend отправляет в COM-порт заданный набор байт.
Описание функции COMSend
Определение | int COMSend(int Device, char* pData, int BufSize); |
Device | ID устройства. Используйте оператор _T для конвертации символьного имени устройства в его ID |
pData | Указатель на буфер отправляемых данных |
BufSize | Размер буфера в байтах |
Возвращаемое значение | Количество отправленных байт (-1 – ошибка выполнения функции) |
Примечание | 1. В меню Настройки проекта - Устройства должен быть выбран Пользовательский протокол 2. Функция блокирует поток выполнения до завершения своей работы |
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "string.h"
__MAIN__()
{
int result = -1;
char abyData[20] = "Hello, world";
// Для использования функции strlen требуется подключить заголовочный файл string.h
result = COMSend(_T("Пользовательский протокол"), abyData, strlen(abyData) );
}
2. Функция COMReceive
Функция COMReceive используется для чтения данных из буфера COM-порта.
Описание функции COMReceive
Определение | int COMReceive(int Device, char* pData, int BufSize, unsigned short TimeOut, unsigned short EndFrameBytes); |
Device | ID устройства. Используйте оператор _T для конвертации символьного имени устройства в его ID |
pData | Указатель на буфер, в котором будут размещены считанные данные |
BufSize | Размер буфера в байтах |
TimeOut | Время ожидания (в мс) получения BufSize байт. По истечению времени происходит завершение работы функции 0 – время ожидания не ограничено |
EndFrameBytes | Если в течение времени, требуемого для передачи указанного количества байт, не поступило новых данных, то происходит завершение работы функции. 0 – функция не завершает свою работу до получения BufSize байт |
Возвращаемое значение | Количество полученных байт (0 – в буфере отсутствуют данные, -1 – ошибка выполнения функции) |
Примечание | 1. В меню Настройки проекта - Устройства должен быть выбран Пользовательский протокол 2. Функция блокирует поток выполнения до завершения своей работы |
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "string.h"
__MAIN__()
{
int result = -1;
char abyData[10] = {0};
// Функция завершает свою работу после получения 10 байт данных
result = COMReceive(_T("Пользовательский протокол"), abyData, sizeof(abyData), 0, 0);
}
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "string.h"
__MAIN__()
{
int result = -1;
char abyData[10] = {0};
// Функция завершает свою работу после получения 10 байт данных
// Если в течение 1000 мс не получено 10 байт - то функция завершает свою работу
result = COMReceive(_T("Пользовательский протокол"), abyData, sizeof(abyData), 1000, 0);
}
Пример
#include "macro.h"
#define __SYS_DEFINE__
#include "string.h"
__MAIN__()
{
int result = -1;
char abyData[10] = {0};
// Функция завершает свою работу после получения 10 байт данных
// Если в течение 1000 мс не получено 10 байт - то функция завершает свою работу
// Если в процессе получения данных возникает пауза, длительность которой равна...
// ...времени передачи 10 байт на установленной для COM-порта скорости...
// ...то функция завершает свою работу
result = COMReceive(_T("Пользовательский протокол"), abyData, sizeof(abyData), 1000, 10);
}
Функции для работы с элементом Холст
Данные функции используются для работы с элементом Холст.
Функция DCMapClear используется для очистки холста.
Описание функции DCMapClear
Определение | BOOL DCMapClear(DWORD dwDCMapID); |
dwDCMapID | Индекс холста (см. настройки элемента Холст) |
Возвращаемое значение | TRUE – операция уcпешно выполнена, FALSE – операция не выполнена (холст с таким индексом не существует) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
DCMapClear(0);
}
Функция DCMapDrawCircle используется для отрисовки на холсте окружности.
Описание функции DCMapDrawCircle
Определение | BOOL DCMapDrawCircle(DWORD dwDCMapID, int x, int y, int Radius, int LineWidth, DWORD LineColor, BOOL DoFill, DWORD FillColor); |
dwDCMapID | Индекс холста (см. настройки элемента Холст) |
x | Координата центра окружности по оси X (относительно верхнего левого края холста) |
y | Координата центра окружности по оси Y (относительно верхнего левого края холста) |
Radius | Радиус окружности |
LineWidth | Толщина контура |
LineColor | Цвет контура окружности в BGR |
DoFill | Флаг заливки окружности цветом (1 – заливка, 0 – отсутствие заливки) |
FillColor | Цвет заливки окружности в BGR |
Возвращаемое значение | TRUE – операция уcпешно выполнена, FALSE – операция не выполнена (холст с таким индексом не существует) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
DCMapDrawCircle(0, 200, 200, 100, 2, 0x0000000, 1, 0xFF0000);
}
Функция DCMapDrawCircleArc используется для отрисовки на холсте дуги.
Описание функции DCMapDrawCircleArc
Определение | BOOL DCMapDrawCircleArc(DWORD dwDCMapID, int x, int y, int Radius, int LineWidth, DWORD LineColor, DWORD StartAngle, DWORD EndAngle); |
dwDCMapID | Индекс холста (см. настройки элемента Холст) |
x | Координата центра дуги по оси X (относительно верхнего левого края холста) |
y | Координата центра дуги по оси Y (относительно верхнего левого края холста) |
Radius | Радиус дуги |
LineWidth | Толщина контура |
LineColor | Цвет контура окружности в BGR |
StartAngle | Угол начала дуги |
EndAngle | Угол конца дуги |
Возвращаемое значение | TRUE – операция уcпешно выполнена, FALSE – операция не выполнена (холст с таким индексом не существует) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
DCMapDrawCircleArc(0, 200, 200, 100, 2, 0x0000000, 70, 190);
}
Функция DCMapDrawEllipse используется для отрисовки на холсте эллипса.
Описание функции DCMapDrawEllipse
Определение | BOOL DCMapDrawCircleArc(DWORD dwDCMapID, int x, int y, int xRadius, int yRadius, int LineWidth, DWORD LineColor, DWORD StartAngle, DWORD EndAngle); |
dwDCMapID | Индекс холста (см. настройки элемента Холст) |
x | Координата центра эллипса по оси X (относительно верхнего левого края холста) |
y | Координата центра эллипса по оси Y (относительно верхнего левого края холста) |
| xRadius | Радиус по X |
| yRadius | Радиус по Y |
LineWidth | Толщина контура |
LineColor | Цвет контура в BGR |
DoFill | Флаг заливки эллипса цветом (1 – заливка, 0 – отсутствие заливки) |
FillColor | Цвет заливки эллипса в BGR |
Возвращаемое значение | TRUE – операция уcпешно выполнена, FALSE – операция не выполнена (холст с таким индексом не существует) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
DCMapDrawEllipse(0, 200, 200, 100, 50, 2, 0x0000000, 1, 0xFF0000);
}
Функция DCMapDrawEllipseArc используется для отрисовки на холсте эллиптической дуги.
Описание функции DCMapDrawEllipseArc
Определение | BOOL DCMapDrawCircleArc(DWORD dwDCMapID, int x, int y, int Radius, int LineWidth, DWORD LineColor, DWORD StartAngle, DWORD EndAngle); |
dwDCMapID | Индекс холста (см. настройки элемента Холст) |
x | Координата центра эллиптической дуги по оси X (относительно верхнего левого края холста) |
y | Координата центра эллиптической дуги по оси Y (относительно верхнего левого края холста) |
Radius | Радиус эллиптической дуги |
LineWidth | Толщина контура |
LineColor | Цвет контура в BGR |
StartAngle | Угол начала эллиптической дуги |
EndAngle | Угол конца эллиптической дуги |
Возвращаемое значение | TRUE – операция уcпешно выполнена, FALSE – операция не выполнена (холст с таким индексом не существует) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
DCMapDrawEllipseArc(0, 200, 200, 100, 50, 2, 0x0000000, 70, 190);
}
Функция DCMapDrawLine используется для отрисовки на холсте линии.
Описание функции DCMapDrawLine
Определение | BOOL DCMapDrawLine(DWORD dwDCMapID, int xStart, int yStart, int xEnd, int yEnd, int LineWidth, DWORD LineColor); |
dwDCMapID | Индекс холста (см. настройки элемента Холст) |
xStart | Координата начальной точки по оси X (относительно верхнего левого края холста) |
yStart | Координата начальной точки по оси Y (относительно верхнего левого края холста) |
xEnd | Координата конечной точки по оси X (относительно верхнего левого края холста) |
yEnd | Координата конечной точки по оси Y (относительно верхнего левого края холста) |
LineWidth | Толщина линии |
LineColor | Цвет линии в BGR |
Возвращаемое значение | TRUE – операция уcпешно выполнена, FALSE – операция не выполнена (холст с таким индексом не существует) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
DCMapDrawLine(0, 200, 200, 400, 500, 2, 0x0000000);
}
Функция DCMapDrawRect используется для отрисовки на холсте прямоугольника.
Описание функции DCMapDrawRect
Определение | BOOL DCMapDrawRect(DWORD dwDCMapID, int x, int y, int Width, int Height, int LineWidth, DWORD LineColor, BOOL DoFill, DWORD FillColor); |
dwDCMapID | Индекс холста (см. настройки элемента Холст) |
x | Координата начальной точки прямоугольника по оси X (относительно верхнего левого края холста) |
y | Координата начальной точки прямоугольника по оси Y (относительно верхнего левого края холста) |
Width | Ширина |
Height | Высота |
LineWidth | Толщина контура |
LineColor | Цвет контура в BGR |
DoFill | Флаг заливки прямоугольника цветом (1 – заливка, 0 – отсутствие заливки) |
FillColor | Цвет заливки прямоугольника в BGR |
Возвращаемое значение | TRUE – операция успешно выполнена, FALSE – операция не выполнена (холст с таким индексом не существует) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
DCMapDrawRect(0, 200, 200, 100, 150, 2, 0x0000000, 1, 0xFF0000);
}
Функция DCMapSetBackColor используется для установки фона холста.
Описание функции DCMapSetBackColor
Определение | BOOL DCMapSetBackColor(DWORD dwDCMapID, DWORD Color); |
dwDCMapID | Индекс холста (см. настройки элемента Холст) |
Color | Цвет фона холста в BGR |
Возвращаемое значение | TRUE – операция успешно выполнена, FALSE – операция не выполнена (холст с таким индексом не существует) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
DCMapSetBackColor(0, 0xFF0000);
}
Функции для работы с экранами и окнами
Функция ScreenJump используется для переключения экрана.
Описание функции ScreenJump
Определение | WORD ScreenJump(WORD ScreenNo); |
ScreenNo | Номер экрана |
Возвращаемое значение | Номер экрана, на который был осуществлен переход. Переключение не произойдет, если у текущего пользователя недостаточно прав доступа для перехода на экран – тогда функция вернет номер текущего экрана, на котором остался пользователь |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Переход на экран 2
// В случае успешного перехода переменная wCurrentNo получит значение 2
WORD wCurrentNo;
wCurrentNo = ScreenJump(2);
}
Функция OpenWindow используется для открытия окна.
Описании функции OpenWindow
Определение | void OpenWindow(int winNo, int x, int y); |
winNo | Номер окна |
x | Координата открытия по X (для верхней левой точки окна) |
y | Координата открытия по Y (для верхней левой точки окна) |
Возвращаемое значение | Отсутствует |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Открыть окно с номером 2
// Верхней левой точке окна будут соответствовать координаты (10, 10)
OpenWindow(2, 10, 10);
}
Функция CloseWindow используется для закрытия окна.
Описание функции CloseWindow
Определение | void CloseWindow(WORD winNo); |
winNo | Номер окна |
Возвращаемое значение | Отсутствует |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Закрыть окно с номером 2
CloseWindow(2);
}
Функции для обработки данных
Макрос GET_BIT используется для извлечения указанного бита целочисленной переменной.
Описание макроса GET_BIT
Определение | bool GET_BIT(Value, BitNumber); |
Value | Целочисленная переменная |
BitNumber | Номер бита |
Возвращаемое значение | Значение бита – 1 (true) или 0 (false) |
Примечание | Для использования макросов true и false следует подключить заголовочный файл stdbool.h |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Приведенный ниже фрагмент кода выполнится только в том случае…
// ...если PSW300.15 имеет значение true
if (GET_BIT(PSW[300], 15) )
{
// ваш код
}
}
Макрос SET_BIT используется для установки указанного бита целочисленной переменной в значение true.
Определение | SET_BIT(Value, BitNumber); |
Value | Целочисленная переменная |
BitNumber | Номер бита |
Возвращаемое значение | Целочисленное значение с установленным битом |
Примечание | Для использования макросов true и false следует подключить заголовочный файл stdbool.h |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Установка бита PSW300.15 в значение true
PSW[300] = SET_BIT(PSW[300], 15);
}
Макрос RESET_BIT используется для установки указанного бита целочисленной переменной в значение false.
Описание макроса RESET_BIT
Определение | RESET_BIT(Value, BitNumber); |
Value | Целочисленная переменная |
BitNumber | Номер бита |
Возвращаемое значение | Целочисленное значение со сброшенным битом |
Примечание | Для использования макросов true и false следует подключить заголовочный файл stdbool.h |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Установка бита PSW300.15 в значение false
PSW[300] = RESET_BIT(PSW[300], 15);
}
Макрос HIWORD используется для получения старшего слова значения типа DWORD.
Описание макроса HIWORD
Определение | WORD HIWORD(DWORD dw); |
dw | Значение типа DWORD |
Возвращаемое значение | Cтаршее слово dw |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
DWORD dwVar = 0xFFFFAAAA;
// PSW0 получит значение 65535 (0xFFFF)
PSW[0] = HIWORD(dwVar);
}
Макрос LOWORD используется для получения младшего слова значения типа DWORD.
Описание макроса LOWORD
Определение | WORD LOWORD(DWORD dw); |
dw | Значение типа DWORD |
Возвращаемое значение | Младшее слово dw |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
DWORD dwVar = 0xAAAAFFFF;
// PSW0 получит значение 65535 (0xFFFF)
PSW[0] = LOWORD(dwVar);
}
Макрос HIBYTE используется для получения старшего байта значения типа WORD.
Определение | BYTE HIBYTE(WORD w); |
w | Значение типа WORD |
Возвращаемое значение | Cтарший байт w |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
WORD wVar = 0xFFAA;
// PSW0 получит значение 255 (0xFF)
PSW[0] = HIBYTE(wVar);
}
Макрос LOBYTE используется для получения младшего байта значения типа WORD.
Описание макроса LOBYTE
Определение | BYTE LOBYTE(WORD w); |
w | Значение типа WORD |
Возвращаемое значение | Младший байт w |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
WORD wVar = 0xAAFF;
// PSW0 получит значение 255 (0xFF)
PSW[0] = LOBYTE(wVar);
}
Макрос MAKEDWORD используется для «сборки» значения типа DWORD из двух значений типа WORD.
Определение | DWORD MAKEDWORD(WORD wl, WORD wh); |
wl | Младшее слово значения |
wh | Старшее слово значения |
Возвращаемое значение | Значение типа DWORD |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
WORD wLow = 0xAAAA;
WORD wHigh = 0xFFFF;
DWORD dwVar;
// dwVar получит значение 0xFFFFAAAA
*(DWORD*)(PSW + 0) = MAKEDWORD(wLow, wHigh);
}
Макрос MAKEWORD используется для «сборки» значения типа WORD из двух значений типа BYTE.
Описание макроса MAKEWORD
Определение | DWORD MAKEWORD(BYTE byl, BYTE byh); |
byl | Младший байт значения |
byh | Старший байт значения |
Возвращаемое значение | Значение типа WORD |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
BYTE byLow = 0xAA;
BYTE byHigh = 0xFF;
// PSW0 получит значение 65450 (0xFFAA)
PSW[0] = MAKEWORD(byLow, byHigh);
}
Макрос MAX возвращает наибольшее из двух числовых значений.
Описание макроса MAX
Определение | MAX(a, b); |
a | Числовое значение |
b | Числовое значение |
Возвращаемое значение | Наибольшее из a и b |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
float rVar1 = 11.22;
float rVar2 = 22.33;
float rMax;
// rMax получит значение 22.33
rMax = MAX(rVar1, rVar2);
*(float*)(PSW + 0) = rMax;
}
Макрос MIN возвращает наименьшее из двух числовых значений.
Описание макроса MIN
Определение | MIN(a, b); |
a | Числовое значение |
b | Числовое значение |
Возвращаемое значение | Наименьшее из a и b |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
float rVar1 = 11.22;
float rVar2 = 22.33;
float rMin;
// rMin получит значение 11.22
rMin = MIN(rVar1, rVar2)
*(float*)(PSW + 0) = rMin;
}
Остальные функции
Функция Beep используется для включения зуммера.
Описание функции Beep
Определение | void Beep(void); |
Возвращаемое значение | Отсутствует |
Примечание | Не имеет эффекта, если зуммер отключен (Настройки проекта – Общие – Отключить зуммер) |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Пока бит PSB300 имеет значение TRUE - зуммер будет активен
if (GetPSB(300) )
{
Beep();
}
}
Функция Delay организует синхронную (блокирующую) задержку выполнения кода функции.
Описание функции Delay
Определение | void Delay(UINT ms); |
ms | Интервал задержки в миллисекундах |
Возвращаемое значение | Отсутствует |
Примечание | Если вызывается в функции с типом вызова Синхронная, то блокирует работу всех остальных функций в пределах элемента, к которому привязана |
Пример
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
// Задержка выполнения следующих строк кода на 1 секунду
Delay(1000);
}
Функция Lock организует блокировку доступа к секции кода для исключения несогласованного доступа к одним и тем же данным из разных функций. Функция UnLock снимает блокировку.
Описание функций Lock и UnLock
Определение | void Lock(int id); void UnLock(int id); |
id | Идентификатор секции (0...9) |
Возвращаемое значение | Отсутствует |
Примечание | 1. Каждому вызову функции Lock должен соответствовать вызов функции UnLock 2. Рекомендуется по возможности избегать доступа к одним и тем же данным из разных функций |
Пример
// Функции func0 и func1 вызываются независимо друг от друга...
// ...и осуществляют запись в один и тот же регистр
// (по возможности такого поведения стоит избегать)
// Для осуществления согласованного доступа (чтобы функции не начали перезаписывать...
// ...регистр одновременно, «испортив» его значение) используется блокировка
void func0()
{
Lock(0);
PSW[300] = 55;
UnLock(0);
}
void func1()
{
Lock(0);
PSW[300] = 66;
UnLock(0);
}
Добавление пользовательских макросов на вкладку Встроенные функции
У пользователя отсутствует возможность подключения дополнительных библиотек, написанных на языке Си. Вместо этого можно добавить на вкладку Встроенные функции пользовательские макросы. Для этого следует в директории установки конфигуратора:
открыть файл \Tool\Macro\sys\macro_def.h;
в конец файла с помощью директивы #define добавить новый макрос;
в папке \Tool\Macro\Definition добавить .html файл, имя которого должно совпадать с именем макроса. В качестве примера структуры файла смотрите уже существующие файлы данной директории.
Синхронное и асинхронное выполнение макросов
При настройке в графическом элементе действия Вызов макроса следует указать режим исполнения: синхронный или асинхронный. Эта настройка определяет, блокирует ли выполнение макроса работу остальных макросов проекта: синхронный — блокирует, асинхронный — не блокирует.

Рассмотрим пример. Пусть в проекте создано два макроса:
Пример
/**************************************************
Name : Func0.c
**************************************************/
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
PSW[0] = 1;
Delay(5000); // 5000 мс = 5 секунд
}
Пример
/**************************************************
Name : Func1.c
**************************************************/
#include "macro.h"
#define __SYS_DEFINE__
__MAIN__()
{
PSW[1] = 2;
}
Значения регистров PSW0 и PSW1 привязаны к цифровым дисплеям.
Создана функциональная область с режимом запуска бит управления (PSB0), в которой настроен вызов макросов Func0 и Func1. На экране также размещен переключатель, устанавливающий бит PSB0 в значение TRUE и таким образом запускающий исполнение обоих макросов.
Если для макроса Func0 настроен режим исполнения Синхронный, то сразу после нажатия на переключатель в регистр PSW0 будет записано значение 1, а спустя 5 секунд в регистр PSW1 будет записано значение 2. То есть фактически макрос Func1 будет запущен на исполнение только после завершения работы макроса Func0, в котором с помощью функции Delay организована блокирующая задержка.
Если для макроса Func0 настроен режим исполнения Асинхронный, то сразу после нажатия на переключатель в регистр PSW0 будет записано значение 1, а в регистр PSW1 – значение 2. То есть фактически макрос Func1 будет запущен на исполнение одновременно с Func0.
При этом режим исполнения макроса Func1 не повлияет на описанное выше поведение; режим исполнения определяет, будет ли настраиваемый макрос блокировать исполнение других макросов проекта.
Особенности работы с файлами
При работе с файлами следует учитывать следующие особенности:
для работы с файлами используются функции заголовочного файла stdio.h;
для работы с директориями используются функции заголовочного файла unistd.h (mkdir, rmdir);
директория монтирования USB-накопителя: /mnt/udisk;
требования к USB-накопителю: файловая система FAT32, объём – не более 32 Гб;
перед работой с USB-накопителем следует проверить факт его монтирования с помощью системного бита SPSB15. Попытка доступа к непримонтированному накопителю может привести к исключению в приложении панели;
для размонтирования USB-накопителя перед его извлечением следует установить системный бит SPSB201. Извлечение накопителя «на горячую» (без размонтирования) может привести к повреждению файлов или выходу из строя самого накопителя;
в режиме эмуляции можно работать с файлами на ПК (при условии, что эти файлы доступны для пользователя, от имени которого запускается конфигуратор). В этом случае пути к файлам должны экранироваться: C:\\test_folder\\test_file.txt
.В качестве рабочей директории используется директория установки конфигуратора;
закрытие файла (fclose) не гарантирует, что сразу после этого буферизированные данные будут записаны в файл. Используйте функцию fflush для принудительной записи. При работе с директориями для аналогичной цели можно использовать функцию system("sync") из заголовочного файла stdlib.h.