nanoframework / Home

:house: The landing page for .NET nanoFramework repositories.
https://www.nanoframework.net
MIT License
866 stars 78 forks source link

Pn532 not working with I2C #1113

Open KasperJSdeVries opened 2 years ago

KasperJSdeVries commented 2 years ago

Library/API/IoT binding

Pn532

Visual Studio version

No response

.NET nanoFramework extension version

No response

Target name(s)

STM

Firmware version

No response

Device capabilities

No response

Description

When using the Pn532 IoT library over I2C you get an exception: "Can't find a PN532". Looking at the I2C signal with a signal analyzer shows weird I2C behaviour. I have no idea what could be causing this.

How to reproduce

No response

Expected behaviour

No response

Screenshots

No response

Sample project or code

No response

Aditional information

No response

Ellerbach commented 2 years ago

Thanks for your submission. Questions:

KasperJSdeVries commented 2 years ago
  • Is the board natively supporting I2C?
  • Is the same board working for example with any other technology? Including on a Raspberry Pi with .NET IoT for example (https://github.com/dotnet/iot/)
  • And of course: are you sure about the way you plug the board on the STM chip?

I'm sure it natively supports I2C as it is a custom board with a working led driver over I2C. It works with the Lp3943. Because it is a custom board im sure it is connected correctly (it's been through multiple review processes).

Ellerbach commented 2 years ago

Because it is a custom board im sure it is connected correctly

Do you have a link on the datasheet of your custom board? I can try to help understanding the pin out. It's quite sensitive, I've been there in the past!

KasperJSdeVries commented 2 years ago

Do you have a link on the datasheet of your custom board? I can try to help understanding the pin out. It's quite sensitive, I've been there in the past!

I cannot do that. However I am quite sure it is correct as it is basically a copy-paste of the development board.

Ellerbach commented 2 years ago

I cannot do that. However I am quite sure it is correct as it is basically a copy-paste of the development board.

Then it's quite hard to help you more than what I already wrote :-( Sorry about that. The PN532 works for me on nano (at least last time I checked few months ago)

KasperJSdeVries commented 2 years ago

Then it's quite hard to help you more than what I already wrote :-( Sorry about that. The PN532 works for me on nano (at least last time I checked few months ago)

Did it work over I2C then or did you use another bus type?

Ellerbach commented 2 years ago

Did it work over I2C then or did you use another bus type?

Works like a charm with all interfaces. I tested it few times in the past. It's one of the only NFC reader that I have with all accessible interfaces.

KasperJSdeVries commented 2 years ago

I tried writing a small program in both C and with nF that reads the version info to give better insight into my issue.

In C

The Program I used:

// Includes
#include <stdbool.h>
#include <stdio.h>
#include <memory.h>
#include "PN532.h"

// Definitions and typedefs
#define PN532_I2C_ADDRESS ((uint16_t)0x48)

// File scope variables
static I2C_HandleTypeDef *_I2C_Bus_Handle;
static GPIO_TypeDef *_Reset_GPIO_Handle;
static uint16_t _IRQ_Pin;
static uint16_t _Reset_Pin;

static uint8_t _Read_Firmware_Version_Command[] = { 0x00, 0x00, 0xff, 0x02, 0xfe, 0xd4, 0x02, 0x2a,
        0x00 };
static uint8_t _Ack_Frame[] = { 0x01, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00 };

// Global functions
void PN532_init(I2C_HandleTypeDef *I2C_Bus_Handle, uint16_t IRQ_Pin,
                GPIO_TypeDef *Reset_GPIO_Handle, uint16_t Reset_Pin)
{
    _I2C_Bus_Handle = I2C_Bus_Handle;
    _Reset_GPIO_Handle = Reset_GPIO_Handle;
    _IRQ_Pin = IRQ_Pin;
    _Reset_Pin = Reset_Pin;
    HAL_StatusTypeDef ret;

    HAL_GPIO_WritePin(_Reset_GPIO_Handle, Reset_Pin, GPIO_PIN_SET);

    ret = HAL_I2C_Master_Transmit(_I2C_Bus_Handle,
                                  PN532_I2C_ADDRESS,
                                  _Read_Firmware_Version_Command,
                                  sizeof(_Read_Firmware_Version_Command),
                                  HAL_MAX_DELAY);

    if(ret != HAL_OK)
    {

        goto error;
    }

    bool done = false;
    uint8_t ack_response[sizeof(_Ack_Frame)];

    while(!done)
    {
        HAL_I2C_Master_Receive(_I2C_Bus_Handle,
        PN532_I2C_ADDRESS,
                               ack_response, sizeof(ack_response),
                               HAL_MAX_DELAY);
        if(ack_response[0] == 0x01)
        {
            done = true;
        }
    }

    if(memcmp(_Ack_Frame, ack_response, sizeof(_Ack_Frame)) != 0)
    {
        goto error;
    }

    done = false;
    uint8_t response[32];

    while(!done)
    {
        HAL_I2C_Master_Receive(_I2C_Bus_Handle,
                               PN532_I2C_ADDRESS,
                               response,
                               sizeof(response),
                               HAL_MAX_DELAY);
        if(response[0] == 0x01)
        {
            done = true;
        }
    }

    return;

error: while(1)
    {
        HAL_Delay(100);

    }
}

It uses the standard STM32 provided functions etc. with a project generated in cubeMX. It is called from the main loop with the required pin and i2c bus info.

Logic output of command frame: image Logic output of ack frame: image Logic output of response frame: image

Now with nF

I used the following program:

using System;
using System.Device.Gpio;
using System.Device.I2c;
using System.Diagnostics;
using System.Threading;

namespace NFCTestApp
{
    public class Program
    {
        public static void Main()
        {
            GpioController gpioController = new GpioController();
            I2cDevice nfciI2CDevice = new I2cDevice(new I2cConnectionSettings(3, 0x24));

            var ResetPin = gpioController.OpenPin(GetPinNumber('C', 12), PinMode.Output);
            ResetPin.Write(PinValue.High);

            var readFirmwareVersionCommand = new byte[] { 0x00, 0x00, 0xff, 0x02, 0xfe, 0xd4, 0x02, 0x2a, 0x00 };
            var ackFrame = new byte[] { 0x01, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00 };

            nfciI2CDevice.Write(readFirmwareVersionCommand);

            var done = false;
            var ackResponse = new byte[ackFrame.Length];

            while (!done)
            {
                nfciI2CDevice.Read(ackResponse);

                if (ackResponse[0] == 0x01)
                    done = true;
            }

            if (ackResponse != ackFrame)
            {
                throw new Exception();
            }

            done = false;
            var response = new byte[32];

            while (!done)
            {
                nfciI2CDevice.Read(response);

                if (response[0] == 0x01)
                    done = true;
            }

            Debug.WriteLine($"{response}");

            Thread.Sleep(Timeout.Infinite);
        }

        internal static int GetPinNumber(char port, byte pin)
        {
            if (port is < 'A' or > 'J' || pin > 15)
                throw new ArgumentException("port has to be between 'A' and 'J', pin has to be in range [0,15]");

            return port - 'A' << 4 | pin & 0x0F;
        }
    }
}

This resulted in the following logic analyzer outputs:

The Command frame: image The Ack frame: image The missing Response frame: image at which point both lines are held low forever and the core locks up.

I hope this extra information helps in seeing where my issue lies.

josesimoes commented 2 years ago

On a quick look at your code above this line if (ackResponse != ackFrame) will always fail because you're "comparing" if one array (object) is equal to another array (object). Being two different objects will always fail the comparison.

In C code your performing a memory comparison. Quite different.

KasperJSdeVries commented 2 years ago

It either doesnt reach the exception or it does work because the exception never gets hit.

Ellerbach commented 2 years ago

@KasperJSdeVries, this is NOT going to work:

if (ackResponse != ackFrame)
{
        throw new Exception();
}

Use something like: https://github.com/nanoframework/nanoFramework.TestFramework/blob/607a85316cbf92cdf4f5afe159196b1eae8b0355/source/TestFramework/TestExtensions.cs#L22

You can of course simplify to only compare byte but you'll have to browse individually each element of the array

KasperJSdeVries commented 2 years ago

@josesimoes @Ellerbach That comparison is not where the issue lies the I2C bus gets pulled low and the mcu locks up before we ever get to that comparison.

Ellerbach commented 2 years ago

@josesimoes @Ellerbach That comparison is not where the issue lies the I2C bus gets pulled low and the mcu locks up before we ever get to that comparison.

Checkout out https://github.com/nanoframework/Home/issues/1113#issuecomment-1227306017 and how to properly wake up the sensor. Note from this implementation: the _i2cDevice.Read will not raised an exception in nanoFramework because we are providing the result of the transaction (see right after here)

Note: you can get with nanoFramework the status of the I2C operation:

var res = nfciI2CDevice.Read(response);

You'll get information on how the transaction went. That may help you.

KasperJSdeVries commented 2 years ago

@Ellerbach I removed the comparison and it worked then, however there is an issue with the I2C device when an exception is thrown. This is an issue seemingly unrelated to the Pn532. The Issue being the core locking up when an exception is thrown while an I2C bus is in use.

KasperJSdeVries commented 2 years ago

and also: why does my bit of code work but the Iot.Device driver doesn't.

Ellerbach commented 2 years ago

I removed the comparison and it worked then,

Good news :-)

however there is an issue with the I2C device when an exception is thrown

it is most likely related to how you are managing the life of this I2cDevice, in your code use a using for example or go in a try/catch loop and dispose it properly. I would vote for something like this.

why does my bit of code work but the Iot.Device driver doesn't.

You have all the sources! So you can debug and check. It maybe related to a tiny something in there!

KasperJSdeVries commented 2 years ago

What I found when investigating the Pn532 driver code was that the read at: https://github.com/nanoframework/nanoFramework.IoT.Device/blob/0d1d97db4da3e306349724d369fd477fa4701055/devices/Pn532/Pn532.cs#L2067-L2070 keeps the clock low as shown: image Why this happens I do not know, however when issueing another read in code the clock gets reset; but the clock gets kept low again at the end of the second read command.