嵌入式作业 |
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点阵+
光标的显示模式。
对于程序的解释我在第二部分的代码都做了重要的注释,看起来应该比较简单。
项目的截图以及连线的部分在第三部分。
由于在平时实验以及最后考试中我们全都用过中断和定时器,就不详细介绍,全部通过代码和注释就能明白。
输入/输出引脚 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口将输出电流
/***********************************************************头文件名: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时间的结构体指针*/
}
}
* 函数名:RTC_SetTIMEConfig
* 描述 :配置RTC
* 输入 :用于读取RTC时间的结构体指针
* 输出 :无
* 调用 :外部调用
在函数实现之前先设置变量,使得设置的初始值得到初始化。
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[] = {"猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗"};
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();
}
* 函数原型: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();
}
因为输入数据并非ASCII码,而12864显示要求为ASCII码,所以需要进行转换。
功能: 万年历的数据处理函数,实现数据的同步。
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;
}
功能: 万年历的数据显示函数,支持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]]);
}
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;
}
}
}//函数结束
被调用函数
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:设定");
}
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); //初始化
}
功能:键盘扫描函数,函数返回值即键值。
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
}
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,不影响运行。三项目截图及连线: