<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>RikiBlog</title><description>分享技术，记录生活</description><link>https://blog.zyfang.top/</link><language>zh_CN</language><item><title>自制FPGA渲染器：软件算法模拟</title><link>https://blog.zyfang.top/posts/fpga_render/algorithm_sim/</link><guid isPermaLink="true">https://blog.zyfang.top/posts/fpga_render/algorithm_sim/</guid><description>本文档介绍了我自制的FPGA渲染器项目的背景、目标</description><pubDate>Tue, 01 Apr 2025 00:00:00 GMT</pubDate><content:encoded/></item><item><title>自制FPGA渲染器：项目说明</title><link>https://blog.zyfang.top/posts/fpga_render/introduction/</link><guid isPermaLink="true">https://blog.zyfang.top/posts/fpga_render/introduction/</guid><description>本文档介绍了我自制的FPGA渲染器项目的背景、目标</description><pubDate>Tue, 01 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近看到很有意思一个项目，裸眼3D显示器。具体的实现原理就是在液晶屏幕前面加一个光栅，通过不同角度看到不同的画面实现3D效果。
&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;//player.bilibili.com/player.html?bvid=BV1DEQoYmEEp&amp;amp;p=1&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot;&amp;gt; &amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;p&gt;这让我想到很有意思的一个问题：即使有三维的显示器，也没有三维的画面内容，往往只能显示一些固定的三维图像，久看很难不看腻，但我们想看的电影、电视剧等内容都是二维的。那能不能通过某种方式，把二维的画面内容转换成三维的呢？这样就可以在享受任何数字内容的身临其境的体验，而且还要保证这个过程还是实时的，可以即插即用。&lt;/p&gt;
&lt;p&gt;这个项目主要涉及到两部分，一部分是立体信息的获取，另一部分是图像的渲染。因为本身缺少一个维度的信息，所以只能通过一些深度学习的方法来估计立体信息。通过这个立体信息，就可以生成多视点的图像，然后通过光栅进行融合显示。&lt;/p&gt;
&lt;h2&gt;立体信息的获取&lt;/h2&gt;
&lt;h2&gt;图像的渲染&lt;/h2&gt;
&lt;p&gt;这个项目的原理并不复杂，但是实现起来工程量还是挺大的。首先是光栅的设计和加工，然后是多视点图像的生成，最后是图像的融合显示。
考虑到光栅的加工难度，很难再单独设计光学硬件。这样一来，实现效果主要聚焦于两大问题，一个是多视点图像的生成，另一个是这些图形的融合。&lt;/p&gt;
</content:encoded></item><item><title>自制FPGA渲染器：系统架构</title><link>https://blog.zyfang.top/posts/fpga_render/system_arch/</link><guid isPermaLink="true">https://blog.zyfang.top/posts/fpga_render/system_arch/</guid><description>本文档介绍了我自制的FPGA渲染器项目的背景、目标</description><pubDate>Tue, 01 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;FPGA系统架构设计&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;对比维度&lt;/th&gt;
&lt;th&gt;状态机流水线（固定功能）&lt;/th&gt;
&lt;th&gt;指令式设计（可编程）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;架构思路&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;每个渲染步骤做成状态机/流水线模块，数据按流程走&lt;/td&gt;
&lt;td&gt;定义指令集，硬件执行指令驱动渲染&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;灵活性&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;低（功能固定，难扩展）&lt;/td&gt;
&lt;td&gt;高（算法可通过程序/微码修改）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;性能&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;高（专用硬件加速，时钟周期可预测）&lt;/td&gt;
&lt;td&gt;中（需要调度，性能受指令开销影响）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;资源利用&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;紧凑（LUT/DSP/BRAM 利用高效）&lt;/td&gt;
&lt;td&gt;占用较大（需指令解码器、寄存器文件、控制器）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;开发难度&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;中（需要模块化设计，但不涉及 ISA）&lt;/td&gt;
&lt;td&gt;高（需要设计 ISA、指令调度、编译/汇编工具链）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;考虑到要在ZYNQ7020实现，资源有限，小bram+DDR3指令式速度不敢想象，同时其实也不需要太复杂的渲染功能，所以我选择了状态机流水线的设计思路。&lt;/p&gt;
&lt;h1&gt;内存存取设计&lt;/h1&gt;
&lt;p&gt;在内存设计方面存在不少难点，根本的原因在于片上的bram比较小，而把数据存在DDR中，每次读取又需要十几个周期的延迟，会造成渲染帧率的降低。具体而言，渲染器需要存取以下几类数据：&lt;/p&gt;
&lt;h2&gt;Z-buffer&lt;/h2&gt;
&lt;p&gt;对于图形的消隐，考虑到效率现代最常用的算法是Zbuffer算法，也就是开辟一个屏幕空间大小的buffer，深度浅的覆盖深度深的，最终完成深度测试。对于非常紧张的bram,很难开一个全局的缓冲区，为此需要把画面分成一个一个的tile，如64x64，对于每一个tile单独进行渲染。&lt;/p&gt;
&lt;h2&gt;纹理&lt;/h2&gt;
&lt;p&gt;在纹理映射步骤中，对于纹理的访问是一个随机读取，而随机读取对于DDR又是非常不友好的，所以最好的方式是依次顺序读取，把小三角形(实际操作可能是矩形)纹理读进bram。对于每一个tile而言，也不能把所有纹理全部加载进来，而只能把正在渲染的三角形面加载进来。在渲染过程中，再配合流水线加载，可以把ddr造成的延迟消除掉。&lt;/p&gt;
&lt;h2&gt;顶点数据&lt;/h2&gt;
&lt;h3&gt;纯计算方案&lt;/h3&gt;
&lt;p&gt;每次算出的所有顶点数据全部 顺序交给下一步tile的纹理映射器，会有大量浪费，因为每个tile只会用到一部分顶点数据，其他的顶点数据白算了。&lt;/p&gt;
&lt;h3&gt;纯不计算方案&lt;/h3&gt;
&lt;p&gt;顶点数据算出来就分配给不同的tile。&lt;/p&gt;
&lt;p&gt;顶点数据的访问也是一个随机读取，很搞笑的是，我认为数据量最少的顶点数据居然也放不进bram，也只能设计ddr存取流程。&lt;/p&gt;
&lt;p&gt;在这里有一个很有意思的权衡，顶点数据如果是顺序存储的，那么可以顺序读取，但是渲染器需要按照三角形的顶点索引来访问顶点数据，这样就会变成随机读取。如果把顶点数据按照三角形的顶点索引顺序存储，那么就可以顺序读取了，但是这样会造成顶点数据的冗余存储。&lt;/p&gt;
&lt;p&gt;同时，怎么把三角形分配到各个tile上也是一个问题，可以把三角形的包围盒和tile进行相交测试，分配到相交的tile上，但是怎么存？可以为每个tile维护一个桶，把相交的三角形放进去，这样在渲染tile时就是顺序读取了，但是在写的时候又是随机写入。你可能说很简单，每个tile维护一个小的bram桶就行了，但是bram桶开多大呢？如果开得太小，如果所有面都到一个tile，可能会溢出；如果满就写一次，满就写一次，又成了随机读取。如果开得太大，又浪费bram资源。统一缓冲区，再分配，倒是一个选择...&lt;/p&gt;
&lt;p&gt;也可以把三角形顺序存储，然后每个tile维护一个索引列表，存储相交的三角形的索引，这样在渲染tile时就是随机读取了。&lt;/p&gt;
&lt;p&gt;不管怎么样，总有一个随机读取。&lt;/p&gt;
&lt;h3&gt;折中方案&lt;/h3&gt;
&lt;p&gt;也许可以浪费一点计算资源，来做一些重复计算，同时减少内存访问？比如说，把顶点分成几个大片（注意是按照乘以MVP矩阵之前的大片，是为了可预测顺序），把每个大片放到ddr中，渲染时再从bram中读取大片数据，ddr顺序读大片。&lt;/p&gt;
&lt;p&gt;我愿称之为双tile方案&lt;/p&gt;
</content:encoded></item><item><title>电动牙刷BLE指令设计</title><link>https://blog.zyfang.top/posts/toothbrush/%E7%94%B5%E5%8A%A8%E7%89%99%E5%88%B7%E6%8C%87%E4%BB%A4%E8%AE%BE%E8%AE%A1/</link><guid isPermaLink="true">https://blog.zyfang.top/posts/toothbrush/%E7%94%B5%E5%8A%A8%E7%89%99%E5%88%B7%E6%8C%87%E4%BB%A4%E8%AE%BE%E8%AE%A1/</guid><pubDate>Tue, 01 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;大端在前，每条指令均为20字节，未使用的字节填充为0&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;0x01 设置牙刷命令&lt;/h2&gt;
&lt;p&gt;该命令用于设置牙刷的工作参数，包括使能状态、振动频率、振动幅度、扫动频率、扫动幅度等。其余字节补零。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字节位置&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;6~20&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;内容&lt;/td&gt;
&lt;td&gt;使能&lt;/td&gt;
&lt;td&gt;振动频率&lt;/td&gt;
&lt;td&gt;振动幅度&lt;/td&gt;
&lt;td&gt;扫动频率&lt;/td&gt;
&lt;td&gt;扫动幅度&lt;/td&gt;
&lt;td&gt;0 填充&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;0x02 刷牙状态服务&lt;/h2&gt;
&lt;p&gt;该命令用于上报电池电量、刷牙进度和固件版本号。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字节位置&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2-3&lt;/th&gt;
&lt;th&gt;4-7&lt;/th&gt;
&lt;th&gt;8~20&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;内容&lt;/td&gt;
&lt;td&gt;电池电量&lt;/td&gt;
&lt;td&gt;刷牙进度（大端）&lt;/td&gt;
&lt;td&gt;版本号（大端）&lt;/td&gt;
&lt;td&gt;0 填充&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;0x03 RFID 标签数据&lt;/h2&gt;
&lt;p&gt;该命令用于上报刷头标签信息，包括唯一ID、生产日期、刷头类型、寿命及使用次数。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字节位置&lt;/th&gt;
&lt;th&gt;1-8&lt;/th&gt;
&lt;th&gt;9-10&lt;/th&gt;
&lt;th&gt;11&lt;/th&gt;
&lt;th&gt;12&lt;/th&gt;
&lt;th&gt;13&lt;/th&gt;
&lt;th&gt;14-15&lt;/th&gt;
&lt;th&gt;16-17&lt;/th&gt;
&lt;th&gt;18~20&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;内容&lt;/td&gt;
&lt;td&gt;标签唯一ID&lt;/td&gt;
&lt;td&gt;年份（大端）&lt;/td&gt;
&lt;td&gt;月&lt;/td&gt;
&lt;td&gt;日&lt;/td&gt;
&lt;td&gt;刷头类型&lt;/td&gt;
&lt;td&gt;寿命（大端）&lt;/td&gt;
&lt;td&gt;使用次数（大端）&lt;/td&gt;
&lt;td&gt;0 填充&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;0x04 同步历史数据服务 - 包1&lt;/h2&gt;
&lt;p&gt;历史数据同步包1用于传输时间戳、刷牙时长及刷头ID。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字节位置&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2-5&lt;/th&gt;
&lt;th&gt;6-7&lt;/th&gt;
&lt;th&gt;8-15&lt;/th&gt;
&lt;th&gt;16~20&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;内容&lt;/td&gt;
&lt;td&gt;包头&lt;/td&gt;
&lt;td&gt;时间戳（大端）&lt;/td&gt;
&lt;td&gt;时长（大端）&lt;/td&gt;
&lt;td&gt;刷头ID（大端）&lt;/td&gt;
&lt;td&gt;0 填充&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;0x04 同步历史数据服务 - 包2&lt;/h2&gt;
&lt;p&gt;历史数据同步包2用于传输刷牙位置等刷牙数据。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字节位置&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2-19&lt;/th&gt;
&lt;th&gt;20&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;内容&lt;/td&gt;
&lt;td&gt;包头&lt;/td&gt;
&lt;td&gt;刷牙数据&lt;/td&gt;
&lt;td&gt;空&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;0x07 刷牙状态数据&lt;/h2&gt;
&lt;p&gt;刷牙状态数据用于传输刷牙状态信息及剩余时间。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字节位置&lt;/th&gt;
&lt;th&gt;1-18&lt;/th&gt;
&lt;th&gt;19&lt;/th&gt;
&lt;th&gt;20&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;内容&lt;/td&gt;
&lt;td&gt;状态数据&lt;/td&gt;
&lt;td&gt;剩余时间&lt;/td&gt;
&lt;td&gt;空&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;0x08 刷牙模式设置&lt;/h2&gt;
&lt;p&gt;刷牙模式设置共5个包，每个包包含分片索引、刷头ID和部分模式数据（每包截取20字节）。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字节位置&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2-9&lt;/th&gt;
&lt;th&gt;10-20&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;内容&lt;/td&gt;
&lt;td&gt;分片索引&lt;/td&gt;
&lt;td&gt;刷头ID（大端）&lt;/td&gt;
&lt;td&gt;模式数据部分&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;0x09 UTC 时间同步&lt;/h2&gt;
&lt;p&gt;用于同步 UTC 时间，4 字节时间戳 + 填充。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字节位置&lt;/th&gt;
&lt;th&gt;1-4&lt;/th&gt;
&lt;th&gt;5-20&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;内容&lt;/td&gt;
&lt;td&gt;UTC 时间（大端）&lt;/td&gt;
&lt;td&gt;0 填充&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;0x0A 图片传输服务 - 信息包&lt;/h2&gt;
&lt;p&gt;该命令用于传输图片的基本信息，包括图片尺寸、总包数及图片格式等。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字节位置&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2-3&lt;/th&gt;
&lt;th&gt;4-5&lt;/th&gt;
&lt;th&gt;6-7&lt;/th&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;th&gt;9-16&lt;/th&gt;
&lt;th&gt;17&lt;/th&gt;
&lt;th&gt;18-19&lt;/th&gt;
&lt;th&gt;20&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;内容&lt;/td&gt;
&lt;td&gt;包头&lt;/td&gt;
&lt;td&gt;图片宽度（大端）&lt;/td&gt;
&lt;td&gt;图片高度（大端）&lt;/td&gt;
&lt;td&gt;总包数（大端）&lt;/td&gt;
&lt;td&gt;图片格式&lt;/td&gt;
&lt;td&gt;标签唯一ID（8字节）&lt;/td&gt;
&lt;td&gt;位置&lt;/td&gt;
&lt;td&gt;保留&lt;/td&gt;
&lt;td&gt;保留&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;包头：0x00 表示信息包&lt;/li&gt;
&lt;li&gt;图片宽度、高度：2字节大端&lt;/li&gt;
&lt;li&gt;总包数：2字节大端&lt;/li&gt;
&lt;li&gt;图片格式：1字节（如 0x01=RGB565）&lt;/li&gt;
&lt;li&gt;其余字节补零或保留&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;0x0A 图片传输服务 - 数据包&lt;/h2&gt;
&lt;p&gt;该命令用于分包传输图片数据，每包最多携带18字节图片数据。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字节位置&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2-19&lt;/th&gt;
&lt;th&gt;20&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;内容&lt;/td&gt;
&lt;td&gt;包序号&lt;/td&gt;
&lt;td&gt;图片数据&lt;/td&gt;
&lt;td&gt;填充&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;包序号：1字节，表示当前数据包的序号（从1开始递增）&lt;/p&gt;
&lt;p&gt;若超出则从1循坏开始(packetnumber)%255 + 1&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;图片数据：最多18字节，若不足18字节则后续字节补0&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;填充：1字节，若图片数据已满18字节则为0，否则用于补齐20字节长度&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每个数据包的格式为：命令字节（0x0A）+ 包序号 + 图片数据（18字节）+ 填充（不足时补0），共20字节。&lt;/p&gt;
&lt;h2&gt;0x0B 同步RFID名称附加信息&lt;/h2&gt;
&lt;p&gt;该命令用于上报刷头标签信息及名称&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字节位置&lt;/th&gt;
&lt;th&gt;1-8&lt;/th&gt;
&lt;th&gt;9&lt;/th&gt;
&lt;th&gt;10 ~20&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;内容&lt;/td&gt;
&lt;td&gt;标签唯一ID&lt;/td&gt;
&lt;td&gt;位置数据1、2、3&lt;/td&gt;
&lt;td&gt;字符串&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>字节大端序小端序的问题</title><link>https://blog.zyfang.top/posts/toothbrush/%E5%AD%97%E8%8A%82%E5%A4%A7%E7%AB%AF%E5%BA%8F%E5%B0%8F%E6%AE%B5%E5%BA%8F%E9%97%AE%E9%A2%98/</link><guid isPermaLink="true">https://blog.zyfang.top/posts/toothbrush/%E5%AD%97%E8%8A%82%E5%A4%A7%E7%AB%AF%E5%BA%8F%E5%B0%8F%E6%AE%B5%E5%BA%8F%E9%97%AE%E9%A2%98/</guid><pubDate>Tue, 01 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;实验现象&lt;/h2&gt;
&lt;p&gt;同步图片是这个
&lt;img src=&quot;image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;实际上屏幕显示
&lt;img src=&quot;image-1.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;问题原因&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;像素数据
0000 0001 0002 0003 0004 0005 0006 0007 0008

生成的指令数据
0A02000000010002000300040005000600070008
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在设备端使用代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;memcpy(&amp;amp;buf, data+2, 18);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于设备端使用的是小端序，所以需要将数据转换为大端序。
因此需要将数据转换为大端序，正确的指令数据应该是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uint16_t pixel = (data[2+i] &amp;lt;&amp;lt; 8) | data[3+i];
buf[offest+i] = pixel&amp;amp;0xFF;
buf[offest+i+1] = (pixel&amp;gt;&amp;gt;8)&amp;amp;0xFF;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;常见情况&lt;/h2&gt;
&lt;p&gt;:::note
在LVGL中也有类似的设置项
即为像素的swap选项
一定记得注意统一😂
:::&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;处理器架构&lt;/th&gt;
&lt;th&gt;默认字节序&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ARM（Cortex-M）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;小端序&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cortex-M（如 STM32、NRF52）等低功耗微控制器常用小端序。&amp;lt;br&amp;gt;ARM Cortex-A 也通常默认小端，但可以配置成大端。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MIPS&lt;/td&gt;
&lt;td&gt;可配置，但多数是&lt;strong&gt;小端序&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;某些嵌入式设备或老旧网络设备可能使用大端。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PowerPC&lt;/td&gt;
&lt;td&gt;多为&lt;strong&gt;大端序&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;早期嵌入式和网络设备中常见。现在较少使用。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RISC-V&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;小端序&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;默认小端序，也支持大端（但很少用）。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;x86（Intel）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;小端序&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;工业控制或嵌入式PC中常见。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>CH340x自动下载电路调试心得</title><link>https://blog.zyfang.top/posts/toothbrush/ch340x%E8%87%AA%E5%8A%A8%E4%B8%8B%E8%BD%BD%E7%94%B5%E8%B7%AF%E8%B0%83%E8%AF%95%E5%BF%83%E5%BE%97/</link><guid isPermaLink="true">https://blog.zyfang.top/posts/toothbrush/ch340x%E8%87%AA%E5%8A%A8%E4%B8%8B%E8%BD%BD%E7%94%B5%E8%B7%AF%E8%B0%83%E8%AF%95%E5%BF%83%E5%BE%97/</guid><pubDate>Tue, 01 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;问题1 识别问题&lt;/h2&gt;
&lt;p&gt;大多串口插件比较离谱的筛选规定
只能识别ttyS..和ttyUSB..(ttyCH341USB0:那我走？)
导致默认不显示，自己去open比较麻烦
于是修改驱动，将设备名称命名为ttySCH341USBxx&lt;/p&gt;
&lt;h2&gt;问题2 自动复位电路调试记录&lt;/h2&gt;
&lt;p&gt;:::tip
问题的本质
CH340X的DTR#虽然有两种模式，分别是默认为低和高，但Linux驱动会自己设置默认值😂
:::&lt;/p&gt;
&lt;p&gt;有RST#和DTR#引脚(注意这里的#号，代表相反的意思)&lt;/p&gt;
&lt;p&gt;默认初始状态或者close以后的状态是全0,即DTR，RST都是低电平，DTR#和RST#都是高电平&lt;/p&gt;
&lt;p&gt;RST#倒是正好，因为单片机的复位逻辑也是RST#&lt;/p&gt;
&lt;p&gt;但DTR#为高就比较麻烦，也就是BOOT引脚为高（和单片机BOOT逻辑正好相反，单片机在下载模式），导致单片机无法正常启动，此时手动按复位键也没有用&lt;/p&gt;
&lt;p&gt;ps：也有的单片机BOOT逻辑是BOOT#，这个要看清楚，如果是这样的就不用修改&lt;/p&gt;
&lt;p&gt;遂修改一下驱动（开源yyds）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static void ch341_port_shutdown(struct tty_port *port)
{
	struct ch341 *ch341 = container_of(port, struct ch341, port);
	struct urb *urb;
	struct ch341_wb *wb;
	int i;

#if (LINUX_VERSION_CODE &amp;gt;= KERNEL_VERSION(3, 16, 0))
	usb_autopm_get_interface_no_resume(ch341-&amp;gt;data);
	ch341-&amp;gt;data-&amp;gt;needs_remote_wakeup = 0;
	ch341-&amp;gt;ctrlout = CH341_CTO_D;//add
    ch341_set_control(ch341, ch341-&amp;gt;ctrlout);//add
	usb_autopm_put_interface(ch341-&amp;gt;data);
    //******existing code
    //对于内核版本较小的修改也是类似的
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;经过测试一切正常，单片机可以正常启动&lt;/p&gt;
&lt;p&gt;另外附一段软件复位&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;sys/ioctl.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;

int main() {
    const char *serial_port = &quot;/dev/ttySCH341USB0&quot;;  // 修改为你的串口设备
    int fd = open(serial_port, O_RDWR | O_NOCTTY);
    if (fd &amp;lt; 0) {
        perror(&quot;open&quot;);
        return EXIT_FAILURE;
    }

    int status;

    // 获取当前控制线状态
    if (ioctl(fd, TIOCMGET, &amp;amp;status) == -1) {
        perror(&quot;TIOCMGET&quot;);
        close(fd);
        return EXIT_FAILURE;
    }

    // 拉高 DTR
    status |= TIOCM_DTR;

    // 拉高 RTS（RTS# = 低电平，置0）
    status |= TIOCM_RTS;

    if (ioctl(fd, TIOCMSET, &amp;amp;status) == -1) {
        perror(&quot;TIOCMSET (DTR low, RTS low)&quot;);
        close(fd);
        return EXIT_FAILURE;
    }

    printf(&quot;DTR# 已拉低，RTS# 已拉低（复位开始）\n&quot;);

    // 延时100ms（可根据需要修改）
    usleep(100000);  // 100,000微秒 = 100ms
    //拉低RST
    status &amp;amp;= ~TIOCM_RTS;

    if (ioctl(fd, TIOCMSET, &amp;amp;status) == -1) {
        perror(&quot;TIOCMSET (RTS high)&quot;);
        close(fd);
        return EXIT_FAILURE;
    }

    printf(&quot;RTS# 已拉高（复位完成）\n&quot;);

    // 保持状态（可根据需要修改延时或退出前清理）
    sleep(1);

    close(fd);
    return EXIT_SUCCESS;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;gcc set_dtr_rts_reset.c -o set_dtr_rts_reset
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item></channel></rss>