应客户需求,制作一款可以在显示屏上显示出沙漏的剩余时间,而且可以选择自定义时间精准一分一秒。
一、显示模块
采用0.96寸四针OLED显示屏IIC接口。
OLED的数据存储格式:
纵向8点,高位在下,先从左到右,再从上到下,每一个Bit对应一个像素点
* B0 B0 B0 B0
* B1 B1 B1 B1
* B2 B2 B2 B2
* B3 B3 ----------> B3 B3
* B4 B4 B4 B4 |
* B5 B5 B5 B5 |
* B6 B6 B6 B6 |
* B7 B7 B7 B7 |
* |
* -----------------------------------
* |
* | B0 B0 B0 B0
* | B1 B1 B1 B1
* | B2 B2 B2 B2
* --> B3 B3 ---------> B3 B3
* B4 B4 B4 B4
* B5 B5 B5 B5
* B6 B6 B6 B6
* B7 B7 B7 B7
OLED三角形绘制函数,调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
void OLED_DrawTriangle(uint8_t X0, uint8_t Y0, uint8_t X1, uint8_t Y1, uint8_t X2, uint8_t Y2, uint8_t IsFilled)
{
uint8_t minx = X0, miny = Y0, maxx = X0, maxy = Y0;
uint8_t i, j;
int16_t vx[] = {X0, X1, X2};
int16_t vy[] = {Y0, Y1, Y2};
if (!IsFilled) //指定三角形不填充
{
/*调用画线函数,将三个点用直线连接*/
OLED_DrawLine(X0, Y0, X1, Y1);
OLED_DrawLine(X0, Y0, X2, Y2);
OLED_DrawLine(X1, Y1, X2, Y2);
}
else //指定三角形填充
{
/*找到三个点最小的X、Y坐标*/
if (X1 < minx) {minx = X1;}
if (X2 < minx) {minx = X2;}
if (Y1 < miny) {miny = Y1;}
if (Y2 < miny) {miny = Y2;}
/*找到三个点最大的X、Y坐标*/
if (X1 > maxx) {maxx = X1;}
if (X2 > maxx) {maxx = X2;}
if (Y1 > maxy) {maxy = Y1;}
if (Y2 > maxy) {maxy = Y2;}
/*最小最大坐标之间的矩形为可能需要填充的区域*/
/*遍历此区域中所有的点*/
/*遍历X坐标*/
for (i = minx; i <= maxx; i ++)
{
/*遍历Y坐标*/
for (j = miny; j <= maxy; j ++)
{
/*调用OLED_pnpoly,判断指定点是否在指定三角形之中*/
/*如果在,则画点,如果不在,则不做处理*/
if (OLED_pnpoly(3, vx, vy, i, j)) {OLED_DrawPoint(i, j);}
}
}
}
}
二、整体思路
时间设定思路:利用串口将所需要设定的时间发送给单片机,然后在对时间数据进行沙漏动画处理。对于串口发送过来的设定时间以30秒为基础进行取整和取余,得到30秒动画的循环次数和最后一步动画的剩余时间数。
if (Serial_GetRxFlag() == 1) //如果接收到数据包
{
Data_Flag = 1;
Beg_Flag = 1;
Serial_TxPacket[0] = Serial_RxPacket[0]; //测试数据是否接收到
Serial_TxPacket[1] = Serial_RxPacket[1];
Serial_TxPacket[2] = Serial_RxPacket[2];
Serial_TxPacket[3] = Serial_RxPacket[3];
Serial_SendPacket();
}
if(Data_Flag == 1)
{
/*****设置各个参数的初始值*****/
ALL_TIME = ((Serial_TxPacket[0]*60)+Serial_TxPacket[1]);//设定时间
y1=0;
y2=0;
y3=63;
Number=0; //当前循环数
n = ALL_TIME / 30; //总循环数
s = ALL_TIME % 30; //剩余时间
Data_Flag = 0;
}
if(Beg_Flag == 1)
{
/*****沙漏开始工作*****/
OLED_Clear();
Min = ALL_TIME / 60;//显示分
Sec = ALL_TIME % 60;//显示秒
if((y1==30)&&(Number != n))//30秒动画完成一次
{
y1=0;
Number++;
}
if(Number == n)//当前循环数=总循环数
{
y2 = 30 - s;
Number++;
}
if((y2==30)&&(Number != n))//最后一组动画完成
{
Number++;
}
动画设定思路:首先定义坐标轴,左上角为(0, 0)点,横向向右为X轴,取值范围:0~127;纵向向下为Y轴,取值范围:0~63;
以线段(63,0)、(63,63)为分界线,左侧为剩余时间显示,右侧为沙漏动画显示。
画出沙漏的框架:
OLED_DrawLine(63, 0, 127, 63); //在(63, 0)和(127, 63)位置之间画直线
OLED_DrawLine(63, 63, 127, 0);
OLED_DrawLine(63, 0, 127, 0); //在(63, 63)和(127, 0)位置之间画直线
OLED_DrawLine(63, 63, 127, 63);
沙漏动画思路:设定一个沙漏漏完为30秒,对于沙漏的动画处理,以30秒为循环进行显示,最后不满30秒的,对沙漏上方进行实际像素点的填充,再进行下漏操作。
30秒一组的循环动画:
制作上面的沙子越来越少的动画,简单来说就是上面的那个实心三角形越来越小,Y1的值越来越大,三个顶点中,有一个是不变的(95,31),上面部分的三角形为等腰直角三角形,我们根据三角函数可以得到另外两个顶点的坐标(63+Y1,Y1),(127-Y1,Y1)。
制作下面沙子越来越多的程序,下面的沙子越来越多,Y1的值越来越大,相对就比较简单,有两个点永远不变,那就是 (63,63)和(127,63),变的只是最上面那个顶点,而且上面那个顶点的X坐标一直没变,就只是Y坐标一直在变(95,63-Y1)。
最后在加入中间沙子流动的动画,实际就是一根线.
代码部分:
if( (Number <= n)&&(Number != n) )
{
OLED_DrawLine(63, 0, 127, 63); //在(63, 0)和(127, 63)位置之间画直线
OLED_DrawLine(63, 63, 127, 0);
OLED_DrawLine(63, 0, 127, 0); //在(63, 63)和(127, 0)位置之间画直线
OLED_DrawLine(63, 63, 127, 63);
OLED_DrawTriangle(63+y1, y1, 95, 31, 127-y1, y1, OLED_FILLED);//上层
OLED_DrawTriangle(63, 63, 95, 63-y1, 127, 63, OLED_FILLED);//下层
OLED_DrawLine(95, 31, 95, 63); //在(95, 31)和(95, 63)位置之间画直线
}
不满30秒的动画:
在时间设定的时候,每次设定的时间不一定都是30的整数倍,所以最后一组动画时需要特殊处理。 即上半部分沙漏的Y2需要有一个初始值,Y2=30-剩余时间数,使得Y2++到30时,正好对应到剩余时间数倒数完成。下半部分沙漏的沙子增加动画,使用变量Y3,初值为63,Y3--,当Y3=Y2时,即表示上半部分沙子漏完,下半部分沙子全部接收完成,动画结束。
代码部分:
if(Number == n+1)
{
OLED_DrawLine(63, 0, 127, 63); //在(63, 0)和(127, 63)位置之间画直线
OLED_DrawLine(63, 63, 127, 0);
OLED_DrawLine(63, 0, 127, 0); //在(63, 63)和(127, 0)位置之间画直线
OLED_DrawLine(63, 63, 127, 63);
OLED_DrawTriangle(63+y2, y2, 95, 31, 127-y2, y2, OLED_FILLED);//上层
OLED_DrawTriangle(63, 63, 95, y3, 127, 63, OLED_FILLED);//下层
OLED_DrawLine(95, 31, 95, 63); //在(95, 31)和(95, 63)位置之间画直线
}
定时器函数 :实现沙漏精确的时间计数,采用中断定时器的方法每秒对变量实行加/减操作。
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)//1秒钟进入一次中断
{
if(Beg_Flag == 1)//动画开始
{
if( ALL_TIME >=0 )//得到设定时间,开始倒数
{
ALL_TIME --;
}
if(y1<=30)//30秒循环组动画,y1每秒+1
{
y1++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
if((y2<=30)&&(Number == n+1)&&(y2!=y3))//不满30秒的动画
{
y2++;
y3--;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
}
}
三、实物效果
电子沙漏动画
该项目主要需要去了解OLED屏是怎么进行数据存储的,IIC通信原理,较为简单的一个小项目,硬件较少,代码逻辑一般,适合新手练手。