海思3516系列芯片SPI速率慢问题深入研究与优化(基于PL022 SPI 控制器)

2023-10-26

海思3516系列芯片SPI速率慢问题深入分析与优化(基于PL022 SPI 控制器)

我在某个海思主控的项目中需要使用SPI接口来驱动一块液晶屏,液晶屏主控为 st7789,分辨率 240x240,图像格式 RGB565。

查阅海思相关手册可知,Hi3516EV200 的 SPI 最高速率为 50MHz,理论上每秒钟可以发送 50M/8=6.25MB 数据。假设我需要在屏幕上以30fps的速率全屏实时显示摄像头的预览画面,每秒的数据量为 240*240*2*30=3456000B=3375KB=3.296MB ,假设 SPI 工作在阻塞模式,则 cpu 使用率为 3.296/6.25*100%=52.7%,看起来还不错。如果我想进一步降低cpu使用率还可以降低预览图分辨率,降低帧率,比如我可以按 240*180 20fps 的规格显示缩略图,那么cpu使用率就可以降到 240*180*2*20/1024/1024/6.25=26.4%

上面这些都是我在写代码前的理论分析,真实效果当然需要写程序简单测试一下。测试代码内容大致如下:使用原生linux提供的 SPI 接口,即/dev/spidevX.X,主程序中一个死循环没有sleep,使用不同的颜色不停地刷全屏,看一秒钟能达到多少次,也就是刷新率能达到多少帧。

实际测试结果,每秒大约5次!肉眼可见的慢!能很明显看到刷屏的时候是从上到下覆盖过来的!

这么慢的速率就算以240*180的分辨率来刷新预览画面也只能达到7、8帧的水平,何况cpu使用率是100%,其他业务也没办法正常运行,所以spi的速率必须优化。

第一步当然是用逻辑分析仪或示波器抓实际波形,观察是时钟信号没有达到50MHz,还是有其他地方浪费时间。对于逻辑分析仪而言,要考虑采样率是否足够,例如逻辑分析仪的采样率是100MHz,理论上可以采集50MHz的信号,实际采集50MHz的信号很可能采集不准;示波器也一样,示波器需要考虑的是带宽,一般入门级示波器都有100MHz带宽,问题不大。对了,别拿DIY的示波器来捣乱。

这张图就是我通过示波器抓到的海思 SPI 时钟线上的波形,问题非常明显,发送的两个字节之间间隔了 1.656us!开发过任意单片机的spi并实际抓过波形的朋友应该都知道,spi的时钟几乎是连续,正常情况下绝对不可能间隔这么长。
在这里插入图片描述

再检查一下spi的时钟有没有达到理论的50MHz,8个clk共计160ns,没问题,达到了理论速率。
在这里插入图片描述

题外话:为什么50MHz的方波在示波器上显示为正弦波?因为我这款示波器的带宽只有100MHz,50MHZ方波信号本身没有超过示波器带宽的上限,但是它的2n+1次谐波分量远远高于示波器带宽。所以想要勉强看到50MHz方波的波形,那么示波器至少要能采集到3次谐波信号,也就是150MHz的信号,这就需要一个200MHz带宽的示波器(我买不起!)

简单计算一下,发送一个字节需要 0.16+1.656=1.816us,也就是1秒钟可以发送 1s/1.816us=549450B=536.6KB 的数据,这个数据量甚至不足理论值的十分之一,换算下来确实1秒钟也只能刷5屏。所以接下来的目标就是找出到底是什么原因让发送的两个字节之间占用了足足1.6us。

首先我的发送代码中没有加任何延时函数,所以这1.6us的延时只能来自于linux内核spi驱动。

查看linux内核spi相关源码(提前安装好完整的海思开发环境,并下载对应版本linux内核源码,打海思linux源码patch)
linux-4.9.y\drivers\spi\spi-pl022.c
由源码可知海思的spi使用的是 ARM PrimeCell SSP (PL022) 控制器,这些驱动代码应该非常成熟了,不会存在这么明显的问题,大概看了看源码也没发现啥耗时的地方,之后专门以 ssp-pl022 和 delay 作为关键字google一番也没有发现类似的问题。思索了一下,我决定放弃修改内核驱动,首先内核代码很成熟,基本轮不到我这种小角色去debug,其次每次修改都要重新编译并烧录内核,太麻烦。所以我决定自己重写一个spi内核模块ko驱动。

非常幸运,在海思SDK的 drv.tgz\drv\extdrv\ssp-st7789 中正好有源码,连芯片型号都一样,基于这个代码做一些简单修改就能用。

经过我的一番修改,实际测试下来发现这个ko模块的性能比linux内核spi的性能有一点点提升,延时从1.656us降低到了1.6us以内,基本等于没有优化,这里就不放截图了。

这个代码就比较简单,读起来也不费劲,大概看了一下还是能找到一点优化的地方,注意看spi_write_XXbyte这几个函数,这几个函数内都有这样的代码

spi_enable();
ssp_writew(SSP_DR,spi_data);
ret = hi_spi_check_timeout();
if(ret != 0)
{
    printk("spi_send timeout\n");
}
spi_disable();

在前面的代码中设置过CS片选信号由spi使能信号控制,也就是说,这段代码每次写一个字节都要拉一次CS信号,效率比较低,我将 spi_enable 和 spi_disable 提出来后再次进行测试,速度确实有一定提升,但提升幅度仍然不大,只有大概几百ns的水平,优化效果仍然不理想。

这段代码只剩下两个函数了,ssp_writew 是写寄存器,根本不能优化,只能想办法优化 hi_spi_check_timeout,来看一下这个函数的实现

static int hi_spi_check_timeout(void)
{
    unsigned int value =  0;
    unsigned int tmp = 0;
    while (1)
    {
        ssp_readw(SSP_SR,value);
        if ((value & SPI_SR_TFE) && (!(value & SPI_SR_BSY)))
        {
            break;
        }

        if (tmp++ > MAX_WAIT)
        {
            printk("spi transfer wait timeout!\n");
            return -1;
        }
        udelay(1);
    }
    return 0;
}

逻辑非常简单,不停地读取发送FIFO空和SPI忙的标志位,延时1us继续读,直到发送完成且SPI空闲。看到这个 udelay(1) 你现在的想法肯定和我当时的想法一样:第一次读取发现寄存器没有置位,延时1us,第二次读取寄存器置位,退出循环,现在的发送间隔是1.6us,去掉这个延时尽快读取寄存器,应该直接能优化到0.6us以内。

想得美!实际测试去掉这个 udelay(1) 以后优化效果确实挺明显的,延时直接缩短到 1.2us 左右,但是这个延时还是太长了。

现在再来看这个函数,就剩一个读寄存器了,为了保证可靠传输,读标志位肯定不能去掉,这已经最简单了,还能怎么优化呢?我们重新梳理一下:去掉 udelay(1) 后间隔缩短到 1.2us 左右,说明这里循环读了很多次寄存器,不然怎么还有这么长的延时?那要不就加打印看看这里到底循环读取了几次?来来来,竞猜一下这里到底循环了几次?十次以内?百次以内?还是千次以内?答案是一次!没错只有一次!

1次这个答案可以说即在意料之外也在意料之中。
说它在意料之外是因为读写寄存器这种操作是非常快的,一般而言几个cpu时钟就能完成,但这里读一个寄存器却花费了 1.2us。
说它在意料之内是因为这些物理寄存器都是通过硬件置位的,以发送 FIFO 为空标志为例,当 SPI 发送完成瞬间它就会被硬件置位,这一点在任何一款单片机上都可以验证,实际操作一下就会发现类似的寄存器是瞬间被置位的。pl022 应该是非常成熟的 SPI 控制器,我觉得芯片设计人员不会范发送 FIFO 为空后很长时间才设置标志位这种低级错误。

接下赖重点思考这个问题:为什么 ssp_readw(SSP_SR,value) 这样一个简单的读寄存器操作要 1.2us 之久?
(目前我测试 ssp_writew 写一个寄存器大概在 100ns 以内,ssp_readw 读一个寄存器大概在 1us 左右)
这个问题无论是百度还是谷歌我找了很久都没有找到确切答案,如果有知道的大佬非常欢迎指导!!!不白嫖知识,私信发红包。
下面是我个人的推测,虽然是推测,但我觉得这就是正确答案,仅供参考:

linux 不同于单片机裸机程序那样可以直接访问寄存器,如果需要读写物理寄存器,在内核态使用 ioremap (在用户态使用 mmap)将一段物理地址映射到内核态(或用户态)的虚拟地址空间,再对映射后的地址进行读写。

来看一下实际读写寄存器的这两个接口,很简单就是直接读写某个地址处的数据(前提是这个地址必须经过映射)。

#define  ssp_readw(addr,ret)       (ret =(*(volatile unsigned int *)(addr)))
#define  ssp_writew(addr,value)    ((*(volatile unsigned int *)(addr)) = (value))

首先可以明确一点,读写寄存器的操作必然会经过MMU。
对于写寄存器来说,不需要考虑同步、脏数据等问题,MMU 应该是直接将这个数据写到物理地址了。
对于读寄存器来说,有 volatile 关键字的存在,这里的代码不会去优化,每次读取必须从物理地址进行读取,这里可能需要 cache 回写等操作导致导致读取的速度非常慢。

在 u-boot 下可以直接读写物理寄存器,应该不需要这么久,几个CLK就可以完成吧?这一点我没有验证过,有测试过的朋友欢迎分享。

总之,耗时的地方找到了,想办法优化吧。我这里有两种优化思路:

  1. 查阅手册可知,SPI的内部收发部分各有一个宽度 16bit、深度为 256 的 FIFO。我可以一次性写入不超过256字节的数据,然后使用 ssp_readw 不停地读取寄存器,直到发送 FIFO 为空。在50MHz时钟的条件下情况下 ssp_readw 的 1.2us 延时相当于发送了8字节左右,理论上来说如果发送的数据量大于8字节,这个 1.2us 延时等于没有。
  2. 每次发送1字节,加适当延时,保证发送相邻两字节之间的间隔尽量短。

目前我采用的就是第二种方法,第一种方法就留给大家验证并开发吧(其实就是我懒)。
来看一下我的现实代码:

void hi_spi_delay(void)
{
    volatile unsigned int tmp = 0;
    while (tmp++ < 30);
}

void spi_write_byte(unsigned char dat)
{
    unsigned short spi_data = 0;
    spi_data = dat;
    ssp_writew(SSP_DR, spi_data);
    hi_spi_delay();
}

这个延迟函数一定要根据编译器、CPU主频、SPI时钟频率等实际测量后进行调整。经过我的反复调整和测量,最终把循环计数设置为了30,来看一下示波器抓到的波形
在这里插入图片描述

间隔 65ns,也就是每字节耗时 225us,大约相当于 36MHz 的SPI时钟频率。

改成其他值行不行呢?这是我的测量结果

  • 30 可以保证在50MHz时钟下,每两字节之间间隔 65ns,
  • 32 可以保证在50MHz时钟下,每两字节之间间隔 80ns,
  • 35 可以保证在50MHz时钟下,每两字节之间间隔 100ns,
    但是,重点来了!循环计数小于29或不加延时的间隔是 60ns,似乎达到了某种限制,具体原因没找到,我担心有丢数据的风险,所以将循环计数设置为了30,这个值也正好是可以明显观察到延时函数有效的最小值,SPI 速率虽然没有真正达到理论值,但是对于目前的使用场景来说已经足够了。

前面说了,延时函数一定要根据实际情况进行修改,确保每字节之间有一定间隔。假设你换了海思的另外一款芯片,或者使用了其他基于 ssp-pl022 控制器的芯片,也遇到了类似的 SPI 速率低的问题,但手头没有示波器进行测量怎么办?假设你所使用的芯片提供了精确到10ns的延时函数,直接拿来用就行。没有这样的函数没关系,可以做一个简单的估算,估算结果与实际肯定有偏差,但不管怎么说这个数值也算比较靠谱的。我们来一步一步分析这个延时函数如何实现。

首先是 volatile 关键字,开发调试期间为了方便分析问题,编译优化选项往往设置为 -O0,不论加不加这个关键字都没问题,但正式程序的编译优化选项一般都会设置为-O2-Os-O3,这种情况下编译器会直接把这个函数优化掉,所以必须加 volatile 关键字。

确定好你所使用的编译器和编译优化参数,有的芯片厂商会提供多个版本的编译器,或者后来编译器更新了,编译器不同可能会导致代码行为不同,编译优化参数不同也会导致代码行为不同,假设最终发布代码使用-Os,那么测试期间也使用-Os,总之确定好这两点,在之后的开发过程中不要更改。

确定好你的延时函数的循环怎么写,包括但不限于

while (tmp++ < 30);

tmp = 30
while(1) {if(tmp-- == 0){break;}};

tmp = 30
while (tmp--);

for (tmp=0; tmp<30; tmp++);

无论怎么写都能达到延时的作用,有的编译器可能非常聪明,发现循环中什么都没做,最终这4种写法都被优化成了相同的汇编代码,但有的编译器可能不会,总之你不能保证编译器会把他们优化成相同的汇编代码,所以确定了延时函数的写法以后在之后的开发过程中不要更改。

下一步将循环计数设置为比较大的数,例如十万,一百万,执行这个函数并计算耗时,不论是用秒表,还是用 gettimeofday,或者是 time 命令,总之最终目的是算出1个循环耗时多少。假设我测量出循环百万次耗时5.3ms,那么循环一次耗时就是5.3ns。向上取整按一次循环耗时6ns计算,为什么这样做?自己想。

假设 SPI 的时钟速率是50MHz,发送数据位宽是 8bit,则发送1次耗时 1s / 50MHz * 8 = 160ns,延时循环的次数为 160 / 6 = 26.6,向上取整为27次。考虑到函数调用的耗时、读写寄存器的耗时等,实际两次发送之间的间隔肯定比它略长,但无论怎么说,27 就是我们估算出来的延时循环计数。

当然还有更靠谱一点的估算方法。目前我的延时函数及对应的汇编代码如下:

void hi_spi_delay(void)
{
    volatile unsigned int tmp = 0;
    while (tmp++ < 30);
}

Dump of assembler code for function hi_spi_delay:
   0x00010454 <+0>:  mov r3, #0
   0x00010458 <+4>:  sub sp, sp, #8
   0x0001045c <+8>:  str r3, [sp, #4]
   0x00010460 <+12>: ldr r3, [sp, #4]
   0x00010464 <+16>: cmp r3, #29
   0x00010468 <+20>: add r3, r3, #1
   0x0001046c <+24>: str r3, [sp, #4]
   0x00010470 <+28>: bls 0x10460 <hi_spi_delay+12>
   0x00010474 <+32>: add sp, sp, #8
   0x00010478 <+36>: bx  lr
End of assembler dump.

循环体对应的就是这5句

   0x00010460 <+12>: ldr r3, [sp, #4]
   0x00010464 <+16>: cmp r3, #29
   0x00010468 <+20>: add r3, r3, #1
   0x0001046c <+24>: str r3, [sp, #4]
   0x00010470 <+28>: bls 0x10460 <hi_spi_delay+12>

我所使用的芯片采用Cortex-A7架构,我实在没找到它的指令周期的文档,这里用Cortex-A9的替代一下,ARM官网文档如下 https://developer.arm.com/documentation/ddi0388/f/Instruction-Cycle-Timings?lang=en ,跳转指令的周期是不确定的,其他4个指令的都是单周期指令,循环中大多情况都是跳转,只有最后一次是不跳转,我们也按单周期计算好了。由此可知,循环一次需要5个周期,CPU的主频是900MHz,假设CPU主频固定,不超频,也不降频进入低功耗模式,那么循环一次耗时 1s / 900MHz * 5 = 5.55ns,这可以说是一个很精确的值了,同样的条件下可以算出需要的循环次数为 160 / 5.55 = 28.8,向上取整为29次。是不是和我目前使用的循环计数30基本一致?

好了,到此为止开发过程中遇到的问题还有我自己的疑惑都讲完了,有问题欢迎大家讨论。

如果你有朋友正好在海思工作,请将本文转发给他,我不知道海思其他产品线的芯片会不会也有这个问题。我这样写的代码多少有些随意,希望海思官方能优化一下 SPI 的速率问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

海思3516系列芯片SPI速率慢问题深入研究与优化(基于PL022 SPI 控制器) 的相关文章

  • 使用 cmake 和 opencv 对符号“gzclose”的未定义引用[关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 我尝试构建该项目 doppia https bitbucket org rodrigob doppia 但发生链接错误 我想这是一
  • CMake:不要为链接中使用的单个库设置 rpath

    我想要做的是配置我的 CMakeLists 文件 以便在构建我的项目时 链接器使用驻留在我的构建树中的共享库 so 的副本来链接可执行文件 但不会在中设置 rpath链接的可执行文件 以便系统必须在加载程序请求时提供该库 具体来说 我想在构
  • 打印堆栈指针的值

    如何在 Linux Debian 和 Ubuntu 中用 C 打印堆栈指针的当前值 我尝试谷歌但没有找到结果 一个技巧是简单地将本地地址作为指针打印出来 但它不可移植 甚至无法保证有效 void print stack pointer vo
  • 如果specfile中的某些条件不满足,如何中止rpm包的安装?

    还有一些事情Requires标签不满足 所以我写了一个脚本来验证这些东西 但是我把它们放在哪里呢 如果没有找到 那么我想退出安装 提示用户在尝试再次安装此 rpm 之前执行这些步骤 writing exit 1 in installtag
  • Azure Nvidia 中的 apt-update 出现公钥错误

    我在 AZURE 上启动了 NVIDIA VM 并尝试使用进行更新sudo apt update但给出错误 Hit 2 http azure archive ubuntu com ubuntu focal InRelease Hit 3 h
  • 在 LINUX 上测量 TLB 未命中的命令

    有人可以指导我使用一个命令来测量 LINUX 上的 TLB 未命中吗 是否可以将轻微页面错误视为 TLB 未命中 您可以使用perf去做这个 前提是你的CPU支持 Use perf list了解可用的计数器 当我拿到这个列表并查找 TLB
  • /etc/php5/conf.d 文件夹中的 .ini 文件有什么用?

    我知道 ini 文件位于 etc php5 cli与 PHP 的 CLI 使用有关 文件位于 etc php5 fpm是关于 PHP 的 FastCGI FPM 方面 但是位于以下位置的 ini 文件又如何呢 etc php5 conf d
  • 使用Linux虚拟鼠标驱动

    我正在尝试实施一个虚拟鼠标驱动程序根据基本 Linux 设备驱动程序书 有一个用户空间应用程序 它生成坐标以及内核模块 See 虚拟鼠标驱动程序和用户空间应用程序代码 http www embeddedlinux org cn Essent
  • Kubernetes Pod 已终止 - 退出代码 137

    我需要一些关于 k8s 1 14 和在其上运行 gitlab 管道所面临的问题的建议 许多作业都会抛出退出代码 137 错误 我发现这意味着容器突然终止 集群信息 库伯内特版本 1 14 使用的云 AWS EKS 节点 C5 4xLarge
  • PyGTK+3(PyGObject)创建屏幕截图?

    我过去 3 天在 google 上搜索 如何使用 PyGTK 3 创建屏幕截图 有关于 pyqt pygtk 2 wx 和 PIL 的 gallizion 教程 顺便说一句 我不需要 scrot imlib2 imagemagick 等外部
  • Amazon EC2 - Apache 服务器重启问题

    当我运行这个命令时 sudo etc init d httpd restart 它给出以下错误 停止 httpd 失败 启动 httpd 98 地址已在使用中 make sock 无法绑定到地址 80 98 地址已在使用 make sock
  • Google PubSub 在阻止和等待消息时没有标准输出

    我正在使用这个问题底部的 Python 代码监听 Google PubSub 消息 它实际上是来自 Google 的异步拉取示例 我运行我的程序并输出到文件 python my script py tee log txt 如果我在接收消息时
  • __libc_start_main 发生了什么?

    我真的很想理解从高级代码到可执行文件的步骤 但是遇到了一些困难 我写了一个空的int main C 文件并尝试通过以下方式破译反汇编objdump d 这是发生的事情 in start 设置对齐方式 将参数压入堆栈 调用 libc star
  • 使用请求和多处理时的奇怪问题

    请检查这个Python代码 usr bin env python import requests import multiprocessing from time import sleep time from requests import
  • 每个进程是否都存在内核堆栈?

    每个用户空间进程是否都存在一个内核堆栈和一个用户空间堆栈 如果两个堆栈都存在 那么每个用户空间进程应该有 2 个堆栈指针 对吗 在 Linux 中 每个任务 用户空间或内核线程 都有一个 8kb 或 4kb 的内核堆栈 具体取决于内核配置
  • Web 本地应用程序 Apache:运行 shell 脚本

    我开发了一个 shell 脚本 我想用它创建一个 UI 我决定使用带有本地服务器的 Web 界面 因为我对 HTML PHP 的了解很少 比 QT 或 Java 的了解更多 我只是希望我的 html 可以在我的计算机上运行 shell 脚本
  • 数百个空闲线程的影响

    我正在考虑使用可能数百个线程来实现通过网络管理设备的任务 这是一个在带有 Linux 内核的 powerpc 处理器上运行的 C 应用程序 在每个任务进行同步以将数据从设备复制到任务的初始阶段之后 任务变得空闲 并且仅在收到警报或需要更改一
  • Linux 的 gcc __attribute__((selectany)) 替代方案?

    我想知道是否有替代方案 attribute selectany 在Linux中 我想定义这样的东西 char a qwe zxc 并将其包含在许多链接在一起的 c 文件中 因此链接器将看到 a 的多个定义 因此不会链接 我读过这个属性 se
  • 模拟用户输入以使用不同参数多次调用脚本

    我必须使用提供的脚本 该脚本在脚本运行时接受用户输入而不是参数 我无法解决这个问题 脚本的一个例子是 bin bash echo param one read one doSomething echo param two read two
  • 从汇编程序获取命令行参数

    通读 专业汇编语言书籍 似乎它提供了用于读取命令行参数的错误代码 我纠正了一点 现在它从段错误变成了读取参数计数 然后是段错误 这是完整的代码 data output1 asciz There are d params n output2

随机推荐

  • 手机连不上 mac 的解决办法

    原文地址 http mobile 51cto com aprogram 386942 htm http www miui com thread 1413676 1 1 html 小米2及其他Android手机无法连接mac解决方案 2013
  • Intellij IDEA运行报Command line is too long解法

    报错内容 Error running ServiceStarter Command line is too long Shorten command line for ServiceStarter or also for Applicati
  • 向女性程序员致敬!

    今天是3月8日 国际妇女节 先祝我娘节日快乐 再祝广大女性们节日快乐 这里特别祝福一下程序媛们 你们冒着脱发 单身 没周末 xxxxxx等各种高风险在互联网事业中与程序猿们同甘共苦 一起撑起了互联网的半边天 而且听闻历史上第一位程序员也是女
  • 【Android开发】用户界面设计-在代码中控制UI界面

    效果图 实现方法 MainActivity package com example test import android app ActionBar LayoutParams import android app Activity imp
  • 三种电源防反接电路(二极管、PMOS)

    最近偶然看到PMOS防反接电路 感觉挺实用的 做个记录 软件 LTspice 二极管串联 以常用的5V 2A为例 常用二极管串联在电路中 在电源反接时 二极管承担所有的电压 有效防止电源反接损坏后级设备 但是 二极管上压降较大 损耗较高 使
  • 了解Golang基本数据类型

    文章目录 前言 一 整数数字 二 浮点数字 请注意 与前面的代码一样 Go 会从使用的值推断其数据类型 三 布尔型 四 字符串 五 常见转义字符 五 默认值 六 类型转换 总结 前言 Go 是一种强类型语言 这意味着你声明的每个变量都绑定到
  • Springboot日志级别

    一 开启Springboot详细日志 在application properties文件中添加以下代码 logging level root debug 二 sql打印在控制台 在application properties文件中添加以下代
  • 预测数值型数据:回归源码分析(1)

    回归模型比较简单 这里先简单介绍下 后面遇到难点再具体分析 回归的一般方法 1 收集数据 采用任意方法收集数据 2 准备数据 回归需要数值型数据 标称型数据将被转成二值型数据 3 分析数据 绘出数据的可视化二维图将有助于对数据做出理解和分析
  • 零基础入门microbit教程

    1 什么是microbit 1 micro bit 百度百科 micro bit 是一款由英国广播电视公司 BBC 推出的专为青少年编程教育设计的微型电脑开发板 2 官网介绍 The Micro bit Educational Founda
  • OC语言——点语法和成员变量的4种作用域及property和synthesize的使用

    点语法 点语法的本质还是方法调用 Person p Person new 点语法的本质还是方法调用 p age 10 p setAge 10 一 点语法注意点 implementation Person setter方法中的死循环 void
  • LLVM - 学习笔记一

    1 工具和库 LLVM中的独立工具 opt 在IR级对程序进行优化的工具 输入必须是LLVM的bitcode 生成的输出文件必须具有相同的类型 llc 通过特定后端将LLVM bitcode转换成目标汇编或目标问价的工具 llvm mc 能
  • Hadoop之MapReduce工作原理

    Hadoop由两部分组成 分别是分布式文件系统HDFS和分布式计算框架MapReduce 其中 分布式文件系统HDFS主要用于大规模数据的分布式存储 而MapReduce则构建在分布式文件系统上 对于存储在分布式文件系统的数据进行分布式计算
  • 【对比Java学Kotlin】在 foreach 中使用 break&continue

    正常情况下 我们只能在 loop 中使用 break 和 continue 但是 foreach 是扩展函数 不属于 loop 的范畴 如果我们想在 foreach 中达到 break 和 continue 的效果 只能使用 return
  • echo source 命令与 setup.bash与.bashrc文件

    编译完毕后键 echo source catkin ws devel setup bash gt gt bashrc source bashrc 本文分析这两条命令 echo 与source 以及setup bash文件与 bashrc两个
  • Flutter 布局Row(水平方向布局)、Column(垂直方向布局)、Wrap(可以自动换行的布局)、Flex(弹性布局)、Stack(叠层布局)、

    1 线性布局 Row 水平方向布局 Row 表示水平方向子组件的布局顺序 是从左往右还是从右往左 默认为系统当前Locale环境的文本方向 如中文 英语都是从左往右 而阿拉伯语是从右往左 TextDirection textDirectio
  • 通过模拟退火改进的Elman神经网络(Matlab代码实现)

    目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码 1 概述 神经网络是一个庞大的体系和概念 根据处理信息的不同方式来区分不同的network 比如根据处理信息结果的传递方向 分前馈型与反馈型 前馈型网络会根据输出数值来调整网
  • dede php调用指定文章,DedeCMS调用指定文章内容的两种实现方法

    有时候我们需要dedecms能够调用指定文章的内容 尤其是在建企业网站的时候 需要在首页调用网站简介联系我们什么的 今天 织梦技术研究中心就给大家介绍两种实现调用指定文章内容的方法 方法一 打开include inc arcpart vie
  • Golang基础 函数详解 函数基础

    文章目录 01 函数声明 02 更多样的参数列表 03 更灵活的返回值列表 参考资料 函数是一个固定的 可重复使用的程序段 子程序 它在实现单一或相关联功能的同时 还可以带有入口和出口 所谓的入口 就是函数参数即形参 通过这个入口把函数的参
  • 最强 Verilog 中 IP核 调用实现及思想

    写在前面 无论是在 ISE 还是 Vivado 中 关于 IP核 的调用都是非常方便的 所以对于初学者来说最关键的不是在 IP Catalog 中设置相关的 IP核 参数 而是在生成相关的 IP核 后该怎么做 也即如何让这些 IP核 为项目
  • 海思3516系列芯片SPI速率慢问题深入研究与优化(基于PL022 SPI 控制器)

    海思3516系列芯片SPI速率慢问题深入分析与优化 基于PL022 SPI 控制器 我在某个海思主控的项目中需要使用SPI接口来驱动一块液晶屏 液晶屏主控为 st7789 分辨率 240x240 图像格式 RGB565 查阅海思相关手册可知