行车记录仪别扔!我把它爆改成打游戏的「掌机」,附全套开源教程(硬件+软件)

面包板社区 2025-08-16 07:30

大家好,我是板哥,最近社区上线了【硬核玩电·创意DIY】活动,欢迎各位电子界朋友们,以电子为笔,以创意为墨来社区交个朋友。

本次活动参加即有奖励。更有开发板大礼包(多款)+京东自营购物金等您来拿!详见文末



本篇正文

前面也参加了面包板社区的多期拆解活动,非常喜欢类似的活动,奖品丰富,又能学习和获得乐趣,还能得到有意思分享的喜悦。一方面可以学习参考别人的设计; 一方面也可以DIY改造, 通过实践,得到技能提升,分享技术。
资讯配图

所以本期活动一定不会落下必须得好好参与一下的。所以一开始参与该活动自己想的宗旨就是:要有技术分享,又要有乐趣,可复制,可以作为学习资料。所以希望找一个合适的主题IDEA,可以详细的分享整个DIY过程。
于是乎是长时间的脑洞中....。每天上下班,闲散步时看到身边的小电子产品都在考虑具不具备可改造,DIY可玩性。
IDEA来源

直到有一天,上班比较早,到了公司停下车,就在车里休息下。但是呢又没什么可做的,就仰躺着发呆,突然看到了行车记录仪,脑瓜子闪现了一个念头...行车记录仪有显示,是不是可以将其改造为一个小的游戏机。在这种等对象,接小孩,等等无比聊赖的时候,如果能来一把坦克大战,那将是多么惬意的事情。

这个想法简直太帅了,就它了,说干就干!

整体设计

先来设计下整体方案,刚好在之前的拆解活动中也分享了拆解的一个行车记录仪,设备还闲置着呢,这不就派上用场了。

还记得有网友都在评论,这是电子垃圾,不过垃圾也可以变废为宝!!!这就是嵌入式开发者的乐趣。

整体设计如下,阴影部分是需要设计的,即需要设计两块板子。

1. 无线手柄

需要使用电池供电,所以需要设计电池充电,自动切换电源,开关机等电源管理功能。

另外就是按键采集部分和控制板通过无线连接,可以使用蓝牙或者其他无线模块。
资讯配图
2. 控制板

尽量考虑不影响原来行车记录仪的功能,游戏机需要使用记录仪的屏幕作为显示,所以考虑增加一个switch,可以切换屏幕接记录仪还是接控制板。

需要实现文件系统用于存储游戏文件
需要设计交互模块,用于导入导出游戏文件等
无线模块用于和游戏手柄通讯
另外就是液晶屏的驱动移植NES等。

总结

有了IDEA就开始干了,在年前就完成了手柄和控制板的硬件设计,板子也焊接好了,趁着过年把软件完成。

本着寓教于乐的精神,后续会连载文章,尽可能详细的分享每一个步骤和细节,这样本系列文章也可以作为嵌入式初学者实践的一个案例来参考学习。

DIY行车记录仪为游戏机

第一部分:无线手柄硬件设计 

硬件已经开源,地址如下:  https://oshwhub.com/qinyunti/key-w, 本文介绍手柄硬件的设计过程。
资讯配图
整体结构

整体结构如下,基于PY32F002B的MCU主控,包括电源管理模块,无线模块用于和控制板通讯,按键采集部分。
资讯配图
硬件设计
1. 最小系统部分

这里MCU选用了py32f002b,因为它真的很便宜! 大概5毛钱一颗,32位Cortex-M0,开发起来也很方便,开发小的东西很好用。
资讯配图
最小系统部分原理图如下
资讯配图
使用SWD调试,引出所有IO可以作为开发板使用。

一个LED指示灯

选择无线模块或者蓝牙模块用于和主控通讯。
资讯配图
资讯配图
2. 按键部分

8路按键采集,使用IIC接口的芯片PCF8574采集
资讯配图
3. 电源部分

主要包括以下部分

USB供电充电,以及转ttl串口,可以直接调试蓝牙模块。

USB和电池供电切换电路

充电部分

开关机控制部分

5V转3V3电源
资讯配图
4. PCB打样与焊接调试

嘉立创在线EDA可以直接下单,注意下单前一定要进行DRC检测避免错误。
资讯配图
打样很快,用了券只要10块钱,几天就到了。
资讯配图
资讯配图
板子回来之后就是焊接测试,建议焊接前先进行电源是否短路等一些基本测试。

然后个人习惯是先焊接MCU和最小系统,然后再按照模块焊接完。

焊接完之后图片如下
资讯配图
资讯配图
分享一个经验,焊接时随时测试电源是否对地短路,否则等全部焊接完如果有短路就不好查了。

测试的话同样的先确认电源没有短路等情况,然后上电,确认电源输出是否正确,

然后连接JLINK看是否能识别芯片,这些都OK基本,后面就是软件调试了。

总结

以上分享了手柄的硬件设计,以及打样焊接调试过程,一点小经验,焊接时随时确认电源。

第二部分:无线手柄软件开发

前面我们完成了手柄的硬件设计与调试,现在来分享详细的软件开发过程。

软件已经开源见:https://github.com/qinyunti/py32f002b-key.git。本着寓教于乐的精神,尽可能详细分享整个过程,本文也可以作为py32f002b的开发参考。

开发环境搭建

我这里基于MDK 5.40,编译器版本6.  MDK的安装就不再赘述。

1. 下载手册和SDK

从以下官网地址https://www.py32.org/mcu/PY32F002Bxx.html下载相关资料
资讯配图
可以从git地址下载固件库, git clone https://github.com/OpenPuya/PY32F002B_Firmware.git

其他资料可以参考

https://pan.baidu.com/s/1GJoXbWn9oOyeqGn6Igg5DA?pwd=6688&_at_=1737128105692#list/path=%2F%E8%B5%84%E6%96%99%E4%B8%8B%E8%BD%BD%2FMCU%E8%B5%84%E6%96%99%2FPY32%E7%B3%BB%E5%88%97%E5%8D%95%E7%89%87%E6%9C%BA

2. 安装MDK支持包

可以如下MDK中在线安装
资讯配图
3. 创建工程

Project->New uVision Project...

选择目录,输入工程名保存
资讯配图
选择芯片型号
资讯配图
选择startup
资讯配图
配置选项
资讯配图
定义宏PY32F002Bx5
资讯配图
File -> New...

Ctrl+S保存

保存为main.c
资讯配图
在Source Group1上点击右键,添加文件
资讯配图
选择刚才添加的main.c
资讯配图
编译
资讯配图
右键点击Target1->Option for Target
资讯配图
选择仿真器,我这里是CMSIS-DAP
资讯配图
然后点击Settings

看到识别到了芯片
资讯配图
确认flash下载算法
资讯配图
如果没有则点击Add添加,算法文件位于D:\Keil_v5\Packs\Puya\PY32F0xx_DFP\1.2.2\Flash
资讯配图
将上述文件复制到

D:\Keil_v5\ARM\Flash

此时点击ADD就可以看到上述算法
资讯配图
点击红色d按钮,下载并进入仿真环境
资讯配图
此时可以仿真调试,说明一切就绪。

4. 添加驱动库

将git上下载的固件库的PY32F002B_HAL_Driver复制到自己的工程,并添加

点击Target右键,添加Group
资讯配图
选中Fn+F2改名为Driver

双击Driver,添加所有c和h文件
资讯配图
并添加头文件包含路径
资讯配图
从D:\Keil_v5\Packs\Puya\PY32F0xx_DFP\1.2.2\MDK\Templates\PY32F002B\Inc下复制

py32f002b_hal_conf.h到自己的工程,同样添加其所在路径到头文件包含路径。

OPT编程方法

硬件上PC0控制了一个LED

默认PC0为复位功能,可以修改为IO功能。

需要修改opt,以下以修改PC0为IO为例介绍。
资讯配图
对应的opt位置如下,需要将该选项字节修改为10,即PC0作为IO,PB6作为SWD
资讯配图
先要添加opt算法,见前面创建工程章节
资讯配图
然后新建

文件py32f002b_OPT.s添加到工程,内容为
;/*****************************************************************************/;/* py32f002b_OPT.S: y32f002b Flash Option Bytes                              */;/*****************************************************************************/;/* <<< Use Configuration Wizard in Context Menu >>>                          */;/*****************************************************************************/;/* This file is part of the uVision/ARM development tools.                   */;/* Copyright (c) 2005-2008 Keil Software. All rights reserved.               */;/* This software may only be used under the terms of a valid, current,       */;/* end user licence from KEIL for a compatible version of KEIL software      */;/* development tools. Nothing else gives you the right to use this software. */;/*****************************************************************************/;// <e> Flash Option BytesFLASH_OPT       EQU     0;// </e>                IF      FLASH_OPT <> 0                AREA    |.ARM.__AT_0x1FFF0080|, CODE, READONLY                DCB     0xAA,  0xDE,  0x55,  0x21                DCB     0x0B,  0x00,  0xF4,  0xFF                DCB     0x00,  0x00,  0xFF,  0xFF                   DCB     0x3F,  0x00,  0xC0,  0xFF                  ENDIF                END
资讯配图
如下位置勾选则下载opt,否则不下载
资讯配图
实际就是一个条件编译,控制是否有定位在地址0x1FFF0080处的16字节数据
资讯配图
这16字节数据可以参考手册的说明,手动修改,也可以

从官网下载PY32OptionBytesConfig_x64.exe生成

https://py32.org/tool/PY32_OptionBytesConfig.html
资讯配图
按照如下配置,成成bin文件
资讯配图
用16进制编辑工具打开查看该16字节内容,复制到上述代码中去。

以上之后直接点击d就会和程序一起下载。

下载完成之后这里不勾选,下次就不会再下载opt了。
资讯配图
LED测试

以上修改opt之后,pc0就变为了IO功能,用于驱动LED

初始化
void led_init(void){  GPIO_InitTypeDef  GPIO_InitStruct;  __HAL_RCC_GPIOC_CLK_ENABLE();                          /* Enable GPIOA clock */  GPIO_InitStruct.Pin = GPIO_PIN_0;  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;            /* Push-pull output */  GPIO_InitStruct.Pull = GPIO_PULLUP;                    /* Enable pull-up */  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;          /* GPIO speed */    /* GPIO initialization */  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);  }

然后HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_0);就可以反转控制LED。

串口驱动/蓝牙模块控制

我们接收中断+FIFO实现串口接收,发送使用查询方式。

FIFO的实现见公众号文章。

串口驱动实现见公众号文章

代码见uart.c/h,fifo.c/h

代码考虑了可移植性,可以方便的移植。
资讯配图
串口使用PA6和PA7(见原理图),初始化如下
void uart_init(uint32_t baud){         UART_HandleTypeDef huart;                GPIO_InitTypeDef  GPIO_InitStruct;                __HAL_RCC_GPIOA_CLK_ENABLE();                          /* Enable GPIOA clock */          __HAL_RCC_USART1_CLK_ENABLE();                GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;                GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;                /* Push-pull output */                GPIO_InitStruct.Pull = GPIO_PULLUP;                    /* Enable pull-up */                GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;          /* GPIO speed */            GPIO_InitStruct.Alternate = GPIO_AF1_USART1;           /* PA6 AF1 USART_TX*/                /* GPIO initialization */                HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);           //LL_USART_InitTypeDef USART_InitStruct;         //USART_InitStruct.BaudRate = baud;         //USART_InitStruct.DataWidth = 16;         //USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE;         //USART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_8;         //USART_InitStruct.Parity = LL_USART_PARITY_NONE;         //USART_InitStruct.StopBits = LL_USART_STOPBITS_1;         //USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX;         //LL_USART_Init(USART1, &USART_InitStruct);         //LL_USART_EnableIT_RXNE(USART1);         huart.Instance = USART1;         huart.Init.BaudRate = baud;         huart.Init.WordLength = UART_WORDLENGTH_8B;         huart.Init.StopBits = UART_STOPBITS_1;         huart.Init.Parity = UART_PARITY_NONE;         huart.Init.HwFlowCtl = UART_HWCONTROL_NONE;         huart.Init.Mode = UART_MODE_TX_RX;         huart.Init.OverSampling = UART_OVERSAMPLING_8;         HAL_UART_Init(&huart);         //24000000/115200 = 208.3333         //USART1->BRR = (208ul<<4) + 5;         __HAL_UART_ENABLE_IT(&huart, UART_IT_RXNE);         HAL_NVIC_SetPriority(USART1_IRQn,1,1);         HAL_NVIC_EnableIRQ(USART1_IRQn);}
串口接收中断时数据写入FIFO中
void uart_rx_cb(uint8_t* buff, uint32_t len){        fifo_in(&s_uart_fifo_dev, buff, len);}void USART1_IRQHandler(void){        uint8_t ch;        ch = LL_USART_ReceiveData8(USART1);        uart_rx_cb(&ch, 1);}
读数据就是从FIFO取出数据
uint32_t uart_read(uint8_t* buffer, uint32_t len){    uint32_t rlen;    CriticalAlloc();    EnterCritical();    rlen = fifo_out(&s_uart_fifo_dev, buffer, len);    ExitCritical();    return rlen;}
发送采用查询方式,发送其实也可以中断+FIFO方式,这里为了简单就直接查询方式。
uint32_t uart_send(uint8_t* buffer, uint32_t len){    for(uint32_t i=0;i<len;i++)    {        while(LL_USART_IsActiveFlag_TXE(USART1) == 0);        LL_USART_TransmitData8(USART1,buffer);    }    return len;}
FIFO实例以及临界段接口
#define CriticalAlloc()#define EnterCritical()     __disable_irq()#define ExitCritical()      __enable_irq()uint8_t s_uart_rx_buffer[64];static fifo_st s_uart_fifo_dev={.in = 0,.len = 0,.out = 0,.buffer = s_uart_rx_buffer,.buffer_len = sizeof(s_uart_rx_buffer),};main.c中初始化串口     uart_init(115200);然后调用  uart_send(s_key_buf,sizeof(s_key_buf));即可发送数据。

蓝牙模块使用AT指令详见模块手册即可。

IIC驱动与PCF8574按键采集

IIC直接使用IO模拟的方式,因为有现成的代码,简单方便。

代码如下
资讯配图
见公众号文章

Pcf8574驱动,设计为完全独立的模块,只依赖iic操作,无需任何修改完全可移植。具体操作参考手册即可。
资讯配图
基于IIC和Pcf8574再封装按键获取接口,见key.c/h
#ifndef KEY_H#define KEY_H#ifdef __cplusplus    extern "C"{#endif#include <stdint.h>void key_init(void);void key_deinit(void);int key_write(uint8_t val);int key_read(uint8_t* val);#ifdef __cplusplus    }#endif#endif
Key.c实现IIC实例,需要的IO接口
static io_iic_dev_st iic_dev={        .scl_write = io_iic_scl_write_port,        .sda_write = io_iic_sda_write_port,        .sda_2read = io_iic_sda_2read_port,        .sda_read = io_iic_sda_read_port,        .delay_pf = io_iic_delay_us_port,        .init = io_iic_init_port,        .deinit = io_iic_deinit_port,        .delayus = 1,};
实现pcf8574实例和需要的接口
pcf8574_dev_st pcf8574_dev={        .start = pcf8574_iic_start_port,        .stop = pcf8574_iic_stop_port,        .read = pcf8574_iic_read_port,        .write = pcf8574_iic_write_port,        .init = pcf8574_iic_init_port,        .deinit = pcf8574_iic_deinit_port,        .addr = 0,};
详见代码。
在main.c中初始化    HAL_Init();    HAL_Delay(1000);    led_init();        key_init();        uart_init(115200);蓝牙初始化#define AT_CONN "AT+CONN=D2BC14D9F46B\r\n"          uart_send((uint8_t*)AT_CONN,strlen(AT_CONN));      HAL_Delay(1000);发送该命令连接从蓝牙,进入透传模式,D2BC14D9F46B为从模块的地址,具体获取参考蓝牙模块的规格书按键和LED使用时间控制    uint32_t led_pre_ts = 0;    uint32_t led_cur_ts = 0;    uint32_t key_pre_ts = 0;    uint32_t key_cur_ts = 0;LED1S反转一次    led_pre_ts = HAL_GetTick();    key_pre_ts = HAL_GetTick();        while (1){            led_cur_ts = HAL_GetTick();            key_cur_ts = HAL_GetTick();            if(diff_u32(led_pre_ts, led_cur_ts) >= LED_PERIOD){                   led_pre_ts = led_cur_ts;                   HAL_GPIO_TogglePin(GPIOCGPIO_PIN_0);            }按键20ms获取一次并发送            if(diff_u32(key_pre_ts, key_cur_ts) >= KEY_PERIOD){                   key_pre_ts = key_cur_ts;                   key_read(&key_now);                   s_key_buf[2= key_now;                   s_key_buf[3= ~key_now;                   uart_send(s_key_buf,sizeof(s_key_buf));            }
测试

测试见视频,先蓝牙从模块接电脑,可以看到每一个按键都正确。
总结

以上详细分享了py32f002b的开发过程,包括串口,LED控制,按键等模块的驱动。可以作为py32ff002b的入门开发参考。
项目的软硬件已经开源:
手柄硬件 https://oshwhub.com/qinyunti/key-w
手柄软件 https://github.com/qinyunti/py32f002b-key.git
作者:qinyunti 来源:面包板社区
原帖地址:https://mbb.eet-china.com/forum/topic/148531_1_1.html

硬核玩电/DIY!赢开发板大礼包!

亲爱的电子工程师、硬件极客、电子爱好者、社区的家人们:

这个夏天,以电子为笔,以创意为墨——来面包板社区造点会"跳动"的电子DIY吧!我们给大家准备了开发板大礼包+京东自营购物礼金!等您来拿哦!


资讯配图基础福利:所有参与者可领取2000 E币(可在面包板社区兑换商城使用)。


资讯配图活动奖项:

  • 硬核奖(1名):开发板大礼包(知名品牌开发板2块【如芯驿、STM32等】+其他开发板1块,市场价不低于1500元) +1000元京东自营商城购物金。

  • 创意奖(1名):开发板礼包(品牌开发板2块【STM32、灵动微等】,市场价不低于500元);+500元京东自营商城购物金。

  • 人气奖(1名):开发板礼包(品牌开发板2块【STM32、灵动微等】,市场价不低于500元)+500元京东自营商城购物金。

  • 达人奖(5名):奖励200元京东自营购物金。

  • 优秀作品奖:内容最生动、故事性最强的作品在面包板社区微信公众号阅读量过万的内容,每篇奖励1000E币,不限篇数。

注:更多详情请访问https://mbb.eet-china.com/forum/topic/153762_1_1.html

资讯配图点击阅读原文,了解活动详情!



声明:内容取材于网络,仅代表作者观点,如有内容违规问题,请联系处理。 
开源 软件
more
美国AI致命短视:中国手握电力、芯片集群、开源三张王牌,三大「阳谋」悄然超车
GPT-5认为这个模型是开源界的Claude 3.5——陈天桥朋友圈里的 MiroMind ODR 让我眼前一亮
谷歌版小钢炮开源!0.27B大模型,4个注意力头,专为终端而生
首个开源多模态Deep Research智能体,超越多个闭源方案
港大联手月之暗面等开源OpenCUA:人人可造专属电脑智能体
模仿人类推理修正过程,阶跃星辰提出形式化证明新范式 | 开源
重新定义CV天花板!吞下17亿图片的Meta最强巨兽DINOv3开源!
首个机器人世界模型开源平台发布
文心开源服务站赋能DAY·成都站圆满落幕,共绘西部AI创新生态新篇章
7B小模型逆袭GPT-4o!复旦&创智邱锡鹏团队造出「世界感知」具身智能体,代码数据完全开源!
Copyright © 2025 成都区角科技有限公司
蜀ICP备2025143415号-1
  
川公网安备51015602001305号