En aquest projecte volem fer un sistema per monitoritzar el ritme cardíac. Per fer-ho disposarem d'un sensor que ens dona un senyal analògic amb una forma aproximada del pols cardíac. Aquest sensor el tindrem connectat a l'entrada analògica AN2.

El sensor incorpora un LED i un sensor de llum, a part d'altres elements electrònics. Es coloca sobre el dit (amb una cinta que l'aguanta) o al lòbul de l'orella (amb una pinça). El sensor de llum rep més o menys llum provinent del LED segons el moment del cicle cardíac. El senyal de sortida del sensor és similar al que es mostra a la figura següent. Els valors concrets poden ser diferents segons el ritme cardíac, la tensió d'alimentació i la forma com està col·locat el sensor.

Veiem que, fonamentalment, el senyal té una zona vall i un pols. Ens interessen els polsos. Seguint les recomanacions del fabricant, considerarem que es passa de pols a vall en el centre del senyal. Per trobar el centre agafarem el valor màxim i el mínim i cercarem el punt mig. Considerarem que el cicle cardíac comença cada cop que travessem el valor central del senyal en sentit ascendent. A la següent gràfica s'indiquen els valors que acabem de comentar i, en vermell, el nom de la variable que farem servir per guardar-los. Els noms en verd no es corresponen estrictament amb les variables del mateix nom.

Atès que no ens cal molta precisió, llegirem el senyal analògic amb només vuit bits. A continuació presentarem una funció d'interrupció que detecta els inicis de pols i en mesura el període; així com un petit programa que encén un LED quan hi ha el pols i l'apaga a la zona vall. La funció d'interrupció estarà lligada al Timer0 i s'executarà cada 2 ms. Anem a veure quina pot ser la configuració del Timer 0. La taula següent té les possibles opcions:
| Bits | Escala | Període | Iteracions | Preselecció |
| 100 | 1/32 | 32 μs | 62,5 | --- |
| 011 | 1/16 | 16 μs | 125 | 131 |
| 010 | 1/8 | 8 μs | 250 | 6 |
| 001 | 1/4 | 64 μs | 500 | --- |
Triem l'opció d'1/8 de la freqüència. La nostra funció d'interrupció haurà de tenir unes primeres línies en les que es guarda l'acumulador i l'STATUS abans de tornar a carregar la preselecció. En el nostre cas són 8 μs i, per tant, posarem una preselecció de 7 per fer 249 iteracions (una menys de les que diu la taula).
Les variables que emprarem seran les següents:
| Nom | Mida | Finalitat | Observacions |
| Lectura | unsigned char | Valor llegit a l'entrada analògica | |
| Temps | unsigned int | S'incrementa cada cop que es fa la interrupció (cada 2 ms) | |
| Max | unsigned char | Valor màxim del senyal en un període | |
| Min | unsigned char | Valor mínim del senyal en un període | |
| Centre | unsigned char | Valor mig del senyal en un període | Centre = (Max + Min) / 2 |
| Periode | unsigned int | Quart de període | Guarda la meitat del nombre d'interrupcions entre dos inicis de pols |
| Comp | unsigned char | Comptador per filtrar soroll | |
| Port | unsigned char | Valor que enviarem al port C | |
| Pols | bit | S'activa quan el senyal està per sobre del valor mig | Bit 0 del port C |
| Tenim1 | bit | S'activa quan ja hem detectat l'inici del primer pols | Bit 3 del port C |
| Estable | bit | S'activa quan ja hem detectat l'inici del segon pols i, per tant, ja tenim dades útils | Bit 2 del port C |
La lectura i el tractament del sensor el farem amb una funció d'interrupció que es representa al diagrama següent. Podem observar que la funció té tres parts: una primera en la que es guarden els valors màxims i mínims del senyal mesurat, una segona en la que es fa el tractament del senyal per detectar els inicis i finals de pols i una tercera en la que es detecta que el ritme cardíac és inferior a 30 pulsacions per minut.
La primera cosa que fem a cada interrupció és llegir l'entrada AN2, on tenim el sensor, i incrementar la variable Temps. Si el valor llegit és superior a Centre, mirarem si és més gran que el màxim que tenim guardat. En cas afirmatiu guardarem el nou valor com a màxim. Per al mínim fem una cosa similar però amb un afegit. És freqüent que, després del pic, hi hagi un segon pic en sentit contrari i, també, que hi hagi soroll en la zona vall. Per això no començarem a cercar el mínim fins que hagi passat mig període. Atès que no sabem el que dura un cicle fins que no ha acabat, fem servir les dades del cicle anterior.
A la segona part de la funció mirem si estàvem (a la interrupció anterior) en la part superior (Pols = 1) o en la inferior (Pols = 0). En la superior comprovem si estem per sota de la meitat, en quin cas voldria dir que ja deixem d'estar a la part superior. Una comprovació similar fem en la part inferior però comprovant que hem passat de mig període com a mesura per filtrar soroll. En tots dos casos no fem res fins que hem detectat que estem per sobre o per sota del mig sis vegades (que comptem amb la variable Comp) per tal de filtrar soroll. Quan ja està clar que estem per sota del valor mig desactivem Pols i calculem uns nous valors de Max, Min i Centre a partir del pols que acabem de veure. D'altra banda, quan ja està clar que estem per sobre del valor mig actualitzem el valor de la variable Periode i reinicialitzem la variable Temps. En tots dos casos reiniciem Comp.
Els valors de les variables només són fiables quan ja hem vist dos inicis de pols. Tenim dos bits de control que ens indiquen si les dades són estables o no. Inicialment els bits Tenim1 i Estable valen zero. Quan s'ha detectat el primer inici de pols s'activarà Tenim1 i quan es detecti el segon s'activarà Estable.
A la tercera part es mira si el temps transcorregut és superior a 2,048 s (la variable Temps és més gran que 1024) i si és així es reinicialitzen totes les variables per tornar a buscar el primer pols.

Disposarem també d'una pantalla paral·lel on podrem mostrar, per exemple, les pulsacions per minut. També podem mostrar altres valors o escriure text.
Si, per exemple, ens interessés mostrar les pulsacions a la pantalla, podríem comptar quants polsos es fan en un temps determinat. Per comptar el temps, podem incrementar una variable apropiada sabent que la funció d'interrupció s'executa cada 2 ms.
El programa de prova que conté la funció d'interrupció que hem comentat és el següent. Podem observar que en el bucle principal no hi ha gran cosa, només escrivim el valor de la variable Periode a la pantalla.
#pragma config FOSC = INTRCIO, WDTE = OFF, PWRTE = OFF, MCLRE = OFF, CP = OFF #pragma config CPD = OFF, BOREN = OFF, IESO = OFF, FCMEN = OFF #include "pic16f690.h" // Carrega el fitxer d'adreces i paràmetres del PIC 16F690 #define ValComp 6 // Nombre de repeticions per eliminar soroll #include <xc.h> // Carrega el fitxer de funcions necessari per al compilador XC8 #define _XTAL_FREQ 4000000 // La freqüència del rellotge és 4 MHz
unsigned char Lectura; // Valor llegit a l'entrada analògica unsigned int Temps; // S'incrementa cada cop que es fa la interrupció (cada 2 ms) unsigned char Max; // Valor màxim del senyal en un període unsigned char Min; // Valor mínim del senyal en un període unsigned char Centre; // Valor mig del senyal en un període unsigned int Periode; // Quart de període // Guarda la meitat del nombre d'interrupcions entre dos inicis de pols unsigned char Comp; // Comptador per filtrar soroll unsigned char Port; // Valor que enviarem al port C bit Pols; // S'activa quan el senyal està per sobre del valor mig bit Tenim1; // S'activa quan ja hem detectat l'inici del primer pols bit Estable; // S'activa quan ja hem detectat l'inici del segon pols char Digits[5]; // Variable amb el número dígit a dígit // Digits[0] són les unitats
// Definició de les funcions que farem servir void BCD(unsigned int Valor); // Converteix un nombre a BCD i a ASCII void EnviaL(char Caracter); // Envia un caràcter void Cursor(char Filera, char Columna); // Posiciona el cursor (filera 1 a 2 i columna 1 a 32, segons pantalla)
void main (void) {
OPTION_REG = 0b10000010; // Configuració de Timer0
// Com a temporitzador basat en rellotge
// 010 - Factor d'escala de 8
// I resistències de pull-up desactivades (valor per defecte)
TRISA = 0xFF; // Posa tots els bits del port A com a entrada
TRISB = 0; // Tot el port B és de sortida
TRISC = 0; // Tot el port C és de sortida
ADCON1 = 0b00010000; // Posa el conversor a 1/8 de la freqüència
TXSTAbits.BRGH = 1; // Configuració de velocitat
BAUDCTLbits.BRG16 = 0; // Paràmetre de velocitat de 8 bits
SPBRG = 25; // Velocitat de 9600 baud
TXSTAbits.SYNC = 0; // Comunicació asíncrona
TXSTAbits.TX9 = 0; // Comunicació de 8 bits
RCSTAbits.SPEN = 1; // Activa comunicació sèrie
TXSTAbits.TXEN = 1; // Activa comunicació
ANSEL = 0b00000101; // Configura port AN0 i AN2 com entrades analògiques
ADCON0 = 0b00001001; // activa el conversor A/D connectat a AN2
// amb el resultat justificat per l'esquerra
Temps = 0; // Inicialització de variables
Periode = 0;
Pols = 0;
Tenim1 = 0;
Estable = 0;
Centre = 127;
Max = 127;
Min = 127;
Port = 0b00000000; // Tots els bits a zero
PORTC = Port; // Apaguem tots els LED
Comp = ValComp; // Nombre de repeticions per eliminar soroll
TMR0 = 7; // Nova preselecció del Timer
INTCON = 0b10100000; // Activa les interrupcions globals i la de Timer0
while (1) { // Inici del bucle de programa
Cursor(1, 1); // Posició
BCD(Periode);
for (int k = 5; k > 0; k--) {
EnviaL(Digits[k-1]); // Envia un caràcter
}
__delay_ms(1000); // Retard d'un segon
}
}
void BCD(unsigned int Valor) {
Digits[0] = Valor % 10; // Unitats
Valor = Valor / 10;
Digits[1] = Valor % 10; // Desenes
Valor = Valor / 10;
Digits[2] = Valor % 10; // Centenes
Valor = Valor / 10;
Digits[3] = Valor % 10; // Milers
Digits[4] = Valor / 10; // Desenes de milers
for (int j = 0; j < 5; j++){ // 5 dígits
Digits[j] = Digits[j] + '0'; // Li sumem el codi ASCII de 0
}
if (Digits[4] == '0') { // Mirem si el primer dígit és 0
Digits[4] = ' '; // Si ho és, hi posem un espai
if (Digits[3] == '0') { // I mirem si ho és el segon
Digits[3] = ' '; // Si ho és, hi posem un espai
if (Digits[2] == '0') { // I mirem si ho és el tercer
Digits[2] = ' '; // Si ho és, hi posem un espai
if (Digits[1] == '0') { // I mirem si ho és el quart
Digits[1] = ' '; // Si ho és, hi posem un espai
} // El 0 de les unitats el mostrarem sempre
}
}
}
}
void EnviaL(char Caracter) {
TXREG = Caracter; // Agafa el caràcter i l'envia
_delay(5); // Donem temps
while (PIR1bits.TXIF == 0) // Esperem que s'acabi d'enviar
; // No fem res
}
void Cursor(char Filera, char Columna) {
char Posicio = 0; // Variable per a calcular la posició
if (Filera == 2) {
Posicio = 64; // La primera columna de la segona fila és 64;
}
if (Columna > 0 && Columna < 33) { // Comprovem que sigui un valor raonable
Posicio = Posicio + Columna; // Sumem les adreces
Posicio = Posicio - 1; // Restem 1 perquè numera des de 0
}
Posicio = Posicio + 128; // Posa el bit de posicionat a 1
EnviaL(254); // Control de la posició del cursor
EnviaL(Posicio); // Canvia el cursor de lloc
}
void interrupt temporit(void) { // funció d'interrupcions
if (INTCONbits.T0IF) { // Comprovem que hi ha interrupció per Timer 0
TMR0 = 7; // Nova preselecció
INTCONbits.T0IF = 0; // Desactivem el bit
ADCON0bits.GO = 1; // Inicia la conversió
while (ADCON0bits.GO == 1) // Mentre no acabi
; // ens esperem
Lectura = ADRESH; // Llegeix el valor (8 bits)
Temps++; // Incrementem Temps
if (Lectura > Centre) {
if (Lectura > Max) {
Max = Lectura; // Guarda el nou màxim
}
} else {
if ((Temps > Periode) && (Lectura < Min)) {
Min = Lectura; // Guarda el nou mínim
}
}
if (Pols == 1) {
if (Lectura < Centre) {
Comp--; // Decrementa Comp
if (Comp == 0) {
Pols = 0; // Desactiva Pols
Centre = ( (unsigned int)Max + (unsigned int)Min )/2;
Max = Centre;
Min = Centre;
Comp = ValComp; // Nombre de repeticions per eliminar soroll
}
}
} else {
if ((Temps > Periode) && (Lectura > Centre)) {
Comp--; // Decrementa Comp
if (Comp == 0) {
Pols = 1; // Activa Pols
Periode = Temps/2; // Nou valor de Periode
Temps = 0; // Reiniciem Temps
Comp = ValComp; // Nombre de repeticions per eliminar soroll
if (Tenim1 == 0) {
Tenim1 = 1; // Ja tenim un inici de pols
} else {
if (Estable == 0) {
Estable = 1; // Ja tenim dos inicis de pols
}
}
}
}
}
if (Temps > 1024) {
Temps = 0; // Reinici de variables
Periode = 0;
Pols = 0;
Tenim1 = 0;
Estable = 0;
Centre = 127;
Max = 127;
Min = 127;
Comp = ValComp; // Nombre de repeticions per eliminar soroll
}
if ( Pols ) {
Port = Port | 0b00000001; // Activa bit 0
} else {
Port = Port & 0b11111110; // Desactiva bit 0
}
if ( Tenim1 ) {
Port = Port | 0b00001000; // Activa bit 3
} else {
Port = Port & 0b11110111; // Desactiva bit 3
}
if ( Estable ) {
Port = Port | 0b00000100; // Activa bit 2
} else {
Port = Port & 0b11111011; // Desactiva bit 2
}
PORTC = Port; // Apaguem tots els LED
}
}

Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.