PING through the MCP23017

I am attempting to read a PING Ultrasonic sensor through an MCP23017 I/O Expander. I knew this would be a learning experience, and it definitely has been so far. I am learning how to use some of the more advanced features of the MCP23017, such as interrupts.

So far, I can start the PING detection sequence, but this is the easy part and only requires toggling a port bit a few times.

Right now, I am trying to get the MCP23017 to interrupt the Atom PRO on IRQ1 (P8 of the Atom PRO). I believe I have the MCP23017 initialized correctly for an interrupt on change of bit 0 of Port B. The part I am very unsure of is how to get the interrupt from the chip to interrupt the Atom PRO.

Below is the current code I have.

[code]PING con p15 ’ PING Ultrasonic Ranger I/O pin
PING_Trigger con 5 ’ Trigger pulse length in uS
PING_Threshld con 25 ’ PING detection threshold

’ PING Ultrasonic Sonar Ranger

PING_I2C var byte ’ For I2C Reads
PING_Count var long ’ For counting loops until pin changes
PING_Detected var bit ’ 1 = Detected, 0 = Nothing detected
PING_Distance var word ’ PING object detection distance
PING_Echo var word ’ PING echo duration
PING_Scale var long ’ Divisor for distance units conversion

I2C_Status var word

’ Write a byte to a device on the I2C bus
dev var byte ’ Device Address/Control Byte
reg var byte ’ Register to write
dat var word ’ Data to write (8 or 16 bits)
writetype var byte ’ Write Type (Mode16 or Mode8)
I2C_Write [dev, reg, dat, writetype]

if (writetype = Mode16) then
	i2cout I2C_SDA, I2C_SCL, FailedWr, dev, reg, [dat.lowbyte, dat.highbyte]
else	
	i2cout I2C_SDA, I2C_SCL, FailedWr, dev, reg, [dat]
endif

goto WriteDone

FailedWr:
return $FF

WriteDone:
return $00

’ Read a single byte from an I2C device register
I2C_Read [dev, reg, dat]

’ if (writetype = Mode16) then
i2cin I2C_SDA, I2C_SCL, FailedRd, dev, reg, [dat.lowbyte]
’ else
’ i2cin I2C_SDA, I2C_SCL, Failed, dev, reg, [dat]
’ endif

goto ReadDone

FailedRd:
return $FF

ReadDone:
return $00

’ Check the PING Ultrasonic Sonar Ranger
result var word
Read_PING_I2C:
PING_I2C = 0
PING_Echo = 0
PING_Count = 0

oninterrupt IRQ1INT, PING_Int

gosub I2C_Write [MCP0_DevWr, gpintenb, %00000001, Mode8]	' Enable bit 0 for interrupt on change

' Start the PING detection cycle
gosub I2C_Write [MCP0_DevWr, gpiob, %0, Mode8]

' Send the PING sensor pulse
gosub I2C_Write [MCP0_DevWr, gpiob, %1, Mode8]
pauseus 5
gosub I2C_Write [MCP0_DevWr, gpiob, %0, Mode8]
gosub I2C_Write [MCP0_DevWr, iodirb, %00000001, Mode8]		' Set bit 0 to input
enable IRQ1INT												' Enable IRQ1INT

serout S_OUT, I9600, "PING: Starting timing loop", 13]

while 1
	pauseus 1
	PING_Echo = PING_Echo + 1
wend
serout S_OUT, I9600, "PING: Exited timing loop", 13]

gosub I2C_Write [MCP0_DevWr, gpintenb, %00000000, Mode8]	' Disable the interrupt

' Create the divisor for cm distance conversion
PING_Scale.highword = 0
PING_Scale.lowword = 2251			' 0.03434 * 65535 cm conversion factor

result = PING_Echo */ PING_Scale
PING_Distance = result / 2

’ PING_Distance = PING_Distance ** 1

goto PING_Done

PING_Int:
serout S_OUT, I9600, "Interrupted, PING Echo = ", DEC Ping_Echo, ", PING_I2C = ", BIN PING_I2C, ", I2C_Status = ", HEX I2C_Status , 13]
resume

PING_Done:
disable IRQ1INT
return PING_Distance[/code]

This is my first try at using interrupts, so I am sure I probably am not setting the Atom PRO up correctly, even though I believe I have it setup as the manual shows.

8-Dale

I assume there is additional code to this, where you have a main function that is calling your function: Read_PING_I2C and you wish for this function to not return until the ping completes. I am not sure how you will ever exit the code:

while 1 pauseus 1 PING_Echo = PING_Echo + 1 wend
This of course could be easily solved by having your interrupt function set a value that you test for and break out of in this while loop…

But that is probably a secondary issue. If I am reading this correctly, you are not receiving the Serout showing that the interrupt happened. I am assumming that the interrupt did not happen before you enabled the code, but you might try putting the enable IRQ1INT higher up in the function.

My assumption is that you do have a wire connecting the appropriate pin (20?) on the MCP23017 to P8 on the Basic Atom…

My next guess is that you probably need to enable the interrupt probably something like:
ienr1.bit1 = 1

Depending on if the intteruppt is to happen on leading or trailing edge you may have to update the appropriate bit in: iegr1

Good Luck

Kurt

Correct. I did not post the entire program because it has a bunch of other code for driving LEDs and such. I just posted what is relevent to the Read_PING_I2C routine.

I did this so I would know it was the interrupt that ocurred. I’ll change the code when I know I am getting the interrupt.

I will do that once I know the interrupt is happening and my code is processing it.

I do not get to the interrupt service routine in my program.

At this point I don’t know if the interrupt is even happening.

Yes. Pin 19 (INTB) is connected to P8/IRQ1 on the Atom PRO.

Why? Isn’t that what the ‘enable IRQ1INT’ command does? I am trying to follow the example on page 177 of the Atom PRO manual. Isn’t this a correct example of how to use interrupts?

Apparently I need a lot of luck right now, because I sure don’t seem to be understanding stuff very easily at the moment. :frowning: :frowning: Maybe I will just give up on this because I am having too much trouble understanding and figuring stuff out right now. :frowning:

8-Dale

I believe it would be a long shot, but try to enable the interrupt at the start of your function, just to make sure it is not a timing issue.

You are probably correct that this does set the appropriate bit in the interrupt mask :blush: . I did not look hard enough at your chips spec to know if the intterupt it generages is on the leading or trailing edge…

The next thing I would do, would be to pull out my logic probe and hook it up to INTB to see if the chip is actually generating the signal that you expect. I would use an Oscilloscope, except I do not have one :frowning: . That would at least help to localize where the problem might be.

I can’t enable the interrupt too soon in the code because I only want the transition from 0 to 1 for the interrupt on change of the MC23017 Port B pin 0 (where I have the PING sensor connected). This should be the same as using a PULSIN command to read a digital pulse from an Atom I/O pin

I can set the MCP23017 interrupts to be active high or low by the ICON.INTPOL bit and the ICON.ODR bit controls whether the interrupt pin is Open Drain (ICON.ODR = 0) or Active HIGH/LOW (ICON.ODR = 1) and overrides ICON.INTPOL if set. I now have ICON.ODR = 0 and ICON.INTPOL = 0 (Active LOW).

I don’t have a logic probe or oscilloscope. I should really have a decent logic probe though, now that I am messing with stuff that is timing sensitive. I doubt I could afford a decent oscilloscope, as much as I would like to have one.

This all may not make any difference though. I thought my code was triggering the PING and sending the PING pulse, but it does not seem to be afterall. If I can’t trigger the PING, there won’t be any interrupt on change of the pin the PING is connected to. So, this may just not work at all no matter what I do. It will be too bad if that is the case, and that does seem to be how it is so far.

8-Dale

I have made a bit of progress… For some reason I can’t make myself stop working on this. I am now actually able to send a pulse to the PING, and it looks like it is the right length as far as I can tell.

I am still not getting an interrupt from the interrupt on change from the MCP23017, or the Atom is not seeing it if it is happening. I have tried Active High and Active LOW interrupts from the MCP23017. It seems that there should be a way to get this to work, but I am at a loss how to do it.

I believe I have everything enabled correctly on the MCP23017 to generate the required interrupt on pin change, and have enabled that for pin 0 of Port B where I have the PING connected. The PING is seeing the pulses I send through the MCP23017. However, I am not sure if I have to read the pin to generate the interrupt or not. I can clear an interrupt by reading the pin or reading the INTCAP register, according to the datasheet.

8-Dale

Any more luck?

Have you tried polling the device?

You might try that to see if you are having reasonable communications with the device. Ie once you signal the Ping sensor to start a ping, you could have a loop that keeps reading from your IO extender chip until the signal for that pin goes high and then loop again until it goes low. You could try and keep a loop count to try to calculate the time/distance or you could enable a timer interrupt to calculate the delta times.

Just an idea :bulb:

Kurt

Not so far.

Yes, and it does not seem to work. I don’t think I can poll fast enough for the PING pulse. I think the PING pulses are measured in uS, so are quite fast.

I would really like to get the interrupt working. I don’t think I am going to have any chance of catching the pulsewidth unless I can get an interrupt when it has changed states. Of course I also would need a timer to time the input pulsewidth.

As far as I can see now, polling is just not going to be fast enough to catch the pulsewidth I have to measure from the PING. I don’t know what I am doing wrong.

Here is the current Read_PING_I2C routine.

[code]’ Check the PING Ultrasonic Sonar Ranger
result var word
Read_PING_I2C:
serout S_OUT, I9600, “Entering PING Routine”, 13]

PING_I2C = 0
PING_Echo = 0
PING_Count = 0

’ gosub I2C_Write [MCP0_DevWr, iodira, 0, Mode8] ’ Make sure all Port A pins are outputs

dir8 = 0
ieg1 = 0													' Set P8/IRQ1 to input
oninterrupt IRQ1INT, PING_Int

gosub I2C_Write [MCP0_DevWr, gpintenb, %00000001, Mode8]	' Enable bit 0 for interrupt on change

' Start the PING detection cycle
gosub I2C_Write [MCP0_DevWr, gpiob, %00000000, Mode8]

enable IRQ1INT												' Enable the IRQ1INT interrupt
serout S_OUT, I9600, "Sending PING Pulse", 13]
' Send the PING sensor pulse
gosub I2C_Write [MCP0_DevWr, gpiob, %00000001, Mode8]
pauseus 5
gosub I2C_Write [MCP0_DevWr, gpiob, %00000000, Mode8]

gosub I2C_Write [MCP0_DevWr, iodirb, %00000001, Mode8]		' Set bit 0 to input
gosub I2C_Read [MCP0_DevWr, gpiob, PING_I2C, Mode8]
serout S_OUT, I9600, "PING: Starting timing loop, PING_I2C = ", BIN PING_I2C , 13]

while PING_I2C <> 1
	gosub I2C_Read [MCP0_DevWr, gpiob, PING_I2C, Mode8]

’ pauseus 1
PING_Echo = PING_Echo + 1
wend

serout S_OUT, I9600, "PING: Exited timing loop", 13]

gosub I2C_Write [MCP0_DevWr, gpintenb, %00000000, Mode8]	' Disable the interrupt on change

' Create the divisor for cm distance conversion
PING_Scale.highword = 0
PING_Scale.lowword = 2251			' 0.03434 * 65535 cm conversion factor

result = PING_Echo */ PING_Scale
PING_Distance = result / 2

’ PING_Distance = PING_Distance ** 1

goto PING_Done

PING_Int:
serout S_OUT, I9600, "Interrupted, PING Echo = ", DEC Ping_Echo, ", I2C_Status = ", HEX I2C_Status , 13]
resume

PING_Done:
disable IRQ1INT
gosub I2C_Write [MCP0_DevWr, iodirb, 0, Mode8] ’ Reset all Port A pins to outputs
return PING_Distance[/code]
8-Dale

After my last post I started to think about it and I think you are right that you will not be able to post quick enough. I wonder if you can even turn bit 0 to input mode quick enough. I have not done the math,

    ...
   ' Start the PING detection cycle 
   1 - gosub I2C_Write [MCP0_DevWr, gpiob, %00000000, Mode8] 

   2 - gosub I2C_Write [MCP0_DevWr, iodirb, %00000001, Mode8]      ' Set bit 0 to input 
   3 - gosub I2C_Read [MCP0_DevWr, gpiob, PING_I2C, Mode8] 

During the execution of line 1, does pin change state before or after the I2C ACK and I2C STOP sequences are completed. For now lets assume at the same time for simplicity… But assume for now we can ignore the timing here and the Ping started at the end of this command.

But if line 2 is needed to set the appropriate bit to read, would take the time to output something like (Start, Addr(8), Ack, Data(8), ACK, Stop)
St a a a a a a a a A d d d d d d d d A E
Or about 20 I2C Clock cycles. Depending on what the I2C clock rate is for this command, this could take quite a few clock cycles. So it is possible that the signal happened before you had a chance to receive it.

Side not on line 3. I am not sure if mode8 is a var or a con…

So I wonder if what you are trying to do is possible? Maybe that is why the SRF08 and SRF10s and the like have their own microcontrollers.

I hope my ramblings here make some sense.

Sorry if not.

Kurt

I think this is just not doable the way I am trying to do it through the I/O Expander. I had to try though, and if it did work, it would have been a wonderful thing for a lot of us.

8-Dale