O protocolo Custom CAN ID é amplamente utilizado na automação industrial para o controle de dispositivos. Nesse contexto, uma placa Arduino oferece uma solução prática para integração de sistemas e também pode atuar como controlador host de Smart Displays, especialmente em aplicações com modelos sem função touch. Por meio do protocolo Custom CAN ID, uma placa Arduino pode controlar Smart Displays sem touch de forma eficiente.
Este artigo apresenta um programa de demonstração que utiliza um Arduino UNO em conjunto com um CAN Bus Shield para controlar um Smart Display por meio do protocolo Custom CAN ID, utilizando um OLED Knob Display como interface de operação. O programa permite explorar diferentes funções e operações de um Smart Display sem touch. O exemplo também serve como referência prática para desenvolvedores que desejam ampliar seus conhecimentos sobre integração de sistemas e controle de Smart Displays com e sem touch utilizando um OLED Knob Display. O diagrama abaixo apresenta a implementação da aplicação.

Esta aplicação requer os seguintes componentes:
- Smart Display Custom CAN ID de 10,1”
- Arduino UNO
- CAN Bus Shield para Arduino UNO
- Módulo OLED Display Knob Rotator
- ST-Link CAN Bus Dongle
- GUI Builder v0.4.2 ou superior
- Arduino IDE v2.0.4
A demonstração é dividida em três etapas:
- Criar o projeto de demonstração no GUI Builder
- Compilar e carregar o projeto
- Programar o Arduino como host
Criar o projeto de demonstração no GUI Builder
Para começar, abra uma versão atualizada do GUI Builder e crie um novo projeto. Insira o nome desejado para o projeto e verifique se a pasta do projeto foi selecionada automaticamente. Em seguida, selecione o tipo de dispositivo; neste exemplo, é utilizado um display de 10,1". Selecione o protocolo desejado e mantenha a orientação do display no modo paisagem padrão. Escolha um template de interface em branco e clique no botão Create para criar o projeto. A figura abaixo apresenta as etapas descritas.

Após a criação do projeto, a página abaixo será exibida.

Para atender ao cenário desta demonstração, são necessárias quatro páginas no total. Portanto, é preciso adicionar mais três páginas. Para isso, localize e clique no botão Page. Em seguida, clique no botão Plus para adicionar as três páginas adicionais.

A próxima etapa é adicionar um plano de fundo personalizado. Acesse a seção Resource e localize a opção Background. Na opção 2, estão disponíveis três planos de fundo predefinidos para as páginas Industry, Medical e Vehicle. Para adicionar outro plano de fundo, clique no botão Add Widget da opção 3. Em seguida, na opção 4, clique no botão de três pontos (...) localizado à esquerda do botão Reset. Essa opção permite adicionar um plano de fundo personalizado para substituir o plano de fundo padrão utilizado na primeira página.

Na seção Pages, é possível selecionar o estilo de plano de fundo desejado entre as opções disponíveis, conforme mostrado na figura abaixo.

Agora, vamos adicionar um Widget personalizado. Para este cenário de demonstração, é utilizado um Multistate Widget personalizado que permite selecionar as páginas Industry, Medical e Vehicle. Primeiro, retorne à seção Controls da primeira página. Em seguida, acesse a seção Resource e localize a opção Multistate Widget. Clique no botão Add Widget da opção 2 e depois em Modify na opção 3. Na etapa 4, exclua todas as imagens padrão associadas ao Widget. Na etapa 5, clique em Add e, na etapa 6, selecione individualmente as imagens personalizadas dos Widgets armazenadas na pasta local do computador. Por fim, na etapa 7, clique em OK para adicionar as imagens personalizadas à lista do Multistate Widget, conforme mostrado na figura abaixo.

Retorne à seção Controls da primeira página. Seguindo o cenário da demonstração, selecione o Multistate Widget personalizado utilizado para escolher entre as páginas Industry, Medical e Vehicle. Arraste e solte o Widget na área de layout definida para a página. Com isso, a configuração da primeira página estará concluída.

Em seguida, aplique o mesmo procedimento às outras três páginas criadas. Na segunda página, selecione um Widget relacionado à aplicação Industry; na terceira, escolha um Widget para a aplicação Medical; e, na quarta, selecione um Widget relacionado à aplicação Vehicle. Arraste e solte cada Widget na área de layout da página correspondente.

Compilar e carregar o projeto
Após criar as quatro páginas e posicionar os respectivos Widgets em cada uma delas, verifique se a página inicial contém o Multistate Widget personalizado para selecionar as páginas Industry, Medical e Vehicle. Em seguida, prossiga com a compilação do projeto. Acesse a seção Page Information, localize o botão Device e clique em Device Properties. Confirme se as configurações do protocolo estão corretas, selecione a porta COM apropriada e defina a taxa de transmissão desejada. Clique em "Connect to Device" para estabelecer a conexão com o dispositivo. Por fim, clique no botão Upload, conforme mostrado na figura abaixo, para iniciar o carregamento do projeto. Aguarde até que o processo seja concluído.

Após a conclusão do carregamento do projeto, clique em OK, conforme mostrado na figura abaixo.

Após a conclusão do carregamento, a tela de inicialização será exibida conforme mostrado na figura abaixo.

Após a tela de inicialização, o Smart Display passa para a primeira página. Com isso, a compilação e o carregamento do projeto por meio do GUI Builder estão concluídos, conforme mostrado na figura abaixo.

Programar o Arduino como host
O trecho de código abaixo começa pela seção "includes", na qual são importadas as bibliotecas e os arquivos de cabeçalho necessários para o programa. Entre eles estão as bibliotecas Adafruit_SSD1327, Adafruit_FT6206, Adafruit_GFX e SPI, além de arquivos de cabeçalho personalizados, como mcp2515_can.h, lcdgfx_gui.h e lcdgfx.h.
Após a seção "includes", encontra-se a seção "defines", na qual são definidas constantes específicas e configurações de pinos. Essa seção inclui definições relacionadas ao encoder rotativo, às dimensões do display OLED, às configurações do barramento CAN, aos Widgets, entre outros parâmetros.
Por fim, uma instrução condicional verifica o tipo de microcontrolador utilizado e seleciona a interface serial correspondente. Se o código estiver sendo executado em um microcontrolador SAMD, é utilizada a interface SerialUSB. Caso contrário, é utilizada a interface Serial padrão.
Em resumo, o trecho de código abaixo importa as bibliotecas e os arquivos de cabeçalho necessários, define as principais constantes e configurações de pinos e seleciona a interface serial adequada de acordo com o tipo de microcontrolador utilizado.

Em seguida, são declaradas e inicializadas diversas variáveis e os pinos necessários para o funcionamento do programa. Por exemplo:
SPI_CS_PIN: esta constante é definida com o valor 9 e representa o pino Chip Select utilizado na comunicação SPI.
CAN_INT_PIN: esta constante é definida com o valor 2 e representa o pino de interrupção do barramento CAN.
CAN: uma instância da classe mcp2515_can é criada e inicializada com o valor SPI_CS_PIN. Ela é utilizada para a comunicação pelo barramento CAN.
ts: uma instância da classe Adafruit_FT6206 é criada e utilizada para as funções de touch.
display: uma instância da classe DisplaySSD1327_128x128_SPI é criada e inicializada com as configurações de pinos necessárias para a comunicação SPI com o display SSD1327.

A seção de código abaixo declara e inicializa diversos arrays formados por variáveis do tipo unsigned char. A função de cada array é descrita a seguir:
“configMsg”: array com 8 elementos do tipo unsigned char que representa uma mensagem de configuração.
`PageIndex_0` a `PageIndex_3`: cada um desses arrays contém 8 elementos do tipo unsigned char e representa os índices das páginas de 0 a 3.
“displayMode”: array com 8 elementos do tipo unsigned char que representa o modo de exibição.
Além disso, existem vários arrays denominados `stmp`, `stmp1`, `stmp2` e assim por diante, até `stmp12`. Cada um também contém 8 elementos do tipo unsigned char e é utilizado para armazenar bytes de dados relacionados a diferentes Widgets ou componentes. Esses arrays são utilizados, por exemplo, para armazenar os dados correspondentes aos respectivos Widgets.
Em resumo, esses arrays armazenam conjuntos de dados necessários ao funcionamento do programa, incluindo mensagens de configuração, índices de página, modos de exibição e dados relacionados aos Widgets.

A função “bitmapDemo” recebe o parâmetro inteiro “imageIndex” e é responsável por desenhar uma imagem no OLED Knob Display.
As imagens utilizadas, bem como suas respectivas posições e dimensões, são definidas no arquivo de cabeçalho "Image.h".

A função abaixo aguarda uma resposta pelo barramento CAN. Durante esse processo, o código executa intervalos de espera e verifica repetidamente o valor da variável de flag Recv. Quando a resposta é recebida, o valor de Recv é redefinido para 0.

O código abaixo define a função `changePageFunction`, responsável pela troca de páginas por meio da comunicação CAN. De acordo com o parâmetro `pageValue` recebido, a função envia a mensagem correspondente pelo barramento CAN para alterar a página exibida. A mensagem enviada varia conforme o valor de `pageValue` e utiliza os respectivos arrays `PageIndex`.

A função "handleTouchEvents" gerencia os eventos de toque no touchscreen. Ela detecta a entrada de toque, converte as coordenadas e executa diferentes ações de acordo com a duração do toque. A função também utiliza `changePageFunction` para atualizar a página atual de acordo com a operação realizada. Quando um toque longo é detectado, `changePageFunction` é chamada com o valor de "nowPage" para retornar à página inicial. Quando um toque curto é detectado, a função é chamada para acessar a página selecionada.

O trecho de código abaixo corresponde à função "setup" do Arduino, executada uma única vez quando o programa é iniciado. Essa função é responsável por inicializar os pinos, estabelecer a comunicação serial, configurar o display, inicializar o barramento CAN e configurar o controlador touchscreen.
Primeiro, a função configura os modos dos pinos pinA, pinB, pinC e EN_Pin. Dependendo da função de cada pino, eles são configurados como entradas com resistores de pull-up habilitados ou como saída. O pino de saída EN_Pin é então definido em nível lógico alto.
Em seguida, a comunicação serial é iniciada com uma taxa de 115200 baud, e o programa aguarda até que a conexão seja estabelecida.
Na sequência, o objeto do display é inicializado e são configurados parâmetros como fonte e cor. Uma imagem bitmap é desenhada em uma posição específica do display e, em seguida, é aplicado um intervalo de 2 segundos.
Uma interrupção é associada ao pino CAN_INT_PIN, e a função MCP2515_ISR é definida como a rotina executada quando essa interrupção ocorre. A interrupção é acionada na borda de descida do sinal.
O barramento CAN é então inicializado com uma taxa de 250 kbit/s. Caso a inicialização falhe, o código aguarda por um determinado período e tenta novamente. Após a inicialização bem-sucedida, o barramento CAN é configurado no modo normal e uma mensagem de confirmação é enviada pela interface serial.
Em seguida, as máscaras e os filtros CAN são configurados para aceitar todos os frames, incluindo frames com identificadores estendidos.
O controlador touchscreen também é inicializado. Caso a inicialização falhe, uma mensagem de erro é enviada pela interface serial e o programa entra em um loop infinito.
Quando o touchscreen é inicializado corretamente, uma mensagem de confirmação é enviada pela interface serial.

Esta função atua como uma rotina de serviço de interrupção (ISR) do controlador CAN MCP2515. Quando uma interrupção é acionada, a função atualiza uma flag e envia uma mensagem pela interface serial.

As funções abaixo estão relacionadas ao gerenciamento do estado do knob. A função “setKnobState” atualiza o estado atual do knob e configura um temporizador utilizado para detectar o estado de inatividade. Já a função “isKnobIdle” verifica, com base no valor desse temporizador, se o knob está inativo.

A função “redrawScreen()” atualiza o conteúdo exibido de acordo com o estado atual do knob. A função verifica se houve alteração de estado, limpa o display e desenha a imagem bitmap correspondente ao estado atual do knob.

O trecho a seguir da função “loop()” lê o estado de três pinos e determina um novo estado com base nos valores obtidos. Se o novo estado for diferente do estado anterior, o programa executa as ações correspondentes, incluindo a atualização do contador, de “MultiStateValue”, de “GaugeValue” e das variáveis de outros widgets.

Após o bloco anterior da função “loop()”, este trecho trata a condição em que o knob é girado em uma determinada direção. O código atualiza as variáveis correspondentes e executa as funções necessárias. Em seguida, a variável que armazena o estado anterior é atualizada para ser utilizada na próxima iteração da função “loop()”.

O trecho a seguir apresenta a continuação da função “loop()”. Após a execução do bloco anterior, são realizadas as seguintes operações.
A variável oldState é atualizada com o valor de newState.
Se nowPage for igual a 0, correspondente à página 1, o código verifica se MultiStateValue é diferente de LastMultiStateValue. Caso os valores sejam diferentes, LastMultiStateValue é atualizado com o valor de MultiStateValue, e o valor de MultiStateValue é armazenado no array stmp1. Em seguida, uma mensagem contendo o valor de MultiStateValue é enviada pelo barramento CAN utilizando o ID 0x716 do widget Multi-State.

Se nowPage for igual a 1, correspondente à página 2, o código realiza verificações semelhantes para as variáveis dos diferentes widgets, incluindo CircleProgressValue, HorizontalSliderValue, VerticalSliderValue, TemperatureValue, GaugeValue, FixedWidgetValue e IndicatorValue. Sempre que uma dessas variáveis for diferente do respectivo valor anterior, o valor anterior é atualizado e uma mensagem contendo o novo valor é enviada pelo barramento CAN utilizando o ID correspondente.

Se nowPage for igual a 2, correspondente à página 3, são realizadas verificações semelhantes para os widgets disponíveis nessa página, como CircleProgressValue, HorizontalSliderValue, GraphValue e AnimationValue. Sempre que uma dessas variáveis for diferente do respectivo valor anterior, o valor anterior é atualizado e uma mensagem contendo o novo valor é enviada pelo barramento CAN utilizando o ID correspondente.

Se nowPage for igual a 3, correspondente à página 4, o código realiza verificações semelhantes para os widgets disponíveis nessa página, como HorizontalSliderValue, GaugeValue, FixedWidgetValue, AnimationValue, IndicatorValue e ToggleButtonValue. Sempre que uma dessas variáveis for diferente do respectivo valor anterior, o valor anterior é atualizado e uma mensagem contendo o novo valor é enviada pelo barramento CAN utilizando o ID correspondente.

Após o bloco anterior da função loop(), a função handleTouchEvents() é chamada para processar os eventos de toque.
Se o knob estiver inativo (isKnobIdle() retornar 1), seu estado será definido como KNOB_IDLE por meio da função setKnobState(). Em seguida, a função redrawScreen() será chamada para atualizar a tela. Com isso, é concluída a parte principal do código do projeto de demonstração.

Após verificar e carregar o programa com sucesso pelo Arduino IDE, o conteúdo exibido no OLED Knob Display será alterado. A imagem do Smart Display será substituída pela indicação Touch ou Rotate. Nesse momento, tanto o Smart Display quanto o OLED Knob Display exibirão a página inicial padrão.

Agora é possível executar os cenários da demonstração. Na página inicial, gire o knob para selecionar uma das três páginas disponíveis: Industry, Medical ou Vehicle, conforme mostrado na imagem abaixo.

Após selecionar a opção desejada, um toque curto no OLED Knob Display faz com que o Smart Display alterne imediatamente para a página selecionada.

Para demonstrar novamente a operação, comece pela página inicial e gire o knob para selecionar uma das outras três páginas. Neste exemplo, será selecionada a página Medical.

Depois de selecionar a página Medical, basta tocar no OLED Knob Display para acessar a página correspondente no Smart Display.

Ao girar o knob para a esquerda ou para a direita, é possível controlar os diferentes widgets disponíveis na página, conforme ilustrado na imagem abaixo.

Ao realizar um toque longo no OLED Knob Display, o sistema retorna automaticamente à página inicial, correspondente à Page 1. Além disso, se o OLED Knob Display permanecer por 10 segundos sem nenhuma operação de rotação ou toque, o display será redefinido e retornará à página inicial.

Com isso, a demonstração utilizando o Smart Display de 10,1" e o OLED Knob Rotator Display está concluída. O exemplo permite selecionar e alternar entre diferentes páginas por meio de toques curtos ou longos no OLED Knob Rotator Display, além de interagir com os diferentes widgets disponíveis em cada página.
Para baixar o programa utilizado neste exemplo e consultar mais informações sobre o projeto de demonstração, acesse o GitHub.
A configuração física utilizada na demonstração é apresentada na imagem abaixo.

Este programa apresenta uma demonstração prática de como utilizar uma placa Arduino em conjunto com um OLED Knob Rotator Display e o protocolo Custom CAN ID para controlar diferentes páginas e navegar entre elas por meio de operações de toque curto ou longo. O exemplo também demonstra como interagir com e controlar os diferentes widgets disponíveis em cada página. O conteúdo serve como uma referência prática tanto para iniciantes quanto para desenvolvedores experientes que desejam aprofundar seus conhecimentos sobre programação do host e sua aplicação no controle de Smart Displays, especialmente aqueles sem funcionalidade touch.