diff --git a/rx/CONFIG.h b/rx/CONFIG.h new file mode 100644 index 0000000..847d69d --- /dev/null +++ b/rx/CONFIG.h @@ -0,0 +1,89 @@ +#include "STC8_SDCC.H" +#include + +/* ======================================================================== + * 系统常量 + * ======================================================================== */ + +#define FOSC 24000000UL /* 系统主频 24MHz */ +#define PWM_PERIOD 5000 /* 20ms / 4us = 5000 步 */ +#define PWM_MIN 250 /* 1.0ms 脉宽 (4us/步) */ +#define PWM_MAX 500 /* 2.0ms 脉宽 */ +#define PWM_MID 375 /* 1.5ms 脉宽 */ +#define CHANNEL_COUNT 4 /* 4路PWM输出 */ + +#define UART_BUF_SIZE 32 + +/* 接收状态 */ +#define RX_STATE_DISCONNECTED 0 +#define RX_STATE_BINDING 1 +#define RX_STATE_CONNECTED 2 + +/* 输出模式 */ +#define OUT_MODE_PWM 0 +#define OUT_MODE_SBUS 1 +#define OUT_MODE_UART 2 +#define OUT_MODE_CRFS 3 + +/* EEPROM 地址 (IAP) */ +#define EEPROM_ADDR_BIND_FLAG 0x0000 +#define EEPROM_ADDR_TX_ADDR 0x0001 /* 5字节 */ +#define EEPROM_ADDR_RF_CH 0x0006 +#define EEPROM_ADDR_BIND_PHRASE 0x0007 /* 6字节 */ +#define EEPROM_BIND_MAGIC 0x5A /* 已对频标志 */ + +/* 数据包格式 (与发射机一致) */ +#define RF_SYNC_BYTE0 0xAA +#define RF_SYNC_BYTE1 0x55 +#define RF_PACKET_SIZE 32 +#define RF_PHRASE_LEN 6 + +/* LED 闪烁模式 */ +#define LED_BLINK_SLOW 0 /* 1Hz - 未连接 */ +#define LED_BLINK_FAST 1 /* 3Hz - 对频中 */ +#define LED_ON 2 /* 常亮 - 已连接 */ +#define LED_OFF 3 /* 熄灭 */ +#define LED_BLINK_DOUBLE 4 /* 双闪 - NRF硬件错误 */ + +/* ======================================================================== + * 全局变量 + * ======================================================================== */ + +/* PWM 通道脉宽值 (4us单位, 放在 xdata) */ +static volatile uint16_t __xdata pwm_ch[CHANNEL_COUNT]; + +/* 接收状态 */ +static uint8_t rx_state = RX_STATE_DISCONNECTED; +static uint8_t out_mode = OUT_MODE_PWM; + +/* 对频信息 (放在 xdata) */ +static uint8_t __xdata bind_phrase[RF_PHRASE_LEN] = "LOVE"; +static uint8_t __xdata tx_addr[NRF_ADDR_WIDTH] = {0xE7, 0xE7, 0xE7, 0xE7, 0xE7}; +static uint8_t rf_channel = 40; + +/* 信号丢失计数 */ +static uint16_t lost_packet_cnt = 0; +#define LOST_PACKET_TIMEOUT 500 /* 500次接收超时后判丢失 (~5秒 @ 100Hz) */ + +/* NRF 硬件错误标志 */ +static uint8_t nrf_hw_error = 0; + +/* 电池电压 */ +static volatile uint16_t bat_voltage_mv = 0; + +/* 回传数据 */ +static uint8_t telem_rssi = 0; /* 信号强度 (0-100) */ +static uint8_t telem_packet_loss = 0; /* 丢包率 (0-100%) */ +static uint8_t telem_rate_hz = 2; /* 回传频率 1-10Hz, 默认2Hz */ + +/* CRFS 模式下接收的飞控转发数据 */ +static uint8_t __xdata crfs_forward_buf[24]; +static uint8_t crfs_forward_len = 0; + +/* UART 接收缓冲 (放在 xdata) */ +static uint8_t __xdata uart_rx_buf[UART_BUF_SIZE]; +static uint8_t uart_rx_idx = 0; +static volatile uint8_t uart_cmd_ready = 0; + +/* 定时器计数 (用于LED闪烁) */ +static volatile uint16_t sys_tick = 0; diff --git a/rx/NRF24.c b/rx/NRF24.c new file mode 100644 index 0000000..eecd3e3 --- /dev/null +++ b/rx/NRF24.c @@ -0,0 +1,317 @@ + +/* ======================================================================== + * NRF24L01 硬件 SPI 实现 (STC8H 内置 SPI) + * P1.3=MISO, P1.4=MOSI, P1.5=SCLK, P5.4=CSN(GPIO) + * ======================================================================== */ + +/* SPI 初始化: 主模式, 模式0, 6MHz @ 24MHz */ +static void NRF_SPI_Init(void) { + SPCTL = SSIG | SPEN | MSTR; /* 0xD0: SSIG=1, SPEN=1, MSTR=1, CPOL=0, CPHA=0, fosc/4 */ + SPSTAT = SPIF | WCOL; /* 清除标志 */ +} + +uint8_t NRF_SPI_TransferByte(uint8_t dat) { + SPDAT = dat; + /* 等待传输完成 */ + while (!(SPSTAT & SPIF)); + /* 清除 SPIF (读 SPSTAT 再读 SPDAT 自动清除, 但这里直接写1清除更可靠) */ + SPSTAT = SPIF; + return SPDAT; +} + +uint8_t NRF24L01_ReadReg(uint8_t reg) { + uint8_t val; + NRF_CSN = 0; + NRF_SPI_TransferByte(NRF_CMD_R_REGISTER | reg); + val = NRF_SPI_TransferByte(0xFF); + NRF_CSN = 1; + return val; +} + +void NRF24L01_WriteReg(uint8_t reg, uint8_t value) { + NRF_CSN = 0; + NRF_SPI_TransferByte(NRF_CMD_W_REGISTER | reg); + NRF_SPI_TransferByte(value); + NRF_CSN = 1; +} + +void NRF24L01_ReadBuf(uint8_t reg, uint8_t *buf, uint8_t len) { + NRF_CSN = 0; + NRF_SPI_TransferByte(NRF_CMD_R_REGISTER | reg); + for (uint8_t i = 0; i < len; i++) + buf[i] = NRF_SPI_TransferByte(0xFF); + NRF_CSN = 1; +} + +void NRF24L01_WriteBuf(uint8_t reg, const uint8_t *buf, uint8_t len) { + NRF_CSN = 0; + NRF_SPI_TransferByte(NRF_CMD_W_REGISTER | reg); + for (uint8_t i = 0; i < len; i++) + NRF_SPI_TransferByte(buf[i]); + NRF_CSN = 1; +} + +/* ======================================================================== + * NRF24L01 初始化与操作 + * ======================================================================== */ + +void NRF24L01_Init(void) { + NRF_CE = 0; + NRF_CSN = 1; + + /* 初始化硬件 SPI */ + NRF_SPI_Init(); + + /* 延时等待上电 */ + for (volatile uint16_t d = 0; d < 5000; d++); + + /* 配置寄存器 */ + NRF24L01_WriteReg(NRF_CONFIG, 0x0E); /* EN_CRC, CRC0, PWR_UP, PTX */ + NRF24L01_WriteReg(NRF_EN_AA, 0x01); /* Pipe0 auto ACK */ + NRF24L01_WriteReg(NRF_EN_RXADDR, 0x01); /* Pipe0 enable */ + NRF24L01_WriteReg(NRF_SETUP_AW, 0x03); /* 5字节地址 */ + NRF24L01_WriteReg(NRF_SETUP_RETR, 0x1A); /* 500us + 15次重试 */ + NRF24L01_WriteReg(NRF_RF_CH, rf_channel); + NRF24L01_WriteReg(NRF_RF_SETUP, NRF_RATE_1M | NRF_PA_MAX); + NRF24L01_WriteReg(NRF_RX_PW_P0, RF_PACKET_SIZE); + NRF24L01_WriteReg(NRF_DYNPD, 0x00); /* 关闭动态负载 */ + NRF24L01_WriteReg(NRF_FEATURE, 0x00); + + /* 设置地址 */ + NRF24L01_WriteBuf(NRF_TX_ADDR, tx_addr, NRF_ADDR_WIDTH); + NRF24L01_WriteBuf(NRF_RX_ADDR_P0, tx_addr, NRF_ADDR_WIDTH); + + /* 清空FIFO */ + NRF_CSN = 0; + NRF_SPI_TransferByte(NRF_CMD_FLUSH_TX); + NRF_CSN = 1; + NRF_CSN = 0; + NRF_SPI_TransferByte(NRF_CMD_FLUSH_RX); + NRF_CSN = 1; + + /* 清除状态 */ + NRF24L01_WriteReg(NRF_STATUS, NRF_STATUS_RX_DR | NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT); + + NRF_CE = 0; +} + +uint8_t NRF24L01_Check(void) { + uint8_t buf[5] = {0xA5, 0xA5, 0xA5, 0xA5, 0xA5}; + uint8_t rx[5]; + NRF24L01_WriteBuf(NRF_TX_ADDR, buf, 5); + NRF24L01_ReadBuf(NRF_TX_ADDR, rx, 5); + for (uint8_t i = 0; i < 5; i++) + if (rx[i] != 0xA5) return 0; + return 1; +} + +void NRF24L01_SetChannel(uint8_t ch) { + if (ch > NRF_MAX_CHANNEL) ch = NRF_MAX_CHANNEL; + rf_channel = ch; + NRF24L01_WriteReg(NRF_RF_CH, ch); +} + +void NRF24L01_SetRXAddr(uint8_t pipe, const uint8_t *addr) { + if (pipe > 5) return; + if (pipe < 2) { + NRF24L01_WriteBuf(NRF_RX_ADDR_P0 + pipe, addr, NRF_ADDR_WIDTH); + } else { + NRF24L01_WriteReg(NRF_RX_ADDR_P0 + pipe, addr[0]); + } +} + +void NRF24L01_RXMode(void) { + NRF_CE = 0; + uint8_t cfg = NRF24L01_ReadReg(NRF_CONFIG); + cfg |= 0x01; /* PRIM_RX = 1 */ + NRF24L01_WriteReg(NRF_CONFIG, cfg); + NRF_CE = 1; +} + +void NRF24L01_TXMode(void) { + NRF_CE = 0; + uint8_t cfg = NRF24L01_ReadReg(NRF_CONFIG); + cfg &= ~0x01; /* PRIM_RX = 0 */ + NRF24L01_WriteReg(NRF_CONFIG, cfg); + NRF_CE = 1; +} + +uint8_t NRF24L01_IsDataReady(void) { + uint8_t status = NRF24L01_ReadReg(NRF_STATUS); + return (status & NRF_STATUS_RX_DR) ? 1 : 0; +} + +uint8_t NRF24L01_RxPacket(uint8_t *data) { + uint8_t status = NRF24L01_ReadReg(NRF_STATUS); + if (!(status & NRF_STATUS_RX_DR)) return 0; + + /* 读取payload */ + NRF_CSN = 0; + NRF_SPI_TransferByte(NRF_CMD_R_RX_PAYLOAD); + for (uint8_t i = 0; i < RF_PACKET_SIZE; i++) + data[i] = NRF_SPI_TransferByte(0xFF); + NRF_CSN = 1; + + /* 清除 RX_DR */ + NRF24L01_WriteReg(NRF_STATUS, NRF_STATUS_RX_DR); + + return 1; +} + +void NRF24L01_FlushRX(void) { + NRF_CSN = 0; + NRF_SPI_TransferByte(NRF_CMD_FLUSH_RX); + NRF_CSN = 1; +} + +/* ======================================================================== + * NRF24L01 对频 + * ======================================================================== */ + +void NRF24L01_EnterBindMode(void) { + /* 进入接收模式, 监听对频请求 */ + NRF_CE = 0; + NRF24L01_WriteReg(NRF_CONFIG, 0x0F); /* PRX, PWR_UP, CRC */ + NRF24L01_WriteReg(NRF_EN_RXADDR, 0x01); + NRF24L01_WriteReg(NRF_RX_PW_P0, RF_PACKET_SIZE); + NRF24L01_WriteBuf(NRF_RX_ADDR_P0, tx_addr, NRF_ADDR_WIDTH); + NRF24L01_WriteReg(NRF_STATUS, 0x70); + NRF_CE = 1; +} + +void NRF_EnterBindMode(void) { + rx_state = RX_STATE_BINDING; + NRF24L01_EnterBindMode(); +} + +uint8_t NRF_DoBindScan(void) { + static uint8_t __xdata buf[RF_PACKET_SIZE]; + + /* 扫描所有通道 */ + for (uint8_t ch = 0; ch <= NRF_MAX_CHANNEL; ch++) { + NRF24L01_SetChannel(ch); + for (volatile uint16_t d = 0; d < 500; d++); /* 延时 */ + + if (NRF24L01_RxPacket(buf)) { + /* 验证同步头 */ + if (buf[0] != RF_SYNC_BYTE0 || buf[1] != RF_SYNC_BYTE1) + continue; + + /* 验证对频短语 (6字节, 从偏移2开始) */ + uint8_t match = 1; + for (uint8_t i = 0; i < RF_PHRASE_LEN; i++) { + if (buf[2 + i] != bind_phrase[i]) { + match = 0; + break; + } + } + if (!match) continue; + + /* 对频成功! 从包中提取地址 (偏移8开始, 5字节) */ + for (uint8_t i = 0; i < NRF_ADDR_WIDTH; i++) { + tx_addr[i] = buf[8 + i]; + } + + /* 保存配置到EEPROM */ + EEPROM_SaveConfig(); + + /* 重新初始化NRF */ + NRF24L01_Init(); + NRF24L01_RXMode(); + + rx_state = RX_STATE_DISCONNECTED; + return 1; + } + } + return 0; +} + +/* ======================================================================== + * NRF24L01 数据包处理 + * ======================================================================== */ + +void NRF_ProcessPacket(void) { + static uint8_t __xdata buf[RF_PACKET_SIZE]; + + if (!NRF24L01_RxPacket(buf)) + return; + + /* 验证同步头 */ + if (buf[0] != RF_SYNC_BYTE0 || buf[1] != RF_SYNC_BYTE1) + return; + + /* 验证对频短语 */ + for (uint8_t i = 0; i < RF_PHRASE_LEN; i++) { + if (buf[2 + i] != bind_phrase[i]) + return; + } + + /* 验证校验和 (XOR) */ + uint8_t checksum = 0; + for (uint8_t i = 0; i < RF_PACKET_SIZE - 1; i++) + checksum ^= buf[i]; + if (checksum != buf[RF_PACKET_SIZE - 1]) + return; + + /* 提取通道数据 (8通道, 偏移8, 每通道2字节大端) */ + for (uint8_t i = 0; i < CHANNEL_COUNT; i++) { + uint16_t raw = ((uint16_t)buf[8 + i * 2] << 8) | buf[8 + i * 2 + 1]; + /* raw: 0~2000, 映射到 PWM_MIN~PWM_MAX (250~500) */ + if (raw > 2000) raw = 2000; + pwm_ch[i] = (uint16_t)((uint32_t)raw * (PWM_MAX - PWM_MIN) / 2000 + PWM_MIN); + } + + /* 更新 RSSI 估计: 从 NRF RPD 寄存器读取载波检测 */ + if (NRF24L01_ReadReg(NRF_RPD) & 0x01) { + /* 有载波 = 信号强 */ + if (telem_rssi < 100) telem_rssi += 5; + } else { + if (telem_rssi > 10) telem_rssi -= 1; + } + if (telem_rssi > 100) telem_rssi = 100; + + /* 更新丢包率: 基于 lost_packet_cnt 估算 */ + telem_packet_loss = 0; /* 收到包 = 当前不丢包, 累积值在外部计算 */ + + /* 更新状态 */ + lost_packet_cnt = 0; + if (rx_state != RX_STATE_CONNECTED) { + rx_state = RX_STATE_CONNECTED; + } +} + +/* ======================================================================== + * NRF24L01 回传发送 + * 包格式: 0xBB 0x44 + voltage(2B, mV) + rssi(1B) + loss(1B) + temp(2B) + * ======================================================================== */ + +static void NRF_SendTelemetry(void) { + static uint8_t __xdata tbuf[8]; + + tbuf[0] = 0xBB; /* sync0 */ + tbuf[1] = 0x44; /* sync1 */ + tbuf[2] = (uint8_t)(bat_voltage_mv >> 8); /* voltage high */ + tbuf[3] = (uint8_t)(bat_voltage_mv & 0xFF); /* voltage low */ + tbuf[4] = telem_rssi; /* RSSI */ + tbuf[5] = telem_packet_loss; /* 丢包率 */ + tbuf[6] = 0; /* temp high (未实现) */ + tbuf[7] = 0; /* temp low */ + + /* 切到 TX 模式发送回传 */ + NRF24L01_TXMode(); + NRF_CSN = 0; + NRF_SPI_TransferByte(NRF_CMD_FLUSH_TX); + NRF_CSN = 1; + NRF_CSN = 0; + NRF_SPI_TransferByte(NRF_CMD_W_TX_PAYLOAD); + for (uint8_t i = 0; i < 8; i++) + NRF_SPI_TransferByte(tbuf[i]); + NRF_CSN = 1; + NRF_CE = 1; + for (volatile uint16_t d = 0; d < 100; d++); + NRF_CE = 0; + + /* 切回 RX 模式 */ + NRF24L01_RXMode(); + NRF24L01_FlushRX(); +} diff --git a/rx/NRF24.h b/rx/NRF24.h index a86c360..6f1e2a7 100644 --- a/rx/NRF24.h +++ b/rx/NRF24.h @@ -1,13 +1,13 @@ +# ifndef CONFIG +# define CONFIG +# include "CONFIG.h" +# endif /** * NRF24L01+ 驱动头文件 (STC8H1K08 移植版, SDCC 兼容) * SPI 引脚: SCK=P1.5, CE=P1.6, IRQ=P1.7, CSN=P5.4, MISO=P1.3, MOSI=P1.4 * 使用 STC8H 硬件 SPI (SPI 默认引脚) */ -#ifndef __NRF24_H__ -#define __NRF24_H__ - -#include "STC8_SDCC.H" - + /* SDCC mcs51 模式下缺少的标准类型 */ typedef unsigned char uint8_t; typedef unsigned int uint16_t; diff --git a/rx/UART.c b/rx/UART.c new file mode 100644 index 0000000..e69de29 diff --git a/rx/UART.h b/rx/UART.h new file mode 100644 index 0000000..9fc09ec --- /dev/null +++ b/rx/UART.h @@ -0,0 +1,4 @@ +# ifndef CONFIG +# define CONFIG +# include "CONFIG.h" +# endif \ No newline at end of file diff --git a/rx/gen_pdf.py b/rx/gen_pdf.py index b3dfa0c..adfcaf3 100644 --- a/rx/gen_pdf.py +++ b/rx/gen_pdf.py @@ -1,58 +1,69 @@ #!/usr/bin/env python3 -"""ZhaTianRX 接收机操作指南 PDF 生成脚本""" +"""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('Helvetica', 'I', 8) - self.cell(0, 8, 'ZhaTianRX 接收机操作指南', 0, 0, 'L') - self.cell(0, 8, f'第 {self.page_no()} 页', 0, 1, 'R') + 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('Helvetica', 'I', 7) - self.cell(0, 10, f'ZhaTianRC 项目 - STC8H1K08 + NRF24L01', 0, 0, 'C') + 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('Helvetica', 'B', 16) + self.set_font('CN', 'B', 16) self.set_text_color(0x1A, 0x1A, 0x2E) - self.cell(0, 12, title, 0, 1) + 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('Helvetica', 'B', 13) + self.set_font('CN', 'B', 13) self.set_text_color(0x2C, 0x3E, 0x50) - self.cell(0, 10, title, 0, 1) + self.cell(0, 10, title, new_x="LMARGIN", new_y="NEXT") self.ln(2) elif level == 3: - self.set_font('Helvetica', 'B', 11) + self.set_font('CN', 'B', 11) self.set_text_color(0x34, 0x49, 0x5E) - self.cell(0, 8, title, 0, 1) + self.cell(0, 8, title, new_x="LMARGIN", new_y="NEXT") self.ln(1) def body_text(self, text): - self.set_font('Helvetica', '', 10) + 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('Helvetica', '', 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('Helvetica', '', 10) - self.cell(5, 5.5, chr(8226) + ' ') + self.set_font('CN', '', 10) + self.cell(5, 5.5, '• ') self.multi_cell(0, 5.5, text) self.ln(1) @@ -62,15 +73,15 @@ class ZhaTianPDF(FPDF): self.set_font('Courier', '', 9) self.ln(1) for line in text.split('\n'): - self.cell(0, 5, ' ' + line, 0, 1, '', True) + 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('Helvetica', 'B', 10) + self.set_font('CN', 'B', 10) self.set_text_color(0xC0, 0x39, 0x2B) - self.cell(0, 6, f'步骤 {num}: {title}', 0, 1) - self.set_font('Helvetica', '', 10) + 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: @@ -78,22 +89,22 @@ class ZhaTianPDF(FPDF): self.ln(2) def table_header(self, cols, widths): - self.set_font('Helvetica', 'B', 9) + 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, 1, 0, 'C', True) + 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('Helvetica', '', 9) + 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, 1, 0, 'C', True) + self.cell(widths[i], 6, col, border=1, align='C', fill=True) self.ln() @@ -105,41 +116,44 @@ def build_pdf(): # ==================== 封面 ==================== pdf.add_page() pdf.ln(40) - pdf.set_font('Helvetica', 'B', 28) + pdf.set_font('CN', 'B', 28) pdf.set_text_color(0x1A, 0x1A, 0x2E) - pdf.cell(0, 15, 'ZhaTianRX', 0, 1, 'C') - pdf.set_font('Helvetica', '', 16) + 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, '接收机操作指南', 0, 1, 'C') + 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('Helvetica', '', 11) + pdf.set_font('CN', '', 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.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('Helvetica', 'I', 9) - pdf.cell(0, 7, '本文档涵盖接收机的所有功能、操作步骤及串口命令参考', 0, 1, 'C') + 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('Helvetica', '', 11) + pdf.set_font('CN', '', 11) toc = [ - '1. 概述', - '2. 硬件引脚分配', - '3. 首次使用 - 对频操作', - '4. 输出模式切换', - '5. 串口命令详解', - '6. 回传遥测功能', - '7. LED 状态指示', - '8. 固件烧录方法', - '9. 附录 - 技术规格', + '1. 概述', + '2. 硬件引脚分配', + '3. 首次使用 - 对频操作', + '4. 输出模式切换', + '5. 串口命令详解', + '6. 回传遥测功能', + '7. LED 状态指示', + '8. 固件烧录方法', + '9. 附录 - 技术规格', + '10. 完整功能清单(38 项)', ] for item in toc: - pdf.cell(0, 8, item, 0, 1) + pdf.set_text_color(0x2C, 0x3E, 0x50) + pdf.cell(0, 8, item, new_x="LMARGIN", new_y="NEXT") pdf.ln(1) # ==================== 1. 概述 ==================== @@ -531,7 +545,7 @@ def build_pdf(): ['主频', '24MHz 内部 IRC'], ['Flash', '8KB (固件占用 ~6.8KB)'], ['内部 RAM', '256 字节'], - ['外部 RAM', '256 字节 (XDATA)'], + ['外部 RAM (XDATA)', '256 字节'], ['射频芯片', 'NRF24L01+ 2.4GHz'], ['SPI 速率', '6MHz (硬件 SPI)'], ['RF 频道', '0~125 (默认 40)'], @@ -612,7 +626,312 @@ def build_pdf(): 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}') diff --git a/rx/main.c b/rx/main.c index 2c08d11..5d5990e 100644 --- a/rx/main.c +++ b/rx/main.c @@ -12,99 +12,16 @@ * TX(P3.1) - UART1 TX / SBUS 输出 (经板上三极管反相) * RX(P3.0) - UART1 RX / 命令输入 * - * NRF24L01 (软件SPI): + * NRF24L01 (硬件SPI): * SCK(P1.5) CE(P1.6) IRQ(P1.7) CSN(P5.4) MISO(P1.3) MOSI(P1.4) */ -#include "STC8_SDCC.H" +# ifndef CONFIG +# define CONFIG +# include "CONFIG.h" +# endif + #include "NRF24.h" -#include - -/* ======================================================================== - * 系统常量 - * ======================================================================== */ - -#define FOSC 24000000UL /* 系统主频 24MHz */ -#define PWM_PERIOD 5000 /* 20ms / 4us = 5000 步 */ -#define PWM_MIN 250 /* 1.0ms 脉宽 (4us/步) */ -#define PWM_MAX 500 /* 2.0ms 脉宽 */ -#define PWM_MID 375 /* 1.5ms 脉宽 */ -#define CHANNEL_COUNT 4 /* 4路PWM输出 */ - -#define UART_BUF_SIZE 32 - -/* 接收状态 */ -#define RX_STATE_DISCONNECTED 0 -#define RX_STATE_BINDING 1 -#define RX_STATE_CONNECTED 2 - -/* 输出模式 */ -#define OUT_MODE_PWM 0 -#define OUT_MODE_SBUS 1 -#define OUT_MODE_UART 2 -#define OUT_MODE_CRFS 3 - -/* EEPROM 地址 (IAP) */ -#define EEPROM_ADDR_BIND_FLAG 0x0000 -#define EEPROM_ADDR_TX_ADDR 0x0001 /* 5字节 */ -#define EEPROM_ADDR_RF_CH 0x0006 -#define EEPROM_ADDR_BIND_PHRASE 0x0007 /* 6字节 */ -#define EEPROM_BIND_MAGIC 0x5A /* 已对频标志 */ - -/* 数据包格式 (与发射机一致) */ -#define RF_SYNC_BYTE0 0xAA -#define RF_SYNC_BYTE1 0x55 -#define RF_PACKET_SIZE 32 -#define RF_PHRASE_LEN 6 - -/* LED 闪烁模式 */ -#define LED_BLINK_SLOW 0 /* 1Hz - 未连接 */ -#define LED_BLINK_FAST 1 /* 3Hz - 对频中 */ -#define LED_ON 2 /* 常亮 - 已连接 */ -#define LED_OFF 3 /* 熄灭 */ -#define LED_BLINK_DOUBLE 4 /* 双闪 - NRF硬件错误 */ - -/* ======================================================================== - * 全局变量 - * ======================================================================== */ - -/* PWM 通道脉宽值 (4us单位, 放在 xdata) */ -static volatile uint16_t __xdata pwm_ch[CHANNEL_COUNT]; - -/* 接收状态 */ -static uint8_t rx_state = RX_STATE_DISCONNECTED; -static uint8_t out_mode = OUT_MODE_PWM; - -/* 对频信息 (放在 xdata) */ -static uint8_t __xdata bind_phrase[RF_PHRASE_LEN] = "LOVE"; -static uint8_t __xdata tx_addr[NRF_ADDR_WIDTH] = {0xE7, 0xE7, 0xE7, 0xE7, 0xE7}; -static uint8_t rf_channel = 40; - -/* 信号丢失计数 */ -static uint16_t lost_packet_cnt = 0; -#define LOST_PACKET_TIMEOUT 500 /* 500次接收超时后判丢失 (~5秒 @ 100Hz) */ - -/* NRF 硬件错误标志 */ -static uint8_t nrf_hw_error = 0; - -/* 电池电压 */ -static volatile uint16_t bat_voltage_mv = 0; - -/* 回传数据 */ -static uint8_t telem_rssi = 0; /* 信号强度 (0-100) */ -static uint8_t telem_packet_loss = 0; /* 丢包率 (0-100%) */ -static uint8_t telem_rate_hz = 2; /* 回传频率 1-10Hz, 默认2Hz */ - -/* CRFS 模式下接收的飞控转发数据 */ -static uint8_t __xdata crfs_forward_buf[24]; -static uint8_t crfs_forward_len = 0; - -/* UART 接收缓冲 (放在 xdata) */ -static uint8_t __xdata uart_rx_buf[UART_BUF_SIZE]; -static uint8_t uart_rx_idx = 0; -static volatile uint8_t uart_cmd_ready = 0; - -/* 定时器计数 (用于LED闪烁) */ -static volatile uint16_t sys_tick = 0; +#include "UART.h" /* ======================================================================== * 函数声明 @@ -513,13 +430,27 @@ static void ADC_Init(void) { ADC_CONTR = (ADC_CONTR & 0xF0) | 0x0B; } -static uint16_t ADC_Read(void) { - ADC_CONTR |= ADC_START; /* 启动转换 */ - while (!(ADC_CONTR & ADC_FLAG)); /* 等待完成 */ - ADC_CONTR &= ~ADC_FLAG; /* 清除标志 */ +/* 读取指定ADC通道 */ +static uint16_t ADC_ReadChannel(uint8_t ch) { + ADC_CONTR = (ADC_CONTR & 0xF0) | ch; /* 切换通道 */ + __asm nop __endasm; /* 等待通道切换稳定 */ + __asm nop __endasm; + ADC_CONTR |= ADC_START; /* 启动转换 */ + while (!(ADC_CONTR & ADC_FLAG)); /* 等待完成 */ + ADC_CONTR &= ~ADC_FLAG; /* 清除标志 */ return ((uint16_t)ADC_RES << 8) | ADC_RESL; } +static uint16_t ADC_Read(void) { + return ADC_ReadChannel(0x0B); /* P3.3/ADC11 */ +} + +/* 读取内部带隙基准电压 (通道14), 用于反算VCC */ +/* VCC(mV) = 1190 * 4096 / bandgap_adc */ +static uint16_t ADC_ReadBandgap(void) { + return ADC_ReadChannel(0x0E); /* 内部带隙基准 */ +} + /* ======================================================================== * IAP/EEPROM 操作 * ======================================================================== */ @@ -701,323 +632,6 @@ static void PWM_SetAllFailsafe(void) { } } -/* ======================================================================== - * NRF24L01 硬件 SPI 实现 (STC8H 内置 SPI) - * P1.3=MISO, P1.4=MOSI, P1.5=SCLK, P5.4=CSN(GPIO) - * ======================================================================== */ - -/* SPI 初始化: 主模式, 模式0, 6MHz @ 24MHz */ -static void NRF_SPI_Init(void) { - SPCTL = SSIG | SPEN | MSTR; /* 0xD0: SSIG=1, SPEN=1, MSTR=1, CPOL=0, CPHA=0, fosc/4 */ - SPSTAT = SPIF | WCOL; /* 清除标志 */ -} - -uint8_t NRF_SPI_TransferByte(uint8_t dat) { - SPDAT = dat; - /* 等待传输完成 */ - while (!(SPSTAT & SPIF)); - /* 清除 SPIF (读 SPSTAT 再读 SPDAT 自动清除, 但这里直接写1清除更可靠) */ - SPSTAT = SPIF; - return SPDAT; -} - -uint8_t NRF24L01_ReadReg(uint8_t reg) { - uint8_t val; - NRF_CSN = 0; - NRF_SPI_TransferByte(NRF_CMD_R_REGISTER | reg); - val = NRF_SPI_TransferByte(0xFF); - NRF_CSN = 1; - return val; -} - -void NRF24L01_WriteReg(uint8_t reg, uint8_t value) { - NRF_CSN = 0; - NRF_SPI_TransferByte(NRF_CMD_W_REGISTER | reg); - NRF_SPI_TransferByte(value); - NRF_CSN = 1; -} - -void NRF24L01_ReadBuf(uint8_t reg, uint8_t *buf, uint8_t len) { - NRF_CSN = 0; - NRF_SPI_TransferByte(NRF_CMD_R_REGISTER | reg); - for (uint8_t i = 0; i < len; i++) - buf[i] = NRF_SPI_TransferByte(0xFF); - NRF_CSN = 1; -} - -void NRF24L01_WriteBuf(uint8_t reg, const uint8_t *buf, uint8_t len) { - NRF_CSN = 0; - NRF_SPI_TransferByte(NRF_CMD_W_REGISTER | reg); - for (uint8_t i = 0; i < len; i++) - NRF_SPI_TransferByte(buf[i]); - NRF_CSN = 1; -} - -/* ======================================================================== - * NRF24L01 初始化与操作 - * ======================================================================== */ - -void NRF24L01_Init(void) { - NRF_CE = 0; - NRF_CSN = 1; - - /* 初始化硬件 SPI */ - NRF_SPI_Init(); - - /* 延时等待上电 */ - for (volatile uint16_t d = 0; d < 5000; d++); - - /* 配置寄存器 */ - NRF24L01_WriteReg(NRF_CONFIG, 0x0E); /* EN_CRC, CRC0, PWR_UP, PTX */ - NRF24L01_WriteReg(NRF_EN_AA, 0x01); /* Pipe0 auto ACK */ - NRF24L01_WriteReg(NRF_EN_RXADDR, 0x01); /* Pipe0 enable */ - NRF24L01_WriteReg(NRF_SETUP_AW, 0x03); /* 5字节地址 */ - NRF24L01_WriteReg(NRF_SETUP_RETR, 0x1A); /* 500us + 15次重试 */ - NRF24L01_WriteReg(NRF_RF_CH, rf_channel); - NRF24L01_WriteReg(NRF_RF_SETUP, NRF_RATE_1M | NRF_PA_MAX); - NRF24L01_WriteReg(NRF_RX_PW_P0, RF_PACKET_SIZE); - NRF24L01_WriteReg(NRF_DYNPD, 0x00); /* 关闭动态负载 */ - NRF24L01_WriteReg(NRF_FEATURE, 0x00); - - /* 设置地址 */ - NRF24L01_WriteBuf(NRF_TX_ADDR, tx_addr, NRF_ADDR_WIDTH); - NRF24L01_WriteBuf(NRF_RX_ADDR_P0, tx_addr, NRF_ADDR_WIDTH); - - /* 清空FIFO */ - NRF_CSN = 0; - NRF_SPI_TransferByte(NRF_CMD_FLUSH_TX); - NRF_CSN = 1; - NRF_CSN = 0; - NRF_SPI_TransferByte(NRF_CMD_FLUSH_RX); - NRF_CSN = 1; - - /* 清除状态 */ - NRF24L01_WriteReg(NRF_STATUS, NRF_STATUS_RX_DR | NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT); - - NRF_CE = 0; -} - -uint8_t NRF24L01_Check(void) { - uint8_t buf[5] = {0xA5, 0xA5, 0xA5, 0xA5, 0xA5}; - uint8_t rx[5]; - NRF24L01_WriteBuf(NRF_TX_ADDR, buf, 5); - NRF24L01_ReadBuf(NRF_TX_ADDR, rx, 5); - for (uint8_t i = 0; i < 5; i++) - if (rx[i] != 0xA5) return 0; - return 1; -} - -void NRF24L01_SetChannel(uint8_t ch) { - if (ch > NRF_MAX_CHANNEL) ch = NRF_MAX_CHANNEL; - rf_channel = ch; - NRF24L01_WriteReg(NRF_RF_CH, ch); -} - -void NRF24L01_SetRXAddr(uint8_t pipe, const uint8_t *addr) { - if (pipe > 5) return; - if (pipe < 2) { - NRF24L01_WriteBuf(NRF_RX_ADDR_P0 + pipe, addr, NRF_ADDR_WIDTH); - } else { - NRF24L01_WriteReg(NRF_RX_ADDR_P0 + pipe, addr[0]); - } -} - -void NRF24L01_RXMode(void) { - NRF_CE = 0; - uint8_t cfg = NRF24L01_ReadReg(NRF_CONFIG); - cfg |= 0x01; /* PRIM_RX = 1 */ - NRF24L01_WriteReg(NRF_CONFIG, cfg); - NRF_CE = 1; -} - -void NRF24L01_TXMode(void) { - NRF_CE = 0; - uint8_t cfg = NRF24L01_ReadReg(NRF_CONFIG); - cfg &= ~0x01; /* PRIM_RX = 0 */ - NRF24L01_WriteReg(NRF_CONFIG, cfg); - NRF_CE = 1; -} - -uint8_t NRF24L01_IsDataReady(void) { - uint8_t status = NRF24L01_ReadReg(NRF_STATUS); - return (status & NRF_STATUS_RX_DR) ? 1 : 0; -} - -uint8_t NRF24L01_RxPacket(uint8_t *data) { - uint8_t status = NRF24L01_ReadReg(NRF_STATUS); - if (!(status & NRF_STATUS_RX_DR)) return 0; - - /* 读取payload */ - NRF_CSN = 0; - NRF_SPI_TransferByte(NRF_CMD_R_RX_PAYLOAD); - for (uint8_t i = 0; i < RF_PACKET_SIZE; i++) - data[i] = NRF_SPI_TransferByte(0xFF); - NRF_CSN = 1; - - /* 清除 RX_DR */ - NRF24L01_WriteReg(NRF_STATUS, NRF_STATUS_RX_DR); - - return 1; -} - -void NRF24L01_FlushRX(void) { - NRF_CSN = 0; - NRF_SPI_TransferByte(NRF_CMD_FLUSH_RX); - NRF_CSN = 1; -} - -/* ======================================================================== - * NRF24L01 对频 - * ======================================================================== */ - -void NRF24L01_EnterBindMode(void) { - /* 进入接收模式, 监听对频请求 */ - NRF_CE = 0; - NRF24L01_WriteReg(NRF_CONFIG, 0x0F); /* PRX, PWR_UP, CRC */ - NRF24L01_WriteReg(NRF_EN_RXADDR, 0x01); - NRF24L01_WriteReg(NRF_RX_PW_P0, RF_PACKET_SIZE); - NRF24L01_WriteBuf(NRF_RX_ADDR_P0, tx_addr, NRF_ADDR_WIDTH); - NRF24L01_WriteReg(NRF_STATUS, 0x70); - NRF_CE = 1; -} - -void NRF_EnterBindMode(void) { - rx_state = RX_STATE_BINDING; - NRF24L01_EnterBindMode(); -} - -uint8_t NRF_DoBindScan(void) { - static uint8_t __xdata buf[RF_PACKET_SIZE]; - - /* 扫描所有通道 */ - for (uint8_t ch = 0; ch <= NRF_MAX_CHANNEL; ch++) { - NRF24L01_SetChannel(ch); - for (volatile uint16_t d = 0; d < 500; d++); /* 延时 */ - - if (NRF24L01_RxPacket(buf)) { - /* 验证同步头 */ - if (buf[0] != RF_SYNC_BYTE0 || buf[1] != RF_SYNC_BYTE1) - continue; - - /* 验证对频短语 (6字节, 从偏移2开始) */ - uint8_t match = 1; - for (uint8_t i = 0; i < RF_PHRASE_LEN; i++) { - if (buf[2 + i] != bind_phrase[i]) { - match = 0; - break; - } - } - if (!match) continue; - - /* 对频成功! 从包中提取地址 (偏移8开始, 5字节) */ - for (uint8_t i = 0; i < NRF_ADDR_WIDTH; i++) { - tx_addr[i] = buf[8 + i]; - } - - /* 保存配置到EEPROM */ - EEPROM_SaveConfig(); - - /* 重新初始化NRF */ - NRF24L01_Init(); - NRF24L01_RXMode(); - - rx_state = RX_STATE_DISCONNECTED; - return 1; - } - } - return 0; -} - -/* ======================================================================== - * NRF24L01 数据包处理 - * ======================================================================== */ - -void NRF_ProcessPacket(void) { - static uint8_t __xdata buf[RF_PACKET_SIZE]; - - if (!NRF24L01_RxPacket(buf)) - return; - - /* 验证同步头 */ - if (buf[0] != RF_SYNC_BYTE0 || buf[1] != RF_SYNC_BYTE1) - return; - - /* 验证对频短语 */ - for (uint8_t i = 0; i < RF_PHRASE_LEN; i++) { - if (buf[2 + i] != bind_phrase[i]) - return; - } - - /* 验证校验和 (XOR) */ - uint8_t checksum = 0; - for (uint8_t i = 0; i < RF_PACKET_SIZE - 1; i++) - checksum ^= buf[i]; - if (checksum != buf[RF_PACKET_SIZE - 1]) - return; - - /* 提取通道数据 (8通道, 偏移8, 每通道2字节大端) */ - for (uint8_t i = 0; i < CHANNEL_COUNT; i++) { - uint16_t raw = ((uint16_t)buf[8 + i * 2] << 8) | buf[8 + i * 2 + 1]; - /* raw: 0~2000, 映射到 PWM_MIN~PWM_MAX (250~500) */ - if (raw > 2000) raw = 2000; - pwm_ch[i] = (uint16_t)((uint32_t)raw * (PWM_MAX - PWM_MIN) / 2000 + PWM_MIN); - } - - /* 更新 RSSI 估计: 从 NRF RPD 寄存器读取载波检测 */ - if (NRF24L01_ReadReg(NRF_RPD) & 0x01) { - /* 有载波 = 信号强 */ - if (telem_rssi < 100) telem_rssi += 5; - } else { - if (telem_rssi > 10) telem_rssi -= 1; - } - if (telem_rssi > 100) telem_rssi = 100; - - /* 更新丢包率: 基于 lost_packet_cnt 估算 */ - telem_packet_loss = 0; /* 收到包 = 当前不丢包, 累积值在外部计算 */ - - /* 更新状态 */ - lost_packet_cnt = 0; - if (rx_state != RX_STATE_CONNECTED) { - rx_state = RX_STATE_CONNECTED; - } -} - -/* ======================================================================== - * NRF24L01 回传发送 - * 包格式: 0xBB 0x44 + voltage(2B) + rssi(1B) + loss(1B) + temp(2B) - * ======================================================================== */ - -static void NRF_SendTelemetry(void) { - static uint8_t __xdata tbuf[8]; - - tbuf[0] = 0xBB; /* sync0 */ - tbuf[1] = 0x44; /* sync1 */ - tbuf[2] = (uint8_t)(bat_voltage_mv >> 8); /* voltage high */ - tbuf[3] = (uint8_t)(bat_voltage_mv & 0xFF); /* voltage low */ - tbuf[4] = telem_rssi; /* RSSI */ - tbuf[5] = telem_packet_loss; /* 丢包率 */ - tbuf[6] = 0; /* temp high (未实现) */ - tbuf[7] = 0; /* temp low */ - - /* 切到 TX 模式发送回传 */ - NRF24L01_TXMode(); - NRF_CSN = 0; - NRF_SPI_TransferByte(NRF_CMD_FLUSH_TX); - NRF_CSN = 1; - NRF_CSN = 0; - NRF_SPI_TransferByte(NRF_CMD_W_TX_PAYLOAD); - for (uint8_t i = 0; i < 8; i++) - NRF_SPI_TransferByte(tbuf[i]); - NRF_CSN = 1; - NRF_CE = 1; - for (volatile uint16_t d = 0; d < 100; d++); - NRF_CE = 0; - - /* 切回 RX 模式 */ - NRF24L01_RXMode(); - NRF24L01_FlushRX(); -} - /* ======================================================================== * 串口命令解析 * ======================================================================== */ @@ -1304,8 +918,8 @@ void main(void) { telem_frame[0] = 0xEE; telem_frame[1] = 10; /* len */ telem_frame[2] = 0x08; /* Battery sensor */ - telem_frame[3] = (uint8_t)(bat_voltage_mv >> 8); /* voltage high (mV*10) */ - telem_frame[4] = (uint8_t)(bat_voltage_mv & 0xFF); /* voltage low */ + telem_frame[3] = (uint8_t)((bat_voltage_mv / 10) >> 8); /* voltage high (10mV单位) */ + telem_frame[4] = (uint8_t)((bat_voltage_mv / 10) & 0xFF); /* voltage low */ telem_frame[5] = 0; /* current high */ telem_frame[6] = 0; /* current low */ telem_frame[7] = 0; /* capacity */ @@ -1370,9 +984,22 @@ void main(void) { static uint16_t last_adc_tick = 0; if (sys_tick - last_adc_tick >= 100) { last_adc_tick = sys_tick; - bat_voltage_mv = ADC_Read(); - /* 粗略转换: 12位ADC, Vref=VCC, 分压比取决于硬件 */ - /* 这里保留原始ADC值, 上位机或飞控自行换算 */ + + /* 读取电池分压 ADC (P3.3/ADC11) */ + uint16_t bat_adc = ADC_Read(); + + /* 读取内部带隙基准 (通道14), 反算 VCC */ + uint16_t bg_adc = ADC_ReadBandgap(); + + /* VCC(mV) = 1190 * 4096 / bg_adc + * V_bat(mV) = bat_adc * VCC / 4096 * 11 (分压比 1K:10K = 1/11) + * 合并: V_bat(mV) = bat_adc * 1190 * 11 / bg_adc + * = bat_adc * 13090 / bg_adc + */ + if (bg_adc > 0) { + uint32_t tmp = (uint32_t)bat_adc * 13090UL / bg_adc; + bat_voltage_mv = (uint16_t)tmp; + } } /* 更新LED */ diff --git a/rx/操作指南.pdf b/rx/操作指南.pdf new file mode 100644 index 0000000..784cf81 Binary files /dev/null and b/rx/操作指南.pdf differ