MYD-YA157C系列定制板JY901(MPU9250+AK8963)模块适配笔记
Coder-wys 2022.4.13
一、模块简介
JY901模块(MPU9250+AK8963)集成高精度的陀螺仪、 加速度计、 地磁场传感器, 采用高性能的微处理器和先进的动力学解算与卡尔曼动态滤波算法, 能够快速求解出模块当前的实时运动姿态。支持串口和 IIC 两种数字接口。 串口速率2400bps~921600bps 可调, IIC 接口支持全速 400K 速率。该定制板已接入USART3与i2c1,如图所示。本次使用I2C+Python3方式读取传感器数据。
由上图可知,我们将JY901陀螺仪对应的VCC、SDA、SCL、GND与YA157C定制板进行连接,对应的管脚分别为I2C1_SCL/PB8,I2C1_SDA/PD13,通过 IIC 接口连接 MCU, 连接方法如下图所示。
注意, 为了能在 IIC总线上面挂接多个模块, 模块的 IIC 总线是开漏输出的, MCU 在连接模块时需要将 IIC 总线通过一个 4.7K 的电阻上拉到 VCC。
注意: VCC 为 3.3V, 要另外接电源供电。 直接用模块上面的电源供电, 可能会产生压降, 使模块实际电压没有 3.3~5V。主要: 单片机内部上拉为弱上拉, 驱动能力有限, 需要硬件上的外部上拉
二、设备树过程
正确连接定制板后(出厂默认已连接好),我们需要检测一下该模块是否成功接入。首先我们使用以下命令来检测目前开发板已成功加载的i2c总线
root@myir:~# i2cdetect -l
终端打印出以下数据:
由图可知,目前系统已加载两条i2c bus adapter,分别为I2C-0(地址40013000),I2C-1(地址5c002000)
接下来,我们使用i2cdetect工具检测成功接入i2c的设备
root@myir:~# i2cdetect -y 1 root@myir:~# i2cdetect -y 0 /* Usage: i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST] i2cdetect -F I2CBUS i2cdetect -l I2CBUS is an integer or an I2C bus name If provided, FIRST and LAST limit the probing range. */
由上图可知,在i2c-1总线下,在0x28地址,显示UU,代表已被驱动占用。
在i2c-0总线下,在0x44地址,成功挂载i2c设备,经过查询可知,JY-901 模块 IIC 通信速率最大支持 400khz, 从机地址为为7bit, 默认地址为 0x50,本定制板中0x44地址为SHT31温湿度模块,未在0x50地址发现JY-901模块。
经查询核心板前置设备树文件stm32mp151.dtsi可知,STM32MP157核心板的i2c1地址应在i2c@40012000地址加载
接下来,我们需要了解该地址是否已被内核成功加载,需要查询内核启动日志,命令如下:
root@myir:~# dmesg | grep i2c
由上图可知,该地址未能被内核成功加载,可能存在管脚未在设备树中定义、设备树未正确配置、管脚占用等可能。我们需重新配置设备树
解压厂商提供的镜像文件MYiR-STM32-kernel.tar,设备树文件在MYiR-STM32-kernel/myir-st-linux/arch/arm/boot/dts文件夹中。配置设备树需了解该模块接入了定制板的哪几个管脚,我们通过前面的篇幅得知,JY-901模块通过I2C1_SCL/PB8,I2C1_SDA/PD13与MPU进行连接,查阅STM32MP157A芯片手册,通过搜索关键字“PB8”与“PD13”,经过观察图表得知,
I2C1_SCL/PB8通过AF4、I2C1_SDA/PD13通过AF5与定制板连通。
通过观察表格我们还发现,“PB8”、“PD13”可能存在管脚复用情况,接下来,我们查询设备树,看一下是否存在管脚复用。
在MYiR-STM32-kernel/myir-st-linux/arch/arm/boot/dts文件夹中,打开stm32mp157-ya157c-pinctrl.dtsi,搜索关键字“'B', 8”与“'D', 13,”(去掉最外侧引号,但保留内侧引号),并将其所在行注释,如下图所示:
如上图所示,可能会搜索到多个结果,分别将它们注释掉。然后在/MYiR-STM32-kernel/myir-st-linux/arch/arm/boot/dts/stm32mp15-pinctrl.dtsi中进行相同的注释操作。
以上操作完成之后,我们进行设备树的配置操作:在/MYiR-STM32-kernel/myir-st-linux/arch/arm/boot/dts/stm32mp15-pinctrl.dtsi和stm32mp157-ya157c-pinctrl.dtsi文件中的相应位置,新增以下内容用以新增配置i2c1_pins_a与i2c1_pins_sleep_a:
/*20220406start*/ i2c1_pins_a: i2c1-0 { pins { pinmux = <STM32_PINMUX('B', 8, AF4)>, /* I2C1_SCL */ <STM32_PINMUX('D', 13, AF5)>; /* I2C1_SDA */ bias-disable; drive-open-drain; slew-rate = <0>; }; }; i2c1_pins_sleep_a: i2c1-1 { pins { pinmux = <STM32_PINMUX('B', 8, ANALOG)>, /* I2C1_SCL */ <STM32_PINMUX('D', 13, ANALOG)>; /* I2C1_SDA */ }; }; /*end*/
接下来,在/MYiR-STM32-kernel/myir-st-linux/arch/arm/boot/dts/stm32mp15xx-ya157c.dtsi文件中,在相应位置定义&i2c1,
&i2c1 { pinctrl-names = "default", "sleep"; pinctrl-0 = <&i2c1_pins_a>; pinctrl-1 = <&i2c1_pins_sleep_a>; i2c-scl-rising-time-ns = <100>; i2c-scl-falling-time-ns = <7>; status = "okay"; /delete-property/dmas; /delete-property/dma-names; };
以上操作配置完成后,我们发现在文件最开始,有#include "stm32mp157-m4-srm-pinctrl.dtsi"操作,我们打开这个文件,搜索关键字“m4_i2c1_pins_a”,将下图框选范围内容修改为以下配置:
/*上图框选部分修改为下面的内容*/ m4_i2c1_pins_a: m4-i2c1-0 { pins { pinmux = <STM32_PINMUX('B', 8, RSVD)>, /* I2C1_SCL */ <STM32_PINMUX('D', 13, RSVD)>; /* I2C1_SDA */ }; };
依次保存刚刚修改过的所有设备树文件。
三、编译并更新kernel
进入内核目录/MYiR-STM32-kernel/myir-st-linux,创建输出文件夹build
PC$ mkdir -p ../build
加载SDK环境变量
PC$ source /opt/st/myir/3.1-snapshot/environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
配置内核
PC$ make ARCH=arm O="$PWD/../build" myc-ya157c_defconfig
/*注:以上加载默认配置,如需配置内核或者想打开内核某一个驱动功能可进入build目录后执行make menuconfig。
编译内核
PC$ make -j16 ARCH=arm uImage vmlinux dtbs LOADADDR=0xC2000040 O="$PWD/../build" PC$ -j16 make ARCH=arm modules O="$PWD/../build" /*以上-j16代表16线程同时编译,请根据实际情况合理分配,使用高性能工作站分配16线程同时编译可在2分钟内结束编译过程,这段时间请耐心等待编译完成。
生成输出文件
PC$ make ARCH=arm INSTALL_MOD_PATH="$PWD/../build/install_artifact" modules_install O="$PWD/../build" PC$ mkdir -p $PWD/../build/install_artifact/boot/ PC$ cp $PWD/../build/arch/arm/boot/uImage $PWD/../build/install_artifact/boot/ PC$ cp $PWD/../build/arch/arm/boot/dts/st*.dtb $PWD/../build/install_artifact/boot/
更新到开发板(本次采用网络更新,演示ip地址为192.168.31.180)
在生成输出文件下 build/install_artifact/使用 scp 方式传输需要更新的文件。
更新 uImage 与设备树:
PC$ scp -r boot/* root@192.168.31.180:/boot/
删除编译生成文件 build/install_artifact/lib 下的软连接文件
PC$ rm lib/modules/<kernel version>/source lib/modules/<kernel version>/build
更新内核 modules:
PC$ scp -r lib/modules/* root@192.168.31.180:/lib/modules/
使用SSH连接开发板,执行更新
PC$ sync & reboot
若使用emmc启动,需要将mmcblk2p2挂载到/mnt下,然后把uImage和设备树拷贝到/mnt(以下命令中未展示此部分,自行scp拷贝或者拖到finalshell中对应位置即可),执行sync并重启,命令如下:
root@myir:~# df -h Filesystem Size Used Avail Use% Mounted on devtmpfs 147M 0 147M 0% /dev /dev/mmcblk2p4 1.1G 976M 19M 99% / tmpfs 213M 64K 213M 1% /dev/shm tmpfs 213M 9.0M 204M 5% /run tmpfs 213M 0 213M 0% /sys/fs/cgroup tmpfs 213M 0 213M 0% /tmp tmpfs 213M 220K 213M 1% /var/volatile /dev/mmcblk2p3 15M 6.7M 6.8M 50% /vendor /dev/mmcblk2p5 120M 1.6M 110M 2% /usr/local tmpfs 43M 0 43M 0% /run/user/0 root@myir:~# mount /dev/mmcblk2p2 /mnt/ root@myir:~# ls /mnt/ boot.scr.uimg mmc0_extlinux splash.bmp stm32mp157c-ya157c-lcd-v2.dtb uImage lost+found mmc1_extlinux stm32mp157c-ya157c-hdmi-v2.dtb stm32mp157c-ya157c-v2.dtb uInitrd root@myir:~# sync & reboot
四、读取JY-901模块数据
使用i2cdetect检测i2c总线情况
root@myir:~# i2cdetect -l
由上图可知,设备树配置完成后,新增了两条总线,i2c1(0x40012000)也能够被系统成功加载了,我们执行如下操作,检测JY-901模块是否加载成功
root@myir:~# i2cdetect -y 0
由上图可以看到,0x50已能够被系统挂载,模块适配成功。查看该地址下的设备寄存器信息:
i2cdump -y 1 0x50
通过查阅JY-901模块说明书得知,模块的 IIC 协议采用寄存器地址访问的方式。 每个地址内的数据均为 16 位数据, 占 2个字节。 寄存器的地址及含义如下表:
地址 |
符号 |
含义 |
0x00 |
SAVE |
保存当前配置 |
0x01 |
CALSW |
校准 |
0x02 |
RSW |
回传数据内容 |
0x03 |
RATE |
回传数据速率 |
0x04 |
BAUD |
串口波特率 |
0x05 |
AXOFFSET |
X轴加速度零偏 |
0x06 |
AYOFFSET |
Y轴加速度零偏 |
0x07 |
AZOFFSET |
Z轴加速度零偏 |
0x08 |
GXOFFSET |
X轴角速度零偏 |
0x09 |
GYOFFSET |
Y轴角速度零偏 |
0x0a |
GZOFFSET |
Z轴角速度零偏 |
0x0b |
HXOFFSET |
X轴磁场零偏 |
0x0c |
HYOFFSET |
Y轴磁场零偏 |
0x0d |
HZOFFSET |
Z轴磁场零偏 |
0x0e |
D0MODE |
D0模式 |
0x0f |
D1MODE |
D1模式 |
0x10 |
D2MODE |
D2模式 |
0x11 |
D3MODE |
D3模式 |
0x12 |
D0PWMH |
D0PWM高电平宽度 |
0x13 |
D1PWMH |
D1PWM高电平宽度 |
0x14 |
D2PWMH |
D2PWM高电平宽度 |
0x15 |
D3PWMH |
D3PWM高电平宽度 |
0x16 |
D0PWMT |
D0PWM周期 |
0x17 |
D1PWMT |
D1PWM周期 |
0x18 |
D2PWMT |
D2PWM周期 |
0x19 |
D3PWMT |
D3PWM周期 |
0x1a |
IICADDR |
IIC地址 |
0x1b |
LEDOFF |
关闭LED指示灯 |
0x1c |
GPSBAUD |
GPS连接波特率 |
0x30 |
YYMM |
年、月 |
0x31 |
DDHH |
日、时 |
0x32 |
MMSS |
分、秒 |
0x33 |
MS |
毫秒 |
0x34 |
AX |
X轴加速度 |
0x35 |
AY |
Y轴加速度 |
0x36 |
AZ |
Z轴加速度 |
0x37 |
GX |
X轴角速度 |
0x38 |
GY |
Y轴角速度 |
0x39 |
GZ |
Z轴角速度 |
0x3a |
HX |
X轴磁场 |
0x3b |
HY |
Y轴磁场 |
0x3c |
HZ |
Z轴磁场 |
0x3d |
Roll |
X轴角度 |
0x3e |
Pitch |
Y轴角度 |
0x3f |
Yaw |
Z轴角度 |
0x40 |
TEMP |
模块温度 |
0x41 |
D0Status |
端口D0状态 |
0x42 |
D1Status |
端口D1状态 |
0x43 |
D2Status |
端口D2状态 |
0x44 |
D3Status |
端口D3状态 |
0x45 |
PressureL |
气压低字 |
0x46 |
PressureH |
气压高字 |
0x47 |
HeightL |
高度低字 |
0x48 |
HeightH |
高度高字 |
0x49 |
LonL |
经度低字 |
0x4a |
LonH |
经度高字 |
0x4b |
LatL |
纬度低字 |
0x4c |
LatH |
纬度高字 |
0x4d |
GPSHeight |
GPS高度 |
0x4e |
GPSYaw |
GPS航向角 |
0x4f |
GPSVL |
GPS地速低字 |
0x50 |
GPSVH |
GPS地速高字 |
0x51 |
Q0 |
四元素Q0 |
0x52 |
Q1 |
四元素Q1 |
0x53 |
Q2 |
四元素Q2 |
0x54 |
Q3 |
四元素Q3 |
通过表格可以得知加速度、角速度、角度所在的寄存器地址,接下来我们通过Python3进行JY-901模块的数据读取,代码在CSDN博主zhmzxysjc87%的基础上进行修改。
#!/usr/bin/env python3 /*开发板需要加载Python3后才可以使用 import smbus import time class Gyro(object): def __init__(self, addr): self.addr = addr self.i2c = smbus.SMBus(0) def get_acc(self): try: self.raw_acc_x = self.i2c.read_i2c_block_data(self.addr, 0x34, 2) self.raw_acc_y = self.i2c.read_i2c_block_data(self.addr, 0x35, 2) self.raw_acc_z = self.i2c.read_i2c_block_data(self.addr, 0x36, 2) except IOError: print("ReadError: gyro_acc") return (0, 0, 0) else: self.k_acc = 16 * 9.8 self.acc_x = (self.raw_acc_x[1] << 8 | self.raw_acc_x[0]) / 32768 * self.k_acc self.acc_y = (self.raw_acc_y[1] << 8 | self.raw_acc_y[0]) / 32768 * self.k_acc self.acc_z = (self.raw_acc_z[1] << 8 | self.raw_acc_z[0]) / 32768 * self.k_acc if self.acc_x >= self.k_acc: self.acc_x -= 2 * self.k_acc if self.acc_y >= self.k_acc: self.acc_y -= 2 * self.k_acc if self.acc_z >= self.k_acc: self.acc_z -= 2 * self.k_acc return (self.acc_x, self.acc_y, self.acc_z) def get_gyro(self): try: self.raw_gyro_x = self.i2c.read_i2c_block_data(self.addr, 0x37, 2) self.raw_gyro_y = self.i2c.read_i2c_block_data(self.addr, 0x38, 2) self.raw_gyro_z = self.i2c.read_i2c_block_data(self.addr, 0x39, 2) except IOError: print("ReadError: gyro_gyro") return (0, 0, 0) else: self.k_gyro = 2000 self.gyro_x = (self.raw_gyro_x[1] << 8 | self.raw_gyro_x[0]) / 32768 * self.k_gyro self.gyro_y = (self.raw_gyro_y[1] << 8 | self.raw_gyro_y[0]) / 32768 * self.k_gyro self.gyro_z = (self.raw_gyro_z[1] << 8 | self.raw_gyro_z[0]) / 32768 * self.k_gyro if self.gyro_x >= self.k_gyro: self.gyro_x -= 2 * self.k_gyro if self.gyro_y >= self.k_gyro: self.gyro_y -= 2 * self.k_gyro if self.gyro_z >= self.k_gyro: self.gyro_z -= 2 * self.k_gyro return (self.gyro_x, self.gyro_y, self.gyro_z) def get_angle(self): try: self.raw_angle_x = self.i2c.read_i2c_block_data(self.addr, 0x3d, 2) self.raw_angle_y = self.i2c.read_i2c_block_data(self.addr, 0x3e, 2) self.raw_angle_z = self.i2c.read_i2c_block_data(self.addr, 0x3f, 2) except IOError: print("ReadError: gyro_angle") return (0, 0, 0) else: self.k_angle = 180 self.angle_x = (self.raw_angle_x[1] << 8 | self.raw_angle_x[0]) / 32768 * self.k_angle self.angle_y = (self.raw_angle_y[1] << 8 | self.raw_angle_y[0]) / 32768 * self.k_angle self.angle_z = (self.raw_angle_z[1] << 8 | self.raw_angle_z[0]) / 32768 * self.k_angle if self.angle_x >= self.k_angle: self.angle_x -= 2 * self.k_angle if self.angle_y >= self.k_angle: self.angle_y -= 2 * self.k_angle if self.angle_z >= self.k_angle: self.angle_z -= 2 * self.k_angle return (self.angle_x, self.angle_y, self.angle_z) def main(): head_gyro = Gyro(0x50) while(True): print(" Acc: " + repr(head_gyro.get_acc()) + "\n", "Gyro: " + repr(head_gyro.get_gyro()) + "\n", "Angle:" + repr(head_gyro.get_angle()) + "\n") time.sleep(0.2) main()
需要注意的是,开发板需要加载Python3后才可以使用,需在文件开头增加#!/usr/bin/env python3,将以上代码复制保存为*.py文件后chmod 777授予可执行权限。输出的数据中acc代表加速度传感器,gyro代表陀螺仪,angle代表角度传感器。