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代表角度傳感器。