Arduino – Entendendo e usando o serialEvent()

Acredito que todo mundo que usou e programou um Arduino pelo menos para um teste simples já enviou/recebeu dados pela serial, certo? Algo muito simples e trivial de fazer até com exemplos prontos na própria IDE Arduino.

Um modelo basico deste tipo de aplicação seria este.

char CharSerialRX;

void setup()
{
  /* Iniciando a Serial e passando a taxa de baudrate */
  Serial.begin(9600); 
}
 
void loop()
{
  /* Quantos bytes esta no buffer de recepção, se maior que 0 
     então devo receber e imprimir na tela
  */
  if(Serial.available() > 0) {
      /* Guardo o caractere recebido na variavel CharSerialRX */
      CharSerialRX = Serial.read();
      /* Escrevo Recebido no terminal */
      Serial.print("Recebido: ");
      /* Envio para a serial  o caractere recebido, apenas ecoando o que recebo */
      Serial.println(CharSerialRX);
  }
}

O código acima foi baseado no exemplo do próprio site do Arduino[link] com algumas pequenas modificações, mas a ideia é um if() ou um while() dentro do loop principal sempre verificando se tem algo no buffer de recepção da serial, porém, a aplicação ganhando forma e conteúdo, e agregando mais maquinas de estados.

Dependendo da quantidade ou o tempo para percorrer um loop inteiro, esta forma de atender a recepção da serial pode não ser eficiente e também não é aplicado a fins profissionais.

E é nessas horas que podemos usar serialEvent()[link], ele é chamado sempre antes de cada interação do loop caso algum byte tenha sido recebido pela serial, sua definição pode ser vista facilmente no arquivo hardware/arduino/cores/arduino/HardwareSerial.cpp

...

#if (RAMEND < 1000)
 #define SERIAL_BUFFER_SIZE 16
#else
 #define SERIAL_BUFFER_SIZE 64
#endif

...

#if !defined(USART0_RX_vect) && defined(USART1_RX_vect)
// do nothing - on the 32u4 the first USART is USART1
#else
#if !defined(USART_RX_vect) && !defined(USART0_RX_vect) && \
    !defined(USART_RXC_vect)
  #error "Don't know what the Data Received vector is called for the first UART"
#else
  void serialEvent() __attribute__((weak));
  void serialEvent() {}
  #define serialEvent_implemented
#if defined(USART_RX_vect)
  ISR(USART_RX_vect)
#elif defined(USART0_RX_vect)
  ISR(USART0_RX_vect)
#elif defined(USART_RXC_vect)
  ISR(USART_RXC_vect) // ATmega8
#endif

...

#if defined(USART1_RX_vect)
  void serialEvent1() __attribute__((weak));
  void serialEvent1() {}
  #define serialEvent1_implemented
  ISR(USART1_RX_vect)
  {
    if (bit_is_clear(UCSR1A, UPE1)) {
      unsigned char c = UDR1;
      store_char(c, &rx_buffer1);
    } else {
      unsigned char c = UDR1;
    };
  }
#endif

#if defined(USART2_RX_vect) && defined(UDR2)
  void serialEvent2() __attribute__((weak));
  void serialEvent2() {}
  #define serialEvent2_implemented
  ISR(USART2_RX_vect)
  {
    if (bit_is_clear(UCSR2A, UPE2)) {
      unsigned char c = UDR2;
      store_char(c, &rx_buffer2);
    } else {
      unsigned char c = UDR2;
    };
  }
#endif

#if defined(USART3_RX_vect) && defined(UDR3)
  void serialEvent3() __attribute__((weak));
  void serialEvent3() {}
  #define serialEvent3_implemented
  ISR(USART3_RX_vect)
  {
    if (bit_is_clear(UCSR3A, UPE3)) {
      unsigned char c = UDR3;
      store_char(c, &rx_buffer3);
    } else {
      unsigned char c = UDR3;
    };
  }
#endif

void serialEventRun(void)
{
#ifdef serialEvent_implemented
  if (Serial.available()) serialEvent();
#endif
#ifdef serialEvent1_implemented
  if (Serial1.available()) serialEvent1();
#endif
#ifdef serialEvent2_implemented
  if (Serial2.available()) serialEvent2();
#endif
#ifdef serialEvent3_implemented
  if (Serial3.available()) serialEvent3();
#endif
}

 

É importante observar que as declarações do serialEvent() não estão dentro do ISR(), ou seja, a função não é chamada na interrupção da recepção dos dados da serial, e sim apenas o buffer da serial é preenchido e logo abaixo o serialEventRun() possui a  chamada da função, caso existir byte(s) no buffer, e neste mesmo arquivo podemos ver que o buffer de recepção no caso do Arduino é 64 bytes, olhando para o define SERIAL_BUFFER_SIZE.

Certo, mas como o serialEvent() é chamado? Se ele é executado antes de cada interação do loop(). Para responder a esta pergunta vamos ver a estrutura do setup() e loop(), acessando o arquivo hardware/arduino/cores/arduino/main.cpp

#include <Arduino.h>

int main(void)
{
	init();

#if defined(USBCON)
	USBDevice.attach();
#endif
	
	setup();
    
	for (;;) {
		loop();
		if (serialEventRun) serialEventRun();
	}
        
	return 0;
}

 

A resposta para a nossa pergunta esta na linha 14 e 15, certo? Beleza, conseguimos ver como funciona nosso serialEvent() e ficamos tranquilo que ele não esta dentro do ISR(), :) certo? Porém, o que é aquele __attribute__((weak))[link] no “void serialEvent() __attribute__((weak)), __attribute__((  )) é usado quando queremos passar algum atributo especial em variáveis, funções ou tipos, para agregar melhor a verificação de erros do compilador ou na otimização.

Neste caso, o (weak) durante o processo de linker do objeto, faz-se um link com simbolo fraco para a função serialEvent(), dizendo que ela esta definida na biblioteca porém poderá ser sobrescrita no arquivo do usuário, no caso no projeto do Arduino, sem este (weak) com certeza receberíamos um erro de “undefined reference to serialEvent“, mais detalhes e outros atributos veja em GCC 4.9.2 – Declaring Attributes of Functions[link].

Sim é um assunto complexo e que merece atenção, irei escrever sobre __attribute__ e como analisar os objetos gerados para melhor compreensão destes símbolos.

Agora um exemplo com o serialEvent().

void setup()
{
  /* Iniciando a Serial e passando a taxa de baudrate */
  Serial.begin(9600); 
}
 
void loop()
{
  /* CODIGO */
  Serial.print(":)\n");
  delay(500);
}


void serialEvent() {
  while (Serial.available()) {
    char CharSerialRX = (char)Serial.read();
    Serial.print("Recebido: ");
    Serial.print(CharSerialRX); 
  }
}

 

A aplicação acima foi baseada no exemplo do site do Arduino[link], vamos comunicar com nossa aplicação pelo Monitor Serial do Arduino e ver a saída.

:)
:)
:)
:)

Recebido: A
Recebido: R
Recebido: D
Recebido: U
Recebido: I
Recebido: N
Recebido: O:)
:)
:)
:)
:)
:)

Recebido: O
Recebido: K:)
:)
:)
:)

Recebido: 2
Recebido: 0
Recebido: 1
Recebido: 5:)

 

Na saída acima, um loop ficara a cada 500ms dando echo com um :) e eu enviei algumas coisas como ARDUINO, OK e 2015.

Espero que tenham gostado desta dica, e de garimbar os arquivos da IDE do Arduino e como podemos percebemos, da para aprender muito com isso, conhecendo com as coisas são implementadas por trás da IDE.

Até a próxima!

 

Referências

http://arduino.cc/en/Serial/Read

http://arduino.cc/en/Tutorial/SerialEvent

https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Function-Attributes.html#Function-Attributes

http://www.keil.com/support/man/docs/armccref/armccref_Cacdgifc.htm

Share Button

CC BY-NC-SA 4.0 Arduino – Entendendo e usando o serialEvent() by Cleiton Bueno is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.