/************************************************ * 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 #include #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); }