(9)Proteus 8.9上的STM32仿真系列(HAL/CubeMaxIDE)—IIC/I2C实验
实验设计: Proteus上的stm32硬件IIC测试失败,本处采用模拟IIC。控制PCF8574电平,并读取其状态从串口1输出。
一、Proteus原理图(芯片stm32f103r6tx)
1.可以不放逻辑分析仪,COMPIM点开波特率设置为9600。
二、CubemaxIDE配置
1.时钟和第一节相同,直接HCLK 8Mhz。
2.使能uart1,设置9600波特率
3.配置PB0,PB1,后面会重新设置输入输出模式,这里简单打开就好。
4.generate code,需要添加6个文件,delay.c/delay.h如上一节,需新添加Soft_IIC.c/Soft_IIC.h和PCF8574.C/PCF8574.h。后两个文件源代码如下。
目录结构:
Soft_IIC.h
#ifndef __SOFT_IIC_H
#define __SOFT_IIC_H
#include "delay.h"
/* 高低电平 */
#define HIGH 1
#define LOW 0
/* IO方向设置 */
#define SDA_IN() {GPIOB->CRL&=~(15<<(0*2));GPIOB->CRL|=4<<(0*2);} //PB0浮空输入模式
#define SDA_OUT() {GPIOB->CRL&=~(3<<(0*2));GPIOB->CRL|=1<<(0*2);} //PB0输出模式10Mhz
/* IO操作 */
#define IIC_SCL(n) (n?HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET)) //SCL
#define IIC_SDA(n) (n?HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET)) //SDA
#define READ_SDA HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) //输入SDA
/* IIC所有操作函数 */
void IIC_Init(void); //IIC初始化
void IIC_Start(void); //产生IIC起始信号
void IIC_Stop(void); //产生IIC停止信号
uint8_t IIC_Wait_Ack(void); //等待ACK信号到来
void IIC_Ack(void); //产生ACK应答
void IIC_NAck(void); //不产生ACK应答
void IIC_Send_Byte(uint8_t txd); //IIC发送一个字节
uint8_t IIC_Read_Byte(unsigned char ack); //IIC读取一个字节
#endif
Soft_IIC.c
#include "Soft_IIC.h"
/***************************************************************
@Function void IIC_Init(void)
@Description IIC初始化
@Input void
@Return void
***************************************************************/
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOB时钟
/* PB0,1初始化设置 */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
IIC_SDA(HIGH);
IIC_SCL(HIGH);
}
/***************************************************************
@Function void IIC_Start(void)
@Description 产生IIC起始信号
SCL : HIGH
SDA : HIGH -> LOW
@Input void
@Return void
***************************************************************/
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA(HIGH);
IIC_SCL(HIGH);
delay_us(4);
IIC_SDA(LOW); //START : when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL(LOW); //钳住I2C总线,准备发送或接收数据
}
/***************************************************************
@Function void IIC_Stop(void)
@Description 产生IIC停止信号
SCL : HIGH
SDA : LOW -> HIGH
@Input void
@Return void
***************************************************************/
void IIC_Stop(void)
{
SDA_OUT(); //sda线输出
IIC_SDA(LOW);
IIC_SCL(HIGH);
delay_us(4);
IIC_SDA(HIGH); //STOP : when CLK is high,DATA change form low to high
delay_us(4);
}
/***************************************************************
@Function uint8_t IIC_Wait_Ack(void)
@Description 等待ACK信号到来
@Input void
@Return 1 : 接收应答失败
0 : 接收应答成功
***************************************************************/
uint8_t IIC_Wait_Ack(void)
{
uint8_t ucErrTime = 0;
SDA_IN(); //SDA设置为输入
IIC_SDA(HIGH);delay_us(1);
IIC_SCL(HIGH);delay_us(1);
while (READ_SDA) {
ucErrTime++;
if (ucErrTime > 250) {
IIC_Stop();
return 1;
}
}
IIC_SCL(LOW); //时钟输出0
return 0;
}
/***************************************************************
@Function void IIC_Ack(void)
@Description 产生ACK应答
@Input void
@Return void
***************************************************************/
void IIC_Ack(void)
{
SDA_OUT(); //sda线输出
IIC_SCL(LOW);
IIC_SDA(LOW);
delay_us(2);
IIC_SCL(HIGH);
delay_us(2);
IIC_SCL(LOW);
}
/***************************************************************
@Function void IIC_NAck(void)
@Description 不产生ACK应答
@Input void
@Return void
***************************************************************/
void IIC_NAck(void)
{
SDA_OUT(); //sda线输出
IIC_SCL(LOW);
IIC_SDA(HIGH);
delay_us(2);
IIC_SCL(HIGH);
delay_us(2);
IIC_SCL(LOW);
}
/***************************************************************
@Function void IIC_Send_Byte(uint8_t txd)
@Description IIC发送一个字节
@Input txd : 发送数据
@Return void
***************************************************************/
void IIC_Send_Byte(uint8_t txd)
{
uint8_t t;
SDA_OUT(); //sda线输出
IIC_SCL(LOW); //拉低时钟开始数据传输
for (t = 0; t < 8; t++)
{
IIC_SDA((txd & 0x80) >> 7);
txd <<= 1;
delay_us(2);
IIC_SCL(HIGH);
delay_us(2);
IIC_SCL(LOW);
delay_us(2);
}
}
/***************************************************************
@Function uint8_t IIC_Read_Byte(unsigned char ack)
@Description 读1个字节,ack=1时,发送ACK,ack=0,发送nACK
@Input ack
@Return 所读数据
***************************************************************/
uint8_t IIC_Read_Byte(unsigned char ack)
{
unsigned char i, receive = 0;
SDA_IN(); //SDA设置为输入
for (i = 0; i < 8; i++)
{
IIC_SCL(LOW);
delay_us(2);
IIC_SCL(HIGH);
receive <<= 1;
if (READ_SDA) receive++;
delay_us(1);
}
if (!ack)
IIC_NAck(); //发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
PCF8574.h
#ifndef __PCF8574_H
#define __PCF8574_H
#include "delay.h"
#include "Soft_IIC.h"
//#define PCF8574_INT PBin(12) //PCF8574 INT脚
#define PCF8574_ADDR 0X40 //PCF8574地址为 0 1 0 0 A2 A1 A0 R/W
uint8_t PCF8574_Init(void);
uint8_t PCF8574_ReadOneByte(void);
void PCF8574_WriteOneByte(uint8_t DataToWrite);
void PCF8574_WriteBit(uint8_t bit,uint8_t sta);
uint8_t PCF8574_ReadBit(uint8_t bit);
#endif
PCF8574.c
#include "PCF8574.h"
//读取PCF8574的8位IO值
//返回值:读到的数据
uint8_t PCF8574_ReadOneByte(void)
{
uint8_t temp=0;
IIC_Start();
IIC_Send_Byte(PCF8574_ADDR|0X01); //进入接收模式
IIC_Wait_Ack();
temp=IIC_Read_Byte(0);
IIC_Stop(); //产生一个停止条件
return temp;
}
//向PCF8574写入8位IO值
//DataToWrite:要写入的数据
void PCF8574_WriteOneByte(uint8_t DataToWrite)
{
IIC_Start();
IIC_Send_Byte(PCF8574_ADDR|0X00); //发送器件地址0X40,写数据
IIC_Wait_Ack();
IIC_Send_Byte(DataToWrite); //发送字节
IIC_Wait_Ack();
IIC_Stop(); //产生一个停止条件
delay_ms(10);
}
//设置PCF8574某个IO的高低电平
//bit:要设置的IO编号,0~7
//sta:IO的状态;0或1
void PCF8574_WriteBit(uint8_t bit,uint8_t sta)
{
uint8_t data;
data=PCF8574_ReadOneByte(); //先读出原来的设置
if(sta==0)data&=~(1<<bit);
else data|=1<<bit;
PCF8574_WriteOneByte(data); //写入新的数据
}
//读取PCF8574的某个IO的值
//bit:要读取的IO编号,0~7
//返回值:此IO的值,0或1
uint8_t PCF8574_ReadBit(uint8_t bit)
{
uint8_t data;
data=PCF8574_ReadOneByte(); //先读取这个8位IO的值
if(data&(1<<bit))return 1;
else return 0;
}
5.main.c中添加代码
(1)包含头文件
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "delay.h"
#include "Soft_IIC.h"
#include "PCF8574.h"
/* USER CODE END Includes */
(2)声明一个uint8_t 记录PCF8574状态
/* USER CODE BEGIN PV */
uint8_t IIC_READ;
/* USER CODE END PV */
(3)主函数
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
delay_init(8);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
PCF8574_WriteOneByte(0xf0); //置PCF8574高四位高电平
IIC_READ=PCF8574_ReadOneByte(); //读取PCF8574状态
HAL_UART_Transmit(&huart1, &IIC_READ, 1, 0xff); //将PCF8574状态从uart1输出
delay_ms(500); //延时500ms
//翻转电平,重复上述操作
PCF8574_WriteOneByte(0x0f);
IIC_READ=PCF8574_ReadOneByte();
HAL_UART_Transmit(&huart1, &IIC_READ, 1, 0xff);
delay_ms(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
6.设置导出hex文件,buildall
三、实验结果
(1)电平翻转及uart1输出
(2)逻辑分析仪捕获的电平信号(建议COMPIM和逻辑分析仪不要同时仿真,要不然速度慢,电脑性能好的同志可以试试)
读取PCF8574信号(0x41 0xf0)
写入PCF8574信号(0x40 0xf0)