|Тип||Занимаемый размер (байт)||Минимальное значение||Максимальное значение|
Логический тип, может принимать только 2 значения — true (правда) и false (ложь). В памяти занимает 1 байт.
Тип позволяет хранить 1 алфавитно-цифровой символ и занимае 1 байт. Для записи символа используются одинарные кавычки.
В памяти хранится число, соответствующее записанному символу в таблице ASCII, поэтому над переменной можно производить арифметические действия.
Переменная это типа — знаковая, диапазон допустимых значений — от -128 до 127.
Тип для хранения однобайтового целого беззнакового числа. Соответственно диапазон значений от 0 до 255.
Пожалуй самый частоиспользуемый тип для хранения целых чисел со знаком — integer (целое число). Занимает 2 байта и может хранить цисла от -32768 до 32767.
На платформе Arduino также присутствует тип , который ничем не отличается от типа int .
Беззнаковое целое число, занимает так же, как и int , 2 байта. Диапазон значений — от 0 до 65535.
Тип long служит для хранение больших целых знаковых чисел. Диапазон его значений от -2147483648 до 2147483647, а занимает в памяти он 4 байта.
Беззнаковое целое число расширенного диапазона может хранить значения от 0 до 4294967295 и занимает 4 байта.
Тип данных чисел с плавающей точкой (или с плавающей запятой). Используется для нецелочисленных расчетов. В Arduino используется например для считывания значений с аналоговых пинов. Диапазон значений от -3.4028235E+38 до 3.4028235E+38,а занимает такая переменная 4 байта.
Точность — 6-7 знаков после запятой.
Тип ничем не отличается от типа float и введен для обратной совместимости. На многих других платформах он имеет большую чем у float точность.
Тип для хранение текстовых строк. Является массивом символов типа char и символа конца строки ‘\0’ в последнем его элементе. Не путать с классами string и String .
Строка может быть создана и инициализирована несколькими способами:
Если забыть указать символ конца строки при посимвольной инициализации или не отвести под него место, то функции работы со строками будут работать некорректно.
Массив — это набор элементов одного типа с доступом к каждому элементу по индексу.
Нумерация индексов массива начинается с 0.
Ключевое слово void используется при объявлении функции, которая не возвращает значения.
Преобразование типов — это приведение значение переменной к другому типа, отличному от типа данной переменной.
Приведение типов делится на явное и неявное.
Пример явного приведения типа:
Пример неявного приведения типа:
Условная конструкция if принимает на вход значение типа boolean , поэтому целочисленное значение переменной a будет приведено к типа boolean .
Integer constants are numbers that are used directly in a sketch, like 123. By default, these numbers are treated as int but you can change this with the U and L modifiers (see below).
Normally, integer constants are treated as base 10 (decimal) integers, but special notation (formatters) may be used to enter numbers in other bases.
|void||Boolean||char||Unsigned char||byte||int||Unsigned int||word|
|long||Unsigned long||short||float||double||array||String-char array||String-object|
The void keyword is used only in function declarations. It indicates that the function is expected to return no information to the function from which it was called.
A Boolean holds one of two values, true or false. Each Boolean variable occupies one byte of memory.
A data type that takes up one byte of memory that stores a character value. Character literals are written in single quotes like this: ‘A’ and for multiple characters, strings use double quotes: «ABC».
However, characters are stored as numbers. You can see the specific encoding in the ASCII chart. This means that it is possible to do arithmetic operations on characters, in which the ASCII value of the character is used. For example, ‘A’ + 1 has the value 66, since the ASCII value of the capital letter A is 65.
Unsigned char is an unsigned data type that occupies one byte of memory. The unsigned char data type encodes numbers from 0 to 255.
A byte stores an 8-bit unsigned number, from 0 to 255.
Integers are the primary data-type for number storage. int stores a 16-bit (2-byte) value. This yields a range of -32,768 to 32,767 (minimum value of -2^15 and a maximum value of (2^15) — 1).
The int size varies from board to board. On the Arduino Due, for example, an int stores a 32-bit (4-byte) value. This yields a range of -2,147,483,648 to 2,147,483,647 (minimum value of -2^31 and a maximum value of (2^31) — 1).
Unsigned ints (unsigned integers) are the same as int in the way that they store a 2 byte value. Instead of storing negative numbers, however, they only store positive values, yielding a useful range of 0 to 65,535 (2^16) — 1). The Due stores a 4 byte (32-bit) value, ranging from 0 to 4,294,967,295 (2^32 — 1).
On the Uno and other ATMEGA based boards, a word stores a 16-bit unsigned number. On the Due and Zero, it stores a 32-bit unsigned number.
Long variables are extended size variables for number storage, and store 32 bits (4 bytes), from -2,147,483,648 to 2,147,483,647.
Unsigned long variables are extended size variables for number storage and store 32 bits (4 bytes). Unlike standard longs, unsigned longs will not store negative numbers, making their range from 0 to 4,294,967,295 (2^32 — 1).
A short is a 16-bit data-type. On all Arduinos (ATMega and ARM based), a short stores a 16-bit (2-byte) value. This yields a range of -32,768 to 32,767 (minimum value of -2^15 and a maximum value of (2^15) — 1).
Data type for floating-point number is a number that has a decimal point. Floating-point numbers are often used to approximate the analog and continuous values because they have greater resolution than integers.
Floating-point numbers can be as large as 3.4028235E+38 and as low as -3.4028235E+38. They are stored as 32 bits (4 bytes) of information.
On the Uno and other ATMEGA based boards, Double precision floating-point number occupies four bytes. That is, the double implementation is exactly the same as the float, with no gain in precision. On the Arduino Due, doubles have 8-byte (64 bit) precision.
Data Types in Arduino
Computers, including the Arduino, tend to be highly data agnostic. At their core, the heart of the device is an arithmetic-logic unit (ALU), which performs (fairly) simple operations on locations in memory: R1+R2, R3*R7, R4&R5, etc. The ALU doesn’t care what that data represents to a user, be it text, integer values, floating point values, or even part of the program code.
All of the context for these operations comes from the compiler, and the directions for the context get to the compiler from the user. You, the programmer, tell the compiler that this value is an integer and that value is a floating point number. The compiler, then, is left trying to figure out what I mean when I say «add this integer to that floating point.» Sometimes that’s easy, but sometimes it’s not. And sometimes it seems like it should be easy, but it turns out to yield results you might not anticipate.
This tutorial will cover the basic data types available in Arduino, what they’re typically used for, and will highlight the effects of using different data types on the size and performance speed of your programs.
You may want to familiarize yourself with a few concepts before we get started:
Defining Data Types
The Arduino environment is really just C++ with library support and built-in assumptions about the target environment to simplify the coding process. C++ defines a number of different data types; here we’ll talk only about those used in Arduino with an emphasis on traps awaiting the unwary Arduino programmer.
Below is a list of the data types commonly seen in Arduino, with the memory size of each in parentheses after the type name. Note: signed variables allow both positive and negative numbers, while unsigned variables allow only positive values.
- boolean (8 bit) — simple logical true/false
- byte (8 bit) — unsigned number from 0-255
- char (8 bit) — signed number from -128 to 127. The compiler will attempt to interpret this data type as a character in some circumstances, which may yield unexpected results
- unsigned char (8 bit) — same as ‘byte’; if this is what you’re after, you should use ‘byte’ instead, for reasons of clarity
- word (16 bit) — unsigned number from 0-65535
- unsigned int (16 bit)- the same as ‘word’. Use ‘word’ instead for clarity and brevity
- int (16 bit) — signed number from -32768 to 32767. This is most commonly what you see used for general purpose variables in Arduino example code provided with the IDE
- unsigned long (32 bit) — unsigned number from 0-4,294,967,295. The most common usage of this is to store the result of the millis() function, which returns the number of milliseconds the current code has been running
- long (32 bit) — signed number from -2,147,483,648 to 2,147,483,647
- float (32 bit) — signed number from -3.4028235E38 to 3.4028235E38. Floating point on the Arduino is not native; the compiler has to jump through hoops to make it work. If you can avoid it, you should. We’ll touch on this later.
This tutorial will NOT cover arrays, pointers, or strings; those are more specialized datatypes with more involved concepts that will be covered elsewhere.
Time and Space
The processor at the heart of the Arduino board, the Atmel ATmega328P, is a native 8-bit processor with no built-in support for floating point numbers. In order to use data types larger than 8 bits, the compiler needs to make a sequence of code capable of taking larger chunks of data, working on them a little bit at a time, then putting the result where it belongs.
This means that it is at its best when processing 8-bit values and at its worst when processing floating point. To demonstrate this fact, I’ve written a simple Arduino sketch which does some very simple math and can easily be altered to use different data types to perform the same calculations.
First, up, let’s dump the code as-is into an Arduino Uno and see what results we get on the serial console.
Okay, lots of stuff there. Let’s take things a bit at a time.
First, if you’re following along, check the compiled size of the code. For addition with bytes, we end up with 2458 bytes of code. Not a lot, and, frankly, most of that is taken up with the serial output stuff. This data point will become important later on, however.
Next, let’s look at the serial port output. What’s the deal with the squares instead of a number for the printed variable values? That happens because the Serial.print() function changes the way it operates based on the type of data which is passed to it. For an 8-bit value (be it a char or byte ), it will simply pipe out that value, in binary. The serial console is then going to try to interpret that data as an ASCII character, and the ASCII characters for 1, 2, and 3 are ‘START OF HEADING’, ‘START OF TEXT’, and ‘END OF TEXT’. Hmm. Not particularly useful, are they, nor easy to display in one character? Hence the square: the serial console is throwing up its hands and saying, ‘I don’t know how to print this, so I made a square for you’. So, lesson one in Arduino datatype finesse: to get the decimal representation of an 8-bit value from Serial.print() , you must add the DEC switch to the function call, like this:
Finally, observe the ‘Elapsed time’ measurement. Discounting the inaccuracies from using the micros() function to measure elapsed time, which we’ll do on all these tests, so we should get a very good RELATIVE measure of the time required for operations, if not a good absolute measure, you can see that adding two 8-bit values requires approximately 4 microseconds of the processor’s time to achieve.
Testing Data Types (Addition)
Okay, let’s move on to test some more data types. If you’re following along at home, you’ll want to change your code, as seen below:
Now, load the code onto your Arduino board. Check the compile size: 2488 bytes for int versus 2458 bytes for byte . Not a lot bigger, but it IS bigger. Again, this is because using data types which require more than 8 bits of storage (like int , long , or float also requires the compiler to generate more actual machine code for the addition to be realized — the processor itself simply doesn’t have the capability of supporting larger data natively. Now, open the serial console and you should see something like this:
Next observation: this time the values printed correctly. That’s because the new datatype we’ve introduced, the int , is correctly interpreted by the compiler as a numeric datatype, and Serial.print() correctly formats the output data to reflect that in the console. So, second lesson of Arduino datatype finesse: if you WANT to send the binary equivalent of a numeric datatype, say, as a means of sharing data with another computing device rather than a user looking at a console, use the Serial.write() function.
Next, let’s check out «Elapsed time» again. We’re up to 12 microseconds now — about 3 times as long! Still pretty short, but this is due to the previously mentioned fact that this is an 8-bit processor, so it needs to jump through some hoops to do 16-bit math, which is what’s required when adding int variables together.
Onward and upward! Now let’s check out the long datatype. Repeat the last code change, except this time replace the two incidents of int with long . Load the code and open your serial console and see what’s happened.
Before we dive into the serial capture, let’s revisit the compile size. I got 2516 bytes, this time- 28 bytes more than using int and 58 more than using byte . Still a pretty small difference but a difference nonetheless, and a difference which could add up if you do a lot of math with long instead of int or byte .
Okay, now on to the serial results. Again, notice that the elapsed time changed. This time, however, it DECREASED from 12 microseconds to 8! How does that work? This is your third lesson in Arduino datatype finesse: what you think is happening may not be what is actually happening. I’m not sure why this occurs — it may be due to some compiler optimization, or due to some run-time optimization which saves time on small value additions which is not present in the int code. Regardless, long is faster than int is not necessarily a safe takeaway here, as we’ll see when we get into multiplication and division.
Okay, last stop, floating point math. Floating point math on the Arduino is tricky because the Arduino lacks a floating point unit, which is fancy-talk for a dedicated section of the processor which handles math with an arbitrary number of digits after a decimal point. Floating point math is also a sticky concept, because while humans can deal well with arbitrary numbers of zeros after the decimal point, computers can’t. This is the origin of the infamous 1 is not 1 bug that some early generation Pentium processors suffered from.
Alter the code as above again, but replace long with float in the two pertinent locations. Load the code and check out the compile size: 3864 bytes! What happened is that by including the floating point data type, you forced the compiler to include the floating point handling code. Clearly, that’s a pretty big chunk of code — it increased the size by a fair margin. Datatype finesse lesson four: don’t use floating point math unless you really, really have to. Most times, that’s going to be limited to giving users feedback about something which is fairly meaningless as an arbitrary integer value: the ADC will return a value like 536, which is cryptic, but converted into floating point it would be something like 2.62V, which is much more useful.
Now look at the run time on this code — back up to 12 microseconds. Also, note that the printed value now includes two zeros after the decimal place. If you want more (or fewer) digits after the decimal, you can add a number of digits into your print command:
Serial.print(x, 3); // print floating point number x with three digits after the decimal.
Testing Data Types (Multiplication/Division)
Now let’s look at what happens with ‘harder’ math — multiplication and division.
Here are some screen grabs for multiplication:
Check out the elapsed times: 4µs for byte , 8 for int or long , and 12 for float — longer for larger data types, and also what we expect to see in terms of ‘harder’ math taking longer. Multiplication is still hardware supported, though — there is a native multiply instruction in the processor which makes multiply operations relatively easy. But what about division?
Oh, my. byte division isn’t too bad at 16µs, but 48 for long ? Ouch. The problem here is that division does NOT have a native instruction in the Atmega instruction set, so the compiler has to do some back flips to create one. So, final lesson: not all mathematical operations are created equal. Divide takes a lot longer than multiply or add (or subtract, but that’s really just add with a minus sign), and something like finding a square root or a sine would take even longer. So long, in fact, that it’s often easier just to maintain a list of values for square roots or sine/cosine/tangent and look up the value that you want than it is to calculate it.
Resources and Going Further
I’m going to leave it here for now. I hope I’ve demonstrated clearly the benefits of using appropriate data types for your variables. The next tutorial will go into some of the really ugly pitfalls that are hiding in mixing data types and using INAPPROPRIATE data types — for instance, data types that are too small for the largest number that you may encounter.
All of the context for these operations comes from the compiler, and the directions for the context get to the compiler from the user. You, the programmer, tell the compiler that THIS value is an integer and THAT value is a floating point number. The compiler, then, is left trying to figure out what I mean when I say «add this integer to that floating point». Sometimes that’s easy, but sometimes it’s not. And sometimes it SEEMS like it SHOULD be easy, but it turns out to yield results you might not anticipate.
Here’s some further reading you may want to explore with your new knowledge of data types:
Interested in learning more foundational topics?
See our Engineering Essentials page for a full list of cornerstone topics surrounding electrical engineering.