Diccionari.cat
Optimot, consultes lingüístiques

caHome tachometerHDHD-tacho-part11

Tacòmetre Digital per a Harley Davidson Sportster (Part 11 - Codi font i llibreries)

Part1 / Part2 / Part3 / Part4 / Part5 / Part6 / Part7 / Part8 / Part9 / Part10 / Part11 / Part12 / Part13 / Part14 / Part15

Un cop pujat el codi al nou repositori de Github (DIGTACHO), en aquesta entrada miraré de resumir les parts més importants del projecte. Com es veu a continuació, caldria separar els fitxers del repositori en 4 grups diferents:

Llibreries genèriques

  • cdefs.h
  • inttypes.h
    Nota: altres llibreries necessàries per compilar aquest projecte com p18f2553.h, timers.h, string.h, usart.h, stdio.h, pwm.h ja estan incloses al software MPLAB de Microchip i per això no estan incloses aquí.

Funcions relacionades amb el protocol SAE J1850

  • j1850.c
  • j1850.h

Funcions relacionades amb el driver MM5450

  • MM5450.c
  • MM5450.h

Main

  • macros.h
  • main.c

He mirat que hi hagi anotacions (en anglès) a tot el fitxer per entendre què és el que fa cada part. De totes maneres, per no fer massa llarga aquesta entrada, copiaré a continuació les parts de codi que considero més interessants:

Codi destacat a j1850.h

Definició del timming i la corresponent funció usant el Timer0 del PIC d’acord amb l’Standard SAE J1850.

// define J1850 VPW timing requirements in accordance with SAE J1850 standard
// all pulse width times in us 
// receiving pulse width
#define RX_SHORT_MIN	us2cntT0CON8(34)      // minimum short pulse time
#define RX_SHORT_MAX	us2cntT0CON8(96)      // maximum short pulse time
#define RX_LONG_MIN	us2cntT0CON8(96)      // minimum long pulse time
#define RX_LONG_MAX	us2cntT0CON8(163)     // maximum long pulse time
#define RX_SOF_MIN	us2cntT0CON8(123)     // l'he posat a 153 (abans 163) minimum start of frame time
#define RX_SOF_MAX	us2cntT0CON8(279)     // l'he posat a 249 (abans 239) maximum start of frame time
#define RX_EOD_MIN	us2cntT0CON8(163)     // minimum end of data time
#define RX_EOD_MAX	us2cntT0CON8(239)     // maximum end of data time
#define RX_EOF_MIN	us2cntT0CON8(239)     // minimum end of frame time, ends at minimum IFS
#define RX_BRK_MIN	us2cntT0CON8(239)     // minimum break time
#define RX_IFS_MIN	us2cntT0CON8(280)     // minimum inter frame separation time, ends at next SOF

Codi destacat a j1850.c

Definició de la funció que tractarà amb qualsevol missatge que arribi pel bus J1850.

/* 
**--------------------------------------------------------------------------- 
** Abstract: Receive J1850 frame (max 12 bytes)
**           Rebre trama J1850 (màxim 12 bytes)
** Parameters: Pointer to frame buffer / punter al missatge al buffer
** Returns: Number of received bytes OR in case of error, error code with bit 7 set as error indication
**          Número de bytes rebuts o en cas d'error, el codi d'error amb el bit 7 usat com a inidcador d'error
**
** NOTE: This file is based on a project from Michael for arduino platform.
** (www.Mictronics.de)
** Code has been modified in order to be compatible with Microchip PIC MCUs.
** Based on Mictronics file j1850.c v1.07
**--------------------------------------------------------------------------- 
*/ 
uint8_t j1850_recv_msg(uint8_t *msg_buf )
{
	uint8_t nbits;			// bit position counter within a byte
	uint8_t nbytes;			// number of received bytes
	uint8_t bit_state;		// used to compare bit state, active or passive
	uint16_t tcnt1_buf;		//XM: used to compare counter
	
	/*wait for responds (if 100us pass without detection of a SOF--> answer with an error)*/	
	timer0_16start(CK16B128);

	while(!is_vpw_active())	// run as long bus is passive (IDLE)
	{
		if(timer0_get() >= WAIT_300us)	// check for 300us
		{
			timer0_stop();
			
			return J1850_RETURN_CODE_NO_DATA | 0x80;    // error, no responds within 100us
		}
	}
	// wait for SOF
	timer0_start(CK8);	// restart timer1
	while(is_vpw_active())	// run as long bus is active (SOF is an active symbol)
	{
		if(timer0_get() >=  RX_SOF_MAX) return J1850_RETURN_CODE_BUS_ERROR | 0x80;	
                // error on SOF timeout
	}

	
	timer0_stop();
	if(timer0_get() < RX_SOF_MIN) return J1850_RETURN_CODE_BUS_ERROR | 0x80; 
             // error, symbol was not SOF

	bit_state = is_vpw_active();   // store actual bus state
	timer0_start(CK8);
	for(nbytes = 0; nbytes < 12; ++nbytes)
	{
		nbits = 8;
		do
		{
			*msg_buf <<= 1; 	//this is for refer bit by bit for every for cycle

			while(is_vpw_active() == bit_state) 
                        // compare last with actual bus state, wait for change
			{
	
				if(timer0_get() >= RX_EOD_MIN	)	// check for EOD symbol
				{
					timer0_stop();
					return nbytes;	// return number of received bytes
				}
			}
			bit_state = is_vpw_active();	// store actual bus state
			tcnt1_buf = timer0_get();
			timer0_start(CK8);

			if( tcnt1_buf < RX_SHORT_MIN) return J1850_RETURN_CODE_BUS_ERROR | 0x80;   
                        // error, pulse was to short

			// check for short active pulse = "1" bit
			if( (tcnt1_buf < RX_SHORT_MAX) && !is_vpw_active() )
				*msg_buf |= 1;

			// check for long passive pulse = "1" bit
			if( (tcnt1_buf > RX_LONG_MIN) && (tcnt1_buf < RX_LONG_MAX) && is_vpw_active() )
				*msg_buf |= 1;

		} while(--nbits); 
                   // end 8 bit while loop. XM: Part of the Do-While. 
                   // While nbits is not 0 has to Do all that above and decrement on bit each time.
		
		++msg_buf;  // store next byte. 
                     // XM: Goes to the next memory direction, this means the next byte on the struct[]
	
	}	// end 12 byte for loop

	// return after a maximum of 12 bytes
	timer0_stop();	
	return nbytes;
}

Codi destacat a MM5450.c

Definició de la funció que enviarà la informació al LED Driver.

/* 
**--------------------------------------------------------------------------- 
** Abstract: Subroutine that sends all of the DATABITS to the chip. It begins by first sending the startbit, then it
**           sends all the bits in each byte of the ledArray.
**           Subrutina que envia tots els DATABITS al chip MM5450. Primer envia l'Start of Frame i després la resta de bits del ledArray
** Parameters: pointer ledArray
** Returns: none
**
** NOTE: This file is based on a project from l.e. hughes for arduino platform
** (http://www2.cs.uidaho.edu/) 
** Code has been modified in order to be compatible with Microchip PIC MCUs.
** Project: stairway_v_1_code.pde 
**--------------------------------------------------------------------------- 
*/ 

void sendDatabits(uint8_t *ledArray_buf) {

	uint8_t temp_byte;	// temporary byte store
	uint8_t nbits;		// bit position counter within a byte
	uint8_t MMbits;
	uint8_t nbytes;

	MMbits=35;
	nbytes=5;

	//Start of frame
  	MMClock_passive();
  	delay20us();
	MMData_active();
	delay20us();
  	MMClock_active();
	delay50us();
  	delay50us();
  	MMClock_passive();
  	delay20us();
	

	do
	{// end nbytes do loop
		temp_byte=*ledArray_buf;	//store byte termporary
		nbits=8;
		delay50us();
		//delay50us();
		delay50us();
		while(nbits-- && MMbits--)	//send 8 bits
		{
			if (temp_byte & 0x80){
				MMData_active();
				delay20us();
				MMClock_active();
	  			delay50us();
	  			MMClock_passive();
	  			delay20us();
			}else{
				MMData_passive();
				delay20us();
	  			MMClock_active();
	  			delay50us();
	  			MMClock_passive();
	  			delay20us();
			}
			temp_byte <<= 1;  // next bit (desplaça els bits cap a la dreta) 0101 -->0010
		}// end nbits while loop
		++ledArray_buf;	// next byte from buffer
	
	} while(--nbytes);
	MMData_passive();
		

}

Codi destacat a macros.h

Definició d’algunes dreceres que ajudaran a simplificar el codi.

// convert microseconds to counter values for each preescaler
//  x usec * (1 sec / 1000000 usec) * (CLOCK clks / 1 sec) * (1 cnt / 8 clks)
//((unsigned char) (((us) * ((unsigned long)(INT_CLK) / 8L) + 500000L) / 1000000L))
#define us2cntT0CON8(us) ((unsigned char) (((us) * ((unsigned long)(INT_CLK) / 8L) + 500000L) / 1000000L))
//  x usec * (1 sec / 1000000 usec) * (CLOCK clks / 1 sec) * (1 cnt / 64 clks)
#define us2cntT0CON64(us) ((unsigned char) (((us) * ((unsigned long)(INT_CLK) / 64L) + 500000L) / 1000000L))
//  x usec * (1 sec / 1000000 usec) * (CLOCK clks / 1 sec) * (1 cnt / 128 clks)
#define us2cntT0CON128(us) ((unsigned char) (((us) * ((unsigned long)(INT_CLK) / 128L) + 500000L) / 1000000L))
//  x usec * (1 sec / 1000000 usec) * (CLOCK clks / 1 sec) * (1 cnt / 256 clks)
#define us2cntT0CON256(us) ((unsigned char) (((us) * ((unsigned long)(INT_CLK) / 256L) + 500000L) / 1000000L))
//FOR TIMER3
//  x usec * (1 sec / 1000000 usec) * (CLOCK clks / 1 sec) * (1 cnt / 8 clks)
//((unsigned char) (((us) * ((unsigned long)(INT_CLK) / 8L) + 500000L) / 1000000L))
#define us2cntT3CON8(us) ((unsigned char) (((us) * ((unsigned long)(INT_CLK) / 8L) + 500000L) / 1000000L))

Definició de les dreceres per configurar els Timers 0, 1 i 3.

//REGISTER T0CON (TIMER 0)
//bit7 (TMR0ON)--> 1=Enables Timer0 / 0=Disables Timer0
//bit6 (T08BIT)--> 1=T0 as 8bit counter / 0=T0 as 16bit counter
//bit5 (T0CS)--> 1=Transtion on T0CK1 pin / 0=internal instruction cycle
//bit4  (T0SE)--> if bit5=0 not used
//bit3 (PSA) --> 1=T0 preescaler not assigned / 0=T0 preescaler assigned (bit2-1-0)
//bit2-1-0 (T0PS2-T0PS0)
/* Define Timer0*/
#define timer0_start(x)	T0CON=x;TMR0L=0;  // Timer0 enabled with a preescaler x
#define timer0_16start(x)	T0CON=x;WriteTimer0(0);  // Timer0 16bit enabled with a preescaler x
#define timer0_get()	TMR0L
#define timer0_stop()	T0CON=STOP;
#define INT_CLK	20000000L/4L

//TIMER1 - enumeration of the preescalers (16 bit counter 0-65535)(8 bit counter 0-255), internal clock = F_CPU/4
//bit7 (RD16)--> 1=Enables timer3 in one 16bit operation / 0= Enables timer3 in two 8bit operation
//bit 6(T1RUN): Timer1 system clock status bit --> fixed to 0
//bit5-4 (T1CKPS1:T1CKPS0) -->Preescaler 11(1:8),10(1:4),01(1:2),00(1:1)
//bit3 (T1OSCEN -Timer1 oscillator enable bit) fixed to 0
//bit2 (T1SYNC) fixed to 0
//bit1 (TMR1CS): 1: External Clock / 0: Internal clock Fosc/4 --> fixed to 0
//bit0 (TMR1ON): 1:Enables T1 / 0: Stops Timer1
/* Define Timer1*/
#define timer1_start(x)	T1CON=x;WriteTimer1(0);  // Timer3 16bit enabled with a preescaler x
#define timer1_get()	ReadTimer1()
#define timer1_stop() T1CON=T1STOP;

//TIMER3 - enumeration of the preescalers (16 bit counter 0-65535), internal clock = F_CPU/4
//REGISTER T0CON (TIMER 0)
//bit7 (RD16)--> 1=Enables timer3 in one 16bit operation / 0= Enables timer3 in two 8bit operation
//bit6 and 3 (T3CCP2:T3CCP1)--> fixed to 00
//bit5-4 (T3CKPS1:T3CKPS0) -->Preescaler 11(1:8),10(1:4),01(1:2),00(1:1)
//bit2 (T3SYNC) fixed to 0
//bit1 (TMR3CS): 1: External Clocl / 0: Internal clock Fosc/4
//bit0 (TMR3ON): 1:Enables T3 / 0: Stops Timer3
enum {
T3STOP	= 0b00000000,
T3CK8	= 0b10110001,
T3CK1	= 0b10000001
};

/* Define Timer3*/
#define timer3_start(x)	T3CON=x;WriteTimer3(0);  // Timer3 16bit enabled with a preescaler x
#define timer3_get()	ReadTimer3()
#define timer3_stop() T3CON=T3STOP;

Codi destacat a main.c

Definició de les llibreries incloses a MPLAB.

/*INCLUDES*/
#include <p18f2553.h>
#include <timers.h>		//TO:j1850 functions	T1:used on main 	T2:PWM for brightness control on Micrel IC    T3:MICREL
#include <string.h>
#include <usart.h>
#include <stdio.h>
#include <pwm.h> //Used to vary the brightness of the MICREL MM5450

Configuració del Timer2 per controlar la lluminositat dels 7 segments.

/******************************************************************
** Configuring timer 2 which provides timing for PWM
** TIMER_INT_OFF: disable timer interrupt
** T2_PS_1_4: Timer2 prescaling set to 16
** T2_POST_1_1: Timer2 postscaling set to 16
********************************************************************/
OpenTimer2(TIMER_INT_OFF & T2_PS_1_16 & T2_POST_1_4);
/******************************************************************
**  configuration of PWM Brightness regulation on MM5450
** PWM period =[(period ) + 1] x 4 x TOSC x TMR2 prescaler. The value of period is from 0x00 to 0xff
** PWM Period= [(30)+1]*4*(1/20e6)*16=0,099ms -->10Hz
*******************************************************************/
OpenPWM1(30); // configuring PWM module 1 --> aprox 1221Hz amb prescaler de 16

//set brightness 
brightness=60;
SetDCPWM1(brightness); // Range goes from (0-1023). 1023 sets PWM duty cycle 100% (full speed).

Configuració de la interrupció del PIC per tal de poder llegir qualsevol missatge que arribi pel bus de dades.

//CONFIG: EXTERNAL INTERRUPTION - RB0 (INT0)
	INTCON2bits.RBPU=0;	//pull-ups deactivated from ports RB (RB0 has alreday one on the circuit)
	INTCONbits.INT0IE = 1; 	//enable INT0 external interrupt
	INTCON2bits.INTEDG0=0;	//INT0 on falling edge.    It was inverted due to the Hardware is already inverted (j1850 vs TTL: 7V is 0V and 0V is 5V. 
				//If schematic changes and INTEDG0 goes to 1, j1850.h has to be changed as well (delete ! in (#define is_vpw_active()	!PORTBbits.RB0)
	RCONbits.IPEN = 1; 	//enable priority levels on interrupts
	INTCONbits.GIEH = 1; 	//enable all high-priority interrupts
INTCONbits.INT0IF = 0; //clear INT0 flag

Codi que permetrà diferenciar entre una pulsació curta al botó per canviar de mode i una pulsació llarga per canviar la lluminositat.

		/**************************************************************************************
		**************   BRIGHTNESS AND MODE CHANGE   *****************************************
		***************************************************************************************/
			if(BUTTON==0){			//rear switch depressed.
				if(counter_switch>25000){
					//do nothing, once switch is released brightness will change
				}else{
					counter_switch=counter_switch+1;
				}				
			}else if(BUTTON==1){		//switch released
				//do nothing
			}

			//change of mode
			if(counter_switch>2000 && counter_switch<10000 && BUTTON==1){
				counter_switch=0;
				if(mode==0){		//RPM
					mode=1;		//--> Fuel Consump
					LED_MODE0=0;
					LED_MODE1=1;
					LED_MODE2=0;
				}else if(mode==1){	//Fuel consump
					mode=2;		//--> Temp
					LED_MODE0=0;
					LED_MODE1=0;
					LED_MODE2=1;
				}else if(mode==2){	//Temp
					mode=3;		//--> Speed
					LED_MODE0=0;
					LED_MODE1=0;
					LED_MODE2=0;
				}else if(mode==3){	//speed
					mode=0;		//--> RPM
					LED_MODE0=1;
					LED_MODE1=0;
					LED_MODE2=0;
				}
			}else if(counter_switch>10000 && BUTTON==1){
			//max value=120, but for safety reasons (too much heat) is software limited to 60
				counter_switch=0;
				//brightness=brightness+10;
				if(brightness==60){		
					brightness=10;
				}else if(brightness==10){
					brightness=60;					
				} //values will be either 10 or 60
				SetDCPWM1(brightness);
			}

Tractament del missatge SAE que hi ha al buffer temporalment fins que un altre missatge nou arribi.

if (recv_nbytes & 0x50){	//Until first signal is not received it will show a "-"
	LATB=display7seg[17];		// "-"
}else{
	if(recv_nbytes & 0x80){	//in case of error
		//rpm[1]=(recv_nbytes && 0x0F);
	}else{		//use the array[2] to save the current value and to store new value in array[1]
		if (j1850_msg_buf[0]==0x28 && j1850_msg_buf[1]==0x1B && j1850_msg_buf[2]==0x10 && j1850_msg_buf[3]==0x02){   	//rpm
			rpm[2]=rpm[1]; 	//save the previous value
			rpm[1]= (((unsigned char)j1850_msg_buf[4]*0x100+(unsigned char)j1850_msg_buf[5])/4);			//0x100=256dec
			//way to know if engine on or not (and a filter to avoid strange values on display)
			if ((engon==0 && rpm[1]>500) || (engon==1 && rpm[1]<500)){
				comptengon=comptengon+1;
				if (comptengon>=4){
					engon=1;	//Engine is ON
					comptengon=0;
				}else{
					//no fer res
				}
			}else{
				comptengon=0;
			}

		}else if(j1850_msg_buf[0]==0xA8 && j1850_msg_buf[1]==0x3B && j1850_msg_buf[2]==0x10 && j1850_msg_buf[3]==0x03){	//Gear
			//current gear, 0xXX = 0x02,0x04,0x08,0x10,0x20, for gears 1-5
			//also filter to avoid strange gear display behaviour (it will check 4 times gear is the same before changing the display)
			gear[0]=j1850_msg_buf[4];
			if (comptcurrentgear==0){		//start counter
				nextgear=gear[0];
				comptcurrentgear=comptcurrentgear+1;
			}else if(comptcurrentgear>=3){	//display will be updated here
				if(mode==3){		
					LATB=display7seg[10];						
				}else{
					if(gear[0]==0x00 ){
						LATB=display7seg[0];
					}else if(gear[0]==0x02){
						LATB=display7seg[1];
					}else if(gear[0]==0x04){
						LATB=display7seg[2];
					}else if(gear[0]==0x08){
						LATB=display7seg[3];
					}else if(gear[0]==0x10){
						LATB=display7seg[4];
					}else if(gear[0]==0x20){
						LATB=display7seg[5];
					}else{
						LATB=display7seg[17];
					}
					comptcurrentgear=0;
				}
			}else{
				if(gear[0]==nextgear){
					comptcurrentgear=comptcurrentgear+1;
				}else{
					comptcurrentgear=0;
				}
			}

		}else if(j1850_msg_buf[0]==0xA8 && j1850_msg_buf[1]==0x49 && j1850_msg_buf[2]==0x10 && j1850_msg_buf[3]==0x10){	//Engine Temp
			temp[1]= (unsigned char)j1850_msg_buf[4]-40;

		}else if(j1850_msg_buf[0]==0x48 && j1850_msg_buf[1]==0x29 && j1850_msg_buf[2]==0x10 && j1850_msg_buf[3]==0x02){	//Speed
			speed[1]= (((unsigned char)j1850_msg_buf[4]*0x100+(unsigned char)j1850_msg_buf[5])/128);		//0x100=256dec
		}
	}
}

Codi inclòs a la rutina d’interrupció. Bàsicament, rebre el missatge i transmetre’l pel RS232.

/****************************************
***********INTERRUPCION ROUTINE********
*****************************************/

#pragma interrupt InterruptHandlerHigh

void InterruptHandlerHigh(){

char i;
char buffer[20];

recv_nbytes=j1850_recv_msg(j1850_msg_buf);
if(recv_nbytes & 0x80){
}else{
	i=0;
	while(i<recv_nbytes){
		USART_hex2ascii(j1850_msg_buf[i]);
		if(i==recv_nbytes-1){
			send_byte(0x0D);	//intro	
		}else{
			send_byte(0x20);	//space
		}
		i=i+1;
	}

}
INTCONbits.INT0IF = 0; //clear INT0 flag
}

Fins aquí els detalls del codi. Per més detall, aneu directament al repositori de Github: DIGTACHO.

Índex de projecte:
Part1 / Part2 / Part3 / Part4 / Part5 / Part6 / Part7 / Part8 / Part9 / Part10 / Part11 / Part12 / Part13 / Part14 / Part15

keywords: HD, harley, davidson, tachometer, tacho, tacòmetre, rpm, J1850, codi, llibreries