做计算机网站有哪些,wordpress 默认模版,app网站欣赏,东莞网站设计效果#xff08; 本篇正在编写、更新状态中.....) 文章目录#xff1a;
前言 前言 本篇#xff0c;详细地用截图解释 CubeMX 对 USART2 的配置#xff0c;HAL函数使用#xff0c;和收发程序的编写。 收、发机制#xff1a;DMA发送 DAM空闲中断接收。 DMA空… 本篇正在编写、更新状态中.....) 文章目录
前言 前言 本篇详细地用截图解释 CubeMX 对 USART2 的配置HAL函数使用和收发程序的编写。 收、发机制DMA发送 DAM空闲中断接收。 DMA空闲中断的搭配相当高效而且最大地节省芯片运行资源。 本篇代码适用于绝大部分的串口模块通信如ESP8266、串口屏、蓝牙模块等。 不讲解串口通信原理 串口通信原理请自行扫盲串口通讯工作原理 只详细地截图CubeMX配置、代码编写过程。 如有错漏欢迎指正。 约定1本篇所述USART通信是指常用的异步通信即UART。 一、准备工作 1、接线 为了方便测试在上篇USART1通信的基础上增加USART2的代码。 使用USART2接串口通信模块本文所用是蓝牙模块ECB02; 实验将通过串口助手(USART1) 对蓝牙模块ECB02 (USART2进行调试和通信。 ① USART1的接线 使用了板载USB转TTL。USART1的PA9、PA10, 已连接USB转TTL。 使用USB线插到开发板上的CMSIS-DAP接口无需外接USB转TTL模块。 ② USART2接线 本文引脚连接如下表:
蓝牙 ECB02 模块STM32 开发板RXDTX-PC10 UART4)TXDRX-PC11 (UART4)GNDGNDVCC3.3V 2、新建工程 为了减少篇幅长度复制之前已建立的工程USART1 -- DMA发送DMA空闲中断接收 复制它整个文件夹、重新修改文件夹名称如USART2 -- DMA发送DMA空闲中断接收。 技巧只能修改工程文件夹名称不要修改工程文件的名称否则CubeMX无法重新生成。 技巧复制已有工程是老司机的日常操作能延用已写好的代码和配置大大减少开发时间。 相关基础文章链接 新建一个工程(STM32F103) 新建一个工程(STM32F407) GPIO 推挽输出模式点亮LED灯 USART1 -- DMA发送 DMA空闲中断 接收不定长数据 二、CubeMX的配置 串口通信的收发机制有很多方式、组合。 本文介绍操作最简单、实用的一种组合 发送DMA。 接收DMA空闲中断接收。 适用于绝大部分的串口模块通信如ESP8266、串口屏、蓝牙模块等。 1、USART2 配置 异步通信 在选择异步通信后将会使用默认引脚TX-PA2, RX-PA3. 我们无需对引脚进行任何配置CubeMX帮我们自动配置好 2、通信协议参数 本篇使用常用配置115200-None-8-1。 如下图4个主要的通信协议参数一般只需修改波特率。蓝色的3项基本万年不动。 3、使能中断 USART1配置DMA后会自动使能中断。 USART2要手动打开中断。 4、打开DMA发送、接收 网上很多教程只使能了USART_RX的DMA而不使能USART_TX的DMA。 除非TX所用的DMA通道已被其它设备占用了否则你为何能容忍它躺平不干活?! 添加完成后的状态 5、设置RX引脚上拉 在选择异步通信后CubeMX会自动配置引脚的工作模式。 这时的默认配置不打开上下拉。这可能会使引脚在悬空状态时电平不确定产生误接收。 我们把RX接收引脚的PA3修改为上拉Pull-up)给引脚固定一个弱上拉以避免悬空时产生误接收。 好了就这么简单。 优先级配置默认就行。DMA配置默认就行。 然后点击 GENERATE CODE生成工程吧 三、发送操作、代码解释 我们打开生成后的Keil工程。 在main.c文件能看到已增加了DMA和USART1的初始化代码。 初始化部分CubeMX已经帮我们生成好了。 发送数据的函数CubeMX也已经生成好了。 在工程中现在就能直接使用下面这 3个 函数发送任何数据
HAL_UART_Transmit (huart1, uint8_t *pData, uint16_t Num, 超时值);
HAL_UART_Transmit_IT (huart1, uint8_t *pData, uint16_t Num);
HAL_UART_Transmit_DMA (huart1, uint8_t *pData, uint16_t Num); 先上板测试后面再解释 在/* USER CODE BEGIN 2 */ 与 /* USER CODE END 2 */ 之间敲入以下发送代码 /* USER CODE BEGIN 2 *//* 用户代码必须写在配对的BEGIN与END之间 */static char strTem[100] Hello World!\r; // 定义一个数组也可以是其它的数据如结构体等HAL_UART_Transmit (huart1, (uint8_t*)strTem, strlen(strTem), 0xFFFF); // 发送方式2HAL_UART_Transmit(), 不推荐使用; 阻塞式发送当调用后程序会一直死等不干其它事了(中断除外)直到发送完毕HAL_UART_Transmit_IT (huart1, (uint8_t*)strTem, strlen(strTem)); // 发送方式3HAL_UART_Transmit_IT(), 推荐使用; 利用中断发送非阻塞式大大减少资源占用; 注意当上次的调用还没完成发送下次的调用会直接返回(放弃)所以要想连接发送两行调用间要么判断串口结构体gState的值要么调用延时HAL_Delay(ms), ms值要大于前一帧发送用时, 用时计算1/(波特率*11*前一帧字节数) while((huart1)-gState ! HAL_UART_STATE_READY); // 等待上条发送结束; 也可以用HAL_Delay延时法但就要计算发送用时; 两种方法都是死等法程序暂时卡死不会往下运行; 如果两次发送间隔时间大如大于100ms, 就不用判断语句了。HAL_UART_Transmit_DMA (huart1,(uint8_t*)strTem, strlen(strTem)); // 发送方式4HAL_UART_Transmit_DMA()推荐使用; 利用DMA发送非阻塞式最大限度减少资源占用; 注意当上次的调用还没完成发送下次的调用会直接返回(放弃); 所以要想连接发送两行调用间要么判断串口结构体gState的值要么调用延时HAL_Delay(ms), ms值要大于前一帧发送用时用时计算1/(波特率*11*前一帧字节数)/* USER CODE END 2 */ 打开电脑的串口助手。 编译、烧录。串口助手马上有显示 如果你那边烧录后没有显示要么是串口号错了要么是没有打勾keil的自动复位。 下面对3个函数的使用逐一解释不建议新手跳过有避坑干货。 1、HAL_UART_Transmit (huart1, uint8_t *pData, uint16_t Num, 超时值); 阻塞式发送。参数串口数据地址发送的字节数ms超时值 每发送一个字节死等好了继续发下一个再死等不断重复。 就是以前标准库种那最普通的死等法只是它增加了一个超时值。 超时值如果指定时间内没发送完毕就直接返回防止卡死。数据发送通信需时 1秒 ÷ 波特率 × 字节数 × 10 × 1000ms。举例115200波特率100字节大约用时 9ms。 新手如果不会计算直接把超时值填大一点如50ms。 2、HAL_UART_Transmit_IT (huart1, uint8_t *pData, uint16_t Num); 利用中断发送。参数串口数据地址发送的字节数 向寄存器填入一个字节程序就继续干其它的事去当一个字节发送完成后会产生发送中断CubeMX生成的回调函数自动填入下一个字节不断重复不用干预。 非阻塞式发送。能大大地减少程序运行时间的占用。 有一点要注意当连续地调用本中断发送函数时调用的间隔时间小于通信所需的用时按上这时后面那条函数调用会直接返回放弃发送。 因为函数内部在发送前会判断串口的忙状态如果在忙还在发送上一包数据就放弃本包数据返回。 解决的方法有两个 ① 最常用的两行中断发送函数间插入HAL_Delay(10)原理参考上面的发送需时。 ② 两行中断发送函数间插入 while((huart1)-gState ! HAL_UART_STATE_READY); 和 HAL_Delay() 一样都是死等但能省了那么一点点运行时间。 3、HAL_UART_Transmit_DMA (huart1, uint8_t *pData, uint16_t Num); DMA发送。参数串口数据地址发送的字节数。 上面的中断发送函数100个字节会产生100次中断。这个DMA发送函数全程只产生一次中断。 调用后函数给DMA数据地址DMA就自动开始搬砖它会把数据逐字节搬运到串口的DR寄存器上等串口发送完这个字节了再自动搬运下一个过程完全不占用程序运行资源。搬完了就产生一个中断给程序打个招呼。通常我们程序上把这个“招呼”也省略了不用理会它。 3个发送函数中推荐使用这个DMA发送函数发送的最优解。 同样的两行DMA发送函数间注意发送间隔否则放弃发送直接返回。处理方法同上。 五、接收代码的编写 发送数据可以调用现成的函数而接收数据现成函数不太好用。 接收也有3个函数和发送的3个函数相对应
HAL_UART_Receive (huart1, uint8_t *pData, uint16_t Num, 超时值);
HAL_UART_Receive_IT (huart1, uint8_t *pData, uint16_t Num);
HAL_UART_Receive_DMA (huart1, uint8_t *pData, uint16_t Num); 一般大家都不使用这三个函数太TM的难用了有兴趣的可csdn搜它们的使用优劣分析。 我们利用HAL库现成的资源另敲十来行代码令串口的接收机制更实用、更灵活。 完成后整个接收过程1个结构体 1个HAL库函数 1个回调函数全程自动接收。 共分4小项下面将有详细操作图解 ① 定义一个结构体变量存放接收的字节数、数据数组。 ② 开启DMA让硬件自动接收数据放到缓存 ③ 重写回调函数当一帧数据接收好了把缓存的数据转存到全局结构体变量里备用。 ④ 在需要使用串口接收的地方如在while中判断接收字节数0, 即为接收到新一帧数据了。 1、定义一个结构体变量存放接收的字节数、数据 ① 首先在main.h文件新建一个结构体类型 在 /* USER CODE BEGIN ET */ 与 /* USER CODE END ET */ 之间新建一个结构体类型。
/* USER CODE BEGIN ET */
/* 所有用户代码必须写在配对的BEGIO与END注释行之间否则重新生成时会被删除 */typedef struct // 声明一个结构体方便管理变量
{uint16_t ReceiveNum; // 接收字节数uint8_t ReceiveData[512]; // 接收到的数据uint8_t BuffTemp[512]; // 接收缓存; 注意这个数组只是一个缓存用于DMA逐个字节接收当接收完一帧后数据在回调函数中转存到 ReceiveData[ ] 存放。即双缓冲有效减少单缓冲的接收过程新数据覆盖旧数据
} xUSATR_TypeDef;extern xUSATR_TypeDef xUSART1 ; // 定义结构体方便管理变量。也可以不用结构体用单独的变量/* USER CODE END ET */它有3个成员 uint16_t ReceiveNum; // 接收字节数只要字节数0即为接收到新一帧数据 uint8_t ReceiveData[512]; // 接收到的数据 uint8_t BuffTemp[512]; // 临时缓存在DMA空闲中断中将把一帧数据复制到ReceivedData[ ] 有些教程还有一个Flag变量用来标记是否接收到数据。我们直接用ReceiveNum判断更简单。 在定义结构体类型的下面一行代码中用extern声明了一个结构体变量它将在main.c中定义。 技巧如果希望定义的结构体类型工程全局可用就要在h文件中定义其它文件引用这个h文件。 技巧如果希望定义的变量能被工程全局调用就在h文件中用extern声明然后在某个c文件中定义。 ② 回到main.c定义结构体变量 在 /* USER CODE BEGIN 0 */ 与 /* END 0 */ 之间使用新建的结构体类型定义我们的结构体变量。
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* 所有用户代码必须写在配对的BEGIO与END注释行之间否则重新生成时会被删除 */xUSATR_TypeDef xUSART1 {0}; // 定义结构体方便管理变量。也可以不用结构体用单独的变量/* USER CODE END 0 */ 现在我们拥有一个了全局变量xUSART1。 以后的其它文件如蓝牙模块驱动、串口屏驱动只要在文件中引用main.h就能通过这个结构体变量使用串口1接收的数据了。 2、开启DMA让硬件自动接收数据 我们整个接收过程仅使用到1个HAL库函数 只需在main()函数的初始化部分调用HAL库函数 HAL_UARTEx_ReceiveToIdle_DMA (串口、缓存、字节数) ; 参数串口、接收缓存区、最大接收字节数 作用使能DMA、使能串口的空闲中断正式进入接收状态。 操作在 main.c的 /* USER CODE BEGIN 2 */ 与 /* END 2 */ 之间插入函数
HAL_UARTEx_ReceiveToIdle_DMA(huart1, xUSART1.BuffTemp, sizeof(xUSART1.BuffTemp)); // 开启DMA空闲中断 插入后的位置如下图 调用函数后硬件就会立刻进入自动接收状态从RX引脚接收到的数据会逐个字节顺序存放到指定缓存中这里我们指定的缓存是xUSART1.BuffTemp。 因为函数内部开启了DMA中断、空闲中断所以达成下列两个条件之一就会触发中断 ① DMA接收的字节数达到了参数中的最大值 ② 串口发生空闲中断即RX引脚超过1字节的时间没有新信号。 当上述中断产生时硬件自动调用其相关的中断服务函数再继而调用回调函数。 CubeMX生成的代码已编写好上述两个中断服务函数还定义了一个它俩最终调用的回调函数。注意这个回调函数是一个弱函数。 因此我们不用管中断服务函数只需重写这个回调函数就能实现对接收数据的处理。 3、重写DMA空闲中断回调函数 DMA完成中断、空闲中断所调用的回调函数 HAL_UARTEx_RxEventCallback串口接收到的字节数); 弱函数定义在stm32xx_hal_gpio.c文件的底部。 现在我们对它进行重写以实现对接收数据的处理。 在main.c的底部/* USER CODE BEGIN 4 */ 与 /* END 4 */ 之间新建函数并编写其代码
/* USER CODE BEGIN 4 */
/* 所有用户代码必须写在配对的BEGIN与 END之间 *//******************************************************************************* 函 数 HAL_UARTEx_RxEventCallback* 功 能 DMA空闲中断回调函数* 参 数 UART_HandleTypeDef *huart // 触发的串口* uint16_t Size // 接收字节* 返回值 无* 备 注 1这个是回调函数不是中断服务函数。技巧使用CubeMX生成的工程中中断服务函数已被CubeMX安排妥当我们只管重写回调函数* 2触发条件当DMA接收到指定字节数时或产生空闲中断时硬件就会自动调用本回调函数无需进行人工调用;* 2必须使用这个函数名称因为它在CubeMX生成时已被写好了各种函数调用、函数弱定义(在stm32xx_hal_uart.c的底部); 不要在原弱定义中增添代码而是重写本函数* 3无需进行中断标志的清理它在被调用前已有清中断的操作;* 4生成的所有DMA空闲中断服务函数都会统一调用这个函数以引脚编号作参数* 5判断参数传进来的引脚编号即可知道是哪个串口接收收了多少字节
******************************************************************************/
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{if (huart huart1) // 判断串口{__HAL_UNLOCK(huart); // 解锁串口状态xUSART1.ReceiveNum Size; // 把接收字节数存入结构体xUSART1.ReceiveNum以备使用memset(xUSART1.ReceiveData, 0, sizeof(xUSART1.ReceiveData)); // 清0前一帧的接收数据memcpy(xUSART1.ReceiveData, xUSART1.BuffTemp, Size); // 把新数据从临时缓存中复制到xUSART1.ReceiveData[], 以备使用HAL_UARTEx_ReceiveToIdle_DMA(huart1, xUSART1.BuffTemp, sizeof(xUSART1.BuffTemp)); // 再次开启DMA空闲中断; 每当接收完指定长度或者产生空闲中断时就会来到这个}
}/* USER CODE END 4 */ 上面代码我们重点解释后四行 ① xUSART1.ReceiveNum Size; 把接收的字节数存入结构体 xUSART1.ReceiveNum以备使用 。 在程序的其它地方判断 ReceivNum 0 就能知道是否收到新一帧数据了。 ② memset(xUSART1.ReceivedData, 0, sizeof(xUSART1.ReceivedData)); 清0前一帧的数据缓存 ③ memcpy(xUSART1.ReceivedData, xUSART1.BuffTemp, Size); 把新数据从临时缓存中复制到xUSART1.ReceivedData[], 以备使用 从结构体和这段回调函数中可以发现这是一个双缓存的操作思路。 .ReceivedData用于存放接收后完整的一帧数据对外使用 。 .BuffTemp用于DMA接收过程是一个中间缓存。 ④ HAL_UARTEx_ReceiveToIdle_DMA(huart1, xUSART1.BuffTemp, sizeof(xUSART1.BuffTemp)); 再次开启DMA空闲中断进入接收状态。 我们在main()函数的初始化部分已调用过这个函数了为什么要在回调函数中再次调用 因为在DMA的中断服务函数里会关闭DMA即只接收一次。所以在接收完一帧后再次调用函数就能让DMA开始工作接收下一帧。在这个位置调用 能让DMA不断地循环工作。 其实在CubeMX配置中DMA有一个选项 Mode的circular, 可以让DMA进行连续地的工作接收完成后无需在回调函数里再次开启DMA 。但是目前的CubeMX版本(V6.10这个参数的选择会使我们上面的DMA接收与发送相冲突。那我们二选一好了自行手工调用。 注意一点本篇的处理是保存最后一帧数据。当有新一帧数据来了会自动盖掉旧帧数据。 至此接收工作已准备妥当。程序运行后硬件会自动接收并把接收的帧数据存放到结构体中。
4、接收的使用示范 我们来试试使用的效果吧 ① 在main.c的while函数里编写接收判断代码 /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ /* 用户代码必须写在配对的BEGIN与END之间 */ if (xUSART1.ReceiveNum) // 判断字节数 { printf(\r USART1 接收到一帧数据 \r); // 提示 printf(字节数%d \r, xUSART1.ReceiveNum); // 显示字节数 printf(ASCII : %s\r, (char *)xUSART1.ReceiveData); // 显示数据以ASCII方式显示即以字符串的方式显示 printf(16进制: ); // 显示数据以16进制方式显示每一个字节的值 for (uint16_t i 0; i xUSART1.ReceiveNum; i) // 逐个字节输出 printf(0x%X , xUSART1.ReceiveData[i]); // 以16进制显示 printf(\r\r); // 显示换行 xUSART1.ReceiveNum 0; // 清0接收标记 } } /* USER CODE END 3 */ ② 工程编译烧录
③ 打开串口助手参数设置 115200-None-8-1, 打开对应的串口端口。 按一下板子右下角的复位键串口输出如下图 ③ 在串口的发送区输入字符串 天气不错喔~~或者其它数据。 点击发送串口助手将通过PA10发送到开发板。在程序的while函数中那段代码判断接收到数据后为了方便观察将通过USART1的PA9发出数据串口助手接收后显示如下 ④ 试试16进制数据的发送。 发送区打勾16进制发送输入随意16进制值不用加0x用空格作间隔。 注意16进制的值不一定是ASCII码表的显示范围值所以在ASCII显示中会出现乱码正常现象。 至此USART1的收发已完整地展示完毕。
如有错漏欢迎留言指正修改~~ USART2 USART3 UART4 UART5 USART6