Acesso aos pneus Redd nas pontes FTDI

Concluímos um grande bloco teórico mostrando como construir um subsistema FPGA para o complexo Redd; como organizar a comunicação entre o FPGA e o processador central do complexo; quão fácil é salvar fluxos de dados de alta velocidade na RAM, que está diretamente conectada ao FPGA, para posterior transferência lenta para o processador central (ou vice-versa, para colocar dados nessa RAM para posterior saída rápida no canal). Revisamos as técnicas de rastreamento do processador Nios II. Podemos otimizar o desempenho do sistema do processador com base no Nios II para que o trabalho seja o mais eficiente possível. Em geral, estudamos toda a teoria mínima necessária, e seria hora de começar a praticar projetando um dispositivo não muito complexo, mas praticamente útil ... Mas há uma MAS.

Pelos comentários dos artigos, notei que alguns leitores acreditam que Redd e FPGA são como Lenin e o Partido. Que eles estão inextricavelmente ligados. De fato, isso não é verdade. Eu só queria começar uma conversa sobre o complexo Redd com algo interessante, mas o que poderia ser mais interessante que o FPGA? Bem, e iniciando uma conversa, interromper de relance é estúpido. E, finalmente, o grande bloco lógico está completo. E para mostrar que os FPGAs estão longe de todo o Redd, proponho fazer aproximadamente três artigos sobre coisas que não estão relacionadas a eles. Bem, e tendo completado este bloco, já vá para a prática de FPGA.



1. Introdução


O mais surpreendente é que, assim que decidi fazer uma digressão em outros tópicos, os bons chefes me jogaram em uma batalha difícil em um projeto em que o trabalho está em andamento com a linguagem VHDL e o Xilinx FPGA. Primeiro, é por isso que durante muito tempo não peguei uma caneta em geral e, em segundo lugar, fica claro que a preparação de artigos práticos exige um grande número de experimentos. É um pouco difícil lidar com VHDL / Verilog e Xilinx / Altera ao mesmo tempo. Portanto, uma pausa nas histórias sobre FPGAs teria que ser feita de qualquer maneira.

Então No primeiro artigo da série, já examinamos o diagrama estrutural do complexo Redd. Vamos fazer mais uma vez.



No artigo de hoje, é improvável que os especialistas em Linux encontrem muitas informações valiosas, mas vale a pena examinar as imagens superficialmente. Aqueles que, como eu, estão acostumados a trabalhar no Windows, encontrarão uma lista de técnicas prontas que permitem trabalhar com o complexo. Em geral, este artigo trará as habilidades desses e de outros grupos de leitores para um denominador comum.


Blocos UART (portas seriais)


No diagrama de blocos, vemos o controlador FT4232 que implementa 4 portas seriais (UART):



Mas se você falar um pouco mais globalmente, o complexo Redd não terá quatro, mas seis portas seriais. Os quatro mencionados acima têm níveis de CMOS e mais dois são soldados na placa-mãe, porque o complexo é baseado em um PC comum.



Conseqüentemente, eles têm níveis - RS232 (mais ou menos 12 volts). Portas RS232 - tudo fica claro com elas, elas são exibidas na forma de dois conectores DB-9 padrão,



e onde procurar linhas com níveis de CMOS? Em geral - em um conector comum. Sua pinagem é mostrada no diagrama do circuito elétrico. Entre outras coisas, existem contatos correspondentes ao UART.



Externamente, esse conector tem a seguinte aparência:



Como usá-lo depende da tarefa. Você pode fazer um chicote para conectar cada dispositivo. Essa abordagem é útil se alguém usar o complexo Redd para testar dispositivos fabricados periodicamente do mesmo tipo. Mas o principal objetivo do complexo ainda é depurar o equipamento que está sendo desenvolvido. E, nesse caso, é mais fácil se conectar a ele de forma temporária. Esse padrão temporário é visível nos protetores de tela para todos os artigos: os fios Aruino são inseridos diretamente no conector. É claro que contar contatos ainda é um prazer e, se eles desaparecerem acidentalmente, é tão difícil restaurar a troca que é mais fácil reconectar tudo do zero; portanto, para facilitar a vida útil, existe uma placa riser à qual você pode conectar pelo menos com a ajuda de conectores de duas linhas, pelo menos com a mesma fiação do Arduino.



Acesso ao software UART


A porta serial é um elemento bem estabelecido e bem padronizado; portanto, o trabalho com ela não passa por algumas bibliotecas FTDI específicas, mas por meios padrão. Vamos ver como essas ferramentas ficam no Linux.

Nomes de portas


De vários artigos e fóruns na rede, segue-se que os nomes das portas fornecidas pelos adaptadores USB-Serial estão no formato / dev / ttyUSB0, / dev / ttyUSB1 e assim por diante. No Linux, todos os dispositivos podem ser visualizados usando os mesmos comandos da exibição de diretórios comuns (na verdade, os dispositivos são os mesmos arquivos). Vamos ver quais nomes estão em nosso sistema. Nós damos o comando:
ls / dev /



Os nomes que nos interessam estão destacados em vermelho. Algo muitos deles. Qual porta corresponde a quê? Aqueles que são bem versados ​​no Linux conhecem milhares de feitiços para todas as ocasiões. Mas para aqueles que ainda trabalharam com o Windows 3.1 (bem, em paralelo com a então bastante animada RT-11), ainda é difícil lembrar, com a idade a nova é mais difícil de lembrar. Portanto, é mais fácil encontrar tudo o tempo todo, usando maneiras simples. E destaquei a entrada desse caminho simples com uma moldura verde. Serial do subdiretório condicional. Agora estamos vendo o espaço para nome / dev / . E vamos ver o espaço / dev / serial :



Ótimo! Nós nos aprofundamos na hierarquia, observamos o espaço / dev / serial / by-id . Olhando para o futuro, direi que, para a exibição correta, você precisa usar o comando ls com a opção –l (obrigado ao meu chefe pelo esclarecimento). Ou seja, damos o comando:
ls –l / dev / serial / by-id



Por um lado, está tudo bem. Agora sabemos quais nomes no espaço / dev / ttyUSBX correspondem a qual dispositivo. Em particular, as portas organizadas pela ponte FT4232 (Quad) têm nomes de ttyUSB3 a ttyUSB6 . Mas, por outro lado, ao considerar esse site, percebi que em Paris, na câmara de pesos e medidas, deve haver necessariamente uma sala na qual o padrão da bagunça é colocado ... Porque, de alguma forma, você precisa ser capaz de medir seu valor. Bem, digamos que a falta de portas / dev / ttyUSB0 e / dev / ttyUSB1 possa ser facilmente explicada. Mas como explicar que as portas “nativas” baseadas na prole da ponte FTDI instalada são numeradas das três principais e o controlador Prolific de terceiros, inserido em um projeto específico, recebeu a porta número 2? Como alguém pode trabalhar nesse ambiente? Amanhã alguém conectará outro controlador ao complexo (já que o complexo permite que diferentes grupos de desenvolvedores trabalhem com equipamentos diferentes ao mesmo tempo), e as portas se moverão novamente. Quais portas precisamos registrar no arquivo de configuração para um aplicativo em funcionamento?

Acontece que nem tudo é tão ruim. Primeiramente, o nome amarelo / dev / ttyUSB3 e o nome azul / dev / serial / by-id / usb-FTDI_Quad_RS232-HS-if00-port0 são dois aliases do mesmo dispositivo. E a segunda opção também pode ser apresentada como o nome da porta, mas já é mais permanente que a primeira. É verdade que, neste caso, tudo está um pouco ruim. Um controlador externo baseado no FT4232 pode ser conectado ao complexo e já será necessário lidar com sua numeração. E aqui "em segundo lugar" vem em nosso auxílio. Ou seja, outra convenção de nomenclatura alternativa. Lembramos que o diretório / dev / serial continha não apenas o subdiretório / by-id , mas também o subdiretório / by-path . Verificamos seu conteúdo (ele está localizado na parte inferior da próxima figura, sob uma linha vermelha).



Tudo aqui está ligado à arquitetura física. E eu já disse muitas vezes que todos os controladores dentro do complexo são soldados aos quadros, para que a hierarquia interna não mude. Portanto, o nome /dev/serial/by-path/pci-0000:00:15.0-usb-0:6.5:1.0-port0 será o mais difícil.

No total, temos a seguinte maneira de procurar o nome da porta (isso deve ser feito uma vez, os resultados para sua instância do complexo podem ser colocados na tabela e usados ​​constantemente):

  1. Emita o comando ls –l / dev / serial / by-id .
  2. Emita o comando ls –l / dev / serial / by-path .
  3. A partir dos resultados do ponto 1, encontre o nome da porta correspondente à porta necessária da ponte requerida. Encontre o mesmo nome da porta nos resultados do parágrafo 2. Pegue o nome físico correspondente a este parágrafo.

Para as portas atendidas pelo controlador na placa-mãe, tudo é um pouco mais complicado. Aqui você não pode fazer o caminho a partir do comando mais simples " ls / dev ", mas precisa se lembrar de algo (bem, ou pelo menos lembre-se de que pode entrar em contato aqui para obter ajuda). Em todo lugar, diz que os nomes de porta típicos são ttyS0-ttyS3 . A questão permanece: em que nomes as portas reais de nosso sistema? Encontrei o seguinte feitiço respondendo a essa pergunta:
ls / sys / class / tty / * / device / driver

Aqui está a resposta do sistema:



Acontece que precisamos usar os nomes / dev / ttyS2 e / dev / ttyS3 . Por que - eu não sei. Mas uma coisa agrada: aqui não estão previstas mudanças especiais; portanto, essas constantes podem ser lembradas e usadas sem medo de que elas mudem.

Desenvolvimento de código


Ao desenvolver, você deve usar o maravilhoso Guia de Programação Serial para Sistemas Operacionais POSIX (o primeiro link direto que você obtém é https://www.cmrr.umn.edu/~strupp/serial.html , mas ninguém sabe quanto tempo vai durar). É especialmente importante que ele ensine como trabalhar com um conjunto completo de sinais, porque as portas do complexo estão totalmente implementadas. É verdade que hoje usaremos apenas as linhas Tx e Rx.

Geralmente dou os resultados do oscilograma, mas agora estou em condições quase reais: o complexo está localizado onde minhas mãos não alcançam, então não consigo conectar a sonda do osciloscópio. Para ver pelo menos algum resultado, a meu pedido, os colegas adicionaram algumas postagens ao complexo de acordo com o seguinte esquema clássico:



Vamos tentar transferir de uma porta para outra. No nosso caso, as portas /dev/serial/by-path/pci-0000:00:15.0-usb-0:6.5:1.2-port0 e /dev/serial/by-path/pci-0000:00:15.0- estão conectadas usb-0: 6.5: 1.3-port0 .

Já discutimos como os programas para o processador central Redd são escritos em um dos artigos anteriores . Portanto, hoje nos restringiremos apenas ao texto do programa escrito sob a impressão do documento Guia de programação serial para sistemas operacionais POSIX . Na verdade, o principal ponto interessante é mudar a estratégia de recepção para leitura sem bloqueio, o resto é trivial. No entanto, considerando a bagunça nos exemplos da rede sobre esse tópico, é melhor ter uma amostra trivial em mãos (será mostrado mais adiante que mesmo um exemplo baseado neste maravilhoso documento não funcionou 100%, o código abaixo difere dos cânones descritos em uma linha, mas mais sobre isso abaixo).

O mesmo código de amostra
#include <cstdio> #include <unistd.h> /* UNIX standard function definitions */ #include <fcntl.h> /* File control definitions */ #include <errno.h> /* Error number definitions */ #include <termios.h> /* POSIX terminal control definitions */ int OpenUART(const char* portName, speed_t baudRate) { //   int fd = open(portName, O_RDWR | O_NOCTTY | O_NDELAY); //     if (fd == -1) { return fd; } //     fcntl(fd, F_SETFL, FNDELAY); //    termios options; tcgetattr(fd, &options); // ,       // ,   .  ... cfsetspeed(&options, baudRate); //    ... // 1  ,   , 8    options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; options.c_cflag |= (CLOCAL | CREAD); // , ... tcsetattr(fd, TCSANOW, &options); return fd; } int main() { printf("hello from ReddUARTTest!\n"); int fd1 = OpenUART("/dev/serial/by-path/pci-0000:00:15.0-usb-0:6.5:1.3-port0", 9600); int fd2 = OpenUART("/dev/serial/by-path/pci-0000:00:15.0-usb-0:6.5:1.2-port0", 9600); if ((fd1 != -1) && (fd2 != -1)) { static const unsigned char dataForSend[] = {0xff,0xfe,0xfd,0xfb}; //      write(fd1, dataForSend, sizeof(dataForSend)); unsigned char dataForReceive[128]; ssize_t cnt = 0; //     ,  , //         int readSteps = 0; //      ,   while (cnt < (ssize_t)sizeof(dataForSend)) { readSteps += 1; ssize_t rd = read(fd2, dataForReceive + cnt, sizeof(dataForReceive) - cnt); //   - ,     if (rd <= 0) { usleep(1000); } else //  -   { cnt += rd; } } //   printf("%d read operations\n", readSteps); printf("Read Data: "); for (unsigned int i = 0; i < cnt; i++) { printf("%X ", dataForReceive[i]); } printf("\n"); } else { printf("Error with any port open!\n"); } //   if (fd1 != -1) { close(fd1); } if (fd2 != -1) { close(fd2); } return 0; } 


Executar - obtemos o resultado previsto:

 hello from ReddUARTTest! 14 read operations Read Data: FF FE FD FB 

Pode-se ver que 4 bytes ocuparam 14 tentativas, ou seja, a leitura não estava bloqueando. Às vezes, o sistema retornava um estado "sem novos dados" e o programa dormia por um milissegundo.

Em geral, está tudo bem, mas sem um osciloscópio, não posso ter certeza de que duas portas baseadas no mesmo chip realmente definem a velocidade. Eu já pulei no fato de que a velocidade era a mesma (pois ele tinha um controlador), mas não o que eu pedi. Vamos pelo menos verificar de alguma forma que seja pelo menos controlado. Para fazer isso, definirei a velocidade da porta de recebimento para dobrar a velocidade da porta de transmissão. E, conhecendo a física do processo de transferência de dados, você pode prever como esses dados são distorcidos durante a recepção. Vejamos a transferência do byte 0xff em forma gráfica. Bit S - start (sempre existe zero), bit P - stop (sempre existe um), 0-7 - bits de dados (para a constante 0xFF - todas as unidades).



Agora, vamos sobrepor essa visualização com uma visão de como tudo será visto por um receptor operando com o dobro da velocidade:



Ótimo. O valor "1111 1110" deve ser aceito (os dados avançam o bit menos significativo), ou seja, 0xFE. A segunda metade do valor transmitido não afeta a recepção, pois as unidades correspondem ao silêncio na linha. Ou seja, transmitimos um byte, um byte também virá.

Construiremos o mesmo gráfico para verificação, que corresponderá ao valor 0xFE transmitido:



Espere o valor "1111 1000" ou 0xF8. Bem, vamos verificar o que esperar com o valor passado 0xFD:



Obtemos o valor 0xE6. Bem, para o valor transmitido 0xFB obtemos o 0x9E recebido (você pode plotar o gráfico e ver por si mesmo). Ótimo! Alteramos uma única linha no aplicativo de teste, substituindo a velocidade de 9600 por 19200:

  int fd2 = OpenUART("/dev/serial/by-path/pci-0000:00:15.0-usb-0:6.5:1.2-port0", 19200); 

Começamos e obtemos este resultado do trabalho:

 hello from ReddUARTTest! 9 read operations Read Data: FE F8 E6 9E 

A propósito, em vão não realizei essa verificação. Inicialmente, usei outras funções de configuração de velocidade (par cfsetispeed / cfsetospeed) e elas não funcionaram! Graças a esse teste, o problema foi identificado e resolvido em tempo hábil. Ao trabalhar com equipamentos, você nunca pode confiar na intuição. Tudo deve ser verificado!

Gerenciamento de linha de energia 220 volts


Em geral, as linhas de energia de 220 volts não estão relacionadas ao tópico do artigo (pontes FTDI), mas estão relacionadas ao tópico desta seção (portas seriais). Vamos dar uma olhada rápida neles.



Quando listamos as portas, vimos este nome:



Esta é uma porta serial virtual. É tão virtual que não importa quais parâmetros ela possui (velocidade da porta, número de bits, formato de paridade etc.). Não importa quais parâmetros ele tenha sido definido, ele ainda será capaz de lidar com comandos perfeitamente. E são essas equipes que controlam as tomadas no complexo.



Ao desenvolver o sistema de comando, foi decidido abandonar interfaces de comando complexas. O gerenciamento usa um byte, sem seqüências de caracteres de enquadramento e outros detalhes, embora o byte seja textual (para que possa ser convenientemente transferido do terminal durante a depuração). Essa concisão é facilmente explicada: a interface de cadeias permite lidar com interferências em um canal UART inseguro. Porém, no nosso caso, fisicamente, o trabalho passa pelo canal USB, protegido por códigos de controle cíclico. O processamento do fluxo de retorno requer a gravação de código adicional ou a liberação constante de buffers, o que nem sempre é conveniente. É por isso que não há referências para seqüências de caracteres, não há respostas. Acredita-se que o canal seja estável. Se você deseja uma resposta, pode solicitá-la explicitamente. Ou seja, o desempenho do bloco sempre pode ser facilmente verificado enviando um byte extra após o comando.

Considere os comandos que podem ser enviados:
A equipeNomeação
'A'Ligue a primeira tomada
'a'Desligue a primeira tomada
'B'Ligue a segunda tomada
'b'Desligue a segunda tomada
'C'Ligue a terceira tomada (se houver)
'c'Desligue a terceira tomada (se houver)
'?'Restaurar status da saída

O comando '?' (ponto de interrogação) é o único que retorna uma resposta. Em resposta, sempre vêm 3 bytes, cada um dos quais corresponde ao estado de uma das saídas. Na verdade, os estados correspondem aos comandos. Por exemplo, 'abc' - todas as três tomadas estão desativadas, 'Abc' - a primeira está ligada, a segunda e a terceira estão desativadas etc.

Para experimentos com este subsistema, sugiro não escrever um programa especial (não é diferente do fornecido anteriormente, apenas os dados enviados às portas serão diferentes), mas usando as ferramentas do SO e jogando interativamente com soquetes.

Após muitas experiências com o rastreamento da porta através do comando cat e o envio de comandos em uma janela paralela usando o programa echo, percebi que, por algum motivo, não consigo obter resultados em um par de terminais ssh baseados em massa (mesmo jogando com as portas com as quais apenas que ele experimentou perfeitamente com seu programa). Portanto, eu tive que instalar o programa minicom padrão. Deixe-me lembrá-lo do comando de instalação:
sudo apt-get minicom

Em seguida, execute-o com o comando:
minicom –D / dev / ttyACM0

O nome da porta é curto, porque com experimentos manuais é mais fácil entrar. No trabalho de software, como sempre, é melhor usar um nome vinculado à hierarquia de hardware. Mais uma vez, observo que não configuro nenhum outro parâmetro de porta porque é virtual. Funcionará com todas as configurações.

Em seguida, pressionamos o ponto de interrogação no terminal e instantaneamente (sem avanço de linha) obtemos uma resposta



Isso significa que todas as tomadas estão desativadas no momento. Digamos que queremos ligar a segunda tomada. Pressione o capital 'B'. Não há reação na tela. Pressione '?' Novamente, temos uma nova linha com a resposta:



Tudo funciona. Não se esqueça de desligar 220 volts (comando 'b'). Você pode sair do terminal pressionando sucessivamente ctrl + A e, em seguida, X. A experiência está concluída.

Pneus SPI e I 2 C


Os barramentos SPI (que também podem funcionar no modo Quad-SPI) e I 2 C são implementados em combinação com pontes universais. Ou seja, em geral, o complexo possui duas pontes, cada uma das quais pode ser ativada no modo SPI ou em I 2 C. No diagrama estrutural, a seção correspondente é assim:



A essência de ligar os barramentos finais é visível no diagrama do circuito elétrico. Considere apenas um dos dois controladores:



Assim, os barramentos SPI e I 2 C não se cruzam de forma alguma. Restrições ao uso conjunto são determinadas apenas por restrições impostas pela FTDI no controlador FT4222H. Infelizmente, a documentação afirma que apenas uma interface pode estar ativa por vez:



Como gerenciar as linhas CFG1_0..CFG1_1 e CFG2_0..CFG2_1, nos encontraremos no próximo artigo. Agora acreditamos que todos eles foram anulados.

Em geral, o trabalho com o controlador está muito bem descrito no documento FT4222H USB2.0 TO QUADSPI / I2C BRIDGE IC ; portanto, não consideraremos os recursos dos modos de operação dos controladores. Tudo está muito claro no documento mencionado.

Quanto ao suporte de software, sua descrição pode ser encontrada no documento não menos notável AN_329 Guia do Usuário da LibFT4222 . Já trabalhamos com a ponte FTDI duas vezes: na segunda metade deste artigo e na segunda metade. Portanto, comparando este documento com esses artigos, você pode descobrir rapidamente e começar a escrever seu próprio código. Deixe-me mostrar o código de referência que envia os dados para o barramento SPI, sem considerar os detalhes de sua implementação; parece dolorosamente que já foi analisado com o FT2232.

Código que envia dados para o barramento SPI.
 #include "../ftd2xx/ftd2xx.h" #include "../LibFT4222/inc/LibFT4222.h" void SpiTest (int pos) { FT_HANDLE ftHandle = NULL; FT_STATUS ftStatus; FT4222_STATUS ft4222Status; //   ftStatus = FT_Open(pos, &ftHandle); if (FT_OK != ftStatus) { // open failed printf ("error: Cannot Open FTDI Device\n"); return; } ft4222Status = FT4222_SPIMaster_Init(ftHandle, SPI_IO_SINGLE, CLK_DIV_4, CLK_IDLE_LOW, CLK_LEADING, 0x01); if (FT4222_OK != ft4222Status) { printf ("error: Cannot switch to SPI Master Mode\n"); // spi master init failed return; } uint8 wrBuf [] = {0x9f,0xff,0xff,0xff,0xff,0xff,0xff}; uint8 rdBuf [sizeof (wrBuf)]; uint16 dwRead; ft4222Status = FT4222_SPIMaster_SingleReadWrite (ftHandle,rdBuf,wrBuf,sizeof (wrBuf),&dwRead,TRUE); if (FT4222_OK != ft4222Status) { printf ("error: Error on ReadWrite\n"); } else { printf ("received: "); for (int i=0;i<6;i++) { printf ("0x%X ",rdBuf[i]); } printf ("\n"); } FT4222_UnInitialize(ftHandle); FT_Close(ftHandle); } 


SPI Bus Parts


Os desenvolvedores de código para microcontroladores geralmente usam o barramento SPI como um gerador de uma frequência predeterminada. De fato, os pulsos gerados de maneira puramente programática via linhas GPIO dependem de muitos fatores. Em primeiro lugar, a ramificação, a rotação do loop requer ciclos do processador. Em segundo lugar, interrupções, DMA e outros fatores imprevistos podem interferir no processador. O SPI é mais ou menos estável, saiba como conseguir colocar bytes no buffer. Uma aplicação típica do bloco SPI, que não tem relação direta com esse próprio SPI, é o controle dos LEDs RGB, para os quais a precisão de definir a duração dos pulsos é muito importante.

Infelizmente, isso não é aceitável para pontes FTDI. O fragmento de código acima irá gerar esses pulsos no barramento:



Nesse caso, as regras de operação do SPI não são violadas; do ponto de vista desse barramento, tudo funciona corretamente. Lembre-se de que as soluções personalizadas habituais nos controladores não funcionarão aqui. É verdade que o complexo possui muitos conectores USB gratuitos. Todos os blocos não padronizados podem ser desenvolvidos separadamente e conectados a eles.

Peças para pneus I 2 C


A única coisa que faz sentido é indicar a ausência de resistores de pull-up para o barramento I 2 C na lateral do complexo. Mas isso é normal: no lado do dispositivo de trabalho, ainda há um elevador. Atualmente, um pull-up pode ser em qualquer voltagem; portanto, é lógico que ele esteja definido no dispositivo de destino.

Conclusão


Hoje, adquirimos habilidades práticas no trabalho com pneus implementados pelas pontes FTDI. Em geral, trabalhar com eles é padrão, mas todo o conhecimento é resumido em um único artigo, para não procurá-los pouco a pouco. Da próxima vez, consideraremos um módulo que controla dispositivos não padrão, implementado com base no controlador STM32. No diagrama estrutural, esta seção corresponde a ele:



Mas, realmente, tudo é um pouco mais interessante lá ...

Source: https://habr.com/ru/post/pt477662/


All Articles