[TOC]

STM32CubeMX-硬件IIC读取AT24C02(阻塞、中断、DMA三种方式)

教程包含通用步骤以及专用步骤,其中,通用步骤为STM32CubeMX配置其他外设工程的通用操作,STM32CubeMX系列笔记基本通用,专用操作则是针对当前工程进行的配置

一、初始准备

1.硬件平台

使用正点原子STM32F4探索者

IMG_20210207_234256

2.软件平台

STM32CubeMX软件平台 V6.2.1

Keil5软件平台 V5.32

STM32CubeProgrammer下载平台

20210207234636

二、操作步骤

1.CubeMX生成初始化代码

1.1 建立工程(通用步骤)

  • 芯片选择

打开cube软件,选择从芯片来创建工程,一般开发都是使用这个来开发,有的时候也可能使用另外两个,但不多,第二个基于ST提供的开发板创建工程,针对性高,第三个则选择ST提供的例程来创建工程

20210519191302

F4探索者的主控为STM32F407ZET6,所以在搜索框找到STM32F407ZE后点击具体芯片,再开始工程

20210208000009

  • 配置时钟源

我们点开SystemCore(系统内核设置),再点击RCC配置HSE和LSE时钟源,这里我都选择使用外部时钟,配置后,我们可以看到右边芯片引脚分配图的两个时钟源引脚点亮,表示时钟配置为外部源

20210519191953

  • 配置时钟树

我们进入ClockConfiguration配置时钟树,使时钟的输入路径和大小符合我们预期,探索者的晶振和时钟倍频如下

20210208003321

一般配置正确时颜色蓝白为主,配置错误时则会出现紫色,提示我们要修改值

20210208003622

具体时钟树的了解可以看我很久之前的文章,有做一些分析

CSDN文章链接-时钟树分析

1.2 IIC配置步骤


前言:IIC是一种串行通信总线,一般用于短距离设备间通信,是一种同步通信方式,具体协议参考之前的博客:IIC协议讲解

补充知识:
同步是指:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式。
异步是指:发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。
同步是阻塞模式,异步是非阻塞模式。
其中SPI、IIC为同步通信 UART为异步通信

关于ST硬件IIC的坊间小传言:

大家在学习正点原子F1的时候原子哥说过,ST的F1的IIC设计的有BUG,容易出现通信问题,所以不推荐使用,但许多人说用起来没感觉,数据传输很正常,没什么问题,我查询了相关文档,发现这个不是空穴来风,ST公司出过STM32F1的勘误手册,上面有提到STM32F10X的IIC在通信时会发生一些错误,CSDN上也有人遇到过硬件IIC BUG的地方STM32硬件IIC的BUG问题;不过这个也是有一定的概率,大多数情况下应该不会出问题,而且我使用的是F4,我也看了STM32F4的勘误表,相对于STM32F1的IIC,IIC外设的BUG少了很多,基本可以稳定运行!!!勘误表IIC部分截图如下,具体两个文档我整理发布到CSDN了:0积分下载链接;这里的文档在ST官网也可以下载的哦,需要可以自行去下载!

  • STM32F10X勘误表

20210524180215

  • STM32F40X勘误表

20210524180809


1.2.1 引脚选择

查看正点原子探索者F4原理图

AT24C02原理图如下:

20210524165255

对应芯片的连接引脚如下,IO口可以复用为硬件IIC1,来和AT24C02进行通信!

20210524165359

1.2.2 开启IIC

点击Connectivity后选择I2C1,使能I2C模式

20210524131552

1.2.3 设置IIC

配置界面选项如下:

20210524194840

对应功能:

  • I2C Speed Mode: IIC模式设置 快速模式和标准模式。标准模式-速率上限为 100kb/s;快速模式-速率上限为 400kb/s,我们使用标准模式,快速模式对硬件设计要求很高
  • I2C Clock Speed:I2C传输速率,默认为100KHz,我们使用默认
  • Clock No Stretch Mode: 时钟没有扩展模式(时钟拉伸clock stretching通过将SCL线拉低来暂停一个传输.直到释放SCL线为高电平,传输才继续进行.clock stretching是可选的,实际上大多数从设备不包括SCL驱动,所以它们不能stretch时钟)

  • Primary Address Length selection: 从设备地址长度 一般为7位,通讯时7位地址+1位读写做开头;

  • Dual Address Acknowledged: 双地址确认,当主地址是7位长度时,我们可以有一个双地址

  • Primary slave address:  从设备初始地址(地址值从0到127,且生成的地址值左移1位

  • General call address detecion:一般呼叫地址检测,关闭不需要

对应功能的具体配置按照上图来选择,设置完成可以看到对应IO口变绿

20210524202129

但此时IO口,并不是我们需要的IO口,原理图上IO口引脚对应的为PB8、PB9,所以我们需要改变一下复用IO口,直接在芯片引脚上选择就可以了;

20210525114806

补充配置1:到这里硬件IIC已经可以使用阻塞延迟发送了,如果需要开启中断方式发送,这要在开启一下中断,配置如下

20210525115104

同时配置中断优先级

20210525115232

补充配置2:如果在需要开启DMA的话,需要在加上如下步骤,这样代码里面就可以使用DMA发送接收了

20210524232209

以上就是IIC在STM32Cube的配置步骤

1.3 串口配置

配置串口用于与上位机通讯,方便检测IIC读写AT24C02有没有成功,具体配置和使用教程可以看我之前的文章:STM32CubeMX-串口中断实验

1.4 生成代码(通用步骤)

点击进入Project Manager 配置生成工程的名字,存储路径(不要有中文)以及编译器,这里我们选MDK-ARM(Keil被收购后改名)

20210519193401

配置生成选项,主要为下面三大块,第一个我们选择只拷贝必要的库,第二个选择为每个外设生成.c和.h文件,保存之前的用户代码,以及删除之前的生成代码,第三个不选择

PS:用户代码段是一下注释之间的代码,只有原始的用户代码段注释才有效,用户自己添加的无效

1
2
3
/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

20210519193757

最后点击生成代码

20210208004314

2.编写代码

首先介绍一下IIC的驱动代码

IIC主要函数和串口等通讯协议主要函数基本相同,一个是发送,一个是接受,在HAL库中,发送和读取主要有三个方式,第一种读写是超时读写,第二中是中断读写,第三个是DMA中断读写,其中第一种阻塞方式发送,CPU资源占有较大,后面两种与中断结合发送接受,CPU资源使用较少,常用函数接口代码介绍如下:

1
2
3
4
5
6
7
//阻塞IIC发送、接受代码原型
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);

HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout);

HAL_I2C_Master_Transmit()主模式下以阻塞模式传输大量数据。

  • 传入参数:hi2c—使用的IIC设备
  • DevAddress—从机地址
  • pData—写入数据的地址
  • Size—写入数据长度
  • Timeout—传输时间上限

HAL_I2C_Master_Receive()主模式下以阻塞模式接受大量数据。

  • 传入参数:hi2c—使用的IIC设备
  • DevAddress—从机地址
  • pData—读入数据存储首地址
  • Size—读取数据长度
  • Timeout—读取时间上限

HAL_I2C_Mem_Write()将阻塞模式下的大量数据写入从机特定的内存地址

  • 传入参数:hi2c—使用的IIC设备

  • DevAddress—从机地址

  • MemAddress—写入内存首地址(写入过程中会自加)

  • MemAddSize—写入内存的大小(8位或16位)

  • pData—指向数据缓冲区的指针

  • Size—要发送的数据量

  • Timeout—超时持续时间

HAL_I2C_Mem_Read()将阻塞模式下的大量数据写入从机特定的内存地址

  • 传入参数:hi2c—使用的IIC设备

  • DevAddress—从机地址

  • MemAddress—读取内存首地址(读取过程中会自加)

  • MemAddSize—读取内存的大小(8位或16位)

  • pData—指向数据缓冲区的指针

  • Size—要发送的数据量

  • Timeout—超时持续时间

HAL_I2C_IsDeviceReady()检查目标设备是否准备好通信

  • 传入参数:hi2c—使用的IIC设备
  • DevAddress—从机地址
  • Trials—测试次数
  • Timeout—超时持续时间
1
2
3
4
5
6
//非阻塞普通中断IIC发送、接受代码原型
HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);

HAL_StatusTypeDef HAL_I2C_Mem_Write_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Mem_Read_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size);

传入参数与上面相同,只是少了一个超时时间

1
2
3
4
5
6
//非阻塞DMA中断IIC发送、接受代码原型
HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);

HAL_StatusTypeDef HAL_I2C_Mem_Write_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Mem_Read_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size);

了解以上IIC的调用接口后,我们来测试AT24C02代码,首先打开工程:

在main.c头部插入以下代码

1
2
3
4
5
6
7
8
9
10
11
/* USER CODE BEGIN PV */
//读写地址
#define AT24C02_Write 0xA0
#define AT24C02_Read 0xA1
//三次写入的字符串
unsigned char str1[]={"jeck666"};
unsigned char str2[]={"1234567"};
unsigned char str3[]={"abcdefg"};
//读取缓存区
uint8_t ReadBuffer[50];
/* USER CODE END PV */

main初始化后代码段插入以下,代码功能:以三种不同的功能,对24C02进行不同数据读写;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 /* USER CODE BEGIN 2 */
HAL_UART_Transmit_IT(&huart1,"Init Ok!\r\n",sizeof("Init Ok!\r\n"));
HAL_Delay(100);
//阻塞方式写入读取
if(HAL_I2C_Mem_Write(&hi2c1,AT24C02_Write,0,I2C_MEMADD_SIZE_8BIT,str1,sizeof(str1),1000)==HAL_OK)
HAL_UART_Transmit_IT(&huart1,"STR1_Write_OK\r\n",sizeof("STR1_Write_OK\r\n"));
HAL_Delay(1000);
HAL_I2C_Mem_Read(&hi2c1,AT24C02_Read,0,I2C_MEMADD_SIZE_8BIT,ReadBuffer,sizeof(str1),1000);
HAL_Delay(1000);
HAL_UART_Transmit_IT(&huart1,ReadBuffer,sizeof(str1));
HAL_Delay(1000);
//中断方式写入读取
if(HAL_I2C_Mem_Write_IT(&hi2c1,AT24C02_Write,0,I2C_MEMADD_SIZE_8BIT,str2,sizeof(str2))==HAL_OK)
HAL_UART_Transmit_IT(&huart1,"STR2_Write_OK\r\n",sizeof("STR2_Write_OK\r\n"));
HAL_Delay(1000);
HAL_I2C_Mem_Read_IT(&hi2c1,AT24C02_Read,0,I2C_MEMADD_SIZE_8BIT,ReadBuffer,sizeof(str2));
HAL_Delay(1000);
HAL_UART_Transmit_IT(&huart1,ReadBuffer,sizeof(str2));
HAL_Delay(1000);
//DMA中断方式写入读取
if(HAL_I2C_Mem_Write_DMA(&hi2c1,AT24C02_Write,0,I2C_MEMADD_SIZE_8BIT,str3,sizeof(str3))==HAL_OK)
HAL_UART_Transmit_IT(&huart1,"STR3_Write_OK\r\n",sizeof("STR3_Write_OK\r\n"));
HAL_Delay(1000);
HAL_I2C_Mem_Read_DMA(&hi2c1,AT24C02_Read,0,I2C_MEMADD_SIZE_8BIT,ReadBuffer,sizeof(str3));
HAL_Delay(1000);
HAL_UART_Transmit_IT(&huart1,ReadBuffer,sizeof(str3));
HAL_Delay(1000);
/* USER CODE END 2 */

3.程序下载,观察现象(通用步骤)

程序下载我一般用两种方式:

第一种是使用MDK自带的下载环境下载程序,我们给单片机连接ST-Link后配置下载,点击魔术棒,选择debug

20210208181431

选择ST-link后,点击setting

20210208181619

添加对应F4的Flash

20210208181825

keil界面点击下载

20210208182113

第二种是使用Stm32Programmer下载软件,该下载软件下载方式多,下载快,下面我使用st-link下载

打开软件,点击connect左边选择stlink后再点击connect连接下载器

20210208182703

点击open file,找到工程路径下MDK文件夹下工程生成的hex文件

20210208182650

之后点击downlod下载,下载结果如下

20210208182721

3.实验现象

通过串口助手进行调试,上电复位后显示三次读写的值,但此处DMA方式存在一个BUG,写入的第一个字节为0,具体原因还在研究中,可能是AT24C02写入间隔时间过短,至少要求间隔5ms,不过由于是测试实验,基本满足使用要求

IIC

wechat