前言

在前面一章中, 学习了 串口通信以及定时器, 本章节中将介绍I2C通信,使用 I2C 通信方式点亮 OLED 模块。由于 OLED 模块支持多种通信方式, OLED 模块的 I2C 通信过程主要通过在数据层进行二次打包, 以达到分类数据包的目的, 以便适配 OLED 的多种通信方式。

准备工具

软件:STM32CubeMx、Keil5 MDK

硬件:STM32F103C8T6核心板、下载器ST_LINK、OLEDI2C通信4针屏幕

本章节工程已上传至百度网盘,此链接永久有效

链接:https://pan.baidu.com/s/13UiC3hnn84yIsA5N1nrmmg?pwd=w8cx 
提取码:w8cx

I2C通信

I2C 是 PHILIPS 公司开发的一种半双工、双向二线制同步串行总线。两线制代表 I2C 只需两根信号线,一根数据线 SDA,另一根是时钟线 SCL。I2C 总线允许挂载多个主设备, 但总线时钟同一时刻只能由一个主设备产生,并且要求每个连接到总线上的器件都有唯一的 I2C 地址,从设备可以被主设备寻址。I2C 通信具有几类信号:

开始信号 S: 当 SCL 处于高电平时, SDA 从高电平拉低至低电平,代表数据传输的开始。

结束信号 P: 当 SCL 处于高电平时, SDA 从低电平拉高至高电平,代表数据传输结束。

数据信号: 数据信号每次都传输 8 位数据,每一位数据都在一个时钟周期内传递,当SCL 处于高电平时候, SDA 数据线上的电平需要稳定,当 SCL 处于低电平的时候,SDA 数据线上的电平允许改变。

应答信号 ACK/NACK: 应答信号是主机发送 8bit 数据,从机对主机发送低电平,表示已经接受数据。

常见用于读取传感器数据的 I2C 传输过程如下表所示:

cubemxIO配置详解 模拟_arm

整个 I2C 通信过程理解成收发快递的过程,设备 I2C 地址理解成学校快递柜的地址,读写位代表寄出和签收快递,寄存器地址则是快递柜上的箱号, 而数据便是需要寄出或者签收的快递。整个过程便是如同到学校的快递柜(从机 I2C 地址),对第几号柜箱(寄存器地址 ,进行寄出或者签收快递(数据) 的过程。 

IIC在CubeMx中的配置

1. 在 connectivity 下找到 I2C1;
2. 配置成 I2C;
3. 配置成 Fast Mode;
4. 其他保持默认。

cubemxIO配置详解 模拟_cubemxIO配置详解 模拟_02

 

cubemxIO配置详解 模拟_cubemxIO配置详解 模拟_03

生成代码,打开工程。

添加以下代码到工程中

/**
  * @brief          写数据或者指令到OLED, 如果使用的是SPI,请重写这个函数
  * @param[in]      dat: 要写入的字节
  * @param[in]      cmd: OLED_CMD 代表写入的字节是指令; OLED_DATA 代表写入的字节是数据
  * @retval         none
  */
void oled_write_byte(uint8_t dat, uint8_t cmd)
{
    static uint8_t cmd_data[2];
    if(cmd == OLED_CMD)
    {
        cmd_data[0] = 0x00;
    }
    else
    {
        cmd_data[0] = 0x40;
    }
    cmd_data[1] = dat;
    HAL_I2C_Master_Transmit(&hi2c1, OLED_I2C_ADDRESS, cmd_data, 2, 10);
}

根据 OLED 的通信方式,需要在第一个字节中指明之后的数据类型,如果是控制指令则需要发送 0x00, 如果是数据指令则需要发送 0x40。

/**
  * @brief          初始化OLED模块,
  * @param[in]      none
  * @retval         none
  */
void OLED_init(void)
{
    oled_write_byte(0xAE, OLED_CMD);    //display off
    oled_write_byte(0x20, OLED_CMD);    //Set Memory Addressing Mode	
    oled_write_byte(0x10, OLED_CMD);    //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
    oled_write_byte(0xb0, OLED_CMD);    //Set Page Start Address for Page Addressing Mode,0-7
    oled_write_byte(0xc8, OLED_CMD);    //Set COM Output Scan Direction
    oled_write_byte(0x00, OLED_CMD);    //---set low column address
    oled_write_byte(0x10, OLED_CMD);    //---set high column address
    oled_write_byte(0x40, OLED_CMD);    //--set start line address
    oled_write_byte(0x81, OLED_CMD);    //--set contrast control register
    oled_write_byte(0xff, OLED_CMD);    //brightness 0x00~0xff
    oled_write_byte(0xa1, OLED_CMD);    //--set segment re-map 0 to 127
    oled_write_byte(0xa6, OLED_CMD);    //--set normal display
    oled_write_byte(0xa8, OLED_CMD);    //--set multiplex ratio(1 to 64)
    oled_write_byte(0x3F, OLED_CMD);    //
    oled_write_byte(0xa4, OLED_CMD);    //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
    oled_write_byte(0xd3, OLED_CMD);    //-set display offset
    oled_write_byte(0x00, OLED_CMD);    //-not offset
    oled_write_byte(0xd5, OLED_CMD);    //--set display clock divide ratio/oscillator frequency
    oled_write_byte(0xf0, OLED_CMD);    //--set divide ratio
    oled_write_byte(0xd9, OLED_CMD);    //--set pre-charge period
    oled_write_byte(0x22, OLED_CMD);    //
    oled_write_byte(0xda, OLED_CMD);    //--set com pins hardware configuration
    oled_write_byte(0x12, OLED_CMD);
    oled_write_byte(0xdb, OLED_CMD);    //--set vcomh
    oled_write_byte(0x20, OLED_CMD);    //0x20,0.77xVcc
    oled_write_byte(0x8d, OLED_CMD);    //--set DC-DC enable
    oled_write_byte(0x14, OLED_CMD);    //
    oled_write_byte(0xaf, OLED_CMD);    //--turn on oled panel
}

OLED_init 函数,该函数主要配置 OLED 参数, 通过调用 oled_write_byte 传入OLED_CMD, 传输控制指令完成配置 。

/**
  * @brief          操作GRAM内存(128*8char数组)
  * @param[in]      pen: 操作类型.
                    PEN_CLEAR: 设置为0x00
                    PEN_WRITE: 设置为0xff
                    PEN_INVERSION: 按位取反
  * @retval         none
  */
void OLED_operate_gram(pen_typedef pen)
{
    uint8_t i, n;

    for (i = 0; i < 8; i++)
    {
        for (n = 0; n < 128; n++)
        {
            if (pen == PEN_WRITE)
            {
                OLED_GRAM[n][i] = 0xff;
            }
            else if (pen == PEN_CLEAR)
            {
                OLED_GRAM[n][i] = 0x00;
            }
            else
            {
                OLED_GRAM[n][i] = 0xff - OLED_GRAM[n][i];
            }
        }
    }
}

/**
  * @brief          发送数据到OLED的GRAM
  * @param[in]      none
  * @retval         none
  */
void OLED_refresh_gram(void)
{
    uint8_t i, n;

    for (i = 0; i < 8; i++)
    {
        OLED_set_pos(0, i);
        for (n = 0; n < 128; n++)
        {
            oled_write_byte(OLED_GRAM[n][i], OLED_DATA);
        }
    }
}

/**
  * @brief          显示一个字符
  * @param[in]      row: 字符的开始行
  * @param[in]      col: 字符的开始列
  * @param[in]      chr: 字符
  * @retval         none
  */
void OLED_show_char(uint8_t row, uint8_t col, uint8_t chr)
{
    uint8_t x = col * 6;
    uint8_t y = row * 12;
    uint8_t temp, t, t1;
    uint8_t y0 = y;
    chr = chr - ' ';

    for (t = 0; t < 12; t++)
    {
        temp = asc2_1206[chr][t];

        for (t1 = 0; t1 < 8; t1++)
        {
            if (temp&0x80)
                OLED_draw_point(x, y, PEN_WRITE);
            else
                OLED_draw_point(x, y, PEN_CLEAR);

            temp <<= 1;
            y++;
            if ((y - y0) == 12)
            {
                y = y0;
                x++;
                break;
            }
        }
    }
}

/**
  * @brief          显示一个字符串
  * @param[in]      row: 字符串的开始行
  * @param[in]      col: 字符串的开始列
  * @param[in]      chr: 字符串
  * @retval         none
  */
void OLED_show_string(uint8_t row, uint8_t col, uint8_t *chr)
{
    uint8_t n =0;

    while (chr[n] != '\0')
    {
        OLED_show_char(row, col, chr[n]);
        col++;

        if (col > 20)
        {
            col = 0;
            row += 1;
        }
        n++;
    }
}

显示过程如下:
1. OLED 模块的初始化,调用 OLED_init
2. 通过显示字符函数,对 stm32 内的 GRAM 数组进行操作
3. 调用 OLED_refresh_gram 函数将 GRAM 数据传输到 OLED 模块的 GRAM 进行显示。
主函数

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_TIM4_Init();
  MX_I2C1_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
	HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1); //初始化BUZZER
	HAL_TIM_Base_Start_IT(&htim3);	//开启定时器3中断
	OLED_init(); //OLED初始化
//	Solitary_brave();
	OLED_operate_gram(PEN_CLEAR);
	OLED_show_string(0,6,"LRJ_ROBOT");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		if(tim3_delay.count_10ms != tim3_delay.last_count_10ms)
		{
			GPIO_PinState pin_state = HAL_GPIO_ReadPin(LEDD_GPIO_Port, LEDD_Pin);//获取LED电平
			OLED_printf(2,5,"LED_PIN = %d",pin_state);
			tim3_delay.last_count_10ms = tim3_delay.count_10ms;
		}
		if(tim3_delay.count_400ms != tim3_delay.last_count_400ms)
		{
			OLED_refresh_gram();//屏幕刷新 400ms
			HAL_GPIO_TogglePin(LEDD_GPIO_Port, LEDD_Pin);
			tim3_delay.last_count_400ms = tim3_delay.count_400ms;
		}
		
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

OLED屏幕实时显示LED电平变化

cubemxIO配置详解 模拟_单片机_04