单片机学习小组
直播中

曹利娟

7年用户 960经验值
私信 关注

华大MCU的应用中的问题记录

华大MCU的应用中的问题记录




   

回帖(2)

刘丽

2022-2-24 15:58:28
1、Timer0

华大MCU有提供库函数,可以在KEIL或者IAR软件上进行编程,大部分常用的外设都可以找到,像串口,定时器,I2C,SPI等等都可以找到demo code,可以直接上华大的官网查找。但是华大的库函数提供的延时函数并不准确,延时1ms实际上延时了1.4ms左右。延时1us实际延时了2.4us。

这个时间差距是有点离谱,连FAE也坦言一般情况下他们都不会用到这两个延时,因为这两个延时好像是利用内部RC振荡器分频得到的,所以并不准确,且容易受到温度的影响。

所以我动手写了一个利用Timer0的精准延时,开始的设想是不利用中断,因为利用中断有些浪费资源了,但是实验效果并不是特别好,延时不准确,所以我还是开启了中断,只是在没有使用延时的时候,可以把时间设置得长一些,这样可以避免频繁进入中断。假设要写一个精准的微妙级别的延时函数,思路是这样的,把Timer0的时钟源选择为PCLK1,然后通过分频把频率降到1MHz,这样的话就可以CNT每隔1us就会增加一次,然后设置基准值寄存器(TMR0_CMPAR
)的值,当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有一些默认是具备特殊功能的,PA13,PA14,PA15,PB3,PB4 端口复位后初始状态为 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的通讯部分,获取电池信息:


举报

李军

2022-2-24 15:58:31
/************************************************************
*函数名称: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!rn");
        return -1;
    }
    SMBus1_Send_Byte(Comcode);
    Ddl_Delay1us(90);            //需要注意的是,这个地方的延时特别长
    if(SMBus1_Wait_Ack() == 1)
    {
        batterry_info.LostContact[0] = 1;
//      printf("Comcode wait ack fail!rn");
        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!rn");
        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;
}
举报

更多回帖

发帖
×
20
完善资料,
赚取积分