这边我记录一下标準库和HAL库撰写一样的感测器的差别,看完就知道HAL库多方便多快速已经很接近Arduino了。
看完这篇一定可以知道我前面几篇文章为什么会说想真正搞懂STM32开发的人要先学标準库再去学HAL库
的原因了。
首先是标準库,利用STM32F0这颗比较低阶的MCU来写三轴感测器的读写,这边我会用硬体的方式来做读写,我前面文章有利用过软体定义I2C的方式来做,有兴趣的可以往回看看差别,有读者还不太了解I2C的话可以看以下整理好的连结,这些是我之前比赛所发的文章:
[DAY 9] I²C协议原理介绍
[DAY 10] 软体实现I2C协议
[DAY 11] 软体实现I2C协议以三轴感测器为例 (ADXL345)
[DAY 12] 三轴感测器读取函示讲解 (ADXL345)
[DAY 13] ADXL345_I2C时序说明 (ADXL345)
我前面文章有做了不少介绍这边就不再重複描述原理了,我会直接贴上标準库和HAL库的程式码并简单说明
STM32F0 标準库(ADXL345)
IIC_ADXL345_Hardware.h
/*等待超时时间*/#define I2CT_FLAG_TIMEOUT ((uint32_t)0x1000)#define I2CT_LONG_TIMEOUT ((uint32_t)(10 * I2CT_FLAG_TIMEOUT))#define DEVICE_ID 0X00 //器件ID,0XE5#define THRESH_TAP 0X1D //敲击阀值#define OFSX 0X1E#define OFSY 0X1F#define OFSZ 0X20#define DUR 0X21#define Latent 0X22#define Window 0X23 #define THRESH_ACK 0X24#define THRESH_INACT 0X25 #define TIME_INACT 0X26#define ACT_INACT_CTL 0X27 #define THRESH_FF 0X28 #define TIME_FF 0X29 #define TAP_AXES 0X2A #define ACT_TAP_STATUS 0X2B #define BW_RATE 0X2C #define POWER_CTL 0X2D #define INT_ENABLE 0X2E#define INT_MAP 0X2F#define INT_SOURCE 0X30#define DATA_FORMAT 0X31#define DATA_X0 0X32#define DATA_X1 0X33#define DATA_Y0 0X34#define DATA_Y1 0X35#define DATA_Z0 0X36#define DATA_Z1 0X37#define FIFO_CTL 0X38#define FIFO_STATUS 0X39//0X0B TO OX1F Factory Reserved //如果ALT ADDRESS脚(12脚)接地,ADXL地址为0X53(不包含最低位).//如果接V3.3,则ADXL地址为0X1D(不包含最低位).//因为开发板接V3.3,所以转为读写地址后,为0X3B和0X3A(如果接GND,则为0XA7和0XA6) #define ADXL345_ADDRESS0xA6uint8_t ADXL345_Write_Register(uint8_t addr,uint8_t val);uint8_t ADXL345_Read_Register(uint8_t addr);uint8_t ADXL345_I2C_Init(void);void ADXL345_xyz_Printf_test(void);#endif /*__BSP_I2C_adxl_H*/
这边直接贴上程式码,再看看之前的利用软体定义I2C写ADXL345的驱动没甚么太大差别,重点是.c要如何使用标準库的方式来实现?网路上的资料大多都是软体的方式来实现I2C,本人当初也是想说少碰点壁直接用软体定义I2C比较快也比较好懂,但懂软体后觉得这样不行硬体的方式也要懂,所以花了一点点时间来研究硬体的方式,在这做个纪录。
IIC_ADXL345_Hardware.c
#include "IIC_ADXL345_Hardware.h"#include "SysTick.h"static __IO uint32_t I2CTimeout = I2CT_LONG_TIMEOUT; static uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode);static void I2C_GPIO_Config(void){ GPIO_InitTypeDef GPIO_InitStructure; RCC_AHBPeriphClockCmd(ADXL345_I2C_SCL_GPIO_CLK,ENABLE); GPIO_InitStructure.GPIO_Pin = ADXL345_I2C_SCL_PIN | ADXL345_I2C_SDA_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_3; GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(ADXL345_I2C_SDA_GPIO_PORT, &GPIO_InitStructure); GPIO_PinAFConfig(ADXL345_I2C_SCL_GPIO_PORT,ADXL345_I2C_SCL_SOURCE,ADXL345_I2C_SCL_AF); GPIO_PinAFConfig(ADXL345_I2C_SDA_GPIO_PORT,ADXL345_I2C_SDA_SOURCE,ADXL345_I2C_SDA_AF);}static void I2C_Mode_Configu(void){ I2C_InitTypeDef I2C_InitStructure; RCC_I2CCLKConfig(RCC_I2C1CLK_SYSCLK); ADXL345_I2C_CLK_INIT(ADXL345_I2C_CLK, ENABLE); /*开启I2C的时钟*/ /* I2C 配置 */ I2C_InitStructure.I2C_Timing=0x10D05E82; I2C_InitStructure.I2C_AnalogFilter=I2C_AnalogFilter_Enable; I2C_InitStructure.I2C_DigitalFilter=0; I2C_InitStructure.I2C_Mode=I2C_Mode_I2C; I2C_InitStructure.I2C_OwnAddress1=0; I2C_InitStructure.I2C_Ack=I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit; I2C_Init(ADXL345_I2C, &I2C_InitStructure); I2C_Cmd(ADXL345_I2C,ENABLE); I2C_AcknowledgeConfig(ADXL345_I2C,ENABLE);}uint8_t ADXL345_I2C_Init(void){ I2C_GPIO_Config(); I2C_Mode_Configu(); delay_ms(2); if(ADXL345_Read_Register(DEVICE_ID) == 0xE5) { ADXL345_Write_Register(DATA_FORMAT,0X2B); //**0x31** 低电平中断输出,13位元全解析度,输出资料右对齐,16g量程 ADXL345_Write_Register(BW_RATE,0x0A); //**0x2C** 资料输出速度为400Hz0A>100HZ ADXL345_Write_Register(POWER_CTL,0x28); //**0X2D** 连结使能,测量模式 ADXL345_Write_Register(INT_ENABLE,0x00); //不使用中断 //*************偏移寄存器************** ADXL345_Write_Register(OFSX,0x00); ADXL345_Write_Register(OFSY,0x00); ADXL345_Write_Register(OFSZ,0x00); printf("/r/nADXL345_OK"); } else { printf("/r/nADXL345_NO"); } return 1; } /* addr为ADXL345的内部暂存器地址,val为要写入的数值 */uint8_t ADXL345_Write_Register(uint8_t addr,uint8_t val){ /*等待I2C Bus空闲*/ I2CTimeout = I2CT_FLAG_TIMEOUT; while(I2C_GetFlagStatus(ADXL345_I2C,I2C_FLAG_BUSY) != RESET) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1); } /*发送设备的地址来呼叫ADXL345,并设定为写模式,以自动模式来发结束停止位*/ I2C_TransferHandling(I2C1,ADXL345_ADDRESS,2,I2C_AutoEnd_Mode ,I2C_Generate_Start_Write); /*等待I2C 发送数据完毕*/ I2CTimeout = I2CT_FLAG_TIMEOUT; while(I2C_GetFlagStatus(ADXL345_I2C,I2C_FLAG_TXIS) == RESET) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2); } /*发送ADXL345内部暂存器地址*/ I2C_SendData(I2C1,addr); /*等待I2C 发送数据完毕*/ I2CTimeout = I2CT_FLAG_TIMEOUT; while(I2C_GetFlagStatus(ADXL345_I2C,I2C_FLAG_TXIS) == RESET) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3); } /*发送要给的数据*/ I2C_SendData(I2C1,val); return 1;}/* 读ADXL345寄存器,addr:寄存器地址,返回值:读到的值 */uint8_t ADXL345_Read_Register(uint8_t addr){ uint8_t temp=0; /*等待I2C Bus空闲*/ I2CTimeout = I2CT_FLAG_TIMEOUT; while(I2C_GetFlagStatus(ADXL345_I2C,I2C_FLAG_BUSY) != RESET) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(5); } /*发送设备的地址来呼叫ADXL345,并设定为写模式,以软体方式发结束停止位*/ I2C_TransferHandling(I2C1,ADXL345_ADDRESS,1,I2C_SoftEnd_Mode ,I2C_Generate_Start_Write); /*等待I2C 发送数据完毕*/ I2CTimeout = I2CT_FLAG_TIMEOUT; while(I2C_GetFlagStatus(ADXL345_I2C,I2C_FLAG_TXIS) == RESET) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(6); } /*发送ADXL345内部暂存器地址*/ I2C_SendData(I2C1,addr); /*等待I2C 发送数据完毕*/ I2CTimeout = I2CT_FLAG_TIMEOUT; while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TC) == RESET) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(7); } //********************************************* /* 再发送一次起始信号并设定为读模式,自动发起结束停止位*/ I2C_TransferHandling(I2C1,ADXL345_ADDRESS,1,I2C_AutoEnd_Mode,I2C_Generate_Start_Read); /*等待接收暂存器接收完毕*/ I2CTimeout = I2CT_FLAG_TIMEOUT; while(I2C_GetFlagStatus(ADXL345_I2C,I2C_FLAG_RXNE) == RESET) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(8); } temp = I2C_ReceiveData(I2C1); /*储存在接收器的数值*/ return temp; /*回传接到的数值*/ } /** * @brief Basic management of the timeout situation. * @param errorCode:错误代码,可以用来定位是哪个地方出错. * @retval 返回0,表示IIC读取失败. */static uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode){ /* Block communication and all processes */ printf("I2C!errorCode = %d",errorCode); return 0;}void ADXL345_xyz_Printf_test(void){int xla, xha, yla, yha, zla, zha;float x, y, z;xla=ADXL345_Read_Register(0x32);// 取得 X 轴 低位元资料xha=ADXL345_Read_Register(0x33);// 取得 X 轴 高位元资料x = (((short)(xha << 8)) + xla) / 256.0;yla=ADXL345_Read_Register(0x34);// 取得 Y 轴 低位元资料yha=ADXL345_Read_Register(0x35);// 取得 Y 轴 高位元资料y = (((short)(yha << 8)) + yla) / 256.0;zla=ADXL345_Read_Register(0x36);// 取得 Z 轴 低位元资料zha=ADXL345_Read_Register(0x37);// 取得 Y 轴 高位元资料 z = (((short)(zha << 8)) + zla) / 256.0;printf("X=%.3f Y=%.3f Z=%.3f\r\n",x,y,z);}
main.c
int main(void){ USART_Config();/*初始化UART*/ ADXL345_I2C_Init(); /*初始化ADXL345*/ delay_ms(2); while(1) { ADXL345_xyz_Printf_test(); delay_ms(200); }}
这样用个UART串口就可以看到ADXL345在输出了,在这边大概分享一下我如何自学标準库的,首先要来stm32f0xx_i2c.h这标準库的最底下看看有哪些函式可以使用,这部分每颗IC都会有些不一样,像我也有自学F429的MCU因为野火这家厂商也免费开源所有资料包含程式和书籍PDF档,所以我在学F429在移植到F030一开始以为会差不多,F429有使用到的函示反而F030没有,但其实都一样只是某些功能整合在同一个函式了,但HAL库就比较不会有这问题产生的函示几乎都一样就差在不同型号的功能多寡而已,只要一种MCU写出来换另一颗只需複製贴上后小修改就好相当方便。
STM32F0 STM32CubeMX所产生的HAL库(ADXL345)
可以看到到我上面标準库写的一大堆配置,接下来我会贴上我利用STM32CubeMX
所产生的开发环境后所撰写的程式,至于使用的函示会跟标準库一样。
STM32CubeMX的配置方法我就不描述了,网路上有很多资料很简单不太会遇到甚么问题
Application/User/Core底下的i2c.h
这个档案不是我自己创建的是STM32CubeMX所产生一个档案,我会把三轴的驱动写在i2c.h和i2c.c里面
/* USER CODE BEGIN Header *//* USER CODE END Header *//* Define to prevent recursive inclusion -------------------------------------*/#ifndef __I2C_H__#define __I2C_H__#ifdef __cplusplusextern "C" {#endif/* Includes ------------------------------------------------------------------*/#include "main.h"/* USER CODE BEGIN Includes */#include "usart.h"#include "math.h"/* USER CODE END Includes */extern I2C_HandleTypeDef hi2c1;/* USER CODE BEGIN Private defines */#define I2CT_FLAG_TIMEOUT ((uint32_t)0x1000)#define I2CT_LONG_TIMEOUT ((uint32_t)(10 * I2CT_FLAG_TIMEOUT))#define DEVICE_ID 0x00 //器件ID,0XE5#define THRESH_TAP 0x1D //敲击阀值#define OFSX 0x1E#define OFSY 0x1F#define OFSZ 0x20#define DUR 0x21#define Latent 0x22#define Window 0x23 #define THRESH_ACK 0x24#define THRESH_INACT 0x25 #define TIME_INACT 0x26#define ACT_INACT_CTL 0x27 #define THRESH_FF 0x28 #define TIME_FF 0x29 #define TAP_AXES 0x2A #define ACT_TAP_STATUS 0x2B #define BW_RATE 0x2C #define POWER_CTL 0x2D #define INT_ENABLE 0x2E#define INT_MAP 0x2F#define INT_SOURCE 0x30#define DATA_FORMAT 0x31#define DATA_X0 0x32#define DATA_X1 0x33#define DATA_Y0 0x34#define DATA_Y1 0x35#define DATA_Z0 0x36#define DATA_Z1 0x37#define FIFO_CTL 0x38#define FIFO_STATUS 0x39#define ADXL345_ADDRESS0xA6/* USER CODE END Private defines */void MX_I2C1_Init(void);/* USER CODE BEGIN Prototypes */uint8_t ADXL345_Write_Register(uint8_t addr,uint8_t val);uint8_t ADXL345_Read_Register(uint8_t addr);uint8_t ADXL345_I2C_Init(void);void ADXL345_xyz_Printf_test(void);/* USER CODE END Prototypes */#ifdef __cplusplus}#endif#endif /* __I2C_H__ */
可以看到我新增的程式码一定都会在CODE BEGIN和CODE END中间,这样在重新GENERATE CODE的时候才不会被删除掉,可以对比我上的标準库IIC_ADXL345_Hardware.h差不多在来是最重要的i2c.c
Application/User/Core底下的i2c.c
这边我不全部贴上了直接贴上2个最重的写和读函式
uint8_t ADXL345_Write_Register(uint8_t addr,uint8_t val){HAL_I2C_Mem_Write(&hi2c1,ADXL345_ADDRESS, addr,1,&val,1, 1000);return 0;}uint8_t ADXL345_Read_Register(uint8_t addr){uint8_t temp=0;HAL_I2C_Mem_Read(&hi2c1,ADXL345_ADDRESS,addr,1,&temp,1,1000);return temp;}
这两个三轴读写程式跟上方标準库的硬体I2C是做一样的事!这已经拿逻辑分析仪验证过了没问题,到这可以理解位什么前面我会说相当接近arduino了吧。
至于这个函式去哪看?一样也能套用标準库的学习方式,去找官方写好的stm32l0xx_hal_i2c.h的最底下看看宣告了哪些的函式,可以看到相当多的函式但也不太担心,STM32的标準库都有做详细的注解
上面我只使用了这两个函式
HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress,uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress,uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
这程式要输入的参数可以再跳进去看,这边先以HAL_I2C_Mem_Write来做简单说明:
上方有详细说明每个参数要输入什么,看不太懂的可以直接去GOOGLE上就懂了!
I2C_HandleTypeDef hi2c式看你要指向哪个I2C
uint16_t DevAddress 是从机设备的地址
uint16_t MemAddress 是从机暂存器的地址
uint16_t MemAddSize 是要写入暂存器地字节数,1字节是等于8bit
uint8_t pData 是要写入暂存器的资料
uint16_t Size 这是要写速资料的字节数,这边也是1
uint32_t Timeout 这个是等待超时时间,就跟我标準所写While轮巡等待时间一样的意思
打完这几个参数就可以完成我标準库烙烙长的程式....
看到这里知道先学标準库的好处了吧,假设今天初心者直接使用HAL库也顺利打出程式,根本不会了解三轴I2C传输到底做了甚么事,这个STM32CubeMX真的超方便的!!
有疑问都可以在下方提出来,我懂的话会尽力回答~
希望我这个分享对初学者碰STM32有很大的帮助!!