一、 使用Harmony新代
1. 下载
笔者使用的Ubuntu20.04的编译环境。Linux环境,简单干净顺手易用,且不会存在Windows大小写的问题。
详细的环境搭建,若需要,请参考笔者的另一篇博客: 用HarmonyOS点亮LED - 基于RISC-V Hi3861开发板
OpenHarmony release主干代码获取, 轻量/小型/标准系统(2.0 Canary)源码的获取:(repo的安装和其他代码获取方式,请参考 OpenHarmony / docs)
- repo init -u https://gitee.com/openharmony/manifest.git -b master --no-repo-verify
- repo sync -c
- repo forall -c 'git lfs pull'
还有一件事,笔者得提一嘴。运行HarmonyOS系统的设备分三类,轻量系统类设备(参考内存≥128KB)、小型系统类设备(参考内存≥1MB)、标准系统类设备(参考内存≥128MB)。我们的Hi3861的RAM为352KB, 应属于轻量系统类设备。
2. 编译
- // 进入源码根目录
- cd master
- // 初次使用,得设置一下代码路径和平台。设置以后就不用再设置了
- hb set
- // 编译之前清一下工作目录, 避免不必要的问题
- hb clean
- // 使用-f就可以强制所有文件都重新编译,避免不必要的冲突问题
- // 默认编译的是debug版本,如果想让串口输出干净一点,就可以跟上 -b release 编译干净的发布版本
- hb build -f -b release
3. 下载和烧录
若需要,请参考笔者的另一篇博客: 用HarmonyOS点亮LED - 基于RISC-V Hi3861开发板
二、GPIO的规划
我们框选的gpio可能和HarmonOS中Hi3861平台代码中默认初始化的gpio有冲突,我们在接下来的使用中要注意修改这样的冲突。
1. GPIO概述
GPIO 是可编程的通用输入/输出接口,用于生成和采集特定应用的输入或输出信号,实现系统和外设之间的通信,方便系统对外设的控制。 Hi3861芯片GPIO符合AMBA2.0的APB协议。
2. GPIO接口具有以下功能特点:
时钟源可选择: 工作模式晶体时钟 24M/40M、低功耗模式 32K 时钟。
1 组 GPIO,共 15 个独立的可配置管脚。
每个 GPIO 管脚都可单独控制传输方向。
每个 GPIO 可以单独被配置为外部中断源。
GPIO 用作中断时有 4 种中断触发方式,中断时触发方式可配:
- 上升沿触发
- 下降沿触发
- 高电平触发
- 低电平触发
GPIO 上报一个中断, CPU 查询上报的 GPIO 编号。
每个中断支持独立屏蔽的功能,脉冲中断支持可清除功能
三、搞定Hi3861的按键中断
1. 引脚GPIO05
结合我们的Hi3861模块,最好用的就是“USER"按键了, 查看原理图,其对应GPIO05
2. 冲突
查看系统外设接口初始化代码:
- device/hisilicon/hispark_pegasus/sdk_liteos/app/wifiiot_app/init/app_io_init.c +27
但是GPIO05在系统初始化代码中,和UART1冲突了,并且默认UART1是打开的。
2.1 我们操作liteos的配置菜单(推荐),关闭有冲突设置
- // 进入hispark_pegasus的liteos目录
- cd device/hisilicon/hispark_pegasus/sdk_liteos
- // 执行以下命令,打开字符终端
- bash build.sh menuconfig
键盘上下键选择"BSP Settings", 按回车进入。在其子菜单中,使用空格键去掉"Enable uart1 IO mux config"前的"*"号即可。
然后按"ESC"一直退出,最后按提示按下“Y"来保存设置
重新编译后设置生效。
2.2 我们也可以手动暴力修改配置文件(不推荐):
- device/hisilicon/hispark_pegasus/sdk_liteos/build/config/usr_config.mk
- 把 CONFIG_UART1_SUPPORT=y 去掉, 保存,即可
- # CONFIG_UART1_SUPPORT is not set
3. 代码逻辑
这里我们采用下降沿触发按键中断的方式。
但默认设置GPIO05的点平为低,所以我们不但需要配置GPIO05为中断管脚,还要把它从CPU内部拉高到高电平。
这样根据电路图当按键按下时,才会有下降沿的产生,才会触发中断处理函数。
- 初始化gpio05
- 设置gpio05为输入
- 设置gpio05内部拉高
- 注册gpio05的中断,中断方式为边沿(下降沿)触发,绑定中断处理函数
注意:
可能当前版本的HarmonyOS整合代码比较仓促,IoT层的代码中没有拉高电平的接口,我们需要从"hi_io.h"中引用。
按键处理的示例代码如下:
- applications/sample/wifi-iot/app/hi3861_car/hi3861_key.c
- #include <stdio.h>
- #include <unistd.h>
- #include "ohos_init.h"
- #include "cmsis_os2.h"
- #include "iot_gpio.h"
- #include "hi_io.h"
- static void *KeyInterruptHandler(const char *arg)
- {
- printf("[debug] %s(%d)\n", __func__, __LINE__);
- (void)arg;
- }
- static void KeyEntry(void)
- {
- /* USER KEY <-> GPIO_5 <-> uart1 rx */
- // 1. init gpio05
- IoTGpioInit(HI_IO_NAME_GPIO_5);
- // 2. set gpio05's direction as input
- IoTGpioSetDir(HI_IO_NAME_GPIO_5, IOT_GPIO_DIR_IN);
- // 3. because of the falling edge interruption, please set gpio2 pull up internal, and please include "hi_io.h"
- hi_io_set_pull(HI_IO_NAME_GPIO_5, HI_IO_PULL_UP);
- // 4. register gpio interruption function
- IoTGpioRegisterIsrFunc(HI_IO_NAME_GPIO_5, IOT_INT_TYPE_EDGE, IOT_GPIO_EDGE_FALL_LEVEL_LOW, (GpioIsrCallbackFunc)KeyInterruptHandler, NULL);
- }
- SYS_RUN(KeyEntry);
修改当前目录的gn文件,添加按键处理的源文件"hi3861_key.c"编译到静态库"hi3861_app"中:
- applications/sample/wifi-iot/app/hi3861_car/BUILD.gn
- static_library("hi3861_app") {
- sources = [
- "hi3861_led.c",
- "hi3861_key.c",
- ]
- include_dirs = [
- "//utils/native/lite/include",
- "//kernel/liteos_m/kal/cmsis",
- "//base/iot_hardware/peripheral/interfaces/kits"
- ]
- }
修改应用程序根目录的gn文件,关联"hi3861_car"目录下的静态库"hi3861_app":
- applications/sample/wifi-iot/app/BUILD.gn
- import("//build/lite/config/component/lite_component.gni")
- lite_component("app") {
- features = [
- "startup",
- "hi3861_car:hi3861_app"
- ]
- }
编译代码,烧录代码并重启,观察"USER"按键按下是的串口输出。
- [debug] KeyInterruptHandler(12)
按键基本代码搞掂嘞! 芜湖~
四、使用Harmony的i2c接口,让OLED ssd1306显示内容
1. I2C硬件连接和协议基本概述
I2C(Inter Integrated Circuit)总线是由Philips公司开发的一种简单、双向二线制同步串行总线。
I2C以主从方式工作,通常有一个主设备和一个或者多个从设备,主从设备通过SDA(SerialData)串行数据线以及SCL(SerialClock)串行时钟线两根线相连,如下图所示。
I2C数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。I2C的起始信号和结束信号(起始和结束条件,都是建立在SCL为高电平的基础上)。
标准的i2c协议,开始传递数据之前需要有个起始信号(CLK为高时,SDA由高拉低),第1字节是由7位机地址和1位读写位(读:1, 写:0)组成的,读写位表明了i2c的数据传输方向。
一旦收到1位应答位,数据就根据读写位规定的方向一个字节一个字节的开始传输。主机可以产生一个停止信号来结束数据的传输(CLK为高时,SDA由低拉高)。
I2C的SDA数据是在SCL为高时保持稳定有效,SCL为低时,可以进行SDA数据的变换。
2. 写SSD1306的I2C寄存器
查看其datasheet手册, 我们发现操作ssd1306的写方式为:
起始信号 + (7位设备地址+1位读写位) + 寄存器地址 + 一字节信息 + 结束信号
ssd1036的8位地址为: 0x78
寄存器地址有两个(0x00/0x40):0x00 — 后面信息为命令, 0x40: 后面信息为数据
3. HarmonyOS2.0 操作Hi3861的i2c接口
前面gpio规划的时候,我们使用的是GPIO0/1 的i2c1来作为SDA/SCL
3.1 默认HarmonOS中是没有打开i2c的驱动, 我们需要打开它
3.1.1 使用sdk配置菜单,配置驱动
- device/hisilicon/hispark_pegasus/sdk_liteos/app/wifiiot_app/init/app_io_init.c +50
- /* I2C MUX: */
- #ifdef CONFIG_I2C_SUPPORT
- /* I2C IO复用也可以选择3/4; 9/10,根据产品设计选择 */
- hi_io_set_func(HI_IO_NAME_GPIO_0, HI_IO_FUNC_GPIO_0_I2C1_SDA);
- hi_io_set_func(HI_IO_NAME_GPIO_1, HI_IO_FUNC_GPIO_1_I2C1_SCL);
- #endif
我们仍然需要到hispark_pegasus的sdk_liteos目录中使用“bash build.sh menuconfig"选配I2C的支持
- // 进入hispark_pegasus的liteos目录
- cd device/hisilicon/hispark_pegasus/sdk_liteos
- // 执行以下命令,打开字符终端
- bash build.sh menuconfig
键盘上下键选择"BSP Settings", 按回车进入。在其子菜单中,使用空格键添加"i2c driver support"前的"*"号即可。
然后按"ESC"一直退出,最后按提示按下“Y"来保存设置。
重新编译后设置生效。
3.1.2 我们也可以手动暴力修改配置文件(不推荐):
- device/hisilicon/hispark_pegasus/sdk_liteos/build/config/usr_config.mk
- // 添加 CONFIG_I2C1SUPPORT=y, 保存,即可
- CONFIG_I2C1SUPPORT=y
3.2 完成i2c的正确初始化
- /* i2c1 <-> gpio0/1 */
- IoTGpioInit(HI_IO_NAME_GPIO_0);
- IoTGpioInit(HI_IO_NAME_GPIO_1);
- hi_io_set_func(HI_IO_NAME_GPIO_0, HI_IO_FUNC_GPIO_0_I2C1_SDA);
- hi_io_set_func(HI_IO_NAME_GPIO_1, HI_IO_FUNC_GPIO_1_I2C1_SCL);
- //设置baudrate:400kbps
- IoTI2cInit(HI_I2C_IDX_1, USR_I2C_BAUDRATE);
- // IoTI2cSetBaudrate(HI_I2C_IDX_0, USR_I2C_BAUDRATE);
3.3 i2c的读写接口
- #include "hi_i2c.h"
- unsigned int IoTI2cWrite(unsigned int id, unsigned short deviceAddr, const unsigned char *data, unsigned int dataLen)
- unsigned int IoTI2cRead(unsigned int id, unsigned short deviceAddr, unsigned char *data, unsigned int dataLen)
4. ssd1306的初始化代码示例如下
具体的配置请参考其数据手册, 点击下载coding_tbl.h
- #include <stdio.h>
- #include <unistd.h>
- #include "ohos_init.h"
- #include "cmsis_os2.h"
- #include "iot_gpio.h"
- #include "iot_i2c.h"
- #include "hi_io.h"
- #include "hi_i2c.h"
- #include "./include/coding_tbl.h"
- #include "./include/hi3861_i2c.h"
- #define OLED_REG_CMD 0x00
- #define OLED_REG_DATA 0x40
- #define OLED_SSD1306_MAX_COLUMN 128
- // 8 bits slave address
- #define OLED_SSD1306_ADDRESS 0x78
- static unsigned int OledSsd1306WriteOneByte(const unsigned char reg_addr, const unsigned char ch)
- {
- unsigned char data[] = {reg_addr, ch};
- return IoTI2cWrite(USR_I2C_BUSNO, OLED_SSD1306_ADDRESS, data, sizeof(data));
- }
- static void InitOledSsd1306()
- {
- usleep(100*1000);
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xAE); //--display off
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0x00); //---set low column address
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0x10); //---set high column address
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0x40); //--set start line address
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xB0); //--set page address
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0x81); // contract control
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xFF); //--128
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xA1); //set segment remap
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xA6); //--normal / reverse
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xA8); //--set multiplex ratio(1 to 64)
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0x3F); //--1/32 duty
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xC8); //Com scan direction
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xD3); //-set display offset
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0x00);
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xD5); //set osc division
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0x80);
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xD8); //set area color mode off
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0x05);
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xD9); //Set Pre-Charge Period
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xF1);
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xDA); //set com pin configuartion
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0x12);
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xDB); //set Vcomh
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0x30);
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0x8D); //set charge pump enable
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0x14);
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xAF); //--turn on oled panel
- }
- void OledSsd1306SetPos(unsigned char x, unsigned char y)
- {
- OledSsd1306WriteOneByte(OLED_REG_CMD, 0xb0+y);
- OledSsd1306WriteOneByte(OLED_REG_CMD, ((x&0xf0)>>4)|0x10);
- OledSsd1306WriteOneByte(OLED_REG_CMD, x&0x0f);
- }
- void OledSsd1306ShowChar(unsigned char x, unsigned char y, unsigned char ch, unsigned char size)
- {
- unsigned char c = 0;
- unsigned char i = 0;
- c = ch - ' ';
- if (x > (OLED_SSD1306_MAX_COLUMN-1)) {
- x = 0;
- y = y+2;
- }
- if (size == 16) {
- OledSsd1306SetPos(x, y);
- for (i=0; i<8; i++) {
- OledSsd1306WriteOneByte(OLED_REG_DATA, F8X16[c*16+i]);
- }
- OledSsd1306SetPos(x, y+1);
- for (i=0; i<8; i++) {
- OledSsd1306WriteOneByte(OLED_REG_DATA, F8X16[c*16+i+8]);
- }
- } else {
- OledSsd1306SetPos(x, y);
- for (i=0; i<6; i++) {
- OledSsd1306WriteOneByte(OLED_REG_DATA, F6x8[c][i]);
- }
- }
- }
- void OledSsd1306ShowString(unsigned char x, unsigned char y, unsigned char *str, unsigned char size)
- {
- unsigned char j=0;
- if (NULL == str)
- return;
- while (str[j] != '\0') {
- OledSsd1306ShowChar(x, y, str[j], size);
- x += 8;
- if (x > OLED_SSD1306_MAX_COLUMN) {
- x = 0;
- y += 2;
- }
- j++;
- }
- }
- void InitOledSsd1306UI(void)
- {
- InitOledSsd1306();
- OledSsd1306FillScreen(0x00);
- usleep(10*1000);
- }
- static void *OledTask(const char *arg)
- {
- (void)arg;
- InitOledSsd1306UI();
- OledSsd1306ShowString(9, 2, "(C) HenryHao", 1);
- }
- static void UiEntry(void)
- {
- osThreadAttr_t attr;
- attr.name = "OledTask";
- attr.attr_bits = 0U;
- attr.cb_mem = NULL;
- attr.cb_size = 0U;
- attr.stack_mem = NULL;
- attr.stack_size = 4096;
- attr.priority = 25;
- if (osThreadNew((osThreadFunc_t)OledTask, NULL, &attr) == NULL) {
- printf("[Error] Falied to create %s(%d)!\n", __func__, __LINE__);
- }
- }
- SYS_RUN(UiEntry);
然后就是在相关的gn文件中创建关联,编译烧录运行,查看oled显示:
五、搞定Hi3861的wifi的ap/sta模式
ap模式配置
- 简单的tcp server编程,用来接收设置
sta模式配置
- 简单的tcp client编程,用来下载wav音乐
- 简答的udp client编程,用来ntp校时
使用HarmonyOS保存信息和文件到Hi3861上
1. 目标:
Hi3861是一款wifi模组,笔者决定小小地手刃一下这个模组,拉它到网络世界走两步。
注意:根据芯片手册说明,因为Hi3861在ap/sta模式共存的时候,只能支持ap/sta分时复用,且需要先开sta再开ap才能不会导致ap模式的信道收到影响。所以这里没有采用ap/sta共存的方式。两种模式是分开使用的。
使用HarmonyOS保存信息和文件到Hi3861上
ap模式配置
- 简单的tcp server编程,用来接收设置
sta模式配置
- 简单的tcp client编程,用来下载wav音乐
- 简答的udp client编程,用来ntp校时
2. 保存信息
如果Hi3861能联网的话,我们肯定是要配置路由器的wifi账号和密码的,为了避免重复输入以及连接新的wifi设备。我们肯定需要有地方存放我们的配置。这时候Hi3861自带的2M珍贵存储空间就派上用场了。
那么问题来了,HarmonyOS2.0的liteOS中怎么存信息,并掉电不丢失嘞?
a. 首先我们需要到使能文件系统 (Canary2.0 对Hi3861默认打开了文件系统)
需要到hispark_pegasus的sdk_liteos目录中使用“bash build.sh menuconfig"选配文件系统的支持。保存退出
b. 我们可以使用 “kv_store.h” 中的接口
只要flash空间够用,UtilsSetValue()接口存储的信息,系统正常重启和掉电后,信息不丢失。
需要 #include “kv_store.h”
- // 存储字符串到文件系统中,以关键字来存取
- UtilsSetValue("your_keyword", "the_string_you_wanna_stored");
- // 从文件系统中获取关键字对应的指定长度的信息到buffer中
- UtilsGetValue("your_keyword", tmp_buffer, tmp_buffer_len);
- // 从文件系统中彻底删除关键字对应的信息
- UtilsDeleteValue("your_keyword");
c. 我们还可以使用libc中open/read/write/close的接口,存取文件
因为Hi3861的自带flash空间有限,存储空间要谨慎使用。
当然,HarmonyOS liteOS也有自己的文件系统的api,有兴趣的读者可以自行研究一下,此处略。因为这里已经可以使用libc的接口。
- //介绍,略,包含头文件,略。 详见 Linux man手册
- open()
- read()
- write()
- close()
制注意:
Hi3861自带存贮资源和内存有限,不建议在工程中包含大数组头文件(4K以上的音频数组文件),如果使用静态的大数组文件在工程中,liteOS在编译的时候没问题,运行的时候会有莫名的崩溃现象,很难解决。
3. ap模式
说白了就是Hi3861自己相当于一个wifi热点,可以和其他同类热点组网,也可以供别人来连接。
目前没用用来组网,只是用来组成一些简单(非标准的)的restful api,用来设置一些参数(e.g.sta的wifi账密)
3.1 打开ap模式
AP模式示例代码:
- #include "hi_wifi_api.h"
- #include "lwip/ip_addr.h"
- #include "lwip/netifapi.h"
- #include "lwip/sockets.h"
- static struct netif *g_lwip_netif = NULL;
- int hi_wifi_start_softap(void)
- {
- int ret;
- errno_t rc;
- char ifname[WIFI_IFNAME_MAX_SIZE + 1] = {0};
- int len = sizeof(ifname);
- hi_wifi_softap_config hapd_conf = {0};
- const unsigned char wifi_vap_res_num = APP_INIT_VAP_NUM;
- const unsigned char wifi_user_res_num = APP_INIT_USR_NUM;
- ip4_addr_t st_gw;
- ip4_addr_t st_ipaddr;
- ip4_addr_t st_netmask;
- //指定热点名称
- unsigned char ssid[] = "Henry-Hi3861";
- /*
- 因为在 device/hisilicon/hispark_pegasus/sdk_liteos/app/wifiiot_app/src/app_main.c 中已经对 wifi 做了初始化,
- 所以这里注释了初始化wifi的代码
- */
- // ret = hi_wifi_init(wifi_vap_res_num, wifi_user_res_num);
- // if (ret != HISI_OK) {
- // printf("hi_wifi_init\n");
- // return -1;
- // }
- rc = memcpy_s(hapd_conf.ssid, HI_WIFI_MAX_SSID_LEN + 1, ssid, sizeof(ssid));
- if (rc != EOK) {
- return -1;
- }
- // 无需密码即可访问
- hapd_conf.authmode = HI_WIFI_SECURITY_OPEN;
- hapd_conf.channel_num = 1;
- // 启动ap模式
- ret = hi_wifi_softap_start(&hapd_conf, ifname, &len);
- if (ret != HISI_OK) {
- printf("[Error] hi_wifi_softap_start\n");
- return -1;
- }
- /* acquire netif for IP operation */
- g_lwip_netif = netifapi_netif_find(ifname);
- if (g_lwip_netif == NULL) {
- printf("[Error] %s: get netif failed\n", __FUNCTION__);
- return -1;
- }
- //设置AP模式的ip地址等信息
- IP4_ADDR(&st_ipaddr, 192,168,1,1); /* input your IP for example: 192.168.1.1*/
- IP4_ADDR(&st_gw, 192,168,1,1); /* input your gateway for example: 192.168.1.1*/
- IP4_ADDR(&st_netmask, 255,255,255,0); /* input your netmask for example: 255.255.255.0*/
- netifapi_netif_set_addr(g_lwip_netif, &st_ipaddr, &st_netmask, &st_gw);
- //启动dhcp功能,连接的子设备可获取ip
- netifapi_dhcps_start(g_lwip_netif, 0, 0);
- return 0;
- }
3.2 启动tcp server,监听/读取网络连接请求。
- char hellowifiiot[] = "\
- <html>\
- <head>\
- <title>HarmonyOS Hi3861</title>\
- </head>\
- <body>\
- <h1>%s</h1>\
- </body>\
- </html>";
- static int url_handler(const int client, char* url)
- {
- int cnt = 0;
- char tmp[URL_FREGMENT_MAX_CNT][URL_FREGMENT_MAX_LENGTH] = {};
- char dst[URL_FREGMENT_MAX_CNT][URL_FREGMENT_MAX_LENGTH] = {};
- strcpy(dst[0], url);
- cnt = split(tmp, URL_FREGMENT_MAX_CNT, dst[0], " ");
- memset(dst, 0, sizeof(dst));
- cnt = split(dst, URL_FREGMENT_MAX_CNT, tmp[1], "//");
- char read_tmp_buf[128] = {}, msg[1024] = {};
- if (0 == strcasecmp("ssid", dst[0])) {
- UtilsSetValue(g_data.tag[USR_WIFI_SSID].name, dst[1]);
- } else if (0 == strcasecmp("passwd", dst[0])) {
- UtilsSetValue(g_data.tag[USR_WIFI_PASSWD].name, dst[1]);
- }
- UtilsGetValue(g_data.tag[USR_WIFI_SSID].name, g_data.tag[USR_WIFI_SSID].value, UTILS_TAG_BUFFER_SIZE);
- UtilsGetValue(g_data.tag[USR_WIFI_PASSWD].name, g_data.tag[USR_WIFI_PASSWD].value, UTILS_TAG_BUFFER_SIZE);
- strcat(read_tmp_buf, "WIFI SSID: ");
- strcat(read_tmp_buf, g_data.tag[USR_WIFI_SSID].value);
- strcat(read_tmp_buf, " \n");
- strcat(read_tmp_buf, "WIFI PASSWD: ");
- strcat(read_tmp_buf, g_data.tag[USR_WIFI_PASSWD].value);
- strcat(read_tmp_buf, " \n");
- sprintf(msg, hellowifiiot, read_tmp_buf);
- write(client, msg, sizeof(msg) - 1);
- return 0;
- }
- static void http_task(void)
- {
- int tcp_server_sockfd, client, size;
- struct sockaddr_in address, remotehost;
- /* create a TCP socket */
- if ((tcp_server_sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
- printf("[Error] can not create socket\n");
- return;
- }
- /* bind to port 80 at any inteRFace */
- address.sin_family = AF_INET;
- address.sin_port = htons(80);
- address.sin_addr.s_addr = INADDR_ANY;
- if (bind(tcp_server_sockfd, (struct sockaddr *)&address, sizeof(address)) < 0) {
- printf("[Error] can't bind socket\n");
- close(tcp_server_sockfd);
- return;
- }
- /* listen for connections (TCP listen backlog = 1) */
- listen(tcp_server_sockfd, 1);
- size = sizeof(remotehost);
- while (1) {
- client = accept(tcp_server_sockfd, (struct sockaddr *)&remotehost, (socklen_t *)&size);
- if (client >= 0) {
- int buflen = 1024;
- int ret;
- unsigned char recv_buffer[1024];
- char buf[1024] = {};
- /* Read in the request */
- ret = read(client, recv_buffer, buflen);
- if (ret <= 0) {
- close(client);
- printf("[Error] read failed\r\n");
- return;
- }
- url_handler(client, (char*)recv_buffer);
- /* Close connection client */
- close(client);
- } else {
- close(client);
- }
- }
- close(tcp_server_sockfd);
- }
3.3 restful api访问
我们在手机或者电脑连上我们的Hi3861的wifi热点(Henry-Hi3861)
在浏览器的地址栏输入:
- // 保存一下sta模式的wifi账密(账号:HUAWEI-Bamboo, 密码:abc123456)
- http://192.168.1.1/ssid/HUAWEI-Bamboo
- http://192.168.1.1/passwd/abc123456
然后我们读取网络数据的时候,再利用"UtilsGetValue()"来保存sta的账密设置,等到sta模式开启的时候,就可以拿到我们想要的wifi账密联网了。
这里也可以利用HarmonyOS应用端(北向开发)来通信,可以编写一个手机app来替代我们的restful api的访问形式。
4. sta模式
目前就是来联网,进行ntp时间更新 + 下载wav格式的音乐来播放
4.1 sta模式开启示例代码
- #include "lwip/sockets.h"
- #include "hi_wifi_api.h"
- #include "lwip/ip_addr.h"
- #include "lwip/netifapi.h"
- int hi_wifi_start_connect(void)
- {
- int ret = 0;
- hi_wifi_assoc_request assoc_req = {0};
- ret |= UtilsGetValue(g_data.tag[USR_WIFI_SSID].name, assoc_req.ssid, HI_WIFI_MAX_SSID_LEN);
- ret |= UtilsGetValue(g_data.tag[USR_WIFI_PASSWD].name, assoc_req.key, HI_WIFI_MAX_KEY_LEN);
- assoc_req.auth = HI_WIFI_SECURITY_WPA2PSK;
- usleep(2000*1000);
- ret |= hi_wifi_sta_connect(&assoc_req);
- if (ret != HISI_OK) {
- printf("[debug] wifi sta connect failed.\n");
- }
- usleep(3000*1000);
- hi_wifi_status connect_status = {0};
- hi_wifi_sta_get_connect_info(&connect_status);
- printf("[debug-wifi] ssid: %s\n", connect_status.ssid);
- printf("[debug-wifi] bssid: %s\n", connect_status.bssid);
- printf("[debug-wifi] channel: %d\n", connect_status.channel);
- printf("[debug-wifi] status: %d\n", connect_status.status);
- return ret;
- }
- int hi_wifi_start_sta(void)
- {
- hi_u32 event_bit;
- int ret;
- char ifname[WIFI_IFNAME_MAX_SIZE + 1] = {0};
- int len = sizeof(ifname);
- unsigned int num = WIFI_SCAN_AP_LIMIT;
- /* use sta mode at first , if can't we just wait */
- // app_main.c already has initialized it
- ret = hi_wifi_deinit();
- if (ret != HISI_OK) {
- printf("[Error] failed to deinit wifi\n");
- }
- const unsigned char wifi_vap_res_num = APP_INIT_VAP_NUM;
- const unsigned char wifi_user_res_num = APP_INIT_USR_NUM;
- ret = hi_wifi_init(wifi_vap_res_num, wifi_user_res_num);
- if (ret != HISI_OK) {
- printf("[Error] failed to reinit wifi\n");
- }
- ret = hi_wifi_sta_start(ifname, &len);
- if (ret != HISI_OK) {
- printf("[Error] %s %s(%d)\n", __FILE__, __func__, __LINE__);
- } else {
- /* register call back function to receive wifi event, etc scan results event,
- * connected event, disconnected event. */
- ret = hi_wifi_register_event_callback(wifi_wpa_event_cb);
- if (ret != HISI_OK) {
- printf("[Error] register wifi event callback failed\n");
- } else {
- /* acquire netif for IP operation */
- g_lwip_netif = netifapi_netif_find(ifname);
- if (g_lwip_netif == NULL) {
- printf("[Error] %s: get netif failed\n", __FUNCTION__);
- } else {
- /* start scan, scan results event will be received soon */
- ret = hi_wifi_sta_scan();
- if (ret != HISI_OK) {
- printf("[Error] %s %s(%d)\n", __FILE__, __func__, __LINE__);
- } else {
- sleep(3); /* sleep 3s, waiting for scan result. */
- /* if received scan results, select one SSID to connect */
- ret = hi_wifi_start_connect();
- }
- }
- }
- }
- return ret;
- }
4.2 成功联网后就可以访问网络啦
访问百度以及从国家授时中心更新时间,就可以随便造了。
wav文件我是自己传到自己的阿里对象存贮空间里,然后就可以随便低频率访问了。
ntp 授时的话,就是udp通信,比较简单。下载的话,就是tcp client,都可以走标准的libc接口。
需要注意的是:
- 在下载大文件的时候,需要关闭看门狗,不然系统会重启。下载好了再开了开门狗就是了。
- 然后就是读写buffer,不要开太大,静态1024字节就够了, 一段一段读就好。malloc的空间有时候分配不到,会导致下载失败。
- ret = hi_wifi_start_sta();
- if (ret == 0) {
- #include <hi_watchdog.h>
- // get_info_from("www.baidu.com");
- hi_watchdog_disable();
- http_download("https://xiansheng-csdn.oss-cn-hongkong.aliyuncs.com/HarmonyOS/Hi3861/hi3861_net/wm8978_test.wav");
- hi_watchdog_enable();
- usleep(10*1000);
- g_data.timestramp = get_ntp_time("ntp.ntsc.ac.cn");
- }
- hi_wifi_stop_sta();
串口调试信息显示已经拿到了音频文件(而且实际我们已经存到flash中了),并且通过ntp获取到了中国国家授时中心的时间
六、为Hi3861找一个i2s接口的音频编解码芯片,播放wav格式音乐
- 选用wm8978,搞定wm8978的硬件连接
- 并让wm8978播放音乐
选用wm8978是因为手边上有一个stm32的探索者开发板,直接就地取材好了。而且wm8978的性能不赖,有时间细调的话,录音、播放效果很赞,支持3D环绕。感觉后面可以做蓝牙音响和录音笔了(额…,还是把自己拍醒好了,简直就像痴人说梦…)
1. wm8978的硬件连接
活不好多说,开干。按照最开始的gpio规划,我们连接一下Hi3861的i2c & i2s接口到 对应的wm8978片子上(以后有空我们再单独打板做一个好了,前期开发比较穷,先做个小手术,凑一凑好了)。风枪吹掉了stm32的cpu,避免上面的软件和硬件对wm8978的影响。
注意:
- 别用质量不好接触不良的杜邦线,量不到时钟波形的时候心都凉了。
- i2s的 datain 和 dataout 管脚别接反了,否则会痛苦好几个钟头找不到不发声儿的问题。
- 在要放弃的时候,坚持一下,还有机会胜利的
2. i2s驱动
我们仍然需要到hispark_pegasus的sdk_liteos目录中使用“bash build.sh menuconfig"选配I2S的支持
- // 进入hispark_pegasus的liteos目录
- cd device/hisilicon/hispark_pegasus/sdk_liteos
- // 执行以下命令,打开字符终端
- bash build.sh menuconfig
键盘上下键选择"BSP Settings", 按回车进入。在其子菜单中,使用空格键添加"i2s driver support"前的"*"号即可。
然后按"ESC"一直退出,最后按提示按下“Y"来保存设置
重新编译后设置生效。
如果你去看app_main.c中的i2s的初始化你会发现,刚好和我们规划的i2s的接口对应了起来。所以,i2s的gpio初始化,我们就不用做了。
3. wm8978的初始化
注意:
- i2c读wm8978不好使,所以我们可以用一个表来存我们写过的数据
- 以下配置目前只是让它发声,要想声音更好听还得继续调试配置
- #include "iot_i2c.h"
- #include "hi_i2c.h"
- #include "../../include/common.h"
- #include "codec_wm8978.h"
- #define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
- #define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
- #define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/
- #define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */
- #define ACK_VAL 0x0 /*!< I2C ack val */
- #define NACK_VAL 0x1 /*!< I2C nack val */
- // wm8978 register val buffer zone (total 58 registers 0 to 57), occupies 116 bytes of memory
- // Because the IIC wm8978 operation does not support read operations, so save all the register values in the local
- // Write wm8978 register, synchronized to the local register values, register read, register directly back locally stored val.
- // Note: wm8978 register val is 9, so use unsigned short storage.
- static unsigned short wm8978_register_tbl[] = {
- 0X0000, 0X0000, 0X0000, 0X0000, 0X0050, 0X0000, 0X0140, 0X0000,
- 0X0000, 0X0000, 0X0000, 0X00FF, 0X00FF, 0X0000, 0X0100, 0X00FF,
- 0X00FF, 0X0000, 0X012C, 0X002C, 0X002C, 0X002C, 0X002C, 0X0000,
- 0X0032, 0X0000, 0X0000, 0X0000, 0X0000, 0X0000, 0X0000, 0X0000,
- 0X0038, 0X000B, 0X0032, 0X0000, 0X0008, 0X000C, 0X0093, 0X00E9,
- 0X0000, 0X0000, 0X0000, 0X0000, 0X0003, 0X0010, 0X0010, 0X0100,
- 0X0100, 0X0002, 0X0001, 0X0001, 0X0039, 0X0039, 0X0039, 0X0039,
- 0X0001, 0X0001
- };
- unsigned int wm8978_reg_write(const unsigned char reg, const unsigned short val)
- {
- unsigned char data[] = {(reg<<1) | ((val >> 8) & 0x01), (val&0xff)};
- wm8978_register_tbl[reg] = val;
- return IoTI2cWrite(HI_I2C_IDX_1, WM8978_DEVICE_ADDR, data, sizeof(data));
- }
- unsigned short wm8978_reg_read(const unsigned char reg)
- {
- #if 0
- unsigned char data[2] = {reg<<1, 0};
- unsigned short val = (unsigned short)-1;
- if (0 != IoTI2cRead(HI_I2C_IDX_1, WM8978_DEVICE_ADDR, data, sizeof(data)))
- ;
- // printf("[Error] %s %s(%d)\n", __FILE__, __func__, __LINE__);
- else
- val = ((data[0] & 0x1)<<8) | data[1];
- return val;
- #else
- return wm8978_register_tbl[reg];
- #endif
- }
- /* 设置I2S工作模式
- fmt:
- 0,LSB(右对齐);
- 1,MSB(左对齐);
- 2,飞利浦标准I2S;
- 3,PCM/DSP;
- len:
- 0, 16位;
- 1, 20位;
- 2, 24位;
- 3, 32位;
- */
- void wm8978_config_i2s(unsigned char fmt, unsigned char len)
- {
- fmt &= 0X03;
- len &= 0X03;
- wm8978_reg_write(4, (fmt<<3) | (len<<5)); //WM8978工作模式设置
- }
- /* wm8978 config adc/dac:
- 使能(1)/关闭(0)
- */
- void wm8978_config_adda(const unsigned char adc, const unsigned char dac)
- {
- unsigned short val;
- //adc
- val = wm8978_reg_read(2);
- if (adc) val |= 3<<0; //R2最低2个位设置为1,开启ADCR&ADCL
- else val &= ~(3<<0); //R2最低2个位清零,关闭ADCR&ADCL.
- wm8978_reg_write(2, val);
- //dac
- val = wm8978_reg_read(3);
- if (dac) val |= 3<<0; //R3低2位设为1,开启DACR&DACL
- else val &= ~(3<<0); //R3低2位清零,关闭DACR&DACL.
- wm8978_reg_write(3, val);
- }
- /* wm8978 输入通道配置:
- mic: MIC开启(1)/关闭(0)
- linein: Line In开启(1)/关闭(0)
- aux: aux开启(1)/关闭(0)
- */
- void wm8978_config_input(const unsigned char mic, const unsigned char linein, const unsigned char aux)
- {
- unsigned short val;
- val = wm8978_reg_read(2);
- if (mic) val |= 3<<2; //开启INPPGAENR, INPPGAENL(MIC的PGA放大)
- else val &= ~(3<<2); //关闭INPPGAENR, INPPGAENL.
- wm8978_reg_write(2, val);
- val = wm8978_reg_read(44);
- if (mic) val |= 3<<4 | 3<<0; //开启LIN2INPPGA, LIP2INPGA, RIN2INPPGA, RIP2INPGA.
- else val &= ~(3<<4 | 3<<0); //关闭LIN2INPPGA,LIP2INPGA,RIN2INPPGA,RIP2INPGA.
- wm8978_reg_write(44, val);
- wm8978_gain_mic(30);
- if (linein) wm8978_gain_linein(5); //LINE IN 0dB增益
- else wm8978_gain_linein(0); //关闭LINE IN
- if (aux) wm8978_gain_aux(7); //AUX 6dB增益
- else wm8978_gain_aux(0); //关闭AUX输入
- }
- /*wm8978 输出配置
- dac: DAC输出(放音), 开启(1)/关闭(0)
- bps: Bypass输出(录音,包括MIC,LINE IN,AUX等)开启(1)/关闭(0)
- */
- void wm8978_config_output(unsigned char dac, unsigned char bps)
- {
- unsigned short val = 0;
- if (dac) val |= 1<<0; //DAC输出使能
- if (bps) {
- val |= 1<<1; //BYPASS使能
- val |= 5<<2; //0dB增益
- }
- wm8978_reg_write(50, val); // Left
- wm8978_reg_write(51, val); // Right
- }
- /* 设置喇叭音量
- voll: 左声道音量(0~63)
- */
- void wm8978_config_vol_speaker(unsigned char voll, unsigned char volr)
- {
- voll &= 0X3F;
- volr &= 0X3F;
- if (voll == 0)
- voll |= 1<<6; //音量为0时, 直接mute
- if (volr == 0)
- volr |= 1<<6;
- wm8978_reg_write(54, voll); //喇叭左声道音量设置
- wm8978_reg_write(55, volr | (1<<8)); //喇叭右声道音量设置,同步更新(SPKVU=1)
- }
- unsigned int wm8978_init(void)
- {
- usleep(30*1000);
- // Power sequence
- //1. Turn on external power supplles, and wait for supply voltage to settle down
- wm8978_reg_write(0, 0); //soft reset wm8978
- usleep(20*1000);
- // 2. mute all analogue outputs
- // 3. set l/r mix enable, and dac enable l/r, in R3
- wm8978_reg_write(3, 0X6C); //LOUT2,ROUT2输出使能(喇叭工作),RMIX,LMIX使能
- // 4. set BUFIOEN = 1, VMIDSEL[1:0] to required value in register R1, wait for he VMID supply to settle
- // 5. set BIASEN = 1, in R1
- wm8978_reg_write(1, 0X1B); //MICEN设置为1(MIC使能),BIASEN设置为1(模拟器工作),VMIDSEL[1:0]设置为:11(5K)
- // 6. set L/ROUT1EN = 1 in R2
- wm8978_reg_write(2, 0X1B0); //ROUT1,LOUT1输出使能(耳机可以工作),BOOSTENR,BOOSTENL使能
- // 7. enable other mixers as required, and other outputs, and remain registers
- //以下为通用设置
- wm8978_reg_write(6, 0); //MCLK由外部提供
- wm8978_reg_write(43, 1<<4); //INVROUT2反向,驱动喇叭
- wm8978_reg_write(47, 1<<8); //PGABOOSTL,左通道MIC获得20倍增益
- wm8978_reg_write(48, 1<<8); //PGABOOSTR,右通道MIC获得20倍增益
- wm8978_reg_write(49, 1<<1); //TSDEN,开启过热保护
- wm8978_reg_write(10, 1<<3); //SOFTMUTE关闭,128x采样,最佳SNR
- wm8978_reg_write(14, 1<<3); //ADC 128x采样率
- wm8978_config_i2s(2, 0);
- wm8978_config_adda(1, 1);
- wm8978_config_input(1, 1, 0);
- wm8978_config_output(1, 0);
- wm8978_config_vol_speaker(30, 30);
- wm8978_config_vol_headset(30, 30); //0-63
- }
4. 使用i2s接口向wm8978中写数据, 播放
我们直接使用libc的文件读接口打开我们下载好的音频文件,跳过wav数据头,播放pcm格式的数据即可。
这样我们就可以播放我们下载的音乐了。嘿嘿
- #include <hi_i2s.h>
- int wm8978_player(char* filename)
- {
- int ret = 0;
- #define WM8978_BUFFER_SIZE 1024
- unsigned char buf[WM8978_BUFFER_SIZE+1] = {};
- wav_header_t header = {};
- uint32_t play_len = 0;
- // unsigned char* play_buf = NULL;
- //创建文件描述符
- int fd = open(filename, O_RDONLY);
- if (fd < 0) {
- ret = -EACCES;
- USR_ERROR_MSG("Open(%s) failed\n", filename);
- return ret;
- } else {
- USR_DEBUG_MSG("Open(%s) successfully\n", filename);
- }
- read(fd, &header, sizeof(wav_header_t));
- play_len = header.chunk_size - 0x2c;
- hi_watchdog_disable();
- while (1) {
- memset(buf, 0, sizeof(buf));
- read(fd, buf, sizeof(buf));
- hi_i2s_write(buf, WM8978_BUFFER_SIZE, 1000);
- if(play_len < WM8978_BUFFER_SIZE)
- break;
- play_len -= WM8978_BUFFER_SIZE;
- }
- close(fd);
- hi_watchdog_enable();
- usleep(500*1000);
- return ret;
- }
- void* I2sTask(const void *arg)
- {
- (void)arg;
- int ret = -1;
- hi_i2s_attribute i2s_cfg = {
- .sample_rate = HI_I2S_SAMPLE_RATE_8K,
- .resolution = HI_I2S_RESOLUTION_16BIT,
- };
- usleep(3000 * 1000);
- ret = hi_i2s_deinit();
- if (ret != HI_ERR_SUCCESS) {
- USR_ERROR_MSG("Failed to deinit i2s!\n");
- }
- usleep(2000 * 1000);
- ret = hi_i2s_init(&i2s_cfg);
- if (ret != HI_ERR_SUCCESS) {
- USR_ERROR_MSG("Failed to reinit i2s!\n");
- }
- wm8978_init();
- usleep(1000*1000);
- UtilsGetValue(g_data.tag[USR_AUDIO_FILE_NAME].name, g_data.tag[USR_AUDIO_FILE_NAME].value, UTILS_TAG_BUFFER_SIZE);
- wm8978_player(g_data.tag[USR_AUDIO_FILE_NAME].value);
- return (void*)ret;
- }
七、结尾
好了,这就是笔者的Hi3861联网播放的功能的基本雏形。
祝大家玩得开心 ~_ ~
简易视频
(如果没有预览,大家可以直接点击视频链接):