基于STM32的智能篮球测温记分记时系统

1、项目概述(20分)

1.1项目简介(5分)

进入21世纪,伴随着电子信息通信技术的应用与普及开发,人们对电子技术的要也越来越高。篮球作为一种体育运动进入了人们的生活。为对比赛进行规范化管理,研究一款篮球比赛测温计时计分系统很重要。
虽然篮球比赛中很早就开始研究应用了电子计分器,但通常都是利用模拟电子器件、数字电子器件或是模拟、数字混合组成的,其稳定性和高准确度计分仍存在一些问题。以STM32F103R6单片机为核心的篮球比赛测温计分器,计分准确,具有体积小,重量轻,能耗低,价格便宜,可靠性高,抗干扰能力强和使用方便等独特的优点。基于STM32的智能篮球比赛测温计时计分系统,就是为了解决这一问题。该系统可以测室温,进行赛程定时设置,及时刷新A、B双方的成绩,赛程时间暂停,报警结束比赛功能。
1.2系统功能需求分析(10分)
本项目使用了STM32F103R6单片机,整个设计为5部分,主控、矩阵键盘、显示、报警、温度模拟。综合应用了STM32中断系统、定时器、计数器、ADC模数转化等知识。
矩阵键盘功能包括“加一分”、“加二分”、“加三分”、“减一分”、“减二分”、“减三分”、“选A队”、选“B队”、“复位”、“启动/暂停”、“减一分钟”、“减二分钟”、“减三分钟”、“关闭蜂鸣器”等。当按下“A”后,选中A队,即可以进行A队的分数加或者减。若此时按下“1”,A队分数加1。当按下“B”后,选中B队,即可以进行B队的分数加或者减。若此时按下“3”,B队分数加3。当按下“*”时,比赛开始计时或者计时暂停。当按下“#”时,复位。当比赛时间为0时,会开启蜂鸣器和小灯,按下“0”时,关闭正在报警的蜂鸣器和小灯。
报警模块主要采用蜂鸣器和LED小灯组成的,当传入为低电平开启蜂鸣器并点亮LED小灯,当传入高电平关闭蜂鸣器和LED小灯。
显示模块通过液晶LCD1602来显示模拟的环境温度,对温度传感器进行温度修改,液晶显示器立即显示最新环境温度。延时一段时间,进入比赛界面,显示比赛节数,比赛剩余时间,两队比分。
温度模拟模块使用了DS18B20温度传感器,可以进行环境温度的加和减,并在液晶显示器上显示当前模拟温度。
系统功能:
1、LCD1602液晶显示器可以显示环境温度,一段时间后显示比赛剩余时间、球队分数。
2、默认比赛为4节,每节10分钟,在默认条件下可以进行比赛时间的修改(例如可以将一节比赛修改为5分钟)。
3、每一节比赛结束后,蜂鸣器会进行报警处理同时LED灯会亮。可以对时间进行暂停或者复位操作。
4、每一场比赛有A、B两只队伍,通过使用矩阵键盘,可以对两支队伍分别进行计分控制。按下A即为选择A队,再按1、2、3可进行对应分数的累计,若果不小心计分错误(如多加了分数),可按下4、5、6进行对应减1、2、3分数的控制。
1.3系统开发工具分析选择、使用方法(5分)
本项目主要应用keil5来编写代码,proteus设计仿真图,来模拟功能的实现。用矩阵键盘进行操作,包括进行比分的修改,时间的控制,蜂鸣器和LED小灯负责警报功能,LED液晶显示器负责显示,温度传感器负责模拟当前环境温度。

2、项目硬件系统设计(30分)

2.1、系统方案原理图(10分)

基于STM32的智能篮球测温记分记时系统_第1张图片

如图所示,使用的芯片为STM32F103R6,显示模块采用LCD1602液晶显示器,通过矩阵键盘的输入来控制比赛时间和分数,通过LCD液晶显示屏来显示。温度传感器可以模拟环境温度并在液晶显示器显示。PA0-PA7引脚接LCD162液晶显示器的D0-D7,PB8引脚接RS,PC13接RW,PB9接E。PC0-PC7引脚分别接连矩阵键盘,PC8引脚接蜂鸣器。PB4引脚连温度传感器DQ引脚。

Proteus仿真图:

基于STM32的智能篮球测温记分记时系统_第2张图片

2.2、STM32F10X原理介绍(5分)
1)内核架构–CM3内核架构:
基于STM32的智能篮球测温记分记时系统_第3张图片

STM32F103是一个ARM单片机,内嵌有一个Cortex-M3(简称CM3)处理器作为核心。
CM3的三条总线通过总线矩阵与STM32F103主要部件及外设相连。
STM32F103的架构包括CM3的内核架构和除内核以外的其他架构。
2)GPIO:(General Purpose Input Output,通用输入输出接口)是MCU与外部电路和设备连接的基本外设。也就是常说的端口或管脚。
GPIO8种工作方式,可由软件配置:
1、浮空输入:上下拉电阻断开,用于不确定高低电平的输入。(复位后处于此状态
2、上拉输入:上拉电阻接通,默认高电平输入。
3、下拉输入:下拉电阻接通,默认低电平输入。
4、模拟输入:施密特触发器截止,模拟量输入常用于AD转换
5、开漏输出:上部MOS管不工作,只能输出低电平,不能输出高电平。如果要输出高电平则需要外接上拉。用于实现电平转换和线与功能的输出。
6、推挽式输出:上下MOS管交替导通,用于较大功率驱动的输出。
7、推挽式复用输出功能:复用功能情况下的推挽输出。
8、开漏复用输出功能:复用功能情况下的开漏输出。
3)NVIC中断控制器:(Nested Vectored Interrupt Controller,NVIC)位于CM3内核,是中断系统硬件设施,用来管理和配置中断。
NVIV的功能:(硬件+控制寄存器组成)
1、管理中断源2、保存中断申请信号3、优先级判断中断嵌套4、屏蔽中断5、中断优先级设置
4)EXTI控制器:主要用来管理片外引脚中断和事件控制。
EXTI控制器是片上外设,可以管理 16路外部引脚中断 和 4路事件 的控制, 共20 路输入。
EXTI --外部中断/事件线路映像:
80个通用I/O端口:均可做外部中断或事件输入线,连接到16个外部中断/事件线上。使用时,须设置AFIO功能,并使能其时钟。(可以是中断也可以是事件)
四个专用的线: 1、EXTI线16连接到PVD输出(中断)。 2、EXTI线17连接到RTC闹钟事件。 3、EXTI线18连接到USB唤醒事件。 4、EXTI线19连接到以太网唤醒事件
注释:PVD(可编程电压监控器),可管理上电复位和掉电中断。
5)ADC模数转化:ADC 的转换分为 2 个通道组:规则通道组和注入通道组。规则通道相当于你正常运行的程序,而注入通道呢,就相当于中断。注入通道的转换可以打断规则通道的转换, 在注入通道被转换完成之后,规则通道才得以继续转换。 ADC 的规则规则通道组最多包含 16 个转换,而注入通道组最多包含 4 个通道。
规则通道组:规则通道和它的转换顺序在ADC_SQRx寄存器中选择,规则组转换的总数应写入ADC_SQR1寄存器的L[3:0]中。
注入通道组:注入组和它的转换顺序在ADC_JSQR寄存器中选择,注入组里转化的总数应写入ADC_JSQR寄存器的L[1:0]中。
2.3 矩阵键盘模块原理
按键由一组独立按键构成,由于弹性按键结构简单,价格较低,故选用弹性按键。同时,每个按键单独连接芯片的对应引脚,简化了电路的设计。本设计共有16个独立的弹性小键来控制分数的加减、计时的开始与结束等操作。均与相对应的GPIO口直接相连,所有GPIO口设置为上拉输入电平触发方式。一旦小键按下,相应的引脚与地接通成低电平,当程序进入按键查询时为低电平,表示键被按下,按键弹起后为高电平。
基于STM32的智能篮球测温记分记时系统_第4张图片

2.4 LCD1602液晶显示器模块原理
液晶显示的原理是利用液晶的物理特性,通过电压对其显示区域进行控制,有电就有显示,这样即可以显示出图形。液晶显示器具有厚度薄、适用于大规模集成电路直接驱动、易于实现全彩色显示的特点,目前已经被广泛应用在便携式电脑、数字摄像机、PDA移动通信工具等众多领域。
液晶显示模块是一个慢显示器件,所以在执行每条指令之前一定要确认模块的忙标志为低电平,表示不忙,否则此指令失效。要显示字符时要先输入显示字符地址,也就是告诉模块在哪里显示字符。所以要有忙等待。

引脚说明:
第1脚:VSS为地电源。
第2脚:VDD接5V正电源。
第3脚:VL为液晶显示器对比度调整端,接正电源时对比度最弱,接地时对比度最高,对比度过高时会产生“鬼影”,使用时可以通过一个10K的电位器调整对比度。
第4脚:RS为寄存器选择,高电平时选择数据寄存器、低电平时选择指令寄存器。
第5脚:R/W为读写信号线,高电平时进行读操作,低电平时进行写操作。当RS和R/W共同为低电平时可以写入指令或者显示地址,当RS为低电平
R/W为高电平时可以读忙信号,当RS为高电平R/W为低电平时可以写入数据。
第6脚:E端为使能端,当E端由高电平跳变成低电平时,液晶模块执行命令。
第7~14脚:D0~D7为8位双向数据线。
第15脚:背光源正极。
第16脚:背光源负极。
本项目中需要LCD1602液晶显示器进行倒计时显示,还需要显示A、B两队分数。LCD162液晶显示器的D0-D7分别接STM32F103R6的PA0-PA7引脚,RS接PB8引脚,RW接PC13。
2.5 蜂鸣器模块原理
蜂鸣器是一种一体化结构的电子讯响器,采用直流电压供电,广泛应用于计算机、打印机、复印机、报警器、电子玩具、汽车电子设备、电话机、定时器等电子产品中作发声器件。蜂鸣器主要分为压电式蜂鸣器和电磁式蜂鸣器两种类型。本系统利用蜂鸣器进行提示,当每一小节比赛计时结束后,即时间=0,(设置为低电平)蜂鸣器进行报警处理,按下“0”键之后,才可以关闭蜂鸣器。

2.6 LED灯模块原理
系统设有一个LED小灯,正极接电源。当系统查询到比赛直接截止时,立刻使STM32相应GPIO口为低电平,点亮相应小灯。系统保持LED灯的状态直到按下复位键,此时灯灭。

2.7 温度传感器模块原理
系统设有一个DS18B20温度传感器,主要用于模拟当前环境温度,并在液晶显示器上显示当前模拟温度。DS18B20适应电压范围宽,电压范围:3.0~5.5V,在寄生电源方式下可由数据线供电独特的单线接口方式,DS18B20在与微处理器连接时仅需要一条线即可实现微处理器与DS18B20的双向通讯DS18B20支持多点组网功能,多个DS18B20可以并联在唯一的三线上,实现组网多点测温DS18B20在使用中不需要任何外围元件,全部传感元件及转换电路集成在形如一只三极管的集成电路内测温范围-55℃~+125℃,在-10~+85℃时精度为±0.5℃可编程的分辨率为9~12位,对应的可分辨温度分别为0.5℃、0.25℃、0.125℃和0.0625℃,可实现高精度测温在9位分辨率时最多在93.75ms内把温度转换为数字,12位分辨率时最多在750ms内把温度值转换为数字,速度快测量结果直接输出数字温度信号。

2.8 系统定时器控制部件设计
定时控制部件是在规定的时刻发出各种操作所需的全部内部和外部控制信号,使各功能元件协调工作,完成指令所规定的功能。主要任务是产生一个工作时序,其工作需要时钟电路提供一个工作频率,外接时钟源可构成时钟电路。单片机的生产工艺不同,时钟的产生方式也不同有内部和外部时钟产生两种时钟方式,本系统采用的是内部时钟产生方式,输入输出两端跨接晶体或陶瓷谐振器,于内部反向器构成稳定的自激振荡器。其发出的脉冲直接送入片内的定时控制部件。

3、系统软件体系设计(30分)

3.1项目软件系统总架构图(10分)
本项目主要子模块为定时器中断模块,矩阵键盘模块,显示模块,警报检测模块,温度传感器AD转化模块
总流程图:

3.2 定时器中断模块
定时器采用内中断模式,实现步骤为:
1、定时器时钟
RCC_APB1PeriphClockCmd();
2、初始化定时器,配置ARR,PSC。
TIM_TimeBaseInit();
3、开启定时器中断,配置NVIC
Void TIM_ITCongfig(); NVIC _Init();
4、使能定时器。TIM_Cmd();
5、编写中断函数

static void GENERAL_TIM2_Mode_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=8M
GENERAL_TIM2_APBxClock_FUN(GENERAL_TIM2_CLK,ENABLE);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 开启定时器时钟,即内部时钟CK_INT=8M
GENERAL_TIM2_APBxClock_FUN(GENERAL_TIM2_CLK, ENABLE);
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
#ifdef SIMULATION
TIM_TimeBaseStructure.TIM_Period = GENERAL_TIM2_Period + 1;
#else
TIM_TimeBaseStructure.TIM_Period = GENERAL_TIM2_Period;
#endif
// 时钟预分频数
TIM_TimeBaseStructure.TIM_Prescaler = GENERAL_TIM2_Prescaler;
// 时钟分频因子 ,没用到不用管
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
// 计数器计数模式,设置为向上计数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
// 重复计数器的值,没用到不用管
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
// 初始化定时器
TIM_TimeBaseInit(GENERAL_TIM2, &TIM_TimeBaseStructure);
// 清除计数器中断标志位
TIM_ClearFlag(GENERAL_TIM2, TIM_FLAG_Update);
// 开启计数器中断
TIM_ITConfig(GENERAL_TIM2, TIM_IT_Update, ENABLE);
// 使能计数器
TIM_Cmd(GENERAL_TIM2, DISABLE);
}
static void GENERAL_TIM3_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 设置中断组为0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
// 设置中断来源
NVIC_InitStructure.NVIC_IRQChannel = GENERAL_TIM3_IRQ;
// 设置主优先级为 0
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
// 设置抢占优先级为3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
static void GENERAL_TIM3_Mode_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=8M
GENERAL_TIM3_APBxClock_FUN(GENERAL_TIM3_CLK,ENABLE);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 开启定时器时钟,即内部时钟CK_INT=8M
GENERAL_TIM3_APBxClock_FUN(GENERAL_TIM3_CLK, ENABLE);
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
#ifdef SIMULATION
TIM_TimeBaseStructure.TIM_Period = GENERAL_TIM3_Period + 1;
#else
TIM_TimeBaseStructure.TIM_Period = GENERAL_TIM3_Period;
#endif
// 时钟预分频数
TIM_TimeBaseStructure.TIM_Prescaler = GENERAL_TIM3_Prescaler;
// 时钟分频因子 ,没用到不用管
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
// 计数器计数模式,设置为向上计数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
// 重复计数器的值,没用到不用管
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
// 初始化定时器
TIM_TimeBaseInit(GENERAL_TIM3, &TIM_TimeBaseStructure);
// 清除计数器中断标志位
TIM_ClearFlag(GENERAL_TIM3, TIM_FLAG_Update);
// 开启计数器中断
TIM_ITConfig(GENERAL_TIM3, TIM_IT_Update, ENABLE);
// 使能计数器
TIM_Cmd(GENERAL_TIM3, ENABLE);
}

3.3 矩阵键盘模块
采用逐位扫描的方式识别按键,并使用软件消除抖动,即在查询到有键被按下后延时一段时间后再次查询这个按键。只有2次查询时按键都为有效状态才认为按键被按下。矩阵键盘模块的主要功能是进行分数的加减、默认初始时间的修改等,其中按键的主要功能为1键加1分,2键加2分,3键加3分,4键减1分,5键减2分,6键减3分。0键关闭蜂鸣器,*启动暂停计时器,#复位计时器,A选择A队,B键选择B队,C键初始状态下计时器加1分钟,D键计时器初始状态下减1分钟。

void KeyFunction(void)
{
char keyCode = 0;
keyCode = KeyScan();
if (keyCode == ‘*’)
{
GENERAL_TIM2->CR1 ^= TIM_CR1_CEN; //启动/暂停计时
startFlag = 1;
}
else if (keyCode == ‘#’) //复位计时
{
TIM_Cmd(GENERAL_TIM2, DISABLE); //停止计时
timeMinute = setTimeMinute;
timeSecond = 0;
time10ms = 0;
roundNum = 1;
scoreA = 0;
scoreB = 0;
startFlag = 0;
}
else if (keyCode == ‘A’)//切换A队
{
teamFlag = 0;
}
else if (keyCode == ‘B’)//切换B队
{
teamFlag = 1;
}
else if (keyCode == ‘C’)//计时器+1
{
if (startFlag == 0)
{
if (timeMinute < 99)
{
timeMinute++;
setTimeMinute++;
}
}
}
else if (keyCode == ‘D’)//计时器-1
{
if (startFlag == 0)
{
if (timeMinute > 1)
{
timeMinute–;
setTimeMinute–;
}
}
}
else if (keyCode == ‘0’)
{
BUZZER_OFF;//关闭蜂鸣器
}
else if (keyCode == ‘1’)
{
if (teamFlag == 0)
{
scoreA++;
}
else
{
scoreB++;
}
}
else if (keyCode == ‘2’)
{
if (teamFlag == 0)
{
scoreA += 2;
}
else
{
scoreB += 2;
}
}
else if (keyCode == ‘3’)
{
if (teamFlag == 0)
{
scoreA += 3;
}
else
{
scoreB += 3;
}
}
else if (keyCode == ‘4’)
{
if (teamFlag == 0)
{
if (scoreA > 0)
{
scoreA–;
}

    }
    else
    {
        if (scoreB > 0)
        {
            scoreB--;
        }
    }
}
else if (keyCode == '5')
{
    if (teamFlag == 0)
    {
        if (scoreA >= 2)
        {
            scoreA -= 2;
        }
        
    }
    else
    {
        if (scoreB >= 2)
        {
            scoreB -= 2;
        }
    }
}
else if (keyCode == '6')
{
    if (teamFlag == 0)
    {
        if (scoreA >= 3)
        {
            scoreA -= 3;
        }
    }
    else
    {
        if (scoreB >= 3)
        {
            scoreB -= 3;
        }
    }
}

}

3.4 警报检测模块原理
警报检测模块是通过判断定时器的时间来完成功能,当定时器的时间为0时,开启蜂鸣器和LED小灯。蜂鸣器通过一PNP三极管进行驱动,如图触发信号有基极引入。

if (timeMinute == 0)
{
timeMinute = setTimeMinute;
timeSecond = 0;
time10ms = 0;
roundNum++;
TIM_Cmd(GENERAL_TIM2, DISABLE); //停止计时
BUZZER_ON;
if (roundNum > 4)
roundNum = 1;
}

3.5 LCD液晶显示驱动模块原理
void LCD_GPIO_Init(void) //GPIO初始化
void LCD_DispStr(uchar column, uchar row, char *p_str) //让液晶从某个位置起连续显示一个字符串
void LCD_DispNChar(uchar column, uchar row, uchar n, char *p_str)//让液晶从某个位置起连续显示N个字符
void LCD_DispOneChar(uchar column, uchar row, char dat) //在某个位置显示一个字符
void LCD_LocateXY(uchar column, uchar row) //向液晶输入显示字符位置的坐标信息
void LCD_Init() //对1602液晶模块进行复位操作
void LCD_WriteCommand(uchar cmd, uchar chk) //向液晶模块写入命令
void LCD_WriteData(uchar dat) //向液晶显示的当前地址写入显示数据
static void LCD_WaitForEnable(void) //等待1602液晶完成内部操作

3.7 温度传感器驱动模块原理

//开始温度转换
void DS18B20_Start_Pin_4(void)// ds1820 start convert
{
DS18B20_Rst_Pin_4();
DS18B20_Check_Pin_4();
DS18B20_Write_Byte_Pin_4(0xcc);// skip rom
DS18B20_Write_Byte_Pin_4(0x44);// convert
}
//初始化DS18B20的IO口 DQ 同时检测DS的存在
//返回1:不存在
//返回0:存在
u8 DS18B20_Init_Pin_4(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);

DS18B20_Rst_Pin_4();

return DS18B20_Check_Pin_4();

}
//从ds18b20得到温度值
//精度:0.1C
//返回值:温度值 (-550~1250)
short DS18B20_Get_Temp_Pin(void)
{
u8 temp;
u8 TL,TH;
short tem;

DS18B20_Start_Pin_4 ();// ds1820 start convert
DS18B20_Rst_Pin_4();
DS18B20_Check_Pin_4();	 
DS18B20_Write_Byte_Pin_4(0xcc);// skip rom跳过rom
DS18B20_Write_Byte_Pin_4(0xbe);// convert	    
TL=DS18B20_Read_Byte_Pin_4(); // LSB   
TH=DS18B20_Read_Byte_Pin_4(); // MSB  高
if(TH>7)
{
    TH=~TH;
    TL=~TL; 
    temp=0;//温度为负  
}
else 
    temp=1;//温度为正
tem=TH; //获得高八位
tem<<=8;    
tem+=TL;//获得底八位
tem=(float)tem*0.625;//转换 

if(temp)
    return tem; //返回温度值
else 
    return -tem;    

}

4 运行效果展示
开启仿真,首先通过温度传感器获取温度,在液晶显示器上显示temp=24

调节温度传感器,改变温度大小

一段时间之后,LCD1602显示屏显示比赛计时计分界面
T:10:00:00
A 000–000 B

按下*键:LCD1602显示屏开始计时(比赛默认10分钟,可进行时间的调节)

按下*键:LCD1602显示屏计时暂停。

先按A键,选择A队,再按1,A队加1分:LCD1602显示屏 001–000

程序运行(按B键,选择B队,再按3,B队加3分):LCD1602显示屏 001–003

程序运行(按B键,选择B队,再按4,B队减1分):LCD1602显示屏 001–002

程序运行(按 # 键,进行复位操作):LCD1602显示屏 时间恢复为10:00:00 分数000–000

程序运行(按C键,比赛时间调整为11分钟):LCD1602显示屏 11:00:00 000–000

定时结束,蜂鸣器报警,LED小灯亮

按下0键,关闭蜂鸣器

5 总结

通过使用STM32F103R6芯片、LCD1602显示屏、矩阵键盘、蜂鸣器、LED小灯、温度传感器等原件,用proteus模拟仿真,实现了篮球比赛测温计时计分系统,实现了测量比赛环境温度、比赛倒计时和比分记录的操作。
主要收获:通过本次STM32的课程设计,我对定时器中断、AD转化、矩阵键盘等知识有了更深的认识。大二下学期学的51单片机,主要是编写程序,用编写的代码实现仿真,但是本学期学的STM32单片机,主要是调用函数,通过高电平与低电平的设置来调用对应函数,需要自己编写的程序相比51少了一些。之前学习51单边机的时候,我做的课程设计是定时器和简易计算器,使用的是数码管来进行显示,很少使用过本学期学习的DMA、串口通讯、AD转化等知识,所以在一开始学习STM32单边机这方面的知识时,就是做那几个实验的时候,也遇到了一些困难,从网上找了很多资源,才把问题逐渐解决。我把AD转化的知识也运用到了课程设计中,模拟环境温度并在LED液晶显示器进行显示,也学会了逐渐实现一些较为复杂的程序。充分的练习了单片机c语言的编程规则。相比于生活中的篮球比赛计分器,往往只能进行比赛分数与时间的控制,我进行了一些创新,将温度传感器加入进来,可以检测环境温度,使用户获得该温度是否适宜比赛等。
不足之处:本学期的学习中,我有许多不足之处,我在完成定时器功能的时候遇到了一些困难,程序的中断代码总是写不好,导致LED显示器有时候不能正常显示。在温度传感器获取环境温度时,总是获取不到数据,因而无法在LED显示器显示对应的温度。通过同学的帮助,也解决了这些问题,并实现了自己预期的效果。在以后的学习过程中,加深对STM32单片机的学习。

6 附录

Main.c
#define __MAIN_C
#include “main.h”
#include “stm32f10x.h”
#include “stm32f10x_gpio.h”
#include “bsp_gpio.h”
#include “bsp_clkconfig.h”
#include “bsp_delay.h”
#include “bsp_lcd1602.h”
#include “bsp_GeneralTim.h”
#include “bsp_keyArray.h”
#include “stdio.h”
#include “DS18B20.h”

CreatByte Flag;
void display(int data);
uint8_t roundNum = 1;
uint8_t scoreA = 0;
uint8_t scoreB = 0;
volatile uint8_t timeMinute = 0;
volatile uint8_t timeSecond = 0;
volatile uint8_t time10ms = 0;
uint8_t setTimeMinute = 10;
int Temp=20;

int main(void)
{
char dis[16] = {0};
dispFlag = 1;
startFlag = 0;
teamFlag = 0;
int Temp=20;
int count=0;
timeMinute = setTimeMinute;
// 使用HSI,SYSCLK = 4M * RCC_PLLMul_x, x:[2,3,…16],最高是64MH
HSI_SetSysClock(RCC_PLLMul_2); //使用内部8MHz晶振,并设置PLL输出为8MHz

// LED 端口初始化
GPIO_Config();
GENERAL_TIM_Init();
KeyArray_GPIO_Init();
LCD_GPIO_Init();//液晶显示器接口初始化
LCD_Init();
LCD_Clear();
DelayMs(250);
LCD_DispStr(0, 0, "    Welcome!    ");
DelayMs(500);
LCD_Clear();
  Temp=DS18B20_Get_Temp_Pin();
  DelayMs(750);
while(1)
	{
	Temp=DS18B20_Get_Temp_Pin();
  DelayMs(100);
	sprintf(dis, " Temp=%d%d", Temp%1000/100 ,Temp%1000%100/10 );
	LCD_DispStr(0, 1, dis);
		count++;
		if(count>20) break;
	}
	DelayMs(1500);
while (1)
{    	  
    if (dispFlag == 1)
    {
        dispFlag = 0;
        sprintf(dis, "R:%1d  T:%02d:%02d:%02d", (int)roundNum, (int)timeMinute, (int)timeSecond, (int)time10ms);//LCD液晶显示
        LCD_DispStr(0, 0, dis);
        DelayMs(10);
        if (teamFlag == 0)//显示A队得分或减分
        {
            sprintf(dis, ">A  %03d--%03d  B ", (int)scoreA, (int)scoreB);
            LCD_DispStr(0, 1, dis);
        }
        else//显示B队得分或减分
        {
            sprintf(dis, " A  %03d--%03d >B ", (int)scoreA, (int)scoreB);
            LCD_DispStr(0, 1, dis);
        }
    }
    KeyFunction();
}

}

bsp_GeneralTim.c
#include “bsp_GeneralTim.h”
// 中断优先级配置
static void GENERAL_TIM2_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 设置中断组为0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
// 设置中断来源
NVIC_InitStructure.NVIC_IRQChannel = GENERAL_TIM2_IRQ;
// 设置主优先级为 0
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
// 设置抢占优先级为3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}

static void GENERAL_TIM2_Mode_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=8M
GENERAL_TIM2_APBxClock_FUN(GENERAL_TIM2_CLK,ENABLE);

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 开启定时器时钟,即内部时钟CK_INT=8M
GENERAL_TIM2_APBxClock_FUN(GENERAL_TIM2_CLK, ENABLE);
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
#ifdef _SIMULATION_
TIM_TimeBaseStructure.TIM_Period = GENERAL_TIM2_Period + 1;
#else
TIM_TimeBaseStructure.TIM_Period = GENERAL_TIM2_Period;
#endif
// 时钟预分频数
TIM_TimeBaseStructure.TIM_Prescaler = GENERAL_TIM2_Prescaler;
// 时钟分频因子 ,没用到不用管
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
// 计数器计数模式,设置为向上计数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
// 重复计数器的值,没用到不用管
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
// 初始化定时器
TIM_TimeBaseInit(GENERAL_TIM2, &TIM_TimeBaseStructure);

// 清除计数器中断标志位
TIM_ClearFlag(GENERAL_TIM2, TIM_FLAG_Update);

// 开启计数器中断
TIM_ITConfig(GENERAL_TIM2, TIM_IT_Update, ENABLE);

// 使能计数器
TIM_Cmd(GENERAL_TIM2, DISABLE);

}

// 中断优先级配置
static void GENERAL_TIM3_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 设置中断组为0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
// 设置中断来源
NVIC_InitStructure.NVIC_IRQChannel = GENERAL_TIM3_IRQ;
// 设置主优先级为 0
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
// 设置抢占优先级为3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}

static void GENERAL_TIM3_Mode_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=8M
GENERAL_TIM3_APBxClock_FUN(GENERAL_TIM3_CLK,ENABLE);

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 开启定时器时钟,即内部时钟CK_INT=8M
GENERAL_TIM3_APBxClock_FUN(GENERAL_TIM3_CLK, ENABLE);
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
#ifdef _SIMULATION_
TIM_TimeBaseStructure.TIM_Period = GENERAL_TIM3_Period + 1;
#else
TIM_TimeBaseStructure.TIM_Period = GENERAL_TIM3_Period;
#endif
// 时钟预分频数
TIM_TimeBaseStructure.TIM_Prescaler = GENERAL_TIM3_Prescaler;
// 时钟分频因子 ,没用到不用管
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
// 计数器计数模式,设置为向上计数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
// 重复计数器的值,没用到不用管
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
// 初始化定时器
TIM_TimeBaseInit(GENERAL_TIM3, &TIM_TimeBaseStructure);

// 清除计数器中断标志位
TIM_ClearFlag(GENERAL_TIM3, TIM_FLAG_Update);

// 开启计数器中断
TIM_ITConfig(GENERAL_TIM3, TIM_IT_Update, ENABLE);

// 使能计数器
TIM_Cmd(GENERAL_TIM3, ENABLE);

}

void GENERAL_TIM_Init(void)
{
GENERAL_TIM2_NVIC_Config();
GENERAL_TIM2_Mode_Config();

GENERAL_TIM3_NVIC_Config();
GENERAL_TIM3_Mode_Config();

}

/***********************END OF FILE/

bsp_keyArray.c
#include “bsp_keyArray.h”
#include “bsp_GeneralTim.h”
#include “stm32f10x.h”
#include “main.h”
#include “bsp_gpio.h”

#define KEY_ROW0_OUT_LOW (GPIO_ResetBits(KEY_ROW0_PORT, KEY_ROW0_PIN))
#define KEY_ROW0_OUT_HIGH (GPIO_SetBits (KEY_ROW0_PORT, KEY_ROW0_PIN))

#define KEY_ROW1_OUT_LOW (GPIO_ResetBits(KEY_ROW1_PORT, KEY_ROW1_PIN))
#define KEY_ROW1_OUT_HIGH (GPIO_SetBits (KEY_ROW1_PORT, KEY_ROW1_PIN))

#define KEY_ROW2_OUT_LOW (GPIO_ResetBits(KEY_ROW2_PORT, KEY_ROW2_PIN))
#define KEY_ROW2_OUT_HIGH (GPIO_SetBits (KEY_ROW2_PORT, KEY_ROW2_PIN))

#define KEY_ROW3_OUT_LOW (GPIO_ResetBits(KEY_ROW3_PORT, KEY_ROW3_PIN))
#define KEY_ROW3_OUT_HIGH (GPIO_SetBits (KEY_ROW3_PORT, KEY_ROW3_PIN))

volatile uint8_t KeyCol = 0xff;
uint8_t keyCodeMap[16] = { //矩阵按键编号
‘1’, ‘2’, ‘3’, ‘A’ , //数字键1、数字键2、数字键3、字母键A
‘4’, ‘5’, ‘6’, ‘B’ , //数字键4、数字键5、数字键6、字母键B
‘7’, ‘8’, ‘9’, ‘C’ , //数字键7、数字键8、数字键9、字母键C
'', ‘0’, ‘#’, ‘D’ //字母键、数字键0、字母键#、字母键D
};

void KeyArray_GPIO_Init(void)//键盘接口初始化
{
GPIO_InitTypeDef GPIO_InitStructure;

GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //禁用jtag,不然写入程序和程序执行都会受影响
RCC_APB2PeriphClockCmd(KEY_COL_CLK, ENABLE);
RCC_APB2PeriphClockCmd(KEY_ROW_CLK, ENABLE);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置引脚模式为通用推挽输出

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置引脚速率为50MHz

GPIO_InitStructure.GPIO_Pin = KEY_ROW_PINS;
GPIO_Init(KEY_COL_PORT, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置引脚模式为内部上拉

GPIO_InitStructure.GPIO_Pin = KEY_COL_PINS;
GPIO_Init(KEY_ROW_PORT, &GPIO_InitStructure);

KEY_ROW0_OUT_HIGH;
KEY_ROW1_OUT_HIGH;
KEY_ROW2_OUT_HIGH;
KEY_ROW3_OUT_HIGH;

}

static uint8_t KeyColScan(void)
{
KeyCol = (uint8_t)GPIO_ReadInputData(KEY_COL_PORT);
//矩阵键盘的列扫描
if (KeyCol != 0x0F)
{
DelayMs(5);
if (KeyCol != 0x0F)
{
switch (KeyCol & 0x0F)//取低四位
{
case 0x0E: //1110 判断为该列第1行的按键按下
return 1;
case 0x0D: //1101 判断为该列第2行的按键按下
return 2;
case 0x0B: //1011 判断为该列第3行的按键按下
return 3;
case 0x07: //0111 判断为该列第4行的按键按下
return 4;
default :
return 0;
}
}
else
{
return 0;
}
}
else
{
return 0;
}
}

char KeyScan(void)
{
//矩阵键盘扫描
char keyNum = 0;//矩阵键盘的数值
char KeyColNum = 0;//矩阵行数

//低位有效
KEY_ROW0_OUT_LOW;//扫描第一行
KeyColNum = KeyColScan();
if ( KeyColNum != 0 )
{
while(KeyColScan() != 0);
keyNum = KeyColNum + 0;//第一行键盘
}
KEY_ROW0_OUT_HIGH;//关闭第一行
KEY_ROW1_OUT_LOW;//扫描第二行
KeyColNum = KeyColScan();
if ( KeyColNum != 0 )
{
while(KeyColScan() != 0);
keyNum = KeyColNum + 4;//第二行键盘
}
KEY_ROW1_OUT_HIGH;

KEY_ROW2_OUT_LOW;
KeyColNum = KeyColScan();
if ( KeyColNum != 0 )
{
    while(KeyColScan() != 0);
    keyNum = KeyColNum + 8;//第三行键盘
}
KEY_ROW2_OUT_HIGH;

KEY_ROW3_OUT_LOW;
KeyColNum = KeyColScan();
if ( KeyColNum != 0 )
{
    while(KeyColScan() != 0);
    keyNum = KeyColNum + 12;//第四行键盘
}
KEY_ROW3_OUT_HIGH;

if (keyNum == 0)
{
    return 0;
}
else
{
    return keyCodeMap[keyNum-1];
}

}

void KeyFunction(void)
{
char keyCode = 0;

keyCode = KeyScan();
if (keyCode == '*')
{
    GENERAL_TIM2->CR1 ^= TIM_CR1_CEN; //启动/暂停计时
    startFlag = 1;
}
else if (keyCode == '#') //复位计时
{
    TIM_Cmd(GENERAL_TIM2, DISABLE); //停止计时
    timeMinute = setTimeMinute;
    timeSecond = 0;
    time10ms = 0;
    roundNum = 1;   
    scoreA = 0;  
    scoreB = 0;  
    startFlag = 0;
}
else if (keyCode == 'A')//切换A队
{
    teamFlag = 0;
}
else if (keyCode == 'B')//切换B队
{
    teamFlag = 1;
}
else if (keyCode == 'C')//计时器+1
{
    if (startFlag == 0)
    {
        if (timeMinute < 99)
        {
            timeMinute++;
            setTimeMinute++;
        }
    }
}
else if (keyCode == 'D')//计时器-1
{
    if (startFlag == 0)
    {
        if (timeMinute > 1)
        {
            timeMinute--;
            setTimeMinute--;
        }
    }
}
else if (keyCode == '0')
{
    BUZZER_OFF;//关闭蜂鸣器
}
else if (keyCode == '1')
{
    if (teamFlag == 0)
    {
        scoreA++;
    }
    else
    {
        scoreB++;
    }
}
else if (keyCode == '2')
{
    if (teamFlag == 0)
    {
        scoreA += 2;
    }
    else
    {
        scoreB += 2;
    }
}
else if (keyCode == '3')
{
    if (teamFlag == 0)
    {
        scoreA += 3;
    }
    else
    {
        scoreB += 3;
    }
}
else if (keyCode == '4')
{
    if (teamFlag == 0)
    {
        if (scoreA > 0)
        {
            scoreA--;
        }
        
    }
    else
    {
        if (scoreB > 0)
        {
            scoreB--;
        }
    }
}
else if (keyCode == '5')
{
    if (teamFlag == 0)
    {
        if (scoreA >= 2)
        {
            scoreA -= 2;
        }
        
    }
    else
    {
        if (scoreB >= 2)
        {
            scoreB -= 2;
        }
    }
}
else if (keyCode == '6')
{
    if (teamFlag == 0)
    {
        if (scoreA >= 3)
        {
            scoreA -= 3;
        }
        
    }
    else
    {
        if (scoreB >= 3)
        {
            scoreB -= 3;
        }
    }
}

}

Bsp_gpio.c
#include “bsp_gpio.h”

/**

  • @brief 初始化控制LED的IO

  • @param 无

  • @retval 无
    */
    void GPIO_Config(void)
    {
    /定义一个GPIO_InitTypeDef类型的结构体/
    GPIO_InitTypeDef GPIO_InitStructure;

     /*开启LED相关的GPIO外设时钟*/
     RCC_APB2PeriphClockCmd( LED1_GPIO_CLK | LED2_GPIO_CLK | LED3_GPIO_CLK | LED4_GPIO_CLK | BUZZER_GPIO_CLK, ENABLE);	
    
     /*设置引脚模式为通用推挽输出*/
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   
    
     /*设置引脚速率为50MHz */   
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
    
     /*选择要控制的GPIO引脚*/
     GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;
    
     /*调用库函数,初始化GPIO*/
     GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);	
     
     /*选择要控制的GPIO引脚*/
     GPIO_InitStructure.GPIO_Pin = LED2_GPIO_PIN;
    
     /*调用库函数,初始化GPIO*/
     GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure);
     
     /*选择要控制的GPIO引脚*/
     GPIO_InitStructure.GPIO_Pin = LED3_GPIO_PIN;
    
     /*调用库函数,初始化GPIOF*/
     GPIO_Init(LED3_GPIO_PORT, &GPIO_InitStructure);
     
     /*选择要控制的GPIO引脚*/
     GPIO_InitStructure.GPIO_Pin = LED4_GPIO_PIN;
    
     /*调用库函数,初始化GPIOF*/
     GPIO_Init(LED4_GPIO_PORT, &GPIO_InitStructure);
    
     /*选择要控制的GPIO引脚*/
     GPIO_InitStructure.GPIO_Pin = BUZZER_GPIO_PIN;
    
     /*调用库函数,初始化GPIOF*/
     GPIO_Init(BUZZER_GPIO_PORT, &GPIO_InitStructure);
    
     /* 关闭所有led灯	*/
     LED1_OFF;
     LED2_OFF;
     LED3_OFF;
     LED4_OFF;
     BUZZER_OFF;
    

}
/***********************END OF FILE/
Bsp_lcd1602.c
#include “bsp_lcd1602.h”

#define CLR_RS (GPIO_ResetBits(RS_GPIO_PORT, RS_GPIO_PIN))
#define SET_RS (GPIO_SetBits(RS_GPIO_PORT, RS_GPIO_PIN))

#if RW_PIN
#define CLR_RW (GPIO_ResetBits(RW_GPIO_PORT, RW_GPIO_PIN))
#define SET_RW (GPIO_SetBits(RW_GPIO_PORT, RW_GPIO_PIN))
#endif

#define CLR_EN (GPIO_ResetBits(EN_GPIO_PORT, EN_GPIO_PIN))
#define SET_EN (GPIO_SetBits(EN_GPIO_PORT, EN_GPIO_PIN))

/**

  • @brief LCD1602_GPIO_Init

  • @param None

  • @retval None
    */
    void LCD_GPIO_Init(void)
    { //GPIO初始化
    GPIO_InitTypeDef GPIO_InitStructure;

    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //禁用jtag,不然写入程序和程序执行都会受影响
    RCC_APB2PeriphClockCmd(DQ_GPIO_CLK, ENABLE);
    RCC_APB2PeriphClockCmd(RS_GPIO_CLK | EN_GPIO_CLK, ENABLE); //打开GPIO时钟
    #if RW_PIN
    RCC_APB2PeriphClockCmd(RW_GPIO_CLK, ENABLE); //打开GPIO时钟
    #endif
    /设置引脚模式为通用推挽输出/
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

    /*设置引脚速率为50MHz */
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_InitStructure.GPIO_Pin = RS_GPIO_PIN;
    GPIO_Init(RS_GPIO_PORT, &GPIO_InitStructure);

    #if RW_PIN
    GPIO_InitStructure.GPIO_Pin = RW_GPIO_PIN;
    GPIO_Init(RW_GPIO_PORT, &GPIO_InitStructure);
    #endif

    GPIO_InitStructure.GPIO_Pin = EN_GPIO_PIN;
    GPIO_Init(EN_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = DQ_GPIO_PIN;
    GPIO_Init(DQ_GPIO_PORT, &GPIO_InitStructure);
    }

/***********************************************
函数名称:LCD_DispStr
功 能:让液晶从某个位置起连续显示一个字符串
参 数:column–位置的列坐标
row–位置的行坐标
p_str–指向字符串存放位置的指针
返回值 :无
***********************************************/
void LCD_DispStr(uchar column, uchar row, char *p_str)
{
char *p_temp;
uchar i;
uchar n = 0;

p_temp = p_str;
while (*(p_str++) != '\0')
{
    n++; //计算字符串有效字符的个数
}
for (i = 0; i < n; i++)
{
    LCD_DispOneChar(column++, row, p_temp[i]);
    if (column == 0x10)
    {
        break;
    }
}

}

/*******************************************
函数名称:DispNchar
功 能:让液晶从某个位置起连续显示N个字符
参 数:column–位置的列坐标
row–位置的行坐标
n–字符个数
p_str–指向字符存放位置的指针
返回值 :无
********************************************/
void LCD_DispNChar(uchar column, uchar row, uchar n, char *p_str)
{
uchar i;

for (i = 0; i < n; i++)
{
    LCD_DispOneChar(column++, row, p_str[i]);
    if (column == 0x10)
    {
        column = 0;
        row ^= 1;
    }
}

}

/*******************************************
函数名称:LCD_DispOneChar
功 能:在某个位置显示一个字符
参 数:column–位置的列坐标
row–位置的行坐标
data–显示的字符数据
返回值 :无
********************************************/
void LCD_DispOneChar(uchar column, uchar row, char dat)
{
LCD_LocateXY(column, row);
LCD_WriteData(dat);
}

/*******************************************
函数名称:LCD_LocateXY
功 能:向液晶输入显示字符位置的坐标信息
参 数:column–位置的列坐标
row–位置的行坐标
返回值 :无
********************************************/
void LCD_LocateXY(uchar column, uchar row)
{
uchar temp;

temp = column & 0x0f;
row &= 0x01;
if (row)
{
    temp |= 0x40; //如果在第2行
}
temp |= 0x80;

LCD_WriteCommand(temp, 0);

}

/*******************************************
函数名称:LCD_Init
功 能:对1602液晶模块进行复位操作
参 数:无
返回值 :无
********************************************/
void LCD_Init()
{
//规定的复位操作
LCD_WriteCommand(0x38, 0);
DelayMs(5);
LCD_WriteCommand(0x38, 0);
DelayMs(5);
LCD_WriteCommand(0x38, 0);
DelayMs(5);

LCD_WriteCommand(0x38, 1); //显示模式设置
LCD_WriteCommand(0x08, 1); //显示关闭
LCD_WriteCommand(0x01, 1); //显示清屏
LCD_WriteCommand(0x06, 1); //写字符时整体不移动
LCD_WriteCommand(0x0c, 1); //显示开,不开游标,不闪烁

}

/------------------------------------------------
清屏函数
------------------------------------------------
/
void LCD_Clear()
{
LCD_WriteCommand(0x01, 0);
DelayMs(5);
}

/*******************************************
函数名称:LCD_SetCursor
功 能:让光标在某个位置闪烁
参 数:column–位置的列坐标
row–位置的行坐标
onFlag-- 1打开光标,0关闭光标
返回值 :无
********************************************/
void LCD_SetCursor(uchar column, uchar row, uchar onFlag)
{
if (onFlag == 1)
{
LCD_WriteCommand(0x0F, 0);
LCD_LocateXY(column, row);
}
}

/*******************************************
函数名称:LCD_WriteCommand
功 能:向液晶模块写入命令
参 数:cmd–命令,
chk–是否判忙的标志,1:判忙,0:不判
返回值 :无
********************************************/
void LCD_WriteCommand(uchar cmd, uchar chk)
{

if (chk)
{
    LCD_WaitForEnable(); // 检测忙信号?
}

SET_EN;
CLR_RS;

#if _RW_PIN_
    CLR_RW;
#endif

DelayUs(200);

GPIO_Write(DQ_GPIO_PORT, (cmd | (GPIO_ReadOutputData(DQ_GPIO_PORT) & (~DQ_GPIO_PIN))));

SET_EN; //产生使能脉冲信号
DelayUs(200);
CLR_EN;

}

/*******************************************
函数名称:LCD_WriteData
功 能:向液晶显示的当前地址写入显示数据
参 数:dat–显示字符数据
返回值 :无
********************************************/
void LCD_WriteData(uchar dat)
{
CLR_EN;
SET_RS;

#if _RW_PIN_
    CLR_RW;
#endif

GPIO_Write(DQ_GPIO_PORT, (dat | (GPIO_ReadOutputData(DQ_GPIO_PORT) & (~DQ_GPIO_PIN))));

DelayUs(200);
SET_EN;
//产生使能脉冲信号
DelayUs(200);
CLR_EN;

}

/*******************************************
函数名称:LCD_WaitForEnable
功 能:等待1602液晶完成内部操作
参 数:无
返回值 :无
********************************************/
static void LCD_WaitForEnable(void)
{
uint16_t later = 0;
GPIO_InitTypeDef GPIO_InitStructure;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;	//第七Pin的工作方式为浮空输入模式,用于检测LCD1602的忙状态
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = DQ_GPIO_PIN;
GPIO_Init( DQ_GPIO_PORT, &GPIO_InitStructure );

CLR_RS;

#if _RW_PIN_
    SET_RW;
#endif

DelayUs(200);
SET_EN;
DelayUs(200);
//    while((DataIn&BUSY)!=0);
while (((GPIO_ReadInputDataBit( DQ_GPIO_PORT, DQ_GPIO_PIN) & BUSY) != 0) && (later < 1000)) //检测忙标志
{
    DelayUs(10);
    later++;
}
CLR_EN;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//使DQ_GPIO_PORT的状态还原成推挽模式
GPIO_Init( DQ_GPIO_PORT, &GPIO_InitStructure );

}

Delay.c

#include “bsp_delay.h”
static uint32_t TimingDelay;
void wait(__IO uint32_t n)
{
while (n–) ;
}
void DelayUs10x(__IO uint32_t nTime)
{
TimingDelay = nTime; //10us

if (SysTick_Config(SystemCoreClock / 100000)) //定时10us
{
    while (1)
        ;
}
while (TimingDelay != 0)   ;
// 关闭SysTick定时器
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;

}
void DelayMs(__IO uint16_t nTime)
{
TimingDelay = nTime; //1ms

if (SysTick_Config(SystemCoreClock / 1000)) //定时1ms
{
    while (1)
        ;
}
while (TimingDelay != 0)
    ;
// 关闭SysTick定时器
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;

}
void TimingDelay_Decrement(void)
{
if (TimingDelay != 0x00)
{
TimingDelay–;
}
}
void SysTick_Handler(void)
{
TimingDelay_Decrement();
}

DS18B20
.c

#include
#include “ds18b20.h”
/*******************CPIO_Pin_4/

void Delay_DS18B20_1us(int num)
{
while(num–) ;
}

void DS18B20_IO_OUT_Pin_4(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);    

}

void DS18B20_IO_IN_Pin_4(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB, &GPIO_InitStructure);    

}

//复位DS18B20
void DS18B20_Rst_Pin_4(void)
{
DS18B20_IO_OUT_Pin_4(); //SET PA0 OUTPUT
DS18B20_DQ_OUT_Pin_4(0); //拉低DQ
Delay_DS18B20_1us(750); //拉低750us
DS18B20_DQ_OUT_Pin_4(1); //DQ=1
Delay_DS18B20_1us(15); //15US
}
//等待DS18B20的回应
//返回1:未检测到DS18B20的存在
//返回0:存在
u8 DS18B20_Check_Pin_4(void)
{
u8 retry=0;
DS18B20_IO_IN_Pin_4();//SET PA0 INPUT
while (DS18B20_DQ_IN_Pin_4&&retry<200)
{
retry++;
Delay_DS18B20_1us(1);
};
if(retry>=200)return 1;
else retry=0;
while (!DS18B20_DQ_IN_Pin_4&&retry<240)
{
retry++;
Delay_DS18B20_1us(1);
};
if(retry>=240)return 1;
return 0;
}
//从DS18B20读取一个位
//返回值:1/0
u8 DS18B20_Read_Bit_Pin_4(void) // read one bit
{
u8 data;
DS18B20_IO_OUT_Pin_4();//SET PA0 OUTPUT
DS18B20_DQ_OUT_Pin_4(0);
Delay_DS18B20_1us(2);
DS18B20_DQ_OUT_Pin_4(1);
DS18B20_IO_IN_Pin_4();//SET PA0 INPUT
Delay_DS18B20_1us(12);
if(DS18B20_DQ_IN_Pin_4)data=1;
else data=0;
Delay_DS18B20_1us(50);
return data;
}
//从DS18B20读取一个字节
//返回值:读到的数据
u8 DS18B20_Read_Byte_Pin_4(void) // read one byte
{
u8 i,j,dat;
dat=0;
for (i=1;i<=8;i++)
{
j=DS18B20_Read_Bit_Pin_4();
dat=(j<<7)|(dat>>1);
}
return dat;
}
//写一个字节到DS18B20
//dat:要写入的字节
void DS18B20_Write_Byte_Pin_4(u8 dat)
{
u8 j;
u8 testb;
DS18B20_IO_OUT_Pin_4();//SET PA0 OUTPUT;
for (j=1;j<=8;j++)
{
testb=dat&0x01;
dat=dat>>1;
if (testb)
{
DS18B20_DQ_OUT_Pin_4(0);// Write 1
Delay_DS18B20_1us(2);
DS18B20_DQ_OUT_Pin_4(1);
Delay_DS18B20_1us(60);
}
else
{
DS18B20_DQ_OUT_Pin_4(0);// Write 0
Delay_DS18B20_1us(60);
DS18B20_DQ_OUT_Pin_4(1);
Delay_DS18B20_1us(2);
}
}
}
//开始温度转换
void DS18B20_Start_Pin_4(void)// ds1820 start convert
{
DS18B20_Rst_Pin_4();
DS18B20_Check_Pin_4();
DS18B20_Write_Byte_Pin_4(0xcc);// skip rom
DS18B20_Write_Byte_Pin_4(0x44);// convert
}
//初始化DS18B20的IO口 DQ 同时检测DS的存在
//返回1:不存在
//返回0:存在
u8 DS18B20_Init_Pin_4(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);

DS18B20_Rst_Pin_4();

return DS18B20_Check_Pin_4();

}
//从ds18b20得到温度值
//精度:0.1C
//返回值:温度值 (-550~1250)
short DS18B20_Get_Temp_Pin(void)
{
u8 temp;
u8 TL,TH;
short tem;

DS18B20_Start_Pin_4 ();                    // ds1820 start convert
DS18B20_Rst_Pin_4();
DS18B20_Check_Pin_4();	 
DS18B20_Write_Byte_Pin_4(0xcc);// skip rom跳过rom
DS18B20_Write_Byte_Pin_4(0xbe);// convert	    
TL=DS18B20_Read_Byte_Pin_4(); // LSB   
TH=DS18B20_Read_Byte_Pin_4(); // MSB  高
    	  
if(TH>7)
{
    TH=~TH;
    TL=~TL; 
    temp=0;//温度为负  
}
else 
    temp=1;//温度为正

tem=TH; //获得高八位
tem<<=8;    
tem+=TL;//获得底八位
tem=(float)tem*0.625;//转换 

if(temp)
    return tem; //返回温度值
else 
    return -tem;    

}
##需要项目源码私我,视频讲解+word课设+仿真图+keil代码

你可能感兴趣的:(stm32,单片机,arm)