• 华大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!
    ");
    		return -1;
    	}
    	SMBus1_Send_Byte(Comcode);
    	Ddl_Delay1us(90);            //需要注意的是,这个地方的延时特别长
    	if(SMBus1_Wait_Ack() == 1)
    	{
    		batterry_info.LostContact[0] = 1;
    //		printf("Comcode wait ack fail!
    ");
    		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!
    ");
    		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;
    }
    

      

  • 相关阅读:
    LeetCode——面试题57
    翻译——5_Summary, Conclusion and Discussion
    LeetCode——114. 二叉树展开为链表
    LeetCode——1103. 分糖果 II
    LeetCode——337. 打家劫舍 III
    LeetCode——994. 腐烂的橘子
    Python——潜在会员用户预测
    Vue中div高度自适应
    webpack中使用vue-resource
    Mint UI组件库 和 Mui
  • 原文地址:https://www.cnblogs.com/young-dalong/p/14989008.html
Copyright © 2020-2023  润新知