16CubeMx+Keil+Proteus仿真STM32
本文例子参考《STM32单片机开发实例——基于Proteus虚拟仿真与HAL/LL库》
源代码:https://github.com/LanLinnet/STM33F103R6
项目要求
掌握(I^2C)的通讯方法和时序,通过串口发送数据,单片机接收并存入AT24C02首地址中。按下按键BTN,单片机将存放在AT24C02首地址中的数据取出并通过串口发送。串口通信参数:波特率为19200bits/s;无校验。
硬件设计
在第一节的基础上,在Proteus中添加电路如下图所示。其中我们添加了一个I2C通信的外设:EEPROM芯片
AT24C02
(在Proteus中为FM24C02
)。此外,还添加了(I^2C)总线调试工具
I2C DEBUGGER
,用于读取(I^2C)输入输出的数据。串口和按键的相关电路可以参考第13节。COMPIM设置如下图所示。
(I^2C):
1)简介:(I^2C)(Inter-Integrated Circuit)总线是由Philips公司提出的一种两线式串行总线。(I^2C)总线属于多主总线,每个节点都可以设置唯一的地址,向总线发送数据的设备作为发送器,从总线接收数据的设备作为接收器。
2)(I^2C)总线:由时钟信号线SCL和双向数据线SDA组成。
3)通信时序:(I^2C)总线的通信时序分为发送器启动/停止通信、数据位传送、接收器返回响应信号三种。
发送器启动/停止通信:SCL保持高电平期间,SDA产生下降沿,即通信启动信号;SCL保持高电平期间,SDA产生上升沿,即通信停止信号。
数据位传送:数据发送器在启动通信之后,便向(I^2C)总线发送数据,发送数据字节长度为1字节,发送顺序高位在前,低位在后,逐位发送。如下图所示,在SCL处于高电平期间,SDA必须保持稳定,SDA低电平代表数据0,高电平代表数据1;只有在SCL处于低电平期间,SDA才能改变电平状态。
接收器返回响应信号:数据发送器每发送1个字节,数据接收器都必须返回1位响应信号,响应信号若为低电平则规定为应答响应位(ACK),表示数据接收器接收该字节数据成功;反之,则称为非应答响应位(NACK),表示数据接收器接收该字节数据失败。
如果数据接收器是主机,则在它收到最后一字节数据后,返回一个非应答位,通知数据发送器结束数据发送,接着主机向(I^2C)总线发送一个停止通信信号,结束通信过程。
AT24C02
1)简介:AT24Cxx是美国Atmel公司出品的单行(E^PROM)系列芯片,xx表示不同的容量。如02表示该芯片的总容量为2kbits(256字节)。
2)引脚:AT24C02芯片引脚如下图所示,引脚功能如下表所示。
其中,1-3引脚参与构成AT24C02在(I^2C)总线上的地址。如图1K/2K的地址所示,地址高4位固定为1010B,低4位的最低位在总线“写”指令中固定为0,在总线“读”指令中固定为1,其余3位就由1-3引脚决定。
3)读写时序:AT24C02的读写方式有写入字节、写入页、读当前地址、随机读取和连续读取5种方式,下面我们介绍本项目中使用的两种。
写入字节时序(Byte Write):写入字节即向AT24C02写入1字节,由下面8步组成。
①主机发送启动通信(Start)信号
②发送器件(芯片)地址(Device Address)
③产生应答响应(ACK)
④发送字地址(Word Address)
⑤产生应答响应(ACK)
⑥发送数据(Data)
⑦产生应答响应(ACK)
⑧发送停止通信(Stop)信号
随机读取时序(Random Read):随机读取即从AT24C02读取1字节,由下面11步组成。
①主机发送启动通信(Start)信号
②发送器件(芯片)地址(Device Address)
③产生应答响应(ACK)
④发送字地址(Word Address)
⑤产生应答响应(ACK)
⑥再次发送启动通信(Start)信号
⑦发送器件(芯片)地址(Device Address)
⑧产生应答响应(ACK)
⑨读取数据(Data)
⑩发送非应答响应(No ACK)
⑪发送停止通信(Stop)信号
打开CubeMX,建立工程。设置PB6、PB7为
GPIO_Output
,PC0为GPIO_Input
,点击“Categories”中的“GPIO”的“User Label”设置如下图所示。这里要注意,STM32F103R6自带一个(I^2C)总线通信模块,但是为了便于移植,我们这里采用GPIO引脚PB6、PB7模拟(I^2C)总线的时序。
随后进行串口设置,如下图所示,这里就不赘述了,具体可以参考第13节。
点击“Generator Code”生成Keil工程。
软件编写
考虑到代码的可移植性,这里将(I^2C)总线时序模拟和AT24C02操作代码分别写入头文件“vI2C.h”“AT24C02.h”中。我们可以先在
...CoreSrc
文件夹中建立这两个头文件,此时Keil可能找不到对应文件,可以直接将文件拽入Keil中进行编辑,然后再在“main.c”文件中进行include。点击“Open Project”在Keil中打开工程,打开“vI2C.h”,添加代码如下。
//I2C总线时序模拟
#ifndef VI2C_H_
#define VI2C_H_
#include "main.h"
//延时1μs
void delay_us(uint16_t n)
{
uint16_t i = n*8; //8MHz,周期为1/8μs
while(i--);
}
//设置数据线模式: I-输入 O-输出
void Pin_vSDA_Mode(char status)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
HAL_GPIO_WritePin(GPIOB, vSDA_Pin, GPIO_PIN_SET);
GPIO_InitStruct.Pin = vSDA_Pin;
GPIO_InitStruct.Pull = GPIO_PULLUP;
if(status == "I") //输入
{
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
}
else if(status == "O") //输出
{
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
}
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
//时钟线输出
void vSCL_Out(uint8_t dat)
{
switch(dat)
{
case 0: HAL_GPIO_WritePin(GPIOB, vSCL_Pin, GPIO_PIN_RESET); break;
default: HAL_GPIO_WritePin(GPIOB, vSCL_Pin, GPIO_PIN_SET); break;
}
}
//写数据线
void vSDA_Out(uint8_t dat)
{
switch(dat)
{
case 0: HAL_GPIO_WritePin(GPIOB, vSDA_Pin, GPIO_PIN_RESET); break;
default: HAL_GPIO_WritePin(GPIOB, vSDA_Pin, GPIO_PIN_SET); break;
}
}
//读数据线
uint8_t vSDA_In()
{
GPIO_PinState PinState;
uint8_t rt;
PinState = HAL_GPIO_ReadPin(GPIOB, vSDA_Pin);
switch(PinState)
{
case GPIO_PIN_RESET: rt = 0; break;
default: rt = 1; break;
}
return rt;
}
//启动I2C通信
void I2C_Start()
{
Pin_vSDA_Mode("O");
vSDA_Out(1);
delay_us(6); //至少延时4.7μs
vSCL_Out(1);
delay_us(6); //至少延时4.7μs
vSDA_Out(0); //下降沿
delay_us(6); //至少延时4.7μs
vSCL_Out(0);
}
//停止I2C通信
void I2C_Stop()
{
Pin_vSDA_Mode("O");
vSDA_Out(0);
delay_us(6); //至少延时4.7μs
vSCL_Out(1);
delay_us(6); //至少延时4.7μs
vSDA_Out(1); //上升沿
delay_us(6); //至少延时4.7μs
}
//发送应答-低电平
void I2C_Ack()
{
Pin_vSDA_Mode("O");
vSDA_Out(0);
delay_us(6); //至少延时4.7μs
vSCL_Out(1);
delay_us(6); //至少延时4.7μs
vSCL_Out(0);
delay_us(6); //至少延时4.7μs
vSDA_Out(1);
delay_us(6); //至少延时4.7μs
}
//写1字节数据
void I2C_WtByte(uint8_t Dat)
{
uint8_t i, tmp;
Pin_vSDA_Mode("O");
for(i = 0; i < 8; i++)
{
tmp = Dat & (0x80>>i); //高位在前,低位在后,逐位发送
vSCL_Out(0);
delay_us(6);
(tmp == 0) ? (vSDA_Out(0)) : (vSDA_Out(1));
delay_us(6);
vSCL_Out(1);
delay_us(6);
}
vSCL_Out(0);
delay_us(6);
vSDA_Out(1);
delay_us(6);
}
//读1字节数据
uint8_t I2C_RdByte()
{
uint8_t Dat = 0, tmp, i;
Pin_vSDA_Mode("I");
vSCL_Out(0);
delay_us(6);
for(i = 0; i < 8; i++)
{
vSCL_Out(1);
delay_us(6);
tmp = vSDA_In();
Dat = Dat << 1; //读1位左移1位
Dat = Dat | tmp;
delay_us(6);
vSCL_Out(0);
delay_us(6);
}
return Dat;
}
#endif /* VI2C_H_ */
打开“AT24C02.h”,添加代码如下。
//AT24C02操作
#ifndef AT24C02_H_
#define AT24C02_H_
#define AT24C02_ADDR 0xa0
#include "main.h"
#include "vI2C.h"
//写入1字节
void AT24C02_Write(uint8_t DatAddr, uint8_t Dat)
{
I2C_Start(); //主机发送启动通信信号
I2C_WtByte(AT24C02_ADDR + 0); //发送器件(芯片)地址
I2C_Ack(); //产生应答响应
I2C_WtByte(DatAddr); //发送字地址
I2C_Ack(); //产生应答响应
I2C_WtByte(Dat); //发送数据
I2C_Ack(); //产生应答响应
I2C_Stop(); //发送停止通信信号
}
//读取1字节
uint8_t AT24C02_Read(uint8_t DatAddr)
{
uint8_t Dat;
I2C_Start(); //主机发送启动通信信号
I2C_WtByte(AT24C02_ADDR + 0); //发送器件地址
I2C_Ack(); //产生应答响应
I2C_WtByte(DatAddr); //发送字地址
I2C_Ack(); //产生应答响应
I2C_Start(); //再次发送启动通信信号
I2C_WtByte(AT24C02_ADDR + 1); //发送器件地址
I2C_Ack(); //产生应答响应
Dat = I2C_RdByte(); //读取数据
I2C_Stop(); //产生非应答信号,发送停止通信信号
return Dat;
}
#endif /* AT24C02_H_ */
随后我们需要在main.c文件中的最前面引入我们自定义的头文件
/* USER CODE BEGIN Includes */
#include "vI2C.h" //引用I2C总线时序模拟头文件
#include "AT24C02.h" //引用AT24C02操作头文件
/* USER CODE END Includes */
在main函数中定义一些全局变量
/* USER CODE BEGIN PV */
uint8_t RcvDat[1]; //存放接收数据数组
uint8_t SndDat[1]; //存放发送数据数组
uint8_t rf = 0; //接收完成标志位
/* USER CODE END PV */
进行串口相关操作
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, RcvDat, 1); //串口1接收中断
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
if(rf==1) //若接收完成
{
rf = 0; //清0标志位
AT24C02_Write(0, RcvDat[0]); //写入1字节
HAL_UART_Receive_IT(&huart1, RcvDat, 1); //每次接收前需调用一次
}
else if(HAL_GPIO_ReadPin(BTN_GPIO_Port, BTN_Pin) == GPIO_PIN_RESET) //若按下按键
{
SndDat[0] = AT24C02_Read(0); //读1字节数据,并存入数组
HAL_UART_Transmit(&huart1, SndDat, 1, 0xffff); //串口1发送1字节,超时65535ms
while(HAL_GPIO_ReadPin(BTN_GPIO_Port, BTN_Pin) == GPIO_PIN_RESET); //直到按键松开
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //串口接收完毕回调函数
{
if(huart == &huart1)
{
rf = 1; //若接收完成,则标志位置1
}
}
/* USER CODE END 4 */
联合调试
点击运行,生成HEX文件。
在Proteus中加载相应HEX文件,点击运行。
打开串口调试助手“XCOM”,选择
COM4
,设置相应的波特率、停止位、数据位、奇偶校验等,勾选“16进制显示”和“16进制发送”,点击“打开串口”。在发送框输入“CD”,点击“发送”。在Proteus中我们可以看到“I2C Debug”接收到数据“CD”。按下按键,同时再观察串口调试助手“XCOM”,可以看到接收窗口收到数据“CD”。
以上是 16CubeMx+Keil+Proteus仿真STM32 的全部内容, 来源链接: utcz.com/z/520557.html