• ADC测试杂谈二:matlab操作串口向FPGA发信



    前言

     何以解忧,唯有串口。

     相关文章:

    ADC测试杂谈一:配置基于matlab+quartus的测试环境

     之前提到,FPGA的JTAG相比MCU的UART,读取数据的速度更快。但是matlab似乎只能通过JTAG收信,而不能通过JTAG向FPGA发信。为了便于通过FPGA向芯片写一些配置信息,我们采用UART串口来向FPGA发送信息。


    一、串口的Verilog简易实现

     UART协议的基本原理是接收端通过一个16倍速的高频时钟对发送端的数据进行过采样,当检测到一个起始码后,就开始接收8位数据。Verilog代码如下:

    //Author: Jiao
    //Date: 2017
    //clk is 50e6 clk50.
    module newart(
    	input dat,
    	input clk,
    	output ex0,
    	output reg [7:0]bufout);
    	
    	reg ck;
    	reg [7:0]buftmp;
    	reg [7:0]count;
    	reg enout;
    	reg enout2;
    	reg enout3;
    	wire ck1;
    	wire rst0;
    	reg rst1;
    	reg [8:0]count1;//325 freq divide;
    	reg [7:0]count2;//180 times,make the ck high;
    	wire dat0;
    	
    	assign rst0=enout2;
    	
    	always@(posedge clk)
    	begin
    		count1<=count1+1;
    		count2<=count2+1;
    		if(count1==0)begin
    			ck<=0;
    			count2<=0;
    			end
    		else begin
    			if(count1==324) count1<=0;
    			if(count2==180) begin ck<=1;count2<=0;end
    			end
    	end
    	
    	always@(negedge rst0 or negedge dat0)//it seems that it is a edge detect,however it is a level driven.
    	begin
    		if(~rst0)enout<=0;
    		else enout<=1;
    	end
    	
    	assign ck1=ck&enout;
    	assign dat0=enout|dat;
    	
    	always@(posedge ck1 or negedge rst1)
    	begin
    		if(~rst1)begin enout2<=1;count<=0;end
    		else begin
    				count<=count+1;
    				case(count)
    				24:buftmp[0]<=dat;
    				40:buftmp[1]<=dat;
    				56:buftmp[2]<=dat;
    				72:buftmp[3]<=dat;
    				88:buftmp[4]<=dat;
    				104:buftmp[5]<=dat;
    				120:buftmp[6]<=dat;
    				136:buftmp[7]<=dat;//the above get 8 bit;
    				152:begin enout2<=0;bufout<=buftmp;end
    				endcase
    			end
    	end
    	
    	always@(posedge ck)
    	begin
    		enout3<=enout2;
    		rst1<=enout3;
    	end
    	
    	assign ex0=~rst1;
    	
    	endmodule
    
    • 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

     这里主要的输入端口是clk(全局的50MHz时钟)和dat(串口输入引脚),对应的波特率为9600,这也是matlab默认的通信速度。

     由于在ADC测试的实际场景下,PC端的上位机并不需要通过串口从FPGA读取数据,所以没有定义上行端口。

    二、matlab对串口的操作

     matlab对串口的操作比较简单,假设当前可用串口为COM8,则代码如下:

        serial_port=serial('COM8');
        fopen(serial_port);
    	fwrite(serial_port,int8(43));
    	fclose(serial_port);
    	delete(serial_port);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    该代码会打开COM8,发送字符’+'(53的ASCII字符是+)后,关闭对串口的占用。

    三、基于UART的寄存器

     UART最多能存8bit数据,假如我需要向FPGA发送更多数据呢?

     以ti的ADS7886测试为例,这是1个1MHz、12位、串行接口的ADC,我可能需要配置ADC的工作速度(1MHz/500KHz/200KHz/100KHz),可能需要读取ADC数据的时钟速度(20MHz/10MHz/5MHz/2MHz/1MHz)等等,所以,需要通过UART串口向FPGA发送更多位的信息。

     借鉴SPI协议中的地址,(参考verilog实现SPI从机 ),可以在UART接口的基础上封装一个类似的协议:规定发送字符1为地址,再发送字符2为该地址的校验码,发送字符3为数据,发送字符4为数据的校验码,发送字符5为中止码。通过这种方式,理论上可以向FPGA传送256*8bit数据。

     Verilog代码如下:

    //Author: Jiao
    //Date: 2020.
    //外部连发5次uart,前四次分别发送地址、地址的校验、数据、数据的校验,第五次发送的空格用以产生一个采样时钟
    `define reglength 42
    module uart2reg(
    	clk50,rst,uart_in,
    	data0,data1,data2,data3,data4,data5,data6,data7,data8,data9,data10,
    							data11,data12,data13,data14,data15,data16,data17,data18,data19,
    							data20,data21,data22,data23,data24,data25,data26,data27,data28,
    							data29,data30,data31,data32,data33,data34,data35,data36,data37,
    							data38,data39,data40,data41,data42);
    input clk50,rst,uart_in;
    output wire [7:0]data0,data1,data2,data3,data4,data5,data6,data7,data8,data9,data10,
    							data11,data12,data13,data14,data15,data16,data17,data18,data19,
    							data20,data21,data22,data23,data24,data25,data26,data27,data28,
    							data29,data30,data31,data32,data33,data34,data35,data36,data37,
    							data38,data39,data40,data41,data42;
    
    integer i;	
    (* noprune *) reg [7:0]reg_out[`reglength:0];//理论上这里可以输出最高256个8位寄存器
    wire data_samp;
    wire [7:0]data_reg0;
    newart u1(uart_in,clk50,data_samp,data_reg0);
    
    reg [7:0]shift1,shift2,shift3,shift4;
    always@(posedge data_samp or negedge rst)
    begin
    	if(~rst)begin
    		shift1<=0;//data_verify
    		shift2<=0;//data
    		shift3<=0;//addr_verify
    		shift4<=0;//addr
    		for(i=0;i<=`reglength;i=i+1)
    			reg_out[i]<=0;
    	end
    	else begin
    		if((shift3==addr_verify(shift4))&&(shift1==addr_verify(shift2)))begin//满足校验条件,清零并采样
    			reg_out[shift4]=shift2;
    			shift1<=0;
    			shift2<=0;
    			shift3<=0;
    			shift4<=0;
    		end
    		else begin
    			shift4<=shift3;
    			shift3<=shift2;
    			shift2<=shift1;
    			shift1<=data_reg0;
    		end
    	end
    end
    
    assign data0=reg_out[0];
    assign data1=reg_out[1];
    assign data2=reg_out[2];
    assign data3=reg_out[3];
    assign data4=reg_out[4];
    assign data5=reg_out[5];
    assign data6=reg_out[6];
    assign data7=reg_out[7];
    assign data8=reg_out[8];
    assign data9=reg_out[9];
    assign data10=reg_out[10];
    assign data11=reg_out[11];
    assign data12=reg_out[12];
    assign data13=reg_out[13];
    assign data14=reg_out[14];
    assign data15=reg_out[15];
    assign data16=reg_out[16];
    assign data17=reg_out[17];
    assign data18=reg_out[18];
    assign data19=reg_out[19];
    assign data20=reg_out[20];
    assign data21=reg_out[21];
    assign data22=reg_out[22];
    assign data23=reg_out[23];
    assign data24=reg_out[24];
    assign data25=reg_out[25];
    assign data26=reg_out[26];
    assign data27=reg_out[27];
    assign data28=reg_out[28];
    assign data29=reg_out[29];
    assign data30=reg_out[30];
    assign data31=reg_out[31];
    assign data32=reg_out[32];
    assign data33=reg_out[33];
    assign data34=reg_out[34];
    assign data35=reg_out[35];
    assign data36=reg_out[36];
    assign data37=reg_out[37];
    assign data38=reg_out[38];
    assign data39=reg_out[39];
    assign data40=reg_out[40];
    assign data41=reg_out[41];
    assign data42=reg_out[42];
    
    function [7:0]addr_verify;
    input [7:0]addr;
    begin
    addr_verify[7]=0^addr[6]^addr[5]^addr[4];   
    addr_verify[6]=0^addr[5]^addr[4]^addr[3]; 
    addr_verify[5]=1^addr[7]^addr[4]^addr[3]^addr[2];
    addr_verify[4]=1^addr[6]^addr[3]^addr[2]^addr[1];
    addr_verify[3]=0^addr[7]^addr[5]^addr[2]^addr[1]^addr[0];
    addr_verify[2]=1^addr[5]^addr[1]^addr[0];
    addr_verify[1]=1^addr[7]^addr[6]^addr[5]^addr[0];   
    addr_verify[0]=0;
    end
    endfunction
    
    endmodule
    
    • 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

     MATLAB端对fwrite进行了一点封装,代码如下:

    function uart_reg_write(serial_port,addr_uart,data_reg)
    % 参数1是实例化的串口对象
    % 参数2是地址,参数3是数据
    addr_verify=bit8verify(addr_uart);
    data_verify=bit8verify(data_reg);
    space=32;
    
    fwrite(serial_port,uint8(addr_uart));
    fwrite(serial_port,uint8(addr_verify));
    fwrite(serial_port,uint8(data_reg));
    fwrite(serial_port,uint8(data_verify));
    fwrite(serial_port,uint8(space));
    
    function resu=bit8verify(data)
    addr=zeros(8,1);
    addr_verify=zeros(8,1);
    dstr=dec2bin(data,8);
    weight=[1 2 4 8 16 32 64 128];
    for i=1:8
        addr(9-i)=str2num(dstr(i));
    end
    addr_verify(8)=xor(0,xor(addr(7),xor(addr(6),addr(5))));   
    addr_verify(7)=xor(0,xor(addr(6),xor(addr(5),addr(4)))); 
    addr_verify(6)=xor(1,xor(addr(8),xor(addr(5),xor(addr(4),addr(3)))));
    addr_verify(5)=xor(1,xor(addr(7),xor(addr(4),xor(addr(3),addr(2)))));
    addr_verify(4)=xor(0,xor(addr(8),xor(addr(6),xor(addr(3),xor(addr(2),addr(1))))));
    addr_verify(3)=xor(1,xor(addr(6),xor(addr(2),addr(1))));
    addr_verify(2)=xor(1,xor(addr(8),xor(addr(7),xor(addr(6),addr(1)))));
    addr_verify(1)=0;
    resu=weight*addr_verify;
    
    • 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

    四、matlab串口操作技巧

     串口调用的过程中存在两个主要的问题。

     一是串口号的变化。同样一根串口线,插到电脑的A孔,被识别为COM8,插到B孔被识别为COM9;今天被识别为COM8,明天被识别为COM9。总之,号码是不固定的。

     二是串口被占用。正常的串口操作过程是,serial声明串口,fopen打开串口,fwrite写串口,fclose+delete释放串口。但是,有可能串口被某些异常进程占用,又或者测试者的代码中途报错退出而没有释放串口,导致该串口不能被再次调用。

     针对串口号变化及串口被占用的问题,采用如下代码来自动识别并打开当前串口:

    serial_resu=IdentifySerialComs; %获取可用串口
    judge_serial=double(isempty(serial_resu));
    if(judge_serial==0)
    % 返回值不是空的,说明当前有串口
        serial_num=num2str(cell2mat(serial_resu(2)));
        com_serial=['COM' serial_num];
        msg6=['Serial is at ',com_serial];
        serial_port=serial(com_serial);
        fopen_ser(serial_port);
    	% 该函数功能为:
    	% 如果被占用,先释放所有串口再fopen指定串口;
    	% 如果未被占用,直接fopen指定串口
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    % Author: Jiao 
    % Date: 2022.
    % 为了处理串口的异常,先检测所有的串口状态是否均为closed,是则继续fopen;
    % 否则先fclose(instrfind)
    function fopen_ser(serial_port)
    
    serial_detect=instrfind();
    serial_detect=serial_detect.Status;
    device_len=size(serial_detect);
    busy_en=0;
    for i=1:device_len
        if strcmp(serial_detect(i),'open')==1
            busy_en=1;
            break;
        end
    end
    if(busy_en)
        fclose(instrfind);
    end
    fopen(serial_port);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    function devices = IdentifySerialComs()
    
    devices = [];
    
    Skey = 'HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM';
    [~, list] = dos(['REG QUERY ' Skey]);
    if(max(size(list))>1)
        if ischar(list) && strcmp('ERROR',list(1:5))  %% strcmp 两个字符串相同返回1
            disp('Error: EnumSerialComs - No SERIALCOMM registry entry')
            return;
        end
    else
        return;
    end
    list = strread(list,'%s','delimiter',' '); %#ok<FPARK> requires strread()
    coms = 0;
    for i = 1:numel(list)  %%numel 返回元素个数
        if strcmp(list{i}(1:3),'COM')
            if ~iscell(coms)
                coms = list(i);
            else
                coms{end+1} = list{i}; %#ok<AGROW> Loop size is always small
            end
        end
    end
    out = 0;
    outK = 0;
    for j=1:2
        switch j
            case 1
                key = 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\';
            case 2
                key = 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\FTDIBUS\';
        end
        [~, vals] = dos(['REG QUERY ' key ' /s /f "FriendlyName" /t "REG_SZ"']);
        if ischar(vals) && strcmp('ERROR',vals(1:5))
            disp('Error: EnumSerialComs - No Enumerated USB registry entry')
            return;
        end
        vals = textscan(vals,'%s','delimiter','\t');
        vals = cat(1,vals{:});
        for i = 1:numel(vals)
            if strcmp(vals{i}(1:min(12,end)),'FriendlyName')
                if ~iscell(out)
                    out = vals(i);
                else
                    out{end+1} = vals{i}; %#ok<AGROW> Loop size is always small
                end
                if ~iscell(outK)
                    outK = vals(i-1);
                else
                    outK{end+1} = vals{i-1}; %#ok<AGROW> Loop size is always small
                end
            end
        end
    end
    
    i_dev=1;Sservices=[];
    for i = 1:numel(coms)
        match = strfind(out,[coms{i},')']);
        ind = 0;
        for j = 1:numel(match)
            if ~isempty(match{j})
                ind = j;
                [~, sers] = dos(['REG QUERY "' outK{ind} '" /f "Service" /t "REG_SZ"']);
                sers = textscan(sers,'%s','delimiter','\t');
                sers = cat(1,sers{:});
                if (numel(sers)>1)
                    sers=strread(sers{2},'%s','delimiter',' ');
                    Sservices{i_dev} = sers{3};
                    i_dev=i_dev+1;
                end
            end
        end
    end
    Sservices=unique(Sservices);
    
    i_dev=1;
    for ss=1:numel(Sservices)
        key = ['HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\' Sservices{ss} '\Enum'];
        [~, vals] = dos(['REG QUERY ' key ' /f "Count"']);
        if ischar(vals) && strcmp('ERROR',vals(1:5))
                    disp('Error: EnumSerialComs - No Enumerated services USB registry entry')
                    return
        end
        vals = textscan(vals,'%s','delimiter','\t');
        vals = cat(1,vals{:});
    
        if (numel(vals)>1)
            vals=strread(vals{2},'%s','delimiter',' ');
            Count=hex2dec(vals{3}(3:end));
            if Count>0
                [~, vals] = dos(['REG QUERY ' key]);
                vals = textscan(vals,'%s','delimiter','\t');
                vals = cat(1,vals{:});
                out=0;
                j=0;
                for i = 1:numel(vals)
                    Enums=strread(vals{i},'%s','delimiter',' ');
                    try nums=hex2dec(Enums{1});
                    catch
                        nums=-1;
                    end
                    if(nums==j)
                        out=['HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\' Enums{3}];
                        [~, listC] = dos(['REG QUERY "' out '" /s /f "PortName" /t "REG_SZ"']);
                        listC = textscan(listC,'%s','delimiter','\t');
                        listC = cat(1,listC{:});
                        if (numel(listC)>1)
                            listC=strread(listC{2},'%s','delimiter',' ');
                            for i = 1:numel(coms)
                                if strcmp(listC{3},coms{i})
                                    [~, NameF] = dos(['REG QUERY "' out '" /s /f "FriendlyName" /t "REG_SZ"']);
                                    NameF = textscan(NameF,'%s','delimiter','\t');
                                    NameF = cat(1,NameF{:});
                                    com = str2double(coms{i}(4:end));
                                    if com > 9
                                        length = 8;
                                    else
                                        length = 7;
                                    end
                                    devices{i_dev,1} = NameF{2}(27:end-length); %#ok<AGROW>
                                    devices{i_dev,2} = com; %#ok<AGROW> Loop size is always small
                                    i_dev=i_dev+1;
                                end
                            end
                        end
                        j=j+1;
                    end
                end
            end
        end
    end
    
    end
    
    • 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
  • 相关阅读:
    Day12 | 每天五道题
    Redisson集成SpringBoot
    pytorch faster_rcnn转为onnx格式
    Git的远程仓库
    Cookie、Session、Token、JWT详解
    12、JAVA入门——二维数组
    记录一次循环引用的问题
    Python 爬虫零基础:探索网络数据的神秘世界
    QT QWT配置环境和运行
    数据结构与算法——串
  • 原文地址:https://blog.csdn.net/jiaozihao53/article/details/128055071