Guia de desenvolvimento do computador industrial EC300 ARM

Os computadores industriais ARM da série EC funcionam com uma versão otimizada do Ubuntu, proporcionando uma base estável para cenários industriais. Além disso, vêm com o seguinte pré-instalado: Nó-vermelho e NeuronEX-Lite, proporcionando uma clara vantagem em termos de diferenciação.

  • Nó-vermelho: Uma ferramenta de programação visual de código aberto. Oferece uma interface de orquestração de fluxos baseada no navegador, na qual é possível ligar e configurar dispositivos e o processamento de dados através da função «arrastar e largar».
  • NeuronEX-Lite: Um software leve de computação na periferia, concebido para a IoT industrial. É compatível com vários protocolos industriais, permitindo uma conectividade rápida dos dispositivos e o processamento de dados em tempo real na ordem dos milissegundos.

Esta arquitetura de software “Double N” permite aos programadores criar rapidamente lógica de negócio através da funcionalidade de arrastar e largar (Node-RED), ao mesmo tempo que oferece funcionalidades profissionais, tais como análise de protocolos, ligação de dispositivos e recolha de dados (NeuronEX-Lite). A série EC consegue uma combinação perfeita de desenvolvimento rápido de negócios e aquisição profissional de dados industriais, melhorando significativamente a eficiência do desenvolvimento e a fiabilidade operacional nas soluções de IoT industrial.

Para ajudar os programadores a compreender e a utilizar os gateways EC, lançámos anteriormente o Guia de Desenvolvimento do EC100. Apresentamos agora o seu guia complementar — Guia de Desenvolvimento do EC300. Antes de começar, se se tiver esquecido de informações básicas, como o URL do backend do gateway EC, consulte o Guia de Aprendizagem do Gateway da Série EC.

Preparação do desenvolvimento

Este guia aplica-se aos seguintes modelos da série EC300:

  • EC300
  • EC301
  • EC300P

Ferramentas

Categoria de ferramentas Ferramenta recomendada Descarregar
Ferramenta SSH MobaXterm ou qualquer outra ferramenta SSH da sua preferência MobaXterm_Installer_v20.5.zip
Ferramenta de depuração em série O XCOM ou qualquer outra ferramenta de depuração em série da sua preferência XCOM V2.6.zip
Ferramenta de hardware Ferramentas auxiliares comuns de depuração: CAN para USB / RS485 para USB / RS232 para USB. Prepare-se NENHUM
Cadeia de ferramentas de compilação cruzada gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu.zip

Ambiente de software

Software Versão
SO Linux incorporado
Kernel Linux 5.10.209
Nodejs v22.17.0
Python python3.8
Concha bash
GLIBC GLIBC_2.31
Docker V26.1.3
Biblioteca QT V5.15.10
Ambiente de trabalho Xfce4

Recursos do dispositivo

Categoria Detalhes Notas
Armazenamento 16G no total Espaço livre predefinido de fábrica ≈ 9 GB;
Ampliável através de cartão SD ou SSD M.2 NVMe
RAM 2G

Espaço livre predefinido de fábrica ≈ 1,4 GB

CPU 4*Cortex-A53@1.8GHz Espaço livre predefinido de fábrica ≈ 98%
NPU 1 TPOS Espaço livre predefinido de fábrica ≈ 100%

Interfaces periféricas

1. Porta de depuração

A série EC300 permite a depuração do sistema através da porta Type-C.

2. Interfaces seriais

Interface Ficheiro do dispositivo EC300 EC300P EC301
RS485-1 /dev/ttyS7 ✔️ ✔️ ✔️
RS485-2 /dev/ttyS2 ✔️
RS232 /dev/ttyS3 ✔️

Nota: O RS485 gere automaticamente o envio e a receção; os utilizadores não precisam de alternar manualmente.

2.1 Teste rápido

Utilize o minicom ferramenta de teste. Para obter instruções detalhadas, pode pesquisar no Google ou consultar uma IA.

2.2 Exemplo em C

uart_example.c :


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUFFER_SIZE 256

typedef struct {
    int baud_rate; // taxa de transmissão
    int data_bits; // bits de dados (5, 6, 7, 8)
    int stop_bits;      // bits de paragem (1,2)
    char parity; // bit de paridade (N: Nenhuma, O: Ímpar, E: Par)
} SerialConfig;

int set_serial_attr(int fd, SerialConfig *config)
{
    struct termios tty;

 if (tcgetattr(fd, &tty) baud_rate) {
 case 9600:   speed = B9600; break;
 case 19200:  speed = B19200; break;
        case 38400:  speed = B38400; break;
 case 57600:  speed = B57600; break;
 case 115200: speed = B115200; break;
        padrão:
 fprintf(stderr, "Velocidade de transmissão não suportada, a utilizar 115200\n");
 velocidade = B115200;
    }
    cfsetispeed(&tty, velocidade);
    cfsetospeed(&tty, velocidade);

 // definir os bits de dados
    tty.c_cflag &= ~CSIZE;
    switch (config->data_bits) {
        case 5: tty.c_cflag |= CS5; break;
 case 6: tty.c_cflag |= CS6; break;
 case 7: tty.c_cflag |= CS7; break;
        case 8: tty.c_cflag |= CS8; break;
 default:
 fprintf(stderr, "Bits de dados não suportados, a utilizar 8\n");
 tty.c_cflag |= CS8;
    }

    // definir os bits de paragem
    if (config->stop_bits == 2) {
 tty.c_cflag |= CSTOPB;
    } else {
 tty.c_cflag &= ~CSTOPB;
    }

    // definir o bit de paridade
    switch (config->parity) {
 case 'N': case 'n':
 tty.c_cflag &= ~PARENB;  // Paridade: Nenhuma
 break;
        case 'O': case 'o':
 tty.c_cflag |= PARENB;   // Paridade: ímpar
 tty.c_cflag |= PARODD;
 break;
 case 'E': case 'e':
            tty.c_cflag |= PARENB;   // Paridade: Par
 tty.c_cflag &= ~PARODD;
 break;
 default:
 fprintf(stderr, "Paridade não suportada, a utilizar N\n");
            tty.c_cflag &= ~PARENB;
    }

 // Outras configurações
    tty.c_cflag |= (CLOCAL | CREAD);    // Ativar os modos de receção e local
    tty.c_cflag &= ~CRTSCTS; // Sem controlo de fluxo por hardware

 tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // Modo de entrada bruta
    tty.c_oflag &= ~OPOST;              // Modo de saída bruta

 tty.c_cc[VMIN] = 1; // Caracteres mínimos de leitura
    tty.c_cc[VTIME] = 0; // Tempo limite de leitura (Unidade: 0,1 segundos)

    if (tcsetattr(fd, TCSANOW, &tty) < 0) {
 perror("tcsetattr");
 return -1;
    }

    return 0;
}

int main(int argc, char *argv[])
{
    int fd;
    char *portname;

    if (argc < 2) {
 fprintf(stderr, "Utilização: %s \n", argv[0]);
 exit(EXIT_FAILURE);
    }

    portname = argv[1];

 // Configurar os parâmetros da porta série
    SerialConfig config = {
 .baud_rate = 115200,  // taxa de transmissão
 .data_bits = 8, // bits de dados
        .stop_bits = 1, // bits de paragem
 .parity = 'N' // bit de paridade (N: Nenhuma, O: Ímpar, E: Par)
    };

    fd = open(portname, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (fd < 0) {
 perror("open");
 exit(EXIT_FAILURE);
    }

    if (set_serial_attr(fd, &config)) {
 close(fd);
 exit(EXIT_FAILURE);
    }

    printf("Teste de eco da porta série a decorrer em %s\n", portname);
    printf("Configuração: %d de baud, %d de bits de dados, %d de bit de paragem, %c de paridade\n",
           config.baud_rate, config.data_bits, config.stop_bits, config.parity);
    printf("Prima Ctrl+C para sair.\n");

    fd_set readfds;
    char buffer[BUFFER_SIZE];
    int n;

 while (1) {
 FD_ZERO(&readfds);
 FD_SET(fd, &readfds);

        // Aguarda indefinidamente por dados (tempo limite desativado)
 if (select(fd + 1, &readfds, NULL, NULL, NULL)  0) {
 buffer[n] = '\0';
                printf("Recebidos %d bytes: %s\n", n, buffer);

 // Repetir dados
 write(fd, buffer, n);
            } else if (n < 0) {
 if (errno != EAGAIN && errno != EWOULDBLOCK) {
 perror("read");
 break;
 }
 }
 }
    }

    close(fd);
    return 0;
}


Compilar: gcc uart_example.c -o uart_example

Teste de funcionamento do RS485-1: ./uart_example /dev/ttyS7

2.3 Exemplo em Python

uart_example.py: 


import serial
import select
import sys
import tty

class SerialConfig:
    def __init__(self):
    self.baud_rate = 115200  taxa de transmissão #
        self.data_bits = 8 # bits de dados
 self.stop_bits = 1 # bits de paragem
 self.parity = 'N' # bit de paridade (N: Nenhuma, O: Ímpar, E: Par)

def set_serial_config(ser, config):
    """Configurar parâmetros da porta série"""
    # definir a taxa de transmissão
    ser.baudrate = config.baud_rate
    
    # definir os bits de dados
    if config.data_bits == 5:
 ser.bytesize = serial.FIVEBITS
    elif config.data_bits == 6:
 ser.bytesize = serial.SIXBITS
    elif config.data_bits == 7:
 ser.bytesize = serial.SEVENBITS
    else:  # O valor por defeito é 8 bits
 ser.bytesize = serial.EIGHTBITS
 
 # definir os bits de paragem
    if config.stop_bits == 2:
 ser.stopbits = serial.STOPBITS_TWO
    else:  # O valor predefinido é 1 bit
 ser.stopbits = serial.STOPBITS_ONE
    
    # definir o bit de paridade
    if config.parity.upper() == 'O':
 ser.parity = serial.PARITY_ODD
    elif config.parity.upper() == 'E':
 ser.parity = serial.PARITY_EVEN
    caso contrário:  # O valor predefinido é None (sem paridade)
 ser.parity = serial.PARITY_NONE
 
 # Desativar o controlo de fluxo por hardware
    ser.rtscts = False
    # Desativar controlo de fluxo por software
    ser.xonxoff = False
    # Definir tempo de espera
    ser.timeout = 0.1  # Tempo de espera de 100 ms
 
 return ser

def main():
    if len(sys.argv) < 2:
 print(f"Utilização: {sys.argv[0]} ")
 print(f"Exemplo: {sys.argv[0]} /dev/ttyS7")
        sys.exit(1)
 
 portname = sys.argv[1]
    buff_size = 256
 
 # Inicializar a configuração da porta série
    config = SerialConfig()
    
    try:
 # Abrir a porta série
 ser = serial.Serial()
 ser.port = portname
 # Aplicar a configuração e abrir a porta série
 ser = set_serial_config(ser, config)
        ser.open()
 
 if not ser.is_open:
 print("Não foi possível abrir a porta série")
 sys.exit(1)
 
 print(f"Porta série {portname} aberta com sucesso")
        print(f"Configurado: velocidade de transmissão {config.baud_rate}, bits de dados {config.data_bits}, "
 f"bits de paragem {config.stop_bits}, paridade {config.parity}")
 print("Prima Ctrl+C para sair do programa")
        
        # Utilizar o select para monitorizar os dados da porta série
 while True:
 # Aguardar até que os dados da porta série estejam legíveis
 readable, _, _ = select.select([ser.fileno()], [], [], None)
            
            if readable:
 # Ler dados
 data = ser.read(buff_size - 1)
 if data:
 # Tentar descodificar como uma cadeia de caracteres; se falhar, apresentar em hexadecimal
 try:
                        text = data.decode('utf-8')
 except UnicodeDecodeError:
 text = f"[Dados binários] {data.hex()}"
 
 print(f"Recebidos {len(data)} bytes: {text}")
                    
                    # Enviar os dados de volta
 ser.write(data)
 
 except serial.SerialException as e:
 print(f"Erro na porta série: {e}")
 sys.exit(1)
    exceto KeyboardInterrupt:
 print("\nInterrupção do utilizador, a sair do programa")
    finalmente:
 se 'ser' estiver em locals() e ser.is_open:
 ser.close()
 print("Porta série fechada")

if __name__ == "__main__":
    main()

Primeiro, instale as dependências necessárias:
pip install pyserial

Teste de funcionamento do RS485-1: python uart_example.py /dev/ttyS7

3. LED

A série EC300 tem, no total, 6 LEDs, 4 dos quais são programáveis pelo utilizador.

Hardware Índice IO Número IO Chip Notas
POW / / / Indicador de alimentação
CORRER 50 GPIO1_C2 gpiochip1 Utilizado pelo sistema; pisca quando está em funcionamento
LED1 137 GPIO4_B1 gpiochip4 Programável pelo utilizador
LED2 51 GPIO1_C3 gpiochip1 Programável pelo utilizador
LED3 143 GPIO4_B7 gpiochip4 Programável pelo utilizador
LED4 52 GPIO1_C4 gpiochip1 Programável pelo utilizador

Nota: O índice IO é calculado a partir do número IO.

3.1 Teste rápido

led_example.sh:

#!/bin/bash

# Verifica se foi fornecido um índice GPIO
if [ -z "$1" ]; then
    echo "Utilização: $0 "
    exit 1
fi

GPIO=$1
GPIO_PATH="/sys/class/gpio/gpio$GPIO"
EXPORT_PATH="/sys/class/gpio/export"

# Verificar se o GPIO já foi exportado
if [ ! -d "$GPIO_PATH" ]; then
    echo "A exportar o GPIO $GPIO..."
    echo "$GPIO" > "$EXPORT_PATH"
    if [ $? -ne 0 ]; then
 echo "Falha ao exportar o GPIO $GPIO. Verifique as permissões ou se está a ser utilizado."
        exit 1
    fi
else
    echo "O GPIO $GPIO já está exportado."
fi

# Definir a direção do GPIO para saída
echo "out" > "$GPIO_PATH/direction"

# Definir o valor inicial para nível baixo
echo 0 > "$GPIO_PATH/value"

echo "A alternar o GPIO $GPIO a cada 1 segundo. Prima Ctrl+C para parar."

# Iniciar a alternância num ciclo
while true; do
    echo 1 > "$GPIO_PATH/value"
    sleep 1
    echo 0 > "$GPIO_PATH/value"
    sleep 1
done

Teste:
bash led_example.sh 137

3.2 Exemplo em C

led_example.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/gpio.h>
#include <errno.h>

#define GPIO_CHIP "/dev/gpiochip4"  // Use gpiochip1
#define GPIO_LINE 137%32             // The offset of the GPIO to be controlled on chip1
#define PERIOD_US 500000            // Period (microseconds)

int main() {
    struct gpiohandle_request req;
    struct gpiohandle_data data;
    int fd, ret;

    // Open the GPIO character device
    fd = open(GPIO_CHIP, O_RDWR);
    if (fd < 0) {
        perror("Unable to open the GPIO device");
        return EXIT_FAILURE;
    }

    // Set up the GPIO request
    memset(&req, 0, sizeof(req));
    req.lineoffsets[0] = GPIO_LINE;  // GPIO line number
    req.flags = GPIOHANDLE_REQUEST_OUTPUT;  // Set to output mode
    req.default_values[0] = 0;       // Set the initial value to low level
    strcpy(req.consumer_label, "gpio-toggle");  // Consumer label
    req.lines = 1;                   // Number of GPIO lines to control

    // Request GPIO control
    ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
    if (ret < 0) {
        perror("Unable to acquire GPIO control");
        close(fd);
        return EXIT_FAILURE;
    }

    // Close the GPIO character device (the line handle has already been acquired)
    close(fd);

    printf("Controlling GPIO chip1 line %d with a period of %.1f seconds...\n", 
           GPIO_LINE, PERIOD_US/1000000.0);

    // Toggle GPIO level periodically
    while (1) {
        data.values[0] = 1;  // High level
        ret = ioctl(req.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
        if (ret < 0) {
            perror("Failed to set GPIO to high level");
            break;
        }
        usleep(PERIOD_US/2);

        data.values[0] = 0;  // Low level
        ret = ioctl(req.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
        if (ret < 0) {
            perror("Failed to set GPIO to low level");
            break;
        }
        usleep(PERIOD_US/2);
    }

    // Close the GPIO line handle
    close(req.fd);

    return EXIT_SUCCESS;
}

Compilar: gcc led_exemplo.c -o led_exemplo
Teste: ./exemplo_led

3.3 Exemplo em Python

led_example.py:

import os
import time
import sys

# Definir constantes
GPIO_CHIP = 4 # Número do chip GPIO
GPIO_LINE = 137 % 32    # Desvio do GPIO a controlar na linha
PERIOD_US = 500000 # Período (microssegundos)

def export_gpio(gpio_num):
    """Exportar GPIO"""
    try:
 with open('/sys/class/gpio/export', 'w') as f:
            f.write(str(gpio_num))
 time.sleep(0.1)  # Aguardar até que a exportação esteja concluída
 return True
    except IOError as e:
 if "já existe" in str(e):
            return True  # GPIO já exportado
 print(f"Falha na exportação do GPIO: {e}")
 return False

def set_gpio_direction(gpio_num, direction):
    """Definir a direção do GPIO"""
    try:
 with open(f'/sys/class/gpio/gpio{gpio_num}/direction', 'w') as f:
 f.write(direction)
        return True
    except IOError as e:
 print(f"Falha ao definir a direção do GPIO: {e}")
 return False

def set_gpio_value(gpio_num, value):
    """Definir valor do GPIO"""
    try:
 with open(f'/sys/class/gpio/gpio{gpio_num}/value', 'w') as f:
 f.write(str(value))
        return True
    except IOError as e:
 print(f"Falha ao definir o valor do GPIO: {e}")
 return False

def unexport_gpio(gpio_num):
    """Desativar a exportação do GPIO"""
    try:
 with open('/sys/class/gpio/unexport', 'w') as f:
 f.write(str(gpio_num))
 return True
    except IOError as e:
        print(f"Falha ao anular a exportação do GPIO: {e}")
 return False

def cleanup_gpio(actual_gpio):
    """Limpar recursos do GPIO: primeiro desligar o LED, depois anular a exportação do GPIO"""
    try:
 print("Desligar o LED...")
 # Definir o GPIO para o nível baixo primeiro para desligar o LED
 if set_gpio_value(actual_gpio, 0):
 print("LED desligado")
        else:
 print("Falha ao desligar o LED")
 
 #  Aguarda um breve período para garantir que o LED está desligado
 time.sleep(0.05)
 
 # Em seguida, retira o GPIO da exportação
        print("Desativar GPIO...")
 if unexport_gpio(actual_gpio):
 print("GPIO desativado com sucesso")
 else:
 print("Falha ao desativar o GPIO")
            
    except Exception as e:
 print(f"Ocorreu um erro durante a limpeza dos recursos do GPIO: {e}")

def main():
    # Calcular o número real do GPIO
    actual_gpio = GPIO_CHIP * 32 + GPIO_LINE
 
 print(f"Chip GPIO: {GPIO_CHIP}, linha GPIO: {GPIO_LINE}")
    print(f"Número real do GPIO: {actual_gpio}")
    print(f"Período de piscar: {PERIOD_US/1000000.0:.1f} segundos")
    print("Prima Ctrl+C para parar o programa")
    
    try:
 # Exportar GPIO
 if not export_gpio(actual_gpio):
 return 1
 
 # Definir o GPIO para o modo de saída
        if not set_gpio_direction(actual_gpio, 'out'):
 cleanup_gpio(actual_gpio)
 return 1
 
 print("Iniciar o controlo do piscar do LED...")
        
        # Alternar o nível do GPIO periodicamente
 enquanto True:
 # Definir para nível alto
 se não set_gpio_value(actual_gpio, 1):
 break
            time.sleep(PERIOD_US / 2000000.0)  # Aguardar meio período
 
 # Definir para nível baixo
 se não set_gpio_value(actual_gpio, 0):
                break
 time.sleep(PERIOD_US / 2000000.0)  # Aguardar meio período
 
 except KeyboardInterrupt:
 print("\nPrograma interrompido")
    exceto Exception como e:
 print(f"Ocorreu um erro: {e}")
 import traceback
 traceback.print_exc()
    finalmente:
 # Limpar recursos GPIO: primeiro desligar o LED, depois desativar a exportação do GPIO
        print("Iniciar limpeza dos recursos GPIO...")
 cleanup_gpio(actual_gpio)
 print("Programa terminado")

if __name__ == "__main__":
    sys.exit(main())

Teste: 
python led_example.py

4. CAN

O EC301 possui duas interfaces CAN:

Interface de hardware Interface de rede
CAN1 [H1 L1] can0
CAN2 [H2 L2] can1

Guia de desenvolvimento do computador industrial EC300 ARM

4.1 Teste rápido

Ligue o CAN1 ao CAN2; teste através da linha de comandos:

can_example.sh:

#!/bin/bash
#Primeiro, inicialize o can0 e o can1 com uma taxa de transmissão de 500k e, em seguida, configure o can0 para receber dados e o can1 para enviar dados.

ip link set can0 down
ip link set can1 down

ip link set can0 type can bitrate 500000
ip link set can1 type can bitrate 500000

ip link set can0 up
ip link set can1 up

echo 4096 > /sys/class/net/can0/tx_queue_len
echo 4096 > /sys/class/net/can1/tx_queue_len

candump can0 &

cansend can1 001234EF#00.11.22.33.44.55.66.77

4.2 Exemplo em C

can_example.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <fcntl.h>
#include <errno.h>
#include <stdint.h>
#include <sys/wait.h> 

#define SEND_CAN "can0"   // CAN interface for sending data
#define RECV_CAN "can1"   // CAN interface for receiving data
#define BITRATE 500000    // Baud rate 500k
#define SAMPLE_POINT 0.8  
#define TX_QUEUE_LEN 4096 

// Configure CAN interface
int setup_can_interface(const char *ifname) {
    char cmd[512];
    FILE *fp;
    
    //  Close interface
    snprintf(cmd, sizeof(cmd), "ip link set %s down", ifname);
    system(cmd);  
    
    // Set CAN parameters
    snprintf(cmd, sizeof(cmd), 
             "ip link set %s type can bitrate %d sample-point %.1f", 
             ifname, BITRATE, SAMPLE_POINT);
    if (system(cmd) != 0) {
        fprintf(stderr, "Failed to configure CAN interface %s\n", ifname);
        return -1;
    }
    
    // Set TX queue length
    snprintf(cmd, sizeof(cmd), "/sys/class/net/%s/tx_queue_len", ifname);
    fp = fopen(cmd, "w");
    if (fp == NULL) {
        perror("Failed to open tx_queue_len");
        return -1;
    }
    fprintf(fp, "%d", TX_QUEUE_LEN);
    fclose(fp);
    
    // Enable interface
    snprintf(cmd, sizeof(cmd), "ip link set %s up", ifname);
    if (system(cmd) != 0) {
        fprintf(stderr, "Failed to bring up CAN interface %s\n", ifname);
        return -1;
    }
    
    printf("CAN interface %s configured:\n", ifname);
    printf("  Bitrate: %d\n", BITRATE);
    printf("  Sample point: %.1f\n", SAMPLE_POINT);
    printf("  TX queue length: %d\n", TX_QUEUE_LEN);
    
    return 0;
}

//Monitor CAN interface data in a child process
void receive_can_messages() {
    int recv_sock;
    struct sockaddr_can recv_addr;
    struct ifreq recv_ifr;
    struct can_frame frame;
    int nbytes;
    
    // Initialize receiver socket
    if ((recv_sock = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
        perror("Receive socket creation failed");
        exit(EXIT_FAILURE);
    }
    
    // Bind receiver interface
    strcpy(recv_ifr.ifr_name, RECV_CAN);
    if (ioctl(recv_sock, SIOCGIFINDEX, &recv_ifr) < 0) {
        perror("IOCTL for receive interface failed");
        close(recv_sock);
        exit(EXIT_FAILURE);
    }
    recv_addr.can_family = AF_CAN;
    recv_addr.can_ifindex = recv_ifr.ifr_ifindex;
    if (bind(recv_sock, (struct sockaddr *)&recv_addr, sizeof(recv_addr)) < 0) {
        perror("Receive socket bind failed");
        close(recv_sock);
        exit(EXIT_FAILURE);
    }
    
    printf("Receive process started (listening on %s)...\n", RECV_CAN);
    
  
    while (1) {
        nbytes = read(recv_sock, &frame, sizeof(frame));
        if (nbytes < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                continue;  
            }
            perror("CAN frame read failed");
            break;
        }
        
       
        if (nbytes == CAN_MTU) {
            printf("[%s] Received CAN frame: ID 0x%08X, DLC %d, data: ", 
                   RECV_CAN, frame.can_id, frame.can_dlc);
            for (int i = 0; i < frame.can_dlc; i++) { 
                printf("%02X ", frame.data[i]); 
            } 
            printf("\n"); 
       } else { 
            printf("Received invalid frame (size: %d bytes)\n", nbytes);
       } 
    } 
    close(recv_sock); 
    exit(EXIT_SUCCESS); 
 }
 // Send CAN frame
 int send_can_frame(int sock, uint32_t can_id, uint8_t *data, uint8_t len) { 
     struct can_frame frame; 
     if (len > CAN_MAX_DLEN) { 
        fprintf(stderr, "Data length too long (max %d bytes)\n", CAN_MAX_DLEN);
        return -1;
    }
    

    memset(&frame, 0, sizeof(frame));
    frame.can_id = can_id;     // Frame ID
    frame.can_dlc = len;       // Data length
    memcpy(frame.data, data, len);  // Data content
    
    // Transmit frame
    int nbytes = write(sock, &frame, sizeof(frame));
    if (nbytes != sizeof(frame)) {
        perror("CAN frame send failed");
        return -1;
    }
    
    printf("[%s] Sent CAN frame: ID 0x%08X, data: ", SEND_CAN, can_id);
    for (int i = 0; i < len; i++) {
        printf("%02X ", data[i]);
    }
    printf("\n");
    
    return 0;
}

int main() {
    int send_sock;
    struct sockaddr_can send_addr;
    struct ifreq send_ifr;
    pid_t recv_pid; 

    //  Configure CAN interfaces for sender and receiver
    if (setup_can_interface(SEND_CAN) < 0) {
        return 1;
    }
    if (setup_can_interface(RECV_CAN) < 0) {
        return 1;
    }

    //  Create child process for receiving data
    recv_pid = fork();
    if (recv_pid < 0) {
        perror("Failed to create receive process");
        return 1;
    } else if (recv_pid == 0) {
        receive_can_messages();
    }

    // Ensure the receiving child process is started and ready
    usleep(100000);  // 100ms

    //  Initialize sender socket
    if ((send_sock = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
        perror("Send socket creation failed");
        kill(recv_pid, SIGTERM); 
        return 1;
    }
    
    // Specify CAN interface
    strcpy(send_ifr.ifr_name, SEND_CAN);
    if (ioctl(send_sock, SIOCGIFINDEX, &send_ifr) < 0) {
        perror("IOCTL for send interface failed");
        close(send_sock);
        kill(recv_pid, SIGTERM);
        return 1;
    }
    send_addr.can_family = AF_CAN;
    send_addr.can_ifindex = send_ifr.ifr_ifindex;
    if (bind(send_sock, (struct sockaddr *)&send_addr, sizeof(send_addr)) < 0) {
        perror("Send socket bind failed");
        close(send_sock);
        kill(recv_pid, SIGTERM);
        return 1;
    }

   // Send test CAN frame (ID 0x123, data 00.11.22.33.44.55.66.77)
    uint8_t test_data[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
    if (send_can_frame(send_sock, 0x12345678, test_data, sizeof(test_data)) < 0) {
        close(send_sock);
        kill(recv_pid, SIGTERM);
        return 1;
    }

    
    sleep(1);

    // Close sender socket and terminate receiving child process
    close(send_sock);
    kill(recv_pid, SIGTERM);  
    waitpid(recv_pid, NULL, 0); 

    printf("Program exited\n");
    return 0;
}

Compilar: gcc can_exemplo.c -o can_exemplo

Teste: ./exemplo_de_can

4.3 Exemplo em Python

can_example.py: 

import time
import threading
import subprocess
import can

# Configure constants
SEND_CAN = "can0"    # CAN interface for sending data
RECV_CAN = "can1"    # CAN interface for receiving data
BITRATE = 500000     # Baud rate 500k
SAMPLE_POINT = 0.8   # Sample point
TX_QUEUE_LEN = 4096  # Transmit queue length

class CANExample:
    def __init__(self):
        self.send_bus = None
        self.recv_bus = None
        self.receive_thread = None
        self.running = False
        
    def setup_can_interface(self, ifname):
        """Configure CAN interface"""
        print(f"Configuring CAN interface {ifname}...")
        
        try:
            # Close interface
            cmd = f"sudo ip link set {ifname} down"
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
            
            # Set CAN parameters
            cmd = f"sudo ip link set {ifname} type can bitrate {BITRATE} sample-point {SAMPLE_POINT}"
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
            if result.returncode != 0:
                print(f"Failed to configure CAN interface {ifname}: {result.stderr}")
                return -1
            
            # Set TX queue length
            queue_file = f"/sys/class/net/{ifname}/tx_queue_len"
            try:
                with open(queue_file, 'w') as fp:
                    fp.write(str(TX_QUEUE_LEN))
            except Exception as e:
                print(f"Fail to set TX queue length: {e}")
                return -1
            
            # Enable interface
            cmd = f"sudo ip link set {ifname} up"
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
            if result.returncode != 0:
                print(f"Failed to enable CAN interface {ifname}: {result.stderr}")
                return -1
            
            print(f"CAN interface {ifname} configured successfully:")
            print(f"  Baud rate: {BITRATE}")
            print(f"  Sample point: {SAMPLE_POINT}")
            print(f"  Transmit queue length: {TX_QUEUE_LEN}")
            
            return 0
            
        except Exception as e:
            print(f"An error occurred while configuring CAN interface {ifname}: {e}")
            return -1
    
    def setup_can_interfaces(self):
        """Configure sending and receiving CAN interfaces"""
        # Configure CAN interfaces for sender and receiver
        if self.setup_can_interface(SEND_CAN) < 0:
            return False
        if self.setup_can_interface(RECV_CAN) < 0:
            return False
        
        print("CAN interface configured successfully")
        return True
        
    def receive_can_messages(self):
        """Thread function for receiving CAN messages"""
        try:
            # Create receiving bus
            self.recv_bus = can.Bus(interface='socketcan', channel=RECV_CAN, bitrate=BITRATE)
            print(f"Receiving process started (listening on {RECV_CAN})...")
            
            while self.running:
                # Set timeout to avoid blocking
                msg = self.recv_bus.recv(timeout=1.0)
                if msg is not None:
                    print(f"[{RECV_CAN}] Received CAN frame: ID 0x{msg.arbitration_id:08X}, "
                          f"DLC {msg.dlc}, data: ", end="")
                    for i, byte in enumerate(msg.data):
                        if i < msg.dlc: print(f"{byte:02X} ", end="") 
                    print() 
         except Exception as e: 
            print(f"Receive error: {e}")
         finally: 
            if self.recv_bus:
               self.recv_bus.shutdown() 
    def send_can_frame(self, can_id, data): 
        """Send CAN frame""" 
        if not self.send_bus: 
            print("Error: sending bus not initialized") 
            return False 
        
        if len(data) > 8:
            print(f"Error: data length too long (maximum 8 bytes)")
            return False
        
        try:
            # Create CAN message
            msg = can.Message(
                arbitration_id=can_id,
                data=data,
                is_extended_id=True
            )
            
            # Send message
            self.send_bus.send(msg)
            
            print(f"[{SEND_CAN}] Send CAN frame: ID 0x{can_id:08X}, data: ", end="")
            for byte in data:
                print(f"{byte:02X} ", end="")
            print()
            
            return True
            
        except Exception as e:
            print(f"Fail to send: {e}")
            return False
    
    def run(self):
        """Main running function"""
        print("CAN bus communication test program")
        print("=" * 30)
        
        # Configure CAN interface
        if not self.setup_can_interfaces():
            print("Failed to configure CAN interface, program exiting")
            return 1
        
        # Create receiving thread
        self.running = True
        self.receive_thread = threading.Thread(target=self.receive_can_messages)
        self.receive_thread.daemon = True
        self.receive_thread.start()
        
        # Wait for receiving thread to start
        time.sleep(0.1)
        
        try:
            # Initialize sending bus
            self.send_bus = can.Bus(interface='socketcan', channel=SEND_CAN, bitrate=BITRATE)
            print(f"Sending bus initialized (interface: {SEND_CAN})")
            
            # Send test CAN frame
            test_data = bytes([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77])
            if not self.send_can_frame(0x12345678, test_data):
                return 1
            
            # Wait for message transmission to complete
            time.sleep(1)
            
        except Exception as e:
            print(f"Program error: {e}")
            return 1
        finally:
            # Clean up resources
            self.running = False
            
            if self.receive_thread and self.receive_thread.is_alive():
                self.receive_thread.join(timeout=2.0)
            
            if self.send_bus:
                self.send_bus.shutdown()
        
        print("Program exiting")
        return 0

def main():
    """Program entry point"""
    can_example = CANExample()
    return can_example.run()

if __name__ == "__main__":
    main()

Primeiro, instale as dependências necessárias:   pip install python-can

Teste:  python can_example.py

5. DI

O EC301 possui duas interfaces DI; intervalo de deteção: 5–28 V

Interface de hardware Índice IO Número IO Grupo Chip
DI-1 19 GPIO0_C3 gpiochip0
DI-2 21 GPIO0_C5 gpiochip0

 

5.1 Teste rápido

di_example.sh:

#!/bin/bash

# Verifica se foi fornecido um índice GPIO
if [ -z "$1" ]; then
    echo "Utilização: $0 "
    exit 1
fi

GPIO=$1
GPIO_PATH="/sys/class/gpio/gpio$GPIO"
EXPORT_PATH="/sys/class/gpio/export"

# Verificar se o GPIO já foi exportado
if [ ! -d "$GPIO_PATH" ]; then
    echo "A exportar o GPIO $GPIO..."
    echo "$GPIO" > "$EXPORT_PATH"
    if [ $? -ne 0 ]; then
 echo "Falha ao exportar o GPIO $GPIO. Verifique as permissões ou se está a ser utilizado."
        exit 1
    fi
else
    echo "O GPIO $GPIO já está exportado."
fi

# Definir a direção do GPIO para entrada
echo "in" > "$GPIO_PATH/direction"

echo "Ler o valor do GPIO $GPIO. Prima Ctrl+C para parar."
# Iniciar a leitura num ciclo
while true; do
    cat "$GPIO_PATH/value"
    sleep 1
done

Teste:  bash di_example.sh 19

Faça o teste ligando o terminal positivo de uma fonte de alimentação de 12 V ao DI1 e o terminal negativo ao COM1; quando recebe 12 V, imprime 0; quando não recebe alimentação, imprime 1. Para obter detalhes sobre a ligação, consulte o especificações de cada produto da série EC300.

5,2 °C Exemplo

di_example.c:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define GPIO_CHIP "/dev/gpiochip0"  // Dispositivo controlador GPIO
#define GPIO_LINE 19 // Número da linha GPIO a testar (interface DI)
#define SAMPLE_INTERVAL_1S 1 // Intervalo de amostragem (segundos)
#define MAX_SAMPLES 100 // Número máximo de amostras

int main() {
    struct gpiohandle_request req;
    struct gpiohandle_data data;
    int fd, ret;
    int sample_count = 0;
    struct timespec start_time, current_time;

    // Abrir dispositivo de caracteres GPIO
    fd = open(GPIO_CHIP, O_RDWR);
    if (fd < 0) {
 perror("Não foi possível abrir o dispositivo GPIO");
 return EXIT_FAILURE;
    }

    // Definir pedido GPIO
    memset(&req, 0, sizeof(req));
    req.lineoffsets[0] = GPIO_LINE;  // Número da linha GPIO
    req.flags = GPIOHANDLE_REQUEST_INPUT;  // Definir para o modo de entrada
    strcpy(req.consumer_label, "di-monitor");  // Etiqueta do consumidor
    req.lines = 1;                   // Número de linhas GPIO a controlar

 // Solicitar controlo do GPIO
    ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
    if (ret < 0) {
        perror("Não foi possível obter o controlo do GPIO");
 close(fd);
 return EXIT_FAILURE;
    }

 // Fechar o dispositivo de caracteres GPIO (identificador da linha já obtido)
    close(fd);

    printf("A monitorizar a linha %d do chip GPIO 0 (interface DI), intervalo de amostragem de %.1f segundos...\n", 
 GPIO_LINE, SAMPLE_INTERVAL_1S/1000000.0);
    printf("Prima Ctrl+C para parar a monitorização\n\n");

 // Obter a hora de início
    clock_gettime(CLOCK_MONOTONIC, &start_time);

 // Monitorizar continuamente o estado de entrada do GPIO
    while (sample_count < MAX_SAMPLES) {
 // Ler o estado atual do GPIO
 ret = ioctl(req.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
        if (ret < 0) {
 perror("Falha ao ler o estado do GPIO");
 break;
 }

 // Obter a hora atual
 clock_gettime(CLOCK_MONOTONIC, &current_time);
        double elapsed = (current_time.tv_sec - start_time.tv_sec) + 
 (current_time.tv_nsec - start_time.tv_nsec) / 1e9;

 // Imprimir o estado e o carimbo de data/hora
        printf("Hora: %.3fs - Estado DI: %d\n", elapsed, data.values[0]);
 
 sample_count++;
 sleep(SAMPLE_INTERVAL_1S);
    }

 // Fechar o identificador da linha GPIO
    close(req.fd);

 printf("Monitorização concluída, %d amostras recolhidas\n", sample_count);
    return EXIT_SUCCESS;
}

Compilar:  gcc di_example.c -o di_example

Teste: ./di_example

5.3 Python Exemplo

di_example.py:

import time
import sys
from typing import Optional

# Constant definitions
GPIO_LINE: int = 19
SAMPLE_INTERVAL: float = 1.0
MAX_SAMPLES: int = 100

def gpio_read(gpio_line: int) -> Optional[int]:
    """Read the state value of the specified GPIO line
    
    Args:
        gpio_line: GPIO line number
        
    Returns:
        GPIO state value (0 or 1), returns None on error
    """
    try:
        # Ensure GPIO has been exported and set to input
        try:
            with open('/sys/class/gpio/export', 'w') as export_file:
                export_file.write(str(gpio_line))
            time.sleep(0.1)
        except IOError:
            pass  # GPIO may have been exported
        
        # Set direction to input
        with open(f'/sys/class/gpio/gpio{gpio_line}/direction', 'w') as direction_file:
            direction_file.write('in')
        
        # Read GPIO value
        with open(f'/sys/class/gpio/gpio{gpio_line}/value', 'r') as value_file:
            return int(value_file.read().strip())
            
    except (IOError, ValueError, FileNotFoundError) as error:
        print(f"GPIO read error: {error}")
        return None

def main() -> int:
    """Main function, execute GPIO monitoring task"""
    print(f"Monitoring GPIO line {GPIO_LINE}, interval {SAMPLE_INTERVAL} seconds...")
    print("Press Ctrl+C to stop\n")
    
    start_time: float = time.monotonic()
    sample_count: int = 0
    
    try:
        while sample_count < MAX_SAMPLES:
            target_time: float = start_time + sample_count * SAMPLE_INTERVAL
            
            # Precise wait
            current_time: float = time.monotonic()
            if current_time < target_time:
                time.sleep(target_time - current_time)
            
            # Read GPIO state
            gpio_value: Optional[int] = gpio_read(GPIO_LINE)
            
            if gpio_value is None:
                print("Failed to read GPIO state")
                break
            
            elapsed: float = time.monotonic() - start_time
            print(f"Time: {elapsed:.3f}s - DI state: {gpio_value}")
            
            sample_count += 1
    
    except KeyboardInterrupt:
        print("\nUser interrupted")
    
    finally:
        # Clean up GPIO
        try:
            with open('/sys/class/gpio/unexport', 'w') as unexport_file:
                unexport_file.write(str(GPIO_LINE))
        except (IOError, FileNotFoundError):
            pass
        
        print(f"\nMonitoring complete, {sample_count} samples collected")
    
    return 0

if __name__ == "__main__":
    sys.exit(main())

Teste: python di_exemplo.py

6. FAZER

O EC301 possui duas interfaces DO, com saída de 5 A/12~24 V CC

Interface de hardware Índice IO Número IO Grupo Chip
DO-1 20 GPIO0_C4 gpiochip0
DO-2 18 GPIO0_C2 gpiochip0

6.1 Teste rápido

do_example.sh: 

#!/bin/bash

# Verificar se foi fornecido um índice GPIO
if [ -z "$1" ]; then
    echo "Utilização: $0 "
    exit 1
fi

GPIO=$1
GPIO_PATH="/sys/class/gpio/gpio$GPIO"
EXPORT_PATH="/sys/class/gpio/export"

# Verificar se o GPIO já foi exportado
if [ ! -d "$GPIO_PATH" ]; then
    echo "A exportar o GPIO $GPIO..."
    echo "$GPIO" > "$EXPORT_PATH"
    if [ $? -ne 0 ]; then
 echo "Falha ao exportar o GPIO $GPIO. Verifique as permissões ou se está a ser utilizado."
        exit 1
    fi
else
    echo "O GPIO $GPIO já está exportado."
fi

# Definir a direção do GPIO para entrada
echo "out" > "$GPIO_PATH/direction"

# Definir o valor inicial para nível baixo
echo 0 > "$GPIO_PATH/value"

echo "A alternar o GPIO $GPIO a cada 1 segundo. Prima Ctrl+C para parar."

# Iniciar a alternância num ciclo
while true; do
    echo 1 > "$GPIO_PATH/value"
    sleep 1
    echo 0 > "$GPIO_PATH/value"
    sleep 1
done

Teste: bash do_example.sh 20

Efetue o teste ligando o terminal positivo de uma carga externa (por exemplo, um LED) ao COM1 e o terminal negativo ao DO1; após executar o programa de teste, o LED irá piscar a intervalos regulares. Para obter detalhes sobre a ligação, consulte o especificações de cada produto da série EC300.

Exemplo em C 6.2

do_example.c:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define GPIO_CHIP "/dev/gpiochip0"  // Dispositivo controlador GPIO
#define GPIO_LINE 20 // Número da linha GPIO a testar (interface DO)
#define INTERVAL_SEC 1 // Intervalo de tempo de ligar/desligar (segundos)
#define MAX_CYCLES 100 // Número máximo de ciclos

volatile sig_atomic_t stop_flag = 0;

void handle_signal(int sig) {
    stop_flag = 1;
}

int main() {
    struct gpiohandle_request req;
    struct gpiohandle_data data;
    int fd, ret;
    int cycle_count = 0;
    struct timespec start_time, current_time;

 // Registar o manipulador de sinais
    signal(SIGINT, handle_signal);
    signal(SIGTERM, handle_signal);

    // Abrir dispositivo de caracteres GPIO
    fd = open(GPIO_CHIP, O_RDWR);
    if (fd < 0) {
 perror("Não foi possível abrir o dispositivo GPIO");
 return EXIT_FAILURE;
    }

    // Definir pedido GPIO
    memset(&req, 0, sizeof(req));
    req.lineoffsets[0] = GPIO_LINE;  // Número da linha GPIO
    req.flags = GPIOHANDLE_REQUEST_OUTPUT;  // Definir para o modo de entrada
    req.default_values[0] = 0;       // Definir o valor inicial para o nível baixo
    strcpy(req.consumer_label, "do-control");  // Etiqueta do consumidor
    req.lines = 1; // Número de linhas GPIO a controlar

    // Solicitar controlo do GPIO
    ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
    if (ret < 0) {
 perror("Não foi possível adquirir o controlo do GPIO");
        close(fd);
 return EXIT_FAILURE;
    }

 // Fechar dispositivo de caracteres GPIO (identificador da linha já adquirido)
    close(fd);

    printf("A monitorizar a linha %d do chip GPIO 0 (interface DO), intervalo de amostragem de %.1f segundos...\n\n", 
 GPIO_LINE, INTERVAL_SEC);
    printf("Prima Ctrl+C para parar a monitorização\n\n");

 // Obter a hora de início
    clock_gettime(CLOCK_MONOTONIC, &start_time);

    // Controlo em loop da ativação/desativação da interface DO
    while (!stop_flag && cycle_count < MAX_CYCLES) {
 // Obter a hora atual
 clock_gettime(CLOCK_MONOTONIC, &current_time);
        double elapsed = (current_time.tv_sec - start_time.tv_sec) + 
 (current_time.tv_nsec - start_time.tv_nsec) / 1e9;

 // Alternar o estado
        int state = cycle_count % 2;
 data.values[0] = state;
 
 ret = ioctl(req.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
        if (ret < 0) {
 perror("Falha ao definir o estado do GPIO");
 break;
 }

        printf("Tempo: %.3fs - Definir estado do DO: %s\n", 
 elapsed, state ? "Alto (ON)" : "Baixo (OFF)");
        
        cycle_count++;
 sleep(INTERVAL_SEC);
    }

    // Garantir que o estado final é de nível baixo
    data.values[0] = 0;
    ioctl(req.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);

    // Fechar o identificador da linha GPIO
    close(req.fd);

 printf("\nControlo concluído, ciclos de ligar/desligar %d executados\n", cycle_count);
    return EXIT_SUCCESS;
}

Compilar: gcc do_example.c -o do_example

Teste: ./do_example

6.3 Exemplo em Python

do_example.py:

import os
import time
import signal
import sys
from typing import NoReturn, Optional

# Constant definitions
GPIO_LINE: int = 20
INTERVAL_SEC: float = 1.0
MAX_CYCLES: int = 100

stop_flag: bool = False

def handle_signal(sig: int, frame) -> None:
    """Signal handler function, set stop flag"""
    global stop_flag
    stop_flag = True

def gpio_control(gpio_line: int, value: bool) -> bool:
    """Control the output state of the specified GPIO line
    
    Args:
        gpio_line: GPIO line number
        value: Output value (True for high level, False for low level)
        
    Returns:
        Returns True on success, False on failure
    """
    try:
        # Ensure GPIO has been exported
        try:
            with open('/sys/class/gpio/export', 'w') as export_file:
                export_file.write(str(gpio_line))
            time.sleep(0.1)
        except IOError:
            pass  # GPIO may have been exported
        
        # Set direction to output
        with open(f'/sys/class/gpio/gpio{gpio_line}/direction', 'w') as direction_file:
            direction_file.write('out')
        
        # Set GPIO value
        with open(f'/sys/class/gpio/gpio{gpio_line}/value', 'w') as value_file:
            value_file.write('1' if value else '0')
        
        return True
    except (IOError, FileNotFoundError) as error:
        print(f"GPIO control error: {error}")
        return False

def main() -> int:
    """Main function, execute GPIO control task"""
    global stop_flag
    
    # Register signal handler
    signal.signal(signal.SIGINT, handle_signal)
    signal.signal(signal.SIGTERM, handle_signal)
    
    print(f"Control GPIO line {GPIO_LINE},interval {INTERVAL_SEC} seconds...")
    print("Press Ctrl+C to stop\n")
    
    start_time: float = time.monotonic()
    cycle_count: int = 0
    
    try:
        while not stop_flag and cycle_count < MAX_CYCLES:
            target_time: float = start_time + cycle_count * INTERVAL_SEC
            
            # Precise wait
            current_time: float = time.monotonic()
            if current_time < target_time:
                time.sleep(target_time - current_time)
            
            state: bool = bool(cycle_count % 2)
            
            if not gpio_control(GPIO_LINE, state):
                break
            
            elapsed: float = time.monotonic() - start_time
            status: str = "High (ON)" if state else "Low (OFF)"
            print(f"Time: {elapsed:.3f}s - DO state: {status}")
            
            cycle_count += 1
    
    except KeyboardInterrupt:
        print("\n User interrupted")
    
    finally:
        # Ensure final state is low level
        gpio_control(GPIO_LINE, False)
        print(f"\n Control completed, {cycle_count} on/off cycles executed")
    
    return 0

if __name__ == "__main__":
    sys.exit(main())

Teste: python do_example.py

7. Botões

Série de produtos EC300 – Botões: Geridos por programas internos, não programáveis pelo utilizador, utilizados para reiniciar ou reinicializar o dispositivo através da configuração na página web.

Interface de hardware Índice IO Número IO Grupo Chip Observações
Botão 0 GPIO0_A0 gpiochip0 Botão de reinicialização/reinício do sistema por predefinição
  • Reiniciar: prima o 1 e solte-o
  • Reinicializar + Reiniciar: manter premido >5s

8. Watchdog de hardware

Todos os produtos EC300 têm o watchdog de hardware ativado por predefinição, sendo gerido por programas internos.

Interface de hardware Índice IO Número IO Grupo Chip Observações
Ativar controlo 113 GPIO3_C1 gpiochip3 Botão de reinicialização/reinício do sistema por predefinição
Watchdog IO 8

GPIO0_B0

gpiochip0

Período recomendado para a alimentação do watchdog: <30 s

Nota: Se os programas internos forem interrompidos, terá de assumir você mesmo o serviço de alimentação do watchdog; caso contrário, o dispositivo irá reiniciar-se periodicamente.

9. Portas de rede

As portas de rede estão divididas em WAN + LAN e são geridas por programas de rede internos; é necessário efetuar a configuração através da interface web. Para mais informações, consulte o Guia de Início Rápido da CE.

10. Comunicações móveis sem fios

A série de produtos EC300 suporta comunicação 4G/5G (o 5G requer a aquisição da versão 5G; para mais informações, contacte o apoio ao cliente). O reconhecimento do módulo e a marcação são geridos por programas internos, pelo que os utilizadores não precisam de se preocupar. Consulte o Guia de Início Rápido da CE.

11. Wi-Fi

A série de produtos EC300 suporta os modos de ponto de acesso Wi-Fi e de cliente Wi-Fi; suporta as bandas de 2,4 GHz e 5 GHz. O Wi-Fi é gerido por programas internos, pelo que os utilizadores não precisam de se preocupar. Para mais informações, consulte: Guia de Início Rápido da CE.

12. Bluetooth

A série de produtos EC300 suporta a funcionalidade Bluetooth.

12.1 Teste rápido

# Entre no modo interativo do shell Bluetooth
bluetoothctl

# No modo interativo, execute os seguintes comandos:
# Verifique o estado do controlador (adaptador) Bluetooth

# Se o adaptador não estiver ativado, ative-o
power on

# Ative o modo de detecção (permita que outros dispositivos encontrem este dispositivo)
discoverable on

# Ative o modo de emparelhamento
pairable on

# Inicie a pesquisa (pesquisa contínua; prima Ctrl+C para parar)
scan on

# Para emparelhar um dispositivo Bluetooth (por exemplo, auscultadores, endereço MAC AA:BB:CC:DD:EE:FF), a título de exemplo:
# Executar no bluetoothctl
# Emparelhar o dispositivo
pair AA:BB:CC:DD:EE:FF

# Considerar o dispositivo de confiança (opcional, para evitar verificações repetidas mais tarde)
trust AA:BB:CC:DD:EE:FF

# Ligue o dispositivo
connect AA:BB:CC:DD:EE:FF

# Se o emparelhamento exigir um código PIN, introduza-o conforme solicitado (normalmente 0000 ou 1234, ou o código indicado no manual do dispositivo)
# Após a ligação bem-sucedida, pode visualizar os detalhes do dispositivo com info AA:BB:CC:DD:EE:FF
info AA:BB:CC:DD:EE:FF

13. Áudio

A série de produtos EC300 é compatível com uma tomada de 3,5 mm para auscultadores.

13.1 Teste rápido

Ligue uns auscultadores de 3,5 mm ou um altifalante à porta HP do dispositivo; crie um ficheiro de script da seguinte forma:

hp_example.sh:

#!/bin/bash

# Definir o dispositivo de reprodução como HP (SAÍDA DE AURICULARES)
amixer -c 0 cset name='Caminho de reprodução' 'HP'

# Definir o volume de reprodução para 200, intervalo 0–255
amixer -c 0 cset name='Playback Volume' 200

# Reproduzir um ficheiro de áudio de exemplo
aplay /run/media/mmcblk0p9/iotrouter.wav

Teste: bash hp_example.sh

 

14. Ecrã

Interface de hardware Resolução EC300 EC300P EC301
HDMI 1920×1080 ✔️ ✔️
LVDS 1280×800 ✔️

14.1 Ecrã LVDS

Suporta o ajuste do brilho da retroiluminação, intervalo de ajuste [0–255]:

echo 128 > /sys/class/backlight/backlight/brightness

echo 255 > /sys/class/backlight/backlight/brightness

Exibição do ecrã de controlo:

echo off > /sys/class/drm/card0-LVDS-1/status

echo on > /sys/class/drm/card0-LVDS-1/status

Interface API do sistema

1. Obter informações básicas sobre o dispositivo

  • URL: GET http://{IP do dispositivo}/rpc-api/data/devinfo
  • Método: OBTER
  • Parâmetros da solicitação: Nenhum

Descrição dos dados de resposta:

Campo Nome do campo Tipo de dados Descrição
código Código de estado da resposta int Devolve sempre o código 200, o que indica que o pedido foi bem-sucedido
data.model Modelo do dispositivo cadeia de caracteres Modelo do dispositivo
data.sn Número de série do dispositivo cadeia de caracteres Número de série do dispositivo
data.versão Versão do dispositivo cadeia de caracteres Versão do software do dispositivo

Exemplo de resposta:


{
    "code": 200,
    "data": {
 "model": "EC100",
 "sn": "4310AA01C8AA2B6B",
        "version": "3802"
    }
}

2. Obter informações sobre a rede WWAN do dispositivo

  • URL: GET http://{IP do dispositivo}/rpc-api/data/wwaninfo
  • Método: OBTER
  • Parâmetros da solicitação: Nenhum

Descrição dos dados de resposta: 

Campo Nome do campo Tipo de dados Descrição
código Código de estado da resposta int Devolve sempre o código 200, o que indica que o pedido foi bem-sucedido
data.ip 4G IP cadeia de caracteres Endereço IP obtido através de 4G
data.mask Máscara de sub-rede cadeia de caracteres Máscara de sub-rede
data.dns IP do servidor DNS cadeia de caracteres Endereço do servidor de resolução DNS
data.signal Intensidade do sinal int Intensidade do sinal 4G
data.ccid Número do cartão SIM cadeia de caracteres Número do cartão SIM
data.imei IMEI cadeia de caracteres Módulo 4G do dispositivo IMEI
data.netInfo Tipo de rede cadeia de caracteres Tipo de rede atual
data.apn.addr Endereço APN cadeia de caracteres Endereço do Nome do Ponto de Acesso (APN)
data.apn.username Nome de utilizador APN cadeia de caracteres Nome de utilizador para autenticação APN
data.apn.password Palavra-passe APN cadeia de caracteres Palavra-passe de autenticação APN

Exemplo de resposta:


{
    "code": 200,
    "data": {
 "ip": "37.64.21.99",
        "máscara": "255.255.255.255",
 "dns": "224.134.125.33",
 "sinal": 90,
        "ccid": "89860480192470076526",
 "imei": "862701080346307",
        "netInfo": "LTE",
 "apn": {
 "addr": "",
 "username": "",
 "password": ""
 }
    }
}

Aplicações de software

1. Programa de gestão interna

Para simplificar a utilização, a série de produtos EC300 inclui um programa de gestão integrado, o iotrouter, que é iniciado automaticamente no arranque. O programa de gestão inclui, entre outras, as seguintes funções:

  • Inicialização do dispositivo
  • Gestão de redes
  • Cão de guarda
  • Monitorização de teclas/botões
  • Firewall
  • Serviço de configuração do dispositivo (porta predefinida 80, a porta de escuta pode ser alterada; ficheiro: /usr/local/src/iotrouter/web/user-config.js)
  • Função de navegação pelos ficheiros do utilizador (caminho predefinido: /home; o caminho pode ser alterado; ficheiro: /usr/local/src/iotrouter/web/user-config.js)

Nota: O programa interno deve permanecer em execução; caso contrário, o dispositivo poderá não funcionar normalmente no arranque. Se o utilizador tiver de desativar o programa de gestão interno, deverá assumir ele próprio os serviços de gestão acima referidos.

2. NeuronEX

A série de produtos EC300 vem com o NeuronEX-Lite pré-instalado. O serviço inicia automaticamente ao arrancar o sistema e fica à escuta na porta 8085. Para mais detalhes, consulte: Percurso de Aprendizagem

  • Verificar o estado do serviço: systemctl status neuronex
  • Reiniciar o serviço: systemctl restart neuronex
  • Parar o serviço: systemctl stop neuronex
  • Desativar o arranque automático do serviço: systemctl disable neuronex

3. Node-Red

A série de produtos EC300 vem com o Node-Red pré-instalado. O serviço inicia automaticamente no arranque e fica à escuta na porta 1880. Para mais detalhes, consulte: Percurso de Aprendizagem

  • Verificar o estado do serviço: systemctl status node-red
  • Reiniciar o serviço: systemctl restart node-red
  • Parar o serviço: systemctl stop node-red
  • Desativar o arranque automático do serviço: systemctl disable node-red

4. FUXA

A série de produtos EC300 vem com o FUXA pré-instalado. O serviço inicia automaticamente no arranque e fica à escuta na porta 1881. Para mais detalhes, consulte: Desenvolvimento Avançado

5. Ambiente de trabalho Xfce4

O ambiente de trabalho predefinido é o Xfce4; isto é apenas um exemplo. Se os utilizadores precisarem de apresentar outras interfaces, como, por exemplo, uma interface QT, podem desenvolvê-las eles próprios.
O gestor de ecrã para o ambiente de trabalho é: lightdm

  • Verificar o estado do serviço: systemctl status lightdm
  • Reiniciar o serviço: systemctl restart lightdm
  • Parar o serviço: systemctl stop lightdm
  • Desativar o arranque automático do serviço: systemctl disable lightdm

6. Programas desenvolvidos pelos utilizadores

Os utilizadores têm total liberdade para desenvolver os seus próprios programas. Os programas dos utilizadores funcionam de forma independente, mas é necessário ter em conta a gestão da memória e do armazenamento para evitar anomalias no sistema. O software pode ser configurado para iniciar automaticamente através de:

  •  /etc/rc.local
  • serviço systemd
  • /etc/init.d sistema

Também prestamos serviços de desenvolvimento e personalização de software. Se necessário, contacte a nossa equipa de vendas.

7. LOGOTIPO de arranque

Os utilizadores podem substituir o logótipo de arranque do sistema por conta própria.

Nome do logótipo Palco de Exposição
logo.bmp Exibido durante o arranque do U-Boot
logo_kernel.bmp Exibido durante o arranque do kernel

7.1 Requisitos de formatação

As imagens do LOGO devem estar no formato BMP de 24 bits, sendo recomendável que a resolução da imagem não exceda a resolução de ecrã do HDMI ou de outros monitores.

7.2 Substituição do LOGO

Quando o dispositivo ligar, copie o ficheiro preparado logo.bmp e logo_kernel.bmp ficheiros para o diretório “boot/” na raiz do sistema de ficheiros da placa de avaliação, substituindo os ficheiros existentes logo.bmp e logo_kernel.bmp ficheiros no diretório.

Nota: Os nomes dos ficheiros dos logótipos não devem ser alterados.