射频识别(RFID)技术

Mailbox

译者:陈广 日期:2020-8-1


终于学到 ST25 最具特色的功能:Mailbox。I2C 和 RF 接口之间可通过 ST25 进行快速的数据传输,称为快速传输模式(Fast transfer mode,FTM)。我们可以把这种模式想像成近距离版的蓝牙,通过手机的 NFC 功能控制单片机或从单片机读取数据。使用 FTM 的前提是 I2C 必须处于启动状态,即单片机必须通电。

单片机读写示例

学习一个新的东西呢,必须先从最简单的例子开始,摸着石头过河,好在意法半导体官方有一个简单的例子,要不这石头还不太好摸。我把这个官方的例子改了下,变得更为简单,官方例程有部分无效代码,搞了半天我才发现。此例程只是单片机这一端操作,不需要手机。也就是单片机向 Mailbox 写数据,然后自己把它读出来。

本例,我们接着《ST25DV64K 开发》这篇文章的程序继续开发。将main()函数中的代码更改如下:

/* USER CODE BEGIN 1 */
uint8_t awritedata[16];
uint8_t areaddata[16];
uint8_t cnt = 0;
uint16_t mblength;
ST25DV_MB_CTRL_DYN_STATUS mbctrldynstatus;
/* USER CODE END 1 */

/* MCU Configuration--------------------------------------------------------*/

/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();

/* USER CODE BEGIN Init */

/* USER CODE END Init */

/* Configure the system clock */
SystemClock_Config();

/* USER CODE BEGIN SysInit */

/* USER CODE END SysInit */

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USB_DEVICE_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
HAL_Delay(1000);
printf("Start Mailbox Operation\r\n");
while( CUSTOM_NFCTAG_Init(CUSTOM_NFCTAG_INSTANCE) != NFCTAG_OK );
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);

//启用Mailbox
if(CUSTOM_NFCTAG_SetMBEN_Dyn(CUSTOM_NFCTAG_INSTANCE) == NFCTAG_OK)
{
    printf("Enable Mailbox Success\r\n");
}
else
{
    printf("Enable Mailbox failed\r\n");
}

HAL_Delay(1000);
//读取Mailbox控制状态
CUSTOM_NFCTAG_ReadMBCtrl_Dyn(CUSTOM_NFCTAG_INSTANCE, &mbctrldynstatus);
/*如果Mailbox中的数据为空。
    HostPutMsg表示I2C端向Mailbox写入的数据
    RfPutMsg表示RF端向Mailbox写入的数据*/
if((mbctrldynstatus.HostPutMsg == 0) && (mbctrldynstatus.RfPutMsg == 0))
{
    //准备写入的数据
    for( cnt = 0; cnt < 16; cnt++ )
    {
        awritedata[cnt] = cnt;
    }
    printf("Write data to Mailbox: ");
    //向Mailbox中写入16字节数据
    if(CUSTOM_NFCTAG_WriteMailboxData(CUSTOM_NFCTAG_INSTANCE, awritedata, 16)==NFCTAG_OK)
    {
        printf("Success\r\n");
    }
    else
    {
        printf("Failed\r\n");
    }
}
//读取Mailbox中存在的数据的长度,正确的长度为:读取的长度+1
CUSTOM_NFCTAG_ReadMBLength_Dyn(CUSTOM_NFCTAG_INSTANCE, (uint8_t *)&mblength);
printf("Length of data in MBCTRL register: %d\r\n", ++mblength);
//读取并打印Mailbox控制状态,在进行Mailbox读写操作前,应当首先使用此方法读取Mailbox状态
CUSTOM_NFCTAG_ReadMBCtrl_Dyn( CUSTOM_NFCTAG_INSTANCE, &mbctrldynstatus );
printf( "Ctrl MB status register value:\r\n" );
printf( "Host(i2c) Missed Message  = %d\r\n", mbctrldynstatus.HostMissMsg );
printf( "RF(reader) Missed Message = %d\r\n", mbctrldynstatus.RFMissMsg );
printf( "Host(i2c) Put Message     = %d\r\n", mbctrldynstatus.HostPutMsg );
printf( "RF(reader) Put Message    = %d\r\n", mbctrldynstatus.RfPutMsg );
printf( "Mailbox Enable            = %d\r\n", mbctrldynstatus.MbEnable );

//读取Mailbox中的所有数据
CUSTOM_NFCTAG_ReadMailboxData(CUSTOM_NFCTAG_INSTANCE, areaddata, 0, mblength);
printf("Data read in Mailbox:\r\n");
for( cnt = 0; cnt < mblength; cnt++ )
{
printf( "0x%02X ", areaddata[cnt] );
}

/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
    
/* USER CODE BEGIN 3 */

}
/* USER CODE END 3 */

运行结果:

Start Mailbox Operation
Enable Mailbox Success
Write data to Mailbox: Success
Length of data in MBCTRL register: 16
Ctrl MB status register value:
Host(i2c) Missed Message  = 0
RF(reader) Missed Message = 1
Host(i2c) Put Message     = 1
RF(reader) Put Message    = 0
Mailbox Enable            = 1
Data read in Mailbox:
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F 

Mailbox 的缓冲区为 256 个字节,本例使用了其中的 16 个字节。首先写入 16 字节的数据,然后将其读出并打印。这里需要注意的是,缓冲区使用的是半双工模式,也就是同一时间,只能是I2C或RF接口中的一个进行读写操作。所以在所有读写操作中都应当使用CUSTOM_NFCTAG_ReadMBCtrl_Dyn函数去读取 Mailbox 控制状态,以决定进一步的操作。比如:HostPutMsg表示I2c接口写入的数据,RfPutMsg表示RF接口写入的数据。只有这两个值都为 0 时(也就是 Mailbox 中没有未读数据),才向 Mailbox 中写入新的数据。

思考题

更改程序,在多个地方添加CUSTOM_NFCTAG_ReadMBCtrl_Dyn函数,打印 Mailbox 状态,观察状态的变化,以完全掌握所有状态的真正含义。

RF 接口向 I2C 接口传送数据

刚才的程序是热身,接下来实现手机向单片机的数据传送。虽然官方有现成的例子,但示例过于复杂及庞大,没办法读懂,只能自行摸索了。过程很艰辛,需要读数据手册,官方文档,开发包源码,一点点试。网上无任何或以借鉴的文章,我算是开拓者,庆幸的是总算完成。

单片机程序

要读取 RF 端传送过来的数据,必须使用 GPO 引脚,并通过 GPO 脉冲引发的中断进行处理,你不能在主循环中持续读取 Mailbox 状态以判断 RF 端是否写入的数据。原来我是那样做的,但发现手机再也没办法通过 NFC 发现 ST25DV64K 芯片了,因为芯片一直在忙,没时间处理手机发过来的 RF 信号。使用中断是最佳选择,中断过来了再去处理数据,避免了长时间频繁的芯片访问。而默认情况下,RF 端向 Mailbox 中写入数据是不会触发 GPO 脉冲的,改变这种情况,需要写 GPO 相关寄存器。问题的关键在于官方包装了读 GPO 寄存器的相关方法,并没有包装写 GPO 寄存器的可直接使用的相关方法。这一块只能自己动手。请注意,在重新使用 STM32CubeMx 生成代码时,自己包装的这些方法会被删除。

GPO 寄存器

GPO 相关寄存器有 4 个,两个静态,两个动态。所谓静态寄存器就是掉电后状态会保存;动态寄存器是掉电后状态不保存,下次上电会载入对应的静态寄存器的内容。我只在官方开发包中找到更改 GPO 静态寄存器的比较好用的方法,所以本例更改的是静态寄存器,也就是说,只需更改一次,即可保存状态。本例更改的是 GPO 静态寄存器,长度一个字节,每个位在数据手册中介绍如下:

图 1:GPO 寄存器

我们需要开启的是 b4 位,当 RF 接口向 Mailbox 写入数据后,在 GPO 上发出脉冲。从上表可看出 GPO 寄存器出厂值为二进制的:10001000b,即十进制的 136。我们要将 b4 置 1,则 GPO 寄存器的值变为二进制的:10011000b,即十进制的 152。

准备工作总算做完,接下来可以进入正题。

GPO 中断回调函数

本来以为按照 STM32 常规的中断处理方法编写中断回调函数就完事了,但发现此路不能,最终读了半天源码才发现官方另外写了面向对象编程中所谓的虚方法__weak void BSP_GPO_Callback(void)(custom_nfc04a1.c文件中),我们只需在 main.c 中重写此方法即可。

打开 main.c 文件,找到/* USER CODE BEGIN PV */,在其下方添加如下代码:

int gpoTrigger = 0;

void BSP_GPO_Callback(void)
{
	gpoTrigger = 1;
}

如下图所示:

图 2:中断回调函数

写单片机程序的规则之一是不要在中断中做耗费时间长的事情,原本我是在回调函数中打印文件,发现只能打印第一个字母,之后的全部无法打印。所以打印这事只能放到主循环里去实现了,中断里只做一件事,设一个标志位,告诉主循环,有中断来了。

主函数代码

更改main()函数如下:

/* USER CODE BEGIN 1 */
uint8_t areaddata[16];
uint8_t cnt = 0;
uint16_t mblength;
ST25DV_EN_STATUS MB_mode;
ST25DV_PASSWD passwd;
ST25DV_MB_CTRL_DYN_STATUS mbctrldynstatus;

/* USER CODE END 1 */

/* MCU Configuration--------------------------------------------------------*/

/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();

/* USER CODE BEGIN Init */

/* USER CODE END Init */

/* Configure the system clock */
SystemClock_Config();

/* USER CODE BEGIN SysInit */

/* USER CODE END SysInit */

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USB_DEVICE_Init();
MX_TIM2_Init();
HAL_Delay(1000);
/* USER CODE BEGIN 2 */
//GPO初始化,GPO回调函数就是在此函数中注册的
int32_t ret;
ret = CUSTOM_GPO_Init();
printf("CUSTOM_GPO_Init return: %d\r\n", ret);

//以下为上一篇文章中的代码
printf("Start Mailbox Operation\r\n");
while( CUSTOM_NFCTAG_Init(CUSTOM_NFCTAG_INSTANCE) != NFCTAG_OK );
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);

HAL_Delay(1000);
CUSTOM_NFCTAG_ReadMBMode(CUSTOM_NFCTAG_INSTANCE, &MB_mode);
    
if(CUSTOM_NFCTAG_SetMBEN_Dyn(CUSTOM_NFCTAG_INSTANCE) == NFCTAG_OK)
{
    printf("Enable Mailbox Success\r\n");
}
else
{
    printf("Enable Mailbox failed\r\n");
}

//将GPO寄存器的值改为152,以打开RF写入中断,此操作之前需要证实密钥
passwd.MsbPasswd = 0;
passwd.LsbPasswd = 0;
ret = CUSTOM_NFCTAG_PresentI2CPassword(CUSTOM_NFCTAG_INSTANCE, passwd);
printf("CUSTOM_NFCTAG_PresentI2CPassword return: %d\r\n", ret); //返回0表示成功
//在进行写寄存器操作之前,必须要执行此句代码打开Mailbox写模式,
//否则写寄存器操作会以失败告终。
ret = CUSTOM_NFCTAG_WriteMBMode(CUSTOM_NFCTAG_INSTANCE, ST25DV_ENABLE);
printf("CUSTOM_NFCTAG_WriteMBMode: %d\r\n", ret); //返回0表示成功
//更改GPO寄存器的值,返回0表示成功
uint8_t gpo = 0x98;
ret = CUSTOM_NFCTAG_WriteRegister(CUSTOM_NFCTAG_INSTANCE,&gpo,ST25DV_GPO_REG,1);
printf("CUSTOM_NFCTAG_WriteRegister return: %d\r\n", ret);
//读取GPO寄存器的值,以确认更改是否成功,应显示152
uint8_t gpoConfig;
CUSTOM_NFCTAG_ReadGPO_Dyn(CUSTOM_NFCTAG_INSTANCE,&gpoConfig);
printf("CUSTOM_NFCTAG_ReadGPO_Dyn:%d\r\n",gpoConfig);
printf("Waiting for Mailbox data from RF...\r\n");
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
    if(gpoTrigger) //如果收到 GPO 中断
    {
        gpoTrigger = 0;
        CUSTOM_NFCTAG_ReadMBCtrl_Dyn( CUSTOM_NFCTAG_INSTANCE, &mbctrldynstatus );
        if(mbctrldynstatus.RfPutMsg) //如果为RF写入Mailbox事件
        { //读取Mailbox中写入的数据长度
            CUSTOM_NFCTAG_ReadMBLength_Dyn(CUSTOM_NFCTAG_INSTANCE, (uint8_t *)&mblength);
            mblength++;
            //读取Mailbox中接收到的数据
            CUSTOM_NFCTAG_ReadMailboxData(CUSTOM_NFCTAG_INSTANCE, areaddata, 0, mblength);
            //打印数据
            printf("Data read in Mailbox:\r\n");
            for( cnt = 0; cnt < mblength; cnt++ )
            {
                printf( "0x%02X ", areaddata[cnt] );
            }
            printf("\r\n");
        }
    }
}
/* USER CODE END 3 */

需要注意,手机靠近和离开都会触发 GPO 中断(可以通过写 GPO 寄存器关掉),所以在收到中断时需要判断是否为 RF 写入事件后再接收数据。运行程序,效果如下:

CUSTOM_GPO_Init return: 0
Start Mailbox Operation
Enable Mailbox Success
CUSTOM_NFCTAG_PresentI2CPassword return: 0
CUSTOM_NFCTAG_WriteMBMode: 0
CUSTOM_NFCTAG_WriteRegister return: 0
CUSTOM_NFCTAG_ReadGPO_Dyn:152
Waiting for Mailbox data from RF...

从运行结果可知,GPO 寄存器的值已经被更改为 152。但还没结束,需要手机端向 Mailbox 写入数据方能查看完整运行结果。

手机端程序

手机端官方有一个功能十分强大的示例,代码相当复杂,我是没办法看完的,只能找我想用的代码慢慢试了,费了些周折总算是弄完了。其实很简单,寄存器由单片机端设置,手机端只需要使用writeMailboxMessage方法发信息即可。

界面设计

本程序在《标签发现(Android)》这篇文章所写程序的基础上继续开发。首先更改界面代码,打开 activity_main.xml 文件,更改代码如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_Send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="向Mailbox发送数据"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:enabled="false"
        android:onClick="btnSend_OnClick"/>
    <TextView
        android:id="@+id/tv_Msg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@id/btn_Send" />

</androidx.constraintlayout.widget.ConstraintLayout>

界面很简单,一个发送信息按钮和一个用于显示提示信息的 TextView。

代码编写

打开 MainActivity.java 文件,更改代码如下:

package com.example.mailbox_android;

import androidx.appcompat.app.AppCompatActivity;

import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.st.st25sdk.NFCTag;
import com.st.st25sdk.STException;
import com.st.st25sdk.TagHelper;
import com.st.st25sdk.type5.st25dv.ST25DVTag;

public class MainActivity extends AppCompatActivity implements
        TagDiscovery.onTagDiscoveryCompletedListener {

    private NfcAdapter nfcAdapter;
    private ST25DVTag mTag;
    TextView tvMsg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
    }

    @Override //页面失去焦点
    protected void onPause() {
        super.onPause();
        if (nfcAdapter != null) {
            nfcAdapter.disableForegroundDispatch(this);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (nfcAdapter == null) {
            Toast.makeText(this, "对不起,您的设备不支持NFC功能!", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }
        if (!nfcAdapter.isEnabled()) {
            Toast.makeText(this, "请在系统设置中开启NFC功能!", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        PendingIntent pi = PendingIntent.getActivity(this, 0, new Intent(this,
                getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
        nfcAdapter.enableForegroundDispatch(this, pi, null, null);

        Intent intent = getIntent();
        String action = intent.getAction();
        Tag nfcTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        if (nfcTag != null) {
            new TagDiscovery(this).execute(nfcTag); //将之前的代码替换为这句
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        setIntent(intent);
    }

    @Override
    public void onTagDiscoveryCompleted(NFCTag nfcTag, TagHelper.ProductID productId, STException e) {
        tvMsg = findViewById(R.id.tv_Msg);
        Button btnSend = findViewById(R.id.btn_Send);
        if (nfcTag != null) {
            String tagName = nfcTag.getName();
            tvMsg.setText("找到标签:" + tagName);
            if (tagName.equals("ST25DV64K-J")) {
                mTag = (ST25DVTag) nfcTag;
                btnSend.setEnabled(true);
            }
        } else {
            tvMsg.setText("标签发现失败!");
        }
    }

    public void btnSend_OnClick(View view) {
        final TextView tvMsg = findViewById(R.id.tv_Msg);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    if (mTag.isMailboxEnabled(true)) {
                        //向 Mailbox 中写入数据
                        byte[] buff = {6, 5, 4, 3, 2};
                        final byte response;
                        response = mTag.writeMailboxMessage(buff);
                        if(response == 0) {
                            runOnUiThread(new Runnable() {
                                public void run() {
                                    tvMsg.setText("成功发送 5 个字节" );
                                }
                            });
                        }
                    } else {
                        runOnUiThread(new Runnable() {
                            public void run() {
                                tvMsg.setText("无法快速传输");
                            }
                        });
                    }
                } catch (STException e) {
                    tvMsg.setText("错误:" + e.getMessage());
                }
            }
        }).start();
    }
}

主要更改的代码在btnSend_OnClick方法里了。

运行程序

首先单片机烧写好之前的程序后,通上电,打开单片机多功能调试助手,做好设置以让程序处于接收 USB 信号状态。按下开发板重启按钮,直至调试助手中出现Waiting for Mailbox data from RF...字样。然后打开手机端程序,将手机摄像头贴近开发板开线部分,如果成功发现标签,会显示找到标签:ST25DV64K-J字样,并且【向Mailbox发送数据】按钮变为可用。按下【向Mailbox发送数据】按钮,观察调试助手,可发现打印出接收到的数据。以下是按两次【向Mailbox发送数据】按钮的结果:

CUSTOM_GPO_Init return: 0
Start Mailbox Operation
Enable Mailbox Success
CUSTOM_NFCTAG_PresentI2CPassword return: 0
CUSTOM_NFCTAG_WriteMBMode: 0
CUSTOM_NFCTAG_WriteRegister return: 0
CUSTOM_NFCTAG_ReadGPO_Dyn:152
Waiting for Mailbox data from RF...
Data read in Mailbox:
0x06 0x05 0x04 0x03 0x02 
Data read in Mailbox:
0x06 0x05 0x04 0x03 0x02 

总算大功告成了,不容易啊,接下来可以写一个相对好玩一点的程序了。

使用手机控制 RGB 灯

在我设计这块开发板的时候就已经想好了,最终效果是使用手机控制 RGB 灯的颜色。今天,终于可以实现,不容易啊!这块开发板主要用于验证,写完这个程序,此开发板也完成了其历史使命。

单片机程序

首先更改main函数外部的部分代码,注意,有一句是系统自动生成的代码,不要动它。

/* USER CODE BEGIN PV */
ST25DV_MB_CTRL_DYN_STATUS mbctrldynstatus;
uint16_t mblength;
uint8_t buff[3];
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void); //这一句是系统自动生成的代码
/* USER CODE BEGIN PFP */
void set_rgb(uint8_t red,uint8_t blue,uint8_t green)
{
	htim2.Instance->CCR1 = red;
	htim2.Instance->CCR2 = blue;
	htim2.Instance->CCR3 = green;
}

void BSP_GPO_Callback(void)
{
    CUSTOM_NFCTAG_ReadMBCtrl_Dyn( CUSTOM_NFCTAG_INSTANCE, &mbctrldynstatus );
    if(mbctrldynstatus.RfPutMsg) //如果为RF写入Mailbox事件
    { 
        //读取Mailbox中接收到的数据
        CUSTOM_NFCTAG_ReadMailboxData(CUSTOM_NFCTAG_INSTANCE, buff, 0, 3);
        //控制RGB灯
        set_rgb(buff[0], buff[1], buff[2]);
    }
}
/* USER CODE END PFP */

由于控制 PWM 耗时非常短,所以跟上段代码,控制灯的代码全部放在中断中完成。上位机就发送三个字节,每个字节分别代表R、G、B的数值。通过这三个数值调整 RGB 灯的颜色即可。

接下来更改main函数代码如下:

/* USER CODE BEGIN 1 */
ST25DV_PASSWD passwd;

/* USER CODE END 1 */

/* MCU Configuration--------------------------------------------------------*/

/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();

/* USER CODE BEGIN Init */

/* USER CODE END Init */

/* Configure the system clock */
SystemClock_Config();

/* USER CODE BEGIN SysInit */

/* USER CODE END SysInit */

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USB_DEVICE_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_3);
//GPO初始化,GPO回调函数就是在此函数中注册的
CUSTOM_GPO_Init();

while( CUSTOM_NFCTAG_Init(CUSTOM_NFCTAG_INSTANCE) != NFCTAG_OK );
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
    
CUSTOM_NFCTAG_SetMBEN_Dyn(CUSTOM_NFCTAG_INSTANCE);

//将GPO寄存器的值改为152,以打开RF写入中断,此操作之前需要证实密钥
passwd.MsbPasswd = 0;
passwd.LsbPasswd = 0;
CUSTOM_NFCTAG_PresentI2CPassword(CUSTOM_NFCTAG_INSTANCE, passwd);
//在进行写寄存器操作之前,必须要执行此句代码打开Mailbox写模式,
//否则写寄存器操作会以失败告终。
CUSTOM_NFCTAG_WriteMBMode(CUSTOM_NFCTAG_INSTANCE, ST25DV_ENABLE);
//更改GPO寄存器的值,返回0表示成功
uint8_t gpo = 0x98;
CUSTOM_NFCTAG_WriteRegister(CUSTOM_NFCTAG_INSTANCE,&gpo,ST25DV_GPO_REG,1);
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

这次代码少了很多,USB 口打印的代码全部被我删除了。另外,如果静态寄存器的值如果没有更改,还是152,完全可以将密钥证实到写 GPO 寄存器的代码删除,做到更为简洁。

手机端程序

手机端也是接着上一个程序继续写。

界面代码

打开 activity_main.xml 文件,更改代码如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/tv_Msg"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:text="Hello World!"
        android:textAlignment="center"
        android:layout_marginTop="100dp"/>

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="90dp">
        <Button
            android:id="@+id/btn_red"
            android:layout_width="match_parent"
            android:layout_height="70dp"
            android:text="红"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:background="#FF0000"
            android:onClick="btn_OnClick"/>
        <Button
            android:id="@+id/btn_orange"
            android:layout_width="match_parent"
            android:layout_height="70dp"
            android:text="橙"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:background="#FF7F00"
            android:onClick="btn_OnClick"/>
        <Button
            android:id="@+id/btn_yellow"
            android:layout_width="match_parent"
            android:layout_height="70dp"
            android:text="黄"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:background="#FFFF00"
            android:onClick="btn_OnClick"/>
        <Button
            android:id="@+id/btn_green"
            android:layout_width="match_parent"
            android:layout_height="70dp"
            android:text="绿"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:background="#00FF00"
            android:onClick="btn_OnClick"/>

    </androidx.appcompat.widget.LinearLayoutCompat>
    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="wrap_content"
        android:layout_height="90dp">
        <Button
            android:id="@+id/btn_cyan"
            android:layout_width="match_parent"
            android:layout_height="70dp"
            android:text="青"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:background="#00FFFF"
            android:onClick="btn_OnClick"/>
        <Button
            android:id="@+id/btn_blue"
            android:layout_width="match_parent"
            android:layout_height="70dp"
            android:text="蓝"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:background="#0000FF"
            android:onClick="btn_OnClick"/>
        <Button
            android:id="@+id/btn_purple"
            android:layout_width="match_parent"
            android:layout_height="70dp"
            android:text="紫"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:background="#FF00FF"
            android:onClick="btn_OnClick"/>
        <Button
            android:id="@+id/btn_white"
            android:layout_width="match_parent"
            android:layout_height="70dp"
            android:text="白"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:background="#FFFFFF"
            android:onClick="btn_OnClick"/>

    </androidx.appcompat.widget.LinearLayoutCompat>

</androidx.appcompat.widget.LinearLayoutCompat>

####程序代码 打开 MainActivity.java,更改代码如下:

package com.example.mailbox_android;

import androidx.appcompat.app.AppCompatActivity;

import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.st.st25sdk.NFCTag;
import com.st.st25sdk.STException;
import com.st.st25sdk.TagHelper;
import com.st.st25sdk.type5.st25dv.ST25DVTag;

public class MainActivity extends AppCompatActivity implements
        TagDiscovery.onTagDiscoveryCompletedListener {

    private NfcAdapter nfcAdapter;
    private ST25DVTag mTag;
    TextView tvMsg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
    }

    @Override //页面失去焦点
    protected void onPause() {
        super.onPause();
        if (nfcAdapter != null) {
            nfcAdapter.disableForegroundDispatch(this);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (nfcAdapter == null) {
            Toast.makeText(this, "对不起,您的设备不支持NFC功能!", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }
        if (!nfcAdapter.isEnabled()) {
            Toast.makeText(this, "请在系统设置中开启NFC功能!", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        PendingIntent pi = PendingIntent.getActivity(this, 0, new Intent(this,
                getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
        nfcAdapter.enableForegroundDispatch(this, pi, null, null);

        Intent intent = getIntent();
        String action = intent.getAction();
        Tag nfcTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        if (nfcTag != null) {
            new TagDiscovery(this).execute(nfcTag); //将之前的代码替换为这句
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        setIntent(intent);
    }

    @Override
    public void onTagDiscoveryCompleted(NFCTag nfcTag, TagHelper.ProductID productId, STException e) {
        tvMsg = findViewById(R.id.tv_Msg);
        if (nfcTag != null) {
            String tagName = nfcTag.getName();
            tvMsg.setText("找到标签:" + tagName);
            if (tagName.equals("ST25DV64K-J")) {
                mTag = (ST25DVTag) nfcTag;
            }
        } else {
            tvMsg.setText("标签发现失败!");
        }
    }

    public void btn_OnClick(final View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    if (mTag.isMailboxEnabled(true)) {
                        //向 Mailbox 中写入数据
                        byte[] buff = new byte[3];
                        int id = view.getId();
                        if (id == R.id.btn_red) {
                            buff = new byte[]{(byte) 255, 0, 0};
                        } else if (id == R.id.btn_orange) {
                            buff = new byte[]{(byte) 255, (byte) 127, 0};
                        } else if (id == R.id.btn_yellow) {
                            buff = new byte[]{(byte) 255, (byte) 255, 0};
                        } else if (id == R.id.btn_green) {
                            buff = new byte[]{0, (byte) 255, 0};
                        } else if (id == R.id.btn_cyan) {
                            buff = new byte[]{0, (byte) 255, (byte) 255};
                        } else if (id == R.id.btn_blue) {
                            buff = new byte[]{0, 0, (byte) 255};
                        } else if (id == R.id.btn_purple) {
                            buff = new byte[]{(byte) 255, 0, (byte) 255};
                        } else if (id == R.id.btn_white) {
                            buff = new byte[]{(byte) 255, (byte) 255, (byte) 255};
                        }
                        final byte response;
                        response = mTag.writeMailboxMessage(buff);
                        if (response != 0) {
                            runOnUiThread(new Runnable() {
                                public void run() {
                                    tvMsg.setText("发送失败!");
                                }
                            });
                        }
                    } else {
                        runOnUiThread(new Runnable() {
                            public void run() {
                                tvMsg.setText("无法快速传输");
                            }
                        });
                    }
                } catch (STException e) {
                    tvMsg.setText("错误:" + e.getMessage());
                }
            }
        }).start();
    }
}

主要更改了btn_OnClick方法的代码,以及将之前的发送按钮相关代码删除。

程序运行效果做了个 gif 动画,如下所示:

图 3:运行效果
;

© 2018 - IOT小分队文章发布系统 v0.3