历史上的今天
返回首页

历史上的今天

今天是:2025年04月02日(星期三)

正在发生

2019年04月02日 | STM8L051的硬件I2C调试

2019-04-02 来源:eefocus

I2C是现代一种极为常见的低速外设通信协议,比起SPI或者UART,它最大的优势应该就是节省芯片管脚了:理论上只要地址够用,多少外设挂I2C总线上都没问题,只占两个管脚。但也因此,I2C的协议就相对复杂一些,以面对多个外设。同时,过多的外设也使得通信速率难以提升,一般只在100kbps或以下。本文不专门介绍I2C的时序和协议,而介绍我在调试STM8L051的硬件I2C的过程以及遇到的问题,和大家分享。


我的实验电路由两个独立的STM8L051模块组成,做一发一收。这两个模块的电路是我自己设计的,通过排针插在面包板上,如图所示。这两个芯片的硬件I2C在PC0和PC1,将他们连起来并用4.7K电阻上拉(请原谅我没有直插电阻然后用贴片凑合的无奈T T)。右边是做接收的模块,将它的串口接出好观察结果(我非常喜欢用串口调试,几乎拿到什么板子,第一件事情就是把串口先调出来)。 


这里写图片描述


首先是用库函数进行开发还是直接写寄存器编程的问题。因为懒,我个人更喜欢用库函数,这次调试也是用库函数编程。其实我感觉意法半导体的单片机(尤其STM32)能够流行,其设计合理的库函数是一个关键原因。另外ST官方也为库函数写了大量的例程,使得参考和移植都会方便。但使用库函数其实会运行很多没必要的代码,以及各种函数调用,都会耗费时间和存储资源,在资源本身就紧张8位单片机上用库函数其实是很低效的。我一个师兄表示他在STM8上一直都是直接写寄存器。 


ST官方库函数的例程中,有两个板子对通的程序,他的设计是,先进行主机发送、从机接收,然后从机发送,主机接收,主机收回后比较数据,判断传输是否有误。为了方便研究,我将例程分开,分别测试主发从收和主收从发两个过程。


一、 主机发送,从机接收

为了观看传输结果,我会事先配置串口。串口配置程序和串口输出字符串程序如下:


void USART_Config(void)

{

  USART_DeInit(USART1); // DeInit

  CLK_PeripheralClockConfig(CLK_Peripheral_USART1, ENABLE); // SysClk for USART1

  SYSCFG_REMAPPinConfig(REMAP_Pin_USART1TxRxPortA, ENABLE); // Remap TX on PA2 and RX on PA3

  USART_Init(USART1, (uint32_t)9600, USART_WordLength_8b, USART_StopBits_1,

             USART_Parity_No, USART_Mode_Tx);

  USART_Cmd(USART1, ENABLE);

}

void UART_SendStr(char *str)

{

  int i = 0;

  for(i=0;str[i]!=0;i++)

  {

    while (!(USART1->SR & 0x80));   /* wait for READY */

    USART_SendData8(USART1,str[i]);

  }

}



程序中串口被remap到了PA2和PA3,这主要是因为STM8L051芯片没有PC2和PC3,所以必须remap。波特率设为9600,只进行输出,不提供中断。


STM8L的硬件I2C在其参考手册RM0031中有详细的叙述(https://www.st.com/content/ccc/resource/technical/document/reference_manual/2e/3b/8c/8f/60/af/4b/2c/CD00218714.pdf/files/CD00218714.pdf/jcr:content/translations/en.CD00218714.pdf )。为了方便,我只实现7位地址的I2C通信。 

在主发从收通信中,主机会遇到的事件包括EV5(发送完START bit)、EV6(发送完从机地址并收到ACK)、EV8(TXE,发送寄存器空,即发送了一个字节)和EV8_2(发送完成)。主机在中断中处理这些问题。I2C设置代码如下:


void I2C_Config(void)

{

  CLK_PeripheralClockConfig(CLK_Peripheral_I2C1, ENABLE);

  I2C_DeInit(I2C1);

  I2C_Init(I2C1, 100000, 0xA0,

           I2C_Mode_I2C, I2C_DutyCycle_2,

           I2C_Ack_Enable, I2C_AcknowledgedAddress_7bit);

  I2C_ITConfig(I2C1, (I2C_IT_TypeDef)(I2C_IT_EVT | I2C_IT_BUF), ENABLE);

}


要发送数据时,I2C先发送开始符号,然后等待发送完成:


I2C_GenerateSTART(I2C1, ENABLE); // Start and into Master Mode

while(NumOfBytes); // Wait for all bytes have been transmitted


主机的中断服务程序在官方样例基础上缩减:


#define SLAVE_ADDRESS 0x30

__IO uint8_t TxBuffer[32] = "Get it!n";

__IO uint8_t NumOfBytes = 9;

__IO uint8_t Tx_Idx =0;

INTERRUPT_HANDLER(I2C1_SPI2_IRQHandler,29)

{

    switch (I2C_GetLastEvent(I2C1))

  {

      /* EV5 */

    case I2C_EVENT_MASTER_MODE_SELECT :

      /* Send slave Address for write */

      I2C_Send7bitAddress(I2C1, SLAVE_ADDRESS, I2C_Direction_Transmitter);

      break;


      /* EV6 */

    case I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED:

      if (NumOfBytes != 0)

      {

        /* Send the first Data */

        I2C_SendData(I2C1, TxBuffer[Tx_Idx++]);


        /* Decrement number of bytes */

        NumOfBytes--;

      }

      if (NumOfBytes == 0)

            {

        I2C_ITConfig(I2C1, I2C_IT_BUF, DISABLE);

      }

      break;


      /* EV8 */

    case I2C_EVENT_MASTER_BYTE_TRANSMITTING:

      /* Transmit Data */

      I2C_SendData(I2C1, TxBuffer[Tx_Idx++]);


      /* Decrement number of bytes */

      NumOfBytes--;


      if (NumOfBytes == 0)

      {

        I2C_ITConfig(I2C1, I2C_IT_BUF, DISABLE);

      }

      break;


      /* EV8_2 */

    case I2C_EVENT_MASTER_BYTE_TRANSMITTED:

      /* Send STOP condition */

      I2C_GenerateSTOP(I2C1, ENABLE);

      I2C_ITConfig(I2C1, I2C_IT_EVT, DISABLE);

      break;


    default:

      break;

  }

}


从机的接收过程更为简单。从机设置时将I2C地址设置为0x30(主机向0x30发送信息),其他和主机相同,然后开启中断等待即可。从机接收过程中遇到的事件有EV1(收到主机发送的本机地址)、EV2(收到一个字节数据)和EV4(停止传输)。其中断服务程序也就是为这些事件准备的:


__IO uint8_t Slave_Buffer_Rx[32];

__IO uint8_t Rx_Idx = 0;

__IO uint16_t Event = 0x00;

uint8_t RecvFlag = 0;


INTERRUPT_HANDLER(I2C1_SPI2_IRQHandler,29)

{

    Event = I2C_GetLastEvent(I2C1);

  switch (Event)

  {

      /******* Slave transmitter ******/

      /* check on EV1 */

    case I2C_EVENT_SLAVE_TRANSMITTER_ADDRESS_MATCHED:

      break;


      /* check on EV3 */

    case I2C_EVENT_SLAVE_BYTE_TRANSMITTING:

      break;

      /******* Slave receiver **********/

      /* check on EV1*/

    case I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED:

      break;


      /* Check on EV2*/

    case I2C_EVENT_SLAVE_BYTE_RECEIVED:

      Slave_Buffer_Rx[Rx_Idx++] = I2C_ReceiveData(I2C1);

      break;


      /* Check on EV4 */

    case (I2C_EVENT_SLAVE_STOP_DETECTED):

            /* write to CR2 to clear STOPF flag */

            I2C1->CR2 |= I2C_CR2_ACK;

            RecvFlag = 1;

      break;


    default:

      break;

  }

}


程序中,我用RecvFlag标记接收完成,主程序在RecvFlag为1时,将收到的字符串从串口发出。主机发来的是“Get it!n”,从串口看到结果如下图所示(发送了2次): 


这里写图片描述

二、主机接收,从机发送

官方例程中的主机接收代码没有用中断,我这里也如此操作,以后有时间再试试主机中断接收。主机设置代码为:


void I2C_Config(void)

{

  CLK_PeripheralClockConfig(CLK_Peripheral_I2C1, ENABLE);

  I2C_DeInit(I2C1);

  I2C_Init(I2C1, 100000, 0xA0,

           I2C_Mode_I2C, I2C_DutyCycle_2,

           I2C_Ack_Enable, I2C_AcknowledgedAddress_7bit);

}


在主机接收过程中,其实传输进程还是主机控制的。在开始传输后,经历EV5、EV6、EV7(主机接收到从机一个字节数据)和EV7_1(主机接收从机最后一个字节),在main()函数中运行如下代码来传输:


while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));

I2C_GenerateSTART(I2C1, ENABLE); // Start and into Master Mode

/* Test on EV5 and clear it */

while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

/* Send slave Address for write */

I2C_Send7bitAddress(I2C1, SLAVE_ADDRESS, I2C_Direction_Receiver);

/* Test on EV6 and clear it */

while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));

/* While there is data to be read */    

while(NumOfBytes)

{

  /* The last bytes need STOP but not ACK */

  if (NumOfBytes == 1)

  {

    /* Disable Acknowledgement */

    I2C_AcknowledgeConfig(I2C1, DISABLE);


    /* Send STOP Condition */

    I2C_GenerateSTOP(I2C1, ENABLE);


    /* Poll on RxNE Flag */

    while ((I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) == RESET));

    /* Read a byte */

    RxBuffer[Rx_Idx++] = I2C_ReceiveData(I2C1);

    /* Decrement the read bytes counter */

    NumOfBytes--;

  }

  /* Test on EV7 and clear it */

  if (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED) )

  {

    /* Read a byte */

    RxBuffer[Rx_Idx++] = I2C_ReceiveData(I2C1);

    /* Decrement the read bytes counter */

    NumOfBytes--;

  }

}



代码中,在收到最后一个字节(NumOfBytes == 1)时将ACK Disable并发送STOP结束传输过程。


在从机发射端,依然使用中断来处理发送过程,从机设置和前一节相同。从机发射要经历EV1、EV3(TXE=1,发送寄存器空,发送了一个字节数据)和EV3_2(AF=1,未收到ACK)中断处理代码如下:


INTERRUPT_HANDLER(I2C1_SPI2_IRQHandler,29)

{

/* check on EV3_2 */

if (I2C_ReadRegister(I2C1, I2C_Register_SR2))

  {

    /* Clears SR2 register */

    I2C1->SR2 = 0;

  }

Event = I2C_GetLastEvent(I2C1);

  switch (Event)

  {

      /******* Slave transmitter ******/

      /* check on EV1 */

    case I2C_EVENT_SLAVE_TRANSMITTER_ADDRESS_MATCHED:

        Tx_Idx = 0;

      break;


      /* check on EV3 */

    case I2C_EVENT_SLAVE_BYTE_TRANSMITTING:

        I2C_SendData(I2C1, Slave_Buffer_Tx[Tx_Idx++]);

      break;

      /******* Slave receiver **********/

      /* check on EV1*/

    case I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED:

      break;


      /* Check on EV2*/

    case I2C_EVENT_SLAVE_BYTE_RECEIVED:

      break;


      /* Check on EV4 */

    case (I2C_EVENT_SLAVE_STOP_DETECTED):

      break;


    default:

      break;

  }

}


STM8L的从机发送的结束机制值得好好吐槽一下。我看到有网络上帖子说主收从发只能收一次,我之前也删掉了if (I2C_ReadRegister(I2C1, I2C_Register_SR2))这个判断,因为I2C_SR2其实是个错误寄存器,我想传输没错误的话应该就不用管它了,然后就只能传一次。直到再次读手册RM0031,看到 EV3-2: AF=1, AF is cleared by writing ‘0’ in AF bit of SR2 register.这句话,AF是Acknowledge Failure,也就是说,它其实是根据没收到ACK来判断传输结束的……将这个寄存器清零后,硬件I2C恢复初始状态。 

最后验证,从机发送Got it!n,主机收到发送到电脑上结果为: 

这里写图片描述

推荐阅读

史海拾趣

Ford Aerospace & Communications Corp公司的发展小趣事

在汽车制造领域,福特一直是技术创新的领导者。20世纪60年代,福特开始将电子设备集成到汽车中,包括车载电话、导航系统以及早期的娱乐系统。这些创新不仅提升了驾驶的便捷性和舒适性,也预示着未来汽车电子化、智能化的趋势。虽然这些技术并不直接属于航空通信范畴,但它们展示了福特在电子技术应用方面的前瞻性和实力。

Cedrat Technologies公司的发展小趣事

随着技术的不断进步和产品质量的提升,Cedrat Technologies开始积极拓展国际市场。公司凭借其卓越的技术实力和优质的产品服务,成功打入欧美等发达国家市场,赢得了众多客户的青睐。同时,公司也积极参与国际电子行业的交流与合作,提升了品牌知名度和国际影响力。

这些故事只是Cedrat Technologies在电子行业发展中的一小部分缩影,它们展示了公司在技术创新、产品应用和市场拓展等方面的努力和成果。未来,随着电子行业的不断发展和变革,相信Cedrat Technologies将继续保持其领先地位,为行业的发展贡献更多的力量。

Apacer公司的发展小趣事

随着业务的不断发展,Apacer宇瞻科技开始在全球范围内布局。除了在台湾设立总部外,公司还在上海设立了宇瞻电子(上海)有限公司,并在美国、欧洲、日本、印度等地设立了分公司。这一全球布局使得宇瞻科技能够更好地服务全球客户,拓展市场份额。

Carlisle Interconnect Components公司的发展小趣事

Carlisle Interconnect Components公司深知品质是赢得市场的关键。因此,公司建立了严格的质量管理体系,从原材料采购到生产制造,再到产品出厂,每一个环节都严格把控。这种对品质的执着追求,使得公司的产品在国际市场上获得了良好的口碑。同时,公司还积极拓展海外市场,与多家国际知名企业建立了稳定的合作关系,进一步提升了公司在全球电子连接器市场的影响力。

Cooper Tools(Eaton)公司的发展小趣事

Carlisle Interconnect Components公司深知品质是赢得市场的关键。因此,公司建立了严格的质量管理体系,从原材料采购到生产制造,再到产品出厂,每一个环节都严格把控。这种对品质的执着追求,使得公司的产品在国际市场上获得了良好的口碑。同时,公司还积极拓展海外市场,与多家国际知名企业建立了稳定的合作关系,进一步提升了公司在全球电子连接器市场的影响力。

超音(CY)公司的发展小趣事

超音(CY)公司成立于XXXX年,初期专注于音频设备的研发和生产。创始人XXX先生凭借对音乐的热爱和对技术的追求,决定进入电子行业,并确定了音频设备作为公司的主要发展方向。在初创阶段,超音面临了诸多挑战,包括资金短缺、技术瓶颈和市场竞争等。然而,通过不懈的努力和创新,超音逐渐在音频设备领域崭露头角。

问答坊 | AI 解惑

怎样才能学好电子技术

怎样才能学好电子技术   这个问题很大,解决这个问题是一个系统工程,首先需要时间,还要多看书和多实践。   电子技术、无线电维修技术绝不是一门容易学好、短时间内能够掌握的科学。由于这门科学所涉及的方方面面很多,各方面又相互 ...…

查看全部问答>

急求verilog lcd1602 驱动程序?

急求verilog lcd1602 驱动程序? 谢谢!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!…

查看全部问答>

求助:wince socket 蓝牙编程中怎样用OBEX协议封装数据

我是刚接触蓝牙开发的,最近要做一个蓝牙文件传输的应用,就是实现蓝牙设备之间的文件传输, MSDN上研究过SOCKET实现的API函数,但是不知道连接成功后发送的数据怎样才能让对方识别, 网上有人说\"这些数据必须是经过OBEX协议封装好的数据\" 请做过这 ...…

查看全部问答>

mount 不上

root@arago:/# mount /dev/mtdblock4 /mnt/aaa/ mtdblock_open ok mtdblock: read on \"filesystem\" at 0x0, size 0x200 end_request: I/O error, dev mtdblock4, sector 0 FAT: unable to read boot sector mtdblock_release nand_sync: c ...…

查看全部问答>

USART配置无论奇或偶校验只有奇校验能用有图。。。。

无校验时收发正常 看了下手册 添加校验的话 要M=1(9BIT ) PCE =  1 PS = 0/1  奇偶作为9bit里的MSB发送 都配置好了以后 用串口调试助手调试 无论怎么配置只有选odd 才能正常发送接收 囧。。。 odd.JPG (7 ...…

查看全部问答>

求助!

我安装了一个IAR Workbench for MSP430 V2.10A版本的软件,安装完成后,不能使用,问题是:当新建workspace时,就提示:xerces panic error,这个错误,确定后就退出了。 各位大侠,谁遇到过此问题,帮帮忙,解决一下,不胜感激!…

查看全部问答>

2.8寸液晶成功在MINISTM32板子上驱动成功!

先上图,看看效果   再来个液晶屏的背面图 [ 本帖最后由 jiaxinhui2011 于 2011-7-8 07:05 编辑 ]…

查看全部问答>

【视频分享】 TI OMAP 5处理器平台强大的图形能力演示

简介:通过业界知名的GL Benchmark 2.5的测试,TI OMAP 5处理器平台具有强大的图形能力,领先目前市场最领先的平板电脑12%左右,同时也领先与其它竞争对手的处理器平台。   $(\'swf_dN8\').innerHTML=AC_FL_RunContent(\'width\', \'550\', ...…

查看全部问答>

开关电源上各个电子元件的作用

你想了解开关电源上各个电子元件的作用?…

查看全部问答>