一、工程背景
1.1 本专栏背景:
(1)STM32 CUBE MX 软件的选用:
对于毕业以后很久没有碰过STM32代码编写的码农来说,如果按照传统的HAL库编写单片机程序费时费力。哪怕是仅仅用裸机跑功能,不仅仅需要重新阅读数据手册和开发板厂家的教程,而且还基于厂家例程改写引脚、外设参数等等。出于项目周期、任务达成两方面考量,为快速完成可供芯片功能、性能测试用途的STM32 demo工程代码,可考虑使用STM32 CUBE MX 软件进行快速代码生成,配合开发板相关手册、网上教程进行排障。(STM32CUBE MX官方下载地址)
(2)RTOS的应用
基于项目多任务实时处理的需求,在项目中添加FREE_RTOS操作系统。借助STM32 CUBE MX 软件和操作系统配套官方配件包(配件包官方下载地址)进行快速移植、配置。
一方面,在公司做项目不像在学校那样慢慢啃视频教程或者教程书籍,力求快速完成一个demo工程实现基本的功能如点灯,在此基础上进行迭代、增改;
另一方面,之前学生时代简单上手STM32工程进行实验,没有深究其中的众多机制,故在此整理从0开始完成功能开发的过程,不仅供日后编者自己查阅,也供有类似需求的开发者参考,抛砖引玉,如有误请大佬指正。
如果读者参考本文的操作遇到其他问题或者对文中的操作有疑问和建议欢迎在评论区与我交流。
文中所用的软件大多通过ST官网或者野火/正点原子等开发板厂家可以获得,我尽量将软件贴在文章中免费下载,如有需要的读者可以联系我免费获取(编者自己也是厌倦了各种会员和关注微信下载的套路,故所有资源无门槛无套路联系我即可获得,愿与各位一起进步!)
(无广)需要相关芯片资料可通过该网站查找下载半导小芯。
1.2 本项目背景
IIC通信协议是单片机项目中常见的集成电路之间的通信协议。掌握IIC读写控制代码的快速生成对于嵌入式,尤其是单片机开发工作的意义不言自明。
IIC总线在标准模式下最高支持100kHz通信速率,特点是连线简单且支持多设备之间的通信。
二、本次开发的环境准备
2.1 硬件环境
本次开发使用F103开发板以及配套的JTAG debugger。开发板通过debugger和USB串口线连接电脑,单片机F103VET6通过PB6、PB7两个引脚连接EEPROM芯片。连接示意图如下
关于SPI协议和FLASH芯片,网上讲解的资料有很多,在此不再赘述。
2.2 软件环境
如本专栏之前文章所述,使用STM32 Cube MX进行代码生成,使用Keil进行代码修改和编译。串口调试工具选择具有基本功能的即可,无特别限制。
本文章基于文章所建立的工程
【基于STM32CubeMX的freertos工程快速生成】串口篇-串口工程快速生成简要步骤及排障。
三、开发过程
3.1 生成代码
在串口工程代码的基础上,打开STM32CubeMX软件的可视化界面,进行如下配置
配置IIC引脚
选择IIC模式,根据所控制的设备配置参数,本工程与AT24C02芯片进行通信,使用默认配置,不使用IIC中断。
F103芯片无需配置引脚上拉也可通信。F407系列需要将两个GPIO引脚配置上拉模式。
增加新的线程
3.2 修改工程
生成代码后,创建驱动文件bsp_i2c_ee.h和bsp_i2c_ee.c。分别添加代码:
/* ============================================= bsp_i2c_ee.h =====================================================s */
#ifndef __I2C_EE_H
#define __I2C_EE_H
#include "stm32f1xx.h"
#include "i2c.h"
/* AT24C01/02每页有8个字节 */
//#define EEPROM_PAGESIZE 8
#define EEPROM_PAGESIZE 8
/* AT24C04/08A/16A每页有16个字节 */
//#define EEPROM_PAGESIZE 16
/* 这个地址只要与STM32外挂的I2C器件地址不一样即可 */
#define I2C_OWN_ADDRESS7 0X0A
#define I2Cx I2C1
#define I2Cx_CLK_ENABLE() __HAL_RCC_I2C1_CLK_ENABLE()
#define I2Cx_SDA_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define I2Cx_SCL_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define I2Cx_FORCE_RESET() __HAL_RCC_I2C1_FORCE_RESET()
#define I2Cx_RELEASE_RESET() __HAL_RCC_I2C1_RELEASE_RESET()
/* Definition for I2Cx Pins */
#define I2Cx_SCL_PIN GPIO_PIN_6
#define I2Cx_SCL_GPIO_PORT GPIOB
#define I2Cx_SCL_AF GPIO_AF4_I2C1
#define I2Cx_SDA_PIN GPIO_PIN_7
#define I2Cx_SDA_GPIO_PORT GPIOB
#define I2Cx_SDA_AF GPIO_AF4_I2C1
/*等待超时时间*/
#define I2CT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define I2CT_LONG_TIMEOUT ((uint32_t)(10 * I2CT_FLAG_TIMEOUT))
#define I2Cx_TIMEOUT_MAX 300
/* Maximum number of trials for HAL_I2C_IsDeviceReady() function */
#define EEPROM_MAX_TRIALS 300
/*信息输出*/
#define EEPROM_DEBUG_ON 0
#define EEPROM_INFO(fmt,arg...) printf("<<-EEPROM-INFO->> "fmt"\n",##arg)
#define EEPROM_ERROR(fmt,arg...) printf("<<-EEPROM-ERROR->> "fmt"\n",##arg)
#define EEPROM_DEBUG(fmt,arg...) do{\
if(EEPROM_DEBUG_ON)\
printf("<<-EEPROM-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
}while(0)
/*
* AT24C02 2kb = 2048bit = 2048/8 B = 256 B
* 32 pages of 8 bytes each
*
* Device Address
* 1 0 1 0 A2 A1 A0 R/W
* 1 0 1 0 0 0 0 0 = 0XA0
* 1 0 1 0 0 0 0 1 = 0XA1
*/
/* EEPROM Addresses defines */
#define EEPROM_Block0_ADDRESS 0xA0 /* E2 = 0 */
#define EEPROM_Block1_ADDRESS 0xA2 /* E2 = 0 */
#define EEPROM_Block2_ADDRESS 0xA4 /* E2 = 0 */
#define EEPROM_Block3_ADDRESS 0xA6 /* E2 = 0 */
#define EEPROM_ADDRESS 0xA0
#define I2C_Handle hi2c1
#define DATA_Size 256
#define EEP_Firstpage 0x00
void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite);
uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr);
uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite);
uint32_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead);
#endif /* __I2C_EE_H */
/* ============================================= bsp_i2c_ee.c =====================================================s */
#include "bsp_i2c_ee.h"
#include "bsp_usart.h"
uint8_t I2c_Buf_Write[DATA_Size];
uint8_t I2c_Buf_Read[DATA_Size];
/**
* @brief 在EEPROM的一个写循环中可以写多个字节,但一次写入的字节数
* 不能超过EEPROM页的大小,AT24C02每页有8个字节
* @param
* @arg pBuffer:缓冲区指针
* @arg WriteAddr:写地址
* @arg NumByteToWrite:写的字节数
* @retval 无
*/
uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite)
{
HAL_StatusTypeDef status = HAL_OK;
/* Write EEPROM_PAGESIZE */
status=HAL_I2C_Mem_Write(&I2C_Handle, EEPROM_ADDRESS,WriteAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t*)(pBuffer),NumByteToWrite, 100);
while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY)
{
}
/* Check if the EEPROM is ready for a new operation */
while (HAL_I2C_IsDeviceReady(&I2C_Handle, EEPROM_ADDRESS, EEPROM_MAX_TRIALS, I2Cx_TIMEOUT_MAX) == HAL_TIMEOUT);
/* Wait for the end of the transfer */
while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY)
{
}
return status;
}
void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite)
{
uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
Addr = WriteAddr % EEPROM_PAGESIZE;
count = EEPROM_PAGESIZE - Addr;
NumOfPage = NumByteToWrite / EEPROM_PAGESIZE;
NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;
/* If WriteAddr is I2C_PageSize aligned */
if(Addr == 0)
{
/* If NumByteToWrite < I2C_PageSize */
if(NumOfPage == 0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
/* If NumByteToWrite > I2C_PageSize */
else
{
while(NumOfPage--)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);
WriteAddr += EEPROM_PAGESIZE;
pBuffer += EEPROM_PAGESIZE;
}
if(NumOfSingle!=0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
}
/* If WriteAddr is not I2C_PageSize aligned */
else
{
/* If NumByteToWrite < I2C_PageSize */
if(NumOfPage== 0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
/* If NumByteToWrite > I2C_PageSize */
else
{
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / EEPROM_PAGESIZE;
NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;
if(count != 0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
}
while(NumOfPage--)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);
WriteAddr += EEPROM_PAGESIZE;
pBuffer += EEPROM_PAGESIZE;
}
if(NumOfSingle != 0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}
/**
* @brief 写一个字节到I2C EEPROM中
* @param
* @arg pBuffer:缓冲区指针
* @arg WriteAddr:写地址
* @retval 无
*/
uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr)
{
HAL_StatusTypeDef status = HAL_OK;
status = HAL_I2C_Mem_Write(&I2C_Handle, EEPROM_ADDRESS, (uint16_t)WriteAddr, I2C_MEMADD_SIZE_8BIT, pBuffer, 1, 100);
/* Check the communication status */
if(status != HAL_OK)
{
/* Execute user timeout callback */
//I2Cx_Error(Addr);
}
while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY)
{
}
/* Check if the EEPROM is ready for a new operation */
while (HAL_I2C_IsDeviceReady(&I2C_Handle, EEPROM_ADDRESS, EEPROM_MAX_TRIALS, I2Cx_TIMEOUT_MAX) == HAL_TIMEOUT);
/* Wait for the end of the transfer */
while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY)
{
}
return status;
}
/**
* @brief 从EEPROM里面读取一块数据
* @param
* @arg pBuffer:存放从EEPROM读取的数据的缓冲区指针
* @arg WriteAddr:接收数据的EEPROM的地址
* @arg NumByteToWrite:要从EEPROM读取的字节数
* @retval 无
*/
uint32_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead)
{
HAL_StatusTypeDef status = HAL_OK;
status=HAL_I2C_Mem_Read(&I2C_Handle,EEPROM_ADDRESS,ReadAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t *)pBuffer, NumByteToRead,1000);
return status;
}
uint8_t I2C_Test(void)
{
uint16_t i;
EEPROM_INFO("写入的数据");
for ( i=0; i<DATA_Size; i++ ) //填充缓冲
{
I2c_Buf_Write[i] =i;
printf("0x%02X ", I2c_Buf_Write[i]);
if(i%16 == 15)
printf("\n\r");
}
//将I2c_Buf_Write中顺序递增的数据写入EERPOM中
I2C_EE_BufferWrite( I2c_Buf_Write, EEP_Firstpage, DATA_Size);
EEPROM_INFO("读出的数据");
//将EEPROM读出数据顺序保持到I2c_Buf_Read中
I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, DATA_Size);
//将I2c_Buf_Read中的数据通过串口打印
for (i=0; i<DATA_Size; i++)
{
if(I2c_Buf_Read[i] != I2c_Buf_Write[i])
{
printf("0x%02X ", I2c_Buf_Read[i]);
EEPROM_ERROR("错误:I2C EEPROM写入与读出的数据不一致");
return 0;
}
printf("0x%02X ", I2c_Buf_Read[i]);
if(i%16 == 15)
printf("\n\r");
}
EEPROM_INFO("I2C(AT24C02)读写测试成功");
return 1;
}
void StartTask_IIC(void const * argument)
{
/* USER CODE BEGIN StartTask_IIC */
if(I2C_Test() ==1)
{
printf("\r\n测试成功\r\n");
}
else
{
printf("\r\n测试失败\r\n");;
}
/* Infinite loop */
for(;;)
{
osDelay(1);
}
/* USER CODE END StartTask_IIC */
}
/*********************************************END OF FILE**********************/
3.3 测试结果
四、总结
IIC设备驱动的编写借助STM32CubeMX 快速生成和HAL的接口可以快速实现。主要难点在于根据芯片手册编写读写协议。IIC协议具备的多设备通信达的能力是一体两面:优点是使用一个IIC外设和两个GPIO引脚可以实现多设备控制、通信,缺点是增加了通信协议的复杂度,需要根据IIC设备的通信时序协议编写、封装功能函数。