#!/usr/bin/env python3 """ZhaTianRX 接收机操作指南 PDF 生成脚本 - 完整版""" from fpdf import FPDF import os # 中文字体路径 CN_FONT = '/root/SourceHanSansCN-VF.ttf' CN_FONT_BOLD = '/root/SourceHanSansCN-VF.ttf' class ZhaTianPDF(FPDF): def __init__(self): super().__init__() self.set_auto_page_break(auto=True, margin=15) self.add_font('CN', '', CN_FONT) self.add_font('CN', 'B', CN_FONT_BOLD) def header(self): if self.page_no() > 1: self.set_font('CN', '', 8) self.set_text_color(0x7F, 0x8C, 0x8D) self.cell(0, 8, 'ZhaTianRX 接收机操作指南', new_x="LMARGIN", new_y="NEXT", align='L') self.cell(0, 8, f'第 {self.page_no()} 页', new_x="LMARGIN", new_y="NEXT", align='R') self.set_draw_color(0xDD, 0xDD, 0xDD) self.line(10, 14, 200, 14) self.ln(4) def footer(self): self.set_y(-15) self.set_font('CN', '', 7) self.set_text_color(0xBB, 0xBB, 0xBB) self.cell(0, 10, 'ZhaTianRC 项目 | STC8H1K08 + NRF24L01+ | SDCC', align='C') def chapter_title(self, title, level=1): if level == 1: self.set_font('CN', 'B', 16) self.set_text_color(0x1A, 0x1A, 0x2E) self.cell(0, 12, title, new_x="LMARGIN", new_y="NEXT") self.set_draw_color(0xC0, 0x39, 0x2B) self.line(10, self.get_y(), 200, self.get_y()) self.ln(4) elif level == 2: self.set_font('CN', 'B', 13) self.set_text_color(0x2C, 0x3E, 0x50) self.cell(0, 10, title, new_x="LMARGIN", new_y="NEXT") self.ln(2) elif level == 3: self.set_font('CN', 'B', 11) self.set_text_color(0x34, 0x49, 0x5E) self.cell(0, 8, title, new_x="LMARGIN", new_y="NEXT") self.ln(1) def body_text(self, text): self.set_font('CN', '', 10) self.set_text_color(0x33, 0x33, 0x33) self.multi_cell(0, 5.5, text) self.ln(2) def bullet(self, text, indent=10): self.set_font('CN', '', 10) self.set_text_color(0x33, 0x33, 0x33) x = self.get_x() self.cell(indent, 5.5, '') self.set_font('CN', '', 10) self.cell(5, 5.5, '• ') self.multi_cell(0, 5.5, text) self.ln(1) def code_block(self, text): self.set_fill_color(0xF5, 0xF5, 0xF5) self.set_text_color(0x1A, 0x1A, 0x2E) self.set_font('Courier', '', 9) self.ln(1) for line in text.split('\n'): self.cell(0, 5, ' ' + line, new_x="LMARGIN", new_y="NEXT", fill=True) self.ln(2) def step_block(self, num, title, desc, code=None): """操作步骤""" self.set_font('CN', 'B', 10) self.set_text_color(0xC0, 0x39, 0x2B) self.cell(0, 6, f'步骤 {num}: {title}', new_x="LMARGIN", new_y="NEXT") self.set_font('CN', '', 10) self.set_text_color(0x33, 0x33, 0x33) self.multi_cell(0, 5.5, desc) if code: self.code_block(code) self.ln(2) def table_header(self, cols, widths): self.set_font('CN', 'B', 9) self.set_fill_color(0x2C, 0x3E, 0x50) self.set_text_color(0xFF, 0xFF, 0xFF) for i, col in enumerate(cols): self.cell(widths[i], 7, col, border=1, align='C', fill=True) self.ln() def table_row(self, cols, widths, fill=False): self.set_font('CN', '', 9) self.set_text_color(0x33, 0x33, 0x33) if fill: self.set_fill_color(0xF0, 0xF0, 0xF0) else: self.set_fill_color(0xFF, 0xFF, 0xFF) for i, col in enumerate(cols): self.cell(widths[i], 6, col, border=1, align='C', fill=True) self.ln() def build_pdf(): pdf = ZhaTianPDF() pdf.set_title('ZhaTianRX 接收机操作指南') pdf.set_author('ZhaTianRC Project') # ==================== 封面 ==================== pdf.add_page() pdf.ln(40) pdf.set_font('CN', 'B', 28) pdf.set_text_color(0x1A, 0x1A, 0x2E) pdf.cell(0, 15, 'ZhaTianRX', new_x="LMARGIN", new_y="NEXT", align='C') pdf.set_font('CN', '', 16) pdf.set_text_color(0x7F, 0x8C, 0x8D) pdf.cell(0, 10, '接收机操作指南', new_x="LMARGIN", new_y="NEXT", align='C') pdf.ln(10) pdf.set_draw_color(0xC0, 0x39, 0x2B) pdf.line(60, pdf.get_y(), 150, pdf.get_y()) pdf.ln(10) pdf.set_font('CN', '', 11) pdf.set_text_color(0x55, 0x55, 0x55) pdf.cell(0, 7, 'MCU: STC8H1K08 | 射频: NRF24L01+ | 编译器: SDCC', new_x="LMARGIN", new_y="NEXT", align='C') pdf.cell(0, 7, '固件版本: 1.0 | 项目: ZhaTianRC', new_x="LMARGIN", new_y="NEXT", align='C') pdf.ln(20) pdf.set_font('CN', '', 9) pdf.set_text_color(0x99, 0x99, 0x99) pdf.cell(0, 7, '本文档涵盖接收机的所有功能、操作步骤及串口命令参考', new_x="LMARGIN", new_y="NEXT", align='C') # ==================== 目录 ==================== pdf.add_page() pdf.chapter_title('目录') pdf.set_font('CN', '', 11) toc = [ '1. 概述', '2. 硬件引脚分配', '3. 首次使用 - 对频操作', '4. 输出模式切换', '5. 串口命令详解', '6. 回传遥测功能', '7. LED 状态指示', '8. 固件烧录方法', '9. 附录 - 技术规格', '10. 完整功能清单(38 项)', ] for item in toc: pdf.set_text_color(0x2C, 0x3E, 0x50) pdf.cell(0, 8, item, new_x="LMARGIN", new_y="NEXT") pdf.ln(1) # ==================== 1. 概述 ==================== pdf.add_page() pdf.chapter_title('1. 概述') pdf.body_text( 'ZhaTianRX 是 ZhaTianRC 遥控器项目的接收机子项目。采用 STC8H1K08 系列单片机作为主控芯片,' 'NRF24L01+ 作为 2.4GHz 射频收发芯片。接收机从发射机接收遥控通道数据,转换为多种输出格式,' '驱动舵机、飞控等设备。' ) pdf.chapter_title('主要特性', 2) features = [ '4 路 50Hz 舵机 PWM 输出 (P3.4-P3.7)', 'SBUS 输出 (100kbps 8E2, 经三极管反相)', 'CRSF/Crossfire 输出 (420kbps 8N1)', 'UART 直通输出 (115200 8N1)', '电池电压检测 (12位ADC)', 'NRF24L01 遥测回传 (电压/RSSI/丢包率)', 'EEPROM 存储对频信息', '串口命令配置 (115200 8N1)', '信号丢失保护 (失控回中位)', 'NRF 模块硬件检测与自动恢复', ] for f in features: pdf.bullet(f) # ==================== 2. 硬件引脚分配 ==================== pdf.add_page() pdf.chapter_title('2. 硬件引脚分配') pdf.chapter_title('2.1 功能引脚', 2) cols = ['功能', '引脚', '说明'] widths = [35, 50, 95] pdf.table_header(cols, widths) rows = [ ['CH1', 'P3.7', '舵机 PWM 输出 1 (50Hz)'], ['CH2', 'P3.6', '舵机 PWM 输出 2'], ['CH3', 'P3.5', '舵机 PWM 输出 3'], ['CH4', 'P3.4', '舵机 PWM 输出 4'], ['BAT_ADC', 'P3.3/ADC11', '电池电压分压输入'], ['LED', 'P3.2', '状态指示灯 (低电平亮)'], ['TX', 'P3.1', 'UART1 TX / SBUS 输出 (经三极管反相)'], ['RX', 'P3.0', 'UART1 RX / 命令输入'], ] for i, row in enumerate(rows): pdf.table_row(row, widths, fill=(i % 2 == 0)) pdf.ln(4) pdf.chapter_title('2.2 NRF24L01 连接引脚', 2) pdf.body_text('NRF24L01 通过 STC8H 硬件 SPI 连接,IRQ 连接到了不具备中断功能的引脚,通过主循环轮询检测。') cols = ['信号', '引脚'] widths = [40, 40] pdf.table_header(cols, widths) rows = [ ['SCK', 'P1.5'], ['CE', 'P1.6'], ['IRQ', 'P1.7'], ['CSN', 'P5.4'], ['MISO', 'P1.3'], ['MOSI', 'P1.4'], ] for i, row in enumerate(rows): pdf.table_row(row, widths, fill=(i % 2 == 0)) # ==================== 3. 首次使用 - 对频操作 ==================== pdf.add_page() pdf.chapter_title('3. 首次使用 - 对频操作') pdf.body_text( '接收机上电后自动检测 EEPROM 中是否存有对频信息。如果没有对频数据,' '自动进入对频模式(LED 快闪 3Hz),等待与发射机配对。' ) pdf.chapter_title('3.1 首次对频', 2) pdf.step_block(1, '给接收机上电', '连接接收机电源(3.3V-5V),观察 LED 状态。' '如果 EEPROM 无数据,LED 会以 3Hz 快闪表示进入对频模式。' '如果 LED 双闪,说明 NRF24L01 模块未检测到,请检查硬件连接。') pdf.step_block(2, '打开发射机并进入对频', '在发射机上操作进入对频模式。发射机会在 0~125 频道上广播对频包,' '包含同步头 (0xAA 0x55)、对频短语和地址信息。') pdf.step_block(3, '等待自动配对', '接收机会自动扫描所有频道,匹配对频短语(默认 "LOVE")。' '匹配成功后自动保存地址到 EEPROM,LED 变为常亮。') pdf.chapter_title('3.2 重新对频', 2) pdf.step_block(1, '清除对频信息', '通过串口发送命令清除 EEPROM 中的对频数据:', 'DEL(BIND)') pdf.step_block(2, '确认接收机进入对频模式', '发送 DEL(BIND) 后,接收机会自动擦除 EEPROM 并进入对频模式,' 'LED 变为 3Hz 快闪。') pdf.step_block(3, '重复首次对频步骤', '在发射机上进入对频模式,等待自动配对。') pdf.chapter_title('3.3 修改对频短语', 2) pdf.step_block(1, '发送 PHRASE 命令', '通过串口发送新的对频短语,1~6 位字母数字:', 'PHRASE(Abc123)') pdf.step_block(2, '确认成功', '接收机返回 "OK" 表示设置成功,短语已保存到 EEPROM。') pdf.step_block(3, '重新对频', '修改短语后需要重新对频。发送 DEL(BIND) 清除旧绑定,' '然后确保发射机也使用相同的短语进行对频。') # ==================== 4. 输出模式切换 ==================== pdf.add_page() pdf.chapter_title('4. 输出模式切换') pdf.body_text( '接收机支持 4 种输出模式,默认为 PWM 模式。切换模式通过串口发送 MODEL 命令实现。' '注意:所有输出模式共用 UART1 TX 引脚 (P3.1),切换模式时会自动调整波特率和帧格式。' ) pdf.chapter_title('4.1 PWM 模式(默认)', 2) pdf.body_text( '4 路 50Hz 舵机 PWM 信号,通过定时器0 4us 中断生成。' '脉宽范围 1.0ms ~ 2.0ms,中位 1.5ms。' ) pdf.step_block(1, '切换到 PWM 模式', '通过串口发送:', 'MODEL(PWM)') pdf.step_block(2, '确认切换成功', '接收机返回 "OK",UART1 恢复 115200 8N1 命令模式。') pdf.step_block(3, '连接舵机', '将舵机信号线连接到对应的 PWM 输出引脚:\n' ' CH1 -> P3.7\n CH2 -> P3.6\n CH3 -> P3.5\n CH4 -> P3.4\n' '注意舵机电源需独立供电,不要从 MCU 引脚取电。') pdf.chapter_title('4.2 SBUS 模式', 2) pdf.body_text( 'SBUS 是 Futaba 制定的串行舵机总线协议,100kbps 8E2 帧格式。' '16 通道 11-bit 编码。接收机输出经板上三极管反相,可直接连接支持 SBUS 的飞控。' ) pdf.step_block(1, '切换到 SBUS 模式', '通过串口发送:', 'MODEL(SBUS)') pdf.step_block(2, '确认切换成功', '接收机返回 "OK",UART1 切换为 100kbps 8E2。') pdf.step_block(3, '连接飞控', '将接收机 TX (P3.1) 连接到飞控的 SBUS RX 引脚。\n' '注意:SBUS 信号已经过三极管反相,可直接连接。') pdf.chapter_title('4.3 CRFS/CRSF 模式', 2) pdf.body_text( 'CRSF (Crossfire) 是 ExpressLRS/TBS 等使用的协议,420kbps 8N1。' '帧格式: 0xEE + length + type + payload + CRC8。' 'RC 通道帧 type=0x16,遥测帧 type=0x08。' ) pdf.step_block(1, '切换到 CRFS 模式', '通过串口发送:', 'MODEL(CRFS)') pdf.step_block(2, '确认切换成功', '接收机返回 "OK",UART1 切换为 420kbps 8N1。') pdf.step_block(3, '连接飞控', '将接收机 TX (P3.1) 连接到飞控的 CRSF RX 引脚。\n' '接收机会发送 RC 通道帧,同时接收飞控发来的 CRSF 遥测帧并转发给发射机。') pdf.chapter_title('4.4 UART 模式', 2) pdf.body_text('UART 直通模式,115200 8N1,预留扩展用途。') pdf.step_block(1, '切换到 UART 模式', '通过串口发送:', 'MODEL(UART)') pdf.step_block(2, '确认切换成功', '接收机返回 "OK",UART1 为 115200 8N1 直通模式。') # ==================== 5. 串口命令详解 ==================== pdf.add_page() pdf.chapter_title('5. 串口命令详解') pdf.body_text( '通过 P3.0 (RX) 输入命令,115200 8N1,回车或换行结束命令。' '接收机在 PWM 和 UART 模式下持续监听串口命令,不影响正常输出。' 'SBUS 和 CRFS 模式下串口用于协议输出,命令解析暂停。' ) pdf.chapter_title('5.1 命令速查表', 2) cols = ['命令', '参数', '说明'] widths = [48, 48, 84] pdf.table_header(cols, widths) cmd_rows = [ ['MODEL(PWM)', '无', '切换到 PWM 输出模式'], ['MODEL(SBUS)', '无', '切换到 SBUS 输出模式'], ['MODEL(UART)', '无', '切换到 UART 直通模式'], ['MODEL(CRFS)', '无', '切换到 CRSF 输出模式'], ['ADDR(0x...)', '5字节十六进制', '设置 NRF24L01 地址'], ['PHRASE(...)', '1~6位字母数字', '设置对频短语'], ['TUN(N)', '0~125', '设置 RF 频道'], ['DEL(BIND)', '无', '清除对频信息,进入对频'], ['TELEM(N)', '1~10', '设置回传频率 (Hz)'], ] for i, row in enumerate(cmd_rows): pdf.table_row(row, widths, fill=(i % 2 == 0)) pdf.ln(4) pdf.chapter_title('5.2 应答格式', 2) cols = ['应答', '含义'] widths = [40, 140] pdf.table_header(cols, widths) resp_rows = [ ['OK', '命令执行成功'], ['ERR', '命令格式错误'], ['ERR_ADDR', '地址参数错误'], ['ERR_RANGE', '参数超出范围'], ['UNK', '未知命令'], ] for i, row in enumerate(resp_rows): pdf.table_row(row, widths, fill=(i % 2 == 0)) pdf.ln(4) pdf.chapter_title('5.3 命令详解', 2) # MODEL 命令 pdf.chapter_title('MODEL() - 切换输出模式', 3) pdf.body_text('功能:切换接收机的输出模式。每次切换成功后,UART1 自动调整为对应协议的波特率和帧格式。') pdf.step_block(1, '连接串口', '将 USB-TTL 转换器的 TX 接接收机 P3.0(RX),GND 接 GND。') pdf.step_block(2, '打开串口终端', '设置 115200 8N1,打开串口。') pdf.step_block(3, '发送命令', '输入 MODEL(PWM) 或 MODEL(SBUS) 等,回车发送。') pdf.step_block(4, '确认应答', '接收机返回 "OK" 表示切换成功。') # ADDR 命令 pdf.chapter_title('ADDR() - 设置 NRF24L01 地址', 3) pdf.body_text('功能:修改 NRF24L01 的 5 字节通信地址。地址需与发射机一致才能通信。') pdf.step_block(1, '准备新地址', '准备 5 个字节的十六进制值,例如 E7 E7 E7 E7 E7。') pdf.step_block(2, '发送命令', '格式:ADDR(0xHH HH HH HH HH),例如:', 'ADDR(0xE7 E7 E7 E7 E7)') pdf.step_block(3, '确认应答', '返回 "OK" 表示地址修改成功,NRF 已重新初始化。') # PHRASE 命令 pdf.chapter_title('PHRASE() - 设置对频短语', 3) pdf.body_text('功能:设置对频短语,1~6 位字母数字。发射机和接收机必须使用相同的短语才能对频。') pdf.step_block(1, '确定短语', '准备 1~6 位字母数字组合,例如 "LOVE"、"Abc123"。') pdf.step_block(2, '发送命令', '例如:', 'PHRASE(LOVE)') pdf.step_block(3, '确认应答', '返回 "OK" 表示设置成功,已保存到 EEPROM。') # TUN 命令 pdf.chapter_title('TUN() - 切换 RF 频道', 3) pdf.body_text('功能:切换 NRF24L01 的 RF 频道 (0~125)。频道需与发射机一致。') pdf.step_block(1, '发送命令', '例如切换到频道 40:', 'TUN(40)') pdf.step_block(2, '确认应答', '返回 "OK" 表示频道切换成功。') # DEL 命令 pdf.chapter_title('DEL(BIND) - 清除对频信息', 3) pdf.body_text('功能:擦除 EEPROM 中的对频数据,使接收机重新进入对频模式。') pdf.step_block(1, '发送命令', '', 'DEL(BIND)') pdf.step_block(2, '确认应答', '返回 "OK",LED 变为 3Hz 快闪表示进入对频模式。') pdf.step_block(3, '重新对频', '在发射机上进入对频模式,等待自动配对。') # TELEM 命令 pdf.chapter_title('TELEM() - 设置回传频率', 3) pdf.body_text('功能:设置遥测回传频率,范围 1~10Hz,默认 2Hz。') pdf.step_block(1, '发送命令', '例如设置为 5Hz:', 'TELEM(5)') pdf.step_block(2, '确认应答', '返回 "OK" 表示设置成功。') # ==================== 6. 回传遥测功能 ==================== pdf.add_page() pdf.chapter_title('6. 回传遥测功能') pdf.body_text( '接收机支持通过 NRF24L01 向发射机回传遥测数据。' '不同输出模式下回传路径不同,PWM 和 SBUS 模式通过 NRF 直接回传,' 'CRFS 模式通过 CRSF 遥测帧回传并转发飞控数据。' ) pdf.chapter_title('6.1 回传数据内容', 2) cols = ['数据项', '来源', '范围'] widths = [40, 70, 70] pdf.table_header(cols, widths) telem_rows = [ ['电池电压', 'P3.3/ADC11 12位采样', 'ADC 原始值 0~4095'], ['RSSI', 'NRF RPD 载波检测', '0~100'], ['丢包率', '滑动窗口估算', '0~100%'], ] for i, row in enumerate(telem_rows): pdf.table_row(row, widths, fill=(i % 2 == 0)) pdf.ln(4) pdf.chapter_title('6.2 各模式回传路径', 2) cols = ['模式', '回传通道', '数据'] widths = [30, 55, 95] pdf.table_header(cols, widths) path_rows = [ ['PWM', 'NRF24L01 TX', '0xBB 0x44 + 电压(2B) + RSSI(1B) + 丢包(1B) + 温度(2B)'], ['SBUS', 'NRF24L01 TX', '同上'], ['CRFS', 'CRSF 遥测帧', 'CRSF Battery sensor (type=0x08) + 飞控数据转发'], ] for i, row in enumerate(path_rows): pdf.table_row(row, widths, fill=(i % 2 == 0)) pdf.ln(4) pdf.chapter_title('6.3 设置回传频率', 2) pdf.body_text('回传频率默认 2Hz,可通过 TELEM 命令调整。频率越高,NRF 切换 TX/RX 越频繁。') pdf.step_block(1, '查看当前频率', '回传频率默认 2Hz,无需查看命令。') pdf.step_block(2, '修改频率', '例如设置为 5Hz:', 'TELEM(5)') pdf.step_block(3, '确认应答', '返回 "OK" 表示设置成功。频率范围 1~10Hz。') pdf.chapter_title('6.4 CRFS 模式下的飞控数据转发', 2) pdf.body_text( '在 CRFS 模式下,接收机不仅发送 CRSF RC 通道帧到飞控,' '还接收飞控发来的 CRSF 遥测帧(如 GPS、姿态、电池等信息),' '通过 NRF24L01 转发给发射机,实现完整的双向通信。' ) pdf.step_block(1, '切换到 CRFS 模式', '', 'MODEL(CRFS)') pdf.step_block(2, '连接飞控', '将接收机 TX 接飞控 RX,接收机 RX 接飞控 TX。\n' '飞控会收到 RC 通道帧,同时发送遥测帧给接收机。') pdf.step_block(3, '自动转发', '接收机自动将飞控发来的 CRSF 帧通过 NRF 转发给发射机,' '无需额外配置。') # ==================== 7. LED 状态指示 ==================== pdf.add_page() pdf.chapter_title('7. LED 状态指示') pdf.body_text( '接收机使用 P3.2 驱动一个 LED(低电平亮),通过不同的闪烁模式指示当前工作状态。' ) cols = ['LED 模式', '效果', '含义'] widths = [35, 50, 95] pdf.table_header(cols, widths) led_rows = [ ['慢闪', '1Hz: 亮500ms 灭500ms', '未连接,等待发射机信号'], ['快闪', '3Hz: 亮167ms 灭167ms', '对频模式,正在搜索发射机'], ['常亮', '持续点亮', '已连接,通信正常'], ['双闪', '1Hz: 亮100ms 灭100ms 亮100ms 灭700ms', 'NRF24L01 模块硬件错误'], ] for i, row in enumerate(led_rows): pdf.table_row(row, widths, fill=(i % 2 == 0)) pdf.ln(4) pdf.chapter_title('LED 状态诊断', 2) pdf.step_block(1, 'LED 双闪', 'NRF24L01 模块未检测到或通信失败。\n' '检查:\n' ' 1. NRF24L01 模块是否正确插入\n' ' 2. 供电电压是否正常 (3.3V)\n' ' 3. SPI 引脚连接是否正确 (P1.3-P1.5, P5.4)\n' '接收机会每 500ms 自动重试检测,模块正常后自动恢复。') pdf.step_block(2, 'LED 慢闪', '接收机已初始化完成,但未收到发射机信号。\n' '检查:\n' ' 1. 发射机是否开机\n' ' 2. 对频短语和频道是否匹配\n' ' 3. 距离是否在有效范围内') pdf.step_block(3, 'LED 快闪', '接收机处于对频模式,正在扫描频道寻找发射机。\n' '请在发射机上进入对频模式。') pdf.step_block(4, 'LED 常亮', '接收机已成功连接发射机,通信正常。\n' '此时可以正常使用遥控器控制设备。') # ==================== 8. 固件烧录方法 ==================== pdf.add_page() pdf.chapter_title('8. 固件烧录方法') pdf.chapter_title('8.1 使用 stcgal (Linux/命令行)', 2) pdf.body_text('stcgal 是一个开源的 STC 单片机烧录工具,支持命令行操作。') pdf.step_block(1, '安装 stcgal', '通过 pip 安装:', 'pip3 install stcgal') pdf.step_block(2, '连接硬件', '将 USB-TTL 转换器连接到接收机:\n' ' USB-TTL TX -> 接收机 P3.0 (RX)\n' ' USB-TTL RX -> 接收机 P3.1 (TX)\n' ' USB-TTL GND -> 接收机 GND\n' '给接收机上电 (3.3V-5V)。') pdf.step_block(3, '烧录固件', '确认串口设备后执行:', 'stcgal -p /dev/ttyUSB0 -b 115200 build/ZhaTianRX.hex') pdf.step_block(4, '等待烧录完成', 'stcgal 会自动握手并烧录,完成后会显示成功信息。' '烧录完成后接收机会自动重启运行新固件。') pdf.chapter_title('8.2 使用 STC-ISP (Windows)', 2) pdf.body_text('STC-ISP 是 STC 官方提供的 Windows 烧录工具。') pdf.step_block(1, '下载 STC-ISP', '从 STC 官方网站下载最新版 STC-ISP 工具。') pdf.step_block(2, '连接硬件', '使用 USB-TTL 转换器连接接收机(同上)。') pdf.step_block(3, '选择芯片型号', '在 STC-ISP 中选择芯片型号:STC8H1K08。') pdf.step_block(4, '选择固件文件', '点击"打开程序文件",选择 build/ZhaTianRX.hex。') pdf.step_block(5, '下载烧录', '点击"下载/编程"按钮,然后给接收机重新上电,' 'STC-ISP 会自动检测并烧录。') pdf.chapter_title('8.3 编译固件', 2) pdf.body_text('如果修改了源代码,需要重新编译生成固件。') pdf.step_block(1, '安装 SDCC', '确保已安装 SDCC 编译器:', 'sudo apt install sdcc') pdf.step_block(2, '编译固件', '在项目目录下执行:', 'cd rx && make clean && make') pdf.step_block(3, '查看输出', '编译成功后,在 build/ 目录下生成:\n' ' ZhaTianRX.hex - Intel HEX 格式固件\n' ' ZhaTianRX.bin - 二进制格式固件\n' ' ZhaTianRX.map - 内存映射文件') # ==================== 9. 附录 ==================== pdf.add_page() pdf.chapter_title('9. 附录 - 技术规格') pdf.chapter_title('9.1 硬件规格', 2) cols = ['项目', '规格'] widths = [60, 120] pdf.table_header(cols, widths) spec_rows = [ ['主控芯片', 'STC8H1K08 (8051 内核)'], ['主频', '24MHz 内部 IRC'], ['Flash', '8KB (固件占用 ~6.8KB)'], ['内部 RAM', '256 字节'], ['外部 RAM (XDATA)', '256 字节'], ['射频芯片', 'NRF24L01+ 2.4GHz'], ['SPI 速率', '6MHz (硬件 SPI)'], ['RF 频道', '0~125 (默认 40)'], ['通信速率', '1Mbps'], ['PWM 输出', '4 路, 50Hz, 1.0~2.0ms'], ['SBUS', '100kbps 8E2'], ['CRSF', '420kbps 8N1'], ['UART 命令', '115200 8N1'], ['ADC', '12位, P3.3/ADC11'], ['工作电压', '3.3V ~ 5V'], ] for i, row in enumerate(spec_rows): pdf.table_row(row, widths, fill=(i % 2 == 0)) pdf.ln(4) pdf.chapter_title('9.2 固件信息', 2) cols = ['项目', '数值'] widths = [60, 120] pdf.table_header(cols, widths) fw_rows = [ ['编译器', 'SDCC 4.2.0'], ['编译参数', '-mmcs51 --model-small --no-xinit-opt'], ['固件大小', '6,756 字节 (HEX)'], ['Flash 占用', '82% (剩余 ~1.4KB)'], ['栈空间', '153 字节空闲'], ['XDATA 占用', '283 字节'], ['源码行数', '~1,350 行'], ] for i, row in enumerate(fw_rows): pdf.table_row(row, widths, fill=(i % 2 == 0)) pdf.ln(4) pdf.chapter_title('9.3 数据包格式', 2) pdf.chapter_title('NRF24L01 下行 (发射机 -> 接收机)', 3) cols = ['偏移', '大小', '内容'] widths = [30, 30, 120] pdf.table_header(cols, widths) pkt_rows = [ ['0', '1', '同步头 0xAA'], ['1', '1', '同步头 0x55'], ['2', '6', '对频短语 (如 "LOVE")'], ['8', '16', '8 通道数据, 每通道 2 字节大端 (0~2000)'], ['24', '7', '保留/扩展'], ['31', '1', 'XOR 校验和'], ] for i, row in enumerate(pkt_rows): pdf.table_row(row, widths, fill=(i % 2 == 0)) pdf.ln(2) pdf.chapter_title('NRF24L01 上行 (接收机 -> 发射机, 回传)', 3) cols = ['偏移', '大小', '内容'] widths = [30, 30, 120] pdf.table_header(cols, widths) telem_pkt_rows = [ ['0', '1', '同步头 0xBB'], ['1', '1', '同步头 0x44'], ['2', '2', '电池电压 (大端)'], ['4', '1', 'RSSI (0~100)'], ['5', '1', '丢包率 (0~100%)'], ['6', '2', '温度 (保留)'], ] for i, row in enumerate(telem_pkt_rows): pdf.table_row(row, widths, fill=(i % 2 == 0)) pdf.ln(2) pdf.chapter_title('CRSF 帧格式', 3) cols = ['偏移', '大小', '内容'] widths = [30, 30, 120] pdf.table_header(cols, widths) crsf_rows = [ ['0', '1', '同步头 0xEE'], ['1', '1', '长度 (type+payload+crc)'], ['2', '1', '类型 (0x16=RC通道, 0x08=电池遥测)'], ['3', '可变', 'Payload'], ['末尾', '1', 'CRC8 (多项式 0xD5)'], ] for i, row in enumerate(crsf_rows): pdf.table_row(row, widths, fill=(i % 2 == 0)) # ==================== 10. 完整功能清单 ==================== pdf.add_page() pdf.chapter_title('10. 完整功能清单(38 项)') pdf.body_text( '以下列出 ZhaTianRX 接收机固件的全部功能,每项附带详细操作步骤。' ) # --- 一、无线通信 --- pdf.chapter_title('一、无线通信 (功能 1~8)', 2) # 功能1 pdf.chapter_title('功能 1: NRF24L01 接收', 3) pdf.body_text('通过 STC8H 硬件 SPI 接收发射机 32 字节数据包。') pdf.step_block(1, '确认硬件连接', '确保 NRF24L01 模块正确连接:\n' ' SCK -> P1.5, CE -> P1.6, IRQ -> P1.7\n' ' CSN -> P5.4, MISO -> P1.3, MOSI -> P1.4\n' ' 供电 3.3V, GND 共地。') pdf.step_block(2, '上电自动初始化', '接收机上电后自动初始化 NRF24L01 模块,设置 SPI 速率 6MHz。' '如果模块检测失败,LED 双闪报警。') pdf.step_block(3, '接收数据', 'NRF24L01 收到有效数据包后,接收机解析 8 通道数据(每通道 2 字节大端 0~2000),' '更新输出 PWM/SBUS/CRSF。') # 功能2 pdf.chapter_title('功能 2: 对频绑定', 3) pdf.body_text('上电 EEPROM 无数据自动进入对频模式,扫描 0~125 频道匹配短语。') pdf.step_block(1, '进入对频模式', '接收机上电后检测 EEPROM。无对频数据则自动进入对频模式(LED 快闪 3Hz)。') pdf.step_block(2, '扫描频道', '接收机在 0~125 频道上逐频道监听,等待接收包含匹配短语的对频包。') pdf.step_block(3, '保存绑定信息', '匹配成功后,将发射机地址、频道写入 EEPROM,LED 变为常亮。') # 功能3 pdf.chapter_title('功能 3: 对频短语', 3) pdf.body_text('默认短语 "LOVE",支持 1~6 位字母数字,通过 PHRASE() 命令修改。') pdf.step_block(1, '查看当前短语', '短语保存在 EEPROM 中,可通过串口读取(需在代码中扩展读取命令)。') pdf.step_block(2, '修改短语', '发送命令修改:', 'PHRASE(Abc123)') pdf.step_block(3, '重新对频', '修改短语后需发送 DEL(BIND) 重新对频。') # 功能4 pdf.chapter_title('功能 4: 地址管理', 3) pdf.body_text('5 字节 NRF24L01 地址,通过 ADDR() 命令修改。') pdf.step_block(1, '发送新地址', '格式:', 'ADDR(0xE7 E7 E7 E7 E7)') pdf.step_block(2, '确认修改', '返回 "OK" 表示成功,NRF 重新初始化。') pdf.step_block(3, '重新对频', '修改地址后需重新对频。') # 功能5 pdf.chapter_title('功能 5: 频道切换', 3) pdf.body_text('0~125 频道,通过 TUN(N) 命令切换。') pdf.step_block(1, '发送频道命令', '例如切到频道 40:', 'TUN(40)') pdf.step_block(2, '确认应答', '返回 "OK" 表示切换成功。') # 功能6 pdf.chapter_title('功能 6: 数据包校验', 3) pdf.body_text('XOR 校验和验证。') pdf.step_block(1, '自动校验', '接收机收到 32 字节数据包后,自动计算前 31 字节的 XOR 校验和,' '与第 32 字节对比。不一致则丢弃该包。') # 功能7 pdf.chapter_title('功能 7: 信号丢失保护', 3) pdf.body_text('连续 ~5 秒无数据自动判丢失,PWM 回中位。') pdf.step_block(1, '信号丢失自动处理', '接收机内部维护一个丢失计数器。每次收到有效数据包清零,' '主循环每 10ms 递增一次。计数超过 500(约 5 秒)判定为信号丢失。') pdf.step_block(2, '丢失后行为', 'PWM 模式下所有通道输出回中位(1.5ms 脉宽),LED 变为慢闪。' '信号恢复后自动回到正常状态。') # 功能8 pdf.chapter_title('功能 8: NRF 硬件检测', 3) pdf.body_text('上电检测 NRF 模块是否存在,失败 LED 双闪并定期重试。') pdf.step_block(1, '上电检测', '接收机初始化时读取 NRF24L01 寄存器验证通信。失败则 LED 双闪。') pdf.step_block(2, '自动恢复', '接收机每 500ms 重试检测 NRF 模块。模块正常后自动恢复运行。') # --- 二、输出模式 --- pdf.add_page() pdf.chapter_title('二、输出模式 (功能 9~12)', 2) # 功能9 pdf.chapter_title('功能 9: PWM 输出(默认)', 3) pdf.body_text('4 路 50Hz 舵机 PWM,P3.4-P3.7,1.0~2.0ms 脉宽,定时器0 4us 中断生成。') pdf.step_block(1, '切换到 PWM 模式', '', 'MODEL(PWM)') pdf.step_block(2, '连接舵机', 'CH1(P3.7) CH2(P3.6) CH3(P3.5) CH4(P3.4) 接舵机信号线。' '舵机电源需独立供电。') pdf.step_block(3, '确认输出', '用示波器或舵机测试 PWM 信号。中位 1.5ms,范围 1.0~2.0ms。') # 功能10 pdf.chapter_title('功能 10: SBUS 输出', 3) pdf.body_text('100kbps 8E2,16 通道 11-bit 编码,经三极管反相输出。') pdf.step_block(1, '切换到 SBUS 模式', '', 'MODEL(SBUS)') pdf.step_block(2, '连接飞控', '接收机 TX(P3.1) 接飞控 SBUS RX。信号已反相,可直接连接。') pdf.step_block(3, '验证输出', '用逻辑分析仪或飞控调参软件查看 16 通道数据。') # 功能11 pdf.chapter_title('功能 11: UART 输出', 3) pdf.body_text('115200 8N1 直通,预留扩展。') pdf.step_block(1, '切换到 UART 模式', '', 'MODEL(UART)') pdf.step_block(2, '连接设备', 'TX(P3.1) 接目标设备 RX。波特率 115200 8N1。') # 功能12 pdf.chapter_title('功能 12: CRFS/CRSF 输出', 3) pdf.body_text('420kbps 8N1,CRSF RC 通道帧 (type=0x16),CRC8 校验。') pdf.step_block(1, '切换到 CRFS 模式', '', 'MODEL(CRFS)') pdf.step_block(2, '连接飞控', 'TX(P3.1) 接飞控 CRSF RX,RX(P3.0) 接飞控 CRSF TX(双向通信)。') pdf.step_block(3, '验证双向通信', '飞控应收到 RC 通道帧,同时接收机转发飞控遥测数据给发射机。') # --- 三、回传遥测 --- pdf.add_page() pdf.chapter_title('三、回传遥测 (功能 13~19)', 2) # 功能13 pdf.chapter_title('功能 13: 电池电压回传', 3) pdf.body_text('BAT_ADC(P3.3) 12 位 ADC 采样,所有模式均可回传。') pdf.step_block(1, '连接电池分压电路', '电池电压经分压电阻接入 P3.3。分压比需确保最高电压不超过 3.3V。') pdf.step_block(2, '读取电压', '接收机自动 12 位 ADC 采样,通过回传通道发送给发射机。' '发射机端显示电压值。') # 功能14 pdf.chapter_title('功能 14: RSSI 回传', 3) pdf.body_text('基于 NRF RPD 载波检测寄存器估算 (0-100)。') pdf.step_block(1, '自动采集', '接收机在每个接收周期读取 NRF RPD 寄存器,估算信号强度。') pdf.step_block(2, '查看 RSSI', 'RSSI 值通过回传通道发送给发射机,在发射机端显示。') # 功能15 pdf.chapter_title('功能 15: 丢包率回传', 3) pdf.body_text('滑动窗口估算 (0-100%)。') pdf.step_block(1, '自动计算', '接收机维护滑动窗口,统计最近 N 个数据包中丢失的比例。') pdf.step_block(2, '查看丢包率', '丢包率通过回传通道发送给发射机。') # 功能16 pdf.chapter_title('功能 16: NRF 回传(PWM/SBUS)', 3) pdf.body_text('PWM/SBUS 模式下通过 NRF TX 发 0xBB 0x44 回传包。') pdf.step_block(1, '自动回传', '在 PWM 或 SBUS 模式下,接收机自动在接收间隙切换 NRF 为 TX 模式发送回传包。') pdf.step_block(2, '配置回传频率', '默认 2Hz,可通过 TELEM 命令调整。', 'TELEM(5)') # 功能17 pdf.chapter_title('功能 17: CRSF 遥测回传', 3) pdf.body_text('CRFS 模式下通过 CRSF Battery sensor 帧 (type=0x08) 回传。') pdf.step_block(1, '自动发送', '在 CRFS 模式下,接收机自动构造 CRSF Battery sensor 遥测帧,' '通过 UART 发送给飞控(飞控再转发给遥控器)。') # 功能18 pdf.chapter_title('功能 18: 飞控数据转发', 3) pdf.body_text('CRFS 模式下接收飞控 UART 发来的 CRSF 帧,通过 NRF 转发给发射机。') pdf.step_block(1, '自动转发', '接收机在 CRFS 模式下持续监听 UART RX,收到飞控发来的 CRSF 帧后,' '通过 NRF 转发给发射机。无需额外配置。') # 功能19 pdf.chapter_title('功能 19: 回传频率可调', 3) pdf.body_text('1~10Hz,默认 2Hz,通过 TELEM(N) 命令设置。') pdf.step_block(1, '设置频率', '例如设置为 5Hz:', 'TELEM(5)') pdf.step_block(2, '确认应答', '返回 "OK" 表示设置成功。') # --- 四、串口命令系统 --- pdf.add_page() pdf.chapter_title('四、串口命令系统 (功能 20~28)', 2) # 功能20-23 for cmd_name, cmd_code, desc in [ ('功能 20: MODEL(PWM)', 'MODEL(PWM)', '切 PWM 模式'), ('功能 21: MODEL(SBUS)', 'MODEL(SBUS)', '切 SBUS 模式'), ('功能 22: MODEL(UART)', 'MODEL(UART)', '切 UART 模式'), ('功能 23: MODEL(CRFS)', 'MODEL(CRFS)', '切 CRSF 模式'), ]: pdf.chapter_title(cmd_name, 3) pdf.body_text(desc) pdf.step_block(1, '发送命令', '', cmd_code) pdf.step_block(2, '确认应答', '返回 "OK" 表示切换成功。') # 功能24 pdf.chapter_title('功能 24: ADDR() - 设置 NRF 地址', 3) pdf.body_text('设置 5 字节 NRF 地址。') pdf.step_block(1, '发送命令', '格式:ADDR(0xHH HH HH HH HH)', 'ADDR(0xE7 E7 E7 E7 E7)') pdf.step_block(2, '确认应答', '返回 "OK" 表示成功。') # 功能25 pdf.chapter_title('功能 25: PHRASE() - 设置对频短语', 3) pdf.body_text('设置对频短语 (1-6位字母数字)。') pdf.step_block(1, '发送命令', '', 'PHRASE(LOVE)') pdf.step_block(2, '确认应答', '返回 "OK" 表示成功。') # 功能26 pdf.chapter_title('功能 26: TUN() - 设置 RF 频道', 3) pdf.body_text('设置 RF 频道 (0-125)。') pdf.step_block(1, '发送命令', '', 'TUN(40)') pdf.step_block(2, '确认应答', '返回 "OK" 表示成功。') # 功能27 pdf.chapter_title('功能 27: DEL(BIND) - 清除对频信息', 3) pdf.body_text('清除 EEPROM 对频信息,重新对频。') pdf.step_block(1, '发送命令', '', 'DEL(BIND)') pdf.step_block(2, '确认应答', '返回 "OK",LED 快闪进入对频模式。') # 功能28 pdf.chapter_title('功能 28: TELEM() - 设置回传频率', 3) pdf.body_text('设置回传频率 (1-10Hz)。') pdf.step_block(1, '发送命令', '', 'TELEM(5)') pdf.step_block(2, '确认应答', '返回 "OK" 表示成功。') # --- 五、LED 状态指示 --- pdf.add_page() pdf.chapter_title('五、LED 状态指示 (功能 29~32)', 2) # 功能29 pdf.chapter_title('功能 29: 慢闪 (1Hz)', 3) pdf.body_text('未连接(等待发射机)。') pdf.step_block(1, '观察 LED', 'LED 以 1Hz 慢闪(亮 500ms 灭 500ms),表示接收机已初始化但未收到发射机信号。') pdf.step_block(2, '排查原因', '检查发射机是否开机、对频短语和频道是否匹配、距离是否在有效范围内。') # 功能30 pdf.chapter_title('功能 30: 快闪 (3Hz)', 3) pdf.body_text('对频中。') pdf.step_block(1, '观察 LED', 'LED 以 3Hz 快闪(亮 167ms 灭 167ms),表示接收机正在对频模式。') pdf.step_block(2, '操作', '在发射机上进入对频模式,等待自动配对。') # 功能31 pdf.chapter_title('功能 31: 常亮', 3) pdf.body_text('已连接正常。') pdf.step_block(1, '观察 LED', 'LED 持续点亮,表示接收机已成功连接发射机,通信正常。') pdf.step_block(2, '正常使用', '此时可以正常使用遥控器控制设备。') # 功能32 pdf.chapter_title('功能 32: 双闪 (1Hz)', 3) pdf.body_text('NRF 模块硬件错误。') pdf.step_block(1, '观察 LED', 'LED 以双闪模式(亮100ms 灭100ms 亮100ms 灭700ms)报警。') pdf.step_block(2, '排查原因', '检查 NRF24L01 模块是否插好、供电 3.3V 是否正常、SPI 引脚连接是否正确。' '接收机会自动重试,模块正常后恢复。') # --- 六、存储与配置 --- pdf.add_page() pdf.chapter_title('六、存储与配置 (功能 33~34)', 2) # 功能33 pdf.chapter_title('功能 33: EEPROM 存储', 3) pdf.body_text('通过 IAP 读写,保存对频标志、地址、频道、短语。') pdf.step_block(1, '自动保存', '对频成功后,接收机自动将地址、频道、短语写入 EEPROM。' '下次上电直接读取使用。') pdf.step_block(2, '清除数据', '发送 DEL(BIND) 命令擦除 EEPROM 对频数据。') # 功能34 pdf.chapter_title('功能 34: 上电自动恢复', 3) pdf.body_text('检测 EEPROM 对频标志,有数据直接进入接收模式。') pdf.step_block(1, '上电流程', '接收机上电后读取 EEPROM 对频标志。\n' ' 有数据 -> 直接进入接收模式,LED 慢闪等待信号\n' ' 无数据 -> 进入对频模式,LED 快闪') # --- 七、硬件外设 --- pdf.chapter_title('七、硬件外设 (功能 35~38)', 2) # 功能35 pdf.chapter_title('功能 35: 硬件 SPI', 3) pdf.body_text('STC8H 内置 SPI 主模式,6MHz,驱动 NRF24L01。') pdf.step_block(1, '自动初始化', '接收机上电后自动配置 STC8H 硬件 SPI 为主模式,速率 6MHz。') pdf.step_block(2, '无需用户操作', 'SPI 通信完全由固件自动管理,用户无需干预。') # 功能36 pdf.chapter_title('功能 36: 定时器0', 3) pdf.body_text('4us 中断,生成 4 路 PWM + 系统滴答 (10ms)。') pdf.step_block(1, '自动配置', '定时器0 配置为 4us 中断,用于生成 PWM 信号和系统滴答时钟。') pdf.step_block(2, '无需用户操作', '定时器由固件自动管理。') # 功能37 pdf.chapter_title('功能 37: UART1', 3) pdf.body_text('使用 Timer2 做波特率发生器,支持 115200/100k/420k 切换。') pdf.step_block(1, '自动切换', '切换输出模式时,UART1 自动调整波特率和帧格式。\n' ' PWM/UART: 115200 8N1\n' ' SBUS: 100kbps 8E2\n' ' CRFS: 420kbps 8N1') # 功能38 pdf.chapter_title('功能 38: ADC', 3) pdf.body_text('12 位,P3.3/ADC11,电池电压采样。') pdf.step_block(1, '连接分压电路', '将电池电压经分压电阻接入 P3.3。确保最高电压不超过 3.3V。') pdf.step_block(2, '自动采样', '接收机自动进行 12 位 ADC 采样,结果通过回传通道发送给发射机。') # ==================== 保存 ==================== output_path = '/root/ZhaTianRC/rx/操作指南.pdf' pdf.output(output_path) print(f'PDF 已生成: {output_path}') print(f'共 {pdf.page_no()} 页') if __name__ == '__main__': build_pdf()