工作任务心得:软件模拟IIC从机模式只读状态,并用串口将数据分段打印

       在首次工作经历中,接到了用软件模拟IIC从机的任务。使单片机起到一个监视器的作用,将主机与从机间的通信数据读取出来,并用串口分段打印出来的任务。

与普通的软件模拟IIC主机模式相比,软件模拟IIC从机模式,要处理的问题会更加复杂。但本实验只涉及到了实现从机模式的只读状态,故难度也会有所降低。

一开始时,我先是想用IO口直接读取SDA和SCL线上的数据信号,但查阅了其他文章后发现。如果直接用IO口去接收高低电平信号存在的弊端过多,具体如下。

  1. 实时性要求高:I2C 通信依赖于精确的时序。使用 I/O 口读取数据要求软件能够以足够高的速率轮询 I/O 状态,以确保不会错过任何通信事件。

  2. 软件复杂性:需要编写复杂的软件逻辑来处理 I2C 协议的时序,包括起始条件、数据位、应答位、停止条件等。

  3. 中断处理困难:在 I2C 通信过程中,通常需要使用中断来处理数据的接收和发送。使用 I/O 口直接读取数据信号将难以实现高效的中断驱动通信。

  4. 硬件支持限制:许多微控制器提供硬件 I2C 支持,包括自动处理 ACK 位、发送和接收数据等。不使用这些硬件功能将浪费硬件资源。

  5. 功耗问题:轮询 I/O 口的状态会增加 CPU 的工作量,从而增加功耗。硬件支持的 I2C 通常具有更优的功耗管理。

  6. 速度限制:软件模拟 I2C 通信很难达到微控制器硬件 I2C 支持的速度。

  7. 错误处理:硬件 I2C 通常具有错误检测机制,如超时检测、仲裁丢失等。软件模拟需要实现这些错误检测机制,增加了开发难度。

  8. 可移植性差:直接使用 I/O 口实现 I2C 通信的代码依赖于特定的硬件特性,这降低了代码的可移植性。

  9. 电磁兼容性问题:硬件 I2C 接口通常设计有特定的电磁兼容性(EMC)特性,而软件模拟可能没有这些特性,导致通信不稳定。

  10. 维护和调试难度:软件模拟 I2C 通信的代码通常难以维护和调试,特别是在通信速率较高或系统复杂度较大的情况下。

故在借阅其他文章后选择使用中断的方式对IIC数据进行解析,并用串口对其进行打印。具体思路如下。

使用中断来解析IIC时序:

1.中断计数器:用来记录SCL触发上升沿中断的次数,如果计够8次,则一字节数据接收完成。计到第九位时,计数器清零,开始接收下一字节数据。

2.起始信号:当SDA触发下降沿中断时,且此时SCL处于高电平时则为起始信号,并在检测到起始信号时将计数器清空。

3.接收数据:当检测到起始信号后,计数器开始对SCL上升沿中断继续计数,当计够八次时,完成一字节数据的接收,将一字节数据存入一个数组内。

4.停止信号:当SCL为高电平时,SDA触发上升沿中断时,则说明产生停止信号,并在检测到停止信号后向数组内插入标志信号。

5.串口分段打印:整IIC通讯结束后,用串口将数据分段发送到电脑。在检测到停止位信号后,向数组内出入一段标志信号(该标志不能与SDA数据冲突)。当检测到标志信号后,用串口向电脑发送换行符。在上位机中选用文本接收模式。并用snprintf函数将文本模式下接收到的SDA数据改为HEX数据。实现串口分段打印。

例程代码如下:

#include "stm32f10x.h"                  // Device header
#include "MYI2C/myi2c.h"
#include "SERIAL/serial.h"
#include "Delay.h"

// 用于存储接收到的数据的数组
uint16_t i2c_rx_buffer_index = 0;                                       //数组索引值
uint8_t i2c_rx_buffer[256]={0};                                         //存放数据数组
uint8_t detected = 0;                                                   //信号标志位
uint8_t scl_interrupt_count = 0;                                                  //SCL计数器

//使用GPIOB的PB6作为SDA,PB7作为SCL
#define SDA_GPIO_PORT       GPIOB
#define SDA_GPIO_PIN        GPIO_Pin_0
#define SCL_GPIO_PORT       GPIOB
#define SCL_GPIO_PIN        GPIO_Pin_1

void MI2C_SLAVE_init(void)                                                        //初始化函数
{
	NVIC_InitTypeDef NVIC_InitStructure;                                          //NVIC句柄
	EXTI_InitTypeDef EXTI_InitStructure;                                          //EXTI句柄
	GPIO_InitTypeDef GPIO_InitStructure;                                          //GPIO句柄
    
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);                          //使能GPIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);                          //打开复用时钟
    
    
    
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_0;                            //初始化GPIO
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;                           //改成浮空输入模式//
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);

	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);                    //初始化EXTI0
	EXTI_InitStructure.EXTI_Line = EXTI_Line0;                                    //选择通道0接收SDA跳变
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;                           //选用中断触发模式
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;                //选择双边沿触发
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;                                     //使能EXTI
    EXTI_Init(&EXTI_InitStructure);
	
    // SCL为上升沿中断
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);                     //初始化EXTI1
	EXTI_InitStructure.EXTI_Line = EXTI_Line1;                                     //选择通道1接收SCL跳变
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;                            //选用中断触发模式
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;                         //选择上升沿跳变
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;                                      //使能EXTI
    EXTI_Init(&EXTI_InitStructure);
    
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				                   //配置NVIC为分组2
    //SDA NVIC
    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;                               //初始化NVIC
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    //SCL NVIC
    NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);    
}

void EXTI0_IRQHandler(void)                                                      //SDA中断处理函数
{
    if (EXTI_GetITStatus(EXTI_Line0) == SET)                                     //判断有无发生中断
    {    
        if (GPIO_ReadInputDataBit(SCL_GPIO_PORT, SCL_GPIO_PIN) == Bit_SET)       //判断当发生中断时,SCL是否为高电平
        {
            if (GPIO_ReadInputDataBit(SDA_GPIO_PORT, SDA_GPIO_PIN) == Bit_RESET) //如果此时SDA线为低电平,则说明检测到起始信号
            { 
                detected = 1;                                                    //将detected置1,说明此时检测到起始信号
                scl_interrupt_count = 0;                                         //清空SCL计数器
            }
            else                                                                 //否则,说明检测到停止信号
            {   
                detected = 2;                                                    //将detected置2,说明此时检测到停止信号
                i2c_rx_buffer[i2c_rx_buffer_index++] = 0xBF;                     //当检测到停止信号时,向数组内插入0xBF作为标志位,用于判断是否发生换行符
            }
        }
        EXTI_ClearITPendingBit(EXTI_Line0);                                      //清除中断标志位
    }
}


void EXTI1_IRQHandler(void)                                                      //SCL上升沿中断处理函数
{
    if (EXTI_GetITStatus(EXTI_Line1) == SET)                                     //判断有无发生中断
    {
    if (detected == 1)                                                           //如果触发SCL上升沿中断,且此时检测到起始信号,此时开始接收数据
        {
        if (scl_interrupt_count < 8)                                             //判断SCL计数值是否小于八  
         {            
           uint8_t bit = GPIO_ReadInputDataBit(SDA_GPIO_PORT, SDA_GPIO_PIN) == Bit_SET ? 1 : 0;    //接收数据
           i2c_rx_buffer[i2c_rx_buffer_index] |= (bit << (7 - scl_interrupt_count));               //将数据存入数组
           scl_interrupt_count++;                                                                  //SCL计数器加1
             
          }             
            else                                                                      //当SCL计数器值大于8时,说明一个字节数据接收完成
             {      
                i2c_rx_buffer_index++;                                                //将数组索引值加1,准备接收下一字节数据 
                scl_interrupt_count = 0;                                              //重置计数器,准备接收下一字节

             } 
         }
           EXTI_ClearITPendingBit(EXTI_Line1);                                    //清除SCL中断标志
     }
}

void serial_init(void)                                                             //usart初始化函数                
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);		
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);	
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);	
    
    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate=9600;
    USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode=USART_Mode_Tx;
    USART_InitStructure.USART_Parity=USART_Parity_No;
    USART_InitStructure.USART_StopBits=USART_StopBits_1;
    USART_InitStructure.USART_WordLength=USART_WordLength_8b;
    USART_Init(USART1,&USART_InitStructure);
    
    USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
    USART_Cmd(USART1,ENABLE);
}

void USART_SendByte(USART_TypeDef *USARTx,uint8_t byte)                          // USART发送一个字节的函数
{   
    while (!(USARTx->SR & USART_SR_TXE));
    USARTx->DR = byte;
    while (!(USARTx->SR & USART_SR_TC));
}


void USART_SendBuffer(USART_TypeDef *USARTx,uint8_t *buffer, uint16_t length)     // USART发送缓冲区数据的函数
 {
    for (uint16_t i = 0; i < length; i++) 
     {
                                                                 
        if (buffer[i] == 0xBF)                                    // 检查是否为0xBF,若是,则发送换行符
        {
            USART_SendByte(USARTx, '\r');                         // 发送回车符
            USART_SendByte(USARTx, '\n');                         // 发送换行符
        } 
        else                                                      //将文本模式下的数据转换成HEX数据
        {                                                   
            char hexStr[4];
            snprintf(hexStr, sizeof(hexStr), "%02X", buffer[i]);  // 将字节转换为HEX字符串
            USART_SendByte(USARTx, hexStr[0]);                    // 发送HEX字符串的高位字符
            USART_SendByte(USARTx, hexStr[1]);                    // 发送HEX字符串的低位字符
            USART_SendByte(USARTx, ' ');                          // 发送空格作为分隔符
        }
    }
}

int main(void)                                                          //主函数
{
    serial_init();                                                      //初始化USART
    MI2C_SLAVE_init();                                                  //初始化I2C
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);                //初始化GPIO_10
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;                     
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOB,&GPIO_InitStructure);
 
    while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == Bit_RESET);     //等待VCC上电
    Delay_ms(10);                                                       //延时10模式
    if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == Bit_SET)            //如果延时10ms后,VCC依旧属于高电平,则说明此时上电状态平稳。(主要用于处理上电后会出现杂波的问题)
    { 
        while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == Bit_SET);   //等待VCC掉电。当掉电结束后,说明I2C通信已经完成     
        USART_SendBuffer(USART1, i2c_rx_buffer,i2c_rx_buffer_index);    //当I2C通讯完成后,将数据用串口发送到电脑  
    }
   
}

以上便是第一次工作总结。这途中遇到了许许多多的问题。大部分都是因为基础知识不够扎实,需要不断学习提升。

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值