Files
ZhaTianRC/rx/gen_pdf.py
2026-06-26 17:57:02 +08:00

624 lines
26 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
class ZhaTianPDF(FPDF):
def __init__(self):
super().__init__()
self.set_auto_page_break(auto=True, margin=15)
def header(self):
if self.page_no() > 1:
self.set_font('Helvetica', 'I', 8)
self.cell(0, 8, 'ZhaTianRX 接收机操作指南', 0, 0, 'L')
self.cell(0, 8, f'{self.page_no()}', 0, 1, 'R')
self.line(10, 14, 200, 14)
self.ln(4)
def footer(self):
self.set_y(-15)
self.set_font('Helvetica', 'I', 7)
self.cell(0, 10, f'ZhaTianRC 项目 - STC8H1K08 + NRF24L01', 0, 0, 'C')
def chapter_title(self, title, level=1):
if level == 1:
self.set_font('Helvetica', 'B', 16)
self.set_text_color(0x1A, 0x1A, 0x2E)
self.cell(0, 12, title, 0, 1)
self.line(10, self.get_y(), 200, self.get_y())
self.ln(4)
elif level == 2:
self.set_font('Helvetica', 'B', 13)
self.set_text_color(0x2C, 0x3E, 0x50)
self.cell(0, 10, title, 0, 1)
self.ln(2)
elif level == 3:
self.set_font('Helvetica', 'B', 11)
self.set_text_color(0x34, 0x49, 0x5E)
self.cell(0, 8, title, 0, 1)
self.ln(1)
def body_text(self, text):
self.set_font('Helvetica', '', 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('Helvetica', '', 10)
self.set_text_color(0x33, 0x33, 0x33)
x = self.get_x()
self.cell(indent, 5.5, '')
self.set_font('Helvetica', '', 10)
self.cell(5, 5.5, chr(8226) + ' ')
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, 0, 1, '', True)
self.ln(2)
def step_block(self, num, title, desc, code=None):
"""操作步骤"""
self.set_font('Helvetica', 'B', 10)
self.set_text_color(0xC0, 0x39, 0x2B)
self.cell(0, 6, f'步骤 {num}: {title}', 0, 1)
self.set_font('Helvetica', '', 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('Helvetica', '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, 1, 0, 'C', True)
self.ln()
def table_row(self, cols, widths, fill=False):
self.set_font('Helvetica', '', 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, 1, 0, 'C', 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('Helvetica', 'B', 28)
pdf.set_text_color(0x1A, 0x1A, 0x2E)
pdf.cell(0, 15, 'ZhaTianRX', 0, 1, 'C')
pdf.set_font('Helvetica', '', 16)
pdf.set_text_color(0x7F, 0x8C, 0x8D)
pdf.cell(0, 10, '接收机操作指南', 0, 1, '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('Helvetica', '', 11)
pdf.set_text_color(0x55, 0x55, 0x55)
pdf.cell(0, 7, 'MCU: STC8H1K08 | 射频: NRF24L01+ | 编译器: SDCC', 0, 1, 'C')
pdf.cell(0, 7, '固件版本: 1.0 | 项目: ZhaTianRC', 0, 1, 'C')
pdf.ln(20)
pdf.set_font('Helvetica', 'I', 9)
pdf.cell(0, 7, '本文档涵盖接收机的所有功能、操作步骤及串口命令参考', 0, 1, 'C')
# ==================== 目录 ====================
pdf.add_page()
pdf.chapter_title('目录')
pdf.set_font('Helvetica', '', 11)
toc = [
'1. 概述',
'2. 硬件引脚分配',
'3. 首次使用 - 对频操作',
'4. 输出模式切换',
'5. 串口命令详解',
'6. 回传遥测功能',
'7. LED 状态指示',
'8. 固件烧录方法',
'9. 附录 - 技术规格',
]
for item in toc:
pdf.cell(0, 8, item, 0, 1)
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")。'
'匹配成功后自动保存地址到 EEPROMLED 变为常亮。')
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', '256 字节 (XDATA)'],
['射频芯片', '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))
# 保存
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()