STC32G 系列的单片机内部集成了一个 I2C 串行总线控制器。I2C 是一种高速同步通讯总线,通讯使用 SCL(时钟线)和 SDA(数据线)两线进行同步通讯。对于 SCL 和 SDA 的端口分,STC32G 系列的单片机提供了切换模式,可将 SCL 和 SDA 切换到不同的 I/O 口上,以方便用户将一组 I2C 总线当作多组进行分时复用。与标准 I2C 协议相比较,忽略了如下两种机制:
发送起始信号(START)后不进行仲裁
时钟信号(SCL)停留在低电平时不进行超时检测
STC32G 系列的 I2C 总线提供了两种操作模式:主机模式(SCL 为输出口,发送同步时钟信号)和从机模式(SCL 为输入口,接收同步时钟信号),这里要实现oled12864的显示,所以用到的是主机模式。
如上图所示,要用到的初始化函数为:void I2C_Init(I2C_InitTypeDef *I2Cx),在主机模式下要对结构体的属性进行设置的只有:I2C_InitStructure.I2C_Mode,I2C_InitStructure.I2C_Enable, I2C_InitStructure.I2C_Speed,I2C_InitStructure.I2C_MS_WDTA,
从stc32g的数据手册知道内部集成的I2C总线功能是有规定引脚的,这里我们选择SCL:P25,SDA:P24:
所以,我们的I2C初始化函数为:
void I2C_config(void)
{
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_Master; //主从选择 I2C_Mode_Master, I2C_Mode_Slave
I2C_InitStructure.I2C_Enable = ENABLE; //I2C功能使能, ENABLE, DISABLE
I2C_InitStructure.I2C_Speed = 13; //I2C总线速度, 0~63
I2C_InitStructure.I2C_MS_WDTA = DISABLE; //使能主机自动发送设置, ENABLE, DISABLE
I2C_Init(&I2C_InitStructure);
I2C_SW(I2C_P24_P25); //I2C_P14_P15,I2C_P24_P25,I2C_P76_P77,I2C_P33_P32
}
接下来看一下官方的库函数中的STC32G_I2C.c文件
//========================================================================
// 函数: u8 Get_MSBusy_Status (void)
// 描述: 获取主机忙碌状态.
// 参数: none.
// 返回: 主机忙碌状态.
// 版本: V1.0, 2012-11-22
//========================================================================
u8 Get_MSBusy_Status(void)
{
return (I2CMSST & 0x80);
}
//========================================================================
// 函数: void Wait (void)
// 描述: 等待主机模式I2C控制器执行完成I2CMSCR.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2012-11-22
//========================================================================
void Wait()
{
while (!(I2CMSST & 0x40));
I2CMSST &= ~0x40;
}
//========================================================================
// 函数: void Start (void)
// 描述: I2C总线起始函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void Start()
{
I2CMSCR = 0x01; //发送START命令
Wait();
}
//========================================================================
// 函数: void SendData (char dat)
// 描述: I2C发送一个字节数据函数.
// 参数: 发送的数据.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void SendData(char dat)
{
I2CTXD = dat; //写数据到数据缓冲区
I2CMSCR = 0x02; //发送SEND命令
Wait();
}
//========================================================================
// 函数: void RecvACK (void)
// 描述: I2C获取ACK函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void RecvACK()
{
I2CMSCR = 0x03; //发送读ACK命令
Wait();
}
//========================================================================
// 函数: char RecvData (void)
// 描述: I2C读取一个字节数据函数.
// 参数: none.
// 返回: 读取数据.
// 版本: V1.0, 2020-09-15
//========================================================================
char RecvData()
{
I2CMSCR = 0x04; //发送RECV命令
Wait();
return I2CRXD;
}
//========================================================================
// 函数: void SendACK (void)
// 描述: I2C发送ACK函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void SendACK()
{
I2CMSST = 0x00; //设置ACK信号
I2CMSCR = 0x05; //发送ACK命令
Wait();
}
//========================================================================
// 函数: void SendNAK (void)
// 描述: I2C发送NAK函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void SendNAK()
{
I2CMSST = 0x01; //设置NAK信号
I2CMSCR = 0x05; //发送ACK命令
Wait();
}
//========================================================================
// 函数: void Stop (void)
// 描述: I2C总线停止函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void Stop()
{
I2CMSCR = 0x06; //发送STOP命令
Wait();
}
//========================================================================
// 函数: void SendCmdData (u8 cmd, u8 dat)
// 描述: I2C发送一个字节数据函数.
// 参数: 命令/数据.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void SendCmdData(u8 cmd, u8 dat)
{
I2CTXD = dat; //写数据到数据缓冲区
I2CMSCR = cmd; //设置命令
Wait();
}
//========================================================================
// 函数: void I2C_WriteNbyte(u8 addr, u8 *p, u8 number)
// 描述: I2C写入数据函数.
// 参数: addr: 指定地址, *p写入数据存储位置, number写入数据个数.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void I2C_WriteNbyte(u8 addr, u8 *p, u8 number) /* WordAddress,First Data Address,Byte lenth */
{
Start(); //发送起始命令
SendData(SLAW); //发送设备地址+写命令
RecvACK();
SendData(addr); //发送存储地址
RecvACK();
do
{
SendData(*p++);
RecvACK();
}
while(--number);
Stop(); //发送停止命令
}
//========================================================================
// 函数: void I2C_ReadNbyte(u8 addr, u8 *p, u8 number)
// 描述: I2C读取数据函数.
// 参数: addr: 指定地址, *p读取数据存储位置, number读取数据个数.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void I2C_ReadNbyte(u8 addr, u8 *p, u8 number) /* WordAddress,First Data Address,Byte lenth */
{
Start(); //发送起始命令
SendData(SLAW); //发送设备地址+写命令
RecvACK();
SendData(addr); //发送存储地址
RecvACK();
Start(); //发送起始命令
SendData(SLAR); //发送设备地址+读命令
RecvACK();
do
{
*p = RecvData();
p++;
if(number != 1) SendACK(); //send ACK
}
while(--number);
SendNAK(); //send no ACK
Stop(); //发送停止命令
}
这个文件中有我们所熟知的I2C通信所必须的Wait(),Start(),Senddata()等的函数,它们的用法用过I2C通信的相信都知道了,但在STC32G_I2C.h这个头文件中只包含了以下几个函数的声明:
void I2C_Init(I2C_InitTypeDef *I2Cx);
void I2C_WriteNbyte(u8 addr, u8 *p, u8 number);
void I2C_ReadNbyte( u8 addr, u8 *p, u8 number);
u8 Get_MSBusy_Status(void);
void SendCmdData(u8 dat, u8 cmd);
所以我们使用I2C总线有两种方法,第一种是把所有函数的声明都添加到头文件中,这样就可以调用Wait(),Start(),Senddata()等函数;第二种方法是用头文件中给的函数,也就是I2C_WriteNbyte(u8 addr, u8 *p, u8 number)这个函数。这里我们详细讲第二种方法,I2C_WriteNbyte()这个函数需要输入三个参数,u8 addr, u8 *p, u8 number,这里指定地址我们可以理解为对oled是进行写数据操纵还是写命令操作,所以输入时输入以下两个中一个就行:
#define OLED_CMD 0x00 //写命令
#define OLED_DATA 0x40 //写数据
p为要输入数据或命令的指针,可以一次输入多个数据,number为数据数。这里还有一点要注意的:SLAW的宏定义需要修改如下:
#define SLAW 0x78
然后就可以移植中景园的oled51例程,需要修改的只有OLED_WR_Byte()这一函数:
void OLED_WR_Byte(u8 dat,u8 mode)
{
#if 0
Start();
SendData(0x78);
RecvACK();
SendData(mode);
RecvACK();
SendData(dat);
SendNAK();
Stop();
#else
I2C_WriteNbyte(mode,&dat,1);
#endif
}
然后就可以显示啦
还有两点要注意的是,这里的memory mode 需要修改一下:
原因可能是库函数中定义的常量太多导致无法显示,修改后就可正常显示。而另一个问题是我发现在单独显示字符串,也就是在主函数中只调用OLED_ShowString()这一函数时oled无法显示,并且好像连单片机都无法正常使用,有大神路过的话希望能帮忙解惑一下。
同样的,项目我放在主页资源上。