<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<title>NOTHIN’ HERE</title>
	<subtitle>这里什么也没有。</subtitle>
	<link href="https://duo3.ink/"/>
	<link rel="self" href="https://duo3.ink/atom.xml"/>
	<id>https://duo3.ink/</id>
	<updated>2026-05-28T04:05:37.445Z</updated>
	<author><name>123哆3</name></author>
	
	<entry>
		<title>困在过去的温湿度计</title>
		<link href="https://duo3.ink/2026/05/27/困在过去的温湿度计/"/>
		<id>https://duo3.ink/2026/05/27/困在过去的温湿度计/</id>
		<published>2026-05-27T08:45:00.000Z</published>
		<updated>2026-05-27T08:45:00.000Z</updated>
		<author><name>123哆3</name></author>
		<summary>去年十月，我买了一块米家智能温湿度计3。到手之后却发现，时间竟会倒着走。拍视频给售后确认了之后，直接退了款，让我自己再下单一个。</summary>
		<content type="html"><![CDATA[<h2>一</h2>
<p>去年十月，我买了一块米家智能温湿度计3。到手之后却发现，时间竟会倒着走。拍视频给售后确认了之后，直接退了款，让我自己再下单一个。</p>
<!-- more -->
<p>新的很好，功能也正常。旧的售后也没要回去。那旧的怎么办？撇了？核心功能温湿度又没坏，所以我把它放在了我的桌前。时间嘛，电脑、手机、手表，哪个不能看。</p>
<p>不过看温湿度的时候，总是会注意到时间。看久了居然也看出了规律来。十分钟一循环。设定时间后，过了十分钟，就会又回到设定的时间。如果设定的时间刚好要在十分钟内跨小时，那么它将会在跨小时的时候重置，连十分钟都不到。我想办法让自己忽略时间，并关掉了星期显示，希望能让它不那么惹眼。</p>
<p>但看多了总觉得膈应。温湿度计的生命只有十分钟，我要给它完整的一生。起初，我以为是出厂固件的问题，但是升级、重刷都没有用。于是我想试试第三方的固件，看看能不能解决。</p>
<h2>二</h2>
<p><a href="https://github.com/pvvx/ATC_MiThermometer">ATC<wbr>_Mi<wbr>Thermometer</a>是一个BLE温度计的自定义固件，适配基于Telink芯片组的小米温湿度计和其他的一些设备，并且还有个<a href="https://pvvx.github.io/ATC_MiThermometer/TelinkMiFlasher.html">Telink Flasher</a>，基于Web Bluetooth，可以在线配置和刷机。</p>
<p>我没想到这么个小玩意还有人在折腾，于是满怀期待地刷了进去。</p>
<p>天啊！它活过了第一个十分钟，时间在十分钟之后依然在走！</p>
<p>看来问题就这么轻易地解决了，官方固件确实有问题。在第三方固件的加持下，甚至还更省电。这位温湿度计能安心地当它的摆件了。</p>
<h2>三</h2>
<p>好景不长，事与愿违。温湿度计还是难逃循环的命运。过了半个小时，我发现时钟又重置回了我设定的时刻。是不是这台机器的内存有问题，让它记录不了走过的这么长时间？</p>
<p>我把记录温湿度的间隔调长，最多记录的条数减到最少，希冀着它能走过更久的时间。上天似乎回应了我的愿望，温湿度计的时钟走过了半个小时，走过了四十五分钟，最终，倒在了整点之前。</p>
<p>整点似乎是个坚固的魔障，每当温湿度计的时钟想要跨越它，都会被无情地弹回上次同步的时刻。我取回温湿度计的记录，一次又一次跨越整点的尝试织成了一张紧密的网，似乎揭示着这温湿度计在一次次循环中沉沦的命运。</p>
<figure data-has-ratio="true" style="--figure-width: 1280; --figure-height: 1034"><img src="/images/article_asset/%E5%9B%B0%E5%9C%A8%E8%BF%87%E5%8E%BB%E7%9A%84%E6%B8%A9%E6%B9%BF%E5%BA%A6%E8%AE%A1/chaos_chart.jpeg" alt="由于时间循环而重叠在一起的折线图" loading="lazy" decoding="async" width="1280" height="1034" fetchpriority="low" /></figure>
<p>算了，我尽力了。我想。以后个隔三差五同步一下时间吧。但是，我亲爱的朋友，你知道的，隔三差五最终总是意味着永远。</p>
<p>直到昨天，我的温湿度计还困在2025年的十二月，没能走出那个冬天。</p>
<h2>四</h2>
<p>阴雨连绵，这两天空气潮湿得让我以为回到了南方。我的注意力又频繁回到了温湿度计上，为我的烦躁寻求一份解释。</p>
<p>我自然注意到了时间， 2025年十二月，那是我最后一次想起来给温湿度计同步的时间，距今已有将近半年。</p>
<p>好奇心又一次被勾起。我想知道这到底是怎么一回事，为什么时间会循环走不出整点，以及能不能修复。</p>
<p>米家温湿度计3 <wbr>/ MJWSD05MMC的主控是Telink TLSR8250，可以软件计时。除此之外，它还有一个单独的RTC时钟芯片AT85163T (PCF85163) 来提供更准确的时间。</p>
<p>平时，主控会用自己的软件时间驱动LCD显示，在屏幕上展示具体的时间。每隔一段时间，主控就会从RTC读取更准确的时间来矫正自身，从而让显示的时间更加精准，同时降低功耗。而这个从RTC同步准确时间的间隔，在官方固件上是十分钟，在pvvx的固件上是每整小时。</p>
<p>对上了，都对上了。看起来是这块RTC芯片出了问题。</p>
<p>在Chat<wbr>GPT的协助下，我打开了<a href="https://github.com/nordicsemi/Android-nRF-Connect">n<wbr>RF Connect</a>。这是之前用<a href="https://github.com/ECTO-1A/AppleJuice">Apple<wbr>Juice</a>整蛊使用i<wbr>Phone的朋友时下载的，没想到还能派上正经用场。</p>
<p>利用pvvx固件暴露的BLE调试命令扫I²C总线。按照文档，我在私有服务<code>0x1F10</code>中的特征<code>0x1F1F</code>下发送了命令<code>0x03</code>，并且得到了返回通知<code>(0x) 03-<wbr>7C-<wbr>88-<wbr>A2</code>。其中<code>03</code>是命令号，后边的就是I²C设备地址了。</p>
<p><code>0x<wbr>A2</code>就是RTC的地址，说明他还能在总线上ACK确认。问题出在RTC的内部。</p>
<p>进一步，发送<code>04-<wbr>01-<wbr>10-<wbr>A2-<wbr>00-<wbr>00</code>读RTC的16个字节，得到<code>04-<wbr>01-<wbr>A2-<wbr>08-<wbr>1F-<wbr>FF-<wbr>7F-<wbr>3F-<wbr>3F-<wbr>07-<wbr>9F-<wbr>FF-<wbr>FF-<wbr>BF-<wbr>BF-<wbr>87-<wbr>83-<wbr>87-<wbr>FF</code>。</p>
<p>其中第四个<code>08</code>，也就是从RTC读出的第一个 (<code>0x00</code>) 字节，是Control<wbr>_1，代表着RTC的状态，按照PCF85163的定义，似乎是正常的。紧跟着下一位的Control<wbr>_2则是<code>1F</code>，<code>AF</code>、<code>TF</code>、<code>AIE</code>、<code>TIE</code>全被点亮，意味着告警和定时器中断。</p>
<p>再往后，<code>FF</code>这一位就是秒了，最高位的<code>VL</code>被置位，表示芯片经历过电压跌落，所记的时间已经不可信。后边的分、时、日之类的也全是非法BCD。</p>
<p>如果运气好的话，把它覆写成正常的值就能让RTC恢复正常。可惜我的运气并不好。覆写成正常值之后半天没有动静，还是原来写入的时间。后来试了很多次，扣电池、短接、重置。除了Control变回了报错值之外，时种一秒也没有走。</p>
<p>排查到这里整件事就已经很明朗了：可能由于时钟源晶振坏了或者缺焊，导致RTC的计时震荡或者计数坏掉了，但自检却没发现，让时钟一直停在最后写入的值。而主控会按照设定好的时间间隔，从RTC读取这个固定的值，所以才会时间循环，一直重置到上次同步的时间。</p>
<h2>五</h2>
<p>知道问题了就好对症下药，把主控从RTC读精确时间这一行为改掉了就行。遗憾的是，似乎不能直接从软件上把RTC直接断电，不然还能更省电。</p>
<p>在Claude的协助下，我Clone了pvvx固件的源码。起初，我们以为把RTC从I²C上屏蔽掉就可以了：</p>
<pre class="shiki shiki-themes custom-nothin-light custom-nothin-dark" style="background-color:transparent;--shiki-dark-bg:transparent;color:#4f504e;--shiki-dark:#c8c9c6" tabindex="0"><span class="code-block-header" aria-hidden="true"><span class="code-block-filename">rtc_pcf85163.c</span><span class="code-block-language">c</span></span><code class="language-c"><span class="line"><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold">void</span><span style="color:#588571;--shiki-light-font-weight:bold;--shiki-dark:#A3CCBA;--shiki-dark-font-weight:bold"> init_rtc</span><span style="color:#2B8074;--shiki-dark:#80A4A0">(</span><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold">void</span><span style="color:#2B8074;--shiki-dark:#80A4A0">)</span><span style="color:#2B8074;--shiki-dark:#80A4A0"> {</span></span>
<span class="line"><span style="color:#2B8074;--shiki-dark:#80A4A0">    (</span><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold">void</span><span style="color:#2B8074;--shiki-dark:#80A4A0">)</span><span style="color:#4F504E;--shiki-dark:#C8C9C6"> rtc_init_b1</span><span style="color:#2B8074;--shiki-dark:#80A4A0">;</span></span>
<span class="line"><span style="color:#2B8074;--shiki-dark:#80A4A0">    (</span><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold">void</span><span style="color:#2B8074;--shiki-dark:#80A4A0">)</span><span style="color:#4F504E;--shiki-dark:#C8C9C6"> rtc_init_b2</span><span style="color:#2B8074;--shiki-dark:#80A4A0">;</span></span>
<span class="line"><span style="color:#4F504E;--shiki-dark:#C8C9C6">    rtc_i2c_addr </span><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold">=</span><span style="color:#30895A;--shiki-dark:#8AAB99"> 0</span><span style="color:#2B8074;--shiki-dark:#80A4A0">;</span></span>
<span class="line"><span style="color:#588571;--shiki-light-font-weight:bold;--shiki-dark:#A3CCBA;--shiki-dark-font-weight:bold">    utime_to_rtc</span><span style="color:#2B8074;--shiki-light-font-weight:bold;--shiki-dark:#80A4A0;--shiki-dark-font-weight:bold">(</span><span style="color:#747E5B;--shiki-light-font-weight:bold;--shiki-dark:#B8BE9E;--shiki-dark-font-weight:bold">wrk</span><span style="color:#2B8074;--shiki-light-font-weight:bold;--shiki-dark:#80A4A0;--shiki-dark-font-weight:bold">.</span><span style="color:#747E5B;--shiki-light-font-weight:bold;--shiki-dark:#B8BE9E;--shiki-dark-font-weight:bold">utc_time_sec</span><span style="color:#2B8074;--shiki-light-font-weight:bold;--shiki-dark:#80A4A0;--shiki-dark-font-weight:bold">,</span><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold"> &#x26;</span><span style="color:#588571;--shiki-light-font-weight:bold;--shiki-dark:#A3CCBA;--shiki-dark-font-weight:bold">rtc</span><span style="color:#2B8074;--shiki-light-font-weight:bold;--shiki-dark:#80A4A0;--shiki-dark-font-weight:bold">)</span><span style="color:#2B8074;--shiki-dark:#80A4A0">;</span></span>
<span class="line"><span style="color:#2B8074;--shiki-dark:#80A4A0">}</span></span>
<span class="line"><span style="color:#4F504E;--shiki-dark:#C8C9C6">...</span></span>
<span class="line"><span style="color:#4F504E;--shiki-dark:#C8C9C6">u32 </span><span style="color:#588571;--shiki-light-font-weight:bold;--shiki-dark:#A3CCBA;--shiki-dark-font-weight:bold">rtc_get_utime</span><span style="color:#2B8074;--shiki-dark:#80A4A0">(</span><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold">void</span><span style="color:#2B8074;--shiki-dark:#80A4A0">)</span><span style="color:#2B8074;--shiki-dark:#80A4A0"> {</span></span>
<span class="line"><span style="color:#588571;--shiki-light-font-weight:bold;--shiki-dark:#A3CCBA;--shiki-dark-font-weight:bold">    utime_to_rtc</span><span style="color:#2B8074;--shiki-light-font-weight:bold;--shiki-dark:#80A4A0;--shiki-dark-font-weight:bold">(</span><span style="color:#747E5B;--shiki-light-font-weight:bold;--shiki-dark:#B8BE9E;--shiki-dark-font-weight:bold">wrk</span><span style="color:#2B8074;--shiki-light-font-weight:bold;--shiki-dark:#80A4A0;--shiki-dark-font-weight:bold">.</span><span style="color:#747E5B;--shiki-light-font-weight:bold;--shiki-dark:#B8BE9E;--shiki-dark-font-weight:bold">utc_time_sec</span><span style="color:#2B8074;--shiki-light-font-weight:bold;--shiki-dark:#80A4A0;--shiki-dark-font-weight:bold">,</span><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold"> &#x26;</span><span style="color:#588571;--shiki-light-font-weight:bold;--shiki-dark:#A3CCBA;--shiki-dark-font-weight:bold">rtc</span><span style="color:#2B8074;--shiki-light-font-weight:bold;--shiki-dark:#80A4A0;--shiki-dark-font-weight:bold">)</span><span style="color:#2B8074;--shiki-dark:#80A4A0">;</span></span>
<span class="line"><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold">    return</span><span style="color:#747E5B;--shiki-dark:#B8BE9E"> wrk</span><span style="color:#2B8074;--shiki-dark:#80A4A0">.</span><span style="color:#747E5B;--shiki-dark:#B8BE9E">utc_time_sec</span><span style="color:#2B8074;--shiki-dark:#80A4A0">;</span></span>
<span class="line"><span style="color:#2B8074;--shiki-dark:#80A4A0">}</span></span></code></pre>
<p>但神奇的是，由于主控会在每跨小时的时候从RTC读取时间，所以主控计算时间时压根就没考虑过小时进位，还是会每跨小时就循环。所以还得把小时进位给加上：</p>
<pre class="shiki shiki-themes custom-nothin-light custom-nothin-dark" style="background-color:transparent;--shiki-dark-bg:transparent;color:#4f504e;--shiki-dark:#c8c9c6" tabindex="0"><span class="code-block-header" aria-hidden="true"><span class="code-block-filename">app.c</span><span class="code-block-language">c</span></span><code class="language-c"><span class="line"><span style="color:#2B8074;--shiki-light-font-weight:bold;--shiki-dark:#80A4A0;--shiki-dark-font-weight:bold">#</span><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold">if</span><span style="color:#2B8074;--shiki-dark:#80A4A0"> (</span><span style="color:#588571;--shiki-light-font-weight:bold;--shiki-dark:#A3CCBA;--shiki-dark-font-weight:bold">DEV_SERVICES</span><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold"> &#x26;</span><span style="color:#588571;--shiki-light-font-weight:bold;--shiki-dark:#A3CCBA;--shiki-dark-font-weight:bold"> SERVICE_HARD_CLOCK</span><span style="color:#2B8074;--shiki-dark:#80A4A0">)</span></span>
<span class="line"><span style="color:#4F504E;--shiki-dark:#C8C9C6">    u8 prev_min </span><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold">=</span><span style="color:#4F504E;--shiki-dark:#C8C9C6"> rtc.minutes</span><span style="color:#2B8074;--shiki-dark:#80A4A0">;</span></span>
<span class="line"><span style="color:#588571;--shiki-light-font-weight:bold;--shiki-dark:#A3CCBA;--shiki-dark-font-weight:bold">    utime_to_rtc</span><span style="color:#2B8074;--shiki-dark:#80A4A0">(</span><span style="color:#4F504E;--shiki-dark:#C8C9C6">wrk.utc_time_sec</span><span style="color:#2B8074;--shiki-dark:#80A4A0">,</span><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold"> &#x26;</span><span style="color:#747E5B;--shiki-dark:#B8BE9E">rtc</span><span style="color:#2B8074;--shiki-dark:#80A4A0">);</span></span>
<span class="line"><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold">    if</span><span style="color:#2B8074;--shiki-dark:#80A4A0">(</span><span style="color:#4F504E;--shiki-dark:#C8C9C6">rtc.minutes </span><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold">!=</span><span style="color:#4F504E;--shiki-dark:#C8C9C6"> prev_min</span><span style="color:#2B8074;--shiki-dark:#80A4A0">)</span></span>
<span class="line"><span style="color:#588571;--shiki-light-font-weight:bold;--shiki-dark:#A3CCBA;--shiki-dark-font-weight:bold">        SET_LCD_UPDATE</span><span style="color:#2B8074;--shiki-dark:#80A4A0">();</span></span>
<span class="line"><span style="color:#2B8074;--shiki-light-font-weight:bold;--shiki-dark:#80A4A0;--shiki-dark-font-weight:bold">#</span><span style="color:#678334;--shiki-light-font-weight:bold;--shiki-dark:#ABB498;--shiki-dark-font-weight:bold">endif</span></span></code></pre>
<p>找个Linux环境编译好，用Telink Flasher给刷进去。</p>
<figure data-has-ratio="true" style="--figure-width: 5886; --figure-height: 3924"><img src="/images/article_asset/%E5%9B%B0%E5%9C%A8%E8%BF%87%E5%8E%BB%E7%9A%84%E6%B8%A9%E6%B9%BF%E5%BA%A6%E8%AE%A1/thermometer_fixed.jpg" alt="正常走时的温湿度计" loading="lazy" decoding="async" width="5886" height="3924" fetchpriority="low" /></figure>
<p>现在，这台温湿度计终于走出了循环的魔沼，向着未来一去不复返。而我也拥有了一台虽然走时不太准，但是也能看的温湿度计。可喜可贺。<span class="post-end-mark">●</span></p>]]></content>
		<link rel="enclosure" href="https://duo3.ink/images/article_asset/困在过去的温湿度计/cover.webp" type="image/webp"/>
		<category term="回顾" label="category"/>
		<category term="刷机" label="tag"/>
		<category term="编程" label="tag"/>
	</entry>
	<entry>
		<title>叉烧</title>
		<link href="https://duo3.ink/2026/05/17/叉烧/"/>
		<id>https://duo3.ink/2026/05/17/叉烧/</id>
		<published>2026-05-17T08:37:00.000Z</published>
		<updated>2026-05-17T08:37:00.000Z</updated>
		<author><name>123哆3</name></author>
		<summary>制作约需一天。</summary>
		<content type="html"><![CDATA[<ul>
<li>制作约需一天。</li>
</ul>
<!-- more -->
<ul>
<li>
<p>食谱来自<a href="https://www.bilibili.com/video/BV1YL4y1K7rm/">简简厨房</a>，有所改良。</p>
</li>
<li>
<p>低温慢烤有助于将肥肉中的油烤出，使其软糯不腻。</p>
</li>
<li>
<p>本食谱中出现所有的糖，如白砂糖、蜂蜜、麦芽糖等，均可用等量的木糖醇或赤藓糖醇替代。</p>
</li>
<li>
<p>如果你觉得还不够味，可以考虑在剩余的叉烧酱中加入适量蜂蜜或麦芽糖，熬至浓稠，淋在切好的肉片上。</p>
</li>
</ul>
<table>
<thead>
<tr>
<th style="text-align:left">原料 — 猪肉</th>
<th style="text-align:right">用量</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">去皮五花肉 <wbr>/ 梅花肉</td>
<td style="text-align:right">500 g</td>
</tr>
<tr>
<td style="text-align:left">小苏打</td>
<td style="text-align:right">2 g</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th style="text-align:left">原料 — 叉烧酱</th>
<th style="text-align:right">用量</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">盐</td>
<td style="text-align:right">2 g</td>
</tr>
<tr>
<td style="text-align:left">白砂糖</td>
<td style="text-align:right">40 g</td>
</tr>
<tr>
<td style="text-align:left">蜂蜜</td>
<td style="text-align:right">20 g</td>
</tr>
<tr>
<td style="text-align:left">生抽</td>
<td style="text-align:right">20 g</td>
</tr>
<tr>
<td style="text-align:left">蚝油</td>
<td style="text-align:right">20 g</td>
</tr>
<tr>
<td style="text-align:left">南乳（红腐乳）</td>
<td style="text-align:right">20 g</td>
</tr>
<tr>
<td style="text-align:left">南乳汁</td>
<td style="text-align:right">10 g</td>
</tr>
<tr>
<td style="text-align:left">芝麻酱<br>或<br>香油</td>
<td style="text-align:right">5 g<br><br>几滴</td>
</tr>
<tr>
<td style="text-align:left">玫瑰露酒 <wbr>/ 白酒 <wbr>/ 料酒</td>
<td style="text-align:right">5 g</td>
</tr>
</tbody>
</table>
<h5>步骤</h5>
<ol>
<li>
<p>五花肉去皮，切成约15 cm长的条；</p>
</li>
<li>
<p>如果你不确定你的猪肉是否新鲜，则加入小苏打，加水浸泡半小时；</p>
</li>
<li>
<p>将所有叉烧酱的原料混合均匀；</p>
</li>
<li>
<p>将猪肉取出，清洗沥干，擦干表面水分，加入叉烧酱，抓拌均匀，放入冰箱腌制一夜；</p>
</li>
<li>
<p>将肉放在烤网上，刷一层叉烧酱，下垫烤盘用于接油，放入烤箱，120°C烤一个小时；</p>
</li>
<li>
<p>翻面，刷叉烧酱，180°C烤约15分钟，直至表面焦褐到你想要的程度；</p>
</li>
<li>
<p>取出、切片、吃。</p>
</li>
</ol>
<figure data-has-ratio="true" style="--figure-width: 5670; --figure-height: 3780"><img src="/images/article_asset/%E5%8F%89%E7%83%A7/cooked.jpg" alt="成品" loading="lazy" decoding="async" width="5670" height="3780" fetchpriority="low" /></figure>
<p>享用！<span class="post-end-mark">●</span></p>]]></content>
		<link rel="enclosure" href="https://duo3.ink/images/article_asset/叉烧/cover.webp" type="image/webp"/>
		<category term="食谱" label="category"/>
		<category term="烹饪" label="tag"/>
		<category term="粤菜" label="tag"/>
	</entry>
	<entry>
		<title>我终于做完了博客</title>
		<link href="https://duo3.ink/2026/04/09/我终于做完了博客/"/>
		<id>https://duo3.ink/2026/04/09/我终于做完了博客/</id>
		<published>2026-04-09T10:06:50.000Z</published>
		<updated>2026-04-09T10:06:50.000Z</updated>
		<author><name>123哆3</name></author>
		<summary>「web 2.0时代，人人都会有自己的个人主页。」如果你经历过那个年代，那么肯定会对那个万物竞发的景象印象深刻：懂点技术的都在折腾自己的博客，而不懂技术的，也要在QQ空间之类的地方装装点点，玩出花来。</summary>
		<content type="html"><![CDATA[<h2>一</h2>
<p><span class="halt-punctuation halt-start">「</span>web 2<wbr>.0时代，人人都会有自己的个人主页。」如果你经历过那个年代，那么肯定会对那个万物竞发的景象印象深刻：懂点技术的都在折腾自己的博客，而不懂技术的，也要在QQ空间之类的地方装装点点，玩出花来。</p>
<!-- more -->
<p>作为「数字原住民」的我也被这盛况感动，思忖着，想在这广袤的互联网空间开辟一块属于自己的地方。</p>
<p>但也只是想想。等我真正拥有了自己的电脑的时候，主流操作系统从Windows XP变成了Windows 7，IE也被Chrome替代。那股人人搭博客的热情也消散得差不多，大家又回到了贴吧、天涯，图的是人多热闹。</p>
<p>但论坛的黄金期也没撑过几年。微博、B站快速成为主流，快手、抖音崭露头角。「人人都有个人主页」的预言的确实现了，只是依附在各个社交平台上。</p>
<p>这很好，每个人都能输出内容，每个人都能被看见。但裹挟在爆炸信息中的我突然想到：我在这片互联网上还是没有一个真正属于自己的地方。</p>
<p>瓜田不纳履，李下不整冠。每个平台有每个平台的规矩与限制。我发现我还是想要一个自己的网站，不寄人篱下，受制于人。</p>
<h2>二</h2>
<p>博客是个人网站最常见的形式。我也了解到了Git<wbr>Hub Pages的存在。Jekyll的主题挑花了眼，配置文档慢慢才琢磨透。输入属于自己的github<wbr>.io网址，看着顶上大片的蓝绿色渐变，兴奋之余，我却觉得，这还不是我想要的。</p>
<p>我想要的究竟是什么？</p>
<p>我在画图上画，在Illustrator上画，在Figma上画。工具换了一轮又一轮，设计稿攒了一版又一版。</p>
<p>外界的趋势也在变：从分页到无限滚动，从网格到瀑布流。我跟着潮流走了一圈，却转身看向了纸。</p>
<p>是<a href="https://m1.material.io">第一代的Material Design</a>提醒了我这一点。它把纸与墨、层次与留白重新带回了屏幕，几乎可以与Apple的<a href="https://developer.apple.com/design/human-interface-guidelines/">人机界面指南</a>分庭抗礼，证明了纸上的排版智慧在屏幕上依然成立。</p>
<p>屏幕越来越大，但为了阅读舒适，网页的行长却缩得越来越窄，在屏幕两侧留下了越来越大的空间。两侧要么空着，要么被目录、作者简介等别的信息填满。</p>
<p>纸媒是怎么处理宽版面的？人们在纸上排版了上千年，这个问题早就有了成熟的答案。看看大开本的报纸、书刊、杂志，答案很明显：分栏。</p>
<p>剩下的则水到渠成。屏幕是横的，分栏也是横着分，横向滚动似乎是一件再自然不过的事情。设计稿画出来了，效果很好。我踌躇满志，决定把这设计给实现。</p>
<h2>三</h2>
<p>这时，静态博客生成器已经很成熟了。我选择了<a href="https://hexo.io">Hexo</a>，生态齐全、部署到Git<wbr>Hub Pages上也方便。唯一的不足是我对前端三件套：HTML、CSS以及JS没有任何了解。</p>
<p>所幸前端很容易上手。而Hexo又把网站结构以及Markdown渲染之类的东西都封装好了，我只需要照猫画虎、按图索骥，把主题写好就行。我一边看菜鸟教程，一边把学到的东西转换成EJS和Stylus。导航栏、文章卡片，网站的整体框架很快就搭建了起来。</p>
<p>现代网页工具终究还是为竖向滚动服务的。当实现到文章页时，这个问题就完全暴露了出来。在CSS中，<code>column-<wbr>width</code>与<code>column-<wbr>count</code>所定义的数值都是「理想的」，浏览器会根据容器和窗口大小重新计算。在纵向滚动时，这个特性很方便。在横向滚动上，却成为了很大的阻碍。</p>
<p>基于<a href="https://www.thetype.com/2017/07/12513/">「行长为字号的整数倍」</a>的原则，我将行长设为了24个字，也就是<code>24em</code>。固定行长也带来了一个意外的收获：我开始像排印刷品一样处理中西文混排，调整用词，避免两端对齐拉出难看的大段空白。我甚至考虑起孤行、孤字。这是以前纸上才会有的体验。</p>
<p>然而，浏览器会自动将栏宽和栏数设定为屏幕刚好能装下。这与我的需求相悖，还完美避开了露出半栏内容，提示用户滚动方向的可能。</p>
<p>我找到了一个邪门的方法：在<code>column</code>内再套一层，在里边定死宽度。这是个未定义行为，在所有的浏览器内核上，分栏的整体宽度都会测量失败，导致栏后边与其他元素重叠。但这是我找到的唯一方法。</p>
<p>最大的问题解决了，我很快定义完了Markdown元素的样式，搭建好了其他几个页面，博客建成近在眼前。</p>
<p>一些朋友告诉我Hexo有点过时了。这在我写的时候就已经察觉到了：EJS很笨重、模板年久失修、没有组件化概念、资源管线老旧。我经常要使用一些很脏的办法来修改一些功能，也有一些地方很难自定义。</p>
<p>我是一个逐新的人，我希望我刚写出来的网站至少用的不是现在就已经过时的技术，因为我将来还要数以年计地维护这个网站。</p>
<p>我转向了<a href="https://svelte.dev/">Svelte</a>，单文件组件、响应式赋值、作用域样式——之前在Hexo里要绕着弯才能做到的事，在这里都是理所应当。</p>
<p>但是Svelte不是静态博客生成器。它是一整个前端组件框架，没有现成的路由、也不管Markdown渲染。这意味着我在享受摆脱Hexo的自由时， 要亲自负责它之前接管的脏活累活。</p>
<p>由于其他事务缠身，我没有很多的精力投入去学习这一崭新的领域。我也急于求成，只想调整视觉效果，不想干这些背后的活计。进展越来越慢，最终搁置。</p>
<h2>四</h2>
<p>这一放就是一整年。期间我无数次想捡起来，却怯于背后要投入的精力。直到vibe coding爆火，我半信半疑，想找个项目试试。</p>
<p>我立即想到搁置的博客。</p>
<p>导入项目，看着之前半成品繁杂的文件，我手足无措，似乎回到了刚下载好Hexo模版的时候。即便有了LLM做帮手，我还是不知道该让它做什么。</p>
<p>我想了想，试着在对话框中输入：「我现在对这个项目毫无头绪，你能看看我接下来应该做什么吗？」</p>
<p>它读了半天文件，又想了一会，开始输出。把我积压一堆的事情抽丝剥茧，排列得井然有序。也刚好，我的前端水平处在写不出来，但能看懂的位置。我一边感叹时代进步，一边审查LLM写的代码。</p>
<p>接下来的事情就容易多了。如果抛去<a href="https://antigravity.google/">Antigravity</a>大砍额度至几近不可用，令我逃窜到<a href="https://openai.com/codex/">Codex</a>的故事，整件事进展得飞快。</p>
<p>我把之前的样式迁移过来，并作了改进，又完成了归档页面。LLM也把之前栏后内容重叠的bug用JS解决了。甚至还用<a href="https://pagefind.app/">Pagefind</a>给网站接上了搜索。当然，LLM还是不擅长视觉上的东西，每次写组件还是错漏百出，要我手动改。但整体还是快到不真实。</p>
<p>再后来，博客就是现在你眼前的样子了。我也终于可以在偌大的互联网中偏安一隅，有了自己的地方。<span class="post-end-mark">●</span></p>]]></content>
		<link rel="enclosure" href="https://duo3.ink/images/article_asset/我终于做完了博客/cover.webp" type="image/webp"/>
		<category term="回顾" label="category"/>
		<category term="设计" label="tag"/>
		<category term="排版" label="tag"/>
		<category term="博客" label="tag"/>
	</entry>
	<entry>
		<title>在 RecyclerView 中实现延迟滚动</title>
		<link href="https://duo3.ink/2024/05/06/在RecyclerView中实现延迟滚动/"/>
		<id>https://duo3.ink/2024/05/06/在RecyclerView中实现延迟滚动/</id>
		<published>2024-05-06T00:27:57.000Z</published>
		<updated>2024-05-06T00:27:57.000Z</updated>
		<author><name>123哆3</name></author>
		<summary>在上一篇中，我们了解了如何使用LinearSmoothScroller让RecyclerView实现更平滑的滚动。本篇我们更进一步，通过一些特殊的方法来实现更细腻的延迟滚动。</summary>
		<content type="html"><![CDATA[<p>在<a href="https://juejin.cn/post/7363932520952512512">上一篇</a>中，我们了解了如何使用Linear<wbr>Smooth<wbr>Scroller让Recycler<wbr>View实现更平滑的滚动。本篇我们更进一步，通过一些特殊的方法来实现更细腻的延迟滚动。</p>
<!-- more -->
<p>由于Recycler<wbr>View的限制，我们无法真正实现延迟滚动。但是，我们可以让元素在View<wbr>Holder固定的大小范围之内上下移动，即操纵translation<wbr>Y，来实现近似延迟滚动的效果。那么这个移动范围何时加、加多少，即是本篇所要着重讨论的内容了。</p>
<p>不过首先，我们先来看一下我们常用的滚动动画曲线⸺贝塞尔曲线。</p>
<h2>1<wbr>. 贝塞尔曲线简介</h2>
<p>在Path<wbr>Interpolator中，我们使用的是一种特殊的三次贝塞尔曲线。其中，<strong>P</strong><sub>0</sub>固定在点 (0<wbr>, 0) ，<strong>P</strong><sub>3</sub>则固定在点 (1<wbr>, 1) ，而<strong>P</strong><sub>1</sub>与<strong>P</strong><sub>2</sub>的坐标则就是<code>Path<wbr>Interpolator(control<wbr>X1<wbr>, control<wbr>Y1<wbr>, control<wbr>X2<wbr>, control<wbr>Y2)</code> 中的四个参数。</p>
<p>我们也知道，贝塞尔曲线的方程为：</p>
<figure data-image-type="svg"><img src="/images/article_asset/%E5%9C%A8RecyclerView%E4%B8%AD%E5%AE%9E%E7%8E%B0%E5%BB%B6%E8%BF%9F%E6%BB%9A%E5%8A%A8/formula_1.svg" alt="Bézier(n,t) = um_{i=0}^n inom{n}{i} dot athbf{P}_i dot (1-t)^{n-i} dot t^i, t n [0,1]" loading="lazy" decoding="async" fetchpriority="low" /></figure>
<p>那么，在上述的情况下，贝塞尔曲线可以被简化为：</p>
<figure data-image-type="svg"><img src="/images/article_asset/%E5%9C%A8RecyclerView%E4%B8%AD%E5%AE%9E%E7%8E%B0%E5%BB%B6%E8%BF%9F%E6%BB%9A%E5%8A%A8/formula_2.svg" alt="athbf{B}(t) = 3t(1-t)^2athbf{P}_1 + 3t^2(1-t) athbf{P}_2+t^3, t n [0,1]" loading="lazy" decoding="async" fetchpriority="low" /></figure>
<p>譬如，在<a href="https://github.com/FoedusProgramme/AccordLegacy/">Accord</a>中，歌词滚动使用的曲线是<code>Path<wbr>Interpolator(0<wbr>.4f<wbr>, 0<wbr>.2f<wbr>, 0f<wbr>, 1f)</code>，那么其方程为：</p>
<figure data-image-type="svg"><img src="/images/article_asset/%E5%9C%A8RecyclerView%E4%B8%AD%E5%AE%9E%E7%8E%B0%E5%BB%B6%E8%BF%9F%E6%BB%9A%E5%8A%A8/formula_3.svg" alt="eftegin{array}{**lr**}x = 3t(1-t)^2dot 0.4 + t^3 y = 3t(1-t)^2dot 0.2 + 3t^2(1-t) + t^3nd{array}ight.&amp; , t n [0,1]" loading="lazy" decoding="async" fetchpriority="low" /></figure>
<p>其在<em>t</em> ∈ [0<wbr>, 1] 时的图像如下：</p>
<figure data-image-type="svg"><img src="/images/article_asset/%E5%9C%A8RecyclerView%E4%B8%AD%E5%AE%9E%E7%8E%B0%E5%BB%B6%E8%BF%9F%E6%BB%9A%E5%8A%A8/graph_1.svg" alt="曲线(0.4, 0.2, 0, 1)的图像" loading="lazy" decoding="async" fetchpriority="low" /></figure>
<h2>2<wbr>. 差值计算</h2>
<p>由于原函数图像只在<em>x</em> ∈ [0<wbr>, 1]​ 中，我们定义：</p>
<figure data-image-type="svg"><img src="/images/article_asset/%E5%9C%A8RecyclerView%E4%B8%AD%E5%AE%9E%E7%8E%B0%E5%BB%B6%E8%BF%9F%E6%BB%9A%E5%8A%A8/formula_4.svg" alt="eft egin{array}{**lr**} y = 0, &amp; x n (-nfin, 0)  y = 1, &amp; x n (1, +nfin) nd{array} ight." loading="lazy" decoding="async" fetchpriority="low" /></figure>
<p>在页面滚动中，式 (3) 中的<em>x</em>为时间，<em>y</em>为位移。延迟滚动即是在<em>x</em>上偏移，让时间错开。在Accord中，偏移的时间是总位移时长的0<wbr>.1倍，即令<em>x</em> <wbr>= <em>x</em> + 0<wbr>.1​，可得：</p>
<figure data-image-type="svg"><img src="/images/article_asset/%E5%9C%A8RecyclerView%E4%B8%AD%E5%AE%9E%E7%8E%B0%E5%BB%B6%E8%BF%9F%E6%BB%9A%E5%8A%A8/formula_5.svg" alt="eft egin{array}{**lr**} x = 3t(1-t)^2dot 0.4 + t^3 + 0.1  y = 3t(1-t)^2dot 0.2 + 3t^2(1-t) + t^3 nd{array} ight. &amp; , t n [0,1]" loading="lazy" decoding="async" fetchpriority="low" /></figure>
<p>用式 (3) 减去式 (5)，即可得到我们所需的移动范围随时间变化的方程。其图像如下：</p>
<figure data-image-type="svg"><img src="/images/article_asset/%E5%9C%A8RecyclerView%E4%B8%AD%E5%AE%9E%E7%8E%B0%E5%BB%B6%E8%BF%9F%E6%BB%9A%E5%8A%A8/graph_2.svg" alt="两条曲线差值的图像" loading="lazy" decoding="async" fetchpriority="low" /></figure>
<p>但是仅仅得出方程我们是无法加以利用的。</p>
<h2>3<wbr>. 贝塞尔曲线拟合</h2>
<p>要利用上得出的差值方程，我们需要将函数图像在其导数<em>f</em> ′(<em>x</em>) <wbr>= 0，即在其极值点两侧分别用三次贝塞尔曲线拟合，并记录极值点在函数中所在的位置。</p>
<p>由于只有两个控制点并且无法控制权重，我们使用了比较简单的方法进行了大致的拟合。</p>
<p>其中，在向下移动时，其函数对应的是<code>Path<wbr>Interpolator(0<wbr>.96f<wbr>, 0<wbr>.43f<wbr>, 0<wbr>.72f<wbr>, 1f)</code>，图像如下：</p>
<figure data-image-type="svg"><img src="/images/article_asset/%E5%9C%A8RecyclerView%E4%B8%AD%E5%AE%9E%E7%8E%B0%E5%BB%B6%E8%BF%9F%E6%BB%9A%E5%8A%A8/graph_3.svg" alt="距离增加时的函数图像" loading="lazy" decoding="async" fetchpriority="low" /></figure>
<p>向上移动时则为<code>Path<wbr>Interpolator(0<wbr>.17f<wbr>, 0f<wbr>, -<wbr>0<wbr>.15f<wbr>, 1f)</code>，如下图所示：</p>
<figure data-image-type="svg"><img src="/images/article_asset/%E5%9C%A8RecyclerView%E4%B8%AD%E5%AE%9E%E7%8E%B0%E5%BB%B6%E8%BF%9F%E6%BB%9A%E5%8A%A8/graph_4.svg" alt="距离减少时的函数图像" loading="lazy" decoding="async" fetchpriority="low" /></figure>
<p>接着，按照单调递增区间和单调递减区间在总时长中所占的比例来定义向下、向上时所需要的时间，选取一个合适的最大距离，就可以近似地模拟出延迟滚动的效果了。</p>
<h2>结尾</h2>
<p>如果你想要一个现有的示例代码，<a href="https://github.com/FoedusProgramme/AccordLegacy/blob/fb5df5ff20c96eb782b6a7aec25d10ffe3c4c73a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt#L1138">可以到这里看看</a>，喜欢的话可以点个Star。</p>
<p>如果你对如何使用Linear<wbr>Smooth<wbr>Scroller让Recycler<wbr>View实现更平滑的滚动感兴趣，欢迎看看这篇文章的<a href="https://juejin.cn/post/7363932520952512512">上一篇</a>，没有数学公式。<span class="post-end-mark">●</span></p>]]></content>
		<link rel="enclosure" href="https://duo3.ink/images/article_asset/在RecyclerView中实现延迟滚动/cover.webp" type="image/webp"/>
		<category term="研究" label="category"/>
		<category term="Android" label="tag"/>
		<category term="编程" label="tag"/>
		<category term="动画" label="tag"/>
	</entry>
	<entry>
		<title>西文不应占据视觉主导</title>
		<link href="https://duo3.ink/2024/04/24/西文不应占据视觉主导/"/>
		<id>https://duo3.ink/2024/04/24/西文不应占据视觉主导/</id>
		<published>2024-04-24T06:57:50.000Z</published>
		<updated>2024-04-24T06:57:50.000Z</updated>
		<author><name>123哆3</name></author>
		<summary>近些年来，国内的海报设计出现了一种趋势：英文由装饰转变为视觉主体，而真正承载信息的中文却退居其次，难以辨识。</summary>
		<content type="html"><![CDATA[<p>近些年来，国内的海报设计出现了一种趋势：英文由装饰转变为视觉主体，而真正承载信息的中文却退居其次，难以辨识。</p>
<!-- more -->
<p>然而，这些海报主要受众的母语仍为汉语，信息传达因此受阻。</p>
<p>为什么要这样做？</p>
<p>西方的平面设计之成熟，字体丰富、风格多样，确实更加繁荣。面对优秀的设计，兼收并采，行施拿来主义，毫不为过。</p>
<p>落到实践处却出了些问题。西文花样繁多，与设计风格相得益彰。相较之下，中文字体选择有限，不易与风格相匹。此外，中文凝练，字字见方，没有西文那一股流动感。</p>
<p>设计一套中文字形并非易事，更别说风格化。所以索性降低中文的影响，将其放置在不惹眼的角落，把本就相称的西文放在主要位置。看，原汁原味！</p>
<p>海报是信息传达的载体，视觉呈现应当服务于信息，别的只能作装饰。然而，当「高级」、「现代」与西文在某些语境中过度绑定，便容易使人忽略信息本身的重要性，形成「西文 <wbr>= 高级」的固有印象，最终让装饰性的元素反客为主。更有甚者，连退作装饰的中文也丢掉，但这些海报仍旧面向以汉语为母语的人。这显然不合理。</p>
<p>这种局面的形成非一日之功，要转变也绝不能一蹴而就。我们能做的，只有在日常的设计中多使用中文，在实践中赋予中文更多的可能性，展现出中文「高级」的一面。毕竟，设计的优劣，从不在于语言，而在于整体的谐美与巧思。<span class="post-end-mark">●</span></p>]]></content>
		<link rel="enclosure" href="https://duo3.ink/images/article_asset/西文不应占据视觉主导/cover.webp" type="image/webp"/>
		<category term="闲话" label="category"/>
		<category term="排版" label="tag"/>
		<category term="设计" label="tag"/>
	</entry>
	<entry>
		<title>葱香木糖醇小曲奇</title>
		<link href="https://duo3.ink/2023/06/02/葱香木糖醇小曲奇/"/>
		<id>https://duo3.ink/2023/06/02/葱香木糖醇小曲奇/</id>
		<published>2023-06-01T23:54:00.000Z</published>
		<updated>2023-06-01T23:54:00.000Z</updated>
		<author><name>123哆3</name></author>
		<summary>制作约需半小时。</summary>
		<content type="html"><![CDATA[<ul>
<li>制作约需半小时。</li>
</ul>
<!-- more -->
<table>
<thead>
<tr>
<th style="text-align:left">原料</th>
<th style="text-align:right">用量</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">有盐黄油<br>或<br>无盐黄油<br>+  　 　盐</td>
<td style="text-align:right">80 g<br><br>80 g<br>1 g</td>
</tr>
<tr>
<td style="text-align:left">糖粉 <wbr>/ 木糖醇</td>
<td style="text-align:right">15 g</td>
</tr>
<tr>
<td style="text-align:left">低筋面粉</td>
<td style="text-align:right">70 g</td>
</tr>
<tr>
<td style="text-align:left">高筋面粉</td>
<td style="text-align:right">10 g</td>
</tr>
<tr>
<td style="text-align:left">玉米淀粉</td>
<td style="text-align:right">8 g</td>
</tr>
<tr>
<td style="text-align:left">小葱 <wbr>/ 蔓越莓干</td>
<td style="text-align:right">适量</td>
</tr>
</tbody>
</table>
<h5>步骤</h5>
<ol>
<li>
<p>将黄油融化至半凝固状态。</p>
</li>
<li>
<p>混合黄油和糖，打蛋器搅打3分钟，直至发白；</p>
</li>
<li>
<p>混合低筋面粉、高筋面粉与玉米淀粉，过筛，分两次间隔一分钟加入黄油中，用打蛋器搅拌，途中记得用刮刀刮下边缘的面糊；</p>
</li>
<li>
<p>加入切细碎的小葱 <wbr>/ 蔓越莓，打蛋器搅匀；</p>
</li>
<li>
<p>用裱花袋将混合好的面糊挤在铺有油纸的烤盘上；</p>
</li>
<li>
<p>烤箱190°C预热5分钟以上，烤盘下垫一层烤盘，双层烤盘放入烤箱；</p>
</li>
<li>
<p>190°C烘烤5分钟定型，180°C烘烤10分钟至边缘焦黄后取出放凉；</p>
</li>
<li>
<p>放凉后用手把饼干放入嘴中。</p>
</li>
</ol>
<figure data-has-ratio="true" style="--figure-width: 1280; --figure-height: 979"><img src="/images/article_asset/%E8%91%B1%E9%A6%99%E6%9C%A8%E7%B3%96%E9%86%87%E5%B0%8F%E6%9B%B2%E5%A5%87/cooked.jpeg" alt="成品" loading="lazy" decoding="async" width="1280" height="979" fetchpriority="low" /></figure>
<p>享用！<span class="post-end-mark">●</span></p>]]></content>
		<link rel="enclosure" href="https://duo3.ink/images/article_asset/葱香木糖醇小曲奇/cover.webp" type="image/webp"/>
		<category term="食谱" label="category"/>
		<category term="烘焙" label="tag"/>
		<category term="甜品" label="tag"/>
	</entry>
</feed>