Técnicas e dicas

Nesta página vou publicar soluções, algoritmos, funções ou dicas para assuntos interessantes.

 

DICA 01 – Enumerar as portas seriais (fixas e/ou dinâmicas) de um PC

Frequentemente tenho necessidade de comunicação PC <=> uC que faço através de adaptadores USB/serial TTL . Uma dificuldade que sempre surge é achar dinamicamente a COM que é atribuída pelo Windows para que o programa no PC consiga se comunicar. Consegui descobrir que o único local em que o Windows armazena as seriais dinâmicas é no Registry na chave HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM. Como para desenvolver aplicações no PC eu uso o Visual Basic, resolvi desenvolver uma função VB (utilizando as APIs do Windows) que resolvesse esse problema. Para isso me baseei em artigos do Binary World. Veja a função: VB COMs

 

DICA 02 – Pointer para função (ANSI C)

O conceito de pointer em C é , para muitos, desconcertante. Mais desconcertante ainda é o conceito de pointer para função. Minha intenção é esclarecer esse conceito e mostrar a grande utilidade que ele pode ter. Vou mostrar isso, mais adiante, através do seu uso na construção de uma Máquina para Autômato Finito Determinístico. Nesse caminho, vamos falar de Expressões Regulares, Autômatos Finitos, Autômatos de Pilha, Notação Polonesa Reversa e Redes Petri.

___________________  Pointer  ____________________

Pointer  é uma variável usada para armazenar um endereço.

Operador unário & aplicado a uma variável qualquer, retorna o endereço de onde essa variável está armazenada.

Operador unário * aplicado a uma variável do tipo pointer devolve o valor armazenado na variável cujo endereço está armazenado na variável pointer.

Declaração de uma variável do tipo pointer ex:  unsigned int * valor – esta declaração tem duas partes: ‘*’ indica que a variável de nome “valor” é do tipo pointer e portanto conterá o endereço de uma outra variável. Mas isso não é suficiente para que o compilador saiba de que tipo é a variável cujo endereço está em “valor”. Por isso existe a segunda parte da declaração “unsigned int” que diz ao compilador que a variável cujo endereço está em “valor” é do tipo “unsigned int”.

Atribuição de endereço a um pointer

unsigned int var;

unsigned int * ptr;

.

ptr = &var;     //A variável ptr agora contém o endereço da variável var

Atribuição indireta a uma variável

unsigned int var, res;

unsigned int * ptr1, * ptr2;

.

ptr1 = &var;          //ptr1 contém o endereço de var

ptr2 = &res;          //ptr2 contém o endereço de res

*ptr2 = *ptr1;      //res contém o valor de var

*ptr2  += 1            //res = res + 1  => res = var + 1

Uso de pointers com vetores – 

Para atribuir o endereço de um vetor a um pointer, simplesmente usa-se o nome do vetor.

unsigned int vet[5];

unsigned int * ptr;

unsigned int res;

.

ptr = vet;                //ptr contém o endereço do primeiro elemento de vet que é vet[0].

ptr++;                     //ptr contém o endereço de vet[1] que é 2 bytes adiante. O pointer é incrementado do número de bytes

//correspondente ao tipo definido para o vetor (unsigned int => 2 bytes).

ptr = &vet[1];       //ptr contém o endereço de vet[1]. Neste caso é necessário usar o operador unário & pois me referi

//a um elemento específico.

res = *(ptr++);   //equivale a res = vet[2];

ptr[2]  e  *(ptr + 2)   São equivalentes

vet[2]  e  *(vet + 2)  São equivalentes

ptr[2]  e  vet[2]  São equivalentes

No caso de matrizes as coisas são mais complicadas –

unsigned int mat[4][2];     //É um vetor de dimensão 4 cujos elementos são 2 unsigned int (portanto 2 + 2 bytes)

unsigned int (* ptr)[2];     //Pointer para uma matriz de 2 unsigned int

unsigned int res;

.

Neste caso, tudo se passa como se tivéssemos um vetor de pointers em que cada elemento (neste caso 4) aponta para um vetor de 2 elementos unsigned int (2 bytes). Portanto, ao incrementar o pointer, o deslocamento é de 4 bytes.

mat => indica o endereço do início da matriz (mat[0][0])

então:

ptr = mat;    ou    ptr = &mat[0];    //Pointer para um vetor de 4 elementos em que cada elemento corresponde a

//2 unsigned int

Se  ptr = mat;  =>  ptr++  aponta para   mat[1][0]

Posso me referenciar aos elementos da matriz tanto pelo nome dela quanto pelo nome do pointer:

sendo ptr = mat;

mat[i][j]  equivale a  ptr[i][j]

res = mat[2][1];  equivale a  res = ptr[2][1];  que equivale a  res = *(*(ptr+2)+1);

De forma geral, para uma matriz de duas dimensões, temos que de-referenciar duas vezes o pointer para obtermos o conteúdo de um elemento:

prt[i][j]   equivale a   *(*(prt + i) + j)   que equivale a   mat[i][j]

Com esses exemplos, é fácil imaginar a complexidade de se trabalhar com pointers em matrizes multidimensionais. Na pratica e na maioria das vezes usamos pointers para vetores (matrizes unidimensionais).

___________________  Pointer para função ____________________

Uma função nada mais é do que uma certa quantidade de código de máquina carregada na memória a partir de um determinado endereço. Analogamente a um pointer para variável, um pointer para uma função contém o endereço de memória do início do código dessa função e da mesma forma que preciso declarar o tipo de variável no caso de pointer para variável, no caso de pointer para função preciso declarar o tipo de função. Isso é feito através da declaração do tipo de retorno e do tipo dos parâmetros da função. Como regra prática, para definir um pointer para função, basta substituir o nome da função numa declaração normal de função por (*prt) , onde prt é o nome do pointer que vou usar:

declaração da função     =>   void exemplo(unsigned int par);

declaração do pointer para essa função   =>   void (*ptr_exemplo)(unsigned int par);

substituindo exemplo por (*ptr_exemplo) criei o pointer  ptr_exemplo  para a função  exemplo

Agora, tendo as declarações, posso atribuir o endereço da função ao pointer para função. Isso é feito simplesmente usando o nome da função:

ptr_exemplo = exemplo;

Para executar essa função usando o pointer, existem duas formas equivalentes:

(*ptr_exemplo)(1234);    ou     ptr_exemplo(1234):

Um dos usos mais comuns do pointer para função é como argumento de uma função. Isso permite ter algo como uma “função dinâmica” para executar códigos diferentes dependendo de um contexto dinâmico:

Considere o seguinte protótipo de função – void teste( void (*ptr_fun)(unsigned int par), unsigned int * out);

ele indica uma função que tem dois parâmetros: ptr_fun que é um pointer para função e out que é um pointer para variável. Então podemos chamar a função teste da seguinte maneira:

considerando as declarações acima e   unsigned int vout;

teste(ptr_exemplo,&vout);    ou    teste(exemplo,&vout);

com isso, na função teste, vou poder executar a função indicada pelo pointer.

 

DICA 03 – Máquina para Autômato Finito Determinístico

 Para manter a formatação do programa, veja aqui a M_AFD .

 

TÉCNICA 01 – Lógica Nebulosa (Fuzzy Logic)

(Referência(*): Fuzzy Logic with Egineering Applications – Timothy J. Ross)

Decidi falar um pouco sobre Lógica Nebulosa porque é uma técnica que vou usar na implementação dos algoritmos de guiagem evitando obstáculos do robô “XR – The First” (http://spaces.atmel.com/gf/project/xr1/).

A Lógica Nebulosa tem tudo a ver com a importância relativa da precisão: Quanto é importante ser exato quando uma resposta aproximada é mais que suficiente? Pessoas inteligentes já se pronunciaram a respeito:

  • Precisão não é verdade (Henri Matisse).
  • Na medida em que as leis da matemática se referem à realidade, elas não são confiáveis. E na medida em que elas são confiáveis, elas não se referem à realidade (Albert Einstein).
  • Na medida em que a complexidade cresce, declarações precisas perdem sentido e declarações que fazem sentido perdem precisão (Lofti Zadeh).

A Lógica Nebulosa cria um compromisso entre significância e precisão. Ela estende a lógica booleana (bivalência: 0 ou 1, preto ou branco, …) adotando a multivalência entre os dois valores booleanos (ex: graus intermediários de cinza entre o branco e o preto). Este conceito representa melhor o mundo real em que nada é estritamente bivalente. No mundo real verdade absoluta e precisão existem apenas como casos extremos. A Lógica Nebulosa define o grau de veracidade em um intervalo numérico multivalente [0,1]. Onde a certeza absoluta é representada pelo valor 1 e o valor 0.5 poderia representar uma “meia certeza”.

A Lógica Nebulosa consegue então representar bem as regras de inferência lógica usadas no raciocínio humano no intervalo [0,1] de graus de verdade. Com isso, ela permite a manipulação de expressões verbais qualitativas e imprecisas inerentes à comunicação humana.

A inferência cria uma conexão entre causa e efeito ou condição e consequência que pode ser representada por regras de inferência que utilizam números ou conjuntos nebulosos (fuzzy) que pode ser expressa por uma estrutura Se… Entã0 (If… Then):

If <causa> Then <efeito>

If <condição> Then <consequência>

Conjuntos nebulosos e pertinência

Loftti Zadeh – 1965

“A noção de conjunto nebuloso proporciona um ponto de partida conveniente para a construção de uma estrutura conceitual comparável, em muitos aspectos, à estrutura usada no caso de conjuntos comuns, mas é mais geral que esta ultima e, potencialmente, pode provar ter um escopo de aplicação mais amplo, particularmente nos campos de classificação de padrões e processamento de informação. Essencialmente, tal estrutura proporciona uma forma natural de abordagem de problemas em que a fonte de imprecisão é a ausência de critérios definidos claramente para as classes de pertinência e não a presença de variáveis aleatórias.”

(*) Como exemplo, podemos avaliar facilmente se alguém tem mais que 1,80 m de altura. Em termos binários e considerando a exatidão ou imprecisão do nosso instrumento de medição, a pessoa pode ser ou não. Por exemplo, se “alto” é um conjunto definido como alturas iguais ou maiores de 1,80 m, um computador não reconheceria um individuo de 1,799 m como sendo membro do conjunto “alto”. Mas como avaliaríamos a incerteza na seguinte questão: A pessoa tem aproximadamente 1,80 m de altura? A incerteza neste caso é devida à imprecisão ou ambiguidade do termo aproximadamente. Uma pessoa de 1,79 m poderia claramente ser membro do conjunto de pessoas “alto aproximadamente 1,80 m”. Na primeira situação, a incerteza se uma pessoa, cuja altura é desconhecida, é 1,80 m ou não é binaria; a pessoa é ou não é, e podemos fazer uma avaliação probabilística dessa perspectiva baseada nos dados de altura de muitas pessoas. Mas a incerteza se uma pessoa é alta aproximadamente 1,80 m não é aleatória. O grau com que a pessoa se aproxima de uma altura de 1,80 m é nebuloso (fuzzy). Na realidade, “altura” é uma questão de grau e é relativa. Entre as pessoas da tribo dos Tutsi em Rwanda e Burundi uma altura de 1,80 m para um homem é considerada baixa. Portanto, 1,80 m pode ser alto em um determinado contexto e baixo em outro. No mundo real (nebuloso), o conjunto de pessoas altas pode se sobrepor com o conjunto de pessoas não altas, coisa impossível quando se seguem os preceitos da lógica binaria clássica. Esta noção de pertinência é, então, central na representação de objetos em um universo por conjuntos definidos nesse universo. Conjuntos clássicos contém objetos que satisfazem propriedades precisas de pertinência; Conjuntos nebulosos contém objetos que satisfazem propriedades imprecisas de pertinência, isto é, a pertinência de um objeto em um conjunto nebuloso pode ser aproximada. Por exemplo, o conjunto de alturas de 1,5 m a 2,1 m é preciso (crisp); o conjunto de alturas na região ao redor de 1,80 m  é impreciso ou nebuloso.

Zadeh estendeu a noção de pertinência binária de modo a acomodar vários níveis de pertinência no intervalo real e continuo [0,1], onde os extremos 0 e 1 indicam nenhuma pertinência e pertinência total respectivamente como nos conjuntos de pertinência binária, mas onde o número infinito de valores entre esses pontos extremos representa vários graus de pertinência para um elemento x em algum conjunto no universo. Os conjuntos X que podem acomodar “graus de pertinência” foram denominados como “fuzzy sets” (conjuntos nebulosos) por Zadeh.

A função pertinência incorpora a representação matemática de pertinência em um conjunto:

μÃ(x) ∈ [0,1]

onde  Ã  => conjunto nebuloso  e  μà => função pertinência

μÃ(x) é o grau de pertinência do elemento x no conjunto nebuloso à ou:

μÃ(x) = grau com que x ∈ Ã

A função pertinência  μÃ(x) ∈ [0,1]  mapeia os elementos do conjunto nebuloso  Ã  para um valor numérico real no intervalo 0 a 1. A figura-1 mostra este mapeamento para um conjunto nebuloso tipico.

Possibilidade e probabilidade

O grau fracionário de pertinência de um elemento a um conjunto nebuloso, expressa a possibilidade desse elemento ser membro desse conjunto. Possibilidade não é sinônimo de probabilidade, probabilidade é a chance de que um elemento seja membro de um conjunto preciso. Possibilidade e probabilidade se encontram nos extremos do intervalo [0,1].

Operações com conjuntos nebulosos

Sejam três conjuntos nebulosos Ã1, Ã2 e Ã3 no universo X. Para um elemento x do universo, são definidas as operações de união, intersecção e complemento para Ã1, Ã2 e Ã3 em X.

União:

μ‹Ã1∪Ã2›(x) = μÃ1(x) ∨ μÃ2(x)

Intersecção:

μ‹Ã1∩Ã2›(x) = μÃ1(x) ∧ μÃ2(x)

Complemento:

μÃ1´(x) = 1 – μÃ1(x)

onde:

∨ operador máximo

∧ operador mínimo

´ complemento

O valor de pertinência de um elemento x no conjunto nulo  Ø  é 0 e o valor da pertinência de qualquer elemento x no inteiro conjunto X é 1:

Ã1 ⊆ X ⇒ μÃ(x) ≤ μX(x)

∀ x∈X,  μØ(x) = 0

∀ x∈X,  μX(x) = 1

O Principio de Morgam se aplica:

‹Ã1∩Ã2›´ =  Ã1´ ∪ Ã2´

‹Ã1∪Ã2›´ =  Ã1´ ∩ Ã2´

Como os conjuntos nebulosos podem se sobrepor, um conjunto e seu complemento também podem se sobrepor portanto:

Ã1 ∪ Ã1´ ≠ X

Ã1 ∩ Ã1´ ≠ Ø

Os conjuntos nebulosos usufruem das mesmas propriedades dos conjuntos precisos:

  • comutativa         Ã1 ∪ Ã2 = Ã2 ∪ Ã1    ;    Ã1 ∩ Ã2 = Ã2 ∩ Ã1
  • associativa          Ã1 ∪ (Ã2 ∪ Ã3) = (Ã1 ∪ Ã2) ∪ Ã3   ;   Ã1 ∩ (Ã2 ∩ Ã3) = (Ã1 ∩ Ã2) ∩ Ã3
  • distributiva         Ã1 ∪ (Ã2 ∩ Ã3) = (Ã1 ∪ Ã2) ∩ (Ã1 ∪ Ã3)   ;   Ã1 ∩ (Ã2 ∪ Ã3) = (Ã1 ∩ Ã2) ∪ (Ã1 ∩ Ã3)
  • idempotência     Ã1 ∪ Ã1 = Ã1   e   Ã1 ∩ Ã1 = Ã1
  • identidade           Ã1 ∪ Ø = Ã1   e  Ã1 ∩ X = Ã1   ;   Ã1 ∩ Ø = Ø   e  Ã1 ∪ X = X
  • transitividade    se  Ã1 ⊆ Ã2  e  Ã2 ⊆ Ã3  então  Ã1 ⊆ Ã3

 

Nota: a partir deste ponto usarei a palavra fuzzy e suas derivadas em lugar de nebuloso.

 

Controlador fuzzy baseado em regras

Um controlador fuzzy cria um canal entre um processo no mundo real e o modelo fuzzy que o representa. O mundo real é retratado através do uso de números reais (crisp) que são passados para o modelo fuzzy através de um processo denominado fuzzificação que utiliza as funções de pertinência fuzzy relativas às variáveis de entrada do controlador. Em seguida, para suas decisões, o controlador se utiliza do conjunto de regras linguísticas de inferência fuzzy definidas para esse modelo para chegar às funções de pertinência fuzzy relativas às variáveis de saída do controlador. Por último, através do processo denominado defuzzificação o controlador gera o valor real (crisp) das variáveis de saída para o controle do processo no mundo real.

Portanto, como vimos acima, um controlador fuzzy é composto de três partes:

  1. Fuzzificação  :   Variáveis de entrada no mundo real ⇒ Modelo fuzzy
  2. Aplicação de regras linguísticas de inferência fuzzy  :   Modelo fuzzy ⇒ Modelo fuzzy
  3. Defuzzificação  :   Modelo fuzzy  ⇒ Variáveis de saída no mundo real

 

Controlador SPAOFE  (Sonar Perception Avoiding Objects Fuzzy Engine)

Para o controlador SPAOFE, vou utilizar os conceitos, as funções e as regras linguísticas de inferência fuzzy (com exceção  das regras para controle da orientação do disco de sensores que é fixo) descritas no artigo “Fuzzy Logic Wall Following of a Mobile Robot Based on the Concept of General Perception”. Aqui estão as definições básicas.

O programa SPAOFE  foi escrito em ANSI C para o microcontrolador ATxmega128A1 usando o compilador CodeVisionAVR 3.07 standard.

O programa, apesar de sintaticamente correto, ainda não foi testado na prática pois a montagem física do robô ainda não está terminada. Enquanto isso, vou explicar como tentei implementar esse controlador fuzzy. Para isso, vou me referir ao programa SPAOFE e à numeração das linhas ali contidas.

  1. 57-59   operações lógicas fuzzy
  2. 65-66   equações das retas que vão formar as funções de pertinência
  3. 83-121   estrutura que contém todas as variáveis associadas à máquina SPAOFE
  4. 124   variável relógio
  5. 174-195   funções da máquina SPAOFE
  6. 199-254   função para inicialização do clock do sistema
  7. 256-1014   função para inicialização das portas do microcontrolador
  8. 1016-1079   funções para inicialização do timer TCD1
  9. 1081-1145   funções para inicialização e uso da USARTF0
  10. 1147-1177   funções e definições para a inicialização e uso do TWIC (I2C)
  11. 1179-1183   Função interrupt para funcionamento do relógio em milisegundos
  12. 1188-1415   Programa principal para teste da máquina SPAOFE

Controlador SPAOFE  (Sonar Perception Avoiding Objects Fuzzy Engine) V1.0

Como falei em “Comentários 22/12/2013”, tive alguns problemas pessoais que me impediram de dar continuidade à exposição acima. Agora, um pouco mais tranquilo, iniciei os testes das funções SPAOFE e, como esperado, encontrei vários bugs. Em função disso estou disponibilizando aqui uma nova versão:  SPAOFE V1.0 . Ainda não é a versão final mas é uma versão funcional em simulação na parte de fuzzificação. A parte de defuzzificação ainda não foi testada. Como houve modificações no programa, a numeração de linhas acima não é mais válida e, portanto, a cada explanação, me referirei à numeração atualizada. Estou anexando o diagrama vetorial para cálculo do General Perception Vector relativo à simulação para o caso sim=0 e os resultados dos testes para sim=0, sim=1, sim=2 e sim=3 .

#define reta_up(n,x)  (x – x1[n])/(x2[n]-x1[n]) // Equation for stright line up
#define reta_down(n,x)  1-(x – x1[n])/(x2[n]-x1[n]) // Equation for stright line down

Estas duas macros definem as equações das retas que vão formar as funções de pertinência. As funções de pertinência são compostas de segmentos de retas que se originam (segmento ascendente) ou terminam (segmento descendente) com ordenada zero.

#define ZE PC[0] // ZE sector
#define LO PC[1] // LO sector
#define HI PC[2] // HI sector
#define RB PA[0] // RB sector
#define RF PA[1] // RF sector
#define LF PA[2] // LF sector
#define LB PA[3] // LB sector
#define VLP PV[0] // VLP sector
#define LP PV[1] // LP sector
#define MP PV[2] // MP sector
#define HP PV[3] // HP sector
#define VHP PV[4] // VHP sector

Estas macros definem os diversos setores das funções de pertinência.

interrupt [TCD1_OVF_vect] void tcd1_overflow_isr(void)

Função de interrupt a cada milissegundo para gerar o tempo do relógio.

unsigned int range_sonar_X(unsigned char sim, unsigned char ender)

Função que simula a leitura do sensor de ultrassom devolvendo a distância (cm) ao objeto detectado.

Aqui temos algumas considerações interessantes a fazer:

  1. No caso presente, trata-se de uma simulação para efeito de teste das funções e não da leitura real dos sensores.
  2. Quando da utilização real dos sensores, deveremos levar em consideração as características do sensor utilizado (no nosso caso o SRF08). Nesse caso vamos definir a distância máxima como 400cm e não vamos alterar o ganho. Isso significa que cada sensor levará no máximo 65ms para completar a medição.
  3. Os 8 sensores poderiam ser acionados todos ao mesmo tempo e, portanto, levaríamos no máximo 65ms para ter todas as leituras. Isto porém não é aconselhável porque os sensores poderiam captar ecos que não lhe correspondessem. Em função disso, deveremos acionar cada sensor um por vez e aguardar o término de sua leitura.
  4. Tendo no máximo 65ms por sensor, para obter a leitura dos 8 sensores precisaremos de no máximo 520ms, o que nos permitiria a atualização das leituras numa frequência mínima de 1.9Hz. Se fizermos a atualização a cada 600ms, e considerando a velocidade máxima do robô como 40cm/s, significa que o robô terá continuado em sua trajetória por 24cm. Isto nos dá um parâmetro para a definição da distância a ser considerada para os bumpers  (no nosso caso os 8 GP2D15).
  5. Uma forma de implementar a atualização dessas distâncias a objetos seria utilizar um timer com interrupt a cada 600ms (ou um tempo menor se utilizarmos a técnica “Checking for Completion of Ranging”) e na rotina de interrupt fazer a leitura dos 8 sensores.

Atualizei o SPAOFE para uma versão beta que contempla a fuzzificação e defuzzificação. Anexei também o resultado de novos testes. Veja aqui: SPAOFE V2.0 .

unsigned char General_Perception_Vector(void)

Função que calcula o vetor General Perception utilizando os vetores Individual Perception dos 8 sensores. Isso é feito fazendo-se a soma vetorial dos vetores individuais para chegar ao ângulo (direção) α do vetor General Perception  e considerando como seu módulo p  o maior módulo dos vetores individuais (equação (5) do artigo (*)“Fuzzy Logic Wall Following of a Mobile Robot Based on the Concept of General Perception”).

void General_Perception_V_Change(void)

Função que calcula a variação do módulo do vetor General Perception no tempo. São consideradas somente as variações positivas. Isso é feito utilizando a equação (7) do artigo (*) para cada sensor que tenha apresentado variação positiva. O resultado é obtido considerando-se o maior valor.

bool Perception_Value_Membership(float Value)

Função que calcula o nível de pertinência da variável fuzzy p (módulo normalizado do vetor General Perception), segundo as funções de pertinência da figura 4b do artigo (*). A pertinência é calculada considerando-se os segmentos de reta descritos em 4b(*) através da utilização das macros:

#define reta_up(n,x) (x – x1[n])/(x2[n]-x1[n])

#define reta_down(n,x) 1-(x – x1[n])/(x2[n]-x1[n])

Para isso, são definidos os pontos xp1, xp2, x1 e x2 que permitem a utilização dessas equações.

bool Perception_Angle_Membership(float Angle)

Função que calcula o nível de pertinência da variável fuzzy α (direção do vetor General Perception), segundo as funções de pertinência da figura 4a do artigo (*). A pertinência é calculada considerando-se os segmentos de reta descritos em 4a(*) através da utilização das macros:

#define reta_up(n,x) (x – x1[n])/(x2[n]-x1[n])

#define reta_down(n,x) 1-(x – x1[n])/(x2[n]-x1[n])

Para isso, são definidos os pontos xp1, xp2, x1 e x2 que permitem a utilização dessas equações.

bool Perception_Change_Membership(float Value)

Função que calcula o nível de pertinência da variável fuzzy p* (mudança no módulo normalizado do vetor General Perception), segundo as funções de pertinência da figura 4c do artigo (*). A pertinência é calculada considerando-se os segmentos de reta descritos em 4c(*) através da utilização das macros:

#define reta_up(n,x) (x – x1[n])/(x2[n]-x1[n])

#define reta_down(n,x) 1-(x – x1[n])/(x2[n]-x1[n])

Para isso, são definidos os pontos xp1, xp2, x1 e x2 que permitem a utilização dessas equações.

void Rules_Steer(void)

Função que avalia quais as regras (Table 1b (*)) para mudança de direção (ψ´) se aplicam. Como resultado obtemos os setores Steer a serem agregados e os valores para seu redimensionamento ou corte.

void Rules_Acceleration(void)

Função que avalia quais as regras (Table 1c (*)) para mudança de velocidade via aceleração () se aplicam. Como resultado obtemos os setores Acceleration a serem agregados e os valores para seu redimensionamento ou corte.

float Scaled_Defuzzy_Steer(float Xinicial,float Xfinal,float Step)

Função que calcula o valor crisp  da variável de saída direção (steer – Figure 5b (*)). Isso é feito agregando os setores de pertinência redimensionados e usando o método do centroide. A agregação é feita pelo uso do OU para conjuntos fuzzy (união). O método do centroide calcula o centro de gravidade da área resultante.

float Scaled_Defuzzy_Acceleration(float Xinicial,float Xfinal,float Step)

Função que calcula o valor crisp  da variável de saída aceleração (acceleration – Figure 5c(*)). Isso é feito agregando os setores de pertinência redimensionados e usando o método do centroide. A agregação é feita pelo uso do OU para conjuntos fuzzy (união). O método do centroide calcula o centro de gravidade da área resultante.

float fHR(float x)  float fR(float x)  float fC(float x)  float fL(float x)  float fHL(float x)

Funções utilizadas pela função Scaled_Defuzzy_Steer para obter os valores normalizados nos diversos setores da variável de saída direção (steer – Figure 5b (*)).

float fEB(float x)  float fB(float x)  float fZ(float x)  float fP(float x)

Funções utilizadas pela função Scaled_Defuzzy_Acceleration para obter os valores normalizados nos diversos setores da variável de saída aceleração (acceleration  Figure 5c (*)).

___________________  A continuar ____________________

Leave a Reply