LubanCat-RK3568 UART串口通信,以及遇到bug笔记

最近因项目需求,需要使用RK3568控制FPGA开发板来实现墨盒打印图片功能。项目的核心是通过UART通信协议将图像的像素点数据从RK3568主控芯片传输至FPGA开发板,再由FPGA控制墨盒打印图像。因此,本篇记录了项目中使用的主要技术、实现过程中的难点以及遇到的坑,旨在为后续的调试和优化提供参考。

1 rk3568开启串口

可以看到编号32、33为uart串口0,官网有两种打开方式,第一种方式不太好使,我直接采用第二种打开方式,通过修改设备树的配置文件进行修改:

bash 复制代码
cd  /boot/uEnv/
ls -al

可以看到uEnv.txt 软连接到uEnvLubanCat2-V3.txt,我们直接修改后者:

因为我们使用的uart3-m0,所以直接打开这个引脚:

bash 复制代码
sudo vim uEnvLubanCat2-V3.txt
# 修改完之后直接重启
sudo reboot

查看是否成功打开串口:

bash 复制代码
ls /dev/ttyS*

这里可以看到已经打开成功。

2 uart串口通信

2.1 uart串口通信代码

C++ 复制代码
# 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>中定义的, 该文件中还包含了各个结构体成员可使用的宏值,具体定义如下:

C 复制代码
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代码

C++ 复制代码
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,下面是测试代码:

C 复制代码
//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代码方面的通用技能