Welcome To Our Support Portal

Browse Impinj resources for help with FAQ, downloads, quick links, and more.


Submit a request

How do I write to user memory with LLRP?

Follow


The Impinj Monza 4 RFID tag chip contains up to 512 bits of user memory. In this post, we'll discuss how to write to that memory in C# using LLRP.

In LLRP, tag writes are defined within AccessSpecs. The function below demonstrates how to create an AccessSpec that writes to user memory.

static void Add_AccessSpec()
{
    MSG_ERROR_MESSAGE msg_err;
    MSG_ADD_ACCESSSPEC msg = new MSG_ADD_ACCESSSPEC();
    msg.AccessSpec = new PARAM_AccessSpec();
  
    /////////////////////////////////////////////////
    // AccessSpec
    /////////////////////////////////////////////////
    // AccessSpecID should be set to a unique identifier.
    msg.AccessSpec.AccessSpecID = 456;
    msg.AccessSpec.AntennaID = 0;
    // We're writing to a Gen2 tag
    msg.AccessSpec.ProtocolID = ENUM_AirProtocols.EPCGlobalClass1Gen2;
    // AccessSpecs must be disabled when you add them.
    msg.AccessSpec.CurrentState = ENUM_AccessSpecState.Disabled;
    msg.AccessSpec.ROSpecID = 0;
    // Setup the triggers
    msg.AccessSpec.AccessSpecStopTrigger =
        new PARAM_AccessSpecStopTrigger();
    msg.AccessSpec.AccessSpecStopTrigger.AccessSpecStopTrigger =
        ENUM_AccessSpecStopTriggerType.Null;
    // OperationCountValue indicate the number of times this Spec is
    // executed before it is deleted. If set to 0, this is equivalent
    // to no stop trigger defined.
    msg.AccessSpec.AccessSpecStopTrigger.OperationCountValue = 1;
  
    /////////////////////////////////////////////////
    // AccessCommand
    //
    // Define which tags we want to write to.
    /////////////////////////////////////////////////
    msg.AccessSpec.AccessCommand = new PARAM_AccessCommand();
    msg.AccessSpec.AccessCommand.AirProtocolTagSpec =
        new UNION_AirProtocolTagSpec();
    PARAM_C1G2TagSpec tagSpec = new PARAM_C1G2TagSpec();
    // Specify the target tag. Which tag do we want to write to?
    tagSpec.C1G2TargetTag = new PARAM_C1G2TargetTag[1];
    tagSpec.C1G2TargetTag[0] = new PARAM_C1G2TargetTag();
    tagSpec.C1G2TargetTag[0].Match = true;
    // We'll use the tag's EPC to determine if this is the label we want.
    // Set the memory bank to 1 (The EPC memory bank on a Monza 4 tag).
    tagSpec.C1G2TargetTag[0].MB = new TwoBits(1);
    // The first (msb) bit location of the specified memory
    // bank against which to compare the TagMask.
    // We'll set it to 0x20, to skip the protocol
    // control bits and CRC.
    tagSpec.C1G2TargetTag[0].Pointer = 0x20;
    tagSpec.C1G2TargetTag[0].TagMask =
        LLRPBitArray.FromHexString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
    tagSpec.C1G2TargetTag[0].TagData =
        LLRPBitArray.FromHexString("300833B2DDD90140000000000000109A");
    msg.AccessSpec.AccessCommand.AirProtocolTagSpec.Add(tagSpec);
  
    /////////////////////////////////////////////////
    // AccessCommandOpSpec
    //
    // Define the data we want to write.
    /////////////////////////////////////////////////
    msg.AccessSpec.AccessCommand.AccessCommandOpSpec =
        new UNION_AccessCommandOpSpec();
    PARAM_C1G2Write wr = new PARAM_C1G2Write();
    wr.AccessPassword = 0;
    // Bank 3 is user memory on a Monza 4 tag.
    wr.MB = new TwoBits(3);
    // OpSpecID should be set to a unique identifier.
    wr.OpSpecID = 111;
    // Write to the base of user memory.
    wr.WordPointer = 0x00;
    // Data to be written.
    wr.WriteData = UInt16Array.FromHexString("0123456789ABCDEF");
    msg.AccessSpec.AccessCommand.AccessCommandOpSpec.Add(wr);
  
    /////////////////////////////////////////////////
    // AccessReportSpec
    //
    // Define when we want to receive AccessReports
    /////////////////////////////////////////////////
    msg.AccessSpec.AccessReportSpec = new PARAM_AccessReportSpec();
    msg.AccessSpec.AccessReportSpec.AccessReportTrigger =
        ENUM_AccessReportTriggerType.End_Of_AccessSpec;
  
    // Send the message and check the reply
    MSG_ADD_ACCESSSPEC_RESPONSE rsp =
        reader.ADD_ACCESSSPEC(msg, out msg_err, 2000);
    if (rsp != null)
    {
        // Success
        Console.WriteLine(rsp.ToString());
    }
    else if (msg_err != null)
    {
        // Error
        Console.WriteLine(msg_err.ToString());
    }
    else
    {
        // Timeout
        Console.WriteLine("Timeout Error.");
    }
}

As with ROSpecs, it is good practice to delete any existing AccessSpecs when first connecting to the reader. This function will remove all AccessSpecs.

static void Delete_AccessSpec()
{
    MSG_ERROR_MESSAGE msg_err;
    MSG_DELETE_ACCESSSPEC msg = new MSG_DELETE_ACCESSSPEC();
    msg.AccessSpecID = 0;
    // Delete all AccessSpecs
    MSG_DELETE_ACCESSSPEC_RESPONSE rsp =
        reader.DELETE_ACCESSSPEC(msg, out msg_err, 2000);
    if (rsp != null)
    {
        // Success
        Console.WriteLine(rsp.ToString());
    }
    else if (msg_err != null)
    {
        // Error
        Console.WriteLine(msg_err.ToString());
    }
    else
    {
        // Timeout
        Console.WriteLine("Timeout Error.");
    }  
}

When we created the AccessSpec, we set its state to disabled. This is required by the LLRP specification. After we add the AccessSpec to the reader, we should enable it with the ENABLE_ACCESS_SPEC message like this.

static void Enable_AccessSpec()
{
    MSG_ERROR_MESSAGE msg_err;
    MSG_ENABLE_ACCESSSPEC msg = new MSG_ENABLE_ACCESSSPEC();
    msg.AccessSpecID = 456;
    MSG_ENABLE_ACCESSSPEC_RESPONSE rsp =
        reader.ENABLE_ACCESSSPEC(msg, out msg_err, 2000);
    if (rsp != null)
    {
        // Success
        Console.WriteLine(rsp.ToString());
    }
    else if (msg_err != null)
    {
        // Error
        Console.WriteLine(msg_err.ToString());
    }
    else
    {
        // Timeout
        Console.WriteLine("Timeout Error.");
    } 
}

Once we enable the ROSpec and AccessSpec, we will start receiving TagReports. If a tag write occurs, the results (success or failure) will be contained in the report. In the raw XML, they look like this. Success 111 4 Here is a TagReport event handler that checks for AccessSpec results and parses them.

static void OnReportEvent(MSG_RO_ACCESS_REPORT msg)
{
    // Loop through all the tags in the report
    for (int i = 0; i < msg.TagReportData.Length; i++)
    {
        if (msg.TagReportData[i].EPCParameter.Count > 0)
        {
            string epc;
            // Two possible types of EPC: 96-bit and 128-bit
            if (msg.TagReportData[i].EPCParameter[0].GetType() ==
                typeof(PARAM_EPC_96))
            {
                epc = ((PARAM_EPC_96)
                        (msg.TagReportData[i].EPCParameter[0])).
                          EPC.ToHexString();
            }
            else
            {
                epc = ((PARAM_EPCData)
                        (msg.TagReportData[i].EPCParameter[0])).
                          EPC.ToHexString();
            }
              
            // Check if a write operation has occurred
            if (msg.TagReportData[i].AccessCommandOpSpecResult[0] != null)
            {
                PARAM_C1G2WriteOpSpecResult result =
                  (PARAM_C1G2WriteOpSpecResult)
                    msg.TagReportData[i].AccessCommandOpSpecResult[0];
                Console.WriteLine(result.NumWordsWritten +
                    " words written to tag with EPC = " + epc);
            }
        }
    }          
}

Now, let's create a main function in our application. To avoid code duplication, we have not covered the ROSpec functions in this tutorial. This example builds upon the LLRP "Hello World" post, which covers ROSpecs and reading tags.

static LLRPClient reader;

    static void Main(string[] args)
    {
        // Create a LLRPClient instance.
        reader = new LLRPClient();
  
        /*
        Connect to the reader.
        Replace "SpeedwayR-10-25-32" with your reader's hostname.
        The second argument (2000) is a timeout value in milliseconds.
        If a connection cannot be established within this timeframe,
        the call will fail.
        */
        ENUM_ConnectionAttemptStatusType status;
        reader.Open("SpeedwayR-10-25-32", 2000, out status);
  
        // Check for a connection error
        if (status != ENUM_ConnectionAttemptStatusType.Success)
        {
            // Could not connect to the reader.
            // Print out the error
            Console.WriteLine(status.ToString());
            // Do something here.
            // Your application should not continue.
            return;
        }
  
        /*
        If you successfully connect to the reader,
        the next step is to create a delegate.
        The delegate determines which function gets called
        when a report event occur.
        */
        reader.OnRoAccessReportReceived += new
            delegateRoAccessReport(OnReportEvent);
  
        // Send the messages
        Delete_AccessSpec();
        Delete_RoSpec();
        Add_RoSpec();
        Add_AccessSpec();
        Enable_AccessSpec();
        Enable_RoSpec();
  
        // Wait until the user presses return
        Console.ReadLine();
  
        // Cleanup the reader by deleting the ROSpec
        Delete_AccessSpec();
        Delete_RoSpec();
    }
}
 

Was this article helpful?
0 out of 0 found this helpful

Comments

Impinj (NASDAQ: PI) wirelessly connects billions of everyday items such as apparel, medical supplies, and automobile parts to consumer and business applications such as inventory management, patient safety, and asset tracking. The Impinj platform uses RAIN RFID, delivering information about items to the digital world and enabling the Internet of Things.