-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlocal-search.xml
More file actions
162 lines (78 loc) · 156 KB
/
local-search.xml
File metadata and controls
162 lines (78 loc) · 156 KB
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>希尔伯特变换生成IQ信号</title>
<link href="/2026/01/13/%E5%B8%8C%E5%B0%94%E4%BC%AF%E7%89%B9%E5%8F%98%E6%8D%A2%E7%94%9F%E6%88%90IQ%E4%BF%A1%E5%8F%B7/"/>
<url>/2026/01/13/%E5%B8%8C%E5%B0%94%E4%BC%AF%E7%89%B9%E5%8F%98%E6%8D%A2%E7%94%9F%E6%88%90IQ%E4%BF%A1%E5%8F%B7/</url>
<content type="html"><![CDATA[<h2 id="背景">背景</h2><p>笔者最近在做一个小项目,里面有一部分需要做ssb调制,我负责数字信号处理部分,得把语音信号转换成IQ信号输出,用于后续的调制和传输。</p><p>传统的IQ信号生成通常需要硬件正交混频器,但在软件无线电架构中,我们可以通过数字信号处理直接生成IQ信号。核心问题是:如何从单路实信号(语音)生成正交的I/Q两路信号?</p><p>有一个办法是做 fft,然后直接在负频域对每个点乘以 -1 实现相移,然后ifft 获得移相后的信号。这个方案有个缺点,板子上需要跑其他任务,fft有些吃资源了,而且需要缓存整帧数据,延迟较大,不适合实时语音处理</p><p>希尔伯特变换提供了一个优雅的解决方案:通过对实信号进行90度相移,可以直接生成解析信号(复信号),其实部和虚部就是I和Q分量。</p><h2 id="希尔伯特变换原理">希尔伯特变换原理</h2><p>对于实信号 <spanclass="math inline"><em>s</em>(<em>t</em>)</span>,做希尔伯特变换本质是将其与<span class="math inline">1/(<em>π</em><em>t</em>)</span> 进行卷积:<spanclass="math display"><em>s</em>̃(<em>t</em>) = <em>s</em>(<em>t</em>) * 1/(<em>π</em><em>t</em>)</span>实质上是对信号的所有频率分量加一个90度的相移,最终得到解析信号,也就是IQ: <span class="math display">$$z(t) = s(t) + j·s̃(t) \\z(t) = I(t) + j·Q(t)$$</span> 也得到了解析信号,也就是IQ。可以直接扔给dac,进而输出给射频前端进行调制。</p><p>当然,对于PSK等相位调制,也可以计算其相位突变进行解调。</p><p>对于语音信号(典型带宽300Hz-3400Hz),希尔伯特变换可以很好地生成正交分量,满足IQ输出的需求。</p><h2 id="仿真">仿真</h2><p>以音频经典16Khz采样率为例,在matlab中弄了一个100阶,通带归一化频率为0.01-0.99 的希尔伯特变换器:</p><figure><imgsrc="/2026/01/13/%E5%B8%8C%E5%B0%94%E4%BC%AF%E7%89%B9%E5%8F%98%E6%8D%A2%E7%94%9F%E6%88%90IQ%E4%BF%A1%E5%8F%B7/image-20260113234725732.png"alt="希尔伯特变换器" /><figcaption aria-hidden="true">希尔伯特变换器</figcaption></figure><p>实际通带大概在80Hz-7.9KHz,带内波动1dB,基本可以接收,移相稳定在-90度。</p><p>给两个频率的信号进去看一下相位差:</p><figure><imgsrc="/2026/01/13/%E5%B8%8C%E5%B0%94%E4%BC%AF%E7%89%B9%E5%8F%98%E6%8D%A2%E7%94%9F%E6%88%90IQ%E4%BF%A1%E5%8F%B7/image-20260113234955083.png"alt="1Khz/2Khz IQ信号补偿群延迟" /><figcaption aria-hidden="true">1Khz/2Khz IQ信号补偿群延迟</figcaption></figure><p>效果也挺好,1KHZ和2KHz位置相位差都是-90度,幅度基本一致。注意,这里对Q路信号的群延迟进行了补偿,I路挂了一个全通滤波器。我们看看不接滤波器有没有什么影响:</p><figure><imgsrc="/2026/01/13/%E5%B8%8C%E5%B0%94%E4%BC%AF%E7%89%B9%E5%8F%98%E6%8D%A2%E7%94%9F%E6%88%90IQ%E4%BF%A1%E5%8F%B7/image-20260114121653052.png"alt="1Khz/2Khz IQ信号不补偿群延迟" /><figcaption aria-hidden="true">1Khz/2Khz IQ信号不补偿群延迟</figcaption></figure><p>幅度没什么变化,相位已经炸了,为什么呢?分析一下原因:</p><p>对于一个线性相位 FIR 滤波器,群延迟是一个固定的时间延迟τ,表示信号通过滤波器后的时间偏移。</p><p>对于 N 阶 采样率为 <spanclass="math inline"><em>F</em><sub><em>s</em></sub></span> 的FIR滤波器,其群延迟为: <span class="math display">$$\tau = \frac{N}{2 \cdot F_s}$$</span> 固定的时间延迟 τ 在频率 f 处产生的相位延迟为: <spanclass="math display">$$\phi(f) = -2\pi f \cdot \tau = -2\pi f \cdot \frac{N}{2 \cdot F_s}$$</span> 也就是相位延迟与频率成正比,频率越高,相位延迟越大。</p><p>对于N 阶希尔伯特变换器而言,理想频率响应为 <spanclass="math display"><em>H</em><sub><em>H</em><em>i</em><em>l</em><em>b</em><em>e</em><em>r</em><em>t</em></sub>(<em>f</em>) = −<em>j</em> ⋅ sign(<em>f</em>) ⋅ <em>e</em><sup>−<em>j</em>2<em>π</em><em>f</em><em>τ</em></sup></span>相位分解为两部分:</p><ol type="1"><li><strong>Hilbert 相移</strong>:-90°(对应 <spanclass="math inline">−<em>j</em></span>)</li><li><strong>群延迟相移</strong>:<span class="math inline">$-2\pi f\cdot \frac{N}{2 \cdot F_s}$</span></li></ol><p>那么输出Q路信号的总相位为: <span class="math display">$$\phi_Q(f) = -90° - 2\pi f \cdot \frac{N}{2 \cdot F_s}$$</span> 如果能在I路加一个N阶段的全通FIR,补偿<spanclass="math inline">$-2\pi f \cdot \frac{N}{2 \cdotF_s}$</span>这一部分的理想相位延迟,就能让所有通带各个频率的相位延迟为-90 度。 <span class="math display">$$\Delta\phi(f) = \phi_Q(f) - \phi_I(f) = \left(-90° - 2\pi f \cdot\frac{N}{2 \cdot F_s}\right) - \left(-2\pi f \cdot \frac{N}{2 \cdotF_s}\right) = -90°$$</span> 构成了理想希尔伯特变换器。</p><p>如果不补偿群延迟的话,计算一下<spanclass="math inline"><em>F</em><sub><em>s</em></sub></span>为16KHz,N为100,频率分别为1Khz和2Khz的相位:</p><figure class="highlight matlab"><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></pre></td><td class="code"><pre><code class="hljs matlab">Fs = <span class="hljs-number">16000</span>;<br>N = <span class="hljs-number">100</span>;<br>tau_delay = N/<span class="hljs-number">2</span> / Fs; <br><br>f_test = [<span class="hljs-number">1000</span>, <span class="hljs-number">2000</span>];<br><br>phase_I = <span class="hljs-number">-2</span>*<span class="hljs-built_in">pi</span> * f_test * tau_delay * <span class="hljs-number">180</span>/<span class="hljs-built_in">pi</span>; <br>phase_Q = <span class="hljs-number">-90</span> - <span class="hljs-number">2</span>*<span class="hljs-built_in">pi</span> * f_test * tau_delay * <span class="hljs-number">180</span>/<span class="hljs-built_in">pi</span>;<br><br>theory_diff = phase_Q - phase_I;<br>theory_diff = <span class="hljs-built_in">mod</span>(theory_diff + <span class="hljs-number">180</span>, <span class="hljs-number">360</span>) - <span class="hljs-number">180</span>; <span class="hljs-comment">% 归一化相位差</span><br><br>phase_I_norm = <span class="hljs-built_in">mod</span>(phase_I + <span class="hljs-number">180</span>, <span class="hljs-number">360</span>) - <span class="hljs-number">180</span>;<br>phase_Q_norm = <span class="hljs-built_in">mod</span>(phase_Q + <span class="hljs-number">180</span>, <span class="hljs-number">360</span>) - <span class="hljs-number">180</span>;<br><br>fprintf(<span class="hljs-string">'1000 Hz: I相位=%.2f°, Q相位=%.2f°, 差值=%.2f°\n'</span>, ...<br> phase_I_norm(<span class="hljs-number">1</span>), phase_Q_norm(<span class="hljs-number">1</span>), theory_diff(<span class="hljs-number">1</span>));<br>fprintf(<span class="hljs-string">'2000 Hz: I相位=%.2f°, Q相位=%.2f°, 差值=%.2f°\n'</span>, ...<br> phase_I_norm(<span class="hljs-number">2</span>), phase_Q_norm(<span class="hljs-number">2</span>), theory_diff(<span class="hljs-number">2</span>));<br></code></pre></td></tr></table></figure><figure><imgsrc="/2026/01/13/%E5%B8%8C%E5%B0%94%E4%BC%AF%E7%89%B9%E5%8F%98%E6%8D%A2%E7%94%9F%E6%88%90IQ%E4%BF%A1%E5%8F%B7/image-20260114125708274.png"alt="群延迟仿真输出" /><figcaption aria-hidden="true">群延迟仿真输出</figcaption></figure><p>嗯,和之前希尔伯特变换器不补偿群延迟的仿真对上了,说明理论没问题。</p>]]></content>
<categories>
<category>蒜苔</category>
</categories>
<tags>
<tag>嵌入式</tag>
<tag>数字信号处理</tag>
<tag>蒜苔</tag>
<tag>软件无线电</tag>
</tags>
</entry>
<entry>
<title>使用marp生成ppt</title>
<link href="/2026/01/01/%E4%BD%BF%E7%94%A8marp%E7%94%9F%E6%88%90ppt/"/>
<url>/2026/01/01/%E4%BD%BF%E7%94%A8marp%E7%94%9F%E6%88%90ppt/</url>
<content type="html"><![CDATA[<h3 id="前言">前言</h3><p>最近由于项目和自己整的一些小玩意的要求,需要频繁做ppt,但是笔者又没什么审美,做出来的很难看,格式反复改,经常出现一个PPT做两天的情况😭。而我自己审美本身就是一坨,经常出现改了好几天还是很丑。</p><p>那么,有没有什么办法把我们从这种状况下解救出来呢🧐,溜github的时候看到了这个玩意:<ahref="https://github.com/marp-team/marp">marp</a></p><figure><imgsrc="/2026/01/01/%E4%BD%BF%E7%94%A8marp%E7%94%9F%E6%88%90ppt/image-20260102193815610.png"alt="marp GitHub页面" /><figcaption aria-hidden="true">marp GitHub页面</figcaption></figure><p>很好,写markdown这活我很熟了。正巧这玩意还有对应的VS Code插件:</p><figure><imgsrc="/2026/01/01/%E4%BD%BF%E7%94%A8marp%E7%94%9F%E6%88%90ppt/image-20260102193928665.png"alt="Marp for VS Code" /><figcaption aria-hidden="true">Marp for VS Code</figcaption></figure><h2 id="基本使用">基本使用</h2><p>我们在VS code里面写几行小代码:</p><figure class="highlight markdown"><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><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs markdown">---<br>marp: true<br>title: 个人汇报<br>paginate: true<br><span class="hljs-section">theme: uncover</span><br><span class="hljs-section">---</span><br><span class="hljs-section"># XXX 工作汇报</span><br><span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">br</span>/></span></span><br><span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">br</span>/></span></span><br><span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">br</span>/></span></span><br><br><span class="hljs-bullet">-</span> 汇报人:Chuann<br><span class="hljs-section">- 日期:2025-1-2</span><br><span class="hljs-section">---</span><br><span class="hljs-section">## 工作总览</span><br><span class="hljs-bullet">-</span> 方向:负责XXXXX 的XXXXX工作<br><span class="hljs-bullet">-</span> 主要工作:<br><span class="hljs-bullet"> -</span> XXX项目硬件电路问题排查<br><span class="hljs-bullet"> -</span> XXXX实验方案设计<br><span class="hljs-bullet"> -</span> 为XXX模块新增了XXXX功能<br><br>---<br><span class="hljs-section"># <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">big</span>></span></span><span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">center</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"font-size: 200px;"</span>></span></span> 谢谢!<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">center</span>></span></span><span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">big</span>></span></span></span><br><!---<br>谢谢大家。<br>---><br></code></pre></td></tr></table></figure><p>代码里面,<code>---</code>是ppt的分页命令,开头是一些配置设置,主题什么的,其他写法和markdown一样,也支持嵌入html语法。</p><p>然后我们点这个按钮,打开实时预览:</p><figure><imgsrc="/2026/01/01/%E4%BD%BF%E7%94%A8marp%E7%94%9F%E6%88%90ppt/image-20260102195553136.png"alt="打开实时预览" /><figcaption aria-hidden="true">打开实时预览</figcaption></figure><p>右侧就能看到实时渲染的ppt了</p><figure><imgsrc="/2026/01/01/%E4%BD%BF%E7%94%A8marp%E7%94%9F%E6%88%90ppt/image-20260102195641735.png"alt="实时渲染ppt" /><figcaption aria-hidden="true">实时渲染ppt</figcaption></figure><p>比如我用html语法塞一张图片进去(markdown自己的图片插入语法也行,但我用html多一些):</p><figure class="highlight markdown"><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></pre></td><td class="code"><pre><code class="hljs markdown"><br><span class="hljs-section">## 工作总览</span><br><span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"image.png"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"400"</span> <span class="hljs-attr">align</span>=<span class="hljs-string">"right"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"margin-right: 20px;"</span>></span></span><br><br><span class="hljs-bullet">-</span> 方向:负责XXXXX 的XXXXX工作<br><span class="hljs-bullet">-</span> 主要工作:<br><span class="hljs-bullet"> -</span> XXX项目硬件电路问题排查<br><span class="hljs-bullet"> -</span> XXXX实验方案设计<br><span class="hljs-bullet"> -</span> 为XXX模块新增了XXXX功能<br><br></code></pre></td></tr></table></figure><p>渲染出来是这样的:</p><figure><imgsrc="/2026/01/01/%E4%BD%BF%E7%94%A8marp%E7%94%9F%E6%88%90ppt/image-20260102201751836.png"alt="插入图片" /><figcaption aria-hidden="true">插入图片</figcaption></figure><p>基本的用法就是这种,这么看,用marp还是要写代码,对于不熟悉markdown的朋友并不友好🤔。但是笔者认为marp这里代码生成ppt的工具,其最大价值主要是可以使用ai生成了,流程熟悉以后抽AI鞭子就行了🤓。</p><h3 id="进阶用法">进阶用法</h3><p>待续,后续会补充自定义主题以及导入主题,设置背景等用法</p>]]></content>
<categories>
<category>妙妙工具</category>
</categories>
<tags>
<tag>自动化</tag>
<tag>ppt</tag>
<tag>妙妙工具</tag>
</tags>
</entry>
<entry>
<title>K230-GNNE驱动移植</title>
<link href="/2025/12/25/K230-GNNE%E9%A9%B1%E5%8A%A8%E7%A7%BB%E6%A4%8D/"/>
<url>/2025/12/25/K230-GNNE%E9%A9%B1%E5%8A%A8%E7%A7%BB%E6%A4%8D/</url>
<content type="html"><![CDATA[<h2 id="硬件平台">硬件平台</h2><p>K230架构图如下:</p><figure><imgsrc="/2025/12/25/K230-GNNE%E9%A9%B1%E5%8A%A8%E7%A7%BB%E6%A4%8D/image-20251226114205250.png"alt="K230架构" /><figcaption aria-hidden="true">K230架构</figcaption></figure><p>黄框是AISubsystem,里面包含了KPU、AI2D、FFT以及SRAM,这里我们只考虑KPU和AI2D。</p><h3 id="gnne">GNNE</h3><p>GNNE,或者叫KPU,这两个在嘉楠手册的MemoryMap中是同一个地址,也就是是同一个外设。</p><p>KPU用于实现神经网络算子,K230的KPU目前支持:int8和int16的卷积、池化,LSTM,RELU等算子以及一些矩阵运算。但是Solfmax等算子还是得交给CPU进行计算。具体支持算子详见:<ahref="https://kendryte-download.canaan-creative.com/developer/k230/HDK/K230%E7%A1%AC%E4%BB%B6%E6%96%87%E6%A1%A3/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf">K230技术参考手册</a>4.2Function Description部分。</p><h3 id="ai2d">AI2D</h3><p>AI2D模块主要进行图像处理,可以对来自CPU或者KPU的数据进行缩放、仿射变化、裁剪、填充等。</p><h2 id="驱动架构设计">驱动架构设计</h2><p>查阅<ahref="https://kendryte-download.canaan-creative.com/developer/k230/HDK/K230%E7%A1%AC%E4%BB%B6%E6%96%87%E6%A1%A3/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf">K230技术参考手册</a>,会发现一个问题,嘉楠并没有提供KPU的寄存器手册,和工程师沟通后,了解到GNNE的驱动分为两部分,一部分包含锁以及轮询等待,另一部分由NNCASE实现,直接在用户态进行IO内存映射。因此,想完成在K230上运行yolo之类的神经网络模型,需要三部分:NNCASE、MMZ驱动、GNNE(KPU+AI2D)驱动。</p><h3 id="nncase">NNCASE</h3><p>NNCASE是嘉楠开发的神经网络编译器,支持CPU和KPU,AI2D作为一个子模块包含在里面的。nncase大致分为两部分,一个是编译器compiler和runtime。</p><p>由于笔者在驱动移植过程中主要负责bsp部分,nncase并没有深入研究,使用方法详见:<ahref="https://www.kendryte.com/k230/dev/01_software/board/ai/K230_nncase_%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97.html">K230nncase开发指南</a>,嘉楠镜像中也有设计好的的demo:<ahref="https://www.kendryte.com/k230_rtos/zh/main/app_develop_guide/ai/index.html">AI应用开发</a>,在<code>menuconfig</code>中<code>K230 SDK Project Configuration ->RT-Smart Configuration -> Enable build Rtsmart examples</code>中勾选即可:</p><figure><imgsrc="/2025/12/25/K230-GNNE%E9%A9%B1%E5%8A%A8%E7%A7%BB%E6%A4%8D/image-20251226173937361.png"alt="嘉楠镜像使能ai demo" /><figcaption aria-hidden="true">嘉楠镜像使能ai demo</figcaption></figure><p>烧录后进入系统,<code>cd app/examples</code>进入工程目录执行对应历程的脚本或者elf即可。</p><h3 id="mmz驱动">MMZ驱动</h3><p>驱动写好后,直接运行demo会报一个很奇怪的错误:</p><figure><imgsrc="/2025/12/25/K230-GNNE%E9%A9%B1%E5%8A%A8%E7%A7%BB%E6%A4%8D/image-20251226174641753.png"alt="image-20251226174641753" /><figcaption aria-hidden="true">image-20251226174641753</figcaption></figure><p>大致原因似乎是<code>gp</code>指针为0,然后有一个<code>gp-0x800</code>的寻址,导致指向了0xfffffffffffff800这个内核空间,并不是驱动报错。</p><p>和嘉楠工程师沟通后,发现缺少一个mmz驱动,具体在这里:<ahref="https://github.com/canmv-k230/mpp">mpp驱动</a>,mmz是整个驱动下面的一个子模块。mmz驱动并没开源,嘉楠提供的是一个静态库,简单看了一下mmz的使用demo:</p><p>驱动的作用是分配非缓存内存以及带缓存的内存,以及物理/虚拟地址映射。大致猜测了一下nncase为什么会对mmz有依赖:</p><ul><li>硬件加速器(GNNE、AI2D)需要物理地址进行 DMA 传输</li><li>需要大块连续物理内存</li><li>用户空间和硬件之间零拷贝共享数据</li><li>NNCASE需要在用户态进行io内存映射</li></ul><h3 id="gnnekpu驱动">GNNE(KPU)驱动</h3><p>之前提到,具体的算子实现以及内存分配由NNCASE和MMZ实现了,在GNNE驱动中,只需要实现阻塞和锁以及中断就行了。所以,对于GNNE和AI2D驱动的ops,需要有如下功能:<code>open</code>、<code>close</code>、<code>poll</code>、<code>control</code>,仔细看rtt设备子系统的结构体<strong><code>rt_device_ops</code></strong>:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_device_ops</span></span><br><span class="hljs-class">{</span><br> <span class="hljs-comment">/* common device interface */</span><br> <span class="hljs-type">rt_err_t</span> (*init) (<span class="hljs-type">rt_device_t</span> dev);<br> <span class="hljs-type">rt_err_t</span> (*open) (<span class="hljs-type">rt_device_t</span> dev, <span class="hljs-type">rt_uint16_t</span> oflag);<br> <span class="hljs-type">rt_err_t</span> (*close) (<span class="hljs-type">rt_device_t</span> dev);<br> <span class="hljs-type">rt_ssize_t</span> (*read) (<span class="hljs-type">rt_device_t</span> dev, <span class="hljs-type">rt_off_t</span> pos, <span class="hljs-type">void</span> *buffer, <span class="hljs-type">rt_size_t</span> size);<br> <span class="hljs-type">rt_ssize_t</span> (*write) (<span class="hljs-type">rt_device_t</span> dev, <span class="hljs-type">rt_off_t</span> pos, <span class="hljs-type">const</span> <span class="hljs-type">void</span> *buffer, <span class="hljs-type">rt_size_t</span> size);<br> <span class="hljs-type">rt_err_t</span> (*control)(<span class="hljs-type">rt_device_t</span> dev, <span class="hljs-type">int</span> cmd, <span class="hljs-type">void</span> *args);<br>};<br></code></pre></td></tr></table></figure><p>有<code>open</code>,<code>close</code>,<code>control</code>,但是没有<code>poll</code>,所以把GNNE当作一个设备子系统注册,<code>dfs_file_ops</code>恰好能满足这一点:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">dfs_file_ops</span></span><br><span class="hljs-class">{</span><br> <span class="hljs-type">int</span> (*open)(<span class="hljs-keyword">struct</span> dfs_file *file);<br> <span class="hljs-type">int</span> (*close)(<span class="hljs-keyword">struct</span> dfs_file *file);<br> <span class="hljs-type">int</span> (*ioctl)(<span class="hljs-keyword">struct</span> dfs_file *file, <span class="hljs-type">int</span> cmd, <span class="hljs-type">void</span> *arg);<br> <span class="hljs-type">ssize_t</span> (*read)(<span class="hljs-keyword">struct</span> dfs_file *file, <span class="hljs-type">void</span> *buf, <span class="hljs-type">size_t</span> count, <span class="hljs-type">off_t</span> *pos);<br> <span class="hljs-type">ssize_t</span> (*write)(<span class="hljs-keyword">struct</span> dfs_file *file, <span class="hljs-type">const</span> <span class="hljs-type">void</span> *buf, <span class="hljs-type">size_t</span> count, <span class="hljs-type">off_t</span> *pos);<br> <span class="hljs-type">int</span> (*flush)(<span class="hljs-keyword">struct</span> dfs_file *file);<br> <span class="hljs-type">off_t</span> (*lseek)(<span class="hljs-keyword">struct</span> dfs_file *file, <span class="hljs-type">off_t</span> offset, <span class="hljs-type">int</span> wherece);<br> <span class="hljs-type">int</span> (*truncate)(<span class="hljs-keyword">struct</span> dfs_file *file, <span class="hljs-type">off_t</span> offset);<br> <span class="hljs-type">int</span> (*getdents)(<span class="hljs-keyword">struct</span> dfs_file *file, <span class="hljs-keyword">struct</span> dirent *dirp, <span class="hljs-type">uint32_t</span> count);<br> <span class="hljs-type">int</span> (*poll)(<span class="hljs-keyword">struct</span> dfs_file *file, <span class="hljs-keyword">struct</span> rt_pollreq *req);<br><br> <span class="hljs-type">int</span> (*mmap)(<span class="hljs-keyword">struct</span> dfs_file *file, <span class="hljs-keyword">struct</span> lwp_avl_struct *mmap);<br> <span class="hljs-type">int</span> (*lock)(<span class="hljs-keyword">struct</span> dfs_file *file, <span class="hljs-keyword">struct</span> file_lock *flock);<br> <span class="hljs-type">int</span> (*flock)(<span class="hljs-keyword">struct</span> dfs_file *file, <span class="hljs-type">int</span>, <span class="hljs-keyword">struct</span> file_lock *flock);<br>};<br></code></pre></td></tr></table></figure><p>dfs文件系统的ops提供了<code>ioctl</code>可以实现硬件层面的锁,<code>poll</code>可以实现阻塞等待,因此,GNNE被当作一个文件注册驱动。</p><h2 id="实现细节">实现细节</h2><p>GNNE和AI2D的驱动基本完全相同,虽然K230手册中提供了AI2D模块的寄存器,但是事实上AI2D的内存映射还是在NNCASE中完成的,驱动只是提供阻塞和锁等等。由于AI2D的实现和GNNE基本完全一致,这里不再讨论。</p><h3 id="gnne驱动实现">GNNE驱动实现</h3><p>首先需要在驱动里实现<code>dfs_files_ops</code>这个结构体:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">dfs_file_ops</span> <span class="hljs-title">gnne_input_fops</span> =</span><br>{<br> .open = gnne_device_open,<br> .close = gnne_device_close,<br> .ioctl = gnne_device_ioctl,<br> .poll = gnne_device_poll,<br>};<br></code></pre></td></tr></table></figure><p><code>gnne_device_open</code>函数用于为每个线程创建独立的句柄,获取设备对象并保存队列等待指针,初始化锁状态为未锁定,初始化锁,最后将句柄绑定到文件描述符。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">gnne_device_open</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> dfs_file *file)</span><br>{<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">gnne_dev_handle</span> *<span class="hljs-title">handle</span>;</span><br> <span class="hljs-type">rt_device_t</span> device;<br><br> handle = rt_malloc(<span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> gnne_dev_handle));<br> <span class="hljs-keyword">if</span> (handle == RT_NULL)<br> {<br> gnne_err(<span class="hljs-string">"malloc failed\n"</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br> }<br> device = (<span class="hljs-type">rt_device_t</span>)file->vnode->data;<br> handle->wait = &device->wait_queue;<br> handle->is_lock = RT_FALSE;<br> file->data = (<span class="hljs-type">void</span> *)handle;<br> <span class="hljs-keyword">return</span> RT_EOK;<br>}<br></code></pre></td></tr></table></figure><p><code>gnne_dev_handle</code>结构体有两个成员,<code>is_lock</code>是锁,而<code>wait</code>是阻塞等待队列。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">gnne_dev_handle</span></span><br><span class="hljs-class">{</span><br> <span class="hljs-type">rt_wqueue_t</span> *wait;<br> <span class="hljs-type">rt_bool_t</span> is_lock;<br>};<br></code></pre></td></tr></table></figure><p><code>gnne_device_close</code>函数用于设备的关闭,在用户空间调用<code>close(fd)</code> 时被触发,负责清理资源并释放硬件锁。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">gnne_device_close</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> dfs_file *file)</span><br>{<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">gnne_dev_handle</span> *<span class="hljs-title">handle</span>;</span><br><br> handle = (<span class="hljs-keyword">struct</span> gnne_dev_handle *)file->data;<br> <span class="hljs-keyword">if</span> (handle == RT_NULL)<br> {<br> gnne_err(<span class="hljs-string">"try to close a invalid handle"</span>);<br> <span class="hljs-keyword">return</span> -RT_EINVAL;<br> }<br> <span class="hljs-keyword">if</span> (handle->is_lock)<br> {<br> kd_hardlock_unlock(g_kpu_lock);<br> }<br> rt_free(handle);<br> file->data = RT_NULL;<br> <span class="hljs-keyword">return</span> RT_EOK;<br>}<br></code></pre></td></tr></table></figure><p>锁的实现在<code>gnne_device_ioctl</code>中,用户空间通过对<code>ioctl()</code>的调用,实现 GNNE硬件的访问控制。GNNE驱动的锁控制有三种模式:<code>GNNE_CMD_LOCK</code>用于实现自旋锁,<code>GNNE_CMD_UNLOCK</code>实现对硬件的解锁,而<code>GNNE_CMD_TRYLOCK</code>只尝试尝试一次上锁,并不进行自旋,所有的锁实现后,把值赋给每个线程字节的锁<code>gnne_dev_handle->is_lock</code>。</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><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">gnne_device_ioctl</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> dfs_file *file, <span class="hljs-type">int</span> cmd, <span class="hljs-type">void</span> *args)</span><br>{<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">gnne_dev_handle</span> *<span class="hljs-title">handle</span>;</span><br> <span class="hljs-type">int</span> ret = <span class="hljs-number">-1</span>;<br> handle = (<span class="hljs-keyword">struct</span> gnne_dev_handle *)file->data;<br> <span class="hljs-keyword">if</span> ((g_kpu_lock == HARDLOCK_MAX)){<br> <span class="hljs-keyword">return</span> ret;<br> }<br> <span class="hljs-keyword">if</span> (cmd == GNNE_CMD_LOCK){<br> <span class="hljs-keyword">if</span> (handle->is_lock == RT_TRUE){<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> }<br> <span class="hljs-keyword">while</span>(kd_hardlock_lock(g_kpu_lock));<br> handle->is_lock = RT_TRUE;<br> ret = <span class="hljs-number">0</span>;<br> }<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (cmd == GNNE_CMD_UNLOCK){<br> <span class="hljs-keyword">if</span> (handle->is_lock == RT_FALSE){<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> }<br> kd_hardlock_unlock(g_kpu_lock);<br> handle->is_lock = RT_FALSE;<br> ret = <span class="hljs-number">0</span>;<br> }<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (cmd == GNNE_CMD_TRYLOCK){<br> <span class="hljs-keyword">if</span> (handle->is_lock == RT_TRUE){<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> }<br> <span class="hljs-keyword">if</span> (!kd_hardlock_lock(g_kpu_lock)){<br> handle->is_lock = RT_TRUE;<br> ret = <span class="hljs-number">0</span>;<br> }<br> }<br> <span class="hljs-keyword">return</span> ret;<br>}<br></code></pre></td></tr></table></figure><p>如果学过操作系统关于竞争与冒险这一部分,应该知道,上锁的语句必须是原子性的,<code>kd_hardlock_lock(g_kpu_lock)</code>的实现如下(<code>hardlock/drv_hardlock.c</code>):</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">kd_hardlock_lock</span><span class="hljs-params">(hardlock_type num)</span><br>{<br> <span class="hljs-keyword">if</span>(num < <span class="hljs-number">0</span> || num >= HARDLOCK_MAX)<br> <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br> <span class="hljs-keyword">if</span>(!readl(hardlock.hw_base + num * <span class="hljs-number">0x4</span>)){<br> LOG_D(<span class="hljs-string">"hardlock-%d locked\n"</span>, num);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> }<br> LOG_D(<span class="hljs-string">"hardlock-%d is busy\n"</span>, num);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br>}<br></code></pre></td></tr></table></figure><p>核心是通过<code>readl(hardlock.hw_base + num * 0x4)</code>这个原子性的操作实现上锁,<code>g_kpu_lock</code>在<code>hardlock/drv_hardlock.h</code>被定义为2,<code>hardlock.hw_base</code>指向Mailbox基地址的<code>0x00A0</code>,也就是这个寄存器:<imgsrc="/2025/12/25/K230-GNNE%E9%A9%B1%E5%8A%A8%E7%A7%BB%E6%A4%8D/image-20251226213106191.png"alt="image-20251226213106191" /></p><p>通过<code>num*0x04</code>进行32位寄存器的寻址,如果读到是0,则为上锁。相比于<code>kd_hardlock_lock(hardlock_type num)</code>,<code>kd_hardlock_unlock(hardlock_type num)</code>多了一句写0解锁的操作。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">kd_hardlock_unlock</span><span class="hljs-params">(hardlock_type num)</span><br>{<br> <span class="hljs-keyword">if</span>(num < <span class="hljs-number">0</span> || num >= HARDLOCK_MAX)<br> <span class="hljs-keyword">return</span>;<br><br> <span class="hljs-keyword">if</span>(readl(hardlock.hw_base + num * <span class="hljs-number">0x4</span>))<br> {<br> writel(<span class="hljs-number">0x0</span>, hardlock.hw_base + num * <span class="hljs-number">0x4</span>);<br> }<br> LOG_D(<span class="hljs-string">"hardlock-%d unlock\n"</span>, num);<br>}<br></code></pre></td></tr></table></figure><p><code>gnne_device_poll</code>实现了GNNE的<code>poll</code>接口,调用<code>gnne_input_fops.poll</code>后,调用<code>rt_event_recv</code>进入阻塞,释放CPU,等待GNNE硬件完成计算,中断唤醒这个任务。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">gnne_device_poll</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> dfs_file *file, <span class="hljs-keyword">struct</span> rt_pollreq *req)</span><br>{<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">gnne_dev_handle</span> *<span class="hljs-title">handle</span>;</span><br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> flags;<br> handle = (<span class="hljs-keyword">struct</span> gnne_dev_handle *)file->data;<br> <span class="hljs-keyword">if</span> (!handle)<br> {<br> gnne_err(<span class="hljs-string">"gnne_dev_handle NULL!"</span>);<br> <span class="hljs-keyword">return</span> -EINVAL;<br> }<br> rt_event_recv(&g_gnne_event, <span class="hljs-number">0x01</span>, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, <span class="hljs-literal">NULL</span>);<br> rt_poll_add(handle->wait, req);<br> <span class="hljs-keyword">return</span> POLLIN;<br>}<br></code></pre></td></tr></table></figure><p>唤醒的事件来自<code>irq_callback</code>,设备就绪的中断发出后,中断回调函数清空标志位,唤醒等待队列并发送事件,唤醒阻塞中的<code>gnne_device_poll</code>。其中,<code>__iowmb()</code>是内存屏障,确保在清除中断标志之前,所有之前的内存操作都已完成,防止编译器或CPU 重排序导致中断标志过早清除。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">void</span> <span class="hljs-title function_">irq_callback</span><span class="hljs-params">(<span class="hljs-type">int</span> irq, <span class="hljs-type">void</span> *data)</span><br>{<br> <span class="hljs-type">rt_wqueue_t</span> *wait = (<span class="hljs-type">rt_wqueue_t</span> *)data;<br> <span class="hljs-keyword">volatile</span> <span class="hljs-type">void</span> *write_addr = (<span class="hljs-type">void</span> *)((<span class="hljs-type">char</span> *)gnne_base_addr + <span class="hljs-number">0x128</span>);<br> <span class="hljs-keyword">if</span> (gnne_base_addr == RT_NULL)<br> {<br> gnne_err(<span class="hljs-string">"gnne interrupts while the hardware is not yet initialized\n"</span>);<br> }<br> <span class="hljs-comment">/*clear kpu intr*/</span><br> __iowmb();<br> *(<span class="hljs-type">rt_uint64_t</span> *)write_addr = <span class="hljs-number">0x400000004</span>;<br> rt_wqueue_wakeup(wait, (<span class="hljs-type">void</span> *)POLLIN);<br> rt_event_send(&g_gnne_event, <span class="hljs-number">0x1</span>);<br>}<br></code></pre></td></tr></table></figure><p>整体调用流程大致如下:</p><pre><code class=" mermaid">sequenceDiagram participant User as 用户进程 participant Poll as gnne_device_poll() participant Event as rt_event_recv() participant HW as GNNE 硬件 participant IRQ as irq_callback() User->>Poll: 调用 poll() Poll->>Event: rt_event_recv() 阻塞等待 Note over Event: 等待事件 g_gnne_event HW->>HW: 完成计算 HW->>IRQ: 触发中断 IRQ->>IRQ: 清除中断标志<br/>*(0x128) = 0x400000004 IRQ->>Event: rt_event_send(g_gnne_event) Note over Event: 事件到达,唤醒 Event-->>Poll: 返回 Poll->>Poll: rt_poll_add() 注册等待队列 Poll-->>User: return POLLIN Note over User: poll() 返回,数据可读</code></pre><p>需要注意的是,这个实现与标准的 poll 机制有所不同:</p><ul><li>标准 poll:先注册等待队列,如果没有数据则返回0,有数据时通过等待队列唤醒</li><li>这个实现:先通过 <code>rt_event_recv</code>同步等待,等待完成后才注册等待队列</li></ul><p>这种设计可能是因为: 1.实际的同步机制完全依赖事件机制(<code>rt_event_recv</code> /<code>rt_event_send</code>) 2. <code>rt_poll_add</code> 只是为了满足poll 框架的接口要求 3. 具体的时序要求与 NNCASE 的调用方式有关</p><p>这使得 poll调用总是阻塞的,更像是一个”同步等待硬件完成”的接口,而不是传统的非阻塞轮询。</p>]]></content>
<categories>
<category>K230-rt-smart</category>
</categories>
<tags>
<tag>K230</tag>
<tag>rt-smart</tag>
<tag>嵌入式</tag>
<tag>bsp</tag>
<tag>gnne</tag>
</tags>
</entry>
<entry>
<title>K230-SPI驱动移植</title>
<link href="/2025/12/18/K230-SPI%E9%A9%B1%E5%8A%A8%E7%A7%BB%E6%A4%8D/"/>
<url>/2025/12/18/K230-SPI%E9%A9%B1%E5%8A%A8%E7%A7%BB%E6%A4%8D/</url>
<content type="html"><![CDATA[<h2 id="spi协议简述">SPI协议简述</h2><h3 id="标准spi">标准SPI</h3><p>SPI(Serial PeripheralInterface)是一种高速、全双工的同步串行通信协议,广泛应用于微控制器与外设(如Flash、传感器、显示屏)之间的通信。</p><p>标准SPI时序如下(CPOL = 1,CPHA = 0):</p><figure><imgsrc="/2025/12/18/K230-SPI%E9%A9%B1%E5%8A%A8%E7%A7%BB%E6%A4%8D/image-20251228001533771.png"alt="标准SPI时序图" /><figcaption aria-hidden="true">标准SPI时序图</figcaption></figure><p>注:图中没有标出如下的建立/保持时间,这些详细时序要求可以参考芯片手册。</p><ul><li><spanclass="math inline"><em>t</em><sub><em>C</em><em>S</em><em>S</em></sub></span>:CS建立时间(CS下降沿到第一个时钟上升沿)</li><li><spanclass="math inline"><em>t</em><sub><em>C</em><em>S</em><em>H</em></sub></span>:CS保持时间(最后时钟下降沿到CS上升沿)</li><li><spanclass="math inline"><em>t</em><sub><em>S</em><em>U</em></sub></span>:数据建立时间(数据稳定到时钟采样沿)</li><li><span class="math inline"><em>t</em><sub><em>H</em></sub></span>:数据保持时间(时钟采样沿后数据保持稳定)</li></ul><p>SPI有4种工作模式,由两个参数决定:</p><ul><li><strong>CPOL(时钟极性)</strong>:空闲时时钟线的电平(0=低电平,1=高电平)</li><li><strong>CPHA(时钟相位)</strong>:数据采样时刻(0=第一个边沿,1=第二个边沿)</li></ul><table><thead><tr><th style="text-align: left;">模式</th><th style="text-align: left;">CPOL</th><th style="text-align: left;">CPHA</th><th style="text-align: left;">空闲时钟</th><th style="text-align: left;">采样边沿</th><th style="text-align: left;">输出边沿</th></tr></thead><tbody><tr><td style="text-align: left;">0</td><td style="text-align: left;">0</td><td style="text-align: left;">0</td><td style="text-align: left;">低电平</td><td style="text-align: left;">上升沿</td><td style="text-align: left;">下降沿</td></tr><tr><td style="text-align: left;">1</td><td style="text-align: left;">0</td><td style="text-align: left;">1</td><td style="text-align: left;">低电平</td><td style="text-align: left;">下降沿</td><td style="text-align: left;">上升沿</td></tr><tr><td style="text-align: left;">2</td><td style="text-align: left;">1</td><td style="text-align: left;">0</td><td style="text-align: left;">高电平</td><td style="text-align: left;">下降沿</td><td style="text-align: left;">上升沿</td></tr><tr><td style="text-align: left;">3</td><td style="text-align: left;">1</td><td style="text-align: left;">1</td><td style="text-align: left;">高电平</td><td style="text-align: left;">上升沿</td><td style="text-align: left;">下降沿</td></tr></tbody></table><p><strong>模式0和模式3最常用</strong>,大多数SPI设备支持其中之一。主从设备必须使用相同的模式才能正常通信。</p><p>详细时序如下(时钟建立时间不准)</p><figure><imgsrc="/2025/12/18/K230-SPI%E9%A9%B1%E5%8A%A8%E7%A7%BB%E6%A4%8D/image-20251228002119199.png"alt="SPI工作模式" /><figcaption aria-hidden="true">SPI工作模式</figcaption></figure><p>标准SPI虽然简单可靠,但在现代应用中面临明显的带宽瓶颈。由于采用单线传输方式,MOSI和MISO各占一根线进行半双工数据传输,即使时钟频率达到50MHz,单线传输速率也仅有6.25MB/s。这在大容量Flash应用中尤为突出:从128MBFlash读取1MB数据需要约160ms,系统启动时加载数MB固件会产生明显延迟,而实时应用(如视频、图像处理)更是无法满足带宽需求。</p><h3 id="增强型spi">增强型SPI</h3><p>一个最简单的办法是增加数据线线,让传输并行化,因此诞生了DualSPI(双线)、Quad SPI(四线)以及OctalSPI(8线)。标准SPI采用全双工设计,MOSI和MISO是独立的单向线路,可同时收发数据。而DSPI/QSPI/OSPI采用半双工设计,数据线是双向复用的,同一时刻只能进行读或写操作。这种设计牺牲了全双工能力,但通过增加并行数据线大幅提升了单向传输带宽。</p><p>三种增强型SPI协议传输可以分成三个阶段:命令、地址、数据,以QSPI为例,其传输时序为:</p><figure><imgsrc="/2025/12/18/K230-SPI%E9%A9%B1%E5%8A%A8%E7%A7%BB%E6%A4%8D/image-20251228190734725.png"alt="QSPI发送模式" /><figcaption aria-hidden="true">QSPI发送模式</figcaption></figure><p>图中,命令为4bit,单线传输;地址和数据都是8bit,4线传输。实际上,命令线和地址线都可以用1/2/4/8线传输(也就是向下兼容),或者省略,进行数据阶段的传输。</p><h2 id="硬件平台">硬件平台</h2><p>从K230芯片架构可以看出,K230有一个Octal SPI控制器(OPI)和两个QuadSPI控制器(QPI)。</p><figure><imgsrc="/2025/12/18/K230-SPI%E9%A9%B1%E5%8A%A8%E7%A7%BB%E6%A4%8D/image-20251226114205250.png"alt="K230芯片架构" /><figcaption aria-hidden="true">K230芯片架构</figcaption></figure><p>对于OPI,其支持1/2/4/8线传输模式,最大时钟频率为200MHz。而QPI控制器支持1/2/4线传输模式,最大频率为100MHz。</p><p>需要注意的是,K230的SPI控制器内部集成了一套自己的DMA(手册里叫Internal DMA),用来直接在SPI与内存之间搬运数据。它和系统外设DMA控制器PDMA是两套独立的硬件,互不复用通道,所以SPI的DMA传输不需要走统一的PDMA框架,对PDMA驱动也没有依赖。</p><h2 id="驱动架构设计">驱动架构设计</h2><h3 id="配置部分">配置部分</h3><p>对于RT-smart而言,其内核中有两个和SPI相关的组件:SPI和QSPI,SPI的基础配置结构体<code>rt_spi_configuration</code>定义了标准SPI的核心参数,而QSPI配置结构体<code>rt_qspi_configuration</code>在标准SPI基础上扩展了多线传输能力</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><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * @brief SPI configuration structure</span><br><span class="hljs-comment"> */</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_spi_configuration</span></span><br><span class="hljs-class">{</span><br> <span class="hljs-type">rt_uint8_t</span> mode;<br> <span class="hljs-type">rt_uint8_t</span> data_width;<br><span class="hljs-meta">#<span class="hljs-keyword">ifdef</span> RT_USING_DM</span><br> <span class="hljs-type">rt_uint8_t</span> data_width_tx;<br> <span class="hljs-type">rt_uint8_t</span> data_width_rx;<br><span class="hljs-meta">#<span class="hljs-keyword">else</span></span><br> <span class="hljs-type">rt_uint16_t</span> reserved;<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br> <span class="hljs-type">rt_uint32_t</span> max_hz;<br>};<br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * @brief QSPI configuration structure</span><br><span class="hljs-comment"> */</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_qspi_configuration</span></span><br><span class="hljs-class">{</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_spi_configuration</span> <span class="hljs-title">parent</span>;</span><br> <span class="hljs-comment">/* The size of medium */</span><br> <span class="hljs-type">rt_uint32_t</span> medium_size;<br> <span class="hljs-comment">/* double data rate mode */</span><br> <span class="hljs-type">rt_uint8_t</span> ddr_mode;<br> <span class="hljs-comment">/* the data lines max width which QSPI bus supported, such as 1, 2, 4 */</span><br> <span class="hljs-type">rt_uint8_t</span> qspi_dl_width ;<br>};<br></code></pre></td></tr></table></figure><p>这种继承设计使得QSPI配置向下兼容标准SPI,同时<code>qspi_dl_width</code>支持8线参数,因此K230的所有SPI控制器(包括OPI和QPI)都可以注册为QSPI总线。</p><p>在RT-smart中,QSPI和SPI设备共用同一套操作接口<code>rt_spi_ops</code>:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * @brief SPI operators</span><br><span class="hljs-comment"> */</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_spi_ops</span></span><br><span class="hljs-class">{</span><br> <span class="hljs-type">rt_err_t</span> (*configure)(<span class="hljs-keyword">struct</span> rt_spi_device *device, <span class="hljs-keyword">struct</span> rt_spi_configuration *configuration);<br> <span class="hljs-type">rt_ssize_t</span> (*xfer)(<span class="hljs-keyword">struct</span> rt_spi_device *device, <span class="hljs-keyword">struct</span> rt_spi_message *message);<br>};<br></code></pre></td></tr></table></figure><p>驱动开发的核心工作就是实现这两个函数指针:</p><ul><li><code>configure</code>:根据配置参数初始化硬件寄存器</li><li><code>xfer</code>:执行实际的数据传输操作</li></ul><p>由于QSPI和标准SPI兼容同一套接口,<code>rt_spi_ops</code>内部两个虚函数参数用的是<code>rt_spi_device</code>,<code>rt_spi_configuration</code>和<code>rt_spi_message</code>。因此,驱动层需要利用<code>rt_qspi_device->parent</code>和<code>rt_spi_device</code>首地址相同的特性进行强制转换:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">rt_err_t</span> <span class="hljs-title function_">drv_spi_configure</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> rt_spi_device *device, <span class="hljs-keyword">struct</span> rt_spi_configuration *configuration)</span><br>{<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_qspi_device</span> *<span class="hljs-title">qspi_device</span> =</span> (<span class="hljs-keyword">struct</span> rt_qspi_device *)device;<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_qspi_configuration</span> *<span class="hljs-title">qspi_cfg</span> =</span> &qspi_device->config;<br> <span class="hljs-comment">// ...</span><br>}<br><span class="hljs-type">rt_ssize_t</span> <span class="hljs-title function_">drv_spi_xfer</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> rt_spi_device *device, <span class="hljs-keyword">struct</span> rt_spi_message *message)</span><br>{<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_qspi_device</span> *<span class="hljs-title">qspi_device</span> =</span> (<span class="hljs-keyword">struct</span> rt_qspi_device *)device;<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_qspi_message</span> *<span class="hljs-title">qspi_msg</span> =</span> (<span class="hljs-keyword">struct</span> rt_qspi_message *)message;<br> <span class="hljs-comment">// ...</span><br>}<br></code></pre></td></tr></table></figure><p>下面看看应用层调用QSPI总线的流程,需要做哪些配置,使用了哪些函数,以及用户的参数是如何从应用层传递到驱动层的ops吧。详细代码参考:<ahref="https://github.com/RT-Thread/rt-thread/blob/master/bsp/k230/drivers/utest/test_spi.c">K230SPI utest</a></p><p>抛开GPIO的功能配置以及输入输出模式选择,首先需要找到已注册的SPI总线,然后将设备挂载到总线上。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* Find QSPI Bus */</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_spi_bus</span> *<span class="hljs-title">spi_bus</span> =</span> (<span class="hljs-keyword">struct</span> rt_spi_bus *)rt_device_find(SPI0_BUS_NAME);<br>qspi_dev = (<span class="hljs-keyword">struct</span> rt_qspi_device *)rt_malloc(<span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> rt_qspi_device));<br><span class="hljs-comment">/* Attach SPI Device */</span><br>ret = rt_spi_bus_attach_device(&(qspi_dev->parent), SPI0_DEV_NAME0, SPI0_BUS_NAME, RT_NULL);<br></code></pre></td></tr></table></figure><p>由于RT-smart不支持直接访问SPI总线,因此应用层必须绑定设备,以设备的形式进行总线操作。这种设计允许一条总线上挂载多个设备,每个设备可以有独立的配置参数。</p><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* SPI Device Config*/</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_qspi_configuration</span> <span class="hljs-title">qspi_cfg</span>;</span><br>qspi_cfg.parent.mode = RT_SPI_MODE_0 | RT_SPI_MSB;<br>qspi_cfg.parent.data_width = <span class="hljs-number">8</span>;<br>qspi_cfg.parent.max_hz = <span class="hljs-number">1000000</span>;<br>qspi_cfg.parent.reserved = <span class="hljs-number">0</span>;<br>qspi_cfg.qspi_dl_width = <span class="hljs-number">1</span>;<br>qspi_cfg.medium_size = <span class="hljs-number">0</span>;<br>qspi_cfg.ddr_mode = <span class="hljs-number">0</span>;<br><br>ret = rt_qspi_configure(qspi_dev, &qspi_cfg);<br></code></pre></td></tr></table></figure><p>核心是<code>rt_qspi_configure</code>这个函数,它的实现在<ahref="https://github.com/RT-Thread/rt-thread/blob/master/components/drivers/spi/dev_qspi_core.c">dev_qspi_core.c</a>中</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><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">rt_err_t</span> <span class="hljs-title function_">rt_qspi_configure</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> rt_qspi_device *device, <span class="hljs-keyword">struct</span> rt_qspi_configuration *cfg)</span><br>{<br> RT_ASSERT(device != RT_NULL);<br> RT_ASSERT(cfg != RT_NULL);<br><br> <span class="hljs-comment">/* reset the CS pin */</span><br> ...............................<br> <span class="hljs-comment">/* If the configurations are the same, we don't need to set again. */</span><br> <span class="hljs-keyword">if</span> (device->config.medium_size == cfg->medium_size &&<br> device->config.ddr_mode == cfg->ddr_mode &&<br> device->config.qspi_dl_width == cfg->qspi_dl_width &&<br> device->config.parent.data_width == cfg->parent.data_width &&<br> device->config.parent.mode == (cfg->parent.mode & RT_SPI_MODE_MASK) &&<br> device->config.parent.max_hz == cfg->parent.max_hz)<br> {<br> <span class="hljs-keyword">return</span> RT_EOK;<br> }<br> <span class="hljs-comment">/* copy configuration items */</span><br> device->config.parent.mode = cfg->parent.mode;<br> device->config.parent.max_hz = cfg->parent.max_hz;<br> device->config.parent.data_width = cfg->parent.data_width;<br><span class="hljs-meta">#<span class="hljs-keyword">ifdef</span> RT_USING_DM</span><br> device->config.parent.data_width_tx = cfg->parent.data_width_tx;<br> device->config.parent.data_width_rx = cfg->parent.data_width_rx;<br><span class="hljs-meta">#<span class="hljs-keyword">else</span></span><br> device->config.parent.reserved = cfg->parent.reserved;<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br> device->config.medium_size = cfg->medium_size;<br> device->config.ddr_mode = cfg->ddr_mode;<br> device->config.qspi_dl_width = cfg->qspi_dl_width;<br><br> <span class="hljs-keyword">return</span> rt_spi_bus_configure(&device->parent);<br>}<br></code></pre></td></tr></table></figure><p>上面的代码我省略了软件CS这一部分,<code>rt_qspi_configure</code>中,对比了<code>rt_qspi_device->config</code>和<code>rt_qspi_configuration</code>的新旧版配置,避免重复配置硬件,将用户配置保存到<code>device->config</code>中,调用底层总线配置函数</p><p><code>rt_qspi_device</code>的定义展示了设备、配置和总线的关系:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * @brief QSPI operators</span><br><span class="hljs-comment"> */</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_qspi_device</span></span><br><span class="hljs-class">{</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_spi_device</span> <span class="hljs-title">parent</span>;</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_qspi_configuration</span> <span class="hljs-title">config</span>;</span><br> <span class="hljs-type">void</span> (*enter_qspi_mode)(<span class="hljs-keyword">struct</span> rt_qspi_device *device);<br> <span class="hljs-type">void</span> (*exit_qspi_mode)(<span class="hljs-keyword">struct</span> rt_qspi_device *device);<br>};<br></code></pre></td></tr></table></figure><p><code>rt_qspi_device</code>的父类是<code>rt_spi_device</code>,而它自己也有一个qspi配置结构体<code>rt_qspi_configuration</code>,再看<code>rt_spi_bus_configure</code>这个函数,它的实现在:<ahref="https://github.com/RT-Thread/rt-thread/blob/master/components/drivers/spi/dev_spi_core.c">dev_spi_core.c</a>,这是配置流程的最后一环</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><code class="hljs c"><span class="hljs-type">rt_err_t</span> <span class="hljs-title function_">rt_spi_bus_configure</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> rt_spi_device *device)</span><br>{<br> <span class="hljs-type">rt_err_t</span> result = -RT_ERROR;<br> <span class="hljs-keyword">if</span> (device->bus != RT_NULL){<br> result = spi_lock(device->bus);<br> <span class="hljs-keyword">if</span> (result == RT_EOK){<br> <span class="hljs-keyword">if</span> (device->bus->owner == device){<br> result = device->bus->ops->configure(device, &device->config);<br> <span class="hljs-keyword">if</span> (result != RT_EOK){<br> LOG_E(<span class="hljs-string">"SPI device %s configuration failed"</span>, device->parent.parent.name);<br> }<br> }<span class="hljs-keyword">else</span>{<br> result = -RT_EBUSY;<br> }<br> spi_unlock(device->bus);<br> }<br> }<span class="hljs-keyword">else</span>{<br> result = RT_EOK;<br> }<br> <span class="hljs-keyword">return</span> result;<br>}<br></code></pre></td></tr></table></figure><p>当确认获取锁后,<code>rt_spi_bus_configure</code>会把配置部分传递给ops结构体:<code>device->bus->ops->configure(device, &device->config)</code>。</p><p>这里存在一个需要注意的问题:<code>rt_spi_bus_configure</code>接收的<code>device</code>参数类型是<code>rt_spi_device</code>(即<code>rt_qspi_device->parent</code>),因此传递给驱动层的配置参数<code>&device->config</code>实际指向<code>rt_qspi_device->parent.config</code>,而不是<code>rt_qspi_device->config.parent</code>。</p><p>这意味着驱动层的 <code>configure</code> 函数收到的第二个参数<code>configuration</code> 其实并不是应用层程序在<code>rt_qspi_configure</code>里设置的那份配置。因此驱动实现时没法使用这个参数,而是要先把<code>device</code> 强转回 <code>rt_qspi_device</code>,再从<code>qspi_device->config</code>(里取真正的配置:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">rt_err_t</span> <span class="hljs-title function_">drv_spi_configure</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> rt_spi_device *device, <span class="hljs-keyword">struct</span> rt_spi_configuration *configuration)</span><br>{<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_qspi_device</span> *<span class="hljs-title">qspi_device</span> =</span> (<span class="hljs-keyword">struct</span> rt_qspi_device *)device;<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_qspi_configuration</span> *<span class="hljs-title">qspi_cfg</span> =</span> &qspi_device->config; <span class="hljs-comment">// 正确的配置</span><br><br> <span class="hljs-comment">// ...</span><br>}<br></code></pre></td></tr></table></figure><h3 id="传输部分">传输部分</h3><p>QSPI消息结构体<code>rt_qspi_message</code>完整描述了传输信息的所有参数。其定义位于:<ahref="https://github.com/RT-Thread/rt-thread/blob/master/components/drivers/include/drivers/dev_spi.h">dev_spi.h</a></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><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_qspi_message</span></span><br><span class="hljs-class">{</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_spi_message</span> <span class="hljs-title">parent</span>;</span><br> <span class="hljs-comment">/* instruction stage */</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span></span><br><span class="hljs-class"> {</span><br> <span class="hljs-type">rt_uint8_t</span> content;<br> <span class="hljs-type">rt_uint8_t</span> qspi_lines;<br> } instruction;<br> <span class="hljs-comment">/* address and alternate_bytes stage */</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span></span><br><span class="hljs-class"> {</span><br> <span class="hljs-type">rt_uint32_t</span> content;<br> <span class="hljs-type">rt_uint8_t</span> size;<br> <span class="hljs-type">rt_uint8_t</span> qspi_lines;<br> } address, alternate_bytes;<br> <span class="hljs-comment">/* dummy_cycles stage */</span><br> <span class="hljs-type">rt_uint32_t</span> dummy_cycles;<br> <span class="hljs-comment">/* number of lines in qspi data stage, the other configuration items are in parent */</span><br> <span class="hljs-type">rt_uint8_t</span> qspi_data_lines;<br>};<br></code></pre></td></tr></table></figure><p>这个结构体继承自标准SPI消息<code>rt_spi_message</code>:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_spi_message</span></span><br><span class="hljs-class">{</span><br> <span class="hljs-type">const</span> <span class="hljs-type">void</span> *send_buf;<br> <span class="hljs-type">void</span> *recv_buf;<br> <span class="hljs-type">rt_size_t</span> length;<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_spi_message</span> *<span class="hljs-title">next</span>;</span><br><br> <span class="hljs-type">unsigned</span> cs_take : <span class="hljs-number">1</span>;<br> <span class="hljs-type">unsigned</span> cs_release : <span class="hljs-number">1</span>;<br>};<br></code></pre></td></tr></table></figure><p><code>rt_qspi_message</code>对应增强型SPI的三个传输阶段:</p><ul><li><strong>instruction</strong>:命令阶段,包含命令内容和线数配置</li><li><strong>address</strong>:地址阶段,包含地址内容、长度和线数配置</li><li><strong>parent</strong>:数据阶段,继承自<code>rt_spi_message</code>,包含发送/接收缓冲区和长度</li></ul><p>每个阶段都可以独立配置线数(1/2/4/8),实现灵活的传输组合。例如下面的配置实际是把QSPI当标准SPI使用:命令和地址阶段都省略(size=0),只进行数据阶段的单线传输。</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><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* Create SPI Message */</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_qspi_message</span> <span class="hljs-title">msg</span>;</span><br>rt_memset(&msg, <span class="hljs-number">0</span>, <span class="hljs-keyword">sizeof</span>(msg));<br><span class="hljs-comment">/*Using Standard SPI*/</span><br>msg.instruction.content = <span class="hljs-number">0</span>;<br>msg.instruction.qspi_lines = <span class="hljs-number">1</span>;<br>msg.address.content = <span class="hljs-number">0</span>;<br>msg.address.size = <span class="hljs-number">0</span>;<br>msg.address.qspi_lines = <span class="hljs-number">1</span>;<br>msg.qspi_data_lines = <span class="hljs-number">1</span>;<br>msg.dummy_cycles = <span class="hljs-number">0</span>;<br><br><span class="hljs-comment">/* SPI Message Config */</span><br>msg.parent.send_buf = tx_data;<br>msg.parent.recv_buf = rx_data;<br>msg.parent.length = TEST_DATA_LENGTH;<br>msg.parent.cs_take = <span class="hljs-number">1</span>;<br>msg.parent.cs_release = <span class="hljs-number">1</span>;<br>msg.parent.next = RT_NULL;<br><br><span class="hljs-comment">/* Transfer Data */</span><br>ret = rt_qspi_transfer_message(qspi_dev, &msg);<br></code></pre></td></tr></table></figure><p><code>rt_qspi_transfer_message</code>函数是传输流程的入口,其实现展示了内核如何处理总线共享和设备切换:</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><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">rt_ssize_t</span> <span class="hljs-title function_">rt_qspi_transfer_message</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> rt_qspi_device *device, <span class="hljs-keyword">struct</span> rt_qspi_message *message)</span><br>{<br> <span class="hljs-type">rt_ssize_t</span> result;<br> RT_ASSERT(device != RT_NULL);<br> RT_ASSERT(message != RT_NULL);<br> result = rt_mutex_take(&(device->parent.bus->lock), RT_WAITING_FOREVER);<br> <span class="hljs-keyword">if</span> (result != RT_EOK){<br> rt_set_errno(-RT_EBUSY);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> }<br> rt_set_errno(RT_EOK);<br><span class="hljs-comment">/* configure SPI bus */</span><br> <span class="hljs-keyword">if</span> (device->parent.bus->owner != &device->parent){<br><span class="hljs-comment">/* not the same owner as current, re-configure SPI bus */</span><br> result = device->parent.bus->ops->configure(&device->parent, &device->parent.config);<br> <span class="hljs-keyword">if</span> (result == RT_EOK){<br> device->parent.bus->owner = &device->parent;<br> }<span class="hljs-keyword">else</span>{<br> rt_set_errno(-RT_EIO);<br> <span class="hljs-keyword">goto</span> __exit;<br> }<br> }<br> result = device->parent.bus->ops->xfer(&device->parent, &message->parent);<br> <span class="hljs-keyword">if</span> (result == <span class="hljs-number">0</span>){<br> rt_set_errno(-RT_EIO);<br> }<br>__exit:<br> rt_mutex_release(&(device->parent.bus->lock));<br> <span class="hljs-keyword">return</span> result;<br>}<br></code></pre></td></tr></table></figure><p>SPI总线经常遇到多设备共享的情况。除了互斥锁,RT-smart还通过总线所有权机制来处理:对比当前总线拥有者是否是当前设备,如果不是则重新配置<code>device->parent.bus->ops->configure(&device->parent, &device->parent.config)</code>。这样不同配置的设备可以共享同一条总线。配置完成后,调用驱动层ops传输数据:<code>device->parent.bus->ops->xfer(&device->parent, &message->parent)</code></p><h2 id="实现细节">实现细节</h2><p>之前提到,驱动层除了中断注册,设备注册等,其核心就是实现<code>rt_spi_ops</code>这个结构体中的虚函数,也就是总线的操作方法:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_spi_ops</span> <span class="hljs-title">k230_qspi_ops</span> =</span><br>{<br> .configure = k230_spi_configure,<br> .xfer = k230_spi_xfer,<br>};<br></code></pre></td></tr></table></figure><p>在驱动中,我们需要实现初始化<code>rt_hw_qspi_bus_init</code>,配置<code>k230_spi_configure</code>,传输<code>k230_spi_xfer</code>以及中断回调函数<code>k230_spi_irq</code>,驱动的详细代码参考:<ahref="https://github.com/RT-Thread/rt-thread/tree/master/bsp/k230/drivers/interdrv/spi">K230SPI驱动</a></p><h3 id="初始化">初始化</h3><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><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">rt_hw_qspi_bus_init</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> <span class="hljs-type">rt_err_t</span> ret;<br> <span class="hljs-type">int</span> i;<br> <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i < <span class="hljs-keyword">sizeof</span>(k230_spi_devs) / <span class="hljs-keyword">sizeof</span>(k230_spi_devs[<span class="hljs-number">0</span>]); i++)<br> {<br> k230_spi_devs[i].base = rt_ioremap((<span class="hljs-type">void</span> *)k230_spi_devs[i].pa_base, k230_spi_devs[i].size);<br> ret = rt_qspi_bus_register(&k230_spi_devs[i].dev, k230_spi_devs[i].name, &k230_qspi_ops);<br> <span class="hljs-keyword">if</span> (ret)<br> {<br> LOG_E(<span class="hljs-string">"%s register fail"</span>, k230_spi_devs[i].name);<br> <span class="hljs-keyword">return</span> ret;<br> }<br> rt_event_init(&k230_spi_devs[i].event, k230_spi_devs[i].event_name, RT_IPC_FLAG_PRIO);<br> rt_hw_interrupt_install(k230_spi_devs[i].<span class="hljs-built_in">vector</span> + SSI_TXE, k230_spi_irq, &k230_spi_devs[i], k230_spi_devs[i].name);<br> rt_hw_interrupt_umask(k230_spi_devs[i].<span class="hljs-built_in">vector</span> + SSI_TXE);<br> rt_hw_interrupt_install(k230_spi_devs[i].<span class="hljs-built_in">vector</span> + SSI_RXF, k230_spi_irq, &k230_spi_devs[i], k230_spi_devs[i].name);<br> rt_hw_interrupt_umask(k230_spi_devs[i].<span class="hljs-built_in">vector</span> + SSI_RXF);<br> rt_hw_interrupt_install(k230_spi_devs[i].<span class="hljs-built_in">vector</span> + SSI_DONE, k230_spi_irq, &k230_spi_devs[i], k230_spi_devs[i].name);<br> rt_hw_interrupt_umask(k230_spi_devs[i].<span class="hljs-built_in">vector</span> + SSI_DONE);<br> rt_hw_interrupt_install(k230_spi_devs[i].<span class="hljs-built_in">vector</span> + SSI_AXIE, k230_spi_irq, &k230_spi_devs[i], k230_spi_devs[i].name);<br> rt_hw_interrupt_umask(k230_spi_devs[i].<span class="hljs-built_in">vector</span> + SSI_AXIE);<br> }<br> <span class="hljs-keyword">return</span> RT_EOK;<br>}<br>INIT_DEVICE_EXPORT(rt_hw_qspi_bus_init);<br></code></pre></td></tr></table></figure><p>驱动初始化在内核启动时完成,通过<code>INIT_DEVICE_EXPORT</code>,实现主要工作包括:</p><ul><li><p><strong>寄存器映射</strong>:通过<code>rt_ioremap</code>将物理地址映射到虚拟地址空间,<code>k230_spi_devs[i].base</code>被内存映射IO指向SPI相关的寄存器,也就是从<code>k230_spi_devs[i].pa_base</code>开始,向高位偏移<code>k230_spi_devs[i].size</code>字节,具体MemoryMap参数参考<ahref="https://github.com/RT-Thread/rt-thread/blob/master/bsp/k230/board/board.h">board.h</a>。</p></li><li><p><strong>总线注册</strong>:调用<code>rt_qspi_bus_register</code>注册QSPI总线。</p></li><li><p><strong>中断注册</strong>:注册发送空(SSI_TXE)、接收满(SSI_RXF)、传输完成(SSI_DONE)和AXI错误(SSI_AXIE)四个中断。</p></li></ul><h3 id="配置">配置</h3><p>配置函数<code>k230_spi_configure</code>首先进行参数校验,确保线数不超过硬件限制(OPI最大8线,QPI最大4线),数据宽度在4-32位范围内,时钟频率不超过硬件最大值。</p><p>然后是配置时钟,先获取SPI控制器的时钟频率,然后计算分频系数:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (qspi_bus->idx == <span class="hljs-number">0</span>){<br> qspi_clk = sysctl_clk_get_leaf_freq(SYSCTL_CLK_SSI0);<br>}<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (qspi_bus->idx == <span class="hljs-number">1</span>){<br> qspi_clk = sysctl_clk_get_leaf_freq(SYSCTL_CLK_SSI1);<br>}<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (qspi_bus->idx == <span class="hljs-number">2</span>){<br> qspi_clk = sysctl_clk_get_leaf_freq(SYSCTL_CLK_SSI2);<br>}<br></code></pre></td></tr></table></figure><p>接下来是将应用层的配置参数映射到硬件寄存器。其中,<code>qspi_dl_width</code>决定了SPI传输模式(标准/双线/四线/八线),<code>mode</code>包含CPOL和CPHA配置,<code>data_width</code>对应数据帧大小。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (qspi_cfg->qspi_dl_width == <span class="hljs-number">1</span>){<br>spi_ff = SPI_FRF_STD_SPI;<br>}<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (qspi_cfg->qspi_dl_width == <span class="hljs-number">2</span>){<br>spi_ff = SPI_FRF_DUAL_SPI;<br>}<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (qspi_cfg->qspi_dl_width == <span class="hljs-number">4</span>){<br> spi_ff = SPI_FRF_QUAD_SPI;<br>}<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (qspi_cfg->qspi_dl_width == <span class="hljs-number">8</span>) {<br> spi_ff = SPI_FRF_OCT_SPI;<br>}<span class="hljs-keyword">else</span>{<br> <span class="hljs-keyword">return</span> -RT_EINVAL;<br>}<br>mode = qspi_cfg_parent->mode & RT_SPI_MODE_3;<br>dfs = qspi_cfg_parent->data_width - <span class="hljs-number">1</span>;<br>qspi_reg->ssienr = <span class="hljs-number">0</span>; <span class="hljs-comment">// 禁用控制器</span><br>qspi_reg->ser = <span class="hljs-number">0</span>; <span class="hljs-comment">// 禁用片选</span><br>qspi_reg->baudr = qspi_clk / max_hz; <span class="hljs-comment">// 时钟分频</span><br>qspi_reg->rx_sample_delay = qspi_bus->rdse << <span class="hljs-number">16</span> | qspi_bus->rdsd; <span class="hljs-comment">// 采样延迟</span><br>qspi_reg->axiawlen = SSIC_AXI_BLW << <span class="hljs-number">8</span>; <span class="hljs-comment">// AXI写突发长度</span><br>qspi_reg->axiarlen = SSIC_AXI_BLW << <span class="hljs-number">8</span>; <span class="hljs-comment">// AXI读突发长度</span><br>qspi_reg->ctrlr0 = (dfs) | (mode << <span class="hljs-number">8</span>) | (spi_ff << <span class="hljs-number">22</span>); <span class="hljs-comment">// 控制寄存器</span><br></code></pre></td></tr></table></figure><p>这里涉及的寄存器包括:<code>ssienr</code>(使能控制)、<code>ser</code>(片选)、<code>baudr</code>(波特率)、<code>rx_sample_delay</code>(接收采样延迟)、<code>axiawlen</code>和<code>axiarlen</code>(AXI突发长度)、<code>ctrlr0</code>(主控制寄存器)。详细的寄存器定义参考<ahref="https://kendryte-download.canaan-creative.com/developer/k230/HDK/K230硬件文档/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf">K230技术参考手册</a>。</p><h3 id="传输">传输</h3><p>传输函数<code>k230_spi_xfer</code>根据传输模式分为两种模式:标准单线SPI和增强型多线SPI。函数首先通过判断<code>qspi_data_lines</code>来决定使用哪种传输方式:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">rt_ssize_t</span> <span class="hljs-title function_">k230_spi_xfer</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> rt_spi_device *device, <span class="hljs-keyword">struct</span> rt_spi_message *message)</span><br>{<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">k230_spi_dev</span> *<span class="hljs-title">qspi_bus</span> =</span> (<span class="hljs-keyword">struct</span> k230_spi_dev *)device->bus;<br> <span class="hljs-type">k230_spi_reg_t</span> *qspi_reg = (<span class="hljs-type">k230_spi_reg_t</span> *)qspi_bus->base;<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_qspi_device</span> *<span class="hljs-title">dev</span> =</span> (<span class="hljs-keyword">struct</span> rt_qspi_device *)device;<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_qspi_configuration</span> *<span class="hljs-title">qspi_cfg</span> =</span> &dev->config;<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_spi_configuration</span> *<span class="hljs-title">qspi_cfg_parent</span> =</span> &dev->config.parent;<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_qspi_message</span> *<span class="hljs-title">msg</span> =</span> (<span class="hljs-keyword">struct</span> rt_qspi_message *)message;<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rt_spi_message</span> *<span class="hljs-title">msg_parent</span> =</span> message;<br><br> <span class="hljs-comment">/* 判断是否为增强型SPI传输 */</span><br> <span class="hljs-keyword">if</span> (msg->qspi_data_lines > <span class="hljs-number">1</span>)<br> {<br><span class="hljs-comment">// ...</span><br> }<span class="hljs-keyword">else</span> {<br><br><span class="hljs-comment">// ...</span><br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="增强型spi传输">增强型SPI传输</h4><p>增强型SPI传输需要更复杂的参数校验和配置。代码首先验证传输参数的合法性,包括数据线数不能超过硬件配置的最大值,数据宽度必须与线数匹配等等。</p><p>传输类型<code>trans_type</code>用于配置<code>spi_ctrlr0</code>寄存器的低2位,决定指令和地址阶段的传输模式。当指令使用多线传输时设置为2,当地址使用多线传输时设置为1。如果指令和地址都使用多线,则保持<code>trans_type</code>为2。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (msg->instruction.qspi_lines != <span class="hljs-number">1</span>){<br>trans_type = <span class="hljs-number">2</span>;<br>}<br><span class="hljs-keyword">if</span> (msg->address.size){<br><span class="hljs-keyword">if</span> (msg->address.qspi_lines != <span class="hljs-number">1</span>){<br>trans_type = trans_type ? trans_type : <span class="hljs-number">1</span>;<br>}<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (trans_type != <span class="hljs-number">0</span>){<br>LOG_E(<span class="hljs-string">"instruction or address line is invalid"</span>);<br><span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br>}<br></code></pre></td></tr></table></figure><p>增强型SPI一般涉及到大量数据的收发,驱动采用了DMA传输模式。这里的DMA是SPI控制器内部的InternalDMA,并不是SoC的PDMA控制器,两者在硬件上完全独立,因此SPI的DMA只需要配置本控制器的寄存器,不用申请PDMA通道。</p><p>由于这个DMA直接访问物理内存,DMA传输需要使用cache对齐的缓冲区。对于发送操作,驱动将用户数据复制到对齐缓冲区并执行cache清理操作,确保数据写入内存;对于接收操作,传输完成后需要使cache无效,然后将数据复制回用户缓冲区。这种设计避免了cache一致性问题:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">rt_uint8_t</span> tmod = msg_parent->recv_buf ? SPI_TMOD_RO : SPI_TMOD_TO;<br><span class="hljs-type">rt_size_t</span> length = msg_parent->length;<br><span class="hljs-type">rt_size_t</span> txfthr = length > (SSIC_TX_ABW / <span class="hljs-number">2</span>) ? (SSIC_TX_ABW / <span class="hljs-number">2</span>) : length - <span class="hljs-number">1</span>;<br><span class="hljs-type">rt_uint8_t</span> cell_size = (qspi_cfg_parent->data_width + <span class="hljs-number">7</span>) >> <span class="hljs-number">3</span>;<br><span class="hljs-type">rt_uint8_t</span> *buf = RT_NULL;<br> <br><span class="hljs-comment">/* 分配cache对齐的DMA缓冲区 */</span><br><span class="hljs-keyword">if</span> (length){<br>buf = rt_malloc_align(CACHE_ALIGN_TOP(length * cell_size), L1_CACHE_BYTES);<br><span class="hljs-keyword">if</span> (buf == RT_NULL){<br>LOG_E(<span class="hljs-string">"alloc mem error"</span>);<br><span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br>}<br></code></pre></td></tr></table></figure><p>寄存器配置包括传输参数和DMA设置。<code>spi_ctrlr0</code>的低2位是传输类型,第5-2位是地址长度(向下对齐到4字节边界),第10-8位为0x2只读,第15-11位是等待周期。FIFO阈值根据传输长度动态调整,发送阈值设置为传输长度和FIFO深度一半的较小值,接收阈值设置为FIFO深度减1,之后取消SSI_DONE和SSI_AXIE中断的掩码,最后使能DMA。</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><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* msg->address.size & ~0x03: Round address.size down to the nearest multiple of 4 and write it to ADDR_L[5:2] */</span><br>qspi_reg->spi_ctrlr0 = trans_type | (msg->address.size & ~<span class="hljs-number">0x03</span>) <br> | (<span class="hljs-number">0x2</span> << <span class="hljs-number">8</span>) | (msg->dummy_cycles << <span class="hljs-number">11</span>);<br>qspi_reg->ctrlr0 &= ~((<span class="hljs-number">3</span> << <span class="hljs-number">22</span>) | (<span class="hljs-number">3</span> << <span class="hljs-number">10</span>));<br><br><span class="hljs-comment">/* Config SPI frame format and transmission mode */</span><br><span class="hljs-keyword">if</span> (length)<br>{<br>qspi_reg->ctrlr0 |= (tmod << <span class="hljs-number">10</span>);<br>qspi_reg->txftlr = (txfthr << <span class="hljs-number">16</span>) | (SSIC_TX_ABW / <span class="hljs-number">2</span>);<br>qspi_reg->rxftlr = (SSIC_RX_ABW - <span class="hljs-number">1</span>);<br> qspi_reg->imr = (<span class="hljs-number">1</span> << <span class="hljs-number">11</span>) | (<span class="hljs-number">1</span> << <span class="hljs-number">8</span>);<br> qspi_reg->dmacr = (<span class="hljs-number">1</span> << <span class="hljs-number">6</span>) | (<span class="hljs-number">3</span> << <span class="hljs-number">3</span>) | (<span class="hljs-number">1</span> << <span class="hljs-number">2</span>);<br> qspi_reg->ctrlr1 = length - <span class="hljs-number">1</span>;<br> qspi_reg->spidr = msg->instruction.content;<br> qspi_reg->spiar = msg->address.content;<br> <span class="hljs-keyword">if</span> (tmod == SPI_TMOD_TO){<br> rt_memcpy(buf, msg_parent->send_buf, length * cell_size);<br> rt_hw_cpu_dcache_clean(buf, CACHE_ALIGN_TOP(length * cell_size));<br> }<br> qspi_reg->axiar0 = (<span class="hljs-type">rt_uint32_t</span>)((<span class="hljs-type">uint64_t</span>)buf);<br> qspi_reg->axiar1 = (<span class="hljs-type">rt_uint32_t</span>)((<span class="hljs-type">uint64_t</span>)buf >> <span class="hljs-number">32</span>);<br>}<br></code></pre></td></tr></table></figure><p>对于只发送指令和地址而没有数据阶段的传输,驱动禁用DMA和中断,直接通过写数据寄存器发送指令和地址,然后轮询状态寄存器等待传输完成。这种方式避免了为短传输分配DMA缓冲区和处理中断的开销:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">else</span>{<br> tmod = SPI_TMOD_TO;<br> qspi_reg->ctrlr0 |= (tmod << <span class="hljs-number">10</span>);<br> qspi_reg->txftlr = ((SSIC_TX_ABW - <span class="hljs-number">1</span>) << <span class="hljs-number">16</span>) | (SSIC_TX_ABW - <span class="hljs-number">1</span>);<br> qspi_reg->rxftlr = (SSIC_RX_ABW - <span class="hljs-number">1</span>);<br> qspi_reg->imr = <span class="hljs-number">0</span>;<br> qspi_reg->dmacr = <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>传输执行时先复位事件,然后使能片选和控制器。有数据传输时等待中断事件;无数据传输时直接写FIFO并轮询等待。传输完成后释放片选并禁用控制器,检查超时和DMA错误,最后处理接收数据并释放缓冲区:</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><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><code class="hljs c">rt_event_control(&qspi_bus->event, RT_IPC_CMD_RESET, <span class="hljs-number">0</span>);<br><br>qspi_reg->ser = <span class="hljs-number">1</span>;<br>qspi_reg->ssienr = <span class="hljs-number">1</span>;<br><span class="hljs-type">rt_uint32_t</span> event;<br><span class="hljs-type">rt_err_t</span> err;<br><br><span class="hljs-comment">/*</span><br><span class="hljs-comment"> * Config QSPI address and instruction,</span><br><span class="hljs-comment"> * if data is empty, unable dma and send address and instruction by write data register.</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">if</span> (length){<br> err = rt_event_recv(&qspi_bus->event, BIT(SSI_DONE) | BIT(SSI_AXIE),<br> RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, <span class="hljs-number">1000</span>, &event);<br>}<span class="hljs-keyword">else</span><br>{<br> err = RT_EOK;<br> event = <span class="hljs-number">0</span>;<br> qspi_reg->dr[<span class="hljs-number">0</span>] = msg->instruction.content;<br> length++;<br> <span class="hljs-keyword">if</span> (msg->address.size){<br> qspi_reg->dr[<span class="hljs-number">0</span>] = msg->address.content;<br> length++;<br> }<br> qspi_reg->txftlr = <span class="hljs-number">0</span>;<br> <span class="hljs-comment">/* Wait for SPI transfer to complete (busy-wait) */</span><br> <span class="hljs-keyword">while</span> ((qspi_reg->sr & <span class="hljs-number">0x5</span>) != <span class="hljs-number">0x4</span>)<br> {<br> <span class="hljs-comment">/* Busy wait */</span><br> }<br>}<br>qspi_reg->ser = <span class="hljs-number">0</span>;<br>qspi_reg->ssienr = <span class="hljs-number">0</span>;<br><span class="hljs-keyword">if</span> (err == -RT_ETIMEOUT){<br> LOG_E(<span class="hljs-string">"qspi%d transfer data timeout"</span>, qspi_bus->idx);<br> <span class="hljs-keyword">if</span> (buf){<br> rt_free_align(buf);<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br><span class="hljs-keyword">if</span> (event & BIT(SSI_AXIE)){<br> LOG_E(<span class="hljs-string">"qspi%d dma error"</span>, qspi_bus->idx);<br> <span class="hljs-keyword">if</span> (buf){<br> rt_free_align(buf);<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br><span class="hljs-comment">/* Read data from FIFO */</span><br><span class="hljs-keyword">if</span> (tmod == SPI_TMOD_RO){<br> rt_hw_cpu_dcache_invalidate(buf, CACHE_ALIGN_TOP(length * cell_size));<br> rt_memcpy(msg_parent->recv_buf, buf, length * cell_size);<br>}<br><span class="hljs-keyword">if</span> (buf){<br> rt_free_align(buf);<br>}<br><span class="hljs-keyword">return</span> length;<br><br></code></pre></td></tr></table></figure><h4 id="标准spi传输">标准SPI传输</h4><p>标准SPI传输使用中断方式而非DMA。驱动首先根据缓冲区是否空确定传输模式:只有发送缓冲区时为只发送模式(<code>SPI_TMOD_TO</code>),只有接收缓冲区时为只读模式(<code>SPI_TMOD_RO</code>),两者都有时为全双工模式(<code>SPI_TMOD_TR</code>)。对于只读模式,如果配置了地址,则切换为EEPROM读模式(<code>SPI_TMOD_EPROMREAD</code>):</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><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (msg_parent->length == <span class="hljs-number">0</span>){<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br><span class="hljs-type">rt_uint8_t</span> cell_size = (qspi_cfg_parent->data_width + <span class="hljs-number">7</span>) >> <span class="hljs-number">3</span>;<br><span class="hljs-type">rt_size_t</span> length = msg_parent->length;<br><span class="hljs-type">rt_size_t</span> count = length > <span class="hljs-number">0x10000</span> ? <span class="hljs-number">0x10000</span> : length;<br><span class="hljs-type">rt_size_t</span> send_single = <span class="hljs-number">0</span>, send_length = <span class="hljs-number">0</span>, recv_single = <span class="hljs-number">0</span>, recv_length = <span class="hljs-number">0</span>;<br><span class="hljs-type">void</span> *send_buf = (<span class="hljs-type">void</span> *)msg_parent->send_buf;<br><span class="hljs-type">void</span> *recv_buf = msg_parent->recv_buf;<br><span class="hljs-type">rt_uint8_t</span> tmod = send_buf ? SPI_TMOD_TO : SPI_TMOD_EPROMREAD;<br>tmod = recv_buf ? tmod & SPI_TMOD_RO : tmod;<br><span class="hljs-comment">// ...</span><br><span class="hljs-keyword">if</span> (tmod == SPI_TMOD_RO && qspi_cfg_parent->data_width == <span class="hljs-number">8</span>){<br> <span class="hljs-keyword">if</span> ((msg->address.size & <span class="hljs-number">7</span>) || (msg->dummy_cycles & <span class="hljs-number">7</span>)){<br> <span class="hljs-comment">// error message</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> }<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (msg->address.size){<br> <span class="hljs-keyword">if</span> (length > <span class="hljs-number">0x10000</span>){<br> <span class="hljs-comment">// error message</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> }<br> tmod = SPI_TMOD_EPROMREAD;<br> }<br>}<br></code></pre></td></tr></table></figure><p>缓冲区准备时,驱动为发送和接收分别分配临时缓冲区。对于EEPROM读模式,需要构造包含指令、地址和dummy字节的发送序列。</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><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (send_buf){<br> send_single = count;<br> send_buf = rt_malloc(count * cell_size);<br> <span class="hljs-keyword">if</span> (send_buf == RT_NULL){<br> LOG_E(<span class="hljs-string">"alloc mem error"</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> }<br> rt_memcpy(send_buf, msg_parent->send_buf, count * cell_size);<br>}<br><span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (tmod == SPI_TMOD_EPROMREAD){<br> send_single = <span class="hljs-number">1</span> + msg->address.size / <span class="hljs-number">8</span> + msg->dummy_cycles / <span class="hljs-number">8</span>;<br> send_buf = rt_malloc(send_single);<br> <span class="hljs-keyword">if</span> (send_buf == RT_NULL)<br> {<br> LOG_E(<span class="hljs-string">"alloc mem error"</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> }<br> <span class="hljs-type">rt_uint8_t</span> *temp = send_buf;<br> *temp++ = msg->instruction.content;<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = msg->address.size / <span class="hljs-number">8</span>; i; i--){<br> *temp++ = msg->address.content >> ((i - <span class="hljs-number">1</span>) * <span class="hljs-number">8</span>);<br> }<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = msg->dummy_cycles / <span class="hljs-number">8</span>; i; i--){<br> *temp++ = <span class="hljs-number">0xFF</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs c">qspi_bus->cell_size = cell_size;<br>qspi_bus->send_buf = send_buf;<br>qspi_bus->recv_buf = recv_buf;<br>qspi_bus->send_length = send_single;<br>qspi_bus->recv_length = recv_single;<br></code></pre></td></tr></table></figure><p>接下来配置传输相关寄存器。<code>ctrlr0</code>设置传输模式,<code>ctrlr1</code>设置本次传输的数据长度。FIFO阈值配置和增强型SPI类似:发送阈值设为FIFO深度的一半,接收阈值根据传输长度动态调整。禁用DMA,使能发送空和接收满中断,配置完成后,复位事件清除之前的事件状态,然后拉低片选并使能控制器启动传输。对于只读模式,需要向数据寄存器写入一个dummy值来触发读操作</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></pre></td><td class="code"><pre><code class="hljs c">qspi_reg->ctrlr0 &= ~((<span class="hljs-number">3</span> << <span class="hljs-number">22</span>) | (<span class="hljs-number">3</span> << <span class="hljs-number">10</span>));<br>qspi_reg->ctrlr0 |= (tmod << <span class="hljs-number">10</span>);<br>qspi_reg->ctrlr1 = count - <span class="hljs-number">1</span>;<br>qspi_reg->txftlr = ((SSIC_TX_ABW / <span class="hljs-number">2</span>) << <span class="hljs-number">16</span>) | (SSIC_TX_ABW / <span class="hljs-number">2</span>);<br>qspi_reg->rxftlr = count >= (SSIC_RX_ABW / <span class="hljs-number">2</span>) ? (SSIC_RX_ABW / <span class="hljs-number">2</span> - <span class="hljs-number">1</span>) : count - <span class="hljs-number">1</span>;<br>qspi_reg->dmacr = <span class="hljs-number">0</span>;<br><span class="hljs-comment">/* Interrupt transmit or receive */</span><br>qspi_reg->imr = (<span class="hljs-number">1</span> << <span class="hljs-number">4</span>) | (<span class="hljs-number">1</span> << <span class="hljs-number">0</span>);<br>rt_event_control(&qspi_bus->event, RT_IPC_CMD_RESET, <span class="hljs-number">0</span>);<br>qspi_reg->ser = <span class="hljs-number">1</span>;<br>qspi_reg->ssienr = <span class="hljs-number">1</span>;<br><span class="hljs-keyword">if</span> (tmod == SPI_TMOD_RO)<br> qspi_reg->dr[<span class="hljs-number">0</span>] = <span class="hljs-number">0</span>;<br></code></pre></td></tr></table></figure><p>传输线程在配置完寄存器并启动传输后,进入事件循环通过<code>rt_event_recv</code>阻塞等待中断事件。这个函数会挂起当前线程,直到接收到指定的事件或超时。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">while</span> (RT_TRUE){<br> <span class="hljs-type">rt_uint32_t</span> event;<br> <span class="hljs-type">rt_err_t</span> err = rt_event_recv(&qspi_bus->event, <br> BIT(SSI_TXE) | BIT(SSI_RXF),<br> RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, <br> <span class="hljs-number">10000</span>, &event);<br><span class="hljs-comment">// ...收发逻辑</span><br>}<br><br></code></pre></td></tr></table></figure><p>当收到SSI_TXE事件时,表示当前批次的数据已经全部写入FIFO。如果还有剩余数据需要发送,驱动将下一批数据(最多64KB)复制到临时缓冲区,更新设备结构体中的缓冲区指针和长度,然后重新使能SSI_TXE中断。对于只发送模式,当所有数据都已发送后,需要轮询状态寄存器等待SPI控制器完成物理传输,因为最后一批数据可能还在FIFO中等待发送:</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><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* Handle Transmit buffer empty */</span><br><span class="hljs-keyword">if</span> (event & BIT(SSI_TXE)){<br> send_length += send_single;<br> <span class="hljs-keyword">if</span> (send_length < length && tmod <= SPI_TMOD_TO){<br> count = length - send_length;<br> count = count > <span class="hljs-number">0x10000</span> ? <span class="hljs-number">0x10000</span> : count;<br> rt_memcpy(send_buf, msg_parent->send_buf + send_length * cell_size, count * cell_size);<br> qspi_bus->send_buf = send_buf;<br> qspi_bus->send_length = count;<br> send_single = count;<br> qspi_reg->txftlr = ((SSIC_TX_ABW / <span class="hljs-number">2</span>) << <span class="hljs-number">16</span>) | (SSIC_TX_ABW / <span class="hljs-number">2</span>);<br> <span class="hljs-keyword">if</span> (tmod == SPI_TMOD_TO)<br> qspi_reg->imr |= (<span class="hljs-number">1</span> << <span class="hljs-number">0</span>);<br> }<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (tmod == SPI_TMOD_TO)<br> {<br> <span class="hljs-comment">/* Wait for SPI transfer to complete (busy-wait) */</span><br> <span class="hljs-keyword">while</span> ((qspi_reg->sr & <span class="hljs-number">0x5</span>) != <span class="hljs-number">0x4</span>)<br> {<br> <span class="hljs-comment">/* Busy wait */</span><br> }<br> <span class="hljs-keyword">break</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><p>当收到SSI_RXF事件时,表示临时缓冲区已经接收满。驱动将数据复制到用户缓冲区,更新已接收的数据量。如果还有剩余数据需要接收,准备接收下一批数据。对于只读模式,每次只能配置一次接收长度(通过<code>ctrlr1</code>寄存器),因此需要禁用控制器、更新<code>ctrlr1</code>、重新使能控制器。</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><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* Handle receive buffer full */</span><br><span class="hljs-keyword">if</span> (event & BIT(SSI_RXF)){<br> rt_memcpy(msg_parent->recv_buf + recv_length * cell_size, recv_buf, recv_single * cell_size);<br> recv_length += recv_single;<br> <span class="hljs-keyword">if</span> (recv_length >= length)<br> {<br> <span class="hljs-keyword">break</span>;<br> }<br> count = length - recv_length;<br> count = count > <span class="hljs-number">0x10000</span> ? <span class="hljs-number">0x10000</span> : count;<br> qspi_bus->recv_buf = recv_buf;<br> qspi_bus->recv_length = count;<br> recv_single = count;<br> qspi_reg->rxftlr = count >= (SSIC_RX_ABW / <span class="hljs-number">2</span>) ? (SSIC_RX_ABW / <span class="hljs-number">2</span> - <span class="hljs-number">1</span>) : count - <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">if</span> (tmod == SPI_TMOD_TR){<br> qspi_reg->imr |= (<span class="hljs-number">1</span> << <span class="hljs-number">0</span>) | (<span class="hljs-number">1</span> << <span class="hljs-number">4</span>);<br> }<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (tmod == SPI_TMOD_RO){<br> qspi_reg->imr |= (<span class="hljs-number">1</span> << <span class="hljs-number">4</span>);<br> qspi_reg->ssienr = <span class="hljs-number">0</span>;<br> qspi_reg->ctrlr1 = count - <span class="hljs-number">1</span>;<br> qspi_reg->ssienr = <span class="hljs-number">1</span>;<br> <span class="hljs-comment">/* Trigger two dummy transfers to restart SPI read in read-only mode.</span><br><span class="hljs-comment"> * This is required by the hardware to ensure correct data reception.</span><br><span class="hljs-comment"> */</span><br> qspi_reg->dr[<span class="hljs-number">0</span>] = <span class="hljs-number">0</span>;<br> qspi_reg->dr[<span class="hljs-number">0</span>] = <span class="hljs-number">0</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><h3 id="中断回调函数">中断回调函数</h3><p>中断回调函数是标准SPI传输的核心,负责实际的FIFO读写操作。函数通过中断向量号识别中断类型,然后执行相应的处理逻辑。</p><p>K230的每个SPI控制器有4个中断源,它们的中断向量号是连续的。通过计算向量号相对于IRQN_SPI0基址的偏移,可以确定是哪个控制器的哪种中断:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">void</span> <span class="hljs-title function_">k230_spi_irq</span><span class="hljs-params">(<span class="hljs-type">int</span> <span class="hljs-built_in">vector</span>, <span class="hljs-type">void</span> *param)</span><br>{<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">k230_spi_dev</span> *<span class="hljs-title">qspi_bus</span> =</span> param;<br> <span class="hljs-type">k230_spi_reg_t</span> *qspi_reg = (<span class="hljs-type">k230_spi_reg_t</span> *)qspi_bus->base;<br> <br><br> <span class="hljs-built_in">vector</span> -= IRQN_SPI0;<br> <span class="hljs-built_in">vector</span> %= (IRQN_SPI1 - IRQN_SPI0);<br> <span class="hljs-comment">// ...</span><br>}<br><br></code></pre></td></tr></table></figure><p>当发送FIFO中的数据量低于阈值时触发SSI_TXE中断。中断处理函数检查状态寄存器sr的bit1(TxFIFOFULL),循环将数据写入FIFO直到缓冲区为空或FIFO满。根据<code>cell_size</code>(数据帧大小)选择8位、16位或32位写入方式。</p><p>当缓冲区数据全部写入FIFO后,处理逻辑根据传输模式有所不同。对于只发送模式(<code>SPI_TMOD_TO</code>),数据全部写入FIFO并不意味着传输完成,因为FIFO中的数据可能还在等待发送。驱动将发送阈值设为0,这样当FIFO完全清空时会再次触发TXE中断,此时才发送事件通知传输线程:</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><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* Handle transmit buffer empty interrupt */</span><br><span class="hljs-keyword">if</span> (<span class="hljs-built_in">vector</span> == SSI_TXE){<br> <span class="hljs-keyword">if</span> (qspi_bus->send_buf == RT_NULL){<br> qspi_reg->imr &= ~<span class="hljs-number">1</span>;<br> }<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (qspi_bus->cell_size == <span class="hljs-number">1</span>){<br> <span class="hljs-keyword">while</span> ((qspi_bus->send_length) && (qspi_reg->sr & <span class="hljs-number">2</span>)){<br> qspi_reg->dr[<span class="hljs-number">0</span>] = *((<span class="hljs-type">rt_uint8_t</span> *)qspi_bus->send_buf);<br> qspi_bus->send_buf++;<br> qspi_bus->send_length--;<br> }<br> }<br> <span class="hljs-comment">// cell_size = 2/4</span><br> <span class="hljs-keyword">if</span> (qspi_bus->send_length == <span class="hljs-number">0</span>){<br> <span class="hljs-keyword">if</span> (((qspi_reg->ctrlr0 >> <span class="hljs-number">10</span>) & SPI_TMOD_EPROMREAD) == SPI_TMOD_TO){<br> <span class="hljs-keyword">if</span> (qspi_reg->txftlr)<br> <span class="hljs-keyword">return</span>;<br> }<br> qspi_reg->txftlr = <span class="hljs-number">0</span>;<br> qspi_reg->imr &= ~<span class="hljs-number">1</span>;<br> rt_event_send(&qspi_bus->event, BIT(SSI_TXE));<br> }<br>}<br> <br></code></pre></td></tr></table></figure><p>当接收FIFO中的数据量达到阈值时触发SSI_RXF中断。中断处理函数检查状态寄存器sr的bit3(ReceiveFIFOEMPTY),循环从FIFO读取数据直到缓冲区满或FIFO空。同样根据<code>cell_size</code>选择读取方式。</p><p>当剩余数据量小于当前FIFO阈值时,动态调整阈值避免最后一批数据无法触发中断。例如,如果阈值设为31(FIFO深度的一半),但只剩20个数据要接收,FIFO永远不会达到31的阈值,导致接收卡死。将阈值调整为19(剩余数据-1)可以确保最后一批数据正确接收:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">vector</span> == SSI_RXF){<br> <span class="hljs-keyword">if</span> (qspi_bus->recv_buf == RT_NULL){<br> qspi_reg->imr &= ~<span class="hljs-number">0x10</span>;<br> }<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (qspi_bus->cell_size == <span class="hljs-number">1</span>){<br> <span class="hljs-keyword">while</span> ((qspi_bus->recv_length) && (qspi_reg->sr & <span class="hljs-number">8</span>)){<br> *((<span class="hljs-type">rt_uint8_t</span> *)qspi_bus->recv_buf) = qspi_reg->dr[<span class="hljs-number">0</span>];<br> qspi_bus->recv_buf++;<br> qspi_bus->recv_length--;<br> }<br> }<br><span class="hljs-comment">// cell_size = 2/4</span><br> <span class="hljs-keyword">if</span> (qspi_bus->recv_length == <span class="hljs-number">0</span>){<br> qspi_reg->imr &= ~<span class="hljs-number">0x10</span>;<br> rt_event_send(&qspi_bus->event, BIT(SSI_RXF));<br> }<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (qspi_bus->recv_length <= qspi_reg->rxftlr){<br> qspi_reg->rxftlr = qspi_bus->recv_length - <span class="hljs-number">1</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><p>传输完成中断(DONE)和AXI错误中断(AXIE)主要用于增强型SPI的DMA传输模式。中断处理函数只需读取相应的清除寄存器来清除中断标志,然后发送事件通知传输线程:</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">vector</span> == SSI_DONE){<br> (<span class="hljs-type">void</span>)qspi_reg->donecr; <span class="hljs-comment">// 读取清除中断</span><br> rt_event_send(&qspi_bus->event, BIT(SSI_DONE));<br>}<br><span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">vector</span> == SSI_AXIE){<br> (<span class="hljs-type">void</span>)qspi_reg->axiecr; <span class="hljs-comment">// 读取清除中断</span><br> rt_event_send(&qspi_bus->event, BIT(SSI_AXIE));<br>}<br></code></pre></td></tr></table></figure><p>中断触发时会屏蔽调度器,导致操作系统无法进行正常的任务调度。因此驱动设计时,中断处理函数应该尽量轻量,只做必要的FIFO读写等操作,通过事件通知机制触发传输线程处理耗时操作。</p>]]></content>
<categories>
<category>K230-rt-smart</category>
</categories>
<tags>
<tag>K230</tag>
<tag>rt-smart</tag>
<tag>嵌入式</tag>
<tag>bsp</tag>
<tag>spi</tag>
</tags>
</entry>
<entry>
<title>K230-开发环境搭建</title>
<link href="/2025/12/12/K230-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
<url>/2025/12/12/K230-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/</url>
<content type="html"><![CDATA[<h2 id="硬件介绍">硬件介绍</h2><p>K230芯片大致框图如下,图源<ahref="https://kendryte-download.canaan-creative.com/developer/k230/HDK/K230%E7%A1%AC%E4%BB%B6%E6%96%87%E6%A1%A3/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf">K230技术参考手册</a></p><figure><imgsrc="/2025/12/12/K230-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/image-20251212195138057.png"alt="K230芯片架构" /><figcaption aria-hidden="true">K230芯片架构</figcaption></figure><p>核心是两颗玄铁RISC-VC908,一个800MHz的小核<code>cpu0</code>,1.6GHz的大核<code>cpu1</code>。嘉楠官方给了两种开发方式,第一种是小核<code>cpu0</code>跑Linux,大核<code>cpu1</code>跑rt-smart的异构架构,另一种是纯rt-smart架构,似乎只跑在大核上。由于多核通信很麻烦,我只用了rt-smart版本进行开发。</p><h2 id="环境搭建">环境搭建</h2><p>k230rt-smart开发环境配置需要两套,一套是<code>rt-thread</code>主线内核,另一套是基础镜像。</p><h3 id="rt-thread内核环境">rt-thread内核环境</h3><p>目前rt-smart的镜像构建只支持在Linux中进行,本人是在wsl2的ubuntu22.04下搭建开发环境的,相比于虚拟机稍微麻烦一些,但是性能会相对好一些,当然有能力还是直接Linux物理机了。</p><p>具体配置流程参考<ahref="https://github.com/RT-Thread/rt-thread/tree/master/bsp/k230">rt-threadK230主线BSP</a>,简单的流程这里一笔带过,重点说明踩过的一些坑:</p><p>首先是GCC工具链:https://download.rt-thread.org/rt-smart/riscv64/riscv64-linux-musleabi_for_x86_64-pc-linux-gnu_251248.tar.bz2,解压完成后在<code>~/.bashrc</code>中写入:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">export</span> RTT_CC=<span class="hljs-string">"gcc"</span><br><span class="hljs-built_in">export</span> RTT_CC_PREFIX=<span class="hljs-string">"riscv64-unknown-linux-musl-"</span><br><span class="hljs-built_in">export</span> RTT_EXEC_PATH=<span class="hljs-string">"<span class="hljs-variable">$USER</span>/toolchain/riscv64-linux-musleabi_for_x86_64-pc-linux-gnu/bin"</span><br></code></pre></td></tr></table></figure><p>然后是一些包:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> apt install -y scons python3-pip u-boot-tools patch<br>pip3 install kconfiglib pycryptodome gmssl<br></code></pre></td></tr></table></figure><p>安装rt-smart所需环境(避免在<code>rt-thread</code>仓库目录下执行)</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><code class="hljs bash">wget https://gitee.com/RT-Thread-Mirror/env/raw/master/install_ubuntu.sh<br><span class="hljs-built_in">chmod</span> 777 install_ubuntu.sh<br>./install_ubuntu.sh --gitee<br><span class="hljs-built_in">rm</span> install_ubuntu.sh<br></code></pre></td></tr></table></figure><p>下载源码并配置环境:</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><code class="hljs bash">git <span class="hljs-built_in">clone</span> git@github.com:RT-Thread/rt-thread.git<br><span class="hljs-built_in">cd</span> rt-thread/bsp/k230<br><span class="hljs-built_in">source</span> ~/.env/env.sh<br>pkgs --update<br></code></pre></td></tr></table></figure><p>还有<ahref="https://github.com/plctlab/rttpkgtool/tree/for-k230">rttpkgtool</a>用于对内核文件进行打包,注意是<code>for-k230</code>分支。</p><h3 id="rtos_k230环境配置">rtos_k230环境配置</h3><p>rt-thread仓库主要是编译内核,还需要用嘉楠的工具制作一个sd卡镜像:<ahref="https://www.kendryte.com/k230_rtos/zh/dev/userguide/how_to_build.html">如何编译固件</a>主要配置如下:</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> dpkg --add-architecture i386<br><span class="hljs-built_in">sudo</span> apt update<br><br><span class="hljs-built_in">sudo</span> apt install -y --no-install-recommends \<br> <span class="hljs-built_in">sudo</span> vim wget curl git git-lfs openssh-client net-tools sed tzdata expect \<br> make cmake binutils build-essential gcc g++ bash patch perl tar cpio unzip \<br> file bc bison flex autoconf automake python3 python3-pip python3-dev \<br> lib32z1 libncurses5-dev fakeroot pigz tree doxygen gawk pkg-config \<br> libssl-dev libc6-dev-i386 libncurses5:i386<br><br><span class="hljs-built_in">sudo</span> apt clean<br></code></pre></td></tr></table></figure><p>下面python环境配置有概率出现一个坑:</p><figure class="highlight bash"><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><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs bash">chuan@localhost:~/rtos_k230$ <span class="hljs-keyword">time</span> make <span class="hljs-built_in">log</span><br>Using prebuilt U-Boot binaries, skipping compilation<br>Adding Image /home/chuan/rtos_k230//output/k230_rtos_01studio_defconfig/uboot/u-boot.bin.gz<br>Image Name: uboot<br>Created: Sat Dec 6 22:42:03 2025<br>Image Type: RISC-V U-Boot Firmware (gzip compressed)<br>Data Size: 300580 Bytes = 293.54 KiB = 0.29 MiB<br>Load Address: 80000000<br>Entry Point: 80000000<br>Convert U-Boot <span class="hljs-built_in">env</span> /home/chuan/rtos_k230/boards/k230_canmv_01studio/default.env => /home/chuan/rtos_k230/output/k230_rtos_01studio_defconfig/images/uboot/env.bin <span class="hljs-keyword">done</span>.<br>Generate U-Boot SPL Done.<br>Generating U-Boot binary with text base: 0x80000000<br>Generate U-Boot Done.<br>scons: Reading SConscript files ...<br>ImportError: cannot import name <span class="hljs-string">'mk_rtconfig'</span> from <span class="hljs-string">'menuconfig'</span> (/home/chuan/.local/lib/python3.10/site-packages/menuconfig.py):<br> File <span class="hljs-string">"/home/chuan/rtos_k230/src/rtsmart/rtsmart/kernel/bsp/maix3/SConstruct"</span>, line 31:<br> objs = PrepareBuilding(<span class="hljs-built_in">env</span>, RTT_ROOT, has_libcpu = True)<br> File <span class="hljs-string">"/home/chuan/rtos_k230/src/rtsmart/rtsmart/kernel/bsp/maix3/../../rt-thread/tools/building.py"</span>, line 421:<br> from menuconfig import mk_rtconfig<br>make[2]: *** [Makefile:26: .parse_config] Error 2<br>make[1]: *** [Makefile:84: rtsmart] Error 2<br><br>real 0m0.721s<br>user 0m0.220s<br>sys 0m0.096s<br></code></pre></td></tr></table></figure><p>因为<code>rtos_k230</code>用的是自己的<code>menuconfig</code>,而系统本身环境自带<code>menuconfig</code>,脚本找不到<code>mk_rtconfig</code>这个函数,因此弄一套虚拟环境:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">cd</span> ~/rtos_k230<br>python3 -m venv .venv_rtos_k230<br><span class="hljs-built_in">source</span> .venv_rtos_k230/bin/activate<br></code></pre></td></tr></table></figure><p>然后在这个环境中安装python环境即可</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">pip3 config <span class="hljs-built_in">set</span> global.index-url https://pypi.tuna.tsinghua.edu.cn/simple<br>pip3 install -U pyyaml pycryptodome gmssl<br></code></pre></td></tr></table></figure><p>repo工具安装:</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">mkdir</span> -p ~/.bin<br>curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo<br><span class="hljs-built_in">chmod</span> a+rx ~/.bin/repo<br><span class="hljs-built_in">echo</span> <span class="hljs-string">'export PATH="$HOME/.bin:$PATH"'</span> >> ~/.bashrc<br><span class="hljs-built_in">source</span> ~/.bashrc<br></code></pre></td></tr></table></figure><p>配置好git后,下载源码:</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 创建工作目录</span><br><span class="hljs-built_in">mkdir</span> -p ~/rtos_k230 && <span class="hljs-built_in">cd</span> ~/rtos_k230<br><br><span class="hljs-comment"># 从 Gitee 下载代码(推荐国内用户,需配置 SSH 密钥)</span><br>repo init -u git@gitee.com:canmv-k230/manifest.git -m rtsmart.xml \<br> --repo-url=git@gitee.com:canmv-k230/git-repo.git<br><br><span class="hljs-comment"># 从 GitHub 下载代码(国际用户)</span><br><span class="hljs-comment"># repo init -u https://github.com/canmv-k230/manifest -m rtsmart.xml \</span><br><span class="hljs-comment"># --repo-url=https://github.com/canmv-k230/git-repo.git</span><br><br><span class="hljs-comment"># 同步代码仓库</span><br>repo <span class="hljs-built_in">sync</span> -j $(<span class="hljs-built_in">nproc</span>)<br></code></pre></td></tr></table></figure><h2 id="系统编译">系统编译</h2><h3 id="基础镜像编译">基础镜像编译</h3><p>我们需要用rtos_k230构建出一个镜像(只需一次即可)</p><p>cd进源码目录,选择<code>01studio</code>的配置文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">cd</span> ~/rtos_k230<br>make list-def<br>make k230_rtos_01studio_defconfig<br></code></pre></td></tr></table></figure><p>然后执行编译:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-keyword">time</span> make <span class="hljs-built_in">log</span><br></code></pre></td></tr></table></figure><p>如果没问题,会生成所需镜像:</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><code class="hljs bash">TOC written at offset 0xe0000, size 448 bytes<br>Image sysimage-sdcard.img generated<br>All images generated successfully<br>Compress image RtSmart-K230_01Studio_rtsmart_local_nncase_v2.9.0.img.gz, it will take a <span class="hljs-keyword">while</span><br>Generated image <span class="hljs-keyword">done</span>, at /home/chuan/rtos_k230//output/k230_rtos_01studio_defconfig/RtSmart-K230_01Studio_rtsmart_local_nncase_v2.9.0.img<br>Build K230 <span class="hljs-keyword">done</span>, board k230_canmv_01studio, config k230_rtos_01studio_defconfig<br><br>real 0m34.594s<br>user 0m20.957s<br>sys 0m8.148s<br></code></pre></td></tr></table></figure><p><code>RtSmart-K230_01Studio_rtsmart_local_nncase_v2.9.0.img</code>这个就是k230的镜像文件,找个sd卡用balenaEtcher或者类似的工具烧进去就行了。</p><h3 id="内核编译">内核编译</h3><p>下面是内核编译,先cd进源码目录:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">cd</span> rt-thread/bsp/k230<br></code></pre></td></tr></table></figure><p>配置<code>menuconfig</code>并编译</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">scons --menuconfig<br>scons -j$(<span class="hljs-built_in">nproc</span>)<br></code></pre></td></tr></table></figure><p>目录下会生成<code>rtthread.bin</code>,然后用脚本对其进行打包并烧录(记得插入sd卡)</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">./build.sh<br>./flashsd.sh <br></code></pre></td></tr></table></figure><p>执行<code>./flashsd.sh</code>时,有可能会提示打不开SD卡,我们查看这个脚本内容:</p><figure class="highlight bash"><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><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-meta">#!/bin/bash</span><br><br><span class="hljs-built_in">source</span> ./utils.sh<br><br>BSP_PATH=$(<span class="hljs-built_in">realpath</span> $(<span class="hljs-built_in">dirname</span> <span class="hljs-variable">$0</span>))<br><span class="hljs-built_in">echo</span> <span class="hljs-string">"BSP_PATH: <span class="hljs-variable">$BSP_PATH</span>"</span><br><br>download_rttpkgtool <span class="hljs-variable">$BSP_PATH</span><br>result=$?<br><span class="hljs-keyword">if</span> [ <span class="hljs-variable">$result</span> -ne 0 ]; <span class="hljs-keyword">then</span><br><span class="hljs-built_in">echo</span> <span class="hljs-string">"ERROR: rttpkgtool is unavailable! Please check your network connection!"</span><br><span class="hljs-built_in">exit</span> 1<br><span class="hljs-keyword">fi</span><br><br><span class="hljs-built_in">pushd</span> <span class="hljs-variable">$BSP_PATH</span>/rttpkgtool > /dev/null<br><br>./script/sdcard.sh > /dev/null<br>result=$?<br><br><span class="hljs-keyword">if</span> [ <span class="hljs-variable">$result</span> -eq 1 ]; <span class="hljs-keyword">then</span><br><span class="hljs-built_in">echo</span> <span class="hljs-string">"ERROR: The kernel file to be flashed does not exist!"</span><br><span class="hljs-built_in">exit</span> 1<br><span class="hljs-keyword">fi</span><br><span class="hljs-keyword">if</span> [ <span class="hljs-variable">$result</span> -eq 2 ]; <span class="hljs-keyword">then</span><br><span class="hljs-built_in">echo</span> <span class="hljs-string">"ERROR: The USB/SDcard does not exist!"</span><br><span class="hljs-built_in">exit</span> 1<br><span class="hljs-keyword">fi</span><br><br><span class="hljs-built_in">echo</span> <span class="hljs-string">"INFO: The kernel file has been flashed to the USB/SDcard successfully!"</span><br><br><span class="hljs-built_in">popd</span> > /dev/null<br></code></pre></td></tr></table></figure><p>实际上烧录用的是<code>rttpkgtool/script/sdcard.sh</code>这个脚本,打开这个脚本,把:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-keyword">if</span> [ -z <span class="hljs-string">"<span class="hljs-variable">${DEST}</span>"</span> ]; <span class="hljs-keyword">then</span><br> DEST=/dev/sde<br><span class="hljs-keyword">fi</span><br></code></pre></td></tr></table></figure><p>将脚本中的<code>/dev/sde</code>改成sd卡实际挂载位置即可。</p><h3 id="wsl2内核编译">WSL2内核编译</h3><p>wsl2还有其他问题,wsl2没法直接访问物理机的外部设备。因此,我们需要借助<code>usbipd</code>把Windows的USB设备通过网络分享给虚拟机。</p><p>windows安装:<ahref="https://github.com/dorssel/usbipd-win">usbipd-win</a>,用winget或者msi安装都行。wsl2内核版本够新自带<code>usbipd</code>。除了这玩意以外,还需要单独编一份wsl2内核,因为wsl2内核默认没开启对存储设备的支持</p><p>先git clone源码下来:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git <span class="hljs-built_in">clone</span> https://github.com/microsoft/WSL2-Linux-Kernel.git<br></code></pre></td></tr></table></figure><p>然后安装依赖</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> apt install libncurses-dev build-essential flex bison libssl-dev libelf-dev dwarves<br></code></pre></td></tr></table></figure><p>在<code>menuconfig</code>中勾选配置:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">make menuconfig KCONFIG_CONFIG=Microsoft/config-wsl<br></code></pre></td></tr></table></figure><p>然后再<code>menuconfig</code>中把<code>Device Drivers -> USB support -> USB Mass Storage support</code>下面全勾上就行了,记得打’*‘直接编译进内核,‘M’需要手动加载</p><figure><imgsrc="/2025/12/12/K230-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/image-20251212195449342.png"alt="wsl2内核menuconfig" /><figcaption aria-hidden="true">wsl2内核menuconfig</figcaption></figure><p>然后编译内核:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">make -j$(<span class="hljs-built_in">nproc</span>) bzImage KCONFIG_CONFIG=Microsoft/config-wsl<br></code></pre></td></tr></table></figure><p>编译结束会在<code>arch/x86/boot/</code>下面生成名为<code>bzImage</code>的内核文件。</p><p>把内核文件复制出来,在<code>C:\Users\用户名</code>目录下的<code>.wslconfig</code>文件中加入(如果不存在就新建一个):</p><figure class="highlight shell"><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></pre></td><td class="code"><pre><code class="hljs shell">[wsl2]<br>processors=4<br>swapFile=E:\\WSL2_share<br>MaxCrashDumpCount=-1<br>kernel=E:\\wsl2_kernel\\bzImage<br></code></pre></td></tr></table></figure><p>如果需要新建就加第一行和最后一行就行了。</p><p>具体如何将USB设备共享给WSL2,参考微软文档:<ahref="https://learn.microsoft.com/zh-cn/windows/wsl/connect-usb">连接USB 设备</a></p>]]></content>
<categories>
<category>K230-rt-smart</category>
</categories>
<tags>
<tag>K230</tag>
<tag>rt-smart</tag>
<tag>嵌入式</tag>
</tags>
</entry>
</search>