Урок короткий, но очень важный. Разберемся в базовых типах данных STM32. Понимание этого вопроса абсолютно необходимо для разработки программ.
Предыдущий урок Список уроков Следующий урок
Все действия в программе мы производим над переменными. Поэтому необходимо точно знать форматы, размеры переменных, диапазоны чисел, которые они способны содержать. Переменные должны быть объявлены с указанием типа данных до использования в программе. Тип данных и задает параметры переменных.
Все это мы знаем из уроков Ардуино. Но базовые типы данных системы Ардуино и компилятора C для STM32 отличаются. Формальное применение знаний, полученных из курса Ардуино, может привести к фатальным последствиям для программ STM32. Давайте переучиваться.
Некоторые базовые типы данных стандартов языка программирования Си зависят от используемого микроконтроллера. В значительной мере от разрядности данных, с которыми он оперирует. Например, для 8ми разрядного микроконтроллера Ардуино тип int это 16 разрядов. Тот же тип для STM32 составляет 32 разряда.
Я не буду рассказывать о принципах, по которым стандарт языка Си определяет форматы базовых типов данных. Я буду освещать этот вопрос применительно к нашему микроконтроллеру STM32, нашей среде программирования.
Выделим из стандарта C99 языка программирования Си следующие базовые типы данных.
Тип данных | Пояснение | Разрядность бит (байт) |
Диапазон чисел |
char int8_t |
Целочисленный знаковый тип. | 8 (1) | - 128 … 127 |
unsigned char uint8_t |
Целочисленный беззнаковый тип. | 8 (1) | 0 … 255 |
short int16_t |
Целочисленный знаковый тип. | 16 (2) | - 32768 … 32768 |
unsigned short uint16_t |
Целочисленный беззнаковый тип. | 16 (2) | 0 … 65535 |
int int32_t |
Целочисленный знаковый тип. | 32 (4) | - 2147483648 … 2147483647 |
unsigned int uint32_t |
Целочисленный беззнаковый тип. |
32 (4) | 0 … 4294967295 |
long int32_t |
Целочисленный знаковый тип. | 32 (4) | - 2147483648 … 2147483647 |
unsigned long uint32_t |
Целочисленный беззнаковый тип. | 32 (4) | 0 … 4294967295 |
long long int64_t |
Целочисленный знаковый тип. | 64 (8) | - 9223372036854775808 … 9223372036854775807 |
unsigned long long uint64_t |
Целочисленный беззнаковый тип. | 64 (8) | 0 … 18446744073709551615 |
float | Формат с плавающей запятой одинарной точности. | 32 (4) | ± (3,4 * 10 -38 … 3,4 * 10 +38) |
double | Формат с плавающей запятой двойной точности. | 64 (8) | ± (1,7 * 10 -308 … 1,7 * 10 +308) |
long double | Формат с плавающей запятой повышенной точности. | 64 (8) | ± (1,7 * 10 -308 … 1,7 * 10 +308) |
_Bool | Логический тип. | 8 (1) | 0 (ложь) или 1 (истина) |
Я подкорректировал параметры в соответствии с нашей средой программирования, поверил все эти типы данных.
Есть в языке Си функция, которая возвращает количество байтов необходимое для указанного в качестве аргумента типа.
int n = sizeof(long); // считать размер типа long
Я написал программу, которая определяет размеры разных типов данных и выводит их через последовательный порт. Это основной блок программы.
sprintf((char *)str,"Тип char - %u байт\r\n", sizeof(char));
outputRes();
sprintf((char *)str,"Тип short - %u байт\r\n", sizeof(short));
outputRes();
sprintf((char *)str,"Тип int - %u байт\r\n", sizeof(int));
outputRes();
sprintf((char *)str,"Тип long - %u байт\r\n", sizeof(long));
outputRes();
sprintf((char *)str,"Тип long long - %u байт\r\n", sizeof(longlong));
outputRes();
sprintf((char *)str,"Тип float - %u байт\r\n", sizeof(float));
outputRes();
sprintf((char *)str,"Тип double - %u байт\r\n", sizeof(double));
outputRes();
sprintf((char *)str,"Тип long double - %u байт\r\n", sizeof(longdouble));
outputRes();
sprintf((char *)str,"Тип _Bool - %u байт\r\n\r\n", sizeof(_Bool));
outputRes();
Вот, что показал монитор последовательного порта CoolTerm.
Кто захочет проверить сам, полностью проект программы можно загрузить по ссылке:
В программах для STM32 можно пользоваться всеми типами данных из таблицы. Но я предлагаю ввести ограничения.
В таблице есть целочисленные типы данных с явно заданной разрядностью.
Их имена образуются из символов:
- int –целое знаковое;
- uint –целое беззнаковое (к int добавили u);
- число разрядов;
- _t – тип.
Формат таких типов данных не зависит от разрядности микроконтроллера.
- int8_t – целое знаковое 8 разрядов;
- uint8_t – целое беззнаковое 8 разрядов;
- int16_t – целое знаковое 16 разрядов;
- uint16_t – целое беззнаковое 16 разрядов;
- int32_t – целое знаковое 32 разрядов;
- uint32_t – целое беззнаковое 32 разрядов;
- int64_t – целое знаковое 64 разрядов;
- uint64_t – целое беззнаковое 64 разрядов;
Хороший стиль использовать при разработке программ для STM32 именно такие описания для целочисленных переменных. Давайте так и будем поступать в дальнейшем.
Я бы еще допустил использование типа char. Он явно указывает на назначение переменной – хранение кода символа и, в какой-то степени, улучшает читаемость программы.
Типы с плавающей запятой будем использовать из таблицы.
Думаю, вы заметили, что отсутствуют привычные логические типы данных bool и boolean.В языке Си современной редакции они заменены на тип _Bool.
Но дело не в формальном изменении имени. Переменные для него не могут принимать значения true и false. Теперь это 0 – ложно и 1 – истинно. Часто в качестве логического типа используют uint8_t.
Можно вернуть привычный логический тип boolean и значения для него, если подключить заголовочный файл stdbool.h. Переназначение имен будет происходить через макросы.
Я предлагаю отказаться от такого способа и оставить принцип описания логических переменных, заданный разработчиками стандарта языка Си.
Но, в проектах C++ тип _Bool не поддерживается. Давайте использовать для логических переменных тип uint8_t.
Конечно, все это не догма, носит рекомендательный характер. Я выражаю свое мнение. Каждый может использовать типы данных на свое усмотрение. Но в дальнейших уроках я собираюсь придерживаться изложенных выше принципов.
Еще два слова по поводу констант. Они тоже могут иметь знак. К беззнаковой константе добавляется буквы U или u.
int x = 234U; // беззнаковое число
int x = 0x001Eu; // беззнаковое число
В противном случае (без буквы U) константа считается знаковой.
int x = 234; // знаковое число
int x = 0x001E; // знаковое число
В случае использования знаковой константы старший разряд считается знаком, и отрицательные числа воспринимаются в дополнительном коде.
В следующем уроке будем займемся обработкой сигналов кнопок.
uint1_t – целое беззнаковое 1 разряд; бит мне захотелось, Кейл не вкусил моего порыва…
почему не писать так:
int32_t x = 234; // знаковое число
uint32_ x = 234; // беззнаковое число
вместо:
int x = 234; // знаковое число
int x = 234u; // беззнаковое число
и сколько бит выделит компилятор для этого «int» ?
Здравствуйте!
В уроке написано — 4 байта.
Странно, у меня тип _Bool вызывает ошибку. А где они все определены кстати и как?
Здравствуйте!, который определяет макросы true, false и bool. Тогда можно работать с bool, как обычно.
0
Попробуйте включить
В статье говорится, что char — Целочисленный знаковый тип. Это в корне неверно. Стандарт языка определяет, что знаковость char зависит от реализации:
The three types char, signed char, and unsigned char are collectively called the character types.
The implementation shall define char to have the same range, representation, and behavior as either signed char or unsigned char.
Один из моментов, на которых массово сыпятся у меня на собеседованиях «знатоки» языка Сю
Приветствую всех! Язык Си изучаю недавно. Однако словосочетание «Целочисленный знаковый тип» сразу бросается в глаза, режет слух. Насколько мне известно, char в разных языках — это символьный тип данных, переменная такого типа может принимать значения из таблицы ASCII. Соответственно целое число и символ (знак) не должны сочетаться в одном типе, они должны быть разными типами, для них должно использоваться свое обозначение. Видимо у разработчиков стандарта языка своя особенная логика. Сравниваю разные языки, многое повторяется, но вот зачем так усложнять, если уже это было придумано в другом языке, например, менять название типов данных и т.п.
Автор забыл указать, что GCC для ядер Cortex M может запросто заоптимизировать в выходном коде тип unsigned long long (64-битное беззнаковое) на float (причём мать его даже не double!) со всеми вытекающими типа потери точности и т п.
реально? Это в каких случаях произойти может? Даже не представляю, как такой ужас отлаживать
Приветствую всех! Чем отличаются типы данных double и long double, int int32_t и long int int32_t, unsigned int
uint32_t и unsigned long int uint32_t? Судя по таблице ничем.