ZYNQ实验---IQ调制实现SSB PART2

一、前言

  本文实验在ZYNQ实验—IQ调制实现SSB PART1的基础上进行优化完善。

下图为IQ调制实现SSB PART1中设想实现设计框图
ZYNQ实验---IQ调制实现SSB PART2_第1张图片
ZYNQ实验---IQ调制实现SSB PART2_第2张图片
该图设计存在的几个问题:

  • PC-PS的UDP传输存在丢包
  • 中断控制发包实际不适合流数据的传输
  • 采用的BRAM模块可以存储的空间较小,PC到PL的时间相对较长,很容易出现RAM读空。

针对以上几个问题提出的解决方案

  • 设计对速率和实时性要求不高,UDP在低速情况下丢包率低。
  • PC数据如果不缓存直接送到PL中时间过长。ZYNQ带4Gbit的DDR选择将数据先完整的存在DDR中再进行PS到PL的传输。
  • 输出的数据是一种流数据形式,使用半空读写方式持续读写FIFO即可保持数据的连续性。

新的IQ调制实现SSB设计框图
ZYNQ实验---IQ调制实现SSB PART2_第3张图片

二、实验准备

实验平台与工具:ZYNQ7020,HackRF One,AN108 ADDA板,Matlab 2021b,Vivado 2018

2.1 IQ数据生成

使用matlab生成IQ基带数据,不同形式的基带数据决定了我们的输出调制方式,本实验先用正弦信号做测试。

fs_au = 32E3;         % 采样频率
f = 2E3;              % 正弦信号测试频率
ts=128;				  % 采样时间,秒为单位
t = 0:1/fs_au:ts-1/fs_au;  % 生成ts秒的时间序列,步进为1/fs_au

% 定义文件路径和文件名
filename = 'nengcd.mp3'; %测试音频
% 读取MP3文件
[y, Fs] = audioread(filename);
% 转换采样率
y_resampled = resample(y, fs_au, Fs);
% 截取ts秒数据
start_time = 1; % 从缓存结束开始
end_time = ts*fs_au; % 截取ts秒
y_s = y_resampled(start_time : end_time);

% % 生成正弦信号
% x = sin(2*pi*f*t);
% y_hilbert=fi(hilbert(x), 1, 16); %定点化数据

% 进行希尔伯特变换生成IQ数据
y_hilbert=fi(hilbert(y_s), 1, 16); %定点化数据

y_real=real(y_hilbert);
y_imag=imag(y_hilbert);

% 绘制采样信号
plot(t, y_real);
xlabel('时间(秒)');
ylabel('幅度');
ylim([-2 2]);
title('原始信号');
grid on;

% IQ数据保存 保存为16进制格式,4个字符表示一个数据
% 这样的数据格式虽然增加了数据量但是便于输出观察,实际2个字节表示16bit即可
filename = 'music_real.txt';
% 打开文件进行写入
fileID = fopen(filename, 'w');
% 将定点数以格式化的方式写入文件
fprintf(fileID, '%s\n', y_real.hex);
% 关闭文件
fclose(fileID);

% 文件名
filename = 'music_imag.txt';
% 打开文件进行写入
fileID = fopen(filename, 'w');
% 将定点数以格式化的方式写入文件
fprintf(fileID, '%s\n', y_imag.hex);
% 关闭文件
fclose(fileID);

2.2 Simulink 仿真及Verliog代码生成

参考文章:
Simulink HDL–如何生成Verliog代码
IQ调制实现SSB PART1

2.3 C#设计PC端软件

在以往的工程中使用C#设计过简单的软件,实验中也设计了一个调试软件。

  1. 红色区域为串口区域,可以接收PS端的串口信息
  2. 黄色区域为网络区域,读取matlab生成的数据文件通过UDP发送至DDR
    UDP 通信协议帧结构
    数据报文结构 0xAA 1byte类型 2bytes数据长度 2bytes分片数 Lbytes数据 1byte和校验
    指令报文结构 0xAA 1byte类型 2bytes指令长度 2bytes指令 1byte和校验

ZYNQ实验---IQ调制实现SSB PART2_第4张图片

2.3 Vivado – PL与PS设计

ZYNQ的设计如图所示
连接关系:PS->AXI Stream FIFO->AXI Stream Data FIFO->上变频和调制模块->DAC输出
ZYNQ实验---IQ调制实现SSB PART2_第5张图片
说明:

  1. AXI Stream FIFO设置半空读写,AXI Stream Data FIFO做缓冲作用。ZYNQ实验 FIFO读写实验(如何平衡跨时钟域的读写)
    ZYNQ实验---IQ调制实现SSB PART2_第6张图片
    PS端对AXI Stream FIFO的半空读写示例程序
//初始化FIFO
DDR_rd(0,4096,IQchioce);
Status = FIFOTxSend(&FifoInstance, FIFOSourceBuffer);//FIFOTxSend是FIFO示例中的发送函数
while(1)
{
	//UDP是按512个数据发送到DDR中的,因此读取2048是从DDR中读4组数据
	for(i=0;i<cmd_picenum;i+=4)
	{
		 // 半空状态读取
		Status = XLlFifo_Status(&FifoInstance);
		Halfempty= Status & 0x00200000;
		if(Halfempty)
		{
			//DDR读取数据,幅值到FIFOSourceBuffer
			  DDR_rd(0,2048,IQchioce);
			//发送数据至FIFO。循环写入相同的2048个数据
			  XLlFifo_IntClear(&FifoInstance,0xffffffff);
			  Status = FIFOTxSend2(&FifoInstance, FIFOSourceBuffer);//FIFOTxSend2只发送2048个数据的
			  if (Status != XST_SUCCESS){
			  xil_printf("Transmisson of Data failed\n\r");
			  return XST_FAILURE;}
			  memset(FIFOSourceBuffer2,0,2048);
		}
		else
		{
			  i=i-4;
		}
	}
}
  1. Data_interception 模块
    该模块将AXI Stream类型的数据转化为上变频模块可以使用的数据格式。实验中DDR的32bit数据的高16bit存I数据,低16bit存Q数据,因此将DDR中读取到的数据分为两路16bit数据送入上变频模块。
    ZYNQ实验---IQ调制实现SSB PART2_第7张图片
  2. PS端代码问题
    IQ调制实现SSB PART1实验中是不涉及PS端设计的,数据存在RAM中进行循环输出。在本实验中PS成为了很关键的沟通PC和FPGA的部分,PS端功能主要涉及串口收发,UDP收发,DDR读写,AXI Stream FIFO读写,某些功能在我的ZYNQ学习专栏中可以找到,网上也有很多实现教程。

三、 实验结果

3.1 输出2kHz正弦信号

ILA 观察信号

ZYNQ实验---IQ调制实现SSB PART2_第8张图片
在这里插入图片描述

示波器观察信号

2kHz在1MHz上经过SSB调制,输出1.002MHz信号
ZYNQ实验---IQ调制实现SSB PART2_第9张图片

3.2 输出音频信号

HackRF One接收SSB信号,解调出音频信号,最终的效果一般。结果不好与收发端设备有关,整个系统比较简易。
ZYNQ实验---IQ调制实现SSB PART2_第10张图片

总结

  整个工程设计虽然简单,但也算我设计的第一个完整的系统。实验数据从PC端一直到DAC输出的过程在框图中看着简单,但在实际调试中遇到了很多的bug和曲折,matlab生成的代码也是存在一定的问题对FPGA设计不熟练的话很容易遇到很多难以发现和解决的问题。这个东西也是抽空做一点慢慢搭起来的,虽然在某些问题上花费了很大的精力但是我也学到了很多东西,各种开发调试的经验,工具平台的了解使用,包括现在对FPGA和嵌入式也有了新的认识等等。

main函数代码

int main()
{
	int Status;
	int Halffull;//半满标记

	XUartPs_Config *Config;
	u16 revnum=0;

	u16 i,j=0;
	u8 state = UART_RXCHECK ;

	ReceivedBufferPtr = ReceivedBuffer ;

	ReceivedFlag = 0 ;
	ReceivedByteNum = 0 ;

	/* Uart init*/
	Config = XUartPs_LookupConfig(UART_DEVICE_ID);
	if (NULL == Config) {
		return XST_FAILURE;
	}
	Status = XUartPs_CfgInitialize(&Uart_PS, Config, Config->BaseAddress);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}
	/* Use Normal mode. */
	XUartPs_SetOperMode(&Uart_PS, XUARTPS_OPER_MODE_NORMAL);
	/* Set uart mode Baud Rate 115200, 8bits, no parity, 1 stop bit */
	XUartPs_SetDataFormat(&Uart_PS, &UartFormat) ;
	/*Set receiver FIFO interrupt trigger level, here set to 1*/
	XUartPs_SetFifoThreshold(&Uart_PS,1) ;
	/* Enable the receive FIFO trigger level interrupt and empty interrupt for the device */
	XUartPs_SetInterruptMask(&Uart_PS,XUARTPS_IXR_RXOVR|XUARTPS_IXR_RXEMPTY);

	SetupInterruptSystem(&IntcInstPtr, &Uart_PS, UART_INT_IRQ_ID);

	/* UDP init*/
	struct netif *netif, server_netif;
	ip_addr_t ipaddr, netmask, gw;

	/*  开发板MAC地址  */
	unsigned char mac_ethernet_address [] ={0x00, 0x0a, 0x35, 0x00, 0x01, 0x02};
	/*  开启中断系统  */
	Init_Intr_System(&IntcInstPtr);
	Setup_Intr_Exception(&IntcInstPtr);
	netif = &server_netif;
	IP4_ADDR(&ipaddr,  192, 168,  1, 30);
	IP4_ADDR(&netmask, 255, 255, 255, 0);
	IP4_ADDR(&gw,      192, 168,  1,  1);

	lwip_init();   //初始化lwIP库

	/* 添加网络接口并将其设置为默认接口 */
	if (!xemac_add(netif, &ipaddr, &netmask, &gw, mac_ethernet_address, XPAR_XEMACPS_0_BASEADDR)) {
		xil_printf("Error adding N/W interface\r\n");
		return -1;
	}

	netif_set_default(netif);
	netif_set_up(netif);        //启动网络
	user_udp_init();            //初始化UDP

	/* FIFO init*/
	Status = XLlFifoPollingExample(&FifoInstance, FIFO_DEV_ID);//初始化FIFO

	while (1)
	{
		/*  将MAC队列中的包传输的LwIP/IP栈中   */
		xemacif_input(netif);
		//revnum=UartRevdata(); //串口通信
		revnum=udp_ReadData(UDPrecvBuffer);
		memcpy(PC2PScmd,UDPrecvBuffer,revnum);
		if(revnum>=6)
		{
			switch(PC2PScmd[1])
			{
			  case 0x00: //DDR数据输出至FIFO
			  {
			  	if(revnum==7 && RX_CheckSum(PC2PScmd,7)==0)
			  	{
			  		cmd_picenum=(u16)PC2PScmd[4]+((u16)PC2PScmd[5]<<8);
			  		xil_printf("cmd_picenum=%x\n\r",cmd_picenum);
			  		DDR_rdIQ(0,4096,IQchioce);
			  		Status = FIFOTxSend(&FifoInstance, FIFOSourceBuffer); //填满FIFO
			  		while(1) //循环输出数据
			  		{
			  			for(i=0;i<cmd_picenum;i+=4)
			  			{
			  				// FIFO_sent
			  				Status = XLlFifo_Status(&FifoInstance);
			  				//xil_printf("Status=%x\n\r",Status);
			  			    Halffull= Status & 0x00200000;
			  				if(Halffull)
			  				{
			  					DDR_rdIQ(i*512,2048,IQchioce);
			  					/* Transmit the Data Stream */
			  					XLlFifo_IntClear(&FifoInstance,0xffffffff);
			  					Status = FIFOTxSend2(&FifoInstance, FIFOSourceBuffer2);//半满FIFO
			  					if (Status != XST_SUCCESS){
			  					xil_printf("Transmisson of Data failed\n\r");
			  					return XST_FAILURE;}
			  				}
			  				else
			  				{
			  					i=i-4;
			  				}
			  			}
			  		}
					memcpy(UDPsentBuffer,PC2PScmd,7);
					memset(PC2PScmd,0,7);
					udp_SentData(7,UDPsentBuffer);
					countnum=0;
			  	}
			  	break;
			  }
			  case 0x01: //写入DDR
			  {
			  	cmd_datalen=PC2PScmd[2]+(PC2PScmd[3]<<8);
			  	cmd_picenum=PC2PScmd[4]+(PC2PScmd[5]<<8);
			  	if(revnum==1031 && RX_CheckSum(PC2PScmd,1031)==0) //1031 为整个数据帧的长度,数据一次传1024字节
			  	{
			  		DDR_wr(cmd_picenum*512,cmd_datalen,IQchioce); //512*16bits=1024*8bits,16bit才是一个完整的数据
			  		memcpy(UDPsentBuffer,PC2PScmd,3);
			  		UDPsentBuffer[3]=0x01;
			  		udp_SentData(4,UDPsentBuffer);
			  		printf("picenum=%d\n",cmd_picenum);
			  		memset(PC2PScmd,0,1031);
			  		countnum=0;
			  	}
			  		break;
			  }
			  case 0x02: //写入IQ选择,分别存在DDR的不同区域
			  {
				if(revnum==7 && RX_CheckSum(PC2PScmd,7)==0)
				{
					IQchioce=PC2PScmd[4];
					memset(PC2PScmd,0,7);
				}
				countnum=0;
			  	break;
			  }
			  default:
			  {
			  	countnum=0;
			  	break;
			  }
		}
	  }
	}
	return 0;}

你可能感兴趣的:(ZYNQ学习笔记,单片机,嵌入式硬件)