CVI动态调用和静态调用CH347DLL还有Python

抱歉不大会在博客插入附件和图片,想看图文版请电邮14518918@qq.com

一, 关于CH347
CH347是南京沁恒最近几年推出来的USB接口芯片。如果你已经很熟悉CH341,那么你一定会爱上CH347。因为,CH347的UART和I2C可以同时存在(CH341只能跳选ACT要不要2k到地,来分时决定要么是UART要么是I2C),CH347的UART在00模式下最高波特率可达9M(CH341只有2M),最招人爱的是CH347的I2C有7种速率可选(CH341只有前4种)。

原厂和淘宝卖家提供的参考文档:

CH347模块,淘宝有售,链接是:https://item.taobao.com/item.htm?abbucket=10&id=849139875587&skuId=5807890887927

实际上CH347T的售价,比CH341T还是要高一点的:

下图是自制GD32E251评估板与CH347模块的I2C互联照片,也是我接下来的硬件测试平台:

二, CVI动态调用CH347DLL.dll
最开始我在官网没有下载到CH347DLL.lib,只搞到了CH347DLL.h和CH347DLL.dll。不得已才开始学CVI动态调用DLL的方法。
有网帖https://blog.csdn.net/qq_41256212/article/details/86743698,《LabwindowsCVI DLL工程的创建及使用》,介绍了CVI动态调用DLL的方法,特摘录如下:
LabwindowsCVI DLL库文件的使用与Visual C++的使用一样,就是调用windows的底层库来实现的,其主要由LoadLibrary、GetProcAddress、FreeLibrary 这3个函数来实现:
1,LoadLibrary函数:
LoadLibrary函数用于加载DLL库文件到内存中,这样我们就能调用DLL函数了,其函数原型如下所示:
HMODULE LoadLibrary(LPCSTR lpLibFileName);
可以看出其参数就是DLL库文件的路径,当其加载DLL库文件成功后,将返回DLL库文件的句柄,否则返回NULL。
2,GetProcAddress函数:
GetProcAddress函数用于提取DLL库文件导出的某个具体DLL函数,其函数原型如下所示:
FARPROC GetProcAddress(HMODULE hModule,LPCSTR lpProcName);
可以看出其有2个参数,一个为DLL库文件的句柄,另一个为DLL导出函数的名称,当其在hModule库中找到与lpProcName想匹配的DLL函数时,返回该DLL函数的地址,否则返回NULL。
3,FreeLibrary函数:
FreeLibrary函数用于释放DLL模块,其函数原型如下所示:
BOOL FreeLibrary( HMODULE hLibModule);
可以看出其只有一个参数,就是DLL模块的句柄,当其释放DLL模块成功时,将返回非零的值,否则返回0值。

然后问Deepseek “CVI如何动态调用DLL函数?”
答:
在LabWindows/CVI中动态调用DLL函数,可以通过Windows API实现。
1,关键步骤说明
加载DLL:使用 LoadLibrary 函数
获取函数指针:使用 GetProcAddress 函数
定义函数指针类型:需与DLL函数原型严格一致
调用函数:通过获取的指针调用DLL函数
释放资源:使用 FreeLibrary 卸载DLL

#include <windows.h>

2,示例代码
// 1. 定义DLL函数原型(需与实际DLL函数签名完全一致)

typedef int (*MyFunctionPtr)(int, char*); // 假设DLL函数返回int,接受int和char*参数  
  
int main()  
{  
    HINSTANCE hDLL = NULL;  
    MyFunctionPtr pFunction = NULL;  
      
    // 2. 加载DLL(注意路径问题)  
    hDLL = LoadLibrary("MyDLL.dll");  
    if (hDLL == NULL) {  
        printf("Error: Failed to load DLL. Code=%d\n", GetLastError());  
        return -1;  
    }  
      
    // 3. 获取函数地址  
    pFunction = (MyFunctionPtr)GetProcAddress(hDLL, "MyExportedFunction");  
    if (pFunction == NULL) {  
        printf("Error: Function not found. Code=%d\n", GetLastError());  
        FreeLibrary(hDLL);  
        return -1;  
    }  
      
    // 4. 调用DLL函数  
    int result = pFunction(42, "Hello from CVI!");  
    printf("DLL returned: %d\n", result);  
      
    // 5. 释放DLL  
    FreeLibrary(hDLL);  
    return 0;  
}  

我于是照猫画虎,在无lib文件前提下,尝试动态调用CH347DLL.dll中的函数。
1,在CVI中新建一个带GUI的Template Project,取名“DynamicCallDLL.prj”保存在“callDLL2”目录。

2,修改main()函数所在的《DynamicCallDLL.c》
 第一行新增

#include <windows.h>  

注意如果这句话须放在第一行,如果挪到其他头文件后面,CVI编译时可能会报错。
 在mian()函数前面,新增几个CH374DLL中的函数指针类型。

typedef int (__stdcall *CH347OpenDevice) (int);                            
typedef int (__stdcall *CH347CloseDevice)(int);                            
typedef int (__stdcall *CH347StreamI2C)  (int, int, void*, int, void*);  

注意,务必要加上关键字__stdcall,不然后面debug调用DLL函数时CVI会报错,而且原函数有几个变量就要在括号中写几个对应的数据类型。
 给界面新增一个按键,其回调函数如下:

int CVICALLBACK on_Test (int panel, int control, int event,  
        void *callbackData, int eventData1, int eventData2)  
{  
HMODULE hDLL;                         // DLL句柄                                 
CH347OpenDevice CH347OpenDevice_DLL;  // 函数指针,打开CH347                 
CH347OpenDevice CH347CloseDevice_DLL; // 函数指针,关闭CH347                 
CH347StreamI2C  CH347StreamI2C_DLL;   // 函数指针,CH374发起I2C时序        
uint8_t USBHandle;                    // CH347的USB句柄,0..15  
uint8_t iWriteBuffer[300]={0};        // I2C写缓存  
uint8_t oReadBuffer[300]={0};         // I2C读缓存  
uint16_t iWriteLength;                // I2C写缓存长度  
uint16_t iReadLength;                 // I2C读缓存长度  
char str[256];                //字符串  
int error;  
  
    switch (event)  
    {  
        case EVENT_COMMIT:  
            // 加载DLL  
            hDLL = LoadLibrary("CH347DLL.dll");  
            if (hDLL == NULL)  
            {  
                // 处理加载失败的情况  
                MessagePopup("错误", "无法加载DLL文件");  
                return -1;  
            }  
  
            // 获取CH347OpenDevice函数地址  
            CH347OpenDevice_DLL = (CH347OpenDevice)GetProcAddress(hDLL, "CH347OpenDevice");  
            if (CH347OpenDevice_DLL == NULL)  
            {  
                // 处理获取函数地址失败的情况  
                MessagePopup("错误", "无法找到指定函数");  
                FreeLibrary(hDLL);  // 释放DLL  
                return -1;  
            }  
            // 获取CH347CloseDevice函数地址  
            CH347CloseDevice_DLL = (CH347CloseDevice)GetProcAddress(hDLL, "CH347CloseDevice");  
            if (CH347CloseDevice_DLL == NULL)  
            {  
                // 处理获取函数地址失败的情况  
                MessagePopup("错误", "无法找到指定函数");  
                FreeLibrary(hDLL);  // 释放DLL  
                return -1;  
            }  
            // 获取CH347StreamI2C函数地址  
            CH347StreamI2C_DLL = (CH347StreamI2C)GetProcAddress(hDLL, "CH347StreamI2C");  
            if (CH347OpenDevice_DLL == NULL)  
            {  
                // 处理获取函数地址失败的情况  
                MessagePopup("错误", "无法找到指定函数");  
                FreeLibrary(hDLL);  // 释放DLL  
                return -1;  
            }  
  
            // 打开CH347  
            USBHandle=0;  
            error = CH347OpenDevice_DLL(USBHandle);  
            if (-1 == error)  
            {  
                // 处理获取函数地址失败的情况  
                MessagePopup("错误", "打开USBIndex=0的CH347失败");  
                FreeLibrary(hDLL);  // 释放DLL  
                return -1;  
            }  
  
            //I2C随机连续读从机A0[00..07]寄存器,赋值到oReadBuffer[00..07]  
            USBHandle       = 0;    //CH347的USBHandle=0  
            iWriteBuffer[0] = 0xA0; //I2C从机地址  
            iWriteBuffer[1] = 0x00; //I2C寄存器地址   
            iWriteLength    = 2;    //I2C写缓存长度  
            iReadLength     = 8;    //I2C读缓存长度  
            //调用CH347OpenDevice函数发起一次I2C随机连续读  
            error = CH347StreamI2C_DLL(USBHandle, iWriteLength, iWriteBuffer, iReadLength, oReadBuffer);  
            if (1 == error)//成功  
            {  
                sprintf (str, "Read A0[00..08h]=%02X, %02X, %02X, %02X, %02X, %02X, %02X, %02X",   
                    oReadBuffer[0], oReadBuffer[1], oReadBuffer[2], oReadBuffer[3],   
                    oReadBuffer[4], oReadBuffer[5], oReadBuffer[6], oReadBuffer[7] );  
                MessagePopup("成功", str);  
            }  
            else               //失败  
            {  
                // 处理I2C操作失败的情况  
                MessagePopup("错误", "I2C随机连续读从机A0[00..07]寄存器失败");  
                return -1;  
            }  
  
            // 关闭CH374  
            error = CH347CloseDevice_DLL(USBHandle);  
            // 释放DLL  
            FreeLibrary(hDLL);  
            break;  
    }  
    return 0;  
}  

尝试运行,可以成功读出I2C从机的A0[00…07]数据:

以上,就是CVI动态调用CH347DLL.dll的核心函数的过程。

三, CVI静态调用CH347DLL.dll
下面,我们看看静态调用CH347DLL.dll,请宝子们待会儿记得把“Easy”扣在公屏上。
第一步,得找齐CH347DLL.dll、CH347DLL.lib、和CH347DLL.h三件套。李大师率先从沁恒官网上下载了“CH341PAR.ZIP”压缩包给我,说在一起的。我尝试在沁恒官网搜索栏输入“CH347”关键词,确实能搜索到一个“CH341PAR.ZIP”:

下载下来,就能找到所需三件套,需要拷贝到CVI的工程文件夹中备用。然后,需要将CH347DLL.lib和CH347DLL.h直接从文件夹拖到CVI的Project Tree窗口中,如下:

第二步,在源代码中直接调用CH347DLL.h中的函数即可:

具体的关键代码,请看回调函数源码:

int CVICALLBACK on_CH347OpenDevice (int panel, int control, int event,  
        void *callbackData, int eventData1, int eventData2)  
{int iMode = 1; //000=低速/20KHz,001=标准/100KHz(默认值),010=快速/400KHz,011=高速/750KHz,100=低速/50KHz,101=标准/200KHz,110=快速/1MHz  
 uint32_t I2CRate_Arr[7]={20, 100, 400, 750, 50, 200, 1000};  
 char str[256]={0}, str1[256]={0};  
   
    switch (event)  
    {  
        case EVENT_COMMIT:  
            SetCtrlVal (panel, PANEL_LED_CH347Open, 0);  
  
            //从界面获取CH347的USBHandle  
            GetCtrlVal (panel, PANEL_NUM_USBHandle, &USBHandle);  
            //从界面获取CH347的iMode  
            GetCtrlVal (panel, PANEL_RNG_iMode, &iMode);  
              
            //尝试打开USBHandle对应的CH347  
            HANDLE DLLresult = CH347OpenDevice(USBHandle);  
            if (INVALID_HANDLE_VALUE==DLLresult)  
            {  
                sprintf(str, "NG, open CH347 with USBHandle=%d\n", USBHandle);  
                //打印log到界面  
                SetCtrlVal (panel, PANEL_TXB_Log, str);  
                return -1;  
            }  
            else  
                sprintf(str, "OK, open CH347 with USBHandle=%d", USBHandle);  
                  
  
            int error = CH347I2C_Set(USBHandle, iMode);  
            if (0==error)      
            {  
                sprintf(str, "NG, set CH347 with I2CRate=%dkHz\n", I2CRate_Arr[iMode]);  
                //打印log到界面  
                SetCtrlVal (panel, PANEL_TXB_Log, str);  
                return -1;  
            }  
            else  
                sprintf(str1, ", I2CRate=%dkHz\n", I2CRate_Arr[iMode]);  
            strcat(str, str1);  
            //打印log到界面  
            SetCtrlVal (panel, PANEL_TXB_Log, str);  
              
            SetCtrlVal (panel, PANEL_LED_CH347Open, 1);  
            break;  
    }  
    return 0;  
}  
  
int CVICALLBACK on_CH347CloseDevice (int panel, int control, int event,  
        void *callbackData, int eventData1, int eventData2)  
{  
 char str[256];  
  
    switch (event)  
    {  
        case EVENT_COMMIT:  
              
            //从界面获取CH347的USBHandle  
            GetCtrlVal (panel, PANEL_NUM_USBHandle, &USBHandle);  
            //尝试关闭USBHandle对应的CH347  
            HANDLE DLLresult = CH347OpenDevice(USBHandle);  
            if (INVALID_HANDLE_VALUE==DLLresult)  
                sprintf(str, "NG, close CH347 with USBHandle=%d\n", USBHandle);  
            else  
                sprintf(str, "OK, close CH347 with USBHandle=%d\n", USBHandle);  
                SetCtrlVal (panel, PANEL_LED_CH347Open, 0);  
            break;  
            //打印log到界面  
            SetCtrlVal (panel, PANEL_TXB_Log, str);  
  
            break;  
    }  
    return 0;  
}  
  
int CVICALLBACK on_CH347I2CRead (int panel, int control, int event,  
        void *callbackData, int eventData1, int eventData2)  
{  
    int error;                      // 函数返回值  
    HMODULE hDLL;                   // DLL句柄                                 
    HANDLE DLLresult;               // DLL调用返回值  
    uint8_t iWriteBuffer[300]={0};  // I2C写缓存  
    uint8_t oReadBuffer[300]={0};   // I2C读缓存  
    uint16_t iWriteLength;          // I2C写缓存长度  
    uint16_t iReadLength;           // I2C读缓存长度  
    char str[256];                  //字符串  
    char str1[256];                  //字符串  
    uint8_t i, SlvAdd, RegAdd, Reglen;  
    int CH347Open;  
      
    switch (event)  
    {  
        case EVENT_COMMIT:  
            SetCtrlVal (panel, PANEL_LED_CH347Read, 0);  
              
            //从界面获取CH347的CH47Open  
            GetCtrlVal (panel, PANEL_LED_CH347Open, &CH347Open);  
            if (0==CH347Open)  
            {  
                sprintf(str, "NG, please open CH347 first\n");  
                //打印log到界面  
                SetCtrlVal (panel, PANEL_TXB_Log, str);  
                return -1;  
            }  
                  
            //从界面获取CH347的USBHandle  
            GetCtrlVal (panel, PANEL_NUM_USBHandle, &USBHandle);  
            //从界面获取SlvAdd  
            GetCtrlVal (panel, PANEL_NUM_SlvAdd, &SlvAdd);  
            //从界面获取RegAdd  
            GetCtrlVal (panel, PANEL_NUM_RegAdd, &RegAdd);  
            //从界面获取Reglen  
            GetCtrlVal (panel, PANEL_NUM_Reglen, &Reglen);  
  
            //I2C随机连续读从机A0[00..07]寄存器,赋值到oReadBuffer[00..07]  
            iWriteBuffer[0] = SlvAdd; //I2C从机地址  
            iWriteBuffer[1] = RegAdd; //I2C寄存器地址   
            iWriteLength    = 2;      //I2C写缓存长度  
            iReadLength     = Reglen; //I2C读缓存长度  
            //调用CH347OpenDevice函数发起一次I2C随机连续读  
            error = CH347StreamI2C(USBHandle, iWriteLength, iWriteBuffer, iReadLength, oReadBuffer);  
            if (0==error)    
                sprintf(str, "NG, read CH347 from %02X[%02X..%02X]\n", SlvAdd, RegAdd, RegAdd+Reglen-1);  
            else  
            {  
                sprintf(str, "OK, read CH347 from %02X[%02X..%02X] = ", SlvAdd, RegAdd, RegAdd+Reglen-1);  
                for (i=0; i<Reglen; i++)  
                {  
                    sprintf(str1, "%02X ", oReadBuffer[i]);  
                    strcat(str, str1);  
                }  
                strcat(str, "\n");  
            }  
            //打印log到界面  
            SetCtrlVal (panel, PANEL_TXB_Log, str);  
              
            SetCtrlVal (panel, PANEL_LED_CH347Read, 1);  
            break;  
      
    }  
    return 0;  
}  

你看,动态调用dll,要用到wondws.h中的三个函数,还要事先申明将要被动态调用的函数名及其带的所有参数类型,还要特别注明_stdcall。而静态调用,就是一个原dll的函数名,拿来就用,清爽多了不是嘛?但动态调用dll的方式,豆包说却更流行,这究竟图个啥呢?在求教了余大师之后,我对静态和动态,又有了新的认识。
静态调用DLL又称隐式调用,在程序编译/链接阶段通过导入库(.lib文件)声明DLL函数,系统在程序启动时自动加载所需DLL。DLL打包进入EXE之后不再需要和EXE一起release给用户。当DLL升级之后,EXE需重新编译打包新版本DLL,故EXE就比较臃肿。
动态调用DLL又称显式调用,无需导入库(.lib)或头文件,直接通过函数名(字符串)查找地址,在程序运行时通过代码动态加载DLL(如LoadLibrary、GetProcAddress)。DLL需要和EXE一起release给用户。当DLL升级之后,只需要更换DLL,而EXE无需重新编译,故EXE就比较清爽。
Deepseek给两者的对比表:
维度 静态调用DLL 动态调用DLL
链接时机 编译时(通过.lib) 运行时(代码显式加载)
依赖文件 需.h和.lib 仅需DLL文件
DLL加载时间 程序启动时自动加载 按需加载(如点击功能时)
错误处理 DLL缺失直接崩溃 可捕获错误并处理(如提示用户)
内存占用 可能较高(提前加载所有DLL) 较低(按需加载)
代码复杂度 低(直接调用函数) 高(需函数指针和类型转换)
适用场景 稳定依赖、快速开发 灵活扩展、热更新、插件化系统

两份CVI源码在此:

我专门切换过CH347的SCL频率,拿示波器观察,还是比较准的,完全不像CH341F那么离谱(CH341F的频率实测值是datasheet上写的两倍,差评),我现在代码设置的是100kHz,示波器显示黄线频率92.4kHz。

去除START和STOP等时序,再展开看A0这部分,频率就是100.8kHz了,很准啊。

CH347的I2C速率选项比CH341多,示波器测了都还比较准。100k-200k-400kHz我都试过,访问GD32E251这个I2C从机没问题,但到750KHz就没戏了:

四, Python动态调用CH347DLL.dll
先上Python程序的运行结果:

再上Python源代码屎山:

参考文档:

Python编程不难,只不过GUI控件跟代码不能相互跳转,搞得敲代码的过程一直就比较窝火,仅这一点上讲,CVI完胜!

这里只说几个Tips
1,当一个DLL文件被用ctypes.WinDLL()函数装载时,就是用的stacall方式了:
common.CH347_dll = ctypes.WinDLL(dll_path)

2, CH341只要成功opendevice之后,上位机调用CH341StreamI2C()函数发起I2C读,即使某个从机地址都不存在,该函数都不会返回false。所以我们用CH341去搜索I2C从机,就只能自己凑一个时序,基本思路就是用最底层的I2C指令(要用到比如mCH341A_CMD_I2C_STREAM这样的命令码),发START+SlvAdd,然后检查从机的ACK,最后发STOP。完了判断从机ACK是否正常应答,来判断某个从机地址是否真的挂上来了。CH347我就没有找到这些带“CMD_I2C”字样的宏定义了,但我可以调用CH347StreamI2C()函数发起发起I2C读(实际上这里的回读长度参数可以设成0,所以时序上就和CH341的方式一样,就只有写从机地址的时序),然后判断返回值是false就能说明该从机地址不存在了。

3,本来最开始我是在myTkinter for CH341的源文件基础上,准备修改成CH347适用的版本。结果一来就卡在ch347OpenDevice()函数上了,我才转战CVI的。在Pycharm中Debug到这句话,就没法继续向下跑了。而Run,会在console窗口报错,说在CH341DLL.dll中找不到ch347OpenDevice()这个函数。淘宝商家也提供了python例程但是我用不来,唯独有一个CH347.py文件可以参考下,我看和我的代码大抵都是一样的啊。无奈求助于苟大师,火眼金睛啊,一来就指出,我源代码里面的ch347应该是CH347。真的是改了就对了。我估计是我在Pycharm IDE中Ctrl+R的时候,把原先的“CH341”错误替换成了“ch347”。哎……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值