一.平台
芯片:STM8S103F3P6
环境:IAR + STVP
系统:WIN7
二. 目的
STM8S103F3P6:使用STM8标准库开发
角色------从机
方式----------硬件IIC
STM32H7:
角色------主机
方式----------IO口模拟IIC主机
主机发送命令包,从机接收后进行判断
主机发送读取命令,从机返回上次命令包判断后要返回的数据包
三.STM8硬件IIC
STM8S103时钟
由于该芯片实际应用是放到控制板作为一个附属芯片,不考虑功耗、尽可能采用高的频率,且需要满足硬件I2C对时钟的需求(后续讲到),由STM8S103参考手册时钟章节可看出,接外部高速时钟最高是16M,内部RC最高16M,因此时钟最高是16M。测试时使用的是内部RC振荡器时钟。
由STM8S系列参考手册时钟章节,时钟树上无对时钟进行倍频的单元,因此HSE或者HSI都是16M时钟输入,如果不进行分频,那么同样对CPU时钟而言都是16M。为了方便 (懒。。。),在初步测试时使用的是内部RC振荡器16M。
硬件IIC
从STM8S103数据手册上看I2C有两种支持速率:最高到100K和400K
使用的是PB5复用I2C_SDA,PB4复用I2C_SCL
需要注意的是:选项字节中的OPT2中的AFR4需要是0,如果不是0则PB4和PB5不是复用为I2C引脚
另外,需要注意的是,由下图看:
I2C在100K和400K速率下对时钟要求不同,且。。且。。。且主机用的是IO口模拟IIC,必须符合相应的最短电平持续时间
还有一点:如果使用400K速度,那么主时钟至少是8M
硬件IIC库
从IIC库的相关文件上开放了一些接口,我仅用到了两个,就只说这两个
I2C_Init
void I2C_Init(uint32_t OutputClockFrequencyHz, uint16_t OwnAddress,
I2C_DutyCycle_TypeDef I2C_DutyCycle, I2C_Ack_TypeDef Ack,
I2C_AddMode_TypeDef AddMode, uint8_t InputClockFrequencyMHz )
功能:I2C硬件初始化
参数:OutputClockFrequencyHz ----- 输出时钟频率
OwnAddress-------本I2C设备地址
I2C_DutyCycle-----占空比,高低电平的时间,和速率有关,详情看手册CCR寄存器
Ack---------应答模式,接收一个字节数据后是否产生应答
AddMode----采用7/10位地址设置
InputClockFrequencyMHz----提供给I2C硬件时钟
I2C_ITConfig
void I2C_ITConfig(I2C_IT_TypeDef I2C_IT, FunctionalState NewState)
功能:I2C的中断配置
参数:I2C_IT ----- 中断类型,如接收到本机地址则产生中断
NewState----中断使能/关闭
四.代码
代码利用测试板和测试过,跑的是100K的速率,能正常运行
CPU时钟:分频配置为不分频,16M
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
I2C硬件初始化
u32 CPU_CLK;// CPU时钟
// GPIOB4作为上拉无中断输入,GPIOB5高阻态快速输出(10M大小)
GPIO_Init(GPIOB,GPIO_PIN_4, GPIO_MODE_IN_PU_NO_IT);
GPIO_Init(GPIOB,GPIO_PIN_5, GPIO_MODE_OUT_OD_HIZ_FAST);
// 获取当前CPU时钟,M为单位
CPU_CLK = CLK_GetClockFreq()/1000000;
/*
I2C初始化:
不需输出时钟,随便写个400000,设备地址0XA0,实际上最后一位是读写控制位
每接收一个字节回应答,7位地址模式,输入I2C时钟是上面的CPU_CLK
*/
I2C_Init(400000, 0XA0 , I2C_DUTYCYCLE_2, I2C_ACK_CURR, I2C_ADDMODE_7BIT, CPU_CLK);//I2C初始化
/*
I2C中断配置:
开启 错误中断、事件中断(如匹配了地址、接收了一字节数据等)、BUF中断(接收和发送相关)
*/
I2C_ITConfig((I2C_IT_TypeDef)(I2C_IT_ERR | I2C_IT_EVT | I2C_IT_BUF), ENABLE) ;
开总中断
__enable_interrupt();
I2C中断函数
在写中断函数之前,先看一下参考手册关于I2C硬件的部分功能描述
I2C接口在接收到地址并且匹配上时,根据ACK可以自动产生一个应答信号,且会将ADDR标志写1,产生一个中断
总结:I2C设备接收到别人发过来的地址,匹配上时,可以自动应答,并且产生中断。
但是,如何对本设备进行读和写的区分呢?因为本次我们配置的是7位地址模式,上面配置本机地址为0XA0,按照最后一位是0还是1,I2C硬件自动识别是读还是写的动作。如果起始信号后,主机发送0XA0,那么主机对从机进行写操作;如果起始信号后,主机发送0XA1,那么主机对从机进行读操作。
从设备接收模式(主机发送、从机接收,因此DR寄存器用来接收数据)
可以得出信息:
.接收到地址后,要清除ADDR标志
.在清除标志后,才会从移位寄存器的数据放到DR寄存器上(因此,接收时要及时清除ADDR标志)
.每接收一个字节数据可以自动应答
.接收一个数据后,RxNE位置1,可产生中断
注意:在接收数据后,DR寄存器没被读出时,I2C接口SCL是一直保持为低电平的,这个时候,主机进行发送是没效果的。因此要在中断中及时读取走DR寄存器
EV1是在地址应答阶段后,解释说明指出要清除ADDR,通过读SR1和SR3
EV2是每接收到一个数据,要通过读DR来清除,如果不读则出现上述情况
EV4是接收到停止信号后,通过读SR1,写CR2寄存器清除标志
从设备发送模式(主机接收、从机发送,因此DR寄存器用来发送数据)
可以得出信息:
.在接收地址后并清除了ADDR标志位后,从设备才会将DR寄存器发出
.如果ADDR标志不清除、DR寄存器没及时写入,SCL会一直被拉低,主机操作无效果(特别重要,清除标志,对DR寄存器写操作越快越往前越好)
.如果发送完成,可以产生中断
请注意地址应答后的阶段是 EV1/EV3-1/EV3,这个时候就是需要及时清标志位和写DR寄存器进行发送。如果不呢?
像我当时在中断中还执行了一些语句,并没有把写DR寄存器放到靠前位置,也有一些多余未优化的语句,导致从逻辑分析仪的图形中可以明显看出SCL被一直拉低了,其实我在主机IO模拟中,应答动作后SCL拉低没有那么长的延时的,而是很规律的时钟。
状态寄存器
SR1:
TXE-------发送状态,如果DR寄存器没有发送,那么置1
RXNE----接收到数据,置1
STOPF–接收到停止信号,置1
BTF-----接收到数据/发送数据完成,但还没读取/再次写入
ADDR–匹配到设备地址
SR2:一些错误标志位
SR3:
TRA:处于接收还是发送的状态
代码:由于一些原因,将I2C的数据包格式协议这些用一些变量和数组代替,有需要用的记得根据自己的需求改动
// 测试相关
u8 Test_read[50] = “HELLO ,THANK U,THANK U VERY MUCH”;
u8 Test_r_num = 31; //从机发送个数
u8 Test_r_pos = 0; // 发送指针
u8 Test_Write[50] = {0};
u8 Test_w_num = 0; // 主机发送数据个数
u8 Commucation_flag = 0;// 通信完成标志
/**
* @brief I2C Interrupt routine.
* @param None
* @retval None
*/
INTERRUPT_HANDLER(I2C_IRQHandler, 19)
//void IIC_Interrupt(void)
{
/* In order to detect unexpected events during development,
it is recommended to set a breakpoint on the following instruction.
*/
//接收发送
if (I2C->SR1&0x02)//地址已经匹配 ADDR标志
{
// 清ADDR标志:读SR1,读SR3
// 主机读,从机发出的情况,DR寄存器写数据越快越好
// 判断是否是读操作 TRA
if ((I2C->SR3)&0x04)
{
if (Test_r_pos < Test_r_num)
{
I2C->DR = Test_read[Test_r_pos ];
Test_r_pos ++;
}
else
I2C->DR = 0XFF;
}
else
{
Test_r_pos=0;
}
Test_w_num= 0;
}
else if ((I2C->SR3)&0x04)//如果是接收状态:
{
// 查看TXE是否DR为空
if ((((I2C->SR1)&0x80) == 0X80))
{
if (Test_r_pos < Test_r_num)
{
I2C->DR = Test_read[Test_r_pos ];
Test_r_pos ++;
}
else
I2C->DR = 0XFF;
}
}
else if (((I2C->SR1)&0x40)&&(!((I2C->SR3)&0x04)))
{
// 读DR,清接收标志
u8 data = I2C->DR;
// 如果主机发送,从机接收到数据
if (Test_w_num < 50)
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』