• UFRJ Nautilus

Programando Microcontroladores em C

Microcontroladores (MCUs) são pequenos computadores programáveis que podem

interagir com o mundo físico. São usados em todos os tipos de sistemas embarcados, de

foguetes a eletrodomésticos e dão infinitas possibilidades para nossos projetos.

Você deve estar familiarizado com a plataforma Arduíno, que nos poupa de todo o

trabalho sujo da programação em baixo nível e permite prototipar rapidamente. Mas por

que abandonar a simplicidade do Arduíno e se dar ao trabalho de programar o MCU

diretamente?


Criar seu projeto diretamente no MCU, sem a abstração proporcionada pelo arduíno permite usar totalmente a sua capacidade e rodar seu código muito mais eficientemente.

Se você pretende ter o seu projeto disponível comercialmente, esqueça, a licença do

ecossistema arduíno te obriga a tornar seu projeto open source. E por fim, programar seu

microcontrolador nativamente é uma grande oportunidade para entender a fundo seu

funcionamento e arquitetura, além de aprender a trabalhar com base em suas limitações.

Agora vamos conhecer o microcontrolador que irá nos acompanhar neste artigo!


Microcontroladores AVR


Neste artigo vamos trabalhar com microcontroladores 8 bits da família AVR (os

mesmos dos arduínos!). Se você já teve experiência com arduíno já sabe das

possibilidades que eles nos dão, mas não custa lembrar. Aqui usaremos o ATTiny85, mas tudo é análogo para outros MCUs da mesma família, qualquer dúvida consulte o datasheet.


Arquitetura de MCUs


Os microcontroladores AVR de 8 bits são baseados na arquitetura RISC, mas não

precisamos dar muito a importância a isso, vamos focar nas partes mais importantes,

comuns a todos os microcontroladores.

  • CPU: Responsável pelas operações lógicas e matemáticas.

  • Memória: Temos dois tipos:

  • ROM(Flash): Memória permanente, onde nosso código ficará guardado.

  • RAM: Memória temporária, armazena valores necessários a execução do código como variáveis e resultados.

  • Periféricos: São módulos independentes dentro do MCU que desempenham funções específicas, os mais úteis são:

  • I/O: Através da portas de entrada e saída podemos interagir com o mundo exterior usando sinais elétricos.

  • USART, I2C e SPI: São protocolos de comunicação serial, podemos comunicar MCU com um computador, sensores e até outro MCU.

  • ADC: O famoso “analogRead()” nos arduínos, os sensores normalmente nos dão dados analógicos (ou seja, uma grande gama de valores, e não o 0 ou 1 digital com o qual trabalhamos no MCU), e usando esse periférico nós podemos manipular os valores digitalmente.


ATTINY85

Agora vamos conhecer o nosso microcontrolador! O ATTINY85 tem 8kb de memória

Flash e 512 bytes de RAM, pode parecer pouco, mas o computador que levou o homem à

Lua tinha 4kb de RAM e 32 kb de ROM. Temos 6 entradas/saídas digitais, 4 entradas

analógicas (4 canais ADC) e até 2 saídas PWM. O ATTINY85 é conhecido pelo seu

consumo baixíssimo, podendo ser alimentado por uma bateria de relógio por anos.


Programando os AVRs


Precisaremos de hardware externo para programar nosso microcontrolador, um

programador ISP, usaremos o popular USBasp e a IDE Atmel Studio. Não vou me estender sobre a instalação e configuração do programador na nossa IDE, dê uma olhada neste tutorial. Se você tiver um arduino por aí também pode usá-lo como programador ISP!


Nosso primeiro projeto!


Esquema de montagem 1. UFRJ Nautilus
Esquema de montagem 1.

Vamos criar nosso projeto no Atmel Studio:


File->New->Project


Dê um nome ao projeto e selecione o device (Nosso MCU alvo) ATTiny85. Cole o

código abaixo, compile e envie ao ATTiny como demonstrado no link acima. É um código

simples para piscar um LED (Ritual de passagem na eletrônica). O LED é apenas uma

abstração, podemos usar os mesmos princípios para interagir com qualquer tipo de circuito

externo, como ponte H, relés e eletrônica “pura”.


#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
	//Define pino 4 como saída
	DDRB |= (1 << DDB4);

while (1)
{
	//Pino 4 HIGH
	PORTB |= (1 << PB4);
	//Delay(espera) de 1 segundo
	_delay_ms(1000);
	PORTB &= ~(1 << PB4);
	//Pino 4 LOW
	_delay_ms(1000);
    }
}

Após enviar o código vamos montar o circuito.


Entendendo o código


Lógica Digital


Antes de mais nada vamos esclarecer alguns conceitos de lógica digital:

  • Bit: Representa um valor lógico. Verdadeiro ou falso, High ou Low, 1 ou 0, e no mundo físico: 5 Volts ou 0 Volts.

  • Byte: Conjunto de 8 bits(8 pode parecer um valor arbitrário, mas não se preocupe com isso).

Hardware Registers


São lugares especiais na memória, bytes, cujos valores afetam o hardware do MCU,

vamos entender melhor agora.


DDRx: Data Direction Register, indica se o pino é entrada ou saída. No código

usamos DDRB, o B indica que estamos trabalhando com os pinos da porta B. Os pinos nos

AVRs são divididos em portas, repare no pinout do Atmega 328P abaixo, temos PB, PC e

PD. No ATiny85 como existem poucos pinos temos apenas o PB.



Existem duas maneiras de alterar os valores nos registers:

1°: Essa é a forma mais intuitiva, associamos 0 (Input) ou 1 (Output) a cada bit,

lembrando que cada bit representa um pino a partir do pino 0, da direita para a esquerda.


DDRB = 0b00010000;


Apenas o pino 4 será um output, repare que é o bit na posição 4 (contando a partir de

0 da direita para a esquerda).

2°: Essa é a forma mais elegante.


DDRB |= (1 << DDB4);


Tornamos o bit na posição 4 um 1. Não podemos trocar o 1 por 0 e esperar que o bit

se torne 0, deveríamos fazer a seguinte operação para zerar o bit 4.


DDRB &= ~(1 << DDB4);


PORTx: Port Data Register, define se o pino está HIGH ou LOW.


Agora que sabemos o que fazem os hardware registers é fácil entender o código

acima. Primeiro definimos o pino 4 da porta B como saída, dentro do loop mudamos o

estado do pino com intervalos de 1s usando a função _delay_ms().

Tente adicionar outro LED, fazê-los piscar alternadamente, o céu é o limite.


Inputs!



Esquema de montagem 2. UFRJ Nautilus
Esquema de montagem 2.
















Não podemos fazer muito apenas controlando o estado dos pinos não é mesmo ?

Agora vamos consultar o estado deles para receber informação do mundo exterior. Nesse

exemplo vamos controlar o nosso LED com um botão e fazê-lo piscar manualmente.


#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    //Pino 4 como output
    DDRB |= (1 << DDB4);
    //Pino 0 com input
    DDRB &= ~(1<<DDB0);
    //Ativa o resistor Pull-up no pino 0
    PORTB |= (1<<PB0);
    //Pino 4 LOW
    PORTB &= ~(1<<PB4);
 
    while (1) 
    {
 	   //Botão pressionado ? (0V no pino 0 ?)
 	   if((PINB & (1 << PINB0)) == 0)
 	   {
 	       //LED ligado
 	       PORTB|= (1<<PB4);
 	   }
 	   else
 	   {
 	       //LED desligado
 	       PORTB &= ~(1<<PB4);
 	   }
    }
}


Temos um register novo no código acima, o PINx, responsável por guardar o estado

atual dos pinos.


Um pequeno adendo:


Você deve estar confuso com expressões como:


(PINB & (1 << PINB0))

PORTB &= ~(1<<PB4);

DDRB |= (1 << DDB4);


Essas são operações lógicas com os bits dos registers, explicá-las vai além do

escopo deste artigo. Mas aqui vai um guia para facilitar a sua vida:


Colocar 1 no bit x:

BYTE |= (1 << x);


Colocar 0 no bit x:

BYTE &= ~(1 << x);


Alternar o valor no bit x:

BYTE ^= (1 << i);


Conclusão


Passamos pelo básico da manipulação dos I/Os dos AVRs, só com isso já podemos

pensar em infinitas aplicações para esses MCUs. Muitas possibilidades não foram

exploradas aqui, conversão analógico digital, comunicação serial, interrupts, entre outras.


Escrito por Gabriel Guimarães.

0 comments

Recent Posts

See All