|
|
|
|
公众号矩阵

基于Harmony 2.0用Hi3861实现WiFi联网下载、播放WAV音乐

运行HarmonyOS系统的设备分三类,轻量系统类设备(参考内存≥128KB)、小型系统类设备(参考内存≥1MB)、标准系统类设备(参考内存≥128MB)。我们的Hi3861的RAM为352KB, 应属于轻量系统类设备。

作者:一点先森来源:鸿蒙社区|2021-09-30 10:11

一、 使用Harmony新代码

1. 下载

笔者使用的Ubuntu20.04的编译环境。Linux环境,简单干净顺手易用,且不会存在Windows大小写的问题。

详细的环境搭建,若需要,请参考笔者的另一篇博客: 用HarmonyOS点亮LED - 基于RISC-V Hi3861开发板

OpenHarmony release主干代码获取, 轻量/小型/标准系统(2.0 Canary)源码的获取:(repo的安装和其他代码获取方式,请参考 OpenHarmony / docs)

  1. repo init -u https://gitee.com/openharmony/manifest.git -b master --no-repo-verify 
  2. repo sync -c 
  3. repo forall -c 'git lfs pull' 

还有一件事,笔者得提一嘴。运行HarmonyOS系统的设备分三类,轻量系统类设备(参考内存≥128KB)、小型系统类设备(参考内存≥1MB)、标准系统类设备(参考内存≥128MB)。我们的Hi3861的RAM为352KB, 应属于轻量系统类设备。

2. 编译

  1. // 进入源码根目录 
  2. cd master 
  3.  
  4. // 初次使用,得设置一下代码路径和平台。设置以后就不用再设置了 
  5. hb set 
  6.  
  7. // 编译之前清一下工作目录, 避免不必要的问题 
  8. hb clean 
  9.  
  10. //  使用-f就可以强制所有文件都重新编译,避免不必要的冲突问题 
  11. // 默认编译的是debug版本,如果想让串口输出干净一点,就可以跟上 -b release 编译干净的发布版本 
  12. hb build -f -b release 

3. 下载和烧录

若需要,请参考笔者的另一篇博客: 用HarmonyOS点亮LED - 基于RISC-V Hi3861开发板

二、GPIO的规划

我们框选的gpio可能和HarmonOS中Hi3861平台代码中默认初始化的gpio有冲突,我们在接下来的使用中要注意修改这样的冲突。

用Hi3861-wifi联网下载、播放wav音乐 - 基于Harmony2.0-鸿蒙HarmonyOS技术社区

1. GPIO概述

GPIO 是可编程的通用输入/输出接口,用于生成和采集特定应用的输入或输出信号,实现系统和外设之间的通信,方便系统对外设的控制。 Hi3861芯片GPIO符合AMBA2.0的APB协议。

用Hi3861-wifi联网下载、播放wav音乐 - 基于Harmony2.0-鸿蒙HarmonyOS技术社区

2. GPIO接口具有以下功能特点:

时钟源可选择: 工作模式晶体时钟 24M/40M、低功耗模式 32K 时钟。

1 组 GPIO,共 15 个独立的可配置管脚。

每个 GPIO 管脚都可单独控制传输方向。

每个 GPIO 可以单独被配置为外部中断源。

GPIO 用作中断时有 4 种中断触发方式,中断时触发方式可配:

  • 上升沿触发
  • 下降沿触发
  • 高电平触发
  • 低电平触发

GPIO 上报一个中断, CPU 查询上报的 GPIO 编号。

每个中断支持独立屏蔽的功能,脉冲中断支持可清除功能

三、搞定Hi3861的按键中断

1. 引脚GPIO05

结合我们的Hi3861模块,最好用的就是“USER"按键了, 查看原理图,其对应GPIO05

2. 冲突

查看系统外设接口初始化代码:

  1. device/hisilicon/hispark_pegasus/sdk_liteos/app/wifiiot_app/init/app_io_init.c  +27 

但是GPIO05在系统初始化代码中,和UART1冲突了,并且默认UART1是打开的。

2.1 我们操作liteos的配置菜单(推荐),关闭有冲突设置

  1. // 进入hispark_pegasus的liteos目录 
  2. cd device/hisilicon/hispark_pegasus/sdk_liteos 
  3.  
  4. // 执行以下命令,打开字符终端 
  5. bash build.sh  menuconfig 

键盘上下键选择"BSP Settings", 按回车进入。在其子菜单中,使用空格键去掉"Enable uart1 IO mux config"前的"*"号即可。

然后按"ESC"一直退出,最后按提示按下“Y"来保存设置

重新编译后设置生效。

用Hi3861-wifi联网下载、播放wav音乐 - 基于Harmony2.0-鸿蒙HarmonyOS技术社区

2.2 我们也可以手动暴力修改配置文件(不推荐):

  1. device/hisilicon/hispark_pegasus/sdk_liteos/build/config/usr_config.mk 
  1. 把 CONFIG_UART1_SUPPORT=y 去掉, 保存,即可 
  2. # CONFIG_UART1_SUPPORT is not set 

3. 代码逻辑

这里我们采用下降沿触发按键中断的方式。

但默认设置GPIO05的点平为低,所以我们不但需要配置GPIO05为中断管脚,还要把它从CPU内部拉高到高电平。

这样根据电路图当按键按下时,才会有下降沿的产生,才会触发中断处理函数。

  • 初始化gpio05
  • 设置gpio05为输入
  • 设置gpio05内部拉高
  • 注册gpio05的中断,中断方式为边沿(下降沿)触发,绑定中断处理函数
用Hi3861-wifi联网下载、播放wav音乐 - 基于Harmony2.0-鸿蒙HarmonyOS技术社区

注意:

可能当前版本的HarmonyOS整合代码比较仓促,IoT层的代码中没有拉高电平的接口,我们需要从"hi_io.h"中引用。

按键处理的示例代码如下:

  1. applications/sample/wifi-iot/app/hi3861_car/hi3861_key.c 
  1. #include <stdio.h> 
  2. #include <unistd.h> 
  3.  
  4. #include "ohos_init.h" 
  5. #include "cmsis_os2.h" 
  6. #include "iot_gpio.h" 
  7. #include "hi_io.h" 
  8.  
  9.  
  10. static void *KeyInterruptHandler(const char *arg) 
  11.     printf("[debug] %s(%d)\n", __func__, __LINE__); 
  12.     (void)arg; 
  13.  
  14. static void KeyEntry(void) 
  15.     /* USER KEY <-> GPIO_5  <->  uart1 rx */ 
  16.     // 1. init gpio05 
  17.     IoTGpioInit(HI_IO_NAME_GPIO_5); 
  18.  
  19.     // 2. set gpio05's direction as input 
  20.     IoTGpioSetDir(HI_IO_NAME_GPIO_5, IOT_GPIO_DIR_IN); 
  21.  
  22.     // 3. because of the falling edge interruption, please set gpio2 pull up internal, and please include "hi_io.h" 
  23.     hi_io_set_pull(HI_IO_NAME_GPIO_5, HI_IO_PULL_UP); 
  24.  
  25.     // 4. register gpio interruption function 
  26.     IoTGpioRegisterIsrFunc(HI_IO_NAME_GPIO_5, IOT_INT_TYPE_EDGE, IOT_GPIO_EDGE_FALL_LEVEL_LOW, (GpioIsrCallbackFunc)KeyInterruptHandler, NULL); 
  27.  
  28. SYS_RUN(KeyEntry); 

修改当前目录的gn文件,添加按键处理的源文件"hi3861_key.c"编译到静态库"hi3861_app"中:

  1. applications/sample/wifi-iot/app/hi3861_car/BUILD.gn 
  1. static_library("hi3861_app") { 
  2.     sources = [ 
  3.         "hi3861_led.c"
  4.         "hi3861_key.c"
  5.     ] 
  6.  
  7.     include_dirs = [ 
  8.         "//utils/native/lite/include"
  9.         "//kernel/liteos_m/kal/cmsis"
  10.         "//base/iot_hardware/peripheral/interfaces/kits" 
  11.     ] 

修改应用程序根目录的gn文件,关联"hi3861_car"目录下的静态库"hi3861_app":

  1. applications/sample/wifi-iot/app/BUILD.gn 
  1. import("//build/lite/config/component/lite_component.gni"
  2.  
  3. lite_component("app") { 
  4.     features = [ 
  5.         "startup"
  6.         "hi3861_car:hi3861_app" 
  7.     ] 

编译代码,烧录代码并重启,观察"USER"按键按下是的串口输出。

  1. [debug] KeyInterruptHandler(12) 

按键基本代码搞掂嘞! 芜湖~

四、使用Harmony的i2c接口,让OLED ssd1306显示内容

1. I2C硬件连接和协议基本概述

I2C(Inter Integrated Circuit)总线是由Philips公司开发的一种简单、双向二线制同步串行总线。

I2C以主从方式工作,通常有一个主设备和一个或者多个从设备,主从设备通过SDA(SerialData)串行数据线以及SCL(SerialClock)串行时钟线两根线相连,如下图所示。

用Hi3861-wifi联网下载、播放wav音乐 - 基于Harmony2.0-鸿蒙HarmonyOS技术社区

I2C数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。I2C的起始信号和结束信号(起始和结束条件,都是建立在SCL为高电平的基础上)。

标准的i2c协议,开始传递数据之前需要有个起始信号(CLK为高时,SDA由高拉低),第1字节是由7位机地址和1位读写位(读:1, 写:0)组成的,读写位表明了i2c的数据传输方向。

一旦收到1位应答位,数据就根据读写位规定的方向一个字节一个字节的开始传输。主机可以产生一个停止信号来结束数据的传输(CLK为高时,SDA由低拉高)。

用Hi3861-wifi联网下载、播放wav音乐 - 基于Harmony2.0-鸿蒙HarmonyOS技术社区

I2C的SDA数据是在SCL为高时保持稳定有效,SCL为低时,可以进行SDA数据的变换。

用Hi3861-wifi联网下载、播放wav音乐 - 基于Harmony2.0-鸿蒙HarmonyOS技术社区

2. 写SSD1306的I2C寄存器

查看其datasheet手册, 我们发现操作ssd1306的写方式为:

起始信号 + (7位设备地址+1位读写位) + 寄存器地址 + 一字节信息 + 结束信号

ssd1036的8位地址为: 0x78

寄存器地址有两个(0x00/0x40):0x00 — 后面信息为命令, 0x40: 后面信息为数据

用Hi3861-wifi联网下载、播放wav音乐 - 基于Harmony2.0-鸿蒙HarmonyOS技术社区

3. HarmonyOS2.0 操作Hi3861的i2c接口

前面gpio规划的时候,我们使用的是GPIO0/1 的i2c1来作为SDA/SCL

3.1 默认HarmonOS中是没有打开i2c的驱动, 我们需要打开它

3.1.1 使用sdk配置菜单,配置驱动

  1. device/hisilicon/hispark_pegasus/sdk_liteos/app/wifiiot_app/init/app_io_init.c  +50 
  2.  
  3.     /* I2C MUX: */ 
  4. #ifdef CONFIG_I2C_SUPPORT 
  5.     /* I2C IO复用也可以选择3/4; 9/10,根据产品设计选择 */ 
  6.     hi_io_set_func(HI_IO_NAME_GPIO_0, HI_IO_FUNC_GPIO_0_I2C1_SDA); 
  7.     hi_io_set_func(HI_IO_NAME_GPIO_1, HI_IO_FUNC_GPIO_1_I2C1_SCL); 
  8. #endif 

我们仍然需要到hispark_pegasus的sdk_liteos目录中使用“bash build.sh menuconfig"选配I2C的支持

  1. // 进入hispark_pegasus的liteos目录 
  2. cd device/hisilicon/hispark_pegasus/sdk_liteos 
  3.  
  4. // 执行以下命令,打开字符终端 
  5. bash build.sh  menuconfig 

键盘上下键选择"BSP Settings", 按回车进入。在其子菜单中,使用空格键添加"i2c driver support"前的"*"号即可。

然后按"ESC"一直退出,最后按提示按下“Y"来保存设置。

重新编译后设置生效。

用Hi3861-wifi联网下载、播放wav音乐 - 基于Harmony2.0-鸿蒙HarmonyOS技术社区

3.1.2 我们也可以手动暴力修改配置文件(不推荐):

  1. device/hisilicon/hispark_pegasus/sdk_liteos/build/config/usr_config.mk 
  1. // 添加 CONFIG_I2C1SUPPORT=y, 保存,即可 
  2. CONFIG_I2C1SUPPORT=y 

3.2 完成i2c的正确初始化

  1. /* i2c1  <-> gpio0/1 */ 
  2. IoTGpioInit(HI_IO_NAME_GPIO_0); 
  3. IoTGpioInit(HI_IO_NAME_GPIO_1); 
  4.  
  5. hi_io_set_func(HI_IO_NAME_GPIO_0, HI_IO_FUNC_GPIO_0_I2C1_SDA); 
  6. hi_io_set_func(HI_IO_NAME_GPIO_1, HI_IO_FUNC_GPIO_1_I2C1_SCL); 
  7.  
  8. //设置baudrate:400kbps 
  9. IoTI2cInit(HI_I2C_IDX_1, USR_I2C_BAUDRATE); 
  10. // IoTI2cSetBaudrate(HI_I2C_IDX_0, USR_I2C_BAUDRATE); 

3.3 i2c的读写接口

  1. #include "hi_i2c.h" 
  2.  
  3. unsigned int IoTI2cWrite(unsigned int id, unsigned short deviceAddr, const unsigned char *data, unsigned int dataLen) 
  4.  
  5. unsigned int IoTI2cRead(unsigned int id, unsigned short deviceAddr, unsigned char *data, unsigned int dataLen) 

4. ssd1306的初始化代码示例如下

具体的配置请参考其数据手册, 点击下载coding_tbl.h

  1. #include <stdio.h> 
  2. #include <unistd.h> 
  3.  
  4. #include "ohos_init.h" 
  5. #include "cmsis_os2.h" 
  6. #include "iot_gpio.h" 
  7. #include "iot_i2c.h" 
  8. #include "hi_io.h" 
  9. #include "hi_i2c.h" 
  10. #include "./include/coding_tbl.h" 
  11. #include "./include/hi3861_i2c.h" 
  12.  
  13. #define OLED_REG_CMD                0x00 
  14. #define OLED_REG_DATA               0x40 
  15. #define OLED_SSD1306_MAX_COLUMN     128 
  16. // 8 bits slave address 
  17. #define OLED_SSD1306_ADDRESS        0x78 
  18.  
  19. static unsigned int OledSsd1306WriteOneByte(const unsigned char reg_addr, const unsigned char ch) 
  20.     unsigned char data[] = {reg_addr, ch}; 
  21.     return IoTI2cWrite(USR_I2C_BUSNO, OLED_SSD1306_ADDRESS, data, sizeof(data)); 
  22.  
  23. static void InitOledSsd1306() 
  24.     usleep(100*1000);                                
  25.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xAE);    //--display off 
  26.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0x00);    //---set low column address 
  27.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0x10);    //---set high column address 
  28.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0x40);    //--set start line address 
  29.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xB0);    //--set page address 
  30.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0x81);    // contract control 
  31.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xFF);    //--128   
  32.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xA1);    //set segment remap  
  33.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xA6);    //--normal / reverse 
  34.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xA8);    //--set multiplex ratio(1 to 64) 
  35.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0x3F);    //--1/32 duty 
  36.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xC8);    //Com scan direction 
  37.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xD3);    //-set display offset 
  38.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0x00); 
  39.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xD5);    //set osc division 
  40.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0x80); 
  41.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xD8);    //set area color mode off 
  42.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0x05); 
  43.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xD9);    //Set Pre-Charge Period 
  44.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xF1); 
  45.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xDA);    //set com pin configuartion 
  46.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0x12); 
  47.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xDB);    //set Vcomh 
  48.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0x30); 
  49.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0x8D);    //set charge pump enable 
  50.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0x14); 
  51.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xAF);    //--turn on oled panel 
  52.  
  53. void OledSsd1306SetPos(unsigned char x, unsigned char y)  
  54. {  
  55.     OledSsd1306WriteOneByte(OLED_REG_CMD, 0xb0+y); 
  56.     OledSsd1306WriteOneByte(OLED_REG_CMD, ((x&0xf0)>>4)|0x10); 
  57.     OledSsd1306WriteOneByte(OLED_REG_CMD, x&0x0f); 
  58.  
  59. void OledSsd1306ShowChar(unsigned char x, unsigned char y, unsigned char ch, unsigned char size
  60. {           
  61.     unsigned char c = 0; 
  62.     unsigned char i = 0; 
  63.  
  64.     c = ch - ' '
  65.      
  66.     if (x > (OLED_SSD1306_MAX_COLUMN-1)) { 
  67.         x = 0; 
  68.         y = y+2; 
  69.     } 
  70.   
  71.     if (size == 16) { 
  72.         OledSsd1306SetPos(x, y);     
  73.         for (i=0; i<8; i++) { 
  74.             OledSsd1306WriteOneByte(OLED_REG_DATA, F8X16[c*16+i]); 
  75.         } 
  76.          
  77.         OledSsd1306SetPos(x, y+1); 
  78.         for (i=0; i<8; i++) { 
  79.             OledSsd1306WriteOneByte(OLED_REG_DATA, F8X16[c*16+i+8]); 
  80.         } 
  81.     } else {     
  82.         OledSsd1306SetPos(x, y); 
  83.         for (i=0; i<6; i++) { 
  84.             OledSsd1306WriteOneByte(OLED_REG_DATA, F6x8[c][i]); 
  85.         }             
  86.      } 
  87.   
  88. void OledSsd1306ShowString(unsigned char x, unsigned char y, unsigned char *str, unsigned char size
  89.     unsigned char j=0; 
  90.  
  91.     if (NULL == str) 
  92.          return
  93.     while (str[j] != '\0') {         
  94.         OledSsd1306ShowChar(x, y, str[j], size); 
  95.         x += 8; 
  96.         if (x > OLED_SSD1306_MAX_COLUMN) { 
  97.             x = 0; 
  98.             y += 2; 
  99.         } 
  100.         j++; 
  101.     } 
  102.  
  103. void InitOledSsd1306UI(void) 
  104.     InitOledSsd1306(); 
  105.     OledSsd1306FillScreen(0x00); 
  106.     usleep(10*1000);  
  107.  
  108. static void *OledTask(const char *arg) 
  109.     (void)arg; 
  110.     InitOledSsd1306UI(); 
  111.     OledSsd1306ShowString(9, 2, "(C) HenryHao", 1); 
  112.  
  113. static void UiEntry(void) 
  114.     osThreadAttr_t attr; 
  115.     attr.name = "OledTask"
  116.     attr.attr_bits = 0U; 
  117.     attr.cb_mem = NULL
  118.     attr.cb_size = 0U; 
  119.     attr.stack_mem = NULL
  120.     attr.stack_size = 4096; 
  121.     attr.priority = 25; 
  122.     if (osThreadNew((osThreadFunc_t)OledTask, NULL, &attr) == NULL) { 
  123.         printf("[Error] Falied to create %s(%d)!\n", __func__, __LINE__); 
  124.     } 
  125. SYS_RUN(UiEntry); 

然后就是在相关的gn文件中创建关联,编译烧录运行,查看oled显示:

用Hi3861-wifi联网下载、播放wav音乐 - 基于Harmony2.0-鸿蒙HarmonyOS技术社区

五、搞定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”

  1. // 存储字符串到文件系统中,以关键字来存取 
  2.    UtilsSetValue("your_keyword""the_string_you_wanna_stored"); 
  3.     
  4.    // 从文件系统中获取关键字对应的指定长度的信息到buffer中 
  5.    UtilsGetValue("your_keyword", tmp_buffer, tmp_buffer_len); 
  6.     
  7.    // 从文件系统中彻底删除关键字对应的信息 
  8.    UtilsDeleteValue("your_keyword"); 

c. 我们还可以使用libc中open/read/write/close的接口,存取文件

因为Hi3861的自带flash空间有限,存储空间要谨慎使用。

当然,HarmonyOS liteOS也有自己的文件系统的api,有兴趣的读者可以自行研究一下,此处略。因为这里已经可以使用libc的接口。

  1. //介绍,略,包含头文件,略。 详见 Linux man手册 
  2. open() 
  3. read() 
  4. write() 
  5. close() 

制注意:

Hi3861自带存贮资源和内存有限,不建议在工程中包含大数组头文件(4K以上的音频数组文件),如果使用静态的大数组文件在工程中,liteOS在编译的时候没问题,运行的时候会有莫名的崩溃现象,很难解决。

3. ap模式

说白了就是Hi3861自己相当于一个wifi热点,可以和其他同类热点组网,也可以供别人来连接。

目前没用用来组网,只是用来组成一些简单(非标准的)的restful api,用来设置一些参数(e.g.sta的wifi账密)

3.1 打开ap模式

AP模式示例代码:

  1. #include "hi_wifi_api.h" 
  2. #include "lwip/ip_addr.h" 
  3. #include "lwip/netifapi.h" 
  4. #include "lwip/sockets.h" 
  5.  
  6. static struct netif *g_lwip_netif = NULL
  7.  
  8. int hi_wifi_start_softap(void) 
  9.     int ret; 
  10.     errno_t rc; 
  11.     char ifname[WIFI_IFNAME_MAX_SIZE + 1] = {0}; 
  12.     int len = sizeof(ifname); 
  13.     hi_wifi_softap_config hapd_conf = {0}; 
  14.     const unsigned char wifi_vap_res_num = APP_INIT_VAP_NUM; 
  15.     const unsigned char wifi_user_res_num = APP_INIT_USR_NUM; 
  16.     ip4_addr_t st_gw; 
  17.     ip4_addr_t st_ipaddr; 
  18.     ip4_addr_t st_netmask; 
  19.     //指定热点名称 
  20.     unsigned char ssid[] = "Henry-Hi3861"
  21.  
  22.     /*  
  23.     因为在 device/hisilicon/hispark_pegasus/sdk_liteos/app/wifiiot_app/src/app_main.c 中已经对 wifi 做了初始化, 
  24.     所以这里注释了初始化wifi的代码 
  25.     */ 
  26.     // ret = hi_wifi_init(wifi_vap_res_num, wifi_user_res_num); 
  27.     // if (ret != HISI_OK) { 
  28.     //     printf("hi_wifi_init\n"); 
  29.     //     return -1; 
  30.     // } 
  31.  
  32.     rc = memcpy_s(hapd_conf.ssid, HI_WIFI_MAX_SSID_LEN + 1, ssid, sizeof(ssid)); 
  33.     if (rc != EOK) { 
  34.         return -1; 
  35.     } 
  36.  
  37.     // 无需密码即可访问 
  38.     hapd_conf.authmode = HI_WIFI_SECURITY_OPEN; 
  39.     hapd_conf.channel_num = 1; 
  40.  
  41.     // 启动ap模式 
  42.     ret = hi_wifi_softap_start(&hapd_conf, ifname, &len); 
  43.     if (ret != HISI_OK) { 
  44.         printf("[Error] hi_wifi_softap_start\n"); 
  45.         return -1; 
  46.     } 
  47.  
  48.     /* acquire netif for IP operation */ 
  49.     g_lwip_netif = netifapi_netif_find(ifname); 
  50.     if (g_lwip_netif == NULL) { 
  51.         printf("[Error] %s: get netif failed\n", __FUNCTION__); 
  52.         return -1; 
  53.     } 
  54.     //设置AP模式的ip地址等信息 
  55.     IP4_ADDR(&st_ipaddr, 192,168,1,1);      /* input your IP for example: 192.168.1.1*/ 
  56.     IP4_ADDR(&st_gw, 192,168,1,1);          /* input your gateway for example: 192.168.1.1*/ 
  57.     IP4_ADDR(&st_netmask, 255,255,255,0);   /* input your netmask for example: 255.255.255.0*/ 
  58.     netifapi_netif_set_addr(g_lwip_netif, &st_ipaddr, &st_netmask, &st_gw); 
  59.  
  60.     //启动dhcp功能,连接的子设备可获取ip 
  61.     netifapi_dhcps_start(g_lwip_netif, 0, 0); 
  62.  
  63.     return 0; 

3.2 启动tcp server,监听/读取网络连接请求。

  1. char hellowifiiot[] = "\ 
  2. <html>\ 
  3. <head>\ 
  4. <title>HarmonyOS Hi3861</title>\ 
  5. </head>\ 
  6. <body>\ 
  7. <h1>%s</h1>\ 
  8. </body>\ 
  9. </html>"; 
  10.  
  11. static int url_handler(const int client, char* url) 
  12.     int cnt = 0; 
  13.     char tmp[URL_FREGMENT_MAX_CNT][URL_FREGMENT_MAX_LENGTH] = {}; 
  14.     char dst[URL_FREGMENT_MAX_CNT][URL_FREGMENT_MAX_LENGTH] = {}; 
  15.  
  16.     strcpy(dst[0], url); 
  17.  
  18.     cnt = split(tmp, URL_FREGMENT_MAX_CNT, dst[0], " "); 
  19.      
  20.     memset(dst, 0, sizeof(dst)); 
  21.     cnt = split(dst, URL_FREGMENT_MAX_CNT, tmp[1], "//"); 
  22.  
  23.     char read_tmp_buf[128] = {}, msg[1024] = {}; 
  24.     if (0 == strcasecmp("ssid", dst[0])) { 
  25.         UtilsSetValue(g_data.tag[USR_WIFI_SSID].name, dst[1]); 
  26.     } else if (0 == strcasecmp("passwd", dst[0])) { 
  27.         UtilsSetValue(g_data.tag[USR_WIFI_PASSWD].name, dst[1]); 
  28.     }  
  29.     UtilsGetValue(g_data.tag[USR_WIFI_SSID].name, g_data.tag[USR_WIFI_SSID].value, UTILS_TAG_BUFFER_SIZE); 
  30.     UtilsGetValue(g_data.tag[USR_WIFI_PASSWD].name, g_data.tag[USR_WIFI_PASSWD].value, UTILS_TAG_BUFFER_SIZE); 
  31.  
  32.     strcat(read_tmp_buf, "WIFI SSID: "); 
  33.     strcat(read_tmp_buf, g_data.tag[USR_WIFI_SSID].value); 
  34.     strcat(read_tmp_buf, " \n"); 
  35.     strcat(read_tmp_buf, "WIFI PASSWD: "); 
  36.     strcat(read_tmp_buf, g_data.tag[USR_WIFI_PASSWD].value); 
  37.     strcat(read_tmp_buf, " \n"); 
  38.     sprintf(msg, hellowifiiot, read_tmp_buf); 
  39.  
  40.     write(client, msg, sizeof(msg) - 1); 
  41.  
  42.     return 0; 
  43.  
  44. static void http_task(void) 
  45.     int tcp_server_sockfd, client, size
  46.     struct sockaddr_in address, remotehost; 
  47.     /* create a TCP socket */ 
  48.     if ((tcp_server_sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 
  49.         printf("[Error] can not create socket\n"); 
  50.         return
  51.     } 
  52.  
  53.     /* bind to port 80 at any inteRFace */ 
  54.     address.sin_family = AF_INET; 
  55.     address.sin_port = htons(80); 
  56.     address.sin_addr.s_addr = INADDR_ANY; 
  57.  
  58.     if (bind(tcp_server_sockfd, (struct sockaddr *)&address, sizeof(address)) < 0) { 
  59.         printf("[Error] can't bind socket\n"); 
  60.         close(tcp_server_sockfd); 
  61.         return
  62.     } 
  63.  
  64.     /* listen for connections (TCP listen backlog = 1) */ 
  65.     listen(tcp_server_sockfd, 1); 
  66.     size = sizeof(remotehost); 
  67.     while (1) { 
  68.         client = accept(tcp_server_sockfd, (struct sockaddr *)&remotehost, (socklen_t *)&size); 
  69.         if (client >= 0) { 
  70.                 int buflen = 1024; 
  71.                 int ret; 
  72.                 unsigned char recv_buffer[1024]; 
  73.                 char buf[1024] = {}; 
  74.              
  75.                 /* Read in the request */ 
  76.                 ret = read(client, recv_buffer, buflen); 
  77.                 if (ret <= 0) { 
  78.                     close(client); 
  79.                     printf("[Error] read failed\r\n"); 
  80.                     return
  81.                 } 
  82.              
  83.                 url_handler(client, (char*)recv_buffer); 
  84.              
  85.                 /* Close connection client */ 
  86.                 close(client); 
  87.         } else { 
  88.             close(client); 
  89.         } 
  90.     } 
  91.     close(tcp_server_sockfd); 

3.3 restful api访问

我们在手机或者电脑连上我们的Hi3861的wifi热点(Henry-Hi3861)

用Hi3861-wifi联网下载、播放wav音乐 - 基于Harmony2.0-鸿蒙HarmonyOS技术社区

在浏览器的地址栏输入:

  1. // 保存一下sta模式的wifi账密(账号:HUAWEI-Bamboo, 密码:abc123456) 
  2. http://192.168.1.1/ssid/HUAWEI-Bamboo 
  3. http://192.168.1.1/passwd/abc123456 
用Hi3861-wifi联网下载、播放wav音乐 - 基于Harmony2.0-鸿蒙HarmonyOS技术社区

然后我们读取网络数据的时候,再利用"UtilsGetValue()"来保存sta的账密设置,等到sta模式开启的时候,就可以拿到我们想要的wifi账密联网了。

这里也可以利用HarmonyOS应用端(北向开发)来通信,可以编写一个手机app来替代我们的restful api的访问形式。

4. sta模式

目前就是来联网,进行ntp时间更新 + 下载wav格式的音乐来播放

4.1 sta模式开启示例代码

  1. #include "lwip/sockets.h" 
  2. #include "hi_wifi_api.h" 
  3. #include "lwip/ip_addr.h" 
  4. #include "lwip/netifapi.h" 
  5.  
  6. int hi_wifi_start_connect(void) 
  7.     int ret = 0; 
  8.     hi_wifi_assoc_request assoc_req = {0}; 
  9.      
  10.     ret |= UtilsGetValue(g_data.tag[USR_WIFI_SSID].name, assoc_req.ssid, HI_WIFI_MAX_SSID_LEN); 
  11.     ret |= UtilsGetValue(g_data.tag[USR_WIFI_PASSWD].name, assoc_req.key, HI_WIFI_MAX_KEY_LEN); 
  12.  
  13.     assoc_req.auth = HI_WIFI_SECURITY_WPA2PSK; 
  14.  
  15.     usleep(2000*1000); 
  16.  
  17.     ret |= hi_wifi_sta_connect(&assoc_req); 
  18.     if (ret != HISI_OK) { 
  19.         printf("[debug] wifi sta connect failed.\n"); 
  20.     } 
  21.  
  22.     usleep(3000*1000); 
  23.  
  24.     hi_wifi_status connect_status = {0}; 
  25.     hi_wifi_sta_get_connect_info(&connect_status); 
  26.     printf("[debug-wifi] ssid: %s\n", connect_status.ssid); 
  27.     printf("[debug-wifi] bssid: %s\n", connect_status.bssid); 
  28.     printf("[debug-wifi] channel: %d\n", connect_status.channel); 
  29.     printf("[debug-wifi] status: %d\n", connect_status.status); 
  30.  
  31.     return ret; 
  32.  
  33. int hi_wifi_start_sta(void) 
  34.     hi_u32 event_bit; 
  35.     int ret; 
  36.     char ifname[WIFI_IFNAME_MAX_SIZE + 1] = {0}; 
  37.     int len = sizeof(ifname); 
  38.     unsigned int  num = WIFI_SCAN_AP_LIMIT; 
  39.  
  40.     /* use sta mode at first , if can't we just wait */ 
  41.  
  42.     // app_main.c already has initialized it 
  43.     ret = hi_wifi_deinit(); 
  44.     if (ret != HISI_OK) { 
  45.         printf("[Error] failed to deinit wifi\n"); 
  46.     } 
  47.     const unsigned char wifi_vap_res_num = APP_INIT_VAP_NUM; 
  48.     const unsigned char wifi_user_res_num = APP_INIT_USR_NUM; 
  49.     ret = hi_wifi_init(wifi_vap_res_num, wifi_user_res_num); 
  50.     if (ret != HISI_OK) { 
  51.         printf("[Error] failed to reinit wifi\n"); 
  52.     } 
  53.  
  54.     ret = hi_wifi_sta_start(ifname, &len); 
  55.     if (ret != HISI_OK) { 
  56.         printf("[Error] %s %s(%d)\n", __FILE__, __func__, __LINE__); 
  57.     } else { 
  58.         /* register call back function to receive wifi event, etc scan results event, 
  59.         * connected event, disconnected event. */ 
  60.         ret = hi_wifi_register_event_callback(wifi_wpa_event_cb); 
  61.         if (ret != HISI_OK) { 
  62.             printf("[Error] register wifi event callback failed\n"); 
  63.         } else { 
  64.             /* acquire netif for IP operation */ 
  65.             g_lwip_netif = netifapi_netif_find(ifname); 
  66.             if (g_lwip_netif == NULL) { 
  67.                 printf("[Error] %s: get netif failed\n", __FUNCTION__); 
  68.             } else { 
  69.                 /* start scan, scan results event will be received soon */ 
  70.                 ret = hi_wifi_sta_scan(); 
  71.                 if (ret != HISI_OK) { 
  72.                     printf("[Error] %s %s(%d)\n", __FILE__, __func__, __LINE__); 
  73.                 } else { 
  74.                     sleep(3);   /* sleep 3s, waiting for scan result. */ 
  75.                      
  76.                     /* if received scan results, select one SSID to connect */ 
  77.                     ret = hi_wifi_start_connect(); 
  78.                 } 
  79.             } 
  80.         } 
  81.     } 
  82.     return ret; 

4.2 成功联网后就可以访问网络啦

访问百度以及从国家授时中心更新时间,就可以随便造了。

wav文件我是自己传到自己的阿里对象存贮空间里,然后就可以随便低频率访问了。

ntp 授时的话,就是udp通信,比较简单。下载的话,就是tcp client,都可以走标准的libc接口。

需要注意的是:

  • 在下载大文件的时候,需要关闭看门狗,不然系统会重启。下载好了再开了开门狗就是了。
  • 然后就是读写buffer,不要开太大,静态1024字节就够了, 一段一段读就好。malloc的空间有时候分配不到,会导致下载失败。
  1. ret = hi_wifi_start_sta(); 
  2. if (ret == 0) { 
  3.     #include <hi_watchdog.h> 
  4.      
  5.     // get_info_from("www.baidu.com"); 
  6.  
  7.     hi_watchdog_disable(); 
  8.     http_download("https://xiansheng-csdn.oss-cn-hongkong.aliyuncs.com/HarmonyOS/Hi3861/hi3861_net/wm8978_test.wav"); 
  9.     hi_watchdog_enable(); 
  10.     usleep(10*1000); 
  11.      
  12.     g_data.timestramp = get_ntp_time("ntp.ntsc.ac.cn"); 
  13. hi_wifi_stop_sta(); 

串口调试信息显示已经拿到了音频文件(而且实际我们已经存到flash中了),并且通过ntp获取到了中国国家授时中心的时间

用Hi3861-wifi联网下载、播放wav音乐 - 基于Harmony2.0-鸿蒙HarmonyOS技术社区

六、为Hi3861找一个i2s接口的音频编解码芯片,播放wav格式音乐

  • 选用wm8978,搞定wm8978的硬件连接
  • 并让wm8978播放音乐

选用wm8978是因为手边上有一个stm32的探索者开发板,直接就地取材好了。而且wm8978的性能不赖,有时间细调的话,录音、播放效果很赞,支持3D环绕。感觉后面可以做蓝牙音响和录音笔了(额…,还是把自己拍醒好了,简直就像痴人说梦…)

1. wm8978的硬件连接

活不好多说,开干。按照最开始的gpio规划,我们连接一下Hi3861的i2c & i2s接口到 对应的wm8978片子上(以后有空我们再单独打板做一个好了,前期开发比较穷,先做个小手术,凑一凑好了)。风枪吹掉了stm32的cpu,避免上面的软件和硬件对wm8978的影响。

注意:

  • 别用质量不好接触不良的杜邦线,量不到时钟波形的时候心都凉了。
  • i2s的 datain 和 dataout 管脚别接反了,否则会痛苦好几个钟头找不到不发声儿的问题。
  • 在要放弃的时候,坚持一下,还有机会胜利的
用Hi3861-wifi联网下载、播放wav音乐 - 基于Harmony2.0-鸿蒙HarmonyOS技术社区

2. i2s驱动

我们仍然需要到hispark_pegasus的sdk_liteos目录中使用“bash build.sh menuconfig"选配I2S的支持

  1. // 进入hispark_pegasus的liteos目录 
  2. cd device/hisilicon/hispark_pegasus/sdk_liteos 
  3.  
  4. // 执行以下命令,打开字符终端 
  5. bash build.sh  menuconfig 

键盘上下键选择"BSP Settings", 按回车进入。在其子菜单中,使用空格键添加"i2s driver support"前的"*"号即可。

然后按"ESC"一直退出,最后按提示按下“Y"来保存设置

重新编译后设置生效。

如果你去看app_main.c中的i2s的初始化你会发现,刚好和我们规划的i2s的接口对应了起来。所以,i2s的gpio初始化,我们就不用做了。

3. wm8978的初始化

注意:

  • i2c读wm8978不好使,所以我们可以用一个表来存我们写过的数据
  • 以下配置目前只是让它发声,要想声音更好听还得继续调试配置
  1. #include "iot_i2c.h" 
  2. #include "hi_i2c.h" 
  3. #include "../../include/common.h" 
  4. #include "codec_wm8978.h" 
  5.  
  6. #define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */ 
  7. #define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */ 
  8. #define ACK_CHECK_EN 0x1            /*!< I2C master will check ack from slave*/ 
  9. #define ACK_CHECK_DIS 0x0            /*!< I2C master will not check ack from slave */ 
  10. #define ACK_VAL 0x0                    /*!< I2C ack val */ 
  11. #define NACK_VAL 0x1                /*!< I2C nack val */ 
  12.  
  13. // wm8978 register val buffer zone (total 58 registers 0 to 57), occupies 116 bytes of memory 
  14. // Because the IIC wm8978 operation does not support read operations, so save all the register values in the local 
  15. // Write wm8978 register, synchronized to the local register values, register read, register directly back locally stored val. 
  16. // Note: wm8978 register val is 9, so use unsigned short storage. 
  17.  
  18. static unsigned short wm8978_register_tbl[] = { 
  19.     0X0000, 0X0000, 0X0000, 0X0000, 0X0050, 0X0000, 0X0140, 0X0000, 
  20.     0X0000, 0X0000, 0X0000, 0X00FF, 0X00FF, 0X0000, 0X0100, 0X00FF, 
  21.     0X00FF, 0X0000, 0X012C, 0X002C, 0X002C, 0X002C, 0X002C, 0X0000, 
  22.     0X0032, 0X0000, 0X0000, 0X0000, 0X0000, 0X0000, 0X0000, 0X0000, 
  23.     0X0038, 0X000B, 0X0032, 0X0000, 0X0008, 0X000C, 0X0093, 0X00E9, 
  24.     0X0000, 0X0000, 0X0000, 0X0000, 0X0003, 0X0010, 0X0010, 0X0100, 
  25.     0X0100, 0X0002, 0X0001, 0X0001, 0X0039, 0X0039, 0X0039, 0X0039, 
  26.     0X0001, 0X0001 
  27. }; 
  28.  
  29. unsigned int wm8978_reg_write(const unsigned char reg, const unsigned short val) 
  30.     unsigned char data[] = {(reg<<1) | ((val >> 8) & 0x01), (val&0xff)}; 
  31.  
  32.     wm8978_register_tbl[reg] = val; 
  33.  
  34.     return IoTI2cWrite(HI_I2C_IDX_1, WM8978_DEVICE_ADDR, data, sizeof(data)); 
  35.  
  36. unsigned short wm8978_reg_read(const unsigned char reg) 
  37.     #if 0 
  38.     unsigned char data[2] = {reg<<1, 0}; 
  39.     unsigned short val = (unsigned short)-1; 
  40.  
  41.     if (0 != IoTI2cRead(HI_I2C_IDX_1, WM8978_DEVICE_ADDR, data, sizeof(data))) 
  42.         ; 
  43.         // printf("[Error] %s %s(%d)\n", __FILE__, __func__, __LINE__); 
  44.     else 
  45.         val = ((data[0] & 0x1)<<8) | data[1]; 
  46.  
  47.     return val; 
  48.     #else  
  49.         return wm8978_register_tbl[reg]; 
  50.     #endif 
  51.  
  52. /* 设置I2S工作模式 
  53. fmt:  
  54.     0,LSB(右对齐); 
  55.     1,MSB(左对齐); 
  56.     2,飞利浦标准I2S; 
  57.     3,PCM/DSP; 
  58. len:  
  59.     0, 16位; 
  60.     1, 20位; 
  61.     2, 24位; 
  62.     3, 32位; 
  63. */ 
  64. void wm8978_config_i2s(unsigned char fmt, unsigned char len) 
  65.     fmt &= 0X03; 
  66.     len &= 0X03; 
  67.     wm8978_reg_write(4, (fmt<<3) | (len<<5));   //WM8978工作模式设置 
  68.  
  69. /* wm8978 config adc/dac:  
  70. 使能(1)/关闭(0) 
  71. */ 
  72. void wm8978_config_adda(const unsigned char adc, const unsigned char dac) 
  73.     unsigned short val; 
  74.      
  75.     //adc 
  76.     val = wm8978_reg_read(2); 
  77.     if (adc) val |= 3<<0;       //R2最低2个位设置为1,开启ADCR&ADCL 
  78.     else val &= ~(3<<0);        //R2最低2个位清零,关闭ADCR&ADCL. 
  79.     wm8978_reg_write(2, val); 
  80.  
  81.     //dac 
  82.     val = wm8978_reg_read(3); 
  83.     if (dac) val |= 3<<0;       //R3低2位设为1,开启DACR&DACL 
  84.     else val &= ~(3<<0);        //R3低2位清零,关闭DACR&DACL. 
  85.     wm8978_reg_write(3, val); 
  86.  
  87.  
  88. /* wm8978 输入通道配置: 
  89. mic: MIC开启(1)/关闭(0) 
  90. linein: Line In开启(1)/关闭(0) 
  91. aux: aux开启(1)/关闭(0) 
  92. */ 
  93. void wm8978_config_input(const unsigned char mic, const unsigned char linein, const unsigned char aux) 
  94.     unsigned short val; 
  95.     val = wm8978_reg_read(2); 
  96.     if (mic) val |= 3<<2;               //开启INPPGAENR, INPPGAENL(MIC的PGA放大) 
  97.     else val &= ~(3<<2);                //关闭INPPGAENR, INPPGAENL. 
  98.     wm8978_reg_write(2, val); 
  99.  
  100.     val = wm8978_reg_read(44); 
  101.     if (mic) val |= 3<<4 | 3<<0;        //开启LIN2INPPGA, LIP2INPGA, RIN2INPPGA, RIP2INPGA. 
  102.     else val &= ~(3<<4 | 3<<0);         //关闭LIN2INPPGA,LIP2INPGA,RIN2INPPGA,RIP2INPGA. 
  103.     wm8978_reg_write(44, val); 
  104.  
  105.     wm8978_gain_mic(30); 
  106.  
  107.     if (linein) wm8978_gain_linein(5);  //LINE IN 0dB增益 
  108.     else wm8978_gain_linein(0);         //关闭LINE IN 
  109.  
  110.     if (aux) wm8978_gain_aux(7);        //AUX 6dB增益 
  111.     else wm8978_gain_aux(0);            //关闭AUX输入 
  112.  
  113. /*wm8978 输出配置 
  114. dac: DAC输出(放音), 开启(1)/关闭(0) 
  115. bps: Bypass输出(录音,包括MIC,LINE IN,AUX等)开启(1)/关闭(0) 
  116. */ 
  117. void wm8978_config_output(unsigned char dac, unsigned char bps) 
  118.     unsigned short val = 0; 
  119.     if (dac) val |= 1<<0;       //DAC输出使能 
  120.     if (bps) { 
  121.         val |= 1<<1;            //BYPASS使能 
  122.         val |= 5<<2;            //0dB增益 
  123.     } 
  124.     wm8978_reg_write(50, val);  // Left 
  125.     wm8978_reg_write(51, val);  // Right 
  126.  
  127. /* 设置喇叭音量 
  128. voll: 左声道音量(0~63) 
  129. */ 
  130. void wm8978_config_vol_speaker(unsigned char voll, unsigned char volr) 
  131.     voll &= 0X3F; 
  132.     volr &= 0X3F; 
  133.     if (voll == 0) 
  134.         voll |= 1<<6;                    //音量为0时, 直接mute 
  135.     if (volr == 0) 
  136.         volr |= 1<<6; 
  137.     wm8978_reg_write(54, voll);          //喇叭左声道音量设置 
  138.     wm8978_reg_write(55, volr | (1<<8)); //喇叭右声道音量设置,同步更新(SPKVU=1) 
  139.  
  140. unsigned int wm8978_init(void) 
  141. {    
  142.     usleep(30*1000); 
  143.     // Power sequence  
  144.     //1. Turn on external power supplles, and wait for supply voltage to settle down 
  145.     wm8978_reg_write(0, 0); //soft reset wm8978 
  146.     usleep(20*1000); 
  147.     // 2. mute all analogue outputs 
  148.     // 3. set l/r mix enable, and dac enable l/r, in R3 
  149.     wm8978_reg_write(3, 0X6C);  //LOUT2,ROUT2输出使能(喇叭工作),RMIX,LMIX使能 
  150.     // 4. set BUFIOEN = 1, VMIDSEL[1:0] to required value in register R1, wait for he VMID supply to settle 
  151.     // 5. set BIASEN = 1, in R1 
  152.     wm8978_reg_write(1, 0X1B);  //MICEN设置为1(MIC使能),BIASEN设置为1(模拟器工作),VMIDSEL[1:0]设置为:11(5K) 
  153.     // 6. set L/ROUT1EN = 1 in R2 
  154.     wm8978_reg_write(2, 0X1B0); //ROUT1,LOUT1输出使能(耳机可以工作),BOOSTENR,BOOSTENL使能 
  155.     // 7. enable other mixers as required, and other outputs, and remain registers 
  156.  
  157.     //以下为通用设置 
  158.     wm8978_reg_write(6, 0);     //MCLK由外部提供 
  159.     wm8978_reg_write(43, 1<<4); //INVROUT2反向,驱动喇叭 
  160.     wm8978_reg_write(47, 1<<8); //PGABOOSTL,左通道MIC获得20倍增益 
  161.     wm8978_reg_write(48, 1<<8); //PGABOOSTR,右通道MIC获得20倍增益 
  162.     wm8978_reg_write(49, 1<<1); //TSDEN,开启过热保护 
  163.     wm8978_reg_write(10, 1<<3); //SOFTMUTE关闭,128x采样,最佳SNR 
  164.     wm8978_reg_write(14, 1<<3); //ADC 128x采样率 
  165.      
  166.     wm8978_config_i2s(2, 0); 
  167.      
  168.     wm8978_config_adda(1, 1); 
  169.     wm8978_config_input(1, 1, 0); 
  170.     wm8978_config_output(1, 0); 
  171.     wm8978_config_vol_speaker(30, 30); 
  172.     wm8978_config_vol_headset(30, 30); //0-63 

4. 使用i2s接口向wm8978中写数据, 播放

我们直接使用libc的文件读接口打开我们下载好的音频文件,跳过wav数据头,播放pcm格式的数据即可。

这样我们就可以播放我们下载的音乐了。嘿嘿

  1. #include <hi_i2s.h> 
  2.  
  3. int wm8978_player(char* filename) 
  4.     int ret = 0; 
  5.     #define WM8978_BUFFER_SIZE 1024 
  6.     unsigned char buf[WM8978_BUFFER_SIZE+1] = {}; 
  7.     wav_header_t header = {}; 
  8.     uint32_t play_len = 0; 
  9.     // unsigned char* play_buf = NULL
  10.  
  11.     //创建文件描述符 
  12.     int fd = open(filename, O_RDONLY); 
  13.     if (fd < 0) { 
  14.         ret = -EACCES; 
  15.         USR_ERROR_MSG("Open(%s) failed\n", filename); 
  16.         return ret; 
  17.     } else { 
  18.         USR_DEBUG_MSG("Open(%s) successfully\n", filename); 
  19.     } 
  20.      
  21.     read(fd, &header, sizeof(wav_header_t)); 
  22.     play_len = header.chunk_size - 0x2c; 
  23.  
  24.     hi_watchdog_disable(); 
  25.     while (1) { 
  26.         memset(buf, 0, sizeof(buf)); 
  27.         read(fd, buf, sizeof(buf)); 
  28.         hi_i2s_write(buf, WM8978_BUFFER_SIZE, 1000); 
  29.         if(play_len < WM8978_BUFFER_SIZE)  
  30.             break; 
  31.         play_len -= WM8978_BUFFER_SIZE; 
  32.     } 
  33.     close(fd); 
  34.     hi_watchdog_enable(); 
  35.     usleep(500*1000); 
  36.     return ret; 
  37.  
  38. void* I2sTask(const void *arg) 
  39.     (void)arg; 
  40.     int ret = -1; 
  41.  
  42.     hi_i2s_attribute i2s_cfg = { 
  43.         .sample_rate = HI_I2S_SAMPLE_RATE_8K, 
  44.         .resolution = HI_I2S_RESOLUTION_16BIT, 
  45.     }; 
  46.     usleep(3000 * 1000); 
  47.     ret = hi_i2s_deinit(); 
  48.     if (ret != HI_ERR_SUCCESS) { 
  49.         USR_ERROR_MSG("Failed to deinit i2s!\n"); 
  50.     } 
  51.     usleep(2000 * 1000); 
  52.     ret = hi_i2s_init(&i2s_cfg); 
  53.     if (ret != HI_ERR_SUCCESS) { 
  54.         USR_ERROR_MSG("Failed to reinit i2s!\n"); 
  55.     } 
  56.  
  57.     wm8978_init(); 
  58.  
  59.     usleep(1000*1000); 
  60.      
  61.     UtilsGetValue(g_data.tag[USR_AUDIO_FILE_NAME].name, g_data.tag[USR_AUDIO_FILE_NAME].value, UTILS_TAG_BUFFER_SIZE); 
  62.      
  63.     wm8978_player(g_data.tag[USR_AUDIO_FILE_NAME].value); 
  64.  
  65.     return (void*)ret; 

七、结尾

好了,这就是笔者的Hi3861联网播放的功能的基本雏形。

祝大家玩得开心 ~_ ~

简易视频

(如果没有预览,大家可以直接点击视频链接):

https://xiansheng-csdn.oss-cn-hongkong.aliyuncs.com/HarmonyOS/Hi3861/hi3861_net/wm8978_play_sample.mp4

想了解更多内容,请访问:

51CTO和华为官方战略合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

【编辑推荐】

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区
  2. 鸿蒙应用Native SDK C++ (JNI)开发实战
  3. 编译易出错?应用安装难?Hi3516开发攻略来啦
  4. 使用Python开发鸿蒙设备程序(2-I2C应用实例)
  5. HarmonyOS Sample 之 NetworkManagement 网络管理功能
  6. HarmonyOS实战— ProgressBar进度条组件基本使用
【责任编辑:jianghua TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢
24H热文
一周话题
本月获赞

订阅专栏+更多

带你轻松入门 RabbitMQ

带你轻松入门 RabbitMQ

轻松入门RabbitMQ
共4章 | loong576

47人订阅学习

数据湖与数据仓库的分析实践攻略

数据湖与数据仓库的分析实践攻略

助力现代化数据管理:数据湖与数据仓库的分析实践攻略
共3章 | 创世达人

14人订阅学习

云原生架构实践

云原生架构实践

新技术引领移动互联网进入急速赛道
共3章 | KaliArch

42人订阅学习

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微