Problems using HSERIN with an 08M2

AllyCat

Senior Member
Hi,

Recently, I've been developing a "Serial to I2C" converter in an 08M2. One application would be to emulate an AXE132, but to drive an LCD carrying one of the "I2C expander" backpacks. The PIC(axe)'s hardware serial UART and an interrupt are used to give adequate speed and reliability. However, this has proved to be a considerable "can of worms":

Firstly, HSERIN uses the same pin as I2C SDA and I've no wish to write (or use) bit-banging I2C code. Fortunately, the "base" PIC has an Alternate Pin Function (APFSEL) SFR which allows the HSER input to be swapped onto Port C.5. That's not ideal (because it's the Serial Programming input) but beggars can't be choosers.

Next, Microchip "forgot" to include a programmable exclusive-or gate on the serial input hardware, so it only works with "TTL" polarity (idle high) whilst PICaxe needs idle-low for programming the chip. For some applications the source could generate "T" data, or alternatively the PICaxe's on-chip "Data Signal Modulator" includes EXOR/Inverter gates which can be arranged to invert the serial data polarity. But both these methods require a "switch" (or pin links) to convert the PICaxe from Programming to Application mode.

Therefore, I devised a simple one-transistor (plus two 100k resistors) inverter stage to permit the PICaxe to automatically switch from Programming mode (at power-up/reset) to normal Running mode. A "hippy-style" schematic diagram is shown at the foot of the Program listing below. If pin C.4 is set as an input (or high) then the NPN transistor is OFF and the normal PICaxe data is passed to the input pin (via the 100k resistor). But if the emitter (C.4) is pulled Low, with a "Weak Pullup" resistor applied to the collector (C.5), then the signal to the PICaxe serial input becomes inverted. Unfortuantely, the PICaxe Editor doesn't support a pullup on the serial input, but the on-chip hardware can be enabled with a SFR command (POKESFR WPUA,32).

Next, it's necessary to generate an interrupt to read the tiny (two-byte) serial input buffer, but C.5 is not a valid interrupt input (why?). So it appears necessary to link C.5 to C.3 (the only remaining spare pin), which does allow a PULLUP to be used (although some might prefer to activate the pullups on both pins with a POKESFR $8C.40).

So finally, I could write a test program (similar to below) to "echo" characters, using a 16 MHz clock and 4800 baud. This worked well for single (i.e. spaced) characters, but even the second character was usually corrupted when concatenated with the first (i.e. using PE's terminal with the "5 ms delay" option cleared). Eventually, I tried using basic PEEKSFR commands (in place of HSERIN) and then all characters were echoed correctly. Is there a flaw in my use of HSERIN in the program?

The program below allows the two versions to be compared, by defining (or not) the "USE_HSERIN" parameter. Theoretically, HSERIN should be better because it can read the input byte immediately, whilst the SFR code must test the buffer flag before reading the byte. However, in practice, the interrupt seems more than fast enough, such that it's usually necessary to read the status flag several times before the byte is fully received into the buffer (the interrupt is generated by the leading edge of the start pulse). Other features of the test program are that it transmits a "tick" (=) every two seconds to show that the main loop is still running, and after a minute it restores the Programming input and reports the number of bytes received (as a check if some characters were not echoed).

Code:
; HSERIN TESTS using 08M2
; AllyCat October 2014       
       
#picaxe 08m2
#no_data
;#define USE_HSERIN	; Selects Alternative buffer-reading code **

; EUSART:SFRs
symbol APFCON 	= $5D	; 128/0=RX on c.5/c.1; 8/0=T1G on c.3/c.4 ; 4/0 = TX on c.4/c.0
symbol BAUDCON	= $7F	; 8 = 16-bit Baud rate Generator (High byte at $7C)
symbol RCREG 	= $79	; Serial receive register
symbol TXREG 	= $7A	; Transmit byte register
symbol TXSTA 	= $7E	; 32 = TX Enable; 4 = High speed Baud rate; 2 = TRMT(RO) = Regiter is EMPTY
symbol RCSTA 	= $7D	; 128 = Ser Port Enable (RX & TX); 16 = Continuous Receive Enable **
symbol PIR1 	= $11	; RCIF = 32 = Received byte NOT yet read, 16 = TXIF = NO byte in buffer

symbol tempb 	= b20					; Temporary (local) variable
symbol timeout	= b21					; Timeout to check for more incoming bytes 
symbol bytecnt = w13					; Number of bytes processed
symbol ptrin   = b22					; Input RAM Buffer Pointer
symbol ptrout  = b23					; Output RAM Buffer Pointer 
symbol BUFST   = 64 					; Start address of hserial buffer
symbol BUFEND  = 127 				; Last address of hserial buffer
symbol MARKER  = c.2					; Unused pin to show when the interrupt is active
symbol BAUD 	= N4800_16			; Serial Baud rate
symbol HBAUD 	= B4800_16			; Hardware Serial Baude rate
;symbol BAUD 	= N9600_16			; )_Works for up to ~5 consecutive characters,then:
;symbol HBAUD 	= B9600_16			; ) receives no more until reset.

init:
#terminal 4800
	setfreq m16
	hsersetup HBAUD,2	; 16=Disable hserin;8=Disable hserout;2=INVERT SEROUT;4 & 1 must be 0
	pause 10
	pokesfr TXREG,"*"				; Mark if/when the Program (re-)starts	
	pokesfr APFCON,128				; Move Hardware RX input to C.5
	disconnect						; Disable Program Downloader
	low c.4						; Enable the external inverter
	pullup %001000					; Pullup the SerIn/Interrupt pin (C.3)
	ptrin = BUFST					; Initialise the RAM serial buffer
	ptrout = BUFST
	pause 100
	hserin w0						; Flush the buffer
	setint 0,8						; Enable Interrupt on C.3 (active low)
	bytecnt = 0
main:
	do
		do while	 ptrin <> ptrout	; The RAM buffer is not empty
			peek ptrout,tempb
			pokesfr TXREG,tempb		; Transmit the byte
			inc ptrout
			if ptrout > BUFEND then	; Wrap buffer if overflow
				ptrout = BUFST
			endif
			do
				peeksfr TXSTA,tempb	; Test Transmit Buffer status
				tempb = tempb & 2
			loop until tempb = 2		; Repeat until buffer is empty	
		loop
		pause 4000
		pokesfr TXREG,"="				; Report that the main loop is still alive
		pause 4000
	 		hsersetup HBAUD,2			; Recover from an overrun error (caused by too low clock rate)
			pokesfr APFCON,128		; Move Hardware RX input to C.5		
	loop until time > 60			; Then restore the serial downloader
	setint off
	input c.4						; Disable inverter
	pullup 0						; Disable pullup
	pause 100	
	reconnect
	hsersetup HBAUD,8					; 8=DISABLE HSEROUT; 2=Invert serout; 4 & 1 must be 0
	serout c.0,BAUD,(cr,lf,#bytecnt," bytes")		; Report the number of bytes processed
end

interrupt:
;	high MARKER					; Test marker   ***
#ifdef USE_HSERIN				; Appears NOT to work correctly
	w0 = -1 							; Set to a non-ASCII value
	hserin w0							; Request an input byte
	if b1 = 0 then						; A byte has been read into b0
reenter:
#else								; DOES seem to work well
	peeksfr PIR1,b0					; Read the Hardware Serial Status byte
	if bit5 = 1 then					; There is an Unread byte in the receive buffer
reenter:
		peeksfr RCREG,b0				; Read it
#endif	
		poke ptrin,b0					; Store the received byte into RAM buffer
		inc ptrin
		if ptrin > BUFEND then		; Wrap buffer if overflow
			ptrin = BUFST
		endif	
		inc bytecnt						; Total byte counter	for testing
		timeout = 0	
		goto interrupt
	endif	
	inc timeout								; Timeout counter
	if timeout < 4 then interrupt	; Don't exit too soon
	timeout = 0
#ifdef USE_HSERIN		
	w0 = -1 								; Final check of the buffer before 

exiting
	hserin w0						; Request an input byte
	if b1 = 0 then reenter			; A byte has been read into b0
#else					
	peeksfr PIR1,b0				; Final check of the buffer before exiting
	if bit5 = 1 then reenter		; A byte has been received
#endif
	setint 0,8 					; Reset Interrupt input C.3 (active low)
;	low MARKER					; Test Marker for end of interrupt  ***
	return
	
	
#rem	; TEST HARDWARE SCHEMATIC:
Uses C.4 Low to Invert, C.3 for Interrupt input and Weak Pullup
               
                     +--------------*-----Vdd
                     |              |
SO----<--------------|-----------+ +++10uF
                     |  _______  | === 
                     | |o  U   | |  |
                     +-0-+  +--0-|--*-*---Vss
       100k            | /W |i | |   _|_
SI->-*-/\/\-*--------*-0 \P %n 0-+   ///      
     |      |  NPN   | | /U |v |
     *---+  +-+  _+--|-0-|--+  0-<>-SCL )
     |   |     \ /!  | | %On   |        }-I2C
     \   |     ===   +-0-!     0-<>-SDA )
  10k/   | 100k |      |_______|                                  
     \   +-/\/\-+        08M2
    _|_            
    ///
The SFR version even "almost works" at 9600 baud, receiving about 5 concatenated bytes correctly (depending on the exact program staructure) before "locking up". This seems to be due to the "Overrun" hardware flag becoming set; the easiest method I've found to reset it (before any more bytes can be received) is to issue another HSERSETUP command (followed by APFCON if appropriate). Conversely, the USE_HSERIN version doesn't seem to work even at 2400 baud. The SFR version does, although it occasionally "holds onto" the echo for a few seconds (possibly because the interrupt is exited before the character has arrived).

Generally, the interrupts in the test code occur during a PAUSE (so the response can be fast), but the following screenshot shows the interrupt entry (Marker in lower waveform) for pairs of concatenated characters when main: is running some real I2C control code. The upper waveform consists a Start Pulse, 7 "eyes" of ASCII data (32 - 127) then the msb (=0), Stop and Start levels, followed by a further 7 data-eyes, etc. However, it's difficult to test (at least using the PE terminal) the maximum latency for the situation where another character arrives just as the Return is being executed.

HSERinterrupt16MHz-2ch.jpg

Cheers, Alan.
 

Technical

Technical Support
Staff member
We would approach this the other way - bit banged i2c on different pins is actually quite simple and would possibly remove a lot of your hassles.

Code:
; *****************************************************************************
; *                                                                           *
; *     I2C Test Program for the PICAXE-08M2                    I2C6.BAS      *
; *                                     ^^^                                   *
; *****************************************************************************
; *                                                                           *
; *     This is an version of I2C3.BAS specifically designed to run on the    *
; *     PICAXE-08M using its bi-directional port abilities.                   *
; *                                                                           *
; *     The core I2C routines use just 140 bytes of code.                     *
; *                                                                           *
; *****************************************************************************


;                              --.---.-- +V
;                               .|. .|.
;                               | | | | 2 x 4K7
;                               |_| |_|
;        PICAXE-08M              |   |           I2C Eeprom / Ram
;       .----------.             |   |             .----------.
;       |       X1 |-------------^---|-------------| SDA   A2 |---.
;       |          |                 |             |       A1 |---{
;       |       X4 |-----------------^-------------| SCL   A0 |---{
;       `----------'                               |       WP |---{
;                                                  `----------'  _|_ 0V


; *****************************************************************************
; *                                                                           *
; *     Define the I2C device being used                                      *
; *                                                                           *
; *****************************************************************************


                                                ; 24LC256, Word Addressed


        Symbol  I2C_ADDRESS     = %10100000     ; $A0


; *****************************************************************************
; *                                                                           *
; *     Variables                                                             *
; *                                                                           *
; *****************************************************************************


        Symbol  tstAdr          = b0
        Symbol  tstVal          = b1


        Symbol  i2cAdr          = w1    ' b3:b2
        Symbol  i2cAdrLsb       = b2
        Symbol  i2cAdrMsb       = b3


        Symbol  i2cVal          = b4


        Symbol  i2cDataByte     = b5
        Symbol  i2cAckBit       = b6
        Symbol  i2cBitCount     = b7


; *****************************************************************************
; *                                                                           *
; *     Bit-Banged I/O definitions                                            *
; *                                                                           *
; *****************************************************************************


        Symbol  SDA             = C.1
        Symbol  SCL             = C.4


        Symbol  SDA_PIN         = pinC.1
        Symbol  SCL_PIN         = pinC.4


; *****************************************************************************
; *                                                                           *
; *     Main Program Code                                                     *
; *                                                                           *
; *****************************************************************************


        ; Wait for Terminal Window Pop-Up


        Pause 1000


        ; Initialise the I2C comms


        Gosub InitI2cDevice


        ; Test I2C


        For i2cAdr = $00 To $3F
          i2cVal = i2cAdr+3
          Gosub WriteI2cData
          Gosub ReadI2cData
          i2cVal = i2cVal-3
          If i2cVal <> i2cAdr Then
            SerTxd( "Er" )
          End If
        Next


        SerTxd( "Ok" )


        End


; *****************************************************************************
; *                                                                           *
; *     High-Level I2C Interface Routines                                     *
; *                                                                           *
; *****************************************************************************


WriteI2cData:


        Gosub I2cSendHeader


        i2cDataByte = i2cVal
        Gosub I2cSendByte


        Gosub InitI2cDevice


        Pause 20                        ; 20mS to allow write


        Return


ReadI2cData:


        Gosub I2cSendHeader


        Gosub I2cStart


        i2cDataByte = I2C_ADDRESS | $01
        Gosub I2cSendByte


        Input SDA                       ; SDA = 1 / Tri-State SDA
        For i2cBitCount = 0 To 7
          Gosub I2cStrobeScl
          i2cVal= i2cVal * 2 | i2cAckBit
        Next


InitI2cDevice:


        Low SCL                         ; SCL = 0
        Low SDA                         ; SDA = 0
        Input SCL                       ; SCL = 1
        Input SDA                       ; SDA = 1
        Return


; *****************************************************************************
; *                                                                           *
; *     Low-Level I2C Interface                                               *
; *                                                                           *
; *****************************************************************************


I2cSendHeader:


        Gosub I2cStart


        i2cDataByte = I2C_ADDRESS & $FE
        Gosub I2cSendByte


; Add these 2 lines for word addressing      
        ;i2cDataByte = i2cAdrMsb
        ;Gosub I2cSendByte


        i2cDataByte = i2cAdrLsb


I2cSendByte:


        For i2cBitCount = 0 TO 7
          If i2cDataByte < $80 Then
            Low SDA                     ; SDA = 0
          Else
            Input SDA                   ; SDA = 1
          End If
          Gosub I2cStrobeScl
          i2cDataByte = i2cDataByte * 2
        Next
        Input SDA                       ; SDA = 1 / Tri-State SDA
        Gosub I2cStrobeScl
        Return


I2cStart:


        Input SDA                       ; SDA = 1
        Input SCL                       ; SCL = 1
        Low SDA                         ; SDA = 0
        Low SCL                         ; SCL = 0
        Return


I2cStrobeScl:


        Input SCL                       ; SCL = 1
        Do
        Loop Until SCL_PIN = 1
        i2cAckBit = SDA_PIN
        Low SCL                         ; SCL = 0
        Return


; *****************************************************************************
; *                                                                           *
; *     End of program                                                        *
; *                                                                           *
; *****************************************************************************
 

AllyCat

Senior Member
Hi,

Thanks technical, it's interestng that bit-bashing I2C is not too tricky, but it is S-L-O-W. I can dim the backlight of the LCD (via the I2C expander) to around 1% brightness using "Hardware" I2C commands (typically 200 us on, 16 ms off) whilst the equivalent bit-bashing can only flash the backlight at 8 Hz (on for 60 ms, off for 60 ms), that's 300 times slower !

However, the I2C is not really the issue, I could use SERIN on (say) C.4 (with hardware I2C on C.1 and C.2), but then still have all the issues with it "blocking" other functions. The advantage of HSERIN is that the main program can be doing other things (even if it's only dimming the backlight via "pseudo PWM" I2C commands).

I do already have a provisional "AXE132 emulation" working via the I2C expander, which is sufficient to (easily) update a full 20 x 4 LCD in less than a second, with (apparently) no significant constraints on the serial input data timing. The code is so "light" on variables that probably a 120 byte circular buffer could be implemented with the 08M2, if there were any need.

However, the one (known) remaining issue is that the code seems to work perfectly (within the obvious limits) using direct SFR access to the serial receive buffer, but the "official" PICaxe SERIN command appears to be returning corrupt data. My "obvious" solution is just to use the SFR access method (since either version requires 3 lines of dedicated code), but I thought it worthwhile to "flag" that there might be some issue with the use of the HSERIN command.

Cheers, Alan.
 
Last edited:

Technical

Technical Support
Staff member
Your code is not the same, the extra 'w0 = -1' will add a "completely different" delay before the data is read when compared to the peek routine.

The hserin command on an M2 part is nothing much more than a peek of the receive buffer, so it is just the timings before that peek happens you need to sort out.
 

AllyCat

Senior Member
Hi,

Thanks again technical.

,,, The hserin command ... is nothing much more than a peek of the receive buffer,
Surely the code can't (shouldn't) read the buffer until a byte has been received (flag set)? In practice, the interrupt seems to be so fast that the interrupt loop may need to execute several times before it "finds" a byte in the buffer, hence the inclusion of the "timeout" counter in my code. BTW, I did also try other versions, such as HSERIN b0 assuming that ASCII zero isn't normally used, with similar results.

But maybe I didn't make it clear in my first post, the first character is "always" received correctly (with either software version) but the second (and usually subsequent) bytes are corrupted only with the HSERIN version. For example with 5 (concatenated) bytes at 4800 baud, the strings 12345 and 67890 (received correctly with the peeksfr code) are returned as 1¦¢5 and 6{unprintable}Ê0 by the HSERIN version. In this particular (random) trial, the 1st and 5th characters appear to be correct (with the middle 3 reduced to 2) and the "byte count" at the end of the run confirms that only 4 bytes were received (on average) for each group of 5.

This is (now) looking rather as if the "resident" command is actually slower than the "DIY" (SFR) version. Is it that HSERIN is not a "real" embedded command, but another of the "pseudo" commands created by the PE?

Cheers, Alan.
 

Technical

Technical Support
Staff member
Surely the code can't (shouldn't) read the buffer until a byte has been received (flag set)?
Of course, the hserin, a real command, checks the RCIF flag and reads the byte from the buffer if the bit is set. If not it moves on to the next command. It wouldn't work at all if it didn't do this. Hserin will process much faster than your pokesfr version, which is basically the same idea, apart from that hserin also has code that checks the various error bits (e.g. overrun or framing) and resets the module as required to clear these errors.

We'll do some tests, but your hardware setup with the APFSEL redirection is very 'unusual' and we'll need to see what exactly is going on. For instance forcing the serin pin to be a UART is not recommended as it will alter the internal readings of the status of the download pin due to the internal silicon change on that pin, and this may affect other areas of the PICAXE firmware, despite the fact you have 'disconnect'ed. You should probably set the APFSEL before the hsersetup as well (not change the pin after the UART module is already enabled). We'll still look into it, but if your version works for you in this situation then just use it!
 

AllyCat

Senior Member
Thanks technical,

IMO the APFSEL switch is not the fundamental issue because IIRC I did most of my testing with an inverter connected from the serial input to feed C.1, and think the results were similar. It was only more recently that I changed to using C.5 as input, so that I could verify the interrupt latency using "real" I2C code in the main loop. At this time I also discovered that APFSEL must be set after HSERSETUP or nothing is received (I assumed that HSERSETUP clears the flag in APFSEL, either intentionally or accidentally).

I've actually been trying various versions of the test code for some weeks now and have generally found the results consistent and repeatable. But my test method (transmitting strings of concatenated characters from the PE terminal) also threw up a "display intelligence anomoly" in the DPScope software. So I thought it best to wait for Wolfgang to fix the issue (which he has now done in version 1.0.9) before inviting others to attempt to reproduce my results.

Yes, as you say, I do believe that I have devised code which works well, but had expected the embedded/dedicated command to work faster and/or more reliably. Also, the functionality may be of interest to others and I would prefer to show (and I suspect others would prefer to see) the use of "normal" documented PICaxe commands instead of rather obscure SFR peeks and pokes.

Cheers, Alan.
 

Technical

Technical Support
Staff member
The M2 hserin is quite limited when compared to the more powerful X2 scratchpad based solution. One of the limitations of the M2 command is that there is no 'error reporting' and so the firmware simply tries to silently reset itself if, for instance, the buffer overruns and generates an error flag. As the only way to correct a buffer overrun in the PIC is to toggle the UART enable bit, and if this enable toggle then also occurs 'mid next byte' you could also get a corrupt byte received, which is what we suspect you are seeing. The safest way to avoid this, as you have already seen, is to send the bytes well spaced apart so that the firmware always has time to process the bytes one by one instead of 'stacking 2 up'.
 

rossko57

Senior Member
I made a little 08M2 based CCTV serial protocol converter. In a real world noisy environment (100's of metres of RS485 bus in a commercial setting, with squirts of tightly packed data) I found the software SERIN to work more reliably than HSERIN. It may well be more inclined to lose data than give trash, but that doesn't matter much for this job. Nice to have the choice of approach!
 
Top