|
NUCLEO-L476RG到手一段时间了,今天来测试一下硬件I2C的操作,听说很多BUG,试一试看如何。
准备用I2C来访问AT24C02 EEPROM,将数据写入到EEPROM中,然后再读取出来,验证读写操作的正确性。
硬件平台如下
NUCLEO-L476RG
前段时间FSL的KL02Z套装,上面的MINIDOCK扩展板上恰好有AT24C02,所以借过来用一下,FSL不要生气啊
标准的ARDUINO接口,放到哪都行。合体照。
硬件连接及原理图
MINI DOCI上的AT24C02连接
ARDUINO接口:SCL及SDA
NUCLEO-L476RG上对应的接口
写程序之前,要了解硬件的基本特性,首先是STM32L476RG上I2C接口支持的特性;其次是AT24C02的特性。
STM32L476RG的I2C功能框图如下
值得一提的是,L4的I2C有自己独立的内核时钟,可以在SYSCLK、PCLK及HSI中任选一种做为I2C的内核时钟。另外L4的I2C支持三种不同的通信速率:100KHZ, 400KHZ,1MHZ。由于AT24C02只支持最高400KHZ,所以这里就选择400KHZ做为内核时钟。
创建I2C测试工程,建立过程略。几点要注意的地方。
首先,本例程中使用的是I2C1,对应的SCL及SDA引脚为PB8及PB9,时钟配置后SYSCLK为80MHZ。
I2C硬件配置参数如图
速度有三种可选:标准,快速,快速加。另外针对不同的特定应用,可以选择设置上升和下降沿的时间,以纳秒为单位,范围为0-120纳秒之间。注意这个TIMING参数,是灰色的,CUBEMX自动计算并设置该值,不像以前的F0和F3系列,需要一个专门的工具来设置TIMING的值,够麻烦的。
下面是使用HAL库与EEPROM通信的主要代码。
- /* USER CODE BEGIN PV */
- /* Private variables ---------------------------------------------------------*/
- #define EEPROM_ADDRESS 0xA0
- #define EEPROM_PAGESIZE 0x11
- #define EEPROM_TIMEOUT 1000
- extern I2C_HandleTypeDef hi2c1;
- char msg[] = "This is a test!";
- char buf[40];
- int16_t Remaining_Bytes;
- uint16_t Memory_Address;
- uint16_t len;
- ......
- //write msg to eeprom
- Remaining_Bytes = strlen(msg);
- Memory_Address = 0;
-
- while(Remaining_Bytes > 0)
- {
- if(HAL_I2C_Mem_Write_DMA(&hi2c1, EEPROM_ADDRESS, Memory_Address, I2C_MEMADD_SIZE_8BIT, (uint8_t *)(msg + Memory_Address), EEPROM_PAGESIZE) != HAL_OK)
- {
- Error_Handler();
-
- }
-
- while(HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
- {
- }
-
- while (HAL_I2C_IsDeviceReady(&hi2c1, EEPROM_ADDRESS, 10, 300) == HAL_TIMEOUT);
-
- while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
- {
- }
-
- Remaining_Bytes -= EEPROM_PAGESIZE;
-
- Memory_Address += EEPROM_PAGESIZE;
- }
-
- //read msg to buff
- if(HAL_I2C_Mem_Read_DMA(&hi2c1 , EEPROM_ADDRESS, 0, I2C_MEMADD_SIZE_8BIT, (uint8_t*)buf, strlen(msg))!= HAL_OK)
- {
- /* Reading process Error */
- Error_Handler();
- }
-
- /* Wait for the end of the transfer */
- while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
- {
- }
复制代码
根据STD库中用户的反馈,I2C出错的原因主要由几方面造成,一是ST设计的I2C接口,通信过程中一些状态变量及标志位的设置非常严格,如果程序逻辑考虑不周全,会导致I2C停止工作,需要软复位后才能继续;另一方面,I2C接口在处理单个,双字节及多字节传输过程中要分开考虑,进一步将问题复杂化了;还有就是结合具体的I2C设备,不同的时序要求,进一步复杂化了通信过程。
这些问题在HAL库中都得到了很好的解决,为此,HAL库中设计了几种不同类型的API函数供调用。一类是通用的接收和发送函数,这类API函数将I2C接口视为类似于“流对象”,通信时从流对象中接收或发送数据;另一类则是类似于EEPROM之类的特殊存储对象,从这些存储对象中接收或发送数据时,都需要提供一个地址,以便准确定位。使用EEPROM来实验时,我们使用的是第二类API函数。两个主要的API函数原型及参数如下。
- /**
- * @brief Transmit in master mode an amount of data in non-blocking mode with DMA
- * @param hi2c : Pointer to a I2C_HandleTypeDef structure that contains
- * the configuration information for the specified I2C.
- * @param DevAddress: Target device address
- * @param pData: Pointer to data buffer
- * @param Size: Amount of data to be sent
- * @retval HAL status
- */
- HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
复制代码
- /**
- * @brief Reads an amount of data in non-blocking mode with DMA from a specific memory address.
- * @param hi2c : Pointer to a I2C_HandleTypeDef structure that contains
- * the configuration information for the specified I2C.
- * @param DevAddress: Target device address
- * @param MemAddress: Internal memory address
- * @param MemAddSize: Size of internal memory address
- * @param pData: Pointer to data buffer
- * @param Size: Amount of data to be read
- * @retval HAL status
- */
- HAL_StatusTypeDef HAL_I2C_Mem_Read_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size)
复制代码
关于这两个函数的更详细的调用方法及说明,请参考API文档。
注意在具体操作EEPROM的时候,还要注意,EEPROM分字节读写及页面读写两种类型,对HAL API的调用还要进行适当的修改。
由于ST HAL API做了大量的后台工作,上面的代码看起来过于简单了。但实际上API函数在后台做了大量的工作,这不正是我们期待的么!
最后来看看运行结果。
UART打印的写入及读取取结果
逻辑分析仪LA
I2C通信全程,LA分析结果
开始通信,顺序依次是开始信号,地址,EEPROM写入地址,第一个数据字节
字符0x0D, 0x0A, 结束信号。
接收开始,顺序依次是开始信号,地址,EEPROM读取地址,第一个数据字节
读取结束
附工程
eeprom.zip
(3.17 MB, 下载次数: 28)
|
|