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:
/* * @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:

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









