Bootstrap

GD32F103 硬件I2C驱动SSD1306-OLED显示屏,PCF8563-RTC实时时钟,SHT30温湿度传感器

简介

网上搜索好多玩GD32 I2C总线的都是用软件GPIO模拟的。有反映硬件I2C不稳定,程序卡死(循环,get flag 那里),于是入手了3件I2C的硬件,空余时间把玩下。走了不少弯路,不过最终还是搞懂,搞通了。嘻嘻~
本次使用的是gd32官网V2.2.0 的demo,参考自带EEPROM的代码进行修改。使用的是I2C1–PB10,11,留意这个stm32的是I2C2了,命名方式gd32的是从0开始的。

硬件I2C配置

把一些共用的代码整合到i2c.c。其他不同硬件区分代入自己的I2C 地址码就可以了,减少代码重复,部分代码如下。
!!!重点,由于本次总共有3个I2C设备地址,i2c.h 里 I2Cx_SLAVE_ADDRESS7 这个定义的8bit地址,试了随便填一个都可以。我理解只是总线初始化使用而已。因为后面使用时读写时,代码里I2C start后,会重传新一次设备地址和(发送/接收)方向,在此处传入要通讯的设备地址就可以了。这样也比较合情理,在总线挂多设备情况
刚开始看demo i2c和at24cxx地址是同一个,走了不少弯路,重新初始化I2C什么的。

i2c_mode_addr_config(I2C1, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, I2Cx_SLAVE_ADDRESS7);
#include "gd32f10x.h"
#include "i2c.h"
#include <stdio.h>

/*!
    \brief      configure the GPIO ports
    \param[in]  none
    \param[out] none
    \retval     none
*/
void gpio_config(void)
{
    /* enable GPIOB clock */
    rcu_periph_clock_enable(RCU_GPIOB);
    /* enable I2C1 clock */
    rcu_periph_clock_enable(RCU_I2C1);

    /* connect PB10 to I2C0_SCL */
    /* connect PB11 to I2C0_SDA */
    gpio_init(GPIOB, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_10 | GPIO_PIN_11);
}

/*!
    \brief      configure the I2C1 interfaces
    \param[in]  none
    \param[out] none
    \retval     none
*/
void i2c_config(void)
{
    /* enable I2C clock */
    rcu_periph_clock_enable(RCU_I2C1);
    /* configure I2C clock */
    i2c_clock_config(I2C1, I2Cx_SPEED, I2C_DTCY_2);
    /* configure I2C address */
    i2c_mode_addr_config(I2C1, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, I2Cx_SLAVE_ADDRESS7);
    /* enable I2C0 */
    i2c_enable(I2C1);
    /* enable acknowledge */
    i2c_ack_config(I2C1, I2C_ACK_ENABLE);
}

//2024-5-21 参考demo修改函数,除地址头外,发送的字符用数组统一收/发。
/*
p_buffer : 要发送的数组
i2c_address :I2C 从设备地址
number_of_byte :要发送的字节长度,多少个Byte字节
*/

/*!
    \brief      read data from the devices
    \param[in]  p_buffer: pointer to the buffer that read form the slave device
		\param[in]	i2c_address: I2C slave device address
    \param[in]  w_address: internal address to start read
    \param[in]  number_of_byte: number of bytes to read
    \param[out] none
    \retval     none

*/
void i2c_read_data(uint8_t* p_buffer, uint8_t i2c_address, uint8_t r_address, uint16_t number_of_byte)
{
    /* wait until I2C bus is idle */
    while(i2c_flag_get(I2C1, I2C_FLAG_I2CBSY));

    if(2 == number_of_byte){
        i2c_ackpos_config(I2C1, I2C_ACKPOS_NEXT);
    }

    /* send a start condition to I2C bus */
    i2c_start_on_bus(I2C1);

    /* wait until SBSEND bit is set */
    while(!i2c_flag_get(I2C1, I2C_FLAG_SBSEND));

    /* send slave address to I2C bus */
    i2c_master_addressing(I2C1, i2c_address, I2C_TRANSMITTER);

    /* wait until ADDSEND bit is set */
    while(!i2c_flag_get(I2C1, I2C_FLAG_ADDSEND));

    /* clear the ADDSEND bit */
    i2c_flag_clear(I2C1, I2C_FLAG_ADDSEND);
    
    /* wait until the transmit data buffer is empty */
    while(SET != i2c_flag_get(I2C1 , I2C_FLAG_TBE));

    /* enable I2C1*/
    i2c_enable(I2C1);
    
    /* send the EEPROM's internal address to write to */
    i2c_data_transmit(I2C1, r_address);  

    /* wait until BTC bit is set */
    while(!i2c_flag_get(I2C1, I2C_FLAG_BTC));

    /* send a start condition to I2C bus */
    i2c_start_on_bus(I2C1);

    /* wait until SBSEND bit is set */
    while(!i2c_flag_get(I2C1, I2C_FLAG_SBSEND));

    /* send slave address to I2C bus */
    i2c_master_addressing(I2C1, i2c_address, I2C_RECEIVER);

    if(number_of_byte < 3){
        /* disable acknowledge */
        i2c_ack_config(I2C1, I2C_ACK_DISABLE);
    }
    
    /* wait until ADDSEND bit is set */
    while(!i2c_flag_get(I2C1, I2C_FLAG_ADDSEND));

    /* clear the ADDSEND bit */
    i2c_flag_clear(I2C1, I2C_FLAG_ADDSEND);

    if(1 == number_of_byte){
        /* send a stop condition to I2C bus */
        i2c_stop_on_bus(I2C1);
    }

    /* while there is data to be read */
    while(number_of_byte){
        if(3 == number_of_byte){
            /* wait until BTC bit is set */
            while(!i2c_flag_get(I2C1, I2C_FLAG_BTC));

            /* disable acknowledge */
            i2c_ack_config(I2C1, I2C_ACK_DISABLE);
        }
        if(2 == number_of_byte){
            /* wait until BTC bit is set */
            while(!i2c_flag_get(I2C1, I2C_FLAG_BTC));

            /* send a stop condition to I2C bus */
            i2c_stop_on_bus(I2C1);
        }

        /* wait until the RBNE bit is set and clear it */
        if(i2c_flag_get(I2C1, I2C_FLAG_RBNE)){
            /* read a byte from the EEPROM */
            *p_buffer = i2c_data_receive(I2C1);

            /* point to the next location where the byte read will be saved */
            p_buffer++; 

            /* decrement the read bytes counter */
            number_of_byte--;
        } 
    }
    
    /* wait until the stop condition is finished */
    while(I2C_CTL0(I2C1) & 0x0200);

    /* enable acknowledge */
    i2c_ack_config(I2C1, I2C_ACK_ENABLE);

    i2c_ackpos_config(I2C1, I2C_ACKPOS_CURRENT);
}

SSD1306 显示屏

1.OLED屏初始化,整合采用了数组方式。代码是少了,就是哪个参数设置那个功能的就要反查了,感觉还是不太理想。

//OLED 初始化函数组
uint8_t OLED_INIT[]={0xAE,0x00,0x10,0x40,0xB0,0x81,0xFF,0xA1,0xA6,0xA8,0x3F,0xC8,0xD3,0x00,0xD5,0x80,0xD8,0x05,0xD9,0xF1,0xDA,0x12,0xDB,0x30,0x8D,0x14,0xAF};

void oled_init(void)
{
		uint8_t i,n;
		n = sizeof OLED_INIT;
		for(i=0;i<n;i++)
		{
			i2c_2Bytes_write(OLED_ADDRESS,OLED_CMD,OLED_INIT[i]);		
		}
}

2.显示也是用了数据方式,更新数据到显示屏的GDDRAM。

//更新GDDRAM数据到显示屏
void oled_refresh(void)
{		
	uint8_t x,y;
	for(y=0;y<8;y++)
	{
		i2c_2Bytes_write(OLED_ADDRESS,OLED_CMD,0xB0+y);		//set page start address for page addressing mode
		i2c_2Bytes_write(OLED_ADDRESS,OLED_CMD,0x00);			//set lower column start address for page addressing mode
		i2c_2Bytes_write(OLED_ADDRESS,OLED_CMD,0x10);			//set higher column start address for page addressing mode
		for(x=0;x<128;x++)
		{
			i2c_2Bytes_write(OLED_ADDRESS,OLED_DAT,OLED_GRAM[x][y]);
		}
	}

}

RTC-实时时钟 PCF8563

获取、设置时间代码

#include "pcf8563.h"
#include "i2c.h"
#include <stdio.h>
#include "systick.h"
#include "oled.h"


unsigned char get_time[5];


uint8_t rtc_get_time(uint8_t r_add)
{
		uint8_t time_dat[1];
		i2c_read_data(time_dat,RTC_ADDRESS,r_add,1);
		return time_dat[0];
}

//读取RTC 寄存器 数据
uint8_t time_str[10];

void get_rtc_time(void)
{
		uint8_t time[5];
		time[0] = rtc_get_time(0x02)&0x7F;		//second
		time[1] = rtc_get_time(0x03)&0x7F;		//minute
		time[2] = rtc_get_time(0x04)&0x3F;		//hour
		time[3] = rtc_get_time(0x05)&0x3F;		//day
		time[4] = rtc_get_time(0x07)&0x1F;		//month
	
//		for(i=0;i<5;i++)
//		{
//			printf("get_time:%0.2X\r\n",time[i]);
//		}

		//BCD转码获取时间信息用于显示。16进制转10进制。
		get_time[0] = (time[0]/16)*10+(time[0]%16);
		get_time[1] = (time[1]/16)*10+(time[1]%16);
		get_time[2] = (time[2]/16)*10+(time[2]%16);
		get_time[3] = (time[3]/16)*10+(time[3]%16);
		get_time[4] = (time[4]/16)*10+(time[4]%16);
		
		printf("日期:%d月-%d日 时间:%.2d:%.2d:%.2d\r\n",get_time[4],get_time[3],get_time[2],get_time[1],get_time[0]);
		
		oled_show_string(12,16,"-   time:  :  :",8,1);		//格式 xx/xx time:xx:xx:xx
		
		sprintf((char*)time_str,"%d",get_time[4]);
		oled_show_string(0,16,time_str,8,1);
		
		sprintf((char*)time_str,"%d",get_time[3]);
		oled_show_string(18,16,time_str,8,1);

		sprintf((char*)time_str,"%.2d",get_time[2]);
		oled_show_string(66,16,time_str,8,1);
		
		sprintf((char*)time_str,"%.2d",get_time[1]);
		oled_show_string(84,16,time_str,8,1);
		
		sprintf((char*)time_str,"%.2d",get_time[0]);
		oled_show_string(102,16,time_str,8,1);
}

uint8_t set_time_data[5] = {0x00,0x00,0x21,0x28,0x05};	//second,minute,hour,day,month 5月28日15时0分0秒
//uint8_t rtc_time_reg[5] = {0x02,0x03,0x04,0x05,0x07};

uint8_t rtc_stop[2] = {0x00,0x20};
uint8_t rtc_start[2] = {0x00,0x00};

void set_rtc_time(void)
{
		i2c_Bytes_write(rtc_stop,RTC_ADDRESS,2);		//clock stop

		i2c_2Bytes_write(RTC_ADDRESS,0x02,set_time_data[0]);
		i2c_2Bytes_write(RTC_ADDRESS,0x03,set_time_data[1]);
		i2c_2Bytes_write(RTC_ADDRESS,0x04,set_time_data[2]);
		i2c_2Bytes_write(RTC_ADDRESS,0x05,set_time_data[3]);
		i2c_2Bytes_write(RTC_ADDRESS,0x07,set_time_data[4]);

		i2c_Bytes_write(rtc_start,RTC_ADDRESS,2);		//clock start
}

SHT30-温湿度传感

本次采用了单次触发模式测试温湿度。注意 I2C 地址和芯片第二脚电平有对应关系。本次用的0x44。0x44是设备的7bit地址,不是8bit。要移位补上R/W后面1bit。
SHT30 device address
single shot mode

#include "sht30.h"
#include "i2c.h"
#include <stdio.h>
#include "systick.h"
#include "oled.h"



/*SHT30 状态寄存器的读取命令。The status register contains information on the 
operational status of the heater, the alert mode and on 
the execution status of the last command and the last 
write sequence.
*/

//读取SHT30状态寄存器信息
uint8_t status_buff[3] = {0};
void read_sht30_status(void)
{
	i2c_2Bytes_write(SHT30_ADDRESS,0xF3,0x2D);		// read the status register

	i2c_Bytes_read(status_buff,SHT30_ADDRESS,3);
//	sht30_data_read(status_buff,SHT30_ADDRESS,0xF3,0x2D,3);
	
	printf("\r\nsht30 status REG_MSB:0x%02X",status_buff[0]);
	printf("\r\nREG_LSB:0x%02X",status_buff[1]);
	printf("\r\nREG_CRC:0x%02X",status_buff[2]);
}

//单次触发 测量并读取温湿度数据,single shot mode
//SHT30 测量温湿度,enable clock stretching,high/medium/low repeatability

//uint8_t H_measurement [2] = {0x2C,0x06};
//uint8_t M_measurement [2] = {0x2C,0x0D};
//uint8_t L_measurement [2] = {0x2C,0x10};

uint8_t measu_data_buff[6] = {0};
uint16_t T_RH[2]={0};

void read_measure_data (void)
{
	uint16_t hum=0,temp=0;
	i2c_2Bytes_write(SHT30_ADDRESS,0x2C,0x10);		// high repeatability measurement with clock stretching enable
	delay_1ms(50);
	i2c_Bytes_read(measu_data_buff,SHT30_ADDRESS,6);		//read the temperature&humidity
		
//		for(i=0;i<6;i++)
//		{
//			printf("0x%02X \r\n",measu_data_buff[i]);
//		
//		}
		
		//数据提取
		temp = (measu_data_buff[0] << 8) | measu_data_buff[1];
		hum = (measu_data_buff[3] << 8) | measu_data_buff[4];
	
		T_RH[0] = temp;
		T_RH[1] = hum;		
//		printf("\r\n measu_data:0x%04X",T_RH[0]);
//		printf("\r\n measu_data:0x%04X",T_RH[1]);
}

uint8_t str[10];
//uint8_t tempstr[]={0x03,0x03,0x3E,0x41,0x41,0x41};
void show_ht_info(void)
{
		float temp,hum;
	
		i2c_2Bytes_write(SHT30_ADDRESS,0x30,0xA2);		//soft reset sht30
		delay_1ms(10);
		read_measure_data();
		
		temp = -45.0f+175.0f*(T_RH[0]/65535.0f);
		hum = 100.0f*(T_RH[1]/65535.0f);

		printf("\r\n温度:%0.1f°C,湿度:%0.1f%%\r\n",temp,hum);
		
		oled_show_string(0,0,"temp:",8,1);
		oled_show_string(0,8,"hum-:",8,1);
		
		sprintf((char *)str,"%0.1f",temp);
		oled_show_string(30,0,str,8,1);
		oled_show_string(56,0,"|",8,1);			//修改自定义字符°C
	
		sprintf((char *)str,"%0.1f",hum);
		oled_show_string(30,8,str,8,1);
		oled_show_string(56,8,"%",8,1);
}

演示视频

gd32d硬件IIC调试

总结

由于是调试,加了不少串口打印信息跟踪代码。
显示屏显示字符串使用了sprintf函数转换成字符。
由于摄氏温度符号,末尾自己增加,修改了 oledfont.h 8x6的代码。
例程是每秒刷新一次时间,每5秒刷新一次温湿度信息。

{0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C},// y
{0x00, 0x44, 0x64, 0x54, 0x4C, 0x44},// z  : 122-32
{0x14, 0x14, 0x14, 0x14, 0x14, 0x14},// horiz lines
{0x03, 0x03, 0x3E, 0x41, 0x41, 0x41},// |->°C bily add 20240602

gd32F103 硬件I2C调试
OLED显示时间,温湿度信息

;