<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>钰添的博客</title>
  
  
  <link href="http://example.com/atom.xml" rel="self"/>
  
  <link href="http://example.com/"/>
  <updated>2024-12-06T15:44:13.935Z</updated>
  <id>http://example.com/</id>
  
  <author>
    <name>yutian</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>c++虚函数介绍</title>
    <link href="http://example.com/2024/12/06/c-%E8%99%9A%E5%87%BD%E6%95%B0%E4%BB%8B%E7%BB%8D/"/>
    <id>http://example.com/2024/12/06/c-%E8%99%9A%E5%87%BD%E6%95%B0%E4%BB%8B%E7%BB%8D/</id>
    <published>2024-12-06T14:36:37.000Z</published>
    <updated>2024-12-06T15:44:13.935Z</updated>
    
    <content type="html"><![CDATA[<h1 id="一、虚函数"><a href="#一、虚函数" class="headerlink" title="一、虚函数"></a>一、虚函数</h1><p>前面已经简单介绍过虚函数了，这里再稍微带下：虚函数就是那些被声明为virtual，并在派生类中重新定义的成员函数。</p><h2 id="1-1、虚函数的作用"><a href="#1-1、虚函数的作用" class="headerlink" title="1.1、虚函数的作用"></a>1.1、虚函数的作用</h2><p>虚函数让派生类可以对基类中的函数进行动态覆盖，<strong>是实现c++多态性的一大工程</strong></p><h2 id="1-2、虚函数是怎么实现的"><a href="#1-2、虚函数是怎么实现的" class="headerlink" title="1.2、虚函数是怎么实现的"></a>1.2、虚函数是怎么实现的</h2><p>虚函数的实现是依赖<strong>虚拟表</strong>。用比较通俗的话解释，就是有一个隐藏的指向基类的指针，我们称为vptr，vptr在创建类实例时自动设置，以便指向该类的虚拟表。这里需要特别注意的是：<strong>vptr是一个真正的指针，这代表它会占一个指针大小的内存，也意味着vptr会被派生类继承。</strong> 而虚拟表中，原理是一个静态数组，保存着一个虚函数对应的其他实例的地址。</p><p><strong>虚拟表的实现原理：</strong>如果你创建一个类，并且类中有定义了虚函数，那么编译器编译的时候就会生成该类的虚函数表，并且为它分配指针，而虚函数表的大小，取决于你定义了多少个虚函数，定义得越多，占用内存越大。不管是基类还是派生类，每个类都有一个虚函数表，并且这个虚函数表中指向的函数都是自己类的函数位置。对于派生类来说，会复制一份基类的虚函数表，如果其中某个虚函数重写了，则会使用新的地址替换旧地址。</p><h2 id="1-3、虚函数传参"><a href="#1-3、虚函数传参" class="headerlink" title="1.3、虚函数传参"></a>1.3、虚函数传参</h2><p>先看看下面例子</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Base</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">fun</span><span class="params">(<span class="type">int</span> x = <span class="number">10</span>)</span> </span>&#123; cout &lt;&lt; <span class="string">&quot;Base::fun(), x = &quot;</span> &lt;&lt; x &lt;&lt; endl; &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Derived</span> : <span class="keyword">public</span> Base &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">fun</span><span class="params">(<span class="type">int</span> x = <span class="number">20</span>)</span> </span>&#123; cout &lt;&lt; <span class="string">&quot;Derived::fun(), x = &quot;</span> &lt;&lt; x &lt;&lt; endl; &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  Derived d1;<span class="comment">//定义派生类</span></span><br><span class="line">  Base *bp = &amp;d1;<span class="comment">//定义一个Base类型的指针，然后指向Derived</span></span><br><span class="line">  bp-&gt;<span class="built_in">fun</span>(); <span class="comment">// 结果输出为10</span></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>默认参数是静态绑定的，虚函数的是动态绑定的</strong>，所以调用的是派生类的fun，但是打印出来的值是基类上fun的参数值</p><ul><li>在 <code>main()</code> 中，<code>bp</code> 是 <code>Base*</code> 类型的指针，尽管它指向的是 <code>Derived</code> 类型的对象，但它的静态类型是 <code>Base*</code>。</li><li>因此，<strong>编译器会使用 <code>Base::fun()</code> 的默认参数</strong>，而不是 <code>Derived::fun()</code> 的默认参数。编译器选择 <code>Base::fun()</code> 时，默认参数 <code>x = 10</code> 会被采用。</li></ul><h2 id="1-4、虚函数与其他函数的关系"><a href="#1-4、虚函数与其他函数的关系" class="headerlink" title="1.4、虚函数与其他函数的关系"></a>1.4、虚函数与其他函数的关系</h2><h3 id="1-4-1、静态函数可以声明为虚函数吗？"><a href="#1-4-1、静态函数可以声明为虚函数吗？" class="headerlink" title="1.4.1、静态函数可以声明为虚函数吗？"></a>1.4.1、静态函数可以声明为虚函数吗？</h3><p><strong>不行</strong><br>之所以虚函数不可以为static函数，是因为虚函数可以实现多态性，也就是同一个函数可能有不同的实现，这个是在调用时才决定调用哪个类的函数，但是如果定义为static，那么在调用类的时候就已经分配好内存了，无论怎么调用都是调用的同一个函数</p><h3 id="1-4-2、构造函数可以是虚函数吗？"><a href="#1-4-2、构造函数可以是虚函数吗？" class="headerlink" title="1.4.2、构造函数可以是虚函数吗？"></a>1.4.2、构造函数可以是虚函数吗？</h3><p><strong>不行</strong><br><strong>构造函数不可以声明为虚函数。同时除了inline和explicit之外，构造函数不允许使用其它任何关键字。</strong><br>如果类中有虚函数，那么编译器会在构造函数中添加代码来创建vptr。如果构造函数时虚的，那么它也需要vptr来访问vtable，可这个时候vptr还没有产生。</p><h3 id="1-4-3、析构函数可以是虚函数吗？"><a href="#1-4-3、析构函数可以是虚函数吗？" class="headerlink" title="1.4.3、析构函数可以是虚函数吗？"></a>1.4.3、析构函数可以是虚函数吗？</h3><p><strong>可以</strong><br>析构函数可以声明为虚函数。<strong>甚至可以说，析构函数最好都定义为虚函数</strong>。如果我们需要删除一个指向派生类的基类指针时，应该把析构函数声明为虚函数。事实上，只要一个类有可能会被其他类继承，就应该声明虚析构函数。<em>为什么？？</em><br>先看下面这个例子来帮助理解</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Base</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  ~<span class="built_in">Base</span>() &#123;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Base destroyed&quot;</span> &lt;&lt; endl;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Derived</span> : <span class="keyword">public</span> Base &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  ~<span class="built_in">Derived</span>() &#123;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Derived destoyed&quot;</span> &lt;&lt; endl;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  Base *bp = <span class="keyword">new</span> Derived; </span><br><span class="line">  <span class="keyword">delete</span> bp; <span class="comment">//使用基类指针删除派生类对象</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输入结果如下：只调用到了基类的析构函数去释放资源，而没有调用派生类的析构函数<br>Base destroyed</p><p>只有将基类的析构函数定位为虚函数，才可以同时调用到基类和派生类的析构函数。</p><p>这里衍生出一个题外话：<em>那为什么默认的析构函数并没有定义为虚函数呢？</em><br>前面我们已经介绍过，一个类有虚函数时，在构造函数中会生成一个vptr指针和vtable，而vptr的一个真实存在的指针类型，会占用内存，如果默认析构函数就定义为虚函数，会造成额外的内存损耗。</p><h3 id="1-4-4、友元函数可以是虚函数吗？"><a href="#1-4-4、友元函数可以是虚函数吗？" class="headerlink" title="1.4.4、友元函数可以是虚函数吗？"></a>1.4.4、友元函数可以是虚函数吗？</h3><p><strong>不行</strong><br>因为友元函数不属于类函数，只有类函数才可以为虚函数。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;一、虚函数&quot;&gt;&lt;a href=&quot;#一、虚函数&quot; class=&quot;headerlink&quot; title=&quot;一、虚函数&quot;&gt;&lt;/a&gt;一、虚函数&lt;/h1&gt;&lt;p&gt;前面已经简单介绍过虚函数了，这里再稍微带下：虚函数就是那些被声明为virtual，并在派生类中重新定义的成员函数。&lt;</summary>
      
    
    
    
    <category term="C++" scheme="http://example.com/categories/C/"/>
    
    
  </entry>
  
  <entry>
    <title>c++常见函数介绍</title>
    <link href="http://example.com/2024/12/06/c-%E5%9F%BA%E7%A1%80%E5%85%A5%E9%97%A8/"/>
    <id>http://example.com/2024/12/06/c-%E5%9F%BA%E7%A1%80%E5%85%A5%E9%97%A8/</id>
    <published>2024-12-06T13:44:09.000Z</published>
    <updated>2024-12-06T14:33:05.267Z</updated>
    
    <content type="html"><![CDATA[<h1 id="一、C-中常见函数"><a href="#一、C-中常见函数" class="headerlink" title="一、C++中常见函数"></a>一、C++中常见函数</h1><h2 id="1-1、构造函数"><a href="#1-1、构造函数" class="headerlink" title="1.1、构造函数"></a>1.1、构造函数</h2><p>每个类都分别定义了它的对象被初始化的方式，类通过一个或几个特殊的成员函数来控制其对象的初始化过程，这些函数叫做构造函数。构造函数的任务是初始化类对象的数据成员，<strong>无论何时，只要类的对象被创建，就会执行构造函数。</strong></p><h2 id="1-2、析构函数"><a href="#1-2、析构函数" class="headerlink" title="1.2、析构函数"></a>1.2、析构函数</h2><p>在类的定义时，名称与类名相同，增加一个～，一般用于释放对象所占用的资源，在对象消失时调用。<strong>如果程序员没有提供析构函数，编译器将隐式地声明一个默认析构函数</strong>（<em>需要注意的是，默认的析构函数不会定义为虚函数，具体原因在虚函数章节会介绍</em>），并在发生导致对象被删除的代码后，提供默认析构函数的定义。</p><h2 id="1-3、内联函数"><a href="#1-3、内联函数" class="headerlink" title="1.3、内联函数"></a>1.3、内联函数</h2><p>在C++中，<strong>内联函数的主要用途是节省内存空间</strong>。简单点理解，就是调用内联函数的时候，其实直接调用的是函数内容。在main()方法中，当调用fun1()函数时，控制权被转移到被调函数的定义。函数被调用的地址和函数定义的地址是不同的，这个控制转移需要时间，增加了开销。<br>当遇到内联函数时，函数的定义会被负责到调用位置。在这个情况下，没有控制转移，这节省了很多时间并减少了开销。</p><h2 id="1-4、虚函数"><a href="#1-4、虚函数" class="headerlink" title="1.4、虚函数"></a>1.4、虚函数</h2><p>虚函数是在基类中被声明为virtual，并在派生类中重新定义的成员函数，可实现成员函数的动态覆盖。（虚函数在日常中使用非常频繁，后面会专门出一篇讲解虚函数的进一步介绍）</p><h2 id="1-5、纯虚函数"><a href="#1-5、纯虚函数" class="headerlink" title="1.5、纯虚函数"></a>1.5、纯虚函数</h2><p>纯虚函数时在基类中声明的虚函数，它<strong>在基类中没有定义，但要求任何派生类都要定义自己的实现方法</strong>。在基类中实现虚函数的方法是在函数原型后加“&#x3D;0”，定义纯虚函数后，基类就不能被实例化，目的就是让派生类去重写这个纯虚函数。<br><em>为什么需要纯虚函数？</em> 因为在很多情况下，基类本身生成的对象是不合情理的。例如，动物可以作为一个基类，派生出老虎、孔雀等子类，但动物本身生成一个对象并不合理。</p><h2 id="1-6、友元函数"><a href="#1-6、友元函数" class="headerlink" title="1.6、友元函数"></a>1.6、友元函数</h2><p><strong>比如A想访问B中的private成员，那么就将A定义为B的友元函数</strong><br>在C++中，友元函数和友元类长用于需要访问类的私有成员以实现特定功能的场景。如果要声明函数为一个类的友元，需要在类定义中在该函数原型前使用关键字friend。<strong>友元不属于类成员，需要在类内定义，但是在类外实现，友元也不支持继承</strong>。</p><pre><code class="c++">#include &lt;iostream&gt;using namespace std;class A&#123;    private:        int width;        void Private_printfwidth(void) &#123;            cout &lt;&lt; &quot;private width:&quot; &lt;&lt; width &lt;&lt; endl;          &#125;    public:        A(int w) &#123;            width = w;        &#125;        friend void Pubilc_printfwidth(A &amp;a);//友元在类内声明&#125;;void Pubilc_printfwidth(A &amp;a) &#123; //在类外定义    cout &lt;&lt; &quot;pubilc width:&quot; &lt;&lt; a.width &lt;&lt; endl;  &#125;int main(void) &#123;    A test(1);    Pubilc_printfwidth(test);    return 0;&#125;</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;一、C-中常见函数&quot;&gt;&lt;a href=&quot;#一、C-中常见函数&quot; class=&quot;headerlink&quot; title=&quot;一、C++中常见函数&quot;&gt;&lt;/a&gt;一、C++中常见函数&lt;/h1&gt;&lt;h2 id=&quot;1-1、构造函数&quot;&gt;&lt;a href=&quot;#1-1、构造函数&quot; class</summary>
      
    
    
    
    <category term="C++" scheme="http://example.com/categories/C/"/>
    
    
  </entry>
  
  <entry>
    <title>vim使用</title>
    <link href="http://example.com/2024/12/01/vim%E4%BD%BF%E7%94%A8/"/>
    <id>http://example.com/2024/12/01/vim%E4%BD%BF%E7%94%A8/</id>
    <published>2024-12-01T07:53:45.000Z</published>
    <updated>2024-12-01T08:41:53.502Z</updated>
    
    <content type="html"><![CDATA[<h1 id="1-熟练使用vim"><a href="#1-熟练使用vim" class="headerlink" title="1. 熟练使用vim"></a>1. 熟练使用vim</h1><p><strong>在日常的开发工作中，ubuntu中的vi编辑器是我们无法避免的需要使用的工具之一，而其又不像普通的文本编辑器一样自由选择，想要随心所欲得使用vi编辑器，提高工作效率，也是需要下一定的心思的，在此将记录本人在工作中积累的一些关于vi编辑器的使用技巧</strong>   </p><h2 id="1-1-注释"><a href="#1-1-注释" class="headerlink" title="1.1. 注释"></a>1.1. 注释</h2><h3 id="1-1-1-多行注释"><a href="#1-1-1-多行注释" class="headerlink" title="1.1.1. 多行注释"></a>1.1.1. 多行注释</h3><ul><li>Step 1：在命令行的模式下，将光标固定在需要注释内容中的第一列，按<code>ctrl+V</code>快捷键进入可视化模式  </li><li>Step 2：使用上下方向键，选中需要注释的行  </li><li>Step 3：按下大写的<code>I</code>键，进入insert模式，然后输入<code>#</code>注释内容  </li><li>Step 4：连续两次按下ESC键，Step 2所选的行就都会被注释掉</li></ul><h3 id="1-1-2-解除多行注释"><a href="#1-1-2-解除多行注释" class="headerlink" title="1.1.2. 解除多行注释"></a>1.1.2. 解除多行注释</h3><ul><li>Step 1：在命令行的模式下，将光标固定在需要接触注释内容中的第一列，按<code>ctrl+V</code>快捷键进入可视化模式  </li><li>Step 2：使用上下方向键，选中需要接触注释的行  </li><li>Step 3：按下大写的<code>I</code>键，进入insert模式，然后将行前的<code>#</code>符号去除  </li><li>Step 4：连续两次按下ESC键，Step 2所选的行就都将解除注释</li></ul><h2 id="1-2-命令行光标移动"><a href="#1-2-命令行光标移动" class="headerlink" title="1.2. 命令行光标移动"></a>1.2. 命令行光标移动</h2><ul><li>ctrl+a    将光标移动到命令行行首  </li><li>ctrl+e    将光标移动到命令行行尾  </li><li>ctrl+u    删除光标前的内容  </li><li>ctrl+k    删除光标后的内容  </li><li>ctrl+w    删除光标前面的单词  </li><li>alt+d    删除光标后面的字符  </li><li>ctrl+l    保留当前命令行的前提下清屏</li></ul><h2 id="1-3-文本中光标的移动"><a href="#1-3-文本中光标的移动" class="headerlink" title="1.3. 文本中光标的移动"></a>1.3. 文本中光标的移动</h2><hr><ul><li>w    移动光标到下一个单词的开头 </li><li>e    移动光标到下一个单词的结尾 </li><li>b    移动光标到上一个单词 </li><li>0    移动光标到本行最开头</li></ul><hr><ul><li>^    移动光标到本行最开头的字符处  </li><li>$    移动光标到本行结尾处  </li><li>A    移动光标至行尾并处于可编辑状态</li></ul><hr><ul><li>gg    移动光标到文档首行  </li><li>G     移动光标到文档尾行  </li><li>:n    跳到第n行  </li><li>u     撤销</li></ul><hr><ul><li>ctrl+f    向下翻页,同page down.  </li><li>ctrl+b    向上翻页,同page up.  </li><li>ctrl+d    向下翻半页 此比较有用  </li><li>ctrl+u    向上翻半页 此比较有用  </li><li>ctrl+e    向下翻一行  </li><li>ctrl+y    向上一行</li></ul><hr><h2 id="1-4-拷贝"><a href="#1-4-拷贝" class="headerlink" title="1.4. 拷贝"></a>1.4. 拷贝</h2><hr><ul><li>yw    表示拷贝从当前光标到光标所在单词结尾的内容  </li><li>dw    表示删除从当前光标到光标所在单词结尾的内容  </li><li>daw  表示删除光标所在的单词</li></ul><hr><ul><li>yy    表示拷贝光标所在行  </li><li>dd    表示删除光标所在行  </li><li>D     表示删除从当前光标到光标所在行尾的内容</li></ul><hr><ul><li>xp    表示交换光标与其后的字符的位置  </li><li>ddp   表示光标所在行与下一行交换</li></ul><hr><h2 id="1-5-替换"><a href="#1-5-替换" class="headerlink" title="1.5. 替换"></a>1.5. 替换</h2><hr><ul><li>:%s#abc#def#g            把文本中的abc全部替换为def  </li><li>:10,50s#abc#def#g       把文本中第10~50行中的adb替换为def  </li><li>:%s#abc#def#gc         如果在g后面加上c，那么每次替换之前会寻求用户的确认</li></ul><hr><h2 id="1-6-删除"><a href="#1-6-删除" class="headerlink" title="1.6. 删除"></a>1.6. 删除</h2><hr><ul><li>:%d    删除全文  </li><li>Ndd    删除往下的N行</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;1-熟练使用vim&quot;&gt;&lt;a href=&quot;#1-熟练使用vim&quot; class=&quot;headerlink&quot; title=&quot;1. 熟练使用vim&quot;&gt;&lt;/a&gt;1. 熟练使用vim&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;在日常的开发工作中，ubuntu中的vi编辑器是我们无法避免的需</summary>
      
    
    
    
    <category term="Linux开发" scheme="http://example.com/categories/Linux%E5%BC%80%E5%8F%91/"/>
    
    
  </entry>
  
  <entry>
    <title>mipi协议介绍</title>
    <link href="http://example.com/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/"/>
    <id>http://example.com/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/</id>
    <published>2024-12-01T07:39:57.000Z</published>
    <updated>2024-12-01T08:41:50.622Z</updated>
    
    <content type="html"><![CDATA[<h1 id="一、基本概念介绍"><a href="#一、基本概念介绍" class="headerlink" title="一、基本概念介绍"></a>一、基本概念介绍</h1><p>CSI-2替MIPI定义了两种高速数据传输接口（物理层选项）和一组控制接口标准</p><ul><li>D-PHY物理层选项</li><li>C-PHY物理层选项</li><li>CCI（Camera Control Interface)</li></ul><h2 id="1-1、Dphy"><a href="#1-1、Dphy" class="headerlink" title="1.1、Dphy"></a>1.1、Dphy</h2><p>MIPI联盟定义的常见D-PHY接口支持高速（HS）和低速（LP）模式，分为</p><ul><li>2组差分时钟</li><li>1组或多组差分数据通道</li></ul><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/dphy.png" class="" title="dphy"><h2 id="1-2、Cphy"><a href="#1-2、Cphy" class="headerlink" title="1.2、Cphy"></a>1.2、Cphy</h2><p>另一种常见的C-PHY接口，为1路或多路单向3-write串行数据通道，<strong>每路都有自己的时钟</strong></p><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/cphy.png" class="" title="cphy"><h2 id="1-3、Mphy"><a href="#1-3、Mphy" class="headerlink" title="1.3、Mphy"></a>1.3、Mphy</h2><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/mphy.png" class="" title="mphy"><h1 id="二、CSI-2-Layer定义"><a href="#二、CSI-2-Layer定义" class="headerlink" title="二、CSI-2 Layer定义"></a>二、CSI-2 Layer定义</h1><p>CSI-2就协议的层级来看，大致可以分成3层：</p><ul><li><strong>物理层（PHY Layer）</strong>：定义传输媒介、电器特性、IO电路、同步机制、制定SoT（Start of Transmission）和EoT（End of Ttransmission）信号等，M-PHY、D-PHY和C-PHY</li><li><strong>协议层（Protocol Layer）</strong>：定义传输数据时，如何标记和交错多个数据流，以便接收端重建每个数据流</li><li><strong>应用层（Application Layer</strong>：对数据流进行处理，如分析、编解码等</li></ul><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/CSI-2.png" class="" title="CSI-2"><h2 id="2-1、物理层"><a href="#2-1、物理层" class="headerlink" title="2.1、物理层"></a>2.1、物理层</h2><p>MIPI会通过CSI的LDF（Lane Distribution Function）将数据平均且有序地分配到每一条Lane，然后通过LMF（Lane Merging Function）将数据重新整合成一条数据Lane</p><h3 id="2-1-1、DPHY-LDF"><a href="#2-1-1、DPHY-LDF" class="headerlink" title="2.1.1、DPHY LDF"></a>2.1.1、DPHY LDF</h3><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/DPHY-LDF.png" class="" title="DPHY-LDF"><h3 id="2-1-2、DPHY-LMF"><a href="#2-1-2、DPHY-LMF" class="headerlink" title="2.1.2、DPHY LMF"></a>2.1.2、DPHY LMF</h3><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/DPHY-LMF.png" class="" title="DPHY-LMF"><h3 id="2-1-3、CPHY-LDF"><a href="#2-1-3、CPHY-LDF" class="headerlink" title="2.1.3、CPHY LDF"></a>2.1.3、CPHY LDF</h3><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/CHPY-LDF.png" class="" title="CHPY-LDF"><h3 id="2-1-4、CPHY-LMF"><a href="#2-1-4、CPHY-LMF" class="headerlink" title="2.1.4、CPHY LMF"></a>2.1.4、CPHY LMF</h3><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/CPHY-LMF.png" class="" title="CPHY-LMF"><h2 id="2-2、协议层（Protocol-Layer）"><a href="#2-2、协议层（Protocol-Layer）" class="headerlink" title="2.2、协议层（Protocol Layer）"></a>2.2、协议层（Protocol Layer）</h2><p>MIPI协议层可以分为3级</p><ul><li><strong>通道管理（Lane Managerment）</strong>：将传输的数据流分配到一个或者多个通道，并在接收端回复原始数据流</li><li><strong>LLP（Low Level Protocol）</strong>：将数据里封装成不同形式的<strong>长包和短包</strong></li><li><strong>封拆像素包（Pixel Packing&#x2F;Unpacking）</strong>：传输端将像素包拆为bytes，接收端将bytes还原</li></ul><h3 id="2-2-1、LLP"><a href="#2-2-1、LLP" class="headerlink" title="2.2.1、LLP"></a>2.2.1、LLP</h3><p>LLP是一种面向字节、基于数据包的协议、支持使用短包和长包格式传输任意数据。主要特性如下：</p><ul><li>8-bit</li><li>每条link最大支持4个VC Channels</li><li>数据的类型、像素深度和格式的描述符</li></ul><h1 id="三、MIPI数据传输"><a href="#三、MIPI数据传输" class="headerlink" title="三、MIPI数据传输"></a>三、MIPI数据传输</h1><p><em>我们前面说到LLP里包括了长包和短包，那么到底什么是短包？什么是长包？</em></p><p>在介绍数据传输之前，我们需要先来了解一下MIPI中的Data Type</p><h2 id="3-1、Data-Type（DT）"><a href="#3-1、Data-Type（DT）" class="headerlink" title="3.1、Data Type（DT）"></a>3.1、Data Type（DT）</h2><p>在我们日常的工作中，可能经常会听到，“sensor输出的Image raw DT是0x2b，输出的PD raw DT是0x30”，那么这里说的DT是什么意思呢？</p><p>不管是长包短包，我们都需要指定数据包的数据类型，也就是Data Type，不同的Data Type代表不同的含义</p><ul><li>短包DT范围：<strong>0x00~0x0F</strong></li><li>长包DT范围：<strong>0x10~0x37</strong></li></ul><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/DT.png" class="" title="DT"><h3 id="3-1-1、短包DT"><a href="#3-1-1、短包DT" class="headerlink" title="3.1.1、短包DT"></a>3.1.1、短包DT</h3><p>短包分为帧同步和行同步（意思就是，MIPI数据在传输的过程中，传递完一行数据、或者传递完一帧数据，都会发送一个短包作为标志），结合下面这张图，在行开始传递时，会发送一个DT为0x02的短包，在行数据传输结束后，会发送一个DT为0x03的短包，帧同步同样道理。</p><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/%E7%9F%AD%E5%8C%85DT1.png" class="" title="短包DT1"><p>通用短包</p><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/%E7%9F%AD%E5%8C%85DT2.png" class="" title="短包DT2"><h3 id="3-1-2、长包DT"><a href="#3-1-2、长包DT" class="headerlink" title="3.1.2、长包DT"></a>3.1.2、长包DT</h3><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/%E9%95%BF%E5%8C%85DT1.png" class="" title="长包DT1"><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/%E9%95%BF%E5%8C%85DT2.png" class="" title="长包DT2"><h2 id="3-2、数据传输过程"><a href="#3-2、数据传输过程" class="headerlink" title="3.2、数据传输过程"></a>3.2、数据传输过程</h2><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/%E7%9F%AD%E5%8C%85Data.png" class="" title="短包Data"><h3 id="3-2-1、短包数据的组成格式"><a href="#3-2-1、短包数据的组成格式" class="headerlink" title="3.2.1、短包数据的组成格式"></a>3.2.1、短包数据的组成格式</h3><p>我们已经了解了短包、长包的基本数据类型后，可以进一步来看MIPI数据到底是怎么传输的，以短包的数据格式为例，我们先介绍一下里面各个成员的含义</p><ul><li><strong>SoT（Start of Transmission）</strong>：这个前面也有提到，指的是传输开始的标志，用于代表数据包的开始，<strong>一般表现为一种电平脉冲</strong>，在日常工作中应该也经常听到，<em>平台收到sof巴拉巴拉的</em></li><li><strong>Data ID</strong>：简称DI，包括数据类型（DT），用以标志数据包的性质，如帧起始、行起始、实际数据等。（<strong>由VC和DT两部分组成</strong>）</li></ul><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/DI.png" class="" title="DI"><ul><li><strong>Data Field</strong>：实际的数据内容，长度可以变化，具体取决于数据包的类型和内容。比如该短包是Frame Start Code，那么它后面的Data Field就会接着Framelength的信息</li><li><strong>ECC（Error Correction Code）</strong>：错误校验码，用于确保数据在传输过程中没有发生错误</li><li><strong>EoT（End of Transmission）</strong>：传输结束的标志，指示该数据的结束。</li></ul><p>了解了DT和短包的数据格式后后，我们进一步来看下数据传输的过程。<strong>数据发送流程包括：退出LPS（Low Power State）状态，然后发送一个SOT的短包作为开始，然后通过长包发送数据，最后发送一个EoT短包，切换回低功耗状态结束。</strong> <em>上面这么说可能还有点抽象，我们举一个实际一点的例子，</em>比如camera在传输一帧数据的时候，应该是一个什么样的流程？*<br>退出LPS后，会发出第一个短包，这个短包的DT应该是0x00，代表Frame Start Code信号，然后会继续发送另一个短包，DT是0x02，代表Line Start Code，然后后面再接实际的长包数据，当一行的长包数据发送完之后，会再发送一个短包DT为0x03，代表Line End Code，然后再继续发送下一行的数据，等到这一帧数据都发送完成后，最后会接一个短包DT为0x01，代表Frame End Code，代表一帧数据都发送完成，然后重新进入LPS，等待下一帧数据到来。</p><p>这里衍生出一个进阶问题：我们都知道MIPI数据传输过程中，可以通过不同的VC channel和不同的DT来区分数据，那么<em>如果两个数据相同VC不同DT或者不同VC的数据，数据包的格式是什么样的呢？</em>在回答上面问题之前，我们先了解一下，长包和短包之间，有哪些区别</p><h3 id="3-2-2、长包的数据组成格式"><a href="#3-2-2、长包的数据组成格式" class="headerlink" title="3.2.2、长包的数据组成格式"></a>3.2.2、长包的数据组成格式</h3><p>先看D-PHY下的数据组成成员（短包数据是没有下面标红的三部分内容的）</p><ul><li><strong>SoT（Start of Tranmission）</strong>：数据开始传输标志</li><li><code>PACKET HEADER（PH</code>：由Data ID（VC + DT）、Word Count（数据的长度）和ECC组成</li><li><code>PACKET DATA</code></li><li><code>PACKET FOOTER（PF)</code>：包含了checksum</li><li><strong>EoT（End of Tranmission）</strong>：数据结束传输标志</li></ul><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/DPHY%E9%95%BF%E5%8C%85%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F.png" class="" title="DPHY长包数据格式"><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/CPHY%E9%95%BF%E5%8C%85%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F.png" class="" title="CPHY长包数据格式"><p>现在我们来回答上面提出的问题：<em>如果两个数据相同VC不同DT或者不同VC的数据，数据包的格式是什么样的呢？</em></p><p>分两种情况：</p><ul><li><strong>使用相同VC，独立DT</strong>：共享帧头（FS）、帧尾（FE）和行头（SoT）行尾（EoT）</li></ul><ul><li><strong>使用不同VC区分</strong>：每个vc拥有自己的帧头（FS）和帧尾（FE）</li></ul><p>我们在日常的工作中，比如在MTK平台，那么要求sensor输出的MIPI协议除了要符合标准MIPI协议之外，还需要符合MTK平台特殊的时序要求，不然可能会出现mipi error，这里主要是对mipi中的THS-Trail有关，那么<em>前面说的数据传输类型，怎么和实际的MIPI时序中的THS-Trail这些联系起来呢？</em></p><img src="/2024/12/01/mipi%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D/trail.png" class="" title="trail"><p>后面会针对上面这种图，进一步解释各个阶段的含义和其中做的工作</p><h1 id="四、Dphy和Cphy的差异"><a href="#四、Dphy和Cphy的差异" class="headerlink" title="四、Dphy和Cphy的差异"></a>四、Dphy和Cphy的差异</h1><p>从时间上来说，CPHY是晚于DPHY设计出来的，从架构上来说，CPHY相对更具先进性。两者最直观的差异是，DPHY是源同步系统，有专门的时钟信号，但CPHY没有同步时钟，时钟是嵌入到数据中的，物理层结构截然不同，编码带宽更大，单从线路上来说，CPHY是一个A&#x2F;B&#x2F;C三线系统。</p><p>对于MIPI CPHY的接收端来说，因为他不传输时钟，若要接收CPHY的数据，必须项恢复时钟，任何再用恢复的时钟采样数据并寻找同步头，最后还需要进行数据解码恢复出最初发送的内容，而发送端的步骤恰好相反。</p><p>CPHY同样是运用差分信号进行数据传输，但特别的是，CPHY是通过三条线之间的差分进行数据传输。CPHY TX端三根线的电平在任意时刻都是不一样的，三者将分别任意处于3&#x2F;4V、1&#x2F;2V、1&#x2F;4V三个值，两两相减后将得到strong1&amp;0和weak 1&amp;0，电平差是1&#x2F;2V则是strong，电平差是1&#x2F;4V，则是weak，大于0为1，小于0为0，这是我们对任意两线间差分状态进行命名：在TX端3根线上传输的信息在RX端相减作差分可以得到6中状态，分别是+x，-x，+y，-y, +z，-z，经常也被称为6种symbol。</p><p><strong>Mipi_pixel_rate</strong></p><ul><li>Dphy &#x3D; xxxMbps&#x2F;lane * 4(lane num) &#x2F; 10bit</li><li>Cphy &#x3D; xxxMsps&#x2F;lane * 3(lane num) &#x2F; 10bit * 2.28</li></ul>]]></content>
    
    
    <summary type="html">MIPI一直以来都是比较晦涩难懂的知识点，但是在日常工作甚至是相关领域的工作中却总被提起，所以很有必要对其原理性进行学习，欢迎大家和我一起来初探MIPI</summary>
    
    
    
    <category term="相机知识体系构建" scheme="http://example.com/categories/%E7%9B%B8%E6%9C%BA%E7%9F%A5%E8%AF%86%E4%BD%93%E7%B3%BB%E6%9E%84%E5%BB%BA/"/>
    
    
  </entry>
  
</feed>
