nRF52832学习记录(十一、TWI总线的应用 SHT21程序移植)

本文详细介绍了nRF52系列MCU的I2C(TWI)接口,包括其基础、寄存器、库函数以及如何初始化和使用。特别地,文章通过SHT21温湿度传感器的移植示例,展示了如何利用库函数进行数据传输,并分析了移植过程中的问题和解决方案。内容涵盖了TWI的使能、发送和接收函数的使用方法。
摘要由CSDN通过智能技术生成

1、nRF52xx TWI介绍

TWI总线基础介绍

nRF52系列处理器的I2C总线 叫做 TWI (I2C compatible two-wire interface),I2C兼容双线接口。I2C通讯为半双工,协议的基础知识我们这里就不过多的介绍,主要介绍一下nRF52系列处理器上的I2C资源。
在nRF52832处理器中,有2条 TWI 总线,TWI0 和 TWM1。
如果TWI 作为主机使用 带有 EasyDMA 则称为 TWIM。
如果TWI 作为从机使用 带有 EasyDMA 则称为 TWIS。

  • I2C兼容
  • 速率可达 100kbps,250kbps 或者 400kbps
  • 支持时钟延长
  • 带 EasyDmA

一次只能分配一个外设来驱动特定的GPIO引脚,同时为了确保在系统处于关闭模式时 TWI 主机所使用的引脚上正确的电平以及当TWI主机被禁用时,这些引脚必须按照下图所示进行配置:
在这里插入图片描述

TWI总线寄存器

11.杀

TWI 库函数介绍

在这一篇记录中,直接开一个小章节先熟悉一下需要用到的库函数

TWI初始化函数

nrf_drv_twi_init 有4个参数:
第一个参数选择使用哪个 TWI 模块(0或者1) 如果使用 EasyDMA 就是TWIM
第二个参数是 TWI 的配置,使用哪个 IO 口、频率、中断优先级等
第三个参数是 自定义的事件处理函数,如果设置为NULL, 则使能 TWI 的阻塞模式。
第四个参数 写 NULL暂时,不太清楚,也是处理函数?第三个写NULL,所以这个也设置为NULL

/*
第一个参数就是定义使用哪一个 TWI 或者是 TWIM(如果使用EasyDMA的话):
        typedef struct
        {
            uint8_t inst_idx;
            union
            {
        #ifdef TWIM_PRESENT
                nrfx_twim_t twim;
        #endif
        #ifdef TWI_PRESENT
                nrfx_twi_t  twi;
        #endif
            } u;
            bool    use_easy_dma;
        } nrf_drv_twi_t;

    nrf_drv_twi_t 结构体中的 nrfx_twi_t 结构体
        typedef struct
        {
            NRF_TWI_Type * p_twi;        ///< Pointer to a structure with TWI registers.
            uint8_t        drv_inst_idx; ///< Driver instance index.
        } nrfx_twi_t;

    nrfx_twi_t 结构体中的  NRF_TWI_Type 就是对应的 TWI 的每个寄存器

    第二个参数定义 TWI 的配置:

     typedef struct
    {
        uint32_t                scl;                 ///< SCL pin number.
        uint32_t                sda;                 ///< SDA pin number.
        nrf_drv_twi_frequency_t frequency;           ///< TWI frequency.
        uint8_t                 interrupt_priority;  ///< Interrupt priority.
        bool                    clear_bus_init;      ///< Clear bus during init.
        bool                    hold_bus_uninit;     ///< Hold pull up state on gpio pins after uninit.
    } nrf_drv_twi_config_t;

    第三个参数是 用户提供的事件处理程序:

    第四个参数是 传递给事件处理的上下文?
 
*/
ret_code_t nrf_drv_twi_init(nrf_drv_twi_t const *        p_instance,
                            nrf_drv_twi_config_t const * p_config,
                            nrf_drv_twi_evt_handler_t    event_handler,
                            void *                       p_context)

TWI使能函数

void nrf_drv_twi_enable(nrf_drv_twi_t const * p_instance) 就一个参数,和初始化函数的第一个参数一致,使用哪个 TWI 模块

TWI 主机发送数据给从机 函数

nrf_drv_twi_tx 有5个参数:
第一个参数,和上面介绍的一样,哪个 TWI 模块;
第二个参数 ,从机的地址;
注意从机地址 address,函数接收的是 7位地址,函数内部会自己加上读写位。
第三个参数,指向发送缓冲区的指针,发送的数据(一个字节);
第四个参数,发送数据的长度(发送几个一个字节的数据);
第五个参数, 如果设置了 stop 条件,在传输成功完成以后,总线上不会生成 stop 信号(允许在下一次传输中重复启动)。

/*
nrfx_err_t nrfx_twi_tx(nrfx_twi_t const * p_instance,
                       uint8_t            address,
                       uint8_t    const * p_data,
                       size_t             length,
                       bool               no_stop)
{
    nrfx_twi_xfer_desc_t xfer = NRFX_TWI_XFER_DESC_TX(address, (uint8_t*)p_data, length);

    return nrfx_twi_xfer(p_instance, &xfer, no_stop ? NRFX_TWI_FLAG_TX_NO_STOP : 0);
}
*/
ret_code_t nrf_drv_twi_tx(nrf_drv_twi_t const * p_instance,
                          uint8_t               address,
                          uint8_t const *       p_data,
                          uint8_t               length,
                          bool                  no_stop)
{
    ret_code_t result = 0;
    if (NRF_DRV_TWI_USE_TWIM)
    {
        result = nrfx_twim_tx(&p_instance->u.twim,
                                address, p_data, length, no_stop);
    }
    else if (NRF_DRV_TWI_USE_TWI)
    {
        result = nrfx_twi_tx(&p_instance->u.twi,
                               address, p_data, length, no_stop);
    }
    return result;
}

TWI 主机从从机读取 函数

nrf_drv_twi_rx 有4个参数:
第一个参数,哪个 TWI 设备;
第二个参数,从机的地址;
第三个参数,指向 保存读取数据的缓存区 的指针;
第四个参数,接收数据的长度(1个长度一个字节)

ret_code_t nrf_drv_twi_rx(nrf_drv_twi_t const * p_instance,
                          uint8_t               address,
                          uint8_t *             p_data,
                          uint8_t               length)

2、nRF52xx TWI 使用示例

今天我们直接使用 TWI库函数移植一下 STH21 温湿度传感器的代码,熟悉一下库函数的使用,以前在使用STM32的时候,常用的都是软件 I2C,就是用IO口模拟的I2C接口,当然如果你能够实现软件 I2C,就说明对 I2C 的原理比较熟悉了

SHT21 程序移植示例

使用硬件 I2C 在库函数中都有提供好的函数,还是比较简单的,我们现在分析一下以前的 I2C 读取程序( I2C的驱动部分就不分析了):

 	/* --------------------
    // measure temperature
    1、发送一个读取 温度 指令 cmd 给SHT21 
    2、等待传感器读取数据
    3、读取传感器的数据 ,3个字节数据
    */
    i2c_start();     // send start sequence (S)
    u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_WRITE);//a write to slave 0x40 
    u8Ack = (u8Ack<<1)|i2c_write(SHT2X_CMD_MEAS_T);      //request to measure temperature	      
    i2c_stop();
	
    delay_ms(SHT2X_TEMP_MEAS_TIME);
            
    i2c_start();                 // send start sequence (SR)
    u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_READ); //  a read from slave 0x40
      
    if(u8Ack==I2C_ACK)  {
        aTemperature.raw = i2c_read(I2C_ACK)<<8;             // read hi byte 
        aTemperature.raw |= i2c_read(I2C_ACK);               // read lo byte
        aTemperature.crc = i2c_read(I2C_NACK);               // read check sum and finish transfere
    }else {
        aTemperature.raw = 0;                       
    }
    i2c_stop();

温度读取函数的步骤可以分为3步:

  1. 发送一个读取 温度 指令 cmd 给SHT21,那么我们在 nRF52832 上面使用库函数怎么实现,使用nrf_drv_twi_tx函数,刚开始的时候,我通过 mpu6050 的驱动修改,发现问题,后面我们会分析什么问题,使用这个函数只需要一句话:
    err_code = nrf_drv_twi_tx(&sht_twi, SHT2X_SLAVEADDRESS, tx_buf, 1, true);
    其中 tx_buf 就是需要发送的 cmd 命令;
  2. 等待传感器读取数据
  3. 读取传感器的数据 ,3个字节数据,读取的话我们使用 nrf_drv_twi_rx 函数,读取3个字节:err_code = nrf_drv_twi_rx(&sht_twi, SHT2X_SLAVEADDRESS, destination, 3);
    其中 destination 是你需要保存的缓冲区的地址;

最后移植成功的的 sht21.c 程序如下:

/****************************************Copyright (c)************************************************
sht21 移植程序
2021/10/5  by qzh
根据手册,sht21读取的时候并不需要指定读取特定寄存器的地址
**---------------------------------------------------------------------------------------------------*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "nrf_drv_twi.h"
#include "sht21.h"

struct {
	  sint16 value;
	  uint16 raw;
	  uint8  crc;
} aTemperature, aHumidity;

//TWI驱动程序实例ID,ID和外设编号对应,0:TWI0  1:TWI1
#define TWI_INSTANCE_ID     0
//TWI传输完成标志
static volatile bool m_xfer_done = false;
//定义TWI驱动程序实例,名称为sht_twi
static const nrf_drv_twi_t sht_twi = NRF_DRV_TWI_INSTANCE(TWI_INSTANCE_ID);

//TWI事件处理函数
void sht_twi_handler(nrf_drv_twi_evt_t const * p_event, void * p_context)
{
    //判断TWI事件类型
	switch (p_event->type)
    {
        //传输完成事件
		case NRF_DRV_TWI_EVT_DONE:
            m_xfer_done = true;//置位传输完成标志
            break;
        default:
            break;
    }
}

//TWI初始化
void twi_sht21_init(void)
{
    ret_code_t err_code;

    const nrf_drv_twi_config_t twi_config = {
       .scl                = SHT_SCL,  //定义TWI SCL引脚
       .sda                = SHT_SDA,  //定义TWI SDA引脚
       .frequency          = NRF_DRV_TWI_FREQ_100K, //TWI速率
       .interrupt_priority = APP_IRQ_PRIORITY_HIGH, //TWI优先级
       .clear_bus_init     = false//初始化期间不发送9个SCL时钟
    };

    err_code = nrf_drv_twi_init(&sht_twi, &twi_config, sht_twi_handler, NULL);
	//检查返回的错误代码
    APP_ERROR_CHECK(err_code);
    //使能TWI
    nrf_drv_twi_enable(&sht_twi);
    printf("twi_master_init ok.\r\n"); 
}
 
/*
在网上参考的I2C设备扫描程序
如果在非阻塞模式下面(目前Demo使用的模式),这个函数直接用会有点问题,出一些错误
如果加上while (m_xfer_done == false){},会“卡死”,出不去
****
如果使用阻塞模式,下面这个行数能够在正常执行,能够获取到 I2C 设备的地址
所以一般使用建议使用阻塞模式
****
*/
void iic_scan_address(void)
{ 
    ret_code_t err_code;
    uint8_t address;
    uint8_t sample_data;
    bool detected_device = false;
    printf("TWI scanner started.\r\n");
    
    twi_sht21_init();//twi init
    nrf_delay_ms(2000);
    for (address = 1; address <= 127; address++)
    {
        err_code = nrf_drv_twi_rx(&sht_twi, address, &sample_data, sizeof(sample_data));
        if (err_code == NRF_SUCCESS)
        {
            detected_device = true;
            printf("TWI-i2c device detected at address 0x%x.\r\n", address);

        }
    }
    if (!detected_device) 
    {
        printf("No device was found.\r\n");
    }
    printf("TWI device scan ended.\r\n");
}

//    fT  = 175.72*u16T/65536.0 - 46.85;
//    fRH = 125.0*u16RH/65536.0 - 6.0;
// -------------------------------------------------------------------
sint16 sht21_calcRH(uint16 u16RH)
{
  sint16 humidityRH;              // variable for result

  u16RH &= ~0x0003;          // clear bits [1..0] (status bits)
  //-- calculate relative humidity [%RH] --

  humidityRH = (sint16)(-600 + (12500*(sint32)u16RH)/65536 ); // RH = -6 + 125 * SRH/2^16
  return humidityRH;                                          // Return RH*100
}

sint16 sht21_calcTemperature(uint16 u16T)
{
  sint16 temperature;            // variable for result

  u16T &= ~0x0003;           // clear bits [1..0] (status bits)

  //-- calculate temperature [癈] --
  temperature= (sint16)(-4685 + (17572*(sint32)u16T)/65536); //T = -46.85 + 175.72 * ST/2^16
  return temperature;                                        //return T*100
}

/*
读取函数,使用库函数,I2C_start 和 ack 会自动处理
注意sht21 的手册, 只需要对 设备地址 写数据 读数据,没有额外特定的寄存器
*/
//bool sht_write(uint8_t register_address, uint8_t value) //这个函数使用需要在需要读取特定的寄存器
bool sht_write(uint8_t cmd)
{
    ret_code_t err_code;
	uint8_t tx_buf[1];

    //准备写入的数据
	// tx_buf[0] = register_address;
    // tx_buf[1] = value;
    tx_buf[0] = cmd;

    m_xfer_done = false;

    err_code = nrf_drv_twi_tx(&sht_twi, SHT2X_SLAVEADDRESS, tx_buf, 1, true);

	while (m_xfer_done == false){}

	if (NRF_SUCCESS != err_code)
    {
        return false;
    }
	return true;	
}

/*
读取函数,
注意SHT21,等待时间直接读取就可以
现在的很多I2C传感器作为从设备,读取的步骤:
1、主机 先写一个 寄存器 地址发送给从设备,所以在读函数里面还是得先调用一次写;
2、然后 主机再 去按照读取的数据大小读取

SHT21 不需要!! 注意代码中注释掉的部分
*/
//bool sht_read(uint8_t register_address, uint8_t * destination, uint8_t number_of_bytes)
bool sht_read(uint8_t * destination, uint8_t number_of_bytes)
{
    ret_code_t err_code;
    //TWI传输完成标志设置为false
    // m_xfer_done = false;
    // err_code = nrf_drv_twi_tx(&sht_twi, SHT2X_SLAVEADDRESS, &register_address, 1, true);
    // //等待TWI总线传输完成
    // while (m_xfer_done == false){}
    // if (NRF_SUCCESS != err_code)
    // {
    //     return false;
    // }
    //TWI传输完成标志设置为false
    m_xfer_done = false;
    err_code = nrf_drv_twi_rx(&sht_twi, SHT2X_SLAVEADDRESS, destination, number_of_bytes);
    //等待TWI总线传输完成
    while (m_xfer_done == false){}
    if (NRF_SUCCESS != err_code)
    {
        return false;
    }
    return true;
}

void SHT2X_THMeasure() {

    uint8 t_value[3];
    uint8 h_value[3];

    #if (SHT2X_RESOLUTION != 0x00)		                                      // only needed if used resolution other than default
    i2c_start();                                                        // send start sequence (S)
    u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_WRITE);               // write to slave 0x40
    u8Ack = (u8Ack<<1)|i2c_write(SHT2X_CMD_RD_REG);                     // request to read from user register  
      
    i2c_start();                                                      	// send start sequence (S)
    u8Ack = (u8Ack<<1)|i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_READ);     // read from slave 0x40
    u8UserReg = i2c_read(I2C_NACK);                                   	// read user register       
      
    i2c_start();                                                      	// send start sequence (S)  
    u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_WRITE);               // write to slave 0x40
    u8Ack = (u8Ack<<1)|i2c_write(SHT2X_CMD_WR_REG);                     // request to write user register
    u8Ack = (u8Ack<<1)|i2c_write(SHT2X_RESOLUTION | (u8UserReg & ~0x81)); // write new user register data
    #endif//(SHT2X_RESOLUTION != 0x00)	
    // --------------------
    // measure temperature
    // --------------------
    // i2c_start();     // send start sequence (S)
    // u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_WRITE);//a write to slave 0x40 
    // u8Ack = (u8Ack<<1)|i2c_write(SHT2X_CMD_MEAS_T);      //request to measure temperature	      
    // i2c_stop();

    //sht_write(SHT2X_SLAVEADDRESS,SHT2X_CMD_MEAS_T);  //发送一条数据就可以等待测量
    sht_write(SHT2X_CMD_MEAS_T);

	nrf_delay_ms(SHT2X_TEMP_MEAS_TIME);
    // delay_ms(SHT2X_TEMP_MEAS_TIME);
    //time_wait(SHT2X_TEMP_MEAS_TIME);
            
    // i2c_start();                 // send start sequence (SR)
    // u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_READ);  //a read from slave 0x40
      
    // if(u8Ack==I2C_ACK)  {
    //     aTemperature.raw = i2c_read(I2C_ACK)<<8;                        // read hi byte 
    //     aTemperature.raw |= i2c_read(I2C_ACK);                          // read lo byte
    //     aTemperature.crc = i2c_read(I2C_NACK);                          // read check sum and finish transfere
    // }else {
    //     aTemperature.raw = 0;                       
    // }
    // i2c_stop();
    // sht_read(SHT2X_SLAVEADDRESS,t_value,3);
    // nrf_delay_ms(2);
    // aTemperature.raw = t_value[0]<<8;
    // aTemperature.raw |= t_value[1];
    // aTemperature.crc = t_value[2]; 
    if(sht_read(t_value,3)== true){
        aTemperature.raw = t_value[0]<<8;
        aTemperature.raw |= t_value[1];
        aTemperature.crc = t_value[2];  
    }else{
        aTemperature.raw = 0;  
    }
    // -------------------------
    // Humidity Measure 
    // -------------------------
    // i2c_start();                                                      // send start sequence (S)
    // u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_WRITE);             // a write to slave 0x40      		 1000 0000
    // u8Ack = (u8Ack<<1)|i2c_write(SHT2X_CMD_MEAS_RH);                  // request to measure humidity F5  1110 0101
	// i2c_stop();
				
	//sht_write(SHT2X_SLAVEADDRESS,SHT2X_CMD_MEAS_RH);  //发送一条数据就可以等待测量
    sht_write(SHT2X_CMD_MEAS_RH);
	nrf_delay_ms(SHT2X_HUMI_MEAS_TIME);	
	// shortTermSleep(SHT2X_HUMI_MEAS_TIME);
    // delay_ms(SHT2X_HUMI_MEAS_TIME);
				
    // i2c_start();                                                      // send start sequence (SR)
    // u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_READ);              // read from slave 0x40     			 1000 0001

    // if(u8Ack==I2C_ACK)  {                                             // timeout
    //     aHumidity.raw = i2c_read(I2C_ACK)<<8;                           // read hi byte 
    //     aHumidity.raw |= i2c_read(I2C_ACK);                             // read lo byte
    //     aHumidity.crc = i2c_read(I2C_NACK);                           // read check sum and finish transfere
    // }else{
    //     aHumidity.raw = 0;
    // }
	// i2c_stop();

    if(sht_read(h_value,3)== true){
        aHumidity.raw = h_value[0] <<8;                           // read hi byte 
        aHumidity.raw |= h_value[1];                             // read lo byte
        aHumidity.crc = h_value[2];                           // read check sum and finish transfere 
    }else{
        aTemperature.raw = 0;  
    }				   	
    aTemperature.value = sht21_calcTemperature(aTemperature.raw);
    aHumidity.value = sht21_calcRH(aHumidity.raw);      // signed value, temperature = aTemperature.value * 0.01?
    if(aTemperature.crc!= sht21_CRC((uint8*)&aTemperature.raw, 2))  {}
    if(aHumidity.crc!= sht21_CRC((uint8*)&aHumidity.raw, 2))  {}
    if(aTemperature.value>5100) aTemperature.value=5100;              //prevent temperature over-/underflow
    else if(aTemperature.value<0) aTemperature.value=0;
    if(aHumidity.value>10000) aTemperature.value=10000;              //prevent temperature over-/underflow
    else if(aTemperature.value<0) aTemperature.value=0;
}

uint16_t getTemperature() {
  return aTemperature.value;
}

uint16_t getHumidity() {
  return aHumidity.value;
}

//==============================================================================
uint8 sht21_CRC(uint8 value[], uint8 u8Bytes) {
//  CRC
    const uint16 POLYNOMIAL = 0x131;  //P(x)=x^8+x^5+x^4+1 = 100110001
    uint8 crc = 0;	
    uint8 byteCtr;
    uint8 bitCtr;

    //calculates 8-Bit checksum with given polynomial
    for (byteCtr = 0; byteCtr < u8Bytes; ++byteCtr) { 
        crc ^= (value[byteCtr]);
        for (bitCtr = 8; bitCtr > 0; --bitCtr)
        { 
          if (crc & 0x80) crc = (crc << 1) ^ POLYNOMIAL;
          else crc = (crc << 1);
        }
    }
    return crc;
}  

额外说明,上面的 TWI 初始化使用了非阻塞模式,如果使用阻塞模式
1、初始化函数变成
err_code = nrf_drv_twi_init(&sht_twi, &twi_config, NULL, NULL);
2、去掉事件处理函数
// void sht_twi_handler(nrf_drv_twi_evt_t const * p_event, void * p_context){...}
3、相应的位置(读写函数中读写数据后)去掉标志位的等待
// while (m_xfer_done == false){}

SHT21 移植问题分析

在 SHT21 移植过程中,开始按照 MPU6050 的程序修改的写函数 和 读函数如下:
bool sht_write(uint8_t register_address, uint8_t value)
bool sht_read(uint8_t register_address, uint8_t * destination, uint8_t number_of_bytes)
然后怎么都出来数据 ……

其实通过 SHT21 以前的程序,不需要指定特定的寄存器写命令和读取数据 ,就能发现和MPU6050的不同
在传感器使用手册中,关于SHT21 使用非主机模式读取 的例子:
在这里插入图片描述
我们再来看看 MPU6050 作为从机被写图示:
在这里插入图片描述
MPU6050 作为从机读取图示:
在这里插入图片描述
通过比较 SHT21 和 MPU6050 读写逻辑,我们可以直观的发现不同之处,所以最后将读写的函数由
bool sht_write(uint8_t register_address, uint8_t value)
bool sht_read(uint8_t register_address, uint8_t * destination, uint8_t number_of_bytes)
改成了
bool sht_write(uint8_t cmd)
bool sht_read(uint8_t * destination, uint8_t number_of_bytes)
具体的实现逻辑可以看上面给出的 sht21.c 程序

最后在主函数中调用

  while(true)
  {
    SHT2X_THMeasure();
    T=(float)getTemperature();
 	H=(float)getHumidity();
    T=(getTemperature()/100.0);
    H=(getHumidity()/100.0);      //OK
    printf("\r\n%4.2f C\r\n%4.2f%%\r\n",T,H);
    nrf_delay_ms(2000);
  }

使用阻塞模式最后的测试效果:
在这里插入图片描述

SHT30 程序(待更新)

SHT30 等项目上开始使用的时候,来更新一下代码

MPU6050 程序(待更新)

同上

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

矜辰所致

您们的鼓励是我奋斗的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值