Files
ZhaTianRC/rx/gen_pdf.py
T
2026-06-27 14:44:10 +08:00

943 lines
42 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 舵机 PWMP3.4-P3.71.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 8E216 通道 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 8N1CRSF RC 通道帧 (type=0x16)CRC8 校验。')
pdf.step_block(1, '切换到 CRFS 模式', '', 'MODEL(CRFS)')
pdf.step_block(2, '连接飞控',
'TX(P3.1) 接飞控 CRSF RXRX(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()