嵌入式 LED 万年历

嵌入式作业

20130080476 孙飞

软件工程 1班

2016-1-7


目录

一作业介绍:... 1

1.1主控单元和按键部分设计... 3

1.2部分引脚设计... 3

二项目实现:... 4

2.1 主函数... 4

2.2 函数RTC_SetTIMEConfig. 7

2.2.1定义属性... 7

2.2.2函数... 8

2.3 RCC_Configuration()10

2.4万年历数据的处理和显示函数... 13

2.4.1函数原型:wanchuliday();13

2.4.2 函数原型:wanxians();14

2.4.3 在LCD12864显示当前时间值... 15

2.4.4显示当前时间值... 16

2.5配置行列式键盘串口引脚... 18

2.5.1 GPIO_Config_key(void)18

2.5.2 keyscan()19

2.6 delay延时模块化程序设计... 21

2.6.1  delay(uint32_t time,uint8_t tm)22

三 项目截图及连线:... 26

 

 

一作业介绍:

作业要求:

  通过一学期的学习,总结设计出万年历,能在屏幕上显示万年历并进行设置。实现时必须使用到考试用到的中断和定时器。

概要:

本作业使用的12864,通过中断实现设置时间,定时器定时进行更新时间,组成一个万年历,在主板显示屏上显示出来万年历,当前时间。

 

采用12864作为主控芯片,利用它定期的读取时钟芯片中的时间并显示在LCD上;通过算法得出阴历日期并显示在LCD。

 

定时单元:使用TIM3定时器,向上技术模式。计数5000次即500MS.通过一个全局变量作为分频后得到一秒的定时中断。

 

显示单元:采用LCD12864点阵显示,操作简单,成本低廉。这个实例采用了串口的方式实现显示,接口定义为:PB7—RST///PSB—PA4通过程序中的KEY.C文件中修改,更改接口后记得打开时钟。

 

选择LCD1602液晶显示模块。LCD1602是字符点阵系列液晶模块。它是一类专门用于显示字母、数字、符号等的点阵型液晶显示模块,

分为四位和八位数据传输方式,提供5*7点阵+光标和5*10点阵+

光标的显示模式。

 

对于程序的解释我在第二部分的代码都做了重要的注释,看起来应该比较简单。

 

项目的截图以及连线的部分在第三部分。

由于在平时实验以及最后考试中我们全都用过中断和定时器,就不详细介绍,全部通过代码和注释就能明白。

1.1主控单元和按键部分设计

1.2部分引脚设计

输入/输出引脚 P0.0~ P0.7、P1.0~P1.7、P2.0~ P2.7 和P3.0~P3.7     

①  P0端口(P0.0~ P0.7) P0是一个8位漏极开路型双向I/O端口。作为输出口用时,每位能以吸收电流的方式驱动8个TTL输入,对端口写1时,又可作高阻抗输入端用。 

②P1端口(P1.0~ P1.7) P1是一个带有内部上拉电阻的8位双向I/O端口。P1的输出缓冲器可驱动(吸收或输出电流方式)4个TTL输入。对端口写1时,通过内部的上拉电阻把端口拉到高电位,这时可用作输入口。作输入口时,因为有内部的上拉电阻,那些被外部信号拉低的引脚会输出一个电流。 

 ③P2端口 (P2.0~P2.7) P2是一个带有内部上拉电阻的8位双向I/O端口。P2的输出缓冲器可驱动(吸收或输出电流方式)4个TTL输入。对端口写1时,通过内部的上拉电阻把端口拉到高电位,这时可用作输入口。P2作输入口使用时,因为有内部的上拉电阻,那些被外部信号拉低的引脚会输出一个电流。 ④P3端口(P3.0~P3.7)  P3口管脚是8个带内部上拉电阻的双向I/O口,可接收输出4个TTL门电流。当P3口写入“1”后,它们被内部上拉为高电平,并用作输入。作为输入,由于外部下拉为低电平,P3口将输出电流

 

二项目实现:

2.1 主函数

/***********************************************************头文件名:main.c

201300800476 孙飞                                               

使用说明:

将LCD12864的PSB与GND短接,RE--PB.12,RW--PB.13,E--PB.15;

   将行列式键盘S1-S8依次与PD.00-PD.07连接好(S1-S4控制行,S5-S8控制列)

***********************************************************/

 

#include "stm32f10x.h"

#include "delay.h"

#include "rcc.h"

#include "stdio.h"

 

 

/*时间结构体*/

struct rtc_time systmtime;

/**

 * @brief  主函数

 * @param  无 

 * @retval 无

 */

 

/*主函数*/

int main(void)

{

   lcd12864_init();/*初始化lcd,同中断中的初始化一样,不做详细解释。*/

 

     delay(50,ms);//延时50毫秒

  //显示内容

     lcd12864_display(1,2,"--万年历系统--");

 lcd12864_display(2,1,"启动中......");

 lcd12864_display(3,1,"请稍等");

     delay(1000,ms);

    

     GPIO_Config_key();/*GPIO的配置,有单独的接口设计,I/O端口置输入输出时有不同的输出、检测等。*/

    

 NVIC_Configuration();//NVIC配置

    

    

 

RTC_CheckAndConfig(&systmtime);  /*初始化检查,如果按了复位键或彻底掉电情况,进入设置以及设置完时间后,

进行检查更改错误、实现更新.*/

     //主循环

     while(1)

     {

       send_command(0x01);       //清屏

         delay(50,ms);//延迟

         Time_Show(&systmtime);//时间进实现农历转化,最终显示出来

        

         send_command(0x01);     //清屏

         delay(50,ms);

RTC_SetTIMEConfig(&systmtime);/* 配置RTC输入,用于读取RTC时间的结构体指针*/

     }

}

2.2 函数RTC_SetTIMEConfig

* 函数名:RTC_SetTIMEConfig

* 描述  :配置RTC

* 输入  :用于读取RTC时间的结构体指针

* 输出  :无

* 调用  :外部调用

2.2.1定义属性

在函数实现之前先设置变量,使得设置的初始值得到初始化。

static uint32_t FirstDisplay = 1;

uint8_tyejingtable[]={0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x20};

uint8_t wandisplay[12];

uint8_t keyaa=0;

uint8_t keyz=0;

uint8_t *WEEK_STR[]    = {"日", "一", "二", "三", "四", "五", "六"};

uint8_t *zodiac_sign[] = {"猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗"};

2.2.2函数

void RTC_SetTIMEConfig(struct rtc_time*tm)

{

         

         /*RTC 初始化*/

         RCC_Configuration();

        

         /*重新配置时间*/

         /*通过使用超级终端用户调整时间*/

         Time_Adjust(tm);

 

         BKP_WriteBackupRegister(BKP_DR1,0xA5A5);

 

 

        /*定义了时钟输出宏,则配置校正时钟输出到PC13*/

        #ifdef RTCClockOutput_Enable

       /*使压水堆和BKP时钟*/

         RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR| RCC_APB1Periph_BKP, ENABLE);

    

       /*允许访问BKP域*/

       PWR_BackupAccessCmd(ENABLE);

    

       /*禁用篡改引脚*/

BKP_TamperPinCmd(DISABLE);/*输出RTCCLK / 64引脚的tamperfunctionality篡改,必须禁用*/

    

       /*时钟使能输出篡改销*/

       BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock);

     #endif

    

       /*清除重置标志*/

       RCC_ClearFlag();

 

}

2.3 RCC_Configuration()

* 函数原型:RCC_Configuration();

* 功能:RTC实时时钟的配置

 

void RCC_Configuration(void)

{

     /*使压水堆和BKP时钟*/  

//使能APB1外设PWR and BKP 的时钟

   RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR| RCC_APB1Periph_BKP,ENABLE);

    

     /*允许访问BKP域*/

 //允许RTC和后备寄存器的访问

     PWR_BackupAccessCmd(ENABLE); 

//PWR_CR的DBP位= 1

    

     /*复位备份域*/      

//将外设BKP的全部 寄存器重设为缺省值

     BKP_DeInit();    //将外设 BKP 的全部寄存器重设为缺省值

    

     /*使LSE有效 */                 //设置外部低速时钟

 RCC_LSEConfig(RCC_LSE_ON);                                 //打开外部低速晶体振荡器

    

     /*等待LSE准备好 */     

//等待外部低速时钟准备好

while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)== RESET);        //等待起振

    

     /*选择LSE作为时钟源*/  

//设置LSE为RTC时钟

   RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);               //设置LSE为RTC时钟源

    

     /*使RTC Clock有效 */                 

//使能RT时钟

     RCC_RTCCLKCmd(ENABLE);

 

 

     /*等待RTC寄存器同步*/

/*等待RTC寄存器(RTC_CNT,RTC_ALR and RTC_PRL)与RTC同步*/

     RTC_WaitForSynchro();

 

     //等待最近一次对RTC的写操作完成

   RTC_WaitForLastTask();                                            //等待上一次对RTC的写操作完成

 

   //使能RTC的秒中断

  RTC_ITConfig(RTC_IT_SEC,ENABLE);

  RTC_ITConfig(RTC_IT_ALR,ENABLE);

 

//等待最近一次对RTC的写操作完成

      RTC_WaitForLastTask();

 

   //设置RTC预分频的值 (32.768khz/(32767+1))=1hz

  RTC_SetPrescaler(32767);

 

//等待最近一次对RTC的写操作完成

      RTC_WaitForLastTask(); 

}

 

 

 

2.4万年历数据的处理和显示函数

因为输入数据并非ASCII码,而12864显示要求为ASCII码,所以需要进行转换。

2.4.1函数原型:wanchuliday();

功能: 万年历的数据处理函数,实现数据的同步。

 void wanchuli(uint32_t year,uint32_tmoon,uint32_t day,uint32_t hour,uint32_t min,uint32_t sec)

       year = year%100;

       wandisplay[0]=year/10;   //年显示数据处理

         wandisplay[1]=year%10;

    

       wandisplay[2]=moon/10;  //月显示数据处理

         wandisplay[3]=moon%10;

    

       wandisplay[4]=day/10;   //天显示数据处理

         wandisplay[5]=day%10;

    

         wandisplay[6]=hour/10;//时显示数据处理

         wandisplay[7]=hour%10;

 

         wandisplay[8]=min/10;//分显示数据处理

         wandisplay[9]=min%10;

 

         wandisplay[10]=sec/10;//秒显示数据处理/

         wandisplay[11]=sec%10;

 

 

}

 

2.4.2 函数原型:wanxians();

功能: 万年历的数据显示函数,支持12864对数据显示的支持。

void wanxians(void)

{

         send_command(0x91);

         send_data(yejingtable[wandisplay[0]]);//年(高位)

         send_data(yejingtable[wandisplay[1]]);//年(低位)

         send_command(0x94);

         send_data(yejingtable[wandisplay[2]]);//月

         send_data(yejingtable[wandisplay[3]]);//月

         send_command(0x96);

         send_data(yejingtable[wandisplay[4]]);//日

         send_data(yejingtable[wandisplay[5]]);//日

         send_command(0x8b);

         send_data(yejingtable[wandisplay[6]]);

         send_data(yejingtable[wandisplay[7]]);

         send_command(0x8d);

         send_data(yejingtable[wandisplay[8]]);

         send_data(yejingtable[wandisplay[9]]);

         send_command(0x8f);

         send_data(yejingtable[wandisplay[10]]);

         send_data(yejingtable[wandisplay[11]]);

}

2.4.3 在LCD12864显示当前时间值

void Time_Show(struct rtc_time *tm)

{   

       u8 i;

       u8 a=1;

       /* 设置循环*/

       while (a)

       {

         /* 每过1s*/

         if (TimeDisplay == 1)

         {

       Time_Display(RTC_GetCounter(),tm);//时间实现农历转化,最终显示出来

           TimeDisplay = 0;

         }

         i=keyscan();

         if(i==0x84)

              {

                   a=0;

                   FirstDisplay= 1;

              }

         }

}//函数结束

2.4.4显示当前时间值

被调用函数

void Time_Display(uint32_tTimeVar,struct rtc_time *tm)

{

        uint32_t BJ_TimeVar;

        uint8_t str[15]; // 字符串暂存

      uint8_t str0[4];

 

        /*  把标准时间转换为北京时间*/

        BJ_TimeVar =TimeVar + 8*60*60;

 

        to_tm(BJ_TimeVar, tm);/*把定时器的值转换为北京时间*/  

  if((!tm->tm_hour && !tm->tm_min&& !tm->tm_sec)  ||(FirstDisplay))

       {

  GetChinaCalendar((u16)tm->tm_year,(u8)tm->tm_mon, (u8)tm->tm_mday, str);   

               

           GetChinaCalendarStr((u16)tm->tm_year,(u8)tm->tm_mon ,(u8)tm->tm_mday,str);

 

      lcd12864_display(1,1,str);

      if(GetJieQiStr((u16)tm->tm_year,(u8)tm->tm_mon, (u8)tm->tm_mday, str0))

      lcd12864_display(3,1,str0);

    

      FirstDisplay = 0;

       }       

 

wanchuli(tm->tm_year,tm->tm_mon,tm->tm_mday,tm->t               m_hour,tm->tm_min,tm->tm_sec);

      wanxians();

      lcd12864_display(2,1,"20");   lcd12864_display(2,3,zodiac_sign[(tm->tm_year-3)%12]);//显示年的属相

                   lcd12864_display(2,4,"年");

                   lcd12864_display(2,6,"月");

                   lcd12864_display(2,8,"日");

                   lcd12864_display(3,5,":");

                   lcd12864_display(3,7,":");

                   lcd12864_display(4,1,"星期");

            lcd12864_display(4,3,WEEK_STR[tm->tm_wday]);

             lcd12864_display(4,5,"B:设定");

}

 

2.5配置行列式键盘串口引脚

2.5.1 GPIO_Config_key(void)

 

void GPIO_Config_key(void)

{

 GPIO_InitTypeDef GPIO_InitStruct;//定义一个结构体

 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);//时钟使能

    

 GPIO_InitStruct.GPIO_Pin=0x0F;//引脚选择,选择D0-D3设置为行线

 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//输出速率

 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//输出

 GPIO_Init(GPIOC,&GPIO_InitStruct); //初始化

    

 GPIO_InitStruct.GPIO_Pin=0xF0;//引脚选择,选择D4-D7设置为列线

 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;//输入

 GPIO_Init(GPIOC,&GPIO_InitStruct); //初始化   

}

2.5.2 keyscan()

功能:键盘扫描函数,函数返回值即键值。

uint8_t keyscan(void)

{

     uint8_tLIE,HANG,k,i=0;

     GPIO_Write(GPIOC,0xF0);//D0-D3拉低,D4-D7拉高

     if((GPIO_ReadInputData(GPIOC)&0xF0)!=0xF0)//有按键按下

     {

       delay(40,ms);//消抖

if((GPIO_ReadInputData(GPIOC)&0xF0)!=0xF0)//再次判断是否按下

        {

LIE=GPIO_ReadInputData(GPIOC);//读取按键按下后得到的代码

            HANG=LIE;//将代码复制给行

LIE=~LIE;//将键码取反,例如:按下某个键得到0111 0000,取反后得到1000 1111;

LIE=LIE&0XF0;//得到列1000 1111&1111 0000得到1000 0000,得到列数

for(i=0;i<4&&((HANG&0xF0)!=0xF0);i++)//逐次将行拉高,判断列数中原来变低的位是否变高

{                                 

  //读到之前检测到为低的列变高则退出

             GPIO_Write(GPIOC, (HANG&0xF0)|(0x01<

//进行行扫描,逐次将行口线拉高,列保持为按下时的状态

HANG=GPIO_ReadInputData(GPIOC);//读取IO口,用以判断是否扫描到行坐标                

            }

           HANG&=0x0F;//将行值取出

           k=LIE|HANG;//行列相加则得到键码

GPIO_Write(GPIOC,0xF0);//D0-D3拉低,D4-D7拉高,此处用来将行列状态初始化为未按下时的状态

while((GPIO_ReadInputData(GPIOC)&0xF0)!=0xF0)//判释放

            {

delay(40,ms);//后沿消抖,时间需长一点,小按键消抖时间可以短一点,大按键抖动严重消抖需长一点

            }

            return k; //返回键码

        }

     }   

     return(0);  //无键按下,返回0

}

2.6 delay延时模块化程序设计

 

2.6.1  delay(uint32_t time,uint8_t tm)

void delay(uint32_t time,uint8_t tm)

{

     uint32_tfac_us,temp;

     uint32_tfac_ms;

SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);//SysTick 时钟源为 AHB 时钟除以 8

     fac_us=SystemCoreClock/8000000;                  

 //延时 1us 计数基值    72M/8M=9   9/9M=1us

     fac_ms= (uint16_t)fac_us * 1000;                

 

   //延时 1ms 计数基值

     if(tm== us)

     {

         SysTick->LOAD= time * fac_us;          

//装载重装寄存器

         SysTick->VAL  = 0x00;                 

  //清空计数器

         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;

//使能开始倒计时

     }

     if(tm==ms)

 {

       SysTick->LOAD =(uint32_t)time*fac_ms;  

//装载重装寄存器

       SysTick->VAL  = 0X00;                        

   //清空计数器

       SysTick->CTRL|= SysTick_CTRL_ENABLE_Msk;

//使能开始倒计时

 }

    

     do

     {

       temp = SysTick->CTRL;

     }

     while(temp&0x01&&!(temp&(1<<16)));    //等待时间到达

    

     SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;

//关闭定时器

     SysTick->VAL=0X00;      //清空计数器

}

2.7 日历详细设计

代码有点多,就举个例子然后主要说下这个函数的具体内容

u8 GetJieQiStr(u16 year,u8 month,u8day,u8 *str)

{

     u8JQdate,JQ,MaxDay;

     u8KK[1]={' '};                  

 /***********定义一个为空格字符(' ')的数组************/

     if(GetJieQi(year,month,day,&JQdate)==0)  return 0;

     JQ= (month-1) *2 ;        //获得节气顺序标号(0~23

     if(day>= 15) JQ++;    //判断是否是上半月

 

     if(day==JQdate)  //今天正是一个节气日

     {

         StrCopy(str,(u8*)JieQiStr[JQ],5);

         return1;

     }

       //今天不是一个节气日

StrCopy(str,(u8*) KK[0],4);   

/************将空格符传送四个进 str 数组,实现在公历日期没有对应农历节气的时候不显示字符************/

     if(day

     {

         //StrCopy(&str[2],(u8*)JieQiStr[JQ],4);

         day=JQdate-day;

     }

     else  //如果今天日期大于本月的节气日期

     {

             if((JQ+1) >23)  return 0;

         //StrCopy(&str[2],(u8*)JieQiStr[JQ+1],4);

         if(day< 15)

         {

              GetJieQi(year,month,15,&JQdate);

              day=JQdate-day;

         }

         else   //翻月

         {

              MaxDay=MonthDayMax[month-1];

              if(month==2)     //润月问题

              {

                    if((year%4==0)&&((year%100!=0)||(year%400==0)))MaxDay++;

              }

              if(++month==13) month=1;

              GetJieQi(year,month,1,&JQdate);

              day=MaxDay-day+JQdate;

         }

     }

     str[10]=day/10+'0';

     str[11]=day%10+'0';

     return1;

通过设置年份对应的键码,使按键设置成立。

const uint8_t year_code[597]=

{

     0x04,0xAe,0x53,//1901 0

     0x0A,0x57,0x48,//1902 3

     0x55,0x26,0xBd,//1903 6

。。。。。。

}//举一部分例子,后边的设置一样,只是按键不同,是通过键盘从左到右的顺序进行设置的。

    

三项目截图及连线:

这里连线就不做表了,直接按这图连或者在代码里的说明进行连线,非常简单,程序有个别小bug,不影响运行。

你可能感兴趣的:(作业)