利用液晶屏显示一个电子时钟是一个初学LCD时不错的练习项目。
我的这个LCD时钟小项目是基于S3C2440A的开发板的,如果想要全部的代码,可以去下载同名的资源。由于篇幅有限,以下只介绍核心代码。
好了开始之前先简单介绍下我的LCD显示原理。
我所采用的LCD显示原理是将显存作为一个二维数组,将该数组的起始地址告诉控制LCD的寄存器,数组中的每一个元素都是一个16BPP的像素。
首先,我们要有一个画点函数如下:
/**************************************************************************
***** 函数名: PutPixel(void)
***** 功能: 绘制像素点
***** 参数: x:横坐标 y:纵坐标 c颜色
***** 返回值: 无
***** 创建者:
***** 创建时间: 2011-03-30
***** 最后更新:2011-03-30
****************************************************************************/
void PutPixel(U32 x,U32 y, U32 c )
{
LCD_BUFFER[y][x] = c;
}
然后我们还需要一个画圆的函数
在此有两种推荐:第一种速度快,第二种算法比较好理解。个人推测是因为第二种算法引入了浮点运算且判断条件较多的原因,即便加入了内存管理即MMU,速度也不是很理想,如果画的点多的话,人眼还是能够看到画圆的过程。
第一种:
/**************************************************************************
***** 函数名: MidpointCircle(int x0,int y0,int r,int color)***** 功能:画个圆
***** 参数: 圆心x,y坐标,半径,颜色
***** 返回值: 无
***** 创建者:
***** 创建时间: 2012-12-23
***** 最后更新: 2012-12-23
****************************************************************************/
void MidpointCircle(int x0,int y0,int r,int color)
{
int x,y;
float d;
x=0;
y=r;
d=5.0/4-r;
while(x<=y)
{
PutPixel(x0+x,y0+y,color);
PutPixel(x0+x,y0-y,color);
PutPixel(x0-x,y0+y,color);
PutPixel(x0-x,y0-y,color);
PutPixel(x0+y,y0+x,color);
PutPixel(x0+y,y0-x,color);
PutPixel(x0-y,y0+x,color);
PutPixel(x0-y,y0-x,color);
if(d<0)
{
d+=x*2.0+3;
}
else
{
d+=2.0*(x-y)+5;
y--;
}
x++;
}
}
第二种:
/**************************************************************************
***** 函数名: draw_circle(unsigned int x1, unsigned int y1,
unsigned int radius,unsigned int color)
***** 功能: 画一个圆
***** 参数: 已知点坐标x1,y1;半径radius,颜色color;
***** 返回值: 无
***** 创建者: 潘星宇
***** 创建时间:2012-12-30
***** 最后更新: 2012-12-30
****************************************************************************/
void draw_circle(unsigned int x1, //圆心横坐标
unsigned int y1, //圆心纵坐标
unsigned int radius, //半径
unsigned int color) //颜色
{
unsigned int i;
for (i=0; i <= 359; i++)
{
one_point_to_another (x1, y1, radius, i, color, 0, 1);
}
}
第二种大家一定会好奇里面的那个函数是什么,这就是我要告诉大家的电子钟的最关键的一个函数:
/**************************************************************************
***** 函数名: one_point_to_another(unsigned int x1, unsigned int y1,
unsigned int length, double angle,
unsigned int color, unsigned int length_start,
unsigned int length_end)
***** 功能: 由一点,长度和角度做线
***** 参数: 已知点坐标x1,y1;长度length,角度angle,颜色color;起始点和结束点;
角度按照钟表原则,0点为0°,顺时针旋转,
函数内部将角度按照象限的原则转化,第一象限为0°的开始。
length_start_points:希望保留起始的点的个数
length_end_points:希望保留最后的点的个数
***** 返回值: 无
***** 创建者: 潘星宇
***** 创建时间:2012-12-25
***** 最后更新: 2012-12-28
****************************************************************************/
void one_point_to_another(unsigned int x1, //已知点横坐标x1
unsigned int y1, //已知点纵坐标y1
unsigned int length, //两点间距长度
float angle, //两点角度,按照钟表原则,0点为0°,顺时针旋转
unsigned int color, //颜色
unsigned int length_start_points, //希望保留起始的点的个数
unsigned int length_end_points) //希望保留最后的点的个数
{
unsigned int x2; //画线用横坐标寄存器
unsigned int y2; //画线用纵坐标寄存器
unsigned int length_i; //画线计数用
unsigned int length_end_i = 0; //画线取最后几个点计数用
float radian; //弧度
if ((angle >= 0) && (angle <= 90))
{
angle = 90 - angle;
}
else if ((angle >= 91) && (angle <= 180))
{
angle = 360 - (angle - 90);
}
else if ((angle >= 181) && (angle <=270))
{
angle = 270 - (angle - 180);
}
else if ((angle >= 271) && (angle <=359))
{
angle = 180 - (angle - 270);
}
radian = (angle / 180)*PIN; //角度与弧度的转换
if (length_start_points >= length) //如果希望保留前面的点超出了线的长度
{
length_start_points = length;
}
if (length_end_points >= length) //如果希望保留后面的点超出了线的长度
{
length_end_points = length;
}
for (length_i = 0; length_i <= length; length_i++)
{
if ((length_start_points > 0) || (length_end_i >= (length - length_end_points))) //希望保留起始点的个数
{
x2 = (unsigned int)(x1 + length_i*cos(radian) + 0.5); //加0.5为了防止大于5的小数被舍去
y2 = (unsigned int)(y1 - length_i*sin(radian) + 0.5); //由于液晶屏的坐标Y轴向下坐标大,与象限刚好相反,故在此是减去
PutPixel(x2,y2,color);
if (length_start_points > 0) //防止自减运算溢出,变成最大值
{
length_start_points--;
}
}
length_end_i++;
}
}
在此补充两点:
(1)大家应该知道数学上的角度,是以第一象限X轴正半轴为起点,逆时针旋转360度,而时钟则是以Y轴正半轴为起点顺时针旋转的。
(2)PIN就是圆周率3.1415923那个π。
请原谅我比较懒,写这个程序的屏和我现在的屏有点不大一样,懒得设置了,贴张图给大家看看。
先把主函数贴出来,如果想要全部的代码,请去下载我的同名资源。
/**************************************************************************
***** 函数名: main()
***** 功能: 电子钟
***** 参数: 无
***** 返回值:
***** 创建者: 潘星宇
***** 创建时间: 2012-12-26
***** 最后更新:2012-12-26
****************************************************************************/
int main(void)
{
static U8 str[30] = {0}; //串口寄存数组
U8 ch; //串口用变量
static U8 UART_i = 0; //串口数组计数用
unsigned char hour_i; //画小时刻度计数用的变量
unsigned char minute_i; //画分钟刻度计数用的变量
U32 hour; //从RTC读取的小时
U32 minute; //分钟
U32 second; //秒
U32 second_ch; //秒控制变量,用于控制每秒才显示一次
static U32 hour_register; //小时寄存器,用于储存上一次画时针的位置,用于清屏
static U32 minute_register; //分钟寄存器
static U32 second_register; //秒寄存器
LCD_Init(); //LCD初始化
rGPBDAT = 0x5e0; //关LED
rGPBCON =0x00115401; //设置GPB5、GPB6、GPB8、GPB10为输出
rGPBUP =0x7ff; //禁止上拉
cal_cpu_bus_clk(); //获取时针频率
Uart_Init( 0,115200 ); //初始化串口
Brush_Background(0xc7e7); //绘制背景
RTC_time_set (0x11, 0x12, 0x26, 0x03, 0x14, 0x55, 0x35);
draw_circle(180,136,123,0xd8a7); //表外框
/**********************************************
*已知起始点、两点距离和角度画线的参数分别为:*
*第一点坐标x1,y1; *
*长度; *
*角度(不是弧度); *
*颜色; *
*希望保留起始的点的个数; *
*希望保留最后的点的个数 *
**********************************************/
//画小时刻度
for (hour_i=0; hour_i<12; hour_i++)
{
one_point_to_another(180, 136, 120, hour_i*30, 0x5922, 0, 15); //从0点画到11点
}
//画分钟刻度
for (minute_i=0; minute_i<60; minute_i++)
{
if (minute_i % 5) //整点处不画刻度
{
one_point_to_another(180, 136, 120, minute_i*6, 0x5922, 0, 1 ); //从1分画到59分
}
}
while (1)
{
hour = rBCDHOUR; //获取小时
minute = rBCDMIN; //获取分钟
second = rBCDSEC; //获取秒
hour = ((hour / 16)*10 + hour % 16) % 12;//由于从RTC读出的都是16进制的,先转成10进制,再将24小时格式转化为12小时格式
minute = (minute / 16)*10 + minute % 16;
second = (second / 16)*10 + second % 16;
if (second != second_ch)
{
//先清除掉上次画的时针,分针,秒针,长度暂分别定为35,60,90
if ((hour_register*30 + (minute_register / 10)*5) != (hour*30 + (minute / 10)*5)) //为了消除分针和时针闪屏的问
{ //只有当分针或时针确实移动了位置才消除上一次画的的值
one_point_to_another(180, 136, 120, hour_register*30 + (minute_register / 10)*5, 0xc7e7, 35, 0);
}
if (minute_register != minute)
{
one_point_to_another(180, 136, 120, minute_register*6, 0xc7e7, 60, 0);
}
one_point_to_another(180, 136, 120, second_register*6, 0xc7e7, 90, 0);
second_ch = second; //使秒控制变量等于当前时钟的秒值,这样直到秒变化,才再次进入这个内部来
one_point_to_another(180, 136, 120, hour*30 + (minute /10)*5, 0xfb00, 35, 0); //时针每小时走30°,分钟动10分钟,时钟动5°
one_point_to_another(180, 136, 120, minute*6, 0x435c, 60, 0);
one_point_to_another(180, 136, 120, second*6, 0xf800, 90, 0);
rtc_display_lcd(320,150,0xb1dd,0x08a8); //显示电子钟,参数分别为横坐标、纵坐标、颜色、背景色
hour_register = hour; //将时分秒的值储存起来,用于下一秒清除上一秒的变化
minute_register = minute;
second_register = second;
}
while (rUTRSTAT0 & 0x1) //当串口接收缓冲器有数据时
{
ch = rURXH0; //接收数据
str[UART_i++] = ch;
if((ch == 0xff)||(UART_i == 29 )) //接收数据完成
{
RTC_time_set (str[0], str[1], str[2], str[3], str[4], str[5], str[6]);
UART_i = 0;
}
}
}
}
这个还带串口修改RTC时间的功能
图如下