Nem preciso dizer que mal podia esperar o fim de semana para fazer minhas verificações. Vamos dar uma olhada nos resultados do teste. E para torná-lo mais interessante, ao longo do caminho vamos considerar a tecnologia de exibição de variáveis "em tempo real", sem parar o núcleo do processador. Bem, e a tecnologia de depuração visual de arquivos elf coletados por compiladores em lote.
Que tipo de teste iremos realizar
A essência do teste foi discutida nos comentários do artigo anterior. O programa de PC original envia dados. O que não é importante. Dados e é isso. O controlador aceita esses dados e os ignora com segurança. Porque o problema era a velocidade de transmissão. As novas bibliotecas podem teoricamente pular alguns dados ou misturar um par de buffers. Portanto, o fluxo deve variar no tempo. Nesses casos, uma sequência pseudo-aleatória ou dados incrementais são enviados.
Não querendo perder muito tempo gerando uma sequência pseudo-aleatória para a origem e o destino (o algoritmo deve ser o mesmo), eu me limitei a uma palavra incremental de 32 bits. A principal desvantagem dessa abordagem é que até três dos quatro bytes podem coincidir em pacotes vizinhos. Mas um, e o primeiro, será diferente. Portanto, para o teste de hoje, parece aceitável para mim.
O fato é que o protocolo USB é um pacote. E o próprio pacote é capturado no nível do hardware. Não deve haver uma situação dentro do bloco em que um byte esteja corrompido e, em seguida, dados úteis sejam enviados novamente. Pelo menos por causa dos problemas que queremos pegar nas bibliotecas agora, isso não vai surgir. Se os dados forem corrompidos, globalmente. Se os dados antigos forem sobrescritos pelos novos, o primeiro byte será sobrescrito primeiro e é diferente em pacotes diferentes.
Em princípio, todos poderão reescrever meu código para uma versão diferente dos dados de teste ... Hoje, simplesmente enviarei uma palavra incremental de 32 bits e, ao recebê-la, verificarei se o incremento ocorre sem quebrar a sequência.
Como rastrearemos o resultado
Como vamos descobrir se tudo funciona? Um LED não será suficiente. Bem, para adicionar um UART, você precisa codificar o código de outra pessoa. Você pode cometer seus erros. Vamos usar uma funcionalidade que conheço há muito tempo, mas sempre usei apenas no ambiente de desenvolvimento Keil. Hoje vou mostrar como usá-lo no Eclipse. Dos comentários ao último artigo, percebi que nem todo mundo conhece essa tecnologia.
A porta de depuração JTAG permite trabalhar apenas quando o núcleo do processador está parado. Isso não é aceitável para USB. Nesse local e durante a operação normal, as paradas são repletas de timeouts e, em nosso caso, mesmo se não capturarmos o timeout, a velocidade pode ser subestimada. Felizmente, a porta de depuração SWD permite monitorar a memória em tempo real. Já em 2016, verifiquei usando um osciloscópio, que permite definir a sincronização pela duração do pulso, o acesso à memória por SWD praticamente não desacelera o núcleo do processador. Mas como o usamos?
A primeira coisa em que contaremos hoje é a capacidade do CubeIDE (que é um Eclipse dopado) de exibir variáveis em tempo real. Criaremos um grupo de variáveis, onde o programa exibirá muitas informações úteis, e começaremos a rastreá-las na tela. Muitas pessoas sabem disso, mas até agora nem todos. Deixe todos saberem agora.
E a segunda é o que os caras e eu descobrimos recentemente. Ninguém em nosso escritório sabia disso. Acontece que se você construir um projeto com um compilador em lote, incorporando informações de depuração do Dwarf-2 nele, então esse arquivo elf pode ser aberto no Eclipse para depuração e você pode obter um link completo com suas fontes. Ao mesmo tempo, as próprias fontes não precisam estar conectadas ao projeto.O depurador puxará automaticamente os caminhos para eles a partir das informações de depuração. Agora eu sempre faço isso. Um projeto GCC ou CLang é construído e eu apenas conecto elf ao Eclipse e o rastreio, sem perder tempo anexando o próprio projeto a este Eclipse. Às vezes, eles até me enviam arquivos elf coletados por conjuntos de ferramentas que não estão na minha máquina. Inclusive compilado em Linux (e trabalho com Windows). O método funciona mesmo nestes casos, desde que o projeto seja enviado em conjunto completo: elfo e suas fontes. Hoje nos ajudará a não finalizar os projetos do autor em termos de sua estrutura. Vou apenas construir tudo com base no makefile "nativo" e, em seguida, conectar com o depurador ao arquivo elf.
Treinamos para nos conectar
A primeira coisa que precisamos fazer no CubeIDE é um projeto para STM32F103. "Espere um minuto!", Exclama o leitor atento ... "O autor acaba de prometer que não terá que fazer nada com o projeto original !!!" Está tudo bem. Essa é a peculiaridade do CubeIDE. Precisamos de um projeto para STM32F103. Algum. O principal é que ele está em STM32F103. Nós o criamos, coletamos e esquecemos. O que está nele não é importante. O próprio fato de sua existência no ambiente de desenvolvimento é importante.
Agora, em CubeIDE, vamos às configurações do depurador. Por exemplo, assim:
Não teríamos que perverter com a criação do projeto esquerdo se selecionássemos o item GDB Hardware Debugging. Eu sempre escolho em Eclipses regulares. Tentei selecioná-lo aqui:
Ai de mim. O projeto da esquerda não será necessário, mas a funcionalidade de exibição de variáveis em tempo real está indisponível. Portanto, infelizmente e ah. Escolha o aplicativo STM32 Cortex-M C / C ++. Já tenho duas configurações lá. Agora, para ter certeza de que não te enganei, vou criar um terceiro. Para fazer isso, clico duas vezes aqui:
Vou nomear a configuração Artigo:
Você precisa selecionar o caminho para o arquivo elf:
Eu escolhi este caminho (não deve haver letras russas em nenhum lugar do caminho):
E aqui o erro pisca . Aqui está, o vermelho:
Para removê-lo, tenho que selecionar um projeto vinculado a STM32F103. É aqui que você precisa acessar o Browse:
e selecionar o projeto esquerdo criado anteriormente.
O vermelho (sinal de erro) sumiu:
Ops! Nesta figura, você pode ver que depois de selecionar um projeto, o nome do arquivo elf saltou. O elfo deste projeto foi registrado. Eu tive que selecionar o que eu precisava depois de especificar o projeto novamente. Não é de admirar que repassei todos os pontos.
Como não temos nada para coletar aqui (coletamos tudo em lotes), precisamos marcar a caixa para que o sistema não tente fazer trabalhos inúteis:
Nesta guia - tudo. Vá para a guia Depurador. É verdade que nada precisa ser mudado aqui. Pelo menos, se tudo estiver configurado da mesma maneira:
Na verdade, você não precisa mudar nada em nenhum outro lugar. Bem, vamos começar a depurar?
Tecnicamente, sim. Organizacionalmente - primeiro precisamos preparar o código que iremos executar.
Verificando a primeira biblioteca
Portanto, baixe o projeto stm32samples / F1-nolib / CDC_ACM em master eddyem / stm32samples GitHub para autoria EddyEm...
Não se esqueça de adicionar a formação de informações de depuração anã-2 ao makefile:
O mesmo com o texto:
CFLAGS += -O2 -g -gdwarf-2 -D__thumb2__=1 -MD
Começamos a editar o código.
Há um loop infinito na função main (). Vou deixar apenas chifres-sim-pernas dele:
while (1){ IWDG->KR = IWDG_REFRESH; // refresh watchdog usb_proc(); get_USB(); }
Farei uma função get_USB () funcional como esta:
uint32_t loop = 0; uint32_t errors = 0; uint32_t errState = 0; int32_t lastData = 0; int32_t show = 0; int32_t pkt = 0; #define USBBUF 63 char tmpbuf[USBBUF+1]; int32_t* pData = (int32_t*) tmpbuf; // usb getline char *get_USB() { int x = USB_receive((uint8_t*)tmpbuf) / sizeof(uint32_t); int i; show += 1; if(!x) return NULL; pkt += 1; // - // ! if (pData [0] == 0) { lastData = 0; errState = 0; loop += 1; } // if (errState) { return NULL; } // for (i=0;i<x;i++) { // ! if (pData[i]!=lastData++) { // ! errState = 1; // errors += 1; // return NULL; } } // return NULL; }
Um monte de variáveis globais são feitas para monitorá-las em tempo real. Os locais são perdidos a cada inicialização. Os globais são visíveis para sempre.
A variável show indicará que o depurador está realmente exibindo tudo. É incrementado cada vez que a função é inserida, quer haja dados ou não. E chamamos a função em um loop infinito o tempo todo.
A variável pkt mostrará que os dados estão realmente chegando (a princípio não vieram de mim). Só aumentará se não sairmos devido ao fato de não haver nada de USB.
lastDatavai mostrar quantos já contamos no teste. Isso garantirá que estamos realmente trabalhando com grandes blocos de dados. O valor desta variável ao final do teste mostra o tamanho do bloco em palavras duplas. Para entender quantos bytes se passaram, você precisa multiplicar o valor por 4. O
loop aumentará quando um bloco de dados chegar, começando em zero. Grosso modo, este é o número do teste. Bem, ou o número da corrida. Quando eu coleto estatísticas para plotagem, há algumas dessas execuções. Tamanhos diferentes dos blocos solicitados, multiplicados por repetições para calcular a média dos resultados.
errState- uma variável auxiliar que evita o aparecimento de uma bola de neve de erros. No primeiro erro, ele voa para um e para de analisar os dados antes de iniciar um novo teste.
erros - o contador dos erros que ocorreram uma vez. No início, eu mesmo cometi um erro de lógica, depois esse contador foi aumentando constantemente. Mas se tudo estiver bem, não deve aumentar.
Eu quase esqueci. Também comentei a verificação do flag na função USB_receive:
O mesmo com o texto:
uint8_t USB_receive(uint8_t *buf){ if(/*!usbON ||*/ !rxNE) return 0; ...
Este sinalizador é definido quando o terminal define a velocidade da porta COM virtual. Meu programa de teste, por outro lado, abre o dispositivo diretamente por meio do driver WinUSB e não faz nada para personalizar a funcionalidade do CDC. A coisa mais fácil a fazer era ignorar esse sinalizador.
Nós vamos. Construa o projeto com um compilador de lote e execute-o no depurador CubeIDE. Como me disseram, nem todos os leitores gostam de desenhos animados. Para alguns, eles distraem a leitura do texto. Mas é muito bom ver isso.
Está funcionando! Está funcionando! Está funcionando!
Nós vamos. Adicione o preenchimento da matriz ao programa de teste:
QByteArray data; data.resize(totalSize); uint32_t* dwPtr = (uint32_t*) data.constData(); for (uint32_t i = 0;i<totalSize/4;i++) { dwPtr[i] = i; }
E fazemos o teste. Obtemos esse tipo de beleza nas variáveis (erro zero):
E aqui está um gráfico da velocidade:
Os valores são um pouco menores do que em uma operação completamente ociosa, mas ainda agradável. Em geral, ST-LINK foi adicionado ao meu sistema e o número de bits em execução no USB depende dos dados que estão sendo bombeados (às vezes, um bit de sincronização pode ser inserido).
Um cavalo esférico funciona no vácuo, mas e o verdadeiro?
Existe um problema potencial com todo este sistema. Agora, a função de recebimento de dados é constantemente chamada em um loop infinito. Não temos nenhum trabalho especial útil no momento. E se eles forem? Então, a chamada desta função pode ocorrer não imediatamente após a chegada do pacote, mas com um atraso.
Vamos testar opções diferentes? Na mente - seria necessário, mas não há tempo. Atualmente estou ocupado trabalhando com um controlador completamente diferente. E então eu me diverti no fim de semana. Portanto, iremos por outro caminho. Iremos colar a recepção de dados ao fato de sua chegada.
Essas coisas são feitas com interrupções. Mas no último artigo foi dito que se esticarmos o manipulador de interrupções USB, o hardware começará a enviar NAKs e todo o encanto da biblioteca em questão será inútil. Como obtemos uma interrupção, mas não nos demoramos na interrupção?
Bem, o caminho é conhecido aqui. No manipulador de interrupção USB, devemos fazer com que, imediatamente após sair, a interrupção também seja disparada, mas alguma outra. E aí iremos rapidamente, com uma latência baixa garantida, levar os dados do buffer do hardware para o nosso interno. Em que interrupção sentar? Examinando o código de inicialização. Ou seja, interromper manipuladores. Nossa tarefa é encontrar o que não foi usado.
Aqui está o arquivo de interesse para nós
\ stm32samples-master \ F1-nolib \ inc \ startup \ vector.c
Deixe-me pegar emprestado uma interrupção do terceiro UART. Na verdade, também não usamos o primeiro. Mas talvez seja algum tempo depois. E nunca usei o terceiro na minha vida. Portanto, pessoalmente, vou sentar-me atrevidamente neste manipulador em particular. É assim que é descrito:
[NVIC_USART3_IRQ] = usart3_isr, \
Sabendo o nome, crie uma função no arquivo main.c:
void usart3_isr() { NVIC_ClearPendingIRQ(USART3_IRQn); get_USB(); }
Esta será uma espécie de função de retorno de chamada. E já vai nos chamar de código que escrevemos recentemente. E vamos comentar a chamada para get_USB () em um loop infinito.
Agora precisamos definir essa interrupção para uma prioridade mais baixa para que não interfira com ninguém. Na vida real, talvez você precise ser criativo ao escolher uma prioridade. Mas hoje vou ficar com o décimo quinto. Adicionamos o seguinte código à parte de inicialização da função main ():
NVIC_SetPriority(USART3_IRQn, 15); NVIC_EnableIRQ(USART3_IRQn);
Bem, agora vem a parte divertida. No manipulador de interrupção USB, adicione uma provocação para acionar a interrupção USART3, se houver uma chamada para nosso endpoint:
O mesmo texto.
#include "stm32f10x.h" … void usb_lp_can_rx0_isr(){ LED_off(LED0); if(USB->ISTR & USB_ISTR_RESET){ … } if(USB->ISTR & USB_ISTR_CTR){ // EP number uint8_t n = USB->ISTR & USB_ISTR_EPID; if (n == 1) { NVIC_SetPendingIRQ(USART3_IRQn); } // copy status register uint16_t epstatus = USB->EPnR[n]; // copy received bytes amount …
Como a prioridade é baixa, nada acontecerá até o final da interrupção USB. Mas assim que terminar, eles vão nos ligar imediatamente. Porque não temos outras interrupções ainda. Mesmo com a décima quinta prioridade, seremos os VIPs.
Nós lançamos. A princípio, é assustador que a variável show não esteja aumentando. Mas é normal. Agora, a função não é chamada incondicionalmente, mas somente após a interrupção real. Portanto, precisamos começar a testar.
Você pode assistir ao processo de teste para sempre.
E aqui está a métrica de velocidade:
Verificando a segunda biblioteca
Agora verificamos a biblioteca usb / 5.CDC_F1 no principal COKPOWEHEU / usb GitHub por COKPOWEHEU... Uma descrição desta biblioteca pode ser encontrada aqui: USB nos registros: STM32L1 / STM32F1 / . É aqui que recebemos funções de retorno de chamada para lidar com a atividade do terminal. Aqui vamos consertar. A variável show não é mais necessária. Sempre somos chamados na chegada dos dados. Caso contrário, obtemos praticamente o mesmo código.
uint32_t loop = 0; uint32_t errors = 0; uint32_t errState = 0; int32_t lastData = 0; int32_t pkt = 0; void data_out_callback(uint8_t epnum){ int i; uint8_t buf[ ENDP_DATA_SIZE ]; int32_t* pData = (int32_t*) buf; int len = usb_ep_read_double( ENDP_DATA_OUT, buf) / sizeof (uint32_t); if(len == 0)return; pkt += 1; // - // ! if (pData [0] == 0) { lastData = 0; errState = 0; loop += 1; } // if (errState) { return NULL; } // for (i=0;i<len;i++) { // ! if (pData[i]!=lastData++) { // ! errState = 1; // errors += 1; // return NULL; } } }
Ao verificar comigo, CubeIDE, por algum motivo, determinou incorretamente o endereço de partida. Talvez haja algum tipo de incompatibilidade com o mesmo projeto de "esquerda". Vamos adiar isso para um estudo separado. Até comecei a entender, mas logo no início entrei com o valor correto do registro do PC. O código foi executado e começou a funcionar. Executamos o teste. O número de erros também é zero:
A velocidade também é decente:
Conclusão
Ambas as bibliotecas USB russas lidaram com testes de carga difíceis. Nenhum deles saiu da corrida. É verdade, eu sei em primeira mão que o teste não prova a ausência de erros, ele revela sua presença. Mas os testes especificamente citados não revelaram nada. Isso dá esperança de que qualquer uma dessas bibliotecas possa ser usada.
Ao longo do caminho, dominamos a substituição da saída de depuração monitorando uma série de variáveis em tempo real por meio da porta SWD. Resumindo, também dominamos a depuração de qualquer aplicativo construído em lote no Eclipse, mas ao longo do caminho, devido à mistura dos dois projetos, tive algumas dificuldades que tive que superar corrigindo o registro do PC diretamente. Mas em um Eclipse normal, esse tipo de mixagem não é necessário. E no final, mesmo com a ajuda de uma foice, um martelo e uma espécie de mãe, o objetivo final foi alcançado. A depuração foi feita. Ao mesmo tempo, os códigos-fonte do Syakh ainda eram exibidos no Eclipse.
Posfácio
Quando o artigo já estava escrito, mas ainda estava em processo de upload para a Habr, surgiu um material tão maravilhoso para a autoria DSarovsky... Lá, também, o acesso ao USB é implementado, mas isso é feito por meio de uma biblioteca feita no meu estilo favorito - o estilo de Konstantin Chizhov.
Sou simplesmente obrigado a notar a existência de uma biblioteca feita em uma versão tão bonita. No momento, verificamos o desempenho com seu autor e descobrimos que até agora sua velocidade é típica, não máxima. Mas é possível que quando você ler essas linhas, já tenha feito overclock. Portanto, vou deixar um link para ele entre outros. Ela simplesmente tem que decolar! Bibliotecas neste estilo não podem deixar de decolar!