要做的事情,各个击破:
- 一、为Hi3861使用Harmony2.0新代码
- 二、规划GPIO
- 三、搞定Hi3861的按键中断(用来切换工作模式)
- 四、使用Harmony的i2c接口,让OLED ssd1306显示内容
- 五、搞定Hi3861的wifi的ap/sta模式
- 用来设置其sta的密码(tcp server)
- 用来联网下载音乐(tcp client),ntp校时(udp client)
- 使用HarmonyOS保存信息和文件到Hi3861上
- 六、为Hi3861找一个音频编解码芯片,这里选用wm8978
- 搞定wm8978的硬件连接
- 使用Harmony的i2s接口,让wm8978播放音乐
- 七、结尾
一、 使用Harmony新代码
1. 下载
笔者使用的Ubuntu20.04的编译环境。Linux环境,简单干净顺手易用,且不会存在Windows大小写的问题。
详细的环境搭建,若需要,请参考笔者的另一篇博客: 用HarmonyOS点亮LED - 基于RISC-V Hi3861开发板
OpenHarmony release主干代码获取, 轻量/小型/标准系统(2.0 Canary)源码的获取:(repo的安装和其他代码获取方式,请参考 OpenHarmony / docs
)
还有一件事,笔者得提一嘴。运行HarmonyOS系统的设备分三类,轻量系统类设备(参考内存≥128KB)、小型系统类设备(参考内存≥1MB)、标准系统类设备(参考内存≥128MB)。我们的Hi3861的RAM为352KB, 应属于轻量系统类设备。
2. 编译
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. 冲突
查看系统外设接口初始化代码:
但是GPIO05在系统初始化代码中,和UART1冲突了,并且默认UART1是打开的。
2.1 我们操作liteos的配置菜单(推荐),关闭有冲突设置
键盘上下键选择"BSP Settings", 按回车进入。在其子菜单中,使用空格键去掉"Enable uart1 IO mux config"前的"*"号即可。
然后按"ESC"一直退出,最后按提示按下“Y"来保存设置
重新编译后设置生效。

2.2 我们也可以手动暴力修改配置文件(不推荐):
device/hisilicon/hispark_pegasus/sdk_liteos/build/config/usr_config.mk
3. 代码逻辑
这里我们采用下降沿触发按键中断的方式。
但默认设置GPIO05的点平为低,所以我们不但需要配置GPIO05为中断管脚,还要把它从CPU内部拉高到高电平。
这样根据电路图当按键按下时,才会有下降沿的产生,才会触发中断处理函数。
-
- 初始化gpio05
-
- 设置gpio05为输入
-
- 设置gpio05内部拉高
-
- 注册gpio05的中断,中断方式为边沿(下降沿)触发,绑定中断处理函数

注意:
可能当前版本的HarmonyOS整合代码比较仓促,IoT层的代码中没有拉高电平的接口,我们需要从"hi_io.h"中引用。
按键处理的示例代码如下:
applications/sample/wifi-iot/app/hi3861_car/hi3861_key.c
修改当前目录的gn文件,添加按键处理的源文件"hi3861_key.c"编译到静态库"hi3861_app"中:
applications/sample/wifi-iot/app/hi3861_car/BUILD.gn
修改应用程序根目录的gn文件,关联"hi3861_car"目录下的静态库"hi3861_app":
applications/sample/wifi-iot/app/BUILD.gn
编译代码,烧录代码并重启,观察"USER"按键按下是的串口输出。
按键基本代码搞掂嘞! 芜湖~
四、使用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配置菜单,配置驱动
我们仍然需要到hispark_pegasus的sdk_liteos目录中使用“bash build.sh menuconfig"选配I2C的支持
键盘上下键选择"BSP Settings", 按回车进入。在其子菜单中,使用空格键添加"i2c driver support"前的"*"号即可。
然后按"ESC"一直退出,最后按提示按下“Y"来保存设置
重新编译后设置生效。

3.1.2 我们也可以手动暴力修改配置文件(不推荐):
device/hisilicon/hispark_pegasus/sdk_liteos/build/config/usr_config.mk
3.2 完成i2c的正确初始化
3.3 i2c的读写接口
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);
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
然后就是在相关的gn文件中创建关联,编译烧录运行,查看oled显示:

五、搞定Hi3861的wifi的ap/sta模式
- ap模式配置
- 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模式配置
- 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”
c. 我们还可以使用libc中open/read/write/close的接口,存取文件
因为Hi3861的自带flash空间有限,存储空间要谨慎使用。
当然,HarmonyOS liteOS也有自己的文件系统的api,有兴趣的读者可以自行研究一下,此处略。因为这里已经可以使用libc的接口。
注意:
Hi3861自带存贮资源和内存有限,不建议在工程中包含大数组头文件(4K以上的音频数组文件),如果使用静态的大数组文件在工程中,liteOS在编译的时候没问题,运行的时候会有莫名的崩溃现象,很难解决。
3. ap模式
说白了就是Hi3861自己相当于一个wifi热点,可以和其他同类热点组网,也可以供别人来连接。
目前没用用来组网,只是用来组成一些简单(非标准的)的restful api,用来设置一些参数(e.g.sta的wifi账密)
3.1 打开ap模式
AP模式示例代码:
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);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
3.3 restful api访问
我们在手机或者电脑连上我们的Hi3861的wifi热点(Henry-Hi3861)

在浏览器的地址栏输入:

然后我们读取网络数据的时候,再利用"UtilsGetValue()"来保存sta的账密设置,等到sta模式开启的时候,就可以拿到我们想要的wifi账密联网了。
这里也可以利用HarmonyOS应用端(北向开发)来通信,可以编写一个手机app来替代我们的restful api的访问形式。
4. sta模式
目前就是来联网,进行ntp时间更新 + 下载wav格式的音乐来播放
4.1 sta模式开启示例代码
4.2 成功联网后就可以访问网络啦
访问百度以及从国家授时中心更新时间,就可以随便造了。
wav文件我是自己传到自己的阿里对象存贮空间里,然后就可以随便低频率访问了。
ntp 授时的话,就是udp通信,比较简单。下载的话,就是tcp client,都可以走标准的libc接口。
需要注意的是:
- 在下载大文件的时候,需要关闭看门狗,不然系统会重启。下载好了再开了开门狗就是了。
- 然后就是读写buffer,不要开太大,静态1024字节就够了, 一段一段读就好。malloc的空间有时候分配不到,会导致下载失败。
串口调试信息显示已经拿到了音频文件(而且实际我们已经存到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的支持
键盘上下键选择"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
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
4. 使用i2s接口向wm8978中写数据, 播放
我们直接使用libc的文件读接口打开我们下载好的音频文件,跳过wav数据头,播放pcm格式的数据即可。
这样我们就可以播放我们下载的音乐了。嘿嘿
七、结尾
好了,这就是笔者的Hi3861联网播放的功能的基本雏形。
祝大家玩得开心 ~_ ~
简易视频
(如果没有预览,大家可以直接点击视频链接):
<video src=“https://xiansheng-csdn.oss-cn-hongkong.aliyuncs.com/HarmonyOS/Hi3861/hi3861_net/wm8978_play_sample.mp4” controls=“controls” width=“500” height=“300”></video>
看着楼主很完调试了一遍,谢谢分享,有学习到。
非常棒的一篇帖子,感谢楼主分享
学习学习~~
写得蛮详细,学习了
有源码吗?
期待啊!!!我想了解一下,get_info_from("www.baidu.com");
g_data.timestramp =get_ntp_time("ntp.ntsc.ac.cn"); 这个两个函数怎么实现的?
棒,这么详细