前言
本教程分三个部分
- 介绍使用官方镜像文件,使用Python进行开发的一个简单小例子,并且简单了解底层是如何运作的。
- 记录使用自制镜像文件,使用C/C++开发基于Linux的Pynq-Z2。完成一个将3.5毫米耳机输入口的音频采集,并输出到一个USB音频设备的任务。
- 自行编译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
类库。下文删除了注释,请自己打开文件查看。
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)