1.项目描述
项目目的
1.自行设计一个电子沙漏的物理结构,将提供的两个LED灯板和RP2040 Game Kit固定
2.通过RP2040 Game Kit上的按键和LCD屏幕设定沙漏一个周期的时间,实现如上图中LED的效果
3.通过RP2040 Game Kit上的姿态传感器来感知沙漏的方向变化,并开始新的沙漏操作
设计思路
沙漏显示部分使用LED点阵,使用串转并芯片节省大量IO。
设计使用4个按键,功能分别是:增加时间,减少时间,开始,停止。
最大定时时间240秒,最小定时时间5秒,单次调整步进5秒。
整体分为两个模式,1.定时设置模式,按增加跟减少可以调整定时时间,按开始后转到沙漏控制模式。2.沙漏控制模式,进行模拟沙漏部分的运算与显示,运行时可以按停止切换到定时模式。或者当计时结束后,也会自动切换到定时设置模式。
沙粒的重力模拟分两部分设计:上半部分:采用查表的方式确定那一颗沙粒落下。下半部分:采用碰撞检测的方式,当发现下面沙粒阻碍移动时,选择一个可以移动的方向继续移动,直到无法移动,当碰到两个方向都能走需要决策的时候,将会采用一次左一次右的方式来确保沙粒能够均匀落到底部。
重力感应部分,定时检测,当发现出现翻转的时候,交换两个沙粒显示的缓冲区,实现沙漏翻转功能。
沙粒控制部分,将整体分为两类动沙粒与静沙粒,其中静沙粒存储在缓存区里,而动沙粒以坐标记录,每次移动只需要判断动沙粒的碰撞即可,当动沙粒无法移动时,其所在的位置会产生一个静沙粒,原本的动沙粒清除,等待下一次流程。
总结:该设计方式主要好处是简单,避免了模拟重力是出现的符合重力规律但是不符合实际常识的问题。但因为每一次都会只控制一颗沙粒,无法实现倾斜沙漏,沙粒自由移动的情况。
2.硬件介绍
-
采用树莓派Pico核心芯片RP2040:
-
双核Arm Cortex M0+内核,可以运行到133MHz
- 264KB内存
-
性能强大、高度灵活的可编程IO可用于高速数字接口
-
片内温度传感器、并支持外部4路模拟信号输入,内部ADC采样率高达500Ksps、12位精度
-
支持MicroPython、C、C++编程
-
-
板上功能:
-
240*240分辨率的彩色IPS LCD,SPI接口,控制器为ST7789
-
四向摇杆 + 2个轻触按键 + 一个三轴姿态传感器MMA7660用做输入控制
-
板上外扩2MB Flash,预刷MicroPython的UF2固件
-
一个红外接收管 + 一个红外发射管
- 一个三轴姿态传感器MMA7660
-
一个蜂鸣器
-
双排16Pin连接器,有SPI、I2C以及2路模拟信号输入
-
可以使用MicroPython、C、C++编程
- USB Type C连接器用于供电、程序下载
-
3.开发环境
RP2040支持多种环境进行开发,它作为一个M0的双核芯片,第一时间想到的就是用ARM的亲儿子keil开发设计,可是树莓派官方并没有提供这一种方式,而是把类似linux的一套搬到RP2040上面了,GCC固然强大,可是使用起来还是比较折腾的,MicroPython固然简单,可是与主流的嵌入式开发相差不小,封装起来的代码使用起来很畅快,但是自己要写一个驱动就麻烦的很。不过早就有大佬实现了这个想法,在keil上搭建了RP2040的开发环境。
这里我是使用了傻孩子大佬的一个开源项目:https://github.com/GorgonMeducer/Pico_Template 。使用这个项目就能轻松的使用keil进行RP2040的开发了。部署非常简单,keil只要额外安装GorgonMeducer.perf_counter.1.9.4.pack这个包(这个包在项目里面也有提供,是一个测量运行时间的组件),就能直接打开工程使用了。
上面的项目再搭配上另一个开源项目:https://github.com/majbthrd/pico-debug/releases (将RP2040的一个核实现为DAP功能),打开后下载 pico-debug-gimmecache.uf2文件,将其烧录到RP2040里,就能利用上RP2040的一个核作为DAP去调试另一个核,让RP2040的开发方式更加接近于传统的单片机的开发方式,而且实现只需要一根USB线就完成下载与调试的功能,再也不用折腾来折腾去了。
4.软件运行流程图
5.主要代码片段及说明
初始化部分
system_init();
stdio_init_all();
LCD_Init();//LCD初始化
LCD_Fill(0,0,LCD_W,LCD_H,WHITE);
MMA7660_Init();
gpio_init(LED_DIN_PIN);
gpio_set_dir(LED_DIN_PIN, GPIO_OUT);
gpio_init(LED_CLK_PIN);
gpio_set_dir(LED_CLK_PIN, GPIO_OUT);
gpio_init(LED_RCLK_PIN);
gpio_set_dir(LED_RCLK_PIN, GPIO_OUT);
gpio_put(LED_DIN_PIN, 0);
gpio_put(LED_CLK_PIN, 0);
gpio_put(LED_RCLK_PIN, 0);
add_repeating_timer_ms(2, repeating_timer_callback, NULL, &timer);
刷新点阵部分
一次显示一行,通过视觉暂存实现显示
uint8_t led_scan_i = 0;
bool repeating_timer_callback(repeating_timer_t *rt)
{
for(uint8_t j=0;j<8;j++)
{
gpio_put(LED_DIN_PIN, led_data_scan[led_scan_i][j]);
gpio_put(LED_CLK_PIN, 0);
gpio_put(LED_CLK_PIN, 1);
}
for(uint8_t j=0;j<8;j++)
{
gpio_put(LED_DIN_PIN, led_data1[led_scan_i][j]);
gpio_put(LED_CLK_PIN, 0);
gpio_put(LED_CLK_PIN, 1);
}
for(uint8_t j=0;j<8;j++)
{
gpio_put(LED_DIN_PIN, led_data_scan[led_scan_i][j]);
gpio_put(LED_CLK_PIN, 0);
gpio_put(LED_CLK_PIN, 1);
}
for(uint8_t j=0;j<8;j++)
{
gpio_put(LED_DIN_PIN, led_data2[led_scan_i][j]);
gpio_put(LED_CLK_PIN, 0);
gpio_put(LED_CLK_PIN, 1);
}
gpio_put(LED_RCLK_PIN, 0);
gpio_put(LED_RCLK_PIN, 1);
led_scan_i++;
if(led_scan_i>=8)led_scan_i = 0;
return true;
}
数组与标志复位
for(uint8_t i=0;i<8;i++)
{
for(uint8_t j=0;j<8;j++)
{
grains_data1[i][j] = 0;
grains_data2[i][j] = 1;
}
}
grains_coordinate[0] = -1;
grains_coordinate[1] = -1;
reduce_num = 0;
temp_time = 0;
配置时间部分
while(1)
{
char str_log[20];
LCD_ShowChinese(0,104,"定时时间",RED,WHITE,32,0);
sprintf(str_log,":%dS ",grains_time);
LCD_ShowString(32*4,104,str_log,RED,WHITE,32,0);
if(gpio_get(KEY_A_PIN) == 0)
{
sleep_ms(10);
while(gpio_get(KEY_A_PIN) == 0);
if(grains_time < 240)
grains_time += 5;
}
if(gpio_get(KEY_B_PIN) == 0)
{
sleep_ms(10);
while(gpio_get(KEY_B_PIN) == 0);
if(grains_time > 5)
grains_time-=5;
}
if(gpio_get(KEY_START_PIN) == 0)
{
sleep_ms(10);
while(gpio_get(KEY_START_PIN) == 0);
break;
}
}
主要显示控制部分
while(1)
{
if(temp_time >= grains_time * 2)
{
temp_time = 0;
if(grains_coordinate[0] == -1 || grains_coordinate[1] == -1)
{
grains_data2[gravity_reduce[reduce_num][0]][gravity_reduce[reduce_num][1]] = 0;
reduce_num++;
grains_coordinate[0] = 7;
grains_coordinate[1] = 7;
}
else if((grains_coordinate[0] > 0 && grains_coordinate[1] > 0) && grains_data1[grains_coordinate[0]-1][grains_coordinate[1]-1] != 1)
{
grains_coordinate[0]--;
grains_coordinate[1]--;
}
else if((grains_coordinate[0] > 0 && grains_coordinate[1] > 0) && grains_data1[grains_coordinate[0]-1][grains_coordinate[1]] != 1 && grains_data1[grains_coordinate[0]][grains_coordinate[1]-1] != 1)
{
static uint8_t grains_lr = 0;
grains_coordinate[grains_lr]--;
grains_lr++;
if(grains_lr > 1)
grains_lr = 0;
}
else if(grains_coordinate[0] > 0 && grains_data1[grains_coordinate[0]-1][grains_coordinate[1]] != 1)
{
grains_coordinate[0]--;
}
else if(grains_coordinate[1] > 0 && grains_data1[grains_coordinate[0]][grains_coordinate[1]-1] != 1)
{
grains_coordinate[1]--;
}
else
{
grains_data1[grains_coordinate[0]][grains_coordinate[1]] = 1;
grains_coordinate[0] = -1;
grains_coordinate[1] = -1;
}
if(MMA7660_GetResult_Y() >= 0)
{
for(uint8_t i=0;i<8;i++)
for(uint8_t j=0;j<8;j++)
{
led_data1[i][j] = grains_data1[i][j];
led_data2[i][j] = grains_data2[i][j];
}
if(grains_coordinate[0] != -1 && grains_coordinate[1] != -1)
led_data1[grains_coordinate[0]][grains_coordinate[1]] = 1;
}
else
{
for(uint8_t i=0;i<8;i++)
for(uint8_t j=0;j<8;j++)
{
led_data1[i][j] = grains_data2[i][j];
led_data2[i][j] = grains_data1[i][j];
}
if(grains_coordinate[0] != -1 && grains_coordinate[1] != -1)
led_data2[grains_coordinate[0]][grains_coordinate[1]] = 1;
}
}
if(gpio_get(KEY_SELECT_PIN) == 0)
{
sleep_ms(10);
while(gpio_get(KEY_SELECT_PIN) == 0);
break;
}
temp_time++;
sleep_us(1150);
}
6.功能演示
定时时间配置
沙子落下
重力感应翻转
7.遇到的主要难题及解决方法
点阵驱动不起来
忘记处理RCLK信号,加上就好了。
没在电子森林找到使用的TFT屏的资料
拿网上不同的几家初始化代码都试了试。
8.未来的计划或建议
可以探索更多的对芯片的开发方式,实验更多有意思的环境,希望电子森林里面能够将各种文档资料完善一下,或者是开放给部分有编辑意向的开发者,尽快完善起来。