这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » MCU » 【GD32VF103之RISC-V开发贴连载】⑤.SPI与IIC通信

共1条 1/1 1 跳转至

【GD32VF103之RISC-V开发贴连载】⑤.SPI与IIC通信

专家
2020-06-25 23:51:56     打赏

   又是半个月过去了,今天是端午节,趁着假期来分享一下关于SPI与IIC的通信接口。SPI模块可以 通过SPI协议与外部设备进行通信。串行外设接口提供了基于SPI协议的数据发送和接收功能可以工作于主机或从机模式。SPI接口支持具有硬件CRC计算和校验的全双工和单工模式。

SPI主要特征如下:

1、具有全双工和单工模式的主从操作;

2、16位宽度,独立的发送和接收缓冲区;

3、8位或16位数据帧格式;

4、低位在前或高位在前的数据位顺序;

5、软件和硬件NSS管理;

6、硬件CRC计算、发送和校验;

7、发送和接收支持DMA模式;

8、支持SPI TI模式;

9、支持SPI NSS脉冲模式;

SPI结构框图如下:

SPI结构框图.pngSPI信号线描述详情如下:

SPI信号描述.png典型的工作模式概括如下:

典型的工作模式.png

SPI基本发送和接收流程:

发送流程:

在完成初始化过程之后,SPI模块使能并保持在空闲状态。在主机模式下,当软件写一个数据到发送缓冲区时,发送过程开始。在从机模式下,当SCK引脚上的SCK信号开始翻转且NSS引脚电平为低发送过程开始。所以在从机模式下,应用程序必须确保在数据发送开始前,数据已经写入发送缓冲区中。

当SPI开始发送一个数据帧时,首先将这个数据帧从数据缓冲区加载到移位寄存器中,然后开始发送加载的数据。在数据帧的第一位发送之后,TBE(发送缓冲区空)位置1。TBE标志位置1说明发送缓冲区为空,此时如果需要发送更多数据,软件应该继续写SPI_DATA寄存器。在主机模式下,若想要实现连续发送功能那么在当前数据帧发送完成前软件应该将下一个数据写入SPI_DATA寄存器中。

接收流程:

在最后一个采样时钟边沿之后接收到的数据将从移位寄存器存入到接收缓冲区且RBNE(接收缓冲区非空)位置1。软件通过读SPI_DATA寄存器获得接收的数据,此操作会自动清除RBNE标志位。在MRU和MRB模式中,为了接收下一个数据帧,硬件需要连续发送时钟信号,而在全双工主机模式(MFD)中仅当发送缓冲区非空时,硬件才接收下一个数据帧。

SPI的寄存器列表如下:

SPI寄存器.png

SPI的库函数列表如下:

SPI库函数.png

SPI流程如下:

1、初始化时钟

2、配置管脚

3、cs管脚配置

4、spi参数配置

5、是否使能crc

6、使能spi

7、spi发送/接收

部分代码如下:

void spi_struct_para_init(spi_parameter_struct* spi_struct)

{

    /* set the SPI struct with the default values */

    spi_struct->device_mode = SPI_SLAVE;

    spi_struct->trans_mode = SPI_TRANSMODE_FULLDUPLEX;

    spi_struct->frame_size = SPI_FRAMESIZE_8BIT;

    spi_struct->nss = SPI_NSS_HARD;

    spi_struct->clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;

    spi_struct->prescale = SPI_PSC_2;

}

void spi_init(uint32_t spi_periph, spi_parameter_struct* spi_struct)

{   

    uint32_t reg = 0U;

    reg = SPI_CTL0(spi_periph);

    reg &= SPI_INIT_MASK;


    /* select SPI as master or slave */

    reg |= spi_struct->device_mode;

    /* select SPI transfer mode */

    reg |= spi_struct->trans_mode;

    /* select SPI frame size */

    reg |= spi_struct->frame_size;

    /* select SPI NSS use hardware or software */

    reg |= spi_struct->nss;

    /* select SPI LSB or MSB */

    reg |= spi_struct->endian;

    /* select SPI polarity and phase */

    reg |= spi_struct->clock_polarity_phase;

    /* select SPI prescale to adjust transmit speed */

    reg |= spi_struct->prescale;

    /* write to SPI_CTL0 register */

    SPI_CTL0(spi_periph) = (uint32_t)reg;


    SPI_I2SCTL(spi_periph) &= (uint32_t)(~SPI_I2SCTL_I2SSEL);

}

void spi_enable(uint32_t spi_periph)

{

    SPI_CTL0(spi_periph) |= (uint32_t)SPI_CTL0_SPIEN;

}

void spi_disable(uint32_t spi_periph)

{

    SPI_CTL0(spi_periph) &= (uint32_t)(~SPI_CTL0_SPIEN);

}

uint16_t spi_crc_get(uint32_t spi_periph,uint8_t crc)

{

    if(SPI_CRC_TX == crc){

        return ((uint16_t)(SPI_TCRC(spi_periph)));

    }else{

        return ((uint16_t)(SPI_RCRC(spi_periph)));

    }

}

void spi_ti_mode_enable(uint32_t spi_periph)

{

    SPI_CTL1(spi_periph) |= (uint32_t)SPI_CTL1_TMOD;

}

void spi_ti_mode_disable(uint32_t spi_periph)

{

    SPI_CTL1(spi_periph) &= (uint32_t)(~SPI_CTL1_TMOD);

}

void spi_crc_error_clear(uint32_t spi_periph)

{

    SPI_STAT(spi_periph) &= (uint32_t)(~SPI_FLAG_CRCERR);

}

    接下来聊聊I2C控制总线。I2C(内部集成电路总线)模块提供了符合工业标准的两线串行制接口,可用于MCU和外部I2C设备的通讯。I2C总线使用两条串行线:串行数据线SDA和串行时钟线SCL。I2C接口模块实现了I2C协议的标速模式,快速模式以及快速模式,具备CRC计算和校验功能,支持SMBus(系统管理总线)和PMBus(电源管理总线)。此外还支持多主机I2C总线架构。I2C接口模块也支持DMA模式,可有效减轻CPU的负担。

I2C主要特征如下:

1、并行总线至I2C总线协议的转换及接口;

2、同一接口既可实现主机功能又可实现从机功能;

3、主从机之间的双向数据传输;

4、支持7位和10位的地址模式和广播寻址;

5、支持I2C多主机模式;

6、支持标速(最高100kHz),快速(最高400kHz)和快速模式(最高1MHz);

7、从机模式下可配置的SCL主动拉低;

8、支持DMA模式;

9、兼容SMBus2.0和PMBus

10、两个中断:字节成功传输中断和错误事件中断;

11、可选择的PEC(报文错误校验)生成和校验;

I2C结构框图如下:

I2C模块框图.png

I2C总线术语

I2C总线术语说明.png

I2C模块有两条接口线:串行数据SDA线和串行时钟SCL线。连接到总线上的设备通过这两根线互相传递信息。SDA和SCL都是双向线,通过一个电流源或者上拉电阻接到电源正极。当总线空闲时,两条线都是高电平。连接到总线的设备输出极必须是开漏或者开集,以提供线与功能。I2C总线上的数据在标准模式下可以达到100Kbit/s,在快速模式下可以达到400Kbit/s,当I2C_FMPCFG寄存器中FMPEN位被置位时,在快速模式下可达1Mbit/s。由于I2C总线上可能会连接不同工艺的设备,逻辑‘0’和逻辑‘1’的电平并不是固定的,取决于VDD的实际电平。         所有的数据传输起始于一个START结束于一个STOP。START起始位定义为S,在SCL为高时,SDA线上出现一个从高到低的电平转换。STOP结束位定义为P,在SCL为高时,SDA线上出现一个从低到高的电平转换。

开始和停止状态.png    I2C寄存器如下:

I2C寄存器.png

    I2C库函数如下:

I2C库函数.png

I2C流程如下:

1、初始化时钟

2、配置管脚

3、使能,配置I2C时钟

4、I2C参数配置

5、使能I2C

6、使能应答

7、数据发送/接收

部分代码如下:

/* I2C register bit mask */

#define I2CCLK_MAX             ((uint32_t)0x00000048U)         /*!< i2cclk maximum value */

#define I2CCLK_MIN              ((uint32_t)0x00000002U)         /*!< i2cclk minimum value */

#define I2C_FLAG_MASK       ((uint32_t)0x0000FFFFU)           /*!< i2c flag mask */

#define I2C_ADDRESS_MASK  ((uint32_t)0x000003FFU)        /*!< i2c address mask */

#define I2C_ADDRESS2_MASK ((uint32_t)0x000000FEU)       /*!< the second i2c address mask */

void i2c_mode_addr_config(uint32_t i2c_periph, uint32_t mode,uint32_t addformat, uint32_t addr) 

{

    /* SMBus/I2C mode selected */

    uint32_t ctl = 0U;


    ctl = I2C_CTL0(i2c_periph);

    ctl &= ~(I2C_CTL0_SMBEN);

    ctl |= mode;

    I2C_CTL0(i2c_periph) = ctl;

    /* configure address */

    addr = addr & I2C_ADDRESS_MASK;

    I2C_SADDR0(i2c_periph) = (addformat | addr);

}

void i2c_clock_config(uint32_t i2c_periph, uint32_t clkspeed, uint32_t dutycyc) 

{

    uint32_t pclk1, clkc, freq, risetime;

    uint32_t temp;


    pclk1 = rcu_clock_freq_get(CK_APB1);

    /* I2C peripheral clock frequency */

    freq = (uint32_t) (pclk1 / 1000000U);

    if (freq >= I2CCLK_MAX) {

        freq = I2CCLK_MAX;

    }

    temp = I2C_CTL1(i2c_periph);

    temp &= ~I2C_CTL1_I2CCLK;

    temp |= freq;

    I2C_CTL1(i2c_periph) = temp;

    if (100000U >= clkspeed) {

        /* the maximum SCL rise time is 1000ns in standard mode */

        risetime = (uint32_t) ((pclk1 / 1000000U) + 1U);

        if (risetime >= I2CCLK_MAX) {

            I2C_RT(i2c_periph) = I2CCLK_MAX;

        } else if (risetime <= I2CCLK_MIN) {

            I2C_RT(i2c_periph) = I2CCLK_MIN;

        } else {

            I2C_RT(i2c_periph) = risetime;

        }

        clkc = (uint32_t) (pclk1 / (clkspeed * 2U));

        if (clkc < 0x04U) {

            /* the CLKC in standard mode minmum value is 4 */

            clkc = 0x04U;

        }

        I2C_CKCFG(i2c_periph) |= (I2C_CKCFG_CLKC & clkc);


    } else if (400000U >= clkspeed) {

        /* the maximum SCL rise time is 300ns in fast mode */

        I2C_RT(i2c_periph) = (uint32_t) (((freq * (uint32_t) 300U)

                / (uint32_t) 1000U) + (uint32_t) 1U);

        if (I2C_DTCY_2 == dutycyc) {

            /* I2C duty cycle is 2 */

            clkc = (uint32_t) (pclk1 / (clkspeed * 3U));

            I2C_CKCFG(i2c_periph) &= ~I2C_CKCFG_DTCY;

        } else {

            /* I2C duty cycle is 16/9 */

            clkc = (uint32_t) (pclk1 / (clkspeed * 25U));

            I2C_CKCFG(i2c_periph) |= I2C_CKCFG_DTCY;

        }

        if (0U == (clkc & I2C_CKCFG_CLKC)) {

            /* the CLKC in fast mode minmum value is 1 */

            clkc |= 0x0001U;

        }

        I2C_CKCFG(i2c_periph) |= I2C_CKCFG_FAST;

        I2C_CKCFG(i2c_periph) |= clkc;

    } else {

    }

}

void i2c_ack_config(uint32_t i2c_periph, uint32_t ack) 

{

    if (I2C_ACK_ENABLE == ack) {

        I2C_CTL0(i2c_periph) |= I2C_CTL0_ACKEN;

    } else {

        I2C_CTL0(i2c_periph) &= ~(I2C_CTL0_ACKEN);

    }

}

void i2c_master_addressing(uint32_t i2c_periph, uint32_t addr,uint32_t trandirection) 

{

    /* master is a transmitter or a receiver */

    if (I2C_TRANSMITTER == trandirection) {

        addr = addr & I2C_TRANSMITTER;

    } else {

        addr = addr | I2C_RECEIVER;

    }

    /* send slave address */

    I2C_DATA(i2c_periph) = addr;

}

void i2c_enable(uint32_t i2c_periph) 

{

    I2C_CTL0(i2c_periph) |= I2C_CTL0_I2CEN;

}

void i2c_disable(uint32_t i2c_periph) 

{

    I2C_CTL0(i2c_periph) &= ~(I2C_CTL0_I2CEN);

}

void i2c_start_on_bus(uint32_t i2c_periph) 

{

    I2C_CTL0(i2c_periph) |= I2C_CTL0_START;

}

void i2c_stop_on_bus(uint32_t i2c_periph)

{

    I2C_CTL0(i2c_periph) |= I2C_CTL0_STOP;

}

void i2c_interrupt_enable(uint32_t i2c_periph, i2c_interrupt_enum interrupt) 

{

    I2C_REG_VAL(i2c_periph, interrupt) |= BIT(I2C_BIT_POS(interrupt));

}

void i2c_interrupt_disable(uint32_t i2c_periph, i2c_interrupt_enum interrupt) 

{

    I2C_REG_VAL(i2c_periph, interrupt) &= ~BIT(I2C_BIT_POS(interrupt));

}

FlagStatus i2c_interrupt_flag_get(uint32_t i2c_periph,i2c_interrupt_flag_enum int_flag) 

{

    uint32_t intenable = 0U, flagstatus = 0U, bufie;


    /* check BUFIE */

    bufie = I2C_CTL1(i2c_periph) & I2C_CTL1_BUFIE;


    /* get the interrupt enable bit status */

    intenable = (I2C_REG_VAL(i2c_periph, int_flag) & BIT(I2C_BIT_POS(int_flag)));

    /* get the corresponding flag bit status */

    flagstatus = (I2C_REG_VAL2(i2c_periph, int_flag)& BIT(I2C_BIT_POS2(int_flag)));


    if ((I2C_INT_FLAG_RBNE == int_flag) || (I2C_INT_FLAG_TBE == int_flag)) {

        if (intenable && bufie) {

            intenable = 1U;

        } else {

            intenable = 0U;

        }

    }

    if ((0U != flagstatus) && (0U != intenable)) {

        return SET;

    } else {

        return RESET;

    }

}

void i2c_interrupt_flag_clear(uint32_t i2c_periph,i2c_interrupt_flag_enum int_flag) 

{

    uint32_t temp;

    if (I2C_INT_FLAG_ADDSEND == int_flag) {

        /* read I2C_STAT0 and then read I2C_STAT1 to clear ADDSEND */

        temp = I2C_STAT0(i2c_periph);

        temp = I2C_STAT1(i2c_periph);

    } else {

        I2C_REG_VAL2(i2c_periph, int_flag) &= ~BIT(I2C_BIT_POS2(int_flag));

    }

}

       此次分享就到这里啦,SPI与I2C通信还是比较复杂,没有串口那么简单了,因为存在主从关系,官方也提供了相应的demo示例,这里不再赘述。感谢各位聆听,咱们下期再会。

I2C体验.jpg




关键词: GD32VF103     RISC-V     开发    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]