最近在做一个输出可调压的电源软件项目,使用的单片机是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;
}