華大MCU的應用中的問題記錄


這兩年芯片的價格越炒越貴,特別是像STM32,TI,NXP等等知名品牌更是水漲船高,甚至有時候你即使你願意花大價錢去購買,也不一定能買得到,所以很多公司紛紛轉向了國產芯片。國產芯片其實也不太好買得到,價錢也不便宜,而且可供參考的資料也是寥寥無幾,有時候你遇到了問題想上網找資料都很難找到可以參考的,這也是國產芯片的一個弱勢吧。

廢話不多說了,接下來談談華大單片機的一些應用吧。因為貨期和價格的問題,我所在公司的一些方案也開始轉向使用國產單片機,綜合來看,華大MCU是一個比較好的選擇。剛開始應用這個MCU的時候我遇到了不少問題,現在記錄下來,以防自己忘記了。

1、Timer0

華大MCU有提供庫函數,可以在KEIL或者IAR軟件上進行編程,大部分常用的外設都可以找到,像串口,定時器,I2C,SPI等等都可以找到demo code,可以直接上華大的官網查找。但是華大的庫函數提供的延時函數並不准確,延時1ms實際上延時了1.4ms左右。延時1us實際延時了2.4us。如下圖:

這個時間差距是有點離譜,連FAE也坦言一般情況下他們都不會用到這兩個延時,因為這兩個延時好像是利用內部RC振盪器分頻得到的,所以並不准確,且容易受到溫度的影響。

所以我動手寫了一個利用Timer0的精准延時,開始的設想是不利用中斷,因為利用中斷有些浪費資源了,但是實驗效果並不是特別好,延時不准確,所以我還是開啟了中斷,只是在沒有使用延時的時候,可以把時間設置得長一些,這樣可以避免頻繁進入中斷。假設要寫一個精准的微妙級別的延時函數,思路是這樣的,把Timer0的時鍾源選擇為PCLK1,然后通過分頻把頻率降到1MHz,這樣的話就可以CNT每隔1us就會增加一次,然后設置基准值寄存器(TMR0_CMPA<B>R
)的值,當CNT增加到基准值寄存器的數值時將會產生一個中斷。比如基准值寄存器設置為10,那就是10us會產生一個中斷,進入中斷后對某一個我們自定義的標志位進行置位,同時延時函數中等待這個標志位置位,等不到就死等,這樣就可以形成一個精准延時。具體代碼如下:

 

/* 定時器時鍾初始化 */
void TimerInit(void)
{  
   stc_tim0_base_init_t stcTimerCfg;
    stc_irq_regi_conf_t stcIrqRegiConf;
    stc_port_init_t stcPortInit;

    stc_clk_freq_t stcClkTmp;
    uint32_t u32tmp;

    MEM_ZERO_STRUCT(stcTimerCfg);
    MEM_ZERO_STRUCT(stcIrqRegiConf);
    MEM_ZERO_STRUCT(stcPortInit);


    /* Get pclk1 */
    CLK_GetClockFreq(&stcClkTmp);
    u32Pclk1 = stcClkTmp.pclk1Freq;

    /* Enable XTAL32 */
    CLK_Xtal32Cmd(Enable);

    /* Timer0 peripheral enable */
    ENABLE_TMR0();

    /*config register for channel B */
    stcTimerCfg.Tim0_CounterMode = Tim0_Sync;
    stcTimerCfg.Tim0_SyncClockSource = Tim0_Pclk1;
    stcTimerCfg.Tim0_ClockDivision = Tim0_ClkDiv2;
    stcTimerCfg.Tim0_CmpValue = (uint16_t)((u32Pclk1/1000000/2ul)*1000 - 1ul);
    TIMER0_BaseInit(TMR_UNIT,Tim0_ChannelB,&stcTimerCfg);

    /* Enable channel B interrupt */
    TIMER0_IntCmd(TMR_UNIT,Tim0_ChannelB,Enable);
    /* Register TMR_INI_GCMB Int to Vect.No.002 */
    stcIrqRegiConf.enIRQn = Int002_IRQn;
    /* Select I2C Error or Event interrupt function */
    stcIrqRegiConf.enIntSrc = TMR_INI_GCMB;
    /* Callback function */
    stcIrqRegiConf.pfnCallback = &Timer0B_CallBack;
    /* Registration IRQ */
    enIrqRegistration(&stcIrqRegiConf);
    /* Clear Pending */
    NVIC_ClearPendingIRQ(stcIrqRegiConf.enIRQn);
    /* Set priority */
    NVIC_SetPriority(stcIrqRegiConf.enIRQn, DDL_IRQ_PRIORITY_15);
    /* Enable NVIC */
    NVIC_EnableIRQ(stcIrqRegiConf.enIRQn);

    /*start timer0*/
    TIMER0_Cmd(TMR_UNIT,Tim0_ChannelB,Enable);

}
//中斷回調函數
void Timer0B_CallBack(void)
{
    /*同步計數方式中斷,該方式定時更加准確*/
    TimerFlag = 1;
}

 

 

//延時函數
void TIM0_CHB_Delay_us(uint16_t us)
{
        TMR_UNIT->CMPBR = (u32Pclk1/1000000/2ul)*us-1ul;
        TMR_UNIT->CNTBR = 0;
        TimerFlag = 0;
        while(!TimerFlag);    
        //延時時間到了,重新修改基准值寄存器的值,使其不頻繁進入中斷,不過不設置也是可以的
        TMR_UNIT->CMPBR = (u32Pclk1/1000000/2ul)*1000-1ul;
        TMR_UNIT->CNTBR = 0;
}                

 

 但是不知道什么原因,1us的時候並不准確,1us延時的時候實際測得是2.6us,但是1us以上就很精確了。

 

 2、GPIO

華大MCU的GPIO有一些默認是具備特殊功能的,PA13PA14PA15PB3PB4 端口復位后初始狀態為 JTAG/SWD 功能有效,我的板子剛好LED燈是接在PA15上的,所以當我的板子在上電的時候,就看到這個LED亮着,但又不是完全亮着,用萬用表量了電平是1.7V左右,無論我怎么操作這個引腳,這個引腳的電平就是不為所動。后面才知道原因是因為它默認就是特殊功能,如果要正常操作這個引腳,必須修改它的功能,步驟就是:

①需要先解鎖,才能對寄存器進行修改;

②因為要把這個引腳的默認狀態TDI修改到GPO,所以需要先使這個TDI的功能無效,具體是修改特殊控制寄存器(PSRCR)b3的值,從1改為0;

③功能選擇寄存器(PFSRxy,x=A,B,...,H,y=1,2,...,15)的FSEL[5:0]設為b000000,表示選擇為Func0

④上鎖

 

 

 具體代碼:

stc_port_init_t stcPortInit;
MEM_ZERO_STRUCT(stcPortInit);
    
stcPortInit.enPinMode = Pin_Mode_Out;
stcPortInit.enExInt = Enable;
stcPortInit.enPullUp = Disable;

PORT_Unlock();
M4_PORT->PSPCR = 0x17;
M4_PORT->PFSRA15 &= ~(0x3f);
PORT_Lock();

//#define LED1_PORT     (PortA)
//#define LED1_PIN     (Pin15)
PORT_Init(LED1_PORT, LED1_PIN, &stcPortInit);

3、UART

在這次項目中,UART我是直接從官方例程中移植到我的項目中,但是發現並沒有數據傳送出來,或者隔了很久才接收到板子上發出的一些錯誤的數據。所以我用KEIL仿真模式進行調試,發現程序死在了 BEAB BKPT 0xAB處,上網查找了資料,具體的原因我還是不太清楚,大概就是我使用了printf()函數,使用了半主機模式,就會出現這種情況,解決的辦法就是使用微庫,也就是MiclroLIB,即勾選上USE MiclroLIB,重新編譯即可。如圖:

 

這個我在使用STM32單片機的時候沒有遇到過,好像只要重定向了都可以。

4、SMBus

SMBus是一種類似I2C的協議,大多數情況下工程師都會選擇用模擬SMBus來進行通訊,當然也可以用硬件SMBus。在上一版項目中我也是用了模擬SMBus來實現通訊的,經過驗證並沒有問題,這一次我在移植過來的時候發現通訊不上了,用示波器和邏輯分析儀看過,還是不知道問題出在哪里(邏輯分析儀用得不熟練)。我用示波器看過官方的延時函數的精度,發現差距比較大,所以懷疑是延時函數的問題,導致時序出錯,所以我才研究了用定時器0(Timer0)做精准延時的函數(上面有講述),但是發現實際上還是沒有作用。后面我去慢慢一條一條地比較代碼,終於發現一點蛛絲馬跡,原來我在采集電池信息的時候,沒有參考上一版的程序,當時覺得寫得比較亂,所以在網上找了一點資料參考,邏輯還是一樣的,只是在某些地方延時不一樣,網上的資料延時比較短,當我完完全全復制我前一版代碼的時候,發現問題解決了!我勒個天,我調了好幾天沒調出來的問題居然是不夠自信,沒有參考自己的勞動成果造成的!

接下來,我把SMBus的代碼貼出來,除了自己以后可以參考,也希望可以幫到有需要的人,這個代碼是MCU作為主機通過SMBus跟電池通訊(電池的電源控制芯片是BQ4050,默認從機地址是0x16):

#include "SMBus.h"
#include "timer.h"
/********************************   SMBus 1   
*#define SMBus1_SCL_PORT		(PortB)
#define SMBus1_SCL_PIN			(Pin06)

#define SMBus1_SDA_PORT		(PortB)
#define SMBus1_SDA_PIN			(Pin07)

#define SMBus1_SCL_H				PORT_SetBits(SMBus1_SCL_PORT,SMBus1_SCL_PIN)

							
#define SMBus1_SCL_L				PORT_ResetBits(SMBus1_SCL_PORT,SMBus1_SCL_PIN)


#define SMBus1_SDA_H				PORT_SetBits(SMBus1_SDA_PORT,SMBus1_SDA_PIN)

#define SMBus1_SDA_L				PORT_ResetBits(SMBus1_SDA_PORT,SMBus1_SDA_PIN)


#define SMBus1_READ_SDA		PORT_GetBit(SMBus1_SDA_PORT,SMBus1_SDA_PIN)
*
*
********************************/
/*********************************
*函數名稱:void SMBus1_SDA_OUT(void)
*函數功能:SDA線的引腳配置為輸出
*函數形參:無
*函數返回值:無
*********************************/
void SMBus1_SDA_OUT(void)
{
		stc_port_init_t stcPortInit;
	  MEM_ZERO_STRUCT(stcPortInit);
    
    stcPortInit.enPinMode = Pin_Mode_Out;
    stcPortInit.enExInt = Enable;
    stcPortInit.enPullUp = Enable;
		stcPortInit.enPinDrv = Pin_Drv_H;
		stcPortInit.enPinOType = Pin_OType_Cmos;

		PORT_Init(SMBus1_SDA_PORT, SMBus1_SDA_PIN, &stcPortInit);
}
/*********************************
*函數名稱:void SMBus1_SDA_IN(void)
*函數功能:SDA線的引腳配置為輸入
*函數形參:無
*函數返回值:無
*********************************/
void SMBus1_SDA_IN(void)
{
		stc_port_init_t stcPortInit;
	  MEM_ZERO_STRUCT(stcPortInit);
    
    stcPortInit.enPinMode = Pin_Mode_In;
    stcPortInit.enExInt = Enable;
    stcPortInit.enPullUp = Enable;
	
		PORT_Init(SMBus1_SDA_PORT, SMBus1_SDA_PIN, &stcPortInit);
}

/*********************************
*函數名稱:void SMBus1_Init(void)
*函數功能:SDA和SCL初始化
*函數形參:無
*函數返回值:無
*備注:都配置為上拉推挽輸出(不上拉,開漏好像也沒影響)  
*********************************/
void SMBus1_Init(void)
{
		stc_port_init_t stcPortInit;
	  MEM_ZERO_STRUCT(stcPortInit);
    
    stcPortInit.enPinMode = Pin_Mode_Out;
    stcPortInit.enExInt = Enable;
    stcPortInit.enPullUp = Enable;
		stcPortInit.enPinDrv = Pin_Drv_H;
		stcPortInit.enPinOType = Pin_OType_Cmos;
		
		PORT_Init(SMBus1_SCL_PORT, SMBus1_SCL_PIN, &stcPortInit);
		PORT_Init(SMBus1_SDA_PORT, SMBus1_SDA_PIN, &stcPortInit);
	
		SMBus1_SCL_H; 
		SMBus1_SDA_H;
	
}


/*********************************
*函數名稱:void SMBus1_Start(void)
*函數功能:SMBus開始通訊
*函數形參:無
*函數返回值:無  
*********************************/
void SMBus1_Start(void)
{
	SMBus1_SDA_OUT(); //sda線輸出
	SMBus1_SCL_L;
	Ddl_Delay1us(2);
	SMBus1_SDA_H;
	Ddl_Delay1us(1);
	SMBus1_SCL_H;
	Ddl_Delay1us(9);
	SMBus1_SDA_L;
	Ddl_Delay1us(9);
	SMBus1_SCL_L;//鉗住I2C總線,准備發送或接收數據
}


/*********************************
*函數名稱:void SMBus1_Stop(void)
*函數功能:SMBus停止通訊
*函數形參:無
*函數返回值:無  
*********************************/
void SMBus1_Stop(void)
{
	SMBus1_SDA_OUT(); //sda線輸出
	SMBus1_SCL_L;
	Ddl_Delay1us(1);
	SMBus1_SDA_L; //STOP:when CLK is high DATA change form low to high
	Ddl_Delay1us(9);
	SMBus1_SCL_H;
	Ddl_Delay1us(9);
	SMBus1_SDA_H;//發送I2C總線結束信號
	Ddl_Delay1us(9);
}


/***********************************************
*函數名稱:uint8_t SMBus1_Wait_Ack(void)
*函數功能:SMBus等待應答
*函數形參:無
*函數返回值:uint8_t類型,返回1表示超時,返回0表示接收到應答
************************************************/
uint8_t SMBus1_Wait_Ack(void)
{
	uint16_t uErrTime=0;

	SMBus1_SDA_IN(); //SDA設置為輸入
	SMBus1_SDA_H;
	Ddl_Delay1us(9);
	SMBus1_SCL_H;
	Ddl_Delay1us(9);
	while(SMBus1_READ_SDA) 
	{
	uErrTime++;
	if(uErrTime > 250)
	{
	SMBus1_Stop();
	return 1;
	}
	//hrt_delay_us(1);
	}
	SMBus1_SCL_L; //時鍾輸出0

	return 0;
}

/***********************************************
*函數名稱:void SMBus1_Ack(void)
*函數功能:SMBus產生應答信號
*函數形參:無
*函數返回值:無
************************************************/
void SMBus1_Ack(void)
{
	SMBus1_SCL_L;
	SMBus1_SDA_OUT();
	SMBus1_SDA_L;
	Ddl_Delay1us(9);
	SMBus1_SCL_H;
	Ddl_Delay1us(9);
	SMBus1_SCL_L;
}

/***********************************************
*函數名稱:void SMBus1_Ack(void)
*函數功能:SMBus產生非應答信號
*函數形參:無
*函數返回值:無
************************************************/
void SMBus1_NAck(void)
{
	SMBus1_SCL_L;
	SMBus1_SDA_OUT();
	SMBus1_SDA_H;
	Ddl_Delay1us(9);
	SMBus1_SCL_H;
	Ddl_Delay1us(9);
	SMBus1_SCL_L;
}

/***********************************************
*函數名稱:void SMBus1_Send_Byte(void)
*函數功能:SMBus發送一個字節的數據
*函數形參:無
*函數返回值:無
************************************************/
void SMBus1_Send_Byte(uint8_t txd)
{
	uint8_t t=0;

	SMBus1_SDA_OUT();
	SMBus1_SCL_L;//拉低時鍾開始數據傳輸
	for(t=0;t<8;t++)
	{
		if((txd&0x80)>>7)
		{
			SMBus1_SDA_H;
		}
		else
		{
			SMBus1_SDA_L;
		}
		txd <<= 1;
		Ddl_Delay1us(8);
		SMBus1_SCL_H;
		Ddl_Delay1us(8);
		SMBus1_SCL_L;
		Ddl_Delay1us(8);
	}
}

/***********************************************
*函數名稱:uint8_t SMBus1_Read_Byte(void)
*函數功能:SMBus接收一個字節的數據
*函數形參:無
*函數返回值:返回這個數據
************************************************/
uint8_t SMBus1_Read_Byte(void)
{
	uint8_t i;
	uint8_t recv=0;

	SMBus1_SDA_IN(); //SDA設置為輸入
	for(i=0; i<8; i++)
	{
		SMBus1_SCL_L;
		Ddl_Delay1us(12);
		SMBus1_SCL_H;
		recv <<= 1;
		if(SMBus1_READ_SDA)
		{
			recv++;
		}
		Ddl_Delay1us(9);
	}

	return recv;
}

  通訊的基礎函數在網上都可以找得到,接下來是跟BQ4050的通訊部分,獲取電池信息:

/************************************************************
*函數名稱:int16_t Get_Battery1_Info(uint8_t slaveAddr, uint8_t Comcode)
*函數功能:獲取電池信息
*函數形參:slaveAddr,從機地址,Comcode,命令
*函數返回值:將數據返回出來,可能是電壓,電流,RSOC,RMC,溫度等,具體跟Comcode相關
*************************************************************/
int16_t Get_Battery1_Info(uint8_t slaveAddr, uint8_t Comcode)
{  
	int16_t Value;
	 uint8_t data[2] = {0};

	SMBus1_Start();
	SMBus1_Send_Byte(slaveAddr);//發送地址
	if(SMBus1_Wait_Ack() == 1)
	{
		batterry_info.LostContact[0] = 1;
//		printf("SlaveAddr wait ack fail!\r\n");
		return -1;
	}
	SMBus1_Send_Byte(Comcode);
	Ddl_Delay1us(90);            //需要注意的是,這個地方的延時特別長
	if(SMBus1_Wait_Ack() == 1)
	{
		batterry_info.LostContact[0] = 1;
//		printf("Comcode wait ack fail!\r\n");
		return -1;
	}

	SMBus1_Start();
	SMBus1_Send_Byte(slaveAddr|0x01);//發送地址
	if(SMBus1_Wait_Ack() == 1)
	{
		batterry_info.LostContact[0] = 1;
//		printf("slaveAddr+1 wait ack fail!\r\n");
		return -1;
	}
	Ddl_Delay1us(50);         //需要注意的是,這個地方的延時特別長
	

		data[0] = SMBus1_Read_Byte(); 
		SMBus1_Ack();
		Ddl_Delay1us(125);       //需要注意的是,這個地方的延時特別長
		data[1] = SMBus1_Read_Byte();
		SMBus1_NAck();

	Ddl_Delay1us(58);            
	SMBus1_Stop();
	Value = (data[0] |(data[1]<<8));
	
	batterry_info.LostContact[0] = 0;
	Ddl_Delay1us(100);

	return Value;
}

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM