概述
前言
串口通信在嵌入式开发中占据非常重要的低位,串口经常被用来调试系统,打印必要的调试信息,帮助我们分析定位问题,同时很多常见的外设也支持串口协议,如WIFI模块、蓝牙模块、4G模块、指纹模块、PM2.5传感器、甲醛传感器等诸多元器件,本节开始,我们正式学习STM32单片机的串口。
一、基础知识
1. STM32物联网套件简介
STM32物联网套件目前有两个版本:基础版和高级版,后续会增加应用版本和语音版,核心板均采用STM32F103C8T6核心板,基础版主要元器件如下所示:
高级版版主要元器件如下所示:
STM32物联网套件致力于带领大家入门物联网,不仅学习STM32,也了解转微信小程序开发、物联网服务器后台开发,真正做到了解一个物联网项目的方方面面,基于此,我们定制了一套通用的WIFI通讯协议(可以理解为类似AT指令,不过集成度更高,几条指令就可以直连接云平台),如三条指令连接腾讯云实例。
后续我们会继续增加涂鸦智能、电信云、移动Onenet、阿里云等主流云平台的支持,力争做到,一套STM32代码,通过定制的WIFI模块可以连接到不同的云平台,也欢迎有产品开发需求的朋友私聊我们咨询、定制物联网方案!
本套物联网套件可以支持大学生参加物联网相关比赛、申请学校大创项目、完成毕业设计等,我们的定位是做一家开源智能硬件服务商,和大家一起探索物联网,我们的使命是推动更多物联网产品的落地和普及,让技术不再是阻碍!
2.通信方式简介
串口使用的通信方式是串行通信,串口通信是相对于并行通信来说的,所谓并行通信指的是数据的各位同时传送,其特点是传输速度快、效率高,但是使用数据线较多,传输成本较高,适合近距离通信,其示意图如下:
所谓串行通信其实是指数据通过一根数据线一位一位依次传送给目标设备,其特点是至少只需要一根传输线即可完成,成本低但传输速断慢,其只需要少数几条线就可以在系统间交换信息。其示意图如下:
3. 串行通信制式
按照数据传输方向,可以分为三张总制式,即单工、半双工、全双工。
( a ) 单工制式
A站和B站数据只能从一个设备发至另一个设备,单向传输。
( b ) 半双工工制式
A站和B站数据传送是双向的,但同一时刻只能有一个方向数据传送,接收开关可由软件控制。
( c ) 全双工制式
A站和B站任意时刻数据都可以同时发送和接收数据
4. STM32串口简介
STM32F103C8T6共有三个USART,分别是USART1 USART2 USART3,USART(通用同步异步收发器)是一个串行通信设备,可以设置位同步模式和异步模式。
一般单片机还有一个UART,它是在 USART 基础上裁剪掉了同步通信功能,只有异步通信。两者区别是,同步通信需要有一根时钟线提供时钟,我们平时一般用的串口通信基本都是 UART。
(1) 串口常见用法
(a) 打印系统调试日志,快速定位问题
(b) 控制串口设备
(c) 通过RS232/RS485等控制工业通讯设备
(2) STM32F103C8T6串口资源
5. 硬件设计
本次串口通信实验使用串口1和串口2,其中串口1用来下载程序,打印系统日志,串口2中断方式接收到数据后立刻返回,连接方法如下:
实物图如下所示:
二、实例
1. 新建工程
使用STM32CubeMX创建一个新的工程,参考环境搭建章节配置方式,设置RCC和PC13引脚输出,使能串口1,选择异步通信方式。
使能串口2,同时进行中断配置
进入Clock configuration页面,选择HSE时钟源,倍频后主时钟为72MHz
切换到Project Manager栏目,设置工程名字、工程保存目录、工具链等信息,具参数如下图所示
点击左边栏目Code Generator,然后勾选Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral,勾选此选项,外设将单独保存在一个文件中,而不是全部都在main.c中。
点击GENERATE按钮,生成新的工程
2. 函数说明
我们先看下串口初始化函数,串口1和串口2配置相同,都是115200波特率,8位字长,一位停止位,无校验,无流控。
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
/* USART2 init function */
void MX_USART2_UART_Init(void)
{
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
}
然后我们看下串口处理HAL_UART_MspInit()函数
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* USART1 clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
}
else if(uartHandle->Instance==USART2)
{
/* USER CODE BEGIN USART2_MspInit 0 */
/* USER CODE END USART2_MspInit 0 */
/* USART2 clock enable */
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART2 GPIO Configuration
PA2 ------> USART2_TX
PA3 ------> USART2_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART2 interrupt Init */
HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
/* USER CODE BEGIN USART2_MspInit 1 */
/* USER CODE END USART2_MspInit 1 */
}
}
此函数在初始化串口过程中配置了RCC时钟、GPIO引脚作用,对于串口2,还额外增加了串口2中断配置功能
3. 修改程序
(1) 首先重定义printf函数
欲实现printf输出到串口,需要将fputc函数里面的输出指向串口(重定向),方法很简单,在keil工程中实现这个函数就好,注意需要增加头文件#include “stdio.h”,否则无法识别FILE。
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
实际写代码时候,常常为了考虑系统兼容性,增加对GNU编译系统的兼容处理,修改如下:
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/**
* @brief Retargets the C library printf function to the USART.
* @param None
* @retval None
*/
PUTCHAR_PROTOTYPE
{
/* Place your implementation of fputc here */
/* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
注意,GNU编译系统中,实现printf重定向,需要使用__io _putchar(int ch)函数,为了保持程序的兼容性,增加宏定义PUTCHAR_PROTOTYPE。
(2) 然后完成串口收发处理
a. usart.c文件中重写HAL_UART_RxCpltCallback 串口接收完成回调函数
uint8_t aRxBuffer;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2)
{
HAL_UART_Transmit(&huart2, &aRxBuffer, 1, 100);
HAL_UART_Receive_IT(&huart2, &aRxBuffer, 1);
}
}
注意该函数中 HAL_UART_Transmit 是串口发送函数, 上文实现收到数据马上发出功能,HAL_UART_Receive_IT函数作用是重新开启串口接收中断。
b. main.c文件中printf定期打印系统日志
extern uint8_t aRxBuffer;
int main(void)
{
/* USER CODE BEGIN 1 */
int flag_led = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart2,&aRxBuffer,1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
printf("reset gpio!rn");
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
printf("set gpio!rn");
HAL_Delay(1000);
}
/* USER CODE END 3 */
}
首先开启串口接收功能,然后就可以调用printf函数,注意增加头文件#include"stdio.h"。
三、下载运行
按照上一节方式给核心板下载程序,可以看到系统正常打印调试日志,通过上位机发送数据到串口2,也可以立刻收到返回消息。
四、小结
如您在使用过程中有任何问题,请加QQ群进一步交流。
QQ交流群:906015840 (备注:物联网项目交流)
源码获取:关注公众号,回复xiaoyi_stm32kits获取资料
开源智能硬件:http://bbs.xiaoyiiot.cn/
小驿物联出品:宁愿做过了后悔,也不要后悔没去做!
最后
以上就是大方皮带为你收集整理的STM32物联网套件基础版06-串口通信的全部内容,希望文章能够帮你解决STM32物联网套件基础版06-串口通信所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复