624 lines
26 KiB
Python
624 lines
26 KiB
Python
#!/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")。'
|
||
'匹配成功后自动保存地址到 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', '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()
|