Вы находитесь на странице: 1из 61

 

UNIVERSIDADE FEDERAL DE UBERLÂNDIA 
 
    
FACULDADE DE ENGENHARIA ELÉTRICA ‐ FEELT 
 

LASEC   
FEELT 
APOSTILA DE SISTEMAS EMBARCADOS 
 

Última Atualização 15/07/2014
1° Edição 03/2011 

Sumário
 

Introdução ..................................................................................................................................... 3 
Microcontrolador ARM ................................................................................................................. 6 
Descrição dos softwares utilizados ............................................................................................... 9 
LED_Blink ..................................................................................................................................... 10 
Exercício Proposto ................................................................................................................... 10 
Programa LED_Blink Comentado ............................................................................................ 12 
Configurando o Clock da CPU ...................................................................................................... 14 
Operadores Lógicos Bit a Bit ....................................................................................................... 15 
Display de 7 Segmentos .............................................................................................................. 17 
Exercício Proposto ................................................................................................................... 18 
Números mágicos em programação ........................................................................................... 20 
Portabilidade e o Pré‐processador C ........................................................................................... 27 
Tipos Primitivos em C .................................................................................................................. 29 
Exercício Proposto ................................................................................................................... 35 
Teclado Matricial ......................................................................................................................... 36 
Exercício Proposto ................................................................................................................... 38 
Display de Cristal Líquido ............................................................................................................ 39 
Exercício Proposto ................................................................................................................... 42 
Interface de Comunicação SPI ..................................................................................................... 43 
Operações “ATOMIC” .................................................................................................................. 44 
“STRUCTURE PADDING”  E  SERIALIZAÇÃO ................................................................................. 49 
BOAS PRÁTICAS EM PROGRAMAÇÃO ......................................................................................... 57 
Escolher Bons Nomes .............................................................................................................. 57 
Variáveis e Modificadores de Tipo .......................................................................................... 57 
Exemplo: .................................................................................................................................. 58 
REGRAS MISRA ............................................................................................................................ 59 
Constantes ............................................................................................................................... 59 
Chaves ..................................................................................................................................... 59 
Exercício Proposto ................................................................................................................... 60 
PROGRAMAÇÃO EM LINGUAGEM C USANDO MÁQUINA DE ESTADO ....................................... 60 
Referências .................................................................................................................................. 61 

Página 2
 
1° Edição 03/2011 

Introdução

Na década de 30 e 40 os primeiros computadores possuíam tarefas


específicas. O Atanasoft-Berry Computer, o ABC, mostrado na Figura 01, foi o
primeiro computador a usar eletricidade, inventado por John Vincent Atanasoff
e Clifford Berry em 1939. O computador tinha válvulas eletrônicas, números
binários, capacitores e 1 quilômetro de fios.

Figura 1- Primeiro computador a usar eletricidade, inventado por John Vincent Atanasoff
e Clifford Berry em 1939.

O primeiro sistema embarcado reconhecido foi o Apollo Guidance


Computer (AGC), desenvolvido por Charles Stark Draper no MIT, na década de
60. O AGC, mostrado na Figura 01, realizava o processamento de dados e o
controle em tempo real do sistema de orientação e navegação do Módulo de
Comando e do Módulo Lunar da espaçonave desenvolvida no Programa Apolo.  

Interface do AGC AGC montado no painel de controle da


Apollo
Figura 2 - Apollo Guidance Computer, considerado o primeiro sistema embarcado
moderno.

O primeiro sistema embarcado de produção em massa foi o computador


guia do míssil nuclear LGM-30 Míssil Minuteman, lançado em 1961. Ele

Página 3
 
1° Edição 03/2011 

possuía um disco rígido para a memória principal. Quando a segunda versão


do míssil entrou em produção em 1966, o computador guia foi substituído por
um novo, que constituiu o primeiro uso em larga escala de circuitos integrados.

A tecnologia desse projeto reduziu o preço de circuitos integrados como a


porta lógica NAND de mil para três dólares americanos cada, permitindo seu
uso em sistemas comerciais. 

“A coisa mais indispensável a um homem é


reconhecer o uso que deve fazer do seu próprio
conhecimento.” – Platão (428 a 348 a.C)

Um dos primeiros minuteman

Figura 3 - O LGM-30 Minuteman é um projeto de míssil balístico intercontinental (ICBM)


nuclear dos Estados Unidos.

Desde suas primeiras aplicações na década de 1960, os sistemas


embarcados vêm reduzindo seu preço. Também tem havido um aumento no
poder de processamento e funcionalidade. Em 1978 foi lançada pela National
Engineering Manufacturers Association a norma para microcontroladores
programáveis.

Em meados da década de 1980, vários componentes externos foram


integrados no mesmo chip do processador, o que resultou em circuitos
integrados chamados microcontroladores e na difusão dos sistemas
embarcados.

Com o custo de microcontroladores menor que um dólar americano,


tornou-se viável substituir componentes analógicos caros como potenciômetros
e capacitores por eletrônica digital controlada por pequenos
microcontroladores. No final da década de 1980, os sistemas embarcados já
eram a norma ao invés da exceção em dispositivos eletrônicos (URL 1).

Página 4
 
1° Edição 03/2011 

Um sistema embarcado (ou sistema embutido) é um sistema no qual o


circuito que processa os dados é dedicado ao dispositivo ou sistema que ele
controla. Diferente de computadores de propósito geral, como o computador
pessoal, um sistema embarcado realiza um conjunto de tarefas predefinidas,
geralmente com requisitos específicos. Já que o sistema é dedicado a tarefas
específicas pode-se otimizar o projeto reduzindo-se tamanho, recursos
computacionais e custo do produto.

Em geral os sistemas embarcados possuem uma capacidade de


processamento reduzida em comparação com computadores desktops. Ao
invés de utilizar microprocessadores, os desenvolvedores preferem utilizar
microcontroladores, pois estes já possuem diversos periféricos integrados no
mesmo chip.

Outra diferença é a variedade de arquiteturas disponíveis tais como ARM,


MIPS, Coldfire/68k, PowerPC, x86, PIC, 8051, Atmel AVR, Renesas H8, SH,
V850, FR-V, M32R, Z80 e Z8. Isso contrasta com o mercado de computadores
pessoais, limitados à somente algumas arquiteturas.

A área de sistemas embarcados é uma das áreas com crescimento mais


dinâmico e rápido no setor industrial. Sistemas embarcados são aplicados em
diversas áreas como automotiva/transporte, governo/militar, equipamentos
hospitalares, telecomunicações, aeronáutica, aeroespacial, eletrodomésticos,
automação residencial, automação industrial, automação agrícola, automação
de indústrias petroquímicas. Estima-se que as casas nos Estados Unidos da
América possuem em média de 30 a 40 aparelhos que utilizam dispositivos
dedicados e que aproximadamente 98% de todos os microprocessadores em
uso no mundo são usados em sistemas embarcados.

A programação de sistemas embarcados já vem ocorrendo a mais de trinta


anos, porém, devido o crescimento da capacidade computacional e o aumento
da complexidade dos circuitos dedicados, as disciplinas que tratam da
programação desses circuitos no meio acadêmico é um assunto relativamente
novo. Além disso, disciplinas relacionadas a esse assunto são consideradas
interdisciplinares, pois combinam áreas como ciência da computação,
engenharia elétrica, matemática aplicada e teoria de controle.

Com o tremendo crescimento na computação embarcada aumentou-se a


demanda de engenheiros e cientistas da computação, contudo, a maioria dos
programas acadêmicos desenvolvem habilidades de programação e projeto de
hardware para computação de propósito geral que operam sobre aplicações
comerciais e, portanto, não desenvolvem habilidades que são adequadas a
programação e desenvolvimento de hardware para sistemas embarcados. O
resultado disso é que as indústrias, nos Estados Unidos, por exemplo, estão
tendo dificuldade em encontrar profissionais com habilidades adequadas para
trabalhar com esses sistemas, o que compromete o desenvolvimento de

Página 5
 
1° Edição 03/2011 

indústrias de base tecnológica. Isso ocorre porque uma empresa de base


tecnológica está fundamentada em seu capital humano e social.

Os países europeus desenvolveram uma comunidade científica chamada


ARTIST. O objetivo do Grupo de Excelência ARTIST é fortificar as pesquisas
de sistemas embarcados na Europa e promover o desenvolvimento desta nova
área multidisciplinar. A ambição desta organização é a produção e
transferência de conhecimento que promovam a inovação industrial (URL 2).
Para atingir esta meta, o Grupo ARTIST já desenvolveu diretrizes curriculares
de graduação em sistemas embarcados.

No Japão e em muitos outros países orientais disciplinas sobre sistemas


embarcados são oferecidas nos cursos de Ciência da Computação e de
Engenharia. A maior parte das universidades da China oferece curso de
graduação em engenharia de software embarcada. Na Coréia 19 universidades
possuem curso de programação de sistemas dedicados.

Várias universidades no Brasil oferecem disciplinas intituladas Computação


Móveis ou Sistemas Móveis que utilizam J2ME, porém, este ambiente
(máquina virtual e subconjunto do sistema JAVA) está disponível apenas em
PDAs, smart phones, algumas IHMs e outros dispositivos com maiores
recursos de memória no hardware. A implementação J2ME CLDC mais
limitada requer de 160KB a 512KB de ROM apenas para a VM e as bibliotecas,
além de 192KB de RAM sem considerar qualquer subsistema de apoio (URL 3,
URL 4). Do ponto de vista comercial existe ainda o custo de licença relacionado
a uma implementação J2ME.

Os sistemas embarcados tratados nesta apostila possuem limitações muito


maiores no hardware como memória reduzida (de 1KB a 64KB de RAM),
baixíssimo consumo, alta integração e capacidade limitada de processamento.
São em sua grande maioria destinados a algum controle/monitoração e
programados utilizando a linguagem C, que, ao contrário do Assembly, permite
portabilidade e certificação SIL (Safety Integrity Level) (URL 5).

Microcontrolador ARM

Todos os exemplos apresentados nesta apostila foram implementados


para o microcontrolador LPC2148 fabricado pela NXP. Ele é baseado no
núcleo ARM7 que possui uma arquitetura RISC e pode operar com instruções
de 16 ou 32 bits.
Arquitetura de microprocessador RISC é utilizada principalmente em
sistemas embarcados como PDAs, telefones celulares, calculadoras,
periféricos do computador, equipamentos POS (Point-Of-Sale). Também

Página 6
 
1° Edição 03/2011 

utilizadas na indústria automotiva, médica e em aplicações de controle


industrial.
Primeiro protótipo do processador, o ARM1, surgiu em 1995 e desde então
mais de um bilhão desses dispositivos já foram construídos e essa tecnologia
não parou de evoluir.
A arquitetura ARM foi desenvolvida com o intuito de se obter o melhor
desempenho possível respeitando as seguintes características:
 Ser simples: tem um conjunto de instruções reduzido, pois sua
arquitetura é baseada na filosofia de projeto RISC (Reduced Instruction
Set Computer);
 Ocupar pouca área: otimização de área feita através de várias
simplificações como, por exemplo, deixar tarefas secundárias ou
específicas (I/O, operações de ponto flutuante, etc) a cargo dos co-
processadores;
 Ter baixo consumo de energia: redução obtida através da simplicidade
do circuito, pipeline curto (operando a baixas frequências) e um projeto
que privilegia o mínimo consumo de energia sempre que o processador
não estiver em operação;
 Possui 16 registradores de uso geral;
 As instruções são de três endereços e o conjunto de instruções é
extensível com o uso de até 16 co-processadores;
 Capacidade de executar instruções de 16 bits utilizando a arquitetura
Thumb – codificação de instruções ARM que permite performance de
32bit a um custo de sistema de 8/16bit;
 Arquitetura ARM é licenciada, de maneira que diversos fabricantes
produzam chips semelhantes.

A família ARM7 é um conjunto de microprocessadores RISC de 32 bits de


baixa tensão otimizados para uso comercial. Oferece até 130MIPs e incorpora
o conjunto de instruções Thumb de 16bits. A família divide-se em:

• ARM7TDMI: Núcleo que processa números inteiros com pipeline de três


estágios que oferece alta performance com um baixíssimo consumo de
energia;
• ARM7TDMI-S: Versão sintetizada do núcleo ARM7TDMI, ideal para
projetos onde portabilidade e flexibilidade são características imprescindíveis;
• ARM7EJ-S: Núcleo otimizado e sintetizado com extensões na arquitetura e
no conjunto de instruções para suportar operações DSP e aceleração nas
aplicações Java utilizando tecnologia ARM Jazelle DBX;
• ARM720T: Núcleo que processa números inteiros, com MMU (unidade de
gerenciamento de memória) e cachê unificada de 8KB para plataformas de
aplicação abertas tais como Windows CE, Linux, Palm CS e Symbian OS.

Página 7
 
1° Edição 03/2011 

Características do ARM7:
 Arquitetura RISC de 32-bit com conjunto de instruções ARM e Thumb;
 Pipeline de 3 estágios (arquitetura von Neumann);
 Performance de até 130 MIPs (Dhrystone 2.1) num típico processador
de 0.13µm;
 Baixíssimo consumo de energia;
 Amplo SO e suporte RTOS – incluindo Windows CE, Palm OS, Symbian
OS, Linux;
 Excelente suporte de debug para desenvolvimento SoC, incluindo
interface ETM;
 Disponibilidade de processos de 0.25µm, 0.18µm e 0.13µm;
 Código é compatível com processadores ARM9 e permitem a
reutilização dos códigos de aplicação;
 Migração e suporte para novas tecnologias de processadores.

O ARM7 é um processador RISC de 32-bit que faz uso de instruções


Thumb para reduzir a densidade de código e executar efetivamente as
instruções.
Muitos periféricos podem ser utilizados junto com essa arquitetura, de
acordo com a finalidade da utilização. Porém alguns são utilizados com mais
frequência. O principal periférico é a memória, pois nela que se encontram
todas as instruções, rotinas de execução, dados, etc. Elas podem ter tamanhos
variados e ser do tipo ROM ou RAM e, atualmente, as mais usadas em
microcontroladores são a Flash e SRAM, respectivamente.
Timers (Temporizadores) também são importantes, pois são utilizados para
gerar bases de tempo que podem ser utilizadas para os mais diversos fins,
como, por exemplo, gerar sinais de clock para outros periféricos, calcular
intervalos de tempo ou medir período de sinais. É possível encontrar
microcontroladores de 3 (NXP) à 32 canais (Texas).
Outro periférico frequentemente utilizado junto com o ARM é o conversor
A/D. Ele quem faz a representação digital de uma grandeza analógica que
pode ser velocidade, temperatura, tempo, etc. Atualmente, pode-se encontrar
de 4 (Zilog) à 16 canais (Atmel, ST) de conversores em um mesmo
microcontrolador.

As principais características do microcontrolador ARM, modelo LPC2148


da NXP são descritas a seguir:

 Clock de até 60MHz, configurado através de PLL interno;


 Realização de operações a até 60 MHz;
 Dois timers/counters de 32 bits;
 Unidade lógica aritmética com arquitetura RISC de 32 bits com
instruções de soma e subtração executadas em um único ciclo,
instruções de multiplicação e multiplicação longa (32x32 bits e resultado

Página 8
 
1° Edição 03/2011 

em 64bits), instrução de multiplicar e acumular usada para


implementação de filtros digitais, etc;
 Memória flash interna de 512 kB;
 Configuração da frequência de operação dos periféricos;
 Periféricos podem ser habilitados e desabilitados individualmente;
 Possui USB 2.0 fullspeed com RAM para o endpoint de 2kB;
 Duas unidades UART (Universal Asynchronous Receiver/Transmitter);
 Duas interfaces com o padrão I2C (Inter-Intergrated Circuit);
 Uma interface com o padrão SPI (Serial Peripheral Interface);
 Uma interface com o padrão SSP (Synchronous Serial Port);
 Interfaces seriais múltiplas incluindo duas UARTs, sendo que uma
possui controle de fluxo implementado em hardware, I2C e SPI;
 Dois conversores A/D de aproximação sucessiva de 10 bits com até 8
entradas analógicas multiplexadas cada que podem realizar até 400.000
conversões por segundo;
 Conversor D/A de 10 bits;
 Controlador de interrupções com prioridade programável;
 etc.

Descrição dos softwares utilizados


 

1) Java SE Runtime Enviroment


2) Eclipse
1.1) Plug-In do Elipse: CDT GNU Cross Development Tools
1.2) Plug-In do Elipse: Zylin Embedded CDT
3) YAGARTO GNU ARM toolchain
4) YAGARTO Tools

O instalador irá acrescentar no Path do Windows os caminhos:


C:\ARM\yagarto\bin e C:\ARM\yagartotools\bin

OBS1: O arquivo Tutorial - Instalando os Softwares para Programar


ARM.pdf contém instruções detalhadas de todos os passos necessários para
realizar a instalação dos softwares.

OBS2: Todos os softwares utilizados são distribuídos gratuitamente pelos seus


respectivos fornecedores.

Página 9
 
1° Edição 03/2011 

LED_Blink

O objetivo deste exemplo é criar um programa que irá configurar o pino


P0.31 de um microcontrolador ARM LPC2148 como entrada/saída digital (I/O).
Este pino será utilizado para acionar um LED, como mostra a Figura 03.
Para atingir esta meta, os seguintes passos serão executados:

Passo 1: Crie um novo projeto denominado LED_Blink. As instruções sobre


como criar um projeto estão no arquivo denominado Tutorial - Como Criar um
Projeto em C para ARM.pdf, disponível no arquivo Aulas.zip

Passo 2: Copiar o conteúdo do arquivo LED_Blink.txt, localizado em


...\Aulas\Aula_01\Programas, e colar no arquivo main.c criado no projeto
LED_Blink. Onde ...\ representa o local onde o arquivo Aulas.zip foi
descompactado.

Passo 3: Compilar o programa de acordo com as instruções fornecidas no


arquivo chamado Tutorial - Como Criar um Projeto em C para ARM.pdf

Passo 4: Transferir o arquivo binário gerado para o microcontrolador ARM.

Exercício Proposto

Exercício 01: Crie um programa para acionar de modo sequencial e


ininterrupto os quatro LEDs mostrados na Figura 04. O LED 1 deverá piscar,
depois o LED 2, e assim sucessivamente, até retornar para o LED 1
novamente. Utilize a função delay_ms do módulo delay_loop para
implementar o tempo de espera.

Página 10
 
1° Edição 03/2011 

C1

22pF
X1 U1
CRYSTAL
C2 FREQ=12MHz 62 19
XTAL1 P0.0/TxD0/PWM1
61 21
XTAL2 P0.1/RxD0/PWM3/EINT0
22
P0.2/SCL0/CAP0.0
22pF 3 26
RTXC1 P0.3/SDA0/MAT0..0/EINT1
5 27
3.3V D1 RTXC2 P0.4/SCK0/CAP0.1/AD0.6
29
P0.5/MISO0/MAT0.1/AD0.7
57 30
RST P0.6/MOSI0/CAP0.2/AD1.0
31
P0.7/SSEL0/PWM2/EINT2
DIODE 33
P0.8/TxD1/PW M4/AD1.1
34
R1 C3 P0.9/RxD1/PWM6/EINT3
35
P0.10/RTS1/CAP1.0/AD1.2
37
P0.11/CTS1/CAP1.1/SCL1
38
47k P0.12/DSR1/MAT1.0/AD1.3
100pF 39
P0.13/DTR1/MAT1.1/AD1.4
41
P0.14/DCD1/EINT1/SDA1
45
P0.15/RI1/EINT2/AD1.5
46
P0.16/EINT0/MAT0.2/CAP0.2
47
P0.17/CAP1.2/SCK1/MAT1.2
53
P0.18/CAP1.3/MISO1/MAT1.3
54
P0.19/MAT1.2/MOSI1/CAP1.2
55
P0.20/MAT1.3/SSEL1/EINT3
1
P0.21/PWM5/AD1.6/CAP1.3
2
P0.22/AD1.7/CAP0.0/MAT0.0
58
P0.23
9
P0.25/AD0.4/AOUT
10
P0.26/AD0.5
11
P0.27/AD0.0/CAP0.1/MAT0.1
13 3.3V
P0.28/AD0.1/CAP0.2/MAT0.2
P0.29/AD0.2/CAP0.3/MAT0.3
14 U2:B D3
15 R7
P0.30/AD0.3/EINT3/CAP0.0
17 3 4
P0.31
300
16 LED-GREEN
P1.16/TRACEPKT0 74HCT04
3.3V 12
P1.17/TRACEPKT1
49 8
VBAT P1.18/TRACEPKT2
4
P1.19/TRACEPKT3
63 48
VREF P1.20/TRACESYNC
7 44
V3A P1.21/PIPESTAT0
51 40
V3 P1.22/PIPESTAT1
43 36
V3 P1.23/PIPESTAT2
23 32
V3 P1.24/TRACECLK
28
P1.25/EXTIN0
59 24
VSSA P1.26/RTCK
50 64
VSS P1.27/TDO
42 60
VSS P1.28/TDI
25 56
VSS P1.29/TCK
18 52
VSS P1.30/TMS
6 20
VSS P1.31/TRST
LPC2138

Figura 4 – LED acionado pelo pino P0.31

C1

22pF
X1 U1
CRYSTAL
C2 FREQ=12MHz 62 19
XTAL1 P0.0/TxD0/PWM1
61 21
XTAL2 P0.1/RxD0/PW M3/EINT0
22
P0.2/SCL0/CAP0.0
22pF 3 26
RTXC1 P0.3/SDA0/MAT0..0/EINT1
5 27
3.3V D1 RTXC2 P0.4/SCK0/CAP0.1/AD0.6
29
P0.5/MISO0/MAT0.1/AD0.7
57 30
RST P0.6/MOSI0/CAP0.2/AD1.0
31
P0.7/SSEL0/PWM2/EINT2
DIODE 33
P0.8/TxD1/PW M4/AD1.1
34
R1 C3 P0.9/RxD1/PW M6/EINT3
35
P0.10/RTS1/CAP1.0/AD1.2
37 3.3V
P0.11/CTS1/CAP1.1/SCL1
47k P0.12/DSR1/MAT1.0/AD1.3
38 U2:D LED 4
100pF 39 R4
P0.13/DTR1/MAT1.1/AD1.4
41 13 12
P0.14/DCD1/EINT1/SDA1
45
P0.15/RI1/EINT2/AD1.5 300
LED-GREEN 3.3V
74HCT04
46
P0.16/EINT0/MAT0.2/CAP0.2
P0.17/CAP1.2/SCK1/MAT1.2
47 U2:C LED 3
53 R3
P0.18/CAP1.3/MISO1/MAT1.3
54 5 6
P0.19/MAT1.2/MOSI1/CAP1.2
55
P0.20/MAT1.3/SSEL1/EINT3 300
1 LED-GREEN 3.3V
P0.21/PW M5/AD1.6/CAP1.3 74HCT04
2
P0.22/AD1.7/CAP0.0/MAT0.0
P0.23
58 U2:B LED 2
R2
9 3 4
P0.25/AD0.4/AOUT
10 300
P0.26/AD0.5
11 LED-GREEN
P0.27/AD0.0/CAP0.1/MAT0.1 74HCT04
13 3.3V
P0.28/AD0.1/CAP0.2/MAT0.2
P0.29/AD0.2/CAP0.3/MAT0.3
14 U2:A LED 1
P0.30/AD0.3/EINT3/CAP0.0
15 R7
17 1 2
P0.31
300
16 LED-GREEN
P1.16/TRACEPKT0 74HCT04
3.3V 12
P1.17/TRACEPKT1
49 8
VBAT P1.18/TRACEPKT2
4
P1.19/TRACEPKT3
63 48
VREF P1.20/TRACESYNC
7 44
V3A P1.21/PIPESTAT0
51 40
V3 P1.22/PIPESTAT1
43 36
V3 P1.23/PIPESTAT2
23 32
V3 P1.24/TRACECLK
28
P1.25/EXTIN0
59 24
VSSA P1.26/RTCK
50 64
VSS P1.27/TDO
42 60
VSS P1.28/TDI
25 56
VSS P1.29/TCK
18 52
VSS P1.30/TMS
6 20
VSS P1.31/TRST
LPC2138

Figura 5 – LEDs acionados pelos pinos P0.31, P0.25, P0.19 e P0.14

Página 11
 
1° Edição 03/2011 

Programa LED_Blink Comentado


/* =============================== C/C++ SOURCE FILE ================================ */
/**
\file
\description Escrever o que a rotina faz
\author Nome do autor
*/
/* ================================================================================== */
/*------------------------------------------------------------------------------------*/
/* INCLUDES */
/*------------------------------------------------------------------------------------*/

#include "LPC214x.h" // A diretiva #include informa ao compilador que ele deve incluir
o arquivo LPC214x.h, pois este arquivo contém endereços de memória e de registradores
que serão utilizados no programa.

#include "cpu_init.h" // A diretiva #include informa ao compilador para incluir o módulo


cpu_init.h, pois este arquivo contém as rotinas de inicialização da CPU do ARM LPC2148.

/*------------------------------------------------------------------------------------*/
/* FUNCTION IMPLEMENTATION */
/*------------------------------------------------------------------------------------*/
int main (void) // A função main() é a primeira função a ser executada, todo programa
deve possuir uma.
{
int j = 0; // Declara a variável j como inteiro de 32bit, porque a arquitetura do ARM
é de 32bit, por isso, a faixa de valores de j é de -2.147.483.648 a +2.147.483.647

cpu_init(); // Chama a rotina que inicializa a CPU do ARM LPC2148

PINSEL1 &= ~((1<<30)|(1<<31)); // O PINSEL1 é um registrador que seleciona a função


dos pinos (P0.16-31) do PORT0 e utiliza 2 bits de configuração para cada pino. Neste
caso, quando o bit 30 e 31 do registrador é igual a 0 (zero), o pino P0.31 é configurado
como entrada/saída (I/O)- Ver item 7.4.1, denominado Pin Function Select Register no
documento LPC214X User Manual.pdf

IODIR0 |= (1<<31); // O registrador IODIR0 é utilizado para definir se um determinado


pino será utilizado como entrada ou saída. Se um determinado Bit deste registrador for
configurado com o valor 1, o pino do microcontrolador correspondente a este bit será
configurado como saída, por outro lado, se um determinado Bit do registrador IODIR0 for
configurado com o valor 0, o pino do microcontrolador correspondente a este bit será
configurado como entrada.

while(1){

IOSET0 = (1<<31); // O registrador IOSET0 é utilizado para produzir nível lógico


alto (1) em um pino configurado como saída. Toda vez que um bit deste registrador for
escrito com o valor 1, o pino correspondente a este bit será forçado a assumir nível
lógico alto (1). Escrever 0 em um bit deste registrador não produz efeito. )- Ver Table
65: GPIO register map (legacy VPB accessible registers) no documento LPC214X User
Manual.pdf. Deste modo, não se deve utilizar IOSET ou IOCLR com o operador ( |= ) ou com
o operador ( &= ), pois o compilador irá gerar código inútil.

for (j = 0; j < 1000000; j++ ) asm volatile ("NOP"); // Espera


// asm("assembly code"); Este é o formato de uma instrução assembly básica
acrescentada em um código C. A instrução assembly NOP (No Operation Performed) faz com
que a CPU permaneça em espera durante o tempo que leva para uma instrução ser executada.
A palavra reservada volatile impede que o compilador otimize esta linha de código,
“forçando” ele a acrescentar no código a instrução NOP, mesmo que ela não realize uma
operação.

IOCLR0 = (1<<31); // // O registrador IOCLR0 é utilizado para produzir nível


lógico baixo (0) em um pino configurado como saída. Toda vez que um bit deste
registrador for escrito com o valor 1, o pino correspondente a este bit será forçado a
assumir nível lógico baixo (0). Escrever 0 em um bit deste registrador não produz
efeito. Deste modo, não se deve utilizar IOSET ou IOCLR com o operador ( |= ) ou com o
operador ( &= ), pois o compilador irá gerar código inútil.

for (j = 0; j < 1000000; j++ ) asm volatile ("NOP"); // Espera


}
return(0); // É boa prática utilizar “return” na função main() para informar ao
sistema operacional quando a execução do programa foi finalizada.
}
/*------------------------------------------------------------------------------------*/
/* EOF */
/*------------------------------------------------------------------------------------*/

Página 12
 
1° Edição 03/2011 

Observação: A definição padrão para a função main segundo ANSI C é:

int main(int argc, char *argv[])


{
...

return 0; // retorna um código de erro para o sistema operacional (0 = nenhum erro)


}

No entanto, em se tratando de sistemas embarcados, isto depende do


sistema operacional usado, do compilador, das bibliotecas e, às vezes, da
forma como se chama a rotina principal (main), a partir do boot que é feito em
linguagem assembly. Por isso, o mais comum é:

int main(void)
{
...

return 0; // retorna um código de erro para o sistema operacional (0 = nenhum erro)


}

Página 13
 
1° Edição 03/2011 

Configurando o Clock da CPU

O clock da cpu é configurado no arquivo cpu_init.h. Este arquivo contém


vários defines que facilitam o ajuste do clock desejado como mostrado a seguir.
// Considerando um cristal de 12 MHz, o valor do Mutiplicador e Divisor são:
#define cristal_12MHz_cpu_60MHz 0x24 // 0x24 - Clock do processador (cclk) configurado para 60MHz
#define cristal_12MHz_cpu_48MHz 0x23 // 0x23 - Clock do processador (cclk) configurado para 48MHz
#define cristal_12MHz_cpu_36MHz 0x42 // 0x42 - Clock do processador (cclk) configurado para 36MHz
#define cristal_12MHz_cpu_24MHz 0x41 // 0x41 - Clock do processador (cclk) configurado para 24MHz
#define cristal_12MHz_cpu_12MHz 0x60 // 0x60 - Clock do processador (cclk) configurado para 12MHz

//--------------------------------------------------------------------------------------
//--- A frequência da CPU deve ser configurada aqui!!! ---------------------------------

#define cpuMHz cristal_12MHz_cpu_60MHz // Define o clock da CPU do ARM

#define bus_div0 0x01 // Barramento com a mesma frequência da CPU


#define bus_div2 0x10 // Barramento com metade (1/2) da frequência da CPU
#define bus_div4 0x00 // Barramento com um quarto (1/4) da frequência da CPU

//--------------------------------------------------------------------------------------
//--- A frequência do barramento dos periféricos deve ser configurada aqui!!! ----------

#define bus_freq bus_div0 // Define a frequência do barramento dos periféricos

Se for necessário criar outros defines, para atender as especificações de


cristais de outras frequências, de acordo com o UM10139 Volume 1: LPC214x
User Manual, a configuração da frequência do CLOCK é fornecida por (CCLK =
M × FOSC).

Onde:
CCLK: valor da freqüência do CLOCK do processador;
M: valor do multiplicador do PLL que controla a da freqüência do CLOCK do processador;
Fosc: frequência do oscilador a cristal/oscilador externo.

Supondo que Fosc = 12MHz e CCLK = 60 MHz:

M = CCLK / Fosc
M = 60 MHz / 12 MHz
M= 5

Consequentemente M - 1 = 4 deverá ser escrito no registrador


PLLCFG[4:0] que possui a função de configurar o valor do multiplicador e
divisor do PLL.
O valor do divisor ( P ) do PLL dever ser configurado de tal forma que a
frequência do PLL (FCCO) fique entre 156 MHz e 320 MHz. Deste modo, para
Fcco = 156 MHz, P = 156 MHz / (2 x 60 MHz) = 1.3 e para Fcco = 320 MHz, P
= 320 MHz / (2 x 60 MHz) = 2.67.
Portanto, o único valor inteiro para P que satisfaz ambas as condições de
acordo com a Tabela 22 do UM10139 Volume 1: LPC214x User Manual é
P = 2. Logo, de acordo com a Tabela 22, o valor 1 deverá ser escrito em
PLLCFG[6:5].

Página 14
 
1° Edição 03/2011 

Operadores Lógicos Bit a Bit

Os operadores lógicos Bit a Bit são muito úteis na configuração de


registradores.
A linguagem de programação C possui operadores que realizam
operações lógicas "bit a bit" em números do tipo inteiro. Uma operação bit a bit
pode ser utilizada para testar, atribuir, ou deslocar bits. No entanto, as
operações bit a bit não podem ser usadas em float, double, long double, void
ou outros tipos mais complexos.
A Tabela 01 mostra os operadores lógicos bit a bit da linguagem de
programação C.
Tabela 01 – Operadores Lógicos Bit a Bit. 
Operador Ação
& E (AND)
| OU (OR)
^ XOR (OU Exclusivo)
~ NÃO (NOT)
>> Deslocamento de bits à direita
>> Deslocamento de bits à esquerda

Para que apenas um BIT seja forçado a assumir nível lógico alto ( 1 ), em
um determinado registrador, é necessário utilizar o operador lógico OU e uma
máscara que possua apenas o bit desejado em nível lógico alto ( 1 ).
A Figura 6 mostra como forçar para nível alto o bit 10 de um registrador
denominado Registrador_X.

BIT: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Registrador_X: 1 0 0 0 0 0 0 0 1 1 1 1 0 1 1 0
Operador OU ou ou ou ou ou ou ou ou ou ou ou ou ou ou ou ou
Máscara: 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0
Registrador_X: 1 0 0 0 0 1 0 0 1 1 1 1 0 1 1 0
Figura 6 – Operador Lógico OU

Na linguagem C esta operação pode ser realizada da seguinte maneira:


uint16_t Registrador_X = 0b1000000011110110; // cria uma variável de 16 bits chamada
Registrador_X e a inicializa com o número do tipo binário 0b1000000011110110

uint16_t Mascara = 1 << 10; //inicializa uma Mascara de 16 bits com o bit 10 igual a 1

Registrador_X |= Mascara; // Esta linha de código realiza os três passos descritos abaixo:

1) Lê o valor do Registrador_X;
2) Realiza uma operação lógica OU bit a bit do valor lido com a Mascara;
3) Armazena o resultado no Registrador_X.

Página 15
 
1° Edição 03/2011 

Para que apenas um BIT seja forçado a assumir nível lógico baixo ( 0 ),
em um determinado registrador, é necessário utilizar o operador lógico E e uma
máscara que possua apenas o bit desejado em nível lógico baixo ( 0 ).
A Figura 7 mostra como forçar para nível alto o bit 10 de um registrador
denominado Registrador_X.

BIT: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Registrador_X: 1 0 1 1 1 1 1 1 1 1 1 1 0 1 1 0
Operador E E E E E E E E E E E E E E E E E
Máscara: 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1
Registrador_X: 1 0 1 1 1 0 1 1 1 1 1 1 0 1 1 0
Figura 7 – Operador Lógico E

Na linguagem C esta operação pode ser realizada da seguinte maneira:


uint16_t Registrador_X = 0b1011111111110110; // cria uma variável de 16 bits chamada
Registrador_X e a inicializa com o número do tipo binário 0b1011111111110110

uint16_t Mascara = ~(1 << 10); //inicializa uma Mascara de 16 bits com o bit 10 igual a 1 e
em seguida inverte todos os bits, isto é, os bits igual a 1 são convertidos em 0 e os bits igual a
0 são convertidos em 1

Registrador_X &= Mascara; // Esta linha de código realiza os três passos descritos abaixo:

1) Lê o valor do Registrador_X;
2) Realiza uma operação lógica E bit a bit do valor lido com a Mascara;
3) Armazena o resultado no Registrador_X.

Para que apenas um BIT seja negado, isto é, se for nível lógico alto ( 1 )
seja forçado a assumir nível lógico baixo ( 0 ) e vice-versa, em um determinado
registrador, é necessário utilizar o operador lógico XOR (OU Exclusivo) e uma
máscara que possua apenas o bit desejado em nível lógico alto ( 1 ).
A Figura 7 mostra como negar o bit 10 de um registrador denominado
Registrador_X.

BIT: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Registrador_X: 1 0 1 1 1 1 1 1 1 1 1 1 0 1 1 0
Operador XOR XOR XOR XOR ... ... ... ... ... .. .. ... ... ... ... XOR XOR
Máscara: 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0
Registrador_X: 1 0 1 1 1 0 1 1 1 1 1 1 0 1 1 0
Figura 8 – Operador Lógico XOR (OU Exclusivo)

Na linguagem C esta operação pode ser realizada da seguinte maneira:


uint16_t Registrador_X = 0b1011111111110110; // cria uma variável de 16 bits chamada
Registrador_X e a inicializa com o número do tipo binário 0b1011111111110110

uint16_t Mascara = 1 << 10; //inicializa uma Mascara de 16 bits com o bit 10 igual a 1
Registrador_X ^= Mascara; // Esta linha de código realiza os três passos descritos abaixo:

1) Lê o valor do Registrador_X;
2) Realiza uma operação lógica XOR bit a bit do valor lido com a Mascara;
3) Armazena o resultado no Registrador_X.

Página 16
 
1° Edição 03/2011 

Display de 7 Segmentos

O objetivo deste exemplo é criar um programa que irá acionar um display


de 7 segmentos, como exibido na Figura 05. Toda vez que um push botton for
pressionado, o display será incrementado de uma unidade até 9.
Após exibir o número 9, na próxima vez que o botão for pressionado, o
display deverá exibir o número 0.

Os seguintes passos deverão ser executados:

Passo 1: Crie um novo projeto denominado Display_7Seg01. As instruções


sobre como criar um projeto estão no arquivo denominado Tutorial - Como
Criar um Projeto em C para ARM.pdf, disponível no arquivo Aulas.zip

Passo 2: Copiar o conteúdo do arquivo Display_7Seg01.txt, localizado em


...\Aulas\Display_7Seg01\Programas, e colar no arquivo main.c criado no
projeto Display_7Seg01. Onde ...\ representa o local onde o arquivo Aulas.zip
foi descompactado.

Passo 3: Compilar o programa de acordo com as instruções fornecidas no


arquivo chamado Tutorial - Como Criar um Projeto em C para ARM.pdf

Passo 4: Transferir o arquivo binário gerado para o microcontrolador ARM.


C1

22pF
X1 U1
CRYSTAL
C2 FREQ=12MHz 62 19
XTAL1 P0.0/TxD0/PWM1
61 21
XTAL2 P0.1/RxD0/PWM3/EINT0
22
P0.2/SCL0/CAP0.0
22pF 3 26
RTXC1 P0.3/SDA0/MAT0..0/EINT1
5 27
3.3V D1 RTXC2 P0.4/SCK0/CAP0.1/AD0.6
29
3.3V

P0.5/MISO0/MAT0.1/AD0.7
57 30
RST P0.6/MOSI0/CAP0.2/AD1.0
31
P0.7/SSEL0/PWM2/EINT2
DIODE 33
P0.8/TxD1/PWM4/AD1.1
34
R1 C3 P0.9/RxD1/PWM6/EINT3
35
P0.10/RTS1/CAP1.0/AD1.2
P0.11/CTS1/CAP1.1/SCL1
37 R2
38 2k2
47k P0.12/DSR1/MAT1.0/AD1.3
100pF 39
P0.13/DTR1/MAT1.1/AD1.4
41
P0.14/DCD1/EINT1/SDA1
45 3.3V
P0.15/RI1/EINT2/AD1.5
46
P0.16/EINT0/MAT0.2/CAP0.2
47
P0.17/CAP1.2/SCK1/MAT1.2
P0.18/CAP1.3/MISO1/MAT1.3
53 U2:B
54 R7
P0.19/MAT1.2/MOSI1/CAP1.2
55 3 4
P0.20/MAT1.3/SSEL1/EINT3
1
P0.21/PWM5/AD1.6/CAP1.3 300
2
P0.22/AD1.7/CAP0.0/MAT0.0 74HCT04
P0.23
58 U2:A
R3
9 1 2
P0.25/AD0.4/AOUT
10 300
P0.26/AD0.5
11
P0.27/AD0.0/CAP0.1/MAT0.1 74HCT04
P0.28/AD0.1/CAP0.2/MAT0.2
13 U2:C
14 R4
P0.29/AD0.2/CAP0.3/MAT0.3
15 5 6
P0.30/AD0.3/EINT3/CAP0.0
17 300
P0.31
U2:D
74HCT04
16 R5
P1.16/TRACEPKT0
3.3V 12 13 12
P1.17/TRACEPKT1
49 8 300
VBAT P1.18/TRACEPKT2
4
P1.19/TRACEPKT3 74HCT04
63
VREF P1.20/TRACESYNC
48 U2:E
7 44 R6
V3A P1.21/PIPESTAT0
51 40 11 10
V3 P1.22/PIPESTAT1
43 36 300
V3 P1.23/PIPESTAT2
23
V3 P1.24/TRACECLK
32 U2:F
74HCT04
28 R8
P1.25/EXTIN0
59 24 9 8
VSSA P1.26/RTCK
50 64 300
VSS P1.27/TDO
42 60
VSS P1.28/TDI 74HCT04
25
VSS P1.29/TCK
56 U3:A
18 52 R9
VSS P1.30/TMS
6 20 1 2
VSS P1.31/TRST
300
LPC2138
74HCT04

Figura 9 - Display de 7 Segmentos acionado pelos pinos P1.25, P1.26, P1.27, P1.28,
P1.29, P1.30 e P1.31

Página 17
 
1° Edição 03/2011 

O display utilizado no circuito da Figura 05 é do tipo anodo comum e


possui a configuração interna mostrada na Figura 06 (b).
Os pinos do microcontrolador que acionam os segmentos estão ligados
aos segmentos de acordo com as informações apresentadas na Tabela 01,
coluna intitulada Pino Ex.01.

a
b
f

g Anodo
Comum
c
e

(a)  (b) 
Figura 10 – (a) Display de 7 seguimentos catodo comum (b) display de 7 seguimentos
anodo comum.

Tabela 01 – Pinos e Segmentos

Pino Ex.01 Pino Ex. 02 Segmento


P1.25 P1.18 a
P1.26 P1.19 b
P1.27 P1.20 c
P1.28 P1.21 d
P1.29 P1.22 e
P1.30 P1.23 f
P1.31 P1.24 g

Exercício Proposto

Exercício 01: Crie um programa para acionar dois displays de 7 segmentos,


conforme mostrado na Figura 07. Toda vez que um push botton for
pressionado, o display será incrementado de uma unidade. Faça os displays
contarem de 0 a 15 e após exibir o número 15, na próxima vez que o botão for
pressionado, o display deverá exibir o número 0.
Os pinos do microcontrolador estão ligados aos segmentos especificados
na Tabela 01.

Página 18
 
1° Edição 03/2011 

Programa:
#include "LPC214x.h" // Informa ao compilador que ele deve incluir o arquivo LPC214x.h.
#include "cpu_init.h" // Informa ao compilador para incluir o arquivo cpu_init.h

int main (void)


{
cpu_init(); // Chama a rotina de inicialização da CPU

int i = 0; // Declara um inteiro de 32bit e inicializa com o valor zero

// O registrador PINSEL2 controla a função dos pinos do PORT1. Se o bit 2 do


registrador PINSEL2 for igual a 0 (zero), os pinos P1.36-26 são configurados como
entrada/saída e se o bit 3 deste registrador for igual a 0 (zero), os pinos P1.25-16 são
configurados como entrada/saída. Por padrão, os bits 2 e 3 são inicializados com valor 0
(zero)- Ver Table 62: Pin function Select register 2 no documento LPC214X User
Manual.pdf

IODIR1 |= (1<<25)|(1<<26)|(1<<27)|(1<<28)|(1<<29)|(1<<30)|(1<<31); // Configura os


pinos P1.25 a P1.31 como saída

IOCLR1 = 0xFFFFFFFF; // Força todos os pinos do PORT1 para nível baixo


IOSET1 = (1<<25)|(1<<26)|(1<<27)|(1<<28)|(1<<29)|(1<<30); // Força nível alto nos
pinos P1.25, P1.26, P1.27, P1.28, P1.29 e P1.30

while(1){
if (!(IOPIN0 & (1<<15))) //Verifica se o Botão foi pressionado
{
IOCLR1 = 0xFFFFFFFF; // Força nível baixo em todos os pinos do PORT1

// Conversor Decimal para 7 Segmentos


switch (i)
{
case 0: // Escreve 0 no display
IOSET1 = (1<<25)|(1<<26)|(1<<27)|(1<<28)|(1<<29)|(1<<30);
break;
case 1: // Escreve 1 no display
IOSET1 = (1<<26)|(1<<27);
break;
case 2: // Escreve 2 no display
IOSET1 = (1<<25)|(1<<26)|(1<<28)|(1<<29)|(1<<31);
break;
case 3: // Escreve 3 no display
IOSET1 = (1<<25)|(1<<26)|(1<<27)|(1<<28)|(1<<31);
break;
case 4: // Escreve 4 no display
IOSET1 = (1<<26)|(1<<27)|(1<<30)|(1<<31);
break;
case 5: // Escreve 5 no display
IOSET1 = (1<<25)|(1<<27)|(1<<28)|(1<<30)|(1<<31);
break;
case 6: // Escreve 6 no display
IOSET1 = (1<<25)|(1<<27)|(1<<28)|(1<<30)|(1<<29)|(1<<31);
break;
case 7: // Escreve 7 no display
IOSET1 = (1<<25)|(1<<26)|(1<<27);
break;
case 8: // Escreve 8 no display
IOSET1 = (1<<25)|(1<<26)|(1<<27)|(1<<28)|(1<<29)|(1<<30)|(1<<31);
break;
case 9: // Escreve 9 no display
IOSET1 = (1<<25)|(1<<26)|(1<<27)|(1<<30)|(1<<31);
break;
default: // O default escreve 0 no display
IOSET1 = (1<<25)|(1<<26)|(1<<27)|(1<<28)|(1<<29)|(1<<30);
}
// O While espera enquanto o Botão estiver pressinado
while (!(IOPIN0 & (1<<15))){ asm volatile ("NOP");}
i++; // Incrementa i
if (i >= 10){ //Verifica se i é maior ou igual a 10
i = 0;
}
}
}
return(0);
}

Página 19
 
1° Edição 03/2011 

C1

22pF
X1 U1
CRYSTAL
C2 FREQ=12MHz 62 19
XTAL1 P0.0/TxD0/PWM1
61 21
XTAL2 P0.1/RxD0/PWM3/EINT0
22
P0.2/SCL0/CAP0.0
22pF 3 26 3.3V
RTXC1 P0.3/SDA0/MAT0..0/EINT1
5 27 (COM)
3.3V D1 RTXC2 P0.4/SCK0/CAP0.1/AD0.6
29

3.3V
P0.5/MISO0/MAT0.1/AD0.7
57 30
RST P0.6/MOSI0/CAP0.2/AD1.0
31
P0.7/SSEL0/PWM2/EINT2
DIODE
P0.8/TxD1/PWM4/AD1.1
33 U3:B
34 R10
R1 C3 P0.9/RxD1/PWM6/EINT3
35 3 4
P0.10/RTS1/CAP1.0/AD1.2
P0.11/CTS1/CAP1.1/SCL1
37 R2 300
38 2k2
47k P0.12/DSR1/MAT1.0/AD1.3 74HCT04
100pF
P0.13/DTR1/MAT1.1/AD1.4
39 U3:C
41 R11
P0.14/DCD1/EINT1/SDA1
45 5 6
P0.15/RI1/EINT2/AD1.5
300
46
P0.16/EINT0/MAT0.2/CAP0.2 74HCT04
P0.17/CAP1.2/SCK1/MAT1.2
47 U3:D
P0.18/CAP1.3/MISO1/MAT1.3
53 R12 U2:B
54 13 12 R7
P0.19/MAT1.2/MOSI1/CAP1.2
55 3 4
P0.20/MAT1.3/SSEL1/EINT3 300
P0.21/PWM5/AD1.6/CAP1.3
1 U3:E
74HCT04 300
2 R13
P0.22/AD1.7/CAP0.0/MAT0.0 74HCT04
P0.23
58 11 10 U2:A
300 R3
9 1 2
P0.25/AD0.4/AOUT 74HCT04
P0.26/AD0.5
10 U3:F 300
11 R14
P0.27/AD0.0/CAP0.1/MAT0.1 74HCT04
P0.28/AD0.1/CAP0.2/MAT0.2
13 9 8 U2:C
14 300 R4
P0.29/AD0.2/CAP0.3/MAT0.3
P0.30/AD0.3/EINT3/CAP0.0
15 U4:A
74HCT04
5 6
P0.31
17 R15 300
1 2 U2:D
74HCT04
16 300 R5
P1.16/TRACEPKT0
3.3V 12 13 12
P1.17/TRACEPKT1 74HCT04
49
VBAT P1.18/TRACEPKT2
8 U4:B 300
P1.19/TRACEPKT3
4 R16 74HCT04
63
VREF P1.20/TRACESYNC
48 3 4 U2:E
7 44 300 R6
V3A P1.21/PIPESTAT0
51 40 11 10
V3 P1.22/PIPESTAT1 74HCT04
43 36 300
V3 P1.23/PIPESTAT2
23
V3 P1.24/TRACECLK
32 U2:F
74HCT04
28 R8
P1.25/EXTIN0
59 24 9 8
VSSA P1.26/RTCK
50 64 300
VSS P1.27/TDO
42 60
VSS P1.28/TDI 74HCT04
25
VSS P1.29/TCK
56 U3:A
18 52 R9
VSS P1.30/TMS
6 20 1 2
VSS P1.31/TRST
300
LPC2138
74HCT04

 
Figura 11 – Display de 7 Segmentos de dezena e unidade.

Números mágicos em programação

Em programação o termo número mágico é dado para números que


aparecem no código, geralmente sem explicação. São chamados de mágicos
por ironia; o seu uso não é considerado uma boa prática de programação.
A maior parte das linguagens de programação permite que criem nomes
descritivos para representar estes números. Estes nomes são chamados
constantes. Seu uso facilita a leitura e a manutenção do código. O uso de
constantes é preferível ao de números mágicos.
Exemplo: Suponha que um programa de cálculo trigonométrico faça uso do
número π em diversos lugares. A princípio o programador usou a aproximação
3.14 e a colocou numericamente em todos os lugares que ela era necessária.
O número 3.14 a princípio é facilmente reconhecível como π por qualquer
pessoa com algum conhecimento de matemática. Porém nos testes o
programador descobriu que precisaria de uma aproximação melhor, como
3.1415926. Agora ele tem que procurar todas as ocorrências de 3.14 no
programa e substituí-la pela nova aproximação. Este procedimento é
trabalhoso e sujeito a erros. Se o programador tivesse usado uma constante
com o nome PI, em vez do número mágico 3.14, bastaria mudar a aproximação
na definição da constante (URL 6).
Isto posto, para evitar a presença de números mágicos no programa do
exemplo anterior, a diretiva #define pode ser utilizada para especificar o
segmento do display que está sendo acionado por um determinado pino do
microcontrolador, como mostrado a seguir:

Página 20
 
1° Edição 03/2011 

Programa com #define:


#include "LPC214x.h"
#include "cpu_init.h"

/*------------------------------------------------------------------------------------*/
/* DEFINITIONS AND MACROS */
/*------------------------------------------------------------------------------------*/
#define seg_a (1<<25) // Pino 25 do PORT1
#define seg_b (1<<26) // Pino 26 do PORT1
#define seg_c (1<<27) // Pino 27 do PORT1
#define seg_d (1<<28) // Pino 28 do PORT1
#define seg_e (1<<29) // Pino 29 do PORT1
#define seg_f (1<<30) // Pino 30 do PORT1
#define seg_g (1<<31) // Pino 31 do PORT1

#define button (1<<15) // Botão

/*------------------------------------------------------------------------------------*/
/* FUNCTION IMPLEMENTATION */
/*------------------------------------------------------------------------------------*/

int main (void) // Primeira função a ser executada, todo programa deve possuir uma.

{
cpu_init(); // Chama a rotina de inicialização da CPU

int i = 0; // Declara um inteiro de 32bit (-2.147.483.648 a +2.147.483.647)

// O registrador PINSEL2 controla a função dos pinos do PORT1. Se o bit 2 do


registrador PINSEL2 for igual a 0 (zero), os pinos P1.36-26 são configurados como
entrada/saída e se o bit 3 deste registrador for igual a 0 (zero), os pinos P1.25-16 são
configurados como entrada/saída. Por padrão, os bits 2 e 3 são inicializados com valor 0
(zero)- Ver Table 62: Pin function Select register 2 no documento LPC214X User
Manual.pdf

IODIR1 |= seg_a|seg_b|seg_c|seg_d|seg_e|seg_f|seg_g; // P1.25 a P1.31 como saída


IOCLR1 = 0xFFFFFFFF; // Força todos os pinos do PORT1 para nível baixo
IOSET1 = seg_a|seg_b|seg_c|seg_d|seg_e|seg_f; // Nível alto nos pinos P1.25 a P1.30

while(1){

if (!(IOPIN0 & button)) //Verifica se o Botão foi pressionado


{

IOCLR1 = 0xFFFFFFFF; // Força nível baixo em todos os pinos do PORT1

switch (i) // Conversor Decimal para 7 Segmentos


{
case 0: IOSET1 = seg_a|seg_b|seg_c|seg_d|seg_e|seg_f; break; // 0 no display
case 1: IOSET1 = seg_b|seg_c; break; // 1 no display
case 2: IOSET1 = seg_a|seg_b|seg_d|seg_e|seg_g; break; // 2 no display
case 3: IOSET1 = seg_a|seg_b|seg_c|seg_d|seg_g; break; // 3 no display
case 4: IOSET1 = seg_b|seg_c|seg_f|seg_g; break; // 4 no display
case 5: IOSET1 = seg_a|seg_c|seg_d|seg_f|seg_g; break; // 5 no display
case 6: IOSET1 = seg_a|seg_c|seg_d|seg_f|seg_e|seg_g; break; // 6 no display
case 7: IOSET1 = seg_a|seg_b|seg_c; break; // 7 no display
case 8: IOSET1 = seg_a|seg_b|seg_c|seg_d|seg_e|seg_f|seg_g; break;// 8
case 9: IOSET1 = seg_a|seg_b|seg_c|seg_f|seg_g; break; // 9 no display
default: IOSET1 = seg_a|seg_b|seg_c|seg_d|seg_e|seg_f; // 0 no display
}

while (!(IOPIN0 & button)); // Espera enquanto o Botão estiver pressinado

i++; // Incrementa i

if (i >= 10){
i = 0;
}
}
}
return(0);
}
/*------------------------------------------------------------------------------------*/
/* EOF */
/*------------------------------------------------------------------------------------*/

Página 21
 
1° Edição 03/2011 

Exercício Resolvido 01: Crie um programa para acionar dois displays de 7


segmentos, conforme mostrado na Figura 08. Toda vez que um push botton for
pressionado, o display será incrementado de uma unidade. Faça os displays
contarem de 0 a 15 e após exibir o número 15, na próxima vez que o botão for
pressionado, o display deverá exibir o número 0. Observe que o segmento (a)
de ambos os displays são acionados pelo mesmo pino do microcontrolador. O
mesmo ocorre com os segmentos (b), (c), (d), (e), (f) e (g) de ambos os
displays, como especificado na Tabela 02.
C1

22pF
X1 U1
CRYSTAL
C2 FREQ=12MHz 62 19
XTAL1 P0.0/TxD0/PWM1
61 21
XTAL2 P0.1/RxD0/PWM3/EINT0
22
P0.2/SCL0/CAP0.0
22pF 3 26
RTXC1 P0.3/SDA0/MAT0..0/EINT1
5 27
3.3V D1 RTXC2 P0.4/SCK0/CAP0.1/AD0.6
29

3.3V
P0.5/MISO0/MAT0.1/AD0.7
57 30
RST P0.6/MOSI0/CAP0.2/AD1.0
31
P0.7/SSEL0/PWM2/EINT2
DIODE 33
P0.8/TxD1/PW M4/AD1.1
34
R1 C3 P0.9/RxD1/PWM6/EINT3
35
P0.10/RTS1/CAP1.0/AD1.2
P0.11/CTS1/CAP1.1/SCL1
37 R2
47k P0.12/DSR1/MAT1.0/AD1.3
38 2k2 U3:B
100pF 39 R10
P0.13/DTR1/MAT1.1/AD1.4
41 3 4
P0.14/DCD1/EINT1/SDA1
45
P0.15/RI1/EINT2/AD1.5 300
74HCT04
P0.16/EINT0/MAT0.2/CAP0.2
46 U3:C
47 R11
P0.17/CAP1.2/SCK1/MAT1.2
53 5 6
P0.18/CAP1.3/MISO1/MAT1.3
54
P0.19/MAT1.2/MOSI1/CAP1.2 300
55
P0.20/MAT1.3/SSEL1/EINT3 74HCT04
P0.21/PW M5/AD1.6/CAP1.3
1 U3:D
2 R12
P0.22/AD1.7/CAP0.0/MAT0.0
58 13 12
P0.23
300
P0.25/AD0.4/AOUT
9 U3:E
74HCT04
10 R13
P0.26/AD0.5
11 11 10
P0.27/AD0.0/CAP0.1/MAT0.1
13 300
P0.28/AD0.1/CAP0.2/MAT0.2
14
P0.29/AD0.2/CAP0.3/MAT0.3 74HCT04
P0.30/AD0.3/EINT3/CAP0.0
15 U3:F
P0.31
17 R14
9 8
16 300
P1.16/TRACEPKT0
3.3V
P1.17/TRACEPKT1
12 U4:A
74HCT04
49
VBAT P1.18/TRACEPKT2
8 R15
4 1 2
P1.19/TRACEPKT3
63 48 300
VREF P1.20/TRACESYNC
7 44
V3A P1.21/PIPESTAT0 74HCT04
51
V3 P1.22/PIPESTAT1
40 U4:B
43 36 R16
V3 P1.23/PIPESTAT2
23 32 3 4
V3 P1.24/TRACECLK
28 300
P1.25/EXTIN0
59 24
VSSA P1.26/RTCK 74HCT04
50
VSS P1.27/TDO
64 R3 Q1
42 60 BC337
VSS P1.28/TDI
25 56 2.2k
VSS P1.29/TCK
18 52
VSS P1.30/TMS
6 20
VSS P1.31/TRST
LPC2138
R4 Q2
BC337
2.2k

 
Figura 12 – Display de 7 Segmentos de dezena e unidade.

Tabela 02 – Segmentos de dois displays diferentes acionados pelo mesmo pino do


microcontrolador.

Segmento
Pino dos
Displays
P1.18 A
P1.19 B
P1.20 C
P1.21 D
P1.22 E
P1.23 F
P1.24 G

Página 22
 
1° Edição 03/2011 

Resolução: Para que o observador tenha a impressão que os números,


exibidos em ambos os displays, estejam sendo mostrados simultaneamente, é
necessário que os displays sejam acionados alternadamente em uma
frequência maior ou igual a 24 vezes por segundo cada um.

Em 1826 o médico e filólogo inglês Peter Mark Roget publicou um estudo


informando que o olho humano retém a imagem que se forma na retina por
alguns décimos de segundo a mais (aproximadamente 1/24 de segundo),
mesmo após o clarão que a provocou haver desaparecido. Portanto, como a
imagem na retina persiste no intervalo de tempo compreendido entre duas
imagens sucessivas, o número em um determinado display deverá ser
mostrado no exato instante em que o número exibido anteriormente estiver
desaparecendo de nossa "memória visual", o que irá produzir a sensação de
que o número está sendo exibido continuamente.

OBS1: Em softwares de simulação como o PROTEUS, por exemplo, a


sensação de que o número está sendo exibido continuamente pode não ser
obtida.

OBS2: Observe que os displays são do tipo catodo comum e o buffer 74HCT04
que aciona cada segmento é inversor, logo, os segmentos serão ligados
quando os pinos do microcontrolador estiverem em nível lógico baixo.

O arquivo intitulado Exercício Resolvido01.txt, localizado em


...\Aulas\Display_7Seg01\Exercício Resolvido01\Programas, contém o
programa do Exercício Resolvido01 digitado.

Programa do Exercício Resolvido 01


#include "LPC214x.h"
#include "cpu_init.h"
#include "stdint.h"

/*------------------------------------------------------------------------------------*/
/* DEFINITIONS AND MACROS */
/*------------------------------------------------------------------------------------*/
#define seg_a (1<<18) // Segmento a do display
#define seg_b (1<<19) // Segmento a do display
#define seg_c (1<<20) // Segmento a do display
#define seg_d (1<<21) // Segmento a do display
#define seg_e (1<<22) // Segmento a do display
#define seg_f (1<<23) // Segmento a do display
#define seg_g (1<<24) // Segmento a do display

#define tran_dez (1<<25) // Transistor da Unidade


#define tran_uni (1<<26) // Transistor da Dezena
#define botton (1<<15) // Botão

/*------------------------------------------------------------------------------------*/
/* FUNCTION IMPLEMENTATION */
/*------------------------------------------------------------------------------------*/
//--------------------------------------------------------------------------------------
//--- Rotina que converte um número decimal para 7 Segmentos ---------------------------
void Decimal_to_7Segment(int i){

switch (i)
{
case 0: IOCLR1 = seg_a|seg_b|seg_c|seg_d|seg_e|seg_f; break; // 0 no display

Página 23
 
1° Edição 03/2011 
case 1: IOCLR1 = seg_b|seg_c; break; // 1 no display
case 2: IOCLR1 = seg_a|seg_b|seg_d|seg_e|seg_g; break; // 2 no display
case 3: IOCLR1 = seg_a|seg_b|seg_c|seg_d|seg_g; break; // 3 no display
case 4: IOCLR1 = seg_b|seg_c|seg_f|seg_g; break; // 4 no display
case 5: IOCLR1 = seg_a|seg_c|seg_d|seg_f|seg_g; break; // 5 no display
case 6: IOCLR1 = seg_a|seg_c|seg_d|seg_f|seg_e|seg_g; break; // 6 no display
case 7: IOCLR1 = seg_a|seg_b|seg_c; break; // 7 no display
case 8: IOCLR1 = seg_a|seg_b|seg_c|seg_d|seg_e|seg_f|seg_g; break; // 8 no display
case 9: IOCLR1 = seg_a|seg_b|seg_c|seg_f|seg_g; break; // 9 no display
default: IOCLR1 = seg_a|seg_b|seg_c|seg_f|seg_g; // 0 no display
}
}

//--- Rotina Principal -----------------------------------------------------------------


int main (void)
{
cpu_init(); // Chama a rotina de inicialização da CPU

int j = 0; // Declara um inteiro de 32bit (-2.147.483.648 a +2.147.483.647)


int uni = 0; // Declara um inteiro de 32bit (-2.147.483.648 a +2.147.483.647)
int dez = 0; // Declara um inteiro de 32bit (-2.147.483.648 a +2.147.483.647)
int cont = 0; // Declara um inteiro de 32bit (-2.147.483.648 a +2.147.483.647)

// O registrador PINSEL2 controla a função dos pinos do PORT1. Se o bit 2 do


registrador PINSEL2 for igual a 0 (zero), os pinos P1.36-26 são configurados como
entrada/saída e se o bit 3 deste registrador for igual a 0 (zero), os pinos P1.25-16 são
configurados como entrada/saída. Por padrão, os bits 2 e 3 são inicializados com valor 0
(zero)- Ver Table 62: Pin function Select register 2 no documento LPC214X User
Manual.pdf

IODIR1 |= seg_a|seg_b|seg_c|seg_d|seg_e|seg_f|seg_g|tran_dez|tran_uni; // Pinos como


saída

IOCLR1 = tran_dez|tran_uni; // Nível baixo nos pinos que acionam os transistores


IOSET1 = seg_a|seg_b|seg_c|seg_d|seg_e|seg_f|seg_g; // Nível baixo nos segmentos

while(1){

IOSET1 = tran_dez; // Faz o Transistor da dezena entrar na saturação


Decimal_to_7Segment(dez);
for (j = 0; j < 5000; j++ ) asm volatile ("NOP"); // Espera até os segmentos
assumirem brilho máximo. Este tempo deve ser configurado na prática

IOCLR1 = tran_dez; // Faz o Transistor da dezena entrar em corte


IOSET1 = seg_a|seg_b|seg_c|seg_d|seg_e|seg_f|seg_g; // Nível baixo nos segmentos
IOSET1 = tran_uni; // Faz o Transistor da unidade entrar em saturação
Decimal_to_7Segment(uni); // Chama Decimal_to_7Segment() para exibir unidade
for (j = 0; j < 5000; j++ ) asm volatile ("NOP"); // Espera

IOCLR1 = tran_uni; // Faz o Transistor da unidade entrar em corte


IOSET1 = seg_a|seg_b|seg_c|seg_d|seg_e|seg_f|seg_g; // Nível baixo nos segmentos

if (!(IOPIN0 & botton)) // Verifica se o Botão foi pressionado


{
while (!(IOPIN0 & botton)); // Espera enquanto o Botão estiver pressinado

cont++; // Incrementa a variável cont

if (cont >= 16){


cont = 0;
}
uni = cont % 10; // uni = resto da divisão de cont por 10
dez = cont / 10; // dez = cont dividido por 10
}
}
return(0);
}

Implementando a rotina Decimal_to_7Segment( ) usando tabela:


#include "LPC214x.h"
#include "cpu_init.h"
#include "stdint.h"

Página 24
 
1° Edição 03/2011 
/*------------------------------------------------------------------------------------*/
/* DEFINITIONS AND MACROS */
/*------------------------------------------------------------------------------------*/

#define seg_a (1<<18) // Pino 18 do PORT1


#define seg_b (1<<19) // Pino 19 do PORT1
#define seg_c (1<<20) // Pino 20 do PORT1
#define seg_d (1<<21) // Pino 21 do PORT1
#define seg_e (1<<22) // Pino 22 do PORT1
#define seg_f (1<<23) // Pino 23 do PORT1
#define seg_g (1<<24) // Pino 24 do PORT1

#define tran_dez (1<<25) // Pino 25 do PORT1


#define tran_uni (1<<26) // Pino 26 do PORT1
#define botton (1<<15) // Botão

/*------------------------------------------------------------------------------------*/
/* FUNCTION IMPLEMENTATION */
/*------------------------------------------------------------------------------------*/

//--------------------------------------------------------------------------------------
//--- Rotina que converte um número decimal para 7 Segmentos ---------------------------
void Decimal_to_7Segment(uint8_t i){

const uint32_t tabela[10] = {


seg_a|seg_b|seg_c|seg_d|seg_e|seg_f, // Escreve 0 no display
seg_b|seg_c, // Escreve 1 no display
seg_a|seg_b|seg_d|seg_e|seg_g, // Escreve 2 no display
seg_a|seg_b|seg_c|seg_d|seg_g, // Escreve 3 no display
seg_b|seg_c|seg_f|seg_g, // Escreve 4 no display
seg_a|seg_c|seg_d|seg_f|seg_g, // Escreve 5 no display
seg_a|seg_c|seg_d|seg_f|seg_e|seg_g, // Escreve 6 no display
seg_a|seg_b|seg_c, // Escreve 7 no display
seg_a|seg_b|seg_c|seg_d|seg_e|seg_f|seg_g, // Escreve 8 no display
seg_a|seg_b|seg_c|seg_f|seg_g // Escreve 9 no display
};

if (i > 9){
i = 0;
}

IOCLR1 = tabela[i];
}

//--- Rotina Principal -----------------------------------------------------------------


int main (void)
{
cpu_init(); // Chama a rotina de inicialização da CPU

int j = 0; // Declara um inteiro de 32bit (-2.147.483.648 a +2.147.483.647)


int uni = 0; // Declara um inteiro de 32bit (-2.147.483.648 a +2.147.483.647)
int dez = 0; // Declara um inteiro de 32bit (-2.147.483.648 a +2.147.483.647)
int cont = 0; // Declara um inteiro de 32bit (-2.147.483.648 a +2.147.483.647)

// O registrador PINSEL2 controla a função dos pinos do PORT1. Se o bit 2 do


registrador PINSEL2 for igual a 0 (zero), os pinos P1.36-26 são configurados como
entrada/saída e se o bit 3 deste registrador for igual a 0 (zero), os pinos P1.25-16 são
configurados como entrada/saída. Por padrão, os bits 2 e 3 são inicializados com valor 0
(zero)- Ver Table 62: Pin function Select register 2 no documento LPC214X User
Manual.pdf

IODIR1 |= seg_a|seg_b|seg_c|seg_d|seg_e|seg_f|seg_g|tran_dez|tran_uni; // Pinos como


saída

IOCLR1 = tran_dez|tran_uni; // Nível baixo nos pinos que acionam os transistores


IOSET1 = seg_a|seg_b|seg_c|seg_d|seg_e|seg_f|seg_g; // Nível baixo nos segmentos

while(1){

IOSET1 = tran_dez; // Faz o Transistor da dezena entrar na saturação


Decimal_to_7Segment(dez);
for (j = 0; j < 5000; j++ ) asm volatile ("NOP"); // Espera até os segmentos
assumirem brilho máximo. Este tempo deve ser configurado na prática

IOCLR1 = tran_dez; // Faz o Transistor da dezena entrar em corte


IOSET1 = seg_a|seg_b|seg_c|seg_d|seg_e|seg_f|seg_g; // Nível baixo nos segmentos
IOSET1 = tran_uni; // Faz o Transistor da unidade entrar em saturação

Página 25
 
1° Edição 03/2011 
Decimal_to_7Segment(uni); // Chama Decimal_to_7Segment() para exibir unidade
for (j = 0; j < 5000; j++ ) asm volatile ("NOP"); // Espera

IOCLR1 = tran_uni; // Faz o Transistor da unidade entrar em corte


IOSET1 = seg_a|seg_b|seg_c|seg_d|seg_e|seg_f|seg_g; // Nível baixo nos segmentos

if (!(IOPIN0 & botton)) // Verifica se o Botão foi pressionado


{
while (!(IOPIN0 & botton)); // Espera enquanto o Botão estiver pressinado

cont++; // Incrementa a variável cont

if (cont >= 16){


cont = 0;
}
uni = cont % 10; // uni = resto da divisão de cont por 10
dez = cont / 10; // dez = cont dividido por 10
}
}
return(0);
}

Página 26
 
1° Edição 03/2011 

Portabilidade e o Pré-processador C
Um código portável possui módulos que são reutilizados quando se migra
de uma plataforma de desenvolvimento para outra ou de um modelo de
microcontrolador para outro.
Ao se modularizar um código, tomando-se os cuidados para torná-lo
portável, a maior parte dos módulos são reutilizados exigindo do programador o
trabalho de reajustar somente os módulos que comandam os dispositivos
periféricos que são particulares a um determinado modelo de microcontrolador.
Deste modo, "Portabilidade" significa escrever o seu programa (código), de
tal forma que o código funcione mesmo em ambientes diferentes, isto é, em
vários processadores, sistemas operacionais, versões de bibliotecas, etc.
Se o programa for portável, basta recompilar o código em um sistema novo
e ele deverá rodar sem problemas.
Por outro lado, os códigos não portáteis geram muitos problemas de
manutenção, controle de versões, possuem legibilidade ruim e são de difícil
compreensão.
O uso criterioso das diretivas do pré-processador auxiliam a tornar o
código portável.
Um pré-processador é um programa que recebe texto e efetua conversões
léxicas nele. As conversões podem incluir substituição de macros, inclusão
condicional e inclusão de outros arquivos.
A linguagem de programação C possui um pré-processador que efetua as
seguintes transformações:
 Substitui trígrafos por equivalentes;
 Concatena arquivos de código-fonte;
 Substitui comentários por espaços em branco;
 Reage a linhas iniciadas com um caractere de cardinal (#),
efetuando substituição de macros, inclusão de arquivos, inclusão
condicional e outras operações.
Segue dois exemplos de uso do pré-processador:
Exemplo 1: O CRC de um Frame MODBUS pode ser calculado de duas
maneiras. A primeira é utilizando tabelas e a segunda é por meio de expressão
aritmética que realiza o cálculo do CRC bit a bit.
Utilizar tabela consome mais memória enquanto calcular bit a bit requer
maior processamento. Deste modo, o melhor método a ser utilizado depende
dos recursos de processamento disponíveis no µC utilizado.
A Figura 13 mostra o uso do pré-processador para facilitar a escolha do
método de cálculo do CRC-16. Caso a macro USE_CRC_TABLE for igual a 1
o CRC-16 será calculado por meio de tabela que exigirá mais memória, mas
em contrapartida será mais rápido. Por outro lado, quando a macro
USE_CRC_TABLE for igual a 0, o cálculo do CRC será realizado por meio de
expressão aritmética que calcula o CRC bit a bit, o que exigirá maior
processamento.

Página 27
 
1° Edição 03/2011 

Figura 13 – Uso do pré-processador no cálculo do CRC-16.

Exemplo 2: Diz respeito a configuração de registradores que controlam os


periféricos de um microcontrolador que é realizada por meio de registradores
especiais onde cada bit ou um conjunto de bits ajustam ou habilitam uma
determinada função.
A multiplexação de funções em pinos de microcontroladores é prática
comum. Por esse motivo, a função desejada deve ser escolhida por meio de
registradores de configuração.
A Figura 14 mostra as funções dos pinos P0.4, P0.5 e P0.6. Supondo que
um dispositivo com interface SPI esta conectado neles, o pino P0.4 deve ser
configurado com a função SCK0, o pino P05 com a função MISO0 e o pino
P0.6 com a função MOSI0.

Figura 14 - Configuração de Registrador.

O registrador que configura as funções dos pinos é o PINSEL0 e, de


acordo com as instruções contidas na figura, o bit 8, 10, e 12 devem ser igual a
1 para que a interface SPI seja habilitada. Neste caso, o pré-processador pode
ser utilizado para facilitar a configuração e deixar claro os bits que assumiram
valor igual a 1 ( PINSEL0 |= (1<<8)|(1<<10)|(1<<12); ). Por outro lado, a
abordagem que utiliza um número hexadecimal para configurar os pinos, deixa
o código de difícil compreensão ( PINSEL0 |= 0x00001500; ).
Vale salientar que o uso do pré-processador não afeta o desempenho do
firmware porque é executado antes da compilação. Ele é executado
automaticamente todas as vezes que o programa é compilado e os comandos
a serem executados são dados através de diretivas do pré-processador.
As linhas que começam com um # são comandos para o pré-processador.
A linha inteira é reservada para este comando (nenhum código C pode

Página 28
 
1° Edição 03/2011 

aparecer nesta linha e comandos do pré-processador não podem estar


separados em diversas linhas).
O uso do pré-processador é recomendável no sentido de elevar a
portabilidade do código e facilitar a compreensão do mesmo.
Ademais, a utilização de compiladores, RTOS e bibliotecas que possuem
códigos abertos consistem em boa prática no desenvolvimento de sistemas
embarcados.
Toda vez que se acrescenta em um projeto bibliotecas com código
fechado, haverá o risco da má operação do código fechado que não poderá ser
solucionado sem o auxílio do fornecedor. Além disso, existe o risco da
repentina descontinuidade do fornecedor/suporte das bibliotecas utilizadas.

Tipos Primitivos em C

As variáveis numéricas em C possuem duas categorias: tipo inteiro ( int ) e


ponto flutuante ( float ).
O hardware que realiza a aritmética de ponto flutuante é mais complexo e,
consequentemente, mais caro do que o hardware utilizado em operações
aritméticas com números inteiros. Assim, para se reduzir custos de produção
em larga escala, a maior parte dos microcontroladores não possuem
hardware dedicado para executar operações matemáticas de dados
representados em ponto flutuante. Por isso, em sistemas embarcados, evita-se
o uso de variáveis do tipo ponto flutuante.
Porém, cada processador possui um
tamanho de palavra definido pelas
características intrínsecas de hardware.
Deste modo, existe uma expectativa que
os compiladores C implementariam o tipo
int correspondente ao tamanho da palavra
definida pelo hardware, permitindo aos
programadores utilizarem o inteiro (int)
com o máximo de eficiência.
Considerando que existem microcontroladores de 8, 16 e 32 bits e que o
compilador C utilizado irá implementar o inteiro de acordo com as
características de hardware, o valor mínimo e máximo do inteiro irá mudar de
acordo com a arquitetura do microcontrolador utilizado. Ao se migrar de um
microcontrolador de 16 bits para um de 8 ocorreria uma redução drástica nos
valores de máximos e mínimos do tipo inteiro e de todos os tipos primitivos
definidos na linguagem C. Portanto, a portabilidade do código fica prejudicada
exigindo do programador alterações no código para que se adéque a nova
arquitetura.

Página 29
 
1° Edição 03/2011 

Com o objetivo de minimizar este problema, a biblioteca stdint.h foi criada.


Ela define o tamanho exato do tipo primitivo de acordo com a norma C99
Standart Data Types criado pela comunidade ANSI/ISSO.
Portanto, em se tratando de programação para microcontroladores, devido
as diferentes arquiteturas existentes, uma boa prática de programação é se
utilizar o C99 Standart Data Types criado pela comunidade ANSI/ISO que
define o tamanho exato do tipo primitivo.
A Tabela 06 mostra os valores de mínimo e máximo do tipo inteiro (int) e
inteiro sem sinal (unsigned int) para arquiteturas de 8, 16 e 32 bit.

Tabela 06 – Tipo inteiro de 8, 16 e 32bit

MICROCONTROLADORES 8, 16 e 32 bit
Arquitetura Tipo n bit n byte Escala de Valores
8 bit int 8 1 -128 a 127
16 bit int 16 2 -32.768 a 32.767
32 bit int 32 4 –2.147.483.648 a 2.147.438.647
8 bit unsigned int 8 1 0 a 255
16 bit unsigned int 16 2 0 a 65.535
32 bit unsigned int 32 4 0 a 4.294.967.295

Observa-se que quando a palavra aumenta um byte o valor de máximo e


mínimo varia drasticamente. Um inteiro de 8 bit varia de -127 a 128 enquanto
um inteiro de 16 bit varia de -32.768 a 32.768 e um de 32 bit de -2.147.483.648
(Dois bilhões cento e quarenta e sete milhões quatrocentos e oitenta e três mil
seiscentos e quarenta e oito)

Deste modo, de acordo com a Tabela 07, se o código for implementado


para um microcontrolador de 32 bit terá problemas de incompatibilidade do tipo
inteiro se for utilizado em um microcontrolador de 16 ou 8 bit. Do mesmo modo,
um firmware criado para um microcontrolador de 16 bit terá problemas de
incompatibilidade do tipo inteiro se for usado em um microcontrolador de 8 bit.

Tabela 07 – Mudança de Plataforma

MUDANÇA DE PLATAFORMA
DE PARA
32 bit 16 ou 8 bit
16 bit 8 bit

Tentando resolver problemas de portabilidade, em 1989, a comunidade


ANSI/ISSO criou o C89 Standart Data Types.
Depois de uma década de testes, o Padrão C89 foi revisado dando origem
ao C99 Standart Data Types, como mostrado na Tabela 08.

Página 30
 
1° Edição 03/2011 

Esse padrão criou o int8_t que especifica o tamanho do inteiro que está
sendo utilizado permitindo que o programador tome providências quando a
operação não for atômica.

Tabela 08 – C99 Standart Data Types

TIPO DESCRIÇÃO
int8_t Inteiro de 8 bit com sinal
uint8_t Inteiro de 8 bit sem sinal
int16_t Inteiro de 16 bit com sinal
uint16_t Inteiro de 16 bit sem sinal
int32_t Inteiro de 32 bit com sinal
uint32_t Inteiro de 32 bit sem sinal
int64_t Inteiro de 64 bit com sinal
uint64_t Inteiro de 64 bit sem sinal

1) int8_t especifica um inteiro com sinal de 8 bit


2) uint8_t especifica um inteiro sem sinal de 8 bit
3) int16_t especifica um inteiro com sinal de 16 bit
4) uint16_t especifica um inteiro sem sinal de 16 bit
5) int32_t especifica um inteiro com sinal de 32 bit
6) uint32_t especifica um inteiro sem sinal de 32 bit
7) int64_t especifica um inteiro com sinal de 64 bit
8) uint64_t especifica um inteiro sem sinal de 64 bit

O exemplo a seguir elucida alguns inconvenientes que surgem quando se


muda de uma plataforma para outra que possui a palavra menor.
Exemplo: Uma indústria possui um equipamento que utiliza um
microcontrolador de 16 bit modelo ST7F269 e decide refazer o projeto e utilizar
a versão de 8 bit ST72264, como mostrado na Figura 11. Tanto um quando o
outro possuem um A/D de 10 bit o que mantém a precisão na leitura do sinal do
sensor.
A empresa pretende fabricar 1.000.000 unidades do equipamento e a
diferença de preço entre o modelo de 16 bit para o de 8 bit é de US$ 2,00.
Portanto a empresa irá economizar 2.000.000,00 (dois milhões de dólares).

Figura 15 – Mudança de plataforma de microcontrolador de 16 bit para


microcontrolador de 8 bit.

Página 31
 
1° Edição 03/2011 

Além disso, vale salientar que a operação de leitura/escrita de um inteiro


de 16 bit em uma arquitetura de 8 bit é uma operação não atômica, portanto, o
trecho da atualização deverá ser protegido.

Exercício Resolvido 02: Quando se deseja acionar um ou mais displays de 7


segmentos utilizando-se um número reduzido de pinos de um determinado
microcontrolador, utiliza-se shift register para acionar os segmentos do display,
como mostrado na Figura 09.
Sabendo que o pino P1.30 do microcontrolador está conectado ao clock
do shift register e o pino P1.31 do microcontrolador conectado ao pino de
dados do shift register, crie um programa para acionar um display de 7
segmentos, conforme mostrado na Figura 09. Toda vez que um push botton for
pressionado, o display será incrementado de uma unidade. Faça o display
contar de 0 a 9 e após exibir o número 9, na próxima vez que o botão for
pressionado, o display deverá exibir o número 0.
C1

22pF
X1 U1
CRYSTAL
C2 FREQ=12MHz 62 19
XTAL1 P0.0/TxD0/PWM1
61 21
XTAL2 P0.1/RxD0/PWM3/EINT0
22
P0.2/SCL0/CAP0.0
22pF 3 26
RTXC1 P0.3/SDA0/MAT0..0/EINT1
5 27
3.3V D1 RTXC2 P0.4/SCK0/CAP0.1/AD0.6
29
3.3V
P0.5/MISO0/MAT0.1/AD0.7
57 30
RST P0.6/MOSI0/CAP0.2/AD1.0
31
P0.7/SSEL0/PW M2/EINT2
DIODE 33
P0.8/TxD1/PWM4/AD1.1
34
R1 C3 P0.9/RxD1/PWM6/EINT3
35
P0.10/RTS1/CAP1.0/AD1.2
P0.11/CTS1/CAP1.1/SCL1
37 R2
38 2k2
47k P0.12/DSR1/MAT1.0/AD1.3
100pF 39
P0.13/DTR1/MAT1.1/AD1.4
41 3.3V
P0.14/DCD1/EINT1/SDA1
45
P0.15/RI1/EINT2/AD1.5
46
P0.16/EINT0/MAT0.2/CAP0.2
47
P0.17/CAP1.2/SCK1/MAT1.2
53
P0.18/CAP1.3/MISO1/MAT1.3 300
54
P0.19/MAT1.2/MOSI1/CAP1.2 300
55
P0.20/MAT1.3/SSEL1/EINT3 330
1
P0.21/PW M5/AD1.6/CAP1.3 300
2
P0.22/AD1.7/CAP0.0/MAT0.0 300
58 300
P0.23
300
9
P0.25/AD0.4/AOUT
10
P0.26/AD0.5
11
P0.27/AD0.0/CAP0.1/MAT0.1
13
P0.28/AD0.1/CAP0.2/MAT0.2
14
P0.29/AD0.2/CAP0.3/MAT0.3
15
P0.30/AD0.3/EINT3/CAP0.0
17
P0.31
16
P1.16/TRACEPKT0
3.3V 12
10

11

12

13
3

P1.17/TRACEPKT1
49 8
VBAT P1.18/TRACEPKT2
P1.19/TRACEPKT3
4 U2
63 48 74HCT164
VREF P1.20/TRACESYNC
1D

7 44
V3A P1.21/PIPESTAT0
SRG8

51 40
V3 P1.22/PIPESTAT1
43 36
V3 P1.23/PIPESTAT2
C1/->

23 32
V3 P1.24/TRACECLK
28
&

P1.25/EXTIN0
R

59 24
VSSA P1.26/RTCK
50 64 3.3V
VSS P1.27/TDO
8

1
2

42 60
9

VSS P1.28/TDI
25 56
VSS P1.29/TCK
18 52
VSS P1.30/TMS
6 20
VSS P1.31/TRST
LPC2138

 
Figura 16 – Acionamento de display de 7 Segmentos com shift register.

Tabela 03 – Conexão dos pinos do shift register 74HCT164 com os segmentos do display

Pino do Shift Pino do Shift Segmento do


Register Register Display
3 Q0
4 Q1 A
5 Q2 B
6 Q3 C
10 Q4 D
11 Q5 E
12 Q6 F
13 Q7 G

Página 32
 
1° Edição 03/2011 

Resolução: A Tabela 04 mostra a operação do circuito integrado 74HCT164.


____
De acordo com esta tabela, o pino MR deve permanecer em nível lógico alto
para que o shift register opere normalmente. Nesta situação, quando os pinos
DSA e DSB assumirem nível lógico alto e ocorrer uma borda de subida de
clock, todos os bits do shift register serão deslocados
( Q0  Q1  Q 2  Q3  Q 4  Q5  Q6  Q7  )  e Q0 assumirá nível lógico
alto. Por outro lado, quando DSA e DSB estiverem em nível lógico baixo e
ocorrer uma borda de subida de clock, todos os bits do shift register serão
deslocados ( Q0  Q1  Q 2  Q3  Q 4  Q5  Q6  Q7  )  e Q0 assumirá
nível lógico baixo.

Tabela 04 – Tabela de operação do circuito integrado 74HCT164

De acordo com a Figura 09, um segmento do display somente é ligado


quando o pino utilizado para acioná-lo assumir nível lógico baixo. Deste modo,
os níveis de tensão em cada pino do shift register para que o display mostre os
números decimais de 0 a 9 são apresentados na Tabela 05.
Tabela 05 – Tensão em cada pino do shift register para que o dispay mostre
os números decimais de 0 a 9

Pinos do Shift Register 74HCT164


3 4 5 6 10 11 12 13
DECIMAL
Segmentos
( NC ) (a) (b) (c) (d) (e) (f) (g)
0 X 0 0 0 0 0 0 1
1 X 1 0 0 1 1 1 1
2 X 0 0 1 0 0 1 0
3 X 0 0 0 0 1 1 0
4 X 1 0 0 1 1 0 0
5 X 0 1 0 0 1 0 0
6 X 1 1 0 0 0 0 0
7 X 0 0 0 1 1 1 1
8 X 0 0 0 0 0 0 0
9 X 0 0 0 0 1 0 0

Página 33
 
1° Edição 03/2011 

Programa do Exercício Resolvido 02


#include "LPC214x.h"
#include "cpu_init.h"
#include <stdint.h>

#define clk (1<<30) // Clock utilizado no shift register


#define data (1<<31) // Dados enviados para o shift register
#define button (1<<15) // Define o pino do botão

//--- Rotina que Serializa um Número Decimal para o Display ----------------------------


void Serialize_to_Display(uint8_t i){

uint8_t num = 0;
uint8_t j = 0;

switch (i)
{
case 0: num = 0b00000001; break; // Escreve 0 no display
case 1: num = 0b01001111; break; // Escreve 1 no display
case 2: num = 0b00010010; break; // Escreve 2 no display
case 3: num = 0b00000110; break; // Escreve 3 no display
case 4: num = 0b01001100; break; // Escreve 4 no display
case 5: num = 0b00100100; break; // Escreve 5 no display
case 6: num = 0b01100000; break; // Escreve 6 no display
case 7: num = 0b00001111; break; // Escreve 7 no display
case 8: num = 0b00000000; break; // Escreve 8 no display
case 9: num = 0b00000100; break; // Escreve 9 no display
default: num = 0b00000001; // Escreve 0 no display
}

for (j = 0; j <= 7; j++ ){


if (num & 1){
IOSET1 = data; // Pino de dados em nível alto
IOCLR1 = clk; // Pino de clock em nível baixo
IOSET1 = clk; // Pino de clock em nível alto
}
else{
IOCLR1 = data; // Pino de dados em nível baixo
IOCLR1 = clk; // Pino de clock em nível baixo
IOSET1 = clk; // Pino de clock em nível alto
}
num = num >> 1;
}
}

//--- Rotina Principal -----------------------------------------------------------------


int main (void){

cpu_init(); // Chama a rotina de inicialização da CPU

uint8_t cont = 0; // Declara um inteiro de 32bit

PINSEL1 |= 0x00000000; // Configura todos os pinos como entrada/saída (I/O)

IODIR1 |= clk|data; // Configura os pinos de clk e data como saída

while(1){

if (!(IOPIN0 & button)) // Verifica se o Botão foi pressionado


{
while (!(IOPIN0 & button)); // Espera enquanto o Botão estiver pressinado

Serialize_to_Display(cont);

cont++; // Incrementa a variável cont

if (cont >= 10){


cont = 0;
}
}
}
return(0);
}

Página 34
 
1° Edição 03/2011 

O arquivo intitulado Exercício Resolvido02.txt, localizado em


...\Aulas\Display_7Seg01\Exercício Resolvido02\Programas, contém o
programa do Exercício Resolvido02 digitado.

Exercício Proposto

Exercício 02: Crie um programa para acionar três displays de 7 segmentos,


conforme mostrado na Figura 10. Considerando que o push botton conectado
no pino P0.15 incrementa a unidade, o que está conectado ao pino P0.8
incrementa a dezena e o que está ligado ao pino P0.0 a centena, toda vez que
um push botton for pressionado, o display correspondente a unidade, dezena
ou centena será incrementado de uma unidade. Faça os displays contarem de
0 a 9 e após exibirem o número 9, na próxima vez que o botão correspondente
for pressionado, o display deverá exibir o número 0.
Os níveis de tensão em cada pino do shift register para que o display
mostre os números decimais de 0 a 9 são apresentados na Tabela 05.
C1

22pF
X1 U1
CRYSTAL
C2 FREQ=12MHz 62 19
3.3V

XTAL1 P0.0/TxD0/PWM1
61 21
XTAL2 P0.1/RxD0/PWM3/EINT0
22 R25
P0.2/SCL0/CAP0.0
22pF 3 26
RTXC1 P0.3/SDA0/MAT0..0/EINT1
5 27
D1 RTXC2 P0.4/SCK0/CAP0.1/AD0.6 2k2
3.3V 29
P0.5/MISO0/MAT0.1/AD0.7
57 30
RST P0.6/MOSI0/CAP0.2/AD1.0
31
P0.7/SSEL0/PWM2/EINT2
DIODE 33
3.3V

P0.8/TxD1/PWM4/AD1.1
34
R1 C3 P0.9/RxD1/PWM6/EINT3
35 R24
P0.10/RTS1/CAP1.0/AD1.2
37
P0.11/CTS1/CAP1.1/SCL1
38
47k P0.12/DSR1/MAT1.0/AD1.3 2k2
100pF 39
P0.13/DTR1/MAT1.1/AD1.4
41 3.3V 3.3V 3.3V
P0.14/DCD1/EINT1/SDA1
45
3.3V

P0.15/RI1/EINT2/AD1.5
46 R2
P0.16/EINT0/MAT0.2/CAP0.2
47
P0.17/CAP1.2/SCK1/MAT1.2
53
P0.18/CAP1.3/MISO1/MAT1.3 2k2 300 300 300
54
P0.19/MAT1.2/MOSI1/CAP1.2 300 300 300
55
P0.20/MAT1.3/SSEL1/EINT3 330 330 330
1
P0.21/PWM5/AD1.6/CAP1.3 300 300 300
2
P0.22/AD1.7/CAP0.0/MAT0.0 300 300 300
58 300 300 300
P0.23
300 300 300
9
P0.25/AD0.4/AOUT
10
P0.26/AD0.5
11
P0.27/AD0.0/CAP0.1/MAT0.1
13
P0.28/AD0.1/CAP0.2/MAT0.2
14
P0.29/AD0.2/CAP0.3/MAT0.3
15
P0.30/AD0.3/EINT3/CAP0.0
17
P0.31
16
P1.16/TRACEPKT0
3.3V 12
10

11

12

13

10

11

12

13

10

11

12

13
3

6
P1.17/TRACEPKT1
49 8
VBAT P1.18/TRACEPKT2
P1.19/TRACEPKT3
4 U2 U3 U4
63 48 74HCT164 74HCT164 74HCT
VREF P1.20/TRACESYNC
1D

1D

1D

7 44
V3A P1.21/PIPESTAT0
SRG8

SRG8

SRG8

51 40
V3 P1.22/PIPESTAT1
43 36
V3 P1.23/PIPESTAT2
C1/->

C1/->

C1/->

23 32
V3 P1.24/TRACECLK
28
&

&

&

P1.25/EXTIN0
R

59 24
VSSA P1.26/RTCK
50 64 3.3V U3(MR) U4(MR)
VSS P1.27/TDO
8

1
2

1
2

1
2

42 60
9

VSS P1.28/TDI
25 56
VSS P1.29/TCK
18 52
VSS P1.30/TMS
6 20
VSS P1.31/TRST
LPC2138

 
Figura 17 – Acionamento de três displays de 7 segmentos com shift register.

Exercício 03: Comente sobre as vantagens e desvantagens de se utilizar o


circuito exibido na Figura 10.

Página 35
 
1° Edição 03/2011 

Teclado Matricial

Crie uma rotina para varrer o teclado matricial 4x4 apresentado na Figura
11. Toda vez que uma tecla numérica do teclado for pressionada, o número
correspondente deve ser exibido no display.
Os diodos D2, D3, D4 e D5 são utilizados em série com as linhas do
teclado para proporcionar proteção para os pinos do microcontrolador,
impedindo que qualquer falha de configuração nos pinos venha a danificá-los.
Por exemplo, se os pinos P1.16 e P1.28 forem configurados como saída e um
deles assumir o nível lógico alto e o outro baixo, quando a tecla 7 for
pressionada, ocorrerá um curto circuito nos pinos que poderá resultar em
danos permanentes de hardware. Os diodos utilizados neste tipo de aplicação
são, em geral, diodos de sinal do tipo 1N914 ou 1N4148.
3.3V
C1

22pF
X1 U1 U2:B
C2 CRYSTAL
62 19 3 4
R7
FREQ=12MHz XTAL1 P0.0/TxD0/PWM1
61 21
XTAL2 P0.1/RxD0/PWM3/EINT0 300
22
P0.2/SCL0/CAP0.0 74HCT04
22pF 3
RTXC1 P0.3/SDA0/MAT0..0/EINT1
26 U2:A
5 27 R3
3.3V D1 RTXC2 P0.4/SCK0/CAP0.1/AD0.6
29 1 2
P0.5/MISO0/MAT0.1/AD0.7
57 30
RST P0.6/MOSI0/CAP0.2/AD1.0 300
31
P0.7/SSEL0/PWM2/EINT2 74HCT04
DIODE
P0.8/TxD1/PWM4/AD1.1
33 U2:C
34 R4
R1 C3 P0.9/RxD1/PWM6/EINT3
35 5 6
P0.10/RTS1/CAP1.0/AD1.2
37
P0.11/CTS1/CAP1.1/SCL1 300
47k P0.12/DSR1/MAT1.0/AD1.3
38 U2:D
74HCT04
100pF 39 R5
P0.13/DTR1/MAT1.1/AD1.4
41 13 12
P0.14/DCD1/EINT1/SDA1
45
P0.15/RI1/EINT2/AD1.5 300
74HCT04
P0.16/EINT0/MAT0.2/CAP0.2
46 R2 U2:E
47 R6
P0.17/CAP1.2/SCK1/MAT1.2
53 11 10
P0.18/CAP1.3/MISO1/MAT1.3 2k2
54 3.3V
P0.19/MAT1.2/MOSI1/CAP1.2 300
P0.20/MAT1.3/SSEL1/EINT3
55 U2:F
74HCT04
1 R8
P0.21/PWM5/AD1.6/CAP1.3
2 9 8
P0.22/AD1.7/CAP0.0/MAT0.0
58
P0.23 300
74HCT04
P0.25/AD0.4/AOUT
9 U3:A
10 R9
P0.26/AD0.5
11 1 2
P0.27/AD0.0/CAP0.1/MAT0.1
13 300
P0.28/AD0.1/CAP0.2/MAT0.2
14
P0.29/AD0.2/CAP0.3/MAT0.3 74HCT04
15
P0.30/AD0.3/EINT3/CAP0.0
17
P0.31 D2
3.3V
P1.16/TRACEPKT0
P1.17/TRACEPKT1
16
12
A 7 8 9
49 8
VBAT P1.18/TRACEPKT2
4 D3
P1.19/TRACEPKT3
63
7
VREF
V3A
P1.20/TRACESYNC
P1.21/PIPESTAT0
48
44
B 4 5 6
51 40 3.3V
43
V3 P1.22/PIPESTAT1
36 D4
V3 P1.23/PIPESTAT2
23
V3 P1.24/TRACECLK
P1.25/EXTIN0
32
28
C 1 2 3
59 24
50
VSSA P1.26/RTCK
64 D5
+
VSS P1.27/TDO
42
25
VSS
VSS
P1.28/TDI
P1.29/TCK
60
56 R10
10k
R11
10k
R12
10k
R13
10k
D 0 =
18 52
VSS P1.30/TMS
1

6 20
VSS P1.31/TRST
LPC2138

Figura 18 – Teclado Matricial

Resolução: Os pinos P1.16, P1.17, P1.18 e P1.19 devem ser configurados


como saída e os pinos P1.28, P1.29, P1.30 e P1.31 como entrada. Toda vez
que o pino P1.16 assumir nível lógico baixo e a tecla 7 for pressionada, o pino
P1.28 também assumirá nível lógico baixo, pois o pino P1.16, configurado
como saída, irá drenar toda corrente fornecida pelo resistor de pull-up R10.

Página 36
 
1° Edição 03/2011 

Programa
#include "LPC214x.h"
#include "cpu_init.h"
#include <stdint.h> // Biblioteca que define o tamanho exato do tipo primitivo

/*------------------------------------------------------------------------------------*/
/* DEFINITIONS AND MACROS */
/*------------------------------------------------------------------------------------*/
#define LINHA_A (1<<16)
#define LINHA_B (1<<17)
#define LINHA_C (1<<18)
#define LINHA_D (1<<19)

#define COLUNA_1 (1<<28)


#define COLUNA_2 (1<<29)
#define COLUNA_3 (1<<30)

#define seg_a (1<<0) // Segmento a


#define seg_b (1<<1) // Segmento b
#define seg_c (1<<2) // Segmento c
#define seg_d (1<<3) // Segmento d
#define seg_e (1<<4) // Segmento e
#define seg_f (1<<5) // Segmento f
#define seg_g (1<<6) // Segmento g

//--------------------------------------------------------------------------------------
//--- Rotina que converte um número decimal para 7 Segmentos ---------------------------
void Decimal_to_7Segment(uint8_t i){

IOCLR0 |= 0xFFFFFFFF; // Força nível baixo em todos os pinos do PORT0

switch (i)
{
case 0: IOSET0 |= seg_a|seg_b|seg_c|seg_d|seg_e|seg_f; break; // 0 no display
case 1: IOSET0 |= seg_b|seg_c; break; // 1 no display
case 2: IOSET0 |= seg_a|seg_b|seg_d|seg_e|seg_g; break; // 2 no display
case 3: IOSET0 |= seg_a|seg_b|seg_c|seg_d|seg_g; break; // 3 no display
case 4: IOSET0 |= seg_b|seg_c|seg_f|seg_g; break; // 4 no display
case 5: IOSET0 |= seg_a|seg_c|seg_d|seg_f|seg_g; break; // 5 no display
case 6: IOSET0 |= seg_a|seg_c|seg_d|seg_f|seg_e|seg_g; break; // 6 no display
case 7: IOSET0 |= seg_a|seg_b|seg_c; break; // 7 no display
case 8: IOSET0 |= seg_a|seg_b|seg_c|seg_d|seg_e|seg_f|seg_g; break; //8 no display
case 9: IOSET0 |= seg_a|seg_b|seg_c|seg_f|seg_g; break; // Escreve 9 no display
default: break; // Escreve 0 no display
}
}

//--------------------------------------------------------------------------------------
//--- Rotina que varre o teclado -------------------------------------------------------
uint8_t Varre_Teclado(void){

uint8_t i = 10; // Se i permanecer igual a 10 significa que nenhuma tecla foi


pressionada
IOCLR1 |= LINHA_A; // Nível baixo na Linha A
if (!(IOPIN1 & COLUNA_1)){
i = 7;
while (!(IOPIN1 & COLUNA_1)); // Aguarda soltar botão
}
if (!(IOPIN1 & COLUNA_2)){
i = 8;
while (!(IOPIN1 & COLUNA_2)); // Aguarda soltar botão
}
if (!(IOPIN1 & COLUNA_3)){
i = 9;
while (!(IOPIN1 & COLUNA_3)); // Aguarda soltar botão
}
IOSET1 |= LINHA_A; // Nível alto na Linha A
IOCLR1 |= LINHA_B; // Nível baixo na Linha B
if (!(IOPIN1 & COLUNA_1)){
i = 4;
while (!(IOPIN1 & COLUNA_1)); // Aguarda soltar botão
}
if (!(IOPIN1 & COLUNA_2)){
i = 5;
while (!(IOPIN1 & COLUNA_2)); // Aguarda soltar botão
}

Página 37
 
1° Edição 03/2011 
if (!(IOPIN1 & COLUNA_3)){
i = 6;
while (!(IOPIN1 & COLUNA_3)){ asm volatile ("NOP");} // Aguarda soltar botão
}
IOSET1 |= LINHA_B; // Nível alto na Linha B
IOCLR1 |= LINHA_C; // Nível baixo na Linha C
if (!(IOPIN1 & COLUNA_1)){
i = 1;
while (!(IOPIN1 & COLUNA_1)); // Aguarda soltar botão
}
if (!(IOPIN1 & COLUNA_2)){
i = 2;
while (!(IOPIN1 & COLUNA_2)); // Aguarda soltar botão
}
if (!(IOPIN1 & COLUNA_3)){
i = 3;
while (!(IOPIN1 & COLUNA_3)); // Aguarda soltar botão
}
IOSET1 |= LINHA_C; // Nível baixo na Linha A
IOCLR1 |= LINHA_D; // Nível baixo na Linha A
if (!(IOPIN1 & COLUNA_2)){
i = 0;
while (!(IOPIN1 & COLUNA_2)); // Aguarda soltar botão
}
IOSET1 |= LINHA_D; // Nível alto na Linha D

return(i);
}
//--------------------------------------------------------------------------------------
//--- Rotina Principal -----------------------------------------------------------------
int main (void)
{
cpu_init(); // Chama a rotina de inicialização da CPU

PINSEL0 |= 0x00000000; // Configura PORT0 como entrada/saída (I/O).


PINSEL1 |= 0x00000000; // Configura PORT0 como entrada/saída (I/O).

IODIR0 |= seg_a|seg_b|seg_c|seg_d|seg_e|seg_f|seg_g; // Configura os pinos que


acionam os segmentos como saída
IODIR1 |= LINHA_A|LINHA_B|LINHA_C|LINHA_D; // Configura os pinos que acionam as
linhas como saída

IOCLR0 |= 0xFFFFFFFF; // Força todos os pinos do PORT0 para nível baixo


IOSET0 |= seg_g; // Liga o segmento g

uint8_t k = 10; // Cria uma variável de 8bit sem sinal (0 a 255)

while(1){
k = Varre_Teclado(); // Varre o teclado
if (k != 10){
Decimal_to_7Segment(k);
}
}
return(0);
}

Exercício Proposto

Exercício 01: Crie um programa para varrer o teclado e acionar três displays
de 7 segmentos, conforme mostrado na Figura 12. Quando uma tecla for
pressionada, o número, correspondente a tecla pressionada, deverá ser exibido
no display da unidade e o número que estava sendo exibido na unidade deverá
ser deslocado para o display da dezena. Do mesmo modo, o número que
estava sendo exibido no display da dezena deverá ser deslocado para o
display da centena.

Página 38
 
1° Edição 03/2011 

3.3V 3.3V 3.3V

X1 U1
CRYSTAL
REQ=12MHz 62 19
XTAL1 P0.0/TxD0/PWM1 300 300 300
61 21
XTAL2 P0.1/RxD0/PWM3/EINT0 300 300 300
22
P0.2/SCL0/CAP0.0 330 330 330
3 26
RTXC1 P0.3/SDA0/MAT0..0/EINT1 300 300 300
5 27
RTXC2 P0.4/SCK0/CAP0.1/AD0.6 300 300 300
29
P0.5/MISO0/MAT0.1/AD0.7 300 300 300
57 30
RST P0.6/MOSI0/CAP0.2/AD1.0 300 300 300
31
P0.7/SSEL0/PWM2/EINT2
33
P0.8/TxD1/PWM4/AD1.1
34
C3 P0.9/RxD1/PWM6/EINT3
35
P0.10/RTS1/CAP1.0/AD1.2
37
P0.11/CTS1/CAP1.1/SCL1
38
P0.12/DSR1/MAT1.0/AD1.3
100pF 39
P0.13/DTR1/MAT1.1/AD1.4
41
P0.14/DCD1/EINT1/SDA1
45
P0.15/RI1/EINT2/AD1.5

10

11

12

13

10

11

12

13

10

11

12

13
3

6
46
P0.16/EINT0/MAT0.2/CAP0.2
P0.17/CAP1.2/SCK1/MAT1.2
47 U2 U3 U4
53 74HCT164 74HCT164 74HCT
P0.18/CAP1.3/MISO1/MAT1.3

1D

1D

1D
54
P0.19/MAT1.2/MOSI1/CAP1.2

SRG8

SRG8

SRG8
55
P0.20/MAT1.3/SSEL1/EINT3
1
P0.21/PWM5/AD1.6/CAP1.3

C1/->

C1/->

C1/->
2
P0.22/AD1.7/CAP0.0/MAT0.0
58

&

&

&
P0.23

R
9 3.3V U3(MR) U4(MR)
P0.25/AD0.4/AOUT

1
2

1
2

1
2
10

9
P0.26/AD0.5
11
P0.27/AD0.0/CAP0.1/MAT0.1
13
P0.28/AD0.1/CAP0.2/MAT0.2
14
P0.29/AD0.2/CAP0.3/MAT0.3
15
P0.30/AD0.3/EINT3/CAP0.0
17
P0.31 D2
3.3V
P1.16/TRACEPKT0
P1.17/TRACEPKT1
16
12
A 7 8 9
49 8
VBAT P1.18/TRACEPKT2
4 D3
P1.19/TRACEPKT3
63
7
VREF
V3A
P1.20/TRACESYNC
P1.21/PIPESTAT0
48
44
B 4 5 6
51 40 3.3V
43
V3 P1.22/PIPESTAT1
36 D4
V3 P1.23/PIPESTAT2
23
V3 P1.24/TRACECLK
P1.25/EXTIN0
32
28
C 1 2 3
59 24
50
VSSA P1.26/RTCK
64 D5
+
VSS P1.27/TDO
42
25
VSS
VSS
P1.28/TDI
P1.29/TCK
60
56 R2
10k
R3
10k
R4
10k
R5
10k
D 0 =
18 52
VSS P1.30/TMS

4
6 20
VSS P1.31/TRST
LPC2138

Figura 19 – Teclado Matricial e três displays acionados por shift register.

Display de Cristal Líquido

Utilize o módulo LCD para acionar um display de cristal líquido


alfanumérico de 2x16 de acordo com as especificações da Tabela 09 e do
diagrama esquemático mostrado na Figura 13. A primeira linha do display
deverá exibir “SIST. EMBARCADOS” e a segunda linha a palavra
“Contador:” seguida de uma variável decimal que será incrementada toda vez
que o push botton conectado ao pino P1.31 do LPC2148 for pressionado.

Tabela 09 – Conexão dos pinos do LPC2148 com os pinos do LCD

Pino do Pino do LCD


LPC2148
P0.10 D4
P0.11 D5
P0.12 D6
P0.13 D7
P0.22 RS
P0.28 RW
P0.29 F

Página 39
 
1° Edição 03/2011 
C1

22pF LCD
X1 U1
LM016L
CRYSTAL
C2 FREQ=12MHz 62 19
XTAL1 P0.0/TxD0/PWM1
61 21
XTAL2 P0.1/RxD0/PWM3/EINT0
22
P0.2/SCL0/CAP0.0
22pF 3 26
RTXC1 P0.3/SDA0/MAT0..0/EINT1
5 27
3.3V D1 RTXC2 P0.4/SCK0/CAP0.1/AD0.6
29
P0.5/MISO0/MAT0.1/AD0.7

VDD
VSS

VEE
57 30

RW
RS

D0
D1
D2
D3
D4
D5
D6
D7
RST P0.6/MOSI0/CAP0.2/AD1.0

E
31
P0.7/SSEL0/PWM2/EINT2
DIODE 33
P0.8/TxD1/PWM4/AD1.1

1
2
3

4
5
6

7
8
9
10
11
12
13
14
34
R1 C3 P0.9/RxD1/PWM6/EINT3
35
P0.10/RTS1/CAP1.0/AD1.2
37
P0.11/CTS1/CAP1.1/SCL1
38
47k P0.12/DSR1/MAT1.0/AD1.3
100pF 39
P0.13/DTR1/MAT1.1/AD1.4
41
P0.14/DCD1/EINT1/SDA1
45
P0.15/RI1/EINT2/AD1.5
46
P0.16/EINT0/MAT0.2/CAP0.2
47
P0.17/CAP1.2/SCK1/MAT1.2
53
P0.18/CAP1.3/MISO1/MAT1.3
54
P0.19/MAT1.2/MOSI1/CAP1.2
55
P0.20/MAT1.3/SSEL1/EINT3
1
P0.21/PWM5/AD1.6/CAP1.3
2
P0.22/AD1.7/CAP0.0/MAT0.0
58
P0.23
9
P0.25/AD0.4/AOUT
10
P0.26/AD0.5
11
P0.27/AD0.0/CAP0.1/MAT0.1
13
P0.28/AD0.1/CAP0.2/MAT0.2
14
P0.29/AD0.2/CAP0.3/MAT0.3
15
P0.30/AD0.3/EINT3/CAP0.0
17
P0.31
16
P1.16/TRACEPKT0
3.3V 12
P1.17/TRACEPKT1
49 8
VBAT P1.18/TRACEPKT2
4
P1.19/TRACEPKT3
63 48
VREF P1.20/TRACESYNC
7 44
V3A P1.21/PIPESTAT0
51 40
V3 P1.22/PIPESTAT1
43 36
V3 P1.23/PIPESTAT2
23 32
V3 P1.24/TRACECLK
28
P1.25/EXTIN0
59 24
VSSA P1.26/RTCK
50 64
VSS P1.27/TDO
42 60
VSS P1.28/TDI
25 56
VSS P1.29/TCK
18 52
VSS P1.30/TMS
6 20

3.3V
VSS P1.31/TRST
LPC2138 R25
2k2

Figura 20 – Acionando um display de cristal líquido alfanumérico de 2x16.

Resolução:

Passo 1: Crie um novo projeto denominado LCD.

Passo 2: Copiar o conteúdo do arquivo LCD.txt, localizado em


...\Aulas\LCD\Programas, e colar no arquivo main.c criado no projeto LCD.

Passo 3: Importar para o projeto LCD os arquivos delay_loop.c,


delay_loop.h, iprintf.c, iprintf.h, lcd.c e lcd.h, localizados na pasta
_Módulos do ARM LPC2148 em C:\ARM\PROGRAMAS\LPC2148\
As instruções sobre como importar arquivos estão no Tutorial - Como
Criar um Projeto em C para ARM.pdf, disponível no arquivo Aulas.zip

Passo 4: Compilar o programa.

Passo 5: Transferir o arquivo binário gerado para o microcontrolador ARM.

Página 40
 
1° Edição 03/2011 

Programa
#include "LPC214x.h"
#include "cpu_init.h"
#include "iprintf.h" // Separa uma string em caracteres para poder enviá-los serialmente
#include "lcd.h" // Módulo com rotinas de inicialização e operação do LCD
#include "stdint.h" // Standard C data types

#define button (1<<31) // Botão

int main(void)
{
cpu_init(); // Chama a rotina de inicialização da CPU
uint16_t i=0;

lcd_init(); // Rotina de inicialização do display

lcd_line1(); // Leva o cursor do display para o início da linha 1


lcd_string("SIST. EMBARCADOS"); // Escreve string na linha
lcd_line2(); // Leva o cursor do display para o início da linha 2
iprintf("Contador:%d",i); // Escreve string na linha

while(1){
if (!(IOPIN1 & button)) // Verifica se o Botão foi pressionado
{
i++;
while (!(IOPIN1 & button)); // Espera soltar botão
lcd_line2(); // Leva o cursor do display para o início da linha 2
iprintf("Contador:%d",i); // Escreve string na linha
}
}
return 0;
}

//------------------------------------------------------------------------------------
// EOF
//------------------------------------------------------------------------------------

Comentário sobre o Módulo LCD

A configuração dos pinos do microcontrolador utilizados para acionar as


linhas de controle e dados do LCD 2x16 é realizada na rotina lcd_init() que se
encontra no arquivo lcd.c.

//---------------------------------------------------------------------
void lcd_init(void)
{

// P0.10, P0.11, P0.12 e P0.13 como entrada/saída (2 bits de configuração cada pino)
PINSEL0 &= ~((1<<20)|(1<<21)|(1<<22)|(1<<23)|(1<<24)|(1<<25)|(1<<26)|(1<<27));

// P0.21, P0.22, P0.28 e P0.29 como entrada/saída (2 bits de configuração cada pino)
PINSEL1 &= ~((1<<10)|(1<<11)|(1<<12)|(1<<13)|(1<<24)|(1<<25)|(1<<26)|(1<<27));

//Configura E, RS, RW, D7, D6, D5 e D4 como saída


IODIR0 |= LCD_DATA_MASK|LCD_CONTROL_MASK|LUZ;

// Força nível lógico baixo nos pinos E, RS, RW, D7, D6, D5 e D4
IOCLR0 = LCD_DATA_MASK|LCD_CONTROL_MASK;

Os registradores PINSEL0 e PINSEL1 utilizam dois bits para configurar a


função de cada pino do microcontrolador, por isso, o número de cada pino não
coincide com a posição dos bits no registrador como ocorre com os
registradores IODIR, IOCLR e IOSET.

Página 41
 
1° Edição 03/2011 

As máscaras LCD_DATA_MASK, LCD_CONTROL_MASK e LUZ foram


definidas no trecho de código “DEFINITIONS AND MACROS”, como
apresentado a seguir:

/*------------------------------------------------------------------------------------*/
/* DEFINITIONS AND MACROS */
/*------------------------------------------------------------------------------------*/

#define E (1<<28) //!< Pino que liga/desliga o LCD, conectado no pino P0.20 do LPC2148
#define RS (1<<22) //!< Informa ao LCD se a informação é um comando ou dado, conectado
no pino P0.22 do LPC2148
#define RW (1<<29) //!< Pino que informa se a operação é de leitura ou escrita,
conectado no pino P0.29 do LPC2148 (0 = write to LCD module, 1 = read from LCD module)
#define busyflag (1<<13) //!< Verifica se o LCD está ocupado, isto é pronto para receber
o próximo comando.

#define DB4 (1<<10) //!< Pino de dado DB4 (LSB), conectado ao pino P0.10 do LPC2148
#define DB5 (1<<11) //!< Pino de dado DB5, conectado ao pino P0.11 do LPC2148
#define DB6 (1<<12) //!< Pino de dado DB6, conectado ao pino P0.12 do LPC2148
#define DB7 (1<<13) //!< Pino de dado DB4 (MSB), conectado ao pino P0.13 do LPC2148
#define LUZ (1<<21) //!< Liga/desliga a backlight do LCD, conectado no pino P0.21 do
LPC2148

#define LCD_DATA_MASK (DB4 | DB5 | DB6 | DB7)


#define LCD_CONTROL_MASK (E | RW | RS)

Exercício Proposto

Exercício 01: Faça um programa para somar, subtrair, multiplicar ou dividir


dois números decimais de apenas um dígito cada. O diagrama esquemático
que deverá ser utilizado é mostrado na Figura 16.

C1

22pF LCD
X1 LM016L
CRYSTAL
U1
C2 FREQ=12MHz 62 19
XTAL1 P0.0/TxD0/PWM1
61 21
XTAL2 P0.1/RxD0/PWM3/EINT0
22
P0.2/SCL0/CAP0.0
22pF 3 26
RTXC1 P0.3/SDA0/MAT0..0/EINT1
5 27
3.3V D1 RTXC2 P0.4/SCK0/CAP0.1/AD0.6
29
P0.5/MISO0/MAT0.1/AD0.7
VDD
VSS

VEE

57 30
RW
RS

D0
D1
D2
D3
D4
D5
D6
D7

RST P0.6/MOSI0/CAP0.2/AD1.0
E

31
P0.7/SSEL0/PWM2/EINT2
DIODE 33
P0.8/TxD1/PWM4/AD1.1
1
2
3

4
5
6

7
8
9
10
11
12
13
14

34
R1 C3 P0.9/RxD1/PWM6/EINT3
35
P0.10/RTS1/CAP1.0/AD1.2
37
P0.11/CTS1/CAP1.1/SCL1
38
47k P0.12/DSR1/MAT1.0/AD1.3
100pF 39
P0.13/DTR1/MAT1.1/AD1.4
41
P0.14/DCD1/EINT1/SDA1
45
P0.15/RI1/EINT2/AD1.5
46
P0.16/EINT0/MAT0.2/CAP0.2
47
P0.17/CAP1.2/SCK1/MAT1.2
53
P0.18/CAP1.3/MISO1/MAT1.3
54
P0.19/MAT1.2/MOSI1/CAP1.2
55
P0.20/MAT1.3/SSEL1/EINT3
1
P0.21/PWM5/AD1.6/CAP1.3
2
P0.22/AD1.7/CAP0.0/MAT0.0
58
P0.23
9
P0.25/AD0.4/AOUT
10
P0.26/AD0.5
11
P0.27/AD0.0/CAP0.1/MAT0.1
13
P0.28/AD0.1/CAP0.2/MAT0.2
14
P0.29/AD0.2/CAP0.3/MAT0.3
15
P0.30/AD0.3/EINT3/CAP0.0
17
P0.31 D2
3.3V
P1.16/TRACEPKT0
P1.17/TRACEPKT1
16
12
A 7 8 9
49 8
VBAT P1.18/TRACEPKT2
4 D3
P1.19/TRACEPKT3
63
7
VREF
V3A
P1.20/TRACESYNC
P1.21/PIPESTAT0
48
44
B 4 5 6
51 40 3.3V
43
V3 P1.22/PIPESTAT1
36 D4
V3 P1.23/PIPESTAT2
23
V3 P1.24/TRACECLK
P1.25/EXTIN0
32
28
C 1 2 3
59 24
50
VSSA P1.26/RTCK
64 D5
+
VSS P1.27/TDO
42
25
VSS
VSS
P1.28/TDI
P1.29/TCK
60
56 R12
10k
R13
10k
R14
10k
R15
10k
D 0 =
18 52
VSS P1.30/TMS
1

6 20
VSS P1.31/TRST
LPC2138

Figura 21 – Teclado e display.

Página 42
 
1° Edição 03/2011 

Interface de Comunicação SPI

O SPI é uma interface de comunicação síncrona que opera no modo full-


duplex. Esta interface é composta por 4 sinais, como descriminado abaixo:

 Sinais de dados: MOSI (Master data Output, Slave data Input) e MISO
(Master data Input, Slave data Output) são responsáveis pela
transferência de dados entre o master e o slave;

 Sinais de controle: SCLK (Serial Clock) e /SS (Slave Select).

Em modo “escravo”, o microcontrolador comporta-se como um


componente da rede, recebendo o sinal de relógio. Em modo “mestre”, o
microcontrolador gera um sinal de relógio e deve ter um pino de I/O para
habilitação de cada periférico.
A interface SPI é muito utilizada em conversor digital analógico, relógio de
tempo real, DIGIPOTs, mémorias flash, cartões SD/MMC, dentre outros
circuitos integrados.
A Figura 17 mostra o diagrama esquemático da ligação entre dois
dispositivos que utilizam a interface de comunicação SPI e a Figura 18 exibe o
diagrama esquemático de um dispositivo mestre comunicando com três
escravos. Os pinos SS1, SS2 e SS3 são utilizados para selecionar o dispositivo
escravo que se deseja comunicar.

SCLK
MOSI
Master MISO Slave
SCLK

Figura 22 - Comunicação entre o dispositivo mestre (máster) e o escravo (slave)


utilizando o protocolo SPI.

SCLK SCLK
MOSI MOSI
MISO MISO Slave 01
SPI Master
SS1 SS
SS2
SS3
SCLK
MOSI
MISO Slave 02
SS

SCLK
MOSI
MISO Slave 03
SS

Figura 23 - Comunicação entre um dispositivo mestre e três escravos utilizando a


interface de comunicação SPI.

Página 43
 
1° Edição 03/2011 

Operações “ATOMIC”

Uma operação é “ATOMIC” quando o processador utiliza apenas um ciclo


de instrução para executá-la. Deste modo, uma operação que é “ATOMIC” em
um microcontrolador com arquitetura de 16 bit não é “ATOMIC” em um
microcontrolador de 8 bit.
A operação não “ATOMIC” ocorre quando o processador tenta atualizar
uma variável maior do que o tamanho da palavra da arquitetura. Por exemplo,
durante a atualização de uma variável de 16 bit em um microcontrolador que
possui arquitetura de 8 bit, pode ocorrer perda de dados ou dados corrompidos
se o processador for interrompido durante o processo de atualização.
A Tabela 10 mostra um resumo das operações, de acordo com a
arquitetura, para o tipo inteiro definido pelo C99 Standard Data Types.

Tabela 10 – Operações Atômicas e Não Atômicas

OPERAÇÕES “ATOMIC”
TIPO µC de 8bit µC de 16bit µC de 32bit
int8_t
uint8_t
int16_t
uint16_t
int32_t
uint32_t
int64_t
uint64_t

 Um operação int8_t em uma arquitetura de 8 bit, ou 16 bit ou 32 bit


ou 64 bit é atômica e o mesmo ocorre com o uint8_t
 Um operação com um int16_t em uma arquitetura de 8 bit não é
atômica, porém em uma arquitetura de 16 bit ou 32 bit ou 64 bit ela
é atômica, o mesmo ocorre com o uint16_t
 Um operação com um int32_t em uma arquitetura de 8 bit ou 16 bit
não é atômica, porém em uma arquitetura de 32 bit ou 64 bit ela é
atômica, o mesmo ocorre com o uint64_t
 Um operação com um int64_t somente é atômica em uma
arquitetura de 64 bit e o mesmo ocorre com o tipo uint64_t

Exemplo 01: Vamos analisar o que ocorre ao se tentar atualizar uma


variável de 32 bit em um µC que possui arquitetura de 8 bit, como apresentado
na Figura 22.

Página 44
 
1° Edição 03/2011 

A Figura 24 ( a ) mostra o código assembly, correspondente a atualização


da variável NoAtomicOperation de 32 bit, realizada em um microcontrolador
PIC, modelo 16F877A, que possui arquitetura de 8 bit.

Figura 24 - Atualização da variável NoAtomicOperation de 32 bit.

A primeira instrução move o valor 0xAB para o registrador de trabalho W e


a segunda armazena o valor no endereço de memória 0x29
A terceira instrução move o valor 0xCD para o registrador de trabalho W e
a quarta armazena o valor no endereço de memória 0x28.
A quinta instrução move o valor 0xEF para o registrador de trabalho W e a
sexta armazena o valor no endereço de memória 0x27.
E, por fim, a sétima instrução move o valor 0x01 para o registrador de
trabalho W e a oitava armazena o valor no endereço de memória 0x26.
Portando, 8 instruções foram necessárias para atualizar o valor da variável
NoATomicOperation, o que caracteriza esta operação como sendo não
atômica.
Por outro lado, a Figura 22 (b) exibe o código assembly correspondente a
atualização da variável AtomicOperation de 32 bit, realizada em um uC ARM,
modelo LPC2148, que possui arquitetura de 32 bit.
A primeira instrução carrega o valor ABCDEF01 do endereço de memória
flash 340 no registrador r3 e a segunda instrução move o valor do registrador r3
para a posição de memória indicada pelo ponteiro de pilha sp.
Apenas uma instrução (str) foi necessária para atualizar a variável, o que
caracteriza esta operação como sendo atômica.

Exemplo 02: As informações em um protocolo MODBUS RTU são


transmitidas em frames que possuem um campo de endereço, um campo com
a função a ser executada, um campo de dados e, por fim, o do CRC.
Entre os frames é acrescentado um silêncio na linha maior ou igual a 3,5
vezes o tempo de transmissão de um caractere utilizado para indicar o fim ou
início de um frame.
Este exemplo analisa uma operação não atômica de uma variável
conhecida como “tick conter”, utilizada na temporização “time out” dos frames
em um protocolo MODBUS.

Página 45
 
1° Edição 03/2011 

Figura 25 - Atualização da variável de 32 bit não atômica, utilizada na temporização de


frames MODBUS, conhecida como “tick counter”.

Em aplicações que necessitam temporizar várias tarefas e que utilizam µC


que possuem pouco recurso de hardware um contador de tempo denominado
TickCounter é muito utilizado. Ele é atualizado toda vez que uma rotina de
interrupção de timer é chamada.
Supondo que a variável TickCounter é do tipo uint32_t, como mostra a
Figura 26, toda vez que a interrupção do timer ocorrer o valor desta variável
será incrementada.
Em outro trecho do código ocorre a verificação do “time out” efetuado pelo
cálculo do tempo decorrido entre o tempo atual e o de transmissão do último
byte.
No exemplo da Figura 26, o valor do TickCounter no instante de tempo em
que o último byte foi transmitido era de 0x000000AB e, depois de algum tempo,
durante a verificação de “time out”, exatamente no instante de atualização da
variável TempoAtual, ocorreu uma interrupção.
Durante a atualização da variável TempoAtual, a instrução assembly
MOVF 29, W move o valor do byte mais significativo da variável TickCounter
para o registrador de trabalho e a instrução MOVWF 31 move o valor do
registrador de trabalho W para a posição de memória do byte mais significativo
da variável TempoAtual.
Na sequência, a instrução assembly MOVF 28, W move o valor do
segundo byte mais significativo da variável TickCounter para o registrador de
trabalho e a instrução MOVWF 30 move o valor do registrador de trabalho W
para a posição de memória do segundo byte mais significativo da variável
TempoAtual.

Página 46
 
1° Edição 03/2011 

Neste instante, ocorre uma interrupção que atualiza o valor da variável


TickCounter fazendo que o valor seja atualizado para 0x00010000. Retornando
da interrupção a atualização da variável TempoAtual prossegue.
A próxima instrução assembly MOVF 27, W move o valor do segundo byte
menos significativo da variável TickCounter para o registrador de trabalho e a
instrução MOVWF 2F move o valor do registrador de trabalho W para a posição
de memória do segundo byte menos significativo da variável TempoAtual.
A próxima instrução assembly MOVF 26, W move o valor do byte menos
significativo da variável TickCounter para o registrador de trabalho e a instrução
MOVWF 2E move o valor do registrador de trabalho W para a posição de
memória do byte menos significativo da variável TempoAtual.
O valor esperado para a variável TempoAtual é 0x0000FFFF, contudo, foi
atualizada com o valor 0x00000000. Após o calculo do tempo decorrido o valor
obtido é 0xFFFFFF55 sendo que o valor esperado é 0x000000AB. O valor
0xFFFFFF55 indica um fim de frame enquanto o valor correto 0x0000FF54
indica que ele ainda não terminou.

Figura 26 - Operação não atômica de uma variável conhecida como “tick conter”,
utilizada na temporização “time out” dos frames em um protocolo MODBUS.

A Figura 27 apresenta a mesma situação realizada em um µC de 32 bit.


Supondo que a variável TickCounter é do tipo uint32_t, toda vez que a
interrupção do timer ocorrer o valor da variável TickCounter será incrementada.

Página 47
 
1° Edição 03/2011 

No exemplo mostrado na Figura 27, o valor do TickCounter no instante de


tempo em que o último byte foi transmitido era de 0x000000AB e, depois de
algum tempo, durante a verificação de “time out”, exatamente no instante de
atualização da variável TempoAtual, ocorreu uma interrupção.
Durante a atualização da variável TempoAtual, a instrução ldr r3, [sp]
carrega o valor da variável TickCounter no registrador r3.
Neste instante, ocorre uma interrupção que atualiza o valor da variável
TickCounter, fazendo com que o valor seja atualizado para 0x00010000 e,
após o retorno da interrupção, a atualização da variável TempoAtual prossegue
com a instrução assembly str r3,[sp, #4] que move o valor do registrador r3
para a posição de memória da variável TempoAtual.
Neste exemplo, o valor da variável foi atualizada com apenas uma
instrução (str), o que caracteriza uma operação atômica.
O valor esperado para a variável TempoAtual era 0x0000FFFF e foi
atualizado com esse valor.
Após o calculo do tempo decorrido, o valor obtido é 0x0000FF54 que
corresponde ao valor esperado.

Figura 27 - Operação atômica de uma variável conhecida como “tick conter”, utilizada na
temporização “time out” dos frames em um protocolo MODBUS.

Página 48
 
1° Edição 03/2011 

“STRUCTURE PADDING”
E
SERIALIZAÇÃO

“Structure Padding”’ está relacionado com alinhamento de memória,


portanto, entender o que é alinhamento de memória é o primeiro passo.
A maior parte dos processadores prefere, ou até mesmo necessita, que o
acesso a memória seja alinhado. Isso significa que quando o processador
acessa um bloco de n bytes na memória, o endereço inicial deve ser um
múltiplo de n.
Por exemplo, uma variável de quatro bytes deve estar limitada em uma
fronteira de quatro bytes (o endereço deve ser múltiplo de 4); uma variável de
dois bytes deve estar limitada em dois bytes (o endereço deve ser múltiplo de
2); e assim sucessivamente.
O alinhamento é importante porque permite a utilização do tipo int com o
máximo de eficiência.
Entretanto, os processadores frequentemente possuem diferentes
requerimentos para o acesso a memória. Por exemplo, a arquitetura Intel x86
permite o acesso desalinhado de memória, mas, neste caso, impõe uma queda
significativa no desempenho.
Um acesso desalinhado em um processador RISC resultará em uma falta
no processador causando uma falha. Se a falha for tratada por uma interrupção
síncrona “trap”, o acesso desalinhado deverá ocorrer via software e será lento.
Em um microcontrolador ARM um acesso de memória desalinhado
resultará em um dado incorreto e, provavelmente, indesejável. Algumas
versões de ARM com unidades de gerenciamento de memória podem realizar
verificação de alinhamento, mas essa característica não é padronizada em
todas as famílias.
A Figura 28 mostra uma palavra de 4 Bytes e seus submúltiplos: meia
palavra e byte.

Figura 28 – Palavra de 4 Bytes e seus submúltiplos: meia palavra e byte.

A Figura 29 mostra o diagrama esquemático da memória do µC ARM de 32


bit modelo LPC 2148.

Página 49
 
1° Edição 03/2011 

Neste exemplo, o endereço de Uma Palavra deve possuir o final 0, 4, 8 ou


C. Isto é, deve ser múltiplo de 4 bytes. Consequentemente, o endereço de
uma Meia Palavra deve terminar com 0, 2, 4, 6, 8, A, C ou E. Isto é, deve ser
múltiplo de 2 bytes. Por fim, o endereço de Byte pode assumir qualquer
valor.

Figura 29 – Uma Palavra, Meia Palavra e Um Byte.

A Figura 30 mostra no diagrama esquemático da memória de um ARM o


alinhamento do tipo inteiro.
A variável Alinhado1 é do tipo inteiro sem sinal de 32 bit, foi inicializada e
declarada com o valor 0xABCEDF01, está representada em verde no diagrama
de memória e possui endereço com final 8, o que indica que está alinhada.
O ARM7 é “little endian”, por isso, o byte menos significativo da variável
está alocado nos endereços menos significativo da memória.
As variáveis Alinhado2 e 3 são do tipo inteiro sem sinal de 16 bit, foram
declaradas e inicializadas com os valores 0x2345 e 0x6789, respectivamente.
Elas estão representadas em amarelo no diagrama da memória, possuem o
endereço com final 4 e 6 (valores múltiplo de 2), o que indica que estão
alinhadas.
As variáveis Alinhado 5, 6, 7 e 8 são do tipo inteiro sem sinal de 8 bit,
foram declaradas e inicializadas com os valores 0x12, 0x34, 0x56, 0x78 e
0x9A, respectivamente, estão representadas em laranja e o endereço delas
pode assumir qualquer valor.
As variáveis foram declaradas e inicializadas na seguinte ordem: primeiro a
variável de 32bit, depois as duas de 16 bit e, por fim, as de 8 bit.

Página 50
 
1° Edição 03/2011 

Figura 30 – Uma Palavra, Meia Meia Palavra e Um Byte alinhados.

A Figura 30 mostra como ficou o mapa de memória, após a inicialização


das variáveis em uma sequência diferente da apresentada no exemplo anterior.

 
Figura 31 – Exemplo de “Structure Padding”.

Página 51
 
1° Edição 03/2011 

Neste exemplo, para alinhar a memória, o compilador acrescentou bytes


sem propósito de modo a manter o endereço das variáveis de 16 bit múltiplos
de 2, esse processo é conhecido como “structure padding”.
Nota-se que o compilador não utilizou a memória de modo eficiente, pois
poderia ter alocado duas variáveis de 8 bit nos endereços com final 452 e 456.
A Figura 32 mostra um exemplo de variáveis desalinhadas na memória. A
variável do tipo inteiro de 32 bit encontra-se desalinhada, pois possui endereço
com final D que não é um múltiplo de 4. O mesmo está ocorrendo com as
variáveis de 16 bit, pois possuem endereço com final 1 e 7, que não são
múltiplos 2.
A escrita e acesso desalinhado de memória geram operações não
atômicas que diminuem o desempenho da CPU e reduzem o espaço de
memória de programa. Este tipo de operação de acesso e escrita de memória
podem ocasionar o travamento ou interrupção da CPU se não for tratado
corretamente.

Figura 32 – Exemplo de variáveis desalinhadas na memória.

O exemplo a seguir mostra como o “Padding” pode afetar a comunicação


MODBUS.

Exemplo: Uma rede MODBUS é composta por um dispositivo Mestre, um meio


físico e um ou mais escravos. A Figura 33 mostra uma rede composta por um
Mestre (CLP), um escreva (o adaptador) e um meio físico RS485.

Figura 33 – Rede MODBUS.

Página 52
 
1° Edição 03/2011 

Os dados que percorrem o meio físico RS485 são organizados em frames


MODBUS, como mostra a Figura 34. Eles possuem um campo de endereço,
que especifica a operação a ser realizada “function code”, um campo de dados
e um para o CRC.

Figura 34 – Frame MODBUS.

De acordo com as especificações MODBUS, o frame que realiza uma


requisição é formado por 1 byte que define o “function code”, dois bytes que
especificam o endereço do primeiro registrador de 16 bit a ser lido e, por fim, a
quantidade de registradores a ser lido.
De modo semelhante, o frame da resposta é composto por um byte que
contém o “function code”, um byte que especifica o número de registradores
lidos e em seguida os valores dos registradores lidos.
A Figura 35 apresenta um exemplo de frame MODBUS que realiza a
requisição da leitura de um sensor e outro frame que fornece a resposta.

 
Figura 35 – Exemplo de Requisição e Resposta de Frame MODBUS.

Observa-se que o frame MODBUS é estruturado em vários campos, e,


portanto, poderia ser implementado na forma de estrutura na Linguagem de
Programação C.

Página 53
 
1° Edição 03/2011 

A estrutura facilitaria a manipulação dos campos, a compreensão do


código e a serialização do frame. Para se serializar o frame, bastaria fornecer
para a rotina de serialização o endereço do primeiro elemento da estrutura,
enviar para a serial o primeiro byte, incrementar o endereço, enviar para a
porta serial o segundo elemento e assim sucessivamente até o último byte do
frame.
Neste exemplo, o valor do registrador, no campo de dados da resposta,
não pode ser estimado porque depende do valor do nível de tensão na entrada
do A/D, da resolução do A/D e da tensão de referência no A/D.
O código C que implementa a estrutura do frame, de acordo com a Figura
36, pode ser dividido em:

1) Estrutura do cabeçalho, contendo o endereço do escravo e o


“function code”;
2) Estrutura do “function code” que lê registradores: de acordo com
especificações MODBUS, a estrutura do “function code” para que o
Mestre solicite a leitura de registradores para o Escravo é composta
pelo endereço do primeiro registrador a ser lido seguido do número
de registradores a serem lidos. Por outro lado, a estrutura do
“function code” para que o Escravo responda solicitação de leitura
do Mestre é composta pelo número de registradores lidos seguido
dos valores dos registradores lidos;
3) Declaração da estrutura do Frame MODBUS com acréscimo do
campo CRC.

Figura 36 – Exemplo de Requisição e Resposta de Frame MODBUS.

Página 54
 
1° Edição 03/2011 

Por fim, a função utilizada na serialização do frame recebe como


parâmetro o endereço do frame e o tamanho dele e envia os bytes para a porta
serial.
A Figura 37 mostra o trecho da memória de um microcontrolador LPC2148
utilizado para armazenar os valores de um frame MODBUS.
Em roxo, no endereço com final 658 está o endereço do escravo. Em
verde, no endereço com final 659 está o “function code”. Em amarelo, no
endereço com final 65C está o endereço do primeiro registrador a ser lido.
Novamente, em amarelo, no endereço com final 65E está o número de
registradores. Em ciano, no endereço com final 660 está armazenado o CRC.
Em vermelho, dentro da estrutura, temos um elemento estranho. O famoso
“PADDING”, cujo valor não pode ser previsto, porque é um valor qualquer
presente na memória.
Conforme já comentado anteriormente, o “PADDING” ocorre porque a
maior parte dos processadores prefere, ou até mesmo necessita, que o acesso
a memória seja alinhado.

Figura 37 - Trecho de memória que armazena um Frame MODBUS em um LPC2148.

A Figura 38 mostra uma rede MODBUS RTU que utiliza o meio físico
RS485. Esta figura também mostra o código da estrutura do ModFrame e o
código da rotina que envia os bytes do Frame MODBUS pela interface serial.
A presença do “Padding”, neste exemplo, traz as seguintes desvantagens:

 Diminui a portabilidade;

Página 55
 
1° Edição 03/2011 

 eleva custo de desenvolvimento de novos produtos (plataformas


diferentes);
 gasto maior com manutenção de código (plataformas diferentes);
 Diminui o desempenho da rede;
 Necessita de microcontroladores mais caros (+memória);
 Localização e posição do “padding” depende do grau de otimização e do
compilador (GCC, Workbench, Vision...).

Figura 38 – Desvantagens do “Structure Padding” na transmissão serial.

Uma solução para o problema é copiar o conteúdo dos campos do frame


para um buffer e em seguida enviar para a porta serial.

Página 56
 
1° Edição 03/2011 

BOAS PRÁTICAS EM PROGRAMAÇÃO


Boas práticas em programação visam tornar o código mais legível, facilitar
a operação de debug e favorecer a reutilização do código em outras
aplicações.
Vale salientar que se o código for gerado para fins comerciais, os nomes
de variáveis, funções, modificadores de tipo e comentários devem ser em
inglês.
Na sequência são apresentadas algumas das práticas de programação
mais comuns:

Escolher Bons Nomes

Quando estiver escolhendo um nome para uma variável ou função, evite


nomes ambíguos, porque é melhor escolher nomes que descrevam o propósito
da variável ou da função.
Nomes abreviados também devem ser evitados, pois outros
programadores podem não entender o que a abreviação significa.
Um bom nome é aquele que descreve da melhor maneira possível, em
apenas algumas palavras, a intenção da variável ou da função.
Por fim, os nomes das funções devem começar com letra maiúscula.

Variáveis e Modificadores de Tipo

Consiste em boa prática em programação especificar o tipo da variável no


inicio do nome de cada variável, como sugerido na Tabela 11.

Tabela 11 – Identificadores sugeridos para especificar o tipo de variável

Tipo de Identificador
Escala de Valores
Dados Sugerido
char Especificação do Compilador c
char8 uint8_t c8
int8_t -128 a 127 i8
uint8_t 0 a 255 u8
int16_t -32768 a 32767 i16
uint16_t 0 a 65535 u16
int32_t –2.147.483.648 a 2.147.438.647 i32
uint32_t 0 a 4.294.967.295 u32
-9.223.372.036.854.775.808 a
int64_t +9.223.372.036.854.775.807 i64
uint64_t 0 a +18.446.744.073.709.551.615 u64
float32 ver IEEE 754 (float) f32
float64 ver IEEE 754 (double) f64

Página 57
 
1° Edição 03/2011 

boolean FALSE e TRUE b


void --- v

Deste modo, se for necessário declarar uma variável do tipo uint8_t,


chamada VelocidadeDoCarro, a variável deverá ser declarada da seguinte
maneira:

uint8_t u8_VelocidadeDoCarro = 0;

Uma variável chamada TemperaturaDoAr do tipo int16_t será declarada


do seguinte modo:

uint16_t u16_AirTemperature = 0;

OBS: É muito importante ao se declarar uma variável inicializá-la com


algum valor.

Seguindo a mesma linha de raciocínio, as variáveis do tipo ponteiro, os


vetores, as estruturas e os enum também devem ser especificados quando
forem declarados. A Tabela 12 apresenta os identificadores sugeridos.

Tabela 12 – Identificadores sugeridos para os modificadores de tipo

Identificador
Modificador de Tipo
Sugerido
Arrays a
Enumeração ( enum ) en
Pointers p
Structures ( struct ) st

Exemplo:

uint16_t VelocidadeDoMercedes = 258;


uint16_t VelocidadeDaFerrari = 350;
uint16_t VelocidadeDoBugatti = 407;

uint16_t *p_VelocidadeDoCarro;
p_VelocidadeDoCarro =&VelocidadeDoMercedes;
 

A linha de código ( uint16_t *p_CarSpeed; ) declara um ponteiro do tipo


inteiro 16 bits cujo nome inicia com ( p ) conforme sugerido na Tabela 12. Este
procedimento facilita o reconhecimento de uma variável do tipo ponteiro em um
programa que possua muitas linhas de código, facilita a compreensão do
código por outros programadores, favorece a reutilização de código, dentre
outras vantagens.

Página 58
 
1° Edição 03/2011 

REGRAS MISRA
Evitar o uso de comentários do tipo /* xxx */ dentro do corpo de funções.
Exemplo:

Correto Evitar

if (u8_Variavel1 != u8_Variavel2) if (u8_Variavel1 != u8_Variavel2)


{ {
u16_pressao = 10000; // valor máximo u16_pressao = 10000; /* valor máximo */
.. ..
} }

/*******************************
* Inicializar_CPU void Inicializar_CPU(void)
********************************/ {
/* Inicializar CPU */
void Inicializar_CPU(void) ..
{ }
..
}

Constantes

Aconselha-se a não utilizar zero antes de constantes do tipo inteiro, porque


ela será tratada pelo compilador C como um número octal.

Por exemplo:

ToneladasDeLaranja = 120;
ToneladasDePera = 071;

Neste exemplo, observa-se que a variável ToneladasDeLaranja assumiu


o valor 120 em decimal, contudo, a variável ToneladasDePera assumiu o valor
57 em decimal e não 71 como se pretendia.

Chaves
 

Chaves não podem ser evitadas, elas devem ser alocadas em uma linha
própria, exceto na inicialização de array, quando vier após while em laços do
tipo do-while ou após definição de nomes de tipos - Typedef.

Página 59
 
1° Edição 03/2011 

Exemplo:

Correto Evitar

if (u16_Largura < 1000)


u32_Area = u16_Altura * u16_Largura;
do
{
if (u16_Largura < 1000){
Código;
u32_Area = u16_Altura * u16_Largura;}
}while( b_Fim == False);
if (u16_Largura < 1000)
{
u32_Area = u16_Altura * u16_Largura;
}

if (u8_Largura != 10)
{ if (u16_Largura < 1000)
CalculaVolume( ); {
} u32_Area = u16_Altura * u16_Largura;
}

Unions:  A declaração union aloca uma única posição de memória onde


podem ser armazenadas várias variáveis diferentes sendo que toda vez que se
armazena um valor, o valor anterior é perdido. As declarações unions devem
ser evitadas na programação de sistemas embarcados.

Goto: O comando goto realiza um salto para um local especificado por um


label. Este comando não deve ser utilizado na programação de sistemas
embarcados.

Exercício Proposto
 

Aplicar as práticas de programação propostas nos programas


apresentados como exemplo e nos programas propostos.
 
 

PROGRAMAÇÃO EM LINGUAGEM C USANDO


MÁQUINA DE ESTADO
 
 

Página 60
 
1° Edição 03/2011 

Referências
 

URL1: Página oficial da Network of Excellence on Embedded Systems Design.


Disponível em:<http://pt.wikipedia.org/wiki/Sistema_embarcado>. Acesso em: março
de 2011.

URL2: Página oficial da Network of Excellence on Embedded Systems Design.


Disponível em:< http://www.artist-embedded.org/artist/>. Acesso em: junho de 2010.

URL3: Página oficial da SUN. Disponível em: <


http://java.sun.com/products/cldc/overview.html#6>. Acesso em: julho de 2010.

URL4: Página oficial da Java Community Process. Disponível em:


<http://jcp.org/en/jsr/detail?id=139>. Acesso em: julho 2010.

URL5: Página oficial da International Electrotechnical Commission. Disponível em:


<http://www.iec.ch/zone/fsafety/pdf_safe/hld.pdf>. Acesso em julho 2010.

URL 6: Página oficial da Wikipedia, a encyclopédia livre. Disponível em: 


<http://pt.wikipedia.org/wiki/N%C3%BAmero_m%C3%A1gico_(inform%C3%A1tica)>.
Acesso em abril 2011.

Edição e Revisão
Eng. Gilson Fonseca Peres Filho
Universidade Federal de Uberlândia gilsonfonseca@yahoo.com.br
Faculdade de Engenharia Elétrica Possui graduação em Engenharia Elétrica pela Universidade Federal de
Disciplina de Sistemas Embarcados Uberlândia (2001). Tem experiência na área de Engenharia Elétrica,
www.feelt.ufu.br com ênfase em Eletrônica Industrial, Sistemas e Controles Eletrônicos,
programação em C, C++, Java e Python, atuando principalmente nos
seguintes temas: processamento paralelo, PLCs, sistemas de aquisição
de dados, redes industriais fieldbus, protocolos de comunicação
Prof. Fábio Vincenzi R. da Silva digitais, softwares para dispositivos móveis, sistemas embarcados e
aplicações para área de TI.

Página 61
 

Вам также может понравиться