【程序】STM32F1单片机I2C中Packet Error Checking(PEC)校验功能和DMA的使用方法

在STM32F1系列的单片机中,当I2C_CR1_ENPEC=1时启用CRC自动校验功能。注意这是一个自动校验的功能。发送方和接收方可以不同时开启自动校验,但发送方必须要发送CRC校验码,接收方也必须接收CRC校验码。

如果经过硬件检验后,接收到的内容完全正确,则双方的I2C_SR2_PEC中的内容(也就是SR2寄存器第15~8位)都应该为,否则表明程序流程没有写对。

发送端将最后一字节数据送入DR后,等待TXE置1,然后将I2C_CR1_PEC置位,注意此时TXE位将不会被自动清除

接收端从DR取出最后一字节数据后,立即将PEC置1。同时,如果是主机端在接收的话,还必须关掉ACK并发送STOP请求。收到CRC校验码后RXNE会置位


请看下面的程序。

两片单片机都是用的I2C1。主机端使用的单片机是STM32F107VCT6,端口为PB8和PB9。从机端用的是STM32F103RET6,端口为PB6和PB7。注意必须要外接上拉电阻,否则会出现arbitration lost的错误!

主机端的串口波特率为115200。(36000000Hz / 115200 ≈ 312 = 0x138)

从机端的串口波特率也为115200。(72000000Hz / 115200 = 625 = 0x271)

【主机端程序(寄存器版)】

#include 
#include 

const uint8_t sendbuf[] = {0x40, 0x45, 0x52, 0x67};
uint8_t recvbuf[100];

void dump(const uint8_t *data, uint16_t size)
{
  while (size--)
    printf("%02x", *data++);
  printf("\n");
}

int fputc(int ch, FILE *fp)
{
  if (fp == stdout)
  {
    if (ch == '\n')
    {
      while ((USART3->SR & USART_SR_TXE) == 0);
      USART3->DR = '\r';
    }
    while ((USART3->SR & USART_SR_TXE) == 0);
    USART3->DR = ch;
  }
  return ch;
}

void test_write(void)
{
  uint8_t i;
  I2C1->CR1 |= I2C_CR1_START;
  while ((I2C1->SR1 & I2C_SR1_SB) == 0);
  I2C1->DR = 0x06;
  while ((I2C1->SR1 & (I2C_SR1_ADDR | I2C_SR1_AF)) == 0);
  if (I2C1->SR1 & I2C_SR1_AF) // 从机复位键按住不放, 可模拟没有应答的情况
  {
    I2C1->SR1 &= ~I2C_SR1_AF;
    I2C1->CR1 |= I2C_CR1_STOP; // 找不到从机时主机必须发出停止信号, 使BUSY清零, 释放总线
    while (I2C1->CR1 & I2C_CR1_STOP);
    printf("No ack!\n");
    return;
  }
  I2C1->SR2; // 清除ADDR
  
  for (i = 0; i < sizeof(sendbuf); i++)
  {
    I2C1->DR = sendbuf[i];
    while ((I2C1->SR1 & I2C_SR1_TXE) == 0);
  }
  I2C1->CR1 |= I2C_CR1_PEC; // TXE不能被PEC清零!
  while ((I2C1->SR1 & I2C_SR1_BTF) == 0);
  
  I2C1->CR1 |= I2C_CR1_STOP;
  while (I2C1->CR1 & I2C_CR1_STOP);
  printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x\n", I2C1->SR1, I2C1->SR2, I2C1->CR1);
}

void test_write_dma(void)
{
  DMA1_Channel6->CNDTR = sizeof(sendbuf); // 不包括PEC
  DMA1_Channel6->CCR |= DMA_CCR6_EN;
  
  I2C1->CR1 |= I2C_CR1_START;
  I2C1->CR2 |= I2C_CR2_DMAEN; // 这里无需置位LAST, 因为LAST仅用于接收模式
  while ((I2C1->SR1 & I2C_SR1_SB) == 0);
  I2C1->DR = 0x06; // 从机地址
  while ((I2C1->SR1 & (I2C_SR1_ADDR | I2C_SR1_AF)) == 0);
  if (I2C1->SR1 & I2C_SR1_AF)
  {
    I2C1->SR1 &= ~I2C_SR1_AF;
    I2C1->CR2 &= ~I2C_CR2_DMAEN;
    DMA1_Channel6->CCR &= ~DMA_CCR6_EN;
    I2C1->CR1 |= I2C_CR1_STOP;
    while (I2C1->CR1 & I2C_CR1_STOP);
    printf("No ack!\n");
    return;
  }
  I2C1->SR2; // 清除ADDR
  
  while ((DMA1->ISR & DMA_ISR_TCIF6) == 0); // 等待DMA送入数据结束
  DMA1->IFCR = DMA_IFCR_CTCIF6; // 清除标志位
  I2C1->CR2 &= ~I2C_CR2_DMAEN;
  DMA1_Channel6->CCR &= ~DMA_CCR6_EN; // I2C传输结束后应关闭DMA
  
  while ((I2C1->SR1 & I2C_SR1_BTF) == 0); // DMA向I2C送入数据结束并不代表传输已结束, 必须等待BTF置位
  // 注意: DMA_ISR_TCIF6比I2C_SR1_BTF先置位
  // 如果只等待TCIF6, 则最后I2C_SR2_PEC将无法清零! 且接收端将丢失最后一字节数据和PEC校验码
  
  I2C1->CR1 |= I2C_CR1_STOP;
  while (I2C1->CR1 & I2C_CR1_STOP); // 必须等待该位清零后才允许后续程序对CR1的写操作
  printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x, DMA1->ISR=0x%08x\n", I2C1->SR1, I2C1->SR2, I2C1->CR1, DMA1->ISR);
}

void test_read(void)
{
  uint8_t size;
  
  I2C1->CR1 |= I2C_CR1_ACK | I2C_CR1_START;
  while ((I2C1->SR1 & I2C_SR1_SB) == 0);
  I2C1->DR = 0x07;
  while ((I2C1->SR1 & (I2C_SR1_ADDR | I2C_SR1_AF)) == 0);
  if (I2C1->SR1 & I2C_SR1_AF)
  {
    I2C1->SR1 &= ~I2C_SR1_AF;
    I2C1->CR1 |= I2C_CR1_STOP;
    while (I2C1->CR1 & I2C_CR1_STOP);
    printf("No ack!\n");
    return;
  }
  I2C1->SR2;
  
  for (size = 0; size < 7; size++) // 范围为0~6, 其中0~5为数据, 6为PEC校验码
  {
    while ((I2C1->SR1 & I2C_SR1_RXNE) == 0);
    recvbuf[size] = I2C1->DR;
    
    if (size == 5)
    {
      I2C1->CR1 &= ~I2C_CR1_ACK;
      I2C1->CR1 |= I2C_CR1_PEC | I2C_CR1_STOP; // 接收完最后一个字节后, 将PEC置位, 准备接收校验码 (此时已经正在接收PEC了)
    }
  }
  while (I2C1->CR1 & I2C_CR1_STOP);
  
  printf("Slave reception terminated! Data: ");
  dump(recvbuf, 7);
  if (I2C1->SR1 & I2C_SR1_PECERR)
  {
    I2C1->SR1 &= ~I2C_SR1_PECERR;
    printf("Reception PEC Error! ");
  }
  printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x\n", I2C1->SR1, I2C1->SR2, I2C1->CR1);
}

void test_read_dma(void)
{
  DMA1_Channel7->CNDTR = 7; // 包括PEC
  DMA1_Channel7->CCR |= DMA_CCR7_EN;
  
  I2C1->CR1 |= I2C_CR1_ACK | I2C_CR1_START;
  I2C1->CR2 |= I2C_CR2_LAST | I2C_CR2_DMAEN;
  while ((I2C1->SR1 & I2C_SR1_SB) == 0);
  I2C1->DR = 0x07; // 从机地址
  while ((I2C1->SR1 & (I2C_SR1_ADDR | I2C_SR1_AF)) == 0);
  if (I2C1->SR1 & I2C_SR1_AF)
  {
    I2C1->SR1 &= ~I2C_SR1_AF;
    I2C1->CR2 &= ~(I2C_CR2_LAST | I2C_CR2_DMAEN);
    DMA1_Channel7->CCR &= ~DMA_CCR7_EN;
    I2C1->CR1 |= I2C_CR1_STOP;
    while (I2C1->CR1 & I2C_CR1_STOP);
    printf("No ack!\n");
    return;
  }
  I2C1->SR2;
  
  while ((DMA1->ISR & DMA_ISR_TCIF7) == 0); // 等待DMA读取数据结束, 读取结束并不意味着数据传输结束
  DMA1->IFCR = DMA_IFCR_CTCIF7;
  I2C1->CR1 &= ~I2C_CR1_ACK; // 必须在接收PEC时关闭ACK!
  I2C1->CR2 &= ~(I2C_CR2_LAST | I2C_CR2_DMAEN);
  DMA1_Channel7->CCR &= ~DMA_CCR7_EN;
  
  I2C1->CR1 |= I2C_CR1_STOP;
  while (I2C1->CR1 & I2C_CR1_STOP); // 等待数据传输真正结束
  
  printf("Slave reception terminated! Data: ");
  dump(recvbuf, 7);
  if (I2C1->SR1 & I2C_SR1_PECERR)
  {
    I2C1->SR1 &= ~I2C_SR1_PECERR;
    printf("Reception PEC Error! ");
  }
  printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x, DMA1->ISR=0x%08x\n", I2C1->SR1, I2C1->SR2, I2C1->CR1, DMA1->ISR);
}

void test_write_wrongcrc(void)
{
  uint8_t i;
  I2C1->CR1 |= I2C_CR1_START;
  while ((I2C1->SR1 & I2C_SR1_SB) == 0);
  I2C1->DR = 0x06;
  while ((I2C1->SR1 & (I2C_SR1_ADDR | I2C_SR1_AF)) == 0);
  if (I2C1->SR1 & I2C_SR1_AF)
  {
    I2C1->SR1 &= ~I2C_SR1_AF;
    I2C1->CR1 |= I2C_CR1_STOP;
    while (I2C1->CR1 & I2C_CR1_STOP);
    printf("No ack!\n");
    return;
  }
  I2C1->SR2;
  
  for (i = 0; i < sizeof(sendbuf); i++)
  {
    I2C1->DR = sendbuf[i];
    while ((I2C1->SR1 & I2C_SR1_TXE) == 0);
  }
  
  I2C1->DR = 0x00; // 发送一个错误的CRC校验码
  while ((I2C1->SR1 & (I2C_SR1_AF | I2C_SR1_BTF)) == 0); // 等待AF或BTF置1
  I2C1->CR1 |= I2C_CR1_STOP;
  while (I2C1->CR1 & I2C_CR1_STOP);
  
  // 接收端发送非应答, 发送端知道接收端没有收到正确的数据
  if (I2C1->SR1 & I2C_SR1_AF)
  {
    I2C1->SR1 &= ~I2C_SR1_AF;
    printf("Not correctly received!\n");
  }
  printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x, DMA1->ISR=0x%08x\n", I2C1->SR1, I2C1->SR2, I2C1->CR1, DMA1->ISR);
  // 发送端和接收端算出的I2C_SR2_PEC都是0x87(但发送端实际发送的校验码是0x00), 即数据和地址码的CRC校验码, PEC不为0表示出了错误
}

int main(void)
{
  RCC->AHBENR |= RCC_AHBENR_DMA1EN; // AHB寄存器有初值!
  RCC->APB1ENR = RCC_APB1ENR_I2C1EN | RCC_APB1ENR_USART3EN | RCC_APB1ENR_UART5EN;
  RCC->APB2ENR = RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPDEN | RCC_APB2ENR_IOPEEN;
  
  AFIO->MAPR = AFIO_MAPR_I2C1_REMAP;
  GPIOB->CRH = 0x44444bff;
  GPIOE->BSRR = GPIO_BSRR_BS15;
  GPIOE->CRH = 0x34444444;
  
  USART3->BRR = 312; // 波特率: 115200
  USART3->CR1 = USART_CR1_UE | USART_CR1_TE;
  
  UART5->BRR = 312; // 波特率: 115200
  UART5->CR1 = USART_CR1_UE | USART_CR1_RE | USART_CR1_RXNEIE;
  NVIC_EnableIRQ(UART5_IRQn);
  
  I2C1->CR2 = 36;
  I2C1->CCR = I2C_CCR_FS | 30;
  I2C1->TRISE = 11;
  I2C1->CR1 = I2C_CR1_ENPEC | I2C_CR1_PE;
  
  DMA1_Channel6->CCR = DMA_CCR6_DIR | DMA_CCR6_MINC;
  DMA1_Channel6->CMAR = (uint32_t)sendbuf;
  DMA1_Channel6->CPAR = (uint32_t)&I2C1->DR;
  
  DMA1_Channel7->CCR = DMA_CCR7_MINC;
  DMA1_Channel7->CMAR = (uint32_t)recvbuf;
  DMA1_Channel7->CPAR = (uint32_t)&I2C1->DR;
  
  printf("I2C Master!\n");
  while (1)
    __WFI();
}

void UART5_IRQHandler(void)
{
  uint8_t data = UART5->DR;
  GPIOE->BRR = GPIO_BRR_BR15; // 灯亮
  if (data == 'a')
    test_write();
  else if (data == 'b')
    test_write_dma();
  else if (data == 'c')
    test_read();
  else if (data == 'd')
    test_read_dma();
  else if (data == 'e')
    test_write_wrongcrc();
  GPIOE->BSRR = GPIO_BSRR_BS15; // 灯灭
}
【从机端程序:普通方式(寄存器版)】

#include 
#include 

// 调试程序时, 先不使用PEC位, 检查程序是否能正常收到所有数据和PEC校验码
// 然后再开PEC, 若I2C总线释放后, I2C_SR2_PEC为0, 则表明程序流程正确

//#define TEST_CRCWRONG

const uint8_t sendbuf[] = {0xb1, 0x45, 0x98, 0xa3, 0x26, 0x77};
uint8_t recvbuf[100];
uint8_t size;

void dump(const uint8_t *data, uint16_t size)
{
  while (size--)
    printf("%02x", *data++);
  printf("\n");
}

int fputc(int ch, FILE *fp)
{
  if (fp == stdout)
  {
    if (ch == '\n')
    {
      while ((USART1->SR & USART_SR_TXE) == 0);
      USART1->DR = '\r';
    }
    while ((USART1->SR & USART_SR_TXE) == 0);
    USART1->DR = ch;
  }
  return ch;
}

int main(void)
{
  RCC->APB1ENR = RCC_APB1ENR_I2C1EN;
  RCC->APB2ENR = RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_USART1EN;
  
  GPIOA->CRH = 0x888444b4;
  GPIOB->CRL = 0xff484444;
  
  USART1->BRR = 625; // 波特率: 115200
  USART1->CR1 = USART_CR1_UE | USART_CR1_TE;
  printf("I2C Slave!\n");
  
  I2C1->OAR1 = 0x06;
  I2C1->CR1 = I2C_CR1_ENPEC | I2C_CR1_ACK | I2C_CR1_PE;
  I2C1->CR2 = I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN | I2C_CR2_ITERREN;
  NVIC_EnableIRQ(I2C1_EV_IRQn);
  NVIC_EnableIRQ(I2C1_ER_IRQn);
  
  while (1)
    __WFI();
}

void I2C1_EV_IRQHandler(void)
{
  if (I2C1->SR1 & I2C_SR1_ADDR)
  {
    I2C1->SR2;  // 读取SR2的目的是清除ADDR位
    size = 0;
  }
  
  if (I2C1->SR2 & I2C_SR2_TRA)
  {
    // 发送
    if (I2C1->SR1 & I2C_SR1_TXE)
    {
      if (size < sizeof(sendbuf))
      {
        I2C1->DR = sendbuf[size];
        size++;
      }
      else if (size == sizeof(sendbuf))
      {
#ifdef TEST_CRCWRONG
        I2C1->DR = 0x00; // 发送一个错误的CRC码, 看接收端能不能检查到
        // 注意: 由于Master-Receiver端必须在PEC校验码后返回一个NACK, 所以从机发送端无法知道主机端到底有没有正确收到数据
#else
        I2C1->CR1 |= I2C_CR1_PEC; // 发送正确的CRC码 (TXE将不会清零)
#endif
        size++;
        
        // 开始发送倒数第二个字节时, TXE置位, DR送入最后一个字节, TXE被清零
        // 开始发送最后一个字节时, TXE置位, PEC置1后TXE仍为1
        // PEC发送完后, 收到NACK请求停止发送数据, AF将置位
        I2C1->CR2 &= ~I2C_CR2_ITBUFEN; // 关闭TXE和RXNE中断, 防止卡死 (BTF中断不会被关闭)
      }
      else
      {
        // 正常情况下不应该进入此分支
        if (I2C1->SR1 & I2C_SR1_BTF)
          printf("error! BTF!\n");
        else
          printf("error 1!\n");
      }
    }
    else
      printf("error 2!\n");
  }
  else
  {
    // 接收
    if ((I2C1->SR1 & (I2C_SR1_RXNE | I2C_SR1_STOPF)) == 0 && size != 0) // size=0时表示处理的是ADDR位
      printf("error 3! I2C1->SR1=0x%04x\n", I2C1->SR1);
    
    if (I2C1->SR1 & I2C_SR1_RXNE) // 注意: 接收PEC时, RXNE仍要置位!
    {
      recvbuf[size] = I2C1->DR;
      size++;
      
      if (size == 4)
        I2C1->CR1 |= I2C_CR1_PEC; // 最后一个字节收到后, 下一个字节应为PEC (此时已经正在接收PEC了)
    }
    
    if (I2C1->SR1 & I2C_SR1_STOPF)
    {
      I2C1->CR1 = I2C1->CR1; // 清除STOPF
      printf("Slave reception terminated! Data: ");
      dump(recvbuf, size);
      printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x\n", I2C1->SR1, I2C1->SR2, I2C1->CR1);
    }
  }
}

void I2C1_ER_IRQHandler(void)
{
  if ((I2C1->SR1 & (I2C_SR1_AF | I2C_SR1_PECERR)) == 0)
    printf("error 4!\n");
  
  if (I2C1->SR1 & I2C_SR1_AF)
  {
    I2C1->SR1 &= ~I2C_SR1_AF;
    while (I2C1->SR1 & I2C_SR1_TXE); // 等待TXE回到0, 防止进入错误的error3分支
    I2C1->CR2 |= I2C_CR2_ITBUFEN; // 重开TXE和RXNE中断
    printf("Slave transmission terminated!\n");
    printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x\n", I2C1->SR1, I2C1->SR2, I2C1->CR1);
  }
  
  if (I2C1->SR1 & I2C_SR1_PECERR)
  {
    I2C1->SR1 &= ~I2C_SR1_PECERR;
    printf("Reception PEC error!\n");
  }
}
【从机端程序:DMA方式(寄存器版)】

#include 
#include 

// 调试程序时, 先不使用PEC位, 检查程序是否能正常收到所有数据和PEC校验码
// 然后再开PEC, 若I2C总线释放后, I2C_SR2_PEC为0, 则表明程序流程正确

const uint8_t sendbuf[] = {0xb1, 0x45, 0x98, 0xa3, 0x26, 0x77};
uint8_t recvbuf[100];

void dump(const uint8_t *data, uint16_t size)
{
  while (size--)
    printf("%02x", *data++);
  printf("\n");
}

int fputc(int ch, FILE *fp)
{
  if (fp == stdout)
  {
    if (ch == '\n')
    {
      while ((USART1->SR & USART_SR_TXE) == 0);
      USART1->DR = '\r';
    }
    while ((USART1->SR & USART_SR_TXE) == 0);
    USART1->DR = ch;
  }
  return ch;
}

int main(void)
{
  RCC->AHBENR |= RCC_AHBENR_DMA1EN;
  RCC->APB1ENR = RCC_APB1ENR_I2C1EN;
  RCC->APB2ENR = RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_USART1EN;
  
  GPIOA->CRH = 0x888444b4;
  GPIOB->CRL = 0xff484444;
  
  USART1->BRR = 625; // 波特率: 115200
  USART1->CR1 = USART_CR1_UE | USART_CR1_TE;
  printf("I2C Slave!\n");
  
  I2C1->OAR1 = 0x06;
  I2C1->CR1 = I2C_CR1_ENPEC | I2C_CR1_ACK | I2C_CR1_PE;
  I2C1->CR2 = I2C_CR2_ITEVTEN | I2C_CR2_ITERREN; // 使用DMA时不可开ITBUFEN中断
  NVIC_EnableIRQ(I2C1_EV_IRQn);
  NVIC_EnableIRQ(I2C1_ER_IRQn);
  
  DMA1_Channel6->CCR = DMA_CCR6_DIR | DMA_CCR6_MINC | DMA_CCR6_TCIE; // 要打开传输完成中断
  DMA1_Channel6->CMAR = (uint32_t)sendbuf;
  DMA1_Channel6->CPAR = (uint32_t)&I2C1->DR;
  NVIC_EnableIRQ(DMA1_Channel6_IRQn);
  
  DMA1_Channel7->CCR = DMA_CCR7_MINC | DMA_CCR7_TCIE; // 要打开传输完成中断
  DMA1_Channel7->CMAR = (uint32_t)recvbuf;
  DMA1_Channel7->CPAR = (uint32_t)&I2C1->DR;
  NVIC_EnableIRQ(DMA1_Channel7_IRQn);
  
  while (1)
    __WFI();
}

void DMA1_Channel6_IRQHandler(void)
{
  DMA1->IFCR = DMA_IFCR_CTCIF6; // 清除标志位
  I2C1->CR2 &= ~I2C_CR2_DMAEN;
  DMA1_Channel6->CCR &= ~DMA_CCR6_EN; // I2C传输结束后应关闭DMA
}

void DMA1_Channel7_IRQHandler(void)
{
  // 这里不需要关ACK, 校验失败时自动发NACK
  DMA1->IFCR = DMA_IFCR_CTCIF7;
  I2C1->CR2 &= ~(I2C_CR2_LAST | I2C_CR2_DMAEN);
  DMA1_Channel7->CCR &= ~DMA_CCR7_EN;
}

void I2C1_EV_IRQHandler(void)
{
  if ((I2C1->SR1 & (I2C_SR1_ADDR | I2C_SR1_STOPF)) == 0)
    printf("error 5!\n");
  
  if (I2C1->SR1 & I2C_SR1_ADDR)
  {
    // 开DMA
    if (I2C1->SR2 & I2C_SR2_TRA) // 读取SR2可清除ADDR位
    {
      DMA1_Channel6->CNDTR = sizeof(sendbuf); // 不包括PEC
      DMA1_Channel6->CCR |= DMA_CCR6_EN;
    }
    else
    {
      DMA1_Channel7->CNDTR = 5; // 包括PEC
      DMA1_Channel7->CCR |= DMA_CCR7_EN;
      I2C1->CR2 |= I2C_CR2_LAST; // 接收完最后一个数据不应答
    }
    I2C1->CR2 |= I2C_CR2_DMAEN;
  }
  
  if (I2C1->SR1 & I2C_SR1_STOPF)
  {
    I2C1->CR1 = I2C1->CR1; // 清除STOPF
    printf("Slave reception terminated! Data: ");
    dump(recvbuf, 5);
    printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x, DMA1->ISR=0x%08x\n", I2C1->SR1, I2C1->SR2, I2C1->CR1, DMA1->ISR);
  }
}

void I2C1_ER_IRQHandler(void)
{
  if ((I2C1->SR1 & (I2C_SR1_AF | I2C_SR1_PECERR)) == 0)
    printf("error 6!\n");
  
  if (I2C1->SR1 & I2C_SR1_AF)
  {
    I2C1->SR1 &= ~I2C_SR1_AF;
    printf("Slave transmission terminated!\n");
    printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x, DMA1->ISR=0x%08x\n", I2C1->SR1, I2C1->SR2, I2C1->CR1, DMA1->ISR);
  }
  
  if (I2C1->SR1 & I2C_SR1_PECERR)
  {
    I2C1->SR1 &= ~I2C_SR1_PECERR;
    printf("Reception PEC error!\n");
  }
}


下载程序后,在电脑上把两个单片机的串口输出都打开。在主机端的串口上发送以下字母可启动通信:

a:主机端普通方式,主发从收

b:主机端DMA方式,主发从收

c:主机端普通方式,主收从发

d:主机端DMA方式,主收从发

e:主机端普通方式,主发从收,但故意发送一个错误的CRC校验码

若在从机端程序中打开TEST_CRCWRONG的定义,则可在c的基础上使用错误的CRC校验码


以下为程序的运行结果,每幅图从a~e依次发送命令。

【从机普通方式】

【程序】STM32F1单片机I2C中Packet Error Checking(PEC)校验功能和DMA的使用方法_第1张图片

【从机普通方式且定义TEST_CRCWRONG】

【程序】STM32F1单片机I2C中Packet Error Checking(PEC)校验功能和DMA的使用方法_第2张图片

【从机DMA方式】

【程序】STM32F1单片机I2C中Packet Error Checking(PEC)校验功能和DMA的使用方法_第3张图片


STM32并不是我们想象中的那样只计算数据部分的CRC校验码,然后与收到的校验码比较,判断是否相等,而是把收到的CRC校验码和数据一起计算,判断最终算出来的值(也就是余数)是不是等于0,如果等于0就表明传输正确。这样的话硬件实现起来要简单一些。发送和接收都是如此,正常情况下传输完毕后SR2的高8位一定为0。


【主机端程序(库函数版)】

#include 
#include 

const uint8_t sendbuf[] = {0x40, 0x45, 0x52, 0x67};
uint8_t recvbuf[100];

void dump(const uint8_t *data, uint16_t size)
{
  while (size--)
    printf("%02x", *data++);
  printf("\n");
}

int fputc(int ch, FILE *fp)
{
  if (fp == stdout)
  {
    if (ch == '\n')
    {
      while (USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET);
      USART_SendData(USART3, '\r');
    }
    while (USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET);
    USART_SendData(USART3, ch);
  }
  return ch;
}

void test_write(void)
{
  uint8_t i;
  I2C_GenerateSTART(I2C1, ENABLE);
  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) == ERROR);
  I2C_Send7bitAddress(I2C1, 0x06, I2C_Direction_Transmitter);
  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR)
  {
    if (I2C_GetFlagStatus(I2C1, I2C_FLAG_AF) == SET) // 从机复位键按住不放, 可模拟没有应答的情况
    {
      I2C_ClearFlag(I2C1, I2C_FLAG_AF);
      I2C_GenerateSTOP(I2C1, ENABLE);
      while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) == SET); // 找不到从机时主机必须发出停止信号, 使BUSY清零, 释放总线
      printf("No ack!\n");
      return;
    }
  }
  
  for (i = 0; i < sizeof(sendbuf); i++)
  {
    I2C_SendData(I2C1, sendbuf[i]);
    while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING) == ERROR);
  }
  I2C_TransmitPEC(I2C1, ENABLE);
  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == ERROR);
  
  I2C_GenerateSTOP(I2C1, ENABLE);
  while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) == SET);
  printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x\n", I2C_ReadRegister(I2C1, I2C_Register_SR1), I2C_ReadRegister(I2C1, I2C_Register_SR2), I2C_ReadRegister(I2C1, I2C_Register_CR1));
}

void test_write_dma(void)
{
  DMA_SetCurrDataCounter(DMA1_Channel6, sizeof(sendbuf)); // 不包括PEC
  DMA_Cmd(DMA1_Channel6, ENABLE);
  I2C_DMACmd(I2C1, ENABLE);
  
  I2C_GenerateSTART(I2C1, ENABLE);
  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) == ERROR);
  I2C_Send7bitAddress(I2C1, 0x06, I2C_Direction_Transmitter);
  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR)
  {
    if (I2C_GetFlagStatus(I2C1, I2C_FLAG_AF) == SET)
    {
      I2C_ClearFlag(I2C1, I2C_FLAG_AF);
      I2C_DMACmd(I2C1, DISABLE);
      DMA_Cmd(DMA1_Channel6, DISABLE);
      I2C_GenerateSTOP(I2C1, ENABLE);
      while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) == SET);
      printf("No ack!\n");
      return;
    }
  }
  
  while (DMA_GetFlagStatus(DMA1_FLAG_TC6) == RESET); // 等待DMA送入数据结束
  DMA_ClearFlag(DMA1_FLAG_TC6); // 清除标志位
  I2C_DMACmd(I2C1, DISABLE);
  DMA_Cmd(DMA1_Channel6, DISABLE); // I2C传输结束后应关闭DMA
  
  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == ERROR); // 等待传输真正结束
  I2C_GenerateSTOP(I2C1, ENABLE);
  while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) == SET);
  printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x, DMA1->ISR=0x%08x\n", I2C_ReadRegister(I2C1, I2C_Register_SR1), I2C_ReadRegister(I2C1, I2C_Register_SR2), I2C_ReadRegister(I2C1, I2C_Register_CR1), DMA1->ISR);
}

void test_read(void)
{
  uint8_t size;
  I2C_AcknowledgeConfig(I2C1, ENABLE);
  I2C_GenerateSTART(I2C1, ENABLE);
  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) == ERROR);
  I2C_Send7bitAddress(I2C1, 0x06, I2C_Direction_Receiver);
  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) == ERROR)
  {
    if (I2C_GetFlagStatus(I2C1, I2C_FLAG_AF) == SET)
    {
      I2C_ClearFlag(I2C1, I2C_FLAG_AF);
      I2C_GenerateSTOP(I2C1, ENABLE);
      while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) == SET);
      printf("No ack!\n");
      return;
    }
  }
  
  for (size = 0; size < 7; size++) // 范围为0~6, 其中0~5为数据, 6为PEC校验码
  {
    while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED) == ERROR);
    recvbuf[size] = I2C_ReceiveData(I2C1);
    
    if (size == 5)
    {
      I2C_AcknowledgeConfig(I2C1, DISABLE);
      I2C_TransmitPEC(I2C1, ENABLE); // 接收完最后一个字节后, 将PEC置位, 准备接收校验码 (此时已经正在接收PEC了)
      I2C_GenerateSTOP(I2C1, ENABLE);
    }
  }
  while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) == SET);
  
  printf("Slave reception terminated! Data: ");
  dump(recvbuf, 7);
  if (I2C_GetFlagStatus(I2C1, I2C_FLAG_PECERR) == SET)
  {
    I2C_ClearFlag(I2C1, I2C_FLAG_PECERR);
    printf("Reception PEC Error! ");
  }
  printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x\n", I2C_ReadRegister(I2C1, I2C_Register_SR1), I2C_ReadRegister(I2C1, I2C_Register_SR2), I2C_ReadRegister(I2C1, I2C_Register_CR1));
}

void test_read_dma(void)
{
  DMA_SetCurrDataCounter(DMA1_Channel7, 7); // 包括PEC
  DMA_Cmd(DMA1_Channel7, ENABLE);
  I2C_DMALastTransferCmd(I2C1, ENABLE);
  I2C_DMACmd(I2C1, ENABLE);
  
  I2C_AcknowledgeConfig(I2C1, ENABLE);
  I2C_GenerateSTART(I2C1, ENABLE);
  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) == ERROR);
  I2C_Send7bitAddress(I2C1, 0x06, I2C_Direction_Receiver);
  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) == ERROR)
  {
    if (I2C_GetFlagStatus(I2C1, I2C_FLAG_AF) == SET)
    {
      I2C_ClearFlag(I2C1, I2C_FLAG_AF);
      I2C_DMALastTransferCmd(I2C1, DISABLE);
      I2C_DMACmd(I2C1, DISABLE);
      DMA_Cmd(DMA1_Channel7, DISABLE);
      I2C_GenerateSTOP(I2C1, ENABLE);
      while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) == SET);
      printf("No ack!\n");
      return;
    }
  }
  
  while (DMA_GetFlagStatus(DMA1_FLAG_TC7) == RESET); // 等待DMA读取数据结束, 读取结束并不意味着数据传输结束
  DMA_ClearFlag(DMA1_FLAG_TC7);
  I2C_AcknowledgeConfig(I2C1, DISABLE); // 必须在接收PEC时关闭ACK!
  I2C_DMALastTransferCmd(I2C1, DISABLE);
  I2C_DMACmd(I2C1, DISABLE);
  DMA_Cmd(DMA1_Channel7, DISABLE);
  
  I2C_GenerateSTOP(I2C1, ENABLE);
  while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) == SET); // 等待数据传输真正结束
  
  printf("Slave reception terminated! Data: ");
  dump(recvbuf, 7);
  if (I2C_GetFlagStatus(I2C1, I2C_FLAG_PECERR) == SET)
  {
    I2C_ClearFlag(I2C1, I2C_FLAG_PECERR);
    printf("Reception PEC Error! ");
  }
  printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x, DMA1->ISR=0x%08x\n", I2C_ReadRegister(I2C1, I2C_Register_SR1), I2C_ReadRegister(I2C1, I2C_Register_SR2), I2C_ReadRegister(I2C1, I2C_Register_CR1), DMA1->ISR);
}

void test_write_wrongcrc(void)
{
  uint8_t i;
  I2C_GenerateSTART(I2C1, ENABLE);
  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) == ERROR);
  I2C_Send7bitAddress(I2C1, 0x06, I2C_Direction_Transmitter);
  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR)
  {
    if (I2C_GetFlagStatus(I2C1, I2C_FLAG_AF) == SET) // 从机复位键按住不放, 可模拟没有应答的情况
    {
      I2C_ClearFlag(I2C1, I2C_FLAG_AF);
      I2C_GenerateSTOP(I2C1, ENABLE);
      while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) == SET);
      printf("No ack!\n");
      return;
    }
  }
  
  for (i = 0; i < sizeof(sendbuf); i++)
  {
    I2C_SendData(I2C1, sendbuf[i]);
    while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING) == ERROR);
  }
  
  I2C_SendData(I2C1, 0x00); // 发送一个错误的CRC校验码
  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == ERROR && I2C_GetFlagStatus(I2C1, I2C_FLAG_AF) == RESET); // 等待AF或BTF置1
  I2C_GenerateSTOP(I2C1, ENABLE);
  while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) == SET);
  
  // 接收端发送非应答, 发送端知道接收端没有收到正确的数据
  if (I2C_GetFlagStatus(I2C1, I2C_FLAG_AF) == SET)
  {
    I2C_ClearFlag(I2C1, I2C_FLAG_AF);
    printf("Not correctly received!\n");
  }
  printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x\n", I2C_ReadRegister(I2C1, I2C_Register_SR1), I2C_ReadRegister(I2C1, I2C_Register_SR2), I2C_ReadRegister(I2C1, I2C_Register_CR1));
  // 发送端和接收端算出的I2C_SR2_PEC都是0x87(但发送端实际发送的校验码是0x00), 即数据和地址码的CRC校验码, PEC不为0表示出了错误
}

int main(void)
{
  DMA_InitTypeDef dma;
  GPIO_InitTypeDef gpio;
  I2C_InitTypeDef i2c;
  USART_InitTypeDef usart;
  
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1 | RCC_APB1Periph_USART3 | RCC_APB1Periph_UART5, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE);
  
  GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE);
  gpio.GPIO_Mode = GPIO_Mode_AF_OD;
  gpio.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
  gpio.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOB, &gpio);
  
  gpio.GPIO_Mode = GPIO_Mode_AF_PP;
  gpio.GPIO_Pin = GPIO_Pin_10;
  GPIO_Init(GPIOB, &gpio);
  
  GPIO_WriteBit(GPIOE, GPIO_Pin_15, Bit_SET);
  gpio.GPIO_Mode = GPIO_Mode_Out_PP;
  gpio.GPIO_Pin = GPIO_Pin_15;
  GPIO_Init(GPIOE, &gpio);
  
  USART_StructInit(&usart);
  usart.USART_BaudRate = 115200;
  usart.USART_Mode = USART_Mode_Tx;
  USART_Init(USART3, &usart);
  USART_Cmd(USART3, ENABLE);
  
  usart.USART_Mode = USART_Mode_Rx;
  USART_Init(UART5, &usart);
  USART_ITConfig(UART5, USART_IT_RXNE, ENABLE);
  USART_Cmd(UART5, ENABLE);
  NVIC_EnableIRQ(UART5_IRQn);
  
  I2C_StructInit(&i2c);
  i2c.I2C_ClockSpeed = 400000;
  I2C_Init(I2C1, &i2c);
  I2C_CalculatePEC(I2C1, ENABLE);
  I2C_Cmd(I2C1, ENABLE);
  
  DMA_StructInit(&dma);
  dma.DMA_DIR = DMA_DIR_PeripheralDST;
  dma.DMA_MemoryBaseAddr = (uint32_t)sendbuf;
  dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
  dma.DMA_PeripheralBaseAddr = (uint32_t)&I2C1->DR;
  DMA_Init(DMA1_Channel6, &dma);
  
  dma.DMA_DIR = DMA_DIR_PeripheralSRC;
  dma.DMA_MemoryBaseAddr = (uint32_t)recvbuf;
  DMA_Init(DMA1_Channel7, &dma);
  
  printf("I2C Master!\n");
  while (1)
    __WFI();
}

void UART5_IRQHandler(void)
{
  uint8_t data = USART_ReceiveData(UART5);
  GPIO_WriteBit(GPIOE, GPIO_Pin_15, Bit_RESET); // 灯亮
  if (data == 'a')
    test_write();
  else if (data == 'b')
    test_write_dma();
  else if (data == 'c')
    test_read();
  else if (data == 'd')
    test_read_dma();
  else if (data == 'e')
    test_write_wrongcrc();
  GPIO_WriteBit(GPIOE, GPIO_Pin_15, Bit_SET); // 灯灭
}


【从机端程序:普通方式(库函数版)】

#include 
#include 

// 调试程序时, 先不使用PEC位, 检查程序是否能正常收到所有数据和PEC校验码
// 然后再开PEC, 若I2C总线释放后, I2C_SR2_PEC为0, 则表明程序流程正确

//#define TEST_CRCWRONG

const uint8_t sendbuf[] = {0xb1, 0x45, 0x98, 0xa3, 0x26, 0x77};
uint8_t recvbuf[100];
uint8_t size;

void dump(const uint8_t *data, uint16_t size)
{
  while (size--)
    printf("%02x", *data++);
  printf("\n");
}

int fputc(int ch, FILE *fp)
{
  if (fp == stdout)
  {
    if (ch == '\n')
    {
      while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
      USART_SendData(USART1, '\r');
    }
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    USART_SendData(USART1, ch);
  }
  return ch;
}

int main(void)
{
  GPIO_InitTypeDef gpio;
  I2C_InitTypeDef i2c;
  USART_InitTypeDef usart;
  
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_USART1, ENABLE);
  
  gpio.GPIO_Mode = GPIO_Mode_AF_PP;
  gpio.GPIO_Pin = GPIO_Pin_9;
  gpio.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &gpio);
  
  gpio.GPIO_Mode = GPIO_Mode_AF_OD;
  gpio.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
  GPIO_Init(GPIOB, &gpio);
  
  USART_StructInit(&usart);
  usart.USART_BaudRate = 115200;
  usart.USART_Mode = USART_Mode_Tx;
  USART_Init(USART1, &usart);
  USART_Cmd(USART1, ENABLE);
  printf("I2C Slave!\n");
  
  I2C_StructInit(&i2c);
  i2c.I2C_Ack = I2C_Ack_Enable;
  i2c.I2C_ClockSpeed = 400000;
  i2c.I2C_OwnAddress1 = 0x06;
  I2C_Init(I2C1, &i2c);
  I2C_CalculatePEC(I2C1, ENABLE);
  I2C_ITConfig(I2C1, I2C_IT_BUF | I2C_IT_EVT | I2C_IT_ERR, ENABLE);
  I2C_Cmd(I2C1, ENABLE);
  NVIC_EnableIRQ(I2C1_EV_IRQn);
  NVIC_EnableIRQ(I2C1_ER_IRQn);
  
  while (1)
    __WFI();
}

void I2C1_EV_IRQHandler(void)
{
  // ADDR位检测, 执行后会清除ADDR位, 因此只能检测一次
  if (I2C_CheckEvent(I2C1, I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED) == SUCCESS) // 无论是发送模式还是接收模式都能检测到
    size = 0;
  
  if (I2C_CheckEvent(I2C1, I2C_EVENT_SLAVE_BYTE_TRANSMITTING) == SUCCESS)
  {
    // 发送
    if (size < sizeof(sendbuf))
      I2C_SendData(I2C1, sendbuf[size++]);
    else if (size == sizeof(sendbuf))
    {
#ifdef TEST_CRCWRONG
      I2C_SendData(I2C1, 0x00); // 发送一个错误的CRC码, 看接收端能不能检查到
      // 注意: 由于Master-Receiver端必须在PEC校验码后返回一个NACK, 所以从机发送端无法知道主机端到底有没有正确收到数据
#else
      I2C_TransmitPEC(I2C1, ENABLE); // 发送正确的CRC码 (TXE将不会清零)
#endif
      size++;
      
      // 开始发送倒数第二个字节时, TXE置位, DR送入最后一个字节, TXE被清零
      // 开始发送最后一个字节时, TXE置位, PEC置1后TXE仍为1
      // PEC发送完后, 收到NACK请求停止发送数据, AF将置位
      I2C_ITConfig(I2C1, I2C_IT_BUF, DISABLE); // 关闭TXE和RXNE中断, 防止卡死 (BTF中断不会被关闭)
    }
  }
  else
  {
    // 接收
    if (I2C_CheckEvent(I2C1, I2C_EVENT_SLAVE_BYTE_RECEIVED) == SUCCESS) // 注意: 接收PEC时, RXNE仍要置位!
    {
      recvbuf[size++] = I2C_ReceiveData(I2C1);
      if (size == 4)
        I2C_TransmitPEC(I2C1, ENABLE); // 最后一个字节收到后, 下一个字节应为PEC (此时已经正在接收PEC了)
    }
    
    if (I2C_CheckEvent(I2C1, I2C_EVENT_SLAVE_STOP_DETECTED) == SUCCESS)
    {
      I2C_Cmd(I2C1, ENABLE); // 清除STOPF
      printf("Slave reception terminated! Data: ");
      dump(recvbuf, size);
      printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x\n", I2C_ReadRegister(I2C1, I2C_Register_SR1), I2C_ReadRegister(I2C1, I2C_Register_SR2), I2C_ReadRegister(I2C1, I2C_Register_CR1));
    }
  }
  
  printf("*");
}

void I2C1_ER_IRQHandler(void)
{
  if (I2C_CheckEvent(I2C1, I2C_EVENT_SLAVE_ACK_FAILURE) == SUCCESS)
  {
    I2C_ClearFlag(I2C1, I2C_FLAG_AF);
    while (I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE) == SET); // 等待TXE回到0
    I2C_ITConfig(I2C1, I2C_IT_BUF, ENABLE); // 重开TXE和RXNE中断
    printf("Slave transmission terminated!\n");
    printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x\n", I2C_ReadRegister(I2C1, I2C_Register_SR1), I2C_ReadRegister(I2C1, I2C_Register_SR2), I2C_ReadRegister(I2C1, I2C_Register_CR1));
  }
  
  if (I2C_GetFlagStatus(I2C1, I2C_FLAG_PECERR) == SET)
  {
    I2C_ClearFlag(I2C1, I2C_FLAG_PECERR);
    printf("Reception PEC error!\n");
  }
  
  printf("*");
}

【从机端程序:DMA方式(库函数版)】

#include 
#include 

// 调试程序时, 先不使用PEC位, 检查程序是否能正常收到所有数据和PEC校验码
// 然后再开PEC, 若I2C总线释放后, I2C_SR2_PEC为0, 则表明程序流程正确

const uint8_t sendbuf[] = {0xb1, 0x45, 0x98, 0xa3, 0x26, 0x77};
uint8_t recvbuf[100];

void dump(const uint8_t *data, uint16_t size)
{
  while (size--)
    printf("%02x", *data++);
  printf("\n");
}

int fputc(int ch, FILE *fp)
{
  if (fp == stdout)
  {
    if (ch == '\n')
    {
      while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
      USART_SendData(USART1, '\r');
    }
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    USART_SendData(USART1, ch);
  }
  return ch;
}

int main(void)
{
  DMA_InitTypeDef dma;
  GPIO_InitTypeDef gpio;
  I2C_InitTypeDef i2c;
  USART_InitTypeDef usart;
  
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_USART1, ENABLE);
  
  gpio.GPIO_Mode = GPIO_Mode_AF_PP;
  gpio.GPIO_Pin = GPIO_Pin_9;
  gpio.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &gpio);
  
  gpio.GPIO_Mode = GPIO_Mode_AF_OD;
  gpio.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
  GPIO_Init(GPIOB, &gpio);
  
  USART_StructInit(&usart);
  usart.USART_BaudRate = 115200;
  usart.USART_Mode = USART_Mode_Tx;
  USART_Init(USART1, &usart);
  USART_Cmd(USART1, ENABLE);
  printf("I2C Slave!\n");
  
  I2C_StructInit(&i2c);
  i2c.I2C_Ack = I2C_Ack_Enable;
  i2c.I2C_ClockSpeed = 400000;
  i2c.I2C_OwnAddress1 = 0x06;
  I2C_Init(I2C1, &i2c);
  I2C_CalculatePEC(I2C1, ENABLE);
  I2C_ITConfig(I2C1, I2C_IT_EVT | I2C_IT_ERR, ENABLE); // 使用DMA时不可开ITBUFEN中断
  I2C_Cmd(I2C1, ENABLE);
  NVIC_EnableIRQ(I2C1_EV_IRQn);
  NVIC_EnableIRQ(I2C1_ER_IRQn);
  
  DMA_StructInit(&dma);
  dma.DMA_DIR = DMA_DIR_PeripheralDST;
  dma.DMA_MemoryBaseAddr = (uint32_t)sendbuf;
  dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
  dma.DMA_PeripheralBaseAddr = (uint32_t)&I2C1->DR;
  DMA_Init(DMA1_Channel6, &dma);
  DMA_ITConfig(DMA1_Channel6, DMA_IT_TC, ENABLE); // 要打开传输完成中断
  NVIC_EnableIRQ(DMA1_Channel6_IRQn);
  
  dma.DMA_DIR = DMA_DIR_PeripheralSRC;
  dma.DMA_MemoryBaseAddr = (uint32_t)recvbuf;
  DMA_Init(DMA1_Channel7, &dma);
  DMA_ITConfig(DMA1_Channel7, DMA_IT_TC, ENABLE); // 要打开传输完成中断
  NVIC_EnableIRQ(DMA1_Channel7_IRQn);
  
  while (1)
    __WFI();
}

void DMA1_Channel6_IRQHandler(void)
{
  DMA_ClearITPendingBit(DMA1_IT_TC6); // 清除标志位
  I2C_DMACmd(I2C1, DISABLE);
  DMA_Cmd(DMA1_Channel6, DISABLE); // I2C传输结束后应关闭DMA
}

void DMA1_Channel7_IRQHandler(void)
{
  // 这里不需要关ACK, 校验失败时自动发NACK
  DMA_ClearITPendingBit(DMA1_IT_TC7);
  I2C_DMALastTransferCmd(I2C1, DISABLE);
  I2C_DMACmd(I2C1, DISABLE);
  DMA_Cmd(DMA1_Channel7, DISABLE);
}

void I2C1_EV_IRQHandler(void)
{
  // 执行I2C_CheckEvent函数会将ADDR位清除, 所以必须单独检测
  if (I2C_GetFlagStatus(I2C1, I2C_FLAG_ADDR) == SET)
  {
    // 开DMA
    if (I2C_CheckEvent(I2C1, I2C_EVENT_SLAVE_TRANSMITTER_ADDRESS_MATCHED) == SUCCESS)
    {
      // 发送模式
      DMA_SetCurrDataCounter(DMA1_Channel6, sizeof(sendbuf)); // 不包括PEC
      DMA_Cmd(DMA1_Channel6, ENABLE);
    }
    else // 此时ADDR位已被清除, 因此不可以检测I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED事件
    {
      // 接收模式
      DMA_SetCurrDataCounter(DMA1_Channel7, 5); // 包括PEC
      DMA_Cmd(DMA1_Channel7, ENABLE);
      I2C_DMALastTransferCmd(I2C1, ENABLE); // 接收完最后一个数据不应答
    }
    I2C_DMACmd(I2C1, ENABLE);
  }
  
  if (I2C_CheckEvent(I2C1, I2C_EVENT_SLAVE_STOP_DETECTED) == SUCCESS)
  {
    I2C_Cmd(I2C1, ENABLE); // 清除STOPF
    printf("Slave reception terminated! Data: ");
    dump(recvbuf, 5);
    printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x, DMA1->ISR=0x%08x\n", I2C_ReadRegister(I2C1, I2C_Register_SR1), I2C_ReadRegister(I2C1, I2C_Register_SR2), I2C_ReadRegister(I2C1, I2C_Register_CR1), DMA1->ISR);
  }
  
  printf("*");
}

void I2C1_ER_IRQHandler(void)
{
  if (I2C_CheckEvent(I2C1, I2C_EVENT_SLAVE_ACK_FAILURE) == SUCCESS)
  {
    I2C_ClearFlag(I2C1, I2C_FLAG_AF);
    printf("Slave transmission terminated!\n");
    printf("I2C1->SR1=0x%04x, I2C1->SR2=0x%04x, I2C1->CR1=0x%04x, DMA1->ISR=0x%08x\n", I2C_ReadRegister(I2C1, I2C_Register_SR1), I2C_ReadRegister(I2C1, I2C_Register_SR2), I2C_ReadRegister(I2C1, I2C_Register_CR1), DMA1->ISR);
  }
  
  if (I2C_GetFlagStatus(I2C1, I2C_FLAG_PECERR) == SET)
  {
    I2C_ClearFlag(I2C1, I2C_FLAG_PECERR);
    printf("Reception PEC error!\n");
  }
  
  printf("*");
}


你可能感兴趣的:(单片机,stm32,c语言,I2C,CRC)