diff --git a/10-ConcurrencyAndSynchronization/Makefile b/10-ConcurrencyAndSynchronization/Makefile new file mode 100644 index 0000000..3864102 --- /dev/null +++ b/10-ConcurrencyAndSynchronization/Makefile @@ -0,0 +1,41 @@ +NAME = mpu6050 + +ifneq ($(KERNELRELEASE),) + +obj-m := $(NAME).o + +else + +NAMELINUX ?= root@192.168.0.130 +MODULEDIR ?= /lib/modules/4.19.83-sunxi/kernel/drivers/iio/accel +KERNELDIR ?= /home/dmitry/GL/orange-pi-4.19.83 +DTSDIR ?= $(PWD)/dtsi + +export ARCH = arm +export CROSS_COMPILE ?= arm-linux-gnueabihf- + +.PHONY: all clean + +all: $(NAME).c + $(MAKE) -C $(KERNELDIR) M=$(CURDIR) modules + +dtbo: + $(KERNELDIR)/scripts/dtc/dtc -I dts -O dtb \ + -o $(DTSDIR)/$(NAME).dtbo \ + $(DTSDIR)/$(NAME).dtsi + +copymod: $(NAME).ko + scp $< $(NAMELINUX):$(MODULEDIR) + +copysshid: + ssh-copy-id -i ~/.ssh/id_rsa.pub $(NAMELINUX) + +copydtbo: + scp $(PWD)/dtsi/$(NAME).dtbo \ + $(NAMELINUX):/boot/overlay-user + +clean: + $(MAKE) -C $(KERNELDIR) M=$(CURDIR) clean + -rm $(DTSDIR)/$(NAME).dtbo + +endif diff --git a/10-ConcurrencyAndSynchronization/README.md b/10-ConcurrencyAndSynchronization/README.md new file mode 100644 index 0000000..ec249ae --- /dev/null +++ b/10-ConcurrencyAndSynchronization/README.md @@ -0,0 +1,21 @@ +# Concurrency and synchronization + +## Homework + +**(based on MPU6050 driver)** + +_NB_: +Communication with peripheral device on i2c is quite slow asynchronous process +which responsiveness depends on the slave latency and bus utilisation. +Thus generally it's better to be done in background process. + +1. Move interaction with MPU6050 into separate thread. + +2. Protect static data of the driver (`g_mpu6050_data` in the master) +for the case of concurrent access. + +3. Limit interaction with MPU6050 in case of frequent requests: +Define validity interval - if the latest read data is older than this threshold +then new data reading is performed, otherwise previously read data is returned. +(This threshold should be configurable parameter with reasonable default.) + diff --git a/10-ConcurrencyAndSynchronization/dtsi/mpu6050.dtsi b/10-ConcurrencyAndSynchronization/dtsi/mpu6050.dtsi new file mode 100644 index 0000000..571772f --- /dev/null +++ b/10-ConcurrencyAndSynchronization/dtsi/mpu6050.dtsi @@ -0,0 +1,22 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "allwinner,sun8i-h3"; + + fragment@0 { + target = <&i2c0>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mpu: mpu6050@68 { + compatible = "gl, mpu6050"; + reg = <0x68>; + delay_ms = <10000>; + status = "okay"; + }; + }; + }; +}; diff --git a/10-ConcurrencyAndSynchronization/mpu6050-regs.h b/10-ConcurrencyAndSynchronization/mpu6050-regs.h new file mode 100644 index 0000000..9d51680 --- /dev/null +++ b/10-ConcurrencyAndSynchronization/mpu6050-regs.h @@ -0,0 +1,33 @@ +#ifndef _MPU6050_REGS_H +#define _MPU6050_REGS_H + +/* Registed addresses */ +#define REG_CONFIG 0x1A +#define REG_GYRO_CONFIG 0x1B +#define REG_ACCEL_CONFIG 0x1C +#define REG_FIFO_EN 0x23 +#define REG_INT_PIN_CFG 0x37 +#define REG_INT_ENABLE 0x38 +#define REG_ACCEL_XOUT_H 0x3B +#define REG_ACCEL_XOUT_L 0x3C +#define REG_ACCEL_YOUT_H 0x3D +#define REG_ACCEL_YOUT_L 0x3E +#define REG_ACCEL_ZOUT_H 0x3F +#define REG_ACCEL_ZOUT_L 0x40 +#define REG_TEMP_OUT_H 0x41 +#define REG_TEMP_OUT_L 0x42 +#define REG_GYRO_XOUT_H 0x43 +#define REG_GYRO_XOUT_L 0x44 +#define REG_GYRO_YOUT_H 0x45 +#define REG_GYRO_YOUT_L 0x46 +#define REG_GYRO_ZOUT_H 0x47 +#define REG_GYRO_ZOUT_L 0x48 +#define REG_USER_CTRL 0x6A +#define REG_PWR_MGMT_1 0x6B +#define REG_PWR_MGMT_2 0x6C +#define REG_WHO_AM_I 0x75 + +/* Register values */ +#define MPU6050_WHO_AM_I 0x68 + +#endif /* _MPU6050_REGS_H */ diff --git a/10-ConcurrencyAndSynchronization/mpu6050.c b/10-ConcurrencyAndSynchronization/mpu6050.c new file mode 100644 index 0000000..6d5405a --- /dev/null +++ b/10-ConcurrencyAndSynchronization/mpu6050.c @@ -0,0 +1,389 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mpu6050-regs.h" + +struct temp_t { + int t_int; + int t_frac; +}; + +struct mpu6050_data { + struct i2c_client *drv_client; + int accel_values[3]; + int gyro_values[3]; + struct temp_t temperature; + struct mutex mutex_mpu; + struct completion read_complete; + struct task_struct *reading_thread; + unsigned int delay_ms; + struct timer_list delay_timer; + bool flag_read; +}; + +static struct mpu6050_data g_mpu6050_data; + +static void delay_timer_callback(struct timer_list *t) +{ + dev_info(&g_mpu6050_data.drv_client->dev, + "delay timer callback, data can be updated\n"); + g_mpu6050_data.flag_read = false; +} + +static int mpu6050_read_data(void) +{ + int temp = 0; + struct i2c_client *drv_client = g_mpu6050_data.drv_client; + + if (drv_client == 0) + return -ENODEV; + + dev_info(&drv_client->dev, "sensor data read:\n"); + + /* accel */ + g_mpu6050_data.accel_values[0] = (s16)((u16)i2c_smbus_read_word_swapped(drv_client, REG_ACCEL_XOUT_H)); + g_mpu6050_data.accel_values[1] = (s16)((u16)i2c_smbus_read_word_swapped(drv_client, REG_ACCEL_YOUT_H)); + g_mpu6050_data.accel_values[2] = (s16)((u16)i2c_smbus_read_word_swapped(drv_client, REG_ACCEL_ZOUT_H)); + /* gyro */ + g_mpu6050_data.gyro_values[0] = (s16)((u16)i2c_smbus_read_word_swapped(drv_client, REG_GYRO_XOUT_H)); + g_mpu6050_data.gyro_values[1] = (s16)((u16)i2c_smbus_read_word_swapped(drv_client, REG_GYRO_YOUT_H)); + g_mpu6050_data.gyro_values[2] = (s16)((u16)i2c_smbus_read_word_swapped(drv_client, REG_GYRO_ZOUT_H)); + /* temp */ + temp = (s16)((u16)i2c_smbus_read_word_swapped(drv_client, REG_TEMP_OUT_H)); + g_mpu6050_data.temperature.t_int = (temp * 1000 / 340 + 35000) / 1000; + g_mpu6050_data.temperature.t_frac = (temp * 1000 / 340 + 35000) % 1000; + + dev_info(&drv_client->dev, "ACCEL[X,Y,Z] = [%d, %d, %d]\n", + g_mpu6050_data.accel_values[0], + g_mpu6050_data.accel_values[1], + g_mpu6050_data.accel_values[2]); + dev_info(&drv_client->dev, "GYRO[X,Y,Z] = [%d, %d, %d]\n", + g_mpu6050_data.gyro_values[0], + g_mpu6050_data.gyro_values[1], + g_mpu6050_data.gyro_values[2]); + dev_info(&drv_client->dev, "TEMP = %02d.%03d\n", + g_mpu6050_data.temperature.t_int, + g_mpu6050_data.temperature.t_frac); + + return 0; +} + +static int mpu6050_read_thread(void *data) +{ + struct mpu6050_data *pdata = data; + + while (!kthread_should_stop()) { + dev_info(&pdata->drv_client->dev, "mpu6050 read thread\n"); + + mutex_lock(&pdata->mutex_mpu); + mpu6050_read_data(); + mutex_unlock(&pdata->mutex_mpu); + + complete(&pdata->read_complete); + mod_timer(&pdata->delay_timer, get_jiffies_64() + + msecs_to_jiffies(pdata->delay_ms)); + pdata->flag_read = true; + + kthread_park(pdata->reading_thread); + if (kthread_should_park()) + kthread_parkme(); + } + return 0; +} + +static ssize_t accel_x_show(struct class *class, + struct class_attribute *attr, char *buf) +{ + if (!g_mpu6050_data.flag_read) { + kthread_unpark(g_mpu6050_data.reading_thread); + wake_up_process(g_mpu6050_data.reading_thread); + wait_for_completion(&g_mpu6050_data.read_complete); + } + mutex_lock(&g_mpu6050_data.mutex_mpu); + sprintf(buf, "%d\n", g_mpu6050_data.accel_values[0]); + mutex_unlock(&g_mpu6050_data.mutex_mpu); + + return strlen(buf); +} + +static ssize_t accel_y_show(struct class *class, + struct class_attribute *attr, char *buf) +{ + if (!g_mpu6050_data.flag_read) { + kthread_unpark(g_mpu6050_data.reading_thread); + wake_up_process(g_mpu6050_data.reading_thread); + wait_for_completion(&g_mpu6050_data.read_complete); + } + + mutex_lock(&g_mpu6050_data.mutex_mpu); + sprintf(buf, "%d\n", g_mpu6050_data.accel_values[1]); + mutex_unlock(&g_mpu6050_data.mutex_mpu); + + return strlen(buf); +} + +static ssize_t accel_z_show(struct class *class, + struct class_attribute *attr, char *buf) +{ + if (!g_mpu6050_data.flag_read) { + kthread_unpark(g_mpu6050_data.reading_thread); + wake_up_process(g_mpu6050_data.reading_thread); + wait_for_completion(&g_mpu6050_data.read_complete); + } + + mutex_lock(&g_mpu6050_data.mutex_mpu); + sprintf(buf, "%d\n", g_mpu6050_data.accel_values[2]); + mutex_unlock(&g_mpu6050_data.mutex_mpu); + + return strlen(buf); +} + +static ssize_t gyro_x_show(struct class *class, + struct class_attribute *attr, char *buf) +{ + if (!g_mpu6050_data.flag_read) { + kthread_unpark(g_mpu6050_data.reading_thread); + wake_up_process(g_mpu6050_data.reading_thread); + wait_for_completion(&g_mpu6050_data.read_complete); + } + + mutex_lock(&g_mpu6050_data.mutex_mpu); + sprintf(buf, "%d\n", g_mpu6050_data.gyro_values[0]); + mutex_unlock(&g_mpu6050_data.mutex_mpu); + + return strlen(buf); +} + +static ssize_t gyro_y_show(struct class *class, + struct class_attribute *attr, char *buf) +{ + if (!g_mpu6050_data.flag_read) { + kthread_unpark(g_mpu6050_data.reading_thread); + wake_up_process(g_mpu6050_data.reading_thread); + wait_for_completion(&g_mpu6050_data.read_complete); + } + + mutex_lock(&g_mpu6050_data.mutex_mpu); + sprintf(buf, "%d\n", g_mpu6050_data.gyro_values[1]); + mutex_unlock(&g_mpu6050_data.mutex_mpu); + + return strlen(buf); +} + +static ssize_t gyro_z_show(struct class *class, + struct class_attribute *attr, char *buf) +{ + if (!g_mpu6050_data.flag_read) { + kthread_unpark(g_mpu6050_data.reading_thread); + wake_up_process(g_mpu6050_data.reading_thread); + wait_for_completion(&g_mpu6050_data.read_complete); + } + + mutex_lock(&g_mpu6050_data.mutex_mpu); + sprintf(buf, "%d\n", g_mpu6050_data.gyro_values[2]); + mutex_unlock(&g_mpu6050_data.mutex_mpu); + + return strlen(buf); +} + +static ssize_t temp_show(struct class *class, + struct class_attribute *attr, char *buf) +{ + if (!g_mpu6050_data.flag_read) { + kthread_unpark(g_mpu6050_data.reading_thread); + wake_up_process(g_mpu6050_data.reading_thread); + wait_for_completion(&g_mpu6050_data.read_complete); + } + + mutex_lock(&g_mpu6050_data.mutex_mpu); + sprintf(buf, "%02d.%03d\n", g_mpu6050_data.temperature.t_int, + g_mpu6050_data.temperature.t_frac); + mutex_unlock(&g_mpu6050_data.mutex_mpu); + + return strlen(buf); +} + +static ssize_t delay_ms_show(struct class *class, + struct class_attribute *attr, char *buf) +{ + sprintf(buf, "%d\n", g_mpu6050_data.delay_ms); + + return strlen(buf); +} + +static ssize_t delay_ms_store(struct class *class, + struct class_attribute *attr, + const char *buf, size_t count) +{ + ssize_t result = 0; + unsigned int delay = 0; + + result = sscanf(buf, "%d", &delay); + if (result != 1) + return -EINVAL; + + g_mpu6050_data.delay_ms = delay; + dev_info(&g_mpu6050_data.drv_client->dev, + "set new delay value: %d mS\n", delay); + + return count; +} + +CLASS_ATTR_RO(accel_x); +CLASS_ATTR_RO(accel_y); +CLASS_ATTR_RO(accel_z); +CLASS_ATTR_RO(gyro_x); +CLASS_ATTR_RO(gyro_y); +CLASS_ATTR_RO(gyro_z); +CLASS_ATTR_RO(temp); +CLASS_ATTR_RW(delay_ms); + +static struct attribute *mpu_class_attrs[] = { + &class_attr_accel_x.attr, + &class_attr_accel_y.attr, + &class_attr_accel_z.attr, + &class_attr_gyro_x.attr, + &class_attr_gyro_y.attr, + &class_attr_gyro_z.attr, + &class_attr_temp.attr, + &class_attr_delay_ms.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(mpu_class); + +/* Device model classes */ +struct class mpu_rc_class = { + .name = "mpu6050", + .owner = THIS_MODULE, + .class_groups = mpu_class_groups, +}; + +static int mpu6050_probe(struct i2c_client *drv_client, + const struct i2c_device_id *id) +{ + int ret; + + g_mpu6050_data.drv_client = drv_client; + + mutex_init(&g_mpu6050_data.mutex_mpu); + + init_completion(&g_mpu6050_data.read_complete); + + g_mpu6050_data.reading_thread = kthread_create(mpu6050_read_thread, + &g_mpu6050_data, "reading_mpu6050"); + + dev_info(&drv_client->dev, + "i2c client address is 0x%X\n", drv_client->addr); + + /* Read who_am_i register */ + ret = i2c_smbus_read_byte_data(drv_client, REG_WHO_AM_I); + if (IS_ERR_VALUE(ret)) { + dev_err(&drv_client->dev, + "i2c_smbus_read_byte_data() failed with error: %d\n", + ret); + return ret; + } + if (ret != MPU6050_WHO_AM_I) { + dev_err(&drv_client->dev, + "wrong i2c device found: expected 0x%X, found 0x%X\n", + MPU6050_WHO_AM_I, ret); + return -1; + } + dev_info(&drv_client->dev, + "i2c mpu6050 device found, WHO_AM_I register value = 0x%X\n", + ret); + + /* Setup the device */ + // reset + if (IS_ERR_VALUE(i2c_smbus_write_byte_data(drv_client, + REG_PWR_MGMT_1, 0x80))) { + dev_err(&drv_client->dev, + "i2c_smbus_write_byte_data() failed\n"); + return -EIO; + } + msleep(100); + // start + if (IS_ERR_VALUE(i2c_smbus_write_byte_data(drv_client, + REG_PWR_MGMT_1, 0x00))) { + dev_err(&drv_client->dev, + "i2c_smbus_write_byte_data() failed\n"); + return -EIO; + } + + if (of_find_property(drv_client->dev.of_node, "delay_ms", NULL)) { + of_property_read_u32(drv_client->dev.of_node, "delay_ms", + &g_mpu6050_data.delay_ms); + dev_info(&drv_client->dev, "dtsi->delay_ms = %d\n", + g_mpu6050_data.delay_ms); + } else { + g_mpu6050_data.delay_ms = 10000; + } + + timer_setup(&g_mpu6050_data.delay_timer, delay_timer_callback, 0); + + ret = class_register(&mpu_rc_class); + if (ret < 0) { + dev_err(&drv_client->dev, "failed to create sysfs class: %d\n", + ret); + return ret; + } + dev_info(&drv_client->dev, "sysfs class created\n"); + + dev_info(&drv_client->dev, "i2c driver probed\n"); + return 0; +} + +static int mpu6050_remove(struct i2c_client *drv_client) +{ + g_mpu6050_data.drv_client = 0; + + class_unregister(&mpu_rc_class); + dev_info(&drv_client->dev, "sysfs class destroyed\n"); + + del_timer(&g_mpu6050_data.delay_timer); + kthread_stop(g_mpu6050_data.reading_thread); + mutex_destroy(&g_mpu6050_data.mutex_mpu); + + dev_info(&drv_client->dev, "i2c driver removed\n"); + return 0; +} + +static const struct of_device_id mpu6050_ids[] = { + { .compatible = "gl, mpu6050", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mpu6050_ids); + +static const struct i2c_device_id mpu6050_idtable[] = { + { "mpu6050", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mpu6050_idtable); + +static struct i2c_driver mpu6050_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "mpu6050", + .of_match_table = of_match_ptr(mpu6050_ids), + }, + .probe = mpu6050_probe, + .remove = mpu6050_remove, + .id_table = mpu6050_idtable, +}; + +module_i2c_driver(mpu6050_i2c_driver); + +MODULE_AUTHOR("Andriy.Khulap "); +MODULE_AUTHOR("Dmitry Domnin"); +MODULE_DESCRIPTION("mpu6050 I2C acc&gyro"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1"); diff --git a/10-ConcurrencyAndSynchronization/result/dmesg.txt b/10-ConcurrencyAndSynchronization/result/dmesg.txt new file mode 100644 index 0000000..b5cabd0 --- /dev/null +++ b/10-ConcurrencyAndSynchronization/result/dmesg.txt @@ -0,0 +1,24 @@ +root@orangepione:~# dmesg | grep mpu6050 +[ 6.104628] mpu6050 0-0068: i2c client address is 0x68 +[ 6.107458] mpu6050 0-0068: i2c mpu6050 device found, WHO_AM_I register value = 0x68 +[ 6.213196] mpu6050 0-0068: dtsi->delay_ms = 10000 +[ 6.213323] mpu6050 0-0068: sysfs class created +[ 6.213330] mpu6050 0-0068: i2c driver probed +[ 392.439916] mpu6050 0-0068: mpu6050 read thread +[ 392.439934] mpu6050 0-0068: sensor data read: +[ 392.443708] mpu6050 0-0068: ACCEL[X,Y,Z] = [-4952, -10656, 13284] +[ 392.443726] mpu6050 0-0068: GYRO[X,Y,Z] = [554, -46, 24] +[ 392.443738] mpu6050 0-0068: TEMP = 24.506 +[ 402.694992] mpu6050 0-0068: delay timer callback, data can be updated +[ 403.721889] mpu6050 0-0068: mpu6050 read thread +[ 403.721906] mpu6050 0-0068: sensor data read: +[ 403.725741] mpu6050 0-0068: ACCEL[X,Y,Z] = [-4948, -11004, 13152] +[ 403.725761] mpu6050 0-0068: GYRO[X,Y,Z] = [401, -415, -128] +[ 403.725775] mpu6050 0-0068: TEMP = 24.553 +[ 413.959345] mpu6050 0-0068: delay timer callback, data can be updated +[ 415.922511] mpu6050 0-0068: mpu6050 read thread +[ 415.922528] mpu6050 0-0068: sensor data read: +[ 415.926684] mpu6050 0-0068: ACCEL[X,Y,Z] = [-5032, -11100, 13196] +[ 415.926704] mpu6050 0-0068: GYRO[X,Y,Z] = [624, -408, -66] +[ 415.926718] mpu6050 0-0068: TEMP = 24.412 +[ 425.991767] mpu6050 0-0068: delay timer callback, data can be updated diff --git a/10-ConcurrencyAndSynchronization/result/load-uboot.txt b/10-ConcurrencyAndSynchronization/result/load-uboot.txt new file mode 100644 index 0000000..a385737 --- /dev/null +++ b/10-ConcurrencyAndSynchronization/result/load-uboot.txt @@ -0,0 +1,68 @@ +U-Boot SPL 2019.04-armbian (Dec 01 2019 - 12:47:04 +0200) +DRAM: 512 MiB +Trying to boot from MMC1 + + +U-Boot 2019.04-armbian (Dec 01 2019 - 12:47:04 +0200) Allwinner Technology + +CPU: Allwinner H3 (SUN8I 1680) +Model: Xunlong Orange Pi One +DRAM: 512 MiB +MMC: mmc@1c0f000: 0 +Loading Environment from EXT4... ** File not found /boot/boot.env ** + +** Unable to read "/boot/boot.env" from mmc0:1 ** +In: serial +Out: serial +Err: serial +Net: phy interface0 +eth0: ethernet@1c30000 +** Reading file would overwrite reserved memory ** +There is no valid bmp file at the given address +starting USB... +USB0: USB EHCI 1.00 +USB1: USB OHCI 1.0 +USB2: USB EHCI 1.00 +USB3: USB OHCI 1.0 +scanning bus 0 for devices... 1 USB Device(s) found +scanning bus 1 for devices... 1 USB Device(s) found +scanning bus 2 for devices... 1 USB Device(s) found +scanning bus 3 for devices... 1 USB Device(s) found + scanning usb for storage devices... 0 Storage Device(s) found +Autoboot in 1 seconds, press to stop +switch to partitions #0, OK +mmc0 is current device +Scanning mmc 0:1... +Found U-Boot script /boot/boot.scr +3798 bytes read in 2 ms (1.8 MiB/s) +## Executing script at 43100000 +U-boot loaded from SD +Boot script loaded from mmc +251 bytes read in 2 ms (122.1 KiB/s) +6842649 bytes read in 331 ms (19.7 MiB/s) +7430672 bytes read in 360 ms (19.7 MiB/s) +Found mainline kernel configuration +29878 bytes read in 10 ms (2.8 MiB/s) +374 bytes read in 7 ms (51.8 KiB/s) +Applying kernel provided DT overlay sun8i-h3-i2c0.dtbo +274 bytes read in 3 ms (88.9 KiB/s) +Applying user provided DT overlay fake_module.dtbo +434 bytes read in 1 ms (423.8 KiB/s) +Applying user provided DT overlay mpu6050.dtbo +4155 bytes read in 4 ms (1013.7 KiB/s) +Applying kernel provided DT fixup script (sun8i-h3-fixup.scr) +## Executing script at 44000000 +## Loading init Ramdisk from Legacy Image at 43300000 ... + Image Name: uInitrd + Image Type: ARM Linux RAMDisk Image (gzip compressed) + Data Size: 6842585 Bytes = 6.5 MiB + Load Address: 00000000 + Entry Point: 00000000 + Verifying Checksum ... OK +## Flattened Device Tree blob at 43000000 + Booting using the fdt blob at 0x43000000 +EHCI failed to shut down host controller. + Loading Ramdisk to 49979000, end 49fff8d9 ... OK + Loading Device Tree to 49909000, end 49978fff ... OK + +Starting kernel ... diff --git a/10-ConcurrencyAndSynchronization/result/read_attrs.txt b/10-ConcurrencyAndSynchronization/result/read_attrs.txt new file mode 100644 index 0000000..f440262 --- /dev/null +++ b/10-ConcurrencyAndSynchronization/result/read_attrs.txt @@ -0,0 +1,29 @@ +root@orangepione:~# cd /sys/class/mpu6050/ +root@orangepione:/sys/class/mpu6050# ls +accel_x accel_y accel_z delay_ms gyro_x gyro_y gyro_z temp +root@orangepione:/sys/class/mpu6050# cat accel_x +-4952 +root@orangepione:/sys/class/mpu6050# cat accel_x +-4952 +root@orangepione:/sys/class/mpu6050# cat accel_x +-4952 +root@orangepione:/sys/class/mpu6050# cat accel_x +-4952 +root@orangepione:/sys/class/mpu6050# cat accel_x +-4952 +root@orangepione:/sys/class/mpu6050# cat accel_x +-4952 +root@orangepione:/sys/class/mpu6050# cat accel_x +-4948 +root@orangepione:/sys/class/mpu6050# cat accel_x +-4948 +root@orangepione:/sys/class/mpu6050# cat accel_x +-4948 +root@orangepione:/sys/class/mpu6050# cat temp +24.553 +root@orangepione:/sys/class/mpu6050# cat temp +24.412 +root@orangepione:/sys/class/mpu6050# cat temp +24.412 +root@orangepione:/sys/class/mpu6050# cat temp +24.412