Inverso eficiente (1 / x) para AVR


12

Estou tentando encontrar uma maneira eficiente de calcular um inverso em um AVR (ou aproximando-o).

Estou tentando calcular o período de pulso de um motor de passo para que eu possa variar a velocidade linearmente. O período é proporcional ao inverso da velocidade ( p = K/v), mas não consigo pensar em uma boa maneira de calcular isso em tempo real.

Minha fórmula é

p = 202/v + 298; // p in us; v varies from 1->100

Testando no Arduino, a divisão parece ser completamente ignorada, ficando pfixa em 298(embora talvez isso seja diferente no avr-gcc). Eu também tentei somar vum loop até exceder 202e contar os loops, mas isso é bem lento.

Eu poderia gerar uma tabela de pesquisa e armazená-la em flash, mas estava pensando se havia outra maneira.

Edit : Talvez o título deva ser "divisão eficiente" ...

Atualização : Como pingswept aponta, minha fórmula para mapear o período para a velocidade está incorreta. Mas o principal problema é a operação de divisão.

Edit 2 : Em uma investigação mais aprofundada, o divide está funcionando no arduino, o problema foi devido à fórmula incorreta acima e a um transbordamento int em outro lugar.


2
V é um número inteiro ou ponto flutuante?
Mjh2007

Um número inteiro, mas como fornece um período em nós, a divisão inteira é precisa o suficiente aqui.
Peter Gibson

Você pode pré-calcular os valores dos 100 números inteiros e criar uma tabela de pesquisa de pré-escaladores para multiplicação, se estiver realmente preocupado com a velocidade. Claro que há uma troca de memória.
RYS 30/09

Respostas:


7

Uma coisa boa sobre a divisão é que mais ou menos todo mundo está fazendo isso. É um recurso bastante básico da linguagem C, e compiladores como o AVR-GCC (chamado pelo Arduino IDE) escolherão o melhor algoritmo de divisão disponível, mesmo quando o microcontrolador não tiver uma instrução de divisão de hardware.

Em outras palavras, você não precisa se preocupar sobre como a divisão é implementada, a menos que você tenha um caso especial muito estranho.


Se você se preocupa, pode gostar de ler os algoritmos de divisão sugeridos oficiais da Atmel (um otimizado para o tamanho do código e outro otimizado para a velocidade de execução; nem consome memória de dados). Eles estão dentro:

http://www.atmel.com/dyn/resources/prod_documents/doc0936.pdf

que é a Nota de aplicação "AVR200: Multiplicar e dividir rotinas" listada na página Atmel para seus (razoavelmente grandes) processadores Atmega como o Atmega 168 e Atmega 328 usados ​​nos Arduinos padrão. A lista de fichas técnicas e notas de aplicação está em:

http://www.atmel.com/dyn/products/product_card.asp?part_id=4720


4

parece-me que tudo o que você precisa é uma tabela de pesquisa de 100 entradas. Não fica muito mais rápido que isso.

#define VALUE_FOR_V_EQUALS_ZERO 0
uint16_t formula_lookup[100] = {VALUE_FOR_V_EQUALS_ZERO, 500, 399, 365, 348, ..., 300};

...

//"calculate" formula
p = formula_lookup[v > 67 ? 67 : v];

EDITAR você na verdade apenas uma tabela de pesquisa de 68 valores, já que os valores de v maiores que 67 sempre são avaliados como 300.


Como eu disse na pergunta, fiquei pensando se havia outra maneira
Peter Gibson

3

Existem algumas técnicas muito boas mencionadas no livro "Hackers Delight, de Henry Warren e em seu site hackersdelight.org . Para uma técnica que funciona bem com microcontroladores menores ao dividir por constantes, consulte este arquivo .


Eles parecem bons para dividir por constantes, como você diz, mas não se aplicam realmente ao meu problema. Ele usa técnicas como pré-calcular o inverso - multiplicar por ele e depois mudar.
Peter Gibson

Esse é um excelente livro!
Windell Oskay

3

Sua função não parece que daria o resultado desejado. Por exemplo, o valor 50 retorna aproximadamente 302, enquanto 100 retorna aproximadamente 300. Esses dois resultados quase não causarão alterações na velocidade do motor.

Se eu entendi direito, você está realmente procurando uma maneira rápida de mapear os números de 1 a 100 para o intervalo de 300 a 500 (aproximadamente), de modo que 1 mapeie para 500 e 100 mapeie para 300.

Talvez tente: p = 500 - (2 * v)

Mas eu posso estar entendendo errado - você está tentando calcular o tempo de uma onda quadrada de frequência constante? O que é o 298?


Sim, obrigado, a fórmula está errada. O objetivo é obter aceleração linear a partir da saída do stepper, variando a velocidade alvo por uma constante a cada intervalo de tempo (velocidade ++, digamos). Isso deve ser mapeado para o período (frequência) em que uma borda + ve é enviada ao controlador do motor de passo - daí a relação inversa (p = 1 / v).
Peter Gibson

Você quer dizer aceleração constante, ou seja, uma velocidade linearmente crescente?
pingswept 13/09/10

Ah sim, aceleração constante, eu muck que quando originalmente escrita a questão e lembre-se corrigi-lo lá também
Peter Gibson

3

Uma maneira eficiente de aproximar divisões é por turnos. por exemplo, se x = y / 103; dividir por 103 é o mesmo que multiplicar por 0,0097087, portanto, para aproximar esse primeiro, selecione um número de turno 'bom' (ou seja, um número de base 2, 2,4,8,16,32 e assim por diante)

Neste exemplo, 1024 é um bom ajuste, pois podemos dizer que 10/1024 = 0,009765 É possível codificar:

x = (y * 10) >> 10;

Lembrando, é claro, de garantir que a variável y não ultrapasse seu tipo quando multiplicada. Não é exato, mas é rápido.


Isso é semelhante às técnicas nos links que o timrorr forneceu e funciona bem para dividir por constantes, mas não ao dividir por um valor desconhecido em tempo de compilação.
Peter Gibson

3

Em outra nota, se você estiver tentando fazer uma divisão em uma CPU que não suporta divisão, há uma maneira muito legal de fazer isso neste artigo da Wiki.

http://en.wikipedia.org/wiki/Multiplicative_inverse

Para aproximar o recíproco de x, usando apenas multiplicação e subtração, pode-se adivinhar um número y e depois substituir repetidamente y por 2y - xy2. Quando a mudança em y se torna (e permanece) suficientemente pequena, y é uma aproximação do recíproco de x.


Interessante, eu me pergunto como isso se compara com os outros métodos mencionados
Peter Gibson

1

Esse processo aqui parece compatível com o mcu, embora possa precisar de um pouco de portabilidade.

Embora pareça que o LUT seria mais fácil. Você precisaria apenas de 100 bytes, menos se usasse alguma interpolação e, como o LUT é preenchido com constantes, o compilador pode até localizá-lo na área de código em vez da área de dados.


Tentei algo semelhante ao somar o divisor até que seja igual ou superior ao dividendo, mas achei que era bastante lento. Parece que o LUT será o caminho - usando o avr-gcc, você precisa de macros especiais em <avr / progmem.h> para armazená-lo em flash.
Peter Gibson

1

Verifique se a divisão está sendo executada como ponto flutuante. Eu uso o Microchip, não o AVR, mas ao usar o C18, você precisa forçar seus literais a serem tratados como ponto flutuante. Por exemplo. Tente alterar sua fórmula para:

p = 202.0/v + 298.0;


1

Você quer rápido, então aqui vai ..... Como o AVR não pode normalizar com eficiência (deslocando para a esquerda até que você não possa mais mudar), ignore os algoritmos de pseudo ponto flutuante. A maneira mais simples para uma divisão inteira muito precisa e rápida em um AVR é ​​através de uma tabela de pesquisa recíproca. A tabela armazenará recíprocos dimensionados por um número grande (digamos 2 ^ 32). Em seguida, você implementa uma multiplicação não assinada32 x não assinada32 = não assinada 64 no assembler, então responda = (numerador * inverseQ32 [denominador]) >> 32.
Eu implementei a função de multiplicação usando o assembler embutido (envolvido na função ac). O GCC suporta "longos longos" de 64 bits, no entanto, para obter o resultado, você precisa multiplicar 64 bits por 64 bits, e não 32x32 = 64 devido a limitações da linguagem C na arquitetura de 8 bits ......

A desvantagem deste método é que você usará 4K x 4 = 16K de flash se desejar dividir por números inteiros de 1 a 4096 ......

Agora, uma divisão não assinada muito precisa é alcançada em cerca de 300 ciclos em C.

Você pode considerar o uso de números inteiros em escala de 24 ou 16 bits para obter mais velocidade e menos precisão.


1
p = 202/v + 298; // p in us; v varies from 1->100

O valor de retorno da sua equação já é p=298que o compilador divide primeiro e depois adiciona, use a resolução muldiv inteira que é:

p = ((202*100)/v + (298*100))/100 

Usando isso é o mesmo multiplicar a*f, com a = número inteiro f = fração.

Esse rendimento, r=a*fmas f=b/cdepois, r=a*b/cmas ainda não funciona, porque a posição dos operadores produz a r=(a*b)/cfunção final ou muldiv, uma maneira de calcular números de fração usando apenas um número inteiro.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.