迪文科技论坛

 找回密码
 立即注册
搜索
查看: 741|回复: 0

【分享】DWIN HMI & OS开发技巧 连载...

[复制链接]

10

主题

84

回帖

798

积分

高级会员

Rank: 4

积分
798
发表于 2025-1-28 14:14:44 | 显示全部楼层 |阅读模式
本帖最后由 luozewei 于 2025-2-3 21:28 编辑

       习惯了keil C开发,DGUS_V7.647开发模式非常难受,所有变量都要手工安排,而且变量地址容易出错,重复,每次修改都需人工校对,工作量巨大。幸好迪文出来了一款全新的DWIN HMI软件,SP,VP变量自动生成,并生成宏定义传递给keil,通过这几天实际总结出一套好的方法分析出来。
       一般keil开发,会有很多几百个甚至上千个变量,对于这么庞大数量的变量,往往会整理成面向对象的结构体,比如我设计了一款电源。那么电源里面按名称分有电压值,电流值,功率值,按行为分有采样值,设定值,限制值,矫正值,用户快捷值,还有输出状态,按钮名字,按钮行为等等。 那么问题来了,这么多变量在UI DGUS软件里面都是属于VP变量,而且在DGUS里面全是独立个体,互相之间没有关联,数量多且凌乱,没办法在DGUS软件里面按对象定义成结构体。好在DWIN HMI有SP描述指针宏定义名,生成后在c51header文件夹内。方法来了,在DWIN HMI放置好控件,并定义好SP宏名,传递到Keil, 在keil里面把结构化的VP地址+DUGS变量起始地址传递给DUGS SP指向的VP指针, 经过这么一一对应后,UI核的控件和keil里面对象内容完全分开了,这样我只关心Keil下对象操作,比如电压采样值,电流采样值更新后,把整个结构体写到UI核可以了,无需再关心控件SP和VP的对应了.

        uint16_t vpAddr = (uint16_t*)&buck.voltage.set - (uint16_t*)&buck + vpBuck_Parameter;        //buck是一个结构体,        vpBuck_Parameter是传递给UI的VP起始地址。获取到buck.voltage.set变量偏移地址+VP起始地址。
        Write_Dgus_Halfword(sp1Data_Vset, vpAddr);      //sp1Data_Vset是定义在Dugs UI里面的数据控件SP指针名。 意思把buck.voltage.set 的内容地址和sp1Data_Vset SP指针指向的VP对应起来。
        ......  //中间代码对buck结构体操作。
       Write_Dgus((uint8_t*)&buck, vpBuck_Parameter, sizeof(buck));        //最后把整个结构体写到UI里面,全部内容一起更新。

       write_Dgus,Read_Dgus函数也做了优化,很多工程师没去考虑C转成汇编后的代码量。C51 data区变量操作只要一条指令,而在xdata区要多条指令这速度要慢很多。Dgus变量传递为底层接口,和UI通讯是最频繁的。这几个函数执行效率直接影响到整个代码效率。具体方法:void Write_Dgus(uint8_t *pBuf8, uint16_t addr, uint16_t datLen); 带进来的*pBuf8 传递给定义在data区指针变量 *pdat。datLen也传递给定义在data区数据变量len16,  并用union变量来做间接二次数据传递到DATA0~DATA3,在Dugs写入时利于等待时间去做buf.lword = *(uint32_t*)pdat8,等待写完成后立即
DATA3 = buf.byte[0];
DATA2 = buf.byte[1];
DATA1 = buf.byte[2];
DATA0 = buf.byte[3];
这只要4条指令就完成32位数据传递。如果用DATA3 = *pBuf8++; 来做需要36*4条指令才能完成32位数据传递。其中速度慢了近100倍( xdata变量部分汇编指令是需要3~4个周期才能完成的)。

2025/02/03 增加了串口2,3,4,5,高速收发。串口模拟空闲中断类似于STM32 UART空闲中断,USMART4.0 for C51,从核心文件上做了C51代码优化。
一、 串口2 用于连接USMART收发。用ring buffer缓冲, 用2枚uart2.Txps和uart2.Txpt做读写,代码非常简洁。

//----------------------------vprintf 重入函数,打印到ring buffer
char putchar(char c)
{        
        if(uart2.Txpt > u2Txpe)                 //判断指针到buffer尾部,指针回头
                uart2.Txpt = &uart2TxBuf;
        *uart2.Txpt++ = c;        
        if(uart2.flag.txBusy == 0){
                SBUF0 = *uart2.Txps++;        //启动UART,直接写TIO=1不好!还没退出本函数就入中断,容易出现嵌套问题
                uart2.flag.txBusy = 1;
        }
        return c;
}
//----------------------------UART2中断收发函数-----------------

void UART2_TX_RX_ISR(void)  interrupt 4
{
        if(RI0){
                uart2RxBuf[indexRx2++] = SBUF0;
                TH1 = U2IDLETM_H;                        //定时器1加载倒计时时间
                TL1 = U2IDLETM_L;                        //定时器1加载倒计时时间
                TR1 = 1;                                        //开启空闲中断 12*64/FCLK   
                TF1 = 0;        
                uart2.flag.idleIF = 1;        
                RI0 = 0;        
        }
        if(TI0){               
                TI0 = 0;        
//----------------------------ring buffer UART输出,直到头尾指针重合时停止               
                if(uart2.Txps > u2Txpe)             //遇到buffer尾,指针掉头。
                        uart2.Txps = &uart2TxBuf;               
                if(uart2.Txps != uart2.Txpt)       //如果读写指针不相等,侧继续打印输出
                        SBUF0 = *uart2.Txps++;
                else{
                        uart2.flag.txBusy = 0;               
                }
        }
}

二、串口空闲中断,每次接收完成都要去看一下数据是否全部收进来,这是非常耗MCU资源的。 设计思路,当每次UART进入接收中断后开启TIM1,设置TIM1溢出时间大于下次RI0中断进来的时间,这样下次UART进来后把TIM1溢出时间复位,直到UART全部世界级接收完成后,UART中断不再进来,TIM1中断,此时整个数据包已经全部接收完成。这里我用的是TF1标志查询,为了减小中断嵌套。 整个代码中我很少用全局中断屏蔽方法,此法虽然简单,但效率非常底下,只要代码结构合理,嵌套深度合适,data去空闲变量足够,就不会出现嵌套问题。

三、USMART4.0是正点原子开发的,作为调试代码用途是非常好使的,比迪文枯燥的82,83指令直观多了,  在这里首先感谢正点原子提供源代码,由于正点原子设计的是STM32 KEIL arm用的,不能直接用于C51代码。我花了近一周从源代码中读懂,理解后开始修改,发现源代码写的太啰嗦特别是一些str处理文件,难以读懂,最后从程序架构理解后,全部抛弃str文件,从新写,这里给大家公布源代码出来,也是很容易理解的。修改了USMART代码执行时间测量精度,从0.1ms精确到1us。有利于测量函数执行时间,做代码优化。
//Ticking_reset()需要测量的函数计时复位,开始计时间,Get_Ticking()需要测量的函数退出后获取从TIM0走的时间。
void Ticking_reset(void)
{
  TL0 = 0;        TH0 = 0;   /* 清空定时器的CNT */
        runTick = 0;              
}
uint32_t Get_Ticking(void)
{
        buf.byte[2] = TH0;
        buf.byte[3] = TL0;        
        buf.iword[0] = runTick;
        if(buf.byte[3] == 0){
                buf.byte[2]++;
                if(buf.byte[2] == 0)
                        buf.iword[0]++;
        }
        return buf.lword;               
}

四、迪文论坛里面2位网友写了T5L C代码的可以donwload bin文件,我做了结构化修改,去掉了原来的流水判断法,缺点是代码全在中断内,无法用高波特率通讯,其二接收数据离散化,中间过程全部用数组下标方法,不利于分析数据,容易出错。我做了结构体指针切换。

//----------------------------UART收发指针结构体
typedef struct {               
        uint8_t        lenTx;                        //Tx BUF数据发送长度
        uint8_t        lenRx;                        //Rx BUF数据接收长度               
        uint8_t        *Txps;  
        uint8_t        *Txpt;         
        uint8_t        *Rxph;                        //RX BUF头指针
        uint8_t        *Rxps;                        //RX 数据尾指针
        uint8_t        *Rxpt;                        //RX 数据尾指针
        uint8_t        *Rxpe;                        //RX BUF尾指针        
        struct{
                uint8_t        id                                                : 3;//串口号               
                uint8_t        crcCheck                        : 1;
                uint8_t        response                        : 1;
                uint8_t        autoUpload                : 1;        
                uint8_t        txBusy                        : 1;        
                uint8_t        idleIF                            : 1;               
        }flag;        
}xdata UART_HandleTypeDef;  

//-------------------从串口接收进来的是char数组指针传递给uartx.Rxps, 进入到UART_Serial_Deal函数后,指针又传递给Deal82Handle_t

//----------------------------82指令写数据包结构体
typedef struct{
   uint16_t                check;
   uint8_t                stretch;                        //除去报头和本身后整个包长度
   uint8_t                command;                        //命令
   uint16_t                addr;                                        //DGUS读写地址
   uint8_t                dat[256-6];                //数据
}xdata Deal82Handle_t;

//这下从Deal82Handle_t结构中看的非常清楚,哪儿是报头,哪儿是长度,哪儿是命令,哪儿是地址,哪儿是数据,进入UART_Serial_Deal后82,83命令非常容易判断了。...

void UART_Serial_Deal(UART_HandleTypeDef *uart)
{        
        uint8_t  strLen;        
        xdata Deal82Handle_t *p82Pack;
        
        if(uart->Rxpt > uart->Rxps){
                p82Pack = (Deal82Handle_t *)uart->Rxps;        
                strLen = uart->Rxpt - uart->Rxps;               
        }
//-------RX环形BUFFER 头指针在后,尾指针在前是,拷贝数据到缓冲
        else{        
                xdata uint8_t wrBuf[256];         
                uint8_t *psDgus = uart->Rxps, *pwrBuf = wrBuf;        
               
                while(psDgus <= uart->Rxpe)
                        *pwrBuf++ = *psDgus++;               
               
                psDgus = uart->Rxph;
                while(psDgus < uart->Rxpt)        
                        *pwrBuf++ = *psDgus++;        
               
                strLen = pwrBuf - wrBuf;               
                p82Pack = (Deal82Handle_t*)&wrBuf;
        }
        Debug_Data_Mesage((uint8_t*)p82Pack, 15);        
        if(strLen < (p82Pack->stretch + 3)){               
                Debug_Text_Mesage("包长度错误");
                return;
        }
        uart->Rxps = uart->Rxpt;                                       
//-----------------------------------------URAT RX指针释放               
        switch (p82Pack->command)
        {        
                case 0x82:                                       
                        Deal82_CMD(uart, (uint8_t*)p82Pack);
                        break;
                        
                case 0x83:        
                        Deal83_CMD(uart, (uint8_t*)p82Pack);               
                        break;        
                        
                default:
                        uart->Rxpt = uart->Rxps = uart->Rxph;                  //结构体指针复位                        
                        Debug_Text_Mesage("未识别命令");
                        UART_Send_Set(&uart3, "未识别命令\n\r", 12);
                        return;
                        break;
                }            
}


五、论坛上一位网友开发的T5LDownload_V2上位机程序,下位机源代码论坛上也给出了V1版,我做了相当大的修改,由于博主没有上传V2版的源代码,我暂时用V1版代码做了测试相当完美,并且能和迪文的下载协议并存。T5LDownload_V2博主看到,看看能否把上位机通讯协议改成迪文一样的,这样不用2套协议了,用T5LHighSpeedDownload V1.0.0.3协议是最好,T5LHighSpeedDownload协议在我的代码字行间也写了一点,T5LHighSpeedDownload 是没有分240字节分段下载的,速度比较快,并且支持cfg文件。
      以下是2套协议识别切换。用的是串口5通讯,
if(uart5.flag.idleIF)
{        
        uart5.flag.idleIF = 0;
        if(Uart5_Handle())
        {                        
                if(Get_Timeout(0) == 0){        //2套PC Donwload协议定时释放,支持2套协议无缝切换
                        check = *(uint16_t*)uart5.Rxps;
                        if(check == 0x5AA5)
                                agreement = 0x5A;
                        else if((check == 0x0110)||(check == 0x0103)||(check == 0x0106))
                                agreement = 0x5B;        
                        else
                                agreement = 0;
                }
                if(agreement == 0x5A){
                        UART_Serial_Deal(&uart5);        
                        Put_Timerout(0, 1000);
                }
                else if(agreement == 0x5B){
                        Deal_OSDL2_CMD(&uart5);
                        Put_Timerout(0, 1000);
                }
                else
                        uart5.Rxpt = uart5.Rxps;
        }
}

六、软件模拟串口,这下可好了,写纯OS代码的朋友有福了,在无需硬件情况下,用电脑可以模拟串口和keil之间通讯。
      安装好configure virtual serial软件,添加COM1-COM5一对,COM2-COM3一对,COM1,COM2是 KEIL软件仿真虚拟的,COM5, COM3是用串口工具我这里是用SSCOM,这样SSCOM和T5L里面的UART2通讯了。我这里代码已经添加好了.\debug.ini文件在上传的文件目录下,可以直接用,可以自己编辑修改。COM口信息。 我把DGUS 读写函数里面的while等待全部注释掉了,一般情况下烧入到T5L芯片也能正常读写,可以支持全程软件仿真。




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|迪文科技论坛 ( 京ICP备05033781号-1 )

GMT+8, 2026-4-15 01:13 , Processed in 0.100428 second(s), 22 queries .

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表