以下作品由安信可社区用户
lclight
制作
之前笔者用Ai-M61点亮了屏幕,并显示了图片(教程在这里:
M61 gpio模拟i2c 点亮0.96寸屏幕
),
这次准备在屏幕上显示动画,制作会走路的小人以及会动的背景~
先看效果(如图),由于视频太大,转成gif后文件也过大,只能缩小分辨率、帧数和时长了,原动画是不断绕地图一周。
上一次教程在屏幕上画了一张图,但它是静态的,如果想做点有趣的东西,那动画也是基础,必不可少,所以这次来学画动画。
像电影一样,一系列不同的“画面”连续播放就形成了动画。而创造不同“画面”一般有两种方式,一种是画面本身不同,另一种是画面里面的东西在动。想象一下这个画面,角色从迷宫左上角走到右下角。
第一种:有4张不同的图,逐次播放,称为帧动画。
第二种:有一张背景和主角,每次播放时,背景直接画,角色画在不同的位置,这样的角色称为精灵(精灵本身也可以有帧动画)。
显然需要角色从迷宫左上角走到右下角,第二种更适合,实际应用中需两种结合来使用。
最典型的例子比如RPG动画里面,角色精灵移动时的左右脚迈步动画就是帧动画,精灵则是画在地图中的不同位置。
那其实就很简单了,
每一帧都是先在屏幕画出背景,再在指定位置画上角色,如果角色可操控,那只需要用中断改变其位置。
貌似很容易实现,但这时又想到了个问题,图片没有透明信息。
如果精灵的图片直接覆盖画上去,会有个矩形白边,如图1。
如果是用“位或”的方式画(暂且不论能不能实现),则连精灵都看不到了,如图2。
而想要的效果是最后的效果,部分覆盖,部分透明,如图3。
这个问题最经典的解决方案就是用遮罩,遮罩如图1。
先用遮罩“位与运算”图1,得到图2,再用精灵“位或运算”图2,得到最终结果图3。
或者就是只画黑色的部分,白色的部分直接忽略不画。
为了实现或绘图的图片能进行或运算,也为了能提高性能,得换种画图方式。
原来的方式是直接在屏幕上逐个绘制图像,那改为
把所有精灵图像逐个画在画板上,画完之后再把画板贴到屏幕
上。
可以理解画板是内存数据,操作内存肯定比i2c的画到屏幕快得多,原来要画多个图像,现在合并成了只画一次。而最关键的是内存操作可以进行位运算。
那么接下来简单说下位运算,以上个教程画的大道寺知世为例:
-
const uint8_t picture_tab[]={
-
0x1,0x2,0x4,0x8,0xF0,0xA0,0x0,0x10,0x10,0x10,0x8,0x8,0x8,0x18,0x10,0x11,
图像的数据逐个改为二进制,比如 0x1 = 00000001,0x2 = 00000010,...,0x11 = 00010001,共16个uint8_t,
从左到右的每一竖列,在1的位置涂黑,就得到了下图的0~15列。
把图片数据全部画完,就得到下图
那么图像合并就很明显了,当两张图像使用“或运算”时,只要相同位置有一个是涂黑的,就涂黑。
“与运算”反之,所以我们只需遮罩用&,图像用|,即可得到目标图像,再把目标图像画到屏幕上。
就像这样,把左下角的精灵贴到背景上,背景就是知世。
另外一个难点,就是绘制时是8位一起,所以当精灵在竖轴的坐标不是8的整倍数时,需要跨越两个page绘制,处理起来麻烦,但不是无法处理,就直接看代码了。
这次代码分为3个文件了。main、spirit和resources
main.c
#include
#include
#include
#include
#include
#include
#include
#include "board.h"
#include "log.h"
#include "bflb_mtimer.h"
#include "bflb_i2c.h"
#include "bflb_gpio.h"
#include "bflb_audac.h"
#include "bflb_dma.h"
#include "bl616_glb.h"
#include "bflb_flash.h"
#include "spirit.h"
#include
#include
#include
#define SDA GPIO_PIN_31
#define SCL GPIO_PIN_30
#define waittime(t) vTaskDelay(0)
struct bflb_device_s *gpio;
uint8_t addr = 0x78;
void i2c_start()
{
bflb_gpio_set(gpio, SDA);
waittime(1);
bflb_gpio_set(gpio, SCL);
waittime(1);
bflb_gpio_reset(gpio, SDA);
waittime(1);
bflb_gpio_reset(gpio, SCL);
}
void i2c_stop()
{
bflb_gpio_reset(gpio, SDA);
waittime(1);
bflb_gpio_set(gpio, SCL);
waittime(1);
bflb_gpio_set(gpio, SDA);
}
void send_byte(uint8_t dat)
{
uint8_t i;
for (i = 0; i<8; i++)
{
if (dat & 0x80)
{
bflb_gpio_set(gpio, SDA);
}
else
{
bflb_gpio_reset(gpio, SDA);
}
waittime(1);
bflb_gpio_set(gpio, SCL);
waittime(1);
bflb_gpio_reset(gpio, SCL);
waittime(1);
dat <<= 1;
}
bflb_gpio_set(gpio, SDA);
waittime(1);
bflb_gpio_set(gpio, SCL);
waittime(1);
bflb_gpio_reset(gpio, SCL);
waittime(1);
}
void oled_wr_byte(uint8_t dat, uint8_t mode)
{
i2c_start();
send_byte(addr);
mode ? send_byte(0x40) : send_byte(0x00);
send_byte(dat);
i2c_stop();
}
void oled_cmd(uint8_t cmd)
{
oled_wr_byte(cmd, 0);
}
void oled_data(uint8_t dat)
{
oled_wr_byte(dat, 1);
}
void page_set(uint8_t page)
{
oled_cmd(0xb0 + page);
}
void column_set(uint8_t col)
{
oled_cmd(0x10 | (col >> 4));
oled_cmd(0x00 | (col & 0x0f));
}
void oled_clear()
{
uint8_t page,col;
for (page = 0; page < 8; ++page)
{
page_set(page);
column_set(0);
for (col = 0; col < 128; ++col)
{
oled_data(0x00);
}
}
}
void oled_full()
{
uint8_t page,col;
for (page = 0; page < 8; ++page)
{
page_set(page);
column_set(0);
for (col = 0; col < 128; ++col)
{
oled_data(0xff);
}
}
}
void oled_display(const uint8_t *ptr_pic)
{
uint8_t page,col;
for (page = 0; page < 8; ++page)
{
page_set(page);
column_set(0);
for (col = 0; col < 128; ++col)
{
oled_data(*ptr_pic++);
}
}
}
void oled_display_r(const uint8_t *ptr_pic)
{
uint8_t page,col,data;
for (page = 0; page < 8; ++page)
{
page_set(page);
column_set(0);
for (col = 0; col < 128; ++col)
{
data=*ptr_pic++;
data=~data;
oled_data(data);
}
}
}
void refresh()
{
uint8_t page,col,data;
dc = dc0;
for (page = 0; page < 8; ++page)
{
page_set(page);
column_set(0);
for (col = 0; col < 128; ++col)
{
data=*dc++;
data=~data;
oled_data(data);
}
}
}
void init_display()
{
uint8_t cmds[25] =
{
0xAE,
0xD5,
0x80,
0xA8,
0X3F,
0xD3,
0X00,
0x40,
0x8D,
0x14,
0x20,
0x02,
0xA1,
0xC8,
0xDA,
0x12,
0x81,
0xEF,
0xD9,
0xf1,
0xDB,
0x30,
0xA4,
0xA6,
0xAF,
};
uint8_t i;
for (i = 0; i < 25; ++i)
{
oled_cmd(cmds[i]);
}
sleep(1);
}
void led_run(void* param)
{
uint8_t heroWalkImg1[] = HERO_IMG1;
uint8_t heroWalkImg2[] = HERO_IMG2;
uint8_t heroMask[] = HERO_MASK;
struct frameImg heroFrames[2] = {
{8, 16, heroWalkImg1, heroMask},
{8, 16, heroWalkImg2, heroMask}
};
struct
spirit hero = {0,0, 1,2, SPIRIT_STATE_SHOW, heroFrames};
uint8_t tmpDir = 0;
gpio = bflb_device_get_by_name("gpio");
bflb_gpio_init(gpio, SDA, GPIO_OUTPUT | GPIO_PULLUP);
bflb_gpio_init(gpio, SCL, GPIO_OUTPUT | GPIO_PULLUP);
bflb_gpio_init(gpio, GPIO_PIN_18, GPIO_INPUT | GPIO_FLOAT | GPIO_SMT_EN | GPIO_DRV_0);
bflb_gpio_init(gpio, GPIO_PIN_14, GPIO_OUTPUT | GPIO_FLOAT | GPIO_SMT_EN | GPIO_DRV_1);
init_display();
oled_full();
sleep(3);
while (true)
{
clean();
draw(&bg);
draw(&hero);
refresh();
switch (tmpDir)
{
case 0:
if (hero.px<110)
{move(&hero, 1, 0);}
else
{
if (bg.px>-72)
{move(&bg, -1, 0);}
else
{tmpDir++;}
}
break;
case 1:
if (hero.py<52)
{move(&hero, 0, 1);printf("hero.py:%d\r\n", hero.py);}
else
{tmpDir++;}
break;
case 2:
if (hero.px>10)
{move(&hero, -1, 0);}
else
{
if (bg.px<0)
{move(&bg, 1, 0);}
else
{tmpDir++;}
}
break;
case 3:
if (hero.py>0)
{move(&hero, 0, -1);}
else
{tmpDir=0;}
break;
}
nextFrame(&hero);
}
}
int main(void)
{
board_init();
xTaskCreate(led_run, (char*)"led_run", 1024*4, NULL, 1, NULL);
vTaskStartScheduler();
}
文件名:spirit.h
#ifndef SPIRIT_H
#define SPIRIT_H
#include
#include "resources.h"
#define SPIRIT_STATE_HIDE 0
#define SPIRIT_STATE_SHOW 1
struct frameImg
{
uint8_t w;
uint8_t h;
uint8_t *img;
uint8_t *mask;
};
struct spirit
{
short px;
short py;
uint8_t curFrame;
uint8_t maxFrame;
uint8_t state;
struct frameImg *frames;
};
uint8_t bgImg[] = BG_IMG;
struct frameImg bgFrame[] = {
{200, 64, bgImg, NULL}
};
struct spirit bg = {0,0, 1,1, SPIRIT_STATE_SHOW, bgFrame};
uint8_t dc0[1024];
uint8_t *dc;
uint8_t nextFrame(struct spirit * sp)
{
sp->curFrame ++;
if (sp->curFrame > sp->maxFrame){
sp->curFrame = 1;
}
return sp->curFrame;
}
uint8_t frame(struct spirit * sp, uint8_t tarFrame)
{
sp->curFrame = tarFrame;
if (sp->curFrame > sp->maxFrame){
sp->curFrame = 1;
}
return sp->curFrame;
}
void move(struct spirit * sp, short opx, short opy)
{
sp->px +=opx;
sp->py +=opy;
}
void draw(struct spirit * sp)
{
short spCol,spLine,spPage,spIdx,dcCol,dcLine,dcIdx,dcNIdx,dcPage,offset;
if (sp->state != SPIRIT_STATE_HIDE){
struct frameImg * spFrame = & sp->frames[sp->curFrame-1];
for (spCol = 0; spCol < spFrame->w; ++spCol)
{
for (spLine = 0; spLine < spFrame->h; spLine+=8)
{
dcLine = sp->py+spLine; dcCol = sp->px+spCol;
if (dcLine>=0 && dcLine < 64 && dcCol>=0 && dcCol < 128){
dcPage = dcLine/8;
spPage = spLine/8;
dcIdx = (dcCol)+(dcPage)*128;
spIdx = spCol+spPage*spFrame->w;
offset = dcLine % 8;
if (offset == 0){
if (spFrame->mask != NULL) { dc0[dcIdx] &= spFrame->mask[spIdx]; }
dc0[dcIdx] |= spFrame->img[spIdx];
}else{
if (spFrame->mask != NULL) { dc0[dcIdx] &= (~((~spFrame->mask[spIdx]) << offset)); }
dc0[dcIdx] |= (spFrame->img[spIdx] << offset);
dcNIdx = (dcCol)+(dcPage+1)*128;
if (dcNIdx>=0 && dcNIdx < 1024){
if (spFrame->mask != NULL) { dc0[dcNIdx] &= ((spFrame->mask[spIdx] >> (8-offset) ) | (0xFF << offset) ); }
dc0[dcNIdx] |= ((spFrame->img[spIdx] >> (8-offset) ) & (~(0xFF << offset)));
}
}
}
}
}
}
}
void clean()
{
for (size_t i = 0; i < 1024; ++i)
{
dc0[i] = 0;
}
}
#endif
文件名:resources.h
#ifndef RESOURCES_H
#define RESOURCES_H
#include
#define HERO_IMG1 {0xFF,0x81,0x85,0xA5,0xA1,0x85,0x81,0xFF,0x0,0xE,0x9,0x0,0x1,0xE,0x8,0x0}
#define HERO_IMG2 {0xFF,0x81,0x85,0xA5,0xA1,0x85,0x81,0xFF,0x0,0x0,0xD,0xA,0xD,0x8,0x0,0x0}
#define HERO_MASK {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}
#define