/******************************************************************************/
/*开发平台:Keil uVision5 */
/*开发语言:C语言 */
/*作者:jerseyceo */
/*实现功能:地磁传感器的应用(指南针) */
/******************************************************************************/
第一步:nrf52832开发板与MMC5603地磁传感器硬件连接
注:MMC5603NJ可以从单个1.62V到3.6 v供应。下面的电路连接图说明了电源连接选项。
nrf52832 MMC5603
引脚: VDD —> VDD
引脚: GND —> GND
引脚: SCL —> SCL
引脚: SDA —> SDA
NOTE:焊接引脚连接上电后,使用万用表测量传感器VDD与GND引脚两端电压,检测传感器是否处于正常工作电压范围,以便进行接下来的keil编程调试与开发。
第二步:学习并掌握硬件TWI(I2C)通信原理,使用I2C通信协议配置传感器工作参数。
1.库函数的应用
图1:TWI应用步骤
1.1.定义TWI驱动程序实例
/* TWI instance ID,ID和外设编号对应,0:TWI0 1:TWI1*/
#define TWI_INSTANCE_ID 0
/* TWI instance. 定义TWI驱动程序实例,使用TWI0:外设0*/
static const nrf_drv_twi_t m_twi = NRF_DRV_TWI_INSTANCE(TWI_INSTANCE_ID);
/* 从机设备地址0x60=0x30<<1(TWI硬件自动完成读写位)*/
#define MMC5603_7BITI2C_ADDRESS 0x30
1.2.初始化TWI
初始化实例:err_code = nrf_drv_twi_init(&m_twi, &twi_mmc5603_config, twi_handler, NULL);
1.2.1.初始化TWI函数原型:
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)
p_instance:指向驱动程序实例结构体
p_config:指向初始化配置结构体
event_handler:事件句柄,如果设置为NULL,则使能TWI阻塞模式
p_context:指向传递给事件句柄的context
1.2.2.初始化配置结构体(nrf_drv_twi_config_t) :
const nrf_drv_twi_config_t twi_mmc5603_config = {
.scl = ARDUINO_SCL_PIN, //定义TWI SCL 引脚
.sda = ARDUINO_SDA_PIN, //定义TWI SDA 引脚
.frequency = NRF_DRV_TWI_FREQ_100K, //TWI 工作频率
.interrupt_priority = APP_IRQ_PRIORITY_HIGH, //TWI优先级
.clear_bus_init = false };
1.2.3.TWI事件句柄(nrf_drv_twi_evt_handler_t) :(TWI工作于非阻塞模式)
//TWI传输完成标志,false表示传输未完成
static volatile bool m_xfer_done = false;
void twi_handler(nrf_drv_twi_evt_t const * p_event, void * p_context)
{
//判断TWI事件类型
switch (p_event->type)
{
//传输事件完成
case NRF_DRV_TWI_EVT_DONE:
//功能代码
if (p_event->xfer_desc.type == NRF_DRV_TWI_XFER_RX)
{
//此处可对传输事件完成后,打印读取数据日志信息。
}
m_xfer_done = true;//置位传输完成标志
break;
case NRF_DRV_TWI_EVT_ADDRESS_NACK: //< Error event: NACK received after sending the address.
case NRF_DRV_TWI_EVT_DATA_NACK : //< Error event: NACK received after sending a data byte.
default:
break;
}
}
1.2.4.使能TWI(nrf_drv_twi_enable):初始化完成后,使能TWI才能进行数据传输
__STATIC_INLINE
void nrf_drv_twi_enable(nrf_drv_twi_t const * p_instance)
{
if (NRF_DRV_TWI_USE_TWIM)
{
nrfx_twim_enable(&p_instance->u.twim);
}
else if (NRF_DRV_TWI_USE_TWI)
{
nrfx_twi_enable(&p_instance->u.twi);
}
}
1.2.5.数据传输
数据发送:向TWI从机发送数据
实例:err_code_tx = nrf_drv_twi_tx(&m_twi, i2c_add, ®_add, 1, false);
ret_code_t nrf_drv_twi_tx(
nrf_drv_twi_t const * p_instance,//指向TWI驱动程序实例结构体
uint8_t address,//指定的从机地址(7位LSB)
uint8_t const * p_data,//指向传输数据缓存(传感器内部寄存器/写入寄存器的命令)
uint8_t length,//发送的字节数
bool no_stop)//如果置位,成功传输后不会生成停止条件(下次传输中可重复启动)
数据接收:从TWI从机读取数据
实例:ret_code_t err_code_rx = nrf_drv_twi_rx(&m_twi, i2c_add, data, sizeof(data));
ret_code_t nrf_drv_twi_rx(
nrf_drv_twi_t const * p_instance,//指向TWI驱动程序实例结构体
uint8_t address,//指定的从机地址(7位LSB)
uint8_t * p_data,//指向接收数据缓存
uint8_t length)//读取的字节数
2.MMC5603地磁传感器原始数据读取(TWI0非阻塞)程序调试
2.1.MMC5603 I2C 时序
MMC5603 数据传输的时序如下图所示,首先产生起始条件,紧跟着发送7位地址 + 读
写位(0=写,1=读),之后发送传输的数据,最后产生停止条件。
图2:MMC5603 数据传输时序
*_*:根据 MMC5603 数据传输时序图,对照MMC5603的寄存器表,编写“写MMC5603寄存器”
和“读MMC5603寄存器”的函数。在编写读/写函数时,需要注意的是:
(1)因为时序要求写操作后产生停止条件,因此调用nrf_drv_twi_tx()函数时,参数“no_stop”必
须为false,即产生停止条件。
(2)配置TWI的工作模式是非阻塞模式,是异步的,所以调用nrf_drv_twi_tx()函数后,要等待
TWI传输完成,代码中通过查询m_xfer_done标志判断TWI传输是否完成。
2.2.写MMC5603寄存器函数
/*@brief I2C write register */
int MMC5603_Write_Reg(unsigned char i2c_add, unsigned char reg_add, unsigned char cmd)
{
/* i2c_add is the 7-bit i2c address of the sensor
* reg_add is the register address to write
* cmd is the value that need to be written to the register
* I2C operating successfully, return 1, otherwise return 0;
*/
m_xfer_done = false;//TWI传输完成标志位设置为 false,表示传输未完成
ret_code_t err_code_tx; /*指示TWI硬件驱动API调用过程的成功或失败*/
uint8_t reg[2]={reg_add,cmd};/*等待写入的寄存器地址,要写入的命名*/
err_code_tx = nrf_drv_twi_tx(&m_twi, i2c_add, reg, sizeof(reg), false);/*写入数据*/
APP_ERROR_CHECK(err_code_tx);/*检查写入过程成功或失败*/
Delay_Ms(5);//硬件I2C实现数据传输需要处理时间,设置延时,避免数据传输冲突
while (m_xfer_done == false);/*一直等待TWI总线传输完成,直到m_xfer_done置为true,跳出函数,结束写操作*/
return 1;
}
NOTE:写MMC5603寄存器函数功能检查(示波器法)
步骤:用示波器线分别连接好SCL和GND及SDA和GND,运行写函数,观察数据传输时序波形图,如下图:
传输时序:起始条件+从机地址/写位+应答位+发送的寄存器地址数据0x28+应答位+写入寄存器的数据0x24+应答位+停止条件
解析:示波器所示的传输时序符合条件,可判断写寄存器函数功能正常。
2.3.单字节读MMC5603寄存器函数
/*@brief I2C read register.*/
int MMC5603_Read_Reg(unsigned char i2c_add, unsigned char reg_add, unsigned char *data)
{
/* i2c_add is the 7-bit i2c address of the sensor
* reg_add is the register address to read
* data is the first address of the buffer that need to store the register value
* I2C operating successfully, return 1, otherwise return 0;
*/
//先写寄存器,确定要读取的(只读)从机寄存器,此处不需要(无法)向目标寄存器写命令;
//再读寄存器,从slave目标(只读)寄存器读取数据到指定数据缓存区;
/* 步骤一: 写寄存器
m_xfer_done = false;//TWI传输完成标志位设置为 false,表示传输未完成
ret_code_t err_code_tx; /*指示TWI硬件驱动API调用过程中发送数据接口函数调用的成功或失败*/
err_code_tx = nrf_drv_twi_tx(&m_twi, i2c_add, ®_add, 1, false);//发送要读取的目标寄存器地址,长度为1字节
APP_ERROR_CHECK(err_code_tx);/*检查写入过程的成功或失败*/
while (m_xfer_done == false);/*一直等待TWI总线传输完成,直到m_xfer_done置为true,跳出函数,结束写操作*/
/* 步骤一: 读寄存器
m_xfer_done = false;//TWI传输完成标志位设置为 false,表示传输未完
/* 从指定地址读取一个字节*/
ret_code_t err_code_rx = nrf_drv_twi_rx(&m_twi, i2c_add, dataBuffer, sizeof(dataBuffer));//将从指定寄存器读取的数据(一个字节)存入指针dataBuffer指向的缓存区。
APP_ERROR_CHECK(err_code_rx);/*检查读取过程的成功或失败*/
while (m_xfer_done == false);/*一直等待TWI总线传输完成,直到m_xfer_done置为true,跳出函数,结束读操作*/
Delay_Ms(5);//硬件I2C实现数据传输需要处理时间,设置延时,避免数据传输冲突
return 1;
}
2.4.多字节读MMC5603寄存器函数
int MMC5603_MultiRead_Reg(unsigned char i2c_add, unsigned char reg_add, int bytesNumber, unsigned char *dataBuffer)
{
/* i2c_add is the 7-bit i2c address of the sensor
* reg_add is the first register address to read
* num is the number of the register to read
* data is the first address of the buffer that need to store the register value
* I2C operating successfully, return 1, otherwise return 0;
*/
//先写寄存器,确定要读取的(只读)从机寄存器,此处不需要(无法)向目标寄存器写命令;
//再读寄存器,从slave目标(只读)寄存器读取数据到指定数据缓存区;
/* 步骤一: 写寄存器
m_xfer_done = false;//TWI传输完成标志位设置为 false,表示传输未完成
ret_code_t err_code_tx; /*指示TWI硬件驱动API调用过程中发送数据接口函数调用的成功或失败*/
err_code_tx = nrf_drv_twi_tx(&m_twi, i2c_add, ®_add, 1, false);//发送要读取的目标寄存器地址,长度为1字节,将no_stop置为true,表示连续写多个寄存器,写完一个寄存器后停止。
APP_ERROR_CHECK(err_code_tx);/*检查写入过程的成功或失败*/
while (m_xfer_done == false);/*一直等待TWI总线传输完成,直到m_xfer_done置为true,跳出函数,结束写操作*/
/* 步骤一: 读寄存器
m_xfer_done = false;//TWI传输完成标志位设置为 false,表示传输未完成
/* 从指定地址读取一个字节*/
ret_code_t err_code_rx = nrf_drv_twi_rx(&m_twi, i2c_add, dataBuffer, sizeof(dataBuffer));//将从指定寄存器读取的数据(多个字节)存入指针data指向的缓存区。读取时寄存器地址自增,读取的数据依次不断改变,存入指针data指向的缓存区。
APP_ERROR_CHECK(err_code_rx);/*检查读取过程的成功或失败*/
while (m_xfer_done == false);/*一直等待TWI总线传输完成,直到m_xfer_done置为true,跳出函数,结束读操作*/
Delay_Ms(5);//硬件I2C实现数据传输需要处理时间,设置延时,避免数据传输冲突
return 1;
}
3.建立KEIL工程调试程序
3.1.检查产品ID,判断master与slave是否正常通信,检测单字节读取寄存器函数功能是否正常
/*********************************************************************************
* decription: Product ID check 产品ID检查
*********************************************************************************/
int MMC5603_CheckID(void)
{
unsigned char pro_id = 0;
/* Read register 0x39, check product ID */
MMC5603_Read_Reg(MMC5603_7BITI2C_ADDRESS, MMC5603_REG_PRODUCTID1, &pro_id);
if(pro_id != MMC5603_PRODUCT_ID)
return -1;
return 1;
}
3.2.自动校准寄存器配置:采用片上自检信号对传感器进行自诊断(自我校准),检测多字节读取寄存器函数功能是否正常,
流程:校准过程排除环境中的磁场对地磁场的干扰,当地的磁偏角对磁场测量也有影响
1)读出存储在寄存器27H/28H/29H处的自检信号
2)计算自检信号阈值,80%的数据从上述寄存器中读出。
3)将阈值写入寄存器1EH、1FH和20H。
4)写入[01000001](TM_M和auto_st_en high)到内部控制寄存器0(1BH)进行自检。
5)读出设备上状态寄存器1 (18H) 的Sat_sensor位的值。
6) Sat_sensor=0,自检合格
/*********************************************************************************
* decription: Auto self-test registers configuration自动校准寄存器配置
*********************************************************************************/
void MMC5603_Auto_SelfTest_Configuration(void)
{
int i;
uint8_t reg_value[3];
int16_t st_thr_data[3]={0};
int16_t st_thr_new[3]={0};
int16_t st_thd[3]={0};
uint8_t st_thd_reg[3];
/* Read trim data from reg 0x27-0x29 ,Read_only:reg_value[3]={0x5C,0x78,0x6E} */
MMC5603_MultiRead_Reg(MMC5603_7BITI2C_ADDRESS,MMC5603_REG_ST_X_VAL,3,reg_value);
for(i=0;i<3;i++)
{
st_thr_data[i] = (int16_t)(reg_value[i]-128)*32;
if(st_thr_data[i]<0)
st_thr_data[i] = -st_thr_data[i];
st_thr_new[i] = st_thr_data[i]-st_thr_data[i]/5;
st_thd[i] = st_thr_new[i]/8;
if(st_thd[i]>255)
st_thd_reg[i] = 0xFF;
else
st_thd_reg[i] = (uint8_t)st_thd[i];
}
/* Write threshold into the reg 0x1E-0x20 */
MMC5603_Write_Reg(MMC5603_7BITI2C_ADDRESS, MMC5603_REG_X_THD, st_thd_reg[0]);
MMC5603_Write_Reg(MMC5603_7BITI2C_ADDRESS, MMC5603_REG_Y_THD, st_thd_reg[1]);
MMC5603_Write_Reg(MMC5603_7BITI2C_ADDRESS, MMC5603_REG_Z_THD, st_thd_reg[2]);
return;
}
/*********************************************************************************
* decription: Auto self-test 自动校准
*********************************************************************************/
int MMC5603_Auto_SelfTest(void)
{
uint8_t reg_status = 0;
/* Write 0x40 to register 0x1B, set Auto_st_en bit high */
MMC5603_Write_Reg(MMC5603_7BITI2C_ADDRESS, MMC5603_REG_CTRL0, MMC5603_CMD_AUTO_ST_EN);
/* Delay 15ms to finish the selftest process */
Delay_Ms(15);
/* Read register 0x18, check Sat_sensor bit */
MMC5603_Read_Reg(MMC5603_7BITI2C_ADDRESS, MMC5603_REG_STATUS1, ®_status);
if((reg_status&MMC5603_SAT_SENSOR))//读取自检信号指示位Sat_sensor,自检合格后保持低位,读取状态寄存器1的值按位与0x20,Sat_sensor位为0,则按位与结果为0,即自检合格,返回值为1。
{
return -1;
}
return 1;
}
3.3.地磁传感器数据采集主函数
int main(void)
{
APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
NRF_LOG_DEFAULT_BACKENDS_INIT();
NRF_LOG_INFO("\r\nTWI magsensor example started.");//利用J-LINK RTT打印调试LOG,打印数据放在RAM中
NRF_LOG_FLUSH();//使用FLUSH函数,将存放在RAM中的LOG调试数据利用J-LINK RTT 窗口打印出来
/* TWI initialization初始化. */
twi_init();
/* Enable the sensor使能传感器. */
/*(1)检查产品ID:读取产品ID寄存器地址的数据,MMC5603_PRODUCT_ID=0x10*/
/*(2)自动校准寄存器参数配置:读取自动校准设置值寄存器数据,计算校准信号阈值,然后将阈值写入自动校准阈值寄存器
/*(3)连续测量工作模式设置:1)带宽设置; 2)ODR速率设置; 3)使能置位/复位; 根据ODR计算测量时间; 4)使能连续测量模式*/
// 00:6.6ms 50Hz
MMC5603_Enable();
while(1)
{
/*磁场矢量缓存区,单位是高斯 */
float mag_raw_data[3] = {0.0};
/* Get the MMC5603 data, unit is gauss */
/*情况一:读取数据寄存器数据并转换为磁场,磁场=(寄存器数据-偏移量)/灵敏度*/
/*情况二:自动切换Auto_SR和SETonly之间的工作模式*/
/*(1)SETonly模式:如果X轴或Y轴输出超过10高斯,则切换到置位模式开始测量*/
/*(2)Auto_SR模式:如果X轴和Y轴的输出都小于8高斯,则切换到连续模式;*/
/*不符合(1)(2)情况则进行定期校准再开始测量(如果校准不成功则进行置位,再开始测量)*/
MMC5603_GetData(mag_raw_data);
/*将数据寄存器转换的16bit磁场量转换为8bit的X/Y/Z三轴磁场数据*/
magnetic_field_x = (uint8_t)mag_raw_data[0]; //unit is gauss
magnetic_field_y = (uint8_t)mag_raw_data[1]; //unit is gauss
magnetic_field_z = (uint8_t)mag_raw_data[2]; //unit is gauss
/* 采样间隔20ms,采样率50Hz ,1s=1000ms=1000000ns*/
Delay_Ms(20);
//循环打印磁场数据
char Buffer_X[100]={0};
char Buffer_Y[100]={0};
char Buffer_Z[100]={0};
sprintf(Buffer_X,"%0.3f ",mag_raw_data[0]);
sprintf(Buffer_Y,"%0.3f ",mag_raw_data[1]);
sprintf(Buffer_Z,"%0.3f ",mag_raw_data[2]);
NRF_LOG_INFO("x1: %s gauss.",Buffer_X);
NRF_LOG_INFO("y1: %s gauss.",Buffer_Y);
NRF_LOG_INFO("z1: %s gauss.",Buffer_Z);
NRF_LOG_FLUSH();
/*Shenzhen Guangdong
Latitude纬度: 22° 32' 43.9" N
Longitude经度: 114° 4' 5.9" E
MA TSO LUNG
Magnetic Declination地磁偏角: -3° 1'
Declination is NEGATIVE (WEST)
Inclination: 34° 6'
Magnetic field strength磁场强度: 45442.3 nT=45.4423μT=0.454423Gauss*/
//方向角计算:方向角是X轴和Y轴读数的反正切,输出方向角与手机指南针功能对比,测量结果良好
float Curent_Angle=(atan2((double)mag_raw_data[1],(double)mag_raw_data[0]) * (180 / 3.14159265) + 180+地磁偏角);
char TEST_OK[]={"OK"};
char Buffer_Angle[100]={0};
sprintf(Buffer_Angle,"%0.2f ",Curent_Angle);
NRF_LOG_INFO("Buffer_Angle: %s ",Buffer_Angle);
NRF_LOG_INFO("%s",TEST_OK);
NRF_LOG_FLUSH();
}
}