你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32 USART串口、IIC和CAN通信

[复制链接]
STMCU小助手 发布时间:2022-4-8 21:45
在简单的学习过了STM32中的简单外设以及中断系统后,在本章节中开始介绍STM32芯片中各个通信接口的配置。在计算机中,按数据传输方式可分为串行通信以及并行通信;按数据同步方式可分为异步通信和同步通信;按数据传输方向课分为单工、半双工和全双工通信。
串行通信: 在一条数据线上,将数据按照二进制位依次传输,传输一位数据占据一个固定的时间长度。适用于计算机之间、计算机与外设之间的远距离通信,其具备占用传输线数量少、长距离传输时成本低的优点,但数据传输控制相比于并行通信复杂。
并行通信: 在多条数据线上,一个数据的多位同时进行传输,通常是8位、16位或32位数据一起进行传输。其具备控制简单、传输速度快的特点,但由于其占用数据线过多,长距离传输数据时成本较高,且接受设备出同时接收数据时容易出现错位即抗干扰能力弱。
异步通信: 通信双方(发送和接收端)使用各自的时钟控制数据的发送和接收,但为了双方的收发协调,时钟需要尽可能的一致。其具备实现容易、成本低的特点,但通信过程中每个字符需要添加2~3位数据作为起止位,各帧之间需有间隔导致了传输效率不高。
同步通信: 通信时需要建立发送方时钟对接收方时钟的直接控制,使得双方达到完全的同步。传输数据的位之间距离为“位间隔”的整数倍,且传送的字符间不留间隙,保持位同步关系。
单工通信: 数据的传输仅能够沿着一个方向,不能反向传输。
半双工通信: 数据的传输可以沿着两个方向,但需要分时进行。
全双工通信: 数据的传输可以同时双向的进行。
通信速率: 衡量通信性能的参数,以波特率来表示,代表着每秒钟传输二进制编码的位数,单位为:位/秒。

1、USART串口通信
1.1 USART介绍

串口通信(Serial communication)是外设和计算机之间经过数据线、地线以及电源线进行数据传输的一种通信方式,属于串行通信。串口是常用的接口标准,规定了电气标准,但没有规定接口插件电缆以及使用的协议。
A. 接口标准
串口通信的接口标准包含了RS-232C、ES-232、RS-422A、RS-485等等,常用的是RS-232C标准,定义了数据终端设备(DTE)与数据通信设备(DCE)之间的物理接口标准。而在RS-232C标准下又有DB25(25针连接器)和DB9(9针连接器)之分,DB9的管脚说明如下表中所示:

~YN_9XF[E}{D0KL69~GL61Q.png

B. 通信协议
RS23的通信协议遵循96-N-8-1格式。其中“96”表示通信波特率为9600;“N”表示无校验位;“8”表述数据位数为8位;“1”表示有1位停止位。

在STM32中的USART(通用同步异步收发器),可以与外部设备进行全双工数据交换,UART(通用异步收发器)是在其基础上剪裁掉了同步通信功能。USART在STM32中应用最多的是对printf重定向,通过输出函数将信息打印到串口助手上显示出来。USART的内部结构框图如下所示:

)DJK7620%`~I4HSNRXQE$`V.png

引脚功能: TX(发送数据输出引脚),RX(接收数据输入引脚),SW_RX(数据接收引脚,内部引脚),nRTS(请求以发送,n表示低电平有效,仅适用于硬件流控制),nCTS(清除以发送,n表示低电平有效,仅适用于硬件流控制)。
数据寄存器: USART_DR,低9位有效,第9位数据是否有效取决于USART控制寄存器1(USART_CR1)的M位设置,当M位为0时表示8位数据字长,M位为1时表示9位数据字长,通常情况下使用8位数据字长。
控制器: USART中有专门控制发送的发送器、控制接收的接收器,以及唤醒单元、中断控制等。
波特率生成: 波特率越大,传输速度越快。
计算公式: 波特率= USART的时钟频率 / (16*USARTDIV)
其中USARTDIV是一个存放在波特率寄存器(USART_BRR)的一个无符号定点数。串口通信常用的波特率为4800、9600、115200等。

1.2 串口通信配置步骤
在对STM32F1的USART进行配置时,需要使用到USART库,包含stm32f10x_usart.c和stm32f10x_usart.h文件。

使能串口时钟和GPIO端口时钟;
STM32F103ZET6芯片中共有5个串口通信资源,其中串口1挂载在APB1总线上,串口2~5挂载在APB2总线上。使用串口时,分别使能对应总线上的串口以及GPIO的时钟即可。调用函数:RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USARTx, ENABLE);

配置GPIO端口,设置串口引脚为复用功能;
使用引脚的USART功能需要将GPIO设置为复用功能,并将串口的Tx引脚配置为复用推挽输出,Rx引脚配置为浮空输入,调用函数:

  1. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  2. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  3. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  4. GPIO_Init(GPIOA, &GPIO_InitStructure);
  5. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
  6. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  7. GPIO_Init(GPIOA, &GPIO_InitStructure);
复制代码

初始化串口参数;
调用函数:void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
其中USART_InitTypeDef是一个结构体类型,其中的成员变量如下:
  1. typedef struct
  2. {
  3.         uint32_t USART_BaudRate;                                //波特率,4800 / 9600 / 115200
  4.         uint16_t USART_WordLength;                        //字长,8 / 9
  5.         uint16_t USART_StopBits;                                //停止位,0.5 / 1 / 1.5 / 2
  6.         uint16_t USART_Parity;                                        //校验位 USART_Parity_NO(无校验)、USART_Parity_Even(偶校验)以及USART_Parity_Odd(奇校验)
  7.         uint16_t USART_Mode;                                        //USART模式 USART_Mode_Rx / USART_Mode_Tx
  8.         uint16_t USART_HardwareFlowControl;        //硬件流控制 只有在硬件流模式下有效,常用无硬件流模式 USART_HardwareFlowControl_None
  9. } USART_InitTypeDef;
复制代码

使能串口;
调用串口函数:void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
设置串口中断类型并使能;
调用函数:void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
其中USARTx选择串口,USART_IT选择中断类型,NewState选择使能或失能。串口中断的类型很多,需要依据具有的场景进行选择,例如在串口接收数据时,产生中断:USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
串口发送数据时:USART_ITConfig(USART1, USART_IT_TC, ENABLE);
设置串口中断优先级,使能串口中断通道;
对串口中断进行了配置,需要初始化NVIC。
编写串口中断服务函数。
使用串口开启了中断功能,就需要在中断服务函数(USARTx_IRQHandler)判断由串口产生的中断是那种类型,然后实现相应的功能。判断中断类型,调用库函数:ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
串口中的接收函数:uint16_t USART_ReceiveData(USART_TYpeDef* USARTx);
发送函数:void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
读取串口状态标志位:FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
1.3 应用示例
使用STM32的USART1实现单片机与电脑端串口助手之间的通信,详细的代码如下:

usart.h

  1. #ifndef _usart_H
  2. #define _usart_H

  3. #include "system.h"

  4. void USART1_Init(u32 bound);

  5. #endif
复制代码

usart.c

  1. #include "usart.h"        
  2.          
  3. int fputc(int ch,FILE *p)
  4. {
  5.         USART_SendData(USART1, (u8)ch);
  6.         while(USART_GetFlagStatus(USART1,USART_FLAG_TC) != SET);
  7.         return ch;
  8. }

  9. void USART1_Init(u32 bound)
  10. {
  11.         GPIO_InitTypeDef GPIO_InitStructure;
  12.         USART_InitTypeDef USART_InitStructure;
  13.         NVIC_InitTypeDef NVIC_InitStructure;
  14.         
  15.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
  16.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);

  17.         
  18.         // 配置GPIO端口
  19.         GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//TX                        
  20.         GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  21.         GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;            
  22.         GPIO_Init(GPIOA,&GPIO_InitStructure);  
  23.         GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//RX                        
  24.         GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;                  
  25.         GPIO_Init(GPIOA,&GPIO_InitStructure);
  26.         

  27.    //USART1 初始化配置
  28.         USART_InitStructure.USART_BaudRate = bound;
  29.         USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  30.         USART_InitStructure.USART_StopBits = USART_StopBits_1;
  31.         USART_InitStructure.USART_Parity = USART_Parity_No;
  32.         USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//ÎÞÓ²¼þÊý¾ÝÁ÷¿ØÖÆ
  33.         USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;        
  34.         USART_Init(USART1, &USART_InitStructure);
  35.         
  36.         USART_Cmd(USART1, ENABLE);  
  37.         
  38.         USART_ClearFlag(USART1, USART_FLAG_TC);
  39.                
  40.         USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

  41.         //Usart1 NVIC 配置
  42.         NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
  43.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
  44.         NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;        
  45.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                        
  46.         NVIC_Init(&NVIC_InitStructure);        
  47. }


  48. void USART1_IRQHandler(void)                        
  49. {
  50.         u8 r;
  51.         if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  
  52.         {
  53.                 r =USART_ReceiveData(USART1);
  54.                 USART_SendData(USART1,r);
  55.                 while(USART_GetFlagStatus(USART1,USART_FLAG_TC) != SET);
  56.         }
  57.         USART_ClearFlag(USART1,USART_FLAG_TC);
  58. }         
复制代码

main.c


  1. #include "system.h"
  2. #include "led.h"
  3. #include "SysTick.h"
  4. #include "usart.h"

  5. int main()
  6. {
  7.         u8 i=0;
  8.         SysTick_Init(72);
  9.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  10.         LED_Init();
  11.         USART1_Init(9600);
  12.         
  13.         while(1)
  14.         {
  15.                 i++;
  16.                 if(i%20==0)
  17.                 {
  18.                         led1=!led1;
  19.                 }
  20.                 delay_ms(10);
  21.         }
  22. }
复制代码

2、I2C与EEPROM通信
2.1 I2C通信及配置步骤

I2C(Inter-Intergrated Circuit)是PHILIPS公司开发的串行总线,用于连接微控制器及其外围设备,属于同步通信。其具有控制方式简单,通信速率较高的优点。I2C总线由两根双向信号线组成,一根是数据线SDA,一根是时钟线SCL。使用I2C通信设备的连接如下图所示:
8`PUQ2WT(W876`W7%`AVZQL.png

I2C的物理层具备如下特点:
(1) 支持多设备,总线上可连接多个I2C设备,支持多主机和多从机设备;
(2) I2C总线只是用SDA和SCL两根线路,SDA传输数据,SCL用于数据收发同步时钟;
(3) 连接在I2C总线上的设备都拥有一个独一无二的地址,主机可使用改地址对不同设备之间进行访问;
(4) I2C总线通过上拉电阻,当I2C设备空闲时,输出高阻态,当所有设备都空闲时,由上拉电阻将总线拉升为高电平;
(5) 多主机设备同时使用I2C总线时,为了防止数据冲突,会采用仲裁方式决定占用情况;
(6) 三种传输模式:标准模式(速率100kbit/s)、快速模式(400kbit/s)以及高速模式(3.4Mbit/s);
(7) 连接到相同I2C总线上的设备数量受到总线最大电容400pF的限制。
I2C的协议层包含了:
(1) 数据有效性规定;
I2C总线在传输数据时,SCL时钟信号为高电平的过程中,数据线上的数据需要保持稳定;在时钟信号为低电平时,数据线上允许有高/低电平的变化。

20210503194956582.png

(2) 起始和停止信号;
SCL时钟线为高电平是,SDA数据线由高电平向低电平的变化表示起始信号;SCL为高电平是,SDA有低电平向高电平的变化表示终止信号。

20210503195014751.png

(3) 应答信号;
当主机发送器件传输一个字节的数据后,其尾部需跟一个校验位,检验位是接收端通过控制SDA来实现,可以提示发送端数据已被接收完成。校验位就是数据或地址传输过程中的响应,包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端,设备接收到数据后,如果要继续接收数据,则需要向对方发送ACK信号即特定的低电平脉冲;若不要继续接收数据,则需要向对方发送NACK信号即特定的高电平脉冲。

20210503195020386.png

(4) 总线寻址方式;
I2C总线寻址方式按照从机地址位数可分为:7位寻址和10位寻址。

20210503195025422.png

D7~D0组成从机的地址,D0为数据传输方向位,置0时表示主机向从机写数据,置1时表示主机由从机读取数据。
(5) 数据传输。
I2C总线上传输的数据比较广泛,包含有地址信号以及数据信号,在一个起始信号发出后,紧接着需要传输一个从机地址,每一次数据的传输由主机产生的终止信号结束。在总线的一次数据传输中,有下以下的方式:
A. 主机向从机发送数据,数据传输方向在整个过程中不变;
B. 主机在第一个字节后,变为从从机读取数据;
C. 传输过程中,需要改变数据流动方向时,起始信号和从机地址被重复产生一次,但两次读/写方向位相反。

2.2 EEPROM(AT24C02)
AT24C02是一个2K位串行CMOS,内部含有256个8位字节,有一个16字节页写缓冲器。AT24C02通过I2C总线进行操作,拥有写保护功能,芯片内部存储的数据可以保证在掉电的情况下不丢失,一般用来存储一些比较重要的数据。

RJU[6F~W@6YRL4VIW(HKIV1.png

AT24C02芯片的地址有7位,高4位为1010,低3位由A0/A1/A2信号线的高/低电平决定。I2C通信中传输地址或数据都是以字节为单位的,在传输地址时,芯片的地址占据7位,还有1位用来选择读/写方向。

20210503195121937.png

2.3 应用示例

24cxx.h

  1. #ifndef _24cxx_H
  2. #define _24cxx_H

  3. #include "system.h"
  4. #include "iic.h"

  5. #define AT24C01                127
  6. #define AT24C02                255
  7. #define AT24C04                511
  8. #define AT24C08                1023
  9. #define AT24C16                2047
  10. #define AT24C32                4095
  11. #define AT24C64                8191
  12. #define AT24C128        16383
  13. #define AT24C256        32767

  14. #define EE_TYPE AT24C02

  15. void AT24CXX_Init(void);
  16. u8 AT24CXX_ReadOneByte(u16 ReadAddr);
  17. void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite);
  18. void AT24CXX_WritelenByte(u16 WriteAddr,u8 DataToWrite,u8 len);
  19. u32 AT24CXX_readLenByte(u16 ReadAddr,u8 Len);
  20. u8 AT24CXX_Check(void);
  21. void AT24CXX_read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead);
  22. void AT24CXX_write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite);

  23. #endif
复制代码

24cxx.c

  1. #include "24cxx.h"
  2. #include "SysTick.h"

  3. void AT24CXX_Init(void)
  4. {
  5.         IIC_Init();
  6. }

  7. u8 AT24CXX_ReadOneByte(u16 ReadAddr)        //从从机AT24Cxx读取一个字节数据
  8. {
  9.         u8 temp=0;
  10.         IIC_start();
  11.         if(EE_TYPE>AT24C16)
  12.         {
  13.                 IIC_send_byte(0xA0);                                 //发送写命令
  14.                 IIC_wait_ack();                                         //等待应答
  15.                 IIC_send_byte(ReadAddr>>8);                //发送高地址
  16.         }
  17.         else
  18.         {
  19.                 IIC_send_byte(0xA0+((ReadAddr/256)<<1));        //发送器件地址,写数据
  20.         }
  21.         IIC_wait_ack();
  22.         IIC_send_byte(ReadAddr%256);                        //发送低地址
  23.         IIC_wait_ack();
  24.         IIC_start();
  25.         IIC_send_byte(0xA1);                                        //进入接收模式
  26.         IIC_wait_ack();
  27.         temp=IIC_read_byte(0);
  28.         IIC_stop();
  29.         return temp;
  30. }
  31. //在AT24Cxx指定地址写入数据
  32. void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
  33. {
  34.         IIC_start();
  35.         if(EE_TYPE>AT24C16)
  36.         {
  37.                 IIC_send_byte(0xA0);                                 //发送写命令
  38.                 IIC_wait_ack();                                        //等待应答
  39.                 IIC_send_byte(WriteAddr>>8);                 //发送高地址
  40.         }
  41.         else
  42.         {
  43.                 IIC_send_byte(0xA0+((WriteAddr/256)<<1));         //发送器件地址,写数据
  44.         }
  45.         IIC_wait_ack();
  46.         IIC_send_byte(WriteAddr%256);
  47.         IIC_wait_ack();
  48.         IIC_send_byte(DataToWrite);
  49.         IIC_wait_ack();
  50.         IIC_stop();
  51.         delay_ms(10);
  52. }
  53. //向AT24CXX指定位置写入长度为len的数据
  54. void AT24CXX_WritelenByte(u16 WriteAddr,u8 DataToWrite,u8 len)
  55. {
  56.         u8 t;
  57.         for(t=0;t<len;t++)
  58.         {
  59.                 AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
  60.         }
  61. }
  62. //从ATC24XX中指定地址开始读长度为len的数据
  63. u32 AT24CXX_readLenByte(u16 ReadAddr,u8 Len)
  64. {
  65.         u8 t;
  66.         u32 temp=0;
  67.         for(t=0;t<Len;t++)
  68.         {
  69.                 temp<<=8;
  70.                 temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1);
  71.         }
  72.         return temp;
  73. }
  74. //检查ATC24XX工作是否正常,0正常,1不正常
  75. u8 AT24CXX_Check(void)
  76. {
  77.         u8 temp;
  78.         temp=AT24CXX_ReadOneByte(255);
  79.         if (temp==0x36)                                       
  80.                 return 0;
  81.         else
  82.         {
  83.                 AT24CXX_WriteOneByte(255,0x36);
  84.                 temp=AT24CXX_ReadOneByte(255);
  85.                 if (temp==0x36)
  86.                         return 0;
  87.         }
  88.         return 1;
  89. }
  90. //从指定地址开始读出指定额数据,pBuffer存放数据的数组首地址,NumToRead需要读出数据的个数
  91. void AT24CXX_read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
  92. {
  93.         while(NumToRead)
  94.         {
  95.                 *pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
  96.                 NumToRead--;
  97.         }
  98. }
  99. //指定地址写入指定个数的数据
  100. void AT24CXX_write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
  101. {
  102.         while(NumToWrite--)
  103.         {
  104.                 AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
  105.                 WriteAddr++;
  106.                 pBuffer++;
  107.         }
  108. }
复制代码

iic.h

  1. #ifndef _iic_H
  2. #define _iic_H

  3. #include "system.h"

  4. /*IIC_SCL时钟端口、引脚定义*/
  5. #define IIC_SCL_PORT                          GPIOB
  6. #define IIC_SCL_PIN                                 (GPIO_Pin_10)
  7. #define IIC_SCL_PORT_RCC                 RCC_APB2Periph_GPIOB

  8. /*IIC_SDA数据端口、引脚定义*/
  9. #define IIC_SDA_PORT                                 GPIOB
  10. #define IIC_SDA_PIN                                  (GPIO_Pin_11)
  11. #define IIC_SDA_PORT_RCC                RCC_APB2Periph_GPIOB

  12. /*IO操作*/
  13. #define IIC_SCL  PBout(10)//SCL
  14. #define IIC_SDA  PBout(11)//SDA
  15. #define READ_SDA PBin(11)//ÊäÈëSDA

  16. //IIC操作函数
  17. u8 IIC_read_byte(u8 ack);
  18. void IIC_send_byte(u8 byte);
  19. void IIC_noack(void);
  20. void IIC_ack(void);
  21. u8 IIC_wait_ack(void);
  22. void IIC_stop(void);//Í£Ö¹ÐźÅ
  23. void IIC_start(void);
  24. void SDA_IN(void);
  25. void SDA_OUT(void);
  26. void IIC_Init(void);

  27. #endif
复制代码

iic.c

  1. #include "iic.h"
  2. #include "SysTick.h"

  3. void IIC_Init(void)
  4. {
  5.         GPIO_InitTypeDef GPIO_InitStructure;
  6.         RCC_APB2PeriphClockCmd(IIC_SCL_PORT_RCC|IIC_SDA_PORT_RCC,ENABLE);
  7.         
  8.         GPIO_InitStructure.GPIO_Pin=IIC_SCL_PIN;
  9.         GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  10.         GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
  11.         GPIO_Init(IIC_SCL_PORT,&GPIO_InitStructure);

  12.         GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;
  13.         GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
  14.         
  15.         IIC_SCL=1;
  16.         IIC_SDA=1;
  17.         
  18. }


  19. /*模拟IIC工作时序*/
  20. void SDA_OUT(void)
  21. {
  22.         GPIO_InitTypeDef GPIO_InitStructure;
  23.         
  24.         GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;
  25.         GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  26.         GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
  27.         GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
  28. }
  29. void SDA_IN(void)
  30. {
  31.         GPIO_InitTypeDef GPIO_InitStructure;
  32.         
  33.         GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;
  34.         GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  35.         GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
  36.         GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
  37. }
  38. void IIC_start(void)                //起始信号
  39. {
  40.         SDA_OUT();
  41.         IIC_SDA=1;
  42.         IIC_SCL=1;
  43.         delay_us(5);
  44.         IIC_SDA=0;
  45.         delay_us(6);
  46.         IIC_SCL=0;
  47. }
  48. void IIC_stop(void)                        //终止信号
  49. {
  50.         SDA_OUT();
  51.         IIC_SCL=0;
  52.         IIC_SDA=0;
  53.         IIC_SCL=1;
  54.         delay_us(6);
  55.         IIC_SDA=1;
  56.         delay_us(6);
  57. }

  58. //等待应答信号的到来,1接收应答失败,0接收应答成功
  59. u8 IIC_wait_ack(void)
  60. {
  61.         u8 tempTime=0;
  62.         IIC_SDA=1;
  63.         delay_us(1);
  64.         SDA_IN();                         //SDA设置输入
  65.         IIC_SCL=1;
  66.         delay_us(1);
  67.         while(READ_SDA)
  68.         {
  69.                 tempTime++;
  70.                 if(tempTime>250)                //接收应答失败
  71.                 {
  72.                         IIC_stop();
  73.                         return 1;
  74.                 }
  75.         }
  76.         IIC_SCL=0;
  77.         return 0;
  78. }

  79. //产生应答信号
  80. void IIC_ack(void)
  81. {
  82.         IIC_SCL=0;
  83.         SDA_OUT();
  84.         IIC_SDA=0;
  85.         delay_us(2);
  86.         IIC_SCL=1;
  87.         delay_us(5);
  88.         IIC_SCL=0;
  89. }

  90. //产生非应答信号
  91. void IIC_noack(void)
  92. {
  93.         IIC_SCL=0;
  94.         SDA_OUT();
  95.         IIC_SDA=1;
  96.         delay_us(2);
  97.         IIC_SCL=1;
  98.         delay_us(5);
  99.         IIC_SCL=0;
  100. }

  101. //发送单字节信息
  102. void IIC_send_byte(u8 byte)        
  103. {
  104.         u8 t;
  105.         SDA_OUT();
  106.         IIC_SCL=0;                                //开始数据传输
  107.         for(t=0;t<8;t++)
  108.         {
  109.                 if((byte&0x80)>0)                //每一次发送最高位
  110.                         IIC_SDA=1;
  111.                 else
  112.                         IIC_SDA=0;
  113.                 byte<<=1;        //发送位左移到最高位
  114.                 delay_us(2);  
  115.                 IIC_SCL=1;
  116.                 delay_us(2);
  117.                 IIC_SCL=0;
  118.                 delay_us(2);
  119.         }
  120. }

  121. //读取单字节信息
  122. u8 IIC_read_byte(u8 ack)
  123. {
  124.         u8 i,receive=0;
  125.         SDA_IN();        //设置为输入
  126.         
  127.         for(i=0;i<8;i++)
  128.         {
  129.                 IIC_SCL=0;
  130.                 delay_us(2);
  131.                 IIC_SCL=1;
  132.                 receive<<=1;
  133.                 if(READ_SDA)
  134.                         receive++;
  135.                 delay_us(1);
  136.         }
  137.         //读取完成后,发送应答信息
  138.         if(!ack)
  139.         {
  140.                 IIC_noack();
  141.         }
  142.         else
  143.         {
  144.                 IIC_ack();
  145.         }
  146.         return receive;
  147. }
复制代码

main.c

  1. #include "system.h"
  2. #include "led.h"
  3. #include "SysTick.h"
  4. #include "usart.h"
  5. #include "24cxx.h"
  6. #include "key.h"

  7. int main()
  8. {
  9.         u8 i=0;
  10.         u8 key;
  11.         u8 k=0;
  12.         
  13.         SysTick_Init(72);
  14.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  15.         LED_Init();
  16.         USART1_Init(9600);
  17.         KEY_Init();
  18.         AT24CXX_Init();
  19.         while(AT24CXX_Check())
  20.         {
  21.                 printf("AT24C02检测不正常\r\n");
  22.                 delay_ms(500);
  23.         }
  24.         printf("AT24C02检测正常\r\n");
  25.         
  26.         while(1)
  27.         {
  28.                 key=KEY_Scan(0);
  29.                 if(key==KEY_UP)
  30.                 {
  31.                         k++;
  32.                         if(k>255)
  33.                         {
  34.                                 k=255;
  35.                         }
  36.                         AT24CXX_WriteOneByte(0,k);
  37.                         printf("写入的数据为:%d\r\n",k);
  38.                 }
  39.                 if(key==KEY_DOWN)
  40.                 {
  41.                         k=AT24CXX_ReadOneByte(0);
  42.                         printf("读取的数据为:%d\r\n",k);
  43.                 }
  44.                 i++;
  45.                 if(i%20==0)
  46.                 {
  47.                         led1=!led1;
  48.                 }
  49.                 delay_ms(10);
  50.         }
  51. }
复制代码

3、CAN通信
3.1 CAN介绍

CAN(Controller Area Network),控制器局域网络,是ISO国际标准化串行通信协议。为了减少线束的数量、通过多个LAN进行大量数据的高速通信的实际需要,1986年德国电气商博世公司开发了面向汽车的CAN通信协议。CAN是国际上应用最广泛的现场总线之一,为分布式系统实现各节点之间实时、可靠的数据通信提供了有利的技术支持。
CAN通信只需要两个信号线,CAN_H和CAN_L,CAN控制器根据两根信号线上的电位差来判断总线电平。总线电平被分为显性电平和隐性电平,发送方通过使总线电平发生变化,从而将信息发送给接收方。
CAN通信具备以下特点:
1、 多主控制;
总线空闲时,所有单元均可发生消息,当两个以上的单元同时发生消息时,依据标识符(ID)决定优先级。ID并不是表示发送的目的地址,而是表示访问总线消息的优先级。两个以上的单元开始发送消息时,对消息ID的每个位进行逐个仲裁比较。仲裁获胜的单元继续发送消息,仲裁失败的单元停止发送进行接收工作。
2、 系统柔软性;
与总线相连的单元没有类似“地址”的信息,因此在总线上增加单元时,总线上的其它单元软硬件及应用层不需要被改变。
3、 通信速度快、距离远;
最高1Mbps(距离小于40m),最远10km(速率低于5Kbps)。
4、 错误检测、错误通知和错误恢复功能;
总线上的所有单元都可检测错误,出现错误的单元会立即通知其它单元,正在发送消息的单元检测出错误后,会强制结束当前的发送。强制结束发送的单元会不断重新发送此消息直到成功发送为止。
5、 故障封闭功能;
可以判断出错的类型是总线上暂时的数据错误(外部噪声)或持续的数据错误(单元内部故障、驱动器故障、断线等)。当总线持续数据错误时,可将引起此故障的单元从总线上隔离。
6、 可连接节点多;
可以同时连接多个单元设备,连接总数理论上无限制,但实际上可连接的单元数受到总线时延和电气负载的限制。降低通信速度,可连接的单元数增加;提高通信速度,可连接单元数减少。

对于CAN通信的两种标准:ISO11898(高速)可以组建一个高速、短距离的闭环网络,该标准下总线最大长度40m,通信速率最高1Mbps,总线两端需各有一个120欧姆的电阻作为阻抗匹配功能,以减少回波反射。ISO11519-2(低速)可以组建一个低速、远距离的开环网络,该标准下总线最大长度1km,通信速率最高125kbps,两根总线独立不形成闭环,两根总线上各自串联一个2.2千欧的电阻。
在IS01898标准下,显性电平对应逻辑0,CAN_H和CAN_L压差为2.5V左右;隐性电平对应逻辑1,CAN_H和CAN_L压差为0V。在总线上显性电平具有优先权,只要有一个单元输出显性电平,总线上即为显性电平。隐性电平具有包容性,只有所有的单元都输出隐性电平,总线上才为隐性电平。
CAN总线和CAN控制器之间需要一个CAN收发器将信号进行转换,通信主要通过5种类型的帧进行,分别为数据帧、遥控帧、错误帧、过载帧以及帧间隔。其中数据帧由7个段构成,分别为帧起始(数据帧的开始段)、仲裁段(帧优先级的段)、控制段(数据的字节数及保留位的段)、数据段(数据内容,0~8字节数据)、CRC段(检查帧错误的段)、ACK段(确认正常接收段)以及帧结束段(数据帧结束的段)。

在STM32F1芯片中自带bxCAN控制器(Basic Extended CAN),基本扩展CAN,支持2.0A和B版本的CAN协议。目的是以最小的CPU负载,高效的管理大量传入消息,并按需要的优先级实现消息发送。

3.2 CAN通信配置步骤

使用库函数对CAN进行配置需要使用到库文件stm32f10x_can.h和stm32f10x_can.c,详细的步骤如下:
1、 使能CAN时钟,将对应的引脚复用映射为CAN功能;
CAN1和CAN2都是挂载在APB1总线上的设备,其发送和接收引脚对应不同的IO口,因此在使能时钟后,还需要使能对应IO口的时钟,并将其设置为复用功能。配置代码如下:

  1. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, EANBLE);
  2. RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, EANBLE);
  3. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
  4. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;                        //上拉输入模式
  5. GPIO_Init(GPIOA, & GPIO_InitStructure);
  6. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
  7. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                //复用推挽输出
  8. GPIO_InitStructure.GPIO_Speede = GPIO_Speed_50MHz;
  9. GPIO_Init(GPIOA, & GPIO_InitStructure);
复制代码

2、 设置CAN工作模式、波特率等;
调用函数:uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct);
参数CAN_InitStruct为结构体指针变量,其成员变量包含CAN工作模式以及波特率初始化的变量:

  1. typedef struct
  2. {
  3.                 uint16_t CAN_prescaler;                //设置CAN外设时钟分频
  4.                 uint8_t CAN_Mode;                        //正常模式(CAN_Mode_Normal)、回环模式(CAN_Mode_LoopBack)、静默模式(CAN_Mode_Silent)以及回环静默模式(CAN_Mode_Silent_LoopBack)
  5.                 uint8_t CAN_SJW;                                //设置CAN重新同步时单次可增加或缩短的最大长度,可配置为1-4tp(CAN_SJW_1/2/3/4tp)
  6.                 uint8_t CAN_BS1;                                //设置位时序BS1长度,CAN_BS1_1/2/3/…/16tp
  7.                 uint8_t CAN_BS2;                                //设置位时序BS2长度,CAN_BS1_1/2/3/…/8tp
  8.                 FunctionalState CAN_TTCM;        //是否使用时间触发功能
  9.                 FunctionalState CAN_ABOM;        //是否使用自动离线管理
  10.                 FunctionalState CAN_AWUM;        //是否使用自动唤醒功能
  11.                 FunctionalState CAN_NART;        //是否使用自动重传功能
  12.                 FunctionalState CAN_RFLM;        //是否使用锁定接收FIFO
  13.                 FunctionalState CAN_TXFP;                //设置发送报文的优先级判断方法,使能时以报文存入发送邮箱的先后顺序发送,失能时按照报文ID优先级发送
  14. }
复制代码

CAN的波特率 = Fpclk1 / ((CAN_BS1+CAN_BS2+1)*CAN_Prescaler)
3、 设置CAN筛选器(过滤器);
调用函数:void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);
其中结构体CAN_FilterInitTypeDef的成员变量为:

  1. typeder struct
  2. {
  3.                 uint16_t CAN_FilterIdHigh;                        //存储需要筛选的ID高16位
  4.                 uint16_t CAN_FilterIdLow;                        //存储需要筛选的ID低16位
  5.                 uint16_t CAN_FilterMaskIdHigh;                //存储需要筛选ID或掩码
  6.                 uint16_t CAN_FilterMaskIdLow;                //存储需要筛选ID或掩码
  7.                 uint16_t CAN_FilterFIFOAssignment;        //设置报文被存储到那一个接收FIFO,FIFO
  8. 0或FIFO1
  9.                 uint8_t CAN_FilterNumber;                        //设置筛选器编号,0~27
  10.                 uint8_t CAN_FilterMode;                        //设置筛选器工作模式,列表模式(CAN_FilterMode_IdList)和掩码模式(CAN_FilterMode_IdMask)
  11.                 uint8_t CAN_FilterScale;                        //设置筛选器位宽,CAN_FilterScale_32bit及CAN_FilterScale_64bit
  12. }
复制代码

4、 选择CAN中断类型,开启中断;
调用函数:void CAN_ITConfig(CAN_TypeDef* CANx, uint32_t CAN_IT, FunctionalState NewState);
5、 CAN发送和接收消息;
发送消息函数:uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage);
其中结构体变量CanTxMsg包含的成员变量为:

  1. typedef struct
  2. {
  3.                 uint32_t StdId;                //报文11位标准标识符,0~0x7FF
  4.                 uint32_t ExtId;                //报文29位扩展标识符,0~0x1FFFFFFF
  5.                 uint8_t IDE;                        //扩展标志位,CAN_ID_STD(标准帧)和CAN_ID_EXT(扩展帧)
  6.                 uint8_t RTR;                        //报文类型标志位,CAN_RTR_Data(数据帧)和CAN_RTR_Remote(遥控帧)
  7.                 uint8_t DLC;                        //数据帧的长度,0~8,当报文是遥控帧是DLC=0
  8.                 uint8_t Data[8];                //数据帧中数据段的数据
  9. }
复制代码

接收消息函数:void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage);
其中结构体变量CanRxMsg包含的成员变量为:

  1. typedef struct
  2. {
  3.                 uint32_t StdId;                //报文11位标准标识符,0~0x7FF
  4.                 uint32_t ExtId;                //报文29位扩展标识符,0~0x1FFFFFFF
  5.                 uint8_t IDE;                        //扩展标志位,CAN_ID_STD(标准帧)和CAN_ID_EXT(扩展帧)
  6.                 uint8_t RTR;                        //报文类型标志位,CAN_RTR_Data(数据帧)和CAN_RTR_Remote(遥控帧)
  7.                 uint8_t DLC;                        //数据帧的长度,0~8,当报文是遥控帧是DLC=0
  8.                 uint8_t Data[8];                //数据帧中数据段的数据
  9.                 uint8_t FMI;                        //存储筛选器编号,表示经过哪一个筛选器存储进接收FIFO的
  10. }
复制代码

6、 CAN状态的获取
调用函数:CAN_TransmitStatus(); CAN_MessagePending(); CAN_GetFlagStatus();

3.3 应用示例

STM32使用按键切换CAN1的通信模式(数据发送、接收),将数据通过串口打印出,详细的代码模块如下:

can.h


  1. #ifndef _can_H
  2. #define _can_H

  3. #include "system.h"
  4.                                                             

  5. #define CAN_RX0_INT_ENABLE 0                                                   //使能中断

  6. void CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode);        //CAN初始化

  7. u8 CAN_Send_Msg(u8* msg,u8 len);                                                        //发送数据

  8. u8 CAN_Receive_Msg(u8 *buf);                                                                //接收数据


  9. #endif
复制代码

can.c

  1. #include "can.h"
  2. #include "usart.h"


  3. void CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
  4. {
  5.         GPIO_InitTypeDef GPIO_InitStructure;
  6.         CAN_InitTypeDef        CAN_InitStructure;
  7.         CAN_FilterInitTypeDef  CAN_FilterInitStructure;
  8.         
  9. #if CAN_RX0_INT_ENABLE
  10.         NVIC_InitTypeDef                  NVIC_InitStructure;
  11. #endif
  12.         
  13.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
  14.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   
  15.         
  16.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;                 
  17.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;         
  18.         GPIO_Init(GPIOA, &GPIO_InitStructure);        

  19.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;                  
  20.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;         
  21.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  22.         GPIO_Init(GPIOA, &GPIO_InitStructure);
  23.         
  24.            CAN_InitStructure.CAN_TTCM=DISABLE;         
  25.           CAN_InitStructure.CAN_ABOM=DISABLE;         
  26.           CAN_InitStructure.CAN_AWUM=DISABLE;
  27.           CAN_InitStructure.CAN_NART=ENABLE;
  28.           CAN_InitStructure.CAN_RFLM=DISABLE;         
  29.           CAN_InitStructure.CAN_TXFP=DISABLE;         
  30.           CAN_InitStructure.CAN_Mode= mode;         
  31.           CAN_InitStructure.CAN_SJW=tsjw;        
  32.           CAN_InitStructure.CAN_BS1=tbs1;
  33.           CAN_InitStructure.CAN_BS2=tbs2;
  34.           CAN_InitStructure.CAN_Prescaler=brp;  
  35.           CAN_Init(CAN1, &CAN_InitStructure);  
  36.         
  37.         //ÅäÖùýÂËÆ÷
  38.          CAN_FilterInitStructure.CAN_FilterNumber=0;         
  39.           CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
  40.           CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;
  41.           CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;
  42.           CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
  43.           CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;
  44.           CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
  45.            CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;
  46.           CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;
  47.           CAN_FilterInit(&CAN_FilterInitStructure);
  48.         
  49. #if CAN_RX0_INT_ENABLE
  50.         CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);                           

  51.         NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
  52.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;   
  53.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;           
  54.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  55.         NVIC_Init(&NVIC_InitStructure);
  56. #endif
  57. }

  58. #if CAN_RX0_INT_ENABLE        
  59.                     
  60. void USB_LP_CAN1_RX0_IRQHandler(void)
  61. {
  62.           CanRxMsg RxMessage;
  63.         int i=0;
  64.     CAN_Receive(CAN1, 0, &RxMessage);
  65.         for(i=0;i<8;i++)
  66.         printf("rxbuf[%d]:%d\r\n",i,RxMessage.Data<i>);
  67. }
  68. #endif

  69. u8 CAN_Send_Msg(u8* msg,u8 len)
  70. {        
  71.         u8 mbox;
  72.         u16 i=0;
  73.         CanTxMsg TxMessage;
  74.         TxMessage.StdId=0x12;         
  75.         TxMessage.ExtId=0x12;         
  76.         TxMessage.IDE=0;               
  77.         TxMessage.RTR=0;               
  78.         TxMessage.DLC=len;                                                
  79.         for(i=0;i<len;i++)
  80.                 TxMessage.Data<i>=msg<i>;                                 
  81.         mbox= CAN_Transmit(CAN1, &TxMessage);   
  82.         i=0;
  83.         while((CAN_TransmitStatus(CAN1, mbox)==CAN_TxStatus_Failed)&&(i<0XFFF)) i++;
  84.         if(i>=0XFFF) return 1;
  85.         return 0;               
  86. }

  87. u8 CAN_Receive_Msg(u8 *buf)
  88. {                                      
  89.          u32 i;
  90.         CanRxMsg RxMessage;
  91.     if( CAN_MessagePending(CAN1,CAN_FIFO0)==0)return 0;         
  92.     CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
  93.     for(i=0;i<RxMessage.DLC;i++)
  94.     buf<i>=RxMessage.Data<i>;  
  95.         return RxMessage.DLC;        
  96. }</i></i></i></i></i>
复制代码

main.c

  1. #include "system.h"
  2. #include "SysTick.h"
  3. #include "led.h"
  4. #include "usart.h"
  5. #include "key.h"
  6. #include "can.h"

  7. int main()
  8. {
  9.         u8 i=0,j=0;
  10.         u8 key;
  11.         u8 mode=0;
  12.         u8 res;
  13.         u8 tbuf[8],char_buf[8];
  14.         u8 rbuf[8];

  15.         SysTick_Init(72);
  16.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  
  17.         LED_Init();
  18.         USART1_Init(9600);
  19.         KEY_Init();
  20.         CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_Normal);/
  21.         
  22.         while(1)
  23.         {
  24.                 key=KEY_Scan(0);
  25.                 if(key==KEY_UP)  
  26.                 {
  27.                         mode=!mode;
  28.                         CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,mode);
  29.                         if(mode==0)
  30.                         {
  31.                                 printf("Normal Mode\r\n");
  32.                         }
  33.                         else
  34.                         {
  35.                                 printf("LoopBack Mode\r\n");
  36.                         }
  37.         
  38.                 }
  39.                 if(key==KEY_DOWN)
  40.                 {
  41.                         for(j=0;j<8;j++)
  42.                         {
  43.                                 tbuf[j]=j;
  44.                                 char_buf[j]=tbuf[j]+0x30;
  45.                         }
  46.                         res=CAN_Send_Msg(tbuf,8);
  47.                         if(res)
  48.                         {
  49.                                 printf("Send Failed!\r\n");
  50.                         }
  51.                         else
  52.                         {
  53.                                 printf("发送数据:%s\r\n",char_buf);
  54.                         }
  55.                         
  56.                 }
  57.                 res=CAN_Receive_Msg(rbuf);
  58.                 if(res)
  59.                 {
  60.                         for(j=0;j<res;j++)
  61.                         {
  62.                                 char_buf[j]=rbuf[j]+0x30;
  63.                         }
  64.                         printf("接收数据:%s\r\n",char_buf);
  65.                 }
  66.                
  67.                 i++;
  68.                 if (i%20==0)
  69.                 {
  70.                         led1=!led1;
  71.                 }
  72.                
  73.                 delay_ms(10);
  74.         }
  75. }
复制代码





收藏 评论0 发布时间:2022-4-8 21:45

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版