驱动开发(4)|鲁班猫rk356x镜像编译,及启用SPI控制器驱动
在进行驱动开发或内核定制时,发现修改SPI为SLAVE模式时,新添设备树,SPI从模式,一直不成功,后来才发现是Linux内核没有开这个功能,需要重新编译(内核/镜像),才能打开SLAVE模式,以下为记录如何配置以及编译(内核/镜像)步骤
1 SDK 开发环境搭建
LubanCat_Chip_SDK是基于Ubuntu LTS 系统开发测试的,在开发过程中,主要是用Ubuntu 20.04版本, 为了不必要的麻烦,推荐用户使用Ubuntu20.04及以上版本。
LubanCat-RK3588系列SDK不支持Ubuntu20.04以下版本环境进行镜像构建,最好使用Ubuntu20.04,我之前使用Ubuntu22.04,结果各种bug,换了系统才编译过
原文地址在这里:LubanCat_Chip_SDK
1.1 安装依赖
sudo apt install git ssh make gcc libssl-dev liblz4-tool u-boot-tools curl \
expect g++ patchelf chrpath gawk texinfo chrpath diffstat binfmt-support \
qemu-user-static live-build bison flex fakeroot cmake gcc-multilib g++-multilib \
unzip device-tree-compiler python3-pip libncurses5-dev python3-pyelftools dpkg-dev
1.2 安装repo
mkdir ~/bin
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
# 如果上面的地址无法访问,可以用下面的:
# curl -sSL 'https://gerrit-googlesource.proxy.ustclug.org/git-repo/+/master/repo?format=TEXT' |base64 -d > ~/bin/repo
chmod a+x ~/bin/repo
echo PATH=~/bin:$PATH >> ~/.bashrc
source ~/.bashrc# 验证是否安装成功
repo --version
2 下载SDK项目
点击进入链接:SDK 密码:hslu
2.1 SDK位置
2.2 解压并更新源码
# 安装tar压缩工具,一般来说系统默认安装了
sudo apt install tar# 在用户家目录创建LubanCat_SDK目录
mkdir ~/LubanCat_SDK# 将下载的SDK源码移动到LubanCat_SDK目录下,xxx为日期
mv LubanCat_Linux_rk356x_SDK_xxx.tgz ~/LubanCat_SDK# 进入LubanCat_SDK目录
cd ~/LubanCat_SDK# 解压SDK压缩包
tar -xzvf LubanCat_Linux_rk356x_SDK_xxx.tgz# 查看解压后的文件,可以看到解压出.repo文件夹
ls -al# 检出.repo目录下的git仓库
# 注意:下面的命令一点要在SDK顶层文件夹中执行,且repo路径一定为.repo/repo/repo
.repo/repo/repo sync -l# 将所有的源码仓库同步到最新版本
.repo/repo/repo sync -c
2.3 使用Git更新单独的源码仓库
# 进入kernel目录下
cd kernel
# 检出到当前提交所在的分支
git checkout stable-4.19-rk356x
# 拉取git仓库
git pull
这里需要注意,直接check是会报错的:
这里需要创建并切换到新的本地分支:
git ls-remote --heads origin
#2fabe9b4c153a2770c3e9d8ab2c6ce9329722767 refs/heads/lbc-develop-5.10
#73eff7a35b424657a529fadbae333f41bc7180f1 refs/heads/lbc-develop-6.1
#7a0d77c7eb8eaf3bf8b699abd3f60d3c406dc605 refs/heads/lbc-develop-6.1-rt36
#427cb5457cb2581d13ab3816f94be7f3035dcdf3 refs/heads/stable-4.19-rk356x
#01a0a0f477dc0fadb08be3dc144662eb8051c352 refs/heads/stable-4.19-rk356x-rt104
#b112b1eb0724e9175eceb97c331f999135e378ce refs/heads/stable-5.10-rk3588
#c6e6c8ab70b472baeafb38a77d1bae69da0f7dfb refs/heads/stable-5.10-rk3588-rt89git checkout -b stable-4.19-rk356x origin/stable-4.19-rk356x
# git checkout stable-4.19-rk356x
3 启用SPI控制器驱动
cd kernel
#使用图形界面配置需要额外安装 libncurses-dev
sudo apt install libncurses-dev
#执行命令
make menuconfig KCONFIG_CONFIG=arch/arm64/configs/lubancat2_defconfig ARCH=arm64
4 开始编译镜像
4.1 安装依赖
sudo dpkg -i debian/ubuntu-build-service/packages/*
sudo apt-get install -f
4.2 选择SDK配置文件
# 选择SDK配置文件
./build.sh lunch
4.3 构建内核deb包
构建内核deb包比编译镜像简单多了,而且只需要安装内核包就可以,无需重新刷机
# 官方命令
./build.sh kerneldeb
# 下面是调用的shell函数
function build_kerneldeb(){check_config RK_KERNEL_DTS RK_KERNEL_DEFCONFIG || return 0build_check_cross_compileecho "============Start building kernel deb============"echo "TARGET_ARCH =$RK_ARCH"echo "TARGET_KERNEL_CONFIG =$RK_KERNEL_DEFCONFIG"echo "TARGET_KERNEL_DTS =$RK_KERNEL_DTS"echo "TARGET_KERNEL_CONFIG_FRAGMENT =$RK_KERNEL_DEFCONFIG_FRAGMENT"echo "=========================================="pwdcd kernelmake ARCH=$RK_ARCH $RK_KERNEL_DEFCONFIG $RK_KERNEL_DEFCONFIG_FRAGMENTmake ARCH=$RK_ARCH bindeb-pkg RK_KERNEL_DTS=$RK_KERNEL_DTS -j$RK_JOBSfinish_build
}
如图所示,编译好之后会在LubanCat_SDK目录下:
官方的就没好用过,一直报错:No rule to make target 'debian/canonical-certs.pem
,所以我就自己手打了,如果没报错的兄弟们可以用以上命令,报错的还是老实自己打吧:
cd kernel
make ARCH=arm64 lubancat2_defconfig
make ARCH=arm64 bindeb-pkg RK_KERNEL_DTS=rk356x-lubancat-generic -j8
报错No rule to make target 'debian/canonical-certs.pem
,可以看我以前的文章:
驱动开发(1)|鲁班猫rk356x内核编译,及helloworld驱动程序编译
安装deb命令:
# 内核为4.19.232版本
sudo dpkg -i linux-headers-4.19.xxx_4.19.xxx-xxx_arm64.deb
sudo dpkg -i linux-image-4.19.xxx_4.19.xxx-xxx_arm64.deb# 内核为5.10.160版本
sudo dpkg -i linux-headers-5.10.xxx_5.10.xxx-xxx_arm64.deb
sudo dpkg -i linux-image-5.10.xxx_5.10.xxx-xxx_arm64.deb# 内核为5.10.198及以上版本
sudo dpkg -i linux-headers-5.10.xxx-芯片型号_5.10.xxx-芯片型号-xxx_arm64.deb
sudo dpkg -i linux-image-5.10.xxx-芯片型号_5.10.xxx-芯片型号-xxx_arm64.deb# 内核为6.1.xxx版本
sudo dpkg -i linux-headers-6.1.xxx-芯片型号_6.1.xxx-芯片型号-xxx_arm64.deb
sudo dpkg -i linux-image-6.1.xxx-芯片型号_6.1.xxx-芯片型号-xxx_arm64.deb
4.4 构建镜像
# 一键编译u-Boot,kernel,Rootfs并打包为update.img镜像
./build.sh
如果出现以下界面,都选3.3v:
如果报错,根据信息修改设备树
:
这里不写怎么修改设备树了,因为老子没报错,嘿嘿,如果以后编译报错,可以考虑补上。
4.5 查看是否打开
zcat /proc/config.gz | grep SPI_SLAVE
如图所示CONFIG_SPI_SLAVE=y表示已经打开。
5 设备树编译
官网教程:设备树编译
5.1 修改设备树
进入kernel/arch/arm64/boot/dts/rockchip找到rk3568-lubancat-2-v3.dts
(这是rk3568xxx,xxx为你板子的型号)
修改:
&spi3 {status = "okay";#address-cells = <1>;#size-cells = <0>;// 40PIN引脚只预留SPI3 CS0引脚,如果有多个CS信号,可以使用gpio模拟cs pinctrl-names = "default", "high_speed";pinctrl-0 = <&spi3m1_cs0 &spi3m1_pins>;pinctrl-1 = <&spi3m1_cs0 &spi3m1_pins_hs>;spi-slave; slave {compatible = "rockchip,spidev"; reg = <0>;spi-max-frequency = <10000000>;};
};
这里设备树pinctrl信息抄的/home/kevin/LubanCat_SDK/kernel/arch/arm64/boot/dts/rockchip/overlay/rk356x-lubancat-spi3-m1-overlay.dts
,所以uEnvLubanCat2-V3.txt
中的spi3就不用打开了,防止引脚重复报错。
具体为什么这样修改,可以去官网看:传送阵
5.2 开始编译
cd kernel
#加载配置文件
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- lubancat2_defconfig
#使用dtbs参数单独编译设备树
make ARCH=arm64 -j4 CROSS_COMPILE=aarch64-linux-gnu- dtbs
5.3 加载设备树
# 1 x86_64cp arch/arm64/boot/dts/rockchip/rk3568-lubancat-2-v3.dtb /home/kevin/LubanCat_SDK/
# 2 将设备拷贝到卡板上(/home/cat/目录下)
# 3 替换设备树
sudo mv /home/cat/rk3568-lubancat-2-v3.dtb /boot/dtb/
# 重启
sudo reboot
6 测试代码
6.1 slave端接收代码
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <linux/spi/spidev.h>
#include <string.h>
#define SPI_DEV_PATH "/dev/spidev3.0"/*SPI 接收 、发送 缓冲区*/
unsigned char rx_buffer[100] = {0};int fd; // SPI 控制引脚的设备文件描述符
static unsigned mode = SPI_MODE_0; //用于保存 SPI 工作模式
static uint8_t bits = 8; // 接收、发送数据位数
static uint32_t speed = 10000000; // 发送速度
static uint16_t delay; //保存延时时间void transfer(int fd, uint8_t *tx, uint8_t *rx, size_t len)
{int ret;struct spi_ioc_transfer tr = {.tx_buf = 0,.rx_buf = (unsigned long)rx,.len = len,.delay_usecs = delay,.speed_hz = speed,.bits_per_word = bits,};ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);if (ret < 1)printf("can't send spi message\n");
}void spi_init(int fd)
{int ret = 0;// 设置 SPI 工作模式(写入)ret = ioctl(fd, SPI_IOC_WR_MODE32, &mode);if (ret == -1)printf("can't set spi mode\n");// 设置数据位数(写入)ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);if (ret == -1)printf("can't set bits per word\n");// 设置SPI工作频率(写入)ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);if (ret == -1)printf("can't set max speed hz\n");// 验证设置unsigned read_mode;uint8_t read_bits;uint32_t read_speed;ioctl(fd, SPI_IOC_RD_MODE32, &read_mode);ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &read_bits);ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &read_speed);printf("spi mode: 0x%x (set: 0x%x)\n", read_mode, mode);printf("bits per word: %d (set: %d)\n", read_bits, bits);printf("max speed: %d Hz (set: %d Hz)\n", read_speed, speed);
}int main(int argc, char *argv[])
{/*打开 SPI 设备*/fd = open(SPI_DEV_PATH, O_RDWR); // open file and enable read and writeif (fd < 0){printf("Can't open %s \n",SPI_DEV_PATH); // open i2c dev file failexit(1);}/*初始化SPI */spi_init(fd);//for(int i=0;i<10000;i++)int recvCount =0; while(1){memset(rx_buffer, 0, sizeof(rx_buffer));printf("=============transfer1\n");transfer(fd, NULL, rx_buffer, sizeof(rx_buffer));printf("=============transfer2\n");for (int j = 0; j < 100; j++) {if(rx_buffer[j] == 0 || rx_buffer[j] == 255){printf("rx_buffer -- \r");fflush(stdout);//recvCount =0; }else{recvCount++;//printf("Received: %d\n", byte);printf("rx_buffer[%d]:%d , recvCount: %d\n", j,(int)rx_buffer[j],recvCount);}//}}close(fd);return 0;
}
这里作为slave如果没有收到信号的话,会一直阻塞在transfer(fd, NULL, rx_buffer, sizeof(rx_buffer));,直到接收到数据。
6.2 master端发送代码
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <linux/spi/spidev.h>#define SPI_DEV_PATH "/dev/spidev3.0"/*SPI 接收 、发送 缓冲区*/
unsigned char tx_buffer[100];int fd; // SPI 控制引脚的设备文件描述符
static unsigned mode = SPI_MODE_0; //用于保存 SPI 工作模式
static uint8_t bits = 8; // 接收、发送数据位数
static uint32_t speed = 10000000; // 发送速度
static uint16_t delay; //保存延时时间void transfer(int fd, uint8_t *tx, uint8_t *rx, size_t len)
{int ret;struct spi_ioc_transfer tr = {.tx_buf = (unsigned long)tx,.rx_buf = 0,.len = len,.delay_usecs = delay,.speed_hz = speed,.bits_per_word = bits,//.tx_nbits = 1,//.rx_nbits = 1};ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);if (ret < 1)printf("can't send spi message\n");
}void spi_init(int fd)
{int ret = 0;// 设置 SPI 工作模式(写入)ret = ioctl(fd, SPI_IOC_WR_MODE32, &mode);if (ret == -1)printf("can't set spi mode\n");// 设置数据位数(写入)ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);if (ret == -1)printf("can't set bits per word\n");// 设置SPI工作频率(写入)ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);if (ret == -1)printf("can't set max speed hz\n");// 验证设置unsigned read_mode;uint8_t read_bits;uint32_t read_speed;ioctl(fd, SPI_IOC_RD_MODE32, &read_mode);ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &read_bits);ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &read_speed);printf("spi mode: 0x%x (set: 0x%x)\n", read_mode, mode);printf("bits per word: %d (set: %d)\n", read_bits, bits);printf("max speed: %d Hz (set: %d Hz)\n", read_speed, speed);
}int main(int argc, char *argv[])
{int fd;/*打开 SPI 设备*/fd = open(SPI_DEV_PATH, O_RDWR); // open file and enable read and writeif (fd < 0){printf("Can't open %s \n",SPI_DEV_PATH); // open i2c dev file failexit(1);}/*初始化SPI */spi_init(fd);// 填充 tx_buffer 为 1 到 100for (int i = 0; i < 100; i++) {tx_buffer[i] = i + 1;}int sendCount = 0;//for(int i=0;i<100000;i++){//sendCount++;/*执行发送*/transfer(fd, tx_buffer, NULL,sizeof(tx_buffer));//printf("send count: %d \r", i);// for (int j = 0; j < 100; j++) // {// printf("tx_buffer %d \n", tx_buffer[j]);// // if(rx_buffer[j] == 0 || rx_buffer[j] == 255)// // {// // printf("rx_buffer -- \r");// // }// // else// // {// // printf("rx_buffer %d \r", rx_buffer[j]);// // }//fflush(stdout);// }}printf("send finished!!!!");sleep(5);close(fd);return 0;
}
总结
在进行驱动开发或内核定制时,若遇到将SPI控制器配置为SLAVE模式失败的情况,通常是由于Linux内核未启用相关功能支持所致。本文详细介绍了两种解决方案:一是通过编译内核deb包实现功能启用,适用于仅需更新内核的场景;二是完整编译系统镜像,适用于需要整体固件更新的情况。