离线
TA的每日心情 | 拍拍 2022-6-27 11:09 |
---|
签到天数: 25 天 [LV.4]
|
有人预言,RISC-V或将是继Intel和Arm之后的第三大主流处理器体系。欢迎访问全球首家只专注于RISC-V单片机行业应用的中文网站
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
本帖最后由 sky 于 2021-5-4 21:53 编辑
前言
1.疑问:
看到标题的诸君可能有疑虑了:"CDK里面不是有现成的IIC外设接口吗?为什么还要多此一举自己写IIC驱动呢?"
官方关于iic外设使用链接:IIC接口实现
2.辩解(吐槽)
诸君稍安勿躁,待我慢慢道来。我打算自己写的原因有二:
下面这张图是官方提供的RVB2601开发板用户手册里面附录带的一个“GPIO 复用关系表”
里面黄框代表IIC复用引脚,红框代表它连接对象,因为我的项目需要用到音频、W800所以,引脚不够用
- 其二,官方提供的使用硬件IIC接口,效果不好把控。
因为我在STM32上使用过硬件IIC,配置也比较简单,使用官方手册里面提供的IIC外设接口方法,效果可以实现,但是不同引脚的效果也不一样,让人心烦。
所以,我打算自己用软件写IIC驱动,个人使用效果—很nice。
一、IIC
1.简介要点
- iic总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL,所有操作都靠这两根线完成。
- iic总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。参考图1可以看出SDA高低电平的变化都是在SCL低电平期间。
2.时序图详解
我写的驱动包括:起始信号、终止信号、主机等待从机应答信号、主机读取数据的应答信号、主机读取最后一个数据的非应答信号、主机发送一个字节数据、主机读取一个字节数据、主机给从机写数据、主机读取从机数据。 下面给大家详细介绍下,希望可以带大家回顾下IIC协议的时序。
(1)引脚定义和基本函数实现
- //iic.h
- //需要的头文件省略没写,这里重点说时序
- csi_gpio_pin_t pin_SCL;
- csi_gpio_pin_t pin_SDA;
- #define IIC_SCL PA21 //这里引脚可以自己选
- #define IIC_SDA PA22
- #define SDA_IN() { csi_gpio_pin_dir(&pin_SDA, GPIO_DIRECTION_INPUT);} //SDA为输入引脚
- #define SDA_OUT() { csi_gpio_pin_dir(&pin_SDA, GPIO_DIRECTION_OUTPUT);} //SDA为输出引脚
-
- #define IIC_SCL1 {csi_gpio_pin_write(&pin_SCL, GPIO_PIN_HIGH);} //给SCL写高电平
- #define IIC_SCL0 {csi_gpio_pin_write(&pin_SCL, GPIO_PIN_LOW);} //给SCL写低电平
-
- #define IIC_SDA1 {csi_gpio_pin_write(&pin_SDA, GPIO_PIN_HIGH);} //给SDA写高电平
- #define IIC_SDA0 {csi_gpio_pin_write(&pin_SDA, GPIO_PIN_LOW);} //给SDA写低电平
- #define READ_SDA csi_gpio_pin_read(&pin_SDA)
复制代码- //iic引脚初始化
- void IIC_Init(void)
- {
- csi_pin_set_mux(IIC_SCL, PIN_FUNC_GPIO); //SCL复用为GPIO
- csi_pin_set_mux(IIC_SDA, PIN_FUNC_GPIO); //SDA复用为GPIO
- csi_gpio_pin_init(&pin_SCL, IIC_SCL);
- csi_gpio_pin_dir(&pin_SCL, GPIO_DIRECTION_OUTPUT); //SCL为输出
- csi_gpio_pin_init(&pin_SDA, IIC_SDA);
- csi_gpio_pin_dir(&pin_SDA, GPIO_DIRECTION_OUTPUT); //先配置成输出
-
- IIC_SCL1; //先把SCL和SDA拉高的目的:给SDA一个下降沿,时序就开始了
- IIC_SDA1;
- }
复制代码
(2)起始信号和终止信号
如下图
- SCL信号线为高电平期间,SDA线由高电平向低电平变化表示起始信号
- SCL信号线为高电平期间,SDA线由低电平向高电平变化表示终止信号
因此:
- //函 数 名:void IIC_Start(void)
- //功 能:IIC起始信号
- void IIC_Start(void)
- {
- SDA_OUT(); //sda线输出
- IIC_SDA1;
- delay_us(4);
- IIC_SCL1;
- delay_us(4);
- IIC_SDA0;//START:when CLK is high,DATA change form high to low
- delay_us(4);
- IIC_SCL0;//钳住I2C总线,准备发送或接收数据
- //delay_us(4);
- }
- //函 数 名:void IIC_Stop()
- //功 能:IIC停止信号
- void IIC_Stop(void)
- {
- SDA_OUT();//sda线输出
- IIC_SCL0;
- delay_us(4);
- IIC_SDA0;//STOP:when CLK is high DATA change form low to high
- delay_us(4);
- IIC_SCL1;
- delay_us(4);
- IIC_SDA1;//发送I2C总线结束信号
- delay_us(4);
- }
复制代码
(3)等待从机应答ACK
数据传送格式:每一个字节必须保证时8位长度。数据传送时,先传高位,每个被传送的字节后面必须跟随一位应答位,在主机向从机传送数据时应答位由从机产生,主机检测从机产生的应答信号,如果从机应答时间超时,主机默认数据传输完成,本段代码就是检测从机的应答信号。
- //等待应答信号到来
- //返回值:1,接收应答失败
- // 0,接收应答成功
- uint8_t IIC_Wait_Ack(void)
- {
- uint8_t ucErrTime=0;
- SDA_IN(); //SDA设置为输入
- IIC_SDA1;delay_us(6);
- IIC_SCL1;delay_us(6);
- while(READ_SDA)
- {
- ucErrTime++;
- if(ucErrTime>250)
- {
- IIC_Stop();
- return 1;
- }
- }
- IIC_SCL0;//时钟输出0
- return 0;
- }
复制代码
(4)主机读数据产生的应答信号或非应答信号
主机读数据时有这样的规定:当主机每读一个数据主机都要回复应答信号。当最后一个字节数据读完后,主机要返回以“非应答”,并终止信号读出操作,这里的应答信号和非应答信号作用是给从机判断是否继续读取数据。由图可以看出应答信号是在SDA低电平期间,SCL由低置高再置低。非应答信号是在SDA位高电平期间SCL由低置高再置低。
- //函 数 名:void IIC_Ack(void)
- //功 能:IIC产生ACK应答信号
- void IIC_Ack(void)
- {
- IIC_SCL0;
- SDA_OUT();
- IIC_SDA0;
- delay_us(10);
- IIC_SCL1;
- delay_us(10);
- IIC_SCL0;
- }
- //函 数 名:void IIC_NAck(void)
- //功 能:IIC产生NACK应答信号
- void IIC_NAck(void)
- {
- IIC_SCL0;
- SDA_OUT();
- IIC_SDA1;
- delay_us(10);
- IIC_SCL1;
- delay_us(10);
- IIC_SCL0;
- }
- ```C++
复制代码
(5)主机发送数据
主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。
主设备往从设备中写数据。数据传输格式如下:
- //IIC发送一个字节
- //返回从机有无应答
- //1,有应答
- //0,无应答
- void IIC_Send_Byte(uint8_t txd)
- {
- uint8_t t;
- SDA_OUT();
- IIC_SCL0;//拉低时钟开始数据传输
- for(t=0;t<8;t++)
- {
- if((txd&0x80)>>7)
- {
- IIC_SDA1;
- }
- else
- IIC_SDA0;
-
- txd<<=1;
- delay_us(10); //这三个延时都是必须的
- IIC_SCL1;
- delay_us(10);
- IIC_SCL0;
- delay_us(10);
- }
- }
- //完整IIC主机写一个字节数据
- uint8_t IIC_Write_1Byte(uint8_t SlaveAddress, uint8_t REG_Address,uint8_t REG_data)
- {
- IIC_Start();
- IIC_Send_Byte(SlaveAddress);
- if(IIC_Wait_Ack())
- {
- IIC_Stop();//释放总线
- return 1;//没应答则退出
- }
- IIC_Send_Byte(REG_Address);
- IIC_Wait_Ack();
- IIC_Send_Byte(REG_data);
- IIC_Wait_Ack();
- IIC_Stop();
- return 0;
- }
复制代码
(5)主机读取数据
主设备从从设备中读数据。数据传输格式如下:
- //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
- uint8_t IIC_Read_Byte(unsigned char ack)
- {
- unsigned char i,receive=0;
- SDA_IN();//SDA设置为输入
- for(i=0;i<8;i++ )
- {
- IIC_SCL0;
- delay_us(10);
- IIC_SCL1;
- receive<<=1;
- if(READ_SDA)receive++;
- delay_us(5);
- }
- if (!ack)
- IIC_NAck();//发送nACK
- else
- IIC_Ack(); //发送ACK
- return receive;
- }
- //IIC读一个字节数据
- uint8_t IIC_Read_1Byte(uint8_t SlaveAddress, uint8_t REG_Address,uint8_t *REG_data)
- {
- IIC_Start();
- IIC_Send_Byte(SlaveAddress);//发写命令
- if(IIC_Wait_Ack())
- {
- IIC_Stop();//释放总线
- return 1;//没应答则退出
- }
- IIC_Send_Byte(REG_Address);
- IIC_Wait_Ack();
- IIC_Start();
- IIC_Send_Byte(SlaveAddress|0x01);//发读命令
- IIC_Wait_Ack();
- *REG_data = IIC_Read_Byte(0);
- IIC_Stop();
- return 0;
- }
复制代码
二、结束语
以上就是一个完整的iic驱动解析,大家有问题欢迎在评论区交流,一起学习,一起进步。下面附录是写的iic驱动的代码,本人能力有限,还请各位大佬多多指教。
附录
驱动下载链接iic驱动
完
|
上一篇: RVB2601应用开发实战系列三: GUI图形显示下一篇: RVB2601开发板ADC读取实验
|