数字温度计

实验任务

阅读资料了解 STM32F103的RTC(实时时钟)原理,使用带SPI或IIC接口的OLED屏显模块实现以下功能:

  1. 读取STM32F103C8T6 内部的时钟(年月日时分秒),日历(星期x),1秒周期,通过串口输出到PC上位机,;

  2. 读取AHT20的温度和湿度,通过OLED,把年月份时分秒、日历和实时温度、湿度显示出来,2秒周期。

RTC简介

一、RTC简介
1、RTC实时时钟特征与原理
RTC (Real Time Clock):实时时钟
  实时时钟是一个独立的定时器。RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
  RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后, RTC的设置和时间维持不变。
  系统复位后,对后备寄存器和RTC的访问被禁止,这是为了防止对后备区域(BKP)的意外写操作。执行以下操作将使能对后备寄存器和RTC的访问:

设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟
设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。
2、RTC组成
APB1接口:用来和APB1总线相连。通过APB1接口可以访问RTC的相关寄存器(预分频值,计数器值,闹钟值)。
RTC核心:由一组可编程计数器组成。分两个主要模块。
第一个是RTC预分频模块,它可以编程产生最长1秒的RTC时间基TR_CLK。如果设置了秒中断允许位,可以产生秒中断。
第二个是32位的可编程计数器,可被初始化为当前时间。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时间相比,当匹配时候如果设置了闹钟中断允许位,可以产生闹钟中断。

在这里插入图片描述
RTC内核完全独立于APB1接口,软件通过APB1接口对RTC相关寄存器访问。但是相关寄存器只在RTC APB1时钟进行重新同步的RTC时钟的上升沿被更新。所以软件必须先等待寄存器同步标志位(RTC_CRL的RSF位)被硬件置1才读。

实验代码

OLED模块代码编写
在工程中导入OLED相关文件:OLED.h、OLED.c、OLED_Font.h。
OLED.h中声明了引脚的初始化函数以及各种函数,用于显示不同的数据类型:

#ifndef __OLED_H
#define __OLED_H

void OLED_Init(void); // OLED初始化
void OLED_Clear(void); // 清屏
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char); // 指定位置显示一个字符
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String); // 指定位置显示一个字符串
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length); // 指定位置显示一个数字
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length); // 指定位置显示一个有符号数字
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length); // 指定位置显示一个十六进制数
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length); // 指定位置显示一个二进制数

#endif


在OLED_I2C_Init()函数中,我们指定PB8作为SCL,PB9作为SDA

/*引脚配置*/
#define OLED_W_SCL(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))

GPIO_InitTypeDef GPIO_InitStructure;

/*引脚初始化*/
void OLED_I2C_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	OLED_W_SCL(1);
	OLED_W_SDA(1);
}在这里插入代码片

I2C协议读取AHT20模块
本次实验将采用软件I2C进行温湿度的采集。
AHT20模块相关信息和代码请到官网下载
下面是部分函数的介绍:
文件中定义了两个变量:T1和H1,分别存放温度和湿度的数据。

uint32_t  H1=0;  //Humility
uint32_t  T1=0;  //Temperature

AHT20芯片的使用过程 read_AHT20_once函数:

void  read_AHT20_once(void)
{
	delay_ms(10);

	reset_AHT20();//重置AHT20芯片
	delay_ms(10);

	init_AHT20();//初始化AHT20芯片
	delay_ms(10);

	startMeasure_AHT20();//开始测试AHT20芯片
	delay_ms(80);

	read_AHT20();//读取AHT20采集的到的数据
	delay_ms(5);
}

AHT20芯片读取数据 read_AHT20函数,由于本实验需要在OLED上显示数据,因此在该函数中进行了部分修改,也就是把得到的数据显示到OLED上。

void read_AHT20(void)
{
	uint8_t   i;

	for(i=0; i<6; i++)
	{
		readByte[i]=0;
	}
	I2C_Start();//I2C启动

	I2C_WriteByte(0x71);//I2C写数据
	ack_status = Receive_ACK();//收到的应答信息
	readByte[0]= I2C_ReadByte();//I2C读取数据
	Send_ACK();//发送应答信息

	readByte[1]= I2C_ReadByte();
	Send_ACK();

	readByte[2]= I2C_ReadByte();
	Send_ACK();

	readByte[3]= I2C_ReadByte();
	Send_ACK();

	readByte[4]= I2C_ReadByte();
	Send_ACK();

	readByte[5]= I2C_ReadByte();
	SendNot_Ack();
	//Send_ACK();

	I2C_Stop();//I2C停止函数
	//判断读取到的第一个字节是不是0x08,0x08是该芯片读取流程中规定的,如果读取过程没有问题,就对读到的数据进行相应的处理
	if( (readByte[0] & 0x68) == 0x08 )
	{
		H1 = readByte[1];
		H1 = (H1<<8) | readByte[2];
		H1 = (H1<<8) | readByte[3];
		H1 = H1>>4;

		H1 = (H1*1000)/1024/1024;

		T1 = readByte[3];
		T1 = T1 & 0x0000000F;
		T1 = (T1<<8) | readByte[4];
		T1 = (T1<<8) | readByte[5];

		T1 = (T1*2000)/1024/1024 - 500;

		AHT20_OutData[0] = (H1>>8) & 0x000000FF;
		AHT20_OutData[1] = H1 & 0x000000FF;

		AHT20_OutData[2] = (T1>>8) & 0x000000FF;
		AHT20_OutData[3] = T1 & 0x000000FF;
	}
	else
	{
		AHT20_OutData[0] = 0xFF;
		AHT20_OutData[1] = 0xFF;

		AHT20_OutData[2] = 0xFF;
		AHT20_OutData[3] = 0xFF;
		printf("读取失败!!!");
	}
	
    // 下面是在OLED上显示数据
    
    OLED_ShowString(1, 1, "Temperature:");
	OLED_ShowNum(1, 13, T1/100, 1);
	OLED_ShowNum(1, 14, (T1/10)%10, 1);
	OLED_ShowChar(1, 15, '.');
	OLED_ShowNum(1, 16, T1%10, 1);
	
	OLED_ShowString(2, 1, "Humidity:");
	OLED_ShowNum(2, 10, H1/100, 1);
	OLED_ShowNum(2, 11, (H1/10)%10, 1);
	OLED_ShowChar(2, 12, '.');
	OLED_ShowNum(2, 13, H1%10, 1);
}

RTC时钟代码编写
创建两个文件:MyRTC.c,MyRTC.h,进行实时时钟的计算和显示。

#include "stm32f10x.h"                  // Device header
#include <time.h>
#include "OLED.h"

uint16_t MyRTC_Time[] = {2023, 11, 21, 14, 54, 00};

void MyRTC_SetTime(void);


char* weekday;
uint16_t iWeek;

// 根据日期计算星期
void CalculateWeekDay(uint16_t year, uint16_t month, uint16_t day) {
	if (month == 1 || month ==2) 
    {
        month +=12;
        year--;
    }
	iWeek = (day + 2 * month + 3 * (month + 1)/5 + year + year/4 - year/100 + year/400) % 7;
	switch (iWeek)
    {
		case 0: weekday = "Monday"; break;
     	case 1: weekday = "Teusday"; break;
     	case 2: weekday = "Wednesday"; break;
     	case 3: weekday = "Thursday"; break;
     	case 4: weekday = "Friday"; break;
     	case 5: weekday = "Saturday"; break;
     	case 6: weekday = "Sunday"; break;
		default: break;
	}
}

// 初始化函数
void MyRTC_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
	
	PWR_BackupAccessCmd(ENABLE);
	
	if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
	{
		RCC_LSEConfig(RCC_LSE_ON);
		while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
		
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
		RCC_RTCCLKCmd(ENABLE);
		
		RTC_WaitForSynchro();
		RTC_WaitForLastTask();
		
		RTC_SetPrescaler(32768 - 1);
		RTC_WaitForLastTask();
		
		MyRTC_SetTime();
		
		BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
	}
	else
	{
		RTC_WaitForSynchro();
		RTC_WaitForLastTask();
	}
}

void MyRTC_SetTime(void)
{
	time_t time_cnt;
	struct tm time_date;
	
	time_date.tm_year = MyRTC_Time[0] - 1900;
	time_date.tm_mon = MyRTC_Time[1] - 1;
	time_date.tm_mday = MyRTC_Time[2];
	time_date.tm_hour = MyRTC_Time[3];
	time_date.tm_min = MyRTC_Time[4];
	time_date.tm_sec = MyRTC_Time[5];
	
	time_cnt = mktime(&time_date) - 8 * 60 * 60;
	
	RTC_SetCounter(time_cnt);
	RTC_WaitForLastTask();
}

// 读取时钟并显示
void MyRTC_ReadTime(void)
{
	time_t time_cnt;
	struct tm time_date;
	
	time_cnt = RTC_GetCounter() + 8 * 60 * 60;
	
	time_date = *localtime(&time_cnt);
	
	MyRTC_Time[0] = time_date.tm_year + 1900;
	MyRTC_Time[1] = time_date.tm_mon + 1;
	MyRTC_Time[2] = time_date.tm_mday;
	MyRTC_Time[3] = time_date.tm_hour;
	MyRTC_Time[4] = time_date.tm_min;
	MyRTC_Time[5] = time_date.tm_sec;
	
	CalculateWeekDay(MyRTC_Time[0], MyRTC_Time[1], MyRTC_Time[2]);
	
	// 下面把数据显示到OLED上	
    
	OLED_ShowNum(3, 1, MyRTC_Time[0], 4);
	OLED_ShowChar(3, 5, '-');
	OLED_ShowNum(3, 6, MyRTC_Time[1], 2);
	OLED_ShowChar(3, 8, '-');
	OLED_ShowNum(3, 9, MyRTC_Time[2], 2);
	
	OLED_ShowNum(4, 1, MyRTC_Time[3], 2);
	OLED_ShowChar(4, 3, ':');
	OLED_ShowNum(4, 4, MyRTC_Time[4], 2);
	OLED_ShowChar(4, 6, ':');
	OLED_ShowNum(4, 7, MyRTC_Time[5], 2);
	
	OLED_ShowString(4, 10, weekday);
}

OLED.h:

#ifndef __MYRTC_H
#define __MYRTC_H

extern uint16_t MyRTC_Time[];

void MyRTC_Init(void);
void MyRTC_SetTime(void);
void MyRTC_ReadTime(void);

#endif

以上就可以实现实时读取时钟并显示
main.c 代码编写

#include "delay.h"
#include "usart.h"
#include "bsp_i2c.h"
#include "OLED.h"
#include "MyRTC.h"

int main(void)
{	
	delay_init(); 
	uart_init(9600);
	OLED_Init();
	MyRTC_Init();
	IIC_Init();
	
	MyRTC_SetTime();
	while(1)
	{
		MyRTC_ReadTime(); // 读取时钟
		read_AHT20_once(); // 读取AHT20
	}
}

实验效果

在这里插入图片描述

实验总结

RTC模块配置完毕,我就能通过读取和设置寄存器来访问RTC的日期和时间。读取日期和时间的方法是读取相应的寄存器值,然后将其转换为易于理解的格式。比如,我可以读取RTC_DR寄存器来获取当前日历日期,通过读取RTC_TR寄存器来获取当前时钟时间,时钟部分秒数不准确,有时过了几秒钟OLED的显示才增加1秒,有时又会一次增加2秒。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值