文章目录
- 前言
- 一、配置GPIO
- 1.GPIO方向设置
- 2.GPIO输出高低电平
- 3.GPIO读取输入电平
- 4.GPIO相关宏定义
- 二、实现IIC协议
- 1.起始/停止信号
- 2.等待应答
- 3.产生应答/不产生应答
- 4.写一个字节
- 5.读一个字节
- 三、mpu6500
- 1.寄存器定义
- 2.读mpu6500操作
- 3.写mpu6500操作
- 4.写操作
- 5.主函数
- 四、调试结果
- 总结
前言
在“”的博文中详细的介绍了IIC协议,并使用ESP32C3模组自带的IIC外设驱动了mpu6500。本博文将介绍不使用ESP32C3的IIC外设,而是通过GPIO来模拟IIC协议,就像以前使用STM32F103x系列MCU时那样将GPIO模拟成IIC的标准协议来驱动IIC器件,并记录我的开发过程。
一、配置GPIO
IIC协议需要用到2个GPIO引脚,一个是时钟引脚SCL,一个是数据收发引脚SDA。SCL引脚需要输出时钟信号;SDA引脚需要读写数据。
1.GPIO方向设置
SDA引脚因为需要收发数据所以会存在GPIO方向的设置,在发送数据时需要将引脚设置成“GPIO_MODE_OUTPUT”模式,在读取数据时需要将引脚设置成“GPIO_MODE_INPUT”模式。
在ESP32C3的源码中有实现GPIO方向设置的API,该API为esp_err_t gpio_set_direction(gpio_num_t gpio_num, gpio_mode_t mode)在头文件#include "driver/gpio.h"中,官方的编程指南介绍如下图:
这里了进行封装一下,方便后面调用,如下图:
2.GPIO输出高低电平
因为ESP32C3没有位带功能,所以无法直接配置别名操作GPIO输出高低电平(如led = 1;led= 0)。在之前对GPIO的学习中知道,要改变GPIO的输出电平那可以对寄存器(GPIO_OUT_W1TS_REG、GPIO_OUT_W1TC_REG)的相应位置位即可,所以还可以通过操作寄存器来实现相对好用的GPIO控制,如下图:
直接操作指定GPIO引脚的寄存器实现高低电平的输出,再借用三目运算‘:’、‘?’来选择输出电平的高低,这样就实现了低配的别名操作。
volatile是不让编译器进行优化,并每次都是取地址上的数据。
3.GPIO读取输入电平
SDA引脚需要读取从机发送的数据,ESP32C3的源码中有读取GPIO输入电平的API,该API为int gpio_get_level(gpio_num_t gpio_num)在头文件#include "driver/gpio.h"中,官方的编程指南介绍如下图:
也对这个API进行封装一下,如下图,是不是有熟悉的味道!!!
4.GPIO相关宏定义
二、实现IIC协议
1.起始/停止信号
ESP32C3官方微秒级延时API:ets_delay_us(uint32_t us),要包含头文件"esp32c3/rom/ets_sys.h"
/*****
* @Edit 2023.7
* @brief 产生IIC起始信号
* @param[in] Null
* @return Null
******/
void iic_start(void)
{
SDA_OUT(); //sda线输出
SCL(0);
ets_delay_us(1);
SDA(1);
ets_delay_us(1);
SCL(1);
ets_delay_us(4);
SDA(0);//START:when CLK is high,DATA change form high to low
ets_delay_us(4);
SCL(0);//钳住I2C总线,准备发送或接收数据
}
/*****
* @Edit 2023.7
* @brief 产生IIC停止信号
* @param[in] Null
* @return Null
******/
void iic_stop(void)
{
SDA_OUT();//sda线输出
SCL(0);
ets_delay_us(1);
SDA(0);//STOP:when CLK is high DATA change form low to high
ets_delay_us(4);
SCL(1);
ets_delay_us(4);
SDA(1);//发送I2C总线结束信号
ets_delay_us(4);
}
2.等待应答
/*****
* @Edit 2023.7
* @brief 等待应答信号到来
* @param[in] Null
* @return 0,接收应答成功
* 1,接收应答失败
******/
uint8_t iic_wait_ack(void)
{
uint8_t ucErrTime=0;
SDA_IN(); //SDA设置为输入
SDA(1); ets_delay_us(1);
SCL(1); ets_delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
iic_stop();
return 1;
}
}
SCL(0);//时钟输出0
return 0;
}
3.产生应答/不产生应答
/*****
* @Edit 2023.7
* @brief 产生ACK应答
* @param[in] Null
* @return Null
******/
void iic_ack(void)
{
SCL(0);
SDA_OUT();
SDA(0);
ets_delay_us(2);
SCL(1);
ets_delay_us(2);
SCL(0);
}
/*****
* @Edit 2023.7
* @brief 不产生ACK应答
* @param[in] Null
* @return Null
******/
void iic_nack(void)
{
SCL(0);
SDA_OUT();
SDA(1);
ets_delay_us(2);
SCL(1);
ets_delay_us(2);
SCL(0);
}
4.写一个字节
/*****
* @Edit 2023.7
* @brief IIC发送一个字节
* @param[in] txd:要发送的一个字节
* @return Null
******/
void iic_send_byte(uint8_t txd)
{
uint8_t t;
SDA_OUT();
SCL(0);//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
SDA((txd&0x80)>>7);
txd<<=1;
ets_delay_us(2); //对TEA5767这三个延时都是必须的
SCL(1);
ets_delay_us(2);
SCL(0);
ets_delay_us(2);
}
}
5.读一个字节
/*****
* @Edit 2023.7
* @brief 读1个字节,ack=1时,发送ACK,ack=0,发送nACK
* @param[in] ack:0,不发送ACK;1,发送ACK
* @return 1个字节
******/
uint8_t iic_read_byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
SCL(0);
ets_delay_us(2);
SCL(1);
receive<<=1;
if(READ_SDA)receive++;
ets_delay_us(1);
}
if (!ack)
iic_nack();//发送nACK
else
iic_ack(); //发送ACK
return receive;
}
如果要在外部调用,需要把这些函数在myiic.h文件中声明,如图:
三、mpu6500
1.寄存器定义
2.读mpu6500操作
3.写mpu6500操作
4.写操作
编写测试任务函数
5.主函数
初始化led和iic接口GPIO,创建led和mpu6500的任务函数
四、调试结果
有两个任务:led任务和mpu6500任务
mpu6500任务调试现象:
上一篇博客的调试现象
读取同一个寄存器的值都是:0x70,说明GPIO模拟IIC协议成功。
led任务调试现象:
总结
通过使用GPIO模拟IIC协议,加深了对ESP32C3 GPIO的认识和相关API的应用,还加深了对IIC协议原理的理解,巩固了相关的知识。行到此处也只是在物联网开发这条路上的一小步而已,长路漫漫,共勉。