Pynq-Z2 开发指南与实例(Linux系统方式)

前言

本教程分三个部分

  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,选择下载的镜像文件,点击写入
Pynq-Z2 开发指南与实例(Linux系统方式)_第1张图片
Pynq-Z2 开发指南与实例(Linux系统方式)_第2张图片
待完成后,不要按系统提示进行格式化,打开DiskGenius。
Pynq-Z2 开发指南与实例(Linux系统方式)_第3张图片
在左侧找到SD卡磁盘,选择较大的分区(大概5GB那个),右键,扩容分区。
Pynq-Z2 开发指南与实例(Linux系统方式)_第4张图片
点击开始,等待约5分钟即可完成。
Pynq-Z2 开发指南与实例(Linux系统方式)_第5张图片
Pynq-Z2 开发指南与实例(Linux系统方式)_第6张图片
剩余时间是假的,不必理会。
完成后,将SD卡从电脑上取下,插入Pynq-Z2的卡槽里(背部)。
Pynq-Z2 开发指南与实例(Linux系统方式)_第7张图片
将启动方式跳线帽选择SD。
Pynq-Z2 开发指南与实例(Linux系统方式)_第8张图片
使用microUSB线连接到电脑,开启电源。
等待4个绿色LED,2个蓝色LED高亮闪烁后,代表启动完成。

使用网线连接Pynq-Z2到电脑的局域网中,将自动获取IP地址。
以小米路由器为例,可以看到开发板的IP地址为192.168.31.69
Pynq-Z2 开发指南与实例(Linux系统方式)_第9张图片
浏览器访问该IP,将自动跳转到Jupyter Notebook
需要输入密码,该linux系统的账号为xilinx,密码为xilinx
Pynq-Z2 开发指南与实例(Linux系统方式)_第10张图片
点击登录
Pynq-Z2 开发指南与实例(Linux系统方式)_第11张图片

文件系统

可以通过文件管理器访问\\<你的Pynq-Z2 IP地址>\xilinx,来访问其文件系统(无法访问根目录)。
在这里插入图片描述
Pynq-Z2 开发指南与实例(Linux系统方式)_第12张图片
如果需要访问完整文件系统,请按以下步骤进行,如不需要请跳过。
下载安装 WinSCP
按如下方法配置
Pynq-Z2 开发指南与实例(Linux系统方式)_第13张图片
点击保存(保存密码),即可以后在左侧选择。
Pynq-Z2 开发指南与实例(Linux系统方式)_第14张图片
点击登录,即可访问完整目录。
Pynq-Z2 开发指南与实例(Linux系统方式)_第15张图片

程序分析

base文件夹中包含了很多python的例子,可以自行参考学习。
本文以base/audio/audio_playback.ipynb为研究对象。
Pynq-Z2 开发指南与实例(Linux系统方式)_第16张图片
该例程使用python控制板载的ADAU1761芯片,采集从耳机接口输入的音频。
Pynq-Z2 开发指南与实例(Linux系统方式)_第17张图片
LINE_IN代表双声道输入接口,HP MIC代表耳麦(传统4段式3.5毫米耳机接口,2声道输出,1声道输入)
首先来看其第一个功能
Pynq-Z2 开发指南与实例(Linux系统方式)_第18张图片
具体实现了选择LINE_IN作为输入,直接输出到HP MIC的输出,不储存。
你需要一根"内录线",即3.5mm公对公音频线,一端插入LINE_IN,一端插入一个输出源(如手机耳机口),再插入一个耳机到HP MIC中,即可听见声音。
可以自己点击上方的运行查看效果,你将听到从LINE_IN输入的声音。
Pynq-Z2 开发指南与实例(Linux系统方式)_第19张图片
下面我们来分析他具体做了什么
访问文件系统\\192.168.31.69\xilinx\pynq\lib,将IP替换为你自己的。
Pynq-Z2 开发指南与实例(Linux系统方式)_第20张图片
这里就是其具体调用的python文件。
Pynq-Z2 开发指南与实例(Linux系统方式)_第21张图片
这两部是加载Pynq开发者为Pynq-Z2写的底层PL文件base.bit,和加载libaudio.so类库。下文删除了注释,请自己打开文件查看。

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的符号,查找地址,并建立调用封送约定。

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口。

    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即可。

首先查看两个配置函数

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

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

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进行配置。
再查看具体功能函数

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选作采样端口。

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以及设备的封装

    // 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);

这里是配置输入输出

    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;
    }

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

    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,打开一个终端。
Pynq-Z2 开发指南与实例(Linux系统方式)_第22张图片
Pynq-Z2 开发指南与实例(Linux系统方式)_第23张图片
你也可以使用putty
Pynq-Z2 开发指南与实例(Linux系统方式)_第24张图片
点击Save可以保存配置供下次使用,点击Open
Pynq-Z2 开发指南与实例(Linux系统方式)_第25张图片
用户名密码都为xilinx

cd pynq_setup
chmod 777 ./setup.sh
./setup.sh

Pynq-Z2 开发指南与实例(Linux系统方式)_第26张图片
运行过程中会提示
在这里插入图片描述
请输入y,回车。
由于Jupyter Notebook拥有root权限,因此sudo时貌似不用输入密码,而如果使用putty,在提示输入xilinx的密码时请输入xilinx
运行完成后会自动重启,稍等半分钟即可启动完成。
将USB音频设备插入,开启终端,输入aplay -l
Pynq-Z2 开发指南与实例(Linux系统方式)_第27张图片
如果一切顺利,此处会显示你的设备。
再输入aplay ./pynq_setup/test.wav
如果顺利,你可以从耳机中听见一段优美的旋律(音量可能较大)。
如果播放失败,请参考下面的进阶安装配置。

进阶安装配置

此部分是针对你个人的USB音频设备的配置调优。
由于.asoundrc仅对当前用户生效,使用浏览器Jupyter Notebook打开的终端是root用户
在这里插入图片描述
而我们的用户名是xilinx,所以请使用putty
你也可以在浏览器终端中输入su xilinx切换在这里插入图片描述
请注意:如果当前用户名不对,音频配置文件不会被加载。如果发生了各种错误,请先核对你的用户名(@前面的是否为xilinx)。

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

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来看当前的详细配置。
Pynq-Z2 开发指南与实例(Linux系统方式)_第28张图片
你可以尝试aplay -D hw-24b-96k test.wav,会提示格式不匹配,无法播放,因此需要这一层转换(实际是调用了alsa的格式转换插件)。
在这里插入图片描述
以此类推,还有16bit 48k, 16bit 96k, 24bit 48k三种配置,除此之外,文件最上面还有

ctl.!default {
	type hw
	card 0
}

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

代表默认配置,如果你不在aplay时输入-D指定,则默认使用default,前面的感叹号是必要的,起警示作用。
那么我的设备有这四种配置,读者的配置可能有所不同,需要您自己修改后替换。具体步骤如下
输入cat /proc/asound/card0/stream0,查看设备属性
Pynq-Z2 开发指南与实例(Linux系统方式)_第29张图片
可见华为降噪耳机3(Type-C)叫CM-Q3,USB Audio设备,有2个Format,每个Format有2个采样率,共有16b48k,16b96k,24b48k,24b96k四种输出格式,只有一种输入格式16b48k
其中Rates表示采样率,Format需要和配置文件中对应,设备之间有差异请仔细核对。
根据这里的信息,再去调整你的.asoundrc,最后重启设备sudo reboot即可。

还有一个是控制功能。
输入amixer contents
Pynq-Z2 开发指南与实例(Linux系统方式)_第30张图片
可以获得配置项,可以看到我的设备,Volume设置的id是4,所以可以
amixer cset numid=4 15
来调整音量。

程序编写

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

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里。

#include "alsa/asoundlib.h"
#include "log.h" 
#include "i2cps.h"
#include "uio.h"
#include "audio_adau1761.h" 
#include 
#include 
#include    
#include 
#include  
#include  

所要使用的所有头文件

struct adau_ctx
{ 
	uint32_t* opt_buf; 
	uint32_t opt_buf_idx;
	uint32_t opt_buf_size;  
};

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

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,不过我没有多做处理。

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

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,初始化各项时间限制数据,采集一个样本

		uint64_t t = getns() - start; 
		while(t < next_sample_time)
		{
			t = getns() - start;
		}

等待1/48000

		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; 
	}

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

	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的格式

	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中初始化函数

	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);  

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

	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,请自行完成安装运行
Pynq-Z2 开发指南与实例(Linux系统方式)_第31张图片
点击新建
Pynq-Z2 开发指南与实例(Linux系统方式)_第32张图片
输入信息
Pynq-Z2 开发指南与实例(Linux系统方式)_第33张图片
建议为自己电脑的一半
Pynq-Z2 开发指南与实例(Linux系统方式)_第34张图片
创建磁盘
Pynq-Z2 开发指南与实例(Linux系统方式)_第35张图片
选择动态创建,大小选择200GB(按个人喜好,不低于100GB)
再打开设置
Pynq-Z2 开发指南与实例(Linux系统方式)_第36张图片
系统-处理器,处理器数量选择自己电脑核心数。
Pynq-Z2 开发指南与实例(Linux系统方式)_第37张图片
后续步骤请参考这篇文章,或自行搜索,直到进入系统。

如果你的桌面很小,记得点击顶部的设备-安装增强功能,并重启。
现在点击设备-共享文件夹
Pynq-Z2 开发指南与实例(Linux系统方式)_第38张图片
点击固定分配,再点击右侧的加号,把自己刚才下载的那些文件所在的文件夹添加进来。
我的是H:\_IDM,因此是
Pynq-Z2 开发指南与实例(Linux系统方式)_第39张图片
点击确定即可在根目录看到。
在这里插入图片描述
不过现在还没完,请输入
sudo adduser <你的用户名> vboxsf
这样才有权限访问它,需要重启才能生效
sudo reboot

右键打开终端

sudo apt install git

这里需要先克隆PYNQ的仓库,我帮大家镜像到了gitee上,并做了修改,请克隆我的仓库。
我做的修改有两处,第一,就是把boards中只留下Pynq-Z2的文件,第二就是把预编译文件放在了dist下,不然后续会报错。如果你克隆我的仓库有问题,可以自行完成上述更改。
你需要先注册一个gitee账号才能克隆这个仓库,建议注册,以后github速度慢,可以先克隆到gitee。
git clone https://gitee.com/Schwarzer/PYNQ.git
Pynq-Z2 开发指南与实例(Linux系统方式)_第40张图片
提示输入用户名密码,请输入。

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

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

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
Pynq-Z2 开发指南与实例(Linux系统方式)_第41张图片
然后需要把arm架构通用镜像复制进来
请先解压pynq_rootfs_arm_v2.5.zip得到bionic.arm.2.5.img

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

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

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

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

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

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

3、删除

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

4、保存更改

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

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

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

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

Pynq-Z2 开发指南与实例(Linux系统方式)_第42张图片
如果你不想修改Pynq-Z2的硬件,可以通过修改加速编译过程:
通过手动make,预先完成编译,然后修改makefile的文件名,脚本就可以跳过这个步骤。

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

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

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

环境配置

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

万事俱备,准备编译

cd ~/PYNQ/sdbuild
source env.sh 

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

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

Pynq-Z2 开发指南与实例(Linux系统方式)_第43张图片
Pynq-Z2 开发指南与实例(Linux系统方式)_第44张图片
如果你想要配置内核,那么中途会弹出配置图形界面,请按需配置。会弹出两次,第二次直接退出即可。
Pynq-Z2 开发指南与实例(Linux系统方式)_第45张图片
如果意外发生中断,建议你重新编译。
这里要注意,如果发生下载中断,可以通过删除~/PYNQ/sdbuild/build/PYNQ来重试
Pynq-Z2 开发指南与实例(Linux系统方式)_第46张图片
Pynq-Z2 开发指南与实例(Linux系统方式)_第47张图片
如果一切顺利,一个小时内将完成编译,文件如下所示
Pynq-Z2 开发指南与实例(Linux系统方式)_第48张图片
如果想重新编译,请make clean
如果遇到困难,欢迎留言,或联系邮箱:[email protected]。谢谢!

你可能感兴趣的:(PYNQ)