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

基于STM32F1的CAN通信之串口通信

[复制链接]
攻城狮Melo 发布时间:2023-10-23 16:56
一、什么是串口通信
串口通信是指外部设备与主控芯片之间,通过数据信号线、地线等,按位进行数据传输的一种通信方式,属于串行通信方式。串行通信是指使用一条数据线依次逐位传输数据,每一位数据占据固定长度的时间。可以看一下简单的串行通信示意图。


微信图片_20231023165606.png

串口通信示意图


二、串口通信有什么用

这里简单列举一下串口通信的用途
• 下载程序
• 外设与单片机通信 单片机给外设发送一些指令或者配置信息,外设给单片机回传一些信息。
• 打印信息 比如将ADC采集到的电压发送给上位机的串口调试助手,或者实时监测某一个变量的变化。

三、STM32的串口通信

普中核心板上使用的STM32F103ZET6有三个USART,两个UART,他们都支持串口通信功能。USART(通用同步异步收发器)与UART(通用异步收发器)相比,多了一个同步功能,可以认为USART是UART的增强型。


四、串口通信相关概念
44.1 波特率
引用专业的说法,波特率表示单位时间内传送的码元符号的个数,它是对符号传输速率的一种度量。其实意思就是波特率表示1s内传输码元的个数。在单片机中数字都是二进制的01表示的,所以波特率可以说是1s内传输01的个数。常见的波特率有38400、9600和115200等。

波特率通常由波特率发生器产生,串口要想实现收发首先要有波特率发生器,网上介绍波特率发生器的作用是输入时钟转换出需要的波特率CLK。个人理解,波特率发生器就是提供一个时钟,这样才能发送出正确波特率的信息,比如1和0需要多久的高/低电平表示。

在串口通信时如果收发双方波特率不相同会导致通信失败,要么是接收不到,要么是接收到的是乱码。

4.2 全双工和半双工
• 全双工可以简单解释为,我在接收消息的同时,你也可以发送消息。
• 半双工可以简单解释为,我在接收消息时,没办法发送消息。类似于对讲机,你说话时占用了信道,对方无法跟你讲话,只有当你说完了,他才可以对你讲话。

4.3 同步通信和异步通信
同步通信和异步通信的区别在于通信双方是否需要时钟同步。同步通信的接收双方之间除了需要数据线之外,还需要一根时钟线,而异步通信不需要。关于二者的详细定义与区别,请大家自行搜索。

五、硬件连接
串口通信只需几条线即可在两个系统间交换信息,特别适用于计算机与计算机、计算机与外设之间的通信,常用的串口通信接口标准有很多,比如RS-232C、RS-232、RS-485等。但是放在单片机开发里,最简单的串口通信就是用四根线VCC、GND、TXD和RXD实现通信。

微信图片_20231023165602.png

串口通信硬件连接示意图



普中核心板上常用的是USART1,其引脚对应如下

• TXD——PA9
• RXD——PA10


六、串口通信程序配置

下面以配置USART1为例,来简单展示一下USART的配置方法。

66.1 使能串口时钟和GPIO时钟
  1. // 使能USART1,GPIOA时钟
  2. RCC_APB2PeriphClockCmd (RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
复制代码


6.2 初始化GPIO
初始化USART1用到的GPIO。TXD引脚设置为复用推挽式输出,RXD引脚设置为输入浮空。
  1. // USART1_TX   GPIOA.9
  2.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;   // PA.9
  3.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  4.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   // 复用推挽输出
  5.     GPIO_Init(GPIOA, &GPIO_InitStructure);   // 初始化GPIOA.9

  6.     // USART1_RX   GPIOA.10初始化
  7.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;   // PA10
  8.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;   // 输入浮空
  9.     GPIO_Init(GPIOA, &GPIO_InitStructure);   // 初始化GPIOA.10  
复制代码

6.3 初始化串口参数
库函数提供了一个结构体,用于初始化串口。其中包括
  1. USART_InitTypeDef USART_InitStructure;
  2.    
  3.     // USART 初始化设置
  4.     USART_InitStructure.USART_BaudRate = bound;   // 串口波特率
  5.     USART_InitStructure.USART_WordLength = USART_WordLength_8b;   // 字长为8位数据格式
  6.     USART_InitStructure.USART_StopBits = USART_StopBits_1;   // 一个停止位
  7.     USART_InitStructure.USART_Parity = USART_Parity_No;   // 无奇偶校验位
  8.     // 无硬件数据流控制
  9.     USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  10.     USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;   // 收发模式
  11.     USART_Init(USART1, &USART_InitStructure);   // 初始化串口1
复制代码

6.4 使能串口
  1. USART_Cmd(USART1, ENABLE);   // 使能串口1
复制代码


6.5 串口接收中断
平时开发过程中经常需要开启串口接收中断,配置串口接收中断的方法与上一篇的外部中断有些类似,主要包括以下步骤
• 配置中断分组(通常在main函数中初始化中配置)
• 设置中断优先级
• 使能中断

配置中断优先级
  1. NVIC_InitTypeDef NVIC_InitStructure;

  2.     // Usart1 NVIC 配置
  3.     NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
  4.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;   // 抢占优先级3
  5.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;   // 子优先级3
  6.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   // IRQ通道使能
  7.     NVIC_Init(&NVIC_InitStructure);   // 根据指定的参数初始化VIC寄存器
复制代码

使能串口接收中断
  1. USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);   // 开启串口接收中断
复制代码

6.6 串口接收中断服务函数
通常接收到的数据会是一帧,很少是一个单独的字符,这里给出一个接收一帧数据的串口中断服务函数。需要注意的是,在初始化串口时,需要使能空闲中断。

使能空闲中断的程序如下
  1. USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);   // 使能空闲中断
复制代码
  1. /*
  2. *==============================================================================
  3. *函数名称:USART1_IRQHandler
  4. *函数功能:USART1中断服务函数
  5. *输入参数:无
  6. *返回值:无
  7. *备  注:无
  8. *==============================================================================
  9. */
  10. u32 gReceCount = 0;   // 接收计数变量
  11. u32 gClearCount = 0;   // 清空接收数组计数变量
  12. u8 gReceFifo[1500];   // 接收数组
  13. u8 gReceEndFlag = 0;   // 接收完成标志位

  14. void USART1_IRQHandler(void)  
  15. {
  16.     if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)   //接收到一个字节  
  17.     {
  18.         gReceFifo[gReceCount++] = USART_ReceiveData(USART1);
  19.     }
  20.     else if(USART_GetITStatus(USART1,USART_IT_IDLE) != RESET)   //接收到一帧数据
  21.     {
  22.         USART1->SR;   // 先读SR
  23.         USART1->DR;   // 再读DR
  24.         
  25.         gReceEndFlag = 1;   // 接收完成标志置1
  26.     }
  27. }
复制代码

接收完成后,接收完成标志位会置1。此时,对接收到的帧进行解析处理。解析完成后需要清除接收数组,同时,不要忘记清除接收完成标志位。
  1. /*
  2. *==============================================================================
  3. *函数名称:Uart_Rece_Pares
  4. *函数功能:解析串口接收内容
  5. *输入参数:无
  6. *返回值:无
  7. *备  注:无
  8. *==============================================================================
  9. */
  10. void Uart_Rece_Pares(void)   // 串口接收内容解析函数
  11. {
  12.     if (gReceEndFlag  == 1)   // 如果接收完成
  13.     {
  14.         // 解析接收内容
  15.         
  16.         // 清空接收数组
  17.         for (gClearCount = 0;gClearCount < gReceCount;gClearCount ++)
  18.         {
  19.             gReceFifo[gClearCount] = ' ';
  20.         }
  21.             
  22.         gReceEndFlag = 0;   // 清除接收完成标志位
  23.         gReceCount = 0;   // 清零接收计数变量
  24.     }
  25. }
复制代码

6.7 串口发送函数
  1. //串口发送函数
  2. void USART1_Send(u8*str)
  3. {
  4.     u8 index=0;
  5.     do
  6.     {
  7.         USART_SendData(USART1,str[index++]);
  8.         while(USART1,str[index++]);
  9.         while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
  10.     }
  11.     while(str[index]!=0);
  12. }
复制代码


其实这里最根本的USART_SendData()本质就是将数据搬运到串口发送的寄存器。当然除了直接用发送函数发送,也可以直接重定向之后用printf发送,这里就不详细介绍了,有需要的友友可以直接去看普中或者正点的教程视频。


七、拓展
7.1 printf重定向

关于重定向的概念这里就不再做介绍了,重定向之后就可以在程序中使用printf直接打印或者发送字符串,不再需要串口发送函数。重定向的方法就是在串口的.c文件中添加下面的程序
  1. // 加入以下函数可以使用printf
  2. #pragma import(__use_no_semihosting)            
  3. // 标准库需要的支持函数                 
  4. struct __FILE
  5. {
  6.     int handle;
  7. };

  8. FILE __stdout;      
  9. //定义_sys_exit()以避免使用半主机模式   
  10. void _sys_exit(int x)
  11. {
  12.     x = x;
  13. }
  14. //重定义fputc函数
  15. int fputc(int ch, FILE *f)
  16. {      
  17.     while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
  18.     USART1->DR = (u8) ch;      
  19.     return ch;
  20. }
复制代码

7.2 接收帧解析
这里的接收帧解析比较简单,比如有些项目要求接收到某些特定字符执行某些操作。这时需要根据接收帧的长度和固定位置的字符来解析命令。

比如项目要求上位机(电脑)发送“BEEP ON”时,蜂鸣器响。这时在解析时只要接收到长度为6,第5和第6个字符分别为“O”,“N”时,开启蜂鸣器即可。
  1. // 解析接收内容
  2. if (gReceCount == 6 && gReceFifo[5] == 'O' && gReceFifo[6] == 'N')
  3. {
  4.    // 开启蜂鸣器
  5. }
复制代码

当然上面的只是粗略的卡命令,也可以写的更详细。

八、实战项目
8.1 前期准备

• CH340驱动
• USB转TTL,用于单片机与电脑的通信
• 串口调试助手

刚买来的普中核心板,不拔短接片的话可以直接通过USB下载程序,或者与电脑进行串口通信,串口为USART1。注意一定要插图中标出来的USB接口,另一个只能用来供电。

微信图片_20231023165410.png

串口通信跳线帽连接



8.2 项目要求
• 单片机上电发送“Sys Ready!”
• 电脑串口助手发送“LED1 ON”(带回车换行),LED1点亮,同时单片机回复“OK!”
• 电脑串口助手发送“LED1 OFF”(带回车换行),LED2熄灭,同时单片机回复“OK!”

88.3 串口程序
  8.3.1 初始化串口

首先是串口初始化程序,需要开启接收中断和空闲中断。
  1. /*
  2. *==============================================================================
  3. *函数名称:uart_init
  4. *函数功能:初始化USART1
  5. *输入参数:bound:波特率
  6. *返回值:无
  7. *备  注:可以修改成输入初始化哪个USART
  8. *==============================================================================
  9. */
  10. void uart_init(u32 bound)
  11. {
  12.     // 相关结构体定义
  13.     GPIO_InitTypeDef GPIO_InitStructure;
  14.     USART_InitTypeDef USART_InitStructure;
  15.     NVIC_InitTypeDef NVIC_InitStructure;

  16.     // 使能USART1,GPIOA时钟
  17.     RCC_APB2PeriphClockCmd (RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

  18.     // USART1_TX   GPIOA.9
  19.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;   // PA.9
  20.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  21.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   // 复用推挽输出
  22.     GPIO_Init(GPIOA, &GPIO_InitStructure);   // 初始化GPIOA.9

  23.     // USART1_RX   GPIOA.10初始化
  24.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;   // PA10
  25.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;   // 浮空输入
  26.     GPIO_Init(GPIOA, &GPIO_InitStructure);   // 初始化GPIOA.10  

  27.     // Usart1 NVIC 配置
  28.     NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
  29.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;   // 抢占优先级3
  30.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;   // 子优先级3
  31.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   // IRQ通道使能
  32.     NVIC_Init(&NVIC_InitStructure);   // 根据指定的参数初始化VIC寄存器

  33.     // USART 初始化设置
  34.     USART_InitStructure.USART_BaudRate = bound;   // 串口波特率
  35.     USART_InitStructure.USART_WordLength = USART_WordLength_8b;   // 字长为8位数据格式
  36.     USART_InitStructure.USART_StopBits = USART_StopBits_1;   // 一个停止位
  37.     USART_InitStructure.USART_Parity = USART_Parity_No;   // 无奇偶校验位
  38.     // 无硬件数据流控制
  39.     USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  40.     USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;   // 收发模式
  41.     USART_Init(USART1, &USART_InitStructure);   // 初始化串口1
  42.   
  43.   USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);   // 开启串口接收中断
  44.     USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);   // 使能空闲中断
  45.    
  46.   USART_Cmd(USART1, ENABLE);   // 使能串口1
  47. }
复制代码

其次需要加上重定向函数,直接复制上面的即可。

8.3.2 串口接收中断服务函数
  1. /*
  2. *==============================================================================
  3. *函数名称:USART1_IRQHandler
  4. *函数功能:USART1中断服务函数
  5. *输入参数:无
  6. *返回值:无
  7. *备  注:无
  8. *==============================================================================
  9. */
  10. u32 gReceCount = 0;   // 接收计数变量
  11. u32 gClearCount = 0;   // 清空接收数组计数变量
  12. u8 gReceFifo[1500];   // 接收数组
  13. u8 gReceEndFlag = 0;   // 接收完成标志位

  14. void USART1_IRQHandler(void)  
  15. {
  16.     if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)   //接收到一个字节  
  17.     {
  18.         gReceFifo[gReceCount++] = USART_ReceiveData(USART1);
  19.     }
  20.     else if(USART_GetITStatus(USART1,USART_IT_IDLE) != RESET)   //接收到一帧数据
  21.     {
  22.         USART1->SR;   // 先读SR
  23.         USART1->DR;   // 再读DR
  24.         
  25.         gReceEndFlag = 1;   // 接收完成标志置1
  26.     }
  27. }
复制代码


8.3.3 接收帧解析函数
  1. /*
  2. *==============================================================================
  3. *函数名称:Uart_Rece_Pares
  4. *函数功能:解析串口接收内容
  5. *输入参数:无
  6. *返回值:无
  7. *备  注:无
  8. *==============================================================================
  9. */
  10. void Uart_Rece_Pares(void)   // 串口接收内容解析函数
  11. {
  12.     if (gReceEndFlag  == 1)   // 如果接收完成
  13.     {
  14.         // 解析接收内容
  15.         if (gReceFifo[6] == 'N')
  16.         {
  17.             Med_Led_StateCtrl (LED1,LED_ON);   // 点亮LED1
  18.             printf ("OK!\r\n");
  19.         }
  20.         
  21.         if (gReceFifo[6] == 'F' && gReceFifo[7] == 'F')
  22.         {
  23.             Med_Led_StateCtrl (LED1,LED_OFF);   // 熄灭LED1
  24.             printf ("OK!\r\n");
  25.         }
  26.         
  27.         // 清空接收数组
  28.         for (gClearCount = 0;gClearCount < gReceCount;gClearCount ++)
  29.         {
  30.             gReceFifo[gClearCount] = ' ';
  31.         }
  32.             
  33.         gReceEndFlag = 0;   // 清除接收完成标志位
  34.         gReceCount = 0;   // 清零接收计数变量
  35.     }
  36. }
复制代码

8.3.3 main函数
  1. int main(void)
  2. {
  3.     Med_Mcu_Iint();   // 系统初始化
  4.     printf ("Sys Ready!\r\n");
  5.    
  6.     while(1)
  7.   {
  8.         Uart_Rece_Pares ();   // 接收帧解析
  9.     }
  10. }
复制代码


转载自: 二土电子
如有侵权请联系删除


收藏 评论0 发布时间:2023-10-23 16:56

举报

0个回答

所属标签

相似分享

官网相关资源

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