From 57522caa0bed023079678c1e26a1ab94843029d1 Mon Sep 17 00:00:00 2001 From: aibin Date: Wed, 5 Jan 2022 16:16:47 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=96=B0=E5=A2=9ENFC=E7=A2=B0=E4=B8=80?= =?UTF-8?q?=E7=A2=B0WiFi=E9=85=8D=E7=BD=91=E6=A1=88=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: aibin Changes to be committed: modified: applications/app/BUILD.gn new file: applications/app/TW401_APP_wifi_nfc/BUILD.gn new file: applications/app/TW401_APP_wifi_nfc/README.md new file: applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/README.md new file: applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/adapter/tw_nfc_adapt.c new file: applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/adapter/tw_nfc_adapt.h new file: applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc.c new file: applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc.h new file: applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_i2c.c new file: applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_i2c.h new file: applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_ndef.c new file: applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_ndef.h new file: applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/interfaces/tw_nfc_kits.h new file: applications/app/TW401_APP_wifi_nfc/tw_wifi_nfc.c new file: applications/app/TW401_APP_wifi_nfc/tw_wifi_sta.c new file: applications/app/TW401_APP_wifi_nfc/tw_wifi_sta.h --- applications/app/BUILD.gn | 3 +- applications/app/TW401_APP_wifi_nfc/BUILD.gn | 37 +++ applications/app/TW401_APP_wifi_nfc/README.md | 252 ++++++++++++++++++ .../TW401_APP_wifi_nfc/tw_nfc_lib/README.md | 133 +++++++++ .../tw_nfc_lib/adapter/tw_nfc_adapt.c | 76 ++++++ .../tw_nfc_lib/adapter/tw_nfc_adapt.h | 66 +++++ .../tw_nfc_lib/frameworks/tw_nfc.c | 180 +++++++++++++ .../tw_nfc_lib/frameworks/tw_nfc.h | 40 +++ .../tw_nfc_lib/frameworks/tw_nfc_i2c.c | 158 +++++++++++ .../tw_nfc_lib/frameworks/tw_nfc_i2c.h | 37 +++ .../tw_nfc_lib/frameworks/tw_nfc_ndef.c | 155 +++++++++++ .../tw_nfc_lib/frameworks/tw_nfc_ndef.h | 119 +++++++++ .../tw_nfc_lib/interfaces/tw_nfc_kits.h | 52 ++++ .../app/TW401_APP_wifi_nfc/tw_wifi_nfc.c | 97 +++++++ .../app/TW401_APP_wifi_nfc/tw_wifi_sta.c | 146 ++++++++++ .../app/TW401_APP_wifi_nfc/tw_wifi_sta.h | 31 +++ 16 files changed, 1581 insertions(+), 1 deletion(-) create mode 100755 applications/app/TW401_APP_wifi_nfc/BUILD.gn create mode 100755 applications/app/TW401_APP_wifi_nfc/README.md create mode 100755 applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/README.md create mode 100755 applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/adapter/tw_nfc_adapt.c create mode 100755 applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/adapter/tw_nfc_adapt.h create mode 100755 applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc.c create mode 100755 applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc.h create mode 100755 applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_i2c.c create mode 100755 applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_i2c.h create mode 100755 applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_ndef.c create mode 100755 applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_ndef.h create mode 100755 applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/interfaces/tw_nfc_kits.h create mode 100755 applications/app/TW401_APP_wifi_nfc/tw_wifi_nfc.c create mode 100755 applications/app/TW401_APP_wifi_nfc/tw_wifi_sta.c create mode 100755 applications/app/TW401_APP_wifi_nfc/tw_wifi_sta.h diff --git a/applications/app/BUILD.gn b/applications/app/BUILD.gn index ef1e2be6..5c29d0c4 100755 --- a/applications/app/BUILD.gn +++ b/applications/app/BUILD.gn @@ -40,11 +40,12 @@ lite_component("app") { # "TW301_Network_wifista:network_wifista_example", # "TW302_Network_wifiap:network_wifiap_example", # "TW303_Network_mqttclient:network_mqttclient_example", - # "TW402_APP_oled_u8g2:app_oled_u8g2_example", # "TW304_Network_tcpserver:network_tcpserver_demo", # "TW304_Network_tcpclient:network_tcpclient_demo", # "TW305_Network_udpclient:network_udpclient_demo", # "TW305_Network_udpserver:network_udpserver_demo", # "TW306_Network_httpclient:network_httpclient_demo", + # "TW401_APP_wifi_nfc:app_wifi_nfc_example", + # "TW402_APP_oled_u8g2:app_oled_u8g2_example", ] } \ No newline at end of file diff --git a/applications/app/TW401_APP_wifi_nfc/BUILD.gn b/applications/app/TW401_APP_wifi_nfc/BUILD.gn new file mode 100755 index 00000000..ea190c61 --- /dev/null +++ b/applications/app/TW401_APP_wifi_nfc/BUILD.gn @@ -0,0 +1,37 @@ +# Copyright (c) 2021 Talkweb Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +static_library("app_wifi_nfc_example") { + + sources = [ + "tw_wifi_sta.c", + "tw_wifi_nfc.c", + "tw_nfc_lib/adapter/tw_nfc_adapt.c", + "tw_nfc_lib/frameworks/tw_nfc_i2c.c", + "tw_nfc_lib/frameworks/tw_nfc_ndef.c", + "tw_nfc_lib/frameworks/tw_nfc.c", + ] + + include_dirs = [ + "//third_party/cJSON", + "//utils/native/lite/include", + "//kernel/liteos_m/components/cmsis/2.0", + "//base/iot_hardware/peripheral/interfaces/kits", + "//foundation/communication/wifi_lite/interfaces/wifiservice", + "//device/talkweb/niobe/sdk_liteos/third_party/lwip_sack/include", + "//applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/interfaces", + "//applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks", + "//applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/adapter", + ] + +} diff --git a/applications/app/TW401_APP_wifi_nfc/README.md b/applications/app/TW401_APP_wifi_nfc/README.md new file mode 100755 index 00000000..7a610096 --- /dev/null +++ b/applications/app/TW401_APP_wifi_nfc/README.md @@ -0,0 +1,252 @@ +# Niobe开发板OLED演示HarmonyOS开机动画 + +本案例程序将演示怎么在拓维Niobe WiFi IoT OLED开发板上,使用u8g2图形库显示HarmonyOS开机动画和品牌LOGO。 + +u8g2 是一个强大的图形库,支持非常多的显示屏。SSD1306就是其支持的一款OLED显示屏幕。图形库通过提供API函数的方法,使用户可以非常方便的显示各种各样图标,字符甚至动效等。 + +u8g2库已经移植到`third_party/u8g2/`目录下方。 + +## U8G2 API分析 + +本案例主要使用了以下几个API完成开机动画显示。 + +## u8g2_Setup_ssd1306_i2c_128x64_noname_f() + +``` +void u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb); +``` + +**描述:** + +u8g2在SSD1306芯片上,使用I2C通信的初始化设置函数,支持128x64点阵像素,支持全屏缓存模式。 + +**参数:** + +| 名字 | 描述 | +| ----------------- | ------------------------------------------------- | +| u8g2 | 表示u8g2信息 | +| ratation | 表示屏幕旋转方向。本例设置为U8G2_R0(横屏不旋转) | +| byte_cb | 字节消息回调接口 | +| gpio_and_delay_cb | GPIO操作消息和延迟消息的回调接口 | + +## u8g2_InitDisplay() + +``` +#define u8g2_InitDisplay(u8g2) u8x8_InitDisplay(u8g2_GetU8x8(u8g2)) +``` + +**描述:** + + 初始OLED显示器,将向OLED显示器发送init消息。此函数会触发两种操作,一种是OLED硬件初始,一种是OLED显示屏初始清屏。 + +**参数:** + +| 名字 | 描述 | +| ---- | ------------ | +| u8g2 | 表示u8g2信息 | + +## u8g2_SetPowerSave() + +``` +#define u8g2_SetPowerSave(u8g2, is_enable) u8x8_SetPowerSave(u8g2_GetU8x8(u8g2), (is_enable)) +``` + +**描述:** + +设置省电模式是否启动。 + +**参数:** + +| 名字 | 描述 | +| --------- | ---------------- | +| u8g2 | 表示u8g2信息 | +| is_enable | 是否省电模式可用 | + +## u8g2_FirstPage() + +``` +void u8g2_FirstPage(u8g2_t *u8g2); +``` + +**描述:** + +分页模式下,向OLED显示器发送第一页缓冲数据,显示第一页。 + +**参数:** + +| 名字 | 描述 | +| ---- | ------------ | +| u8g2 | 表示u8g2信息 | + +## u8g2_NextPage() + +``` +uint8_t u8g2_NextPage(u8g2_t *u8g2); +``` + +**描述:** + +分页模式下,判断缓冲区中是否还有下一页内容。 + +**参数:** + +| 名字 | 描述 | +| ---- | ------------ | +| u8g2 | 表示u8g2信息 | + +## u8g2_DrawXBMP() + +``` +void u8g2_DrawXBMP(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, const uint8_t *bitmap); +``` + +**描述:** + +在程序中使用位图,绘制bitmap位图到OLED显示器中。 + +**参数:** + +| 名字 | 描述 | +| ------ | ---------------- | +| u8g2 | 表示u8g2信息 | +| x | x轴上的起点坐标 | +| y | y轴上的起点坐标 | +| w | 位图的宽度 | +| h | 位图的高度 | +| bitmap | 位图字节数据指针 | + +## 软件设计 + +**主要代码分析** + +开发板启动直接显示`未来已来,远方不远`的Talkweb Logo,按键一次后可以进入HarmonyOS开机动画。 + +1. DrawTalkwebLogo函数封装了“未来已来,远方不远”的Talkweb Logo位图绘制过程。 + +2. DrawHarmonyOSAnimation函数封装了HarmonyOS开机动画的绘图绘制过程。 + +3. while循环进行HarmonyOS开机动画和Talkweb Logo循环显示。 + +4. GPIO按键中断改变`g_startBootAnimationFlag`的值,来控制while循环的进出。 + + +```c +static void GpioSsdIsrFunc(char *arg) +{ + (void)arg; + + //消除按键抖动 + unsigned int tickCount = osKernelGetTickCount(); + unsigned int count = tickCount - g_lastTickCount; + g_lastTickCount = tickCount; + + if (count > 50) + { + g_startBootAnimationFlag ^= 1; + osSemaphoreRelease(g_gpioIsrSemId); + } + + return; +} + +/** + * 初始化开发板上的按键GPIO中断 + */ +static void InitOledKeyGpioIsr(void) +{ + g_gpioIsrSemId = osSemaphoreNew(32, 0, NULL); + + IoTGpioInit(IOT_GPIO_IO_GPIO_5); + IotIoSetFunc(IOT_GPIO_IO_GPIO_5, HI_IO_FUNC_GPIO_5_GPIO); + IotIoSetPull(IOT_GPIO_IO_GPIO_5, IOT_IO_PULL_UP); + + uint32_t ret = 0; + ret = IoTGpioSetDir(IOT_GPIO_IO_GPIO_5, IOT_GPIO_DIR_IN); + if (ret != 0) + { + printf("===== ERROR ======gpio -> hi_gpio_set_dir1 ret:%d\r\n", ret); + return; + } + ret = IoTGpioRegisterIsrFunc(IOT_GPIO_IO_GPIO_5, IOT_INT_TYPE_EDGE, IOT_GPIO_EDGE_RISE_LEVEL_HIGH, GpioSsdIsrFunc, NULL); + + return; +} + +/** + * 绘制HarmonyOS开机动画 + */ +static void DrawHarmonyOSAnimation(void) +{ + for (int i = 0; i < 58; i++) + { + u8g2_FirstPage(&u8g2); + do + { + u8g2_DrawXBMP(&u8g2, 0, 0, 128, 64, openHarmony_All[i]); + } while (u8g2_NextPage(&u8g2)); + } +} + +/** + * 绘制Talkweb LOGO,未来已来,远方不远 + */ +static void DrawTalkwebLogo(void) +{ + u8g2_FirstPage(&u8g2); + do + { + u8g2_DrawXBMP(&u8g2, 0, 0, 128, 64, talkweb_end); + } while (u8g2_NextPage(&u8g2)); +} + +static void OledU8g2Task(void) +{ + //初始化OLED开发板上的按键中断事件 + InitOledKeyGpioIsr(); + + //U8G2_R0:屏幕旋转方向,横屏不旋转。 + u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, U8g2Hi3861I2cByteCb, U8g2Hi3861GpioAndDelayCb); + + //初始OLED显示器,将向OLED显示器发送init消息 + //此函数会触发两种操作,一种是OLED硬件初始,一种是OLED显示屏初始清屏。 + u8g2_InitDisplay(&u8g2); + + //开启省电模式 + u8g2_SetPowerSave(&u8g2, 0); + + while (1) + { + DrawTalkwebLogo(); + osSemaphoreAcquire(g_gpioIsrSemId, osWaitForever); + + while (g_startBootAnimationFlag) + { + DrawHarmonyOSAnimation(); + osDelay(300U); + DrawTalkwebLogo(); + osDelay(300U); + } + } +} +``` + +## 编译调试 + +### 修改 BUILD.gn 文件 + +修改 `applications/app/BUILD.gn` 路径中的 BUILD.gn 文件,指定 `app_oled_u8g2_example` 参与编译。 + +``` +# "TW303_Network_mqttclient:network_mqttclient_example", +# "TW304_Network_httpclient:network_httpclient_example", +# "TW305_Network_ntpclient:network_ntpclient_example", +"TW402_APP_oled_u8g2:app_oled_u8g2_example", +``` + +### 运行结果 + +示例代码编译烧录代码后,按下开发板的RESET按键,可以直接看到OLED开发板的开机Talkweb Logo,按下开发板上的SW6/SW7按键,可以进入HarmonyOS动画循环播放,再按下SW6/SW7按键,可以停止HarmonyOS动画。 +SW6/SW7按键在OLED开发板上,在正下方中部左侧是SW6按键,右侧是SW7按键,且开发板上有标注。 + +![image-20210930161121332](figure/image-20210930161121332.gif) + diff --git a/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/README.md b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/README.md new file mode 100755 index 00000000..debcfe73 --- /dev/null +++ b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/README.md @@ -0,0 +1,133 @@ +# tw_components_nfc_lite + +## nfc轻量系统工具包 + +此工具包实现了nfc读写标签的一些基本功能,使用的标准ndef格式。 + +## 基础环境构建 + +测试框架依赖于python运行环境,在使用测试框架之前可参阅以下方式进行配置。 + +- [环境配置](https://gitee.com/openharmony/docs/tree/master/zh-cn/device-dev/subsystems/subsys-testguide-envbuild.md) + +## 预备知识 + +### NDEF(NFC Data Exchange Format) + +NFC数据交换格式(NDEF)是一种标准化的数据格式,可用于在任何兼容的NFC设备和另一个NFC设备或标签之间交换信息。该数据格式由NDEF信息和NDEF记录组成。 + +NDEF格式用于存储和交换信息,如URI、纯文本等,使用一种普遍理解的格式。像一些NFC标签可被配置为NDEF标签,由一个NFC设备写入可以被任何其他NDEF兼容设备理解和访问的数据(NDEF记录)。 + +NDEF信息也可以用来在两个活跃的NFC设备之间以 "点对点 "模式交换数据。通过在通信过程中遵守NDEF数据交换格式,那些本来对彼此没有任何意义或共同语言的设备能够以一种有组织的、可相互 + +理解的方式分享数据。 + +NDEF消息是NDEF记录的基本 "运输 "机制,每个消息包含一个或多个NDEF记录。 + +### TLV + +为了使手机准确识别NFC标签信息,需要使用TLV格式。 + +TLV:Tag,Length,Value组成的数据单元。Tag一个字节,Length一个字节,然后是接着数据payload,数据长度和length一样。 + + +## 目录简介 + +以下是nfc轻量系统工具包框架的目录层级架构,在使用此框架过程中可在相应目录查找对应组件。 + +``` +nfc +├── adapter +├── frameworks +├── interfaces +├── test +├── BUILD.gn +└── README.md +``` + +### adapter:nfc工具库适配目录 + +#### adapter/tw_nfc_adapt: 底层适配文件 + +底层适配文件主要包含两部分: + +1. I2C接口的初始化和读写工作 + +2. 与NFC芯片相关的一些参数定义,包括NFC芯片地址、EEPROM起始地址、页大小等... + +### frameworks:核心框架代码存放目录 + +#### frameworks/tw_nfc_i2c: i2c读写NFC芯片的接口封装 + +主要API接口: + +1. I2C写NFC芯片EEPROM数据的功能函数: + + BOOL TWNfcI2CWriteMem(unsigned int addr, MemFrame *mem) + +2. I2C读NFC芯片EEPROM数据的功能函数: + + BOOL TWNfcI2CReadMem(unsigned int addr, unsigned int readSize, unsigned char *memBuf) + +#### frameworks/tw_nfc_ndef: nfc标准ndef格式相关函数 + +主要API接口: + +1. ndef包制作函数: + + BOOL TWNdefPackMake(NdefFrame *ndef, MemFrame *mem) + +2. ndef包解析函数: + + BOOL TWNdefPackParsing(NdefFrame *ndef, unsigned char *mem) + +#### frameworks/tw_nfc: nfc读写标签相关函数 + +主要API接口: + +1. 写nfc标签函数: + + BOOL TWNfcTagWrite(NdefFrame *param, unsigned char tagCnt) + +2. 读nfc标签函数: + + NdefParsingList *TWNfcTagRead(void) + +3. nfc读标签后的内存释放函数: + + void NfcReadTagFree(NdefParsingList *list); + + 注意: 读标签函数会使用malloc函数,请在处理完读取到的标签数据后,使用NfcReadTagFree()函数进行内存释放!!! + +### interfaces/tw_nfc_kits: API接口存放目录 + + 用户调用nfc工具包接口的头目录 + +### test:模块测试用例代码存放目录 + + 此目录中演示了如何使用nfc工具库进行标签的写入和读取。 + +### test/tw_nfc_readtag + + nfc标签读取功能的演示demo + +### test/tw_nfc_writetag + + nfc标签写入功能的演示demo + + 注意:当需要使用测试demo时,需在BUILD.gn文件中打开相关文件的编译选项 + +BUILD:模块编译文件 + +README:模块介绍文件 + +## 特别注意 + +此工具包暂时只支持以下ndef格式: + +1. 单个ndef record数据payload长度最大只支持256byte(默认为256byte),可在tw_nfc_ndef.h中修改定义 + +2. 只支持标准RTD格式中的UTF8数据格式和URI数据格式 + +3. 不支持带ID的ndef包格式 + diff --git a/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/adapter/tw_nfc_adapt.c b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/adapter/tw_nfc_adapt.c new file mode 100755 index 00000000..3b975708 --- /dev/null +++ b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/adapter/tw_nfc_adapt.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021 Talkweb Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "tw_nfc_adapt.h" + +void TWNfcI2cInit(void) +{ + unsigned int ret = 0; + + IoTGpioInit(IOT_GPIO_IO_GPIO_13); + IoTGpioInit(IOT_GPIO_IO_GPIO_14); + + IoTIoSetPull(IOT_GPIO_IO_GPIO_13, IOT_IO_PULL_UP); + IoTIoSetPull(IOT_GPIO_IO_GPIO_14, IOT_IO_PULL_UP); + + IoTIoSetFunc(IOT_GPIO_IO_GPIO_13, IOT_IO_FUNC_GPIO_13_I2C0_SDA); + IoTIoSetFunc(IOT_GPIO_IO_GPIO_14, IOT_IO_FUNC_GPIO_14_I2C0_SCL); + + ret = IoTI2cInit(NFC_I2C_INDEX, 400000); + if (ret != IOT_SUCCESS) + { + printf("I2c Init failed 0, ret = %u\r\n", ret); + } + + // csn引脚 + IoTGpioInit(IOT_GPIO_IO_GPIO_9); + IoTIoSetFunc(IOT_GPIO_IO_GPIO_9, 0); + IoTIoSetPull(IOT_GPIO_IO_GPIO_9, IOT_IO_PULL_UP); + IoTGpioSetDir(IOT_GPIO_IO_GPIO_9, IOT_GPIO_DIR_OUT); + IoTGpioSetOutputVal(IOT_GPIO_IO_GPIO_9, 0); +} + +int NfcI2cWrite(const unsigned char *data, unsigned int dataLen) +{ + osDelay(1); + unsigned int ret = IoTI2cWrite(NFC_I2C_INDEX, NFC_WRITE_ADDR, data, dataLen); + if (ret != IOT_SUCCESS) + { + osDelay(1); + ret = IoTI2cWrite(NFC_I2C_INDEX, NFC_WRITE_ADDR, data, dataLen); + if (ret != IOT_SUCCESS) + { + printf("IoTI2cWrite fail 1, ret = %u\r\n", ret); + return IOT_FAILURE; + } + } + + return IOT_SUCCESS; +} + +int NfcI2cRead(const unsigned char *data, unsigned int dataLen) +{ + unsigned int ret = IoTI2cRead(NFC_I2C_INDEX, NFC_READ_ADDR, data, dataLen); + if (ret != IOT_SUCCESS) + { + osDelay(1); + ret = IoTI2cRead(NFC_I2C_INDEX, NFC_READ_ADDR, data, dataLen); + if (ret != IOT_SUCCESS) + { + printf("IoTI2cRead fail 5, ret = %u\r\n", ret); + return IOT_FAILURE; + } + } + return IOT_SUCCESS; +} \ No newline at end of file diff --git a/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/adapter/tw_nfc_adapt.h b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/adapter/tw_nfc_adapt.h new file mode 100755 index 00000000..13d117d0 --- /dev/null +++ b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/adapter/tw_nfc_adapt.h @@ -0,0 +1,66 @@ +#ifndef _TW_WIFI_ADAPT_H +#define _TW_WIFI_ADAPT_H +/* + * Copyright (c) 2021 Talkweb Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "ohos_init.h" +#include "cmsis_os2.h" +#include "iot_i2c.h" +#include "iot_errno.h" +#include "iot_gpio.h" + +#ifndef TRUE +#define TRUE 1U +#endif + +#ifndef FALSE +#define FALSE 0U +#endif + +#ifndef BOOL +#define BOOL unsigned char +#endif + +// NFC I2C索引号 +#define NFC_I2C_INDEX IOT_I2C_IDX_0 + +// NFC 芯片地址 +#define NFC_ADDR 0xAE + +// NFC 芯片写地址 +#define NFC_WRITE_ADDR 0xAE + +// NFC 芯片读地址 +#define NFC_READ_ADDR 0xAF + +// NFC 芯片EEPROM 页大小 +#define NFC_PAGE_SIZE 16 + +// NFC 芯片EEPROM 开始地址 +#define NFC_EEPROM_ADDR_START 0X10 + +// NFC 芯片EEPROM 结束地址 +#define NFC_EEPROM_ADDR_END 0X387 + +// NFC 芯片EEPROM读写区域大小 +#define NFC_EEPROM_SIZE (NFC_EEPROM_ADDR_END - NFC_EEPROM_ADDR_START + 1) + +void TWNfcI2cInit(void); +int NfcI2cWrite(const unsigned char *data, unsigned int dataLen); +int NfcI2cRead(const unsigned char *data, unsigned int dataLen); + +#endif diff --git a/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc.c b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc.c new file mode 100755 index 00000000..46756f33 --- /dev/null +++ b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc.c @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2021 Talkweb Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "tw_nfc.h" +#include + +/** + * @brief 写NFC标签函数 + * @param param 待写入的NFC标签信息 + * @param tagCnt 待写入的NFC标签个数 + * @return 返回TRUE表示写入NFC标签成功 + */ +BOOL TWNfcTagWrite(NdefFrame *param, unsigned char tagCnt) +{ + if (param == NULL) + { + printf("TWNfcTagWrite() param err!\r\n"); + return FALSE; + } + + if (tagCnt <= 0) + { + printf("TWNfcTagWrite() tagCnt <= 0!\r\n"); + return FALSE; + } + + MemFrame *memFrame = (MemFrame *)malloc(sizeof(MemFrame) * tagCnt); + if (memFrame == NULL) + { + printf("TWNfcTagWrite() malloc err!\r\n"); + return FALSE; + } + memset(memFrame, 0, sizeof(MemFrame) * tagCnt); + + // 获取TLV协议格式中的消息长度 + unsigned short tlvLen = 0; + for (unsigned int i = 0; i < tagCnt; i++) + { + if (TWNdefPackMake(¶m[i], &memFrame[i]) != TRUE) + { + printf("TWNdefPackMake() tagCnt[%u] err!\r\n", i); + free(memFrame); + return FALSE; + } + tlvLen += memFrame[i].len; + } + + unsigned int nfcStartAddr = 0; + MemFrame tlvHeadBuf = {0}; + tlvHeadBuf.data[0] = NFC_TLV_HEAD; + if (tlvLen > 255) + { + tlvHeadBuf.data[1] = 0xff; + tlvHeadBuf.data[2] = (unsigned char)(tlvLen >> 8) & 0xff; + tlvHeadBuf.data[3] = (unsigned char)(tlvLen >> 0) & 0xff; + tlvHeadBuf.len = 4; + nfcStartAddr = NFC_EEPROM_ADDR_START + 4; + } + else + { + tlvHeadBuf.data[1] = tlvLen; + tlvHeadBuf.len = 2; + nfcStartAddr = NFC_EEPROM_ADDR_START + 2; + } + + if (TWNfcI2CWriteMem(NFC_EEPROM_ADDR_START, &tlvHeadBuf) != TRUE) + { + printf("TWNfcI2CWriteMem() tlvHeadBuf err!\r\n"); + free(memFrame); + return FALSE; + } + + for (unsigned int i = 0; i < tagCnt; i++) + { + if (TWNfcI2CWriteMem(nfcStartAddr, &memFrame[i]) != TRUE) + { + printf("TWNfcI2CWriteMem() tagCnt[%u] err!\r\n", i); + free(memFrame); + return FALSE; + } + nfcStartAddr += memFrame[i].len; + } + + free(memFrame); + return TRUE; +} + +/** + * @brief 读NFC标签函数 + * @return 返回读取到的NFC标签链表头 + */ +NdefParsingList *TWNfcTagRead(void) +{ + unsigned char *memBuf = (unsigned char *)malloc(NFC_EEPROM_SIZE); + if (memBuf == NULL) + { + printf("TWNfcTagRead() malloc err!\r\n"); + return NULL; + } + memset(memBuf, 0, NFC_EEPROM_SIZE); + + TWNfcI2CReadMem(NFC_EEPROM_ADDR_START, NFC_EEPROM_SIZE, memBuf); + + if (memBuf[0] != NFC_TLV_HEAD) + { + printf("TWNfcTagRead() err: memBuf[0] != %02x \r\n", NFC_TLV_HEAD); + free(memBuf); + return NULL; + } + + unsigned int memLen = 0; + unsigned int startAddr = 0; + if (memBuf[1] == 0xff) + { + memLen = (memBuf[2] << 8) | memBuf[3]; + startAddr = 4; + } + else + { + memLen = memBuf[1]; + startAddr = 2; + } + + if (memLen > (NFC_EEPROM_SIZE - startAddr)) + { + printf("TWNfcTagRead() err: tlv memLen overflow, memLen = %u\r\n", memLen); + free(memBuf); + return NULL; + } + + NdefParsingList *head, *node, *end; + end = head = (NdefParsingList *)malloc(sizeof(NdefParsingList)); + while (startAddr <= memLen) + { + node = (NdefParsingList *)malloc(sizeof(NdefParsingList)); + end->next = node; + node->next = NULL; + end = node; + + if (TWNdefPackParsing(&node->ndef, &memBuf[startAddr]) != TRUE) + { + printf("TWNdefPackParsing() err\r\n"); + free(memBuf); + NfcReadTagFree(head); + return NULL; + } + startAddr += (node->ndef.payloadLen + 4); + startAddr += (node->ndef.dataType == RTD_UTF8) ? 3 : 1; + } + + free(memBuf); + return (NdefParsingList*)head; +} + +/** + * @brief 读NFC标签释放函数 + * @param list 待释放的NFC标签链表头 + */ +void NfcReadTagFree(NdefParsingList *list) +{ + NdefParsingList *p = list, *pr = NULL; + + while (p != NULL) + { + pr = p; + p = p->next; + free(pr); + } +} diff --git a/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc.h b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc.h new file mode 100755 index 00000000..39d4ae28 --- /dev/null +++ b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Talkweb Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _TW_NFC_H +#define _TW_NFC_H +#include "tw_nfc_i2c.h" +#include "tw_nfc_ndef.h" + +/** + * @brief 写NFC标签函数 + * @param param 待写入的NFC标签信息 + * @param tagCnt 待写入的NFC标签个数 + * @return 返回TRUE表示写入NFC标签成功 + */ +BOOL TWNfcTagWrite(NdefFrame *param, unsigned char tagCnt); + +/** + * @brief 读NFC标签函数 + * @return 返回读取到的NFC标签链表头 + */ +NdefParsingList *TWNfcTagRead(void); + +/** + * @brief 读NFC标签释放函数 + * @param list 待释放的NFC标签链表头 + */ +void NfcReadTagFree(NdefParsingList *list); + +#endif diff --git a/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_i2c.c b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_i2c.c new file mode 100755 index 00000000..4669f2cf --- /dev/null +++ b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_i2c.c @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2021 Talkweb Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "tw_nfc_i2c.h" + +/** + * @brief I2C写NFC EEPROM函数 + * @param addr 写入的EEPROM内存起始地址 + * @param mem 带写入的数据信息 + * @return 返回TRUE表示写入数据成功 + */ +BOOL TWNfcI2CWriteMem(unsigned int addr, MemFrame *mem) +{ + if (mem == NULL) + { + printf("TWNfcI2CWriteMem| mem is NULL\r\n"); + return FALSE; + } + + if (mem->len <= 0) + { + printf("TWNfcI2CWriteMem| mem dataLen <= 0\r\n"); + return FALSE; + } + + unsigned int ret = 0; + int dataLen = mem->len; + unsigned char *pData = mem->data; + unsigned short regAddr = addr; + unsigned char dataBuf[NFC_PAGE_SIZE + 2] = {0}; + int pageLen = NFC_PAGE_SIZE - (regAddr % NFC_PAGE_SIZE); + if (dataLen <= pageLen) + { + dataBuf[0] = (unsigned char)(regAddr >> 8) & 0xFF; + dataBuf[1] = (unsigned char)(regAddr >> 0) & 0xFF; + memcpy_s(&dataBuf[2], NFC_PAGE_SIZE, pData, dataLen); + ret = NfcI2cWrite(dataBuf, (dataLen + 2)); + if (ret != IOT_SUCCESS) + { + printf("IoTI2cWrite fail 1, ret = %u\r\n", ret); + return FALSE; + } + return TRUE; + } + else // 跨页写 + { + dataBuf[0] = (unsigned char)(regAddr >> 8) & 0xFF; + dataBuf[1] = (unsigned char)(regAddr >> 0) & 0xFF; + memcpy_s(&dataBuf[2], NFC_PAGE_SIZE, pData, pageLen); + ret = NfcI2cWrite(dataBuf, (pageLen + 2)); //写完首页剩余长度 + if (ret != IOT_SUCCESS) + { + printf("IoTI2cWrite fail 0, ret = %u\r\n", ret); + return FALSE; + } + + dataLen -= pageLen; + regAddr += pageLen; + pData += pageLen; + for (;;) + { + if (dataLen >= NFC_PAGE_SIZE) + { + dataBuf[0] = (unsigned char)(regAddr >> 8) & 0xFF; + dataBuf[1] = (unsigned char)(regAddr >> 0) & 0xFF; + memcpy_s(&dataBuf[2], NFC_PAGE_SIZE, pData, NFC_PAGE_SIZE); + ret = NfcI2cWrite(dataBuf, (NFC_PAGE_SIZE + 2)); + if (ret != IOT_SUCCESS) + { + printf("IoTI2cWrite fail 2, ret = %u\r\n", ret); + return FALSE; + } + + dataLen -= NFC_PAGE_SIZE; + regAddr += NFC_PAGE_SIZE; + pData += NFC_PAGE_SIZE; + } + else + { + dataBuf[0] = (unsigned char)(regAddr >> 8) & 0xFF; + dataBuf[1] = (unsigned char)(regAddr >> 0) & 0xFF; + memcpy_s(&dataBuf[2], NFC_PAGE_SIZE, pData, dataLen); + ret = NfcI2cWrite(dataBuf, dataLen + 2); + if (ret != IOT_SUCCESS) + { + printf("IoTI2cWrite fail 3, ret = %u\r\n", ret); + return FALSE; + } + + return TRUE; + } + } + } + + return FALSE; +} + +/** + * @brief I2C读NFC EEPROM函数 + * @param addr 读取的EEPROM内存起始地址 + * @param readSize 读取的字节数 + * @param memBuf 读取的数据缓冲区 + * @return 返回TRUE表示读取数据成功 + */ +BOOL TWNfcI2CReadMem(unsigned int addr, unsigned int readSize, unsigned char *memBuf) +{ + int ret = 0; + + if (memBuf == NULL) + { + printf("TWNfcI2CReadMem() param err: memBuf = NULL\r\n"); + return FALSE; + } + + if (addr < NFC_EEPROM_ADDR_START) + { + printf("TWNfcI2CReadMem() param err: addr = %02x, NFC_EEPROM_ADDR_START = %02x\r\n", + addr, NFC_EEPROM_ADDR_START); + return FALSE; + } + + if ((addr + readSize - 1) > NFC_EEPROM_ADDR_END) + { + printf("TWNfcI2CReadMem() param err: readSize is overflow! readSize = %u, romSize = %u\r\n", + readSize, (NFC_EEPROM_ADDR_END - NFC_EEPROM_ADDR_START + 1)); + return FALSE; + } + + unsigned char dataBuf[2] = {0}; + dataBuf[0] = (unsigned char)(addr >> 8) & 0xFF; + dataBuf[1] = (unsigned char)(addr >> 0) & 0xFF; + + ret = NfcI2cWrite(dataBuf, 2); + if (ret != IOT_SUCCESS) + { + printf("IoTI2cWrite() fail, ret = %u\r\n", ret); + return FALSE; + } + ret = NfcI2cRead(memBuf, readSize); + if (ret != IOT_SUCCESS) + { + printf("IoTI2cRead() fail, ret = %u\r\n", ret); + return FALSE; + } + + return TRUE; +} diff --git a/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_i2c.h b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_i2c.h new file mode 100755 index 00000000..462a1c6a --- /dev/null +++ b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_i2c.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 Talkweb Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _TW_NFC_I2C_H +#define _TW_NFC_I2C_H +#include "tw_nfc_adapt.h" +#include "tw_nfc_ndef.h" + +/** + * @brief I2C写NFC EEPROM函数 + * @param addr 写入的EEPROM内存起始地址 + * @param mem 带写入的数据信息 + * @return 返回TRUE表示写入数据成功 + */ +BOOL TWNfcI2CWriteMem(unsigned int addr, MemFrame *mem); + +/** + * @brief I2C读NFC EEPROM函数 + * @param addr 读取的EEPROM内存起始地址 + * @param readSize 读取的字节数 + * @param memBuf 读取的数据缓冲区 + * @return 返回TRUE表示读取数据成功 + */ +BOOL TWNfcI2CReadMem(unsigned int addr, unsigned int readSize, unsigned char *memBuf); + +#endif diff --git a/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_ndef.c b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_ndef.c new file mode 100755 index 00000000..249bb47c --- /dev/null +++ b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_ndef.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2021 Talkweb Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "tw_nfc_ndef.h" + +/** + * @brief NDEF包制作函数 + * @param ndef 传入NDEF相关参数 + * @param mem 保存根据NDEF参数制作的NDEF数据 + * @return 返回TRUE表示NDEF数据制作成功 + */ +BOOL TWNdefPackMake(NdefFrame *ndef, MemFrame *mem) +{ + if (ndef->payloadLen > NDEF_RECORD_SIZE) + { + printf("payloadLen is too long, payloadLen = %u, maxLen = %u\r\n", ndef->payloadLen, NDEF_RECORD_SIZE); + return FALSE; + } + + switch (ndef->packtype) + { + case START_PACK: + mem->data[0] = 0x91; + break; + + case MIDDLE_PACK: + mem->data[0] = 0x11; + break; + + case END_PACK: + mem->data[0] = 0x51; + break; + + case START_END_PACK: + mem->data[0] = 0xD1; + break; + + default: + printf("not support packtype \r\n"); + return FALSE; + } + + mem->data[1] = 0x01; + switch (ndef->dataType) + { + case RTD_UTF8: + ndef->payloadLen += 3; + break; + + case RTD_URI: + ndef->payloadLen += 1; + break; + + default: + printf("not support dataType \r\n"); + return FALSE; + } + mem->data[2] = ndef->payloadLen; + mem->data[3] = ndef->dataType; + mem->len = ndef->payloadLen + 4; + + if (ndef->dataType == RTD_UTF8) + { + mem->data[4] = 2; + mem->data[5] = 'z'; + mem->data[6] = 'h'; + memcpy_s(&mem->data[7], ndef->payloadLen, ndef->payload, ndef->payloadLen); + } + else + { + mem->data[4] = ndef->uriVal; + memcpy_s(&mem->data[5], ndef->payloadLen, ndef->payload, ndef->payloadLen); + } + + return TRUE; +} + +/** + * @brief NDEF包解析函数 + * @param ndef 保存解析后的NDEF相关参数 + * @param mem 被解析的NDEF数据 + * @return 返回TRUE表示NDEF数据解析成功 + */ +BOOL TWNdefPackParsing(NdefFrame *ndef, unsigned char *mem) +{ + NdefHead head; + + memcpy_s(&head, 1, &mem[0], 1); + if(head.IL == 1) + { + printf("TWNdefPackParsing() err: dont support ndef ID type\r\n"); + return FALSE; + } + + if(head.SR != 1) + { + printf("TWNdefPackParsing() err: dont support ndef SR = 0 type\r\n"); + return FALSE; + } + + if(head.TNF != TNF_INSIDE_RTD) + { + printf("TWNdefPackParsing() err: ndef TNF != TNF_INSIDE_RTD type\r\n"); + return FALSE; + } + + ndef->payloadLen = mem[2]; + if(ndef->payloadLen > NDEF_RECORD_SIZE) + { + printf("TWNdefPackParsing() err: payloadLen = %u, NDEF_RECORD_SIZE = %u\r\n",ndef->payloadLen,NDEF_RECORD_SIZE); + return FALSE; + } + + ndef->dataType = mem[3]; + if((ndef->dataType != RTD_UTF8)&&(ndef->dataType != RTD_URI)) + { + printf("TWNdefPackParsing() err: dataType is invalid\r\n"); + return FALSE; + } + + unsigned char type = (head.MB<<2)|(head.ME<<1)|head.CF; + switch(type) + { + case 0: ndef->packtype = MIDDLE_PACK; break; + case 2: ndef->packtype = END_PACK; break; + case 4: ndef->packtype = START_PACK; break; + case 6: ndef->packtype = START_END_PACK; break; + default: printf("TWNdefPackParsing() err: packtype is invalid!\r\n"); return FALSE; + } + + if(ndef->dataType == RTD_UTF8) + { + ndef->payloadLen -= 3; + memcpy_s(ndef->payload,NDEF_RECORD_SIZE,&mem[4+3], ndef->payloadLen); + } + else + { + ndef->uriVal = mem[4]; + ndef->payloadLen -= 1; + memcpy_s(ndef->payload,NDEF_RECORD_SIZE,&mem[4+1], ndef->payloadLen); + } + + return TRUE; +} diff --git a/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_ndef.h b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_ndef.h new file mode 100755 index 00000000..67000ad2 --- /dev/null +++ b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/frameworks/tw_nfc_ndef.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2021 Talkweb Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _TW_NFC_NDEF_H +#define _TW_NFC_NDEF_H +#include "tw_nfc_adapt.h" + +#define NFC_TLV_HEAD 0x03 +#define NDEF_RECORD_SIZE 256 + +// NDEF NTF类型 +typedef enum{ + TNF_NULL = 0, // 空 + TNF_INSIDE_RTD = 1, // NFC 论坛知名类型,包括UTF8、ASCII、URI... + TNF_RFC2406 = 2, // 定义于RFC 2046[RFC 2046]的媒体类型 + TNF_RFC3986 = 3, // 定义于 RFC 3986[RFC 3986]的绝对 URI + TNF_OUTSIDE_RTD = 4, // NFC 论坛外部自定义类型 + TNF_UNKNOW = 5, // 未知 + TNF_STATIC = 6, // 不可更改(参考2.3.3节) + TNF_DEFAULT = 7 // 保留 +} TnfType; + +// NDEF TNF_INSIDE_RTD类型 +typedef enum{ + RTD_UTF8 = 'T', // UTF8类型 + RTD_URI = 'U', // URI类型 +} RtdType; + +typedef enum{ + START_PACK, // 分包开始包 + MIDDLE_PACK, // 分包中间包 + END_PACK, // 分包结束包 + START_END_PACK // 不分包,开始包也是结束包 +}PackType; + +// NDEF URI +typedef enum{ + RTD_URI_freeForm, // 0x00 No prepending is done ... the entire URI is + // contained in the URI Field + RTD_URI_httpWWW, // 0x01 http://www. + RTD_URI_httpsWWW, // 0x02 https://www. + RTD_URI_http, // 0x03 http:// + RTD_URI_https, // 0x04 https:// + RTD_URI_tel, // 0x05 tel: + RTD_URI_mailto, // 0x06 mailto: + RTD_URI_ftpAnonymous, // 0x07 ftp://anonymous:anonymous@ + RTD_URI_ftpFtp, // 0x08 ftp://ftp. + RTD_URI_ftps, // 0x09 ftps:// + RTD_URI_sftp, // 0x0A sftp:// + RTD_URI_smb, // 0x0B smb:// + RTD_URI_nfs, // 0x0C nfs:// + RTD_URI_ftp, // 0x0D ftp:// + RTD_URI_dav, // 0x0E dav:// + RTD_URI_news, // 0x0F news: + RTD_URI_telnet, // 0x10 telnet:// + RTD_URI_imap, // 0x11 imap: + RTD_URI_rtps, // 0x12 rtsp:// + RTD_URI_urn, // 0x13 urn: +} UriVal; + +// NDEF首字节解码 +typedef struct{ + unsigned char TNF : 3; // TNF标志是 3-bit 字段,指示TYPE字段值的结构 + unsigned char IL : 1; // IL 标志是 1-bit 字段,置 1 表示在首部存在一个单字节ID_LENGTH字段 + unsigned char SR : 1; // SR 标志是 1-bit 字段,置 1 表示PAYLOAD_LENGTH字段为单字节 + unsigned char CF : 1; // CF 标志是 1-bit 字段,置 1 表示 NDEF 消息分块传输 + unsigned char ME : 1; // ME 标志是 1-bit 字段,置 1 表示 NDEF 消息的结束 + unsigned char MB : 1; // MB 标志是 1-bit 字段,置 1 表示 NDEF 消息的开始 +} NdefHead; + +// NDEF参数结构体 +typedef struct{ + PackType packtype; + RtdType dataType; + UriVal uriVal; + unsigned char payloadLen; + unsigned char payload[NDEF_RECORD_SIZE]; +} NdefFrame; + +// 普通内存数据结构体 +typedef struct{ + int len; + unsigned char data[NDEF_RECORD_SIZE + 4]; +} MemFrame; + +// NDEF格式链表,用于NDEF标签解码 +typedef struct NdefList{ + struct NdefList *next; + NdefFrame ndef; +}NdefParsingList; + +/** + * @brief NDEF包制作函数 + * @param ndef 传入NDEF相关参数 + * @param mem 保存根据NDEF参数制作的NDEF数据 + * @return 返回TRUE表示NDEF数据制作成功 + */ +BOOL TWNdefPackMake(NdefFrame *ndef, MemFrame *mem); + +/** + * @brief NDEF包解析函数 + * @param ndef 保存解析后的NDEF相关参数 + * @param mem 被解析的NDEF数据 + * @return 返回TRUE表示NDEF数据解析成功 + */ +BOOL TWNdefPackParsing(NdefFrame *ndef, unsigned char *mem); + +#endif diff --git a/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/interfaces/tw_nfc_kits.h b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/interfaces/tw_nfc_kits.h new file mode 100755 index 00000000..d3761aa4 --- /dev/null +++ b/applications/app/TW401_APP_wifi_nfc/tw_nfc_lib/interfaces/tw_nfc_kits.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021 Talkweb Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _TW_NFC_KITS_H +#define _TW_NFC_KITS_H +#include "tw_nfc.h" + +/** +* 特别注意: +* 此工具包暂时只支持以下ndef格式: +* 1. 单个ndef record数据payload长度最大只支持256byte(默认为256byte),可在tw_nfc_ndef.h中修改定义 +* 2. 只支持标准RTD格式中的UTF8数据格式和URI数据格式 +* 3. 不支持带ID的ndef包格式 +*/ + +/** + * @brief NFC芯片I2C接口初始化 + */ +extern void TWNfcI2cInit(void); + +/** + * @brief 写NFC标签函数 + * @param param 待写入的NFC标签信息 + * @param tagCnt 待写入的NFC标签个数 + * @return 返回TRUE表示写入NFC标签成功 + */ +extern BOOL TWNfcTagWrite(NdefFrame *param, unsigned char tagCnt); + +/** + * @brief 读NFC标签函数 + * @return 返回读取到的NFC标签链表头 + */ +extern NdefParsingList *TWNfcTagRead(void); + +/** + * @brief 读NFC标签释放函数 + * @param list 待释放的NFC标签链表头 + */ +extern void NfcReadTagFree(NdefParsingList *list); + +#endif diff --git a/applications/app/TW401_APP_wifi_nfc/tw_wifi_nfc.c b/applications/app/TW401_APP_wifi_nfc/tw_wifi_nfc.c new file mode 100755 index 00000000..f0b77b5f --- /dev/null +++ b/applications/app/TW401_APP_wifi_nfc/tw_wifi_nfc.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2021 Talkweb Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "cJSON.h" +#include "cmsis_os2.h" +#include "ohos_init.h" +#include "tw_nfc_kits.h" +#include "wifi_device.h" +#include "tw_wifi_sta.h" + + +static TWWifiInfo TWReadWifiInfoFromNfcTags(void) { + TWNfcI2cInit(); + osDelay(10); + + TWWifiInfo m_wifiInfo = {0}; + + for (;;) { + + NdefParsingList *head = TWNfcTagRead(); + + unsigned char nfc_content[NDEF_RECORD_SIZE] = {0}; + + NdefParsingList *node = head->next; + if (head != NULL && node != NULL) { + memset(nfc_content, '\0', sizeof(nfc_content)); + memcpy(nfc_content, node->ndef.payload, node->ndef.payloadLen); + + printf("nfctag: %s \r\n", nfc_content); + + cJSON *cjson = cJSON_Parse(nfc_content); + if (cjson != NULL) { + cJSON *ssidItem = cJSON_GetObjectItem(cjson, "ssid"); + if (ssidItem != NULL) { + char *c_ssid = cJSON_GetStringValue(ssidItem); + memcpy(m_wifiInfo.ssid, c_ssid, strlen(c_ssid)); + } + + cJSON *pwdItem = cJSON_GetObjectItem(cjson, "pwd"); + if (pwdItem != NULL) { + char *c_password = cJSON_GetStringValue(pwdItem); + memcpy(m_wifiInfo.password, c_password, strlen(c_password)); + } + } + } + + NfcReadTagFree(head); + + if (m_wifiInfo.ssid != NULL && strlen(m_wifiInfo.ssid) > 0) { + break; + } + + osDelay(200); + } + + return m_wifiInfo; +} + +static void *TWWifiNfcThreadTask(void) { + + TWWifiInfo m_wifiInfo = TWReadWifiInfoFromNfcTags(); + + printf("wifi ssid: %s , password: %s \r\n", m_wifiInfo.ssid, m_wifiInfo.password); + + TWWifiConnect(m_wifiInfo); +} + +static void TWWifiNfcExample(void) { + printf("\nentry wifi nfc example.\r\n"); + + osThreadAttr_t attr; + + attr.name = "TWWifiNfcExample"; + attr.attr_bits = 0U; + attr.cb_mem = NULL; + attr.cb_size = 0U; + attr.stack_mem = NULL; + attr.stack_size = 1024 * 4; + attr.priority = 25; + + if (osThreadNew((osThreadFunc_t)TWWifiNfcThreadTask, NULL, &attr) == NULL) { + printf("Falied to create TWWifiNfcThreadTask!\n"); + } +} + +APP_FEATURE_INIT(TWWifiNfcExample); diff --git a/applications/app/TW401_APP_wifi_nfc/tw_wifi_sta.c b/applications/app/TW401_APP_wifi_nfc/tw_wifi_sta.c new file mode 100755 index 00000000..5d638d4e --- /dev/null +++ b/applications/app/TW401_APP_wifi_nfc/tw_wifi_sta.c @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2021 Talkweb Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "tw_wifi_sta.h" + +#include +#include +#include + +#include "cmsis_os2.h" +#include "lwip/api_shell.h" +#include "lwip/netifapi.h" +#include "wifi_device.h" + +int g_wifiState = 0; + +/** + * @brief 打印WifiLinkedInfo信息 + * @param info 需要打印信息的WifiLinkedInfo指针 + */ +static void PrintWifiLinkedInfo(WifiLinkedInfo *info) { + if (!info) return; + + static char macAddress[32] = {0}; + unsigned char *mac = info->bssid; + snprintf(macAddress, sizeof(macAddress), "%02X:%02X:%02X:%02X:%02X:%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + printf("info: bssid: %s, rssi: %d, connState: %d, reason: %d, ssid: %s\r\n", + macAddress, info->rssi, info->connState, info->disconnectedReason, + info->ssid); +} + +/** + * @brief WiFi连接状态改变回调 + * @param state WiFi连接状态,参考WifiEventState + * @param info WiFi连接信息 + */ +static void OnWifiConnectionChanged(int state, WifiLinkedInfo *info) { + if (!info) return; + + printf("%s %d, state = %d \r\n", __FUNCTION__, __LINE__, state); + PrintWifiLinkedInfo(info); + + g_wifiState = state; +} + +/** + * @brief WiFi扫描状态改变回调 + * @param state WiFi连接状态,参考WifiEventState + * @param size + */ +static void OnWifiScanStateChanged(int state, int size) { + printf("%s %d, state = %X, size = %d\r\n", __FUNCTION__, __LINE__, state, + size); +} + +/** + * @brief 毫秒级延时 + * @param ms 需要延迟的毫秒数 + */ +static void Delay(uint32_t ms) { + uint32_t usPerTicks = (1000 * 1000) / osKernelGetTickFreq(); + osDelay((ms * 1000) / usPerTicks); + usleep((ms * 1000) % usPerTicks); +} + +int TWIsWifiConnected(void) { + if (IsWifiActive()) { + WifiLinkedInfo wifiLinkedInfo = {0}; + WifiErrorCode wifiErrorCode = GetLinkedInfo(&wifiLinkedInfo); + if (WIFI_SUCCESS == wifiErrorCode) { + return WIFI_CONNECTED == wifiLinkedInfo.connState && + wifiLinkedInfo.ipAddress != 0; + } + } + + return 0; +} + +void TWWifiConnect(TWWifiInfo wifiInfo) { + WifiErrorCode errCode; + + WifiEvent eventListener = { + .OnWifiConnectionChanged = OnWifiConnectionChanged, + .OnWifiScanStateChanged = OnWifiScanStateChanged}; + + WifiDeviceConfig apConfig = {0}; + memcpy(apConfig.ssid, wifiInfo.ssid, strlen(wifiInfo.ssid)); + memcpy(apConfig.preSharedKey, wifiInfo.password, strlen(wifiInfo.password)); + apConfig.securityType = WIFI_SEC_TYPE_PSK; + + Delay(10); + + errCode = RegisterWifiEvent(&eventListener); + printf("RegisterWifiEvent errCode: %d\r\n", errCode); + + int networkId = -1; + //启动WiFi STA模式 + errCode = EnableWifi(); + printf("EnableWifi errCode: %d\r\n", errCode); + Delay(100); + + errCode = AddDeviceConfig(&apConfig, &networkId); + printf("AddDeviceConfig errCode: %d\r\n", errCode); + + g_wifiState = 0; + errCode = ConnectTo(networkId); + printf("ConnectTo(%d) errCode: %d\r\n", networkId, errCode); + + while (!g_wifiState) { + Delay(10); + } + printf("g_wifiState: %d\r\n", g_wifiState); + Delay(3000); + + // 联网业务开始 + // 这里是网络业务代码... + struct netif *iface = netifapi_netif_find("wlan0"); + if (iface) { + err_t ret = netifapi_dhcp_start(iface); + printf("netifapi_dhcp_start: %d\r\n", ret); + + Delay(2000); // 等待DHCP服务分配IP地址 + + ret = netifapi_netif_common(iface, dhcp_clients_info_show, NULL); + printf("netifapi_netif_common: %d\r\n", ret); + + g_wifiState = WIFI_STATE_AVALIABLE; + + } + + printf("The Niobe connect wifi success. \r\n"); +} diff --git a/applications/app/TW401_APP_wifi_nfc/tw_wifi_sta.h b/applications/app/TW401_APP_wifi_nfc/tw_wifi_sta.h new file mode 100755 index 00000000..1cdbd81c --- /dev/null +++ b/applications/app/TW401_APP_wifi_nfc/tw_wifi_sta.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 Talkweb Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WIFI_STA_H +#define WIFI_STA_H + +typedef struct { + unsigned char ssid[32]; + unsigned char password[32]; +} TWWifiInfo; + + +int TWIsWifiConnected(void); + + +void TWWifiConnect(TWWifiInfo wifiInfo); + +#endif + -- Gitee From b610002e750337aedbb17975c6bf8f39d30da4eb Mon Sep 17 00:00:00 2001 From: aibin Date: Wed, 5 Jan 2022 17:21:13 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- applications/app/TW401_APP_wifi_nfc/README.md | 313 +++++------------- .../figure/wifi-nfc-uml.png | Bin 0 -> 32590 bytes 2 files changed, 81 insertions(+), 232 deletions(-) create mode 100755 applications/app/TW401_APP_wifi_nfc/figure/wifi-nfc-uml.png diff --git a/applications/app/TW401_APP_wifi_nfc/README.md b/applications/app/TW401_APP_wifi_nfc/README.md index 7a610096..a11bb2b3 100755 --- a/applications/app/TW401_APP_wifi_nfc/README.md +++ b/applications/app/TW401_APP_wifi_nfc/README.md @@ -1,252 +1,101 @@ -# Niobe开发板OLED演示HarmonyOS开机动画 +# Niobe开发板NFC碰一碰WiFi配网 -本案例程序将演示怎么在拓维Niobe WiFi IoT OLED开发板上,使用u8g2图形库显示HarmonyOS开机动画和品牌LOGO。 +本案例程序将演示怎么在拓维Niobe开发板上,使用NFC扩展板实现碰一碰配网。 -u8g2 是一个强大的图形库,支持非常多的显示屏。SSD1306就是其支持的一款OLED显示屏幕。图形库通过提供API函数的方法,使用户可以非常方便的显示各种各样图标,字符甚至动效等。 - -u8g2库已经移植到`third_party/u8g2/`目录下方。 - -## U8G2 API分析 - -本案例主要使用了以下几个API完成开机动画显示。 - -## u8g2_Setup_ssd1306_i2c_128x64_noname_f() - -``` -void u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb); -``` - -**描述:** - -u8g2在SSD1306芯片上,使用I2C通信的初始化设置函数,支持128x64点阵像素,支持全屏缓存模式。 - -**参数:** - -| 名字 | 描述 | -| ----------------- | ------------------------------------------------- | -| u8g2 | 表示u8g2信息 | -| ratation | 表示屏幕旋转方向。本例设置为U8G2_R0(横屏不旋转) | -| byte_cb | 字节消息回调接口 | -| gpio_and_delay_cb | GPIO操作消息和延迟消息的回调接口 | - -## u8g2_InitDisplay() - -``` -#define u8g2_InitDisplay(u8g2) u8x8_InitDisplay(u8g2_GetU8x8(u8g2)) -``` - -**描述:** - - 初始OLED显示器,将向OLED显示器发送init消息。此函数会触发两种操作,一种是OLED硬件初始,一种是OLED显示屏初始清屏。 - -**参数:** - -| 名字 | 描述 | -| ---- | ------------ | -| u8g2 | 表示u8g2信息 | - -## u8g2_SetPowerSave() - -``` -#define u8g2_SetPowerSave(u8g2, is_enable) u8x8_SetPowerSave(u8g2_GetU8x8(u8g2), (is_enable)) -``` - -**描述:** - -设置省电模式是否启动。 - -**参数:** - -| 名字 | 描述 | -| --------- | ---------------- | -| u8g2 | 表示u8g2信息 | -| is_enable | 是否省电模式可用 | - -## u8g2_FirstPage() - -``` -void u8g2_FirstPage(u8g2_t *u8g2); -``` - -**描述:** - -分页模式下,向OLED显示器发送第一页缓冲数据,显示第一页。 - -**参数:** - -| 名字 | 描述 | -| ---- | ------------ | -| u8g2 | 表示u8g2信息 | - -## u8g2_NextPage() - -``` -uint8_t u8g2_NextPage(u8g2_t *u8g2); -``` - -**描述:** - -分页模式下,判断缓冲区中是否还有下一页内容。 - -**参数:** - -| 名字 | 描述 | -| ---- | ------------ | -| u8g2 | 表示u8g2信息 | - -## u8g2_DrawXBMP() +## 软件设计 -``` -void u8g2_DrawXBMP(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, const uint8_t *bitmap); -``` +### UML顺序图 -**描述:** +![image-20220105170242091](figure/wifi-nfc-uml.png) -在程序中使用位图,绘制bitmap位图到OLED显示器中。 +**主要逻辑分析** -**参数:** +开发板启动之后,程序会循环检测NFC标签内容,如果发现合法的wifi信息数据,使用wifi信息,连接指定的wifi。 -| 名字 | 描述 | -| ------ | ---------------- | -| u8g2 | 表示u8g2信息 | -| x | x轴上的起点坐标 | -| y | y轴上的起点坐标 | -| w | 位图的宽度 | -| h | 位图的高度 | -| bitmap | 位图字节数据指针 | +1. Niobe开发板启动,碰一碰程序循环检测NFC标签内容是否拥有合法的wifi信息。 -## 软件设计 +2. 使用带NFC功能的手机向NFC扩展板碰一碰,写入可用的wifi热点信息。 -**主要代码分析** - -开发板启动直接显示`未来已来,远方不远`的Talkweb Logo,按键一次后可以进入HarmonyOS开机动画。 - -1. DrawTalkwebLogo函数封装了“未来已来,远方不远”的Talkweb Logo位图绘制过程。 - -2. DrawHarmonyOSAnimation函数封装了HarmonyOS开机动画的绘图绘制过程。 - -3. while循环进行HarmonyOS开机动画和Talkweb Logo循环显示。 - -4. GPIO按键中断改变`g_startBootAnimationFlag`的值,来控制while循环的进出。 - - -```c -static void GpioSsdIsrFunc(char *arg) -{ - (void)arg; - - //消除按键抖动 - unsigned int tickCount = osKernelGetTickCount(); - unsigned int count = tickCount - g_lastTickCount; - g_lastTickCount = tickCount; - - if (count > 50) - { - g_startBootAnimationFlag ^= 1; - osSemaphoreRelease(g_gpioIsrSemId); - } - - return; -} - -/** - * 初始化开发板上的按键GPIO中断 - */ -static void InitOledKeyGpioIsr(void) -{ - g_gpioIsrSemId = osSemaphoreNew(32, 0, NULL); - - IoTGpioInit(IOT_GPIO_IO_GPIO_5); - IotIoSetFunc(IOT_GPIO_IO_GPIO_5, HI_IO_FUNC_GPIO_5_GPIO); - IotIoSetPull(IOT_GPIO_IO_GPIO_5, IOT_IO_PULL_UP); - - uint32_t ret = 0; - ret = IoTGpioSetDir(IOT_GPIO_IO_GPIO_5, IOT_GPIO_DIR_IN); - if (ret != 0) - { - printf("===== ERROR ======gpio -> hi_gpio_set_dir1 ret:%d\r\n", ret); - return; - } - ret = IoTGpioRegisterIsrFunc(IOT_GPIO_IO_GPIO_5, IOT_INT_TYPE_EDGE, IOT_GPIO_EDGE_RISE_LEVEL_HIGH, GpioSsdIsrFunc, NULL); - - return; -} - -/** - * 绘制HarmonyOS开机动画 - */ -static void DrawHarmonyOSAnimation(void) -{ - for (int i = 0; i < 58; i++) - { - u8g2_FirstPage(&u8g2); - do - { - u8g2_DrawXBMP(&u8g2, 0, 0, 128, 64, openHarmony_All[i]); - } while (u8g2_NextPage(&u8g2)); - } -} - -/** - * 绘制Talkweb LOGO,未来已来,远方不远 - */ -static void DrawTalkwebLogo(void) -{ - u8g2_FirstPage(&u8g2); - do - { - u8g2_DrawXBMP(&u8g2, 0, 0, 128, 64, talkweb_end); - } while (u8g2_NextPage(&u8g2)); -} - -static void OledU8g2Task(void) -{ - //初始化OLED开发板上的按键中断事件 - InitOledKeyGpioIsr(); - - //U8G2_R0:屏幕旋转方向,横屏不旋转。 - u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, U8g2Hi3861I2cByteCb, U8g2Hi3861GpioAndDelayCb); - - //初始OLED显示器,将向OLED显示器发送init消息 - //此函数会触发两种操作,一种是OLED硬件初始,一种是OLED显示屏初始清屏。 - u8g2_InitDisplay(&u8g2); - - //开启省电模式 - u8g2_SetPowerSave(&u8g2, 0); - - while (1) - { - DrawTalkwebLogo(); - osSemaphoreAcquire(g_gpioIsrSemId, osWaitForever); - - while (g_startBootAnimationFlag) - { - DrawHarmonyOSAnimation(); - osDelay(300U); - DrawTalkwebLogo(); - osDelay(300U); - } - } -} -``` +3. Niobe碰一碰程序检测到合法的wifi热点信息,启动wifi连接。 ## 编译调试 ### 修改 BUILD.gn 文件 -修改 `applications/app/BUILD.gn` 路径中的 BUILD.gn 文件,指定 `app_oled_u8g2_example` 参与编译。 +修改 `applications/app/BUILD.gn` 路径中的 BUILD.gn 文件,指定 `app_wifi_nfc_example` 参与编译。 ``` -# "TW303_Network_mqttclient:network_mqttclient_example", -# "TW304_Network_httpclient:network_httpclient_example", -# "TW305_Network_ntpclient:network_ntpclient_example", -"TW402_APP_oled_u8g2:app_oled_u8g2_example", +# "TW305_Network_udpclient:network_udpclient_demo", +# "TW305_Network_udpserver:network_udpserver_demo", +# "TW306_Network_httpclient:network_httpclient_demo", +"TW401_APP_wifi_nfc:app_wifi_nfc_example", +# "TW402_APP_oled_u8g2:app_oled_u8g2_example", ``` ### 运行结果 -示例代码编译烧录代码后,按下开发板的RESET按键,可以直接看到OLED开发板的开机Talkweb Logo,按下开发板上的SW6/SW7按键,可以进入HarmonyOS动画循环播放,再按下SW6/SW7按键,可以停止HarmonyOS动画。 -SW6/SW7按键在OLED开发板上,在正下方中部左侧是SW6按键,右侧是SW7按键,且开发板上有标注。 - -![image-20210930161121332](figure/image-20210930161121332.gif) - +- 示例代码编译烧录代码后,按下开发板的RESET/SW2按键,启动碰一碰程序; + +- 连接串口工具,查看串口日志; + +- 使用带NFC功能的手机,向NFC扩展板写入可用的WiFi热点信息,写入数据必须是NDEF文本格式,数据格式为JSON,数据格式如下所示: + + ```json + {"ssid":"HUAWEI_B311_A741","pwd":"12345678"} + ``` + +- 串口日志运行如下: + + ```text + ready to OS start + + sdk ver:Hi3861V100R001C00SPC025 2020-09-03 18:10:00 + + FileSystem mount ok. + wifi init success! + hilog will init. + hievent will init. + hievent init success. + hiview init success. + entry wifi nfc example. + + nfctag: {"ssid":"HUAWEI_B311_A741","pwd":"12345678"} + + wifi ssid: HUAWEI_B311_A741 , password: 12345678 + + RegisterWifiEvent errCode: 0 + + EnableWifi errCode: 0 + + AddDeviceConfig errCode: 0 + + ConnectTo(1) errCode: 0 + + No crash dump found! + + +NOTICE:SCANFINISH + +NOTICE:CONNECTED + OnWifiConnectionChanged 54, state = 1 + + info: bssid: A2:B9:EC:41:A7:C2, rssi: 0, connState: 0, reason: 0, ssid: HUAWEI_B311_A741 + + g_wifiState: 1 + + netifapi_dhcp_start: 0 + + server : + server_id : 192.168.26.1 + mask : 255.255.255.0, 1 + gw : 192.168.26.1 + T0 : 86400 + T1 : 43200 + T2 : 75600 + clients <1> : + mac_idx mac addr state lease tries rto + 0 20579e6185a4 192.168.26.116 10 0 1 2 + netifapi_netif_common: 0 + + The Niobe connect wifi success. + ``` + + diff --git a/applications/app/TW401_APP_wifi_nfc/figure/wifi-nfc-uml.png b/applications/app/TW401_APP_wifi_nfc/figure/wifi-nfc-uml.png new file mode 100755 index 0000000000000000000000000000000000000000..5d661e10d6f0ad4d87bb3c77dff0d31815df30c8 GIT binary patch literal 32590 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 VyL>(JF{t@ax|)XQe0A$P{{^5-E|>rS literal 0 HcmV?d00001 -- Gitee From 39bdd343b0980c9acb228ac5835291fb79ec706c Mon Sep 17 00:00:00 2001 From: aibin Date: Wed, 5 Jan 2022 17:25:54 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- applications/app/README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1f281183..27772049 100755 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ | TW305 | Network | udpserver | [UDP服务端联网演示](applications/app/TW305_Network_udpserver/README.md) | 核心板 | | TW305 | Network | udpclient | [UDP客户端联网演示](applications/app/TW305_Network_udpclient/README.md) | 核心板 | | TW306 | Network | httpclient | [HTTP客户端联网演示](applications/app/TW306_Network_httpclient/README.md) | 核心板 | +| TW401 | APP | wifi_nfc | [NFC碰一碰WiFi配网演示](applications/app/TW401_APP_wifi_nfc/README.md) | 核心板 + 底板+NFC扩展板 | | TW402 | APP | oled_u8g2 | [OLED U8G2库综合应用演示](applications/app/TW402_APP_oled_u8g2/README.md) | 核心板 + OLED扩展板| ## 五、源码目录简介 @@ -127,4 +128,3 @@ - diff --git a/applications/app/README.md b/applications/app/README.md index 413be4b2..bacf6a53 100755 --- a/applications/app/README.md +++ b/applications/app/README.md @@ -44,4 +44,5 @@ | TW305 | Network | udpserver | [UDP服务端联网演示](TW305_Network_udpserver/README.md) | 核心板 | | TW305 | Network | udpclient | [UDP客户端联网演示](TW305_Network_udpclient/README.md) | 核心板 | | TW306 | Network | httpclient | [http客户端联网演示](TW306_Network_httpclient/README.md) | 核心板 | +| TW401 | APP | wifi_nfc | [NFC碰一碰WiFi配网演示](TW401_APP_wifi_nfc/README.md) | 核心板 + 底板+NFC扩展板 | | TW402 | APP | oled_u8g2 | [OLED U8G2库综合应用演示](TW402_APP_oled_u8g2/README.md) | 核心板 + OLED扩展板| -- Gitee