數(shù)控電源學(xué)習(xí)-軟件代碼分解學(xué)習(xí)
首先從AD采樣端口講起,目前樂工的數(shù)控電源,AD采樣端口采用的是P1.1和P1.2.這兩個端口一個用于輸出電壓采樣,一個用于輸出電流采樣.
硬件原理圖很簡單,真正的重點是ADC端口的配置以及端口數(shù)據(jù)采集轉(zhuǎn)換的流程.正式開始,樂工的數(shù)控電源采用的單片機型號為STC15W4K16S4,該型號的單片機目前是STC公司最強的單片機.查找STC技術(shù)手冊,在ADC一章,可以看到ADC采樣端口內(nèi)部的拓撲結(jié)構(gòu),最上面為ADC各功能寄存器,最左邊為具有ADC采樣功能的單片機端口,中間為比較器以及DAC轉(zhuǎn)換模塊,再往右為逐次比較寄存器,是該ADC端口采樣的方式,最右邊為ADC采樣結(jié)果保存寄存器.
由ADC端口采樣拓撲結(jié)構(gòu),我們可以獲得很多的信息.第一,該ADC端口采樣位數(shù)為10位,這一指標直接影響ADC采樣的精度,專業(yè)術(shù)語為ADC采樣分辨率,由該技術(shù)指標,我們可以計算出數(shù)控電源能采樣的最小輸入電壓.因為單片機為5V供電,則ADC采樣口最大輸入電壓為5V,根據(jù)Vadc(min)=5*(1/2^10)=4.88mV,則該單片機能分辨的最小輸入電壓為4.88mV.
根據(jù)上圖我們能獲得的第二個信息是,該型號的單片機具有ADC采樣功能的端口為P1.0口,所以當電路中需要進行ADC采樣時,需要使用P1.0口.
獲得的第三個信息是,能控制ADC采樣口的特殊功能寄存器包括ADC_POWER(ADC電源控制寄存器),SPEED1,SPEED0(ADC采樣速度寄存器),ADC_FLAG(ADC轉(zhuǎn)換結(jié)束標志位),ADC_START(ADC轉(zhuǎn)換啟動控制位),CHS2,CHS1,CHS0(模擬通道選擇控制位)等.
獲得的第四個信息是ADC采樣數(shù)據(jù)保存寄存器包括ADC_RES,ADC_RESL.由該寄存器的使用,我們就知道所謂10位ADC采樣的來源,其實就是兩個8位的寄存器組合起來.不過有一個寄存器只使用2位,另一個寄存器使用8位,具體哪個寄存器使用2位,哪個寄存器使用8位是由另一個寄存器CLK_DIV中的ADRJ位控制的.
至此該單片機的ADC采樣端口控制寄存器已經(jīng)講述完畢,大家可以參考STC的技術(shù)手冊,上述有詳細的講解.
接著上樂工的程序,按照樂工的源代碼,首先是ADC端口的宏定義,這個沒有什么疑問.
#define ADC_POWER 0X80//ADC電源控制位
#define ADC_FLAG 0X10//ADC轉(zhuǎn)換結(jié)束標志
#define ADC_START 0X08//ADC轉(zhuǎn)換啟動控制位
#define ADC_SPEEDLL 0X00//ADC轉(zhuǎn)換速度540個時鐘周期
#define ADC_SPEEDL 0x20//ADC轉(zhuǎn)換速度360個時鐘周期
#define ADC_SPEEDH 0x40//ADC轉(zhuǎn)換速度180個時鐘周期
#define ADC_SPEEDHH 0x60//ADC轉(zhuǎn)換速度90個時鐘周期
#define ADC_CH7 0X07//ADC轉(zhuǎn)換位數(shù)
接著是功能函數(shù)的申明
void InitAdc();//ADC端口初始化
uint GetAdc10BitResult(uchar channel);//ADC采樣數(shù)據(jù)讀取
再其次是各功能函數(shù)
ADC端口初始化
void InitAdc()
{
P1ASF=0XFF;
//設(shè)置P1口為AD口ADC_RES=0X00;//AD轉(zhuǎn)化寄存器清零
ADC_RESL=0X00;//AD轉(zhuǎn)化寄存器清零
ADC_CONTR=ADC_POWER|ADC_SPEEDLL;
//打開AD電源,確定AD轉(zhuǎn)換速率delay_1ms(1);
}
上述程序都沒有問題,但是下面的ADC采樣數(shù)據(jù)讀取,我就有疑問了,希望樂工以及群里的高手能解答一下,謝謝.
//讀取ADC采樣結(jié)果 uint GetAdc10BitResult(uchar AdcNum) { uint AdcResult; uchar i; if(AdcNum>ADC_CH7)//ADC采樣位數(shù)判斷 return 1024; ADC_RES=0X00;//AD轉(zhuǎn)化寄存器清零 ADC_RESL=0X00;//AD轉(zhuǎn)化寄存器清零 ADC_CONTR=(ADC_CONTR&0XE0)|ADC_START|AdcNum; _nop_(); _nop_(); _nop_(); _nop_(); for(i=0;i<250;i++) { if(ADC_CONTR&ADC_FLAG) { ADC_CONTR=ADC_CONTR&(~ADC_FLAG); if(PCON2&(0X01<<5)) { AdcResult=(uint)(ADC_RES&0X03); AdcResult=(AdcResult<<8)|ADC_RESL; } else { AdcResult=(uint)(ADC_RES); AdcResult=(AdcResult<<2)|(ADC_RESL&0X03); } return AdcResult; } } return 1024; }
這里的疑問是ADC_CH7等于7,也就是當ADC_NUM大于7是,就不執(zhí)行下面的代碼,該功能函數(shù)直接返回1024,我有兩個疑問,第一為什么采樣的位數(shù)大于7就不執(zhí)行,為什么不是大于10.第二個疑問是返回值為什么是1024,不是0.
第二個疑問是下面這段代碼
為什么i需要小于250,這段代碼因為沒有搞懂它的作用,所以后面的代碼看的云里霧里,不知道是是什么作用,希望大家能解答,謝謝.今天我就講到這里,我把我懂得東西貼出來,把我不懂的東西也貼出來,希望大家能指點指點我.
將重新整理注釋的ADC采樣代碼上傳,供大家參考,其實還是樂工的功勞,我自己只是把它貼出來用于消化而已
#include "Ad.h" #include "Delay.h" //ADC端口初始化 void InitAdc() { P1ASF=0XFF;//設(shè)置P1口為AD口 ADC_RES=0X00;//AD轉(zhuǎn)化寄存器清零 ADC_RESL=0X00;//AD轉(zhuǎn)化寄存器清零 ADC_CONTR=ADC_POWER|ADC_SPEEDLL;//打開AD電源,確定AD轉(zhuǎn)換速率 delay_1ms(1); } //讀取ADC采樣結(jié)果 uint GetAdc10BitResult(uchar AdcNum) { uint AdcResult; uchar i; if(AdcNum>ADC_CH7)//ADC采樣位數(shù)判斷 return 1024; ADC_RES=0X00;//AD轉(zhuǎn)化寄存器清零 ADC_RESL=0X00;//AD轉(zhuǎn)化寄存器清零 ADC_CONTR=(ADC_CONTR&0XE0)|ADC_START|AdcNum;//ADC寄存器配置 _nop_(); _nop_(); _nop_(); _nop_(); for(i=0;i<250;i++)// { if(ADC_CONTR&ADC_FLAG)//數(shù)模轉(zhuǎn)換結(jié)束 { ADC_CONTR=ADC_CONTR&(~ADC_FLAG);//ADC采樣寄存器置零 if(PCON2&(0X01<<5))//ADRJ為1 { AdcResult=(uint)(ADC_RES&0X03);//采樣數(shù)據(jù)高兩位保存于ADC_RES AdcResult=(AdcResult<<8)|ADC_RESL;//采樣數(shù)據(jù)低八位保存于ADC_RESL } else { AdcResult=(uint)(ADC_RES);//采樣數(shù)據(jù)低八位保存于ADC_RES AdcResult=(AdcResult<<2)|(ADC_RESL&0X03);//采樣數(shù)據(jù)高兩位保存于ADC_RESL } return AdcResult; } } return 1024; }
第一個寄存器為IAP_DATA,作用為保存讀寫數(shù)據(jù).
第二個寄存器為IAP_ADDRH,IAP_ADDRL,作用為ISP_IAP操作時的地址寄存器高八位和低八位
第三個寄存器是ISP_IAP命令寄存器IAP_CMD,作用是對EEPROM中的數(shù)據(jù)存儲區(qū)進行讀,編程,擦除操作
第四個寄存器為IAP_TRIG,當需要進行IAP操作時,都需要先將IAP_FLAG寫入5A再寫入A5,才能接著進行IAP操作,該操作看手冊我沒有看的明白,需要結(jié)合實際的代碼來分析
第五個寄存器為IAP_CONTR,為IAP控制寄存器,用于控制IAP的使能,復(fù)位起始地址,讀,擦除,編程時間等,該寄存器是EEPROM中比較關(guān)鍵的寄存器之一
第六個寄存器為電源控制寄存器PCON,當檢測到電源電壓過低時,不進行EEPROM_IAP操作
首先依舊是老套路,寄存器的宏定義
sfr IAP_DATA = 0xC2; sfr IAP_ADDRH = 0xC3; sfr IAP_ADDRL = 0xC4; sfr IAP_CMD = 0xC5; sfr IAP_TRIG = 0xC6; sfr IAP_CONTR = 0xC7; sfr ISP_DATA = 0xC2; sfr ISP_ADDRH = 0xC3; sfr ISP_ADDRL = 0xC4; sfr ISP_CMD = 0xC5; sfr ISP_TRIG = 0xC6; sfr ISP_CONTR = 0xC7; #define CMD_IDLE 0X00//空閑模式 #define CMD_READ 0X01//Iap字節(jié)讀命令 #define CMD_PROGRAM 0X02//Iap字節(jié)編程命令 #define CMD_ERASE 0X03//Iap扇區(qū)擦除命令 #define ENABLE_IAP 0X80//Iap使能設(shè)置 //#define ENABLE_IAP 0x80//if SYSCLK<30MHz //#define ENABLE_IAP 0x81//if SYSCLK<24MHz //#define ENABLE_IAP 0x82//if SYSCLK<20MHz //#define ENABLE_IAP 0x83//if SYSCLK<12MHz //#define ENABLE_IAP 0x84//if SYSCLK<6MHz //#define ENABLE_IAP 0x85//if SYSCLK<3MHz //#define ENABLE_IAP 0x86//if SYSCLK<2MHz //#define ENABLE_IAP 0x87//if SYSCLK<1MHz #define SetVoltageAddress 0X0005// #define SetCurrentAddress 0X0205//
接著是各功能函數(shù)的申明
void IapIdle();//關(guān)閉Iap功能 void IapEraseSector(uint addr);//扇區(qū)擦除 uchar IapReadByte(uint addr);//從Isp/Iap/EEPROM中讀取一字節(jié) IapReadByte16BitData(uint adr);//從Isp/Iap/EEPROM中讀取兩字節(jié) void IapProgramByte(uint addr,uchar dat);//向Isp/Iap/EEPROM中寫一字節(jié) void IapProgram16BitData(uint adr, uint dat);////向Isp/Iap/EEPROM中寫兩字節(jié)
接下來是各功能函數(shù)的具體實現(xiàn),樂工參考了STC單片機的例程,我把樂工的代碼貼出來同時把我的疑問也寫出來,希望大家指點,謝謝先.
首先是
void IapIdle()//關(guān)閉Iap功能 { IAP_CONTR=0X00;//關(guān)閉IAP功能 IAP_CMD=0X00;//清除命令寄存器 IAP_TRIG=0X00;//清除觸發(fā)寄存器 IAP_ADDRH=0X80;//將地址設(shè)置到非IAP區(qū)域 IAP_ADDRL=0X00; }
在該函數(shù)中IAP_ADDRH=0X80;//將地址設(shè)置到非IAP區(qū)域
IAP_ADDRL=0X00;
這里沒有看懂,不知道將地址設(shè)置到非IAP區(qū)域是什么意思,查閱STC技術(shù)手冊,看到是否與IAP字節(jié)讀時EEPROM結(jié)束扇區(qū)末尾地址有關(guān),望樂工解答,先謝謝樂工.
接著是扇區(qū)擦除操作代碼
void IapEraseSector(uint IapAddress0)//扇區(qū)擦除 { IAP_CONTR=ENABLE_IAP;//使能IAP IAP_CMD=CMD_ERASE;//設(shè)置IAP命令 IAP_ADDRL=IapAddress0;//設(shè)置IAP低地址 IAP_ADDRH=IapAddress0>>8;//設(shè)置IAP高地址 IAP_TRIG=0x5a;//寫觸發(fā)命令(0x5a) IAP_TRIG=0xa5;//寫觸發(fā)命令(0xa5) _nop_();//等待ISP/IAP/EEPROM操作完成 IapIdle(); }
接著是讀一字節(jié)數(shù)據(jù)
//從Isp/Iap/EEPROM中讀取一字節(jié) uchar IapReadByte(uint IapAddress1) { uchar BufferData0;//緩沖數(shù)據(jù) IAP_CONTR=ENABLE_IAP;//使能IAP IAP_CMD=CMD_READ;//設(shè)置IAP命令 IAP_ADDRL=IapAddress1;//設(shè)置IAP低地址 IAP_ADDRH=IapAddress1>>8;//設(shè)置IAP高地址 IAP_TRIG=0x5a;//寫觸發(fā)命令(0x5a) IAP_TRIG=0xa5;//寫觸發(fā)命令(0xa5) _nop_();//等待ISP/IAP/EEPROM操作完成 BufferData0=IAP_DATA;//讀ISP/IAP/EEPROM數(shù)據(jù) IapIdle();//關(guān)閉IAP功能 return BufferData0; //返回 }
//從Isp/Iap/EEPROM中讀取兩字節(jié) IapReadByte16BitData(uint IapAddress2) { uint BufferData16Bit;//緩沖數(shù)據(jù) BufferData16Bit=IapReadByte(IapAddress2); BufferData16Bit<<=8; IapAddress2++; BufferData16Bit=BufferData16Bit|IapReadByte(IapAddress2); return BufferData16Bit;}
該代碼中IapAddress2++作用是否是將該數(shù)值加一后用于存儲下一段數(shù)據(jù)
//向Isp/Iap/EEPROM中寫一字節(jié) void IapProgramByte(uint IapAddress3,uchar BufferData1) { IAP_CONTR=ENABLE_IAP;//使能IAP IAP_CMD=CMD_PROGRAM;//設(shè)置IAP命令 IAP_ADDRL=IapAddress3;//設(shè)置IAP低地址 IAP_ADDRH=IapAddress3>>8;//設(shè)置IAP高地址 IAP_DATA=BufferData1;//寫ISP/IAP/EEPROM數(shù)據(jù) IAP_TRIG=0x5a;//寫觸發(fā)命令(0x5a) IAP_TRIG=0xa5;//寫觸發(fā)命令(0xa5) _nop_();//等待ISP/IAP/EEPROM操作完成 IapIdle(); } //向Isp/Iap/EEPROM中寫兩字節(jié) void IapProgram16BitData(uint IapAddress4, uint BufferData16Bit) { uchar DataTemp1,DataTemp2; DataTemp1=BufferData16Bit/256; DataTemp2=BufferData16Bit%256; IapProgramByte(IapAddress4, DataTemp1); IapAddress4++; IapProgramByte(IapAddress4, DataTemp2); }
這幾段代碼的整理以及消化花的時間不多,相對于昨天ADC采樣來說,簡單了一點.
這里我很感謝也很感激樂工,每次我有問題,他都能及時給我解答給我?guī)椭?我很感謝樂工.謝謝
STC15系列自帶六路PWM輸出,功能還是比較強大的.首先我們還是先要認識一下PWM輸出功能寄存器的配置.
第一個寄存器為P_SW2,端口配置寄存器.左右為用于擴展SFR訪問控制使能,說的直白一點就是,如果單片機想訪問外部特殊功能寄存器SFR或者外部RAM,必須先將設(shè)置該寄存器.需要補充一點,外部功能寄存器不一定在單片機外部.因為現(xiàn)在單片機的功能比較強大,內(nèi)部集成了很多的模塊,所謂外部設(shè)備只是相對于內(nèi)部的RAM和SFR.其實說穿了就是單片機訪問的地址不同而已.
第二個寄存器為PWMCFG,PWM配置寄存器,該寄存器還是比較重要的.主要作用有兩個.第一個當PWM計數(shù)器歸零時是否觸發(fā)ADC轉(zhuǎn)換功能.第二個作用是設(shè)置PWM輸出口的初始電平.
第三個寄存器為PWMCR,PWM控制寄存器.該寄存器的作用也比較重要,主要作用為設(shè)置單片機IO口的狀態(tài),如果為0則為普通的單片機IO口(GPIO),如果為1則為PWM輸出口.該寄存器說穿了就是用于控制單片機IO的PWM功能的開啟與關(guān)閉.
第四個寄存器為PWMIF,PWM中斷標志寄存器,主要作用是當PWM發(fā)生翻轉(zhuǎn)時,通過該寄存器的設(shè)置,程序會進入相應(yīng)的中斷程序執(zhí)行相應(yīng)的程序.
第五個寄存器為PWMFDCR,PWM外部異常控制寄存器,該寄存器的作用是當PWM輸出口外部發(fā)生異常時(比如短路,過載,過壓等特殊情況),通過該寄存器的配置可以使PWM輸出口變?yōu)楦咦钁B(tài)輸入口.
第六個寄存器為PWM計數(shù)器,分為PWMCH和PWMCL,通過PWMCH和PWMCL來設(shè)置PWM輸出的周期.
第七個寄存器為PWMCKS,PWM時鐘選擇寄存器.作用很簡單,用于時鐘源的選擇以及預(yù)分頻作用.
第八個寄存器為PWM2翻轉(zhuǎn)計數(shù)器,主要用于設(shè)置PWM波形翻轉(zhuǎn)的時刻.
上述寄存器為STC15W4K系列的PWM輸出寄存器,上述寄存器的配置主要作用是控制IO口輸出PWM波形以及輸出的周期等
針對上述PWM輸出的寄存器配置,我想說說自己的理解.
其實上述寄存器配置貌似很多,細細想想,一點都不復(fù)雜.大家如果覺得不好理解,可以結(jié)合普通的PWM波形理解.對于普通的PWM波形,需要考慮的因素包括PWM的周期,導(dǎo)通和關(guān)閉的時間等.結(jié)合到單片機的PWM輸出,其實大同小異.首先是周期,那需要涉及的東西就是時鐘源,分頻器.考慮到高低電平的時間,那就需要通過計數(shù)器來實現(xiàn).再加上單片機IO的輸出狀態(tài)功能,那就需要涉及到IO口的功能配置等.最后當單片機PWM輸出發(fā)生翻轉(zhuǎn)時,是否需要執(zhí)行其他的動作,那就需要涉及到中斷,這是我的理解,大致上單片機IO的PWM輸出配置就是這些功能.
接下來我們上樂工的代碼,通過樂工的代碼來看看具體的寄存器的配置
#include"PWM10Bit.H" //PWM配置函數(shù) // 參數(shù): none // 返回: none void PWM10BitConfig(void) { u8 xdata *px; EAXSFR();//訪問XFR P_SW2 |= 0x80 PWMCR|=0x02;//PWM通道3為PWM輸出口,受PWM波形發(fā)生器控制 PWMCFG|=0x02;//PWM3輸出口初始電平1 P21=0;//端口P2^1 P2n_push_pull(0x01<<1);//P2^1口為強推挽輸出,P2M1&=~(bitn),P2M0|=(bitn)為01 PWMCR|=0x04;//PWM通道4為PWM輸出口,受PWM波形發(fā)生器控制 PWMCFG|=0x04;//PWM4輸出口初始電平1 P22=1;//端口P2^1 P2n_push_pull(0x01<<2);//P2^2口為強推挽輸出,P2M1&=~(bitn),P2M0|=(bitn)為01 px=PWMCH;//PWM計數(shù)器的高字節(jié) *px=1023/256; // *px=4095/256; px++; *px=1023%256;//PWM計數(shù)器的低字節(jié) // *px=4095%256;//PWM計數(shù)器的低字節(jié) px++;//PWMCKS,PWM時鐘選擇 *px=PwmClk_1T;//時鐘源: PwmClk_1T,PwmClk_2T, ... PwmClk_16T, PwmClk_Timer2 EAXRAM();//恢復(fù)訪問XRAM P_SW2 &= ~0x80 PWMCR|=ENPWM;//使能PWM波形發(fā)生器,PWM計數(shù)器開始計數(shù) PWMCR |= ENPWM; PWMCR|=ECBI;// 允許PWM計數(shù)器歸零中斷 }
上述代碼中,樂工申明了一個指針型變量*PX, 該變量的類型為整型,存儲區(qū)域為xdata.解釋一下xdata,所謂xdata只是用于指明該變量的保存區(qū)域為外部數(shù)據(jù)存儲區(qū).這里大家有疑問的地方可能第一個就是xdata這里.其實單片機一般的數(shù)據(jù)默認存儲區(qū)是通過編譯器來配置的(以keil為例).C51有三種存儲模式,分別為小模式,緊湊型模式和大模式.小模式對應(yīng)存儲類型為data型,緊湊型模式對應(yīng)存儲類型為pdata,大模式對應(yīng)存儲類型為xdata(該資料我引用與單片機原理及應(yīng)用(黃勤主編)).
接下來EAXSFR()這段代碼的作用為配置單片機的操作對象為擴展SFR區(qū)域,大家可以參考STC技術(shù)手冊.
在上述的代碼段中主要是用于PWM口的配置,沒有什么難點.
最后這段關(guān)于PWM中斷函數(shù)我覺得還是比較重要的,也是樂工整個項目真正的難點以及核心所在
//PWM中斷函數(shù) void PWM_int(void) interrupt PWM_VECTOR { u8 xdata *px; u8 SW2_tmp; if(PWMIF&CBIF)//PWM計數(shù)器歸零中斷標志 { PWMIF&=~CBIF;//清除中斷標志 SW2_tmp=P_SW2;//保存SW2設(shè)置 EAXSFR();//訪問XFR,P_SW2|=0x80,訪問擴展RAM中的寄存器必須EAXSFR=1 px=PWM3T2H; *px=OUT_Current_PWM/256; px++; *px=OUT_Current_PWM%256; px=PWM4T2H; *px=OUT_Voltage_PWM/256;//OUT_Current_PWM px++; *px=OUT_Voltage_PWM%256; P_SW2 = SW2_tmp;//恢復(fù)SW2設(shè)置 } }