STM32入门笔记10_USART串口通信案例:上位机控制LED亮灭(附TIM定时器和EXTI综合案例)
USART串口通信
通信接口
名称 | 引脚 | 双工 | 时钟 | 电平 | 设备 |
---|---|---|---|---|---|
USART | TX、RX | 全双工 | 异步 | 单端 | 点对点 |
I2C | SCL、SDA | 半双工 | 同步 | 单端 | 多设备 |
SPI | SCLK、MOSI、MISO、CS | 全双工 | 同步 | 单端 | 多设备 |
CAN | CAN_H、CAN_L | 半双工 | 异步 | 差分 | 多设备 |
USB | DP、DM | 半双工 | 异步 | 差分 | 点对点 |
USART有同步和异步两种通讯方式, 但同步只用作特殊功能, 一般只使用异步通讯
串口通信
通信方式
串行通信与并行通信
串行异步通信和串行同步通信
异步通信:
一次通信传送一个字符帧, 发送的字符之间的时间间隔可以是任意的, 优点是通信设备简单、价格低廉, 但因为具有起始位和停止位, 传输效率较低
同步通信:
进行通信前先建立同步, 发送频率和接受方的接受频率要同步。在发送信息时, 将多个字符加上同步字符组成一个信息帧, 有一个统一的时钟控制发送端的发送, 接收端识别到同步字符后, 就认为开始一个信息帧, 此后位数作为实际传输信息处理。优点: 传输速度较快, 可用于点对多点 缺点: 需要使用专用的时钟控制线实现同步, 对于长距离通信成本较高, 通信速率也会降低。一般用于同一PCB上芯片级之间的通信
串口参数及时序(串行异步通信)
硬件电路
电平标准
差分信号的干扰小, 一般TTL电平和RS232电平传输距离只有十几米, 而RS485电平的传输范围可达数千米
串口时序
USART简介
USART框图
USART基本结构
数据帧

起始位侦测
数据采样
波特率发生器
案例: 上位机控制LED亮灭
说明:
按键输入端为PA1 采用EXTI中断
PA0输出控制LED1, PA2输出控制LED2
LED2可通过按键控制
LED1通过上位机下达指令控制
利用Timer定时器每隔十秒进入一次中断, 获取LED1和LED2的亮灭状态
硬件接线
PA0接LED1, PA1接按键,PA2接LED2,PA9为单片机的Tx发送端接CH3400的Rx端,PA10为单片机的Rx接收断接CH3400的Tx端
初始化USART
/*
串口通信初始化函数 抢占优先级0 响应优先级0
*/
void Serial_Init(void)
{
// RCC使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置GPIO
// TX 发送端
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// RX 接收断
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置USART串口通信
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate=9600; // 波特率
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None; // 无流控
USART_InitStructure.USART_Mode=USART_Mode_Rx | USART_Mode_Tx; // 两种模式都打开
USART_InitStructure.USART_Parity=USART_Parity_No; // 奇偶校验
USART_InitStructure.USART_StopBits=USART_StopBits_1; // 停止位
USART_InitStructure.USART_WordLength=USART_WordLength_8b; // 8位
USART_Init(USART1, &USART_InitStructure);
// 打开USART中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 接收寄存器非空(正在接收)
// 配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel= USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStructure);
// 启动USART
USART_Cmd(USART1, ENABLE);
}
主要代码
Serial.c
#include "stm32f10x.h"
char Down_String[] = "AABBCCDD01000000\r\n"; // 保存从下位机获取的数据
char Header[] = "AABBCCDD00"; // 协议头+数据来源
char Up_String[100]; // 保存从上位机传送来的数据
uint8_t idx; //下标
uint8_t Decode_Flag;
/*
串口通信初始化函数 抢占优先级0 响应优先级0
*/
void Serial_Init(void)
{
// RCC使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置GPIO
// TX 发送端
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// RX 接收断
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置USART串口通信
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate=9600; // 波特率
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None; // 无流控
USART_InitStructure.USART_Mode=USART_Mode_Rx | USART_Mode_Tx;
USART_InitStructure.USART_Parity=USART_Parity_No; // 奇偶校验
USART_InitStructure.USART_StopBits=USART_StopBits_1; // 停止位
USART_InitStructure.USART_WordLength=USART_WordLength_8b; // 8位
USART_Init(USART1, &USART_InitStructure);
// 打开USART中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 接收寄存器非空(正在接收)
// 配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel= USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStructure);
// 启动USART
USART_Cmd(USART1, ENABLE);
}
/*
发送单个数据
*/
void Send_Byte(uint16_t Byte)
{
USART_SendData(USART1, Byte);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待发送完毕
}
/*
发送字符串
*/
void Serial_SendString(char * String)
{
uint8_t i;
for(i=0; String[i] != '\0'; ++i)
{
Send_Byte(String[i]);
}
}
/*
向上位机传送监控的信息
*/
void Transimit_Message(void)
{
Serial_SendString((char*)Down_String);
}
/*
获取LED信息 LED为低电平驱动
*/
void Get_LEDInfo(void)
{
// LED1
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)==1){ // LED1灭
Down_String[11] = '0';
}else{
Down_String[11] = '1';
}
// LED2
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2)==1){ // LED2灭
Down_String[13] = '0';
}else{
Down_String[13] = '1';
}
}
/*
下位机解码上位机传送的信息
*/
uint8_t Decode_Info(char * String)
{
for(uint8_t i=0; i < 8; ++i)
{
if(String[i] != Header[i])
{
return 0;
}
}
// 判断是否有保留位
for(uint8_t i=12; i <= 15; ++i)
{
if(String[i] != '0') return 0;
}
// 控制LED1亮灭
if(String[10] == '0' && String[11] == '1')
{
// 开灯
GPIO_ResetBits(GPIOA, GPIO_Pin_0);
}
else if(String[10] == '0' && String[11] == '0')
{
// 关灯
GPIO_SetBits(GPIOA, GPIO_Pin_0);
}
else return 0;
return 1;
}
/*
根据反馈解码状态打印信息
*/
void Decode_Info_Print(void)
{
if(Decode_Flag == 0)
{
// 解码失败, 请用正确的协议进行通讯
Serial_SendString("Decoded failed!\r\nPlease ensure the communication protocol is correct!\r\n");
}
else
{
// 解码成功!LED1状态转换成功
Serial_SendString("Decoded successful!\r\n");
}
Decode_Flag = 0;
}
/*
中断函数
*/
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) // 接受寄存器不空
{
uint8_t Serial_RxData;
Serial_RxData = USART_ReceiveData(USART1); // 接受数据
Up_String[idx++] = (char)Serial_RxData;
// 清除中断标志
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
Serial.h
#ifndef __SERIAL_H
#define __SERIAL_H
#include "stm32f10x.h"
void Serial_Init(void);
void Send_Byte(uint16_t Byte);
void Serial_SendString(char * String);
void Transimit_Message(void);
void Get_LEDInfo(void);
uint8_t Decode_Info(char * String);
void Decode_Info_Print(void);
extern char Down_String[];
extern char Header[];
extern uint8_t idx;
extern uint8_t Decode_Flag;
extern char Up_String[100];
#endif
LED.c
#include "stm32f10x.h"
/*
初始化LED1
*/
void LED1_Init(void)
{
// RCC使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_0); // 设置高电平
}
/*
关灯 低电平驱动
*/
void LED1_Off(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_0);
}
/*
开灯
*/
void LED1_On(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_0);
}
/*
翻转灯泡状态
*/
void LED1_Turn(void)
{
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)==1){
LED1_On();
}else{
LED1_Off();
}
}
void LED2_Init(void)
{
// RCC使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_2); // 设置高电平
}
/*
关灯 低电平驱动
*/
void LED2_Off(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2);
}
/*
开灯
*/
void LED2_On(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2);
}
/*
翻转灯泡状态
*/
void LED2_Turn(void)
{
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2)==1){
LED2_On();
}else{
LED2_Off();
}
}
key.c
#include "stm32f10x.h"
#include "LED.h"
/*
初始化按键 PA1 抢占优先级1 响应优先级1
*/
void Key_Init(void)
{
// RCC使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// 初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; // 上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1; // PA1
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置AFIO
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1);
// 配置中断
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line1;
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
}
/*
按键中断函数
*/
void EXTI1_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line1) == SET)
{
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1)==0)
{
LED2_Turn();
}
// 清除中断标志
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
Timer.c
#include "stm32f10x.h"
#include "Serial.h"
/*
初始化定时器 抢占优先级0 响应优先级1
*/
void Timer_Init(void)
{
// RCC开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 选择时钟源
TIM_InternalClockConfig(TIM2); // 内部时钟
// 配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; // 内部时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; // 计数模式
TIM_TimeBaseInitStructure.TIM_Period=50000-1; // 加载到自动重装寄存器的值
TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1; // 预分频的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0; // 重复计数器的值
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //
TIM_ClearFlag(TIM2, TIM_FLAG_Update); // 清除标志
// 使能更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn; // 中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; // 响应优先级
NVIC_Init(&NVIC_InitStructure);
// 启动定时器
TIM_Cmd(TIM2, ENABLE);
return;
}
/*
10秒钟进入一次中断 获取系统信息
*/
void TIM2_IRQHandler(void)
{
// 判断标志位
if (TIM_GetITStatus(TIM2, TIM_IT_Update)==SET)
{
// 获取LED信息
Get_LEDInfo();
// 将信息发送给上位机
Transimit_Message();
// 清除标志位
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
main.c
#include "stm32f10x.h"
#include "delay.h"
#include "LED.h"
#include "key.h"
#include "Timer.h" // 定时器 10s 获取一次信息
#include "Serial.h" // 串口相关程序
// 上位机控制LED亮灭
int main(void)
{
LED1_Init();
LED2_Init();
Key_Init();
Timer_Init();
Serial_Init();
while(1)
{
// 接收到一个完整协议的数据 进行解析
if(idx >= 16)
{
// 解码
Decode_Flag = Decode_Info((char*)Up_String);
// 打印解码信息
Decode_Info_Print();
idx = 0;
}
}
}
效果展示
视频链接
【单片机作业: USART通信案例上位机控制LED灯亮灭】
参考资料
嵌入式单片机STM32原理及应用
【STM32入门教程-2023持续更新中】