本帖最后由 正点原子运营 于 2023-8-25 14:34 编辑
第四十四章 磁力计实验 1)实验平台:正点原子探索者STM32F407开发板
2) 章节摘自【正点原子】STM32F407开发指南 V1.1
3)购买链接:https://detail.tmall.com/item.htm?id=609294673401
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32f407_explorerV3.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)STM32技术交流QQ群:151941872
探索者STM32F407开发板自带了ST480MC传感器。本章,我们将介绍如何使用磁力计ST480MC,将读取到的XYZ原始数据显示到LCD,并将XY数据转换为XY平面的方位角(角度范围0~360°,正北方向为0°)、将读取到的T转换为温度显示到LCD。本章分为如下几个小节: 本章分为如下几个小节: 44.1 ST480MC介绍 44.2 硬件设计 44.3 程序设计 44.4 下载验证
44.1 ST480MC介绍
44.1.1 ST480MC简介ST480MC是一款由Sendia公司生产的三轴电子磁力计,内部进行信号处理,通过IIC接口进行输出。ST480MC提供的输出信号与它在XYZ方向上测量的磁场成比例,如果需要消除比例因子,需要用户自行校准。ST480MC主要特点如下: 1)测量范围:最大±48高斯 2)高分辨率:0.15μT/LSB(X/Y轴),0.25μT/LSB(Z轴) 3)高灵敏度:X/Y轴灵敏度:667 LSBs/Gauss,Z轴灵敏度:400 LSBs/Gauss 4)工作温度范围:-40~+85℃ 5)供电电压:2.2~3.6V 6)16位ADC采样 7)内部带有温度传感器 8)支持中断
ST480MC传感器的检测轴如图44.1.1.1所示: 在ST480MC传感器,通过MUX选择器选择采集的是X、Y、Z的数据,然后通过16位ADC获取X、Y、Z的数据,同样,传感器的温度也是通过ADC采集,采集到的数据通过IIC接口进行输出。ST480MC内部也有供给用户读写的EEPROM区域,在本实验中有读写ST480MC内部EEPROM的接口,但是没有用到,感兴趣的可以自行研究。ST480MC内部结构图如图44.1.1.2所示: 图44.1.1.2中SDA和SCLK是连接MCU的IIC接口,ST480MC的8pin WLCSP封装只支持IIC协议,QFN封装支持IIC/SPI协议,这里我们主要介绍使用IIC驱动ST480MC。
每一个IIC设备都有自己的设备地址,ST480MC也不例外,ST480MC的设备地址是包括不可编程部分和可编程部分,可编程部分是根据上图的硬件引脚A0所决定。设备地址最后一位用于设置数据的传输方向,即读操作/写操作,0是写操作,1是读操作,具体格式如下图44.1.1.4所示: 本实验将A0接地,那么设备地址(不包含R/W位)为0x0C,读设备地址(R/W位:1)为0x19,写设备地址(R/W位:0)为0x18。
44.1.2 ST480MC工作模式简介接下来我们分别ST480MC三种工作模式:单次测量模式、突发模式、唤醒模式。
1. 单次测量模式 当ST480MC设置为该模式(或第一次启动)时,ST480MC进入空闲状态,等待主机发出命令执行特定采集。在接收到主机的查询命令后,ST480MC进行数据采集,然后返回ST480MC的状态和采集的数据,最后返回睡眠模式,直到下次主机查询。
2. 突发模式 当ST480MC设置为突发模式后,就会不断转换XYZT数据,转换完成后INT引脚为高电平,如果还没有转换完成,INT引脚则为低电平(必须在数据转换完成后才能读取到数据)。
3. 唤醒模式 当ST480MC设置为唤醒模式后,在超过设定的阈值后,INT引脚为高电平,如果没有超过设定的阈值,INT引脚为低电平。
44.1.3 ST480MC寄存器简介在这里简单介绍一下ST480MC比较重要的命令。
l 命令列表 ST480MC只监听一组命令,除复位命令外,所有命令都会生成一个可读取的状态字节,描述如下表所示: 所有模式启动命令(SB/SW/SM)中的参数是半个字节,指定传感器按顺序执行的转换《zyxt》,在后面的程序中,我们只使用了SM命令。如果要测量zyxt数据,就得把对应的位置1,即zyxt为1111。假如说不需要某一项的数据,则将对应的位清零即可。
l 状态字节 状态字节是ST480MC响应主机发出的命令而发送的第一个字节,各位描述如下图所示: 1)3个MODE位:MODE位与我们设置的模式有关。本实验设置为单次测量模式,对应MODE位为001。 2)ERROR位:如果命令被拒绝或在存储器中检测到不可纠正的错误,则该位为1,否则为0。 3)SED位:如果标记非易失性存储中的位错误已被纠正,则该位为1,否则为0。 4)RS位:每当ST480MC复位时,该位为1,否则为0。 5)D[1:0]位:仅在RM(ReadMeasurement)命令有意义,此数据作为ST480MC的响应,响应字节数对应2*D[1:0]+2,因此响应字节数为2、4、6或8。
l 寄存器映射关系 ST480MC有10个16位的非易失性存储寄存器,以及相同数量的易失性存储器,RR和WR命令只影响易失性存储器,无法直接访问非易失性存储器。易失性存储寄存器映射关系如下图所示(Address[1:0]=0x00): 图44.1.3.2 ST480MC寄存器映射关系图 44.1.4 ST480MC工作时序简介在探索者STM32F407开发板的上,A0是接GND的,所以ST480MC的设备地址是0x0C(不含最低位)。在前面我们已经介绍过IIC总线的基本读写,所以我们只介绍ST480MC的数据传输时序(橙色代表主机发送,蓝色代表从机发送)。
下面把实验中到的数据传输时序讲解一下,分别是对ST480MC的写时序和读时序。ST480MC写寄存器时序图如图44.1.4.1所示: 上图展示的主机向ST480MC写操作时序图,主机发送开始信号,主机在IIC总线发送第1个字节的数据为ST480MC的写设备地址0x18,等待ST480MC的应答信号,用于寻找总线上找到ST480MC,等待ST480MC的应答信号在获得ST480MC的应答信号之后,继续发送第2个字节数据,该字节数据是ST480MC的写寄存器命令,再等到ST480MC的应答信号,主机开始发送1个字节的写数据的高八位,然后等待ST480MC的应答信号,继续发送1个字节的写数据的低八位,然后等待ST480MC的应答信号。最后发送1个字节的待写入寄存器地址,等待ST480MC的应答信号。最后发出停止信号,终止数据传输。
说完寄存器写入方式之后,下面看一下图44.1.4.2关于ST480MC的读寄存器时序。 ST480MC读取寄存器的过程是一个复合的时序,其中包含写时序和读时序。先看第一个通信过程,这里是写时序,起始信号产生后,主机发送ST480MC写设备地址0x18,获取ST480MC的应答信号后,接着发送需要读取命令,等待ST480MC的应答信号;发送读寄存器地址,获取ST480MC的应答信号后,重新产生起始信号;主机发送ST480MC读设备地址0x19,获取从机应答信号后,接着从机返回刚刚在写时序中内存地址的数据,以字节为单位传输在总线上,假如主机获取数据后返回的是应答信号,那么从机会一直传输数据,这里我们读取一个寄存器的地址,所以返回的是状态寄存器State和寄存器的高八位和低八位;当主机发出停止信号,从机就会结束传输。
ST480MC读取数据过程与读取寄存器过程十分相似,少了发送read commond步骤,ST480MC读数据时序图如图44.1.4.3所示: ST480MC读取数据的过程也是一个复合的时序,其中包含写时序和读时序。先看第一个通信过程,这里是写时序,起始信号产生后,主机发送ST480MC写设备地址0x18,获取ST480MC的应答信号后,接着发送读寄存器地址,获取ST480MC的应答信号后,重新产生起始信号;主机发送ST480MC读设备地址0x19,获取从机应答信号后,接着从机返回刚刚在写时序中内存地址的数据,以字节为单位传输在总线上,假如主机获取数据后返回的是应答信号,那么从机会一直传输数据,这里我们读取N个寄存器的地址,所以返回的是状态寄存器State和N个寄存器的高八位和低八位;当主机发出停止信号,从机就会结束传输。
44.2 硬件设计
1. 例程功能开机的时候先检测ST480MC是否存在,在检测到ST480MC之后,可以通过KEY0进入校准模式,KEY_UP结束校准。在初始化完成后(必须进入一次校准,不然数据不是准确的数据),就会不断的读取ST480MC的数据,同时在LCD上面显示相关信息。LED0闪烁用于提示程序正在运行。
2. 硬件资源1)LED灯 LED0 – PF9 2 ) 独立按键 KEY0 – PE4 KEY_UP – PA0 3)正点原子 2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动) 4)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面) 5)磁力计 ST480MC
3. 原理图我们主要来看看ST480MC和开发板的连接,如下图所示: ST480MC的SCL和SDA分别连接在STM32的PB8和PB9上。本实验通过软件模拟IIC信号建立起与ST480MC的通信,进行数据发送与接收,LCD屏幕进行显示。通过原理图可知本实验使用的ST480MC没有INT引脚,所以源码里面只使用单次测量模式读取数据。
44.3 程序设计
44.3.1 程序流程图 44.3.2 程序解析本实验中,我们通过GPIO来模拟IIC,所以不需要在Drivers/STM32F4xx_HAL_Driver分组下添加IIC的HAL库文件支持。实验工程中,我们复制IIC实验工程,重命名为磁力计实验,新增st480mc.c文件存放ST480MC驱动。
1. ST480MC驱动代码 这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。ST480MC驱动源码包括两个文件:st480mc.c和st480mc.h。
在IIC实验中已经对IIC协议中的需要用到的信号都用函数封装好了,那么现在就要定义符合ST480MC时序的函数。为了使代码功能更加健全,所以在st480mc.h中宏定义了ST480MC的相关寄存器,具体定义如下: - #define ST480MC_ADDR 0X0C /* ST480MC IIC器件地址(A0 = 0时) */
- #define ST480MC_RESET 0XF0 /* ST480MC 复位命令 */
- #define ST480MC_READ_REG 0x50 /* ST480MC 读寄存器 */
- #define ST480MC_WRITE_REG 0x60 /* ST480MC 写寄存器 */
- #define ST480MC_READ_DATA 0x4F /* ST480MC 读取全部数据(zxyt) */
- #define ST480MC_BURST_MODE 0x1F /* ST480MC 突发模式读取数据(zxyt) */
- #define ST480MC_SINGLR_MODE 0x3F /* ST480MC 单次测量读取数据(zxyt) */
- #define ST480MC_SENS_XY 667 /* X,Y 轴灵敏度; 单位: LSB/Gauss */
- #define ST480MC_SENS_Z 400 /* Z 轴灵敏度 单位: LSB/Gauss */
复制代码下面先看一下st480mc_read_nbytes函数,实现从ST480MC芯片指定地址读取N字节数据,代码如下: - /**
- *@brief 从ST480MC读取N字节数据
- * @note ST480MC的命令发送,也是用该函数实现(不带参数的命令, 也会有一个状态寄存器需
- * 要读取)
- *@param addr : 寄存器地址/命令
- *@param length : 读取长度
- *@param buf : 数据存储buf
- *@retval 0, 操作成功
- * 其他, 操作失败
- */
- uint8_t st480mc_read_nbytes(uint8_t addr, uint8_t length, uint8_t *buf)
- {
- uint8_t i;
- iic_start();
- iic_send_byte((ST480MC_ADDR << 1) | 0X00); /* IIC地址最低位是0, 表示写入 */
- if (iic_wait_ack())
- {
- iic_stop();
- return 1;
- }
- iic_send_byte(addr); /* 写地址/命令 */
- iic_wait_ack();
- iic_start();
- iic_send_byte((ST480MC_ADDR << 1) | 0x01); /* IIC地址最低位是1, 表示读取 */
- iic_wait_ack();
- for (i = 0; i < length; i++) /* 循环读取 数据 */
- {
- buf = iic_read_byte(1);
- }
- iic_stop();
- return 0;
- }
复制代码该函数首先调用iic_start函数产生起始信号,然后调用iic_send_byte函数发送第1个字节数据设备写地址,等待ST480MC设备返回应答信号;收到应答信号后,继续发送第2个1字节数据内存地址addr;等待接收应答后,发送第3个1字节数据设备读地址;最后ST480MC最先收到的是状态字节(一个字节),随后是读取的数据(数据都是两个字节),返回应答信号,主机调用iic_stop函数产生停止信号终止数据传输,等待ST480MC读取完毕。
接下来看一下st480mc_write_register函数,实现从ST480MC芯片指定地址写入数据,其定义如下: - /**
- *@brief ST480MC写寄存器
- *@param reg : 寄存器地址
- *@param data : 写入的值
- *@retval 0, 操作成功
- * 其他, 操作失败
- */
- uint8_t st480mc_write_register(uint8_t reg, uint16_t data)
- {
- iic_start();
- iic_send_byte((ST480MC_ADDR << 1) | 0X00); /* IIC地址最低位是0, 表示写入 */
- if (iic_wait_ack())
- {
- iic_stop();
- return 1;
- }
- iic_send_byte(ST480MC_WRITE_REG); /* 发送写寄存器命令 */
- iic_wait_ack();
- iic_send_byte(data >> 8); /* 发送高字节数据 */
- iic_wait_ack();
- iic_send_byte(data & 0XFF); /* 发送低字节数据 */
- iic_wait_ack();
- iic_send_byte(reg << 2); /* 发送寄存器地址(低2位默认是0) */
- iic_wait_ack();
- iic_stop();
- return 0;
- }
复制代码该函数首先调用iic_start函数产生起始信号,然后调用iic_send_byte函数发送第1个字节数据设备写地址,等待ST480MC设备返回应答信号;收到应答信号后,继续发送第2个1字节写入数据内存地址ST480MC_WRITE_REG;等待接收应答后,发送第3个1字节数据写入内存地址的数据data高字节;等待接收应答后,发送第4个1字节数据写入内存地址的数据data低八位;等待接收应答后,发送第5个1字节数据写入内存地址的数据,因为数据地址的低2位为00,所以寄存器左移两位即:reg<<2;ST480MC设备写完数据,返回应答信号,主机调用iic_stop函数产生停止信号终止数据传输,等待ST480MC写入完毕。
介绍完写寄存器函数,那么我们接下来开始介绍st480mc_read_register函数,实现ST480MC内部寄存器的读取,代码如下: - /**
- *@brief ST480MC读寄存器
- *@param reg : 寄存器地址
- *@retval 读取到的值
- * 0XFFFF, 则可能表示错误
- */
- uint16_t st480mc_read_register(uint8_t reg)
- {
- uint8_t buf[3];
- uint8_t i;
- iic_start();
- iic_send_byte((ST480MC_ADDR << 1) | 0X00); /* IIC地址最低位是0, 表示写入 */
- iic_wait_ack();
- iic_send_byte(ST480MC_READ_REG); /* 发送读寄存器命令 */
- iic_wait_ack();
- iic_send_byte(reg << 2); /* 发送寄存器地址(低2位默认是0) */
- iic_wait_ack();
-
- iic_start();
- iic_send_byte((ST480MC_ADDR << 1) | 0x01); /* IIC地址最低位是1, 表示读取 */
- iic_wait_ack();
- for (i = 0; i < 3; i++) /* 循环读取 数据 */
- {
- buf = iic_read_byte(1);
- }
- iic_stop();
-
- if(buf[0] & 0X10)
- {
- return 0XFFFF;
- }
- return ((uint16_t)buf[1] << 8) | buf[2]; /* 返回寄存器数据(16位) */
- }
复制代码该函数与我们介绍第一个函数相似,首先调用iic_start函数产生起始信号,然后调用iic_send_byte函数发送第1个字节数据设备写地址,等待ST480MC设备返回应答信号;收到应答信号后,继续发送第2个1字节数据内存地址reg<<2(读取地址的低2位也为00);等待接收应答后,发送第3个1字节数据设备读地址;最后ST480MC设备接收完数据(一个状态字节和一个两字节的数据),返回应答信号,主机调用iic_stop函数产生停止信号终止数据传输,等待ST480MC读取完毕。
最后,我们介绍三个读取数据函数,代码如下: - /**
- *@brief ST480MC使用单次模式 读取一次磁力计数据(只读 X,Y,Z轴数据),读取一次至少
- * 15ms以上!
- * @note 一次性读取XYZT,需要时间为: Tstby + Tactive + m * Tconvm + Tconvt
- * Tstby : 从IDLE状态到待机状态时间, 250us
- * Tactive : 待机到激活状态时间, 8us
- * Tconvm : 单轴转换时间, (2 + 2^DIG_FILT) * 2^OSR * 0.064 ms, 其中
- * DIG_FILT默认为2, OSR默认为0
- * Tconvt : 温度转换时间, 2^OSR2 * 0.192ms, OSR2默认为0
- * 因此, 一次性读取XYZT所需总时间为 = 0.25 + 0.008 + 3 * (2 + 2^2) *
- * 2 ^ 0* 0.064+ 2^0 * 0.192 ≈ 1.6ms
- * 但是, ST480MC有个IDLE TO DATA READY的时间 : Tconv, 这个一般需要
- * 16ms, 所以, 一次转换到数据读取, 基本要18ms左右
- *
- *@param pmagx : X轴磁力计原始值指针
- * pmagy : Y轴磁力计原始值指针
- * pmagz : Z轴磁力计原始值指针
- *@retval 0, 成功
- * 其他, 异常
- */
- uint8_t st480mc_read_magdata(int16_t *pmagx, int16_t *pmagy, int16_t *pmagz)
- {
- uint8_t buf[7];
- /*发送单次测量命令(不测量温度) */
- st480mc_read_nbytes(ST480MC_SINGLR_MODE & 0XFE, 1, buf);
- delay_ms(15); /* 延时15ms,基本能争取读取数据 */
- /* 发送读取数据命令(不读取温度) */
- st480mc_read_nbytes(ST480MC_READ_DATA & 0XFE, 7, buf);
- if(buf[0] & 0X10) /* 读取数据异常 */
- {
- return buf[0]; /* 发生错误了 */
- }
- else
- {
- *pmagx = (short int)(buf[1] << 8) | buf[2]; /* 组合数据 */
- *pmagy = (short int)(buf[3] << 8) | buf[4]; /* 组合数据 */
- *pmagz = (short int)(buf[5] << 8) | buf[6]; /* 组合数据 */
- }
- return 0;
- }
- /**
- *@brief ST480MC使用单次模式 读取一次温度传感器, 读取一次至少15ms以上!
- * @note 一次性读取XYZT,需要时间为: Tstby + Tactive + m * Tconvm + Tconvt
- * Tstby : 从IDLE状态到待机状态时间, 250us
- * Tactive : 待机到激活状态时间, 8us
- * Tconvm : 单轴转换时间, (2 + 2^DIG_FILT) * 2^OSR * 0.064 ms, 其中
- * DIG_FILT默认为2, OSR默认为0
- * Tconvt : 温度转换时间, 2^OSR2 * 0.192ms, OSR2默认为0
- * 因此, 一次性读取XYZT所需总时间为 = 0.25 + 0.008 + 3 * (2 + 2^2) *
- * 2^0 * 0.064 + 2^0 * 0.192 ≈1.6ms
- * 但是, ST480MC有个IDLE TO DATA READY的时间: Tconv, 这个一般需要16ms,
- * 所以,一次转换到数据读取, 基本要18ms左右
- *
- *@param ptemp : 温度值(℃)指针
- *@retval 0, 成功
- * 其他, 异常
- */
- uint8_t st480mc_read_temperature(float *ptemp)
- {
- uint8_t buf[9];
- st480mc_read_nbytes(ST480MC_SINGLR_MODE, 1, buf); /* 发送单次测量命令(含温度) */
- delay_ms(15); /* 延时15ms,基本能争取读取数据 */
- st480mc_read_nbytes(ST480MC_READ_DATA, 9, buf); /* 发送读取数据命令(含温度) */
- if(buf[0] & 0X10) /* 读取数据异常 */
- {
- return buf[0]; /* 发生错误了 */
- }
- else
- {
- *ptemp = (uint16_t)(buf[1] << 8) | buf[2]; /* 得到温度传感器原始值 */
- *ptemp = (*ptemp - 46244) / 45.2f + 25; /* 根据原厂提供的计算公式,换算成℃ */
- }
- return 0;
- }
- /**
- *@brief ST480MC读取磁力计数据(只读 X,Y,Z轴数据),读times次取平均
- * @note 本函数耗时 ≈ 15 * times ms
- *
- *@param pmagx : X轴磁力计原始值指针
- * pmagy : Y轴磁力计原始值指针
- * pmagz : Z轴磁力计原始值指针
- * times : 读取多少次取平均
- *@retval 0, 成功
- * 其他, 异常
- */
- uint8_t st480mc_read_magdata_average(int16_t *pmagx, int16_t *pmagy,
- int16_t *pmagz, uint8_t times)
- {
- uint8_t i = 0;
- uint8_t error_cnt = 0;
- int32_t magx = 0;
- int32_t magy = 0;
- int32_t magz = 0;
- while(i < times) /* 连续读取times次 */
- {
- if(st480mc_read_magdata(pmagx, pmagy, pmagz) == 0) /* 读取数据是否正常? */
- {
- magx += *pmagx; /* 累加 */
- magy += *pmagy;
- magz += *pmagz;
-
- i++;
- error_cnt = 0;
- }else
- {
- error_cnt++; /* 错误计数器 */
- delay_ms(10);
- if(error_cnt > 100) /* 连续100次出错, 直接返回异常 */
- {
- return 0XFF;
- }
- }
- }
-
- *pmagx = magx / times; /* 取平均值 */
- *pmagy = magy / times; /* 取平均值 */
- *pmagz = magz / times; /* 取平均值 */
-
- return 0;
- }
复制代码st480mc_read_magdata函数:首先设置为单次模式,然后读取一次磁力计的x,y,z轴原始数据,iic数据有7位:status,x高八位,x低八位,y高八位,y低八位,z高八位,z低八位,。 st480mc_read_temperature函数:在单次模式下,读取一次转换后的温度数据。 st480mc_read_magdata_average函数:调用st480mc_read_magdata函数,读取多次磁力计的x,y,z轴原始数据,取平均值。
我们在main函数主要调用st480mc_read_temperature函数和st480mc_read_magdata_average函数实现读取磁力计的数据。
2. main.c代码 我们读取到的x,y,z轴数据是原始数据,此时的数据还不能直接用作方位角的计算,因为此时的磁力计可能受到其他一些磁性材料的影响,导致圆心坐标偏移,所以我们要对磁力计进行校准,将漂移值移除,使圆心回到原点。
常用校准方法有:平面校准法、立体八字校准法、十面校准法。本实验采用平面校准法,针对XY轴进行校准,将开发板在XY平面内旋转一周,等价于将地球磁场矢量绕着点O(x0,y0)(即圆心)垂直于XY平面的法线旋转,这样就可以得到圆心的位置x_offeset = (x_max + x_min) / 2,y_offeset = (y_max + y_min) / 2。立体八字校准法与十面校准法我们不做介绍,感兴趣的可以自己去了解一下。
我们以XY平面为例,假设圆心O点发生了偏移,如图44.3.2.2.1所示: 我们校准后,使得圆心O点回到零点(0,0),如图44.3.2.2.2所示: 最后,我们打开main.c,这里就不全部贴出来了,仅介绍三个重要函数: - /**
- *@brief 罗盘(磁力计)校准函数
- * @note 这里我们使用最简单的平面校准方法.
- * 进入此函数后,请水平转动开发板至少一周(360°),转动完成后, 按WKUP键退出!
- *@param 无
- *@retval 无
- */
- voidcompass_calibration(void)
- {
- int16_t x_min = 0;
- int16_t x_max = 0;
- int16_t y_min = 0;
- int16_t y_max = 0;
- int16_t magx, magy, magz;
- uint8_t res;
- uint8_t key = 0;
-
- lcd_clear(WHITE);
- lcd_show_string(10, 90, 240, 16, 16, "CompassCalibration", RED);
- lcd_show_string(10, 110, 240, 16, 16, "Pls rotatehoriz one cycle!", RED);
- lcd_show_string(10, 130, 240, 16, 16, "If done,press WKUP key!", RED);
-
- while (1)
- {
- key = key_scan(0);
-
- if (key == WKUP_PRES) /* 结束校准 */
- {
- break;
- }
-
- res =st480mc_read_magdata(&magx, &magy, &magz); /* 读取数据 */
-
- if (res == 0)
- {
- x_max = x_max < magx ? magx : x_max; /* 记录x最大值 */
- x_min = x_min > magx ? magx : x_min; /* 记录x最小值 */
- y_max = y_max < magy ? magy : y_max; /* 记录y最大值 */
- y_min = y_min > magy ? magy : y_min; /* 记录y最小值 */
- }
-
- LED0_TOGGLE(); /* LED0闪烁,提示程序运行 */
- }
-
- g_magx_offset = (x_max + x_min) / 2; /* X轴偏移量 */
- g_magy_offset = (y_max + y_min) / 2; /* Y轴偏移量 */
-
- /* 串口打印水平校准相关参数 */
- printf("x_min:%d\r\n", x_min);
- printf("x_max:%d\r\n", x_max);
- printf("y_min:%d\r\n", y_min);
- printf("y_max:%d\r\n", y_max);
-
- printf("g_magx_offset:%d\r\n", g_magx_offset);
- printf("g_magy_offset:%d\r\n", g_magy_offset);
- }
- /**
- *@brief 罗盘获取角度
- * @note 获取当前罗盘的角度(地磁角度)
- *@param 无
- *@retval 角度
- */
- floatcompass_get_angle(void)
- {
- float angle;
- int16_t magx, magy, magz;
- /* 读取原始数据, 10次取平均 */
- st480mc_read_magdata_average(&magx, &magy, &magz, 10);
-
- magx = (magx - g_magx_offset) ; /* 根据校准参数, 计算新的输出 */
- magy = (magy - g_magy_offset) ; /* 根据校准参数, 计算新的输出 */
- /* 根据不同的象限情况, 进行方位角换算 */
- if ((magx > 0) && (magy > 0))
- {
- angle = (atan((double)magy / magx) * 180) / 3.14159f;
- }
- else if ((magx > 0) && (magy < 0))
- {
- angle = 360 + (atan((double)magy / magx) * 180) / 3.14159f;
- }
- else if ((magx == 0) && (magy > 0))
- {
- angle = 90;
- }
- else if ((magx == 0) && (magy < 0))
- {
- angle = 270;
- }
- else if (magx < 0)
- {
- angle = 180 + (atan((double)magy / magx) * 180) / 3.14159f;
- }
-
- if (angle > 360) angle = 360; /* 限定方位角范围 */
- if (angle < 0) angle = 0; /* 限定方位角范围 */
- return angle;
- }
- int main(void)
- {
- uint8_t t = 0;
- uint8_t key;
- float angle;
- float temperature;
- int16_t magx, magy, magz;
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
- delay_init(168); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- usmart_dev.init(84); /* 初始化USMART */
- led_init(); /* 初始化LED */
- key_init(); /* 初始化按键 */
- lcd_init(); /* 初始化LCD */
- while (st480mc_init()) /* ST480MC初始化 */
- {
- lcd_show_string(30, 110, 200, 16, 16, "ST480MCError", RED);
- delay_ms(200);
- lcd_fill(30, 110, 239, 130 + 16, WHITE);
- delay_ms(200);
- }
- RST:
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "ST480MCTEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 110, 200, 16, 16, "KEY0 tocalibration", RED);
- while (1)
- {
- key = key_scan(0);
-
- if (key == KEY0_PRES) /* KEY0 按下 ,执行校准 */
- {
- compass_calibration(); /* 校准函数 */
- lcd_clear(WHITE); /* 清屏 */
- goto RST; /* 校准完后,跳到RST, 重新显示提示信息 */
- }
-
- delay_ms(10);
- t++;
-
- if (t == 20) /* 0.2秒左右更新一次温度/磁力计原始值 */
- {
- angle =compass_get_angle(); /* 执行一次约150ms */
- user_show_angle(30, 130, angle); /* 显示角度 */
-
- st480mc_read_temperature(&temperature); /* 读取温湿度值 */
- user_show_temprate(30, 150, temperature); /* 显示温度 */
-
- st480mc_read_magdata(&magx, &magy, &magz); /* 读取原始数据 */
- user_show_mag(30, 170, "MagX:", magx); /* 显示magx */
- user_show_mag(30, 190, "MagY:", magy); /* 显示magy */
- user_show_mag(30, 210, "MagZ:", magz); /* 显示magz */
-
- t = 0;
- LED0_TOGGLE(); /* LED0闪烁 */
- }
- }
- }
复制代码下面分别介绍一下这三个函数。
compass_calibration,该函数用于校准磁力计ST480MC,校准方法采用的是我们前面介绍的平面校准法,校准完成后的偏移量offeset存到全局变量g_magx_offset和g_magy_offset。
compass_get_angle,该函数用于计算XY平面的方位角angle。首先,获取x,y,z的原始数据,然后x和y加上校准完成后的偏移量,最后再计算XY平面的方位角angle,atan求出来的角度范围-90°~+90°,所以想要得到0°~360°,我们进行方位角计算式,就要根据x,y判断象限,不同象限的情况下进行不同的计算。
main函数,则比较简单,初始化相关外设,在循环中不断获取磁力计数据,并在LCD上显示数据;我们可以通过按键KEY0进入校准模式,KEY_UP退出校准模式。
44.4 下载验证在代码编译成功后,我们通过下载代码到开发板上,可以看到LED0不停闪烁,提示程序已经在运行了。LCD显示磁力计的MagX,MagY,MagZ原始数据和计算后的Angle,Temp数据,如图44.4.1所示: 按下KEY0,进入校准模式,如图44.4.2所示: 退出校准模式后,可以与手机指南针的航向角进行对比,如果发现误差较大,则需要再次进行校准。 |