【2025电源模块创意大赛】超轻型自制飞机用航电系统-作品提交

一、作品简介

国内个人自制的超轻型载人飞机已经有一定市场,但是因为成本及空间限制,大多数人都没有安装飞机航电设备,日常飞行缺乏设备提供可靠的数据参考。
现有民航或专用航电仪表因为法规因素,产品认证成本高昂,导致销售价格动辄上万,功能复杂点甚至十几万,远远超过大家的承受能力,急需一款简单易用、成本相对低廉的航电装置。

给个参考吧,下面这个MGL出的高度、空速、升降速率表要卖500美刀,这还是不含税不含运费的价格,对于国内大多数飞机爱好者来说,这个价格简直是天价了,就不用说其他更高级的航电仪表,随随便便几万十几万。图片里的仪表拆开后里面简单的令人发指,做工非常山寨,我自己手工做出来的质感都更好。内部结构简单到不行,只有电源、MCU、RTC、EEPROM、LCD屏和RS232接口、两个气压传感器。

基于上述原因,计划开发一款基于CANBUS总线的可扩展航电系统,根据需要可使用基础模块并通过多个拓展功能模块实现更加丰富的功能。目前已经在开发部分模块,其余的陆续开发中,此处介绍的是显示模块

主要物料清单:

图中很多物料都是手头有啥用啥,所以看起来有点乱。这里我用的LCD屏幕是一款双联270*160单色屏,主要是显示内容可以更丰富,另外也是考虑到户外强光用普通全彩LCD不行,需要用1000nit左右特殊高亮的。

硬件上,采用 Raspberry Pi 官方研发的 RP2040 微控制器芯片,搭载了ARM Cortex M0 + 双核处理器,高达 133MHz 的运行频率,内置了 264KB 的 SRAM 和 2MB 的内存,还板载有多达 26 个多功能的 GPIO 引脚。

主控产品特性:

  • 采用了 Raspberry Pi 官方设计的 RP2040 微控制器芯片
  • 搭载了双核 ARM Cortex M0 + 处理器,运行频率高达 133MHz 灵活时钟
  • 内置了 264KB 的 SRAM 和 2MB 的片上 Flash
  • USB1.1 主机和设备支持
  • 支持低功耗睡眠和休眠模式
  • 可通过 USB 识别为大容量存储器进行拖放式下载程序
  • 多达 26 个多功能的 GPIO 引脚
  • 2 个 SPI,2 个 I2C,2 个 UART,3 个 12 位 ADC,16 个可控 PWM 通道
  • 精确的片上时钟和定时器
  • 温度传感器
  • 片上加速浮点库
  • 8 个可编程 I/O (PIO) 状态机,用于自定义外设支持

二、系统框图

该模块主要作用是通过CANBUS获取姿态、发动机监控、空气电脑等子模块的数据,并通过LCD屏幕进行展示,以便飞行员及时了解飞机姿态、高度、空速、升降率、航向、飞行时间以及发动机转速、缸头温度、机油压力等参数信息。

板载RTC电路计算实时时间,以便计算飞行时间等参数。

显示屏采用双联屏(主要是手头刚好有),单块分辨率270*160,并且通过两个FPC排线接到板子。因为飞机在空中可能面临阳光直射情况,毕竟不是波音737那种大客机,一般自制飞机都是裸奔状态,也就是连座舱罩都没有,屏幕会面临阳光直射情况,需要足够背景光并且支持亮度调节,这里通过光敏电阻获取环境光照强度,并且通过pwm恒流背光电路调节。

模块设计了RS232接口以便跟其他商用航电通讯,获取数据,比如通过Garmin G5获取姿态高度等辅助信息。为了更好的扩展性,还保留了IIC接口外扩,为了适配外部传感器的电源电压,保留了3种跳线可选。输入信号通过编码器开关,可以打开菜单设置参数以及校准,并且作为气压修正数据输入方式,数据存储到EEPROM里保证掉电不丢失。

主控采用了RP2040,为了方便调试这里外加了Type-C接口,未来可能会内置CAN BOOTLOADER,以便通过CAN总线进行集中软件版本管理和升级。

三、各部分功能说明

原理图分为五个子页,第一个是MCU主控相关的,实现最小系统功能。

第二部分是双联屏LCD接口,采用28Pin 0.5mm连接器。背光采用恒流芯片,PWM调光的方式,实际这里只需要一套背光芯片,因为LCD排线上只有一个连接了内部LED灯。

第三部分CAN部分子页面,主要包含了DB9主连接器,两路供电输入分别是蓄电池12V和6V的VMAIN,以及一路CAN通讯,收发器采用了XL1042,控制器采用了XL2515,兼容主流MCP2515,mcu通过SPI接口来控制。

第四页为其他杂七杂八的,包括编码器开关、Sensor扩展接口、光敏传感器、RTC时钟、EEPROM、RS232接口电路及LED指示灯。

第五页为电源部分,这里DCDC包含了两部分,一个是+12V蓄电池输入电压后转为+5.9V,与+6V的VMAIN同时接入MPS的MPM38222芯片。

VMAIN是来自接口板的备用电源,会设置不间断电源来保证飞机断电时系统正常工作。

MPM38222双通道DC-DC模块,模块集成了内置功率MOSFET和电感的单片降压开关变换器。能够大幅度简化电源系统设计,实现简单易用的解决方案。

  • 指标性能

  • 原理图

根据原理图可以明显看出来,该模块外围非常简单,几个输入输出滤波电容,外加分压电阻反馈就搞定了,跟一个LDO没啥区别,可以平替了。

四、作品源码

  • IIC接口测试源代码
# I2C Scanner MicroPython
from machine import Pin, I2C
import time

led = Pin(22, Pin.OUT)
i2c = I2C(1, scl=Pin(27), sda=Pin(26),freq=100_00)
print('I2C SCANNER')

def main():
    while True:
        devices = i2c.scan()
        if len(devices) == 0:
          print("No i2c device !")
        else:
          print('i2c devices found:', len(devices))
          for device in devices:
            print("I2C hexadecimal address: ", hex(device))
            led.toggle()
            time.sleep(3)
if __name__ == "__main__":
    main()
  • 测试结果

  • LED测试源代码
# I2C Scanner MicroPython
from machine import Pin, I2C
import time

pin22 = Pin(22, Pin.OUT)
i2c = I2C(1, scl=Pin(27), sda=Pin(26),freq=100_00)

#LED Test
while True:
  pin22.value(1)
  time.sleep(0.5)
  pin22.value(0)
  time.sleep(0.5)
  • 测试结果

PixPin_2025-11-15_22-17-47

  • CAN接口测试源代码
from machine import Pin, SPI, PWM, I2C
import time

XL2515_SPI_PORT = 0
XL2515_SCLK_PIN = 18
XL2515_MOSI_PIN = 19
XL2515_MISO_PIN = 16
XL2515_CS_PIN = 17
XL2515_INT_PIN = 20


class RP2350_CAN:
    def __init__(self, rate_kbps = "100KBPS", spi_cs = XL2515_CS_PIN, irq = XL2515_INT_PIN, spi_port = XL2515_SPI_PORT, spi_clk = XL2515_SCLK_PIN,spi_mosi = XL2515_MOSI_PIN, spi_miso = XL2515_MISO_PIN,  spi_freq=10_000_000):
        self.can_rate_arr = {
            "5KBPS"   : [0x27, 0XB4, 0x06],
            "10KBPS"  : [0x13, 0XB4, 0X06],
            "20KBPS"  : [0x09, 0XB4, 0x06],
            "50KBPS"  : [0x03, 0XB4, 0x06],
            "100KBPS" : [0x01, 0xB4, 0x06],
            "125KBPS" : [0x01, 0xB9, 0x04],
            "250KBPS" : [0x01, 0x90, 0x02],
            "500KBPS" : [0x00, 0x90, 0x02],
        }

        self.spi = SPI(spi_port, spi_freq, polarity = 0, phase = 0, bits = 8, sck = Pin(spi_clk), mosi = Pin(spi_mosi), miso = Pin(spi_miso))
        self.cs = Pin(spi_cs, Pin.OUT)
        self.cs(1)
        self.int = Pin(irq, Pin.IN, Pin.PULL_UP)
        self.int.irq(handler = self.int_callback, trigger = Pin.IRQ_FALLING)
        self.recv_flag = False
        self.reset()
        time.sleep(0.1)
        self.config(rate_kbps)
        
        
    def config(self, rate_kbps):
        CNF3 = 0x28
        CNF2 = 0x29
        CNF1 = 0x2A
        
        TXB0SIDH = 0x31
        TXB0SIDL = 0x32
        TXB0DLC = 0x35
        
        RXB0SIDH = 0x61
        RXB0SIDL = 0x62
        RXB0CTRL = 0x60
        RXB0DLC = 0x65
        
        RXF0SIDH = 0x00
        RXF0SIDL = 0x01
        RXM0SIDH = 0x20
        RXM0SIDL = 0x21
        
        CANINTF = 0x2C
        CANINTE = 0x2B
        
        CANCTRL = 0x0F
        REQOP_NORMAL = 0x00
        CLKOUT_ENABLED = 0x07
        CANSTAT = 0x0E
        OPMODE_NORMAL = 0x00
        
        self.write_byte(CNF1, self.can_rate_arr[rate_kbps][0])
        self.write_byte(CNF2, self.can_rate_arr[rate_kbps][1])
        self.write_byte(CNF3, self.can_rate_arr[rate_kbps][2])
        self.write_byte(TXB0SIDH, 0xFF);
        self.write_byte(TXB0SIDL, 0xE0);
        self.write_byte(TXB0DLC, 0x40 | 0x08);

        # Set RX
        self.write_byte(RXB0SIDH, 0x00);
        self.write_byte(RXB0SIDL, 0x60);
        self.write_byte(RXB0CTRL, 0x60);
        self.write_byte(RXB0DLC, 0x08);

        self.write_byte(RXF0SIDH, 0xFF);
        self.write_byte(RXF0SIDL, 0xE0);
        self.write_byte(RXM0SIDH, 0xFF);
        self.write_byte(RXM0SIDL, 0xE0);

        # can int
        self.write_byte(CANINTF, 0x00); # clean interrupt flag
        self.write_byte(CANINTE, 0x01); # Receive Buffer 0 Full Interrupt Enable Bit

        self.write_byte(CANCTRL, REQOP_NORMAL | CLKOUT_ENABLED)
        dummy = self.read_byte(CANSTAT)
        if ((dummy & 0xe0) != OPMODE_NORMAL):
            print("OPMODE_NORMAL")
            self.write_byte(CANCTRL, REQOP_NORMAL | CLKOUT_ENABLED) #set normal mode
        
    def send(self, can_id, data):
        TXB0CTRL = 0x30
        TXB0SIDH = 0x31
        TXB0SIDL = 0x32
        TXB0EID8 = 0x33
        TXB0EID0 = 0x34
        TXB0DLC  = 0x35
        TXB0D0   = 0x36
        dly = 0
        while ((self.read_byte(TXB0CTRL) & 0x08) and (dly < 50)):
            dly += 1
            time.sleep_ms(1)
        
        self.write_byte(TXB0SIDH, (can_id >> 3) & 0XFF)
        self.write_byte(TXB0SIDL, (can_id & 0x07) << 5)
        
        self.write_byte(TXB0EID8, 0)
        self.write_byte(TXB0EID0, 0)
        self.write_byte(TXB0DLC, len(data))
        for i in range(len(data)):
            self.write_byte(TXB0D0 + i, data[i])
        self.write_byte(TXB0CTRL, 0x08)
    
    def recv(self, can_id):
        RXB0SIDH = 0x61
        RXB0SIDL = 0x62
        CANINTF = 0x2C
        CANINTE = 0x2B
        RXB0DLC = 0x65
        RXB0D0 = 0x66
        
        if self.recv_flag == False:
            return None
        self.recv_flag = False
        
        self.write_byte(RXB0SIDH, (can_id >> 3) & 0XFF)
        self.write_byte(RXB0SIDL, (can_id & 0x07) << 5)
        
        while True:
            if (self.read_byte(CANINTF) & 0x01):
                len = self.read_byte(RXB0DLC)
                buf = bytearray(len)
                for i in range(len):
                   buf[i] = self.read_byte(RXB0D0 + i)
                self.write_byte(CANINTF, 0);
                self.write_byte(CANINTE, 0x01)  # enable
                self.write_byte(RXB0SIDH, 0x00) # clean
                self.write_byte(RXB0SIDL, 0x60)
                return buf
    def int_callback(self, pin):
        self.recv_flag = True
    
    def reset(self):
        CAN_RESET = 0xC0
        self.cs(0)
        self.spi.write(bytearray([CAN_RESET]))
        self.cs(1)
    def read_byte(self, reg):
        CAN_READ = 0x03
        self.cs(0)
        self.spi.write(bytearray([CAN_READ, reg]))
        data = self.spi.read(1)
        self.cs(1)
        return data[0]
    def write_byte(self, reg, data):
        CAN_WRITE = 0x02
        self.cs(0)
        self.spi.write(bytearray([CAN_WRITE, reg, data]))
        self.cs(1)
        
        
if __name__ == "__main__":
    can = RP2350_CAN()
    while True:
        can.send(0x123, [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88])
        recv_data = can.recv(0x123)
        if recv_data != None:
            print("recv:", [hex(i) for i in recv_data])
        time.sleep(1)
  • 测试结果

五、作品功能演示视频

  • CAN接口通讯测试

六、项目总结

首先非常感谢MPS和EEworld举行的该活动,能让我有机会接触到MPS的产品,其实之前学习国外开源作品时候就有看到MPS超小体积的电源模块,外围实在是太简单了,能把体积做到非常小。本想买几片试试,结果搜了下元器件商城价格太贵了,于是就放弃了。后来上海电子展,还有幸去MPS柜台咨询了下,现场工作人员很热情,当时我提了申请样品一直没通过,人家直接说给我个联系方式给你发,只是碍于自己没啥批量需求没好意思找人家要。可是这次活动给了我实际接触了解MPS产品的机会,通过DIY过程,感觉这产品太简单了,焊接上去就能工作,不要太简单。

只是这次时间对我来说仓促了点,因为整个产品是个复杂的系统,分成多个模块,目前只是做了其中一个,后面会逐步完善完成其他部分,后续也会逐步发到论坛里。

1 个赞

这个想法还是很不错的

需要考虑下单板的可靠性,气压对器件的影响,产品卖那么贵也是有原因的

嗯,感谢宝贵意见。设计之前是研究过目前成熟商用方案的,同时已经考虑单板可靠性测试需求,功能测试完成后会进行高低温、EMC等相关测试。