Adicionado Firmware de controle de ganho para Arduino

parent ff27a456
/************************************************
* Universidade Federal do Rio Grande do Sul
* Escola de Engenharia
* Curso de Engenharia Elétrica
*
* ECG Portátil com Controle Automático de Ganho
*
*
*
* Arthur Christoff Koucher
* Gabriel Marins da Costa
* Luis Eduardo Davoglio Estradioto
*
*
**************************************************/
#include <avr/io.h>
#include <avr/interrupt.h>
#define BUFFER_SIZE 700
#define COOLDOWN 2000 //Tempo em Segundos para uma mudança de ganho e outra
/* PINOS DIGITAIS DE INTERACE */
#define G3 12 //D12 - A3 do Ganho - OUTPUT
#define G2 11 //D11 - A2 do Ganho - OUTPUT
#define G1 10 //D10 - A1 do Ganho - OUTPUT
#define G0 9 //D9 - A0 do Ganho - OUTPUT
#define G3_BIT_MASK 4 // PORTB4 no ATMEGA328p
#define G2_BIT_MASK 3 // PORTB3 no ATMEGA328p
#define G1_BIT_MASK 2 // PORTB2 no ATMEGA328p
#define G0_BIT_MASK 1 // PORTB1 no ATMEGA328p
#define ANALOG_SATURATION 13 //D13 - LED que indica saturação do sinal do ADC - OUTPUT
#define SAMPLING_COMPLETE 7 //D7 - LED que indica finalização da amostragem do sinal - OUTPUT
#define GAIN_SWITCH 8 //D8 - LED que indica quando foi alterado o ganho - OUTPUT
#define AGC_START_PIN 2 //D2 - Botão que inicia rotinas de AGC - INPUT_PULLUP
#define GAIN_MINUS_PIN 3 //D3 - Botão que diminui manualmente o ganho - INPUT_PULLUP
#define GAIN_PLUS_PIN 4 //D4 - Botão que aumenta manualmente o ganho - INPUT_PULLUP
/* PINOS ANALÓGICOS DE INTERFACE */
#define IN1 A0 //A0 - Entrada do ADC
#define IN2 A1 //A1 - Entrada do ADC
#define F_SAMPLE 256 //Frequência de Amostragem, em Hz
unsigned long millis();
/* OPÇÕES DE GANHO */
const uint16_t GAIN_OPT[16] = {1, 2, 4, 8, 10, 20, 40, 80, 100, 200, 400, 800, 1000, 2000, 4000, 8000};
volatile uint8_t GAIN_INDEX = 11; //Default como 800 de ganho
volatile uint8_t LAST_GAIN_INDEX = 11;
/* BUFFER DO SINAL AMOSTRADO */
volatile uint16_t SAMPLE[BUFFER_SIZE];
volatile uint16_t SAMPLE_index = 0;
volatile uint16_t SAMPLE_READ = 0;
volatile uint64_t SUM_SAMPLE = 0;
volatile uint64_t N_SAMPLE = 0;
volatile double AVG_SAMPLE = 0;
/* PROTÓTIPOS DAS FUNÇÕES AUXILIARES */
void initial_configs();
void Aux_LED_HANDLERS();
//
void CHECK_AGC_STATE();
void REFRESH_GAIN();
void CHECK_BUTTONS_PRESSED();
void AGC();
/* Variáveis de Estado */
volatile uint32_t SATURATION_COUNT = 0;
volatile uint8_t AGC_STATE = 0; //Inicia com AGC desligado
/* Variáveis Auxiliares de Timers, ADCs, LEDs, etc*/
volatile uint16_t TIMER1_OVF_COUNT = 0; //Quantidade de overflows do Timer 1
unsigned long lastMillis = 0; //última contagem de Millis
unsigned long lastGainChangeMillis = 0; // última contagem de tempo desde a mudança de ganho
unsigned long lastLEDToggled = 0;
unsigned long CURRENT = 0; // Uso geral para armazenar os millis
volatile uint8_t INT0_PRESSED = 0; // Auxiliar para debounce por software da interrupção EXTINT0
volatile uint16_t INT1_PRESSED = 0; // Auxiliar para debounce por software da interrupção EXTINT1
volatile uint16_t PCINT2_PRESSED = 0; // Auxiliar para debounce por software da interrupção PCINT20
unsigned long lastButtonsChecked = 0; // Auxiliar para checar a última checagem dos botões
unsigned long lastSatChange = 0; // Auxiliar para checar a última mudança de ganho por saturação
unsigned long lastAGCExecuted = 0; // Auxiliar para checar a última execução do AGC
int main()
{
init(); //Necessario para iniciar funções padrão do arduino, como millis
initial_configs();
while(1)
{
/* 1) Atualização dos Ganhos */
CURRENT = millis();
if((CURRENT - lastGainChangeMillis) > COOLDOWN)
//Atualiza os pinos com o ganho setado caso tenha passado do CoolDown
{
lastGainChangeMillis = CURRENT;
REFRESH_GAIN();
//Serial.print("REFRESH |");
//Serial.println(GAIN_OPT[GAIN_INDEX]);
//Pisca LED de ganho se trocou
if (GAIN_INDEX != LAST_GAIN_INDEX)
{
PORTB |= (1 << PORTB0);
LAST_GAIN_INDEX = GAIN_INDEX;
}
}
/* Aux Desliga LEDs acesos e zera contagem de botões pressionados (debounce) */
Aux_LED_HANDLERS();
/* 2) Verifica Botões Pressionados */
CHECK_BUTTONS_PRESSED();
/* 3) Cálculo do Controle Automático de Ganho */
//Serial.println(AGC_STATE);
if (AGC_STATE != 0 && (CURRENT - lastAGCExecuted) > 3000) //Se der errado, alterar tempo
{
AGC();
lastAGCExecuted = millis();
}
}
}
void initial_configs()
/* Função com configurações iniciais de pinos, interfaces e interrupções*/
{
sei();
Serial.begin(9600);
pinMode(G3, OUTPUT);
pinMode(G2, OUTPUT);
pinMode(G1, OUTPUT);
pinMode(G0, OUTPUT);
pinMode(ANALOG_SATURATION, OUTPUT);
pinMode(GAIN_SWITCH, OUTPUT);
pinMode(SAMPLING_COMPLETE, OUTPUT);
//Checar se está funcionando OK
pinMode(AGC_START_PIN, INPUT_PULLUP);
pinMode(GAIN_PLUS_PIN, INPUT_PULLUP);
pinMode(GAIN_MINUS_PIN, INPUT_PULLUP);
/* Inicialização do ADC */
//Desativa Buffers digitais dos canais analógicos (A0 e A1)
DIDR0 |= (1 << ADC1D) | (1 << ADC0D);
//Canal 0 do MUX, referência em AVcc
ADMUX = 0;
ADMUX |= (1 << 6);
//Canal - 0001 MUX[3:0]
ADMUX |= (1 << 0);
//Ativa ADC, ativa interrupção e prescaler de 128 no clock do ADC (16 MHz / 128 = 125kHz)
ADCSRA |= (1 << ADEN) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); //| (1 << ADATE);
//Define fonte do trigger de auto conversão - Timer 1 OVF
//ADCSRB |= (1 << ADTS2) | (1 << ADTS1);
/* Inicialização dos Timers */
//Timer 1 - Utilizado para controlar as amostragens
//Desativa configurações padrão do ambiente Arduino
TCCR1A = 0;
//Prescaler de 1 (sem prescaling)
TCCR1B = 0x01;
//Ativa Interrupção de Overflow de Timer
TIMSK1 |= (1 << 0);
//Timer de 62500 contagens - 256 Hz a 16MHz de Clock
TCNT1 = 3035; //Ou 62500 contagens para 256Hz aprox
/* Configuração das Interrupções Externas */
//Ativa interrupções externas INT1 e INT0
EIMSK |= (1 << INT1) | (1 << INT0);
//EXTINT0 e EXINT1 acionadas no nível baixo dos pinos (botão pressionado)
//EICRA |= (1 << ISC11) | (1 << ISC01); OBSOLETO
//Ativa interrupção de Pin Change 2
PCICR |= (1 << PCIE2);
//Ativa Interrupção no pino D4 (PORTD4, PCINT20 correspondente)
PCMSK2 |= (1 << PCINT20);
}
void Aux_LED_HANDLERS()
/* Função Auxiliar para o controle dos LEDs */
/*
* Pinos Envolvidos
* D13 - PortB5 - LED de saturação
* D8 - PortB0 - LED de troca de Ganho
* D7 - PortD7 - LED de amostragem Completa
*/
{
//Desliga LEDs de amostragem de troca de ganho após um tempo
CURRENT = millis();
if((CURRENT - lastLEDToggled) > 500)
{
// PORTB &= ~(1 << PORTB0); //GAIN_SWITCH
PORTD &= ~(1 << PORTD7); //SAMPLING_COMPLETE
lastLEDToggled = CURRENT;
}
//Reseta Debounce dos Botões
if ((CURRENT - lastButtonsChecked) > 4000)
{
INT0_PRESSED = 0;
INT1_PRESSED = 0;
PCINT2_PRESSED = 0;
lastButtonsChecked = CURRENT;
}
}
void AGC()
/* Função que implementa o controle automático de ganho caso a variável de estado correspondente
(AGC_STATE) esteja ativa */
/*
*
*/
{
uint16_t MAX_V = SAMPLE[0];
uint16_t MIN_V = SAMPLE[0];
uint16_t i;
for(i = 0; i < BUFFER_SIZE; i++)
{
if (SAMPLE[i] > MAX_V)
{
MAX_V = SAMPLE[i];
}
else if(SAMPLE[i] < MIN_V)
{
MIN_V = SAMPLE[i];
}
}
uint16_t AMPLITUDE;
AMPLITUDE = MAX_V - MIN_V;
AVG_SAMPLE = SUM_SAMPLE / N_SAMPLE;
SUM_SAMPLE = 0;
N_SAMPLE = 0;
//Serial.println((long)AVG_SAMPLE);
Serial.println(AMPLITUDE);
INT0_PRESSED = 0; /////////////////////////////////////////////GAMBS
if (AMPLITUDE >= 750 && GAIN_INDEX >= 1 && AMPLITUDE <= 1025 )
{
GAIN_INDEX--;
}
else if (AMPLITUDE <= 420 && GAIN_INDEX < 11)
{
GAIN_INDEX ++;
}
//Controle de Saturação
//Para o caso em que há uma saturação DC em 5V
if(SATURATION_COUNT >= 1000)
{
//Liga LED
//PORTB |= (1 << PORTB5);
if(AGC_STATE == 1 && GAIN_INDEX > 0 && (CURRENT - lastSatChange) > 3000)
{
// GAIN_INDEX--; //Diminui ganho forçadamente com AGC ligado
lastSatChange = CURRENT;
SATURATION_COUNT = 0;
}
}
}
void REFRESH_GAIN()
/* Função que dado o ganho do estado atual, atualiza as saídas digitais */
/*
* Pinos Envolvidos
* A3 - D12 - PortB4
* A2 - D11 - PortB3
* A1 - D10 - PortB2
* A0 - D9 - PortB1
*
*/
{
//Serial.print("Refresh: |");
//Serial.println(GAIN_OPT[GAIN_INDEX]);
if(GAIN_OPT[GAIN_INDEX] == 8000)
//A3 = 1; A2 = 1; A1 = 1; A0 = 1
{
PORTB |= (1 << G3_BIT_MASK) | (1 << G2_BIT_MASK) | (1 << G1_BIT_MASK) | (1 << G0_BIT_MASK);
}
else if (GAIN_OPT[GAIN_INDEX] == 4000)
//A3 = 1; A2 = 1; A1 = 1; A0 = 0
{
PORTB |= (1 << G3_BIT_MASK) | (1 << G2_BIT_MASK) | (1 << G1_BIT_MASK);
PORTB &= ~(1 << G0_BIT_MASK);
}
else if (GAIN_OPT[GAIN_INDEX] == 2000)
//A3 = 1; A2 = 1; A1 = 0; A0 = 1
{
PORTB |= (1 << G3_BIT_MASK) | (1 << G2_BIT_MASK) | (1 << G0_BIT_MASK);
PORTB &= ~(1 << G1_BIT_MASK);
}
else if (GAIN_OPT[GAIN_INDEX] == 1000)
//A3 = 1; A2 = 1; A1 = 0; A0 = 0
{
PORTB |= (1 << G3_BIT_MASK) | (1 << G2_BIT_MASK);
PORTB &= ~((1 << G1_BIT_MASK) | (1 << G0_BIT_MASK));
}
else if (GAIN_OPT[GAIN_INDEX] == 800)
//A3 = 1; A2 = 0; A1 = 1; A0 = 1
{
PORTB |= (1 << G3_BIT_MASK) | (1 << G1_BIT_MASK) | (1 << G0_BIT_MASK);
PORTB &= ~(1 << G2_BIT_MASK);
}
else if (GAIN_OPT[GAIN_INDEX] == 400)
//A3 = 1; A2 = 0; A1 = 1; A0 = 0
{
PORTB |= (1 << G3_BIT_MASK) | (1 << G1_BIT_MASK);
PORTB &= ~((1 << G2_BIT_MASK) | (1 << G0_BIT_MASK));
}
else if (GAIN_OPT[GAIN_INDEX] == 200)
//A3 = 1; A2 = 0; A1 = 0; A0 = 1
{
PORTB |= (1 << G3_BIT_MASK) | (1 << G0_BIT_MASK);
PORTB &= ~((1 << G2_BIT_MASK) | (1 << G1_BIT_MASK));
}
else if (GAIN_OPT[GAIN_INDEX] == 100)
//A3 = 1; A2 = 0; A1 = 0; A0 = 0
{
PORTB |= (1 << G3_BIT_MASK);
PORTB &= ~((1 << G2_BIT_MASK) | (1 << G1_BIT_MASK) | (1 << G0_BIT_MASK));
}
else if (GAIN_OPT[GAIN_INDEX] == 80)
//A3 = 0; A2 = 1; A1 = 1; A0 = 1
{
PORTB |= (1 << G2_BIT_MASK) | (1 << G1_BIT_MASK) | (1 << G0_BIT_MASK);
PORTB &= ~(1 << G3_BIT_MASK);
}
else if (GAIN_OPT[GAIN_INDEX] == 40)
//A3 = 0; A2 = 1; A1 = 1; A0 = 0
{
PORTB |= (1 << G2_BIT_MASK) | (1 << G1_BIT_MASK);
PORTB &= ~((1 << G3_BIT_MASK) | (1 << G0_BIT_MASK));
}
else if (GAIN_OPT[GAIN_INDEX] == 20)
//A3 = 0; A2 = 1; A1 = 0; A0 = 1
{
PORTB |= (1 << G2_BIT_MASK) | (1 << G0_BIT_MASK);
PORTB &= ~((1 << G3_BIT_MASK) | (1 << G1_BIT_MASK));
}
else if (GAIN_OPT[GAIN_INDEX] == 10)
//A3 = 0; A2 = 1; A1 = 0; A0 = 0
{
PORTB |= (1 << G2_BIT_MASK);
PORTB &= ~((1 << G3_BIT_MASK) | (1 << G1_BIT_MASK) | (1 << G0_BIT_MASK));
}
else if (GAIN_OPT[GAIN_INDEX] == 8)
//A3 = 0; A2 = 0; A1 = 1; A0 = 1
{
PORTB |= (1 << G1_BIT_MASK) | (1 << G0_BIT_MASK);
PORTB &= ~((1 << G3_BIT_MASK) | (1 << G2_BIT_MASK));
}
else if (GAIN_OPT[GAIN_INDEX] == 4)
//A3 = 0; A2 = 0; A1 = 1; A0 = 0
{
PORTB |= (1 << G1_BIT_MASK);
PORTB &= ~((1 << G3_BIT_MASK) | ( 1 << G2_BIT_MASK) | (1 << G0_BIT_MASK));
}
else if (GAIN_OPT[GAIN_INDEX] == 2)
//A3 = 0; A2 = 0; A1 = 0; A0 = 1
{
PORTB |= (1 << G0_BIT_MASK);
PORTB &= ~((1 << G3_BIT_MASK) | (1 << G2_BIT_MASK) | (1 << G1_BIT_MASK));
}
else if (GAIN_OPT[GAIN_INDEX] == 1)
//A3 = 0; A2 = 0; A1 = 0; A0 = 0
{
PORTB &= ~((1 << G3_BIT_MASK) | (1 << G2_BIT_MASK) | (1 << G1_BIT_MASK) | (1 << G0_BIT_MASK));
}
else
{
GAIN_INDEX = 11; // Back to Default
}
}
void CHECK_BUTTONS_PRESSED()
/* Função auxiliar para checar se os botões de alteração de ganho ou AGC_ON foram acionados */
/* Emula também uma forma de Software Debounce. O ideal seria realmente fazer um debounce,
porém, como é necessário lidar com interrupções
para manter a execução do programa contínua, foi preferível
utilizar as ISRs para filtrar o ruído das chaves.
Os valores para filtragem das somas de acionamento dos botões são empíricos */
/* Pinos envolvidos
D2 - AGC_ON (EXTINT0)
D3 - GAIN_MINUS_PIN (EXTINT1)
D4 - GAIN_PLUS_PIN (PCINT2) */
{
unsigned long deb;
CURRENT = millis();
deb = CURRENT - lastButtonsChecked;
if(INT0_PRESSED >= 100 && ((CURRENT - lastButtonsChecked) > COOLDOWN))
{
//Altera o estado do AGC
AGC_STATE ^= (1<<0);
PORTB ^= (1 << PORTB5); //Altera estado do LED do AGC
lastButtonsChecked = millis();
INT0_PRESSED = 0;
}
if(INT1_PRESSED >= 30 && deb > COOLDOWN)
{
//DESATIVA AGC
AGC_STATE = 0;
PORTB &= ~(1 << PORTB5);
//Diminui o Ganho manualmente
if(GAIN_INDEX > 0)
{
GAIN_INDEX--;
}
INT1_PRESSED = 0;
lastButtonsChecked = millis();
}
if(PCINT2_PRESSED >= 2 && deb > COOLDOWN)
{
//DESATIVA AGC
AGC_STATE = 0;
PORTB &= ~(1 << PORTB5);
//Aumenta o Ganho manualmente
if (GAIN_INDEX < 11)
{
GAIN_INDEX++;
}
PCINT2_PRESSED = 0;
lastButtonsChecked = millis();
}
}
ISR (TIMER1_OVF_vect)
/* Rotina de Interrupção de Timer 1 OVF */
/* Será utilizado para controlar a taxa de amostragem do ADC */
{
// uint16_t leitura = analogRead(IN1);
//Inicia conversão do ADC
ADCSRA |= (1 << ADSC);
//Limpa Flag de Overflow
TIFR1 |= (1 << 0);
TCNT1 = 3035; //Ou 62500 contagens para 256Hz aprox
}
ISR (INT0_vect)
/* Rotina de Interrupção Externa 0 */
/* Utilizado para alterar a flag do AGC */
{
INT0_PRESSED++;
//Serial.println(INT0_PRESSED);
//Limpa flag
EIFR |= (1 << INTF0);
//Serial.println(INT0_PRESSED);
}
ISR (INT1_vect)
/* Rotina de Interrupção Externa 1 */
/* Utilizado para diminuir manualmente o ganho do sistema */
{
INT1_PRESSED++;
//Serial.println(INT1_PRESSED);
//Limpa flag
EIFR |= (1 << INTF1);
}
ISR (PCINT2_vect)
/* Rotina de Interrupção de Mudança de Pino 2 */
/* Utilizado para aumentar manualmente o ganho do sistema */
/* Utilizando o Pino Digital 4 (PORTD4) e a máscara PCINT20 (Cap 17. Datasheet) */
{
PCINT2_PRESSED++;
//Serial.println(PCINT2_PRESSED);
}
ISR (ADC_vect)
{
//Lê valor convertido
SAMPLE_READ = ADC;
SAMPLE[SAMPLE_index] = SAMPLE_READ;
SAMPLE_index++;
SUM_SAMPLE += SAMPLE_READ;
N_SAMPLE++;
//Rola o Buffer se estiver cheio
if (SAMPLE_index >= BUFFER_SIZE)
{
PORTD |= (1 << PORTD7);
SAMPLE_index = 0;
}
//Checa Saturação na parte superior
if(SAMPLE_READ >= 1010)
{
SATURATION_COUNT++;
}
//Limpa Flag de Interrupção
ADCSRA |= (1 << ADIF);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment