STM8 硬件 I2C IIC 驱动 OLED AT24C02 实现单主机多从机通讯
本例最大的特点就是主机带防卡死功能,即使人为的干扰总线,主机也还能正常工作
/*
//STM8S105_IAR_V3.11
//本例由panxiaoyi(QQ68354248)原创,并在21ic.com首发,转载请保留,谢谢!
//本例使用硬件IIC读写AT24C02,并驱动IIC接口的OLED显示屏,适用于单主机,多从机通讯
//本例的主机发送和主机接收均使用软件查询方式
//本例在每次等待总线响应时,都加入了计数递减功能,当计数到零时就强制退出等待来防止主机被卡死
//本例没有错误重发数据功能,遇到错误后则IIC重新复位
*/
#include "stdio.h"
#include "iostm8s105k4.h"
#include "SYSTEM.h"
#include "UART.h"
#include "DELAY.h"
#include "IIC.h"
#include "FONT0805.h"
#include "SH1106.h"
#include "IIC_ROM.h"
#define AT24C02 160 //从机地址
unsigned short A;
unsigned char A2[]={"0AABBCCDD"};
unsigned char A3[]={"066778899"};
unsigned char A4[8];
unsigned char i;
int main( void )
{
SYSTEM_Init(); //系统初始化
UART2_Init(); //串口2初始化
IIC_Init(); //IIC初始化
asm("RIM"); //插入汇编,使能全局中断
printf("QQ68354248\n"); //使用该语句前必须包含stdio.h文件并重定义putchar函数
OLED_Init();
OLED_Clear(); //清屏
OLED_Cursor(11,1); //光标列行定位
OLED_ShowString("STM8S105 => SH1106"); //显示字符串
OLED_Cursor(53,3);
OLED_ShowU16(A); //以十进制显示U16变量
OLED_Cursor(65,5);
OLED_ShowChar('H'); //显示字符
A2[0]=24; //把AT24C02的ROM地址放在数组成员[0]里面
A3[0]=24;
while(1)
{
A++;
OLED_Cursor(53,3);
OLED_ShowU16(A);
if(A==3000) IIC_WriteData(AT24C02,9,A2); //主机写入(从机地址,写入数量,待写数据)
if(A==6000) IIC_WriteData(AT24C02,9,A3); //写入地址后,再写ROM地址,再写需要保存的数组
//DelayMs(2); //AT24C02内部写操作时,对寻址不响应,建议延时操作
IIC_ROM_Addr(AT24C02,24); //要读AT24C02的数据时,先写人ROM的地址(伪写)
IIC_ReadData(AT24C02,8,A4); //主机读取(从机地址,读取数量,保存地址)
OLED_Cursor(47,7);
for(i=0;i<8;i++) OLED_ShowChar(*(A4+i)); //显示读取的数据
if(A>=6000) A=0;
//下面是测试语句,如果IIC卡死,则LED不会闪烁
PE_DDR_DDR5=1; //输入=0, 输出=1
PE_CR1_C15=1; //如果是输出,0开漏,1推换; 如果是输入,0高阻抗,1上拉
if((A%100)==0) PE_ODR_ODR5=~PE_ODR_ODR5; //翻转电平驱动LED
}
}
/*
//STM8S105_IAR_V3.11
//本例由panxiaoyi(QQ68354248)原创,并在21ic.com首发,转载请保留,谢谢!
//本例使用硬件IIC读写AT24C02,并驱动IIC接口的OLED显示屏,适用于单主机,多从机通讯
//本例的主机发送和主机接收均使用软件查询方式
//本例在每次等待总线响应时,都加入了计数递减功能,当计数到零时就强制退出等待来防止主机被卡死
//本例没有错误重发数据功能,遇到错误后则IIC重新复位
*/
#include "IIC.h"
#define IIC_COUNT 64 //设定计数初值,CPU越快,通讯速度越慢,则此数值应更大
u8 iic_count=IIC_COUNT; //计数防卡死,如果CPU时钟远大于16M,则建议使用U16变量
u8 *count=&iic_count;
//-------------------------------------------
#pragma vector=19+2 //STM8S的IIC中断向量就一个,请留意中断标记是否有清零
__interrupt void I2C_IRQHandler(void) //本例没有用到该中断函数
{
I2C_ITR_ITEVTEN=0; //关闭事件中断
I2C_ITR_ITBUFEN=0; //关闭从机的接收缓冲未读/发送缓冲空中断,主机模式时无效!
I2C_ITR_ITERREN=0; //关闭错误与仲裁失败中断
}
//-------------------------------------------
void IIC_Init(void)
{
CLK_PCKENR1 |= 1; //外设IIC时钟门控使能
I2C_CR2_SWRST=1; //软件复位IIC,复位后SWRST自动清零
I2C_CR1=0;
I2C_CR2=0;
I2C_CR1_NOSTRETCH=0; //时钟延展使能(默认)
I2C_CR1_ENGC=1; //响应广播地址0x00
I2C_CR2_ACK=1; //回复ACK使能
//下面是100K通讯速度的设置(设置高电平宽度)
//输出时钟的电平的宽度比 L:H=1:1 (固定值)
//要求总线SCL电平的上升时间最大为1000ns
I2C_FREQR=16; //请填写驱动IIC的时钟,如16则表示16MHz
I2C_CCRH=0;
I2C_CCRH_F_S=0; //标准模式
I2C_CCRL=80; //设置H电平=80 则L电平=80 速度16M/(80+80)=100K
I2C_TRISER_TRISE=17; //设置SCL的最大上升值=1000/62.5+1=17
//下面是400K通讯速度的设置(设置高电平宽度)
//输出时钟的电平的宽度比 L:H=2:1 (默认值)
//要求总线SCL电平的上升时间最大为300ns
I2C_FREQR=16; //请填写驱动IIC的时钟,如16则表示16MHz
I2C_CCRH=0;
I2C_CCRH_F_S=1; //快速模式,其实就是改变L:H的比值
I2C_CCRL=14; //设置H电平=14 则L电平=28 速度16M/(14+28)=380K
I2C_TRISER_TRISE=6; //设置SCL的最大上升值=300/62.5+1=6
I2C_CR1_PE=1; //使能IIC
I2C_CR2_ACK=1; //回复ACK使能,只有在使能IIC后,本语句才能生效
}
//-------------------------------------------
void IIC_SendAddr(u8 addr, u8 rw) //主机发送地址与读写指令(高7位器件地址+1位R/W)
{
if(!iic_count) return; //如果曾经出现过卡死就退出
I2C_CR2_START=1; //发送START
iic_count=IIC_COUNT; //计数赋初始值
while(!I2C_SR1_SB) if(!(*count)--) break; //判断等待START完成,如果卡死就退出循环
if(!iic_count) return;
if(rw) I2C_DR=addr+1; else I2C_DR=addr; //加载(地址+读写位,SB即被硬件自动清零)
iic_count=IIC_COUNT;
while(!I2C_SR1_ADDR) if(!(*count)--) break; //判断等待地址发送完成,完成后,数据寄存器空置位
I2C_SR3; //读取SR1后再读写SR3,ADDR标记位清零
}
//-------------------------------------------
void IIC_ReadData(u8 addr, u8 x, u8 *dat) //主机读取多个数据(从机地址,读取数量,保存地址)
{
u8 i=0;
IIC_SendAddr(addr,1); //定位从机,读取
while(i<x)
{
if(!iic_count) break;
iic_count=IIC_COUNT;
while(!I2C_SR1_RXNE) if(!(*count)--) break; //等待收到数据
if(i==(x-2)) I2C_CR2_ACK=0; //准备读最后一个字节之前关闭ACK
*(dat+i)=I2C_DR; //读写DR即可清零RXNE
i++;
}
I2C_CR2_ACK=1; //回复ACK使能
IIC_Stop();
}
//-------------------------------------------
void IIC_WriteByte(u8 dat) //主机写单个字节
{
if(!iic_count) return;
I2C_DR=dat;
iic_count=IIC_COUNT;
while(!I2C_SR1_TXE) if(!(*count)--) break; //判断等待缓冲寄存器空
}
//-------------------------------------------
void IIC_WriteData(u8 addr, u8 x, u8 *dat) //主机写入多个数据(从机地址,写入数量,待写的数据地址)
{
u8 i=0;
IIC_SendAddr(addr,0); //定位从机,写入
while(i<x)
{
IIC_WriteByte(*(dat+i));
i++;
}
IIC_Stop();
}
//-------------------------------------------
void IIC_Stop(void) //主机发送STOP后,设备就进入了从模式,从机发送STOP则是释放总线
{
I2C_CR2_STOP=1; //发送STOP信号(只有当前字节接收或者发送完成后,STOP才开始)
iic_count=IIC_COUNT;
while(I2C_SR3_MSL) if(!(*count)--) break; //判断等待STOP发送完成
iic_count=IIC_COUNT; //计数重新赋值
I2C_SR2_AF=0; //清零各种错误
I2C_SR2_ARLO=0;
I2C_SR2_BERR=0;
}
//-------------------------------------------
void IIC_Off(void)
{
I2C_CR1_PE=0; //先关闭IIC
CLK_PCKENR1 &= (~1); //再关闭IIC输入时钟
}
当然,本例也具有软件模拟IIC功能,用来驱动OLED的效果也是非常好的(只能是主机单写功能)
//软件模拟IIC主机发送
//在时钟16M下,速度相当于硬件IIC的400K速率
#define IIC_SCL PB_ODR_ODR4 //SCL
#define IIC_SDA PB_ODR_ODR5 //SDA
void IIC_Delay(void)
{
asm("nop");
asm("nop");
}
//IIC初始化
void IIC_Init(void)
{
PB_CR1_C14=0; //设置PB4开漏输出, 由CR2相应的位做输出摆率控制
PB_CR1_C15=0;
PB_CR2_C24=1; //设置PB4速度,0=2MHz,1=10MHz
PB_CR2_C25=1;
PB_DDR_DDR4=1; //设置PB4为输出
PB_DDR_DDR5=1;
IIC_SCL=1; //时钟数据STOP
IIC_Delay();
IIC_SDA=1;
IIC_Delay();
}
//向接收方写入一个字节
void IIC_WriteByte(u8 dat)
{
u8 i;
for(i=0;i<8;i++)
{
if(dat&0x80) IIC_SDA=1; else IIC_SDA=0;
IIC_Delay();
IIC_SCL=1;
IIC_Delay();
IIC_SCL=0;
IIC_Delay();
dat<<=1;
}
//模拟接收ACK
IIC_SDA=0;
IIC_Delay();
IIC_SCL=1;
IIC_Delay();
IIC_SCL=0;
IIC_Delay();
IIC_SDA=0;
IIC_Delay();
}
//写接收方地址与读写方向
void IIC_SendAddr(u8 addr, u8 rw)
{
IIC_Stop(); //加入此句是为了兼容再次START
IIC_SDA=0; //发起Stair信息
IIC_Delay();
IIC_SCL=0;
IIC_Delay();
if(rw) IIC_WriteByte(addr+1); else IIC_WriteByte(addr); //加载(地址+读写位)
}
//结束信号
void IIC_Stop(void)
{
IIC_SCL=1;
IIC_Delay();
IIC_SDA=1;
IIC_Delay();
}
本OLED驱动具有较高的通讯效率
//
#include "SH1106.h"
//SSD1306(12864)的列显示从列地址0开始
// SH1106(13264)的列显示从列地址2开始
//光标定位
void OLED_Cursor(unsigned char x, unsigned char y)
{
unsigned char x2;
x2=x+2;
IIC_SendAddr(0x78,0);
IIC_WriteByte(0x00);
IIC_WriteByte(0xb0+y);
IIC_WriteByte(((x2&0xf0)>>4)|0x10);
IIC_WriteByte((x2&0x0f));
IIC_Stop();
}
//清屏
void OLED_Clear(void)
{
unsigned char i,n;
for(i=0;i<8;i++)
{
IIC_SendAddr(0x78,0); //高7位器件地址+1位R/W
IIC_WriteByte(0x00); //准备写指令
IIC_WriteByte(0xb0+i); //设置页地址(0~7)
IIC_WriteByte(0x00); //设置显示位置—列低地址
IIC_WriteByte(0x10); //设置显示位置—列高地址
IIC_SendAddr(0x78,0);
IIC_WriteByte(0x40); //准备写数据
for(n=0;n<132;n++) IIC_WriteByte(0); //兼容128和132点阵
IIC_Stop();
}
}
//=====================================================================================
void OLED_ShowChar(unsigned char dat) //显示字符
{
unsigned char i=0;
unsigned int No;
No=dat-32; //字模数据是由空格开始,空格字符的ASCII的值就是32
No=No*5; //每个字符的字模是5个字节
IIC_SendAddr(0x78,0);
IIC_WriteByte(0x40);
while(i<5) //一个字符的字模是5个字节,就是5*8点阵
{
IIC_WriteByte(font0805[No]);
i++;
No++;
}
IIC_WriteByte(0); //每个字符之间空一列
IIC_Stop();
}
//=====================================================================================
void OLED_ShowString(unsigned char *s) //显示字符串,C编译器会在字符串后面加\0
{
while(*s) //检测字符串结束符
{
OLED_ShowChar(*s++);
}
}
//=======================================================================================
void OLED_ShowU16(unsigned int dat) //显示U16变量
{
unsigned int i;
for(i=10000; i>=1; i=i/10)
{
OLED_ShowChar(dat/i%10+0x30);
}
}
//初始化
void OLED_Init(void) //下列参数参考OLED厂家的设置
{
DelayMs(99);
IIC_SendAddr(0x78,0); //总线OLED地址
IIC_WriteByte(0x00); //准备写指令
IIC_WriteByte(0xae); //关显示
IIC_WriteByte(0xd5); //晶振频率
IIC_WriteByte(0x80);
IIC_WriteByte(0xa8); //duty 设置
IIC_WriteByte(0x3f); //duty=1/64
IIC_WriteByte(0xd3); //显示偏移
IIC_WriteByte(0x00);
IIC_WriteByte(0x40); //起始行
IIC_WriteByte(0x8d); //升压允许
IIC_WriteByte(0x14);
IIC_WriteByte(0x20); //page address mode
IIC_WriteByte(0x02);
IIC_WriteByte(0xc8); //行扫描顺序:从上到下
IIC_WriteByte(0xa1); //列扫描顺序:从左到右
IIC_WriteByte(0xda); //sequential configuration
IIC_WriteByte(0x12);
IIC_WriteByte(0x81); //微调对比度,本指令的 0x81 不要改动,改下面的值
IIC_WriteByte(0x01); //微调对比度的值,可设置范围 0x00~0xff
IIC_WriteByte(0xd9); //Set Pre-Charge Period
IIC_WriteByte(0xf1);
IIC_WriteByte(0xdb); //Set VCOMH Deselect Level
IIC_WriteByte(0x40);
IIC_WriteByte(0xaf); //开显示
IIC_Stop();
}
最后是源代码和图片
|
|