打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
华大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

④上锁

 

 具体代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#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的通讯部分,获取电池信息:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/************************************************************
*函数名称: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;
}
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
【新提醒】基于HC32F460开发板的RTC电子时钟
MSP430F149模拟IIC读写24C02程序
I2C模拟
基于51单片机的电子密码锁系统设计制作,电路图+源程序
学51单片机-基于PCF8591的AD采样和DA输出【实际应用篇】
[智能硬件] 3、三分钟看懂智能硬件原理
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服