Problem Serial LCD on P0 at 2400 baud with HSERVO enabled

I am somewhat embarrassed :blush: to say that it has been a few months since I had the time to sit down and experiment on the Brat. I am trying to rearrange some of the servos and the like such that I could free up the pins for the hardware serial port and possibly the hardware I2C pins. This is the first time I reprogrammed the brat since converting from IDE 8.0.0.0 to 8.0.1.0. I thought I would do this in steps. First rearrange the servos and update the defines in the program that are associated with these servos. The first mistake I made was to try to use P9, which made it pretty noisy! I fixed that but then found that my LCD display on top was not working properly (mostly getting garbage). This LCD is a Sparkfun serial LCD that I have configured to run at 2400 baud as I had problems earlier at 9600 baud.

I found that if I commented out the ENABLEHSERVO command along with the other HSERVO commands, the LCD display worked fine. I extracted some of the BRAT code and included it below. Please note, there are two versions of the HANDLE_IRPD code as at first I thought it was the timer interrupt that might be causing the problems and so I tried to reconfigure this code to not require the interrupt. I have not fully debugged this code yet (probably timing issues), but it sometimes gets the right code from the TV remote…

Suggestions?

; Simple Test program to check the serial lcd with HSERVO enabled and possible timer running...

; define which LCD I am using...
;SEETRON	con	1
LCDPIN	con p0
#ifdef SEETRON
LCD_BAUD	con n2400
#else
LCD_BAUD	con i2400
#endif


;Interrupt init 
USE_HSERVO con 1
#ifdef USE_HSERVO
HSERVO_GROUPS con 4
ENABLEHSERVO 
#endif

ONINTERRUPT WKPINT_3,handle_irpd 

USE_TIMER con 1
#ifdef USE_TIMER
ONINTERRUPT TIMERAINT,handle_timera 
#endif

PMR5.bit3 = 1 
TMA=4   ;increments on clock/256 
ENABLE WKPINT_3 

#ifdef USE_TIMER
ENABLE TIMERAINT
#endif

LoopCount var word
lasttime var long 
overflowtime var long 
currenttime var long 
diff var long 
 
tmaCycle var long 
datacount var sbyte 
irpd_data var word 
command var byte 


; variables for LCD... Not sure how to pass an array to a gosub so.
LCD_String var byte(17)

lasttime = -1   ;indicate next data should be a startpulse 
;End of System setup.  Add user variables below this line. 

gosub LCD_Init

low 1 
low 2 
pause 1000 

LCD_String = "Hello Brat|"
gosub LCD_Print[1, 0]

command=0xFF 
LoopCount = 0
main 
	LoopCount = LoopCount + 1
	if LoopCount = 32767 then
		gosub LCDConvertNumToString[TCA]
		gosub LCD_Print[1,1]
		LoopCount = 0
	endif
	if(command<>0xFF)then 
		gosub LCDConvertNumToString[command]
		gosub LCD_Print[2,1]
		command = 0xff
	endif 
	goto main 
   
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Handle IRPD - try to not use timer interrupt as the largest value we need appears to be 195...

handle_irpd 

#ifdef USE_TIMER
   currenttime=overflowtime+TCA 
   if(currenttime<0)then         ;If we overflow to a negative number reset everything 
      overflowtime=0 
      currenttime=TCA 
      lasttime=-1 
   endif 
   if(lasttime<0)then         ;If negtive lasttime, reset 
      datacount=0 
      lasttime=currenttime 
   else                  ;else process pulses 
      datacount=datacount+1 
      diff=currenttime-lasttime 
      irpd_data=irpd_data>>1       
      if(diff>175 AND diff<195)then   ;If we get a start pulse reset datacount and irpd_data 
         irpd_data=0 
         datacount=0 
      elseif(diff>100 and diff<120)   ;we got a "1" pulse 
         irpd_data.bit10=1 
      elseif(diff>65 and diff<85)      ;we got a "0" pulse 
         irpd_data.bit10=0 
      else                     ;we got an invalid pulse so reset 
         lasttime=-1 
         resume 
      endif 
      if(datacount=11)then 
         lasttime=-1 
         command = irpd_data&0x7F 
      else 
         lasttime = currenttime 
      endif 
   endif    
   Resume 

#else

	tmaCycle=TCA 

 	if (irr1.bit6) then
 		irr1.bit6 = 0
 		datacount = -1
 		resume
 	endif
 	TMA = 0xc		; reset tma
 	TMA = 4			; divide by 256

   if(datacount<0)then         ;If negtive data count reset things... 
      datacount=0 
   else                  ;else process pulses 
      datacount=datacount+1 
      irpd_data=irpd_data>>1       
      if(tmaCycle>175 AND tmaCycle<195)then   ;If we get a start pulse reset datacount and irpd_data 
         irpd_data=0 
         datacount=0 
      elseif(tmaCycle>100 and tmaCycle<120)   ;we got a "1" pulse 
         irpd_data.bit10=1 
      elseif(tmaCycle>65 and tmaCycle<85)      ;we got a "0" pulse 
         irpd_data.bit10=0 
      else                     ;we got an invalid pulse so reset 
         datacount = -1
         resume 
      endif 
      if(datacount=11)then 
         datacount =-1 
         command = irpd_data&0x7F 
      endif 
   endif    
   Resume 
#endif

handle_timera 
   overflowtime=overflowtime+256 
   resume
   
;
; Serial LCD functions added by Kurt
;

LCD_Init
	; make sure SerLCD has a chance to finish initializing after power-up
	; Reset to 9600 and powerup... - Uncomment if having problems with LCD.
;	serout LCDPIN, I9600, [0x12]
	Pause(1000)
	
	; Now lets try setting it to 2400 baud.  - uncomment if having problems
;	Serout LCDPIN, I9600, [0x7c]	; 124, <control k>
;	pause 2
;	Serout LCDPIN, I9600, [11]	; 124, <control k>
#ifndef SEETRON	
	; Set the backlight to low
	Serout LCDPIN, LCD_BAUD, [0x7C, 0x80]	; 124, 128 - Off
#endif	

	; Output the clear screen command
	gosub LCD_WriteCommand[1] ;	// Clear the screen
	Pause(5)
	
	
	
	return

;
; Main LCD output function
;
line var sbyte
fPad var byte
LCD_Print[line, fPad]
	DISABLE WKPINT_3 
#ifdef USE_TIMER
	DISABLE TIMERAINT 
#endif
	if line = 1 then
		gosub LCD_WriteCommand[0x80]
	elseif line = 2		
		gosub LCD_WriteCommand[0xC0]
	endif
			
	Pause(1)

	for line = 0 to 14	; try now to write to last position as it appears to autoscroll...
		if fPad <> 2 and LCD_String(line) > 0  and LCD_String(line) <> "|" then
			serout LCDPIN, LCD_BAUD, [LCD_String(line)]
		else
			if fPad = 0 then
				goto LCDP_Enable
			endif
			fPad = 2
			serout LCDPIN, LCD_BAUD, " "]
		endif
		Pause(1)
	next
LCDP_Enable						
	ENABLE WKPINT_3 
#ifdef USE_TIMER
	ENABLE TIMERAINT 
#endif	
	return

;
;DISPLAY DATA RAM ADDRESSES
;CHARACTER 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
;       + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
;LINE 1 | 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
;LINE 2 | C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
;
ich var byte
iline var byte
LCD_SetPos[ich, iline]
	if iline = 1 then
		gosub LCD_WriteCommand[0x80+ich]
	else
		gosub LCD_WriteCommand[0xC0+ich]
	endif
	
	pause 10
	return			

; clears the display and resets the current location to the upper left */
LCD_ClearAndReturn

	gosub LCD_WriteCommand[1] ;		// clear display
	Pause(100)
	
	return

fRight var byte
ichShift var byte
LCD_ShiftDisplay[fRight, ichShift]
	
	if fRight <> 0 then
		fRight = 0x1c
	else
		fRight = 0x18
	endif		
	while (ichShift > 0)
		gosub LCD_WriteCommand[fRight]
		ichShift = ichShift -1
	wend
	return

; Send a command to the LCD, first send a command prefix character and then then command
cmd var byte
LCD_WriteCommand[cmd]
	serout LCDPIN, LCD_BAUD, [0xfe]
	pause 1
	serout LCDPIN, LCD_BAUD, [cmd]
	return

iCnT1 var byte
iCnt2 var byte	
iOut var sword		
LCDConvertNumToString[iOut]
	if iOut < 0 then
		LCD_String(0) = "-"
		iOut = -iOut
		iCnt1 = 1
		iCnt2 = 1
	else
		iCnt1 = 0	
		iCnt2 = 0	
	endif
	
	while (iOut > 0)
		LCD_String(iCnt1) = "0" + iOut // 10
		iOut = iOut / 10
		iCnt1 = iCnt1 + 1
	wend
	if iCnt1 = 0 then
		LCD_String = "0"
	else
		LCD_String(iCnt1) = 0
		iCnt1 = iCnt1 - 1
		
		while (iCnt1 > iCnt2)
			swap LCD_String(iCnt1), LCD_String(iCnt2)
			iCnt1 = iCnt1 - 1
			iCnt2 = iCnt2 + 1
		wend
	endif
	return

I must be missing something because I see no code to actually handle the servos(eg no hservo commands).

You are correct. Just enabling hservo casued the LCD to stop working, regardless of having work to do or not…

EDIT: Note - this code was stripped out of my version of your BRAT code, that I added LCD support to.

To make it easier to play with, it also has a tendancy to screw up output to S_OUT as well. Here is a modified version of the above with the LCD code stripped out. Note: the output starts to look better if either I disable the timer or the HSERVO.

;Interrupt init 
USE_HSERVO con 1
#ifdef USE_HSERVO
HSERVO_GROUPS con 4
ENABLEHSERVO 
#endif

ONINTERRUPT WKPINT_3,handle_irpd 

USE_TIMER con 1
#ifdef USE_TIMER
ONINTERRUPT TIMERAINT,handle_timera 
#endif

PMR5.bit3 = 1 
TMA=4   ;increments on clock/256 
ENABLE WKPINT_3 

#ifdef USE_TIMER
ENABLE TIMERAINT
#endif

LoopCount var word
lasttime var long 
overflowtime var long 
currenttime var long 
diff var long 
 
tmaCycle var long 
datacount var sbyte 
irpd_data var word 
command var byte 


; variables for LCD... Not sure how to pass an array to a gosub so.
LCD_String var byte(17)

lasttime = -1   ;indicate next data should be a startpulse 
;End of System setup.  Add user variables below this line. 

;gosub LCD_Init

low 1 
low 2 
pause 1000 

command=0xFF 
LoopCount = 0
main 
	LoopCount = LoopCount + 1
	if LoopCount = 32767 then
		gosub LCDConvertNumToString[TCA]
;		gosub LCD_Print[1,1]
		serout s_out, i2400, "Time: ", str LCD_String\16\0, 13, 10]
		LoopCount = 0
	endif
	if(command<>0xFF)then 
		gosub LCDConvertNumToString[command]
;		gosub LCD_Print[2,1]
		serout s_out, i2400, "Cmd: ", str LCD_String\16\0, 13, 10]
		command = 0xff
	endif 
	goto main 
   
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Handle IRPD - try to not use timer interrupt as the largest value we need appears to be 195...

handle_irpd 

#ifdef USE_TIMER
   currenttime=overflowtime+TCA 
   if(currenttime<0)then         ;If we overflow to a negative number reset everything 
      overflowtime=0 
      currenttime=TCA 
      lasttime=-1 
   endif 
   if(lasttime<0)then         ;If negtive lasttime, reset 
      datacount=0 
      lasttime=currenttime 
   else                  ;else process pulses 
      datacount=datacount+1 
      diff=currenttime-lasttime 
      irpd_data=irpd_data>>1       
      if(diff>175 AND diff<195)then   ;If we get a start pulse reset datacount and irpd_data 
         irpd_data=0 
         datacount=0 
      elseif(diff>100 and diff<120)   ;we got a "1" pulse 
         irpd_data.bit10=1 
      elseif(diff>65 and diff<85)      ;we got a "0" pulse 
         irpd_data.bit10=0 
      else                     ;we got an invalid pulse so reset 
         lasttime=-1 
         resume 
      endif 
      if(datacount=11)then 
         lasttime=-1 
         command = irpd_data&0x7F 
      else 
         lasttime = currenttime 
      endif 
   endif    
   Resume 

#else

	tmaCycle=TCA 
 	TMA = 0xc		; reset tma
 	TMA = 4			; divide by 256

 	if (irr1.bit6) then
 		irr1.bit6 = 0
 		datacount = -1
 		resume
 	endif

   if(datacount<0)then         ;If negtive data count reset things... 
      datacount=0 
   else                  ;else process pulses 
      datacount=datacount+1 
      irpd_data=irpd_data>>1       
      if(tmaCycle>175 AND tmaCycle<195)then   ;If we get a start pulse reset datacount and irpd_data 
         irpd_data=0 
         datacount=0 
      elseif(tmaCycle>100 and tmaCycle<120)   ;we got a "1" pulse 
         irpd_data.bit10=1 
      elseif(tmaCycle>65 and tmaCycle<85)      ;we got a "0" pulse 
         irpd_data.bit10=0 
      else                     ;we got an invalid pulse so reset 
         datacount = -1
         resume 
      endif 
      if(datacount=11)then 
         datacount =-1 
         command = irpd_data&0x7F 
      endif 
   endif    
   Resume 
#endif

handle_timera 
   overflowtime=overflowtime+256 
   resume
   

iCnT1 var byte
iCnt2 var byte	
iOut var sword		
LCDConvertNumToString[iOut]
	if iOut < 0 then
		LCD_String(0) = "-"
		iOut = -iOut
		iCnt1 = 1
		iCnt2 = 1
	else
		iCnt1 = 0	
		iCnt2 = 0	
	endif
	
	while (iOut > 0)
		LCD_String(iCnt1) = "0" + iOut // 10
		iOut = iOut / 10
		iCnt1 = iCnt1 + 1
	wend
	if iCnt1 = 0 then
		LCD_String = "0"
	else
		LCD_String(iCnt1) = 0
		iCnt1 = iCnt1 - 1
		
		while (iCnt1 > iCnt2)
			swap LCD_String(iCnt1), LCD_String(iCnt2)
			iCnt1 = iCnt1 - 1
			iCnt2 = iCnt2 + 1
		wend
	endif
	return

kurte,
Have you tried using Hserout (pins 14,15 I think). I use this to talk to the Sabertooth in serial mode and also can run HSERVOs without a problem.
-Drew

Thanks, that is maybe where I am headed, although I was thinking of using these two pins for a bluetooth connection

Any software serial based commands can be screwed up by hservo. The slower the baud rate the less likely your data will be screwed up however. Old version of hservo are really bad about this. The latest version in 8.0.1.0 should be able to run software serial at the same time upto 9600 baud without glitching. Anything higher and I suspect you’ll still get some glitching. Note that 8.0.1.0’s hservo is more efficient than 8.0.0.0 beta so expect worse glitching with the beta. As mentioned by others HSerial is the best option however when using HServo.

Note that hservo isn’t screwing up output to s_out(specifically). It screws up timings on everything so any command that is sensative to timing(eg pulsin,pulsout,serin,serout,pause,pauseus) will be ā€œscrewedā€ with. The amount is minimal aned can be ignored in most cases. However bit banged serial is VERY sensitive to timing errors. Note if you use bitbanged serial on ANY pin while running hservo you will see the same problem.

Thanks Acidtech.

The interesting thing is that it was working reliably use 2400 baud on the LCD on the Beta, but am having problems with it on the released version. I fully understand it is not S_OUT specific. I simply hacked the test program that showed the problem to use S_OUT instead of the LCD in my case P0. So that someone like yourself could try to replicate the issue without having to duplicate my hardware setup.

On both the Beta and the released versions I am having better luck when I disable my timer interrupt while the bit bang function is active.

My version of reading the TV code using the IRPD that is not using the interrupt is starting to work reasonably well. I will tweek it some more to see if I can get it to work at least as well as the interrupt version. I may also experiment with different values of TMA where we try clock/512 or maybe even clock/2048 and see if that impacts the reliability or not.

I assume enabling HSERVO starts the TimerW interrupt code even if there are no active or pending operations? My guess is that this is probably a rare case that may not be worth the extra code to disable the interrupts (TimerW) or the like until there is something to do? Just a thought. Also if TimerW is active, is TCNT a simple up counter that we may be able to make use of for timings? My guess is you are using it in a more advanced way using maybe some of the general registers GRA-GRD?

I know that this is sort of off my own topic, but it would help me to understand how much overhead a simple timer interrupt has on the system. For example something that looks as simple as:

ONINTERRUPT TIMERAINT,handle_timera 
TMA=4   ;increments on clock/256 
ENABLE TIMERAINT
overflowtime var long 
main
    goto main
handle_timera 
   overflowtime=overflowtime+256 
   resume

How much overhead does handle_timera cause?
If I understand this correctly this interrupt handler will be called every 65536 clock cycles.

If I am reading the H83664 manual correctly, I think that interrupt processing will require: 14+ cycles for interrupt latency stuff 10 for rte 6 add.l # ? (preample and postamble code - saving and restore Registers)

What do you think? Should I just try to avoid bit bang functions?

Thanks again
Kurt

The Hservo int will start running when an hservo command is processed. Once a servo is started the int can never be disabled because a servo wants a pulse every 20ms even when the servo isn’t moving(Yes I know most digitals ones don’t need the continuous pulses). Still I think your main problem is going to be the timera int in basic. There is a lot more overhead in a basic interrupt than an assembly one so yes, Disabling TimerA’s int while sending commands to the LCD and re-enabling after word is a good idea.

Thanks Nathan,

That is what I assumed. For the fun of it, I added printing out TCNT in my periodic serout. I found with HSERVO enabled (but not hservo commands given) that the timerW was active and when I did not enable Hservo TCNT remained 0. However it is again probably a rare case that they are enabled and not used or that the servos are disabled by setting their possitions to -16000. But this is more of an academic exercise as the real program (Brat) **does **use HSERVO commands…

The more interesting question is, could the bit bang functions be made more robust if you took the hservo code into account when you calculate the wait loop timings. For example if your normal wait time was 1000, maybe with HSERVO the wait time might be something like 975… Probably based on some statitical averaging of the overhead.
But since I have not seen this code, I am not know if this would add additional overhead or how difficult it would be impliment in your code…

Again sorry :blush: this is probably way off topic and there are probably very few of us that are interested in this. We could discuss this more off line if you are interested.

That is what I figured as well. I will probably try to eliminate the timer interrupt as I was starting to get pretty good results by using the timerA without interrupts and simply reseting the counter after each bit is received. If you get an overflow in this time frame you know that the max time between bits was exceeded so it should work fine.

However this may not help my LCD display as my LCD_Print code (which was included in the first post) disables the Timer and WKPINT_3 while it is trying to output to the LCD at 2400 baud and the LCD is now not working realiably on the released 8.0.1.0 IDE. But I will try to hack around on it and see if increasing pauses between characters and the like may improve on it’s reliablility.

If not, I may end up using the hardware serial port, which I was hoping to save for the bluetooth adapter.

Thanks again
Kurt

Quick update:

To try to see what was going on and just for the fun of it, I hooked up the Parallax USB Oscilloscope to P0 (where the LCD is connected to). I also hooked up the second lead up to P15 (HSERIAL) and I hacked up a program to output one character at a time to both ports (hserout ā€œaā€]) and (serout p0, i2400, ā€œaā€]).

I found that the bit bang output pretty well matched the hardware generated signal. I then enabled HSERVO. Again for the most part the signal pretty well matched, but in addition to what ther serout is generating on P0, I am getting a pulse on P0. This is probably the standard servo pulse every 20ms. So far I have not found a way to disable this pulse. I have tried hservo of -16000 as well as a low p0 command and still I get the pulse.

Note: If I do not do any output on P0, I do not get this pulse. However if I do a serout or something as simple as: high p0, the pulse starts.

My guess is this is probably a bug.

FYI - I changed the LCD pin to P16 and so far the LCD appears to be working fine.

I recall seeing this before(p0 getting hit when hservo was being used, but I thought I fixed it in the current release(but maybe not). Send me you’re email and I’ll send you a link to test my current inhouse version. If it fixes your problem I’ll update the current release. [email protected]