[RISC-V MCU 应用开发] 第四十四章、CH32V103应用教程——I2C-PEC,主机发送从机接收

[复制链接]
 楼主| RISCVLAR 发表于 2021-1-6 14:25 | 显示全部楼层 |阅读模式
I2c, IO, ni, ST, ic
本帖最后由 RISCVLAR 于 2021-1-6 14:24 编辑

CH32V103应用教程——I2C-PEC,主机发送从机接收

本章教程主要在前面第38章的基础上进行PEC模式下的主机发送从机接收实验。
注意,本章例程使用CH32V103硬件IIC。

1、I2C简介及相关函数介绍
IIC包错误校验(PEC)是为了提供传输的可靠性而增加一项 CRC8 校验的步骤,使用以下多项式对每一位串行数据进行计算:
                         C=X8+X2+X+1
PEC 计算是由控制寄存器的 ENPEC 位激活,对所有信息字节进行计算,包括地址和读写位在内。在发送时,启用 PEC 会在最后一字节数据之后加上一个字节的 CRC8 计算结果;而在接收模式,在最后一字节被认为是 CRC8 校验结果,如果和内部的计算结果不符合,就会回复一个 NAK,如果是主接收器,无论校验结果正确与否,都会回复一个 NAK。
关于I2C其他相关介绍,在前面章节已经介绍,在此不再赘述。
关于CH32V103各模式介绍以及具体信息,可参考CH32V103应用手册。I2C标准库函数在第十四章节已介绍,在此不再赘述。

2、硬件设计
本章教程使用开发板硬件I2C进行主机发送从机接收实验,需用到两个开发板,将两个开发板对应IIC引脚连接起来即可。
注意:此处需要将开发板I2C引脚外接上拉电阻。

3软件设计
本章教程在前面第38章基础上使用硬件I2C在PEC模式下进行主机发送从机接收实验,具体程序如下:
iic.h文件
  1. #ifndef __IIC_H
  2. #define __IIC_H

  3. #include "ch32v10x_conf.h"

  4. /* I2C Mode Definition */
  5. #define HOST_MODE   0
  6. #define SLAVE_MODE   1

  7. /* I2C Communication Mode Selection */
  8. #define I2C_MODE   HOST_MODE
  9. //#define I2C_MODE   SLAVE_MODE

  10. /* Global define */
  11. #define Size   7
  12. #define RXAdderss   0x02
  13. #define TxAdderss   0x02

  14. void IIC_Init( u32 bound, u16 address );
  15. void I2C1_ER_IRQHandler(void );

  16. #endif
iic.h文件主要进行宏定义和函数声明;
iic.c文件
  1. #include "iic.h"

  2. void I2C1_ER_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));

  3. /*******************************************************************************
  4. * Function Name  : IIC_Init
  5. * Description    : Initializes the IIC peripheral.
  6. * Input          : None
  7. * Return         : None
  8. *******************************************************************************/
  9. void IIC_Init( u32 bound, u16 address )
  10. {
  11.     GPIO_InitTypeDef GPIO_InitStructure;
  12.     I2C_InitTypeDef I2C_InitTSturcture;
  13.     NVIC_InitTypeDef NVIC_InitStructure;

  14.     RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE );
  15.     GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE);
  16.     RCC_APB1PeriphClockCmd( RCC_APB1Periph_I2C1, ENABLE );

  17.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
  18.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
  19.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  20.     GPIO_Init( GPIOB, &GPIO_InitStructure );

  21.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  22.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
  23.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  24.     GPIO_Init( GPIOB, &GPIO_InitStructure );

  25.     I2C_InitTSturcture.I2C_ClockSpeed = bound;
  26.     I2C_InitTSturcture.I2C_Mode = I2C_Mode_I2C;
  27.     I2C_InitTSturcture.I2C_DutyCycle = I2C_DutyCycle_16_9;
  28.     I2C_InitTSturcture.I2C_OwnAddress1 = address;
  29.     I2C_InitTSturcture.I2C_Ack = I2C_Ack_Enable;
  30.     I2C_InitTSturcture.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
  31.   I2C_Init( I2C1, &I2C_InitTSturcture );

  32. #if (I2C_MODE == SLAVE_MODE)
  33.     NVIC_InitStructure.NVIC_IRQChannel = I2C1_ER_IRQn;
  34.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
  35.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  36.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  37.     NVIC_Init( &NVIC_InitStructure );

  38.     I2C_ITConfig( I2C1, I2C_IT_ERR, ENABLE );

  39. #endif

  40.     I2C_Cmd( I2C1, ENABLE );
  41.     I2C_CalculatePEC( I2C1, ENABLE );

  42. #if (I2C_MODE == HOST_MODE)
  43.     I2C_AcknowledgeConfig( I2C1, ENABLE );

  44. #endif
  45. }


  46. /*******************************************************************************
  47. * Function Name  : I2C1_ER_IRQHandler
  48. * Description    : This function IIC PEC error exception.
  49. * Input          : None
  50. * Output         : None
  51. * Return         : None
  52. ********************************************************************************/
  53. void I2C1_ER_IRQHandler(void )
  54. {
  55.     if( I2C_GetITStatus( I2C1, I2C_IT_PECERR ) != RESET )
  56.     {
  57.         printf( "PECEER\r\n" );
  58.         I2C_ClearITPendingBit( I2C1, I2C_IT_PECERR );
  59.     }
  60. }
iic.c文件主要包括两个函数,一个是IIC初始化配置函数,包括硬件IIC对应GPIO引脚的初始化配置、IIC的初始化配置以及从机模式下的NVIC配置;一个是中断服务函数,由CH32V103应用手册可知,每个I2C模块都有两种中断向量,分别是事件中断和错误中断,此处为错误中断,中断源为PECERR,当PEC发生错误异常时进入中断服务函数。
main.c文件
  1. /********************************** (C) COPYRIGHT *******************************
  2. * File Name          : main.c
  3. * Author             : WCH
  4. * Version            : V1.0.0
  5. * Date               : 2020/04/30
  6. * Description        : Main program body.
  7. *******************************************************************************/

  8. #include "debug.h"
  9. #include "iic.h"

  10. /* Global Variable */
  11. u8 TxData[Size] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
  12. u8 RxData[Size];

  13. /*******************************************************************************
  14. * Function Name  : main
  15. * Description    : Main program.
  16. * Input          : None
  17. * Return         : None
  18. *******************************************************************************/
  19. int main(void)
  20. {
  21.     u8 i=0;
  22.     u8 pecValue;

  23.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
  24.     Delay_Init();
  25.     USART_Printf_Init(115200);
  26.     printf("SystemClk:%d\r\n",SystemCoreClock);

  27. #if (I2C_MODE == HOST_MODE)

  28.     printf("IIC Host mode\r\n");

  29.     //IIC进行主机发送初始化配置
  30.     IIC_Init( 80000, TxAdderss);

  31.     //当I2C1处于空闲状态时,跳过此while循环,开启起始信号
  32.     while( I2C_GetFlagStatus( I2C1, I2C_FLAG_BUSY ) != RESET );

  33.     //开启I2C1通信起始信号
  34.     I2C_GenerateSTART( I2C1, ENABLE );

  35.     //当最后一个事件为I2C_EVENT_MASTER_MODE_SELECT事件时,说明选择I2C作为主机模式,跳过此while循环,进行下一步
  36.     while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_MODE_SELECT ) );

  37.     //发送地址来选择从机设备
  38.     I2C_Send7bitAddress( I2C1, 0x02, I2C_Direction_Transmitter );

  39.     //当最后一个事件为I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED事件时,说明选择I2C进行主机发送,跳过此while循环,进行下一步数据发送
  40.     while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED ) );

  41.     while( i<7 )
  42.     {
  43.         //此段为发送数据
  44.         if( i<6)
  45.         {
  46.             if( I2C_GetFlagStatus( I2C1, I2C_FLAG_TXE ) !=  RESET )
  47.             {
  48.                 I2C_SendData( I2C1, TxData[i] );
  49.                 i++;
  50.             }
  51.         }

  52.         //此段为发送PEC值。在发送时:在最后一个TXE事件时设置I2C1_CTLR1寄存器的PEC传输位, PEC将在最后一个 字节后被发送。
  53.         if( i==6)
  54.         {
  55.             if( I2C_GetFlagStatus( I2C1, I2C_FLAG_TXE ) !=  RESET )
  56.             {
  57.                 //开启当前I2C1的PEC传输
  58.                 I2C_TransmitPEC( I2C1, ENABLE );
  59.                 i++;
  60.             }
  61.         }
  62.     }

  63.     //当最后一个事件为I2C_EVENT_MASTER_BYTE_TRANSMITTED事件时,说明发送结束,跳过此while循环,进行下一步
  64.     while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED ) );

  65.     //开启I2C通信停止信号
  66.     I2C_GenerateSTOP( I2C1, ENABLE );

  67. #elif (I2C_MODE == SLAVE_MODE)
  68.     printf("IIC Slave mode\r\n");

  69.     //IIC从机模式初始化
  70.     IIC_Init( 80000, RXAdderss);

  71.     //当最后一个事件为I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED事件时,说明选择I2C作为从机进行数据接收,跳过此while循环,进行数据接收
  72.     while( !I2C_CheckEvent( I2C1, I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED ) );

  73.     while( i<7 )
  74.     {
  75.         //此循环是对发送数据前五个值的接收
  76.         if( i<5 )
  77.         {
  78.             if( I2C_GetFlagStatus( I2C1, I2C_FLAG_RXNE ) !=  RESET )
  79.             {
  80.                 RxData[i] = I2C_ReceiveData( I2C1 );
  81.                 i++;
  82.             }
  83.         }

  84.         //此段是对发送数据最后一个数据的接收以及获取当前IIC的PEC值
  85.         if( i==5 )
  86.         {
  87.             //得到当前IIC的PEC值(内部计算结果)
  88.             pecValue = I2C_GetPEC( I2C1 );

  89.             if( I2C_GetFlagStatus( I2C1, I2C_FLAG_RXNE ) !=  RESET )
  90.             {
  91.                 RxData[i] = I2C_ReceiveData( I2C1 );
  92.                 i++;
  93.             }
  94.         }

  95.         //此段是对发送数据后接一个字节的CRC8计算结果的接收
  96.         if( i==6 )
  97.         {
  98.             //在接收时:在最后一个RXNE事件之后设置I2C1_CTLR1寄存器的PEC位
  99.             I2C_TransmitPEC( I2C1, ENABLE ); //设置I2C_CR1寄存器的PEC位

  100.             if( I2C_GetFlagStatus( I2C1, I2C_FLAG_RXNE ) !=  RESET )
  101.             {
  102.                 RxData[i] = I2C_ReceiveData( I2C1 ); //此处接收到的值为CRC8计算结果的值
  103.                 i++;
  104.             }
  105.         }
  106.     }

  107.     printf( "pecValue:%02x\r\n", pecValue);
  108.     printf( "RxData:\r\n" );

  109.     for( i=0; i<7; i++ )
  110.     {
  111.         printf( "%02x\r\n", RxData[i] );
  112.     }

  113. #endif

  114.     while(1);
  115. }

main.c文件主要进行主机模式下的数据接收配置和从机模式下的数据发送配置。其中在7位地址模式下,发送的第一个字节为地址字节,头7位代表的是目标从设备地址,第8位决定了后续报文的方向,0代表是主设备写入数据到从设备,1代表是主设备向从设备读取信息。其中I2C_Send7bitAddress函数会根据第三个输入进行写入和读取的判断。
关于PEC值的发送和接收,在发送时,在最后一个TXE事件时设置I2C1控制寄存器1的PEC传输位,PEC将在最后一个字节后被发送;在接收时,在最后一个RXNE事件后设置I2C1控制寄存器1的PEC传输位,在最后一字节被认为是 CRC8 校验结果,如果和内部的计算结果不符合,就会回复一个 NAK,如果是主接收器,无论校验结果正确与否,都会回复一个 NAK。

4下载验证
将编译好的程序分别在主机模式和从机模式下下载到两个开发版,并将两个开发板的PB8和PB9引脚接上拉电阻然后连接起来,将两个开发板进行复位,即可进行I2C通信,从机接收开发板串口打印情况具体如下:
图片1.png



43、IIC-PEC,主机发送从机接收.rar

479.71 KB, 下载次数: 219

您需要登录后才可以回帖 登录 | 注册

本版积分规则

133

主题

296

帖子

44

粉丝
快速回复 在线客服 返回列表 返回顶部

133

主题

296

帖子

44

粉丝
快速回复 在线客服 返回列表 返回顶部