前言

本教程分三个部分

  1. 介绍使用官方镜像文件,使用Python进行开发的一个简单小例子,并且简单了解底层是如何运作的。
  2. 记录使用自制镜像文件,使用C/C++开发基于Linux的Pynq-Z2。完成一个将3.5毫米耳机输入口的音频采集,并输出到一个USB音频设备的任务。
  3. 自行编译Pynq-Z2镜像文件的步骤(高级)

使用Python开发Pynq的简单介绍

概述

本节使用了Pynq官方镜像v2.5,基于Ubuntu 18.04,Linux 4.19.0。将简单介绍安装流程,Python程序样例简介与简单认识底层实现原理。

前期准备

一张 microSD卡(推荐16GB以上)
一个 microSD卡读卡器
一根 网线
一个 路由器
一根 3.5mm公对公音频线(内录线)(如果需要体验实验结果)
下载 Pynq-Z2 磁盘镜像(本节基于v2.5)
安装 Win32DiskImager
安装 DiskGenius
安装 WinSCP(如果需要访问完整文件系统)

下载完成后请安装下载的软件

安装配置

将SD卡插入电脑,打开Win32DiskImager,选择下载的镜像文件,点击写入

待完成后,不要按系统提示进行格式化,打开DiskGenius。

在左侧找到SD卡磁盘,选择较大的分区(大概5GB那个),右键,扩容分区。

点击开始,等待约5分钟即可完成。

剩余时间是假的,不必理会。
完成后,将SD卡从电脑上取下,插入Pynq-Z2的卡槽里(背部)。

将启动方式跳线帽选择SD。

使用microUSB线连接到电脑,开启电源。
等待4个绿色LED,2个蓝色LED高亮闪烁后,代表启动完成。

使用网线连接Pynq-Z2到电脑的局域网中,将自动获取IP地址。
以小米路由器为例,可以看到开发板的IP地址为192.168.31.69

浏览器访问该IP,将自动跳转到Jupyter Notebook
需要输入密码,该linux系统的账号为xilinx,密码为xilinx

点击登录

文件系统

可以通过文件管理器访问\\<你的Pynq-Z2 IP地址>\xilinx,来访问其文件系统(无法访问根目录)。

如果需要访问完整文件系统,请按以下步骤进行,如不需要请跳过。
下载安装 WinSCP
按如下方法配置

点击保存(保存密码),即可以后在左侧选择。

点击登录,即可访问完整目录。

程序分析

base文件夹中包含了很多python的例子,可以自行参考学习。
本文以base/audio/audio_playback.ipynb为研究对象。

该例程使用python控制板载的ADAU1761芯片,采集从耳机接口输入的音频。

LINE_IN代表双声道输入接口,HP MIC代表耳麦(传统4段式3.5毫米耳机接口,2声道输出,1声道输入)
首先来看其第一个功能

具体实现了选择LINE_IN作为输入,直接输出到HP MIC的输出,不储存。
你需要一根”内录线”,即3.5mm公对公音频线,一端插入LINE_IN,一端插入一个输出源(如手机耳机口),再插入一个耳机到HP MIC中,即可听见声音。
可以自己点击上方的运行查看效果,你将听到从LINE_IN输入的声音。

下面我们来分析他具体做了什么
访问文件系统\\192.168.31.69\xilinx\pynq\lib,将IP替换为你自己的。

这里就是其具体调用的python文件。

这两部是加载Pynq开发者为Pynq-Z2写的底层PL文件base.bit,和加载libaudio.so类库。下文删除了注释,请自己打开文件查看。

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
class AudioADAU1761(DefaultIP):
def __init__(self, description):
super().__init__(description)

self._ffi = cffi.FFI()
self._libaudio = self._ffi.dlopen(LIB_SEARCH_PATH + "/libaudio.so")
self._ffi.cdef("""void config_audio_pll(int iic_index);""")
self._ffi.cdef("""void config_audio_codec(int iic_index);""")
self._ffi.cdef("""void select_line_in(int iic_index);""")
self._ffi.cdef("""void select_mic(int iic_index);""")
self._ffi.cdef("""void deselect(int iic_index);""")
self._ffi.cdef("""void bypass(unsigned int audio_mmap_size,
unsigned int nsamples,
int uio_index, int iic_index) ;""")
self._ffi.cdef("""void record(unsigned int audio_mmap_size,
unsigned int * BufAddr, unsigned int nsamples,
int uio_index, int iic_index);""")
self._ffi.cdef("""void play(unsigned int audio_mmap_size,
unsigned int * BufAddr, unsigned int nsamples,
int uio_index, int iic_index);""")

self.buffer = numpy.zeros(0).astype(numpy.int32)
self.sample_rate = None
self.sample_len = len(self.buffer)
self.iic_index = None
self.uio_index = None
self.configure()

这里是将native层(原生、C/C++层)的函数暴露给python层使用,根据函数定义去解析libaudio.so的符号,查找地址,并建立调用封送约定。

1
2
3
4
5
6
7
8
9
10
def configure(self, sample_rate=48000,
iic_index=1, uio_name="audio-codec-ctrl"):
self.sample_rate = sample_rate
self.iic_index = iic_index
self.uio_index = get_uio_index(uio_name)
if self.uio_index is None:
raise ValueError("Cannot find UIO device {}".format(uio_name))

self._libaudio.config_audio_pll(self.iic_index)
self._libaudio.config_audio_codec(self.iic_index)

这里是设置采样率,配置时钟,让底层去查找在linux系统中注册的IO口。

1
2
3
4
5
6
7
8
9
   def select_line_in(self): 
self._libaudio.select_line_in(self.iic_index)
def bypass(self, seconds):
if not 0 < seconds <= 60:
raise ValueError("Bypassing time has to be in (0,60].")

self.sample_len = math.ceil(seconds * self.sample_rate)
self._libaudio.bypass(self.mmio.length,
self.sample_len, self.uio_index, self.iic_index)

其余函数都是对native层的封装调用,可以说,做的并不好,逻辑全在native层里,python也没封装的咋样。
为此需要进一步查看libaudio.so中是如何实现的,可以查看源码
访问目录\\192.168.31.69\xilinx\pynq\lib\_pynq\_audio,即为libaudio.so的源码。由于我们是Pynq-Z2,所以查看audio_adau1761.cpp即可。

首先查看两个配置函数

1
2
self._libaudio.config_audio_pll(self.iic_index)
self._libaudio.config_audio_codec(self.iic_index)

由于过长,全文请自行查看文件。

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
extern "C" void config_audio_pll(int iic_index) {
...
// Poll PLL Lock bit
u8TxData[0] = 0x40;
u8TxData[1] = 0x02;
do {
if (writeI2C_asFile(iic_fd, u8TxData, 2) < 0){
printf("Unable to write I2C %d.\n", iic_index);
}
if (readI2C_asFile(iic_fd, u8RxData, 6) < 0){
printf("Unable to read I2C %d.\n", iic_index);
}
} while((u8RxData[5] & 0x02) == 0);
...
}

extern "C" void config_audio_codec(int iic_index) {
...
// Mute Mixer1 and Mixer2 here, enable when MIC and Line In used
write_audio_reg(R4_RECORD_MIXER_LEFT_CONTROL_0, 0x00, iic_fd);
write_audio_reg(R6_RECORD_MIXER_RIGHT_CONTROL_0, 0x00, iic_fd);
// Set LDVOL and RDVOL to 21 dB and Enable left and right differential
write_audio_reg(R8_LEFT_DIFFERENTIAL_INPUT_VOLUME_CONTROL, 0xB3, iic_fd);
write_audio_reg(R9_RIGHT_DIFFERENTIAL_INPUT_VOLUME_CONTROL, 0xB3, iic_fd);
// Enable MIC bias
write_audio_reg(R10_RECORD_MICROPHONE_BIAS_CONTROL, 0x01, iic_fd);
// Enable ALC control and noise gate
write_audio_reg(R14_ALC_CONTROL_3, 0x20, iic_fd);
// Put CODEC in Master mode
write_audio_reg(R15_SERIAL_PORT_CONTROL_0, 0x01, iic_fd);
...
}

可见,是根据数据手册,使用IIC接口对ADAU1761进行配置。
再查看具体功能函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
extern "C" void select_line_in(int iic_index) {
int iic_fd;
iic_fd = setI2C(iic_index, IIC_SLAVE_ADDR);
if (iic_fd < 0) {
printf("Unable to set I2C %d.\n", iic_index);
}

// Mixer 1 (left channel)
write_audio_reg(R4_RECORD_MIXER_LEFT_CONTROL_0, 0x01, iic_fd);
// Enable LAUX (MX1AUXG)
write_audio_reg(R5_RECORD_MIXER_LEFT_CONTROL_1, 0x07, iic_fd);

// Mixer 2
write_audio_reg(R6_RECORD_MIXER_RIGHT_CONTROL_0, 0x01, iic_fd);
// Enable RAUX (MX2AUXG)
write_audio_reg(R7_RECORD_MIXER_RIGHT_CONTROL_1, 0x07, iic_fd);

if (unsetI2C(iic_fd) < 0) {
printf("Unable to unset I2C %d.\n", iic_index);
}
}

这一步是将LINE_IN选作采样端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
extern "C" void bypass(unsigned int audio_mmap_size,
unsigned int nsamples,
int uio_index, int iic_index) {
int i, status;
void *uio_ptr;
int DataL, DataR;
int iic_fd;

uio_ptr = setUIO(uio_index, audio_mmap_size);
iic_fd = setI2C(iic_index, IIC_SLAVE_ADDR);
if (iic_fd < 0) {
printf("Unable to set I2C %d.\n", iic_index);
}

这里是取得linux对iic以及设备的封装

1
2
3
4
5
6
7
8
9
// Mute mixer1 and mixer2 input
write_audio_reg(R23_PLAYBACK_MIXER_LEFT_CONTROL_1, 0x00, iic_fd);
write_audio_reg(R25_PLAYBACK_MIXER_RIGHT_CONTROL_1, 0x00, iic_fd);
// Enable Mixer3 and Mixer4
write_audio_reg(R22_PLAYBACK_MIXER_LEFT_CONTROL_0, 0x21, iic_fd);
write_audio_reg(R24_PLAYBACK_MIXER_RIGHT_CONTROL_0, 0x41, iic_fd);
// Enable Left/Right Headphone out
write_audio_reg(R29_PLAYBACK_HEADPHONE_LEFT_VOLUME_CONTROL, 0xE7, iic_fd);
write_audio_reg(R30_PLAYBACK_HEADPHONE_RIGHT_VOLUME_CONTROL, 0xE7, iic_fd);

这里是配置输入输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for(i=0; i<nsamples; i++){
//wait for RX data to become available
do {
status = \
*((volatile unsigned *)(((uint8_t *)uio_ptr) + I2S_STATUS_REG));
} while (status == 0);
*((volatile unsigned *)(((uint8_t *)uio_ptr) + I2S_STATUS_REG)) = \
0x00000001;

// Read the sample from the input
DataL = *((volatile int *)(((uint8_t *)uio_ptr) + I2S_DATA_RX_L_REG));
DataR = *((volatile int *)(((uint8_t *)uio_ptr) + I2S_DATA_RX_R_REG));

// Write the sample to output
*((volatile int *)(((uint8_t *)uio_ptr) + I2S_DATA_TX_L_REG)) = DataL;
*((volatile int *)(((uint8_t *)uio_ptr) + I2S_DATA_TX_R_REG)) = DataR;
}

这里是关键,将音频数据取样,并直接发送。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    write_audio_reg(R23_PLAYBACK_MIXER_LEFT_CONTROL_1, 0x00, iic_fd);
write_audio_reg(R25_PLAYBACK_MIXER_RIGHT_CONTROL_1, 0x00, iic_fd);
write_audio_reg(R22_PLAYBACK_MIXER_LEFT_CONTROL_0, 0x00, iic_fd);
write_audio_reg(R24_PLAYBACK_MIXER_RIGHT_CONTROL_0, 0x00, iic_fd);
write_audio_reg(R29_PLAYBACK_HEADPHONE_LEFT_VOLUME_CONTROL, 0xE5, iic_fd);
write_audio_reg(R30_PLAYBACK_HEADPHONE_RIGHT_VOLUME_CONTROL, 0xE5, iic_fd);

if (unsetUIO(uio_ptr, audio_mmap_size) < 0){
printf("Unable to free UIO %d.\n", uio_index);
}
if (unsetI2C(iic_fd) < 0) {
printf("Unable to unset I2C %d.\n", iic_index);
}
}

这里是收尾。
音频数据就这么简单地被获取了,可以按照他的方法,实现我们自己的应用程序。
读者可以按图索骥,分析其他函数与例程。

进阶开发

概述

本节使用笔者自己编译的Pynq-Z2镜像文件(基于v2.5),如果你也想自己编译,请看第三节。自己编译的原因是,由于本节的目标是将ADAU1761的数据输出到一个USB音频设备,但是官方镜像的Linux内核没有编译ALSA(底层音频库),也没有USB Audio的驱动,如果手动进行开发非常麻烦。所以自己将内核配置为支持上述选项。

前期准备

一个 USB音频设备(本文以华为Type-C降噪耳机3为例)
一个 USB转接头(USB A转Type-C母)(如果你的设备是Type-C耳机)
安装 Putty(可选)
下载 Pynq镜像 fole
下载 环境配置脚本 6ffd

安装配置

请参考第一节进行安装镜像,并插入SD卡,上电启动。
打开WinSCP,并连接到开发板。
解压环境配置脚本,将pynq_setup文件夹拖入/home/xilinx
浏览器访问http://192.168.31.69:9090,点击右上角的New-Terminal,打开一个终端。

你也可以使用putty

点击Save可以保存配置供下次使用,点击Open

用户名密码都为xilinx

1
2
3
cd pynq_setup
chmod 777 ./setup.sh
./setup.sh

运行过程中会提示

请输入y,回车。
由于Jupyter Notebook拥有root权限,因此sudo时貌似不用输入密码,而如果使用putty,在提示输入xilinx的密码时请输入xilinx
运行完成后会自动重启,稍等半分钟即可启动完成。
将USB音频设备插入,开启终端,输入aplay -l

如果一切顺利,此处会显示你的设备。
再输入aplay ./pynq_setup/test.wav
如果顺利,你可以从耳机中听见一段优美的旋律(音量可能较大)。
如果播放失败,请参考下面的进阶安装配置。

进阶安装配置

此部分是针对你个人的USB音频设备的配置调优。
由于.asoundrc仅对当前用户生效,使用浏览器Jupyter Notebook打开的终端是root用户

而我们的用户名是xilinx,所以请使用putty
你也可以在浏览器终端中输入su xilinx切换


请注意:如果当前用户名不对,音频配置文件不会被加载。如果发生了各种错误,请先核对你的用户名(@前面的是否为xilinx)。

请打开.asoundrc文件,可以用WinSCP打开编辑,或是在本地编辑后,拖到WinSCP里进行上传替换。
文件是针对各个音频格式做的详细配置,由于我的耳机支持4种配置,你可以看见

1
2
3
4
5
6
7
8
9
10
11
pcm.at24b96k {
type plug
slave.pcm "hw-24b-96k"
}
pcm.hw-24b-96k {
type hw
card 0
device 0
rate 96000
format S24_3LE
}

就代表这个配置是24位,96000采样率的,第一个pcm.at24b96k中,slave.pcm引用了下面的hw-24b-96k,这一步是必要的,这样就可以让alsa(音频驱动)自己进行格式转换,如播放一个16位48000采样率的音频,和播放设备不匹配,会自动进行转换。
配置了这个如何使用呢?有两种办法
第一,在aplay -D 24b96k test.wav中声明自己的配置名称。-D后面跟着名称。

可见正常播放,你也可以增加-v来看当前的详细配置。

你可以尝试aplay -D hw-24b-96k test.wav,会提示格式不匹配,无法播放,因此需要这一层转换(实际是调用了alsa的格式转换插件)。

以此类推,还有16bit 48k, 16bit 96k, 24bit 48k三种配置,除此之外,文件最上面还有

1
2
3
4
5
6
7
8
9
ctl.!default {
type hw
card 0
}

pcm.!default {
type plug
slave.pcm "hw-16b-48k"
}

代表默认配置,如果你不在aplay时输入-D指定,则默认使用default,前面的感叹号是必要的,起警示作用。
那么我的设备有这四种配置,读者的配置可能有所不同,需要您自己修改后替换。具体步骤如下
输入cat /proc/asound/card0/stream0,查看设备属性

可见华为降噪耳机3(Type-C)叫CM-Q3,USB Audio设备,有2个Format,每个Format有2个采样率,共有16b48k,16b96k,24b48k,24b96k四种输出格式,只有一种输入格式16b48k
其中Rates表示采样率,Format需要和配置文件中对应,设备之间有差异请仔细核对。
根据这里的信息,再去调整你的.asoundrc,最后重启设备sudo reboot即可。

还有一个是控制功能。
输入amixer contents

可以获得配置项,可以看到我的设备,Volume设置的id是4,所以可以
amixer cset numid=4 15
来调整音量。

程序编写

对于笔者来说,已经研究了4天,有两个好消息:USB音频设备可以正常播放音频了,ADAU1761也可以正常采集音频了。剩下的就是写一个程序把他们连起来。我花了一天时间调试了一个可以用的程序,开源出来给大家参考。

1
2
3
4
5
6
su xilinx 
cd
git clone https://gitee.com/Schwarzer/adausb.git
cd adausb
make
sudo ./adausb

需要注意由于uio设备无法被非root用户访问,所以需要使用sudo ./adausb
如果正常的话,你将从USB耳机中听到LINE_IN输入的声音。
你可以指定声卡,如./adausb at24b48k,如不填则等同于./adausb default
你可以指定延时,如./adausb at24b48k 10000,为10ms,延时越大,缓冲区越大,越不容易发生错误。

如果出现underrun occurred,说明输入速度慢于输出了,这是由于,官方的PL层可能出现了问题,应该为48000的采样率,实际测试为48740,太快了,所以代码里做了调整。听上去可能有很短暂的一个空白声,几乎无影响。下面来看代码,我的代码都在adausb.cpplog.h里。

1
2
3
4
5
6
7
8
9
10
11
#include "alsa/asoundlib.h"
#include "log.h"
#include "i2cps.h"
#include "uio.h"
#include "audio_adau1761.h"
#include <memory.h>
#include <stdint.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <time.h>

所要使用的所有头文件

1
2
3
4
5
6
struct adau_ctx
{
uint32_t* opt_buf;
uint32_t opt_buf_idx;
uint32_t opt_buf_size;
};

传递给adau采样线程的上下文

1
2
3
4
5
6
7
8
9
adau_ctx ctx;
pthread_t adau_thread;
snd_pcm_t *pcm;
volatile uint8_t running;

extern "C" void int_handler(int dummy) {
loge("\ninterrupted!\n");
running = 0;
}

捕获Ctrl+C,不过我没有多做处理。

1
2
3
4
5
6
7
8
static timespec ts;
uint64_t getns() {
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
uint64_t ret = ts.tv_sec;
ret *= 1000000000;
ret += ts.tv_nsec;
return ret;
}

获取纳秒级的时间,这是由于采样率为48740,需要手动修正到48000

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
void* adau_sampling(void* pctx)
{
adau_ctx* ctx = (adau_ctx*)pctx;
void* uio_ptr = setUIO(0, 65536);
if (uio_ptr < 0) {
loge("unable to mmap uio\n");
exit(-1);
}
int iic_fd = setI2C(1, IIC_SLAVE_ADDR);
if (iic_fd < 0) {
loge("unable to set i2c %d\n", 1);
exit(-1);
}

uint32_t status, l, r;

uint64_t start = getns();
uint64_t sample_cnt = 1;
double sample_rate = 48000.0;
double second_ns = 1000000000.0;
double next_sample_time = second_ns / sample_rate;

while (running)
{
do {
status = *((volatile unsigned*)(((uint8_t*)uio_ptr) + I2S_STATUS_REG));
} while (status == 0);
*((volatile unsigned*)(((uint8_t*)uio_ptr) + I2S_STATUS_REG)) = 0x00000001;
l = *((volatile int*)(((uint8_t*)uio_ptr) + I2S_DATA_RX_L_REG));
r = *((volatile int*)(((uint8_t*)uio_ptr) + I2S_DATA_RX_R_REG));

初始化I2C,UIO,初始化各项时间限制数据,采集一个样本

1
2
3
4
5
uint64_t t = getns() - start; 
while(t < next_sample_time)
{
t = getns() - start;
}

等待1/48000

1
2
3
4
5
6
7
8
	ctx->opt_buf[ctx->opt_buf_idx++] = l;
ctx->opt_buf[ctx->opt_buf_idx++] = r;
if (ctx->opt_buf_idx >= ctx->opt_buf_size)
{
ctx->opt_buf_idx = 0;
}
next_sample_time = second_ns * ++sample_cnt / sample_rate;
}

放到缓冲区,计算下一个采样时间(保证精度)

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
	if (unsetUIO(uio_ptr, 65536) < 0) {
loge("unable to free UIO %d\n", 0);
}
if (unsetI2C(iic_fd) < 0) {
loge("unable to unset I2C %d\n", 1);
}
}

int main(int argc, char ** argv)
{
char * pcm_name = (char *)"default";
if(argc >= 2)
{
pcm_name = argv[1];
logd("pcm_name = %s\n", pcm_name);
}

int ret = snd_pcm_open(&pcm, pcm_name, SND_PCM_STREAM_PLAYBACK, 0);
if(ret < 0){
loge("audio open error %d\n", ret);
return -1;
}

logd("initialize\n");
running = 1;
signal(SIGINT, int_handler);

int latency = 10000;
if(argc >= 3)
sscanf(argv[2], "%d", &latency);
ret = snd_pcm_set_params(pcm, SND_PCM_FORMAT_S24_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 2, 48000, 1, latency);
if(ret < 0){
loge("cannot set params\n");
return -1;
}

初始化ALSA API,设置PCM的格式

1
2
3
4
5
6
7
8
9
10
11
12
snd_pcm_uframes_t framesize, periodsize;
snd_pcm_get_params(pcm, &framesize, &periodsize);
logd("frame size = %d period size = %d\n", framesize, periodsize);

uint32_t blocks = 16;
ctx.opt_buf_size = periodsize * 2 * blocks;
ctx.opt_buf = new uint32_t[ctx.opt_buf_size];
ctx.opt_buf_idx = 0;

config_audio_pll(1);
config_audio_codec(1);
select_line_in(1);

使用官方的adau1761.cpp中初始化函数

1
2
3
4
5
6
7
8
9
10
11
ret = pthread_create(&adau_thread, NULL, adau_sampling, &ctx);
if (ret) {
loge("create adau thread failed");
exit(-1);
}

logd("adau priority max\n");
sched_param param;
pthread_getschedparam(adau_thread, &ret, &param);
param.sched_priority = sched_get_priority_max(ret);
pthread_setschedparam(adau_thread, ret, &param);

创建采样线程,给他优先级拉满

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
uint32_t block_idx = 0;
uint32_t block_cnt = blocks / 4;

uint64_t start_time = getns();
double sample_rate = 48000.0;
double block_time_numerator = periodsize * 1000000000 ; //need divide by sample_rate
double next_feed_time = block_time_numerator * block_cnt / sample_rate;

snd_pcm_prepare(pcm);

while (running)
{
uint64_t t = getns() - start_time;
if(t >= next_feed_time)
{
uint32_t * buf = ctx.opt_buf + periodsize * 2 * block_idx++;
if(block_idx == blocks) block_idx = 0;

ret = snd_pcm_writei(pcm, buf, periodsize);
if(ret == -EPIPE){
snd_pcm_recover(pcm, ret, 0);
}
else if(ret < 0){
loge("writei err %s\n", snd_strerror(ret));
}
else if(ret != periodsize){
loge("short %d %d\n", ret, periodsize);
}
next_feed_time = block_time_numerator * (++block_cnt) / sample_rate;
}
}

当采样到一定个数时,向ALSA提交缓冲区。
当然,笔者的程序非常不完美。需要修改才能保证持久稳定运行。

编译Pynq-Z2镜像文件

其他的不用我多说什么,如果你想自己调整内核,我把我的经验分享在这里。

前期准备

需要下载大量文件!
建议你去学校机房下载。笔者就是去学校机房把网线拔下来插自己电脑上下的,1000M网比较快,建议准备一个代理。
本文是基于2.5的,如果后续有更新,请参考这篇文章,和这篇文章

笔者电脑为Windows 10 64位,16GB内存,i7-6700HQ,请预留至少100GB硬盘

下载 Vivado + SDK 2019.1 需要注册xilinx账号 22GB
下载 Petalinux 2019.1 7GB
下载 VirtualBox 选择自己的架构,并且安装
下载 Ubuntu 16.04.6镜像
原本,编译非常耗时,完整需要大概一天。不过我们参考文档可知,可以加速编译,请先下载
下载 arm架构的通用文件镜像 2GB
下载 预先编译的Pynq文件 50MB

配置

下载完成后,应该有如下这些文件

首先安装VirtualBox,请自行完成安装运行

点击新建

输入信息

建议为自己电脑的一半

创建磁盘

选择动态创建,大小选择200GB(按个人喜好,不低于100GB)
再打开设置

系统-处理器,处理器数量选择自己电脑核心数。

后续步骤请参考这篇文章,或自行搜索,直到进入系统。

如果你的桌面很小,记得点击顶部的设备-安装增强功能,并重启。
现在点击设备-共享文件夹

点击固定分配,再点击右侧的加号,把自己刚才下载的那些文件所在的文件夹添加进来。
我的是H:\_IDM,因此是

点击确定即可在根目录看到。

不过现在还没完,请输入
sudo adduser <你的用户名> vboxsf
这样才有权限访问它,需要重启才能生效
sudo reboot

右键打开终端

1
sudo apt install git

这里需要先克隆PYNQ的仓库,我帮大家镜像到了gitee上,并做了修改,请克隆我的仓库。
我做的修改有两处,第一,就是把boards中只留下Pynq-Z2的文件,第二就是把预编译文件放在了dist下,不然后续会报错。如果你克隆我的仓库有问题,可以自行完成上述更改。
你需要先注册一个gitee账号才能克隆这个仓库,建议注册,以后github速度慢,可以先克隆到gitee。
git clone https://gitee.com/Schwarzer/PYNQ.git

提示输入用户名密码,请输入。

随后就开始安装了,由于笔者已经安装过了,不能再次安装,所以可能有遗漏之处。
我的用户名是schwarzer,安装文件放在/idm,请根据自己情况修改!
安装Vivado和SDK,请一路下一步,选WebPack,安装路径为/home/<你的用户名>/xilinx

1
2
3
4
5
6
cd
mkdir downloads
cd downloads
tar -xf /idm/Xilinx_Vivado_SDK_2019.1_0524_1430.tar.gz
cd Xilinx_Vivado_SDK_2019.1_0524_1430
./xsetup

安装Petalinux

1
2
3
4
5
sudo apt install tofrodos iproute gawk xvfb make net-tools libncurses5-dev tftpd
sudo apt install zlib1g-dev zlib1g:i386 libssl-dev flex bison libselinux1 gnupg wget diffstat
sudo apt install chrpath socat xterm autoconf libtool tar unzip texinfo gcc-multilib
sudo apt install build-essential libsdl1.2-dev libglib2.0-dev screen pax gzip
/idm/petalinux-v2019.1-final-installer_2.run /home/schwarzer/xilinx

安装完成后,你的xilinx路径下应该是这样,请把petalinux文件夹改名为petalinux-v2019.1-final

然后需要把arm架构通用镜像复制进来
请先解压pynq_rootfs_arm_v2.5.zip得到bionic.arm.2.5.img

1
2
mkdir ~/PYNQ/sdbuild/prebuilt
cp /idm/bionic.arm.2.5.img ~/PYNQ/sdbuild/prebuilt

如果你没有用我的gitee仓库:
1、还需要把预编译文件复制,用了就不要了

1
2
mkdir ~/PYNQ/dist
cp /idm/pynq-2.5.tar.gz ~/PYNQ/dist

2、添加环境配置文件,将以下内容保存为xilinx.sh,保存在~\PYNQ\sdbuild\xilinx.sh

1
2
3
4
5
export XILINX_VIVADO=/home/${USER}/xilinx/Vivado/2019.1
export PATH=/home/${USER}/xilinx/Vivado/2019.1/bin:/home/${USER}/xilinx/Vivado/2019.1/lib/lnx64.o:$PATH
export PATH=/home/${USER}/xilinx/SDK/2019.1/bin:/home/${USER}/xilinx/SDK/2019.1/gnu/microblaze/lin/bin:/home/${USER}/xilinx/SDK/2019.1/gnu/arm/lin/bin:/home/${USER}/xilinx/SDK/2019.1/gnu/microblaze/linux_toolchain/lin64_le/bin:/home/${USER}/xilinx/SDK/2019.1/gnu/aarch32/lin/gcc-arm-linux-gnueabi/bin:/home/${USER}/xilinx/SDK/2019.1/gnu/aarch32/lin/gcc-arm-none-eabi/bin:/home/${USER}/xilinx/SDK/2019.1/gnu/aarch64/lin/aarch64-linux/bin:/home/${USER}/xilinx/SDK/2019.1/gnu/aarch64/lin/aarch64-none/bin:/home/${USER}/xilinx/SDK/2019.1/gnu/armr5/lin/gcc-arm-none-eabi/bin:/home/${USER}/xilinx/SDK/2019.1/tps/win64/cmake-3.3.2/bin:/home/${USER}/xilinx/SDK/2019.1/gnuwin/bin:$PATH
export PATH=/home/${USER}/xilinx/DocNav:$PATH
export PYNQ_UBUNTU_REPO=https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/

还有环境配置脚本,将以下内容保存为env.sh,保存在~\PYNQ\sdbuild\env.sh

1
2
3
source ~/xilinx/petalinux-v2019.1-final/settings.sh
source ./xilinx.sh
petalinux-util --webtalk off

3、删除

1
2
rm -rf ~/PYNQ/boards/Pynq-Z1
rm -rf ~/PYNQ/boards/ZCU104

4、保存更改

1
2
3
cd ~/PYNQ
git add .
git commit -m 'update'

如果你用我的,就不用修改上述内容了

随后准备开始编译
如果你要修改内核配置,请打开~/PYNQ/sdbuild/scripts/,在如图所示位置添加

1
petalinux-config --get-hw-description=$BSP_BUILD/hardware_project -c kernel

如果你不想修改Pynq-Z2的硬件,可以通过修改加速编译过程:
通过手动make,预先完成编译,然后修改makefile的文件名,脚本就可以跳过这个步骤。

1
2
3
cd ~/PYNQ/boards/Pynq-Z2/petalinux_bsp/hardware_project
make
mv ./makefile ./makefiled

同样,完成上述修改后,记得

1
2
3
cd ~/PYNQ
git add .
git commit -m 'update'

环境配置

1
2
cd ~/PYNQ/sdbuild/scripts
./setup_host.sh

万事俱备,准备编译

1
2
cd ~/PYNQ/sdbuild
source env.sh

使用以下命令以加速编译,如果想体验完整编译,直接make

1
make PREBUILT="../prebuilt/bionic.arm.2.5.img" PYNQ_SDIST="../dist/pynq-2.5.tar.gz" BOARDS="Pynq-Z2"

如果你想要配置内核,那么中途会弹出配置图形界面,请按需配置。会弹出两次,第二次直接退出即可。

如果意外发生中断,建议你重新编译。
这里要注意,如果发生下载中断,可以通过删除~/PYNQ/sdbuild/build/PYNQ来重试

如果一切顺利,一个小时内将完成编译,文件如下所示

如果想重新编译,请make clean
如果遇到困难,欢迎留言,或联系邮箱:cnschwarzer@qq.com。谢谢!