Функция Serial.read()
Данные из буфера последовательного порта считываются при помощи функции Serial.read(). Она извлекает один байт данных, уменьшая количество доступной информации в буфере. Ниже приведен пример использования Serial.read():
void setup() { Serial.begin(9600); } void loop() { if(Serial.available()) { byte kol = Serial.read(); //чтение количества Serial.println(kol); //————————————————- char znak=Serial.read(); //чтение кода символа Serial.println(znak); } delay(80); }
Вышеуказанная программа считывает данные из последовательного порта и отправляет их на терминал. Существует два способа интерпретации данных. В первом случае байт, считанный с порта, рассматривается как число. Терминал отобразит соответствующий символ ASCII, во втором случае прочитанный байт рассматривается как код ASCII. Когда вы запустите программу и введете букву «a» в первом случае, вы получите код «97», а во втором букву «a».
Логический оператор NOT (!)
Оператор NOT может использоваться, чтобы проверить, содержит ли переменная значение — другими словами, он может использоваться, чтобы проверить, оценивается ли переменная как ложь.
int x = 0;
if(!x) {
// если x принимает значение ложь, код здесь будет запущен
}
// еще один способ написания вышеуказанного кода
if (x == 0) {
}
1 2 3 4 5 6 7 8 |
intx=; if(!x){ // если x принимает значение ложь, код здесь будет запущен } if(x==){ } |
Пример работы оператора NOT
В следующем примере показан логический оператор NOT, используемый в скетче. Хотя этот скетч не является практическим применением оператора NOT, но он демонстрирует, как он работает.
В скетче каждый символ, отправленный в Arduino из окна Монитор порта, включит светодиод, за исключением символа «a».
void setup() {
Serial.begin(9600);
pinMode(13, OUTPUT); // Светодиод на пине 13 UNO
}
void loop() {
char rx_byte;
if (Serial.available() > 0) { // доступен ли символ?
rx_byte = Serial.read(); // считывание символа
if (!(rx_byte == ‘a’)) {
digitalWrite(13, HIGH);
}
else {
digitalWrite(13, LOW);
}
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
voidsetup(){ Serial.begin(9600); pinMode(13,OUTPUT);// Светодиод на пине 13 UNO } voidloop(){ charrx_byte; if(Serial.available()>){// доступен ли символ? rx_byte=Serial.read();// считывание символа if(!(rx_byte==’a’)){ digitalWrite(13,HIGH); } else{ digitalWrite(13,LOW); } } } |
Оператор not инвертирует логику второго оператора if, как показано ниже:
if (!(rx_byte == ‘a’)) {
digitalWrite(13, HIGH);
}
1 2 3 |
if(!(rx_byte==’a’)){ digitalWrite(13,HIGH); } |
Если переменная rx_byte содержит символ «a», тогда выражение в операторе if будет оцениваться как истинное, однако оператор NOT изменяет результат на ложь, так что при получении «a» светодиод гаснет.
Точно так же, если переменная rx_byte содержит любой символ, кроме «a», выражение обычно оценивается как ложь, но оператор NOT инвертирует ложный результат в истину.
Приведенный выше код можно было бы проще написать, используя оператор отношения не равно (! =), как показано ниже:
if (rx_byte != ‘a’) {
digitalWrite(13, HIGH);
}
1 2 3 |
if(rx_byte!=’a’){ digitalWrite(13,HIGH); } |
Логический оператор NOT имеет практическое применение в более сложных скетчах. Просто помните, что если результат сравнения когда-либо потребуется инвертировать, можно использовать логический оператор NOT. Его можно использовать, чтобы заставить оператор if оценивать значение истина, когда оно обычно оценивается как ложь.
Логический оператор OR (||)
Логический оператор OR прописывается в скетчах в виде двух вертикальных вертикальных черточек (||), расположенных на той же клавише, что и обратная косая черта (\) на клавиатуре (английская раскладка). Нажатие Shift + \ (клавиши Shift и обратная косая черта) вводит вертикальный символ вертикальной черты.
Следующий скетч демонстрирует использование логического оператора OR для проверки букв алфавита в верхнем и нижнем регистре.
void setup() {
Serial.begin(9600);
pinMode(13, OUTPUT); // Светодиод на пине 13 UNO
}
void loop() {
char rx_byte;
if (Serial.available() > 0) { // доступен ли символ?
rx_byte = Serial.read();
if (rx_byte == ‘a’ || rx_byte == ‘A’) {
digitalWrite(13, HIGH);
}
else {
digitalWrite(13, LOW);
}
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
voidsetup(){ Serial.begin(9600); pinMode(13,OUTPUT);// Светодиод на пине 13 UNO } voidloop(){ charrx_byte; if(Serial.available()>){// доступен ли символ? rx_byte=Serial.read(); if(rx_byte==’a’||rx_byte==’A’){ digitalWrite(13,HIGH); } else{ digitalWrite(13,LOW); } } } |
Программа включит светодиод на плате Arduino Uno, если строчная буква «а» или заглавная буква «А» отправлены из окна Монитор порта. При отправке любого другого символа светодиод гаснет.
Как работает логический оператор OR
Приведенный ниже код взят из приведенного выше скетча и показывает логический оператор OR:
if (rx_byte == ‘a’ || rx_byte == ‘A’) {
digitalWrite(13, HIGH);
}
1 2 3 |
if(rx_byte==’a’||rx_byte==’A’){ digitalWrite(13,HIGH); } |
Код в теле оператора if будет выполняться, если переменная rx_byte содержит «a» OR (||) (или), если она содержит «A«. Оператор OR использовался для проверки того или иного символа (A OR a).
Код может быть изменен для включения светодиода при получении символа «a», символа «b» или символа «c», как это сделано в коде ниже:
if ((rx_byte == ‘a’) || (rx_byte == ‘b’) || (rx_byte == ‘c’)) {
digitalWrite(13, HIGH);
}
1 2 3 |
if((rx_byte==’a’)||(rx_byte==’b’)||(rx_byte==’c’)){ digitalWrite(13,HIGH); } |
В приведенном выше коде все операторы сравнения, равные реляционным операторам, заключены в круглые скобки () для облегчения чтения кода. Это также позволяет избежать непонимания того, какой оператор вычисляется первым (что вычисляется первым == или ||?).
== имеет более высокий приоритет, чем || что означает, что сначала вычисляется ==. Круглые скобки имеют наивысший приоритет, поэтому все, что находится в скобках, будет оцениваться первым. В этом случае нет необходимости ставить круглые скобки, но это облегчает чтение.
Протокол I2C в Arduino
На следующем рисунке показаны контакты платы Arduino UNO, которые используются для связи по протоколу I2C.
Линия протокола I2C | Контакт платы Arduino UNO |
SDA | A4 |
SCL | A5 |
Для осуществления связи по протоколу I2C в плате Arduino используется библиотека <Wire.h>. В ней содержатся следующие функции для связи по протоколу I2C.
1. Wire.begin(address).
Эта команда производит инициализацию библиотеки Wire и осуществляет подключение к шине I2C в качестве ведущего (master) или ведомого (slave). 7-битный адрес ведомого в данной команде является опциональным и если он не указан , то устройство (плата Arduino) подключается к шине I2C в качестве ведущего (master).
2. Wire.read().
Эта функция используется для считывания байта, принятого от ведущего или ведомого.
3. Wire.write().
Эта функция используется для записи данных в устройство, являющееся ведомым или ведущим.
От ведомого ведущему (Slave to Master): ведомый записывает (передает) данные ведущему когда в ведущем работает функция Wire.RequestFrom().
От ведущему ведомому (Master to Slave): в этом случае функция Wire.write() должна использоваться между вызовами функций Wire.beginTransmission() и Wire.endTransmission().
Функцию Wire.write() можно использовать в следующих вариантах:
- Wire.write(value); value — значение передаваемого одиночного байта;
- Wire.write(string) – для передачи последовательности байт;
- Wire.write(data, length); data – массив данных для передачи в виде байт, length – число байт для передачи.
4. Wire.beginTransmission(address).
Эта функция используется для начали передачи по протоколу I2C устройству с заданным адресом ведомого (slave address). После этого вызывается функция Wire.write() с заданной последовательностью байт для передачи, а после нее функция endTransmission() для завершения процесса передачи.
5. Wire.endTransmission().
Эта функция используется для завершения процесса передачи ведомому устройству, который до этого был инициирован функциями beginTransmission() и Wire.write().
6. Wire.onRequest().
Эта функция вызывается когда ведущий запрашивает данные с помощью функции Wire.requestFrom() от ведомого устройства. В этом случае мы можем использовать функцию Wire.write() для передачи данных ведущему.
7. Wire.onReceive().
Эта функция вызывается когда ведомое устройство получает данные от ведущего. В этом случае мы можем использовать функцию Wire.read() для считывания данных передаваемых ведущим.
8. Wire.requestFrom(address,quantity).
Эта функция используется в ведущем устройстве чтобы запросить байты (данные) с ведомого устройства. После этого используется функция Wire.read() чтобы принять данные переданные ведомым устройством.
address: 7-битный адрес устройства, с которого запрашиваются байты (данные).
quantity: число запрашиваемых байт.
Функции Serial.find() и Serial.findUntil()
Функция Serial.find() считывает содержимое буфера в поисках конкретной строки. Функция возвращает true, когда строка найдена и false, когда данные не найдены. Ниже приведен пример кода программы:
void setup() { Serial.begin(9600); } void loop() { if(Serial.find(«test»)) Serial.println(«ok.»); }
В данном примере программа считывает данные из буфера и когда полученные данные соответствуют строке поиска (test), то отображается сообщение (ok).
Serial.find() ожидает данные в течение времени, определенного с помощью функции Serial.setTimeout(). В случаях, когда в буфере имеется большое количество данных, то их можно обработать почти мгновенно. Когда же буфер пуст, функция ожидает следующую порцию данных в течение некоторого времени и заканчивается, возвращая соответствующее значение.
Serial.findUntil() — это вариант Serial.find(), который отличается от предшественника дополнительным аргументом, позволяющим прервать загрузку буфера последовательного порта. Пример синтаксиса показан ниже:
Serial.findUntil ( «text», «K»);
Функция будет считывать данные из последовательного порта, пока не будет найдена строка поиска (text) или прочитан символ «K», или пока не пройдет определенное время (по умолчанию – 1 секунда).
Arduino string (c style strings)
For C object Strings an ok place to look is the Arduino c string Reference.
However the above link does not detail the functions available for c-style strings. For that look to a standard reference. This link is good as it orders the functions in the order of most used.
P.S. Have a look at strtok() as this can allow you to process a
command line with multiple commands separated with a delimiter e.g. a
semi-colon — Useful for a more complex serial command decoder.
Comparison of String and c string
The following table shows the difference in memory usage:
Type | Flash | SRAM |
---|---|---|
String | 3912 | 248 |
c string | 2076 | 270 |
Difference String cf c string | +1836 | -22 |
Note: It appears that the String class uses
less SRAM but it, in fact uses the heap and uses more than c string uses
(it is just not easy to measure because it can change all the time as strings are created and destroyed).
Using Class String to control strings is undoubtedly the easiest way
and is very similar to higher level languages such as python but it comes
at a cost — that cost is Flash program memory.
Our Serial.read() protocol
Let’s make these the protocol rules that we’ll enforce in our Arduino program.
- New messages will be read as soon as they arrive
- Messages will be no longer than 12 bytes
- Every message will end with a newline character ‘\n’ – which we will call out terminating character
This is a pretty basic protocol, but it will help us with our strategy.
First we need a place to store the incoming bytes from the serial receive buffer – we can use a char array for that. Then we need to check if anything is even available in the serial receive buffer – we can use Serial.available for that. Then we need to actually read in a byte – we can use Serial.read() for that.
Before we put the byte into our char array, we’ll need to check the incoming byte to make sure it is not a terminating character.
- Create a character array to store incoming bytes
- Check to see if there is anything in the serial receive buffer to be read – Serial.available()
- While there is something to be read then…
- Read in the byte to a temporary variable – Serial.read()
- Check to see if what we read is part of our message OR a terminating character
- If it is part of our message, then save it to a character array
- If it is a terminating character, then output the message and prepare for the next message
- If the message has exceeded the max message length in the protocol, then stop reading in more bytes and output the message (or doing something else with it)
Should You Use String?
If you search on the Web, you will find that there can be problems in
using heap based systems and that is due to memory fragmentation.
Fragmentation in the heap is caused when strings of larger length can
not be allocated to a ‘freed’ memory block (since the block that had
been previously ‘freed’ up is too small); they have to be allocated in
new memory. This leaves lots of small blocks of unused memory.
In the extreme case memory fragmentation, caused when you have low
SRAM! and lots of string manipulations, can cause your program to
hang — the solution is to press the reset button! For small programs
with low numbers of Strings, its fine (there won’t be #enough memory
fragmentation to cause the program to hang.
The following statement is from a consortium of car
manufacturers MISRA (Motor Industry Software Reliability Association).
They specifically forbid using heap based memory management because
safety is crucial:
MISRA C++ rule 18-4-1, dynamic heap memory allocation cannot be used.
This document describes static, dynamic memory fragmentation etc. but its final conclusion is this:
Exhaustion is still
our major impediment to using dynamic memory in real-time embedded
systems. A good failure policy based around std::bad_alloc can
address many of the issues, but in high integrity systems dynamic
memory usage will remain unacceptable.
For programs that do not use many strings the Arduino string class is
fine. However if you have low levels of SRAM and use lots of string
manipulations you could run into fragmentation problems (random
failure).
What exactly is a String?
As noted at the start, a string is a line of text stored in SRAM.
Note: This section applies to both Strings and c-strings.
Both Strings and ‘strings’ are dynamic entities meaning they can be
changed whenever you want since they exist in SRAM. For an embedded
system that typically has very low SRAM available (Arduino Uno 2k Byte)
it means the SRAM will disappear fast unless you put all your constant
strings into Flash.
In fact strings are doubly wasteful in C or C++, because they use
both Flash and SRAM.
The first reason is initialisation. You probably want to start off a
string with some information in it e.g. «Initialising I2C» etc.;
Sometimes you don’t e.g. a receive buffer for a serial input. To do the
initialisation you need somewhere to store the string when the power is
off, and that store is Flash memory.
The second reason is that strings are defined as existing in RAM so
that they can be changed as your program runs.
In some cases you don’t want an
updatable string e.g for a text message that never changes. The problem
is that the compiler won’t know that the string is never going to
change.
Пример скетча для print и println
В завершении давайте рассмотрим реальный скетч, в котором мы используем монитор порта Arduino IDE для вывода отладочной информации с платы контроллера.
void setup() { // Объявляем работу с последоватлеьным портом в самом начале Serial.begin(9600); // Теперь мы можем писать сообщения Serial.println ("Hello, Arduino Master"); } void loop() { // Выводим таблицу с информацией о текущих значениях портов Serial.print("Port #\t\t"); Serial.println("Value"); Serial.print("A0\t\t"); Serial.println(analogRead(A0)); Serial.print("A1\t\t"); Serial.println(analogRead(A1)); Serial.println("--------"); delay(1000); }
Функция Serial.write()
В отличие от Serial.print() и Serial.println(), функция Serial.write() позволяет отправлять один байт информации (число). Ниже приведен синтаксис Serial.write():
Serial.write (число); Serial.write («текст»); Serial.write (массив, длина);
Примеры использования Serial.write():
byte a[]={65,66,67,68,69}; void setup() { Serial.begin(9600); } void loop() { Serial.print(65); // отправляет в терминал два символа 6 и 5 Serial.write(65); // отправляет в терминал код 65 (буква A в кодировке ASCII) Serial.write(a,3); // отправляет в терминал коды 65, 66, 67 (A, B, C) delay(800); }
Как вы можете видеть в данном примере, при отправке числа 65 с помощью Serial.print() в терминале получим два символа 6 и 5, а отправка числа 65 с использованием Serial.write() в терминале будет интерпретироваться как код ASCII 65, т.е «А».
Code Structure
Libraries
In Arduino, much like other leading programming platforms, there are built-in libraries that provide basic functionality. In addition, it’s possible to import other libraries and expand the Arduino board capabilities and features. These libraries are roughly divided into libraries that interact with a specific component or those that implement new functions.
To import a new library, you need to go to Sketch > Import Library
In addition, at the top of your .ino file, you need to use ‘#include’ to include external libraries. You can also create custom libraries to use in isolated sketches.
Pin Definitions
To use the Arduino pins, you need to define which pin is being used and its functionality. A convenient way to define the used pins is by using:
‘#define pinName pinNumber’.
The functionality is either input or output and is defined by using the pinMode () method in the setup section.
Variables
Whenever you’re using Arduino, you need to declare global variables and instances to be used later on. In a nutshell, a variable allows you to name and store a value to be used in the future. For example, you would store data acquired from a sensor in order to use it later. To declare a variable you simply define its type, name and initial value.
It’s worth mentioning that declaring global variables isn’t an absolute necessity. However, it’s advisable that you declare your variables to make it easy to utilize your values further down the line.
Instances
In software programming, a class is a collection of functions and variables that are kept together in one place. Each class has a special function known as a constructor, which is used to create an instance of the class. In order to use the functions of the class, we need to declare an instance for it.
Setup()
Every Arduino sketch must have a setup function. This function defines the initial state of the Arduino upon boot and runs only once.
Here we’ll define the following:
- Pin functionality using the pinMode function
- Initial state of pins
- Initialize classes
- Initialize variables
- Code logic
Loop()
The loop function is also a must for every Arduino sketch and executes once setup() is complete. It is the main function and as its name hints, it runs in a loop over and over again. The loop describes the main logic of your circuit.
For example:
Note: The use of the term ‘void’ means that the function doesn’t return any values.
How to use Arduino Serial Read?
- Arduino Serial read command is used for reading any data available at the Serial Port of Arduino board.
- I have also designed a Proteus simulation which you can download from the below button, and I have explained this simulation in the last step of this tutorial:
Download How to use Arduino Serial Read ?
- For example, you have some serial module, let’s say GPS module (most of the GPS module works at serial port).
- So, when you connect your GPS module with Arduino, you have to connect the TX pin of GPS with the RX pin of Arduino.
- Now the TX pin of GPS will be sending/transmitting the data and because this pin is connected with the RX pin of Arduino, so Arduino will keep receiving the data.
- Now the data is coming to Arduino but you have to write some code to read this incoming serial data and then save it in some variable.
- And in order to read this data, we need to use the Arduino Serial Read command.
- Arduino Serial read command reads the incoming data from Serial Port and then saves it in some variable.
- Here’s the syntax of the Arduino Serial Read command:
char data = Serial.read();
One important thing is, in order to make Arduino Serial Read command work, you have to first initialize the Serial Port in Arduino, as shown below:
Serial.begin(9600);
Note:
- Arduino USB Port which is plugged into the computer and is used for uploading code also works on the same serial port.
- So, if you have anything plugged in pin # 0 of Arduino then you can’t upload the code in Arduino.
Now, let’s design a simple example in which we will be receiving data from Serial Port and then saving it in some variable.
So, connect your Serial device with your Arduino board and now upload the below code to your Arduino board:
void setup() { Serial.begin(9600); // Serial Port initialization } void loop() { if(Serial.available()) // Chek for availablity of data at Serial Port { char data = Serial.read(); // Reading Serial Data and saving in data variable Serial.print(data); // Printing the Serial data } }
- Now, you need to open the Serial Monitor of Arduino which is used for debugging purposes.
- So, whenever you write something on Serial Port, it got printed on the Serial monitor.
- So, whatever you will be receiving in the Serial Port you will get in the Serial Monitor.
- Here are some random data of GSM module coming on serial port and showing in serial monitor:
Вопросы и ответы
Serial begin 9600 – что это значит?
Эти три слова похожи на какую-то мантру. И действуют они так же: каким-то волшебным образом наша плата ардуино организовывает канал связи и начинает информационный обмен. Конечно, на самом деле никакой мистики во всем этом нет, но заклинание мы запомнить должны:
Serial.begin(9600);
Эта строчка говорит контроллеру ардуино, что нужно обратиться к последовательному порту по UART интерфейсу, открыть его для записи и подписаться на любые события с его стороны (для получения данных). Она обязательно должна использоваться перед выводом информации в монитор порта или считыванием данных.
На каких платах работает функция?
Эта функция работает на любых платах Arduino: Uno, Mega, Nano, Leonardo и т.д. Особенностью плат с несколькими «железными» последовательными портами является возможность вызывать объект Serial для каждого из этих портов.
Что будет, если не использовать функцию в скетче?
Все последующие команды для работы с последовательным портом не будут работать. Вы не сможете ни отправлять данные с помощью функций print() или println(), ни получать данные с помощью read() или readBytes().
Можно ли сократить функцию Serial.begin()?
Для многих начинающих программистов написание непривычных конструкций вызывает дискомфорт. Вы можете «сократить» Serial.begin() до синонима и использовать его, если так будет удобно. Для этого нужно воспользоваться #define. Вот пример:
Но следует сразу отметить, что данный способ использовать не рекомендуется, т.к. это усложняет и запутывает программу, а пользы и экономии места очень мало.
Пример вывода на дисплей LCD1602 через последовательный порт UART Arduino из-под Linux средствами языка Python
Короче говоря, есть комп с линуксом, к которому подключена Arduino через USB, а к арудине подключен дисплей LCD1602, и мы хотим на него выводить инфу.
Сначала привожу полный код программы для Arduino UNO, к которой подключен дисплей LCD1602:
Скачать исходник.
Я сделал здесь решетку ‘#’ в качестве символа завершения передачи пакета данных. Как только в потоке данных встречается символ #, данные выводятся на дисплей, и буфер обнуляется, при этом сам символ ‘#’ не выводится. Конечно, можно было бы использовать ‘\n’ или др.
Далее мы напишем скрипт на Python, который будет выводить инфу на дисплей. Я выбрал Python, потому что это прикладной язык и он лучше всего подходит для подобных задач. С такими языками как C++/C# и т.п. больше возни с библиотеками, а здесь всё просто, особенно если это дело происходит под линуксом.
Первым делом устанавливаем необходимые библиотеки (для 2-ой или 3-ей версии python)
$sudo apt-get install python-serial
или
$sudo apt-get install python3-serial
Далее в интерпретаторе python пишем:
Здесь ардуина у меня подключена к порту /dev/ttyUSB0 — это я узнавал из Arduino IDE. Обычно она всегда на этом порту сидит, если других устройств на последовательный порт не подключено.
Как вы уже догадались, и в первой, и во второй программы должна быть указано одинаковая скорость в бодах. В моем случае это 9600 — стандартная, хотя и маленькая скрость. Может быть и больше (читай выше).
Краткие выводы
Функция digitalRead одна из важнейших и часто используемых в ардуино. С ее помощью мы «чувствуем» окружающий мир. Правда, в отличие от analogRead, картина всегда получается черно-белой: функция возвращает только два варианта значений: HIGH или LOW. Во многих случаях этого вполне хватает. Например, если нам нужно просто получить сигнал о возникновении какого-либо события, такого как срабатывание датчика движения, звука, вибрации.
Использовать digitalRead () очень просто – нужно просто передать ей номер пина, с которого будет считан сигнал. Функция возвратит нам число, равное HIGH или LOW, которое мы затем сможем сверить в блоке условия и выполнить какие-то действия. Самое главное, собрать схему так, чтобы на входе не получать случайные значения. В этой статье мы привели несколько примеров с советами.