Jetson Nano I2C说明及Python案例

1、Nano I2C 硬件

Nano 七组硬件I2C 总线及对应关系: (分别有1.8V和3.3V)

Nano I2C Bus.png
2、Jetson Nano + IMU (MPU6050)

惯性测量单元(IMU)是一种电子设备,它使用加速度计和陀螺仪(有些搭配磁场传感器件)组合来测量和报告当前设备的速度、方向和重力信息。

硬件连接 (Nano+MPU6050)

1.VCC ---接Nano引脚第17脚; (接 5V 非3.3V)
2.GND ---接Nano 引脚第25脚;
3.SCL ---接Nano 引脚第5脚(GEN2_I2C_SCL),电平已转换成3.3V电平;
4.SDA ---接Nano 引脚第3脚(GEN2_I2C_SDA),电平已转换成3.3V电平;

软件配置
默认Nano L4T 的内核Kernel Image已包含了MPU6050 I2C Driver,不需要修改Kernel 代码, 但需要修改DTS 以支持MPU6050对应I2C和寄存器配置!

hardware\nvidia\platform\t210\porg\kernel-dts\tegra210-porg-p3448-common.dtsi
根据硬件连接修改如下:
1、去掉/*#include "porg-platforms/tegra210-porg-super-module-e2614.dtsi"*/
2、添加
    i2c@7000c400 {  /*bus1 */
        tw_icm20628: icm20628@68 {
            compatible = "invensense,mpu6xxx";
            reg = <0x68>;
            interrupt-parent = <&gpio>;
            interrupts = <TEGRA_GPIO(Z, 0) 0x01>;
            accelerometer_matrix= [01 00 00 00 01 00 00 00 01];
            gyroscope_matrix    = [01 00 00 00 01 00 00 00 01];
            geomagnetic_rotation_vector_disable = <1>;
            gyroscope_uncalibrated_disable = <1>;
            quaternion_disable = <1>;
            status = "okay";
            
            #address-cells = <1>;
            #size-cells = <0>;
            vcc-supply = <&p3448_vdd_3v3_sys>;
            vcc-pullup-supply = <&p3448_vdd_3v3_sys>;
        };
    };
3、烧录dtb 文件
sudo ./flash.sh -r -k DTB jetson-nano-qspi-sd mmcblk0p1

4、检查I2C 设备 (可查看dmesg log), 若显示UU,则参考附录中的说明,检查I2C 的DTS 配置是否有地址相同设备!
nvidia@tw-nano:~$ sudo i2cdetect -y -r 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --    

5、i2c-tools 读写是否成功以验证设备连接
sudo i2cdump -f -y 1 0x86
sudo i2cget -y 1 0x68 0x41   (TEMPERATURE-OUT0)

PS: MPU6050 特性及应用

  • MPU-6000(6050)的角速度测量范围为±250、±500、±1000与±2000°/sec (dps),可准确追踪快速与慢速动作;
  • MPU-6000(6050)的加速器测量范围为±2g、±4g±8g与±16g;
  • 以数字输出6轴或9轴的旋转矩阵、四元数(quaternion)、欧拉角格式(Euler Angle forma)的融合演算数据
  • 数字运动处理(DMP: Digital Motion Processing)引擎可减少复杂的融合演算数据、感测器同步化、姿势感应等的负荷
  • 通讯采用400kHz的IIC或最高达20MHz的SPI(MPU-6050没有SPI);
  • MPU-6000(6050)可在不同电压下工作,VDD供电为2.5V±5%、3.0V±5%或3.3V±5%,逻辑接口VDDIO供电为1.8V± 5%(MPU6000仅用VDD);
  • 应用场景:姿势识别、遥控拍摄、画面放大缩小、滚动、快速下降中断、high-G中断、零动作感应、触击感应、摇动感应功能等等
3、 Python + I2C 读取陀螺仪数据

Step1. 获取用户对I2C总线操作权限 (ex. nvidia as the login name)

$sudo usermod -aG i2c $USER
sudo usermod -aG i2c nvidia
--- 重启系统确保更改I2C等有效 ---

Step2. MPU6050 运行结果如下:

jetbot@jetbot:~/work/mpu-6050-Python$ python3 example.py 
Accelerometer data
x: -7.570465649414062
y: 6.232106921386718
z: 0.7589619262695312
Gyroscope data
x: 1.5801526717557253
y: 3.2900763358778624
z: 0.6793893129770993
Temp: 44.906470588235294oC
Accelerometer data
x: -7.615955480957031
y: 6.241683728027343
z: 0.6344634399414062
Gyroscope data
x: 1.6870229007633588
y: 3.3969465648854964
z: 1.1984732824427482
Temp: 45.00058823529412oC

Step3. Python example案例代码

"""This program handles the communication over I2C
between a Jetson Nano and a MPU-6050 Gyroscope / Accelerometer combo.
Made by: Dennis/TW
Released under the MIT License
Copyright 2019
"""

import smbus

class mpu6050:

    # Global Variables
    GRAVITIY_MS2 = 9.80665
    address = None
    bus = smbus.SMBus(1)

    # Scale Modifiers
    ACCEL_SCALE_MODIFIER_2G = 16384.0
    ACCEL_SCALE_MODIFIER_4G = 8192.0
    ACCEL_SCALE_MODIFIER_8G = 4096.0
    ACCEL_SCALE_MODIFIER_16G = 2048.0

    GYRO_SCALE_MODIFIER_250DEG = 131.0
    GYRO_SCALE_MODIFIER_500DEG = 65.5
    GYRO_SCALE_MODIFIER_1000DEG = 32.8
    GYRO_SCALE_MODIFIER_2000DEG = 16.4

    # Pre-defined ranges
    ACCEL_RANGE_2G = 0x00
    ACCEL_RANGE_4G = 0x08
    ACCEL_RANGE_8G = 0x10
    ACCEL_RANGE_16G = 0x18

    GYRO_RANGE_250DEG = 0x00
    GYRO_RANGE_500DEG = 0x08
    GYRO_RANGE_1000DEG = 0x10
    GYRO_RANGE_2000DEG = 0x18

    # MPU-6050 Registers
    PWR_MGMT_1 = 0x6B
    PWR_MGMT_2 = 0x6C

    SELF_TEST_X = 0x0D
    SELF_TEST_Y = 0x0E
    SELF_TEST_Z = 0x0F
    SELF_TEST_A = 0x10

    ACCEL_XOUT0 = 0x3B
    ACCEL_XOUT1 = 0x3C
    ACCEL_YOUT0 = 0x3D
    ACCEL_YOUT1 = 0x3E
    ACCEL_ZOUT0 = 0x3F
    ACCEL_ZOUT1 = 0x40

    TEMP_OUT0 = 0x41
    TEMP_OUT1 = 0x42

    GYRO_XOUT0 = 0x43
    GYRO_XOUT1 = 0x44
    GYRO_YOUT0 = 0x45
    GYRO_YOUT1 = 0x46
    GYRO_ZOUT0 = 0x47
    GYRO_ZOUT1 = 0x48

    ACCEL_CONFIG = 0x1C
    GYRO_CONFIG = 0x1B

    def __init__(self, address):
        self.address = address

        # Wake up the MPU-6050 since it starts in sleep mode
        self.bus.write_byte_data(self.address, self.PWR_MGMT_1, 0x00)

    # I2C communication methods

    def read_i2c_word(self, register):
        """Read two i2c registers and combine them.

        register -- the first register to read from.
        Returns the combined read results.
        """
        # Read the data from the registers
        high = self.bus.read_byte_data(self.address, register)
        low = self.bus.read_byte_data(self.address, register + 1)

        value = (high << 8) + low

        if (value >= 0x8000):
            return -((65535 - value) + 1)
        else:
            return value

    # MPU-6050 Methods

    def get_temp(self):
        """Reads the temperature from the onboard temperature sensor of the MPU-6050.

        Returns the temperature in degrees Celcius.
        """
        # Get the raw data
        raw_temp = self.read_i2c_word(self.TEMP_OUT0)

        # Get the actual temperature using the formule given in the
        # MPU-6050 Register Map and Descriptions revision 4.2, page 30
        actual_temp = (raw_temp / 340) + 36.53

        # Return the temperature
        return actual_temp

    def set_accel_range(self, accel_range):
        """Sets the range of the accelerometer to range.

        accel_range -- the range to set the accelerometer to. Using a
        pre-defined range is advised.
        """
        # First change it to 0x00 to make sure we write the correct value later
        self.bus.write_byte_data(self.address, self.ACCEL_CONFIG, 0x00)

        # Write the new range to the ACCEL_CONFIG register
        self.bus.write_byte_data(self.address, self.ACCEL_CONFIG, accel_range)

    def read_accel_range(self, raw = False):
        """Reads the range the accelerometer is set to.

        If raw is True, it will return the raw value from the ACCEL_CONFIG
        register
        If raw is False, it will return an integer: -1, 2, 4, 8 or 16. When it
        returns -1 something went wrong.
        """
        # Get the raw value
        raw_data = self.bus.read_byte_data(self.address, self.ACCEL_CONFIG)

        if raw is True:
            return raw_data
        elif raw is False:
            if raw_data == self.ACCEL_RANGE_2G:
                return 2
            elif raw_data == self.ACCEL_RANGE_4G:
                return 4
            elif raw_data == self.ACCEL_RANGE_8G:
                return 8
            elif raw_data == self.ACCEL_RANGE_16G:
                return 16
            else:
                return -1

    def get_accel_data(self, g = False):
        """Gets and returns the X, Y and Z values from the accelerometer.

        If g is True, it will return the data in g
        If g is False, it will return the data in m/s^2
        Returns a dictionary with the measurement results.
        """
        # Read the data from the MPU-6050
        x = self.read_i2c_word(self.ACCEL_XOUT0)
        y = self.read_i2c_word(self.ACCEL_YOUT0)
        z = self.read_i2c_word(self.ACCEL_ZOUT0)

        accel_scale_modifier = None
        accel_range = self.read_accel_range(True)

        if accel_range == self.ACCEL_RANGE_2G:
            accel_scale_modifier = self.ACCEL_SCALE_MODIFIER_2G
        elif accel_range == self.ACCEL_RANGE_4G:
            accel_scale_modifier = self.ACCEL_SCALE_MODIFIER_4G
        elif accel_range == self.ACCEL_RANGE_8G:
            accel_scale_modifier = self.ACCEL_SCALE_MODIFIER_8G
        elif accel_range == self.ACCEL_RANGE_16G:
            accel_scale_modifier = self.ACCEL_SCALE_MODIFIER_16G
        else:
            print("Unkown range - accel_scale_modifier set to self.ACCEL_SCALE_MODIFIER_2G")
            accel_scale_modifier = self.ACCEL_SCALE_MODIFIER_2G

        x = x / accel_scale_modifier
        y = y / accel_scale_modifier
        z = z / accel_scale_modifier

        if g is True:
            return {'x': x, 'y': y, 'z': z}
        elif g is False:
            x = x * self.GRAVITIY_MS2
            y = y * self.GRAVITIY_MS2
            z = z * self.GRAVITIY_MS2
            return {'x': x, 'y': y, 'z': z}

    def set_gyro_range(self, gyro_range):
        """Sets the range of the gyroscope to range.

        gyro_range -- the range to set the gyroscope to. Using a pre-defined
        range is advised.
        """
        # First change it to 0x00 to make sure we write the correct value later
        self.bus.write_byte_data(self.address, self.GYRO_CONFIG, 0x00)

        # Write the new range to the ACCEL_CONFIG register
        self.bus.write_byte_data(self.address, self.GYRO_CONFIG, gyro_range)

    def read_gyro_range(self, raw = False):
        """Reads the range the gyroscope is set to.

        If raw is True, it will return the raw value from the GYRO_CONFIG
        register.
        If raw is False, it will return 250, 500, 1000, 2000 or -1. If the
        returned value is equal to -1 something went wrong.
        """
        # Get the raw value
        raw_data = self.bus.read_byte_data(self.address, self.GYRO_CONFIG)

        if raw is True:
            return raw_data
        elif raw is False:
            if raw_data == self.GYRO_RANGE_250DEG:
                return 250
            elif raw_data == self.GYRO_RANGE_500DEG:
                return 500
            elif raw_data == self.GYRO_RANGE_1000DEG:
                return 1000
            elif raw_data == self.GYRO_RANGE_2000DEG:
                return 2000
            else:
                return -1

    def get_gyro_data(self):
        """Gets and returns the X, Y and Z values from the gyroscope.

        Returns the read values in a dictionary.
        """
        # Read the raw data from the MPU-6050
        x = self.read_i2c_word(self.GYRO_XOUT0)
        y = self.read_i2c_word(self.GYRO_YOUT0)
        z = self.read_i2c_word(self.GYRO_ZOUT0)

        gyro_scale_modifier = None
        gyro_range = self.read_gyro_range(True)

        if gyro_range == self.GYRO_RANGE_250DEG:
            gyro_scale_modifier = self.GYRO_SCALE_MODIFIER_250DEG
        elif gyro_range == self.GYRO_RANGE_500DEG:
            gyro_scale_modifier = self.GYRO_SCALE_MODIFIER_500DEG
        elif gyro_range == self.GYRO_RANGE_1000DEG:
            gyro_scale_modifier = self.GYRO_SCALE_MODIFIER_1000DEG
        elif gyro_range == self.GYRO_RANGE_2000DEG:
            gyro_scale_modifier = self.GYRO_SCALE_MODIFIER_2000DEG
        else:
            print("Unkown range - gyro_scale_modifier set to self.GYRO_SCALE_MODIFIER_250DEG")
            gyro_scale_modifier = self.GYRO_SCALE_MODIFIER_250DEG

        x = x / gyro_scale_modifier
        y = y / gyro_scale_modifier
        z = z / gyro_scale_modifier

        return {'x': x, 'y': y, 'z': z}

    def get_all_data(self):
        """Reads and returns all the available data."""
        temp = get_temp()
        accel = get_accel_data()
        gyro = get_gyro_data()

        return [accel, gyro, temp]

if __name__ == "__main__":
    mpu = mpu6050(0x68)
    print(mpu.get_temp())
    accel_data = mpu.get_accel_data()
    print(accel_data['x'])
    print(accel_data['y'])
    print(accel_data['z'])
    gyro_data = mpu.get_gyro_data()
    print(gyro_data['x'])
    print(gyro_data['y'])
    print(gyro_data['z'])


I2C Python 方法可以参考:
MPU-6050-Python
Raspberry-Pi-I2C-Python
NV Forum TX2-MPU6050

附录:
  • I2C 相关
nvidia@tw-nano:~$ sudo i2cdetect -F 2
Functionalities implemented by /dev/i2c-2:
I2C                              yes
SMBus Quick Command              no
SMBus Send Byte                  yes
SMBus Receive Byte               yes
SMBus Write Byte                 yes
SMBus Read Byte                  yes
SMBus Write Word                 yes
SMBus Read Word                  yes
SMBus Process Call               yes
SMBus Block Write                yes
SMBus Block Read                 no
SMBus Block Process Call         no
SMBus PEC                        yes
I2C Block Write                  yes

案例:
GEN3_I2C_SDA/SCL (PIN232/234) 连接的是EEPROM(AT24C02D)

nvidia@tw-nano:~$ sudo i2cdetect -y -r 2
Warning: Can't use SMBus Quick Write command, will skip some addresses
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                                                 
10:                                                 
20:                                                 
30: -- -- -- -- -- -- -- --                         
40:                                                 
50: 50 -- -- -- -- -- -- 57 -- -- -- -- -- -- -- -- 
60:                                                 
70:                                                 

From the i2cdetect man page:

INTERPRETING THE OUTPUT
       Each  cell  in  the  output  table  will  contain  one of the following
       symbols:

       · "--". The address was probed but no chip answered.

       · "UU". Probing was skipped, because this address is currently  in  use
         by  a  driver.  This  strongly  suggests that there is a chip at this
         address.

       · An address number in hexadecimal, e.g. "2d" or "4e". A chip was found
         at this address.
nvidia@tw-nano:~$ cat /sys/bus/i2c/devices/i2c-0/name 
7000c000.i2c
nvidia@tw-nano:~$ cat /sys/bus/i2c/devices/i2c-1/name 
7000c400.i2c
nvidia@tw-nano:~$ cat /sys/bus/i2c/devices/i2c-2/name 
7000c500.i2c
nvidia@tw-nano:~$ cat /sys/bus/i2c/devices/i2c-3/name 
7000c700.i2c
nvidia@tw-nano:~$ cat /sys/bus/i2c/devices/i2c-4/name 
7000d000.i2c
nvidia@tw-nano:~$ cat /sys/bus/i2c/devices/i2c-5/name 
7000d100.i2c
nvidia@tw-nano:~$ cat /sys/bus/i2c/devices/i2c-6/name 
Tegra I2C adapter
nvidia@tw-nano:~$ ls /sys/firmware/devicetree/base/i2c@7000*
/sys/firmware/devicetree/base/i2c@7000c000:
'#address-cells'   clock-names   compatible   dmas         iommus          name      reg           resets         status
 clock-frequency   clocks        dma-names    interrupts   linux,phandle   phandle   reset-names  '#size-cells'   temp-sensor@4c

/sys/firmware/devicetree/base/i2c@7000c400:
'#address-cells'   clock-frequency   clocks       compatible   dmas      i2cmux@70     interrupts   iqs263@44       name      reg           resets            '#size-cells'
 ak8963@0d         clock-names       cm32180@48   dma-names    gpio@20   icm20628@68   iommus       linux,phandle   phandle   reset-names   rt5659.1-001a@1a   status

/sys/firmware/devicetree/base/i2c@7000c500:
'#address-cells'      battery-gauge@55   clock-names   compatible   dmas         iommus          name      reg           resets         status
 battery-charger@6b   clock-frequency    clocks        dma-names    interrupts   linux,phandle   phandle   reset-names  '#size-cells'

/sys/firmware/devicetree/base/i2c@7000c700:
'#address-cells'   clock-names   compatible   dmas         iommus          name                         phandle            reg           resets         status
 clock-frequency   clocks        dma-names    interrupts   linux,phandle   nvidia,restrict-clk-change   print-rate-limit   reset-names  '#size-cells'

/sys/firmware/devicetree/base/i2c@7000d000:
'#address-cells'   clocks       dmas         linux,phandle   max77621@1c                      nvidia,require-cldvfs-clock   reset-names   sda-gpio
 clock-frequency   compatible   interrupts   max77620@3c     name                             phandle                       resets       '#size-cells'
 clock-names       dma-names    iommus       max77621@1b     nvidia,bit-bang-after-shutdown   reg                           scl-gpio      status

/sys/firmware/devicetree/base/i2c@7000d100:
'#address-cells'   clock-names   compatible   dmas         iommus          name      reg           resets         status
 clock-frequency   clocks        dma-names    interrupts   linux,phandle   phandle   reset-names  '#size-cells'

root@jetbot:~/work/mpu-6050-Python# i2cdump -y 1 0x68
No size specified (using byte-data access)
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 83 01 00 4d d4 e1 02 ce 0d 61 04 0c 28 52 52 b7    ??.M?????a??(RR?
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
30: 00 00 00 00 00 00 00 00 00 00 01 f1 0c 1c cc c6    ..........??????
40: 74 f5 50 00 00 00 00 00 00 00 00 00 00 00 00 00    t?P.............
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
60: 00 00 00 00 00 00 00 00 00 00 00 00 3f 00 00 72    ............?..r
70: 00 00 00 00 00 68 00 00 00 00 00 00 00 00 00 00    .....h..........
80: 83 01 00 4d d4 e1 02 ce 0d 61 04 0c 28 52 52 b7    ??.M?????a??(RR?
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
b0: 00 00 00 00 00 00 00 00 00 00 01 f1 0c 1c cc c6    ..........??????
c0: 74 f5 60 00 00 00 00 00 00 00 00 00 00 00 00 00    t?`.............
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 3f 00 03 df    ............?.??
f0: 00 00 00 00 00 68 00 00 00 00 00 00 00 00 00 00    .....h..........

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容