【linux-IMX6ULL-RTC-IIC-SPI配置思路】

1. RTC简介

  RTC(Real-Time Clock)即实时时钟,本质上也属于定时器的一种,不过这个定时器是在单片机断电后,利用外部电源例如纽扣电池,依然能正常运行,其功耗较低,一般由晶体振荡器产生,常见的是32.768khz的晶振,因为这个晶振经过2^15分频后就能得到一个1HZ的时钟信号,也就是1s;其他的详细功能这里就不再赘述;

1.1 IMX6ULL中的RTC

  RTC在IMX6ULL中称为:"SNVS":安全的非易失性存储,在有纽扣电池作为后备电源的情况下,不管系统主电源是否断电,SNVS 都正常运行;其RTC的初始时间是:1970-01-01T00:00:00Z
  注意事项1在IMX6ULL中使用RTC:我们要实现的是掉电依然能保持RTC的运行,所以我们使用的是下图的SNVS_LP中的RTC也叫SRTC,而对于SNVS_HP中的RTC我们不进行使用,因为其掉电后数据时间就会清零;因此剩下的配置思路就是和普通定时器类似:
  注意事项2:不要被寄存器手册误导
  ①、SRTC 计数器是 32 位的,不是 47 位!
  ②、SNVS_SRTCMR 的 bit14:0 这 15 位是 SRTC 计数器的高 15 位。
  ③、SNVS_SRTCLR 的 bit31:bit15 这 17 位是 SRTC 计数器的低 17 位;

1.2 SNVS_LP中的SRTC配置流程

  上述已经讲述使用SRTC的原因,其配置思路如下:

  1. 关闭RTC时钟
  2. 写入新的时间值:年月日时分秒
  3. 使能RTC时钟

1.3 程序实现

/*时间设置函数*/
void rtc_setdatetime(struct rtc_datetime *datetime)
{
    uint64_t seconds =0 ;
    /*这里先保存时钟值,方便后序的修改*/
    unsigned int tmp = SNVS->LPCR;
    /*设置之前要先关闭时钟*/
    rtc_disenable();
    seconds=rtc_coverdate_to_seconds(datetime);/*得到秒数,年月日转换成秒数*/
    SNVS->LPSRTCMR = (unsigned int)(seconds>>17);/*保存高32位*/
    SNVS->LPSRTCLR = (unsigned int)(seconds<<15);/*把高位都清零,保存低32位*/
    if(tmp&(0x01))/*判断以前是否打开了RTC,如果相等代表是已经打开了RTC,则进行处理*/
    {
        rtc_enable();
    }
}
/*rtc初始化函数*/
/*默认上电运行是1970年1月1日的零时零点,所以要进行配置他的时间*/
void rtc_init(void)
{
    /*首先设置的是HPCOMR寄存器*/
    struct rtc_datetime rtcData;/*定义一个结构体变量*/
    SNVS->HPCOMR |= (1<<31)|(1<<8);/*关闭安全模式,向所有软件开放*/
    rtcData.year=2024;
    rtcData.month=5;
    rtcData.day=10;
    rtcData.hour=10;
    rtcData.minute=15;
    rtcData.second=30;
    rtc_setdatetime(&rtcData);
    /*开启RTC时钟*/
    rtc_enable();
}

2. IIC通信协议

2.1 IIC基础

  IIC是串行同步半双工的通信方式,适合短距离的板内通信,最大速率约400Khz,可调整上拉电阻R的值来提高通信速率,R值一般取4.7K或者2.2K,需要两根线SDA数据线和SCL时钟线,可挂载多个设置,但是同一时间只能有一个设备进行通信;

2.2 IIC通信协议

  根据通信协议可以直观的了解到IIC通信的过程,其基本包括两部分,一部分是读的时序,一部分是写的时序;同时要了解一下通信的基本术语:

  • 起始位:就是告诉挂载的设置我要开始发送或者接受信息了,一般就是在SCL高电平的时候SDA来个下降沿信号就代表起始位
  • 数据传输:包含地址信息,与那个设备通信,数据信息等
  • 应答信号:ACK,应答信号就是代表对于任务完成
  • 停止位:与起始位相反,代表通信结束

2.2.1 IIC写时序

  对于写时序,其时序图如下:注意的几个点就是SDA的所有bit信号只有在SCL是高电平期间是有效的;其次就发送的命令顺序:
  start->ID&W->ACK->R_Adress->ACK->DATA->ACK->STOP



2.2.2 IIC读时序

  对于读时序,我们要注意的是主机发送地址码找到要读取的位置,然后发送写的命令,把要读取的寄存器的地址写给从设备,然后再发送从机的地址并附上读的命令,这时候从机就会从对应的寄存器地址取出内容发送给主机,其时序图如下所示,基本和写是类似的,相比写时序而言,就是后面加了一个读的时序;


在这里插入图片描述


3. IIC通信的硬件框图及配置流程

  对于IIC通信有硬件IIC和软件模拟的IIC,本质都是产生上述的IIC通信时序,对于IMX6ULL而言,有4个硬件IIC,因此这里主要是针对硬件IIC进行配置,大体上就是开启IIC外设时钟,然后配置控制寄存器。和使能相关的位,最后就是封装一个读寄存器和写寄存器的函数;

3.1 IMX6ULL的硬件IIC框图

  对于IMX6ULL的硬件IIC的特性如下所示:有标准模式100Khz和高速模式400Khz两种模式;
  ①、与标准 I2C 总线兼容。
  ②、多主机运行
  ③、软件可编程的 64 中不同的串行时钟序列。
  ④、软件可选择的应答位。
  ⑤、开始/结束信号生成和检测。
  ⑥、重复开始信号生成。
  ⑦、确认位生成。
  ⑧、总线忙检测
  对于IMX6ULL的硬件IIC的框图如下:

3.1 IIC配置流程

  这里主要是针对IMX6ULL的硬件IIC的配置流程,如下:

  1. IIC初始化:包括关闭IIC,设置好分频系数、开启IIC;
  2. IIC的读程序编写:包括发送起始信号,假读,判断是否忙,然后清除标志位,设置接受数据集,对于IMX6ULL而言倒数第二个字节要发送停止ACK的信号,因此如果是要读取一个字节的数据,那么就要读完就立刻发送应答位,对于多个字节的读取,每次都要判断是否忙和清除标志位,并在倒数第二个字节发送应答信号表示读取结束,最后发送停止信号;
  3. IIC的写程序的编写:也是和读类似,发送起始信号,首先判断是否忙,然后清除标志位,后面就是发送数据,每次发送一个字节的数据都要判断是否忙和仲裁检测发送应答位ACK,最后就是清除状态标志位和发送停止位;

3.2 硬件IIC代码实现

/*配置IIC前要关闭IIC*/
/*基础知识点*/
/*初始化IIC,通过输入参数可以直接确定输入的IIC模式*/
void i2c_init(I2C_Type *base)
{
    /*关闭IIC*/
    base->I2CR &= ~(1<<7);
    /*设置频率*/
    base->IFDR=0x15;/*640分频:66000000/640=103.125kHZ*/
    /*打开IIC,使能IIC*/
    base->I2CR|=(1<<7);
}
/* start 信号的产生及从机地址的发送*/
unsigned char i2c_master_start(I2C_Type *base,unsigned char address,enum i2c_direction direction)
{
    if(base->I2SR&(1<<5))/*为真的话就是IIC忙*/
    {
        return 1;/*代表忙*/
    }
    /*设置为主机模式*/
    base->I2CR |= (1<<5)|(1<<4);
    /*产生Start信号*/
     base->I2DR =((unsigned int)address<<1)|(direction==kI2C_Read?1:0);/*为1的话就是读,为0的话就是写*/
     return 0;
}
/*STOP的信号*/
unsigned char i2c_master_stop(I2C_Type *base)
{
    unsigned short timeout = 0xffff;
    /*清除I2CR的bit5:3*/
    base->I2CR &= ~((1<<5)|(1<<4)|(1<<3));
    /*等待I2C忙结束*/
    while((base->I2SR&(1<<5)))/*为1的话就是一直忙*/
    {
        timeout--;
        if(timeout==0)/*超时跳出*/
        {
            return I2C_STATUS_TIMEOUT;
        }
    }
    return I2C_STATUS_OK;/*停止信号产生成功*/
}
/*重复发送信号*/
unsigned char i2c_master_repeate_start(I2C_Type *base,unsigned char address,enum i2c_direction direction)
{
    /*检测一下IIC是否忙,或者工作在从机模式下*/
    if((base->I2SR*(1<<5)) && (((base->I2CR)&(1<<5)) == 0))
    {
        return 1;
    }
    /*设置寄存器*/
    base->I2CR |= (1<<4)|(1<<2);
    /*把器件地址发送过去*/
    base->I2DR =((unsigned int)address<<1)|(direction==kI2C_Read?1:0);/*为1的话就是读,为0的话就是写*/

    return I2C_STATUS_OK;
}
//错误点1,位移不对,base->I2CR |= (1 << 7);	这里写成了:base->I2CR |= (1 << 4);
unsigned char i2c_check_and_clear_error(I2C_Type *base, unsigned int status)
{
	/* 检查是否发生仲裁丢失错误 */
	if(status & (1<<4))
	{
		base->I2SR &= ~(1<<4);		/* 清除仲裁丢失错误位 			*/
		base->I2CR &= ~(1 << 7);	/* 先关闭I2C 				*/
		base->I2CR |= (1 << 7);		/* 重新打开I2C 				*/
		return I2C_STATUS_ARBITRATIONLOST;
	} 
	else if(status & (1 << 0))     	/* 没有接收到从机的应答信号 */
	{
		return I2C_STATUS_NAK;		/* 返回NAK(No acknowledge) */
	}
	return I2C_STATUS_OK;
}
/*主机对从机进行数据的写入*/
void i2c_master_write(I2C_Type *base,const unsigned char *buff,unsigned int size)
{
    /*等待上一个发送完成*/
    while(!(base->I2SR&(1<<7)));
    /*清除中断*/
    base->I2SR &= ~(1<<1);
    /*发送数据*/
    base->I2CR |= (1<<4);
    while(size--)
    {
        base->I2DR = *buff++;
        while(!(base->I2SR &(1<<1)));/*等待数据传输完成*/
        base->I2SR &= ~(1<<1);
        /*检测ACK*/
        if(i2c_check_and_clear_error(base,base->I2SR))
        {
            break;
        }
    }
    base->I2SR &= ~(1<<1);
    i2c_master_stop(base);
}
/*读数据*/
void i2c_master_read(I2C_Type *base,unsigned char *buff,unsigned int size)
{
    /*问题程序*/
    volatile uint8_t dumpy = 0;/*假读*/
    dumpy++;
    /*等待传输完成*/
    while(!((base->I2SR)&(1<<7)));
    /*清除标志位*/
    base->I2SR &= ~(1<<1);
    /*接受数据*/
    base->I2CR &= ~((1<<4)|(1<<3));
    if(size == 1)
    {
        base->I2CR |= (1<<3);/*NACK;倒数第二个数据要发一个不要应答了的信号*/
    }
    dumpy=base->I2DR;/*假读*/
    while(size--)
    {
        while(!(base->I2SR&(1<<1)));/*等待传输完成*/
        base->I2SR &= ~(1<<1);      /*清除标志位*/

        if(size==0)
        {
            i2c_master_stop(base);
        }
        if(size==1)
        {
            base->I2CR |= (1<<3);/*NACK;倒数第二个要给个应答*/
        }
        *buff++=base->I2DR;
    }
}
unsigned char i2c_master_transfer(I2C_Type *base, struct i2c_transfer *xfer)
{
	unsigned char ret = 0;
	enum i2c_direction direction = xfer->direction;	
	base->I2SR &= ~((1 << 1) | (1 << 4));			/* 清除标志位 */
	/* 等待传输完成 */
	while(!((base->I2SR >> 7) & 0X1)){}; 
	/* 如果是读的话,要先发送寄存器地址,所以要先将方向改为写 */
    if ((xfer->subaddressSize > 0) && (xfer->direction == kI2C_Read))
    {
        direction = kI2C_Write;
    }
	ret = i2c_master_start(base, xfer->slaveAddress, direction); /* 发送开始信号 */
    if(ret)
    {	
		return ret;
	}
	while(!(base->I2SR & (1 << 1))){};			/* 等待传输完成 */
    ret = i2c_check_and_clear_error(base, base->I2SR);	/* 检查是否出现传输错误 */
    if(ret)
    {
      	i2c_master_stop(base); 						/* 发送出错,发送停止信号 */
        return ret;
    }
    /* 发送寄存器地址 */
    if(xfer->subaddressSize)
    {
        do
        {
			base->I2SR &= ~(1 << 1);			/* 清除标志位 */
            xfer->subaddressSize--;				/* 地址长度减一 */
            base->I2DR =  ((xfer->subaddress) >> (8 * xfer->subaddressSize)); //向I2DR寄存器写入子地址
			while(!(base->I2SR & (1 << 1)));  	/* 等待传输完成 */
            /* 检查是否有错误发生 */
            ret = i2c_check_and_clear_error(base, base->I2SR);
            if(ret)
            {
             	i2c_master_stop(base); 				/* 发送停止信号 */
             	return ret;
            }  
        } while ((xfer->subaddressSize > 0) && (ret == I2C_STATUS_OK));

        if(xfer->direction == kI2C_Read) 		/* 读取数据 */
        {
            base->I2SR &= ~(1 << 1);			/* 清除中断挂起位 */
            i2c_master_repeate_start(base, xfer->slaveAddress, kI2C_Read); /* 发送重复开始信号和从机地址 */
    		while(!(base->I2SR & (1 << 1))){};/* 等待传输完成 */
            /* 检查是否有错误发生 */
			ret = i2c_check_and_clear_error(base, base->I2SR);
            if(ret)
            {
             	ret = I2C_STATUS_ADDRNAK;
                i2c_master_stop(base); 		/* 发送停止信号 */
                return ret;  
            }   	          
        }
    }	
    /* 发送数据 */
    if ((xfer->direction == kI2C_Write) && (xfer->dataSize > 0))
    {
    	i2c_master_write(base, xfer->data, xfer->dataSize);
	}

    /* 读取数据 */
    if ((xfer->direction == kI2C_Read) && (xfer->dataSize > 0))
    {
       	i2c_master_read(base, xfer->data, xfer->dataSize);
	}
	return 0;	
}

4. SPI通信

4.1 SPI通信基础

&emps; SPI通信相对于IIC而言,其通信速率可达几百MH在,因此其再通信速率上的优势是很大的,对于SPI而言其是串行同步全双工的通信方式,其有四根线进行通信,MOSI,MISO,SCL,CS,这四根线;如下图所示:

  术语解释,如下:

  • CS:片选信号,用来和哪个数据进行通信时进行拉低就行
  • MISO:也称为:SDI,主机设备接受数据,从机设备发送数据
  • MOSI:也称为:SDO,主机设备发送数据,从机设备接受数据
  • SCLK:时钟信号,固定频率

  SPI的通信模式一般有四种模式,根据选择时钟极性(CPOL)和相位极性(CPHA)的不同,可以有四种组合,时序如下:我们常用的是CPOL=0,CPHA=0这种模式。

4.1 SPI通信时序

  如同UART,IIC有自己的通信协议,对于SPI而言也有自己的通信协议,这个通信协议的实现方式有硬件实现也有软件时间,具体的实现可以根据需求来定,其SPI的通信时序如下:

  可以看出其通信的时序非常简单,也就是片选拉低,开始通信,根据时钟极性和相位极性对MOSI和MISO的数据进行读取,这里CPOL=CPHA=0,因此是再时钟的上升沿对数据进行读取,根据上述的时序图可以很好的看出来;

4.3 硬件SPI通信模式

  对于IMX6ULL而言,其有硬件SPI,但是片选信号我们可以用自定义的引脚进行拉低实现,也就是片选引脚初始化位GPIO的模式,不采用硬件模式,硬件只负责产生时钟信号和接受发送数据;对于6ULL而言其内部的SPI称为:ECSPI(Enhanced Configurable Serial Peripheral Interface),其实和实际的SPI基本是一样的;ECSPI 有 64*32 个接收FIFO(RXFIFO)和 64*32 个发送 FIFO(TXFIFO),ECSPI 特性如下:

  ①、全双工同步串行接口。
  ②、可配置的主/从模式。
  ③、四个片选信号,支持多从机。
  ④、发送和接收都有一个 32x64 的 FIFO。
  ⑤、片选信号 SS/CS,时钟信号 SCLK 极性可配置。
  ⑥、支持 DMA。

4.3 硬件SPI配置流程

  SPI的通信是非常简答的,因此对于硬件SPI的配置也比较简单,主要就是要配置其SPI的时钟源,其次就是配置SPI相关的控制寄存器,例如配置好时钟极性,设置好分频系数,对SPI通信进行使能等,另外就是对SPI数据的接受和发送,这个功能是在一个函数中同时完成的,因为SPI是全双工的通信方式,在接受数据的同时也可以发送数据;注意在接受数据时要选好通道并判忙,例如线上有数据时才能进行数据的接受,不然接受的数据就是错误的,因此要进行提前的判断

  1. 初始化时钟源
  2. 配置好SPI控制寄存器
  3. 设置分频系数并使能SPI
  4. 读取和发送寄存器的控制

4.4 硬件SPI代码实现

  其代码实现如下:可以看到代码是非常简短的,相比于IIC通信而言,而且其通信的速率也是非常快的;

#include "bsp_spi.h"
/*初始化*/
void spi_init(ECSPI_Type *base)
{
    base->CONREG = 0;/*清零*/
    base->CONREG |= (1<<0)|(1<<3)|(1<<4)|(7<<20);/*设置4个位*/
    base->CONFIGREG = 0;/*清零*/
    base->PERIODREG = 0x2000;
    /*设置SPI的时钟*/
    base->CONREG &= ~((0xf<<12)|(0xf << 8));
    base->CONREG |= (9<<12);/*一级10分频:get :6Mhz*/

}
/*SPI Sent And Receive Function*/
unsigned  char spich0_read_write_byte(ECSPI_Type *base,unsigned char txdata)
{
    uint32_t spirxdata = 0;
    uint32_t spitxdata = txdata;
    /*选择通道0,base*/
    base->CONREG &= ~(3<<18);
    base->CONREG |= (0<<18);
    /*数据发送*/
    while((base->STATREG & (1<<0))==0);
    base->TXDATA=spitxdata;
    /*数据接收*/
    while((base->STATREG & (1<<3))== 0);
    spirxdata = base->RXDATA;
    return spirxdata;
}
  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值