一、改造目标与思路
Nikon F90X / N90S 采用一套复杂的机制,来进行过片控制。

6. 胶片进片(过片)
- 当参考开关(也就是我们监测的ref)断开四次(相当于四个画幅)时,自动装片操作停止。
- 参考开关始终保持在导通状态。该开关在胶片每进给约 19 mm(半幅)时断开一次。
- 当参考开关断开后,监测胶片进片光电遮断器(intp)的脉冲输出,进片停止定时控制开始。
- 胶片进片光电遮断器在进给一个画幅期间输出 114 个脉冲。
- 在单张拍摄(S)、低速连拍(CL)或高速连拍(CH)模式下,拍摄后序列电机和进片电机同时转动,以进给胶片并为快门上弦及反光镜复位。但如果因电池电量不足或低温导致反光镜动作和胶片进给时间超出规定值,则序列电机在快门帘完全走完后自动转动,随后进片电机再转动(此操作称为“方波模式”)。
当电池电量恢复后,自动恢复正常操作。
7. 胶卷末尾(卷片结束)
如果胶片进片操作无法在规定时间内完成,进片电机停止,取景器和 LCD 面板上会显示警告指示,并伴有蜂鸣声。
此时按下快门释放按钮,仅进片电机转动,并重复上述操作。
但当计数器显示数字超过 37 时,进片电机不工作。当计数器显示数字低于 37 时,若胶片正常进给,警告指示消失。从下一画幅起可正常拍摄。
8. 胶片倒片
- 即使未装入胶片时激活倒片操作,倒片电机也会转动 2 秒。
- 若启动倒片时因电池电量不足导致进片电机停止,则快门释放操作被锁定。此时,请尝试再次激活倒片操作。当胶片完全倒回暗盒后,快门释放锁定解除。
在一般状态下,正常全画幅过片约 114 个 INTP (光遮断传感器,其与卷片齿轮耦合,INTK点亮,监测传感器通过齿孔旋转,在不断的遮断/打开之间获得脉冲,来计算过片量)脉冲。

方画幅 / 半格改造需要在胶片走到约 5/8 位置(实测约 71 脉冲,可配置为 50 等)时强行停住,让机身认为本帧结束,同时不改动机身固件。

可行路径:
| 路径 | 结论 |
|---|---|
| 外刹电机 + 串口写 RAM 恢复状态 | 实用主路径,本项目采用 |
PS:单纯靠附件口把 FE7E 改成 71、或伪造 REF(P5.5 ~#57)不能稳定实现方画幅;
真正可靠的是:INTP 计数到目标值 → 拉停 IN1/IN2 → 用串口清 RAM 残留态。
通过监测真实的INTP,并电控拉停IN1/IN2,我们可以实现比较精确的过片量控制,从而任意实现半格/方格乃至N格的控制。

二、硬件连接
因为机身请求5V IO控制,所以本次试验我们采用了Arduino Uno进行控制。

| 信号 | Arduino Uno | 机身 |
|---|---|---|
| INTP 脉冲 | D2(中断输入) | 顺序光耦 TP-INTP |
| 电机刹停 IN1 | D6(开漏拉低) | TP-IN1 |
| 电机刹停 IN2 | D7(开漏拉低) | TP-IN2 |
| 串口 RX | D10 | 机身 TX |
| 串口 TX | D11 | 机身 RX |
| 地 | GND | 共地 |
| 调试 | USB 115200 | — |
刹停采用开漏拉低(BRAKE_OPEN_DRAIN=1),松刹时引脚改回输入,由机身上拉释放电机。
附件口协议与 Nikon 官方附件一致:1200 bps 唤醒 → S1000 识别 → 9600 bps → 0x80 读 RAM / 0x81 写 RAM。
三、ROM 里卷片链在干什么(为什么要外刹 + 写 RAM)
3.1 关键 RAM 变量
| 地址 | 含义 |
|---|---|
FE7D | INTP 脉冲计数(过片中递增) |
FE7E | 停止阈值(由 EEPROM 演算,不是固定 114) |
FEE1 | 连拍空闲计时;无脉冲 ≥ 0xC8(~200ms) → FE4E=0x23(End) |
FEE4 | 堵转计时(16 bit) |
FE4E | 错误 / 模式码(0=正常,02=装卷,23/33=End 等) |
FED4.1 | 过片活跃 |
FED4.6 | 电机运行(来自 FE20.7) |
FEBBH | 过片阶段标志 |
FE50 / FE51 | LCD 字符(52/5A = End 显示) |
FEC5.5 | 置位后 FE51=0x5A |
FD21 | 帧计数相关(影响串口 busy 门) |
3.2 外刹 @71 时机身处于什么状态
外刹停住物理胶片后,机身 MCU 仍认为过片进行中:
FED4.1常为 1FE7D停在 ~47(71 脉冲附近)FEE1继续向 200ms 超时计数- 若不干预 →
FE4E=0x23,LCD 显示 End
方画幅要的是:停在半帧位置,但 LCD 不过 End、下一张能正常拍。
3.3 串口写 RAM:什么时候能写、什么时候不能
附件 0x81 写 RAM 经 4767 → 47A2 拷贝,拷贝本身不查 FED4,但之前有 busy 门 @34FC:
FED4.1且FD21 ≥ 0x26(约第 37 张后)→ 常返回 busyFEB2.2、FEB0.1、FED5≠0等也会挡写- 读 RAM(
0x80) 通常比写容易
因此:
- 过片中途 fast 抢写
FEE1几乎总失败(严格 ACK + busy),与等待 25ms 还是 80ms 无关 - full 恢复用「发写 + 读回验证」,不依赖每次 ACK,所以能稳定成功
3.4 正常过片收尾 @24BE(恢复要模拟什么)
ROM 正常过完一片后 @24BE 会:
- 清
FEB2.2 FEBC/FEBD := 0FE9F := 0x04- 调用过片链
4584H
固件 slim 恢复路径 resumeAdvanceStateMinimal() 即对此的精简模拟;手动 x / w 用更全的 ClearLcdEndState + ResumeAdvanceState。
四、固件架构(v17.2)
4.1 状态机
BOOT → IDLE ⇄ LATCH
↓ 半按快门 5 脉冲(LATCH 后 3 脉冲)
ARM → RUN → HOLD →(verify ok)→ IDLE
↓ partial
LATCH(轮询恢复)
- ISR(D2):微秒级计数 INTP,
BRAKE_AT到点拉刹 - 主循环:SoftwareSerial 阻塞做 RAM 恢复(瓶颈在这里)
- 装卷保护:
FE4E ∈ {01,02,04,21}时禁止 ARM
4.2 一帧成功路径(当前推荐配置)
PREBOOT ok ← 空闲时自动握手,保持 FEC1.4 会话
RUN
BRAKE@50/71 ← BRAKE_AT 可配置
BRAKE full ~3.5–4s ← v17.2 优化后
BRAKE verify ok
OK brake=… hold_ms=…
BRAKE auto brake off
v17.2 相对早期版本的主要优化:
| 变更 | 效果 |
|---|---|
| 去掉 fast 路径 | 省 ~850ms 无效 ACK 等待 |
| slim 恢复掩码改直写 | 省 7 次读 RAM(~1s) |
去掉二次 FEE4 写 | 再省 ~150ms |
| verify 后自动松刹进 IDLE | 修复「verify ok 但刹不松」 |
slim resume 补 FEBC/FEBD | 对齐 ROM @24BE |
实测优化前 hold_ms ≈ 5.8s,优化后目标 <5s,约 3.8–4.2s。
4.3 两档恢复策略
| 场景 | 函数 | 特点 |
|---|---|---|
| 自动 BRAKE | f90xCamBrakeFullRecover | slim 写 + 读回 verify,~20 次写 + 7 次读 |
手动救机 x / w | ClearLcdEndState + ResumeAdvanceState | 全量掩码写,更稳 |
附Uno代码:
/*
* F90X 卷片控制 — v17.0
*
* D2=INTP D6/D7=IN1/IN2 机身TX→D10 RX←D11 GND
* USB 115200:r/p x w c b e l h
*/
#include "f90x_cam_serial.h"
#define FW_VERSION "v17.2"
#ifndef AUTO_RELEASE_BRAKE_ON_X
#define AUTO_RELEASE_BRAKE_ON_X 1
#endif
#define PIN_INTP 2
#define PIN_IN1 6
#define PIN_IN2 7
#define BRAKE_OPEN_DRAIN 1
const uint8_t BRAKE_AT = 71;
const uint16_t BRAKE_CLR_SETTLE_MS = 100;
const uint16_t POST_CLR_GAP_MS = 40;
const uint16_t LATCH_END_POLL_MS = 2000;
const uint16_t LATCH_END_FAIL_MS = 8000;
const uint16_t LATCH_SERIAL_DEFER_MS = 500;
const uint8_t ARM_PULSES = 5;
const uint8_t ARM_PULSES_LATCH = 3;
const uint16_t ARM_WINDOW_MS = 120;
const uint16_t CONFIRM_MS = 80;
const uint16_t COOLDOWN_MS = 300;
const uint16_t BOOT_MS = 1000;
const uint16_t PREBOOT_RETRY_MS = 15000;
const uint16_t PREBOOT_FAIL_MS = 3000;
const uint16_t LOAD_BLOCK_POLL_MS = 1000;
const uint8_t LATCH_STUCK_FAIL_N = 3;
enum State { ST_BOOT, ST_IDLE, ST_ARM, ST_RUN, ST_HOLD, ST_LATCH };
volatile State gSt = ST_BOOT;
volatile uint16_t gCnt = 0;
volatile uint32_t gArmUs = 0;
volatile bool gCamBusy = false;
uint32_t gBootMs = 0;
uint32_t gLastIntpMs = 0;
uint32_t gCoolUntilMs = 0;
uint32_t gHoldStartMs = 0;
uint32_t gBrakeClrDoneMs = 0;
uint32_t gLastEndPollMs = 0;
uint32_t gLatchQuietUntilMs = 0;
uint8_t gArmN = 0;
uint8_t gCntAtBrake = 0;
bool gBrakeClrPending = false;
bool gBrakeClrDone = false;
bool gBrakeClrVerified = false;
static volatile bool gFromLatch = false;
static volatile bool gBlockArmForLoad = false;
uint32_t gLastPrebootMs = 0;
uint32_t gLastLoadPollMs = 0;
bool gEndProbeDone = false;
uint8_t gLatchClrFailN = 0;
void onIntp();
static void printHexByte(uint8_t v) {
if (v < 0x10) Serial.print('0');
Serial.print(v, HEX);
}
static void printHexBuf(const uint8_t *buf, uint8_t len) {
for (uint8_t i = 0; i < len; i++) {
if (i) Serial.print(' ');
printHexByte(buf[i]);
}
}
static bool printWindErrorDiag() {
uint8_t b = 0;
uint16_t w = 0;
bool ok = true;
Serial.print(F("WIND "));
if (f90xCamReadByte(0xFE4E, &b)) {
Serial.print(F(" FE4E=0x"));
printHexByte(b);
} else ok = false;
delay(25);
if (f90xCamReadByte(0xFEE1, &b)) {
Serial.print(F(" FEE1=0x"));
printHexByte(b);
} else ok = false;
delay(25);
if (f90xCamReadByte(0xFE7D, &b)) {
Serial.print(F(" FE7D=0x"));
printHexByte(b);
} else ok = false;
delay(25);
if (f90xCamReadByte(0xFE7E, &b)) {
Serial.print(F(" FE7E=0x"));
printHexByte(b);
} else ok = false;
delay(25);
if (f90xCamReadWord(0xFEE4, &w)) {
Serial.print(F(" FEE4=0x"));
printHexByte((uint8_t)(w & 0xFF));
printHexByte((uint8_t)(w >> 8));
} else ok = false;
if (!ok) {
Serial.print(F(" (partial)"));
f90xCamPrintLastRx();
}
Serial.println();
return ok;
}
static void printAdvanceChainExtra() {
uint8_t feb0 = 0, feb2 = 0, fe40 = 0, fed5 = 0, fed7 = 0, feb5 = 0, fe47 = 0;
uint8_t fe9f = 0, fe5e = 0;
uint16_t fe70 = 0, fee4w = 0;
if (f90xCamReadByte(0xFEB0, &feb0)) {
Serial.print(F(" FEB0=0x"));
printHexByte(feb0);
if (feb0 & 0x04) Serial.print(F(" [.2=待过片!]"));
Serial.println();
}
delay(20);
if (f90xCamReadByte(0xFEB2, &feb2)) {
Serial.print(F(" FEB2=0x"));
printHexByte(feb2);
if (feb2 & 0x04) Serial.print(F(" [.2=过片处理中!]"));
Serial.println();
}
delay(20);
if (f90xCamReadByte(0xFE40, &fe40)) {
Serial.print(F(" FE40=0x"));
printHexByte(fe40);
if (fe40 == 0x0C) Serial.print(F(" (过片镜像态)"));
else if (fe40 == 0x0D) Serial.print(F(" (FEBBH忙)"));
else if (fe40 == 0x0E) Serial.print(F(" (错误/等待态)"));
Serial.println();
}
delay(20);
if (f90xCamReadByte(0xFED5, &fed5)) {
Serial.print(F(" FED5=0x"));
printHexByte(fed5);
if (fed5 & 0x04) Serial.print(F(" [.2=过片链]"));
Serial.println();
}
delay(20);
if (f90xCamReadByte(0xFED7, &fed7)) {
Serial.print(F(" FED7=0x"));
printHexByte(fed7);
Serial.println();
}
delay(20);
if (f90xCamReadWord(0xFE70, &fe70)) {
Serial.print(F(" FE70=0x"));
printHexByte((uint8_t)(fe70 & 0xFF));
printHexByte((uint8_t)(fe70 >> 8));
Serial.println(F(" (延迟函数指针)"));
}
delay(20);
if (f90xCamReadByte(0xFEB5, &feb5)) {
Serial.print(F(" FEB5=0x"));
printHexByte(feb5);
if (feb5) Serial.print(F(" (异步忙)"));
Serial.println();
}
delay(20);
if (f90xCamReadByte(0xFE47, &fe47)) {
Serial.print(F(" FE47=0x"));
printHexByte(fe47);
if (fe47 & 0x20) Serial.print(F(" [.5→FE40=0E]"));
Serial.println();
}
delay(20);
if (f90xCamReadByte(0xFE9F, &fe9f)) {
Serial.print(F(" FE9F=0x"));
printHexByte(fe9f);
Serial.println();
}
delay(20);
if (f90xCamReadByte(0xFE5E, &fe5e)) {
Serial.print(F(" FE5E=0x"));
printHexByte(fe5e);
if (fe5e == 0) Serial.print(F(" (倒计时归零→0E)"));
Serial.println();
}
delay(20);
if (f90xCamReadWord(0xFEE4, &fee4w)) {
Serial.print(F(" FEE4=0x"));
printHexByte((uint8_t)(fee4w & 0xFF));
printHexByte((uint8_t)(fee4w >> 8));
Serial.println();
}
}
static bool printFilmEndDiag(const __FlashStringHelper *tag) {
uint8_t fd14[18];
uint8_t fed[7];
uint8_t fe57 = 0, fe24 = 0, fe50 = 0, fe51 = 0;
uint8_t fed4 = 0, fec5 = 0, fe46 = 0;
uint8_t febb = 0, fec4 = 0, fe7d = 0;
uint8_t fd2d = 0;
uint8_t fd39[10];
uint8_t n = 0;
uint8_t fedLen = 0;
uint8_t fd39Len = 0;
bool ok = true;
Serial.print(tag);
Serial.println(F("FILM/LCD"));
if (f90xCamReadRam(0xFD14, 18, fd14, sizeof(fd14), &n) && n >= 18) {
Serial.print(F(" FD14="));
printHexBuf(fd14, 2);
Serial.print(F(" FD16(eep186)="));
printHexBuf(fd14 + 2, 8);
Serial.print(F(" FD21="));
printHexByte(fd14[13]);
Serial.print(F(" FD22="));
printHexBuf(fd14 + 14, 2);
Serial.print(F(" FD25=shutter "));
printHexByte(fd14[17]);
Serial.println();
} else {
ok = false;
Serial.println(F(" FD14..FD25 read fail"));
f90xCamPrintLastRx();
}
delay(40);
if (f90xCamReadByte(0xFD2D, &fd2d)) {
Serial.print(F(" FD2D=0x"));
printHexByte(fd2d);
Serial.println(F(" (帧计数相关)"));
} else ok = false;
delay(25);
if (f90xCamReadRam(0xFD39, 10, fd39, sizeof(fd39), &fd39Len) && fd39Len >= 1) {
Serial.print(F(" FD39="));
printHexBuf(fd39, fd39Len > 4 ? 4 : fd39Len);
Serial.println();
} else ok = false;
delay(25);
if (f90xCamReadRam(0xFED0, 7, fed, sizeof(fed), &fedLen) && fedLen >= 7) {
Serial.print(F(" FED0=0x"));
printHexByte(fed[0]);
Serial.print(F(" FED6=0x"));
printHexByte(fed[6]);
if (fed[6] & 0x02) Serial.print(F(" [FED6.1]"));
Serial.println();
} else {
ok = false;
Serial.println(F(" FED0..FED6 read fail"));
}
delay(25);
if (f90xCamReadByte(0xFE57, &fe57)) {
Serial.print(F(" FE57=0x"));
printHexByte(fe57);
Serial.println();
} else ok = false;
delay(25);
if (f90xCamReadByte(0xFE24, &fe24)) {
Serial.print(F(" FE24=0x"));
printHexByte(fe24);
if (fe24 == 0x72) Serial.print(F(" (=114脉冲?)"));
Serial.println();
} else ok = false;
delay(25);
if (f90xCamReadByte(0xFED4, &fed4)) {
Serial.print(F(" FED4=0x"));
printHexByte(fed4);
if (fed4 & 0x02) Serial.print(F(" [.1=LCD52]"));
if (fed4 & 0x40) Serial.print(F(" [.6=过片中!]"));
Serial.println();
} else ok = false;
delay(25);
if (f90xCamReadByte(0xFE7D, &fe7d)) {
Serial.print(F(" FE7D=0x"));
printHexByte(fe7d);
if (fe7d != 0) Serial.print(F(" (半帧计数残留)"));
Serial.println();
} else ok = false;
delay(25);
if (f90xCamReadByte(0xFEBB, &febb)) {
Serial.print(F(" FEBBH=0x"));
printHexByte(febb);
if (febb & 0x01) Serial.print(F(" [.0=过片忙]"));
Serial.println();
} else ok = false;
delay(25);
printAdvanceChainExtra();
if (f90xCamReadByte(0xFEC4, &fec4)) {
Serial.print(F(" FEC4=0x"));
printHexByte(fec4);
Serial.println();
} else ok = false;
delay(25);
if (f90xCamReadByte(0xFEC5, &fec5)) {
Serial.print(F(" FEC5=0x"));
printHexByte(fec5);
if (fec5 & 0x20) Serial.print(F(" [FEC5.5→FE51=5A]"));
Serial.println();
} else ok = false;
delay(25);
if (f90xCamReadByte(0xFE46, &fe46)) {
Serial.print(F(" FE46=0x"));
printHexByte(fe46);
Serial.println();
} else ok = false;
delay(25);
if (f90xCamReadByte(0xFE50, &fe50) && f90xCamReadByte(0xFE51, &fe51)) {
Serial.print(F(" LCD FE50=0x"));
printHexByte(fe50);
Serial.print(F(" FE51=0x"));
printHexByte(fe51);
bool lcdEnd = (fe51 == 0x5A) && (fe50 == 0x52 || fe50 == 0x4D || fe50 == 0x54);
if (lcdEnd) Serial.print(F(" [LCD End]"));
Serial.println();
} else ok = false;
if (ok && n >= 18) {
uint8_t fd21 = fd14[13];
if (fe51 == 0x5A && fe50 == 0x52) {
Serial.println(F(" => LCD End @第5张: 外刹残留态(FE4E已清)"));
Serial.println(F(" FE50=52←FED4.1 FE51=5A←FEC5.5 非片尾"));
} else if (fe51 == 0x5A) {
Serial.println(F(" => LCD End态 (ROM @1C45/@3CFD)"));
} else if (fed4 & 0x40) {
Serial.println(F(" => FED4.6 过片链卡住 — 按 w 或 x"));
} else if (fe7d != 0) {
Serial.println(F(" => FE7D 半帧残留 — 按 w 恢复"));
} else if (febb & 0x07) {
Serial.println(F(" => FEBBH 过片忙 — 按 w"));
} else {
Serial.println(F(" => 无 LCD End;若仍不过片看 FEB2/FE40"));
}
if (fd21 >= 0x01 && fd21 <= 0x28) {
Serial.print(F(" => FD21=第"));
Serial.print((int)fd21 - 1);
Serial.println(F(" 张附近"));
}
}
return ok;
}
static void printRamDiag(const __FlashStringHelper *tag) {
gCamBusy = true;
if (!f90xCamEnsureSession(true)) {
Serial.println(F("RAM bootstrap fail — press b"));
gCamBusy = false;
return;
}
delay(100);
Serial.print(tag);
printWindErrorDiag();
printFilmEndDiag(F(""));
gCamBusy = false;
}
static void printUsbHelp() {
Serial.println(F("--- USB commands (115200, Uno USB) ---"));
Serial.println(F(" r read wind-error + film-end RAM"));
Serial.println(F(" p read film/LCD (FD21 FE50/FE51 FD2D)"));
Serial.println(F(" c clear wind-error (FE4E/FEE1/FEE4)"));
Serial.println(F(" x clear LCD End + resume (LATCH 成功自动松刹)"));
Serial.println(F(" w resume advance (FEB0/FEB2/FE40 chain)"));
Serial.println(F(" b bootstrap camera serial"));
Serial.println(F(" e end serial session"));
Serial.println(F(" l force IDLE + release brake"));
Serial.println(F(" h this help"));
}
static bool lcdEndLooksCleared(uint8_t fe50, uint8_t fe51) {
return fe51 != 0x5A && fe50 != 0x52;
}
static bool fe4eIsLoadMode(uint8_t fe4e) {
return fe4e == 0x01 || fe4e == 0x02 || fe4e == 0x04 || fe4e == 0x21;
}
static void updateLoadArmBlock() {
if (gCamBusy || !f90xCamIsReady()) return;
if (gSt != ST_IDLE && gSt != ST_LATCH) return;
uint32_t now = millis();
if (gLastLoadPollMs != 0 && now - gLastLoadPollMs < LOAD_BLOCK_POLL_MS) return;
gLastLoadPollMs = now;
gCamBusy = true;
uint8_t fe4e = 0;
bool wasBlocked = gBlockArmForLoad;
if (f90xCamReadByte(0xFE4E, &fe4e)) {
gBlockArmForLoad = fe4eIsLoadMode(fe4e);
if (gBlockArmForLoad && !wasBlocked) {
Serial.print(F("ARM block load FE4E=0x"));
printHexByte(fe4e);
Serial.println();
} else if (!gBlockArmForLoad && wasBlocked) {
Serial.println(F("ARM load block off"));
}
}
gCamBusy = false;
}
static void releaseBrakeToIdle(const __FlashStringHelper *tag) {
brakeOff();
gFromLatch = false;
gSt = ST_IDLE;
gArmN = 0;
gCnt = 0;
gBrakeClrPending = false;
gBrakeClrDone = false;
gBrakeClrVerified = false;
gLatchClrFailN = 0;
gLatchQuietUntilMs = 0;
gLastEndPollMs = millis() + LATCH_END_FAIL_MS;
Serial.print(tag);
Serial.println(F(" auto brake off"));
}
static void releaseBrakeIfLatched(const __FlashStringHelper *tag) {
if (gSt != ST_LATCH) return;
releaseBrakeToIdle(tag);
}
static bool clearEndAggressive(const __FlashStringHelper *tag) {
for (uint8_t n = 0; n < 3; n++) {
if (f90xCamClearEndStateFast()) {
Serial.print(tag);
Serial.println(F(" fast ok"));
return true;
}
if (f90xCamClearEndStateBrake()) {
Serial.print(tag);
Serial.println(F(" med ok"));
return true;
}
if (f90xCamClearEndState()) {
Serial.print(tag);
Serial.println(F(" full ok"));
return true;
}
delay(40);
}
return false;
}
// 手动清 End:握手 → 读 FE4E → 三档重试写零 → 读回验证
static bool manualClearEnd() {
gCamBusy = true;
if (!f90xCamEnsureSession(true)) {
Serial.println(F("MANUAL bootstrap fail — try b"));
gCamBusy = false;
return false;
}
delay(100);
uint8_t fe4e = 0xFF;
if (f90xCamReadByte(0xFE4E, &fe4e)) {
Serial.print(F("MANUAL before FE4E=0x"));
printHexByte(fe4e);
Serial.println();
} else {
Serial.println(F("MANUAL read fail — blind CLR"));
f90xCamPrintLastRx();
}
bool ok = clearEndAggressive(F("MANUAL CLR"));
if (!ok) ok = f90xCamClearEndStateRetry(3);
if (f90xCamReadByte(0xFE4E, &fe4e)) {
Serial.print(F("MANUAL after FE4E=0x"));
printHexByte(fe4e);
if (fe4e == 0) {
Serial.println(F(" OK"));
f90xCamEndSession(false);
gEndProbeDone = false;
} else {
Serial.println(F(" still error"));
}
} else if (ok) {
Serial.println(F("MANUAL CLR sent (unverified)"));
} else {
Serial.println(F("MANUAL CLR FAIL — retry c or b then c"));
f90xCamPrintLastRx();
}
gCamBusy = false;
return ok;
}
// 仅恢复卷片链(LCD 已清但电机不动时用)
static bool manualResumeAdvance() {
gCamBusy = true;
if (!f90xCamEnsureSession(true)) {
Serial.println(F("RESUME bootstrap fail — try b"));
gCamBusy = false;
return false;
}
delay(100);
uint8_t fed4 = 0, fe7d = 0, feb2 = 0, fe40 = 0;
if (f90xCamReadByte(0xFED4, &fed4) && f90xCamReadByte(0xFE7D, &fe7d)) {
Serial.print(F("RESUME before FED4=0x"));
printHexByte(fed4);
Serial.print(F(" FE7D=0x"));
printHexByte(fe7d);
Serial.println();
}
bool ok = false;
for (uint8_t n = 0; n < 3 && !ok; n++) {
ok = f90xCamResumeAdvanceState();
delay(60);
}
bool gotAfter = f90xCamReadByte(0xFED4, &fed4) && f90xCamReadByte(0xFE7D, &fe7d);
(void)f90xCamReadByte(0xFEB2, &feb2);
(void)f90xCamReadByte(0xFE40, &fe40);
if (gotAfter) {
Serial.print(F("RESUME after FED4=0x"));
printHexByte(fed4);
Serial.print(F(" FE7D=0x"));
printHexByte(fe7d);
Serial.print(F(" FEB2=0x"));
printHexByte(feb2);
Serial.print(F(" FE40=0x"));
printHexByte(fe40);
Serial.println();
if (ok) {
Serial.println(F(" OK —按 l 松刹后试快门过片"));
f90xCamEndSession(false);
gEndProbeDone = false;
} else {
Serial.println(F(" partial — 按 r 看 FEB2/FE40/FEBBH,再试 w 或关机"));
if (!ok) f90xCamPrintLastRx();
}
} else if (ok) {
Serial.println(F("RESUME sent (unverified)"));
} else {
Serial.println(F("RESUME FAIL — 按 r 或 b 后重试"));
f90xCamPrintLastRx();
}
gCamBusy = false;
return ok;
}
// 清 LCD End(FE4E=0 仍显示 End):FEC5.5/FED4.1/FE50/FE51
static bool manualClearLcdEnd() {
gCamBusy = true;
if (!f90xCamEnsureSession(true)) {
Serial.println(F("LCD CLR bootstrap fail — try b"));
gCamBusy = false;
return false;
}
delay(100);
uint8_t fe50 = 0, fe51 = 0;
if (f90xCamReadByte(0xFE50, &fe50) && f90xCamReadByte(0xFE51, &fe51)) {
Serial.print(F("LCD before FE50=0x"));
printHexByte(fe50);
Serial.print(F(" FE51=0x"));
printHexByte(fe51);
Serial.println();
}
bool ok = false;
bool lcdOk = false;
bool chainOk = false;
for (uint8_t n = 0; n < 3 && !ok; n++) {
ok = f90xCamClearLcdEndState() && f90xCamResumeAdvanceState();
delay(60);
}
if (f90xCamReadByte(0xFE50, &fe50) && f90xCamReadByte(0xFE51, &fe51)) {
lcdOk = lcdEndLooksCleared(fe50, fe51);
chainOk = f90xCamAdvanceChainLooksIdle();
Serial.print(F("LCD after FE50=0x"));
printHexByte(fe50);
Serial.print(F(" FE51=0x"));
printHexByte(fe51);
if (lcdOk && chainOk) {
Serial.println(F(" OK"));
gEndProbeDone = false;
gLatchClrFailN = 0;
#if AUTO_RELEASE_BRAKE_ON_X
releaseBrakeIfLatched(F("LCD CLR"));
#endif
} else if (lcdOk) {
Serial.println(F(" LCD ok chain busy —试 w 或再 x"));
gEndProbeDone = false;
pollRecoverAutoRelease(true);
} else {
Serial.println(F(" still End — retry x"));
}
} else if (ok) {
Serial.println(F("LCD CLR sent (unverified)"));
} else {
Serial.println(F("LCD CLR FAIL"));
f90xCamPrintLastRx();
}
gCamBusy = false;
return ok;
}
static void probeCamEndOnce() {
if (gEndProbeDone || gCamBusy || !f90xCamIsReady()) return;
if (gSt != ST_IDLE && gSt != ST_LATCH) return;
gCamBusy = true;
uint8_t fe4e = 0, fe50 = 0, fe51 = 0;
bool gotFe4e = f90xCamReadByte(0xFE4E, &fe4e);
delay(20);
bool gotLcd = f90xCamReadByte(0xFE50, &fe50) && f90xCamReadByte(0xFE51, &fe51);
delay(20);
if (gotFe4e && fe4e != 0) {
Serial.print(F("CAM wind-error FE4E=0x"));
printHexByte(fe4e);
Serial.println(F(" — press c"));
} else if (gotLcd && fe51 == 0x5A &&
(fe50 == 0x52 || fe50 == 0x4D || fe50 == 0x54)) {
Serial.print(F("CAM LCD-End FE50=0x"));
printHexByte(fe50);
Serial.print(F(" FE51=0x"));
printHexByte(fe51);
Serial.println(F(" — press x (c无效)"));
}
gEndProbeDone = true;
gCamBusy = false;
}
static bool tryBrakeFullClear() {
uint32_t t0 = millis();
bool verified = false;
if (!f90xCamBrakeFullRecover(&verified)) {
Serial.print(F("BRAKE full fail "));
Serial.print(millis() - t0);
Serial.println(F("ms"));
return false;
}
Serial.print(F("BRAKE full "));
Serial.print(millis() - t0);
Serial.println(F("ms"));
if (verified) {
Serial.println(F("BRAKE verify ok"));
gBrakeClrVerified = true;
gLatchClrFailN = 0;
gEndProbeDone = false;
} else {
Serial.println(F("BRAKE partial — x/w if no wind"));
}
return true;
}
static void pollRecoverAutoRelease(bool verified) {
#if AUTO_RELEASE_BRAKE_ON_X
if (verified) releaseBrakeIfLatched(F("LATCH CLR"));
#else
(void)verified;
#endif
}
static void tryIdlePreboot() {
if (gCamBusy || f90xCamIsReady()) return;
if (gSt != ST_IDLE && gSt != ST_LATCH) return;
if (gSt == ST_LATCH && millis() < gLatchQuietUntilMs) return;
uint32_t now = millis();
if (gLastPrebootMs != 0 && now - gLastPrebootMs < PREBOOT_RETRY_MS) return;
gLastPrebootMs = now;
gCamBusy = true;
if (f90xCamBootstrap(false)) {
Serial.println(F("PREBOOT ok"));
gEndProbeDone = false;
} else {
Serial.println(F("PREBOOT fail — press b"));
gLastPrebootMs = now - PREBOOT_RETRY_MS + PREBOOT_FAIL_MS;
}
gCamBusy = false;
}
static void pollEndAndClear() {
if (gSt != ST_LATCH) return;
gCamBusy = true;
uint32_t nextPollMs = LATCH_END_POLL_MS;
if (gBrakeClrVerified) {
if (f90xCamIsReady()) {
uint8_t fe51 = 0, fe50 = 0;
if (f90xCamReadByte(0xFE51, &fe51) &&
f90xCamReadByte(0xFE50, &fe50) &&
lcdEndLooksCleared(fe50, fe51) &&
f90xCamAdvanceChainLooksIdle()) {
gLatchClrFailN = 0;
nextPollMs = 30000;
#if AUTO_RELEASE_BRAKE_ON_X
releaseBrakeToIdle(F("LATCH ok"));
#endif
goto done;
}
gBrakeClrVerified = false;
Serial.println(F("LATCH re-check: need CLR again"));
}
}
if (!f90xCamIsReady()) {
if (!f90xCamBootstrap(false)) {
Serial.println(F("END poll bootstrap fail"));
nextPollMs = LATCH_END_FAIL_MS;
goto done;
}
}
delay(50);
{
uint8_t fe4e = 0;
bool readOk = f90xCamReadByte(0xFE4E, &fe4e);
bool needRecover = false;
if (readOk && fe4e == 0) {
uint8_t fe51 = 0, fe50 = 0;
bool gotLcd = f90xCamReadByte(0xFE51, &fe51) &&
f90xCamReadByte(0xFE50, &fe50);
if (gotLcd && fe51 == 0x5A &&
(fe50 == 0x52 || fe50 == 0x4D || fe50 == 0x54)) {
needRecover = true;
} else if (!f90xCamAdvanceChainLooksIdle()) {
needRecover = true;
} else {
gLatchClrFailN = 0;
nextPollMs = 10000;
goto done;
}
} else if (readOk) {
Serial.print(F("END det FE4E=0x"));
printHexByte(fe4e);
Serial.println();
needRecover = true;
} else {
Serial.println(F("END read fail — blind CLR"));
needRecover = true;
}
if (needRecover) {
bool verified = false;
if (f90xCamBrakeFullRecover(&verified)) {
gLatchClrFailN = 0;
Serial.println(verified ? F("LATCH CLR verify ok") : F("LATCH CLR partial"));
if (verified) {
gBrakeClrVerified = true;
pollRecoverAutoRelease(true);
} else {
uint8_t fe51 = 0, fe50 = 0;
if (f90xCamReadByte(0xFE50, &fe50) &&
f90xCamReadByte(0xFE51, &fe51) &&
lcdEndLooksCleared(fe50, fe51)) {
Serial.println(F("LATCH LCD ok — auto brake off, try w if stuck"));
pollRecoverAutoRelease(true);
}
}
gEndProbeDone = false;
} else {
gLatchClrFailN++;
Serial.print(F("LATCH CLR FAIL n="));
Serial.println(gLatchClrFailN);
nextPollMs = LATCH_END_FAIL_MS;
if (gLatchClrFailN >= LATCH_STUCK_FAIL_N) {
Serial.println(F("STUCK: force brake off → IDLE (try w/x)"));
releaseBrakeIfLatched(F("STUCK"));
gLatchClrFailN = 0;
}
}
}
}
done:
gLastEndPollMs = millis() + nextPollMs;
gCamBusy = false;
}
static void handleUsbCommand(char c) {
switch (c) {
case 'c':
case 'C':
manualClearEnd();
return;
case 'x':
case 'X':
manualClearLcdEnd();
return;
case 'w':
case 'W':
manualResumeAdvance();
return;
}
if (gCamBusy) return;
switch (c) {
case 'r':
case 'R':
printRamDiag(F("MANUAL "));
break;
case 'p':
case 'P':
gCamBusy = true;
if (!f90xCamEnsureSession(true)) {
Serial.println(F("FILM bootstrap fail — press b"));
} else {
delay(100);
printFilmEndDiag(F("MANUAL "));
}
gCamBusy = false;
break;
case 'h':
case 'H':
case '?':
printUsbHelp();
break;
case 'b':
case 'B':
gCamBusy = true;
gLastPrebootMs = millis();
if (f90xCamBootstrap(true))
Serial.println(F("bootstrap OK"));
else
Serial.println(F("bootstrap FAIL"));
gCamBusy = false;
break;
case 'e':
case 'E':
gCamBusy = true;
f90xCamEndSession(true);
gLastPrebootMs = 0;
gEndProbeDone = false;
Serial.println(F("session end"));
gCamBusy = false;
break;
case 'l':
case 'L':
releaseBrakeToIdle(F("force"));
break;
default:
break;
}
}
static void brakeOn() {
pinMode(PIN_IN1, OUTPUT);
pinMode(PIN_IN2, OUTPUT);
#if BRAKE_OPEN_DRAIN
digitalWrite(PIN_IN1, LOW);
digitalWrite(PIN_IN2, LOW);
#else
digitalWrite(PIN_IN1, HIGH);
digitalWrite(PIN_IN2, HIGH);
#endif
}
static void brakeOff() {
#if !BRAKE_OPEN_DRAIN
digitalWrite(PIN_IN1, LOW);
digitalWrite(PIN_IN2, LOW);
#endif
pinMode(PIN_IN1, INPUT);
pinMode(PIN_IN2, INPUT);
}
static void intpListenOn() {
pinMode(PIN_INTP, INPUT);
attachInterrupt(digitalPinToInterrupt(PIN_INTP), onIntp, RISING);
}
static void startCooldown() {
gCoolUntilMs = millis() + COOLDOWN_MS;
}
static bool canArm() {
return millis() >= gCoolUntilMs;
}
static void enterLatch() {
gSt = ST_LATCH;
gLatchQuietUntilMs = millis() + LATCH_SERIAL_DEFER_MS;
gLastEndPollMs = millis() + LATCH_SERIAL_DEFER_MS;
}
static void endFrame() {
Serial.print(F("OK brake="));
Serial.print(gCntAtBrake);
Serial.print(F(" hold_ms="));
Serial.println(millis() - gHoldStartMs);
gCnt = 0;
gArmN = 0;
startCooldown();
#if AUTO_RELEASE_BRAKE_ON_X
if (gBrakeClrVerified) {
releaseBrakeToIdle(F("BRAKE"));
return;
}
#endif
Serial.println(F("LATCH"));
enterLatch();
}
static void discardBurst() {
if (gFromLatch) {
gFromLatch = false;
gArmN = 0;
brakeOn();
gSt = ST_LATCH;
startCooldown();
return;
}
brakeOff();
gSt = ST_IDLE;
gCnt = 0;
gArmN = 0;
startCooldown();
}
void onIntp() {
uint32_t t = micros();
gLastIntpMs = millis();
if (gSt == ST_IDLE || gSt == ST_LATCH) {
if (!canArm()) return;
if (gBlockArmForLoad) return;
gFromLatch = (gSt == ST_LATCH);
gSt = ST_ARM;
gArmN = 1;
gArmUs = t;
return;
}
if (gSt == ST_ARM) {
gArmN++;
uint8_t needArm = gFromLatch ? ARM_PULSES_LATCH : ARM_PULSES;
if (gArmN >= needArm &&
(t - gArmUs) < (uint32_t)ARM_WINDOW_MS * 1000UL) {
brakeOff();
gFromLatch = false;
gSt = ST_RUN;
gCnt = gArmN;
gBrakeClrPending = false;
gBrakeClrDone = false;
gBrakeClrVerified = false;
Serial.println(F("RUN"));
}
return;
}
if (gSt == ST_RUN) {
gCnt++;
if (gCnt >= BRAKE_AT) {
gSt = ST_HOLD;
gHoldStartMs = millis();
gCntAtBrake = gCnt;
brakeOn();
gBrakeClrPending = true;
gBrakeClrDone = false;
gBrakeClrVerified = false;
Serial.print(F("BRAKE@"));
Serial.println(gCnt);
}
return;
}
if (gSt == ST_HOLD) {
gCnt++;
}
}
void setup() {
pinMode(PIN_INTP, INPUT);
brakeOff();
intpListenOn();
Serial.begin(115200);
gBootMs = millis();
f90xCamForceReset();
Serial.print(F("F90X "));
Serial.println(FW_VERSION);
Serial.print(F("BRAKE_AT="));
Serial.println(BRAKE_AT);
Serial.println(F("USB: r/p x w c b e l h"));
}
void loop() {
uint32_t now = millis();
while (Serial.available()) {
handleUsbCommand((char)Serial.read());
}
if (gSt == ST_IDLE || gSt == ST_LATCH) {
tryIdlePreboot();
updateLoadArmBlock();
probeCamEndOnce();
}
if (gSt == ST_BOOT) {
brakeOff();
if (now - gBootMs >= BOOT_MS) {
gSt = ST_IDLE;
Serial.println(F("READY"));
}
return;
}
if (gSt == ST_ARM && now - gLastIntpMs >= CONFIRM_MS) {
discardBurst();
return;
}
if (gSt == ST_HOLD) {
if (gBrakeClrPending && !gBrakeClrDone && !gCamBusy &&
now - gHoldStartMs >= BRAKE_CLR_SETTLE_MS) {
gCamBusy = true;
(void)tryBrakeFullClear();
gBrakeClrDone = true;
gBrakeClrPending = false;
gBrakeClrDoneMs = millis();
gCamBusy = false;
}
if (!gBrakeClrDone) return;
if (now - gBrakeClrDoneMs < POST_CLR_GAP_MS) return;
endFrame();
return;
}
if (gSt == ST_LATCH) {
if (!gCamBusy && now >= gLatchQuietUntilMs && now >= gLastEndPollMs) {
pollEndAndClear();
}
}
}

发表回复