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;
}
 

你可能感兴趣的:(c语言,单片机,嵌入式硬件)