目录
1. 串行通信
1.1 串行通信概述
1.2 串行通信协议
2. 实验任务
3. 硬件原理
4. 利用STM32CubeMX创建MDK工程
5. 串行通信实验
5.1 UART串口printf,scanf函数串口重定向
5.2 UART串口printf输出实验
5.3串口控制LED实验
6.调试与验证
7.总结
串口调试助手: 下载地址
1. 串行通信
1.1 串行通信概述
串口通信(Serial Communications)的概念非常简单,串口按位(bit)发送和接收字节。
串口:可以算是一个泛称,一般指代的是串口时序标准。UART、RS232、RS485、TTL都遵循着类似的通信时序协议,因此都被通称为串口。
串行通信(Serial Communication)是指计算机主机与外设之间以及主机系统与主机系统之间数据的串行传送。使用一条数据线,将数据一位一位地依次传输,每一位数据占据一个固定的时间长度。其只需要少数几条线就可以在系统间交换信息,特别适用于计算机与计算机、计算机与外设之间的远距离通信。
串行通信按照发送时钟源和接收时钟源是否需要保持一致,又可分为同步通信和异步通信两种。
使用 RS-232 标准的串口设备间常见的通讯结构见下图:
串口通讯结构图
根据通讯使用的电平标准不同,串口通讯可分为 TTL 标准及 RS-232 标准:
通讯标准 | 电平标准 (发送端) |
5V TTL | 逻辑 1: 2.4V-5V |
逻辑 0: 0~0.5V | |
RS-232 | 逻辑 1: -15V~-3V |
逻辑 0: +3V~+15V |
DB9 接口中的公头及母头的各个引脚的标准信号线接法见下图:
1.2 串行通信协议
串口通讯的数据包由发送设备通过自身的 TXD 接口传输到接收设备的 RXD 接口。在串口通讯的协议层中,规定了数据包的内容,它由启始位、主体数据、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据,其组成见下图:
2. 实验任务
利用STM32CubeMX,创建MDK工程,采用串口调试助手发送指令,实现对LED的控制。
1 ------ 点亮LED0
2 ------ 点亮LED1
3 ------ 互斥点亮两个LED
4 ------ 互斥点亮两个LED
3. 硬件原理
4. 利用STM32CubeMX创建MDK工程
选择File下的New Project:
选择芯片类型(本文为stm32f103RBt6),选择下边的item,然后Start Project:
点击左侧的System Core下的SYS,将Debug设置为Serial Wire:
配置时钟:将RCC下的HSE设置为Crystal/Ceramic Resonator
结合开发版的硬件电路,进行GPIO设置
选择GPIO,依次将PA8、PD2设置为GPIO_Output:
USART1参数配置:
在 Connectivity 中选择 USART1 设置,并选择 Asynchronous 异步通信。
波特率为 115200 Bits/s。传输数据长度为 8 Bit。奇偶检验 None,停止位 1 ,接收和发送都使能。
配置NVIC
检查是否已经使能串口中断:
结合开发版的硬件电路,选择Clock Configuration,做如下配置:
项目配置:
在Project Manager下的Project中设置工程名称和工程路径,并选择编译软件。取消勾选Use lastest available version,选择其他版本:
代码生成设置:
在Code Generate中选择第二个,然后Generate Code,即生成代码:
可以打开MDK工程编辑了。以下将通过几个实验,说明串口的用法。
初始化程序说明。
串口设置的一般步骤可以总结为如下几个步骤:
- 串口时钟使能,GPIO 时钟使能
- 串口复位
- GPIO 端口模式设置
- 串口参数初始化
- 开启中断并且初始化 NVIC(如果需要开启中断才需要这个步骤)
- 使能串口
- 编写中断处理函数
使用CubeMX配置,前面6个步骤已经自动生成了,我们只需要编写中断处理函数和主函数就可以了。
5. 串行通信实验
5.1 UART串口printf,scanf函数串口重定向
在学习C语言时我们经常使用C语言标准函数库输入输出函数,比如printf、scanf、getchar等。为让开发板也支持这些函数需要把USART发送和接收函数添加到这些函数的内部函数内。
在C语言HAL库中,fputc函数是printf函数内部的一个函数,功能是将字符ch写入到文件指针f所指向文件的当前写指针位置,简单理解就是把字符写入到特定文件中。
fgetc函数与fputc函数非常相似,实现字符读取功能。在使用scanf函数时需要注意字符输入格式。
注意:
在MDK中使用ARMCC即第5版本的编译器时,使用fput和fgetc函数达到重定向C语言HAL库输入输出函数必须在MDK的工程选项把“UseMicroLIB”勾选上,MicoroLIB是缺省C库的备选库,它对标准C库进行了高度优化使代码更少,占用更少资源。
为使用printf、scanf函数需要在文件中包含stdio.h头文件。
在usart.c文件的user code 0 区域内:
输入如下内容:
#include "stdio.h"
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1 //方便调试,改为“#if 0”不要以下功能
#if defined (__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050) //AC6编译器
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
__asm (".global __use_no_semihosting\n\t");
void _sys_exit(int x)
{//定义_sys_exit()以避免使用半主机模式
x = x;
}
/* __use_no_semihosting was requested, but _ttywrch was */
void _ttywrch(int ch)
{
ch = ch;
}
FILE __stdout;
#elif defined ( __CC_ARM ) //AC5编译器
//#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
void _sys_exit(int x)
{
x = x;
}
#endif
重定义fputc函数
//int fputc(int ch, FILE *f)
//{
// while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
// USART1->DR = (u8) ch;
// return ch;
//}
//重定向 c 库函数 printf 到串口 USARTx,重定向后可使用 printf 函数
//重定向 c 库函数 scanf 到串口 USARTx,重写向后可使用 scanf、 getchar 等函数
//使用 printf、 scanf 函数需要在文件中包含 stdio.h 头文件。
#if defined ( __GNUC__ ) && !defined (__clang__)
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#define GETCHAR_PROTOTYPE int __io_getchar(FILE *f)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#define GETCHAR_PROTOTYPE int fgetc(FILE *f)
#endif
//改写fputc函数。【有多个串口是,需要修改下面的代码】
PUTCHAR_PROTOTYPE
{
//发送一个字节数据到串口USARTx
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000); //发送数据到串口
return ch;
}
GETCHAR_PROTOTYPE
{
uint8_t ch = 0;
//等待串口输入数据
//while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) == RESET);
HAL_UART_Receive(&huart1,&ch, 1, 0xffff);
return ch;
}
#endif
5.2 UART串口printf输出实验
打开Keil文件后,点击Application,在 main.c 文件里的 while(1) 循环内的user code 3区域:
输入如下代码:
if (__ARMCC_VERSION > 6000000)
printf("您编译工程时选择的是ARMCLANG(AC6)编译器\n");
else
printf("您编译工程时选择的是ARMCC(AC5)编译器\n");
printf("串口输出测试\n\n");
HAL_Delay(2000);
打开串口调试助手,实验结果如下:
选择AC5编译器时的调试结果:
选择AC6编译器时的调试结果:
5.3串口控制LED实验
打开Keil文件后,点击Application,在 main.c 文件里的 while(1) 循环内的
/* USER CODE BEGIN Includes */
和
/* USER CODE END Includes */
之间添加以下代码
char ch;
GPIO_PinState ledxPinState;
在main.c的user code 3区域:
输入如下代码:
if (__ARMCC_VERSION > 6000000)
printf("2您编译工程时选择的是ARMCLANG(AC6)编译器\n\n");
else
printf("您编译工程时选择的是ARMCC(AC5)编译器\n\n");
printf("串口控制LED实验,请输入以下数字:\n");
printf("1 ------ 点亮LED0\n");
printf("2 ------ 点亮LED1\n");
printf("3 ------ 互斥点亮两个LED\n");
printf("4 ------ 互斥点亮两个LED\n");
printf("您还可以重复输入上次输入的数字,观察灯效果. \n");
ch=getchar();
printf("已接收您刚才输入的字符: %c\n",ch);
switch (ch)
{
case '1':
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
if(HAL_GPIO_ReadPin(LED0_GPIO_Port,LED0_Pin))
printf("\n已关闭LED0,请在开发板上观察效果\n\n\n\n");
else
printf("\n已点亮LED0,请在开发板上观察效果\n\n\n\n");
break;
case '2':
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);;
if(HAL_GPIO_ReadPin(LED1_GPIO_Port,LED1_Pin))
printf("\n已关闭LED1,请在开发板上观察效果\n\n\n\n");
else
printf("\n已点亮LED1,请在开发板上观察效果\n\n\n\n");
break;
case '3':
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
ledxPinState = HAL_GPIO_ReadPin(LED0_GPIO_Port,LED0_Pin);
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,(GPIO_PinState)!ledxPinState);
printf("\n两个LED互斥已点亮,请在开发板上观察效果\n\n\n\n");
break;
case '4':
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
ledxPinState = HAL_GPIO_ReadPin(LED0_GPIO_Port,LED0_Pin);
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,ledxPinState);
if(HAL_GPIO_ReadPin(LED0_GPIO_Port,LED0_Pin))
printf("\n已同时关闭两个LED,请在开发板上观察效果\n\n\n\n");
else
printf("\r\n已同时点亮两个LED,请在开发板上观察效果\n\n\n\n");
break;
default:
/* 如果不是指定指令字符,打印提示信息 */
printf("\r\n请输入以下1~4之间的数字,然后观察点亮效果。\n\n\n");
break;
}
打开串口调试助手,实验结果如下:
选择AC6编译器时的调试结果:
如果你需要AC5编译器,请参考如下博文安装设置:
Keil MDK5.37以上版本自行添加AC5(ARMCC)编译器的方法_armcc下载:
Keil MDK5.37以上版本自行添加AC5(ARMCC)编译器的方法_armcc下载_笑春风oO的博客-CSDN博客
6.调试与验证
如果您需要虚拟仿真调试,请参考专栏如下博文的5.1节:
基础篇003. 使用STM32CubeMX创建MDK工程,实现流水灯的仿真与下载验证:
https://blog.csdn.net/qcmyqcmy/article/details/129159801
如果您需要在Proteus中仿真调试,请参考本专栏的博文:
基础篇004. 采用Proteus + STM32CubeMX + MDK-ARM学习流水灯:
https://blog.csdn.net/qcmyqcmy/article/details/129250108
将程序下载到开发板进行验证:
7.总结