STM32F407+CubeMx串口通信实验(学习记录)

news2024/11/13 9:28:38

一、环境

硬件:STM32F407ZGT6开发板
软件:STM32CubeMx、Keil5 MDK、串口调试助手

PS:前面实验部分的代码都是可以正常运行的,但是在学习过程中我也踩了很多坑(包括一些未弄明白的),我会记录在问题总结部分。

二、使用STM32CubeMx初始化配置

1.选择芯片型号

首先选择自己使用的板子对应的芯片型号,我这里使用的是STM32F407ZGT6的芯片。

2.配置SYS

点击System Core下拉栏的SYS,选择Debug调试,选择serial wire。

3.配置RCC

配置RCC时钟,由于这里使用的是外部高速晶振,点击HSE,选择Crystal/Ceramic Resonator。

4.配置时钟树

这里有几个需要特别关注的点:

第一,HSE频率要对应开发板的晶振频率,我使用的板子晶振频率为8MHz,如果这设置的频率不匹配,那么串口输出时可能会出现乱码;

第二,选择HSE时钟;

第三,选择锁相环;

第四,HCLK频率设置为最高的168MHz。

5.配置USART

点击Connectivity,选择USART1,模式选择为异步通信即Asynchronous,并且使能串口中断。

点击Configuration选项卡,可以看到波特率被自动配置为115200。

6.生成工程代码

三、串口通信实验

1.阻塞式发送

利用HAL_UART_Transmit()函数发送。

首先在main函数中定义一个需要发送的字符串:

uint8_t Transmit_data[]={"Hello CHINA!\r\n"};      //定义一个需要发送的字符串

然后在while语句中写两行代码:

HAL_UART_Transmit(&huart1,Transmit_data,sizeof(Transmit_data),10);  //发送字符串
HAL_Delay(500);                                                     //延时500ms

代码编译成功之后,点击下载到单片机,打开串口调试助手,连接串口,可以看到串口在不停地打印字符串。

注意这里的波特率要和初始化配置的一致,否则会出现乱码。

2.重定向printf打印

通过对printf重定向,使用printf 可以用来串口打印数据。

首先在main.c文件中添加头文件"stdio.h"

然后在main.c文件中重定义fputc函数

//重定义fputc函数
int fputc(int ch,FILE *f)
{
    HAL_UART_Transmit(&huart1,(uint8_t*)&ch,1,100);
    return ch;
}

接着在while语句中调用printf函数输出字符串。

printf("Hello everyone!\r\n");

为了避免使用半主机模式,可以先点击魔术棒,再点击Target选项,勾选Use Micro LIB。

如果这时出现编译报错,那么打开启动文件,注释掉第60行和第407行,编译一次后再取消注释,然后重新编译后就可以了。(不同的芯片可能这两行代码不一定是在第60行和第407行,注意内容正确。)

具体为什么这样操作一下就可以了,我也没太搞懂,如果有清楚的大佬,请在评论区留下您的宝贵回答。

然后将代码下载到开发板上,就可以输出字符串了。

3.阻塞式接收

利用HAL_UART_Receive()函数接收。

首先定义一个数组buffer,用来作字符接收缓冲区。

然后在while语句中调用HAL_UART_Receive()函数,接收串口发来的数据。

再然后在后面调用HAL_UART_Transmit()函数,将接收的数据发送到串口调试助手里面。

HAL_UART_Receive(&huart1,buffer,6,0xFFFF);                            //接收字符串
HAL_UART_Transmit(&huart1,buffer,6,0xFFFF);                           //发送字符串                                  

这里特别注意两个点:

第一,超时时间设置的是最大值0xFFFF。因为这样可以阻塞住程序的执行,直到整个字符串传输完成。

第二,传输字符串长度这里我是相较于定义buffer字符串长度长1个单位的。

因为在接收数据时,每次接收完成后,接收数据寄存器(RDR)还保留着上一次循环发送的最后一个字符。

此处的字长+1后,每次循环结束留在接收数据寄存器(RDR)中的最后一个字符就是空,就不会出现接收的最后一个字符移动到第一个字符位置这种现象。

不加1发送第一次是正确的,后面就会丢失数据;加1发送接收正确,但是第二行往往是空行。

因为在C语言中,数组默认是以'\0'结尾的,只有这样才能输出正确且自动换行。(此解释存疑)

将代码编译后下载到开发板上,在串口助手的发送区输入字符,点击发送,可以看到接收区会显示Hello,证明接收成功,多发送几个字符,查看接收数据有无丢失。

这里仍然存在一点问题:第二行是空行。(未解决)

4.串口发送指令控制LED灯

首先定义一个长度为2个字节的数组用来发送指令。

然后在while语句中编写代码。若输入'R1',则红色LED灯亮起;若输入'G1',则绿色LED灯亮起。

printf("Input your order:\r\n");
HAL_UART_Receive(&huart1,receive_data,3,0xFFFF);                        //接收字符串
printf("Your order is:\r\n");
HAL_UART_Transmit(&huart1,receive_data,3,100);                          //发送字符串
GPIO_PinState state = GPIO_PIN_SET;
if(receive_data[1]=='1')
{
    state = GPIO_PIN_RESET;
}
if(receive_data[0]=='R')
{
    HAL_GPIO_WritePin(GPIOF, LED_RED_Pin, state);
}
else if(receive_data[0]=='G')
{
    HAL_GPIO_WritePin(GPIOF, LED_GREEN_Pin, state);
}
HAL_Delay(1000);                                                        //延时1000ms

代码编译下载后运行,可以实现预期效果。

存在的问题:

9.6由于输出的字符串,首次是正确的,可以正确开启LED灯,但是后面就会乱套(未解决)

9.7发送接收命令过了一夜没问题了,但是要过5分钟左右才能执行下一次命令。

     分析:可能是阻塞式发送的原因,

循环未执行完成(通过在循环末尾添加关闭LED的方式排除该可能)

或者数据发送流程未结束

5.中断式发送和接收

阻塞式发送和接收非常浪费CPU资源,必须阻塞住程序的执行,直到完成发送或接收、或者等待超时。另外在接收时,只能接收固定长度的数据。中断式发送和接收可以解决CPU资源浪费的问题。

首先中断式发送和接收使用的函数分别是HAL_UART_Transmit_IT()函数和HAL_UART_Receive_IT()函数。

相比于阻塞式发送和接收的HAL_UART_Transmit()函数和HAL_UART_Receive()函数,这两个函数的参数都少了一个超时时间。

HAL_UART_Transmit_IT(&huart1,receive_data,2);               //中断发送字符串
HAL_UART_Receive_IT(&huart1,receive_data,2);                //中断接收字符串

由于中断接收数据不会堵塞程序的运行,那么还没有等到接收到数据,程序就会向下继续执行,这样就会导致,当程序执行到下次循环时,可能上次的数据还没有接收完成,便又开启串口中断接收。为了避免冲突,将中断接收函数放置在while循环外只执行一次。

中断模式的HAL_UART_Receive_IT()函数不会堵塞,在开启中断接收后,程序就继续向下执行了,这时不能直接对数据进行分析,因为数据很可能还没有接收完成。

使用中断处理函数可以在数据接收完成后再对数据进行分析处理。

在stm32f4xx.it.c文件中找到串口的中断处理函数。

然后在HAL_UART_IRQHandler()函数中找到接收完成中断回调函数HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart)。

由于在这里是弱定义的,所以在main.c文件中重新定义该函数,并将主要的执行语句放入该函数体内。特别注意,由于中断接收函数 HAL_UART_Receive_IT()只执行了一次,为了能够继续输入命令控制LED,所以在函数体末尾再执行一次中断接收函数 HAL_UART_Receive_IT()开启接收中断。

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    HAL_UART_Transmit_IT(&huart1,receive_data,2);               //中断发送字符串
    GPIO_PinState state = GPIO_PIN_SET;
    if(receive_data[1]=='1')
    {
        state = GPIO_PIN_RESET;
    }
    if(receive_data[0]=='R')
    {
        HAL_GPIO_WritePin(GPIOF, LED_RED_Pin, state);
    }else if(receive_data[0]=='G')
    {
        HAL_GPIO_WritePin(GPIOF, LED_GREEN_Pin, state);
    }
    HAL_UART_Receive_IT(&huart1,receive_data,2);                //中断接收字符串
}

然后将receive_data定义为全局变量。

代码编译下载后运行,可以实现用串口控制LED灯的预期效果,效果不错。

提示:这里的字长都是和定义的数组长度是一致的,输出也不会出错,跟之前的解释有些出入,暂时还没搞懂原因。

9.7如果收发2个字节的字符串,不会出错。3个字节及以上的就会出错。

3个字符的字符串是先输出第1个字符,再换行输出前2个字符,接着输出所有的字符。

4个字节的字符串是先输出前2个字符,又输出所有的字符,又换行输出2个字符。

6.串口DMA模式

DMA(Direct Memory Access),直接内存访问。DMA用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。可以在无需任何 CPU 操作的情况下通过 DMA 快速移动数据。这样能节省CPU资源。

首先在STM32CubeMX中选择Connectivity-USART1-DMA Settings中添加2个DMA通道。

使用HAL_UART_Transmit_DMA()和HAL_UART_Receive_DMA()函数来收发数据。

将原本的HAL_UART_Transmit_IT()和HAL_UART_Receive_IT()函数替换成HAL_UART_Transmit_DMA()和HAL_UART_Receive_DMA()函数。

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    HAL_UART_Transmit_DMA(&huart1,receive_data,2);               //中断发送字符串
    GPIO_PinState state = GPIO_PIN_SET;
    if(receive_data[1]=='1')
    {
        state = GPIO_PIN_RESET;
    }
    if(receive_data[0]=='R')
    {
        HAL_GPIO_WritePin(GPIOF, LED_RED_Pin, state);
    }else if(receive_data[0]=='G')
    {
        HAL_GPIO_WritePin(GPIOF, LED_GREEN_Pin, state);
    }
    HAL_UART_Receive_DMA(&huart1,receive_data,2);                //中断接收字符串
}

代码编译下载后运行,可以实现用串口控制LED灯。

老问题:2个字节的字符串输出没问题,3个字节及以上的字符串输出会出错,原因不明。

7.收发不定长数据

利用串口空闲中断可以实现收发不定长数据。

使用HAL库的一个扩展函数HAL_UARTEx_ReceiveToIdle_DMA()来接收数据。

首先给receive_data数组一个较大的长度。

然后将HAL_UART_Receive_DMA()函数替换为HAL_UARTEx_ReceiveToIdle_DMA()函数。

这个函数对应的中断回调函数是HAL_UARTEx_RxEventCallback(),这个回调函数同样是弱定义的,在main.c文件中进行定义。

在函数体内部首先判断中断触发源,再使用DMA发送数据,然后使用上述扩展函数进行发送。

特别提示:阻塞模式和中断模式的ReceiveToIdle函数没什么特别注意的点,但是对于DMA模式的ReceiveToIdle函数来说,除了串口空闲中断以外,DMA的“传输过半中断”也会触发RxEventCallback回调函数,若接收的数据长度达到设置的receive_data数组的一般时,也会触发一次RxEventCallback中断回调。

在本实验中,此功能无甚用处,所以将DMA的“传输过半中断”进行关闭。

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if(huart == &huart1)                                 //判断中断触发源
    {
        HAL_UART_Transmit_DMA(&huart1,receive_data,Size);     //发送字符串
        HAL_UARTEx_ReceiveToIdle_DMA(&huart1,receive_data,sizeof(receive_data));//接收字符串
        __HAL_DMA_DISABLE_IT(&hdma_usart1_rx,DMA_IT_HT);      //关闭传输过半中断
    }
}

在主函数中只运行一次的接收字符串函数下也得关闭传输过半中断。

HAL_UARTEx_ReceiveToIdle_DMA(&huart1,receive_data,sizeof(receive_data));  //接收字符串
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx,DMA_IT_HT);                          //关闭传输过半中断

编译时,会发现hdma_usart1_rx未定义,这是因为这个变量定义在其他文件中,所以需要在main.h文件中进行声明。

extern DMA_HandleTypeDef hdma_usart1_rx;

编译下载后运行,就可以发送和接收不定长的数据了。

四、遇到的问题总结

1.串口乱码问题(已解决)

第一,上位机和板子端的波特率不一致。这里的上位机指的是串口调试助手。

第二,注意CubeMx中HSE的值务必要和自己板子上实际晶振大小一致。由于CubeMx默认F407使用的晶振频率是25MHz,所以我在一开始的时候也是输出乱码,后面改为8MHz,成功解决乱码问题。

2.字符串接收问题(未解决)

这是原本的阻塞式接收的代码,这里存在两个问题,下面会详细说明。

第一,超时时间设置太短。

超时时间设置为100ms,这样只能传输第一个字符,而后续的字符会丢失,并且这个字符串首字符会持续不断地传输,因为接收完第一个字符已经达到超时时间,这时就会将字符发送到上位机(这里指的是PC端的串口调试助手),但是由于字符串还没有传输完成,所以又会进行下一次循环,一直持续下去。

解决办法:将超时时间更改为0xFFFF,但仍然存在问题。

第二,传输数据的寄存器会保留上一次发送的最后一个字符。

这里上一次发送的最后一个字符是空格,就会如下图发送一次,接收到上一次的最后一位加这一次的前4位。

解决办法:将传输字符串长度相较于定义长度加1。

最后就可以正常输出,结果如实验部分--阻塞式接收的结果。

3.串口2和3用不了(未解决)

9.6又试了一次,同样的配置,串口3好像就是用不了。

怀疑是串口被烧了,后面找个万用表测一下。

目前存在的问题总结:

1.字符串长度要+1,不然输出混乱,但是+1后,串口助手接收到的数据第二行是空行(阻塞式收发数据)

2.LED灯控制,响应第一个命令后,5分钟左右才能响应第二个命令。(阻塞式收发数据控制LED灯)

3.两个字节数据收发正常,三个字节及以上的字符串输出就会出错(普通中断模式和DMA中断模式)

4.串口2、3无法使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2118534.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

物联网之ESP32控制GPIO输出点亮LED、闪烁LED灯

MENU 前言原理GPIO引脚LED 硬件电路设计软件设计1、点亮一颗LED2、闪烁的LED 前言 不论学习什么单片机,最简单的外设莫过于IO口的高低电平控制LED,本文介绍如何使用Arduino控制ESP32的GPIO输出。通过本文的学习,掌握一定的Arduino程序架构知识…

求求你们别再跟风考PMP了!这几类人才真正需要这本证书!

救命啊!!!这几天刷某书,看到好多人在吐槽说考了PMP证书一点用都没有,每三年还得花个千把块钱去续证,劝大家都别考。 这可能是PMP身上最大的一口“黑锅”了。作为全球认可、含金量极高的项目管理资格认证&am…

AXI4主机测试

前面对AXI4协议进行了比较详细的分析,本篇文章将会写一个主机代码来实现AXI4协议的时序。 设计思路:本次设计的主要目的是验证AXI4_FULL总线的时序,并且提升对AXI4_FULL总线协议的理解,因此可以采用状态机来控制,先向…

孩子用的台灯哪个牌子好?挑选护眼台灯先了解护眼台灯十大排名

孩子们的日常生活中有高达80%的时间是在阅读、做作业或面对电脑屏幕中度过的,因此对良好照明的需求显得尤为严格和精确。一些家长可能认为,只要孩子使用的是纸质材料,不像电子产品那样对眼睛有害,使用普通的台灯照明就足够了&…

mysql快速定位cpu 占比过高的sql语句

mysql快速定位cpu 占比过高的sql语句 当MySQL数据库的CPU使用率异常升高时,定位导致问题的SQL语句可以通过以下步骤进行 1、使用top命令找出mysl进程中占用CPU靠前的线程 #找出mysql 的进程号 ps -ef | grep mysql#根据进程号,找出占用CPU靠前的线程号…

【QT】文件读写,文件对话框

一.QFile类 QFile提供了从文件中读取和写入数据的能力。 我们通常会将文件路径作为参数传给QFile的构造函数。不过也可以在创建好对象最后,使用setFileName()来修改。 QFile是QIODevice的子类,利用QFile可以对文件进行打开,读取&#xff0c…

Bootstrap 5.3版本创建常用页面

你可以根据自己的主题需求来自定义样式。Bootstrap提供了丰富的CSS类和组件,使得创建响应式、移动优先的网站变得简单。下面我将指导你如何基于Bootstrap 5.3来编写一个简单但自定义主题的页面。 引入Bootstrap 首先,确保你的HTML文件中已经正确引入了B…

Vue学习:v-model绑定文本框、单选按钮、下拉菜单、复选框等

v-model指令可以在组件上使用以实现双向绑定&#xff0c;之前学习过v-model绑定文本框和下拉菜单&#xff0c;今天把表单的几个控件单选按钮radio、复选框checkbox、多行文本框textarea都试着绑定了一下。 一、单行文本框和多行文本框 <p>1.单行文本框</p> 用户名…

程序员日志之DNF编年史

目录 传送门正文日志1、概要2、超高度总结概括3、详细编年史3.1、大背景3.2、冒险家 传送门 SpringMVC的源码解析&#xff08;精品&#xff09; Spring6的源码解析&#xff08;精品&#xff09; SpringBoot3框架&#xff08;精品&#xff09; MyBatis框架&#xff08;精品&…

YOLOv5改进 | 模块缝合 | C3 融合RFCAConv增强感受野空间特征 【二次融合 小白必备】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 专栏目录 &#xff1a;《YOLOv5入门 改…

飞牛fnOS安装KDE桌面

飞牛fnOS安装KDE桌面 这段时间新出的nas系统飞牛os真不错&#xff0c;基于debian的可折腾性又高了不少&#xff0c;今天就来给这个系统装个桌面&#xff0c;插上显示器也能当个电脑自己进自己的管理界面&#xff0c;播放下视频&#xff0c;上上网啥的。 文章目录 飞牛fnOS安装…

问卷调查,动静IP应该如何选择?

在探讨问卷调查这一领域时&#xff0c;选择使用动态IP还是静态IP&#xff0c;成为了许多从业者及市场研究者面临的重要决策&#xff0c;它不仅关乎数据收集的效率与质量&#xff0c;还直接影响到问卷调查的合法性与安全性。本文将从多个维度深入分析这两种IP类型的优劣&#xf…

python-网页自动化(三)

如果遇到使用 ajax 加载的网页&#xff0c;页面元素可能不是同时加载出来的&#xff0c;这个时候尝试在 get 方法执行完 成时获取网页源代码可能并非浏览器完全加载完成的页面。所以&#xff0c;这种情况下需要设置延时等待一定时间&#xff0c;确保全部节点都加载出来。 那么&…

每日一练8:dd爱框框(含链接)

1.链接 登录—专业IT笔试面试备考平台_牛客网 2.题目 3.代码 #include<iostream> #include<vector>using namespace std;const int N 1e7 5;int n,x;vector<int> v(N);int main() {cin >> n >> x;for(int i 0; i < n;i) cin >> v…

服务端性能测试:行业流行性能监控工具介绍

行业流行性能监控工具有哪些 Linux 自带命令 Vmstat&#xff0c;Top 等 Nmon Collectd InfluxDB Grafana Prometheus Grafana 行业流行性能监控工具的介绍 Linux 自带命令 Vmstat&#xff0c;Top 等 vmstat 和 top 都是 Linux 系统自带的命令&#xff0c;提供了实时的…

每日一练:轮转数组

一、题目要求 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: …

hive时间函数

一、随机示例&#xff08;想到哪里写哪里&#xff09; 1.系统时间函数 查询 select current_timestamp --当前格式化时间,current_date --当前格式化日期,unix_timestamp() --当前unix时间戳 结果&#xff1a; 2.时间函数转换 查询 --将时间戳转化为格式化时间 sel…

微片水凝胶如何用于4D生物打印?快来了解一下!

大家好&#xff0c;今天我们来聊聊一项4D 活细胞生物打印技术——《Jammed Micro-Flake Hydrogel for 4D Living Cell Bioprinting》发表于《Advanced Materials》。在组织器官再生应用中&#xff0c;构建具有复杂几何形状和功能的载细胞结构至关重要。而水凝胶基4D生物墨水的发…

免费 U 盘数据恢复 - 用 4 种免费方法从随身U 盘恢复文件

如何在不使用软件的情况下从 USB 驱动器恢复已删除的文件&#xff1f;如何取消删除 USB 闪存驱动器&#xff1f;首先&#xff0c;不要对拇指驱动器进行任何进一步的更改。然后下载奇客数据恢复&#xff0c;这是一款免费的U 盘恢复工具&#xff0c;能够从各种问题中恢复笔式驱动…

网络层协议-ARP协议

网络层协议-ARP协议 1&#xff09;概述 ARP&#xff1a;地址解析协议&#xff0c;作用&#xff1a;根据IP地址查询MAC地址 数据包发送前需要进行封包&#xff0c;在数据链路层需要封装源mac地址是自己的mac&#xff0c;目的mac地址是别人&#xff0c;但是不知道别人的mac地址…