糖尿病康复,内容丰富有趣,生活中的好帮手!
糖尿病康复 > STM32串口通信 串口调试助手

STM32串口通信 串口调试助手

时间:2022-07-11 04:43:33

相关推荐

STM32串口通信 串口调试助手

STM32系列博客:

STM32学习之旅① 开发环境搭建

STM32学习之旅② 固件库的使用及工程模板的建立

STM32学习之旅③ 从点灯到代码移植

STM32学习之旅④ USART串口和上位机通信

STM32学习之旅⑤ SPI控制TFT,从底层到底层的设计

目录:

一、认识其本质

(一)串口

(二)协议

(三)时序

(四)上位机

二、所需材料

三、USART的介绍

四、USART串口的配置

五、发送函数

(一)单字节发送

(二)数据流发送

六、接收函数

七、串口打印,重定向printf函数

一、认识其本质

(一)串口

串口是串行接口 (Serial Interface)的简称,它是指数据一位一位地顺序传送,其特点是通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传送速度较慢。一条信息的各位数据被逐位按顺序传送的通讯方式称为串行通讯。串行通讯的特点是:数据位的传送,按位顺序进行,最少只需一根传输线即可完成;成本低但传送速度慢。串行通讯的距离可以从几米到几千米;根据信息的传送方向,串行通讯可以进一步分为单工、半双工和全双工三种。

(二)协议

所谓协议,就是通信双方约定好的规定,通信双方只有遵守这个规定才能够完成任务。举个栗子就是周幽王烽火戏诸侯,双方约定好以烽火为信号进行通信,但是愚蠢的周幽王为博美人褒姒一笑破坏了这个规定,最后付出的代价是惨重的。可见,通信双方只有遵守协议才能够完成通信。

(三)时序

时序就是协议的实际化,它实质上是一些列的脉冲信号,通信双方将信息按照预先定好的规定(协议)转换成一系列的脉冲信号,通过总线发送给接收方,接收方再将接收到的数据按照规定进行解析,从而得到发送方发送过来的数据。

(四)上位机

上位机和下位机其实是一个相对的概念,上位机指的是可以直接发出操控命令的计算机,一般指PC机,能够显示各种信号变化(液压,水位,温度等),能够将信息直接传递给人。下位机是直接控制设备获取设备状况的计算机,一般是PLC/单片机single chip microcomputer/slave computer/lower computer之类的,下位机需要PC机来对其进行控制。

二、所需材料

USB转TTL串口

串口助手, 密码:9vjy

三、USART的介绍

stm32有丰富的通讯外设,USART(Universal Synchronous Asynchronous Receiver Transmitter)、SPI(Serial Peripheral interface)、I2c(Inter-Integrated Circuit)、CAN(Controller Area Network),因为stm32有完整的且强大的固件库,这使得配置串口的难度大大降低了,和用软件IO口模拟通信时序相比,硬件的支持可以大大提高通信的速率、大大降低出错的概率,从而提高了通信的质量和效率。用IO口模拟USART难度较大,它对延时要求比较苛刻,且出错的概率较大,所以一般很少用IO口模拟USART。IO口模拟I2c比较常见,由于I2c的最高通信速度只有3.4M/s,单片机的IO口速度可以完美驾驭。由于SPI多用于一些较高速的通信,例如LCD、OLED、TFT显示器的写入,EEPROM (Electrically Erasable Programmable read only memory)的写入和读取,用IO口模拟效果不是很理想,所以建议使用硬件自带接口。

关于USART,以下是官方的介绍

四、USART串口的配置

先来看一下stm32的系统结构

通过对stm32几个模块的操作,我们可以发现stm32外设配置的一些基本套路:打开相应的时钟->配置相应的引脚功能->声明对应的结构体->利用相应的Init函数进行初始化

打开打开USATT1、GPIOA、AFIO的时钟

void usart_config()

{

/*打开USATT1、GPIOA、AFIO的时钟*/

RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 \

| RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

/*配置对应的串口引脚*/

usart_release_gpio_init();

/*配置串口中断*/

usart_para_config();

USART_ClearFlag(USART1,USART_FLAG_TC); //清除发送完成标志位

NVIC_Config(); //初始化NVIO

USART_Cmd(USART1, ENABLE); //使能串口1

}

配置相应的IO口,将其设为复用推挽输出和浮空输入

void usart_release_gpio_init()

{

GPIO_InitTypeDef GPIO_InitStruct;

/*配置PA9为复用推外输出*/

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOA, &GPIO_InitStruct);

/*配置PA10为浮空输入*/

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_Init(GPIOA, &GPIO_InitStruct);

}

配置NVIC(Nested Vectored Interrupt Controller),即内嵌向量中断控制器,它是用来配置中断抢占优先级和从优先级(响应优先级)的

关于抢占优先级和响应优先级区别:

高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。

抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断。

抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行。

如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;

void NVIC_Config(void)

{

NVIC_InitTypeDef NVIC_InitStructure; //NVIC 初始化结构体声明

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //设置串口1 中断

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级0

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级为0

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能

NVIC_Init(&NVIC_InitStructure);

}

配置串口协议

void usart_para_config(void)

{

USART_InitTypeDef USART_InitStruct;

USART_InitStruct.USART_BaudRate = 115200;

USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;

USART_InitStruct.USART_WordLength = USART_WordLength_8b;//8

USART_InitStruct.USART_Parity = USART_Parity_No; //N

USART_InitStruct.USART_StopBits = USART_StopBits_1; //1

USART_Init(USART1, &USART_InitStruct);

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能接收中断

}

五、发送函数

(一)单字节发送

在main函数中调用USART_SendData(USART1, 0x08);这个函数就能够完成单字节的发送了

#include "stm32f10x.h"

#include "stm32f10x_gpio.h"

#include "timer.h"

#include "usart.h"

int main()

{

SystemInit(); //初始化系统,系统时钟设定为72MHz

systick_init(72);//配置systick,中断时间设置为72000/72000000 = 1us

usart_config();

while(1)

{

USART_SendData(USART1, 0x08);

delay_ms(100);

}

}

打开串口助手就能够看到串口发来的数据了

(二)数据流发送

数据流简单来说就是一串连续的信息序列,一串序列中有若干个字节,每个字节分别对应着通信双方预先约定好的数据含义,例如第一位代表地址、第二位代表数据流向、最后一位代表结束标志、其余位代表数据。数据流的长度可长可短,由通信双方确定,但通信的过程中不能够变化。

定义一个协议栈

typedef struct

{

u8 head;

u8 tail;

u8 direction;

u8 data[4];

}send_stack;

send_stack tx_stack;

void tx_stack_init()

{

tx_stack.head = 0xaa; //协议栈头,起始位,1010 1010b

tx_stack.direction = 0x09;//数据流方向,0x09表示从单片机发出

memset(tx_stack.data, 0, sizeof(tx_stack.data));//把tx_stack.data[]全部初始化为零

tx_stack.tail = 0xdd; //协议栈尾,结束位,1101 1101b,栈头和栈尾最好能互补

}

将协议栈内的数据依次发出

void usart_senddata()

{

u8 i;

while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);

USART_SendData(USART1, tx_stack.head);

while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);

USART_SendData(USART1, tx_stack.direction);

while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);

for(i = 0; i < 4; i++)

{

USART_SendData(USART1, tx_stack.data[i]);

while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);

}

USART_SendData(USART1, tx_stack.tail);

while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);

}

打开串口助手可以看到串口发来的数据流

六、接收函数

接收函数和发送函数类似,先定义接收协议栈

typedef struct

{

u8 head;

u8 tail;

u8 direction;

u8 data[4];

u8 lock_flag;

u8 data_pt;

}receive_stack;

receive_stack rx_stack;

void rx_stack_init()

{

rx_stack.head = 0x00;//协议栈头,起始位

rx_stack.direction = 0x00; //数据流方向,0x09表示从单片机发出

memset(rx_stack.data, 0, sizeof(rx_stack.data));//把tx_stack.data[]全部初始化为零

rx_stack.tail = 0x00;//协议栈尾,结束位

rx_stack.data_pt = 0x00;

rx_stack.lock_flag = UNLOCK;

}

接收数据需要借助中断来完成

void USART1_IRQHandler(void)

{

u8 receive_data;

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //判断读寄存器是否非空

{

receive_data = USART_ReceiveData(USART1);//接收单个字节的串口数据

if(rx_stack.lock_flag == UNLOCK)//如果接收协议栈未锁柱

{

if(receive_data == 0xaa)

{

rx_stack.head = receive_data;

}

else if(receive_data == 0xf9)

{

rx_stack.direction = receive_data;

}

else if(receive_data == 0xdd)

{

rx_stack.tail = receive_data;

if(rx_stack.data_pt >= 4)// && (rx_stack.tail == 0xdd))

{

rx_stack.data_pt = 0;

rx_stack.lock_flag = LOCK;

}

}

else

{

rx_stack.data[rx_stack.data_pt] = receive_data;

rx_stack.data_pt++;

if(rx_stack.data_pt > 4)// && (rx_stack.tail == 0xdd))

{

rx_stack.data_pt = 0;

rx_stack.lock_flag = LOCK;

}

}

}

USART_ClearITPendingBit(USART1, USART_IT_RXNE);//清除接受中断标志

}

}

将接收到的数据流进行解析,用灯的亮灭将控制命令现实化

void ptr_handle(u8 *stack)

{

u8 *stack_pt;

stack_pt = stack;

if(*stack_pt == 0xff)

{

key0.key_change_bit = CHGE_IN;

if((key0.led_on_off % 2) == 1)

{

}

else

{

key0.led_on_off = key0.led_on_off >=3 ? 0 : key0.led_on_off + 1;

}

}

else

{

key0.key_change_bit = CHGE_IN;

if((key0.led_on_off % 2) == 1)

{

key0.led_on_off = key0.led_on_off >=3 ? 0 : key0.led_on_off + 1;

}

else

{

}

}

stack_pt ++;

if(*stack_pt == 0xff)

{

key1.key_change_bit = CHGE_IN;

if((key1.led_on_off % 2) == 1)

{

}

else

{

key1.led_on_off = key1.led_on_off >=3 ? 0 : key1.led_on_off + 1;

}

}

else

{

key1.key_change_bit = CHGE_IN;

if((key1.led_on_off % 2) == 1)

{

key1.led_on_off = key1.led_on_off >=3 ? 0 : key1.led_on_off + 1;

}

else

{

}

}

stack_pt ++;

if(*stack_pt == 0xff)

{

key2.key_change_bit = CHGE_IN;

if((key2.led_on_off % 2) == 1)

{

}

else

{

key2.led_on_off = key2.led_on_off >=3 ? 0 : key2.led_on_off + 1;

}

}

else

{

key2.key_change_bit = CHGE_IN;

if((key2.led_on_off % 2) == 1)

{

key2.led_on_off = key2.led_on_off >=3 ? 0 : key2.led_on_off + 1;

}

else

{

}

}

stack_pt ++;

if(*stack_pt == 0xff)

{

key3.key_change_bit = CHGE_IN;

if((key3.led_on_off % 2) == 1)

{

}

else

{

key3.led_on_off = key3.led_on_off >=3 ? 0 : key3.led_on_off + 1;

}

}

else

{

key3.key_change_bit = CHGE_IN;

if((key3.led_on_off % 2) == 1)

{

key3.led_on_off = key3.led_on_off >=3 ? 0 : key3.led_on_off + 1;

}

else

{

}

}

rx_stack.lock_flag = UNLOCK;

}

主函数要做的,就是循环判断是否有灯的状态需要改变,每次接收到上位机发来的命令后把当前的状态发送到上位机

#include "stm32f10x.h"

#include "stm32f10x_gpio.h"

#include "timer.h"

#include "gpio_config.h"

#include "usart.h"

int main()

{

SystemInit(); //初始化系统,系统时钟设定为72MHz

systick_init(72);//配置systick,中断时间设置为72000/72000000 = 1us

led_gpio_init();

usart_config();

tx_stack_init();

while(1)

{

if (key0.key_change_bit == CHGE_IN)

{

if((key0.led_on_off % 2) == 1)

{

LED0_ON; //打开LED0

tx_stack.data[0] = 0xff;

}

else

{

LED0_OFF; //关闭LED0

tx_stack.data[0] = 0x00;

}

key0.key_change_bit = NO_CHGE;

}

if (key1.key_change_bit == CHGE_IN)

{

if((key1.led_on_off % 2) == 1)

{

LED1_ON; //打开LED1

tx_stack.data[1] = 0xff;

}

else

{

LED1_OFF; //关闭LED1

tx_stack.data[1] = 0x00;

}

key1.key_change_bit = NO_CHGE;

}

if (key2.key_change_bit == CHGE_IN)

{

if((key2.led_on_off % 2) == 1)

{

LED2_ON; //打开LED2

tx_stack.data[2] = 0xff;

}

else

{

LED2_OFF; //关闭LED2

tx_stack.data[2] = 0x00;

}

key2.key_change_bit = NO_CHGE;

}

if (key3.key_change_bit == CHGE_IN)

{

if((key3.led_on_off % 2) == 1)

{

LED3_ON; //打开LED3

tx_stack.data[3] = 0xff;

}

else

{

LED3_OFF; //关闭LED3

tx_stack.data[3] = 0x00;

}

key3.key_change_bit = NO_CHGE;

usart_senddata();

}

if(rx_stack.lock_flag == LOCK)

{

ptr_handle(rx_stack.data); //运行协议解析函数

}

}

}

七、串口打印,重定向printf函数

使用printf函数,需要包含其头文件stdio.h,即标准输入输出头文件,std是standard的缩写,是标准的意思;i是input,输入;o是output,输出;h是head,头,头文件的意思。

然后在usart,c中编写字符写入函数,将格式化后的字符串依次写入到发送总线上

int fputc(int ch, FILE *f)

{

while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET)

{

}

USART_SendData(USART1, (uint8_t) ch);

return ch;

}

在主函数中调用printf函数,实现格式化输出

#include "stm32f10x.h"

#include "stm32f10x_gpio.h"

#include "timer.h"

#include "usart.h"

int main()

{

SystemInit(); //初始化系统,系统时钟设定为72MHz

systick_init(72);//配置systick,中断时间设置为72000/72000000 = 1us

led_gpio_init();

usart_config();

tx_stack_init();

while(1)

{

printf("Hello World!\n%d\n\n", 12345);

delay_ms(100);

}

}

如果觉得《STM32串口通信 串口调试助手》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。