×
嵌入式 > 技术百科 > 详情

STM8硬件IIC从机

发布时间:2020-06-09 发布时间:
|

一.平台


芯片: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)


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
ZigBee技术语音图像无线监控系统的设计与实现