前言
網上有些人說STM32的硬體I2C使用起來有問題,我用起來一點問題都沒有,下面大緻說一下最近做這個的心得
CubeMX設定
軟體采用最新的CubeMX和SDK生成,晶片型号STM32F107RC
首先時鐘使用正常的72M,這部配置設定置很多晶片都一樣
I2C的配置也是普普通通,保持預設就好,不使用DMA傳輸
I2C初始化代碼
以下代碼由CubeMX自動生成,在使用者代碼區(USER CODE)添加了解除寫保護WR的代碼,除此之外沒有其它修改
void MX_I2C1_Init(void)
{
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK)
{
Error_Handler();
}
}
void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(i2cHandle->Instance==I2C1)
{
/* USER CODE BEGIN I2C1_MspInit 0 */
/* USER CODE END I2C1_MspInit 0 */
__HAL_RCC_GPIOB_CLK_ENABLE();
/**I2C1 GPIO Configuration
PB6 ------> I2C1_SCL
PB7 ------> I2C1_SDA
*/
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* I2C1 clock enable */
__HAL_RCC_I2C1_CLK_ENABLE();
/* I2C1 interrupt Init */
HAL_NVIC_SetPriority(I2C1_EV_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(I2C1_EV_IRQn);
HAL_NVIC_SetPriority(I2C1_ER_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(I2C1_ER_IRQn);
/* USER CODE BEGIN I2C1_MspInit 1 */
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, GPIO_PIN_RESET); //DISABLE wr
/* USER CODE END I2C1_MspInit 1 */
}
}
從裝置讀寫
一般的EEPROM,像AT24C02這種小容量的裝置,位址都隻需要8位,頁大小一般是16位元組一個頁
而像AT24C32C、AT24C64C這種32K、64K位元組的大容量EEPROM,8位位址是不夠的,使用了16位位址,頁大小在這兩個器件中也變成了32位元組
正是由于容量的不同,導緻代碼上需要做差異化處理,才能正确讀取EEPROM晶片
以下代碼可以參考,寫的時候無法跨頁,是以寫大量資料的時候,隻能一頁一頁的寫,兩次寫之間保證5MS的間隔
讀沒有跨頁的影響,可以一次性把全部資料讀出來,但是要注意,讀和寫之間,是要有5MS的間隔的,否則讀不到資料。也就是說每次寫完,延遲5MS,就能保證後續的程式沒有問題。
//#define I2C_MEMADD_SIZE I2C_MEMADD_SIZE_8BIT //小容量EEPROM晶片8位位址用此參數
#define I2C_MEMADD_SIZE I2C_MEMADD_SIZE_16BIT //大容量EEPROM晶片16位位址用此參數
#define ADDR_AT24C02_Write 0xA0 //EEPROM I2C寫位址
#define ADDR_AT24C02_Read 0xA0+1 //EEPROM I2C讀位址
typedef enum SYS_PARA_ENUM {
LOCAL_IP = 0,
UDP_LOCAL_PORT,
UDP_PC_PORT,
NETMASK,
GATEWAY,
SERVER_IP,
SERVER_PORT,
VERSION,
SN_NUM, //16位元組
SENSOR_TYPE = SN_NUM + 4,
SENSOR_DATA_TYPE,
SENSOR_INTERVAL,
SYS_PARA_MAX, //end
}sys_para_e;
//請注意,為了友善flash讀寫操作,此處的每一項均設為uint32_t類型
//如果不為uint32_t類型,則flash_write函數将出現錯誤
typedef struct SYS_PARA_TYPE {
uint32_t local_ip; //本機IP,大端模式
uint32_t udp_local_port; //本機端口号
uint32_t udp_pc_port; //PC端口号
uint32_t netmask; //本機子網路遮罩,大端模式
uint32_t gateway; //本機網關,大端模式
uint32_t server_ip; //伺服器IP,大端模式
uint32_t server_port; //伺服器端口号
uint32_t version; //stm32軟體版本号
uint32_t sn_num[4]; //SN号 5 6 7 8
uint32_t sensor_type; //傳感器類型 9
uint32_t sensor_data_type; //傳感器資料類型
uint32_t sensor_interval; //傳感器采集時間間隔
}sys_para_t;
/* -----------------------------------------------------------------------------
函數名: i2c_write
作者: glx
日期: 2020-11-10
功能: 資料寫入eeprom
輸入參數: pData:資料指針
傳回值: 類型:HAL_StatusTypeDef
HAL_OK:操作成功
HAL_ERROR:操作失敗
修改記錄:
------------------------------------------------------------------------------*/
HAL_StatusTypeDef i2c_write(void *pData)
{
HAL_StatusTypeDef ret = HAL_ERROR;
uint8_t i, page, pageSize;
uint8_t *p = (uint8_t *)pData;
if(pData != NULL)
{
if(I2C_MEMADD_SIZE == I2C_MEMADD_SIZE_8BIT)
{
pageSize = 16; //一頁16位元組,不能跨頁寫
page = SYS_PARA_MAX/4;
}
else if(I2C_MEMADD_SIZE == I2C_MEMADD_SIZE_16BIT)
{
pageSize = 32; //一頁32位元組,不能跨頁寫
page = SYS_PARA_MAX/8;
}
//寫完整的頁
for(i = 0; i<page; i++)
{
ret = HAL_I2C_Mem_Write(&hi2c1, ADDR_AT24C02_Write, i*pageSize, I2C_MEMADD_SIZE, p, pageSize, 100);
p += pageSize;
if(ret != HAL_OK)
{
printf("I2C_Write Sys Para write error\r\n");
return HAL_ERROR;
}
HAL_Delay(5);
}
//寫殘缺的頁
if(SYS_PARA_MAX > (page*4))
{
ret = HAL_I2C_Mem_Write(&hi2c1, ADDR_AT24C02_Write, i*pageSize, I2C_MEMADD_SIZE, p, 4*SYS_PARA_MAX-pageSize*page, 100);
if(ret != HAL_OK)
{
printf("I2C_Write Sys Para write error\r\n");
return HAL_ERROR;
}
HAL_Delay(5);
}
}
else
{
printf("I2C_Write pData NULL\r\n");
return HAL_ERROR;
}
return HAL_OK;
}
/* -----------------------------------------------------------------------------
函數名: i2c_read
作者: glx
日期: 2020-11-10
功能: 讀取eeprom資料
輸入參數: pData:資料指針
傳回值: 類型:HAL_StatusTypeDef
HAL_OK:操作成功
HAL_ERROR:操作失敗
修改記錄:
------------------------------------------------------------------------------*/
HAL_StatusTypeDef i2c_read(void *pData)
{
HAL_StatusTypeDef ret = HAL_ERROR;
if(pData != NULL)
{
ret = HAL_I2C_Mem_Read(&hi2c1, ADDR_AT24C02_Read, 0, I2C_MEMADD_SIZE, (uint8_t *)pData, SYS_PARA_MAX*4, 100);
if(ret != HAL_OK)
{
printf("I2C_Read Sys Para read error\r\n");
return HAL_ERROR;
}
}
else
{
printf("I2C_Read pData NULL\r\n");
return HAL_ERROR;
}
return HAL_OK;
}
讀寫的代碼
//用于儲存系統參數
sys_para_t sys_para = {0};
//用于儲存系統預設參數
sys_para_t default_para = {
.local_ip = 107<<24 | 10<<16 | 168<<8 | 192,
.udp_local_port = 18080,
.udp_pc_port = 18081,
.netmask = 0<<24 | 255<<16 | 255<<8 | 255,
.gateway = 1<<24 | 10<<16 | 168<<8 | 192,
.server_ip = 9<<24 | 10<<16 | 168<<8 | 192,
.server_port = 18082,
.version = 20201124,
.sn_num[0] = 0xffffffff,
.sn_num[1] = 0xffffffff,
.sn_num[2] = 0xffffffff,
.sn_num[3] = 0xffffffff,
.sensor_type = SENSOR_DOOR,
.sensor_data_type = SENSOR_DATA_GPIO,
.sensor_interval = 1000,
};
i2c_read(&sys_para);
i2c_write(&default_para);