16CubeMx+Keil+Proteus仿真STM32

编程

本文例子参考《STM32单片机开发实例——基于Proteus虚拟仿真与HAL/LL库》

源代码:https://github.com/LanLinnet/STM33F103R6

项目要求

掌握(I^2C)的通讯方法和时序,通过串口发送数据,单片机接收并存入AT24C02首地址中。按下按键BTN,单片机将存放在AT24C02首地址中的数据取出并通过串口发送。串口通信参数:波特率为19200bits/s;无校验。

硬件设计

  1. 在第一节的基础上,在Proteus中添加电路如下图所示。其中我们添加了一个I2C通信的外设:EEPROM芯片AT24C02(在Proteus中为FM24C02)。


    此外,还添加了(I^2C)总线调试工具I2C DEBUGGER,用于读取(I^2C)输入输出的数据。


    串口和按键的相关电路可以参考第13节。COMPIM设置如下图所示。

  2. (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)总线发送一个停止通信信号,结束通信过程。

  3. 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)信号

  4. 打开CubeMX,建立工程。设置PB6、PB7为GPIO_Output,PC0为GPIO_Input,点击“Categories”中的“GPIO”的“User Label”设置如下图所示。


    这里要注意,STM32F103R6自带一个(I^2C)总线通信模块,但是为了便于移植,我们这里采用GPIO引脚PB6、PB7模拟(I^2C)总线的时序。

    随后进行串口设置,如下图所示,这里就不赘述了,具体可以参考第13节。


  5. 点击“Generator Code”生成Keil工程。

软件编写

  1. 考虑到代码的可移植性,这里将(I^2C)总线时序模拟和AT24C02操作代码分别写入头文件“vI2C.h”“AT24C02.h”中。我们可以先在...CoreSrc文件夹中建立这两个头文件,此时Keil可能找不到对应文件,可以直接将文件拽入Keil中进行编辑,然后再在“main.c”文件中进行include。

  2. 点击“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_ */

  3. 随后我们需要在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 */

联合调试

  1. 点击运行,生成HEX文件。

  2. 在Proteus中加载相应HEX文件,点击运行。

  3. 打开串口调试助手“XCOM”,选择COM4,设置相应的波特率、停止位、数据位、奇偶校验等,勾选“16进制显示”和“16进制发送”,点击“打开串口”。在发送框输入“CD”,点击“发送”。在Proteus中我们可以看到“I2C Debug”接收到数据“CD”。按下按键,同时再观察串口调试助手“XCOM”,可以看到接收窗口收到数据“CD”。

以上是 16CubeMx+Keil+Proteus仿真STM32 的全部内容, 来源链接: utcz.com/z/520557.html

回到顶部