最近因项目需求,需要使用RK3568控制FPGA开发板来实现墨盒打印图片功能。项目的核心是通过UART通信协议将图像的像素点数据从RK3568主控芯片传输至FPGA开发板,再由FPGA控制墨盒打印图像。因此,本篇记录了项目中使用的主要技术、实现过程中的难点以及遇到的坑,旨在为后续的调试和优化提供参考。
1 rk3568开启串口
可以看到编号32、33为uart串口0,官网有两种打开方式,第一种方式不太好使,我直接采用第二种打开方式,通过修改设备树的配置文件进行修改:
cd /boot/uEnv/
ls -al
可以看到uEnv.txt 软连接到uEnvLubanCat2-V3.txt,我们直接修改后者:
因为我们使用的uart3-m0,所以直接打开这个引脚:
sudo vim uEnvLubanCat2-V3.txt
# 修改完之后直接重启
sudo reboot
查看是否成功打开串口:
ls /dev/ttyS*
这里可以看到已经打开成功。
2 uart串口通信
2.1 uart串口通信代码
# UartUtil.hpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h>
#include <string.h>
#include <sys/ioctl.h>
#include <iostream>
#include <fstream>
using namespace std;
class UartUtil
{
public:
UartUtil()
{
}
~UartUtil()
{
close(m_nUartFd);
}
public:
bool InitUart(const char* fdUrl)
{
m_nUartFd = open(fdUrl, O_RDWR);
if (m_nUartFd < 0)
{
printf("Fail to Open %s device\n", fdUrl);
return 0;
}
//第三部分代码/
struct termios opt;
//清空串口接收缓冲区
tcflush(m_nUartFd, TCIOFLUSH);
// 获取串口参数opt
tcgetattr(m_nUartFd, &opt);
// //设置串口输出波特率
cfsetospeed(&opt, B9600);
//设置串口输入波特率
cfsetispeed(&opt, B9600);
//B115200
// cfsetospeed(&opt, B115200);
// //设置串口输入波特率
// cfsetispeed(&opt, B115200);
//设置数据位数
opt.c_cflag &= ~CSIZE;
opt.c_cflag |= CS8;
//opt.c_iflag |= IGNCR;
// 设置OFILL标志,启用填充字符功能
//opt.c_oflag |= OFILL;
//开启XON/XOFF流控制
//opt.c_cflag |= IXON;
//CS6
//opt.c_cflag |= CS6;
// //校验位
// opt.c_cflag &= ~PARENB;
// opt.c_iflag &= ~INPCK;
//设置停止位,停止位为1
opt.c_cflag &= ~CSTOPB;
//更新配置
tcsetattr(m_nUartFd, TCSANOW, &opt);
return true;
}
bool WriteData1(unsigned char dataBuf)
{
int bufSize = write(m_nUartFd, &dataBuf, sizeof(dataBuf));
if(bufSize > 0)
{
return true;
}
else
{
printf("Write Data Failed!!\n");
return false;
}
return true;
}
private:
int m_nUartFd;
};
这里的fdUrl参数就是步骤1中的 /dev/ttyS3,可以在InitUart函数里面设置串口通信参数,具体我这代码怎么来的,是参考官网代码进行封装的,如果想要自己封装uart串口功能的,具体可以参考以下地址。
Lubancat Uart串口代码地址
2.2 uart代码参数
2.2.1 termios结构体
它是在头文件<termios.h>包含的<bits/termios.h>中定义的, 该文件中还包含了各个结构体成员可使用的宏值,具体定义如下:
struct termios {
tcflag_t c_iflag; /* input mode flags */
tcflag_t c_oflag; /* output mode flags */
tcflag_t c_cflag; /* control mode flags */
tcflag_t c_lflag; /* local mode flags */
cc_t c_line; /* line discipline */
cc_t c_cc[NCCS]; /* control characters */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
#define _HAVE_STRUCT_TERMIOS_C_ISPEED 1
#define _HAVE_STRUCT_TERMIOS_C_OSPEED 1
};
c_iflag:输入(input)模式标志,用于控制如何对串口输入的字符进行处理,常用的选项值见下表。
c_oflag:输出(output)模式标志,用于控制串口的输出模式,常用的选项值见下表。
c_cflag:控制(control)模式标志,用于控制串口的基本参数,如数据位、停止位等, 常用配置见下表,特别地,c_cflag结构体成员还包含了波特率的参数。
c_lflag:本地(local)模式标志,主要用于控制驱动程序与用户的交互,在串口通信中, 实际上用不到该成员变量。
3 RGBA转CMY代码
void PrintImage::RGBASerialImage(std::string picUrl, bool isSave)
{
cv::Mat image = cv::imread(picUrl, cv::IMREAD_UNCHANGED);
if(image.empty())
{
std::cerr<<"RGBASerialImage Failed To Load Image!!"<<std::endl;
return;
}
cv::resize(image, image, cv::Size(100, 100));
delayImg = image;
if (image.channels() == 4)
{
std::vector<cv::Mat> channels(4);
cv::split(image, channels);
cv::Mat rgbImg;
std::vector<cv::Mat> arrChRGB;
arrChRGB.push_back(channels[0]);
arrChRGB.push_back(channels[1]);
arrChRGB.push_back(channels[2]);
cv::merge(arrChRGB,rgbImg);
cv::Mat resImg;
rgbImg.copyTo(resImg, channels[3]);
// 获取图像的尺寸
int height = resImg.rows;
int width = resImg.cols;
// 遍历每个像素点
for (int i = 0; i < height; ++i)
{
vector<uchar> arrCPixels;
vector<uchar> arrMPixels;
vector<uchar> arrYPixels;
for (int j = 0; j < width; ++j)
{
// 获取当前像素的 BGR 值
cv::Vec3b pixel = resImg.at<cv::Vec3b>(i, j);
uchar blue = pixel[0];
uchar green = pixel[1];
uchar red = pixel[2];
if(red == 0 && green == 0 && blue == 0)
{
arrCPixels.push_back(0x00);
arrMPixels.push_back(0x00);
arrYPixels.push_back(0x00);
continue;
}
///////////
//R-->C
//G-->M
//B-->Y
float perCPixel = (255.0f - (float)red) / 255.0f;
float perMPixel = (255.0f - (float)green) / 255.0f;
float perYPixel = (255.0f - (float)blue) / 255.0f;
uchar cPixel = perCPixel * INKJET_DIV;
uchar mPixel = perMPixel * INKJET_DIV;
uchar yPixel = perYPixel * INKJET_DIV;
if(cPixel == 10)cPixel++;
if(mPixel == 10)mPixel++;
if(yPixel == 10)yPixel++;
arrCPixels.push_back(cPixel);
arrMPixels.push_back(mPixel);
arrYPixels.push_back(yPixel);
}
m_arrCPixels.push_back(arrCPixels);
m_arrMPixels.push_back(arrMPixels);
m_arrYPixels.push_back(arrYPixels);
}
}
else
{
std::cout << "Please Input The Correct Path Of Picture!!" << std::endl;
}
}
这里解释一下,为什么遇到10就自增像素点,就要说到我遇到的一个bug,通过串口通信发送像素点给FPGA开发板时,其他像素点都是一个Byte的16进制数,发送10时接收到两个16进制数。
3.1 传值bug
可以看到只要是发送10,uart自动接收两个Bytes,下面是测试代码:
//uart_send.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h>
#include <string.h>
#include <sys/ioctl.h>
const char default_path[] = "/dev/ttyS3";
int main(int argc, char *argv[]) {
int fd;
unsigned char nData;
// 如果有输入参数,就将其转换为 unsigned char 类型
if (argc > 1) {
nData = (unsigned char)atoi(argv[1]); // 将字符串转换为整数
} else {
nData = 0xff; // 默认值
}
fd = open(default_path, O_RDWR);
if (fd < 0) {
printf("无法打开 %s 设备\n", default_path);
return 0;
}
struct termios opt;
// 清空串口接收缓冲区
tcflush(fd, TCIOFLUSH);
// 获取串口参数
tcgetattr(fd, &opt);
// 设置串口输出和输入波特率
cfsetospeed(&opt, B9600);
cfsetispeed(&opt, B9600);
// 设置数据位数
opt.c_cflag &= ~CSIZE;
opt.c_cflag |= CS8;
// 无校验位
opt.c_cflag &= ~PARENB;
opt.c_iflag &= ~INPCK;
// 设置停止位
opt.c_cflag &= ~CSTOPB;
// 更新配置
tcsetattr(fd, TCSANOW, &opt);
// 发送数据;写入时需要 nData 的地址和大小
write(fd, &nData, sizeof(nData));
close(fd);
return 0;
}
总结
这个项目的目标是通过RK3568主控芯片与FPGA开发板的配合,实现墨盒打印图像的功能。通过UART通信协议将图像的像素点数据传输给FPGA,再由FPGA控制墨盒完成打印。这一过程涉及多个技术环节,包括数据传输、图像处理和打印控制,后续有时间再去整理fpga代码方面的通用技能