玩嗨 OpenHarmony:基于 OpenHarmony 的简易示波器(下篇)

玩嗨 OpenHarmony:基于 OpenHarmony 的简易示波器(下篇)

原文来自HarmonyOS技术社区的老渔翁文章《基于OpenHarmony操作系统的简易示波器开发心得》,原文链接请点击文章结尾的“阅读原文”

基于OpenHarmony的简易示波器(上篇)

上篇,我们介绍了用OpenHarmony制作简易示波器的一些硬件,包括: 小凌派-RK2206开发板、模数转换芯片ADS1256、Sitronix ST7789V LCD显示器,本篇将继续为大家介绍这个简易示波器到底是如何实现检测峰值、频率,然后进行波形显示的。

LCD显示

LCD型号为ST7789V,采用SPI通信方式,数据传输协议如下:

4-Line Serial Interface => 16-bit/pixel(RGB 5-6-5-bit input),65K-Color,3Ah="05h"

数据传输时序图如下:

LCD使用的是SPI(编者注:串行外设接口Serial Peripheral Interface的缩写)协议,SPI_CS与GPIO0_PC0相连接,SPI_CLK与GPIO0_PC1相连接,SPI_MOSI与GPIO0_PC2相连接,RES与GPIO0_PC3相连接,DC与GPIO0_PC6相连接。

初始化代码分析

本程序可使用SPI或GPIO模拟SPI与LCD进行通信。以下我们主要对SPI与LCD进行通信进行代码分析。这部分代码为SPI初始化的代码。首先用 SpiIoInit() 函数将GPIO0_PC0复用为SPI0_CS0n_M1,GPIO0_PC1复用为SPI0_CLK_M1,GPIO0_PC2复用为SPI0_MOSI_M1。最后调用 LzI2cInit()函数初始化SPI0端口。

if (SpiIoInit(m_spiBus) != LZ_HARDWARE_SUCCESS) {

    printf("%s, %d: SpiIoInit failed!\n", __FILE__, __LINE__);

    return __LINE__;

}

if (LzSpiInit(LCD_SPI_BUS, m_spiConf) != LZ_HARDWARE_SUCCESS) {

    printf("%s, %d: LzSpiInit failed!\n", __FILE__, __LINE__);

    return __LINE__;

}

这部分代码为GPIO初始化的代码。首先用 LzGpioInit()函数将GPIO0_PC3初始化为GPIO引脚,然后用 LzGpioSetDir()将引脚设置为输出模式,最后调用 LzGpioSetVal()输出低电平。

/* 初始化GPIO0_C3 */

LzGpioInit(LCD_PIN_RES);

LzGpioSetDir(LCD_PIN_RES, LZGPIO_DIR_OUT);

LzGpioSetVal(LCD_PIN_RES, LZGPIO_LEVEL_HIGH);



/* 初始化GPIO0_C6 */

LzGpioInit(LCD_PIN_DC);

LzGpioSetDir(LCD_PIN_DC, LZGPIO_DIR_OUT);

LzGpioSetVal(LCD_PIN_DC, LZGPIO_LEVEL_LOW);

通过SPI往LCD屏幕写数据操作

这部分的代码是向LCD写入数据,具体如下:

LzSpiWrite(LCD_SPI_BUS, 0, &dat, 1);

配置ST7789V启动

/* 重启lcd */

LCD_RES_Clr();

LOS_Msleep(100);

LCD_RES_Set();

LOS_Msleep(100);

LOS_Msleep(500);

lcd_wr_reg(0x11);

/* 等待LCD 100ms */

LOS_Msleep(100);

/* 启动LCD配置,设置显示和颜色配置 */

lcd_wr_reg(0X36);

if (USE_HORIZONTAL == 0)

{

    lcd_wr_data8(0x00);

}

else if (USE_HORIZONTAL == 1)

{

    lcd_wr_data8(0xC0);

}

else if (USE_HORIZONTAL == 2)

{

    lcd_wr_data8(0x70);

}

else

{

    lcd_wr_data8(0xA0);

}

lcd_wr_reg(0X3A);

lcd_wr_data8(0X05);

/* ST7789S帧刷屏率设置 */

lcd_wr_reg(0xb2);

lcd_wr_data8(0x0c);

lcd_wr_data8(0x0c);

lcd_wr_data8(0x00);

lcd_wr_data8(0x33);

lcd_wr_data8(0x33);

lcd_wr_reg(0xb7);

lcd_wr_data8(0x35);

/* ST7789S电源设置 */

lcd_wr_reg(0xbb);

lcd_wr_data8(0x35);

lcd_wr_reg(0xc0);

lcd_wr_data8(0x2c);

lcd_wr_reg(0xc2);

lcd_wr_data8(0x01);

lcd_wr_reg(0xc3);

lcd_wr_data8(0x13);

lcd_wr_reg(0xc4);

lcd_wr_data8(0x20);

lcd_wr_reg(0xc6);

lcd_wr_data8(0x0f);

lcd_wr_reg(0xca);

lcd_wr_data8(0x0f);

lcd_wr_reg(0xc8);

lcd_wr_data8(0x08);

lcd_wr_reg(0x55);

lcd_wr_data8(0x90);

lcd_wr_reg(0xd0);

lcd_wr_data8(0xa4);

lcd_wr_data8(0xa1);

/* ST7789S gamma设置 */

lcd_wr_reg(0xe0);

lcd_wr_data8(0xd0);

lcd_wr_data8(0x00);

lcd_wr_data8(0x06);

lcd_wr_data8(0x09);

lcd_wr_data8(0x0b);

lcd_wr_data8(0x2a);

lcd_wr_data8(0x3c);

lcd_wr_data8(0x55);

lcd_wr_data8(0x4b);

lcd_wr_data8(0x08);

lcd_wr_data8(0x16);

lcd_wr_data8(0x14);

lcd_wr_data8(0x19);

lcd_wr_data8(0x20);

lcd_wr_reg(0xe1);

lcd_wr_data8(0xd0);

lcd_wr_data8(0x00);

lcd_wr_data8(0x06);

lcd_wr_data8(0x09);

lcd_wr_data8(0x0b);

lcd_wr_data8(0x29);

lcd_wr_data8(0x36);

lcd_wr_data8(0x54);

lcd_wr_data8(0x4b);

lcd_wr_data8(0x0d);

lcd_wr_data8(0x16);

lcd_wr_data8(0x14);

lcd_wr_data8(0x21);

lcd_wr_data8(0x20);

lcd_wr_reg(0x29);

这部分代码将ST7789V配置为 4-Line Serial Interface => 16-bit/pixel(RGB 5-6-5-bit input),65K-Color

编译调试

修改 vendor\lockzhiner\rk2206\sample 路径下 BUILD.gn 文件,指定 lcd_example 参与编译。

"./b0_lcd:lcd_example",

修改 device/lockzhiner/rk2206/sdk_liteos 路径下 Makefile 文件,添加 -llcd_example 参与编译。

hardware_LIBS = -lhal_iothardware -lhardware -llcd_example

运行结果

示例代码编译烧录代码后,按下开发板的RESET按键,通过串口助手查看日志,并请使用带有LCD屏幕显示如下:

************Lcd Example***********

************Lcd Example***********

波形显示

通过将采集的幅值进行计算,使最后的值在屏幕大小的范围内,进行描点画图。

void drawCurve( float rawValue,uint16_t color)  

{

    uint16_t x;

    int y;

    y = (int) rawValue/30+30;   //data processing code

    if(y<0 || y > 240)

    y = lastY;

    //这里之所以是120-rawValue/280,与屏幕的扫描方向有关,如果出现上下颠倒的情况,可以改成120 + 

    if(firstPoint)//如果是第一次画点,则无需连线,直接描点即可

    {

        LCD_DrawPoint(0,y,color);

        lastX=0;

        lastY=y;

        firstPoint=0;

    }

    else

    {

        x=lastX+2;

        if(x<320)  //不超过屏幕宽度

        {

            LCD_DrawLine(lastX,lastY,x,y,color);

            lastX=x;

            lastY=y;

        }

        else  //超出屏幕宽度,清屏,从第一个点开始绘制,实现动态更新效果

        {

            //LCD_Fill(0, 0, LCD_W, LCD_H, LCD_WHITE);//清屏//清屏,白色背景

            LCD_DrawPoint(0,y,color);

            lastX=0;

            lastY=y;

        }

  }

}

峰值检测

通过查找ad采集的数据内的最大值和最小值,然后相减即得峰峰值。

float Get_Vpp(float arr[])

{

        uint16_t i;

        float MAX=0,MIN=3500,Vpp=0; 

        for(i=0;i<Ns;i++)                    

        // 扫描ADC数组,获取最大值和最小值

        {

            if(arr[i]>MAX)          

                MAX=arr[i];

            if(arr[i]<MIN)

                MIN=arr[i];

        }

        Vpp=MAX-MIN;

        return Vpp;

}

频率检测

通过fft变换,FFT变换的数据需要两部分,实部和虚部,由于变换的是数据是AD采集的实数据,所以只需将采集的值存入实部,虚部存入零即可。通过变换将时域信号转换到频域,然后通过取模排序,然后计算即可得到频率。他的基本思想是把原始的 N 点序列,依次分解成一系列的短序列。充分利用 DFT 计算式中指数因子所具有的对称性质和周期性质,进而求出这些短序列相应的 DFT 并进行适当组合,达到删除重复计算,减少乘法运算和简化结构的目的。当N是素数时,可以将 DFT算转化为求循环卷积,从而更进一步减少乘法次数,提高速度。

1. FFT变换函数

void fft(const float real_in[], const float imag_in[], float real_out[], float imag_out[], const int n, int isign) {

    if (isign != 1 && isign != -1) {//isign=1,正变换;isign=-1,逆变换 

        return;

    }

    const int Lv = mylog(n, 2);//蝶形级数 

    int L;//蝶形运算级数,用于循环

    int N;//蝶形运算数据量,用于循环 

    int distance;//蝶形运算两节点间的距离,用于循环(distance=N/2) 

    int group;//蝶形运算的组数 

    float tmpr1, tmpi1, tmpr2, tmpi2;//临时变量

    int i, j, k;

    for (i = 0; i < n; i++) {//数位倒读 

        j = rev(i, Lv);

        real_out[j] = real_in[i];

        imag_out[j] = imag_in[i];

    }

    L = 1;

    distance = 1;

    N = 2;

    group = n >> 1;

    for (; L <= Lv; L++) {//蝶形循环 

        for (i = 0; i < group; i++) {//每级蝶形各组循环 

            for (k = 0; k < distance; k++) {//每组蝶形运算 

                float theta = -2 * PI * k / N * isign;//旋转因子,逆变换的角度与正变换相反 

                tmpr1 = real_out[N * i + k];

                tmpi1 = imag_out[N * i + k];//X(k)

                tmpr2 = mycos(theta) * real_out[N * i + k + distance] - mysin(theta) * imag_out[N * i + k + distance];

                tmpi2 = mycos(theta) * imag_out[N * i + k + distance] + mysin(theta) * real_out[N * i + k + distance];//WN(k)*X(k+N/2)

                real_out[N * i + k] = tmpr1 + tmpr2;

                imag_out[N * i + k] = tmpi1 + tmpi2;//X(k)=X(k)+WN(K)*X(k+N/2)

                real_out[N * i + k + distance] = tmpr1 - tmpr2;

                imag_out[N * i + k + distance] = tmpi1 - tmpi2;//X(k+N/2)=X(k)-WN(K)*X(k+N/2)

                if (isign == -1) {//逆变换结果需除以N,即除以Lv次2 

                    real_out[N * i + k] *= 0.5;

                    imag_out[N * i + k] *= 0.5;

                    real_out[N * i + k + distance] *= 0.5;

                    imag_out[N * i + k + distance] *= 0.5;

                }

            }

        }

        N <<= 1;

        distance <<= 1;

        group >>= 1;

    }

}

2. 取模运算函数

void PowerMag(void)

{

    uint16_t i=0;

    float Y,X,Mag;

    for (i=0; i < Ns/2; i++)

   {

       X =((float)y2r[i])/32768* Ns;

       Y = ((float)y2i[i])/32768* Ns;

       Mag = sqrt(X*X+ Y*Y)/Ns;         // 先平方和,再开方

       y2[i] = (uint32_t)(Mag*65536);           

   }

    y2[0] = y2[0]/2;      //直流

}


3. 峰值排序

然后将FFT变换的幅值进行排序,同时也对他们的下标进行了排序,以便后续的计算,即除了直流信号的第一个频率点即为改信号的频率。

void sorting (void)

{

    uint16_t i,j;

    uint32_t temp1;

    

    for(i=0;i<Ns/2;i++)                     //下标赋初值

    {

        xb[i]=i;

    }

    for(j=0;j<(Ns/2-1);j++)                 // 冒泡排序

    { 

        for(i=1;i<(Ns/2-j-1);i++)       //直流项不参与排序  从第二项开始              

        { 

            if(y2[i]<y2[i+1]) 

            {               

                temp1=y2[i];                //交换数据

                y2[i]=y2[i+1]; 

                y2[i+1]=temp1;



                temp1=xb[i];                            //交换下标

                xb[i]=xb[i+1]; 

                xb[i+1]=temp1; 

            }        

        }                                                               

    }

}

4. 计算频率

通过计算即可得到频率,采样点数将采样频率进行平分,通过排序取得的幅值最大的那个点的下标进行相乘即为频率,1.47为补偿系数,因为ADS1256采集数据后有延时,导致进行FFT变换后所对应的幅值最大点的下标前移,导致计算频率时候会偏小。

fre=Fs*xb[1]*1.47/Ns;

心得体会

通过OpenHarmony操作系统 + 小凌派-RK2206开发板进行项目开发,OpenHarmony的实时性好,稳定性高,瑞芯微RK2206芯片接口比较丰富,移植适配稳定性较好,整体开发进度比较顺利,开发的难度都集中在数据处理算法上。通过这一次简易示波器的应用开发,整体上对OpenHarmony和国产芯片开发还是蛮认可的,是一次不错的学习体验!

写在最后

我们最近正带着大家玩嗨OpenHarmony。如果你有好玩的东东,欢迎投稿,让我们一起嗨起来!有点子,有想法,有Demo,立刻联系我们:

合作邮箱:zzliang@atomsource.org