• PIC12F510作为PMBus主机


    最近在做一个输出可调压的电源软件项目,使用的单片机是PIC12F510,看了下手册发现资源有限,内存只有38个字节,当然Flash也多不到哪里去,只适合做一些小型的应用。

    本电源产品要使用华为的GDC1K5D5023-PD 电源模块作为主输出,可调电压范围是33V-55V,内置标准PMBus协议,使用CRC8校验。PIC12F510使用3.3V供电,作为PMBus主机对模块输出电压进行调节,该单片机没有硬件I2C,只能使用IO进行模拟,另外还需要一个外部输入电源进行调压设置(类似ADC调压器),单片机ADC模块(8位)检测这个外部电压来设置对应的输出电压(电压范围在2.28V-2.84V之间线性调压,对应模块的输出电压在45V-55V之间,不在此范围的电压设置模块的输出电压为默认的50V),并根据Linear16格式写入到GDC1K5D5023-PD 电源模块,关于Linear16的格式可参考这篇博文:PMBUS Linear11 与 Linear16 数据格式转换过程详解-CSDN博客

    根据公式:Voltage = V * 2^N,这其中V就是我们需要设置的值,查模块手册,N = -9,以设置45V输出电压为例:45 = V * 2^-9,求得V = 23040,也就是我们发送给模块的值。

    另附一个CRC8校验工具的网址:CRC校验工具-ME2在线工具

    需要注意的是:PIC12F510由于资源有限,当函数嵌套层数过多时编译器会出现硬件堆栈溢出风险的提示,这个之前没有遇到过,为了避免这个潜在的风险,有些地方我直接去掉函数调用而直接将代码写到一个函数里,虽然结构看起来乱一些,但项目小,没有必要搞到那么层次清楚。

    主要函数作用及程序如下:

    1、Start, Stop, Send_abyte, Wait_answer:这些主要用于模拟底层I2C通信;

    2、IIC_Transmit:用于发送模块调压指令,但由于有硬件堆栈溢出风险,故没有单独写成函数,而是直接将里面的代码放在了需要的地方;

    3、PMBusMaster_Crc8MakeBitwise:CRC8校验码生成函数,实测过该代码是正常的,此处使用了另外一个函数,所以它暂未被使用;

    4、CRC8:CRC8校验生成函数;

    5、Adjust_Voltage:发送需要的电压给输出模块,参数就是Linear16格式的输出电压值;

    6、GPIO_Init,ADC_Init,Timer0_Init:是一些初始化函数,Timer0的溢出时间为10毫秒;

    7、Read_ADC_Value:这个是ADC数据采样函数,在主函数中1毫秒采集一次,共32次求平均值;

    8、main:主函数中每50毫秒根据ADC采集结果执行一次调压操作。由于PIC12F510没有中断功能,所以主程序只能判断TMR0的值是否到达0xFF判断它是否溢出。

    #include    

    //配置字相关内容

    //#pragma __CONFIG(0x0FCA); //use inner 4MHz RC,
    // CONFIG
    #pragma config OSC = IntRC      // Oscillator Select (INTOSC with 1.125 ms DRT)
    #pragma config WDT = OFF        // Watchdog Timer Enable bit (WDT disabled)
    #pragma config CP = OFF         // Code Protect (Code protection off)
    #pragma config MCLRE = OFF      // Master Clear Enable bit (GP3/MCLR pin functions as GP3, MCLR internally tied to VDD)
    #pragma config IOSCFS = ON      // Internal Oscillator Frequency Select bit (8 MHz INTOSC Speed)
    // #pragma config statements should precede project file includes.
    // Use project enums instead of #define for ON and OFF.

    //使用内部8M内部振荡器

    #define     _XTAL_FREQ          8000000 //used by __delay_us() and __delay_ms() if needed

    //调压模块的I2C从机地址,根据手册,当Address引脚悬空时,地址为91,即16进制0x5B

    #define     I2C_SLAVE_ADDRESS   0x5B    //Slave device address, the address pin is floating rather than 10K resistor

    //PMBus输出调压指令:0x21
    #define     VOUT_COMMAND        0x21    //PMBus command
    #define     PEC                 1       //the PEC is needed

    //CRC8相关多项式值及初值
    #define     CRC8_POLY               0x07    //CRC8 polynomial value
    #define     CRC8_INIT_REM          0x0     //CRC8 initial value

    //模拟I2C用到的引脚,in_SDA用于读取从机的ACK信号,SDA的模式会切换

    //#define     IIC_SCL             GP4     //IIC SCL
    //#define     IIC_SDA             GP5     //IIC SDA
    #define     SDA_0               GPIO&=(~(1<<5))
    #define     SDA_1               GPIO|=(1<<5)
    #define     SCL_0               GPIO&=(~(1<<4))
    #define     SCL_1               GPIO|=(1<<4)
    #define     in_SDA              GPIObits.GP5


    //#define     DEVIATION           7       //the deviation value of 8-bit ADC
    //#define     V56_OUT             220     //56V output AD value, 3.3V reference voltage
    //#define     V45_OUT             177     //45V output AD value, 3.3V reference voltage
    //#define     V56                     56
    //#define     V45                     45
    //#define     PORTBIT(ADR,BIT_LOC) ((unsigned)(&ADR)*8+(BIT_LOC))
    //static bit     in_SDA     @PORTBIT(GPIO,1);//SDA data
    //#define     VOUT(adc)       (unsigned int)(45.0*adc/177.0)

    //CRC8校验计算及输出调压缓冲,由于内存有限,只能共享一些数据

    unsigned char PMBusMaster_TransmitBuffer[4];
    //unsigned char PMBusMaster_CrcMsg[4];
    //unsigned char adc_cnt = 0;

    //本来还想使用其他变量,并将一些标志位保存在同一个变量,但后来将程序不断优化,省掉了一些不必要的变量

    //==========================================================================================================================
    //The higher 2 bits of cTimer are used to store the flags of the voltage-adjusting operation.
    //Each time we adjust the voltage, it is essential to check them and see whether to call the Adjust_Voltage function or not. 
    //If bit 7 is set, then bit 6 must be cleared, and vice versa.
    //The purpose is to avoid sending VOUT_COMMAND(0x21) command to the module ceaselessly, for once the voltage is adjusted,
    //we don't need to set the same value repeatedly unless the ADC result is changed,i.e,to set other values.
    //==========================================================================================================================
    unsigned char cTimer = 0;

    //The highest bit indicates that the conversion is finished.
    //The left lower 7 bits count the ADC and the maximum value is 32.
    unsigned char cSample_flag = 0;
    unsigned int ADC_Sample = 0;
    unsigned char ADC_Result = 0;
    unsigned char cAdc_cnt = 0;
    //unsigned int vout = 0;
    signed int vout = 0;
    //==================================================================
    //
    //                          I2C Interface
    //
    //==================================================================

    //static void Delay_us(unsigned char n) 
    //{
        //__delay_us(2);
        /*
        while(n--){
            asm("nop");    //1 nop is about 1us
        }
         */

        //unsigned char a;
        //a = 2;
        //while(a!=0)
        //{    
        //a--;
        //}    
    //}

    /******************************************************
    name: void Start(void)
    brief: send START condition
    input: none
    output: none
     *****************************************************/
    static void Start(void) 
    {
        SDA_1;
        SCL_1; //bus free
        __delay_us(3);
        SDA_0;
        __delay_us(3);
        SCL_0;
    }

    /******************************************************
    name:void Stop(void)
    brief:send STOP condition
    input: none
    output:none
     *****************************************************/
    static void Stop(void) 
    {
        SCL_0;
        SDA_0;
        __delay_us(3);
        SCL_1;
        __delay_us(3);
        SDA_1;
    }

    /***********************************************
    //Send one byte to the IIC bus
     ********************************************************/
    static void Send_abyte(unsigned char data) 
    {
        unsigned char i = 0;

        for (i = 0; i < 8; i++)//8 bits
        {
            SCL_0; //let the SDA change the bit to be sent
            __delay_us(3);//system function        
            if (data & 0x80)//current bit is 1
            {
                SDA_1;
            } 
            else 
            {
                SDA_0;
            }
            data <<= 1;
            __delay_us(3);
            SCL_1; //send the current bit
            __delay_us(1);
            //delay_us(2);
        }
        SCL_0; //set SCL to 0 to prepare to send the next byte, that is, let the SDA change data
    }

    /*************************************************
     * wait for answer from the slave
     *****************************************************/
    static unsigned char Wait_answer(void)
    {
        unsigned char i = 0;

    //将SDA改为输入模式,获取从机的ACK 信号
        TRIS = 0x2C; //0010 1100 set SDA to input mode, GP5: SDA    
        __delay_us(3);
        SCL_1;
        __delay_us(3);
        while ((in_SDA != 0)&&(i < 100))//wait for the SDA to be 0(slave device will set SDA to 0 to acknowledge a byte) while SCL is high level
        {
            //Prints("No ack...",1);
            i++;
            if (i >= 96) {
                return 11; //timeout and no response from the slave
            }
        }
        SCL_0; //set SCL to 1 to prepare to send the next byte
        __delay_us(10);

    //将SDA恢复为输出模式
        TRIS = 0x0C; //0000 1100 set the SDA to output mode, GP5: SDA    
        __delay_us(10);
        //prints("ack...",1);
        return 0;
    }

    /****************************************************
    //Send one byte to the slave device
     *******************************************************/
    /*
    void Write_abyte(unsigned char addr,unsigned char data)
    {
        Start();         //start condition
        Send_abyte(I2C_SLAVE_ADDRESS);//send the device address
        Wait_answer();   //wait ACK
        Send_abyte(addr);//send the address    
        Wait_answer();
        Send_abyte(data);//send the data
        Wait_answer();
        Stop();         //stop condition
        //Delayxms(10);    
    }
     */

    /****************************************************
    //Send the data needed to adjust the voltage
     *******************************************************/
    static void IIC_Transmit(unsigned char *data, unsigned char size) 
    {
        Start(); //start condition
        Send_abyte(I2C_SLAVE_ADDRESS << 1); //send the device address
        Wait_answer(); //wait ACK
        while (size--) 
        {
            Send_abyte(*data); //send the data
            Wait_answer();
        }
        Stop(); //stop condition
        __delay_us(200);
        //Delayxms(10);    
    }

    #if PEC
    /***************************************************************************//**
     * @brief   Calculate the Packet Error Checking byte.
     * @param   PMBusMaster_CRC Initial value.
     * @param    PMBusMaster_Poly The polynomial to use for the calculation.
     * @param    *PMBusMaster_PMsg Pointer to the bytes from the PMBus transaction.
     * @param    PMBusMaster_MsgSize Number of bytes in the last transaction.
     * @return  The PEC byte.
     ******************************************************************************/

    static unsigned char PMBusMaster_Crc8MakeBitwise(unsigned char PMBusMaster_CRC, unsigned char PMBusMaster_Poly, unsigned char *PMBusMaster_Pmsg, unsigned int PMBusMaster_MsgSize) 
    {
        unsigned int i, j, carry;
        unsigned char msg;

        PMBusMaster_CRC = *PMBusMaster_Pmsg++; // first byte loaded in "crc"        
        for (i = 0; i < PMBusMaster_MsgSize - 1; i++) 
        {
            msg = *PMBusMaster_Pmsg++; // next byte loaded in "msg"

            for (j = 0; j < 8; j++) 
            {
                carry = PMBusMaster_CRC & 0x80; // check if MSB=1            
                PMBusMaster_CRC = (PMBusMaster_CRC << 1) | (msg >> 7); // Shift 1 bit of next byte into crc
                if (carry) PMBusMaster_CRC ^= PMBusMaster_Poly; // If MSB = 1, perform XOR
                msg <<= 1; // Shift left msg byte by 1
                msg &= 0x00FF;
            }
        }
        // The previous loop computes the CRC of the input bit stream. To this, 
        // 8 trailing zeros are padded and the CRC of the resultant value is 
        // computed. This gives the final CRC of the input bit stream.
        for (j = 0; j < 8; j++) 
        {
            carry = PMBusMaster_CRC & 0x80;
            PMBusMaster_CRC <<= 1;
            if (carry) PMBusMaster_CRC ^= PMBusMaster_Poly;
        }

        PMBusMaster_CRC &= 0x00FF; //We only want one byte (lower)

        return (PMBusMaster_CRC);
    }

    /***************************************************************************//**
     * @brief   Calculate the Packet Error Checking byte.
     * @param   data: the data buffer to be calculated.
     * @param    data_len: data length of the buffer.
     * @return  The PEC byte.
     ******************************************************************************/

    static unsigned char CRC8(unsigned char *data, int data_len) 
    {
        unsigned char data_in;
        unsigned char i = 0;
        unsigned char crc = 0x00; //initial value
        unsigned char crc_poly = 0x07; //the polynomial
        while (data_len--) 
        {
            data_in = *data++;
            crc = crc ^ data_in;
            for (i = 0; i < 8; i++) 
            {
                if (crc & 0x80)//the bit 7 is 1 then we XOR the polynomial and then left shift one bit
                {
                    crc = (crc << 1) ^ crc_poly;
                } else//directly left shift one bit
                {
                    crc = crc << 1;
                }
            }
        }

        return (crc ^0x00);
    }


    #endif

    /******************************************************************************//**
     * @brief   Set the output voltage of the Power module.
     * @param   PMBus_Volt: The voltage to be set, format: linear16.
     *          Linear 16: Mode = 000, N = 10100(-12,signed), V = Mantissa(unsigned), 
     *          actual voltage = V*2^N, for example, if we set output voltage to 48V
     *          the V = 48/2^-12 ==> V = 196608, not a valid mantissa value. 
     *          according to the sent data: 44 21 66 02 6E(HEX format), we can infer that
                the N = -9 when output voltage is 1.2V 
     * @return  None.
     ******************************************************************************/

    static void Adjust_Voltage(unsigned int volt)
    {
        unsigned char crc8 = 0;    
        
        PMBusMaster_TransmitBuffer[0] = I2C_SLAVE_ADDRESS << 1;
        PMBusMaster_TransmitBuffer[1] = VOUT_COMMAND;
        PMBusMaster_TransmitBuffer[2] = volt & 0x00FF; //lower byte of voltage
        PMBusMaster_TransmitBuffer[3] = volt >> 8; //higher byte of voltage
        //crc8=PMBusMaster_Crc8MakeBitwise(CRC8_INIT_REM,CRC8_POLY,PMBusMaster_TransmitBuffer,4);
        crc8 = CRC8(PMBusMaster_TransmitBuffer, 4); //This method is proved right
        PMBusMaster_TransmitBuffer[0] = VOUT_COMMAND;
        PMBusMaster_TransmitBuffer[1] = volt & 0x00FF; //lower byte of voltage
        PMBusMaster_TransmitBuffer[2] = volt >> 8; //higher byte of voltage
        PMBusMaster_TransmitBuffer[3] = crc8;
        
        /*
        PMBusMaster_TransmitBuffer[0] = VOUT_COMMAND;
        PMBusMaster_TransmitBuffer[1] = 0x00; //lower byte of voltage
        if(volt==V56)
        {
            PMBusMaster_TransmitBuffer[2] = 0x6E; //higher byte of voltage
            PMBusMaster_TransmitBuffer[3] = 0x6A;    
        }
        else if(volt == V45)
        {
            PMBusMaster_TransmitBuffer[2] = 0x5A; //higher byte of voltage
            PMBusMaster_TransmitBuffer[3] = 0xE6;
        }
        else //no action
        {
            //TODO        
        }
         */

        //send the buffer
        //Here we don't call the IIC_Transmit function directly, instead we copy the conent of it
        //to eliminate the potential hardware overflow error.
        //warning: (1393) possible hardware stack overflow detected; estimated stack depth: 3
        //IIC_Transmit(PMBusMaster_TransmitBuffer, 4);
        if(volt > 0 && volt <= 28160)
        {
            Start(); //start condition
            Send_abyte(I2C_SLAVE_ADDRESS << 1); //send the device address
            Wait_answer(); //wait ACK
            for (crc8 = 0; crc8 < 4;  crc8++) 
            {
                Send_abyte(PMBusMaster_TransmitBuffer[crc8]); //send the data
                Wait_answer();
            }
            Stop(); //stop condition
            __delay_us(200);    
        }
        /*
        unsigned char crc8=0;
        PMBusMaster_TransmitBuffer[0]=VOUT_COMMAND;
        PMBusMaster_TransmitBuffer[1]=volt&0x00FF;//lower byte of voltage
        PMBusMaster_TransmitBuffer[2]=volt>>8;//higher byte of voltage
        PMBusMaster_CrcMsg[0] = I2C_SLAVE_ADDRESS << 1;        // 1st CRC byte = slave address...
        PMBusMaster_CrcMsg[1]=PMBusMaster_TransmitBuffer[0];
        PMBusMaster_CrcMsg[2]=PMBusMaster_TransmitBuffer[1];
        PMBusMaster_CrcMsg[3]=PMBusMaster_TransmitBuffer[2];
        crc8=PMBusMaster_Crc8MakeBitwise(CRC8_INIT_REM,CRC8_POLY,PMBusMaster_CrcMsg,4);
        PMBusMaster_TransmitBuffer[3]=crc8;
         */
    }

    /***************************************************************************//**
     * @brief   Initialize the GPIO that we need.
     * @param   None.
     * @return  None.
     ******************************************************************************/

    void GPIO_Init(void) 
    {
        TRIS = 0x0C; //0b00001100; GP3(RESET) can only be used as input mode    
        CM1CON0 = 0; //close the comparator
        GPIO = 0x30; //0b00110000;GP4: SCL, GP5: SDA, GP2: ADC
    }

    /***************************************************************************//**
     * @brief   Initialize the ADC module.
     * @param   None.
     * @return  None.
     ******************************************************************************/

    void ADC_Init(void) 
    {
        /*
         * bit 7-6 ANS<1:0>: ADC Analog Input Pin Select bits(1), (2)
            00 = No pins configured for analog input
            01 = AN2 configured as an analog input
            10 = AN2 and AN0 configured as analog inputs
            11 = AN2, AN1 and AN0 configured as analog inputs
         */
        //ADCON0=0b00000000; //the highest two bits are zero that are digital port

        //Set up ADC    
        ADCON0bits.ADCS = 0x01; // select ADC conversion clock select as Fosc/8 where FOSC = 8MHz
        ADCON0bits.ANS = 0x01; // AN2 configured as an analog input
        ADCON0bits.CHS = 0x02; // This selects which analog input pin(s) to use for the ADC conversion(s)
        // for this example we are using GP2 as our input
        ADCON0bits.ADON = 1; // ADC is powered-on       
    }

    /***************************************************************************//**
     * @brief   Initialize the timer0 module.
     * @param   None.
     * @return  None.
     ******************************************************************************/

    void Timer0_Init(void) 
    {
        /*OPTION register configuration
         *  bit 7 GPWU: Enable Wake-up On Pin Change bit (GP0, GP1, GP3)
            1 = Disabled
            0 = Enabled
            bit 6 GPPU: Enable Weak Pull-Ups bit (GP0, GP1, GP3)
            1 = Disabled
            0 = Enabled
            bit 5 T0CS: Timer0 Clock Source Select bit
            1 = Transition on T0CKI pin
            0 = Internal instruction cycle clock (CLKOUT)
            bit 4 T0SE: Timer0 Source Edge Select bit
            1 = Increment on high-to-low transition on T0CKI pin
            0 = Increment on low-to-high transition on T0CKI pin
            bit 3 PSA: Prescaler Assignment bit
            1 = Prescaler assigned to the WDT
            0 = Prescaler assigned to Timer0
            bit 2-0 PS<2:0>: Prescaler Rate Select bits
         */
        //TRISGPIO = 0x08;      //G0,G1,G2,G4,G5 as output mode,G3 as input mode
        //GPIO = 0x00;          //turn off all led
        //TMR0 = 0x3F;          //initialization timer,timing 0.05second
        OPTION = 0xD7; //11010111, time0 select internal clock,Fosc/4, prescale=1:256
        TMR0 = 0xB4; //178 instruction cycles,  Need 180 but Timer 0 looses 2 at load. initialization timer,timing about 0.01second
    }

    /***************************************************************************//**
     * @brief   Get the ADC value.
     * @param   None.
     * @return  None.
     ******************************************************************************/

    static void Read_ADC_Value(void) 
    {
        //unsigned int ADCValue;
        //if (cSample_flag == 0) 
        //{
            ADCON0bits.GO = 1; // start conversion
            __delay_us(2);
            while (ADCON0bits.GO); // wait for conversion to finish        
            //ADCValue = ADRESH << 8;       // get the 2 msbs of the result and rotate 8 bits to the left
            //ADCValue = ADCValue + ADRESL; // now add the low 8 bits of the resut into our return variable
            ADC_Sample += ADRES; // return the 8-bit result in a single variable

            if (++cAdc_cnt >= 32) 
            {
                //cSample_flag = 1;
                cAdc_cnt = 0;
                //cSample_flag |= (1 << 7); //set the conversion finished flag
                ADC_Result = (unsigned char) (ADC_Sample >> 5);
                ADC_Sample = 0;
            }
        //}
    }

    static void ADC_Process(void) 
    {
        Read_ADC_Value();
        
        //if (cSample_flag)
        //{
            //cSample_flag = 0;
        //}
        //ADC_GO();
        //while (ADC_IS_BUSY);//wait while the ADGO bit is 1
        //adc_result = ADC_GetADCResult();    
    }

    /***************************************************************************//**
     * @brief   Initialize the system oscillator.
     * @param   None.
     * @return  None.
     ******************************************************************************/

    void OSC_Init(void) 
    {
        /*
         * bit 5 IOSCFS: Internal Oscillator Frequency Select bit
        1 = 8 MHz INTOSC speed
        0 = 4 MHz INTOSC speed
        bit 4 MCLRE: Master Clear Enable bit
        1 = GP3/MCLR pin functions as MCLR
        0 = GP3/MCLR pin functions as GP3, MCLR internally tied to VDD
        bit 3 CP: Code Protection bit
        1 = Code protection off
        0 = Code protection on
        bit 2 WDTE: Watchdog Timer Enable bit 
        1 = WDT enabled
        0 = WDT disabled
        bit 1-0 FOSC<1:0>: Oscillator Selection bits
        00 = LP oscillator with 18 ms DRT
        01 = XT oscillator with 18 ms DRT
        10 = INTOSC with 1.125 ms DRT (2)
        11 = EXTRC with 1.125 ms DRT (2)
         */
    }

    int main(void) 
    {
        OSC_Init();
        GPIO_Init();
        ADC_Init();
        Timer0_Init();
        while (1) 
        {
            ADC_Process();
            if (TMR0 == 0xFF) //Timer0溢出
            {
                if ((++cTimer & 0x3F) >= 5)//50ms timeout
                {
                    //GP0=~GP0;
                    //due to RAM limitation, we can't use float variable along with DIV and Multiply instructions.
                    //Adjust_Voltage(V56);
                    // vout =(unsigned int) (45.0*ADC_Result/177.0);
                    if(ADC_Result <= 75)//If input voltage is less than or equal to 1V, set the deault output: 50V
                    {
                        vout = 25600;
                        Adjust_Voltage(vout);
                    }
                    else
                    {
                        vout = (ADC_Result-176)*130+45*512; //130 means the step the target by which the voltage(linear 16 format) is adjusted per 1 ADC value 
                        if(vout > 28160)//55V maximum
                        {
                            vout = 28160;
                        }
                        else if(vout < 23040) //45V minumum
                        {
                            vout = 23040;
                        }
                        Adjust_Voltage(vout);
                    }
                    
                    /*if (((ADC_Result > V56_OUT - DEVIATION) && (ADC_Result < V56_OUT + DEVIATION))
                            ||(ADC_Result >= V56_OUT))//56V output
                    {
                        GP0 = 1;
                        if ((cTimer & 0x80) == 0)//56V output bit is not set
                        {
                            Adjust_Voltage(0x6E00);
                            cTimer |= (1 << 7); //set the 56V output bit
                            cTimer &= ~(1 << 6); //clear the 45V output bit
                        }
                    } else if ((ADC_Result > V45_OUT - DEVIATION) && (ADC_Result < V45_OUT + DEVIATION))//45V output
                    {
                        GP0 = 0;
                        if ((cTimer & 0x40) == 0)//45V output bit is not set
                        {
                            Adjust_Voltage(0x5A00);
                            cTimer |= (1 << 6); //set the 45V output bit
                            cTimer &= ~(1 << 7); //clear the 56V output bit
                        }
                    } else //any other value, we don't adjust the output voltage
                    {
                        //TODO
                        GP0 = 0;
                        //Adjust_Voltage(0x5A00);
                    }*/
                    cTimer &= 0xC0; //clear the lower 4 bits
                }
                TMR0 = 0xB4; //reload the initial value of timer0
            }
            __delay_ms(1);
        }

        return 0;
    }
     

  • 相关阅读:
    【重识云原生】第四章云网络4.7.6节——virtio-blk存储虚拟化方案
    MIKE水动力笔记12_数字化海图1之提取确值水深点
    Mybatis中的#{}和${}的区别
    Word控件Spire.Doc 【图像形状】教程(3) :在 C#/VB.NET 中的指定位置插入图像
    思腾云计算
    通讯网关软件011——利用CommGate X2ODBC实现DDE数据转入ODBC
    面向对象编程——类与对象(C#)(未写完)
    球场输了?卡塔尔背地里可能赢麻了!
    GDPU 数据结构 天码行空4
    【JAVA】强引用、软引用、弱引用、幻象引用有什么区别?
  • 原文地址:https://blog.csdn.net/jmmx/article/details/133858991