OrangePi Kunpeng Pro体验——安装Hass与驱动SPI小屏幕

OrangePi Kunpeng Pro 是一款面向开发者和爱好者的高性能开发板。在本次测评中,主要将以前的一些代码在该开发板上实现,包括docker部署hass,引脚驱动SPI小屏幕。中间遇到了一些小小问题,但都成功了,一起来试试吧~ 

一、开箱

1. 开箱全貌

快递第三天收到了主办方寄来的OrangePi Kunpeng Pro套装(主板,8G,电源,散热组件,32GB存储卡),SD卡中已经安装了最新的openEuler系统,即插即用👍。

 

2. 主板观赏

以下说几个比较关注的,具体的主板说明可参考官方链接。

正面:CPU、内存、无线网卡和一些接口指示灯。

接口:PD电源输入、HDMI、多个USB3.0接口和一个千M网口。

 

3. 背面接口

清晰明了的三种不同存储接入接口:SD卡、SSD和EMMC,并可通过两个拨码开关选择启动方式。特别注意的是,虽然M2接口支持nvme和sata两种SSD硬盘,但是默认是nvme硬盘,如果接入的是sata硬盘,需要进行额外的操作(如图就是sata硬盘,额外操作在后续会介绍)。

 

二、基础入门

0. 说明

1) 账号密码均为:openEuler

 

1. 烧录系统到sata固态硬盘(nvme可跳过前三个步骤)

SD卡速度慢,手头有一个sata固态硬盘,正好用上。但是sata的固态硬盘,需要额外的修改才能够被开发板识别。

1) 系统烧录:使用balenaEtcher,将系统烧录到硬盘中(使用移动硬盘盒),随后插入到开发板M.2接口中(由于无螺丝,使用了胶带简单固定),但此时还无法读取到这个硬盘。

2) 暂不更改拨码开关,从SD卡进入系统,在此系统下更新SATA 驱动需要的的dt.img 文件。

首先进入/opt/opi_test/sata 文件夹:

cd /opt/opi_test/sata

然后运行下update.sh 脚本来更新SATA 对应的dt.img

sudo ./update.sh

然后重启,使用lsblk查看硬盘,可正常识别:

3) 将SD卡的dt.img配置,更新到sata硬盘中(需要根据情况修改sata硬盘的节点名称,如图为sda)

sudo dd if=/opt/opi_test/dt_img/dt_drm_sata.img of=/dev/sda count=4096 seek=114688 bs=512

4) 切换拨码开关,以SSD方式启动,顺利开机。使用df -h可查看当前系统空间。

 

2. 增加swap内存

开发板内存有8G,大部分应用已经完全足够,不够时还可以通过设置swap扩展系统内存。

1) 创建一个swap文件

sudo fallocate -l 16G /swapfile

2) 依次配置

sudo chmod 600 /swapfile # 权限为root用户可以读写 
sudo mkswap /swapfile 
sudo swapon /swapfile 
free -h # 查看内存结果

可以看到swap空间为15G(小数部分被直接忽略了),使用free -m可以看到更详细的数据。

3) 设置重启自动生效

将对应的配置添加到/etc/fstab 文件中。

echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

 

3. 配置无线网络

前面都用的是有线网口,接下来通过ssh配置其连接无线wifi。

1) 使用 nmcli 命令扫描附近的 Wi-Fi 网络:

nmcli dev wifi list

2) 使用 nmcli 命令连接到你的 Wi-Fi 网络。假设Wi-Fi SSID 是 SSIDWiFi,密码是 MyPassword,请进行修改:

sudo nmcli dev wifi connect SSIDWiFi password MyPassword

3) 验证连接状态:

nmcli dev status 
# 或者 ifconfig

 

4. 安装docker

方法1:直接使用 YUM 安装 Docker。简单,但可能安装的是系统软件仓库中提供的较老版本的 Docker。

sudo yum update -y
sudo yum install -y docker
sudo systemctl enable docker
sudo systemctl start docker

方法2:通过 YUM 源安装 Docker。首先添加了 Docker 的 YUM 源,然后使用 yum install 命令安装 Docker 软件包。确保了安装的是最新版本的 Docker,并且可以通过 YUM 包管理器进行更新。

1) 更新

sudo yum update -y

2) 添加 Docker YUM 源

需要添加如下源,如果后面update报错,可以删除该文件。

简单说明:Docker 官方提供了适用于 CentOS/RHEL 的 YUM 源,而 openEuler 在很大程度上与 CentOS/RHEL 兼容,因此使用这些源进行 Docker 的安装。

sudo nano /etc/yum.repos.d/docker-ce.repo

添加以下内容:

[docker-ce-stable]
name=Docker CE Stable - $basearch
baseurl=https://download.docker.com/linux/centos/7/$basearch/stable
enabled=1
gpgcheck=1
gpgkey=https://download.docker.com/linux/centos/gpg

3) 安装 Docker

sudo yum install -y docker-ce docker-ce-cli containerd.io

4) 启动 Docker 服务

sudo systemctl start docker                 # 启动服务
sudo systemctl enable docker                # 开机自启
sudo systemctl status docker                # 查看状态

5) 测试

sudo docker run hello-world

6) 其他一些指令

以下为使用docker常用的一些指令:

docker --version                     # 查看 Docker 版本
docker run hello-world               # 运行一个 Hello World 容器
docker ps -a                         # 列出所有容器
docker images                        # 列出所有镜像
docker stop CONTAINER_ID             # 停止一个运行中的容器
docker start CONTAINER_ID            # 启动一个停止的容器
docker rm CONTAINER_ID               # 移除一个容器
docker rmi IMAGE_ID                  # 移除一个镜像
docker logs CONTAINER_ID             # 查看容器日志
docker exec -it CONTAINER_ID /bin/bash # 进入一个运行中的容器
docker stats CONTAINER_ID            # 查看容器的资源使用情况
docker build -t my-image:latest .    # 构建一个 Docker 镜像
docker pull ubuntu:latest            # 拉取一个 Docker 镜像
docker push my-image:latest          # 推送一个 Docker 镜像到仓库
docker info                          # 显示 Docker 系统信息
docker network ls                    # 查看 Docker 网络配置
docker network create NETWORK_NAME   # 创建一个 Docker 网络
docker network connect NETWORK_NAME CONTAINER_ID # 连接容器到指定网络
docker network disconnect NETWORK_NAME CONTAINER_ID # 断开容器与网络的连接

 

三、功耗测量

虽然针对这个高性能开发板,低功耗是不太可能了,但是测量功耗可以明确对电源的需求。目前开发板搭配了最高3A的电源(仅考虑12V)。

1.说明

开发板使用了256G Sata固态硬盘作为系统盘,插入了网线和电源,不接显示器使用ssh登录。使用系统自带的风扇调节方案无修改。依次测量开机、CPU25%、50%和75%运行下、和关机的功耗。

 

2. 测量程序

写一个cpu_stress.py程序,占用一个核进行满负荷运行(25%CPU占用)。多开可占用更多的CPU资源。

#!/bin/bashecho "Starting CPU stress test..."while true; do# 执行一些无限循环的计算任务,例如计算圆周率echo "scale=5000; 4*a(1)" | bc -l >/dev/null
done

 

3. 结果

1) 开机:接入typeC供电后,开发板自动开机。首先开发板通过PD协议让电源输入电压升到12V,风扇启动,电流最大到1.1A,经过40s后稳定到660mA。

2) cpu运行测量:依次测量CPU占用25%、50%和75%时的功耗,由于其中1核被设置为了AI核,无法被该程序调用,因此最高占用只有75%。在25%、50%和75%占用时,电流分别为750mA、800mA和970mA

3) 关机:最后,通过指令poweroff,使开发板关机,测量功耗。此时电压仍保持在12V,电流为280mA,风扇不转。

 

四、Docker部署Hass

在安装docker后,依次安装homeassistant、数据库、mqtt服务器、esphome、nodered,并让他们互相链接,是指一个完善的、且可方便更新的智能家居管理系统,而且不影响其他服务的安装。

1. 配置docker

在上述安装好docker后,通过如下指令添加当前用户到Docker组:

sudo usermod -aG docker ${USER}
newgrp docker    # 重新登录,以应用用户组更改

 

 

2. 安装docker-compose

由于 openEuler 使用 ARM 架构,需要下载适用于 ARM 的 Docker Compose 二进制文件:

1) 下载 Docker Compose

sudo curl -L "https://github.com/docker/compose/releases/download/$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep 'tag_name' | cut -d'"' -f4)/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

2) 设置执行权限

sudo chmod +x /usr/local/bin/docker-compose

3) 验证安装

docker-compose --version

 

3. 准备hass文件夹

首先需要创建一个hass-all文件夹,用于放上述几个容器的文件,并配置文件夹权限。随后需要配置mqtt需要的文件,和nodered需要的文件夹。

1) 创建hass-all文件夹

cd
mkdir /home/openEuler/hass-all
sudo chmod -R 777 /home/openEuler/hass-all/    # 给文件夹权限

2)配置mosquitto.conf文件

cd /home/openEuler/hass-all/mosquitto/config
touch mosquitto.conf
chmod 777 mosquitto.conf
nano mosquitto.conf

3)配置nodered权限

cd /home/openEuler/hass-all
mkdir nodered
chmod 777 nodered/

 

 

4. 写入docker-compose.yml文件

在hass-all中写入docker-compose.yml,方便一键启动:

cd /home/openEuler/hass-all
nano docker-compose.yml

 

随后,写入如下内容,注意mariadb数据库中的password,可根据自己情况进行修改:

version: '3'
services:homeassistant:container_name: homeassistantimage: homeassistant/home-assistant:stablevolumes:- /home/openEuler/hass-all/homeassistant:/configenvironment:- TZ=Asia/Shanghainetwork_mode: hostrestart: unless-stoppeddepends_on:- mosquitto- mariadbmosquitto:container_name: mosquittoimage: eclipse-mosquitto:latestvolumes:- /home/openEuler/hass-all/mosquitto/config:/mosquitto/config- /home/openEuler/hass-all/mosquitto/data:/mosquitto/data- /home/openEuler/hass-all/mosquitto/log:/mosquitto/logrestart: alwaysports:- "1883:1883"- "9001:9001"mariadb:container_name: mariadbimage: mariadb:latestvolumes:- /home/openEuler/hass-all/mariadb:/var/lib/mysqlenvironment:- MYSQL_ROOT_PASSWORD=password- MYSQL_DATABASE=homeassistant- MYSQL_USER=root- MYSQL_PASSWORD=passwordrestart: alwaysports:- "3306:3306"nodered:container_name: noderedimage: nodered/node-red:latestvolumes:- /home/openEuler/hass-all/nodered:/datauser: "node-red"restart: alwaysports:- "1880:1880"esphome:container_name: esphomeimage: esphome/esphomevolumes:- /home/openEuler/hass-all/esphome:/confignetwork_mode: hostrestart: alwaysports:- "6052:6052"- "6123:6123"

 

 

5. 启动docker-compose

随后,通过指令启动上述docker:

docker-compose up       # 关闭窗口后就会停止上述docker,方便调试
# docker-compose up -d    # 后台运行

 

 

6. 进入homeassistant

通过ifconfig查看开发板IP地址,随后浏览器输入IP:8123端口,进行homeassistant配置,完成后如图,可以看到当地的天气啦!

 

7. 后续配置

1) 数据库配置:由于homeassistant的内置数据库效率低,在后面多设备情况下,可能会影响稳定性,因此使用mariadb作为其数据库。

修改/home/openEuler/hass-all/homeassistant/configuration.yaml文件,加入如下内容,其中的数据库password需要与上面对应:

recorder:db_url: mysql://root:password@127.0.0.1/homeassistant?charset=utf8

2) 界面添加:将上述的nodered和esphome添加到homeassistant界面中。

修改/home/openEuler/hass-all/homeassistant/configuration.yaml文件,加入如下内容,其中的IP需要对应修改为自己的:

panel_iframe:nodered:title: 'Node-Red'icon: 'mdi:shuffle-variant'#填写node-red的地址url: 'http://192.168.10.181:1880/'esphome:title: 'ESPHome'icon: 'mdi:car-esp'#填写node-red的地址url: 'http://192.168.10.181:6052/'

完成上述后,重启docker-compose,可以看到如下内容。

 

3)安装HACS

HACS可以帮助homeassistant扩展更多的界面和应用,如小米、天气卡片等。

参考官方:https://hacs.xyz/docs/setup/download/,使用container的教程

打开HA的bash,输入如下指令即可

wget -O - https://get.hacs.xyz | bash -

随后打开homeassistant中的高级模式。最后添加集成HACS,并进行相应配置,即可显示HACS内容。

4) 配置Node-Red

在Node-Red中添加节点node-red-contrib-home-assistant-websocket,并安装。

5)配置MQTT

在homeassistant中添加集成MQTT,并配置如下

 

五、使用SPI小屏幕

开发板和小电脑最大的区别是,开发板上有引出多功能引脚,可以方便连接外部设备和传感器。在这里,测试使用该开发板驱动SPI小屏幕。

1. 查看手册

  SPI小屏幕包括引脚:GND VCC SCL SDA RES DC CS BLK引脚,1.14寸st7789 TFT屏幕,定义如下。

  开发板的引脚如下,需要使用到SPI引脚和几个通用引脚。

 

2. 引脚控制测试

根据手册,进行引脚控制。

0)报错解决:测试过程中,发现gpio_operate -h报错,究其原因是sudo yum update导致库更新不兼容,解决办法是将对应库降级,即可解决。

# 降级
sudo yum downgrade glibc glibc-common
# 重新加载驱动
lsmod | grep gpio
dmesg | grep gpio
# 重启
sudo reboot

 

1)基础引脚测试

使用引脚2-20,读取其方向为输入,随后读取其value,发现为1。通过杜邦线连接2-20和GND,在此读取value,发现变成了1。

gpio_operate -h                    # 帮助help
gpio_operate get_direction 2 20    # 查看引脚方向,0表示输入,1表示输出
gpio_operate get_value 2 20        # 获取引脚值

2) SPI回环测试

回环测试是指将SPI的SDI和SDO连接,发出去的数据被自己接收,查看收发数据是否一致判断SPI工作是否正常。(回环测试也可用于UART中)

ls /dev/spidev0.0            # 查看SPI设备
# 控制SPI进行测试,依次测试不连接和连接O/I的情况
sudo spidev_test -v -D /dev/spidev0.0

3) python控制引脚

写一个read_gpio.py程序,将上述bash指令由python调用,读取2-20引脚并显示其引脚状态。

import subprocess
import timedef get_gpio_value(gpio_group, gpio_pin):result = subprocess.run(f'gpio_operate get_value {gpio_group} {gpio_pin}', shell=True, capture_output=True, text=True)return result.stdout.strip()def main():gpio_group = 2gpio_pin = 20while True:value = get_gpio_value(gpio_group, gpio_pin)print(f'GPIO {gpio_group}-{gpio_pin} Value: {value}')time.sleep(1)  # 每隔一秒读取一次if __name__ == '__main__':main()

4) cpp控制引脚

上述都是在gpio_operate基础上进行引脚操作,不是特别方便。利用最底层,通过直接操作 sys/class/gpio来控制GPIO。以下是一个示例,周期读取GPIO2_20引脚状态并显示。

注意:GPIO2_20 是指第 2 组的第 20 个引脚,编号规则通常是 (组号 * 32) + 引脚号,例如 2 * 32 + 20 = 84。因此这里的引脚设置为84。

创建名为 gpio_read.cpp 的文件:

#include <iostream>
#include <fstream>
#include <string>
#include <unistd.h>using namespace std;class GPIO {
public:GPIO(int pin) : pinNumber(pin) {exportGPIO();setDirection("in");}~GPIO() {unexportGPIO();}int getValue() {ifstream gpioValueFile("/sys/class/gpio/gpio" + to_string(pinNumber) + "/value");int value = -1;if (gpioValueFile.is_open()) {gpioValueFile >> value;gpioValueFile.close();} else {cerr << "Unable to get value for GPIO" << endl;}return value;}private:int pinNumber;void exportGPIO() {ofstream gpioExportFile("/sys/class/gpio/export");if (gpioExportFile.is_open()) {gpioExportFile << pinNumber;gpioExportFile.close();} else {cerr << "Unable to export GPIO" << endl;}usleep(100000); // 等待 GPIO 文件系统创建}void unexportGPIO() {ofstream gpioUnexportFile("/sys/class/gpio/unexport");if (gpioUnexportFile.is_open()) {gpioUnexportFile << pinNumber;gpioUnexportFile.close();} else {cerr << "Unable to unexport GPIO" << endl;}}void setDirection(const string& direction) {ofstream gpioDirectionFile("/sys/class/gpio/gpio" + to_string(pinNumber) + "/direction");if (gpioDirectionFile.is_open()) {gpioDirectionFile << direction;gpioDirectionFile.close();} else {cerr << "Unable to set direction for GPIO" << endl;}}
};int main() {int gpioPin = 84; // GPIO2_20 的编号GPIO gpio(gpioPin);while (true) {int value = gpio.getValue();cout << "GPIO " << gpioPin << " Value: " << value << endl;sleep(1); // 每秒读取一次}return 0;
}

编译和运行程序

g++ -o gpio_read gpio_read.cpp
sudo ./gpio_read

5) cpp控制SPI

使用 /dev/spidevX.Y 进行 SPI 通信,开发板为0.0的SPI,使用标准的 C++ 库和 ioctl 系统调用来控制 SPI 设备。

创建名为 spi_control.cpp 的文件:

#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include <cstring>using namespace std;class SPI {
public:SPI(const string& device, uint8_t mode, uint32_t speed) {fd = open(device.c_str(), O_RDWR);if (fd < 0) {perror("Failed to open SPI device");exit(1);}// 设置 SPI 模式if (ioctl(fd, SPI_IOC_WR_MODE, &mode) == -1) {perror("Failed to set SPI mode");exit(1);}// 设置 SPI 速度if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) == -1) {perror("Failed to set SPI speed");exit(1);}this->speed = speed;this->mode = mode;}~SPI() {close(fd);}void transfer(uint8_t* tx_buffer, uint8_t* rx_buffer, size_t length) {struct spi_ioc_transfer spi;memset(&spi, 0, sizeof(spi));spi.tx_buf = reinterpret_cast<unsigned long>(tx_buffer);spi.rx_buf = reinterpret_cast<unsigned long>(rx_buffer);spi.len = length;spi.speed_hz = speed;spi.bits_per_word = bits_per_word;if (ioctl(fd, SPI_IOC_MESSAGE(1), &spi) == -1) {perror("Failed to transfer SPI message");exit(1);}}private:int fd;uint32_t speed;uint8_t mode;uint8_t bits_per_word = 8;
};int main() {string device = "/dev/spidev0.0"; // 根据需要修改设备路径uint8_t mode = SPI_MODE_0;uint32_t speed = 500000; // 500kHzSPI spi(device, mode, speed);uint8_t tx_buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05};uint8_t rx_buffer[sizeof(tx_buffer)];while (true) {spi.transfer(tx_buffer, rx_buffer, sizeof(tx_buffer));cout << "Sent data: ";for (size_t i = 0; i < sizeof(tx_buffer); ++i) {cout << "0x" << hex << static_cast<int>(tx_buffer[i]) << " ";}cout << endl;cout << "Received data: ";for (size_t i = 0; i < sizeof(rx_buffer); ++i) {cout << "0x" << hex << static_cast<int>(rx_buffer[i]) << " ";}cout << endl;sleep(1); // 每秒进行一次通信}return 0;
}

编译和运行:

g++ -o spi_control spi_control.cpp
sudo ./spi_control

 

3. 程序封装

将上述的GPIO和SPI程序优化,并放置在工程文件夹下,方便后续调用。

1) 新建一个工程文件夹,名为TFT_SHOW,并包括文件夹include和src,后面创建的文件夹结构如下。

project_root/
├── include/
│   ├── GPIO.h
│   └── SPI.h
│   └── TFT.h
├── src/
│   ├── GPIO.cpp
│   ├── SPI.cpp
│   ├── TFT.cpp
│   └── main.cpp
├── CMakeLists.txt

2) 将上述GPIO优化,包括GPIO.h放在include中,GPIO.cpp放在src中。

#ifndef GPIO_H
#define GPIO_H#include <string>class GPIO {
public:enum Direction {IN,OUT};GPIO(int pin, Direction direction);~GPIO();void setDirection(Direction direction);void setValue(int value);int getValue();private:int pinNumber;void exportGPIO();void unexportGPIO();std::string directionToString(Direction direction);
};#endif // GPIO_H
#include "GPIO.h"
#include <fstream>
#include <iostream>
#include <unistd.h>using namespace std;GPIO::GPIO(int pin, Direction direction) : pinNumber(pin) {exportGPIO();setDirection(direction);
}GPIO::~GPIO() {unexportGPIO();
}void GPIO::setDirection(Direction direction) {ofstream gpioDirectionFile("/sys/class/gpio/gpio" + to_string(pinNumber) + "/direction");if (gpioDirectionFile.is_open()) {gpioDirectionFile << directionToString(direction);gpioDirectionFile.close();} else {cerr << "Unable to set direction for GPIO" << endl;}
}void GPIO::setValue(int value) {ofstream gpioValueFile("/sys/class/gpio/gpio" + to_string(pinNumber) + "/value");if (gpioValueFile.is_open()) {gpioValueFile << value;gpioValueFile.close();} else {cerr << "Unable to set value for GPIO" << endl;}
}int GPIO::getValue() {ifstream gpioValueFile("/sys/class/gpio/gpio" + to_string(pinNumber) + "/value");int value = -1;if (gpioValueFile.is_open()) {gpioValueFile >> value;gpioValueFile.close();} else {cerr << "Unable to get value for GPIO" << endl;}return value;
}void GPIO::exportGPIO() {ofstream gpioExportFile("/sys/class/gpio/export");if (gpioExportFile.is_open()) {gpioExportFile << pinNumber;gpioExportFile.close();} else {cerr << "Unable to export GPIO" << endl;}usleep(100000); // 等待 GPIO 文件系统创建
}void GPIO::unexportGPIO() {ofstream gpioUnexportFile("/sys/class/gpio/unexport");if (gpioUnexportFile.is_open()) {gpioUnexportFile << pinNumber;gpioUnexportFile.close();} else {cerr << "Unable to unexport GPIO" << endl;}
}string GPIO::directionToString(Direction direction) {return (direction == IN) ? "in" : "out";
}

3) 将上述SPI优化,包括SPI.h放在include中, SPI.cpp放在scr中。

#ifndef SPI_H
#define SPI_H#include <string>
#include <cstdint>
#include <cstddef>class SPI {
public:SPI(const std::string& device, uint8_t mode, uint32_t speed);~SPI();void transfer(uint8_t* tx_buffer, uint8_t* rx_buffer, size_t length);private:int fd;uint32_t speed;uint8_t mode;uint8_t bits_per_word = 8;
};#endif // SPI_H
#include "SPI.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include <cstring>
#include <iostream>using namespace std;SPI::SPI(const std::string& device, uint8_t mode, uint32_t speed) {fd = open(device.c_str(), O_RDWR);if (fd < 0) {perror("Failed to open SPI device");exit(1);}// 设置 SPI 模式if (ioctl(fd, SPI_IOC_WR_MODE, &mode) == -1) {perror("Failed to set SPI mode");exit(1);}// 设置 SPI 速度if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) == -1) {perror("Failed to set SPI speed");exit(1);}this->speed = speed;this->mode = mode;
}SPI::~SPI() {close(fd);
}void SPI::transfer(uint8_t* tx_buffer, uint8_t* rx_buffer, size_t length) {struct spi_ioc_transfer spi;memset(&spi, 0, sizeof(spi));spi.tx_buf = reinterpret_cast<unsigned long>(tx_buffer);spi.rx_buf = reinterpret_cast<unsigned long>(rx_buffer);spi.len = length;spi.speed_hz = speed;spi.bits_per_word = bits_per_word;if (ioctl(fd, SPI_IOC_MESSAGE(1), &spi) == -1) {perror("Failed to transfer SPI message");exit(1);}
}

4)CMakeLists.txt

cmake_minimum_required(VERSION 3.10)# 设置项目名称和版本
project(GPIOSPIControl VERSION 1.0)# 指定 C++ 标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)# 包含头文件目录
include_directories(${PROJECT_SOURCE_DIR}/include)# 查找所有源文件
file(GLOB SOURCES ${PROJECT_SOURCE_DIR}/src/*.cpp)# 添加可执行文件
add_executable(main ${SOURCES})# 链接必要的库
target_link_libraries(main pthread)

5)简单测试

在src中写一个main.cpp,内容如下,进行测试:

#include "GPIO.h"
#include "SPI.h"
#include <iostream>
#include <unistd.h>using namespace std;
#define SPI_MODE_0 0int main() {// GPIO 示例GPIO gpio(84, GPIO::IN); // 例如 GPIO2_20 对应的编号为 84while (true) {int value = gpio.getValue();cout << "GPIO Value: " << value << endl;sleep(1); // 每秒读取一次}// SPI 示例/*string device = "/dev/spidev0.0"; // 根据需要修改设备路径uint8_t mode = SPI_MODE_0;uint32_t speed = 500000; // 500kHzSPI spi(device, mode, speed);uint8_t tx_buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05};uint8_t rx_buffer[sizeof(tx_buffer)];while (true) {spi.transfer(tx_buffer, rx_buffer, sizeof(tx_buffer));cout << "Sent data: ";for (size_t i = 0; i < sizeof(tx_buffer); ++i) {cout << "0x" << hex << static_cast<int>(tx_buffer[i]) << " ";}cout << endl;cout << "Received data: ";for (size_t i = 0; i < sizeof(rx_buffer); ++i) {cout << "0x" << hex << static_cast<int>(rx_buffer[i]) << " ";}cout << endl;sleep(1); // 每秒进行一次通信}*/return 0;
}

编译和运行:

# 进入文件夹
cd /home/openEuler/TFT_SHOW/
# 创建build文件并编译
mkdir build
cd build
cmake ..
# 编译
make
# 运行
sudo ./main

 

4. 引脚连接

TFT显示屏引脚 <----------> OrangePi Kunpeng Pro引脚

GND <----------> GND

VCC <----------> 3.3V

SCL <----------> GPIO2_25(SPI0_SDO)

SDA <----------> GPIO2_27(SPI0_SCLK)

RES <----------> GPIO2_20(84)

DC <----------> GPIO4_00(128)

CS <----------> GPIO2_26(SPI0_CS)

BLK <----------> GPIO0_03(3)

 

5. TFT驱动程序撰写

include中添加一个tft.h,包括如下内容,根据开源的eSPI_TFT进行修改适配

#ifndef TFT_H
#define TFT_H#include <iostream>
#include <string>
#include "GPIO.h"
#include "SPI.h"using namespace std;// 定义相关宏和命令
#define TFT_INIT_DELAY 0x80
#define TFT_NOP 0x00
#define TFT_SWRST 0x01
#define TFT_SLPIN 0x10
#define TFT_SLPOUT 0x11
#define TFT_NORON 0x13
#define TFT_INVOFF 0x20
#define TFT_INVON 0x21
#define TFT_DISPOFF 0x28
#define TFT_DISPON 0x29
#define TFT_CASET 0x2A
#define TFT_PASET 0x2B
#define TFT_RAMWR 0x2C
#define TFT_RAMRD 0x2E
#define TFT_MADCTL 0x36
#define TFT_COLMOD 0x3A// 其他宏定义
#define TFT_MAD_MY 0x80
#define TFT_MAD_MX 0x40
#define TFT_MAD_MV 0x20
#define TFT_MAD_ML 0x10
#define TFT_MAD_RGB 0x00
#define TFT_MAD_BGR 0x08
#define TFT_MAD_MH 0x04
#define TFT_MAD_SS 0x02
#define TFT_MAD_GS 0x01#ifdef TFT_RGB_ORDER
#if (TFT_RGB_ORDER == 1)
#define TFT_MAD_COLOR_ORDER TFT_MAD_RGB
#else
#define TFT_MAD_COLOR_ORDER TFT_MAD_BGR
#endif
#else
#ifdef CGRAM_OFFSET
#define TFT_MAD_COLOR_ORDER TFT_MAD_BGR
#else
#define TFT_MAD_COLOR_ORDER TFT_MAD_RGB
#endif
#endif#define TFT_IDXRD 0x00
#define ST_CMD_DELAY 0x80
#define ST7789_240x240_XSTART 0
#define ST7789_240x240_YSTART 0// ST7789 特定命令
#define ST7789_NOP 0x00
#define ST7789_SWRESET 0x01
#define ST7789_RDDID 0x04
#define ST7789_RDDST 0x09
#define ST7789_RDDPM 0x0A
#define ST7789_RDD_MADCTL 0x0B
#define ST7789_RDD_COLMOD 0x0C
#define ST7789_RDDIM 0x0D
#define ST7789_RDDSM 0x0E
#define ST7789_RDDSR 0x0F
#define ST7789_SLPIN 0x10
#define ST7789_SLPOUT 0x11
#define ST7789_PTLON 0x12
#define ST7789_NORON 0x13
#define ST7789_INVOFF 0x20
#define ST7789_INVON 0x21
#define ST7789_GAMSET 0x26
#define ST7789_DISPOFF 0x28
#define ST7789_DISPON 0x29
#define ST7789_CASET 0x2A
#define ST7789_RASET 0x2B
#define ST7789_RAMWR 0x2C
#define ST7789_RGBSET 0x2D
#define ST7789_RAMRD 0x2E
#define ST7789_PTLAR 0x30
#define ST7789_VSCRDEF 0x33
#define ST7789_TEOFF 0x34
#define ST7789_TEON 0x35
#define ST7789_MADCTL 0x36
#define ST7789_IDMOFF 0x38
#define ST7789_IDMON 0x39
#define ST7789_RAMWRC 0x3C
#define ST7789_RAMRDC 0x3E
#define ST7789_COLMOD 0x3A// 其他定义
#define TFT_BGR 0
#define TFT_RGB 1
#define ST7789_2_DRIVER
#define TFT_RGB_ORDER TFT_RGB
#define TFT_WIDTH 240
#define TFT_HEIGHT 135// 定义引脚
#define TFT_DC 128
#define TFT_RST 84
#define TFT_BL 3class TFT_ST7789
{
public:uint colstart = 40;uint rowstart = 53;TFT_ST7789(SPI &spi, GPIO &dc, GPIO &rst, GPIO &bl);int tft_init(uint8_t r = 0, uint8_t g = 0, uint8_t b = 0);void tft_deinit();void tft_transRotation();void tft_invertDisplay(bool i);uint16_t color565(uint8_t r, uint8_t g, uint8_t b);void fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color);void fillScreen(uint32_t color);void pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data);void tft_drawrgb(uint8_t *rgb, uint32_t len);void tft_drawrgb(uint8_t *r, uint8_t *g, uint8_t *b, uint32_t len);
#ifdef TFT_OPENCVvoid tft_drawrgb(Vec3b *rgb, uint32_t len);void tft_drawjpg(string path, uint16_t *dat);void tft_drawjpg(Mat &img, uint16_t *dat);void tft_drawjpg(string path);void tft_drawjpg(Mat &img);
#endifvoid tft_drawbgr(uint8_t *bgr, uint32_t len);void tft_drawbgr(uint8_t *r, uint8_t *g, uint8_t *b, uint32_t len);
#ifdef TFT_OPENCVvoid tft_drawbgr(Vec3b *bgr, uint32_t len);
#endifprivate:void pin_init();void st7789_init();void tft_commandList(const uint8_t *addr);uint8_t spi_read_write(uint8_t send_data);void spi_writenb(const char *tbuf, uint32_t len);void tft_Write_8(uint8_t dat);void tft_Write_16(uint16_t C);void tft_Write_16(const uint16_t *C, uint32_t len);void tft_Write_16(uint16_t C, uint32_t len);void tft_Write_32(uint32_t C);void tft_Write_32C(uint16_t C, uint16_t D);void tft_Write_32D(uint32_t C);void tft_writecmd(uint8_t c);void tft_writedat(uint8_t d);void pushPixels(const void *data_in, uint32_t len);void setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1);void pushBlock(uint16_t color, uint32_t len);SPI &spi;GPIO &dc;GPIO &rst;GPIO &bl;
};#endif // TFT_H
在src中添加一个tft.cpp文件
#include "TFT.h"
#include <unistd.h>
#include <iostream>using namespace std;TFT_ST7789::TFT_ST7789(SPI &spi, GPIO &dc, GPIO &rst, GPIO &bl) : spi(spi), dc(dc), rst(rst), bl(bl)
{pin_init();
}void TFT_ST7789::pin_init()
{rst.setValue(1);dc.setValue(1);bl.setValue(1);
}int TFT_ST7789::tft_init(uint8_t r, uint8_t g, uint8_t b)
{pin_init();rst.setValue(1);usleep(5000);rst.setValue(0);usleep(20000);rst.setValue(1);usleep(150000);st7789_init();tft_transRotation();fillScreen(color565(r, g, b));return 0;
}void TFT_ST7789::st7789_init()
{static const uint8_t st7789[] = {8,TFT_SLPOUT, TFT_INIT_DELAY, 255,TFT_COLMOD, 1 + TFT_INIT_DELAY, 0x55, 10,TFT_MADCTL, 1, 0x00,TFT_CASET, 4, 0x00, 0x00, 0x00, 0xF0,TFT_PASET, 4, 0x00, 0x00, 0x00, 0xF0,TFT_INVON, TFT_INIT_DELAY, 10,TFT_NORON, TFT_INIT_DELAY, 10,TFT_DISPON, TFT_INIT_DELAY, 255};tft_commandList(st7789);
}void TFT_ST7789::tft_commandList(const uint8_t *addr)
{uint8_t numCommands = *(addr++);uint8_t numArgs;uint8_t ms;while (numCommands--){tft_writecmd(*(addr++));numArgs = *(addr++);ms = numArgs & TFT_INIT_DELAY;numArgs &= ~TFT_INIT_DELAY;while (numArgs--){tft_writedat(*(addr++));}if (ms){ms = *(addr++);usleep((ms == 255 ? 500 : ms) * 1000);}}
}void TFT_ST7789::tft_transRotation()
{tft_writecmd(TFT_MADCTL);tft_writedat(TFT_MAD_MX | TFT_MAD_MV | TFT_MAD_COLOR_ORDER);
}void TFT_ST7789::tft_invertDisplay(bool i)
{tft_writecmd(i ? TFT_INVON : TFT_INVOFF);tft_writecmd(i ? TFT_INVON : TFT_INVOFF);
}void TFT_ST7789::tft_deinit()
{// 反初始化过程
}uint16_t TFT_ST7789::color565(uint8_t r, uint8_t g, uint8_t b)
{return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}void TFT_ST7789::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color)
{setWindow(x, y, x + w - 1, y + h - 1);pushBlock(color, w * h);
}void TFT_ST7789::fillScreen(uint32_t color)
{fillRect(0, 0, TFT_WIDTH, TFT_HEIGHT, color);
}void TFT_ST7789::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data)
{setWindow(x, y, x + w - 1, y + h - 1);pushPixels(data, w * h);
}void TFT_ST7789::pushPixels(const void *data_in, uint32_t len)
{uint16_t *data = (uint16_t *)data_in;tft_writecmd(TFT_RAMWR);for (uint32_t i = 0; i < len; i++){tft_Write_16(data[i]);}
}void TFT_ST7789::pushBlock(uint16_t color, uint32_t len)
{tft_writecmd(TFT_RAMWR);for (uint32_t i = 0; i < len; i++){tft_Write_16(color);}
}void TFT_ST7789::tft_drawrgb(uint8_t *rgb, uint32_t len)
{if (TFT_WIDTH * TFT_HEIGHT != len)return;uint16_t dat[len];for (uint32_t i = 0; i < len; i++){dat[i] = color565(rgb[i * 3 + 0], rgb[i * 3 + 1], rgb[i * 3 + 2]);}pushImage(0, 0, TFT_WIDTH, TFT_HEIGHT, dat);
}void TFT_ST7789::tft_drawrgb(uint8_t *r, uint8_t *g, uint8_t *b, uint32_t len)
{if (TFT_WIDTH * TFT_HEIGHT != len)return;uint16_t dat[len];for (uint32_t i = 0; i < len; i++){dat[i] = color565(r[i], g[i], b[i]);}pushImage(0, 0, TFT_WIDTH, TFT_HEIGHT, dat);
}#ifdef TFT_OPENCV
void TFT_ST7789::tft_drawrgb(Vec3b *rgb, uint32_t len)
{if (TFT_WIDTH * TFT_HEIGHT != len)return;uint16_t dat[len];for (uint32_t i = 0; i < len; i++){dat[i] = color565(rgb[i][0], rgb[i][1], rgb[i][2]);}pushImage(0, 0, TFT_WIDTH, TFT_HEIGHT, dat);
}void TFT_ST7789::tft_drawjpg(string path, uint16_t *dat)
{Mat img = imread(path);Mat imgResize;resize(img, imgResize, Size(TFT_WIDTH, TFT_HEIGHT));Scalar color;for (int i = 0; i < TFT_HEIGHT; i++){for (int j = 0; j < TFT_WIDTH; j++){color = imgResize.at<Vec3b>(i, j);dat[i * TFT_WIDTH + j] = color565(color[2], color[1], color[0]);}}pushImage(0, 0, TFT_WIDTH, TFT_HEIGHT, dat);
}void TFT_ST7789::tft_drawjpg(Mat &img, uint16_t *dat)
{Mat imgResize;resize(img, imgResize, Size(TFT_WIDTH, TFT_HEIGHT));Scalar color;for (int i = 0; i < TFT_HEIGHT; i++){for (int j = 0; j < TFT_WIDTH; j++){color = imgResize.at<Vec3b>(i, j);dat[i * TFT_WIDTH + j] = color565(color[2], color[1], color[0]);}}pushImage(0, 0, TFT_WIDTH, TFT_HEIGHT, dat);
}void TFT_ST7789::tft_drawjpg(string path)
{uint16_t dat[TFT_WIDTH * TFT_HEIGHT];tft_drawjpg(path, dat);pushImage(0, 0, TFT_WIDTH, TFT_HEIGHT, dat);
}void TFT_ST7789::tft_drawjpg(Mat &img)
{uint16_t dat[TFT_WIDTH * TFT_HEIGHT];tft_drawjpg(img, dat);pushImage(0, 0, TFT_WIDTH, TFT_HEIGHT, dat);
}
#endifvoid TFT_ST7789::tft_drawbgr(uint8_t *bgr, uint32_t len)
{if (TFT_WIDTH * TFT_HEIGHT != len)return;uint16_t dat[len];for (uint32_t i = 0; i < len; i++){dat[i] = color565(bgr[i * 3 + 2], bgr[i * 3 + 1], bgr[i * 3 + 0]);}pushImage(0, 0, TFT_WIDTH, TFT_HEIGHT, dat);
}void TFT_ST7789::tft_drawbgr(uint8_t *b, uint8_t *g, uint8_t *r, uint32_t len)
{if (TFT_WIDTH * TFT_HEIGHT != len)return;uint16_t dat[len];for (uint32_t i = 0; i < len; i++){dat[i] = color565(r[i], g[i], b[i]);}pushImage(0, 0, TFT_WIDTH, TFT_HEIGHT, dat);
}#ifdef TFT_OPENCV
void TFT_ST7789::tft_drawbgr(Vec3b *bgr, uint32_t len)
{if (TFT_WIDTH * TFT_HEIGHT != len)return;uint16_t dat[len];for (uint32_t i = 0; i < len; i++){dat[i] = color565(bgr[i][2], bgr[i][1], bgr[i][0]);}pushImage(0, 0, TFT_WIDTH, TFT_HEIGHT, dat);
}
#endifvoid TFT_ST7789::tft_writecmd(uint8_t c)
{dc.setValue(0); // Command modespi.transfer(&c, nullptr, 1);dc.setValue(1); // Data mode
}void TFT_ST7789::tft_writedat(uint8_t d)
{dc.setValue(1); // Data modespi.transfer(&d, nullptr, 1);
}void TFT_ST7789::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1)
{int32_t addr_row = 0xFFFF;int32_t addr_col = 0xFFFF;x0 += colstart;x1 += colstart;y0 += rowstart;y1 += rowstart;tft_writecmd(TFT_CASET);tft_Write_32C(x0, x1);tft_writecmd(TFT_PASET);tft_Write_32C(y0, y1);tft_writecmd(TFT_RAMWR);
}void TFT_ST7789::tft_Write_8(uint8_t dat)
{spi.transfer(&dat, nullptr, 1);
}void TFT_ST7789::tft_Write_16(uint16_t C)
{uint8_t data[2] = {static_cast<uint8_t>(C >> 8), static_cast<uint8_t>(C & 0xFF)};spi.transfer(data, nullptr, 2);
}void TFT_ST7789::tft_Write_16(const uint16_t *C, uint32_t len)
{for (uint32_t i = 0; i < len; i++){tft_Write_16(C[i]);}
}void TFT_ST7789::tft_Write_16(uint16_t C, uint32_t len)
{for (uint32_t i = 0; i < len; i++){tft_Write_16(C);}
}void TFT_ST7789::tft_Write_32(uint32_t C)
{uint8_t data[4] = {static_cast<uint8_t>(C >> 24),static_cast<uint8_t>(C >> 16),static_cast<uint8_t>(C >> 8),static_cast<uint8_t>(C & 0xFF)};spi.transfer(data, nullptr, 4);
}void TFT_ST7789::tft_Write_32C(uint16_t C, uint16_t D)
{uint8_t data[4] = {static_cast<uint8_t>(C >> 8),static_cast<uint8_t>(C & 0xFF),static_cast<uint8_t>(D >> 8),static_cast<uint8_t>(D & 0xFF)};spi.transfer(data, nullptr, 4);
}void TFT_ST7789::tft_Write_32D(uint32_t C)
{uint8_t data[4] = {static_cast<uint8_t>(C >> 24),static_cast<uint8_t>(C >> 16),static_cast<uint8_t>(C >> 8),static_cast<uint8_t>(C & 0xFF)};spi.transfer(data, nullptr, 4);
}

 

6. 测试程序

在src中添加一个main.cpp文件,添加如下代码:

#include "TFT.h"
#include "GPIO.h"
#include "SPI.h"
#include <iostream>
#include <unistd.h>using namespace std;#define SPI_MODE_0 0int main() {// 初始化 GPIO 和 SPIGPIO dc(128, GPIO::OUT);GPIO rst(84, GPIO::OUT);GPIO bl(3, GPIO::OUT);SPI spi("/dev/spidev0.0", SPI_MODE_0, 25000000);    // 设置最高25M// 初始化 TFT 显示屏TFT_ST7789 tft(spi, dc, rst, bl);if (tft.tft_init() != 0) {cerr << "Failed to initialize TFT" << endl;return -1;}cout << "TFT initialized successfully" << endl;// 基本测试:填充屏幕颜色cout << "Filling screen with red color" << endl;tft.fillScreen(tft.color565(255, 0, 0)); // 红色sleep(2);cout << "Filling screen with green color" << endl;tft.fillScreen(tft.color565(0, 255, 0)); // 绿色sleep(2);cout << "Filling screen with blue color" << endl;tft.fillScreen(tft.color565(0, 0, 255)); // 蓝色sleep(2);// 刷新屏幕tft.tft_deinit();cout << "TFT test completed" << endl;return 0;
}

 

7. 运行效果

运行程序后,屏幕依次刷新为黑色、红色、绿色、蓝色,最后停在蓝色。测试过程中发现刷新的速度特别慢,即使将SPI的速率改为最高25M,也难以满足正常刷新要求。应该是SPI驱动库没有选对,后续有机会再进行优化。

 

六、评测观点

1. 可以夸夸的地方

性能强悍:相比于之前使用的树莓派4B和4B,桌面流畅度和网页浏览提升很多。

接口丰富:开发板没有为了卡片式而过分缩减接口,有两个标准的HDMI接口、背面完整长度的M2接口和EMMC接口、typeC形式的USB3接口。

主动散热:性能强悍带来的是功耗爆炸,主动可调散热让性能释放更加自由。

 

2. 值得改进的地方

系统资源目前官方安装的系统为openEuler,还未有更多专用适配的系统例如Ubuntu、安卓等。

官方案例:手册很完善,但是官方示例较少,用户难以快速体验到开发板的优势。

引脚驱动:手册中提供了gpio_operate方式进行gpio操作,但是针对python、C、C++等语言的支持库不完善。

 

3. 最后说说

非常荣幸能够获得测评OrangePi Kunpeng Pro的机会,在测试过程中也尽可能将以往的一些小代码应用在这块优秀的开发板中,最后也都成功实现了,实属不易。相信随着加入OrangePi Kunpeng Pro的开发者增多,官方支持的加大,OrangePi Kunpeng Pro将会越来越好,毕竟,性能高的底子还是有的。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/18924.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

删除中间节点

题目链接 删除中间节点 题目描述 注意点 node既不是链表头节点&#xff0c;也不是链表尾节点 解答思路 将当前节点的值替换为下一个节点的值&#xff0c;并将当前节点的next指针设置为下一个节点的next指针&#xff0c;可以理解为删除了当前节点 代码 /*** Definition f…

考研计组chap1计算机系统概述

目录 一、计算机发展历程(不考了) 二、计算机硬件的基本组成 3 1.五个部分 &#xff08;1&#xff09;输入设备 &#xff08;2&#xff09;控制器 &#xff08;3&#xff09;运算器 &#xff08;4&#xff09;&#xff08;主&#xff09;存储器 &#xff08;5&#xff0…

被忽视的模块化领域:聚合、结算与执行层

原文标题&#xff1a;《Aggregation, settlement, execution》撰文&#xff1a;Bridget Harris 编译&#xff1a;Chris&#xff0c;Techub News 在关注度和创新方面&#xff0c;模块化堆栈的各个部分并不一样&#xff0c;虽然之前有许多项目在数据可用性&#xff08;DA&#xf…

[AI OpenAI] OpenAI董事会成立安全与保障委员会

这个新委员会负责就所有OpenAI项目的关键安全和保障决策提出建议&#xff1b;在90天内提出建议。 今天&#xff0c;OpenAI董事会成立了一个由主席Bret Taylor、Adam D’Angelo、Nicole Seligman和Sam Altman&#xff08;CEO&#xff09;领导的安全与保障委员会。该委员会将负责…

虹科Pico汽车示波器 | 免拆诊断案例 | 2017款吉利帝豪GL车发动机偶尔无法起动

故障现象  一辆2017款吉利帝豪GL车&#xff0c;搭载JLC-4G18发动机和手动变速器&#xff0c;累计行驶里程约为39.3万km。车主反映&#xff0c;该车发动机偶尔无法起动。故障发生频率比较频繁&#xff0c;冷机状态下故障比较容易出现。 故障诊断  接车后试车&#xff0c;故…

【Windows】本地磁盘挂载 Minio 桶

目录 1.软件安装安装winfsp支持安装rclone 2.新建rclone远程存储类型S3服务类型验证方式地区终端地址ACL服务端加密KMS 3.挂载存储盘 1.软件安装 安装winfsp支持 下载地址 或 下载地址2 文件为msi文件&#xff0c;下载后双击直接安装即可&#xff0c;可以选择安装路径 安装r…

手机号码携号转网查询保障用户权益、信息透明、优化用户体验

携号转网服务是指在同一本地网范围内&#xff0c;蜂窝移动通信用户&#xff08;不含物联网用户&#xff09;变更签约的基础电信业务经营者而用户号码保持不变的一项服务。近年来&#xff0c;随着通信行业的不断发展&#xff0c;携号转网服务已成为满足用户个性化需求、提升服务…

Strust2 远程代码执行漏洞[s2-005]

漏洞复现环境搭建请参考 http://t.csdnimg.cn/rZ34p kali切换jdk版本请参考 Kali安装JAVA8和切换JDK版本的详细过程_kali安装jdk8-CSDN博客 漏洞原理 Strust2会将http的每个参数名解析成为OGNL语句执行&#xff0c;OGNL表达式通过#来访问Struts的对象&#xff0c;并且通过过…

JS裁剪图片底部的水印

效果 源码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Image Popup</title><style>…

达梦 结果拼接=多行结果返回一列字符串.

sql 转换 查询出多行数据 (select t.PROPERTY from JD_CODING t left join DELIVERY_OF c on t.VALUE c.TYPE where t.PROPERTY stackingType group by t.PROPERTY) 更改后 转为一列的拼接字符串 ( select listagg( distinct t.PROPERTY,,) within group ( order by t.P…

MiniPCIe/SATA双用插槽无法识别minipcie模块怎么回事!

在计算机和嵌入式系统设计中,MiniPCIe/SATA双用插槽作为一种高度集成的解决方案,提供了极大的灵活性与扩展能力。它不仅能够支持MiniPCIe接口的无线网卡、固态硬盘控制器等模块,还能适应SATA接口的硬盘或固态存储设备,大大丰富了系统配置的可能性。尽管设计初衷良好,但在实…

STP19NF20 丝印 19NF20 场效应管19A 200V 直插 TO-220

STP19NF20 功率MOSFET的应用领域相当广泛&#xff0c;主要包括&#xff1a; 1. 电源管理&#xff1a;用于高效率电源管理电路&#xff0c;如直流-直流转换器和交流-直流电源适配器。 2. 开关模式电源&#xff08;SMPS&#xff09;&#xff1a;在需要高效能和紧凑型尺寸的开关…

【学习】软件测试小伙伴,这几点助你提升软件测试水平

在数字化时代&#xff0c;软件已经无处不在&#xff0c;影响着我们的日常生活、工作乃至整个社会的运行。在这个背景下&#xff0c;软件测试成为确保产品质量的关键环节&#xff0c;关乎用户体验和社会信任。本文将为您梳理一些关于软件测试你必须了解的知识点&#xff0c;并阐…

JAVAEE之多线程进阶(2)_ CAS概念、实现原理、ABA问题及解决方案

前言 在并发编程时&#xff0c;常常会出现线程安全问题&#xff0c;那么如何保证原子性呢&#xff1f;常用的方法就是加锁。在Java语言中可以使用 Synchronized和CAS实现加锁效果。  Synchronized关键字保证同步的&#xff0c;这会导致有锁&#xff0c;但是锁机制存在以下问题…

douyin-vue:使用Vue3、Pinia和Vite5打造高度还原的抖音仿制项目

一&#xff1a;引言 在前端技术日新月异的今天&#xff0c;Vue.js作为一款流行的前端框架&#xff0c;不断吸引着开发者的目光。最近&#xff0c;GitHub上出现了一个备受瞩目的项目——douyin-vue&#xff0c;这是一个基于Vue3、Pinia和Vite5的移动端短视频项目&#xff0c;旨…

通过ESP32芯片模组实现产品智能化升级,启明云端乐鑫代理商

随着科技的不断进步&#xff0c;物联网&#xff08;IoT&#xff09;已经渗透到我们生活的方方面面&#xff0c;成为现代生活不可或缺的一部分。在这场智能化革命中&#xff0c;乐鑫科技以其创新的ESP32芯片模组&#xff0c;为智能家居和智能设备的发展注入了新的活力。作为乐鑫…

msi安装mysql8 启动失败,提示只有在任务处于完成状态(RanToCompletion、Faulted 或 Canceled)时才能释放它。

解决方案: 1.打开服务,找到安装的mysql 2. 右击&#xff0c;打开属性&#xff0c;进入【登录】选项卡&#xff0c;选择本地系统账户。 3. 点击确定-->应用 4.服务中选择开始服务 5.服务启动成功后,在安装步骤中继续点击执行

Post Microsoft Build and AI Day 上海开发者日

点击蓝字 关注我们 编辑&#xff1a;Alan Wang 排版&#xff1a;Rani Sun 这个六一怎么过&#xff1f;来微软 Reactor&#xff0c;一起过儿童节吧&#xff01; 6月1日&#xff0c;Microsoft Azure & Microsoft Reactor 面向大小朋友特别推出六一特辑&#xff0c;「Post Mic…

开源进销存系统

推荐一款开源的进销存系统 项目地址&#xff1a;进销存系统 仓库管理系统 SAAS进销存 进销存ERP: 进销存系统 仓库管理系统 SAAS进销存 进销存ERPhttps://gitee.com/flyemu/jxc.git 主要功能模块 销售 采购 库存 资料 设置 支持saas多租户&#xff0c;100%开源可二开 …

iPhone用户推荐使用的藏汉翻译小助手:藏汉翻译通小程序,支持藏文OCR识别文字提取,卫藏语、安多语、康巴语学习背单词!

网上冲浪时&#xff0c;遇到不会的汉语词汇&#xff0c;可以复制到藏汉翻译通小程序中进行翻译。如果不会拼音&#xff0c;可以使用图片识别功能扫一扫文字&#xff0c;即可OCR识别提取文字。 此外&#xff0c;藏汉翻译通小程序现在还支持背单词和会话速成课程&#xff0c;支持…