打印
[其他ST产品]

【正点原子K210连载】第十七章 六轴传感器——原始数据读取实验《DNK210使用指南-SDK版》

[复制链接]
534|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 正点原子官方 于 2024-10-9 09:59 编辑

第十七章 六轴传感器——原始数据读取实验

本章将介绍板载六轴传感器的使用,Kendryte K210官方SDK提供了IIC接口的API函数能很方便地帮助我们驱动板载的六轴传感器。通过本章的学习,读者将学习到板载六轴传感器的基本使用。
本章分为如下几个小节:
17.1 SH3001驱动介绍及使用方法
17.2 硬件设计
17.3 程序设计
17.4 运行验证


17.1 SH3001驱动介绍及使用方法
SH3001是正点原子DNK210板载的六轴传感器芯片,是一款六轴IMUInternal measurement unit,惯性从测量单元)。SH3001内部集成有三轴加速度计和三轴陀螺仪(角速度计),相较于多组件方案,免除了组合加速度计和陀螺仪时可能遇到的轴间差问题,并减小了体积和功耗。
本章将介绍Kendryte K210使用IIC接口驱动开发板板载的SH3001六轴传感器。
17.1.1 IIC介绍
IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器以及其外围设备。它是由数据线SDA和时钟线SCL构成的串行总线,可发送和接收数据,在CPU与被控IC之间、ICIC之间进行双向传送。
IIC总线有如下特点:
①总线由数据线SDA和时钟线SCL构成的串行总线,数据线用来传输数据,时钟线用来同步数据收发。
②总线上每一个器件都有一个唯一的地址识别,所以我们只需要知道器件的地址,根据时序就可以实现微控制器与器件之间的通信。
③数据线SDA和时钟线SCL都是双向线路,都通过一个电流源或上拉电阻连接到正的电压,所以当总线空闲的时候,这两条线路都是高电平。
④总线上数据的传输速率在标准模式下可达100kbit/s,在快速模式下可达400kbit/s,在高速模式下可达3.4Mbit/s。
⑤总线支持设备连接。在使用IIC通信总线时,可以有多个具备IIC通信能力的设备挂载在上面,同时支持多个主机和多个从机,连接到总线的接口数量只由总线电容400pF的限制决定。IIC总线挂载多个器件的示意图,如下图所示。
图17.1.1.1 IIC总线挂载多个器件
下面来学习IIC总线协议,IIC总线时序图如下所示:
图17.1.1.2 IIC总线时序图
为了便于大家更好的了解IIC协议,我们从起始信号、停止信号、应答信号、数据有效性、数据传输以及空闲状态等6个方面讲解,大家需要对应图14.1.1.2的标号来理解。
① 起始信号
SCL为高电平期间,SDA由高到低的跳变。起始信号是一种电平跳变时序信号,而不是一个电平信号。该信号由主机发出,在起始信号产生后,总线就处于被占用状态,准备数据传输。
② 停止信号
SCL为高电平期间,SDA由低到高的跳变。停止信号也是一种电平跳变时序信号,而不是一个电平信号。该信号由主机发出,在停止信号发出后,总线就处于空闲状态。
③ 应答信号
发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。 应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
观察上图标号③就可以发现,有效应答的要求是从机在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主机,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主机接收器发送一个停止信号。
④ 数据有效性
IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。数据在SCL的上升沿到来之前就需准备好。并在下降沿到来之前必须稳定。
⑤ 数据传输
I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。
⑥ 空闲状态
IIC总线的SDASCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
了解前面的知识后,下面介绍一下IIC的基本的读写通讯过程,包括主机写数据到从机即写操作,主机到从机读取数据即读操作。下面先看一下写操作通讯过程图,如下图所示。
图17.1.1.3 写操作通讯过程图
主机首先在IIC总线上发送起始信号,那么这时总线上的从机都会等待接收由主机发出的数据。主机接着发送从机地址+0(写操作)组成的8bit数据,所有从机接收到该8bit数据后,自行检验是否是自己的设备的地址,假如是自己的设备地址,那么从机就会发出应答信号。主机在总线上接收到有应答信号后,才能继续向从机发送数据。注意:IIC总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。
接着讲解一下IIC总线的读操作过程,先看一下读操作通讯过程图,如下图所示。
图17.1.1.4 读操作通讯过程图
        主机向从机读取数据的操作,一开始的操作与写操作有点相似,观察两个图也可以发现,都是由主机发出起始信号,接着发送从机地址+1(读操作)组成的8bit数据,从机接收到数据验证是否是自身的地址。那么在验证是自己的设备地址后,从机就会发出应答信号,并向主机返回8bit数据,发送完之后从机就会等待主机的应答信号。假如主机一直返回应答信号,那么从机可以一直发送数据,也就是图中的(n byte + 应答信号)情况,直到主机发出非应答信号,从机才会停止发送数据。
Kendryte K2103I²C总线接口,根据用户的配置,总线接口可以用作I²C MASTER SLAVE模式。I²C接口支持:
1. 标准模式(0100Kb/s
2. 快速模式(<= 400Kb/s
3. 7-/10-位寻址模式
4. 批量传输模式
5. 中断或轮询模式操作
Kendryte K210官方SDK提供了多个操作IIC接口的API函数,这里我们只讲述本实验用到的函数,这些函数介绍如下:
1, i2c_init函数
该函数主要用于IIC的配置初始化,该函数原型及参数描述如下代码所示:
void i2c_init(i2c_device_number_t i2c_num, uint32_t slave_address, uint32_t address_width,
              uint32_t i2c_clk);
/* IIC设备号配置参数 */
typedef enum _i2c_device_number
{
    I2C_DEVICE_0,
    I2C_DEVICE_1,
    I2C_DEVICE_2,
    I2C_DEVICE_MAX,
} i2c_device_number_t;
该函数共有四个配置参数,第一个为IIC设备号,第二个为从机设备地址,第三个是配置地址宽度为7bit还是10bit,我们一般使用的是7bit,第四个参数为配置IIC时钟,IIC初始化完成之后就能够使用IIC对应的引脚发送和接收数据。
下面介绍IIC发送数据的API函数。
2i2c_send_data函数
该函数用于发送数据,如下代码所示:
int i2c_send_data(i2c_device_number_t i2c_num, const uint8_t *send_buf, size_t send_buf_len);
函数一共有三个参数,第一个参数是发送数据的IIC设备号,第二个参数为要发送的数据,第三个数据是发送数据的长度,此函数是普通发送模式,SDK也提供用DMA发送数据的API函数,发送成功函数返回0,返回其他说明发送异常。
3i2c_recv_data函数
该函数用于接收数据,如下代码所示:
int i2c_recv_data(i2c_device_number_t i2c_num, const uint8_t *send_buf, size_t send_buf_len, uint8_t *receive_buf,size_t receive_buf_len);
函数一共有五个参数,第一个参数是发送数据的IIC设备号,第二个参数为要发送的数据,第三个数据是发送数据的长度,第四个参数为接收数据的buf,第五个参数为接收数据的长度。这里发送的数据通常为从机的寄存器命令,用于读取相关数据。
17.1.2 SH3001介绍
SH3001内部集成有三轴加速度计和三轴陀螺仪,输出都是16位的数字量,可通过I2C接口与之进行数据交互。加速度计的测量范围最大可配置为±16gg为重力加速度),静态测量精度高。陀螺仪的角速度测量范围最大可配置为±2000dps),具有良好的动态相应特性。
SH3001的特点如下:
1. 加速度计量程(g):±2、±4±8、±16
2. 加速度计灵敏度(LSB/g):16384819240962048
3. 陀螺仪量程(dps):±125±250±500±1000±2000
4. 陀螺仪灵敏度(LSBdps):26213165.532.816.4
5. 封装:LGA142.5*3.0*0.9mm3
SH3001传感器的检测轴,如下图所示:
17.1.2.1 SH3001检测轴方向
更多有关SH3001的芯片特性以及内部寄存器描述,请参考SH3001的数据手册——《SH3001.pdf》,读者可在Aà硬件资料à芯片资料下找到这份文档。
为了方便大家使用正点原子DNK210开发板板载的SH3001六轴传感器,正点原子团队为SH3001编写了一份驱动代码,我们会在17.3小节介绍。
SH3001的内部框图如图所示:
图17.1.2.2 SH3001框图
其中,SCLSDA可以连接MCUIIC接口,MCU通过这个IIC接口来控制SH3001,另外还有一个IIC接口:MSCKMSDA,这个接口可用来连接外部从设备,比如气压传感器。VDDIOIO口电压,该引脚最低可以到1.8V我们一般直接接VDD即可。SDO是从IIC接口(接MCU)的地址控制引脚,该引脚控制IIC地址的最低位。如果接GND,则SH3001IIC地址是:0X36,如果接VDD,则是0X37,注意:这里的地址是不包含数据传输的最低位的(最低位用来表示读写)!!
下面简单介绍一下本实验用到的SH3001比较重要的寄存器。
l 陀螺仪配置寄存器(GYRO_CONFIG
陀螺仪配置寄存器有6个,寄存器地址分别是0x280x290x2B0x8F0x9F0xAF,用来配置陀螺仪量程等参数,描述如下图所示;
图17.1.2.3 GYRO_CONFIG_0寄存器
图17.1.2.4 GYRO_CONFIG_1寄存器
图17.1.2.5 GYRO_CONFIG_2寄存器
图17.1.2.6 GYRO_CONFIG_3寄存器
图17.1.2.7 GYRO_CONFIG_4寄存器

图17.1.2.8 GYRO_CONFIG_5寄存器
其中,我们介绍一些比较重要的配置,GYRO_CONFIG_1[3:0]位用于配置陀螺仪的输出数据频率:0000:1000Hz0001500Hz0010:250Hz0011:125Hz0100:63Hz0101:31Hz1000:2kHz1001:4kHz1010:8kHz1011:16kHz110032kHz;我们一般设置为0001,即500Hz
GYRO_CONFIG_2bit 4位用于设置是否使能陀螺仪数字低通滤波器(LPF)。一般我们设置为1,即使能LPF
GYRO_CONFIG_3GYRO_CONFIG_4GYRO_CONFIG_5寄存器的[2:0]位分别用于设置陀螺仪X轴、Y轴和Z轴的满量程范围:010::125dps;011250dps100:500dps101:1000dps110:2000dps;我们一般设置X轴、Y轴和Z轴为110,即2000dps。
l 加速度即配置寄存器ACC_CONFIG
加速度配置寄存器有4个,寄存器地址分别是:0x220x230x250x26,用来配置加速度计量程等参数,描述如下图所示:
图17.1.2.9 ACC_CONFIG_0寄存器
图17.1.2.10 ACC_CONFIG_1寄存器
图17.1.2.11 ACC_CONFIG_2寄存器
图17.1.2.12 ACC_CONFIG_3寄存器
这里,我们要配置的是,ACC_CONFIG_1[3:0]位用于设置加速度计的输出数据频率:0000:1000Hz0001:500Hz0010:250Hz0011:125Hz0100:63Hz0101:31Hz0110:16Hz1000:2000Hz1001:4000Hz1010:8000Hz。我们一般设置为0001,即500Hz。
ACC_CONFIG_2[2:0]位用于设置加速度计的满量程范围:010±16g011±8g;100±4g101±2g;一般我们设置为010,即±16g。
ACC_CONFIG_3[7:5]位用于设置加速度计低通滤波器的截止频率:000ODR×0.40001ODR×0.25010ODR×0.11011ODR×0.04;100ODR×0.02;一般我们设置为001,即低通滤波器的截止频率为ODR×0.25=500Hz×0.25=125HzACC_CONFIG_3bit 3位用于设置加速度计是否使能低通滤波器,一般我们设置为1,即使能。
17.2 硬件设计
17.2.1 例程功能
1. 初始化SH3001等外设后,在死循环里面不停读取:加速度传感器和陀螺仪等原始数据,通过串口调试助手显示数据,在LCD模块上面显示温度、六轴原始数据等信息。
17.2.2 硬件资源
1. SH3001
        IIC_SCL - IO22
        IIC_SDA - IO23
17.2.3 原理图
本章实验内容,需要使用到板载的SH3001芯片,正点原子DNK210开发板上的SH3001芯片连接原理图,如下图所示:
17.2.3.1 SH3001连接原理图
17.3 程序设计
17.3.1 IIC驱动
为了方便使用IIC进行数据收发,我们重新构建了IIC相关驱动函数存放于iic.ciic.h文件(区别IIC的库文件i2c.ci2c.h),
#define ADDRESS_WIDTH   7
#define I2C_CLK_SPEED   400000
这两个宏分别用于设置IIC地址的宽度和IIC的时钟频率。
/**
* @brief       向寄存器reg写入数据
* @param       addr    : 从机地址
* @param       reg     : 寄存器命令
* @param       length  : 数据长度
* @param       data_buf: 要写入的数据
* @retval      返回值  : 0,成功
*                        1,失败
*/
uint16_t i2c_hd_write(uint8_t addr, uint8_t reg, uint16_t length, uint8_t *data_buf)
{
    uint16_t error = 1;
    uint8_t data[length + 1];

    if (_current_addr != addr)
    {
        i2c_hardware_init(addr);
    }

    data[0] = reg;
    for (size_t i = 0; i < length; i++)
    {
        data[i+1] = *data_buf;
        data_buf++;
    }

    error = i2c_send_data(I2C_DEVICE_0, data, length + 1);
    return error;
}
我们通过向SH3001寄存器写数据便可配置传感器的功能,SH3001的寄存器都由相应的指令去访问,参考SH3001的数据手册——《SH3001.pdf》了解,所以我们写数据前需要先发送一个8bit的寄存器指令,等待传感器应答后才能发送我们要写入的数据。
/**
* @brief       从寄存器reg读取数据
* @param       addr    : 从机地址
* @param       reg     : 寄存器命令
* @param       length  : 数据长度
* @param       data_buf: 要读取的数据
* @retval      返回值  : 0,成功
*                        1,失败
*/
uint16_t i2c_hd_read(uint8_t addr, uint8_t reg, uint16_t length, uint8_t *data_buf)
{
    if (_current_addr != addr)
    {
        i2c_hardware_init(addr);
    }
    uint16_t error = 1;

    error = i2c_recv_data(I2C_DEVICE_0, ®, 1, data_buf, length);
    return error;
}
读函数和写函数的前一部分执行过程有点相似,我们在发送指令后进入接收状态,MCU每次接收到数据后发送应答信号,从机设备便会继续发送数据,等待接收到我们所需的数据长度时发送非应答信号便可停止接收。
17.3.2 SH3001驱动
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。SH3001驱动源码包括两个文件:sh3001.csh3001.h。
/*****************************HARDWARE-PIN*********************************/
/* 硬件IO口,与原理图对应 */
#define PN_IMU_SCL             (22)
#define PIN_IMU_SDA             (23)
#define PIN_IMU_INT             (20)

/*****************************SOICMWARE-GPIO********************************/
/* 软件GPIO口,与程序对应 */
#define IMU_INT_GPIONUM         (0)

/*****************************FUNC-GPIO************************************/
/* GPIO口的功能,绑定到硬件IO*/
#define FUNC_IMU_INT             (FUNC_GPIOHS0 + IMU_INT_GPIONUM)
#define FUNC_ICM_SCL             (FUNC_I2C0_SCLK)
#define FUNC_ICM_SDA             (FUNC_I2C0_SDA)
这里是DNK210开发板SH3001传感器的IIC引脚定义和功能编号,下面介绍SH3001的相关寄存器,其定义如下:
#define SH3001_ACC_XL               (0x00)
#define SH3001_ACC_XH                (0x01)
#define SH3001_ACC_YL                (0x02)
#define SH3001_ACC_YH                (0x03)
#define SH3001_ACC_ZL                (0x04)
#define SH3001_ACC_ZH                (0x05)
…/* 此处省略部分代码 */
#define SH3001_GYRO_CONF3            (0x8F)
#define SH3001_GYRO_CONF4            (0x9F)
#define SH3001_GYRO_CONF5            (0xAF)
#define SH3001_CHIP_ID1              (0xDF)
下面来看一下sh3001_read_nbytes函数,实现SH3001芯片指定地址读取N字节数据,代码如下:
/**
* @brief       SH3001读取N字节数据
* @NOTE        SH3001的命令发送,也是用该函数实现(不带参数的命令, 也会有一个状态寄存器需要读取)
* @param       devaddr  : 寄存器地址
* @param       regaddr  : 寄存器命令
* @param       length   : 读取长度
* @param       readbuf  : 数据存储buf
* @retval      0, 操作成功
*              其他, 操作失败
*/
unsigned char sh3001_read_nbytes(uint8_t devaddr, uint8_t regaddr, uint16_t length, uint8_t *readbuf)
{
    i2c_hd_read(devaddr, regaddr, length, readbuf);
    return (SH3001_TRUE);
}
该函数直接调用i2c_hd_read函数即可,非常方便使用,成功返回0IIC读取过程可参考17.1.1小节。
接下来看一下sh3001_write_nbytes函数,实现从SH3001芯片指定地址写入数据,其定义如下:
/**
* @brief       SH3001写入N字节数据
* @param       devaddr  : 寄存器地址
* @param       regaddr  : 寄存器命令
* @param       length   : 写入长度
* @param       writebuf : 数据存储buf
* @retval      0, 操作成功
*              其他, 操作失败
*/
unsigned char sh3001_write_nbytes(uint8_t devaddr, uint8_t regaddr, uint16_t length, uint8_t *writebuf)
{

    i2c_hd_write(devaddr, regaddr, length, writebuf);
    return (SH3001_TRUE);
}
该函数直接调用i2c_hd_write函数即可,函数返回0表示发送成功,这里也不多介绍。
最后,我们介绍一下读取数据函数,代码如下:
/**
* @brief       读温度值
* @param      
* @retval      温度值,单位为℃(float类型)
*/
float sh3001_get_tempdata(void)
{
    unsigned char regdata[2] = {0};
    unsigned short int tempref[2] = {0};

    sh3001_read_nbytes(SH3001_ADDRESS, SH3001_TEMP_CONF0, 2, ®data[0]);
    tempref[0] = ((unsigned short int)(regdata[0] & 0x0F) << 8) | regdata[1];

    sh3001_read_nbytes(SH3001_ADDRESS, SH3001_TEMP_ZL, 2, ®data[0]);
    tempref[1] = ((unsigned short int)(regdata[1] & 0x0F) << 8) | regdata[0];

    return ( (((float)(tempref[1] - tempref[0])) / 16.0f) + 25.0f );
}

/**
* @brief       读取补偿后SH3001陀螺仪和加速度的数据(推荐使用)
* @param       accdata[3]  : acc X,Y,Z;
* @param       gyrodata[3] : gyro X,Y,Z;
* @retval      
*/
void sh3001_get_imu_compdata(short accdata[3], short gyrodata[3])
{
    unsigned char regdata[15] = {0};
    unsigned char paramp;
    int acctemp[3], gyrotemp[3];

    sh3001_read_nbytes(SH3001_ADDRESS, SH3001_ACC_XL, 15, regdata);
    accdata[0] = ((short)regdata[1] << 8) | regdata[0];
    accdata[1] = ((short)regdata[3] << 8) | regdata[2];
    accdata[2] = ((short)regdata[5] << 8) | regdata[4];
    gyrodata[0] = ((short)regdata[7] << 8) | regdata[6];
    gyrodata[1] = ((short)regdata[9] << 8) | regdata[8];
    gyrodata[2] = ((short)regdata[11] << 8) | regdata[10];
    paramp = regdata[14] & 0x1F;

    acctemp[0] = (int)( accdata[0] + \
                        accdata[1] * ((float)g_compcoef.cXY / 1024.0f) + \
                        accdata[2] * ((float)g_compcoef.cXZ / 1024.0f) );

    acctemp[1] = (int)( accdata[0] * ((float)g_compcoef.cYX / 1024.0f) + \
                        accdata[1] + \
                        accdata[2] * ((float)g_compcoef.cYZ / 1024.0f) );

    acctemp[2] = (int)( accdata[0] * ((float)g_compcoef.cZX / 1024.0f) + \
                        accdata[1] * ((float)g_compcoef.cZY / 1024.0f) + \
                        accdata[2] );

    if (acctemp[0] > 32767)
    {
        acctemp[0] = 32767;
    }
    else if (acctemp[0] < -32768)
    {
        acctemp[0] = -32768;
    }

    if (acctemp[1] > 32767)
    {
        acctemp[1] = 32767;
    }
    else if (acctemp[1] < -32768)
    {
        acctemp[1] = -32768;
    }

    if (acctemp[2] > 32767)
    {
        acctemp[2] = 32767;
    }
    else if (acctemp[2] < -32768)
    {
        acctemp[2] = -32768;
    }

    accdata[0] = (short)acctemp[0];
    accdata[1] = (short)acctemp[1];
    accdata[2] = (short)acctemp[2];

    gyrotemp[0] = gyrodata[0] - (paramp - g_compcoef.paramP0) * g_compcoef.jX * g_compcoef.xMulti;
    gyrotemp[1] = gyrodata[1] - (paramp - g_compcoef.paramP0) * g_compcoef.jY * g_compcoef.yMulti;
    gyrotemp[2] = gyrodata[2] - (paramp - g_compcoef.paramP0) * g_compcoef.jZ * g_compcoef.zMulti;

    if (gyrotemp[0] > 32767)
    {
        gyrotemp[0] = 32767;
    }
    else if (gyrotemp[0] < -32768)
    {
        gyrotemp[0] = -32768;
    }

    if (gyrotemp[1] > 32767)
    {
        gyrotemp[1] = 32767;
    }
    else if (gyrotemp[1] < -32768)
    {
        gyrotemp[1] = -32768;
    }

    if (gyrotemp[2] > 32767)
    {
        gyrotemp[2] = 32767;
    }
    else if (gyrotemp[2] < -32768)
    {
        gyrotemp[2] = -32768;
    }

    gyrodata[0] = (short)gyrotemp[0];
    gyrodata[1] = (short)gyrotemp[1];
    gyrodata[2] = (short)gyrotemp[2];

    //printf("%d %d %d %d %d %d\r\n", accdata[0], accdata[1], accdata[2], gyrodata[0], gyrodata[1], gyrodata[2]);
}
sh3001_gettemp_data函数:读取转换后的温度数据。
sh3001_getimucompdata函数:调用sh3001_read_nbytes读数据,读取加速度计和陀螺仪的xy,z轴原始数据。
下面看一下SH3001的初始化函数,其定义如下:
/**
* @brief       初始化SH3001接口
* @param      
* @retval      SH3001_TRUE, 成功
*              SH3001_FALSE, 异常
*/
unsigned char sh3001_init(void)
{
    unsigned char i = 0;
    unsigned char regdata = 0;

    sh3001_hardware_init();        /* 绑定IIC功能引脚 */
    i2c_hardware_init(SH3001_ADDRESS); /* 初始化IIC */
    msleep(10);

    /* 读取CHIP ID */
    do
    {
        sh3001_read_nbytes(SH3001_ADDRESS, SH3001_CHIP_ID, 1, ®data);
    }while ((regdata != 0x61) && (i++ < 3));

    if ((regdata != 0x61))
    {
        printf("SH3001 CHIP ID:0X%X\r\n", regdata); /* 打印错误的ID */
        return SH3001_FALSE;
    }

    sh3001_module_reset();                  /* 重置内部模块 */

    /* ACC配置: 500Hz, 16G, cut off Freq(BW)=500*0.25Hz=125Hz, enable filter; */
    sh3001_acc_config(SH3001_ODR_500HZ,
                      SH3001_ACC_RANGE_16G,
                      SH3001_ACC_ODRX025,
                      SH3001_ACC_FILTER_EN);

  /* GYRO配置: 500Hz, X\Y\Z 2000deg/s, cut off Freq(BW)=181Hz, enable filter; */
    sh3001_gyro_config( SH3001_ODR_500HZ,
                        SH3001_GYRO_RANGE_2000,
                        SH3001_GYRO_RANGE_2000,
                        SH3001_GYRO_RANGE_2000,
                        SH3001_GYRO_ODRX00,
                        SH3001_GYRO_FILTER_EN);

    /* 温度配置: 输出速率63Hz, 使能温度测量 */
    sh3001_temp_config(SH3001_TEMP_ODR_63, SH3001_TEMP_EN);

    /* SH3001进入正常工作模式 */
    sh3001_switch_powermode(SH3001_NORMAL_MODE);

    /* 读取补偿系数 */
    sh3001_comp_init(&g_compcoef);

    return SH3001_TRUE;
}
在初始化函数中,我们主要对该模块的加速度、陀螺仪,还有温度传感器的使能和设置,并且选择normal模式。
17.3.3 图片处理代码
上一章我们讲解了LCD显示字符串的实验,细心的小伙伴们可以发现LCD上显示一个一个字符的速度非常慢,因为我们在显示字符串过程中需要一个一个点的定位和LCD内存的读写,两个设备的频繁通讯非常耗时,所以我们K210采用以图片的形式显示各种信息,我们创建一个RGB565图片缓存区,要显示什么内容直接对缓存区的数据操作即可,最后一次写入到LCD显示的GRAM中。
正点原子团队编写了一份图片处理代码用于RGB565RGB888的图片处理,源码存放在image_process.cimage_process.h文件,内容较多,我们将函数的功能以表格的形式简单介绍下。读者如果对图片处理过程感兴趣,可自行查看源码学习。
函数名及功能如下表所示:
函数名
功能
image_init
为图片申请内存
image_deinit
释放图片内存
image_crop
图片切割
image_draw
在一张图片上写入图片
image_resize
图片缩放
image_replace
RGB888图片翻转
image_rgb888_to_gray
RGB888转灰度图
image_invert
RGB888颜色值翻转
image_strech_chart
RGB888去除暗角
rgb888_to_rgb565
RGB888RGB565
draw_string_rgb565_image
RGB565图片上写入字符串
draw_box_rgb565_image
RGB565图片上写矩形框
draw_fill_rectangle_image
RGB565图片上填充矩形颜色条
draw_point_rgb565_image
RGB565图片上画点
表17.3.3.1 图片处理函数功能表
以上函数基本满足我们例程中涉及的图片处理所需要的功能。
17.3.4 main.c代码
main.c中的代码如下所示:
#include "sleep.h"
#include "sysctl.h"
#include "image_process.h"
#define LCD_SPI_CLK_RATE 15000000
#include "./BSP/SH3001/sh3001.h"
#include "./BSP/IIC/iic.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/LCD/lcdfont.h"

static uint16_t lcd_gram[320 * 240] __attribute__((aligned(32))); /* 定义一个LCD显示缓存区 */

/**
* @brief       显示数据
*  @NOTE       以图片显示的方式更快  
* @param       x, y : 坐标
* @param       title: 标题
* @param       data: 角度
* @retval      
*/
void user_show_data(uint16_t x, uint16_t y, char * title, short data)
{
    char buf[15];

    sprintf(buf,"%s%d", title, data);          /* 格式化输出 */
draw_fill_rectangle_image(lcd_gram, 320, x, y, x + 120, y + 16, WHITE);  
/* 清除上次缓存数据 */
draw_string_rgb565_image(lcd_gram, 320, 240, x, y, buf, BLUE);
/* 将字符串写入LCD缓存区 */

    // lcd_draw_fill_rectangle(x, y, x + 120, y + 16, WHITE);   /* 清除上次数据(最多显示15个字符,15*8=120) */
    // lcd_draw_string(x, y, buf, BLUE);  /* 显示字符串 */
}

int main(void)
{
    uint8_t t = 0;
    float temperature;                          /* 温度值 */
    short acc_data[3];                          /* 加速度传感器原始数据 */
    short gyro_data[3];                         /* 陀螺仪原始数据 */

    sysctl_pll_set_freq(SYSCTL_PLL0, 800000000);
    sysctl_pll_set_freq(SYSCTL_PLL1, 400000000);
    sysctl_pll_set_freq(SYSCTL_PLL2, 45158400);
    sysctl_set_power_mode(SYSCTL_POWER_BANK6, SYSCTL_POWER_V18);
    sysctl_set_power_mode(SYSCTL_POWER_BANK7, SYSCTL_POWER_V18);
    sysctl_set_spi0_dvp_data(1);

    lcd_init();
    lcd_set_direction(DIR_YX_LRUD);

    /* 初始化 IMU */
    while (sh3001_init())     /* 检测不到SH3001 */
    {
        msleep(10);
    }
    msleep(500);

    /* 清空LCD缓存区 */
    for (size_t i = 0; i < 320 * 240; i++)
    {
        lcd_gram = 0xFFFF;
    }

   while (1)
    {
        t++;
        sh3001_get_imu_compdata(acc_data, gyro_data); /* 读取原始数据 */

        if (t == 20)
        {        
            temperature = sh3001_get_tempdata();    /* 读取温度值 */
            printf("\r\ntemp=%.2f\r\n", temperature);
            // lcd_clear(WHITE);
            user_show_data(30, 10, "TEMP  :", temperature);
            user_show_data(30, 30, "ACC_X :", acc_data[0]);
            user_show_data(30, 50, "ACC_Y :", acc_data[1]);
            user_show_data(30, 70, "ACC_Z :", acc_data[2]);
            user_show_data(30, 90, "GYRO_X:", gyro_data[0]);
            user_show_data(30, 110, "GYRO_Y:", gyro_data[1]);
            user_show_data(30, 130, "GYRO_Z:", gyro_data[2]);

            printf("ACC_X:%d\r\n", acc_data[0]);
            printf("ACC_Y:%d\r\n", acc_data[1]);
            printf("ACC_Y:%d\r\n", acc_data[2]);
            printf("GYRO_X:%d\r\n", gyro_data[0]);
            printf("GYRO_Y:%d\r\n", gyro_data[1]);
            printf("GYRO_Z:%d\r\n", gyro_data[2]);
            lcd_draw_picture(0, 0, 320, 240, (uint16_t *)lcd_gram);
            t = 0;
        }
    }
}
可以看到我们先定义一个uint16_t 类型,长度为320*240的数组,用于LCD的图像缓存。
user_show_data函数用于格式化显示我们需要显示的数据,里面我们也保留了LCD用打点的方式显示图片,大家可以取消注释对比下显示的速度差异。
最后是main函数的内容,我们先定义温度和六轴原始数据的变量,接着是初始化SH3001,SH3001初始化后面有个清空LCD缓存区的操作,因为申请的内存里面数据的初值是不确定的,所以我们需要格式化下。
最后在一个循环中从SH3001读取温度值、加速度值和陀螺仪值并通过LCD显示和打印输出。
17.4 运行验证
将DNK210开发板连接到电脑主机,通过VSCode将固件烧录到开发板中,可以看到“串行终端”窗口中输出了一系列信息,如下图所示:
图17.4.1 “串行终端”窗口打印输出
可以看到,“串行终端”不断输出从SH3001获取到的温度值、加速度值和陀螺仪值。
同时LCD也不断刷新数据显示,如下图所示:
图17.4.2 LCD显示数据

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

88

主题

89

帖子

1

粉丝