Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

PIC16F887 misreads serial data. Timing issue?

Status
Not open for further replies.

iNfraNe

New Member
Hello guys!

This is my first time on this forum and in need of some help.

I've been trying to read incoming MIDI serial data (31250 kbps, start bit, 8 data bits, stop bit) on the RB5 pin. The MIDI data is connected via an optocoupler (6N137) directly to the pin, which is pulled up by the weak-pull-up feature built into the pic. The PIC runs on either an external crystal (8MHz, 22pF caps) or the internal 8MHz oscillator.

In the software, I poll the RB5 pin until it is low, then wait 48 usec (start bit + 16 usec to reach the middle of the first bit), read the bit, store it and wait the appropriate amount of time for the next bit. After storing 8 bits, I check for the presence of the stop bit and then the program goes into a stall.

With the debug feature of the PicKit2 I've been able to read the stored data. I am sending this midi message:
1011cccc 0nnnnnnn (where c = the midi channel number, n = CC number. I've left out the start/stop bit here for ease of viewing)
And the computer reads this message perfectly, so the sending part works.

However, when I look at the stored value for the serial data, it is not correct. By changing the value for c and for n, I've managed to decipher what it IS recording and come to the following conclusion:

It records the bits too slow!
Code:
scccc1101ssnnnnnnn0s
 1 234 56 78

Now, seeing how this happens both when using external or internal oscillators, I have no clue where to look. What can destabalize the clock? Anything in the hardware/software I should look at? Can the debug mode of the PicKit2 cause destabalization?

Thank you in advance for your help,

Bart

PS, I've included my midi_in code:
Code:
midi_in 
	btfsc   PORTB,5         ;Detect start bit
	goto    midi_in
;
;   Found start...could be as much as 4 cycles late if start happened
; just after the btfsc.  Not a problem.  Wait 32 cycles (16us) to get
; to center of start bit.
;
get_midi_message
	movlw   7               ;(1) Started -- get to center of bit-time
	movwf   z               ;(1)

m_in0   nop                    ;(6 x 4) + 3 = 27 cycles
	decfsz  z,F
	goto    m_in0           ;/

	movlw   8               ;(1) Eight bits of data to get
	movwf   l               ;(1)
	clrf    recv            ;(1)  [32 cycles to here]
;
;   Delay one bit-width (64 cycles) to get to center of LSB.
; Gather the bits into recv.
;
m_in1   movlw   14              ;(1)
	movwf   z               ;(1)

mdy_0   nop                     ;\
	decfsz  z,F             ;((13 x 4) + 3) = 55
	goto    mdy_0           ;/

	bcf     STATUS,C        ;(1) Default 0
	rrf     recv,F          ;(1) Put 0 in MSB of recv, then
	btfsc   PORTB,5         ;(1/2) sample the serial data and
	bsf     recv,7          ;(1) set the bit if sample was=1
	decfsz  l,F             ;((7 x 3) + 2) = 23, Do all bits
	goto    m_in1           ;/ [total cycles = 64, MSB = 63]

	bsf     flags,4         ;Say byte is good (test below). [MSB=64]
;
;   Time to first third of stop bit.  (55 cycles)
;
	movlw   18              ;(1)
	movwf   z               ;(1)

mdy_1   decfsz  z,F             ;((17 x 3) + 2) = 53
	goto    mdy_1           ;/

	btfss   PORTB,5         ;Stop bit showed up?
	bcf     flags,4         ;If not, indicate framing error

	retlw   0
 
Bart,

There's nothing wrong with doing serial reception that way and it clearly shows that you have a good understanding of asynchronous data, but you are making life unnecessarily hard for yourself because the 16F887 has a built in serial UART.

It's only necessary to choose your clock frequency and internal UART settings for 31250 baud, 8 bits, no parity, and the UART will receive whole serial bytes for you while your code is doing other things, like interpreting the information already received. Once it has collected a whole byte for you, you can get it with a single read from the receiver register.

You will almost certainly have to start buffering your input at some point because there will probably be situations where data arrives faster than you can deal with it - MIDI is rhythmic in nature - heavy activity 'on the beat', when lots of events will arrive all at once - but quieter in the spaces in between, where your code will have a chance to catch up again.
 
Yes, use the internal UART (if the polarity is wrong, then use an external transistor to invert it) - and I would suggest NOT using the internal weak pullups, use a MUCH lower value external resistor instead.

If you google there are lot's of PIC MIDI examples out there.
 
Thank you both for your quick responses.

I was unaware of the existance of the UART on this PIC (gotta read the datasheet right xD). Anyway, I know this is a very bad reason, but I've already built the PCB and I'd rather it would just work the way it is now. If all else fails I will make a jumper wire connection between RB5 and RC7 (RX), but for now I'd like to know what's going wrong here.

As for the weak pull-up, I've also trying pulling it up by a 220 ohm resistor, which gave the same faulty result. The data sheet says that the respons time of the optocoupler is well below the microsecond range of the serial data, so I've ruled this out as the problem.

Thing is, I've succesfully used the same code on a pic16f690, without any timing problems using a crystal... Will anything affect the clock the way it does now?

I've tried using a scope to see the serial data or the clock, but I've only got either my soundcard (thus limited to 44.1 kHz) or a prehistoric scope which is really not useful for digital stuff... So I'm kind of limited in my debugging methods here.

Any ideas?
 
Thank you both for your quick responses.

I was unaware of the existance of the UART on this PIC (gotta read the datasheet right xD). Anyway, I know this is a very bad reason, but I've already built the PCB and I'd rather it would just work the way it is now. If all else fails I will make a jumper wire connection between RB5 and RC7 (RX), but for now I'd like to know what's going wrong here.

As for the weak pull-up, I've also trying pulling it up by a 220 ohm resistor, which gave the same faulty result. The data sheet says that the respons time of the optocoupler is well below the microsecond range of the serial data, so I've ruled this out as the problem.

The speed of the opto-coupler is limited by how fast the pullup cam actually 'pull up', and using the weak internal pullups is probably limiting it?.

Thing is, I've succesfully used the same code on a pic16f690, without any timing problems using a crystal... Will anything affect the clock the way it does now?

I've tried using a scope to see the serial data or the clock, but I've only got either my soundcard (thus limited to 44.1 kHz) or a prehistoric scope which is really not useful for digital stuff... So I'm kind of limited in my debugging methods here.

Any ideas?

Try the serial code from my tutorials, you will have to adjust the timing loops though for 8MHz and for MIDI.
 
I've reprogrammed the midi sort of based of your RS232 code. I think the delay loops in your tutorial are off tho, but I might be wrong.

Anyhow, I don't know why, but with the new code it seems to just work. Yes, also with the weak pull up :) Thank you for your help!

If anyone wants a working 8MHz midi in code:
Code:
midi_in ; wait for start bit
	btfsc   PORTB,5         ;Detect start bit
	goto    midi_in
; Get the midi message at 31250 bps (bitlength 32 us). Startbit = 0, stopbit = 1, 8 data bits. Get 10 bits
get_midi_message
	; midi message found, delay half a bit
	; v 4 cycles
	bcf flags,4 ; set midi message received correctly (will be set wrong on midi error)
	movlw 0x08 ;record 8 data bits
	movwf midi_bitcount
	clrf midi_recv ;clear the midi receive register
	; v delay 26 cycles
	movlw 0x08
	movwf d1
	midi_halfbit_delay ; (7*3)+2 = 23
		decfsz d1,f
		goto midi_halfbit_delay
	nop
	; delay the start bit (64 cycles + 2 cycles left to check for the midi)
	movlw 0x15 
	movwf d1
	btfsc PORTB,5 ; check if start bit is still present, if not, and with an error flag
		goto midi_error
	midi_start_bit_delay ;(20*3)+2=62
		decfsz d1,f
		goto midi_start_bit_delay
;up to here, 96 cycles burnt
midi_record_bit ; (4+56+4)=64 per loop
	; v 4 cycles
	btfss PORTB,5 ; test the input
	bcf STATUS,C
	btfsc PORTB,5
	bsf STATUS,C
	; v delay 56 cycles
	movlw 0x12
	movwf d1
	midi_bit_delay ; (17*3)+2=53
		decfsz d1,f
		goto midi_bit_delay
	nop
	; v 4 cycles
	rrf midi_recv ;rotate the PORTB,5 value into the MSB of recv
	decfsz midi_bitcount,f ;decrement bitcounter until 8 bits have completed
	goto midi_record_bit
	nop
midi_end
	; if correct, exits 4 cycles "late" (compared to center of stopbit)
	btfss PORTB,5 ;did the stop bit arrive?
		goto midi_error ;no! flag an error
	; midi received succesfully, return!
	return 
midi_error
	; flag unsuccesful transfer of serial data
	bsf flags,4
	; return from midi in with a flagged error
	return
 
A few rules for MIDI -

1) Use the UART. You're making life harder on yourself by not doing so. Pins 25 (RC6/TX/CK) and 26 (RC7/RX/DT) are the on-chip serial UART pins.

2) MIDI is a current loop interface, so the receive side will require the use of an optoisolator. This optoisolator MUST be a high speed type (rise time less than 2uS) as 1 bit time is only 32uS (32uS x 10 bits = 320uS per received byte, which is the period of the 31.25K baud rate). The Sharp PC900 and the 6N138 are the most commonly used opto's in MIDI applications, with the 6N138 being the most common.

3) MIDI only allows for an error percentage of +/-1% error. Being off in your baud rate by just 1uS will cause a 3% error per bit, which exceeds this spec. Use of the internal oscillator WILL cause framing error issues because of this...I know this from experience. Therefore, a crystal oscillator MUST be used in MIDI applications. Here are the SPBRG values for both low and high speed BRG modes -

Low Speed BRG (TXSTA,BRGH = 0) -

4MHz - 0x01
8MHz - 0x03
12MHz - 0x05
16MHz - 0x07
20MHz - 0x09

High Speed BRG (TXSTA,BRGH = 1) -

1MHz - 0x01
4MHz - 0x07
8MHz - 0x0F
12MHz - 0x17
16MHz - 0x1F
20MHz - 0x27

Shown below is the preferred schematic for a 6N138 optoisolator for MIDI receive (even though the lines on pins 6 and 7 are shown crossing, there is no physical connection between the two) -

**broken link removed**

You can buffer the TX output from the PIC optionally, but not really needed as MIDI is only a 5mA current loop and PIC outputs can source/sink at least 3x that (8051 processors require it as their source current isn't enough to drive the current loop on its own).

I am a MIDI software and hardware developer and am very well versed on the PIC 16F886/887 as well as the 8051, so feel free to ask any questions you may have on the subject.
 
Last edited:
Hi Jon,

thank you for your reply. I am now doing all of the above, except for the UART, and it is indeed working :)
 
OK...should you decide to use the UART (I think you'll be much happier doing this and will decrease software overhead immensely if you do plus will have the option of using UART interrupt for MIDI receive), here is a code template you can build on that will auto-set the SPBRG value as long as you define the XTAL_FREQ variable with your crystal frequency -

Code:
		list			p=16F887, r=dec, w=-302
		include			<P16F887.INC>
		__config		_CONFIG1, _LVP_OFF & _FCMEN_OFF & _IESO_OFF & _BOR_OFF & _PWRTE_ON & _WDT_OFF & _HS_OSC
		
;NOTE - Use _XT_OSC instead of _HS_OSC if using a 4MHz crystal. _HS_OSC is for crystal values from above 4MHz up to 20MHz.
;This code is defaulted for use with a 16MHz crystal.

;*********************************************
;
;		Variables for UART SPBRG setup
;
;*********************************************


#define		XTAL_FREQ		16000000			;16MHz crystal (change this to your crystal frequency)
#define		BAUD			31250				;baud rate for MIDI specification
#define		BRGVALUE		((XTAL_FREQ / BAUD) / 64) - 1

;*********************************************
;
;		Variables for Interrupt Software Stack
;
;*********************************************

		cblock			0x70				;interrupt stack
					W_TEMP
					STATUS_TEMP
					PCLATH_TEMP
					RX_TEMP
		endc

;*********************************************
;
;		Start Here
;
;*********************************************

		org			0x0000				;reset vector
		goto			START

		org			0x0004				;interrupt vector
		movwf			W_TEMP				;store W
		swapf			STATUS,W			;store STATUS
		movwf			STATUS_TEMP
		banksel			0				;default to bank 0
		movfw			PCLATH				;store PCLATH
		movwf			PCLATH_TEMP
			
;*********************************************
;
;		Interrupt Handler
;
;*********************************************

;place interrupt handler here if applicable

ISRExit		movfw			PCLATH_TEMP			;restore PCLATH
		movwf			PCLATH
		swapf			STATUS,W			;restore STATUS
		movwf			STATUS
		swapf			W_TEMP,F			;restore W
		swapf			W_TEMP,W
		retfie							;return to main code

;*********************************************
;
;		Initialization Routine
;
;*********************************************

START		clrf			PORTA				;clear port latches
		clrf			PORTB
		clrf			PORTC
		clrf			PORTD
		clrf			PORTE

		banksel			ANSEL				;bank 3
	
		clrf			ANSEL				;all pins digital I/O mode
		clrf			ANSELH
	
		banksel			TRISA				;bank 1
		
		clrf			TRISA				;all ports output
		clrf			TRISB
		movlw			b'11000000'			;RC0-RC5 output
		movwf			TRISC				;RC6 UART TX, RC7 UART RX
		clrf			TRISD
		clrf			TRISE

		movlw			BRGVALUE			;set baud rate
		movwf			SPBRG
		clrf			TXSTA				;asynchronous serial mode, low speed BRG
		bsf			TXSTA,TXEN			;enable transmit
	
		banksel			0				;bank 0

		bsf			RCSTA,SPEN			;enable serial port
		bsf			RCSTA,CREN			;enable continuous receive

;*********************************************
;
;		Place Main Code Here
;
;*********************************************		

		end

With this code, you can use crystal values between 4 and 20MHz in 2MHz increments (i.e. 4MHz, 6MHz, 8MHz, etc etc). You can either periodically check the UART RX interrupt flag (PIR1,RCIF) to see if data has been received or you can enable the serial receive interrupt and write an interrupt handler that transfers the received data from RCREG to some other memory location so incoming MIDI data will be removed from the receive buffer automatically (safest approach to prevent buffer overrun error).

For transmitting you would load the data into W first, then poll the transmit buffer clear flag (PIR1,TXIF) until it goes high, then transfer the transmit data from W to TXREG and the UART does the rest. The function routines are shown below -

Code:
;*********************************************
;
;		Place Main Code Here
;
;*********************************************

MAIN		btfss			PIR1,RCIF			;rx data present?
		goto			$-1				;no, check again
		call			MIDIRx				;yes, get data
		
;continue with main code




;*********************************************
;
;		Serial Functions
;
;*********************************************		

;serial receive - received data will be placed in RX_TEMP 

MIDIRx		movfw			RCREG				;yes, place received data in W
		movwf			RX_TEMP				;store received data into RX_TEMP
		btfss			RCSTA,OERR			;rx overrun error?
		btfsc			RCSTA,FERR			;no, rx framing error?
		call			SerErrClr			;yes, clear serial error
MIDIRxExit	return							;no error, return to main code

;serial transmit - place transmit data into W prior to calling this routine

MIDITx		btfss			PIR1,TXIF			;tx buffer clear?
		goto			$-1				;no, check again
		movwf			TXREG				;yes, drop tx data into tx buffer
		return							;return to main code
		
;serial overrun/framing error clear
		
SerErrClr	bcf			RCSTA,CREN			;disable rx
		movfw			RCREG				;read rx buffer twice to clear buffer
		movfw			RCREG
		bsf			RCSTA,CREN			;enable rx
		return							;return to main code

For the transmit, say you wanted to send a controller change message to controller 80 on MIDI channel 5 with a data value of 127. You could either do it this way -

Code:
		movlw			0xB4				;control change MIDI channel 5
		call			MIDITx
		movlw			0x50				;controller 80
		call			MIDITx
		movlw			0x7F				;data value 127
		call			MIDITx

Or you could buffer the messages by setting up MIDI message byte variables at the top of the code, then call up a "message send" routine -

Code:
;place before first "org" statement in code

MIDISTAT	EQU			0x20
MIDICONT	EQU			0x21
MIDIDATA	EQU			0x22

;main code section

		movlw			0xB4				;control change MIDI channel 5
		movwf			MIDISTAT			;to status buffer
		movlw			0x50				;controller 80
		movwf			MIDICONT			;to controller buffer
		movlw			0x7F				;data value 127
		movwf			MIDIDATA			;to data buffer
		call			MIDISend
		
;continue with main code
		
		
MIDISend	movfw			MIDISTAT			;send status byte
		call			MIDITx
		movfw			MIDICONT			;send controller byte
		call			MIDITx
		movfw			MIDIDATA			;send data byte
		return

The second method makes things a bit more universal.
 
Last edited:
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top