Типы данных в ардуино ide
Сначала приводит A и B , к указанному типу, а потом вычисляет результат A+B .
Приводит указатель A к указанному типу указателя.
Спецификаторы памяти:
(указываются перед типом)
Объявление переменной в виде константы, её можно читать, но нельзя менять, т.к. она хранится в области flash памяти.
Объявление переменной, значение которой может быть изменено без явного использования оператора присвоения =. Используются для работы с прерываниями.
Объявление локальной переменной, значение которой не теряется, между вызовами функции. Если переменная объявлена глобально (вне функций), то её нельзя подключить в другом файле.
Объявление глобальной переменной, которая определена во внешнем файле.
Объявление локальной переменной, значение которой требуется хранить в регистрах процессора, а не в ОЗУ, для обеспечения ускоренного доступа.
Значения некоторых констант:
Ложь, используются вместо 0
Истина, используется вместо 1
Низкий уровень
Высокий уровень
Конфигурация вывода как вход
Конфигурация вывода как выход
Конфигурация вывода как вход с подтяжкой
Передача младшим битом вперёд
Передача старшим битом вперёд
Тактовая частота Arduino в Гц
Число Пи
Половина числа Пи
Два числа Пи
Число Эйлера
Префиксы:
Запись числа в 2ой системе ( 0b 10101)
Запись числа в 2ой системе ( B 10101)
Запись числа в 8ой системе ( 0 12345)
Запись числа в 16ой системе ( 0x 1234A)
Модификаторы:
Число типа long (12345 L )
Число типа long lond (12345 LL )
Число беззнакового типа (12345 U )
Комбинация модификаторов (12345 UL )
Показатель экспоненты (3 E -5 = 3•10-5)
Переменные, массивы, объекты, указатели, ссылки, . :
Это указание имени и типа переменной.
int A; // объявление переменной А
Это выделение памяти под переменную.
A =1; // определение ранее объявленной A
Действуют постоянно, в любом месте кода.
Создаются внутри функций, циклов и т.д.
удаляются из памяти при выходе из них.
Указывается в одинарных кавычках.
char A=' Z '; // присвоение символа «Z»
Указывается в двойных кавычках.
String A ; // присвоение строки «XY»
Это переменная с указанием класса, вместо типа, через объект можно обращаться к методам класса
Ссылка, это альтернативное имя переменной, она возвращает значение переменной, на которую ссылается.
int A=5; // создана переменная A = 5
int & C=A; // создана ссылка C на переменную A
A++; C++; // в результате A=7 и C=7
// Ссылку нельзя переопределить: &C=Z;
Указатель, это переменная, значением которой является адрес.
int * Y1=&A; // указателю Y1, передан адрес переменной A
int ( * Y2)(int)=F; // указателю Y2, передан адрес функции F
B=Y1; // получаем адрес переменной A из указателя Y1
B= * Y1; // получаем значение A разыменовывая указатель
// Указатель можно переопределять: Y1=&Z;
Создание альтернативного имени для типа
typedef bool dbl; // создаём свой тип «dbl», как тип bool
dbl A=1; // создаём переменную A типа bool
Это переменная состоящая из нескольких однотипных элементов, доступ к значениям которых осуществляется по их индексу.
int A[5]; // объявлен массив A из 5 элементов типа int
int A[2]=; // объявлен и определён массив A из 2 эл-тов
char A[ ]="Hi"; // создана строка A, как массив символов
Начнём с типов памяти микроконтроллера, их целых три:
В ближайшее время нас будет интересовать только SRAM память, в которой хранятся переменные, именно о них дальше и пойдёт речь.
Двоичная система
И так далее. Помимо закономерности увеличения разрядов и чисел есть ещё одна: приглядитесь к числам в двоичной системе со всеми нулями справа от единицы:
10 | 2 |
100 | 4 |
1000 | 8 |
10000 | 16 |
Именно, степень двойки! Именно на степенях двойки в цифровом мире завязано очень много. Чтобы получить количество десятичных чисел, которые могут быть закодированы заданным количеством бит, нужно возвести 2 в степень количества бит. Смотрим на таблицу выше и продолжаем:
- 1 кБ = 1024 Б
- 1 МБ = 1024 кБ
- 1 ГБ = 1024 МБ
- И так далее
Двоичная система является родной для микроконтроллера, и для работы с отдельными битами существует целый ряд инструментов, о них мы поговорим в уроке о битовых операциях из раздела продвинутых уроков.
Другие системы исчисления
Базис | Префикс | Пример | Особенности |
2 (двоичная) | B или 0b (ноль бэ) | B1101001 | цифры 0 и 1 |
8 (восьмеричная) | 0 (ноль) | 0175 | цифры 0 – 7 |
10 (десятичная) | нет | 100500 | цифры 0 – 9 |
16 (шестнадцатеричная) | 0x (ноль икс) | 0xFF21A | цифры 0-9, буквы A-F |
Переменные
- 1 байт = 8 бит = 256
- 2 байта = 16 бит = 65 536
- 4 байта = 32 бита = 4 294 967 296
Да, больше четырёх байт в ардуино (точнее в МК от AVR) уже не влезет, при использовании обычных типов данных. Для работы с разными диапазонами значений используются разные типы данных (переменных). По сути можно использовать 4 байта для хранения чего угодно, но это не оптимально. Это как знать, что вам нужно будет унести максимум 200 мл воды (меньше 1 байта), но вы всё равно берёте 19 литровую бутыль (2 байта). Или железнодорожную цистерну на 120 тонн (4 байта). Если хотите писать красивый и оптимальный код, используйте соответствующие типы данных. Кстати, вот они:
Типы данных
*Не встречал упоминания об этом в официальных источниках, но Ардуино (точнее компилятор) также поддерживает 64 битные числа, соответственно тип данных int64_t и uint64_t Максимальный размер всех типов данных хранится в константах, и его можно использовать в коде по надобности:
Помимо целочисленных типов ( byte , int , long ) есть более интересные:
Есть ещё несколько нестандартных типов, которые иногда встречаются в чужом коде:
Объявление и инициализация переменных
- тип_данных имя; // объявление
- тип_данных имя = значение; // объявление и инициализация
- Также можно объявить и инициализировать несколько переменных через запятую: int a = 0, b, с = 10;
Преобразование типов
И всё! (int)val будет обрабатываться как int , а не как byte .
Преобразование _cast (Pro)
Иногда можно встретить преобразование типов через оператор cast. Отличную статью можно глянуть на Хабре, а я кратко опишу 4 основных каста:
Как пользоваться: на примере предыдущего примера
Константы
Область видимости
Переменные, константы и другие типы данных (структуры и перечисления) имеют такое важное понятие, как область видимости. Она бывает
- Глобальной
- Локальной
- Формальной (параметр)
Глобальная
Глобальная переменная объявляется вне функций и доступна для чтения и записи в любом месте программы, в любой её функции.
Локальная
Локальная переменная живёт внутри функции или внутри любого блока кода, заключённого в < фигурные скобки >, доступна для чтения и записи только внутри него. При попытке обратиться к локальной переменной из другой функции (за пределами её < блока >) вы получите ошибку, потому что локальная переменная создаётся заново при выполнении содержащего её блока кода (или функции) и удаляется из памяти при завершении выполнения этого блока (или функции):
Важный момент: если имя локальной переменной совпадает с глобальной, то приоритет обращения по имени в функции отдаётся локальной переменной:
Формальная (параметр)
Формальная переменная, она же параметр, передаваемый в функцию, ведёт себя как обыкновенная локальная переменная, но появляется при немного других условиях: при вызове функции. Эту переменную можно читать и менять внутри её функции. Также читайте отдельный урок про функции.
Структуры (Pro)
Ярлык будет являться новым типом данных, и, используя этот ярлык, можно объявлять уже непосредственно саму структуру:
Также есть вариант объявления структуры без создания ярлыка, т.е. создаём структуру, не объявляя её как тип данных со своим именем.
- Обращение к члену структуры производится вот по такой схеме: имя_структуры.имя_переменной и позволяет менять или читать значение.
- Если две структуры имеют одинаковую структуру (объявлены одним ярлыком) то можно одну структуру просто приравнять к другой, все переменные запишутся соответственно на свои места.
- Ещё одним удобным вариантом является присваивание значения вот таким образом: имя_структуры = (ярлык) ;
Рассмотрим большой пример, где показано всё вышеописанное
Для чего нужны структуры? В большинстве примеров в интернете приводится использование структур для хранения адресных данных, т.е. создания базы данных адресов: имя, фамилия, номер телефона, итд. На моей практике структуры оказались очень удобными для создания меню с большим количеством режимов и настроек (скажем несколько каналов, у каждого одинаковый набор настроек). Структуры очень удобно передавать и получать например при помощи RF24 модулей, т.е. вместо массивов удобнее использовать структуры и передавать одной строчкой сразу кучу типов данных. Также структуру можно целиком записать в eeprom одной строчкой (командой put) и одной строчкой оттуда же её считать, не заморачиваясь с номерами ячеек, как это происходит при записи данных вручную.Размер элемента структуры
Структуры позволяют делать одну очень интересную вещь для оптимизации памяти: указывать максимальный вес элемента в битах. Таким образом можно делать даже однобитные флаги (обычный bool / boolean занимает в памяти 8 бит). Делается это при помощи оператора двоеточие :
Вложенные структуры
Перечисления (Pro)
Таким образом мы объявили ярлык. Теперь, используя этот ярлык, можно объявить само перечисление:
Также как и у структур, можно объявить перечисление без создания ярлыка (зачем нам лишняя строчка?):
Созданное таким образом перечисление является переменной, которая может принимать указанные для неё имена, также с этими именами её можно сравнивать. Теперь самое главное: имена для программы являются числами, начиная с 0 и далее по порядку увеличиваясь на 1. В абстрактном примере выше имя1 равно 0, имя2 равно 1, имя3 равно 2, и так далее. Помимо указанных имён, перечислению можно приравнять и число напрямую, но как бы зачем. Рассмотрим пример!
Также порядок автонумерации перечислений можно изменить, задав начальное значение. От него всё будет и дальше изменяться на единицу:Таким образом SET1 имеет значение 1, SET2 будет 2 и так далее по порядку.
Пользовательские типы (Pro)
Создаёт тип данных под названием color , который будет абсолютно идентичен типу byte (то есть принимать 0-255). Теперь с этим типом можно создавать переменные:
Почему не нужно использовать typedef для struct и enum В языке Си (без ++) при создании структуры/перечисления по ярлыку нужно писать слово struct/enum перед ярлыком, иначе будет ошибка. Либо саму структуру нужно объявить как typedefВ С++ (и на Ардуино) этого делать не нужно! Наоборот, typedef в этом применении может приводить к ошибкам. Например:
Пространство имён (Pro)
Чтобы использовать содержимое из пространства имён, нужно обратиться через его название и оператор разрешения области видимости ::
Более подробный пример:
Также есть оператор using , позволяющий не использовать каждый раз обращение к пространству имён. Например, в отдельном файле у нас есть пространство имён с различными функциями. Чтобы в основном файле программы каждый раз не писать ярлык пространства имён с :: , можно написатьИ ниже по коду можно будет пользоваться содержимым пространства имён без обращения через имя::
Спецификаторы (Pro)
Помимо возможности сделать переменную константой при помощи спецификатора const у нас есть ещё несколько интересных инструментов по работе с переменной.
static
Статическая локальная:
Статичная глобальная Статичная глобальная переменная становится доступной только в данном файле, спецификатор static позволяет спрятать её от воздействий из других файлов программы.extern
volatile
Видео
Логический тип, может принимать только 2 значения - true (правда) и false (ложь). В памяти занимает 1 байт.
Тип позволяет хранить 1 алфавитно-цифровой символ и занимае 1 байт. Для записи символа используются одинарные кавычки.
В памяти хранится число, соответствующее записанному символу в таблице ASCII, поэтому над переменной можно производить арифметические действия.
Переменная это типа - знаковая, диапазон допустимых значений - от -128 до 127.
Тип для хранения однобайтового целого беззнакового числа. Соответственно диапазон значений от 0 до 255.
Пожалуй самый частоиспользуемый тип для хранения целых чисел со знаком - integer (целое число). Занимает 2 байта и может хранить цисла от -32768 до 32767.
На платформе Arduino также присутствует тип , который ничем не отличается от типа int .
unsigned int
Беззнаковое целое число, занимает так же, как и int , 2 байта. Диапазон значений - от 0 до 65535.
Тип long служит для хранение больших целых знаковых чисел. Диапазон его значений от -2147483648 до 2147483647, а занимает в памяти он 4 байта.
unsigned long
Беззнаковое целое число расширенного диапазона может хранить значения от 0 до 4294967295 и занимает 4 байта.
float
Тип данных чисел с плавающей точкой (или с плавающей запятой). Используется для нецелочисленных расчетов. В Arduino используется например для считывания значений с аналоговых пинов. Диапазон значений от -3.4028235E+38 до 3.4028235E+38,а занимает такая переменная 4 байта.
Точность - 6-7 знаков после запятой.
double
Тип ничем не отличается от типа float и введен для обратной совместимости. На многих других платформах он имеет большую чем у float точность.
string
Тип для хранение текстовых строк. Является массивом символов типа char и символа конца строки '\0' в последнем его элементе. Не путать с классами string и String .
Строка может быть создана и инициализирована несколькими способами:
Если забыть указать символ конца строки при посимвольной инициализации или не отвести под него место, то функции работы со строками будут работать некорректно.
массив
Массив - это набор элементов одного типа с доступом к каждому элементу по индексу.
Нумерация индексов массива начинается с 0.
Ключевое слово void используется при объявлении функции, которая не возвращает значения.
Преобразование типов
Преобразование типов - это приведение значение переменной к другому типа, отличному от типа данной переменной.
Приведение типов делится на явное и неявное.
Пример явного приведения типа:
Пример неявного приведения типа:
Условная конструкция if принимает на вход значение типа boolean , поэтому целочисленное значение переменной a будет приведено к типа boolean .
Каждая компьютерная система тесно связана с таким понятием как «тип данных». Это связано со спецификой информации, хранящейся в памяти контроллера.
Arduino, построенная на базе микроконтроллеров семейства ATmega, использует основные типы данных. Знание типов данных крайне важно для правильного программирования. Ниже перечислены типы данных, используемые в Arduino IDE:
Начинающие программисты, пишущие программное обеспечение для ПК, не могут полностью осознать, как важно подобрать правильный тип данных для сохранения информации. В основном это связано с тем, что использование, например, 4 байтов вместо 1 байта практически не имеет значения в системах, оснащенных несколькими Гб оперативной памяти.
В случае с Arduino быстро выясняется, что необходимо экономить оперативную память (RAM). При написании программы, объем доступной оперативной памяти быстро уменьшается, и вы должны отслеживать, насколько она эффективна используется.
Преобразования между типами данных
Часто случается, что необходимо поменять один тип данных на другой. Например, заменить символ на число или число на символ. В Arduino IDE мы имеем несколько функций, которые позволяют производить преобразование между типами данных. В следующей таблице приведены функции преобразования данных.
Диапазон переменных
Arduino IDE базируется на языке C/C++. Из него же позаимствован способ обработки переменных. При написании программы можно использовать как глобальные переменные, так и локальные переменные.
В основном это является преимуществом, поскольку из любой функции мы получаем доступ к переменной, без необходимости передачи информации в качестве параметра вызова.
Но в некоторых случаях, к сожалению, это является недостатком. Используя готовые функции, может оказаться так, что это же имя переменной одновременно используется и для других целей и из-за этого программа может работать неправильно.
Второй тип переменной – локальная переменная. Мы определяем ее в теле функции, и она доступна только на уровне этой функции. Локальная переменная инициализируется при вызове функции и уничтожается после завершения ее работы. Использование локальных переменных снижает спрос на оперативную память, но в то же время затрудняет передачу информации между различными функциями программы.
Настоятельно рекомендуется использовать локальные переменные и функции с параметрами вызова. Глобальные переменные следует использовать в тех ситуациях, когда одна и та же переменная используется в нескольких функциях.
Следующий код иллюстрирует место и способ декларации глобальных и локальных переменных:
Как показано в приведенном выше примере, переменные, объявленные внутри функции, являются локальными переменными, а переменные, объявленные вне тела функций, являются глобальными переменными.
Давайте рассмотрим другой код.
Следующий код можно скомпилировать и запустить, а результат работы программы наблюдать с помощью монитора последовательного порта, доступного в Arduino IDE (функции Serial.begin и Serial.print предназначены для отправки данных через последовательный порт)
Изменим немного код:
Директивы const, static, volatile
Директивы компилятора позволяют осуществить специальную обработку некоторых элементов программного кода и заменить их при компиляции или выполнении кода.
Альтернативой const является define. Создатели Arduino IDE рекомендуют использовать именно const. Ниже приведены примеры для пользовательских констант:
Как видно const указывает на тип данных, а define присваивает значение без указания типа. Отсутствие точки с запятой в конце строки с define не упущение, а правильный синтаксис, заимствованный из C/C++.
Хорошей практикой является использование define для присвоения словесных обозначений для входов/выходов, а для остальных констант рекомендуется использовать директиву const.
Директива static позволяет определить способ инициализации локальных переменных. Обычно локальная переменная инициализируется во время вызова функции и уничтожается при ее завершении. Добавление директивы static приведет к тому, что с точки зрения охвата переменная останется по-прежнему локальной, но не будет уничтожена после завершения выполнения функции.
При следующем вызове функции, переменная уже не будет повторно инициализирована, а будет иметь значение, полученное при предыдущем завершении работы функции. Лучше всего проверить это на примере:
Последней важной директивой является volatile. Ее следует использовать в случае, когда значение переменной может быть изменено с помощью функции обработчика прерывания.
Понимание целесообразности использования volatile не просто. Вы должны знать, как обрабатываются прерывания. В целом обслуживание прерываний заключается в остановке выполнения основного кода программы с целью выполнения определенных инструкций с последующим возвращением к основному коду.
Если прерывания изменит значение переменной, которая используется в программе, то может оказаться так, что это изменение не будет обновлено. В этом случае на помощь приходит директива volatile, которая принудительно обновляет значение переменной после выполнения прерывания в основной программе.
Проще говоря, необходимо запомнить, что переменные, используемые в прерываниях, должны быть объявлены директивой volatile.
Читайте также: