O Franzininho, o Arduino baseado no microcontrolador ATTiny85, pode ser considerada uma das placas mais versáteis para Arduino no Brasil. Com tamanho reduzido, ótima documentação e pelo fato de haver projetos utilizando-a estarem aparecendo, esta se configura como uma placa ideal para projetos diversos no mundo maker.
Aproveitando o surgimento e popularização desta placa, este artigo, feito numa parceria entre o Fábio Souza e eu, mostra como utilizar o gerenciador de tarefas TickAttack (portado para o ATTiny85) em conjunto com a Franzininho, ambos com a finalidade de permitir fácil e robusto controle ao Robô Franzininho.
Material utilizado
Para reproduzir este projeto, será preciso ter à disposição os seguintes materiais:
- Uma placa Franzininho;
- Um Power Bank (preferencialmente um que disponibilize alimentação com corrente máxima de 2A ou superior);
- Um módulo Bluetooth HC-05;
- Um driver de motor com ponte H L298N;
- Um Chassis de robô com duas rodas.
TickAttack – portabilidade para ATTiny85
Para a realização do projeto, o TickAttack foi portado para o microcontroldor ATTiny85. Em relação ao projeto original do TickAttack, devido a restrições de recursos (para se ter o menor overhead possível), os seguintes recursos foram deixados de lado:
- Encapsulamento de variáveis relevantes ao Kernel cooperativo;
- Mecanismo de dados compartilhados;
- Mecanismo de medição de performance (por tarefa).
Em suma, preservou-se somente o necessário, afim de deixar a maior parte dos recursos alocáveis para o projeto principal / tarefas a serem controladas pelo TickAttack.
O TickAttack já portado para o ATTiny85 pode ser obtido do repositório oficial do projeto, clicando aqui.
Robô Franzininho – circuito esquemático
O circuito esquemático do robô Franzininho pode ser visto na figura 1.
Observação: O uso do protoboard é opcional. Este foi colocado no esquema com a finalidade de facilitar a visualização.
No final da montagem, seu robô deve se parecer com o da figura 2.
Código-fonte: TickAttack + Franzininho
Abaixo, segue o código-fonte do Robô Franzininho. O código-fonte foi feito para a Arduino IDE, portanto a forma de programa-lo é a mesma que foi abordada no artigo original da placa.
Se preferir, pode obter o código-fonte no GitHub oficial do projeto, clicando aqui.
/*
* TickAttack for Arduino
* Author: Pedro Bertoleti
* Date: Jul/2017
*/
/*
* IMPORTANT:
* 1) This code was writen to be compiled for ATTINY85, using Timer1 interrupt as Tick generator.
* For porting it to another platforms, the major effort is replacing interrupt timer routines for
* those allowed in desired platform.
* 2) NEVER use delays into Tasks! A task manager tries to execute all the tasks
* in real-time and in the "time to execute" it´s needed. A delay will certanly
* spoil it all.
* NEVER use blocking functions inside tasks.
* AVOID USING very time expensive loops inside tasks.
*/
/*
* Using this Task Manager
*
* Write inside Task1, Task2, Task3 and Task4 functions what must be done
* in these tasks (first of all, read IMPORTANT item #2. Do not ignore what
* is written there).
*
* If you need more tasks, simply write more functions according to Tasks´
* function modules (in other words, copy, paste, change the name, declare
* prototypes and that´s it!). Please, do not forget to refresh NUMBER_OF_TASKS define
* and create a define for your tasks´ index too.
*/
#include <SoftwareSerial.h>
#include "avr/wdt.h"
/*
* Defines
*/
#define INDEX_TASK_1 0
#define INDEX_TASK_2 1
#define INDEX_TASK_3 2
#define INDEX_TASK_4 3
#define NUMBER_OF_TASKS 4
#define TIME_TO_EXECUTE_TASK1 100 //time unit: ms
#define TIME_TO_EXECUTE_TASK2 500 //time unit: ms
#define TIME_TO_EXECUTE_TASK3 1000 //time unit: ms
#define TIME_TO_EXECUTE_TASK4 1000 //time unit: ms
#define TASK_TIMEOUT 1000 //time unit: ms
#define YES 1
#define NO 0
/*
* Global variables
*/
void (*ScheduledTasks[NUMBER_OF_TASKS])(void); //It stores the function pointers of Task.
int TimeScheduledTasks[NUMBER_OF_TASKS]; //It stores the task´s times (time period to execute)
int RecentTasksTimeToExecute[NUMBER_OF_TASKS]; //It stores the recent task´s times ("time to execute" each task)
char TimerIntGeneraed; //It indicates if timer interrupt has been generated
char TaskIsExecuting; //It indicates if a task in executing (important information for consider task timeout validation)
int TaskTimeout; //used for timeout counting
SoftwareSerial mySerial(2, 6); //pin 2(RX) and pin 6 (TX) - software serial
char IsThereCommandToBeCleared; //It indicates if there's a command to be cleared (so the robot won't repeat "forever" the lst command sent)
/*
* Constants
*/
const byte PIN_A = 0;
const byte PIN_B = 1;
const byte PIN_C = 3;
const byte PIN_D = 4;
/*
* Prototypes
*/
void InitTasks(void);
void ExecuteTask(void);
void Task1(void); //task number one - write into this function what this taks will make
void Task2(void); //task number two - write into this function what this task will make
void Task3(void); //task number three - write into this function what this task will make
void Task4(void); //task number four - write into this function what this task will make
/*
* Application function prototypes
*/
void SetupApplication(void);
void GoForward(void);
void GoBackwards(void);
void GoLeft(void);
void GoRight(void);
void StopRobot(void);
/*
* Functions
*/
/*
* Applications Functions
*/
void SetupApplication(void)
{
pinMode(PIN_A, OUTPUT);
pinMode(PIN_B, OUTPUT);
pinMode(PIN_C, OUTPUT);
pinMode(PIN_D, OUTPUT);
mySerial.begin(9600);
}
void GoForward(void)
{
digitalWrite(PIN_A, HIGH);
digitalWrite(PIN_B, LOW);
digitalWrite(PIN_C, HIGH);
digitalWrite(PIN_D, LOW);
}
void GoBackwards(void)
{
digitalWrite(PIN_A, LOW);
digitalWrite(PIN_B, HIGH);
digitalWrite(PIN_C, LOW);
digitalWrite(PIN_D, HIGH);
}
void StopRobot(void)
{
digitalWrite(PIN_A, LOW);
digitalWrite(PIN_B, LOW);
digitalWrite(PIN_C, LOW);
digitalWrite(PIN_D, LOW);
}
void GoRight(void)
{
digitalWrite(PIN_A, LOW);
digitalWrite(PIN_B, HIGH);
digitalWrite(PIN_C, HIGH);
digitalWrite(PIN_D, LOW);
}
void GoLeft(void)
{
digitalWrite(PIN_A, HIGH);
digitalWrite(PIN_B, LOW);
digitalWrite(PIN_C, LOW);
digitalWrite(PIN_D, HIGH);
}
/*
* TickAttack Functions
*/
//Function: initialize and schedule tasks
//Params: nothing
//Return: nothing
void InitTasks(void)
{
//init function pointers of tasks
ScheduledTasks[INDEX_TASK_1] = Task1;
ScheduledTasks[INDEX_TASK_2] = Task2;
ScheduledTasks[INDEX_TASK_3] = Task3;
ScheduledTasks[INDEX_TASK_4] = Task4;
//init temporization values of each task. These values do no change during execution
TimeScheduledTasks[INDEX_TASK_1] = TIME_TO_EXECUTE_TASK1;
TimeScheduledTasks[INDEX_TASK_2] = TIME_TO_EXECUTE_TASK2;
TimeScheduledTasks[INDEX_TASK_3] = TIME_TO_EXECUTE_TASK3;
TimeScheduledTasks[INDEX_TASK_4] = TIME_TO_EXECUTE_TASK4;
//init recent temporization values of each task. These values will change during execution (they´re used to decide which task must be executed)
RecentTasksTimeToExecute[INDEX_TASK_1] = TIME_TO_EXECUTE_TASK1;
RecentTasksTimeToExecute[INDEX_TASK_2] = TIME_TO_EXECUTE_TASK2;
RecentTasksTimeToExecute[INDEX_TASK_3] = TIME_TO_EXECUTE_TASK3;
RecentTasksTimeToExecute[INDEX_TASK_4] = TIME_TO_EXECUTE_TASK4;
//It indicates that there´s no task executing
TaskIsExecuting = NO;
}
//Function: Task 1 function (reads serial incoming data and control robot)
//Params: nothing
//Return: nothing
void Task1(void)
{
if (mySerial.available())
{
//reads serial data
int cmd = mySerial.read();
IsThereCommandToBeCleared = YES;
//controlling robot
switch(cmd)
{
case 'a':
GoForward();
break;
case 'b':
GoLeft();
break;
case 'd':
GoRight();
break;
case 'e':
GoBackwards();
break;
case 'c':
StopRobot();
break;
default:
IsThereCommandToBeCleared = NO;
break;
}
}
}
//Function: Task 2 function (clear a command sent previously to robot)
//Params: nothing
//Return: nothing
void Task2(void)
{
if (IsThereCommandToBeCleared == YES)
StopRobot();
}
//Function: Task 3 function
//Params: nothing
//Return: nothing
void Task3(void)
{
}
//Function: Task 4 function
//Params: nothing
//Return: nothing
void Task4(void)
{
}
void setup()
{
//Init/configures all tasks
InitTasks();
//Init/configures application variables and functions
IsThereCommandToBeCleared = NO;
SetupApplication();
//setup TIMER 1 = Toverflow = (Timer Max Value(255) x prescaler(32))/fosc(8 MHz) = 1,02 ms
TCCR1 = (1<<CS12)|(1<<CS11); //Prescale = 32
TIMSK = 1<<TOIE1; //Timer 1 Overflow Interrupt Enable
sei(); //Globla interrupt Enable
TimerIntGeneraed = NO;
}
//Function: timer 1 isr
//Params: nothing
//Return: nothing
ISR(TIMER1_OVF_vect)
{
char i;
TimerIntGeneraed = YES;
//Here, the "time to execute" of each task is refreshed
for (i=0; i<NUMBER_OF_TASKS; i++)
{
if (RecentTasksTimeToExecute[i] > 0)
RecentTasksTimeToExecute[i]--;
}
if (TaskIsExecuting == YES)
{
TaskTimeout--;
if (!TaskTimeout)
{
//Timeout has reached (possibly a task has crashed. Microcontroller must be reset)
wdt_enable(WDTO_15MS);
while(1);
}
}
}
//Function: Execute tasks
//Params: nothing
//Return: nothing
void ExecuteTask(void)
{
char i;
for (i=0; i<NUMBER_OF_TASKS; i++)
{
//Check if it´s time to execute a task
if ((ScheduledTasks[i] != 0) && (RecentTasksTimeToExecute[i] == 0))
{
TaskIsExecuting = YES;
TaskTimeout = TASK_TIMEOUT;
ScheduledTasks[i](); //executes the task
TaskIsExecuting = NO;
RecentTasksTimeToExecute[i] = TimeScheduledTasks[i]; //reagendamento da tarefa
}
}
}
void loop()
{
if ((TimerIntGeneraed == YES) && (NUMBER_OF_TASKS))
{
TimerIntGeneraed = NO;
ExecuteTask();
}
}
Vídeo – Robô Franzininho em ação!
Veja abaixo um vídeo do Robô Franzininho em ação:
Vá além: utilize a Franzininho + TickAttack nos seus projetos
Neste artigo foi mostrado um uso divertido e adequado para o TickAttack em conjunto com a Franzininho: o controle do Robô Franzininho, um robô sob rodas. Agora que você já sabe como desenvolver um projeto utilizando-os, o céu é o limite!
Portanto, explore sem medo o TickAttack em conjunto com os recursos da Franzininho o quanto puder, desde robôs e controles de I/O até sistemas supervisórios. E, claro, compartilhe com a comunidade, contribuindo assim com o projeto e novas ideias!




