ÍNDICE DE CONTEÚDO
- CORDIC – Introdução
- 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.
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/* * @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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
/* * @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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
/* * @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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
/** * @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:
1 |
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:
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
- Repositório no github contendo a aplicação demo.
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