首页 > 其他分享 >HAL库开发--I2C的配置方式和读写操作

HAL库开发--I2C的配置方式和读写操作

时间:2024-06-16 19:28:59浏览次数:13  
标签:HAL u8 读写操作 -- void ADDR I2C PCF8563 cs2

知不足而奋进 望远山而前行


目录

知不足而奋进 望远山而前行​编辑

文章目录

前言

目标

内容

需求

I2C功能配置

I2C编码

移植PCF8563驱动

I2C读写实现

总结


前言

在嵌入式系统开发中,I2C(Inter-Integrated Circuit)总线是一种常见且重要的通信协议,用于连接多种外设和传感器,实现数据的可靠传输和控制。本文将详细介绍如何在STM32开发板上配置和操作I2C,以及如何利用I2C接口与PCF8563实时时钟(RTC)芯片进行通信。通过正确配置I2C通信参数和实现读写功能,我们能够有效地设置和读取PCF8563的时钟信息,从而在嵌入式应用中实现精确的时间管理功能。


目标

  1. 掌握I2C配置方式
  2. 掌握I2C读写操作

内容

需求

开发板中的PCF8563的RTC时钟设置和读取。

I2C功能配置

选择对应的I2C,打开。

配置完成后可以查询引脚是否符合要求。

I2C编码

移植PCF8563驱动
#ifndef __PCF8563_H__
#define __PCF8563_H__

#include "stm32f4xx.h"
#include "i2c.h"

#ifndef u8
#define u8 uint8_t
#endif

#ifndef u16
#define u16 uint16_t
#endif

// 如果定义了该宏,Alarm的相关函数才会被编译并启用,被注释掉就是禁用
//#define PCF8563_ALARM_ENABLE
// 如果定义了该宏,Timer的相关函数才会被编译并启用,被注释掉就是禁用
//#define PCF8563_TIMER_ENABLE

// 设备地址
#define		PCF8563_ADDR  0x51
// 存储地址:时间的存储地址开始位置
#define		PCF8563_REG_TD   0x02

// 控制寄存器2
#define		PCF8563_REG_CS2   0x01

// I2C写操作
#define 	I2C_WRITE(a, r, p, n)		I2C1_write(a, r, p, n)
// I2C读操作
#define 	I2C_READ(a, r, p, n)		I2C1_read(a, r, p, n)


// 秒、分、时、星期、日、月、年、世纪
// year = 2023, month = 2, day = 28, week = 4;
// hour = 23, minute = 59, second = 52;
typedef struct {

    u16 year;
    u8 month;
    u8 day;
    u8 week;
    u8 hour;
    u8 minute;
    u8 second;

} Clock_t;

// 警报、闹铃Alarm结构体
typedef struct {
    u8 minute;
    u8 hour;
    u8 day;
    u8 weekday;
} Alarm_t;

typedef enum {
	
	HZ4096 = 0,
	HZ64	 = 1,
	HZ1	 	 = 2,
	HZ1_60 = 3,
}TimerFreq;


void PCF8563_init();

void PCF8563_set_clock(Clock_t c);

void PCF8563_get_clock(Clock_t *c);

void PCF8563_set_alarm(Alarm_t alarm);
void PCF8563_enable_alarm();
void PCF8563_disable_alarm();
void PCF8563_alarm_clear_flag();

void PCF8563_set_timer(TimerFreq freq ,u8 countdown);
void PCF8563_enable_timer();
void PCF8563_disable_timer();
void PCF8563_timer_clear_flag();

#ifdef PCF8563_ALARM_ENABLE
extern void PCF8563_on_alarm();
#endif

#ifdef PCF8563_TIMER_ENABLE
extern void PCF8563_on_timer();
#endif

#endif
#include "PCF8563.h"
#include <stdio.h>

#define NUMBER_TD	7

void PCF8563_init() {

}

void PCF8563_set_clock(Clock_t c) {

    u8 p[NUMBER_TD];				// 用于存储时间字节数组
    u8 Cent; // 世纪:0代表本世纪20, 1代表下个世纪,年99->00时交替

    // 将数值转成一个字节表达 (BCD)

    // 秒:VL 1 1 1 - 0 0 0 0
    p[0] = ((c.second / 10) << 4) + (c.second % 10);

    // 分: X 1 1 1 - 0 0 0 0
    p[1] = ((c.minute / 10) << 4) + (c.minute % 10);

    // 时: X X 1 1 - 0 0 0 0
    p[2] = ((c.hour / 10) << 4) + (c.hour % 10);

    // 天: X X 1 1 - 0 0 0 0
    p[3] = ((c.day / 10) << 4) + (c.day % 10);

    // 周: X X X X - X 0 0 0
    p[4] = c.week;

    // 世纪 20xx , 21xx
    Cent = (c.year >= 2100 ? 1 : 0);
    // 月: C X X 1 - 0 0 0 0
    p[5] = (Cent << 7) + ((c.month / 10) << 4) + (c.month % 10);

    // 年: 1 1 1 1 - 0 0 0 0   2023, 2036
    p[6] = ((c.year % 100 / 10) << 4) + (c.year % 10);

    // 设置数据Write
    I2C_WRITE(PCF8563_ADDR, PCF8563_REG_TD, p, NUMBER_TD);
}

void PCF8563_get_clock(Clock_t *c) {

    u8 Cent; // 世纪:0代表本世纪20, 1代表下个世纪,年99->00时交替
    u8 p[NUMBER_TD];				// 用于存储时间字节数组
    // 循环读取Read: 秒、分、时、星期、日、月、年、世纪
    I2C_READ(PCF8563_ADDR, PCF8563_REG_TD, p, NUMBER_TD);

//			for(i = 0; i < NUMBER; i++){
//				printf("%d-> %d \n", (int)i, (int)p[i]);
//			}
//			printf("----------------\n");
    // 秒:VL 1 1 1 - 0 0 0 0 转成十进制
    c->second = ((p[0] >> 4) & 0x07) * 10 + (p[0] & 0x0F);

    // 分: X 1 1 1 - 0 0 0 0 转成十进制
    c->minute = ((p[1] >> 4) & 0x07) * 10 + (p[1] & 0x0F);

    // 时: X X 1 1 - 0 0 0 0 转成十进制
    c->hour	 = ((p[2] >> 4) & 0x03) * 10 + (p[2] & 0x0F);

    // 天: X X 1 1 - 0 0 0 0 转成十进制
    c->day 	 = ((p[3] >> 4) & 0x03) * 10 + (p[3] & 0x0F);

    // 周: X X X X - X 0 0 0 转成十进制
    c->week 	 = p[4] & 0x07;

    // 世纪
    // 月: C X X 1 - 0 0 0 0
    c->month	 = ((p[5] >> 4) & 0x01) * 10 + (p[5] & 0x0F);
    Cent 		 = p[5] >> 7;		// 0->20xx年 1->21xx年

    // 年: 1 1 1 1 - 0 0 0 0   1969, 2023
    c->year   = ((p[6] >> 4) & 0x0F) * 10 + (p[6] & 0x0F);
    c->year	+= Cent == 0 ? 2000 : 2100;
}


void PCF8563_set_alarm(Alarm_t alarm){
		// 想将某个类型关闭掉,传一个负数
		// 或者多传1个字段,低4位,根据01决定是否启动对应类型 0000 0011
	
    u8 a[4];
//		a = 0;	// 分钟
//		I2C_WRITE(PCF8563_ADDR, 0x09, &a, 1);
//
//		a = 0;	// 小时
//		I2C_WRITE(PCF8563_ADDR, 0x0A, &a, 1);

    // 分 M	1 1 1 - 0 0 0 0			enable->0, diabled->0x80
    a[0] = ((alarm.minute / 10) << 4) + (alarm.minute % 10) + 0;

    // 时 H	x 1 1 - 0 0 0 0			enable->0, diabled->0x80
    a[1] = ((alarm.hour / 10) << 4) + (alarm.hour % 10) + 0;

    // 天 D	x 1 1 - 0 0 0 0			enable->0, diabled->0x80
    a[2] = ((alarm.day / 10) << 4) + (alarm.day % 10) + 0;

    // 周 W x x x - x 0 0 0			enable->0, diabled->0x80
    a[3] = alarm.weekday + 0;

    I2C_WRITE(PCF8563_ADDR, 0x09, a, 4);
}

void PCF8563_enable_alarm(){
    u8 cs2 = 0; // 控制状态寄存器2
		// 配置 cs2 寄存器以启用Alarm -------------------------------------------------
    I2C_READ(PCF8563_ADDR, 0x01, &cs2, 1);
		// printf("cs2: %d \n", (int)cs2);
    // 清除Alarm标记,AF设置为0,下一次闹钟到点时,才能触发
    cs2 &= ~0x08;
    // 开启Alarm中断, AIE为1,启用Alarm闹钟
    cs2 |= 0x02;
    I2C_WRITE(PCF8563_ADDR, 0x01, &cs2, 1);
}
void PCF8563_disable_alarm(){
    u8 cs2 = 0; // 控制状态寄存器2
		// 配置 cs2 寄存器以启用Alarm -------------------------------------------------
    I2C_READ(PCF8563_ADDR, 0x01, &cs2, 1);
		// printf("cs2: %d \n", (int)cs2);
    // 清除Alarm标记,AF设置为0,下一次闹钟到点时,才能触发
    cs2 &= ~0x08;
    // 开启Alarm中断, AIE为0,禁用Alarm闹钟
    cs2 &= ~0x02;
    I2C_WRITE(PCF8563_ADDR, 0x01, &cs2, 1);
}
void PCF8563_alarm_clear_flag(){
		
    u8 cs2 = 0; // 控制状态寄存器2
    // 01H寄存器中AF位,会在Alarm触发时,被置为1.
    // 必须手动置为0,下一个Alarm才能触发。

    // 配置 cs2 寄存器
    I2C_READ(PCF8563_ADDR, 0x01, &cs2, 1);
//		printf("cs2: %d \n", (int)cs2);
    // 清除Alarm标记,AF设置为0,下一次闹钟到点时,才能触发
    cs2 &= ~0x08;
    I2C_WRITE(PCF8563_ADDR, 0x01, &cs2, 1);
}

void PCF8563_set_timer(TimerFreq freq ,u8 countdown){
		u8 p = 0;
		// 设置Timer运行频率、启用timer source clock
		p = 0x80 + freq; // 64Hz
		I2C_WRITE(PCF8563_ADDR, 0x0E, &p, 1);
	
		// 设置Timer计数值(每个周期)
		p = countdown;
		I2C_WRITE(PCF8563_ADDR, 0x0F, &p, 1);
	
}

void PCF8563_enable_timer(){
    u8 cs2 = 0; // 控制状态寄存器2
		// 通过cs2启用Timer
		I2C_READ(PCF8563_ADDR, 0x01, &cs2, 1);
		// 开启Timer中断:	 TIE
		cs2 |=  0x01;
		// 清除Timer flag; TF
		cs2 &= ~0x04;
		I2C_WRITE(PCF8563_ADDR, 0x01, &cs2, 1);
}

void PCF8563_disable_timer(){
    u8 cs2 = 0; // 控制状态寄存器2
		// 通过cs2启用Timer
		I2C_READ(PCF8563_ADDR, 0x01, &cs2, 1);
		// 开启Timer中断:	 TIE
		cs2 &= ~0x01;
		// 清除Timer flag; TF
		cs2 &= ~0x04;
		I2C_WRITE(PCF8563_ADDR, 0x01, &cs2, 1);
}

void PCF8563_timer_clear_flag(){
    u8 cs2 = 0; // 控制状态寄存器2
		// 通过cs2启用Timer
		I2C_READ(PCF8563_ADDR, 0x01, &cs2, 1);
		// 清除Timer flag; TF
		cs2 &= ~0x04;
		I2C_WRITE(PCF8563_ADDR, 0x01, &cs2, 1);
}


// 当中断触发时,此函数会执行(Alarm、Timer)
void ext_int3_call() {
		u8 cs2;
		I2C_READ(PCF8563_ADDR, PCF8563_REG_CS2, &cs2, 1);
//		printf("cs2: %d \n", (int) cs2);
	
		
#ifdef PCF8563_TIMER_ENABLE
		// Alarm Flag && AIE
		if((cs2 & 0x08) && (cs2 & 0x02)){
				// 清除Alarm标记,避免下次无法触发
				PCF8563_alarm_clear_flag();
				// 事件触发
				PCF8563_on_alarm();
		}
#endif
		
#ifdef PCF8563_TIMER_ENABLE
		// Timer Flag && TIE
		if((cs2 & 0x04) && (cs2 & 0x01)){
				// 清除Timer标记,避免下次无法触发
				PCF8563_timer_clear_flag();
				// 事件触发
				PCF8563_on_timer();
		}
#endif
}

只需要实现 i2c_write和i2c_read,驱动就可以正常运行。

/* USER CODE BEGIN WHILE */
// pcf8563初始化
PCF8563_init();

Clock_t c;
c.year = 2023;
c.month = 3;
c.day = 10;
c.week = 2;
c.hour = 23;
c.minute = 59;
c.second = 55;
PCF8563_set_clock(c);

while (1)
{
    PCF8563_get_clock(&c);
    printf("%d-%d-%d %d %d:%d:%d\r\n", c.year, c.month, c.day, c.week, c.hour, c.minute,c.second);
    HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
I2C读写实现

添加头定义

/* USER CODE BEGIN Prototypes */
uint8_t I2C1_write(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t len);
uint8_t I2C1_read(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t len);

/* USER CODE END Prototypes */

添加c实现

/* USER CODE BEGIN 1 */
#include "string.h"
uint8_t I2C1_write(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t len) {
	uint8_t write_addr = addr << 1;
    // uint8_t buff[len + 1];
    // buff[0] = reg;
    // memcpy(&buff[1], data, len);
    // HAL_I2C_Master_Transmit(&hi2c1, addr << 1, buff, len + 1, HAL_MAX_DELAY);
	return HAL_I2C_Mem_Write(&hi2c1, write_addr, reg, I2C_MEMADD_SIZE_8BIT, data, len, HAL_MAX_DELAY);
}
uint8_t I2C1_read(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t len) {
	uint8_t write_addr = addr << 1;
	uint8_t read_addr = (addr << 1) | 1; // 0xA3
    // HAL_I2C_Master_Transmit(&hi2c1, addr << 1, &reg, 1, HAL_MAX_DELAY);
    // HAL_I2C_Master_Receive(&hi2c1, (addr << 1) | 0x01, data, len, HAL_MAX_DELAY);
	return HAL_I2C_Mem_Read(&hi2c1, read_addr, reg, I2C_MEMADD_SIZE_8BIT, data, len, HAL_MAX_DELAY);
}
/* USER CODE END 1 */
  • HAL库的设备地址,要的是具体的读写地址。
  • HAL库中的写操作中的数据,包含了寄存器地址。
  • HAL库中读操作,需要先写再读。
  • 返回值不是读写的数据,而是读写成功与否的枚举值

总结

本文详细介绍了在STM32微控制器上配置和使用I2C接口的方法,特别是与PCF8563实时时钟芯片的集成。首先,我们通过HAL库选择并打开了适当的I2C通道,并确保引脚配置符合要求。随后,我们实现了简单而有效的I2C读写函数(i2c_write和i2c_read),并将其用于移植PCF8563驱动,实现对RTC的初始化、设置和读取操作。

在编码实现中,我们注意到HAL库提供了方便的API来管理I2C的传输和操作,包括单字节和多字节的读写功能。通过这些步骤,我们成功地将PCF8563的时钟设置为特定日期和时间,并能够周期性地读取当前时钟值并输出到终端。这种能力对于许多嵌入式应用而言至关重要,特别是需要精确时间管理的系统。

通过学习本文所述的方法和实现步骤,开发人员不仅能够掌握STM32上I2C接口的基本配置和操作,还能在此基础上进行更复杂的外设集成和应用开发。有效的I2C通信管理和PCF8563驱动移植,为嵌入式系统的时钟管理提供了可靠的解决方案,有助于提高系统的稳定性和功能性。

标签:HAL,u8,读写操作,--,void,ADDR,I2C,PCF8563,cs2
From: https://blog.csdn.net/xuewenyu_/article/details/139724905

相关文章

  • 基于Typora、Gitee和picgo搭建图床
    基于Typora、Gitee和picgo搭建图床使用Typora编辑文本上传图片的时候,会发现图片都是保存在本地的,如果上传到博客图片会显示不出来,还需要自己手动一张一张往上贴,怎么解决?(1)首先下载一个picgo链接:https://pan.baidu.com/s/1Uf5BH7EegbhcLJ-CwUpceQ?pwd=ezta提取码:ezta......
  • python如何判断字符串不为空格
    1、使用字符串长度判断。len(s)==0 则字符串为空if len(username) ==0 or len(password) == 0:  #判断输入的用户名或密码是否为空    print('用户名或密码不能为空')2、isspace判断是否字符串全部是空格。s.isspace()==Trueif username.isspace(......
  • 【Python】高阶函数
    高阶函数高阶函数是接受另一个函数作为参数,并返回一个函数作为结果的函数。这种类型的函数是函数式编程的核心,因为它们允许对行为进行抽象和复用,使得代码更加简洁和灵活。defapply_function(func,value):returnfunc(value)defsquare(x):returnx*xpr......
  • python做的游戏有哪些
    比较大型的,使用Python的游戏有两个,一个是《EVE》,还有一个是《文明》。另外GitHub上有很多开源的小游戏,下面给大家介绍一下:1.Github上面有个项目FreePythonGames,里面集合了不少的Python开发的小游戏,能玩,也适合新手用来练练手,另外PyGame这个网站里面里面集合了很多Python......
  • 端口服务
    端口服务信息扫描的思路一个服务一个端口本机端口​ Windows​ netstat-aon|findstr3306​ LInux​ netstat-an|grep3306远程机器端口​telnet192.168.142.13780​ wget192.168.142.13780​ nc-vz192.168.142.137445​ nc-vz192.168.1......
  • Centos 7 Docker 安装
    1、设置主机网络,关闭防火墙,selinux等[root@localhost~]#cat/etc/sysconfig/network-scripts/ifcfg-ens32TYPE=EthernetBOOTPROTO=staticNAME=ens32DEVICE=ens32ONBOOT=yesIPADDR=192.168.xxx.10NETMASK=255.255.255.0GATEWAY=192.168.xxx.2DNS1=192.168.xxx.2DNS......
  • 3.7每日总结
    所花时间:7h代码量:600博客量:1了解的知识点:    记事本packagecom.example.myapplication; importandroid.annotation.SuppressLint;importandroid.content.Intent;importandroid.database.Cursor;importandroid.database.sqlite.SQLiteDatabase;importandroid......
  • 裸函数和调用约定
    一、裸函数在正常的函数编译中,即使函数没有定义函数体的内容,编译器也依然会编译出部分汇编指令用来执行函数。但是如果定义一个裸函数void_declspec(naked)test()编译器将不会操作这个函数,不会给其生成汇编指令(但是会在主函数中生成call和jmp指令指向这个裸函数)可以看到......
  • QtCreator CMakeLists.txt添加模块(Modules)
    修改以下位置,添加模块...set(CMAKE_CXX_STANDARD20)#设置C++标准#查找Qt6find_package(QTNAMESQt6Qt5REQUIREDCOMPONENTSWidgets**Multimedia**)find_package(Qt${QT_VERSION_MAJOR}REQUIREDCOMPONENTSWidgets**Multimedia**)...#链接Qt6模块和库target_l......
  • Nivdia向量数据库图检索最新标杆——CAGRA
    本文连接:https://wanger-sjtu.github.io/CARGA/CAGRA 是N社在RAFT项目中最新的ANN向量索引。这是一种高性能的、GPU加速的、基于图的方法,尤其是针对小批量情况进行了优化,其中每次查找只包含一个或几个查询向量。与其他像HNSW、SONG等这类基于图的方法相似,CAGRA在索引训练......