CORDIC em Firmware

CORDIC em Firmware

Caro leitor(a), neste artigo iremos apresentar o início da parte prática descrita no texto anterior, onde apresentamos uma introdução à estrutura básica e funcionamento do processador CORDIC. Vimos que ele pode ser usado em diversos modos, computando diferentes tipos de funções trigonométricas, hiperbólicas e lineares. Observamos também que uma das características que mais chama a atenção é sua simplicidade de construção, sendo necessários apenas operações de deslocamento, adição e uma pequena tabela de somente leitura. Além disso, também foi mostrado que uma vez determinada a resolução desejada, o cálculo de tais funções é executado em tempo constante, ou seja, O(1), sendo indicado para uso em sistemas embarcados de tempo real.

Neste segundo momento vamos aproveitar a simplicidade da estrutura interna do CORDIC e implementar um processador em firmware elementar, que inicialmente será capaz de calcular seno e cosseno de um determinado ângulo. Outras funções poderão ser adicionadas futuramente, mas essas duas primeiras podem ser perfeitas para demonstração, uma vez sendo comum a necessidade de geração de sinais senoidais com microcontroladores de poucos recursos.

O Hardware utilizado, arduino é perfeito para isso

O uso do algoritmo Cordic em software é, de modo geral, pouco recomendado em processadores 32 bits como ARM-Cortex ou RX63, ou microcontroladores de 16 bits mais poderosos como o dsPIC da Microchip, pois essas arquiteturas geralmente são dotadas de recursos de permitem implementações mais poderosas de calculadores de funções lineares e trigonométricas, sendo que já tem em hardware multiplicação, divisão e até mesmo cálculo de raíz quadrada.

A quantidade de memória de programa nesses dispositivos também é bem mais elevada, sendo facilmente superior a 32 KB, aliados a instruções de endereçamento poderosas de 16, 32 e até mesmo 64 bits. Uma tabela de busca em ROM oferece execução em tempo constante O(1), porém com menos latência. Porém a implementação em firmware do algoritmo Cordic é muito bem-vinda em arquiteturas menores, os bons 8 bits que estão na ativa e em muitas aplicações “deep-embedded”. É o caso de arquiteturas como PIC, AVR, 8051 e MSP430 – famílias G e F (além de processadores sintetizáveis para FPGA).

O Cordic, para arquiteturas com poucas instruções, exige a mesma coisa para implementações de alto desempenho em hardware. Precisa dos seguintes componentes:

  • 6 registradores de 16 bits (um para cada elemento de rotação);
  • 1 shift registers para executar as multiplicações;
  • 1 somador (ou instrução de soma);
  • 1 tabela com dimensão de Numero de rotações x 16 bits.

Os componentes acima estão sempre disponíveis em “embedded-targets” de arquiteturas dotadas de pouco poder de processamento, que é o caso dos conhecidos arduino Uno e Mega. Ambos possuem um processador de 8 bits AVR dotado de 131 instruções bem poderosas, além do sistema de enderaçamento poderosíssimo com “indexers” (os famosos pares X, Y e Z), geralmente encontrados em arquiteturas maiores, é perfeito para isso.

Abaixo uma cortesia, uma imagem da aquitetura simplificada de um AVR padrão utilizado em Arduinos.

Arquitetura interna do AVR utilizado em Arduinos - CORDIC em Firmware
Figura 1: Arquitetura interna do AVR utilizado em Arduinos

O Firmware de demonstração

O firmware de demonstração buscou ser o mais simples e portável possível, sendo compilável sem qualquer alteração para qualquer outro Arduino. Foi desenvolvido em C++, que no ambiente Arduino é bem mais popular que o tradicional C. Os arquivos cordic.cpp, cordic.h e frac_math_helper.h são responsáveis por implementar a “engine” para cáculo de seno, cosseno e gerenciamento do formato numérico, respectivamente. Falando em formato numérico, essa é outra vantagem do uso do cordic, sua implementação suporta tanto ponto-fixo, como flutuante. Para essa demonstração optamos pelo formato em ponto fixo obedecendo as regras abaixo: 

  • Para os resultados de seno e cosseno, o formato numérico é Q15, ou seja, 15 bits de mantissa com range entre -1 e 1;
  • Para ângulos (passados em radianos, somente!), formato IQ3.12, com 3 bits inteiros e 12 de mantissa, variando entre -7 e 7.

O uso de ponto fixo permite lidar com os problemas de overflow, e ao mesmo tempo oferece macros para conversão entre Q (ponto-fixo) e float (ponto-flutuante). Essas macros são mostradas em frac_math_helper.h abaixo:

/*
 * @brief helper for fractional values
 *
 */

#ifndef __FRAC_MATH_HELPER_H
#define __FRAC_MATH_HELPER_H


/** convert to frac16 macro */
#define TO_FRAC_16(x) (signed short)(x * 32768.0)
#define TO_FRAC_12(x) (signed short)(x * 4096.0)


/** convert to float from frac16 */
#define FRAC_16_TO_FLOAT(x) (float)((float)x / 32768.0)
#define FRAC_12_TO_FLOAT(x) (float)((float)x / 4096.0)


/** type of fractionals */
typedef signed short frac16;
typedef signed short frac12;



#endif /* __FRAC_MATH_HELPER_H */

A interface e implementação da classe cordic é mostrada abaixo:

/*
 *  @brief cordic trignometric class
 */

#ifndef __CORDIC_H
#define __CORDIC_H

#include "frac_math_helper.h"


class cordic_engine {

public:
    /* Constructor / Desctructor */
    cordic_engine(int res);
    ~cordic_engine();

    /* helper functions */
    void set_resolution(int res);
    int get_resolution(void);

    /* math functions */
    frac16 sine(frac12 angle);
    frac16 cosine(frac12 angle);

    /* rotate functions */
    /** TODO: Implement rotation functions */

private:
    frac12 *m_atanTab;
    int resolution;
};


#endif /* __CORDIC_H */

Vejam que a rotina de rotação não está disponível por enquanto. A ideia é comentar mais sobre o seu uso na próxima parte desta série, em compensação a resolução pode ser computada em tempo de execução, dependendo dos recursos de memória ou velocidade disponível. Contudo, dado o uso do formato fracionário acima de 24 iterações, a resolução não muda. Para contornar essa limitação, deixo a cargo do leitor reimplementar a classe com um formato mais poderoso como Q31 e IQ7.24, eis a implementaçao de cordic:

/*
 * @brief cordic class implementation
 */
 
#include <math.h>
#include "cordic.h"


/** static variables */
static const frac16 cordic_gain = TO_FRAC_16(0.607252935f);

/*
 * cordic private core routine
 */
static inline void CordicCompute(frac16 *x, frac16 *y, frac12 *z, int iter, const frac12 *tab)
{
    frac16 xtmp = 0, xprev = 0, ytmp = 0, yprev = 0;
    frac12 ztmp = 0;
  
    /*
     *initialize cordic variables:
     */
    xtmp = *x;
    ytmp = *y;
    ztmp = *z;


    signed int  d = ( ztmp < 0? -1 : 1);


    for( int i = 0; i < iter; i++) {
        /*
         * To compute cordic new operation we need
         * to execute for N iterations the following
         * equation sets:
         *
         * xnext = x - y*d*2 ^ -i
         * ynext = y + x*d*2 ^ -i
         * znext = z - d*atantab[i]
         */

        xprev = (xtmp >> i);
        yprev = (ytmp >> i);
        d =( ztmp < 0? -1 : 1);

        if(d > 0) {
            xtmp = xtmp - yprev;
            ytmp = ytmp + xprev;
            ztmp = ztmp -  tab[i];
        }
        else {
            xtmp = xtmp + yprev;
            ytmp = ytmp - xprev;
            ztmp = ztmp +  tab[i];
        }

    }

    /*
     * after end of computation deposit the result:
     */
    *x = xtmp;
    *y = ytmp;
    *z = ztmp;

}



/** Cordic public methods */

/*
 * cordic_engine()
 */
cordic_engine::cordic_engine(int res)
{

    resolution = res;
    m_atanTab = new frac12[resolution];

    /*
     * based on resolution computes the atantab
     */
     frac16 base = TO_FRAC_12(0.785398f);

     for(int i = 0 ; i < res; i++) {
         m_atanTab[i] = base >> i;
     }

}

/*
 * ~cordic_engine
 */
cordic_engine::~cordic_engine()
{
    /* only needs to destroy atan tab memory area */
    delete []m_atanTab;
}

/*
 * set resulotion()
 */
void cordic_engine::set_resolution(int res)
{
    /*
     * WARNING, this function is not thread safe,
     * make sure the cordic is not running in other
     * context before to invoke this method
     */
     resolution = res;
     frac16 base = TO_FRAC_12(0.785398f);

     delete []m_atanTab;
     m_atanTab = new frac12[res];

     /*
      * re-computes the new length lookup
      */
     for(int i = 0 ; i < res; i++) {
         m_atanTab[i] = base >> i;
     }


}


/*
 * get_resolution()
 */
int cordic_engine::get_resolution(void)
{
    return(resolution);
}

/*
 * sine()
 */
frac16 cordic_engine::sine(frac12 angle)
{
    frac16 x, y;
    frac12 z;

    /*
     * intialize cordic for Sine
     */
    z = angle;
    x = cordic_gain;
    y = 0;

 
    /* compute it */
    CordicCompute(&x, &y, &z, resolution, m_atanTab);

    return(y);
}

/*
 * cosine()
 */
frac16 cordic_engine::cosine(frac12 angle)
{
    frac16 x, y;
    frac12 z;

    /*
     * intialize cordic for Sine
     */
    z = angle;
    x = cordic_gain;
    y = 0;

    /* compute it */
    CordicCompute(&x, &y, &z, resolution, m_atanTab);

    return(x);
}

A aplicação principal é igualmente simples, apenas computa seno e cosseno de um ângulo de 0º a 90º ou de 0º a -90º (selecionável através de uma constante), e disponibiliza seu resultado via terminal. Na própria IDE do arduino a ferramenta de console pode ser utilizada para verificar o resultado dos cálculos. Dica: Aproveite e teste o resultado dos cálculos com diferentes resoluções. Eis o arquivo principal:

/**
 *     @brief Cordic application demo
 */

#include <math.h>
#include "cordic.h"
#include "frac_math_helper.h"

/** Defines the resolution of our cordic engine */
#define DEMO_RESOLUTION 16

/** Define if angle increments or decrements */
#define DIRECTION       1

/** Variables used in this file */
cordic_engine cordic(DEMO_RESOLUTION);

/** Angle variable */
frac12 angle = TO_FRAC_12(0.0f);
frac16 cosine;
frac16 sine;

/**
 * @brief Arduino default setup routine
 */
void setup (void)
{
  /* Open serial port */
  Serial.begin(115200);

}

/**
 * @brief Arduino default main loop
 */
void loop (void)
{
  /* Prints a greeting message */
  Serial.println("Welcome to cordic Demo App \n\r");


  for (;;) {

    /* take sine and cosine of current angle and
     * prints on the console
     */
    sine = cordic.sine(angle);
    cosine = cordic.cosine(angle);


    Serial.print("Sine value of   ");

    /* converts angles to degrees */
    Serial.print((FRAC_12_TO_FLOAT(angle) * 180.0f) / 3.14159f);
    Serial.print("  is: ");
    Serial.print(FRAC_16_TO_FLOAT(sine));
    Serial.print("\n\r");

    /* converts angles to degrees  in cosine too*/
    Serial.print("Cosine value of   ");
    Serial.print((FRAC_12_TO_FLOAT(angle) * 180.0f) / 3.14159f);
    Serial.print("  is:  ");
    Serial.print(FRAC_16_TO_FLOAT(cosine));
    Serial.print("\n\r");

#if DIRECTION == 1
    /* We compute a quarter turn of canonical circle
     * which stills sufficient to demonstrate
     * basic cordic funcionality
     */
    angle += TO_FRAC_12(0.02f);
    if (angle > TO_FRAC_12(1.5708f)) {
    angle = TO_FRAC_12(0.0f);
    }
#else
    angle -= TO_FRAC_12(0.02f);
    if (angle < TO_FRAC_12(-1.5708f)) {
    angle = TO_FRAC_12(0.0f);
    }
#endif
    

     delay(500);
   
  }
}

Na seção Links úteis está disponível para o link para o repositório no github, para efetuar um teste rápido. Você pode buscar o projeto diretamente pelo link, ou pela linha de comando, na pasta Arduino (supondo que você tenha o client git instalado em sua máquina), digite:

git clone https://github.com/uLipe/cordic_demo_arduinoMega.git

Abra a IDE Arduino, vá em File->Sketchbook e carregue a pasta demo, conecte seu Arduino à porta USB, e clique no ícone carregar. Ele irá compilar e gravar a imagem na sua placa. Abrindo o terminal serial com velocidade 115200 bps, você deve ver algo assim na tela:

Captura de Tela 2016-06-03 às 8.46.30 PM

Conclusão

Mesmo sendo indicado para ser sintetizado em hardware, o algoritmo cordic, se bem realizado, pode ser uma boa pedida para microcontroladores com poucos recursos e mesmo para ferramentas de protipagem de baixo custo como o Arduino. Pois em todas as suas implementações os componentes básicos sempre estarão disponíveis em processadores de 8 bits. Mas não se engane, apesar de ter escrito que ele nem sempre é a melhor escolha para uso como calculador de funções trigonométricas em processadores maiores, com algumas derivações, o “core” da engine cordic pode ser aproveitado para muitas outras aplicações, inclusive em processamento digital de sinais e modulação em quadratura de sinais (Dica: A engine cordic é uma alternativa super eficiente para modulação em quatratura, pois entrega o resultado de ambas as componentes com um único processamento, e pode ficar ainda melhor se aprovetarmos o set SIMD oferecido nos Cortex-M4/M7).

Ná próxima parte da série, iremos avançar mais, e implementaremos o cordic no lugar onde ele é realmente poderoso, em hardware, será esse meu primeiro post com FPGA aqui pro embarcados. E você leitor, o que achou? Que tal gerar aquela senoide sem precisar recorrer a uma tabela em ROM? Hein? Até a próxima.

Links úteis

Referências

  • ANDRAKA, Ray – A survey of CORDIC algorithms for FPGA based computers;
  • CORDIC FAQ by DSP Guru, disponível aqui 
  • NEVES, Felipe – Rádio definido por software aplicado a ambientes automotivos, 2012

CORDIC - Um processador trigonométrico de ponto fixo

CORDIC – Introdução
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Home » Software » CORDIC em Firmware

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: