系统软件构成

启动流程

BM1688 的系统软件属于典型的嵌入式ARM64 Linux,由bootloader、kernel、ramdisk和Ubuntu 20.04 构成,当开机后,依次执行如下:

_images/image14.png

其中:boot ROM、bootloader基于fsbl和u-boot构建;kernel基于Linux的5.10分支构建;Ubuntu 20.04 基于Ubuntu官方arm64源构建,不包含GUI相关组件。

eMMC分区

分区设备文件

挂载点

文件系统

内容

/dev/mmcblk0p1

/boot

FAT32

存放kernel和ramdisk镜像

/dev/mmcblk0p2

/recovery

EXT4

存放recovery mode镜像

/dev/mmcblk0p3

存放配置信息,目前未使用

/dev/mmcblk0p4

/media/root-ro

EXT4

Ubuntu 20.04 系统的read-only部分

/dev/mmcblk0p5

/media/root-rw

EXT4

Ubuntu 20.04 系统的read-write部分

/dev/mmcblk0p6

/data

EXT4

存放sophon sdk的驱动和运行时环境

关于第四和第五分区的说明:第四分区存储了Ubuntu 20.04 系统的关键部分,挂载为只读;第五分区存储Ubuntu 20.04 运行过程中产生的文件,挂载为可读可写。两个分区通过overlayfs聚合后挂载为系统的根目录,如下图所示:

_images/image15.png

用户通常无需关注此细节,对于日常使用来说是透明的,正常操作根目录下文件即可,但当用df等命令查看分区使用率等操作时请知悉此处,如下图:

_images/image16.png

docker

核心板系统已经预装了docker服务,您可以用docker info命令查看状态。注意docker的根目录被配置到了/data/docker目录下,与默认设置不同。

文件系统支持

如果您使用参考底板,当插入U盘或者移动硬盘后(需考虑USB供电能力),存储设备会被识别为/dev/sdb1或类似节点,与桌面PC Linux环境下相同。文件系统支持FAT、FAT32、EXT2/3/4、NTFS。 BM1688 不支持自动挂载,所以需要手工进行挂载:sudo mount /dev/sdb1 /mnt。当访问NTFS格式的存储设备时,预装的内核版本仅支持读取,如果需要写入,需要手工安装ntfs-3g软件包,请参考https://packages.ubuntu.com/search?keywords=ntfs-3g。完成数据写入后,请及时使用sync或umount操作,关机时请使用sudo poweroff命令,避免暴力下电关机,以免数据丢失。

Ethernet操作指南

操作示例

Ethernet模块默认为编入内核,不需另外执行加载操作。

内核下使用网口的操作步骤如下:

  • 配置ip地址和子网掩码

sudo ifconfig eth0 xxx.xxx.xxx.xxx netmask xxx.xxx.xxx.xxx up

  • 设置缺省网关

sudo route add default gw xxx.xxx.xxx.xxx

  • 挂载 nfs

sudo busybox mount -t nfs -o nolock xxx.xxx.xxx.xxx:/your/path /mount-dir

Ex: sudo busybox mount -t nfs -o nolock 192.168.2.10:/NFS /mnt/sd

tftp使用说明

  • 首先在Windows下打开Tftp server

  1. 如果没有tftp工具,请先下载tftpd64工具,链接为https://pjo2.github.io/tftpd64/;

  2. 点击Download page,下载tftpd64位软件安装包后,安装tftpd工具;

  3. 对tftpd64工具进行以下配置;

    _images/image17.png

    可以点击Browse按键将图中的Current Directory设置为tftp进行文件传输的路径比如:C:Program FilesTftpd64;

    Server interfaces设置为本机的网络接口,例如192.168.137.1;

    进入setting界面,勾选TFTP页面中的Reduce ‘//’ in file path。

    注意:请一定要使用64位软件,并勾选Reduce ‘//’ in file path。

在windows下打开Tftp server之后可以在uboot以及kernel下从PC端的Tftp server下载文件。

  • uboot下通过tftp下载

  1. 将板卡和PC通过网线连接,使用串口线连接PC和板卡,打开PC的串口终端。

  2. 上电后在看到串口终端提示Hit any key to stop autoboot时按下回车进入u-boot的命令行模式,看到提示符sophon#

  3. 输入以下命令配置网络:

    setenv ipaddr 192.168.137.11
    setenv gatewayip 192.168.337.1
    setenv serverip 192.168.137.1;
    

    ipaddr是板端IP,serverip是tftp server所在PC的IP,gatewayip是网关地址。

  4. 输入以下命令开始tftp下载:

    tftp xxxxxxxxx filename
    

    这条命令表示从tftp server上下载名filename的文件到地址xxxxxxxxx。

  5. 通过tftp升级系统

    产品支持在uboot阶段通过tftp升级系统,请先下载可以使用的系统安装包tftp.tgz。将安装包解压缩后,把所有的安装文件拷贝到PC server的Current Directory路径下。 此时点击PC server的Show Dir按键,可以显示路径下的系统升级文件。

    _images/image18.png

    在配置网络后输入以下命令进行系统升级:

    tftp 0x120000000 boot.scr   // 下载boot.scr到0x120000000
    source 0x120000000          //执行boot.scr文件
    

    此时串口会打印以下信息,并使用”#”提示升级进度。

    _images/image19.png

    耐心等待直到串口打印”Please remove the installation medium, then reboot”,系统升级成功。

  • kernel下通过tftp上传下载

在PC端运行Tftp server后,将板卡和PC通过网线连接。进入系统后输入以下命令下载文件和上传文件。

下载文件:

sudo busybox tftp –g -r [remote file name] [server ip]

备注: remote file name 为欲下载的文件名称,server ip为下载文件所在server的ip地址 (ex:sudo busybox tftp -g -r test.txt 192.168.0.11)。

上传文件:

sudo busybox tftp –p -l [local file name] [server ip]

备注: local file name 为本地欲上传的文件名称,server ip为上传文件的目标server的ip地址 (ex:sudo busybox tftp -p -l test.txt 192.168.0.11)。

IPv6说明

IPv6环境配置方法如下:

  • 配置ip地址以及网关

sudo ip -6 addr add <ipv6 address>/ipv6 prefixlen dev <port name>

Ex:sudo ip -6 addr add 3FFE:FFFF:7654:FEDA:1245:BA98:3210:4564/64 dev eth1

  • Ping指定的IPv6地址

#ping -6 <ipv6 address>

Ex: ping -6 3FFE:FFFF:7654:FEDA:1245:BA98:3210:4563

IEEE 802.3x流控功能

流控功能描述

BM1688 Ethernet支持IEEE 802.3x所定义的流控功能,透过发送流控帧以及接收对端所发送过来的流控帧的方式来达到流控的目的。

  • 发送流控帧:

    在接收由对端发送过来的封包的过程中,若发现目前接收端的接收对列可能无法满足接收后续送达的封包时,则本地端会发送流控帧至对端,要求对端暂停一段时间不发送封包,藉此来进行流量控制。

  • 接收流控帧:

    当本地端接收到由对端发送过来的流控帧时,本地端会根据帧内的流控时间描述延迟发送封包至对端,等到过了流控延迟时间后,再启动发送。若在等待过程中收到了由对端发送的流控帧其描述的流控时间为0时,则会直接启动发送。

流控功能配置

发送流控帧功能的相关文件配置在linux/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c

static int flow_ctrl = FLOW_AUTO;
module_param(flow_ctrl, int, 0644);
MODULE_PARM_DESC(flow_ctrl, "Flow control ability [on/off]");

static int pause = PAUSE_TIME;
module_param(pause, int, 0644);
MODULE_PARM_DESC(pause, "Flow Control Pause Time");

若欲修改默认pause time,可配置 pause至目标植。

ethtool配置接口流控功能

用户可以通过标准ethtool工具接口进行流控功能的使能。

ethtool –a eth0 命令查看eth0口流控功能状态;打印如下

# ethtool -a eth0
Pause parameters for eth0:
Autonegotiate: on
RX: off
TX: off

其中,RX流控是关闭的,TX流控是关闭的;用户可以通过以下命令打开或关闭TX流控:

# ethtool -A eth0 tx off(关闭TX流控)
# ethtool -A eth0 tx on(打开TX流控)

备注:ethtool工具默认不会编入文件系统,需要用户在需要时候加入。

USB操作指南

操作准备

USB 3.0 Host/Device的操作准备如下:

  • Linux 内核使用SDK发布 Kernel。

  • 文件系统可以使用本地文件系统ext4或squashfs,也可以使用NFS。

  • Shell script“run_usb.sh”. run_usb.sh使用内核的USB ConfigFS功能来客制化USB device装置.使用者可参考并修改run_usb.sh来变更PID/VID与function的相关参数. 详细操作可参考内核文件” linux/Documentation/usb/gadget_configfs.txt”。

linux Host

USB 3.0 Host 操作过程 (以 usb1 为例)

步骤1. 启动平台,加载ext4或squashfs,也可以使用NFS.

步骤2. 设置usb角色

echo host > /sys/kernel/debug/usb/39110000.usb/mode

U盘操作范例

插入检测:

直接插入U盘,观察是否枚举成功. 正常情况下串口打印为:

[ 72.061964] usb 1-1: new high-speed USB device number 2 usingdwc2
[ 72.315816] usb-storage 1-1:1.0: USB Mass Storage device detected
[ 72.335934] scsihost0: usb-storage 1-1:1.0
[ 73.363027] scsi 0:0:0:0: Direct-Access Generic STORAGE DEVICE1532 PQ: 0 ANSI: 6
[ 73.374407] sd 0:0:0:0: Attached scsigeneric sg0 type 0
[ 73.558597] sd 0:0:0:0: [sda] 30253056 512-byte logical blocks:(15.5 GB/14.4 GiB)
[ 73.566961] sd 0:0:0:0: [sda] Write Protect is off
[ 73.571922] sd 0:0:0:0: [sda] Mode Sense: 21 00 00 00
[ 73.577899] sd 0:0:0:0: [sda] Write cache: disabled, read cache:enabled, doesn't support DPO or FUA
[ 73.593961] sda: sda1[ 73.602607] sd 0:0:0:0: [sda] Attached SCSI removable disk

其中,sda1表示U盘或移动硬盘上的第一个分区,当存在多个分区时,会出现sda1,sd2,sda3等字样。

初始化及应用:

插入检测后,进行如下操作:

sdXY中X代表磁盘号,Y代表分区号,请根据具体系统环境进行修改。

  • 分区命令操作的具设备节点为sdX,范例: ~$ fdisk /dev/sda。

  • 用mkdosfs工具格式化的具体分区为sdXY: ~$ mkdosfs -F 32 /dev/sda1。

  • 挂载的具体分区为sdXY: ~$ mount /dev/sda1 /mnt。

  1. 查看分区信息

    • 运行命令”ls /dev”查看系统设备文件,若没有分区信息sdXY,表示还没有分区. 请用fdisk进行分区后进入步骤2。

    • 若有分区信息sdXY,则已经检测到U盘分区,进入步骤2。

  2. 查看格式化信息

    • 若没有格式化,请使用mkdosfs进行格式化后进入步骤3。

    • 若已格式化,进入步骤3。

  3. 挂载目录

    • 运行”mount /dev/sdaXY /mnt”挂载目录。

  4. 对硬盘进行读写操作

linux Device

USB 3.0 Device 操作过程 (以 usb0 为例)

步骤1. 编译USB3.0 Device相关的内核驱动模块

  • 进入menuconfig的如下路径,并配置如下。

Device Driver --->
   [*] USB support --->
      <*> USB Gadget Support --->
         <M> USB functions configurable through configfs
         [*] Abstract Control Model (CDC ACM)
         [*] Mass storage
  • 编译内核模块,生成.ko文件。

步骤2. 加载驱动
insmod /mnt/system/ko/usb_f_mass_storage.ko

步骤3. 启动平台,加载ext4或squashfs文件系统,也可以使用NFS。

步骤4. 將otg controller 切换至device mode

步骤5. echo device > /sys/kernel/debug/usb/39010000.usb/mode

步骤6. 运行shell script “run_usb.sh”

/etc/run_usb.sh probe msc /dev/mmcblkXY

/etc/run_usb.sh start

其中mmcblkXY为第X个磁盘的eMMC或SD中第Y个分区. 请用户根据具体情况选择。

步骤7. 在Host端可将平台当成一个普通的USB存储设备,对其进行分区,格式化,读写等。

USB Device终端设备操作范例

平台作Device时可当作终端设备,操作如下:

步骤1. 插入模块.
insmod /mnt/system/ko/u_serial.ko
insmod /mnt/system/ko/usb_f_acm.ko
insmod /mnt/system/ko/usb_f_serial.ko

步骤2. 將otg controller 切换至device mode

echo device > /sys/kernel/debug/usb/39010000.usb/mode

步骤3. 运行shell script “run_usb.sh”
/etc/run_usb.sh probe acm
/etc/run_usb.sh start

通过USB将平台与Host端相连,即可在Host端将平台识别成USB终端设备,并在/dev目录下生成相应的设备节点ttyACMX,X 为同类型终端设备号码。在device端/dev目录下会生成ttyGSY,Y为同类型终端设备号码。

Host和Device可透过终端设备进行数据传输。

USB Device RNDIS设备操作范例

平台作Device时可当作RNDIS设备,操作如下:

步骤1. 插入模块.
insmod /mnt/system/ko/u_ether.ko
insmod /mnt/system/ko/usb_f_ecm.ko
insmod /mnt/system/ko/usb_f_eem.ko
insmod /mnt/system/ko/usb_f_rndis.ko

步骤2. 將otg controller 切换至device mode

echo device > /sys/kernel/debug/usb/39010000.usb/mode

步骤3. 运行shell script “run_usb.sh”
/etc/run_usb.sh probe rndis
/etc/run_usb.sh start

步骤4. 通过USB将平台与Host端相连,即可在Host端将平台识别成USB Remote NDIS设备,在Windows安装”Remote NDIS Compatible Device”驱动。

_images/USBOpe012.png _images/USBOpe013.png

步骤5. 在单板设定IP地址,例如”ifconfig usb0 192.168.3.101 up”。

步骤6. 在Window设定IP地址。

_images/USBOpe004.png

Host和Device可透过RNDIS设备进行数据传输。

操作中需注意问题

操作中需要注意的问题如下:

  • 系统开机后默认是Host mode. 若要使用Device mode须加载模块并执行USB ConfigFS 脚本. 当切换Device前,用户须确认以下事项:

    • USB Cable未连接Host。

    • 平台上的硬件须切换到对应的USB mode。例如: 在切换到Device mode前,须关闭平台上的USB 5V供电。若平台上有带Hub,需关闭Hub电源并切换路径Switch到Device mode connector。

  • 切换为Device mode后,若要再使用Host mode,用户须重新启动平台。

  • 当平台做终端设备时,因TTY终端特性,若在短时间内传送大量数据, 可能造成数据遗失。用户使用此功能须注意此限制。

  • 在Uboot下使用USB Host读取U盘时,注意若平台上有Hub,须打开Hub电源并切换路径Switch到正确的Connector。

SD/MMC 卡操作指南

操作准备

  1. 使用SDK 发布的 U-boot 和 kernel。

  2. 文件系统:
    对于SD/MMC卡来说,SDK仅支持FAT文件系统,支持可读可写。
    启动至kernel后需挂载至/mnt/sd目录或根据项目需求的目录即可。
  3. 可透过fdisk工具实现分区的工作。

  4. BM1688 SD 支持 2.0与3.0:

目前SD卡支持1.8/3.3V VDDIO,EMMC仅支持1.8V,使用者需注意。

操作过程

  1. 默认SD/MMC 相关驱动模块已全部编入内核,不需再额外执行加载命令。

  2. 插入卡片上电启动,可以在U-boot下,通过fat相关指令查看卡片内容。 启动平台至kernel后,会自扫卡识别产生响应节点:/dev/mmcblk0和/dev/mmcblk0p1。

  3. Uboot下卡片不支持热插拔操作,kernel下支持热插拔。在kernel下插入 SD 卡,就可以对 SD卡进行相关的操作。 具体操作请参见“3.3 操作示例”

操作示例

SD 卡的读写操作示例如下。

初始化及应用:

待 SD卡插入后,进行如下操作(下文 X 为分区号,其值由 fdisk 工具进行分区时决定):
指定fdisk 操作的具体目录为: ~ $ fdisk /dev/mmcblk0 // 需分情况讨论,如果是EMMC启动,SD的设备节点通常是 /dev/mmcblk1 或 /dev/mmcblk2。

步骤1. 检查分区信息

  1. 若没有显示出 p1,表示SD卡还没有分区, 请在Linux下用 fdisk 工具进行分区或是在windows系统上将SD卡进行格式化之后,进入步骤 2。

  2. 若有显示分区信息 p1,则表示 SD卡已经被检测到,并已进行过分区,可进入步骤 2进行挂载。

步骤2. 挂载

  1. ~ $ mount /dev/mmcblk1pX /mnt/sd ,此命令会将SD卡上第X个分区挂载至/mnt/sd目录

操作中需要注意的问题

  1. 需确保SD卡与卡槽硬件脚位接触良好,如若接触不良,有可能会出现检测错误或读写数据错误相关错误信息,并导致读写失败。

  2. 每次插入 SD 卡后,都需要做一次挂载操作,才能读写 SD 卡;如果SD 卡已经挂载到文件系统,拔卡前则必须做一次卸载(umount)操作,否则有可能在下次插入SD卡后看不到SD卡分区。另,异常拔卡亦需要进行卸载动作。

  3. 必须确保 SD 卡已经创建分区,并将该分区格式化为 FAT或FAT32文件系统(LINUX下通过 fdisk命令,Windows下使用磁盘管理工具)。

  4. 在正常操作过程中不能进行的操作:

    • 读写 SD 卡时不要拔卡,否则会打印一些异常信息,并且可能会导致卡中文件或文件系统被破坏。

    • 若当前目录是处于挂载目录之下如/mnt/sd 时,则无法进行卸载操作,必须离开当前目录如/mnt/sd,才能进行卸载操作。

    • 系统中读写挂载目录的进程没有完全结束前,不能进行卸载操作,必须完全结束操作挂载目录的任务才能正常卸载。

    • 在操作过程中出现异常时的操作:

      1. 如果因为读写数据或其它不明原因导致文件系统被破坏,读写SD卡时可能会出现文件系统错误信息,这时需要进行卸载操作,拔卡,再次插卡并挂载,才能再次正常读写 SD 卡。

      2. 因为SD卡的注册,检测/注销过程需要一定的时间,因此拔卡后若再快速地插入卡,有可能会出现检测不到SD卡的现象。

      3. 如果在测试过程中异常拔卡,使用者需要按 ctrl+c 以回退出到kernel shell 下,否则会一直不停地打印异常信息。

      4. SD 卡上有一个以上的分区时,可以通过挂载操作切换挂载不同的分区,但最后需确认挂载操作的次数与卸载操作次数相等,才能确保完全卸载所有的挂载分区。

I2C操作指南

操作准备

I2C的操作准备如下:

  • 使用SDK发布的kernel。

操作过程

  • 加载内核。默认I2C相关模块已全部编入内核,不需要再执行加载命令。

  • 在控制台下运行I2C读写命令或者自行在内核态或者用户态编写I2C读写程序,就可以对挂载在I2C控制器上的外围设备进行读写操作。

接口速率设置说明

如果要更改接口速率,需要修改build/boards/default/dts/sophon/sophon_base.dtsi中i2c node里的clock_frequency,如下所示,并重新编译内核。

i2c0: i2c@29000000 {
   compatible = "snps,designware-i2c";
   clocks = <&clk CV186X_CLK_I2C0>;
   reg = <0x0 0x29000000 0x0 0x1000>;
   clock-frequency = <400000>;

   #size-cells = <0x0>;
   #address-cells = <0x1>;
   resets = <&rst RST_I2C0>;
   reset-names = "i2c0";
};

I2C读写命令示例:

可在linux终端上发iic相关命令detect总线设备和对总线上i2c设备进行读写。

  1. i2cdetect -l

检测系统中的iic总线(在BM1688中可为i2c-0~8)

  1. i2cdetect -y -r N 检测接到i2c-N总线上的所有设备地址,如下检测i2c-2上有哪些设备:

_images/I2COpe002.png
  1. i2cdump -f -y N M 查看i2c-N上地址为M的设备中所有寄存器的值

  2. i2cget -f -y 0 0x3c 0x00//读取i2c-0上地址为0x3c的设备上0x00寄存器的值

  3. i2cset -f -y 0 0x3c 0x40 0x12//写入i2c-0上地址为0x3c的设备上0x40寄存器

内核态I2C读写程序示例:

此示例说明在内核态下如何通过I2C读写程序对I2C外围设备进行读写操作。

步骤 1. 假设已知外围设备挂载在I2C控制器0上,调用i2c_get_adapter()函数以获得I2C控制器结构体 adapter:

adapter = i2c_ger_adapter(0);

步骤 2. 透过i2c_new_device()函数关连I2C控制器与I2c外围设备,以得到I2C外围设备的客户端结构体 client:

client = i2c_new_device(adapter, &info)

备注:info结构体提供i2c外围设备的设备地址

步骤 3. 调用I2C核心层提供的标准读写函数对外围设备进行读写操作:

ret = i2c_master_send(client, buf, count);

ret = i2c_master_recv(client, buf, count);

备注:client为步骤2所得之客户端结构体,buf为需要读写的寄存器地址以及数据,count为buf的长度。

代码示例如下:

//宣告一个外围设备名字叫做”dummy”,设备地址为0x3c
static struct i2c_board_info info = {
               I2C_BOARD_INFO("dummy", 0x3C),
};
static struct i2c_client *client;

static int cvi_i2c_dev_init(void) {
   //分配 i2c控制器指针
   struct i2c_adapter *adapter;

   adapter =i2c_get_adapter(0);
   client = i2c_new_device(adapter, &info);
   i2c_put_adapter(adapter);
   return 0;
}

static int i2c_dev_write(char *buf, unsigned int count){
   int ret;

   ret = i2c_master_write(client, buf, count);
   return ret;
}

static int i2c_dev_read(char *buf, unsigned int count) {
   int ret;

   ret =i2c_master_recv(client, buf, count);
   return ret;
}

用户态I2C读写程序示例:

此操作示例在用户态下通过I2C读写程序实现对I2C外围设备的读写操作。

步骤 1. 打开I2C总线对应的设备文件,获取文件描述符:

i2c_file = open("/dev/i2c-0", O_RDWR);
   if (i2c_file < 0) {
      printf("open I2C device failed %d\n", errno);
      return -ENODEV;
   }



步骤 2.
进行数据读写:
ret = ioctl(file, I2C_RDWR, &packets);
   if (ret < 0) {
      perror("Unable to send data");
      return ret;
   }

备注:需于flags指定读写操作
struct i2c_msg messages[2];
   int ret;

   /*
   * In order to read a register, we first do a "dummy write" by writing
   * 0 bytes to the register we want to read from.  This is similar to
   * the packet in set_i2c_register, except it's 1 byte rather than 2.
   */
   outbuf = reg;
   messages[0].addr = addr;
   messages[0].flags = 0;
   messages[0].len = sizeof(outbuf);
   messages[0].buf = &outbuf;

   /* The data will get returned in this structure */
   messages[1].addr = addr;
   /* | I2C_M_NOSTART */
   messages[1].flags = I2C_M_RD;
   messages[1].len = sizeof(inbuf);
   messages[1].buf = &inbuf;

SPI操作指南

操作准备

SPI的操作准备如下:

  • 使用SDK发布的内核以及文件系统。文件系统可使用SDK所发布的squashFS或ext4。也可透过本地文件系统再透过网络挂载至NFS。

操作过程

  • 加载内核。把SPI相关模块全部编入内核,不需要再执行加载命令。

  • 在控制台下运行SPI读写命令或者自行在内核态或者用户态编写SPI读写程序,就可以对挂载在SPI控制器上的外围设备进行读写操作。

操作示例

内核态SPI读写程序示例:

此操作示例说明在内核态下如何通过SPI读写程序实现对SPI外围设备的读写操作。

步骤 1. 调用SPI核心层函数 spi_busnum_to_master(),以获取一个描述SPI控制器结构体:

master = spi_busnum_to_master(bus_num);

//bus_num为要读写的SPI外围设备所在的控制器号

//master为描述SPI控制器的spi_master结构体类型指针

步骤 2. 通过spi外围设备在核心层的名称调用SPI核心层函数取得挂载在SPI控制器上描述SPI外围设备的结构体:

snprintf(str, sizeof(str), “%s.%u”, dev_name(&master->dev), cs);

dev = bus_find_device_by_name(&spi_bus_type, NULL, str);

spi = to_spi_device(dev);

//spi_buf_type为描述SPI总线的bus_type结构体类型变量

//spi为描述SPI外围设备spi_device结构体类型指针

步骤 3. 调用SPI核心层函数将spi_transfer添加到spi_message队列中。

spi_message_init(&m)

spi_message_add_tail(&t, &m)

//t为spi_transfer结构体类型变量

//m为spi_message结构体类型变量

步骤 4. 调用SPI核心层停工的读写函数对外围设备进行读写操作

status = spi_sync(spi, &m);

status = spi_async(spi, &m)

//spi为描述SPI外围设备的spi_device结构体类型指针

//spi_sync函数为进行spi同步读写操作

//spi_async函数为进行spi异步读写操作

代码示例如下:

此段代码示例仅供参考,而非实际应用功能。

//传入SPI控制器总线号和片选号
static unsigned int busnum;module_param(busnum, uint, 0);
MODULE_PARM_DESC(busnum, "SPI busnumber (default=0)");

static unsigned int cs;
module_param(cs, uint, 0);
MODULE_PARM_DESC(cs, "SPI chip select (default=0)");

extern struct bus_typespi_bus_type;

//宣告SPI控制器的结构体
static struct spi_master *master;

//宣告SPI外围设备的结构体
static struct spi_device *spi_device;

static int __init spidev_init(void) {
   char *spi_name;
   struct device *spi;
   master =spi_busnum_to_master(busnum);
   spi_name = kzalloc(strlen(dev_name(&master->dev)), GFP_KERNEL);

   if (!spi_name)
      return -ENOMEM;

   snprintf(spi_name,sizeof(spi_name), "%s.%u", dev_name(&master->dev),cs);
   spi = bus_find_device_by_name(&spi_bus_type, NULL, spi_name);
   if (spi == NULL)
      return -EPERM;

   spi_device = to_spi_device(spi);
   if (spi_device ==NULL)
      return -EPERM;

   put_device(spi);
   kfree(spi_name);

   return 0;
}
int spi_dev_write(, void *buf,unsigned long len, int buswidth)
{
   struct spi_device *spi = spi_device;
   struct spi_transfer t = {
      .speed_hz = 2000000,
      .tx_buf = buf,
      //buf里需依外围设备规范填入device addr, register addr, write data等资讯
      .len = len,
   };
   struct spi_message m;
   spi->mode = SPI_MODE_0;

   if (buswidth == 16)
      t.bits_per_word = 16;
      else
         t.bits_per_word = 8;
      if (!spi) {
         return -ENODEV;
      }
      spi_message_init(&m);
      spi_message_add_tail(&t, &m);
      return spi_sync(spi, &m);
}
int spi_dev_read(unsigned char devaddr, unsigned char reg_addr,void*buf, size_t len)
{
   struct spi_device *spi = spi_device;
   int ret;
   u8 txbuf[4] = { 0, };
   struct spi_transfer t = {
      .speed_hz = 2000000,
      .rx_buf =buf,
      .len = len,
      };
   struct spi_message m;
   spi->mode = SPI_MODE_0;

   if (!spi) {
      return -ENODEV;
   }
   txbuf[0] = devaddr;
   txbuf[1] = 0;
   txbuf[2] = reg_addr;//txbuf[1]&txbuf[2]需根据外围设备位宽来决定填写1 byte or 2 bytes,此范例为2     |bytes位宽
   t.tx_buf =txbuf;

   spi_message_init(&m);
   spi_message_add_tail(&t, &m);
   ret = spi_sync(spi, &m);

   return ret;
}

用户态SPI读写程序示例:

此操作示例在用户态下实现对挂载在SPI控制器0上的SPI外围设备的读写操作。(具体实现可参考 tools/spi/spidev_test.c)

步骤1: 打开SPI总线对应的设备文件,获取文件描述符。

tatic const char *device = "/dev/spidev3.0";
…
fd = open(device, O_RDWR);
if (fd < 0)
   pabort("can't open device");

备注: SPI控制器1上挂载的外围设备默认节点为 “dev/spidev1.0”

SPI控制器2上挂载的外围设备默认节点为 “dev/spidev2.0”

SPI控制器3上挂载的外围设备默认节点为 “dev/spidev3.0”

仅需替换节点名称即可,其馀操作与SPI控制器0上挂载的外围设备相同。

步骤2: 通过ioctl设置SPI传输模式:

/*
*spi mode
*/
ret = ioctl(fd, SPI_IOC_WR_MODE32, &mode);
if (ret == -1)
   pabort("can't set spi mode");
ret = ioctl(fd, SPI_IOC_RD_MODE32, &mode);
if(ret == -1)
   pabort("can't get spi mode");

备注:mode 值配置请参考下图或是内核代码 include/linux/spi/spi.h,

Ex. mode = SPI_MODE_3 | SPI_LSB_FIRST;

#define SPI_CPHA 0x01 /* clock phase */
#define SPI_CPOL 0x02 /* clock polarity */
#define SPI_MODE_0 (0|0) /* (original MicroWire) */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)

步骤3: 通过ioctl设置SPI传输频宽:

/*
* bits per word
*/
ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
if (ret == -1)
   pabort("can't set bits per word");
ret = ioctl(fd,SPI_IOC_RD_BITS_PER_WORD, &bits);
if (ret == -1)
   pabort("can't get bits per word");

步骤4: 通过ioctl设置SPI传输速度 (一般建议speed = 25M):
/*
* max speed hz
*/
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if (ret == -1)
   pabort("can't set max speed hz");

ret = ioctl(fd,SPI_IOC_RD_MAX_SPEED_HZ, &speed);
if (ret == -1)
   pabort("can't get max speed hz");

步骤5: 使用ioctl进行数据读写:
ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
if (ret < 1)
   pabort("can't send spi message");

备注: tr传输一帧消息的spi_ioc结构体数组首地址。

GPIO操作指南

GPIO的操作准备如下:

  • 使用SDK发布的kernel

操作过程

  • 默认GPIO相关模块已全部编入内核,不需要再执行加载命令。

  • 在控制台下运行GPIO读写命令或者自行在内核态或者用户态编写GPIO读写程序,就可以对GPIO进行输入输出操作。

操作示例

GPIO操作命令示例:

步骤1: 在控制台使用echo命令, 指定待操作的GPIO编号N:

echo N > /sys/class/gpio/export

N为待操作的GPIO编号,GPIO编号 = GPIO组号值 + 偏移值.

以原理图中GPIO_num管脚为例,num % 32 = base … off,对应的组号为base,偏移值为off。 例如GPIO32,32 % 32 = 1 … 0,则其组号为1,偏移值为0,组号1对应的组号值为448

因此GPIO编号N为448 + 0 = 448

组号值对应如下:

组号0 对应linux组号值为 480
组号1 对应linux组号值为 448
组号2 对应linux组号值为 416
组号3 对应linux组号值为 384
组号4 对应linux组号值为 352
组号5 对应linux组号值为 320
pwr_gpio 对应linux组号值为 288

echo N > /sys/class/gpio/export 之后, 生成/sys/class/gpio/gpioN目录

步骤2: 在控制台使用echo命令设置GPIO方向:

设置为输入:echo in > /sys/class/gpio/gpioN/direction

设置为输出:echo out > /sys/class/gpio/gpioN/direction

例: 设置GPIO32 (即编号448)方向为输入:
echo in > /sys/class/gpio/gpio448/direction
设置GPIO32 (即编号448)方向为输出:
echo out > /sys/class/gpio/gpio448/direction

步骤3: 在控制台使用cat命令查看GPIO输入值, 或使用echo命令设置GPIO输出值:

查看输入值: cat /sys/class/gpio/gpioN/value

输出低:echo 0 > /sys/class/gpio/gpioN/value

输出高:echo 1 > /sys/class/gpio/gpioN/value

步骤4: 使用完毕后, 在控制台使用echo命令释放资源:

echo N > /sys/class/gpio/unexport

注:可以通过打开 CONFIG_DEBUG_FS 选项开启gpio的sysfs debug功能,在操作前通过如下命令查看gpio pin具体对应的组号值:

cat /sys/kernel/debug/gpio

内核态GPIO操作程序示例:

内核态GPIO读写操作程序示例:

步骤1: 注册GPIO:

gpio_request(gpio_num, NULL);

gpio_num为要操作的GPIO编号,该编号等于”GPIO组号 + 组内偏移号”

步骤2: 设置GPIO方向:

对于输入:gpio_direction_input(gpio_num)
对于输出:gpio_direction_output(gpio_num, gpio_out_val)

步骤3: 查看GPIO输入值或设置GPIO输出值:

查看输入值: gpio_get_value(gpio_num);
输出低:gpio_set_value(gpio_num, 0);
输出高:gpio_set_value(gpio_num, 1);

步骤4: 释放注册的GPIO编号:

gpio_free(gpio_num);

内核态GPIO中断操作程序示例:

步骤1: 注册GPIO:

gpio_request(gpio_num, NULL);

gpio_num为要操作的GPIO编号,该编号等于”GPIO组号 + 组内偏移号”

步骤2: 设置GPIO方向:

gpio_direction_input(gpio_num);

对于要作为中断源的GPIO引脚,方向必须配置为输入对于输出

步骤3: 映射操作的GPIO编号对应的中断号:

irq_num = gpio_to_irq(gpio_num);

中断号为gpio_to_irq(gpio_num)的返回值

步骤4: 注册中断:

request_irq(irq_num, gpio_dev_test_isr, irqflags, "gpio_dev_test",&gpio_irq_type))

Irqflags为需要注册的中断类型,常用类型为:

IRQF_SHARED :共享中断;

IRQF_TRIGGER_RISING :上升沿触发;

IRQF_TRIGGER_FALLING :下降沿触发;

IRQF_TRIGGER_HIGH :高电平触发;

IRQF_TRIGGER_LOW :低电平触发

步骤5: 结束时释放注册的中断和GPIO编号:

free_irq(gpio_to_irq(gpio_num), &gpio_irq_type);
gpio_free(gpio_num);

用户态GPIO操作程序示例:

用户态GPIO读写操作程序示例:

步骤1: 将要操作的GPIO编号export:

fp = fopen("/sys/class/gpio/export", "w");
fprintf(fp, "%d", gpio_num);
fclose(fp);

gpio_num为要操作的GPIO编号,该编号等于”GPIO组号 + 组内偏移号”

步骤2: 设置GPIO方向:

fp = fopen("/sys/class/gpio/gpio%d/direction", "rb+");
对于输入:fprintf(fp, "in");
对于输出:fprintf(fp, "out");

fclose(fp);

步骤3: 查看GPIO输入值或设置GPIO输出值:

fp = fopen("/sys/class/gpio/gpio%d/direction", "rb+");
查看输入:fread(buf, sizeof(char), sizeof(buf) - 1, fp);
输出低:
   strcpy(buf, “0”);
   fwrite(buf, sizeof(char), sizeof(buf) - 1, fp);
输出高:
   strcpy(buf,“1”);
   fwrite(buf, sizeof(char), sizeof(buf) - 1, fp);

步骤4: 将操作的GPIO编号unexport:

fp = fopen("/sys/class/gpio/unexport", "w");
fprintf(fp, "%d", gpio_num);
fclose(fp);

UART操作指南

UART的操作准备如下

  • 使用SDK发布的kernel;

  • 将需要测试的设备树节点status改为“okay”;

    uart0: serial@29180000 {
        compatible = "snps,dw-apb-uart";
        reg = <0x0 0x29180000 0x0 0x1000>;
        clock-frequency = <200000000>;
        reg-shift = <2>;
        reg-io-width = <4>;
        status = "okay";
    };
    

操作过程

命令行操作示例:

  • 设置好对应引脚的PINMUX;

  • 执行以下指令设定波特率为115200,数据位8,停止位1(ttySx,x为0~8):

    stty -F /dev/ttySx ispeed 115200 ospeed 115200 cs8 stop 1
  • 进行收发数据:

    echo 123 > /dev/ttySx # 向uartx输入123
    cat /dev/ttySx

用户态UART操作程序示例:

/* Open your specific device (e.g., /dev/mydevice): */
fd = open ("/dev/ttyS0", O_RDWR);
if (fd < 0) {
    /* Error handling. See errno. */
    return -1;
}

struct termios t;
tcgetattr(fd, &t);
cfsetispeed(&t, B115200); // 设置输入波特率为115200
cfsetospeed(&t, B115200); // 设置输出波特率为115200
t.c_cflag &= ~PARENB;   // 不使用奇偶校验
t.c_cflag &= ~CSTOPB;   // 使用1位停止位
t.c_cflag &= ~CSIZE;    // 清除数据位设置
t.c_cflag |= CS8;       // 设置数据位为8位
tcsetattr(fd, TCSANOW, &t);
// read/write ...

Watchdog操作指南

Watchdog的操作准备如下:

  • 使用SDK发布的Linux kernel。

模块编译

  • Linux WDT驱动默认是build-in形式,不需要手动编译和insmod。

操作示例

Watchdog SDK采用Linux标准WDT框架, 为用户提供操作接口。

  • Watchdog 特征

    Watchdog 默认是开启的,默认超时时长30s,由内核负责喂狗,内核异常时会重启,客户可自行决定是否在用户层使用 Watchdog,

    应用 open Watchdog设备节点后,喂狗操作转由用户层负责,内核停止喂狗,当用户层或者内核层异常时,都会触发wdt 重启设备。

    Watchdog 支持高精度超时时长,可以配置并以任意超时时长工作,没有特殊限制(例如:只支持1秒, 2秒, 5秒, 10秒, 21秒, 42秒, 85秒,当使用者配置timtout时间为8秒, 驱动会选择大于等于该值的timeout即10秒)

  • 使用方式

    用户可以在Linux shell下运行Watchdog 操作命令或者自行在内核态或者用户态编写程序,操作硬件Watchdog

    用户只需打开, start/stop wdt或设定timeout,即可使用watchdog。当watchdog timeout发生时, 系统重新启动.

    注意:watchdog 默认以30s超时时长运行,设置超时时长需要先stop wdt

  • 包含 WATCHDOG 接口头文件

#include <linux/watchdog.h>
  • 打开 WATCHDOG

    open /dev/watchdog 或 /dev/watchdog0 设备节点,watchdog 即被启动。

int wdt_fd = -1;

wdt_fd = open("/dev/watchdog", O_WRONLY);
if (wdt_fd == -1)
{
   // fail to open watchdog device
}
  • 关闭 WATCHDOG

    驱动支持”Magic Close”, 在关闭watchdog前必须将magic 字符’V’写入watchdog设备。
    如果userspace daemon没有发送’V’而直接关闭设备,则watchdog驱动持续计数, 在给定时间内未喂狗仍会导致timeout, 系统重启.

    参考代码如下:

int option = WDIOS_DISABLECARD;

ioctl(wdt_fd, WDIOC_SETOPTIONS, &option);
if (wdt_fd != -1)
{
   write(wdt_fd, "V", 1);
   close(wdt_fd);
   wdt_fd = -1;
}
  • 设定 TIMEOUT值

通过标准的 IOCTL 命令 WDIOC_SETTIMEOUT设定 timeout,单位为秒.

int timeout = 10;
ioctl(wdt_fd, WDIOC_SETTIMEOUT, &timeout);
  • PING watchdog (喂狗)

    通过标准的 IOCTL 命令 WDIOC_KEEPALIVE喂狗.

while (1) {
   ioctl(fd, WDIOC_KEEPALIVE, 0);
   sleep(1);
}

PWM操作指南

PWM的操作准备如下:

  • 使用SDK发布的kernel。

操作过程

  • 插入模块: insmod cv186x_pwm.ko;

  • 在控制台下运行PMW读写命令或者自行在内核态或者用户态编写PWM读写程序,就可以对PWM进行输入输出操作;

  • PWM 操作在定频时钟100MHz,共有20路,每路可单独控制;

  • BM1688共有20个PWM IP (pwmchip0/ pwmchip4/ pwmchip8/ pwmchip12/ pwmchip16), 各IP控制5路讯号, 总共可控制20路讯号; 电路图上以 pwm0~pwm15表示

    在Linux sysfs中, pwm0~pwm3的device node各自如下:

    /sys/class/pwm/pwmchip0/pwm0~3

    在Linux sysfs中, pwm4~pwm7的device node各自如下:

    /sys/class/pwm/pwmchip:mark:4/pwm0~3

    以此类推

操作示例

PWM操作命令示例:

步骤1:

在控制面板使用echo命令,配置待操作的PWM编号, 此例为PWM1:

echo 1 > /sys/class/pwm/pwmchip0/export

步骤2:

设置PWM一个周期的持续时间,单位为ns:

echo 1000000 >/sys/class/pwm/pwmchip0/pwm1/period

步骤3:

设置一个周期中的”ON”时间,单位为ns,即占空比=duty_cycle/period=50% :

echo 500000 >/sys/class/pwm/pwmchip0/pwm1/duty_cycle

步骤4:

设置PWM使能

echo 1 >/sys/class/pwm/pwmchip0/pwm1/enable

通过文件IO操作程序示例:

用户态GPIO读写操作程序示例:

步骤1: 配置待操作的PWM编号, 以PWM1为例:

fd = open("/sys/class/pwm/pwmchip0/export", O_WRONLY);
if(fd < 0)
{
dbmsg("open export error\n");
return -1;
}
ret = write(fd, "1", strlen("0"));
if(ret < 0)
{
dbmsg("Export pwm1 error\n");
return -1;
}

步骤2: 设置PWM一个周期的持续时间,单位为ns:

fd_period = open("/sys/class/pwm/pwmchip0/pwm1/period", O_RDWR);
ret = write(fd_period, "1000000”,strlen("1000000”));
if(ret < 0)
{
dbmsg("Set period error\n");
return -1;
}

步骤3: 设置一个周期中的”ON”时间,单位为ns: (此例占空比为50%)

fd_duty = open("/sys/class/pwm/pwmchip0/pwm1/duty_cycle", O_RDWR);
ret = write(fd_duty, "500000", strlen("500000"));
if(ret < 0)
{
dbmsg("Set period error\n");
return -1;
}

步骤4: 设置PWM使能

fd_enable = open("/sys/class/pwm/pwmchip0/pwm1/enable", O_RDWR);
ret = write(fd_enable, "1", strlen("1"));
if(ret < 0)
{
   dbmsg("enable pwm0 error\n");
   return -1;
}

ADC操作指南

ADC的操作准备如下:

  • 使用SDK发布的kernel。

操作过程

  • 插入模块: insmod cv186x_saradc.ko。

  • 在控制台下运行ADC读写命令或者自行在内核态或者用户态编写ADC读写程序,就可以对ADC进行输入输出操作。

  • 用户层通过访问IIO接口来实现5通道,12-bit ADC的触发、采样等操作。

  • 1.5v ref参考电压。

  • adc引脚和sysfs文件对应关系如下:

    adc1 对应sysfs文件为 in_voltage1_raw
    adc2 对应sysfs文件为 in_voltage2_raw
    adc3 对应sysfs文件为 in_voltage3_raw
    sar0 对应sysfs文件为 in_voltage4_raw
    sar1 对应sysfs文件为 in_voltage5_raw
  • 电压值计算公式:vol = val * 1500 / 4096,单位:mV

操作示例

ADC操作命令示例:

步骤1: | 指定ADC通道 1~5, 此例为ADC1:

echo 1 > /sys/bus/iio/devices/iio:device0/in_voltage1_raw

步骤2:

读出刚才指定的ADC channel值:
cat /sys/bus/iio/devices/iio:device0/in_voltage1_raw

用户态ADC读取操作程序示例:

用户态ADC读写操作程序示例:

fd = open("/sys/bus/iio/devices/iio:device0/in_voltage1_raw", O_RDWR|
O_NOCTTY|O_NDELAY);
if (fd < 0)
    printf("open adc err!\n");

write(fd, "1", 1);
lseek(fd, -1, SEEK_CUR);


char buffer[512] = {0};
int len = 0;
unsigned int adc_value = 0;

len = read(fd, buffer, 5);
if (len != 0) {
    printf("read buf: %s\n", buffer);
    adc_value= atoi(buffer);
    printf("adc value is %d\n", adc_value);
}
write(fd, "0", 1);
close(fd);

PINMUX操作指南

uboot下pinmux设置

u-boot下pinmux头文件u-boot-2021.10/board/cvitek/cv186x/pinmux目录下,要在uboot下配置pinmux,需要包含cv186x_pinmux.h,其中:

  • PINMUX_CONFIG(PIN_NAME, FUNC_NAME, GROUP):
    • PIN_NAME:引脚名,对应pinlist文档中第一列的Signal Name;

    • FUNC_NAME:功能名,对应pinlist文档中要切换的功能;

    • GROUP:该功能所在group,对应pinlist文档中group列(具体见group_pin_t enum);

例如,要将PWR_WAKEUP0引脚的功能切为PWR_IRRX0:

  • 首先,去pinlist中找到PWR_WAKEUP0引脚所在行,并找到group(7)和目标function(PWR_IRRX0):
    _images/pinmux0.png
  • 在代码中通过PINMUX_CONFIG(PWR_WAKEUP0, PWR_IRRX0, G7)设置好pinmux;

  • 重新编译u-boot,烧录固件重启;

kernel下pinmux设置

kernel下pinmux头文件位于linux_5.10/drivers/pinctrl/cvitek/pinctrl-cv186x.h,使用同u-boot。

userspace下pinmux设置

userspace下提供了cvi_pinmux工具,支持设置pinmux、内部上下拉以及驱动能力,使用方法如下:

cvi_pinmux for bm1688
./cvi_pinmux -p          <== List all pins
./cvi_pinmux -l          <== List all pins and its func
./cvi_pinmux -r pin      <== Get func from pin
./cvi_pinmux -w pin/func <== Set func to pin
./cvi_pinmux -c <pin name>,<0 or 1 or 2>  <== Set pin pull up/down (0:pull down; 1:pull up; 2:pull off)
./cvi_pinmux -d <pin name>,<0 ~ 15>  <== Set pin driving

例如,要设置PWR_WAKEUP0引脚为PWR_IRRX0功能,并设置其内部上拉,驱动能力设置为15(0~15数值越大驱动能力越强):

  • cvi_pinmux -w PWR_WAKEUP0/PWR_IRRX0

  • cvi_pinmux -c PWR_WAKEUP0,1

  • cvi_pinmux -d PWR_WAKEUP0,15

另外,可以通过cvi_pinmux -l列出soc上所有pin和其functions,或通过cvi_pinmux -r pin_name,读取对应pin所包含的功能。

BM1688内置mcu功能说明

BM1688内置mcu加载程序和启动

内置8051 mcu的固件已经放在”fsbl/plat/cv186x/prebuilt/cv186x_mcu_fw.bin”,编译fsbl时,固件被编入fip.bin。mcu固件将在bl2阶段加载。进入系统后可以查看使用以下命令检查mcu是否正在运行。

busybox devmem 0x05025018

通过这个命令检查寄存器的bit 1位是否为1,若为1,代表mcu已启动。 将此位写0,即可关闭mcu。

内置mcu功能

目前内置mcu承担了两部分的功能,其一是负责ddr retrain的部分功能。其二是按键与led的相关需求。以下是mcu按键与led功能介绍。

  • 以下代码仅在SE9上运行:mcu通过读OEM的产品参数来判断是否是SE9。如果是则开启以下功能

  • STAT指示灯在系统加载过程中亮红灯:mcu将pwr_gpio4拉低熄灭绿灯、pwr_gpio5拉高亮红灯。

  • SSD指示灯在系统加载过程中亮红灯:mcu将pwr_gpio3拉低熄灭绿灯、pwr_gpio6拉高亮红灯。

  • 电源指示灯开机长亮关机熄灭:mcu将pwr_gpio1拉低熄灭,拉高点亮。

  • 长按电源键2s下电:mcu检测到引脚PWR_BUTTON1低电平2s,通知系统层下电。三个指示灯熄灭。

  • 长按电源键5s下电:mcu检测到PWR_BUTTON1低电平5s,mcu控制系统下电。三个指示灯熄灭。

  • 短按电源键上电:mcu控制系统上电,电源指示灯亮绿灯。其他指示灯亮红灯。

  • 长按复位键12s恢复出厂设置:mcu检测到PWR_ON低电平12s,通知系统层恢复出厂设置并重启。STAT、SSD亮红灯。

  • 短按复位键重启:mcu检测到引脚低电平控制系统热重启。STAT、SSD亮红灯。

mcu通过向0x0502601c写1通知系统层软件下电。写2通知系统层恢复出厂。另外mcu记录了由mcu触发的上下电原因,可以从寄存器0x05026030读出,0~3bit记录下电原因:

  • 0x1:短按重启

  • 0x2:长安12s回恢复出厂设置

  • 0x4:长按2s下电

  • 0x5:长按5s下电

  • 0x6:127摄氏度过热下电

4~7bit记录上电原因:

  • 0x1:短按重启

  • 0x2:长按恢复出厂设置

  • 0x3:短按上电

mcu根据OEM判断是否开启相关功能

mcu根据OEM中的product参数判断是否执行按键与led代码。fsbl阶段将OEM(256Byte)从EMMC的硬件boot1分区读入mcu的sram的最后256Byte。具体请见OEM说明。mcu读其中的product参数,若参数为SE9,则启用相关功能。可以进入kernel后使用下面的命令将SE9写入OEM的product参数:

echo 0 > /sys/block/mmcblk0boot1/force_ro
echo "SE9" > se9.txt
dd if=se9.txt of=/dev/mmcblk0boot1 count=4 bs=1 seek=208
echo 1 > /sys/block/mmcblk0boot1/force_ro

CAN操作指南

准备过程

CAN 的操作准备如下:

使用 SDK 发布的 kernel

操作过程

加载内核,默认 CAN 驱动已经全部编入内核,不需要再执行加载命令

在控制台下使用 ip 命令配置 can 波特率,使用 cansend 发送数据,使用 candump 接收数据

使用 ifconfig -a 命令可以查看 can 的网络节点

_images/CAN.png

CAN 读写命令示例

波特率设置 CAN2.0 A/B 波特率设置成 250K

ip link set can0 up type can bitrate 250000

如果是CANFD模式,波特率设置成 250K 的方法如下

ip link set can0 type can bitrate 250000 sample-point 0.8 dbitrate 250000 dsample-point 0.75 fd on

打开 CAN 设备

ifconfig can0 up

接收 CAN 数据

candump can0
_images/CANDUMP.png

发送 CAN 数据

cansend can0 -i 0x55 0x12 0x13 0x14 0x13

其中 -i 是指定 CAN 发送帧的 ID,0x55 就是 CAN ID

_images/CANSEND.png

芯片的 CAN 总线可以支持发送标准帧和拓展帧,但是在同一种配置下仅支持接收标准帧或者仅支持接收拓展帧。下面是切换接收模式的命令。默认状态下仅接收拓展帧。

#切换模式前先关闭CAN

canconfig can0 stop 或者 ifconfig can0 down

#切换到仅接收标准帧模式

canconfig can0 ctrlmode STD on

#切换到仅接收拓展帧模式

canconfig can0 ctrlmode STD off

#打开CAN

canconfig can0 start 或者 ifconfig can0 up

切换模式是通过内核中的ctrlmode新增CAN_CTRLMODE_STD实现的。

#define CAN_CTRLMODE_STD 0x100
sdvt_can_dev->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
                                CAN_CTRLMODE_LISTENONLY |
                                CAN_CTRLMODE_BERR_REPORTING |
                                CAN_CTRLMODE_FD |
                                CAN_CTRLMODE_ONE_SHOT |
                                CAN_CTRLMODE_STD;

操作示例

内核态 can 读写程序示例:

此操作示例说明内核状态下如何接收 can 数据

步骤 1 调用 alloc_can_skb 函数,获得 can 网络节点的 skb

步骤 2 判断帧类型,调用 receive_remote_frame 或者 receive_frame 来读取 FIFO 中的数据,将接收到的数据传递给结构体 cf.

static void sdvt_can_read_fifo(struct net_device *dev)
{
        struct net_device_stats *stats = &dev->stats;
        struct sdvt_can_classdev *cdev = netdev_priv(dev);
        struct canfd_frame *cf;
        struct sk_buff *skb;
        int m_i_i   = 0;



        skb = alloc_can_skb(dev, (struct can_frame **)&cf);
        // cdev->ops->write_reg(cdev,SDVT_CAN_FIFO_FLUSH,0x2);
        if (!skb) {
                stats->rx_dropped++;
                return;
        }

        if(cmd_o.irq_status0_8b & (1 << SDVT_CAN_IRQ_REMOTE_FRAME)){
                // Read the length from RX LENGTH FIFO
                receive_remote_frame(cdev,&cmd_o,&cfg_o);
                cf->can_id = cmd_o.ident_32b | CAN_RTR_FLAG;
                netdev_dbg(dev, "remote frame\n");
        }else{
                receive_frame(cdev,&cmd_o,&cfg_o);
                cf->len =  cmd_o.rx_len_2d_8b[1];
                cf->can_id =  cmd_o.ident_32b;
                for (m_i_i = 0; m_i_i < cf->len; m_i_i += 1) {
                        ((u_int8_t *)cf->data)[m_i_i] = cmd_o.rx_data_2d_8b[m_i_i];
                }
        }

        cmd_o.irq_status1_8b = 0x00;
        cmd_o.irq_status0_8b = 0x00;
        dev_info(cdev->dev,"\n");

        stats->rx_packets++;
        stats->rx_bytes += cf->len;
        netif_receive_skb(skb);
}

此操作示例说明内核状态如何发送 can 数据:

步骤 1 获得结构体 cf

步 骤 2 接 从 结 构 体 cf 获 得 的 cf->len 赋 值 给 cmd_o.dlc_4b, 将 cf->data 赋 值 给 cmd_o.data_2d_8b,然后通过 send_command(cdev,&cmd_o,&cfg_o) 函数将这些值写入到 can FIFO 中.

static netdev_tx_t stvd_can_tx_handler(struct sdvt_can_classdev *cdev)
{
        struct canfd_frame *cf = (struct canfd_frame *)cdev->tx_skb->data;
        int m_i_i   = 0;
        cmd_o.data_len_code_4b  = cf->len;
        cmd_o.dlc_4b = cmd_o.data_len_code_4b;

        pr_info("send data:");
        for (m_i_i = 0; m_i_i < cmd_o.data_len_code_4b; m_i_i += 1) {
                cmd_o.data_2d_8b[m_i_i] = ((u_int8_t *)cf->data)[m_i_i];
                pr_info(" get xmit data %#x", cmd_o.data_2d_8b[m_i_i]);
        }
        pr_info("\n");

        send_command(cdev,&cmd_o,&cfg_o);

        return NETDEV_TX_OK;
}

用户态 can 读写程序示例 can 用户态的数据接收使用的是 socket 的方式

can 用户态发送和接收数据,使用 socket() 函数创建 socket 描述符,但是要注意 socket 的 domain,type,protocol 的设定,然后使用 bind()函数将一个地址族中的特定地址赋给 socket。建立连接后,使用 write/read 进行数据读写。

int main(int argc, char **argv)
{
        struct can_frame frame = {
                .can_id = 1,
        };
        struct ifreq ifr;
        struct sockaddr_can addr;
        char *interface;
        int family = PF_CAN, type = SOCK_RAW, proto = CAN_RAW;
        int loopcount = 1, infinite = 0;
        int s, opt, ret, i,nbytes, err,dlc = 0, rtr = 0, extended = 0;
        int verbose = 0;
        int count = 0;
        int read_enable =0;
        char buf[BUF_SIZ];
        unsigned char send_data[8] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08};

        interface = "can0";

        printf("interface = %s, family = %d, type = %d, proto = %d\n",
                interface, family, type, proto);

        s = socket(family, type, proto);
        if (s < 0) {
                perror("socket");
                return 1;
        }

        addr.can_family = family;
        strcpy(ifr.ifr_name, interface);
        if (ioctl(s, SIOCGIFINDEX, &ifr)) {
                perror("ioctl");
                return 1;
        }
        addr.can_ifindex = ifr.ifr_ifindex;

        if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
                perror("bind");
                return 1;
        }

        for (i = 0; i < sizeof(send_data); i++) {
                frame.data[dlc] = send_data[i];
                dlc++;
        }
        frame.can_dlc = dlc;


        if (extended) {
                frame.can_id &= CAN_EFF_MASK;
                frame.can_id |= CAN_EFF_FLAG;
        } else {
                frame.can_id &= CAN_SFF_MASK;
        }

        if (rtr)
                frame.can_id |= CAN_RTR_FLAG;

        ret = write(s, &frame, sizeof(frame));

        read_enable = 1;
        while (read_enable) {
                if ((nbytes = read(s, &frame, sizeof(struct can_frame))) < 0) {
                        perror("read");
                        return 1;
                } else {
                        if (frame.can_id & CAN_EFF_FLAG)
                                n = snprintf(buf, BUF_SIZ, "<0x%08x> ", frame.can_id & CAN_EFF_MASK);
                        else
                                n = snprintf(buf, BUF_SIZ, "<0x%03x> ", frame.can_id & CAN_SFF_MASK);

                        n += snprintf(buf + n, BUF_SIZ - n, "[%d] ", frame.can_dlc);
                        for (i = 0; i < frame.can_dlc; i++) {
                                n += snprintf(buf + n, BUF_SIZ - n, "%02x ", frame.data[i]);
                        }
                        if (frame.can_id & CAN_RTR_FLAG)
                                n += snprintf(buf + n, BUF_SIZ - n, "remote request");

                        fprintf(out, "%s\n", buf);

                        do {
                                err = fflush(out);
                                if (err == -1 && errno == EPIPE) {
                                        err = -EPIPE;
                                        fclose(out);
                                        out = fopen(optout, "a");
                                        if (!out)
                                                exit (EXIT_FAILURE);
                                }
                        } while (err == -EPIPE);

                        n = 0;
                }
        }


        close(s);
        return 0;
}

4G/5G模块操作指南

检查 USB 设备枚举

首先,您需要确认系统是否已经识别并枚举了连接的 USB 设备。使用 lsusb 命令可以列出所有已连接的 USB 设备:

lsusb

如果命令的输出中显示了您的设备信息,这意味着设备已经被系统成功识别。例如,下面的输出展示了一个已经被系统枚举的 Fibocom 无线模块:

Bus 004 Device 002: ID 2cb7:0a06 Fibocom Wireless

在本例中,我们关注的是 Fibocom 的 FM650 型号模块。

验证网络接口是否注册

接下来,需要确认网络接口是否已经在系统中注册。运行 ifconfig -a 命令可以列出所有的网络接口,包括那些尚未配置的:

ifconfig -a

检查输出结果,以确保您的网络接口已经列出,这表明接口已经在系统中注册,即使它可能还没有配置 IP 地址。

查看 USB 串口是否正常

# 列出所有连接的 USB 串行设备
ls /dev/ttyUSB*

# 在本文的环境中,执行上述命令应会显示如下设备列表:
# 注意:这些设备信息表明了各个 USB 设备的总线位置和设备 ID

Bus 004 Device 002: ID 2cb7:0a06 Fibocom Wireless Inc. FM650 Module
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

# 如果你的设备没有在列表中显示,需要进行故障排除:
# 确认 USB 设备是否已经正确连接到计算机上。
# 检查设备的模块是否已经正确插入到转接口中,以及转接口是否有任何物理损坏。

启动拨号服务(仅支持 supportlist 上的型号)

systemctl start lteModemManager

等待一段时间后,若成功启动,可使用 ifconfig -a 命令查看网卡获取的上网 IP。

验证是否连上网

# lteModemManager 服务启动后在 60s 左右的时间内会自动配置 DNS 为 8.8.8.8
systemd-resolved --status

# 打印如下
Link 8 (usb0)
    Current Scopes: DNS
DefaultRoute setting: yes
    LLMNR setting: yes
MulticastDNS setting: no
DNSOverTLS setting: no
    DNSSEC setting: no
DNSSEC supported: no
Current DNS Server: 8.8.8.8
    DNS Servers: 8.8.8.8

# 也可以手动进行设置,操作如下
# 修改 DNS 设置
vim /etc/systemd/resolved.conf

# 在文件中找到 [Resolve] 部分,并设置 DNS 为 Google 的公共 DNS
[Resolve]
DNS=8.8.8.8

# 保存并退出,然后重启 systemd-resolved 服务以应用更改
systemctl restart systemd-resolved

# 检查配置是否生效,尝试 ping 百度网站
ping baidu.com

备注

SIM 卡不支持热插拔,需要可联网的 SIM。

WiFi 和 Bluetooth 操作指南

WiFi/BT操作流程

确认 PCIE 是否枚举上

# 使用 lspci 命令列出所有 PCI 设备
lspci

# 如果系统正常,你将看到类似以下的输出:
# 其中包含了 PCI 设备的列表和相关信息

00:00.0 PCI bridge: Device 1f1c:186a (rev 01)
01:00.0 Network controller: Realtek Semiconductor Co., Ltd. Device b852

加载 WiFi 驱动

insmod /mnt/system/ko/3rd/8852be.ko

加载蓝牙驱动

insmod /mnt/system/ko/3rd/hci_uart.ko

备注

上述两个内核模块已存在,只需加载就行。

安装对应的 wpa 和 bluez 工具

dpkg -i wpa_xxx.deb bluez_xxx.deb

上述两个安装包可根据模块官网信息使用 apt install 进行安装。

配置 wpa_supplicant

为了使您的系统能够自动连接到无线网络,您需要在 /etc/wpa_supplicant/ 目录中创建一个名为 wpa_supplicant.conf 的配置文件。以下是一个配置示例,它会配置您的系统以自动连接到一个名为“123”,密码为“12345678”的无线网络:

# wpa_supplicant 全局控制接口设置
ctrl_interface=/var/run/wpa_supplicant

# 网络配置块
network={
    ssid="123"          # 无线网络的名称(SSID)
    psk="12345678"      # 无线网络的预共享密钥(密码)
}

启动 wpa 服务

systemctl start wpa_supplicant

启动 dhclient 服务,自动获取 IP

# 启动 dhclient 以自动获取 IP 地址
systemctl start dhclient

# 成功获取 IP 地址后,可以使用 ifconfig -a 命令查看网络接口信息
# 以下是在本环境中执行该命令后的输出示例:

wlp1s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
    inet 192.168.137.175  netmask 255.255.255.0  broadcast 192.168.137.255
    inet6 fe80::7a8a:86ff:fe51:59d6  prefixlen 64  scopeid 0x20<link>
    ether 78:8a:86:51:59:d6  txqueuelen 1000  (Ethernet)
    RX packets 450  bytes 85032 (85.0 KB)
    RX errors 0  dropped 0  overruns 0  frame 0
    TX packets 193  bytes 15220 (15.2 KB)
    TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

测试网络连通

# 手动配置 DNS
vim /etc/systemd/resolved.conf

# 在文件中找到 [Resolve] 部分,并设置 DNS 为 Google 的公共 DNS
[Resolve]
DNS=8.8.8.8

# 保存并退出,然后重启 systemd-resolved 服务以应用更改
systemctl restart systemd-resolved

# 检查配置是否生效,尝试 ping 百度网站
ping baidu.com

配置蓝牙(需确认蓝牙连接的 UART 引脚,并进行相应的 pinmux 切换)

# 配置 UART 引脚
cvi_pinmux -w UART4_RX/UART4_RX
cvi_pinmux -w UART4_TX/UART4_TX
cvi_pinmux -w UART4_CTS/UART4_CTS
cvi_pinmux -w UART4_RTS/UART4_RTS

# 启动蓝牙 HCI
rtk_hciattach -n -s 115200 ttyS4 rtk_h5 &

# 如果蓝牙初始化失败,需要断电重启
# 重置蓝牙
cvi_pinmux -w PAD_MIPI1_TX0P/GPIO185
echo 345 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio345/direction
echo 1 > /sys/class/gpio/gpio345/value
echo 0 > /sys/class/gpio/gpio345/value

查看蓝牙节点是否生成

# 检查蓝牙节点
hciconfig -a

# 预期输出示例
hci0:   Type: Primary  Bus: UART
    BD Address: 78:8A:86:51:59:D7  ACL MTU: 1021:5  SCO MTU: 255:11
    UP RUNNING PSCAN
    RX bytes:912735 acl:38 sco:0 events:4708 errors:0
    TX bytes:44343 acl:37 sco:0 commands:218 errors:0
    Features: 0xff 0xff 0xff 0xfa 0xdb 0xbf 0x7b 0x87
    Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3
    Link policy: RSWITCH HOLD SNIFF PARK
    Link mode: SLAVE ACCEPT

启动蓝牙

# 启动蓝牙服务
systemctl start bluetooth

使用 BlueZ 工具进行通信

# 进入蓝牙命令行界面
bluetoothctl

# 命令行显示
Agent registered
[CHG] Controller 78:8A:86:51:59:D7 Pairable: yes
[bluetooth]#

# 打开蓝牙电源
power on

# 打印如下
[bluetooth]# power on
Changing power on succeeded
[bluetooth]#

# 扫描周围的蓝牙设备
scan on

# 打印如下
[bluetooth]# scan on
Discovery started
[CHG] Controller 78:8A:86:51:59:D7 Discovering: yes
[NEW] Device 7B:4F:A0:69:9F:CB 7B-4F-A0-69-9F-CB
[NEW] Device 73:D7:AE:07:F8:79 73-D7-AE-07-F8-79
[NEW] Device 68:F3:5C:FD:59:25 68-F3-5C-FD-59-25
[NEW] Device 1C:3A:C6:A6:AC:B2 1C-3A-C6-A6-AC-B2
[NEW] Device 5A:27:AF:9D:F0:BE 5A-27-AF-9D-F0-BE

# 蓝牙配对
pair <XX:XX:XX:XX:XX:XX>

# 打印如下
pair F4:1A:9C:BA:30:E3
Attempting to pair with F4:1A:9C:BA:30:E3
[CHG] Device F4:1A:9C:BA:30:E3 Connected: yes
Request confirmation
[agent] Confirm passkey 442308 (yes/no): yes
[CHG] Device F4:1A:9C:BA:30:E3 Modalias: bluetooth:v038Fp1200d1436
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: 00001105-0000-1000-8000-00805f9b34fb
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: 0000110a-0000-1000-8000-00805f9b34fb
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: 0000110c-0000-1000-8000-00805f9b34fb
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: 00001112-0000-1000-8000-00805f9b34fb
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: 00001115-0000-1000-8000-00805f9b34fb
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: 00001116-0000-1000-8000-00805f9b34fb
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: 0000111f-0000-1000-8000-00805f9b34fb
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: 0000112f-0000-1000-8000-00805f9b34fb
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: 00001132-0000-1000-8000-00805f9b34fb
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: 00001200-0000-1000-8000-00805f9b34fb
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: 00001800-0000-1000-8000-00805f9b34fb
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: 00001801-0000-1000-8000-00805f9b34fb
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: 00001855-0000-1000-8000-00805f9b34fb
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: 0000fdaa-0000-1000-8000-00805f9b34fb
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: 98b97136-36a2-11ea-8467-484d7e99a198
[CHG] Device F4:1A:9C:BA:30:E3 UUIDs: ada499be-27d6-11ec-9427-0a80ff2603de
[CHG] Device F4:1A:9C:BA:30:E3 ServicesResolved: yes
[CHG] Device F4:1A:9C:BA:30:E3 Paired: yes
Pairing successful

WiFi AP操作流程

设置 5G 无线接入点 (AP)

为了设置 5G AP,您需要使用 US 的国家代码:

insmod /mnt/system/ko/3rd/8852be.ko rtw_country_code=US
sudo apt install iw
iw reg set US
ifconfig wlp1s0 192.168.1.10

安装 DHCP 服务器

对于大多数 Linux 发行版,您可以使用包管理器安装 isc-dhcp-server。例如,在 Debian/Ubuntu 上,您可以使用以下命令:

sudo apt update
sudo apt install isc-dhcp-server

配置 DHCP 服务器

您需要编辑 DHCP 服务器的配置文件,通常是 /etc/dhcp/dhcpd.conf,以定义 IP 地址范围和其他网络设置。以下是一个配置示例:

subnet 192.168.1.0 netmask 255.255.255.0 {
    range 192.168.1.11 192.168.1.254;
    option domain-name-servers 8.8.8.8, 8.8.4.4;
    option routers 192.168.1.1;
}

指定监听的接口

/etc/default/isc-dhcp-server 文件中,指定 DHCP 服务器应该监听的网络接口:

INTERFACESv4="wlp1s0"
INTERFACESv6=""

启动服务:

systemctl start isc-dhcp-server

设置无线接入点

您可以使用 hostapd 来设置无线接入点。首先,安装 hostapd:

sudo apt install hostapd

安装后最好禁用自启动,在配置 hostapd 之前,最好禁用其自动启动,以避免在配置文件未完全设置好时自动启动:

sudo systemctl unmask hostapd
sudo systemctl disable hostapd

然后,创建配置文件 /etc/hostapd/hostapd.conf,包含如下内容:

 interface=wlp1s0
 driver=nl80211

 # Control interface directory
 ctrl_interface=/var/run/hostapd
 # Control interface group
 ctrl_interface_group=0

 ssid=SE9
 wpa=2
 wpa_passphrase=12345678
 wpa_key_mgmt=WPA-PS

 country_code=US

#ieee80211d=1
#ieee80211n=1
ieee80211ac=1

hw_mode=a
channel=40

# 和上述一样开启channel bonding
ht_capab=[MAX-AMSDU-3839][HT40-][SHORT-GI-20][SHORT-GI-40][DSSS_CCK-40]
require_ht=1

# 来自官网的解释:
# vht_capab: VHT capabilities (list of flags)
# vht_max_mpdu_len: [MAX-MPDU-7991] [MAX-MPDU-11454]
# Indicates maximum MPDU length
#
# Short GI for 80 MHz: [SHORT-GI-80]
# Indicates short GI support for reception of packets transmitted with TXVECTOR
# params format equal to VHT and CBW = 80Mhz
#
# SU Beamformee Capable: [SU-BEAMFORMEE]
# Indicates support for operation as a single user beamformee
vht_capab=[MAX-MPDU-3895][SHORT-GI-80][SU-BEAMFORMEE]

# Require stations to support VHT PHY (reject association if they do not)
require_vht=1

# 0 = 20 or 40 MHz operating Channel width
# 1 = 80 MHz channel width
# 2 = 160 MHz channel width
# 3 = 80+80 MHz channel width
vht_oper_chwidth=0


#详情可以查看官网
beacon_int=100
dtim_period=2
max_num_sta=255
preamble=1
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wmm_enabled=1
eapol_key_index_workaround=0
eap_server=0
rsn_pairwise=CCMP