IIC通信
《用CubeMX学习STM32》
注释 点击上面蓝字进入完整专栏,这个系列所有文章都会整合到这个专栏
6、STM32 I2C通信
前言: IIC协议简介
维基百科的解释如下: I2C(Inter-Integrated Circuit)字面上的意思是积体电路之间,它其实是I2C Bus简称,所以中文应该叫集成电路总线,它是一种串行通讯汇流排,使用多主从架构,由飞利浦公司在1980年代为了让主机板、嵌入式系统或手机用以连接低速周边装置而发展。I2C的正确读法为“I平方C”("I-squared-C"),而“I二C”("I-two-C")则是另一种错误但被广泛使用的读法。自2006年10月1日起,使用I2C协定已经不需要支付专利费,但制造商仍然需要付费以取得I2C从属装置位址。
我们需要了解的是 : I2C通信只需要两根线即可进行数据传输; 即串行数据线(SDA::Serial DAta)和串行时钟线(SCL::Serial CLock); IIC总线具有多主设备的优点, 就是有多个主机进行数据发送并且不会有混乱.
IIC总线可以用简单、优雅四个字形容,很方便。下面是IIC的时序图:
IIC总共有三种信号:开始信号,结束信号,应答信号
说明:高电平为1,低电平为0
1、图中S即为起始信号: SCL处于1时, SDA由高变低↓—>Start
下面是STM32的这部分代码,每句话我基本上都加了注释,可以对照时序图看, 便于理解
2、图中末尾P为结束信号: SCL=1, SDA 由低变高↑
3、数据传送: 每次数据传送都是8个字节; SCL=1时, SDA的数据不可以变化, 只有SCL为低电平, 数据线的数据才可以变化;
3.1、 数据传输流程:SCL=0, —>开始数据传输, 传输完一位之后,—> SCL=1(拉高时钟线);===
下面接着循环, 直到传输完一个字节8位数据.
下面举个例子:
e.g.
传输1001 1100?
SCL=0 --> SDA=1 --> SCL=1 (先传送最高位MSB)
SCL=0 --> SDA=0 --> SCL=1
SCL=0 --> SDA=0 --> SCL=1
…
**Tips:**在IIC协议中,起始信号必须有. 因为IC必须知道什么时候开始了,才可以正确判断接受数据.
下面开始Cube配置+IAR编程
6.1 操作简介
(1) 我所使用的STM32开发板板载了一个24C02的EEPROM存储芯片; 这个芯片就是通过IIC总线和单片机通信的;
(2) 通过GPIO软件模拟IIC通信 , 实现对24C02芯片内部数据的读写操作 .
注:
本次实验的代码是用的原子的底层代码, 修改了部分代码, 和Cube配置的工程以及HAL库函数匹配. 野火的例程没有看过,如果这个代码又有什么限制或者问题, 可以评论或者私信告诉我, 共同学习,感激不尽.
6.1 软件流程图
开机检测24C02--->主循环检测按键--->串口打印相关信息...
6.2 硬件接口
以我用的单片机为例:
但是这里并没有用板子上的硬件 IIC, 只是用这两个对应的引脚做软件模拟 IIC
Step1 : STM32CubeMX配置
-
(1) 像前面那样配置好基础的东西[RCC--SYS--时钟树配置不变]
-
(2) 配置IIC通信用的两个引脚(SCL&SDA)
-
两个引脚都配置为输出模式, 在程序中需要改变引脚方向, 那个时候直接操作寄存器改动引脚输入输出即可, 配置的时候先默认配置一个输出;
-
补充: 后面又加上了两个LED引脚, 用以做指示
-
(3) Project Manager and Generate Code
Step2 : IAR或Keil编程
-
(1) IIC部分代码
-
我把我改过部分的底层代码分享在网盘里面
链接:https://pan.baidu.com/s/17ogW2r-WzN9u-w511VzBJQ
提取码:9m4v-
.c文件放在 src 文件夹,.h文件复制到 Inc 文件夹
-
在myiic.h里面有两句宏定义SDA_OUT()和SDA_IN(); 如果你用的是F4的板子是可以直接用的, 但是如果用的是F1的或者其他的, 这个宏定义要改一下, 因为不同的芯片寄存器的名字和操作不太一样. 如果有人用的F1在这里遇到困难可以评论或者私信我, 这里就不单独写了
-
说明 : 24cxx文件是24CXX系列芯片的底层驱动文件, myiic文件是修改后的iic通信的驱动文件; 在主函数里面就是调用myiic里面的函数去实现通信的功能
部分底层代码分析
大概说一下底层驱动, 24cxx文件是通过iic协议对24c02芯片通信的底层驱动,直接用就可以了。
myiic文件是iic的关键.
关于起始信号, 结束信号就不多说了
在myiic.h中宏定义了SDA引脚的输入输出模式, 因为读数据的时候需要SDA这根线为输入模式, 往里面读数据
关于应答信号之类的几个函数,有兴趣可以去了解一下具体的IIC的协议总结或者数据手册
发送字节函数
IIC协议发送字节, 一个字节8位, 通过一个for循环发送;
txd是要发送的一个字节, IIC是先传输最高位, 所以让txd和0x80相与, 再右移7位
数据为1就给数据线(SDA)引脚写高电平, 然后txd左移一位,进入下一循环, 逐渐把8bits的数据发送完毕
读一个字节函数
IIC发送数据是往外发送数据, 所以SDA为输出模式, 但是读取字节是把外面的读进来;
举个形象一点的例子, 你的大脑就是24C02储存芯片, 你的身体器官就是IIC,即你的身体器官是通过IIC协议与外界进行数据交换的
发送字节就是你用嘴巴给别人说话把你脑子里想的东西告诉别人; 而接收字节就是你通过眼睛读书, 把知识记到脑子里
这样就很好理解了吧
-
(2) 按键部分
- 新建一个文件Ctrl+N—>Ctrl+S保存为 key.c到Src文件夹 ; 再新建一个保存在Inc文件夹, 命名为key.h
- 把.c和.h文件添加到工程中
+- 写代码
#include "key.h"
#include "main.h"
/******************************************************************************************************
*【文件名称】 : key.c
*【文件描述】 : 核心板板载按键驱动代码
*【文件功能】 : 实现按键输入扫描功能
*【主控芯片】 : STM432F407zg
*【实验平台】 : STM32F4xx开发板
*【编写环境】 : IAR 8.30.1
*【编写时间】 : 2019-12-31
*【作 者】 : 李剀(KevinLee)
*【历史记录】 :
<1> $【修改时间】
$【修改内容】
$【修改详情】
$【修改人员】
*******************************************************************************************************/
/**
* @brief 按键处理函数
* @param mode:0,不支持连续按;1,支持连续按;
* @retval 0: 没有任何按键按下 1: KEY0按下
* 2: WKUP按下 WK_UP
*/
uint8_t KEY_Scan(uint8_t mode)
{
static uint8_t key_up = 1;//按键按松开标志
if (mode)
key_up=1; //支持连按
if (key_up && (KEY0 == 0 || WK_UP != 0))
{
HAL_Delay(10);//去抖动
key_up = 0;
if(KEY0 == 0)
return 1;
else if(WK_UP != 0)
return 2;
}else if(KEY0 != 0 && WK_UP == 0)
key_up = 1;
return 0;// 无按键按下
}
key.h代码如下
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"
/******************************************************************************************************
*【文件名称】 : key.h
*【文件描述】 : 核心板板载按键驱动代码
*【文件功能】 : 实现按键输入扫描功能
*【主控芯片】 : STM432F407zg
*【实验平台】 : STM32F4xx开发板
*【编写环境】 : IAR 8.30.1
*【编写时间】 : 2019-12-31
*【作 者】 : 李剀(KevinLee)
*【历史记录】 :
<1> $【修改时间】
$【修改内容】
$【修改详情】
$【修改人员】
*******************************************************************************************************/
#define KEY0 HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) //PE4
#define WK_UP HAL_GPIO_ReadPin(WKUP_GPIO_Port, WKUP_Pin) //PA0
#define KEY0_PRES 1 // 宏定义按键按下的键值
#define WKUP_PRES 2
uint8_t KEY_Scan(uint8_t); //按键扫描函数
#endif
-
(3) 串口重定向printf
-
(4) main.c文件
① 包含头函数
② 自定义变量
- TEXT_Buffer数组存放的是等会要写入24c02芯片的数据
③ 主函数变量定义
- key用于获取扫描按键值; datatemp数组作为传输数据的暂存地点
④ 初始化操作
- 在/ USER CODE BEGIN 2 /下, 先初始化IIC, 在测试串口, 并用串口打印提示信息, 然后检测存储芯片是否存在
/* USER CODE BEGIN 2 */
AT24CXX_Init(); //IIC初始化
printf("Usart is Ok!!!\n");
printf("***************************\n");
printf("** STM32 IIC TEST **\n");
printf("** Kevin_8_Lee **\n");
printf("** KEY0:Write WKUP:Read **\n"); // 打印提示信息; KEY0按下写数据;WKUP按下读数据
printf("***************************\n");
// 检测24C02芯片是否存在
while(AT24CXX_Check())
{
printf("未检测到24C02!\n");
HAL_Delay(500);
HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin); // LED0闪烁
}
printf("24C02 Ready!\n");
/* USER CODE END 2 */
⑤ while(1)循环
- 循环进行安检扫描, 如果KEY0按下, 就把之前定义的一个字符串数组的数据写入24C02, 如果检测到WKUP按键按下就把24C02内部的数据读取出来并打印在串口
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
key = KEY_Scan(0); // 按键扫描获取键值
// 如果KEY0按下,则向24C02芯片写入指定数据
if (key == KEY0_PRES)
{
printf("开始写入24C02...\n");
AT24CXX_Write(0,(uint8_t *)TEXT_Buffer,SIZE);
printf("传输完成\n\n");
}
// 如果WKUP按键按下,则从24C02芯片读出数据, 并打印在串口
if (key == WKUP_PRES)
{
printf("开始读24C02......\n");
AT24CXX_Read(0,datatemp,SIZE);
printf("读取到的数据为 : %s", datatemp);
}
// 下面的是进行一定的延时, 保证上面扫描安全运行, 同事
// 以LED闪烁提示系统正在运行
i++;
HAL_Delay(10);
if (i == 20)
{
HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);
i = 0;
}
}
/* USER CODE END 3 */
实际效果
串口助手打印出的结果:
2020年4月19日2020年4月19日18:00:01补充:完整工程链接—>IIC通信
Don’t ever let somebody tell you, you can’t do something!!!
Author : 李光辉
date : Sun Apr 19 16:28:12 CST 2020
blog ID: Kevin_8_Lee
blog site : https://blog.csdn.net/Kevin_8_Lee