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总线寄存器
TWI 库函数介绍
在这一篇记录中,直接开一个小章节先熟悉一下需要用到的库函数
TWI初始化函数
nrf_drv_twi_init 有4个参数: 第一个参数选择使用哪个 TWI 模块(0或者1) 如果使用 EasyDMA 就是TWIM 第二个参数是 TWI 的配置,使用哪个 IO 口、频率、中断优先级等 第三个参数是 自定义的事件处理函数,如果设置为NULL, 则使能 TWI 的阻塞模式。 第四个参数 写 NULL暂时,不太清楚,也是处理函数?第三个写NULL,所以这个也设置为NULL
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 信号(允许在下一次传输中重复启动)。
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的驱动部分就不分析了):
i2c_start();
u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_WRITE);
u8Ack = (u8Ack<<1)|i2c_write(SHT2X_CMD_MEAS_T);
i2c_stop();
delay_ms(SHT2X_TEMP_MEAS_TIME);
i2c_start();
u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_READ);
if(u8Ack==I2C_ACK) {
aTemperature.raw = i2c_read(I2C_ACK)<<8;
aTemperature.raw |= i2c_read(I2C_ACK);
aTemperature.crc = i2c_read(I2C_NACK);
}else {
aTemperature.raw = 0;
}
i2c_stop();
温度读取函数的步骤可以分为3步:
- 发送一个读取 温度 指令 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 命令; - 等待传感器读取数据
- 读取传感器的数据 ,3个字节数据,读取的话我们使用
nrf_drv_twi_rx 函数,读取3个字节:err_code = nrf_drv_twi_rx(&sht_twi, SHT2X_SLAVEADDRESS, destination, 3); 其中 destination 是你需要保存的缓冲区的地址;
最后移植成功的的 sht21.c 程序如下:
#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;
#define TWI_INSTANCE_ID 0
static volatile bool m_xfer_done = false;
static const nrf_drv_twi_t sht_twi = NRF_DRV_TWI_INSTANCE(TWI_INSTANCE_ID);
void sht_twi_handler(nrf_drv_twi_evt_t const * p_event, void * p_context)
{
switch (p_event->type)
{
case NRF_DRV_TWI_EVT_DONE:
m_xfer_done = true;
break;
default:
break;
}
}
void twi_sht21_init(void)
{
ret_code_t err_code;
const nrf_drv_twi_config_t twi_config = {
.scl = SHT_SCL,
.sda = SHT_SDA,
.frequency = NRF_DRV_TWI_FREQ_100K,
.interrupt_priority = APP_IRQ_PRIORITY_HIGH,
.clear_bus_init = false
};
err_code = nrf_drv_twi_init(&sht_twi, &twi_config, sht_twi_handler, NULL);
APP_ERROR_CHECK(err_code);
nrf_drv_twi_enable(&sht_twi);
printf("twi_master_init ok.\r\n");
}
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();
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");
}
sint16 sht21_calcRH(uint16 u16RH)
{
sint16 humidityRH;
u16RH &= ~0x0003;
humidityRH = (sint16)(-600 + (12500*(sint32)u16RH)/65536 );
return humidityRH;
}
sint16 sht21_calcTemperature(uint16 u16T)
{
sint16 temperature;
u16T &= ~0x0003;
temperature= (sint16)(-4685 + (17572*(sint32)u16T)/65536);
return temperature;
}
bool sht_write(uint8_t cmd)
{
ret_code_t err_code;
uint8_t tx_buf[1];
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;
}
bool sht_read(uint8_t * destination, uint8_t number_of_bytes)
{
ret_code_t err_code;
m_xfer_done = false;
err_code = nrf_drv_twi_rx(&sht_twi, SHT2X_SLAVEADDRESS, destination, number_of_bytes);
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)
i2c_start();
u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_WRITE);
u8Ack = (u8Ack<<1)|i2c_write(SHT2X_CMD_RD_REG);
i2c_start();
u8Ack = (u8Ack<<1)|i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_READ);
u8UserReg = i2c_read(I2C_NACK);
i2c_start();
u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_WRITE);
u8Ack = (u8Ack<<1)|i2c_write(SHT2X_CMD_WR_REG);
u8Ack = (u8Ack<<1)|i2c_write(SHT2X_RESOLUTION | (u8UserReg & ~0x81));
#endif
sht_write(SHT2X_CMD_MEAS_T);
nrf_delay_ms(SHT2X_TEMP_MEAS_TIME);
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;
}
sht_write(SHT2X_CMD_MEAS_RH);
nrf_delay_ms(SHT2X_HUMI_MEAS_TIME);
if(sht_read(h_value,3)== true){
aHumidity.raw = h_value[0] <<8;
aHumidity.raw |= h_value[1];
aHumidity.crc = h_value[2];
}else{
aTemperature.raw = 0;
}
aTemperature.value = sht21_calcTemperature(aTemperature.raw);
aHumidity.value = sht21_calcRH(aHumidity.raw);
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;
else if(aTemperature.value<0) aTemperature.value=0;
if(aHumidity.value>10000) aTemperature.value=10000;
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) {
const uint16 POLYNOMIAL = 0x131;
uint8 crc = 0;
uint8 byteCtr;
uint8 bitCtr;
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);
printf("\r\n%4.2f C\r\n%4.2f%%\r\n",T,H);
nrf_delay_ms(2000);
}
使用阻塞模式最后的测试效果:
SHT30 程序(待更新)
SHT30 等项目上开始使用的时候,来更新一下代码
MPU6050 程序(待更新)
同上
|