NWatch 移植

"简单介绍了一下移植过程"

Posted by Yummy on February 13, 2020

Nwatch源码移植简介

你好,这里是Nwatch移植的过程介绍。

首先

明确目标,想要将Arduino上面的GUI程序移植到stm32中。

对此,先可以考虑将Nwatch的图像显示到stm32的屏幕上。

通过阅读其源码可以发现,它使用了一个

1
byte oledBuffer[FRAME_BUFFER_SIZE];	 //oled的缓冲区

来开辟显存,存放需要用来显示的内容,通过不断的刷新其缓存数据,来达到一个流畅的动画效果。

因为手上只有一块stm32f4的单片机和屏幕,性能相对较高,所以萌生了试一试的想法。

那么值得考虑一个问题,选择的屏幕是否可以达到这样的刷新率?

测试刷新率

所以直接搬了vlad7235stm32f103_oled_gcc4.9,用于stm32f103 + ssd1306 OLED屏幕的测试程序,它显示了旋转的线框立方体。

因为直接使用正点原子显示不同的颜色的时候刷屏和刷新位图的速度很慢,本以为刷屏会达不到预期效果。结果使用

1
void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2);

的时候发现绘制的效果却如丝般顺滑。

tip: GRAM的特性,因为每次只写入了一条直线,,后写入的直线不会直接覆盖前面的图形,屏幕出现黑黑的一坨,所以需要对缓冲区进行清空LCD_Clear(WHITE);

源码说明

在common.h中间的头文件说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//常用代码库
#include <stdlib.h> 
#include <stdio.h>
#include <string.h>
#include <limits.h>

#include "config.h"         //一些预编译条件
#include "util.h"           //我寻思着可能是Arduino一些常用定义
#include "typedefs.h"       //本程序的数据结构都在里面
#include "debug.h"          //方便调试实现的宏定义

#include "m_rtc.h"          //RTC时钟
#include "buttons.h"        //按钮功能
#include "millis.h"         //开启定时器获取时间
#include "functions.h"      //函数功能
#include "alarms.h"         //闹铃菜单
#include "diag.h"           //版本信息
#include "m_display.h"      //显示设置
#include "games.h"          //游戏选取界面
#include "timedate.h"       //时间日期设置
#include "settings.h"       //设置界面
#include "sleep.h"          //睡眠时间设定
#include "sound.h"          //音量设置
#include "m_main.h"         //主菜单界面
#include "game1.h"
#include "game2.h"
#include "game3.h"
#include "stopwatch.h"       //秒表
#include "torch.h"
//#include "screenshot.h"    //输出oledbuff里面的数据
#include "normal.h"          //时间显示界面
#include "ui.h"              //电量显示
#include "tunemaker.h"       //旋转矩阵

#include "global.h"          //应该是usb检测和引脚初始化
#include "display.h"         //显示函数
#include "time.h"            //时间计算
#include "alarm.h"           //闹铃设置
#include "appconfig.h"       //flash模拟eeprom,写入appconfig数据
#include "tune.h"            //播放音调(这里没有实现)
#include "animation.h"       //动画
#include "draw.h"            //通过draw_bitmap等函数绘制在oledBuffer里
#include "menu.h"            //菜单函数库

#include "english.h"         //字体库和字符宏定义
#include "lang.h"            //语言选择
#include "tunes.h"           //乐谱
#include "resources.h"       //位图

Draw

定位到c_loop()可以看到在循环体中大概的运行逻辑,在display_update()中间实现了各个部件的绘制过程

1
2
if(drawFunc != NULL)
	busy = drawFunc();

通过按键void buttons_setFuncs(button_f btn1, button_f btn2, button_f btn3)设置按键对对应的函数功能,

通过buttons_update()检测按键是否按下,并且button->onPress()执行buttons_setFuncs设置的函数功能,

接着又扯到drawFunc(),这个函数指针drawFunc()无法定位,且制定执行了哪一个函数并不知道,那么就挑一个软柿子捏,void c_setup()中间包含了以下两串代码:

1
2
3
// Set watchface
display_set(watchface_normal);
display_load();

用于设置显示时间显示界面的函数。

找到在void watchface_normal()->display_setDrawFunc(draw)

用于设置drawFunc函数指针的指针指向static display_t draw()这个函数。

继续向下扒,会发现所有这类型的static display_t draw()函数都会采用引用void draw_bitmap(byte x, byte yy, const byte* bitmap, byte w, byte h, bool invert, byte offsetY)这个超快速位图绘制函数。

在将图像绘制在oledBuffer缓冲区后,只需要引用

1
2
// End drawing, send to OLED
	draw_end();

就可以将oledBuffer发送至显示屏,因为我使用的是TFT屏幕,所以我只能模拟oled的刷新过程,128x64个像素点是以一个八位数据表示一小列的

0xff (0) 0xff(1) 0xff(2) 0xff(125) 0xff(126) 0xff(127)
0xff(128) 0xff(129) 0xff(130) 0xff(…) 0xff(…) 0xff(…)
0xff(256) 0xff(257) 0xff(258) 0xff(…) 0xff(…) 0xff(…)
0xff(384) 0xff(385) 0xff(386) 0xff(…) 0xff(…) 0xff(…)
0xff(512) 0xff(513) 0xff(514) 0xff(…) 0xff(…) 0xff(…)
0xff(640) 0xff(641) 0xff(642) 0xff(…) 0xff(…) 0xff(…)
0xff(768) 0xff(769) 0xff(770) 0xff(…) 0xff(…) 0xff(…)
0xff(896) 0xff(897) 0xff(898) 0xff(…) 0xff(1022) 0xff(1023)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void oled_flush(void)
{

	u16 i,j;
	for(i=0;i<FRAME_BUFFER_SIZE;i++)     //FRAME_BUFFER_SIZE=128x64/8=1024
	{
	  byte next = oledBuffer[i]; // Load next byte
    for(j=0;j<8;j++)
    {
			if(display_dir)
			{
		    if(next&0x01<<(j%8))
		    LCD_Fast_DrawPoint(i%128,j%8+8*(i/128),POINT_COLOR);
		    else
		   LCD_Fast_DrawPoint(i%128,j%8+8*(i/128),BACK_COLOR);
			}
			else
			{
				if(next&0x01<<(j%8))
		    LCD_Fast_DrawPoint(j%8+8*(i/128),127-i%128,POINT_COLOR);
	    	else
		    LCD_Fast_DrawPoint(j%8+8*(i/128),127-i%128,BACK_COLOR);
			}

		}			
	
	}

}

oledBuffer[FRAME_BUFFER_SIZE]为8行,128列:

第i个oledBuffer[i]的第j位值对应的x坐标:i%128

第i个oledBuffer[i]的第j位值对应的y坐标:j%8+8*(i/128)

通过void LCD_Fast_DrawPoint(u16 x,u16 y,u16 color)就可以将点绘制在TFT的GRAM中间,这里的GRAM不需要每次清空,因为只需要将oledBuffer每次写入后清空,就可以使下一次写入GRAM的值覆盖原有的值。

在源码中先去除了appConfig的条件判断,自己瞎写写条件让程序跑起来。