Поутру учим Си, переход на ST много времени не займет..
В принципе, программа на языке Си может быть написана в любой форме, даже в виде однострочной программы, поскольку компилятор требует только соблюдения правил синтаксиса. Однако, для ясности и простоты, все же рекомендуется обратить внимание на стиль программного кода. Типичная структура программы на языке Си:
/* Включение заголовочных файлов */
#include <avr/io.h>.
#include <stdio.h>.
/* Объявление макросов */
#define PI 3.141
/* Типы данных */
typedef struct {
int a, b;
} element;
/* Глобальные переменные */
element e;
/* Функции */
int main(void) {
// Локальные переменные
int x;
// Программа
printf("Hello world!\n");
}
В программе может быть написан текст, который не компилируется, и который может быть полезен программисту при объяснениях или примечаниях. Комментарии также могут быть использованы для временного удаления разделов программы из выполнения. Пример двух типов комментариев:
// Однострочный комментарий.
// Комментарием считается комментарий между двумя строками.
// текст, следующий за двумя косыми чертами
/*
Многострочный комментарий
Начало и конец комментария определяются
косыми чертами и звездочками
*/
Базовые типы данных языка Си:
Тип | Минимум | Максимум | Бит | Байт |
---|---|---|---|---|
(знаковый) char | -128 | 127 | 8 | 1 |
беззнаковый char | 0 | 255 | 8 | 1 |
(знаковый) short | -32768 | 32767 | 16 | 2 |
беззнаковый короткий | 0 | 65535 | 16 | 2 |
(знаковый) long | -2147483648 | 2147483647 | 32 | 4 |
беззнаковая длинная | 0 | 4294967295 | 32 | 4 |
float | -3.438 | 3.438 | 32 | 4 |
double | -1.7308 | 1.7308 | 64 | 8 |
Вам не нужно использовать слово "signed" в скобках, потому что по умолчанию типы данных являются биполярными.
В языке Си нет специального текстового типа данных. Вместо этого используются массивы типа char (о которых подробнее позже) и "алфавит" ASCII, где каждая буква и символ имеют свой порядковый номер.
Программа может использовать особый тип хранения данных - переменные. Имена переменных могут содержать буквы латинского алфавита, цифры и знаки подчеркивания. Имя не должно начинаться с цифры. При объявлении переменной перед ней должен быть написан тип данных. Для присвоения значения переменной используется знак равенства (=). Пример использования переменных:
char c; // объявление переменной c типа char
c = 65; // Присвоение значения переменной c = 65.
c = 'A'; // A является также значением 65
// в наборе символов ASCII.
// Объявление и инициализация переменной i20 типа int.
int i20 = 55;
// Объявление нескольких переменных типа unsigned short
unsigned short x, y, test_variable;
Константы объявляются так же, как и переменные, но перед ними ставится ключевое слово const. Значение констант не может быть изменено во время работы программы. Пример использования:
const int x_factor = 100; // присвоение константы типа int
Структуры могут быть построены из базовых типов данных с помощью ключевого слова struct. Структура подобна комбинированному типу данных. Тип объявляется с помощью ключевого слова typedef. Пример структуры для создания и использования типа данных:
// Объявление нового типа данных point.
typedef struct {
// Координаты x и y и код цвета
int x, y;
char color;
} point;
// Объявление переменной point
point p;
// Устанавливаем координаты точки
p.x = 3;
p.y = 14;
Массивы (последовательности) могут быть построены из типов данных. Массив также может быть многомерным (таблица, куб и т.д.). Пример использования одномерного и двумерного массива:
// Объявление одномерного и двумерного массивов.
char text[3];
int table[10][10];
// Формирование текста из массива символов
text[0] = 'H'; // заглавная буква
text[1] = 'i'; // прописная буква
text[2] = 0; // терминатор текста (нулевой байт)
// Изменение элемента таблицы
table[4][3] = 1;
Переменные, константы и функции, возвращающие значение, могут быть использованы для построения утверждений (операций). Значения выражений можно присваивать переменным, использовать в качестве параметров функций и в различных условных операторах.
В языке C поддерживаются следующие арифметические операции: сложение (+
), вычитание (-
), умножение (*
), деление (/
) и взятие модуля (%
). Примеры использования арифметических операций:
int x, y;
// взятие остатка потом умножение на 3.
// x принимает значение 9
x = (13 % 5) * 3;
// Оператор суммирования-присвоения,
// x принимает значение 14
x += 5;
// Экспресс-метод вычитания, x принимает значение 13
x--;
Логические операции - это отрицание (!
), логическое умножение (&&
) и логическое сложение (||
). Пример использования операций:
bool a, b, c;
// Инициализация
a = true;
b = false;
// Возвраты
// c должно быть ложным, из-за отрицания истины
c = !a;
// Логическое умножение
// c должно быть ложным,
// потому что один из операндов ложен
c = a && b;
// Логическое сложение
// c должно быть истинным,
// потому что один из операндов истинный
c = a || b;
ВНИМАНИЕ!
typedef unsigned char bool;
enum state { false, true };
Сравнение значений чисел дает логические значения. Термины сравнения: эквивалентность (==
), xor (!=
), больше чем (>
), больше или равно (>=
), меньше чем (<
) и меньше чем или равно (<=
). Пример использования:
Boolean b = (5 > 4);
// Операция "Не равно".
// Результатом операции является ложь
b = (4 != 4);
// Арифметическая операция, операция сравнения
// и логическая операция. b ложно, потому что
// первый оператор логического умножения ложен
b = (x + 4 > 15) && (y < 4);
Битовые операции предназначены для работы с данными в двоичной системе счисления. Они могут применяться ко всем данным целочисленного типа. Битовые операции похожи на логические операции, но отличаются тем, что операция выполняется над каждым битом отдельно, а не над всем числом. В языке Си битовые операции - это инверсия (~
), конъюнкция (&
), дизъюнкция (|
), исключающее или (^
), сдвиг влево (<<
) и сдвиг вправо (>>
).
// Указывает на объявление 8-битной переменной типа char.
// Значение переменной равно 5 в десятичной системе
// счисления, 101 в двоичной.
unsigned char c = 5;
// Переменная c дизъюнкция на 2 (двоичный 010)
// c устанавливается в 7 (двоичное 111)
c = c | 2;
// Сдвиг битов влево на 2
// c устанавливается в 28 (двоичное 11100)
c = c << 2;
Битовые операции незаменимы при работе с регистрами микроконтроллера.
Функция -- это раздел программы, который может быть вызван по своему имени для выполнения. Функция может иметь параметры и возвращать одно значение. Если функция не возвращает значение, ее тип -- void. Если функция не имеет параметров, то вместо объявления параметров при использовании компилятора более старого языка Си необходимо записать void. Пример функции сложения и функции без возвращаемого значения:
// Функция с двумя параметрами типа int,
// которая возвращает значение типа int.
int sum(int a, int b) {
// Сложение двух чисел и возвращение суммы
return a + b;
}
// Функция без параметров и возвращаемого значения
void power_off(void) {
}
Чтобы использовать функцию, ее необходимо вызвать. Перед вызовом функция должна быть объявлена в коде программы. Пример вызова функции сложения.
int x;
int y = 3;
// Вызов функции сложения с переменной и константой
// в качестве параметров.
x = sum(y, 5);
// Вызов функции выхода, параметры не заданы
power_off();
Выполнение программы на языке Си начинается с функции main, что делает ее обязательной функцией.
Условный оператор позволяет выполнить или не выполнить оператор или сегмент программы, следующий за условием, в зависимости от истинности утверждения в скобках. Ключевым словом в условном утверждении является if. Пример использования:
// Утверждение истинно, и оператор выполняется,
// потому что 2 + 1 больше 2
if ((2 + 1) > 2) x = 5;
// Если x = 5, а y = 3, то будет выполнено
// следующее утверждение.
if ((x == 5) && (y == 3)) {
// произвольное действие
y = 4;
my_function();
}
Условный оператор может быть длиннее, а также содержать оператор или фрагмент программы, который будет выполнен в случае, если оператор не будет истинным. Для этого после выражения if необходимо использовать ключевое слово else.
// Является ли x 5?
if (x == 5) {
// произвольное действие
z = 3;
}
// Если x не 5, то x 6 ?
else if (x == 6)
{
// произвольное действие
q = 3;
}
// Если x не было ни 5, ни 6....
else {
// произвольное действие
y = 0;
}
Если вам нужно сравнить утверждение с несколькими разными значениями, целесообразно использовать ключевое слово switch. Пример использования:
int y;
// Условный оператор для сравнения y
switch (y) {
// y равно 1?
case 1:
// произвольное действие
function1();
break;
// y равно 2 ?
case 2:
// произвольное действие
function2();
break;
// Все остальные случаи
default:
// произвольное действие
functionX();
// оператор break не нужен,
// поскольку сравнение все равно заканчивается
}
Циклы могут быть использованы для выполнения раздела программы несколько раз.
оператор while выполняется до тех пор, пока утверждение в круглых скобках не станет истинным.
int x = 0;
// Цикл продолжается до тех пор, пока x не станет меньше 5.
while (x < 5) {
// увеличиваем x на единицу
x++;
}
Цикл с ключевым словом for похож на цикл while, но с добавлением оператора, который будет выполняться перед циклом, и оператора, который будет выполняться во время каждого цикла, указанного в круглых скобках.
Пример:
int i, x = 0;
// Первоначально i устанавливается равным нулю.
// Цикл будет выполняться до тех пор, пока.
// i будет меньше 5. В конце каждого цикла
// i увеличивается на 1.
for (i = 0; i < 5; i++) {
// увеличиваем x на 2
x += 2;
}
// В этом случае x должно быть равно 10
Циклы while и for могут быть в исключительных случаях прерваны ключевым словом break. Ключевое слово continue может быть использовано для запуска следующего цикла без выполнения следующего кода.
int x = 0, y = 0;
// Бесконечный цикл, так как 1 - логическая истина.
while (1) {
// Выход из цикла, когда x достигнет 100
if (x >= 100) break;
// Увеличиваем x, чтобы цикл никогда не закончился.
x++;
// Если x равно 10 или меньше, цикл начинается.
// следующий цикл
if (x <= 10) continue;
// увеличиваем y
y++;
}
// в этот момент y равен 90
Функции микроконтроллера по обработке текста необходимы в первую очередь для отображения текста на ЖК-экране и отправки/получения его через коммуникационные интерфейсы.
sprintf
Функция sprintf работает аналогично функции printf, распространенной в языке C. Разница в том, что результат работы функции выводится в буфер (массив символов), а не в стандартный вывод (который на микроконтроллерах обычно представляет собой последовательный интерфейс, на ПК - окно консоли). Функция имеет переменное количество аргументов, которые форматируются в соответствии с форматом.
Пример 1:
#include <stdio.h>.
char buffer[20];
int a = 1, b = 2;
int len = sprintf(
buffer, "%d плюс %d равно %d", a, b, a + b
);
// Результатом будет текстовая строка "1 плюс 2 равно 3".
Например, функция sprintf записывает в буфер символьного массива текст, состоящий из трех аргументов, отформатированный в соответствии с текстовой строкой format. В текстовой строке формата маркер (спецификатор) обозначает целочисленный аргумент %d, который должен быть заменен в тексте. В конце сгенерированного текста автоматически добавляется нулевой байт, чтобы отметить конец текстовой строки. Пользователь должен убедиться, что длина генерируемого текста и нулевого байта не превышает длины символьного массива. Функция sprintf упрощает построение фраз и предложений из различных типов переменных. Функция возвращает длину текста, хранящегося в символьном массиве (длина не включает нулевой байт). В случае ошибки возвращается отрицательное число.
Пример 2:
#include <stdio.h>.
char x[20];
sprintf(x, "%s is %d years old", "Juku", 10);
// Тот же результат можно определить как константу:
char x[] = "Juku is 10 years old";
В текстовой строке формата второго примера маркер %s заменяется первым аргументом текстового типа, а %d - вторым аргументом целочисленного типа. Маркеров столько, сколько аргументов, и их типы должны совпадать. Аргументы также должны быть представлены в том же порядке, что и маркеры. Для разных типов аргументов существуют соответствующие маркеры формата:
Маркер | Описание | Пример |
---|---|---|
%c | Маркер символов | 'a' |
%i или %d | Целое число | 123 |
%f | Дробь | 3.14f |
%s | Текст | "пример" |
%X | Шестнадцатеричное число | 0x3F |
В заголовочных файлах stdio.h, stdlib.h и string.h стандартной библиотеки есть целая куча других функций для выполнения различных текстовых операций. Например, их можно использовать для преобразования текста в числа, для объединения и сравнения текстов, для поиска символов в тексте и т.д.
Генерировать случайные числа на контроллере AVR не очень просто. Во-первых, генератор случайных чисел должен быть посеян числом, из которого генерируется последовательность произвольных чисел. Одно и то же число всегда генерирует одну и ту же последовательность, поэтому для достижения большей случайности полезно посеять число, которое не всегда одинаково -- например, использовать время суток или значение с отключенного входа АЦП.
Например, для генерации случайного числа от 1 до 16 (включительно).
#include <stdlib.h>.
srand(100); // случайное начальное число
int x = 1 + (rand() % 16);
Типы данных.
Целочисленные типы:
SINT (char)
USINT (unsigned char)
INT (short int)
UINT (unsigned int)
DINT (long)
UDINT (unsigned long)
LINT (64 бит целое)
ULINT (64 бит целое без знака).
Действительные типы:
REAL (float)
LREAL (double).
Специальные типы BYTE
, WORD
, DWORD
, LWORD
представляют собой битовые строки длиной 8, 16, 32 и 64 бит соответственно. Битовых полей в ST нет. К битовым строкам можно непосредственно обращаться побитно. Например:
a.3 := 1; (* Установить бит 3 переменной a *)
К битовым строкам можно применять операции, доступные для целых без знака.
Логический тип BOOL
. Может иметь значение TRUE
или FALSE
. Физически переменная типа BOOL может соответствовать одному биту. Обычно так и есть, если речь идет о дискретном входе либо выходе ПЛК или прямоадресуемой переменной (см. ниже). В иных случаях CoDeSys выделяет один байт. Это вызвано тем, что большинство микропроцессоров не умеют непосредственно адресоваться к отдельным битам памяти.
Строки STRING
. Являются именно строкой, а не массивом. Поэтому нет возможности обращаться к отдельным символам. Зато можно сравнивать и копировать строки стандартными операторами. Например так: strA := strB
. В IEC есть стандартный набор функций для работы со строками. Внутренний формат строк не стандартизован.
Специальные типы в стандарте IEC определены для длительности (TIME), времени суток (TOD), календарной даты (DATE) и временного штампа (DT). Работа со временем и интервалами встречается в ПЛК программах повсеместно.
Применение структур (STRUCT) не отличается от C. Описание структуры должно предшествовать объявлению переменной данного типа. Допускаются вложенные структуры и массивы.
Массивы (ARRAY) строятся из элементов любых типов, включая структуры и массивы. Из особенностей нужно отметить возможность задания повтора значений при инициализации.
Например:
bX ARRAY[0..20] OF BOOL := TRUE, 10(FALSE), 9(TRUE);
Значение FALSE повторено здесь 10 раз и значение TRUE, соответственно 9 раз. Массивы (и структуры) можно копировать с помощью обычной операции присваивания: bY := bX; Перечисления аналогичны перечислениям C. Пример:
TYPE TEMPO:
(Adagio := 1, Andante := 2);
END_TYPE
На базе целых можно построить типы, имеющие ограниченный диапазон значений. Например:
TYPE DAC10:
INT (0..16#3FF);
END_TYPE
Для любого типа можно создать псевдоним (typedef в C). Например:
TYPE DEGR : UINT
END_TYPE.
Константы.
В IEC используются типизированные константы. Например:
2#10001000 (байт в двоичном формате)
INT#40 (целое)
t#10h14m5s (время дня)
D#2006-01-31 (дата)
Символьные константы можно объявлять локально или глобально в специальной секции раздела объявлений: VAR_CONSTANT.
Идентификаторы.
__X
).Инициализация переменных.
По умолчанию, все переменные инициализируются нулем. Иное значение переменной можно указать явно при ее объявлении. Например:
str1: STRING := 'Hello world'.
Переменные можно объявить как RETAIN или PERSISTENT. Это означает требование их размещения в энергонезависимой памяти контроллера (если она есть). Их инициализация производится только при первом запуске программы или по специальной команде.
Прямая адресация.
Помимо общей памяти данных в контроллере предусмотрены 3 специальные области памяти. Это область входов, выходов и прямо-адресуемая область. Эти области служат для связи системы исполнения контроллера и пользовательской программы. В простейших ПЛК распределение памяти в области входов/выходов фиксировано изготовителем. Мы можем непосредственно обращаться к нужному адресу из программы (например, %QB5 дает 5-й байт в области выходов) или объявить переменную, расположенную в соответствующем месте.
Преобразование типов.
Неявное преобразование типов запрещено. Любое преобразование нужно делать явно с помощью специальных операторов. Запомнить их легко. Пишем исходный тип, далее _TO_
и нужный тип. Например:
iX := REAL_TO_INT(rX);
Операторы и функции.
Наиболее часто используемые операторы и функции ST приведены в таблице 1. В IEC определен целый ряд общепринятых и специализированных (для ПЛК программ) функций и функциональных блоков. В их числе таймеры, триггеры, счетчики, регуляторы и многое другое.
Операция | ST |
---|---|
Присваивание | := |
Алгебраические операции | +, -, *, / |
Замена знака | - |
Остаток от деления | MOD |
Абсолютное значение | ABS |
Сравнения | < , > , <= , >= |
Равенство, неравенство | =, <> |
И, ИЛИ, НЕ | AND, OR, NOT |
Исключающее ИЛИ | XOR |
Побитный сдвиг влево | SHL(in,n) |
вправо | SHR(in,n) |
Циклический сдвиг влево | ROL(in,n) |
вправо | ROR(in,n) |
Выбор их двух значений | MAX(in0, in1) |
наибольшего, наименьшего | MIN(in0, in1) |
Бинарный выбор | SEL(g, in0, in1) |
Мультиплексор | MUX(k, in0,...,inN) |
Таблица 1. Основные операторы и функции ST.
Написать ++x;
или x += 1;
в ST, конечно, нельзя. Единственный вариант -- это x := x + 1;
.
sizeof
Оператор SIZEOF(in)
. Смысл его должен быть очевиден для C программистов.
sprintf
Такого оператора в ST нет. Преобразовать любой переменную любого типа в строку можно с помощью .._TO_STRING
.
Указатели
В IEC указателей нет. Иногда адрес переменной можно получить с помощью оператора ADR, разыменовывание дает '^'. Например:
pt : POINTER TO INT;
var_int1:INT;
var_int2:INT;
pt := ADR(var_int1);
var_int2 := pt^;
Объединений нет. При необходимости это удается обойти, разместив несколько переменных разного типа по одному адресу в прямо адресуемой памяти либо посредством указателей. Аналогично можно получить доступ к элементам строки 'совместив' ее с массивом.
Точка с запятой.
Не смотря на явное родство ST с языком Паскаль, точка с запятой используется в нем также как в языке C. То есть она служит не разделителем, а признаком конца оператора. Естественно, в одной строке может быть любое число операторов. Кроме того, точка с запятой служит пустым оператором. Текст любого программного компонента (POU) на ST должен включать хотя бы один оператор.
Программные скобки.
В ST нет привычных программных скобок ( {}
в C или begin и end в Паскале). Вместо этого каждая программная конструкция имеет собственную закрывающую программную скобку. Например: END_FUNCTION
или END_IF
. Каждому кто имел 'счастье' видеть многоэтажные лесенки завершающих скобок в C, такое решение должно быть понятно.
IF ELSE
Оператор выбора IF почти эквивалентен соответствующему оператору в C. Условием может служить переменная или выражение только логического типа. В ином случае, необходимо выполнить явное приведение к логическому типу.
IF <Condition1> THEN
<Statement1>
ELSIF <Condition2> THEN
<Statement2>
ELSE
<Statement3>
END_IF;
CASE
Аналогичен switch в C. Альтернативные ветки не имеют закрывающей программной скобки и не могут выполняться одна за другой. Соответственно оператор break
не имеет смысла и отсутствует. При указании значений констант альтернативы можно перечислить их через запятую или указать диапазон. Например:
CASE (x + 2)/3 OF
0:
y := 1;
1,2:
y := 4;
z := 5;
3..50:
y := 7
ELSE
y := 0;
END_CASE
WHILE и REPEAT
Цикл WHILE совершенно аналогичен циклу while в C. REPEAT UNTIL
соответствует do while
. Тонкое отличие состоит в том, что REPEAT выполнятся до тех пор, пока условие не примет значение TRUE.
FOR
Цикл FOR применяется для выполнения группы выражений заранее известное число раз. Цикл включает установку начального значения, конечное значение, опционально шаг (по умолчанию 1) и тело цикла. Пример:
FOR I := 1 TO 100 BY 10 DO x := x*y; END_FOR
На С этому соответствует:
for (I = 1; I <= 100; I += 10) x = x*y;
Бесконечный цикл в ST обычно записывается как WHILE TRUE DO
.
Оператору break
в циклах ST соответствует оператор EXIT
. Оператор continue
отсутствует.
main и PLC_PRG
Прекращение выполнения и возврат управления операционной системе был бы для контроллерной программы полным крахом. Поэтому любая контроллерная программа имеет цикл:
while(1)
{
ReadInputs();
PLC_PRG();
WriteOutputs();
}
В многозадачных проектах система исполнения создает независимый главный цикл для каждой задачи. Любая ваша программа выполняется в цикле, если вы не предприняли специальных мер.
Принимайте это как факт. Сложности в этом нет, устойчивые навыки приобретаются за несколько часов.
Многозадачность.
Многозадачность в CoDeSys доступна даже для ПЛК, выполненных на 8 разрядных микропроцессорах без ОС. В простейшем случае, вместо вызова единственной PLC_PRG
, система попеременно подставляет вызовы разных пользовательских программ. Конечно, с учетом их приоритетов и времен циклов выполнения. Так реализуется невытесняющая многозадачность.
Минимальное время цикла задач и список активирующих событий необходимо уточнить в документации на контроллер.
Функции
Для возврата значения функции в C используется return()
. В IEC каждая функция имеет неявно объявленную переменную для возврата значения. Ее наименование совпадает с именем функции. То есть
int GetX(int x) { ... return(x); }
на ST будет выглядеть так:
GetX := x;
RETURN;
Как и в языке C передавать параметры, при вызове функции, можно перечисляя их в скобках через запятую: GetXY(1,2)
.
Память для переменных функции выделяется в стеке.
Значения внутренних переменных не сохраняются между вызовами (но не у функциональных блоков). Объявления локальных static переменных в функциях не предусмотрено. Для функций и функциональных блоков допускается передача параметров по ссылке (VAR_IN_OUT)
.
Функции IEC могут иметь переменное число параметров. Типичный пример – это функция MUX, приведенная в таблице 1. Она может работать с переменным числом параметров, причем любого типа. Перегрузки стандартных операций нет.
Типа void нет. Любая функция должна иметь хотя бы один параметр и возвращать значение. Используйте тип BOOL, он обеспечивает минимальные издержки. Как и в С, вызовы функции можно включать в выражения.
Функциональные блоки.
В функциональный блок параметры передаются путем присваивания значений: GetX(x := 2).
При объявлении данных выделяются входные, выходные и внутренние переменные. На основе объявления функционального блока можно объявить один или несколько его экземпляров. Объявление экземпляра приводит к выделению памяти данных, совершенно аналогично созданию переменных типа структура.
Естественно код при этом не увеличивается. В отличие от функции, значения переменных экземпляра функционального блока сохраняются между его вызовами. Это означает, что к переменным экземпляра можно обращаться в любое время, вне зависимости от его вызова.
В объявлении экземпляра можно выполнять дополнительную инициализацию переменных.
При трансляции с ST в C, для каждого функционального блока приходится писать функцию инициализации.
Источники