• Giovana Veiga

[PT-EN] Programando Microcontroladores em C

Updated: 2 days ago

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 o

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.

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.















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.



Programming Microcontrollers in C

-Introduction

Microcontrollers (MCUs) are small programmable computers that can interact with the physical world. They are used in all kinds of embedded systems, from rockets to household appliances and we give endless possibilities for our projects.


You should be familiar with the Arduino platform, which saves us all the dirty work of low-level programming and allows us to prototype quickly. But why abandon the simplicity of Arduino and take the trouble to program the MCU directly?


Creating your project directly on the MCU, without the abstraction provided by arduino allows you to use its full capacity and run your code much more efficiently. If you intend to have your project commercially available, forget it, the arduino ecosystem license forces you to make your project open source. And finally, programming your microcontroller natively is a great opportunity to fully understand its operation and architecture, and learn how to work based on your limitations.



Now let's meet the microcontroller that will accompany us in this article!


-AVR microcontrollers

In this article we will work with 8-bit microcontrollers from the AVR family (the same as your arduinos!), if you've had experiences with Arduino you already know the possibilities they give us, but it won’t hurt remembering.

In this article we will use ATTiny85, but everything is analogous for other MCUs of the same family. If you have any questions, consult the datasheet.


-Architecture of MCUs

The 8-bit AVR microcontrollers are based on the RISC architecture, but we don't really need to know what it means, let's focus on the vital parts, common to all microcontrollers.

CPU: Responsible for logical and mathematical operations.

Memory: We’ve got two kinds:

ROM(Flash): Permanent memory, where our code will be stored.

RAM: Temporary memory, stores values necessary to execute the code such as variables and results.

Peripherals: These are independent modules within the MCU that perform specific functions, the most useful are:

I/O: Through the input and output doors we can interact with the outside world using electrical signals.

USART, I2C and SPI: They are serial communication protocols, we can communicate the MCU with a computer, sensors and even another MCU.

ADC: The famous "analogRead()" in arduinos. Sensors usually give us analog data (a wide range of values, and not the 0s or 1s we work with in the MCU), using this peripheral we can manipulate these values digitally.


-ATTINY85



Now let's meet our microcontroller! The ATTINY85 has 8kb of Flash memory and 512 bytes of RAM, it may seem little, but the computer that took man to the moon had 4kb of RAM and 32 kb of ROM. We have 6 digital inputs/outputs, 4 analog inputs (4 ADC channels) and up to 2 PWM outputs. The ATTINY85 is known for its very low consumption and can be powered by a coin cell battery for years.



-Programming the AVRs

We will need external hardware to program our microcontroller, an ISP programmer, we will use the popular USBasp and the Atmel Studio IDE.

I will not dwell on the installation and configuration of the programmer in our IDE, take a look at this tutorial.

If you have an arduino laying arround you can also use it as an ISP programmer!


-Our first project!




We'll create our first project at Atmel Studio:

File->New->Project

Name the project and select the ATTiny85 device (our target MCU). Paste the code below, compile and submit to ATTiny as shown in the link above. It's a simple code to flash an LED (A rite of passage in electronics). The LED is just an abstraction, we can use the same ideas to interact with any kind of external circuit, like H-bridge, relays and "pure" electronics.

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

int main(void) {

//Define pin 4 as DDRB output |= (1 << DDB4);

while (1) {

//Pin 4 HIGH PORTB |= (1 << PB4); //Delay(hold) 1 second _delay_ms(1000); PORTB &= ~(1 << PB4); //Pin 4 LOW _delay_ms(1000); } } After sending the code, we'll set up the circuit.


-Understanding the code

-Digital Logic

First of all let us clarify some digital logic concepts:

Bit: It represents a logical value. True or false, High or Low, 1 or 0, and in the physical world: 5 Volts or 0 Volts.

Byte: A 8-bit set(8 may seem like an arbitrary value, but don't worry about it).

-Hardware Registers

These are special places in memory, bytes, whose values affect the MCU hardware, we’ll take a closer look at them now.

DDRx: Data Direction Register, indicates whether the pin is an input or an output. In the code above we’ve used DDRB, the B indicates that we are working with the pins of port B. The pins in the AVRs are divided into ports, notice the Atmega 328P pinout below, we have PB,PC and PD. In the ATiny85 as there are only a few pins we only have PB.



There are two ways to change the values in the registers:


1°: This is the most intuitive way, we associate 0(Input) or 1(Output) to each bit, remembering that each bit represents a pin from pin 0, from right to left.

DDRB = 0b00010000;

Only pin 4 will be an output, notice that it is the bit in position 4 (counting from 0 from right to left).


2°: This is the most elegant form.

DDRB |= (1 << DDB4);

We make the bit in position 4 a 1. We cannot change the 1 for 0 and expect the bit to become 0, we should do the following operation to zero the 4 bit.

DDRB &= ~(1 << DDB4);


PORTx: Port Data Register, defines whether the pin is HIGH or LOW.

Now that we know what the hardware registers do it's easy to understand the code above. First we set pin 4 of port B as output, inside the loop we change the pin state with 1s intervals using the _delay_ms() function.

Try adding another LED, make them flash alternately, the sky is the limit.


-Inputs!




We can't do much just by controlling the state of the pins, can we ? Now let's check their condition to get information from the outside world. In this example we will control our LED with a button and make it flash manually.

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

int main(void) {

//Pin 4 as DDRB output |= (1 <<< DDB4); //Pin 0 with DDRB input &= ~(1<<<DDB0); //Activate the Pull-up resistor on pin 0 PORTB |= (1<<<PB0); //Pin 4 LOW PORTB &= ~(1<<PB4);

while (1) {

//Button pressed ? (0V on pin 0 ?) if((PINB & (1 << PINB0)) == 0) {

//LED on PORTB|= (1<<PB4); } else {

//LED off PORTB &= ~(1<<PB4); } } }

We have a new register in the code above, PINx, responsible for saving the current state of the pins.

-A little addendum:

You must be confused by expressions like:

(PINB & (1 << PINB0)) PORTB &= ~(1<<PB4); DDRB |= (1 << DDB4);

These are logical operations with the bits of the registers, explaining them goes beyond the scope of this article. But here's a guide to make your life easier:

Put 1 in bit x: BYTE |= (1 << x);

Put 0 in bit x: BYTE &= ~(1 << x);

Alternate the value in bit x: BYTE ^= (1 << i);


- Conclusion

We went through the basics of AVR I/O manipulation, with that knowledge alone we can think of infinite applications for those MCUs. Many possibilities were not explored here, analog digital conversion, serial communication, interrupts, among others.

Written by Gabriel Guimarães