这两年芯片的价格越炒越贵,特别是像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有一些默认是具备特殊功能的,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
④上锁
具体代码:
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; } |
联系客服