【单片机与嵌入式】stm32串口通信入门

一、串口通信/协议

(一)串口通信简介

串口通信是一种通过串行传输方式在电子设备之间进行数据交换的通信方式。它通常涉及两条线(一条用于发送数据,一条用于接收数据),适用于各种设备,从微控制器到计算机等。串口通信的关键特点包括:

1.串行传输:数据位按照顺序一个接一个地传输,与并行传输相比,节省了引脚和线缆。
2.异步或同步传输:串口通信可以是异步的(通过单独的时钟信号进行数据同步)或同步的(通过时钟信号直接同步数据传输)。
3.通信速率:串口通信的速率通常以波特率(bps,每秒传输的位数)来衡量,典型的速率有9600、19200、38400、115200等。
4.简单性和广泛应用:串口通信协议相对简单,因此在许多嵌入式系统、传感器和计算机外围设备中广泛使用。
5.标准和协议:常见的串口标准包括RS-232、RS-485等,每种标准有特定的电气特性和通信协议。
6.通信模式:通信可以是单向的(如仅发送或仅接收),也可以是双向的(可以发送和接收数据)。

(二)通信模式(数据传输方式)

1、串行通信与并行通信

串行通信和并行通信是两种数据传输方式,它们有着明显的区别:

串行通信:

  定义:串行通信是一种逐位地传输数据的方式,数据位按照顺序一个接一个地传输
  传输方式:使用单条线路传输数据,一般包括发送线(TX)和接收线(RX)。
  优点:相比并行通信,串行通信可以减少线路和引脚数量,降低成本和复杂度。
  典型应用:常见于长距离通信和嵌入式系统,如串口通信(RS-232、RS-485)、USB等。


并行通信:

  定义:并行通信是同时传输多个数据位的方式,每个数据位使用一个独立的信号线路
  传输方式:通常使用多个并列的线路,每个线路传输一个数据位,同时进行。
  优点:传输速度快,适合于需要高速数据传输的应用。
  缺点:需要更多的线路和引脚,因此成本较高且布线复杂。
  典型应用:在计算机内部的数据总线(如PCI总线)、内存访问等地方常见并行通信。

可以将两种数据传输方式理解为“串联”与“并联”。

2、单工、半双工、全双工通信

单工、半双工和全双工通信是描述数据在通信中传输方向和能力的术语.单工通信适合于单向传输、无需反馈的应用;半双工通信适合于双向通信但不能同时进行;全双工通信适合需要同时双向传输数据的场合。

单工通信:

  定义:单工通信是指数据只能在一个方向上传输的通信方式。发送方只能发送数据,接收方只能接收数据,不能同时发送和接收。
  特点:通信是单向的,类似于单行道,信息只能在一个方向上流动。
  应用场景:常见于广播和一些简单的传感器网络中,如无线电广播、键盘到计算机的数据传输等。

半双工通信:

  定义:半双工通信允许数据在两个方向上传输,但不能同时进行。在某一时刻,通信设备要么发送数据,要么接收数据。
  特点:通信设备在发送数据时不能同时接收,反之亦然。类似于单行道,但是可以在两个方向上切换流量。
  应用场景:广泛应用于无线电对讲机、对讲电话以及以太网的半双工模式。


全双工通信:

  定义:全双工通信允许数据在两个方向上同时进行传输,发送和接收可以同时进行。
  特点:通信设备可以同时发送和接收数据,就像双车道道路一样,流量可以在两个方向上同时流动。
  应用场景:常见于电话通信、以太网等需要同时双向传输数据的场合,如互联网视频会议、数据中心的服务器通信等。

(三)串口协议和RS-232标准介绍

1、串口协议

串口协议通常指的是一种通过串行通信进行数据传输的通信协议。串口协议是通过串行通信传输数据的一种约定和规范,通常包括以下几个方面:

(1)数据格式:定义了数据位(通常为7或8位)、校验位(可选)、停止位(通常为1或2位)等格式。这些位组合在一起形成一个完整的数据帧。
(2)波特率:指数据传输速率,单位为波特(bps)。常见的波特率有9600、19200、38400、115200等。
(3)通信协议:指定了数据如何被解释、传输和接收。常见的协议包括ASCII码、Modbus、SPI等,具体的选择取决于应用的需求和设备的兼容性。
(4)硬件接口:定义了物理连接和电气特性,如信号电平、线路配置(如RS-232、RS-485)、引脚定义等。

2、RS-232标准

RS-232是最早的一种串行通信标准,定义了数据传输的电气特性和信号架构。它具有以下特点:

(1)电气特性:RS-232标准规定了发送和接收设备之间的电平范围。典型的RS-232电平为正负12V,用于表示逻辑1和逻辑0。
(2)连接方式:RS-232通常使用DB-9或DB-25连接器,分别具有9个或25个引脚,用于连接串口设备。
(3)信号线:RS-232定义了多条信号线,包括发送数据(TX)、接收数据(RX)、数据就绪(DSR)、数据载波检测(DCD)、请求发送(RTS)和清除发送(CTS)等。
(4)应用范围:RS-232广泛应用于计算机和外围设备之间的通信,如调制解调器、终端设备、打印机等。

尽管RS-232标准已经存在多年,但随着技术的进步,如USB和以太网的普及,RS-232在某些领域的使用逐渐减少。然而,它仍然是许多传统设备中不可或缺的通信接口之一,且在工业控制、测量设备等领域仍然广泛使用。

3、RS232电平与TTL电平的区别

RS-232和TTL电平在电气特性、应用场景和连接方式上有很大的不同。RS-232适用于长距离和抗干扰要求较高的通信环境,而TTL电平适合于数字电路和低功耗设备之间的短距离通信。选择合适的电平标准取决于具体的应用需求,包括通信距离、抗干扰能力、功耗和设备兼容性等因素。

RS-232电平

(1)RS-232标准定义了发送和接收设备之间的电平范围,典型的RS-232电平为正负12V。逻辑1通常对应于负电平(-3V 至 -15V之间),逻辑0对应于正电平(+3V 至 +15V之间)。这种电平范围使得RS-232在长距离传输和抗干扰能力方面表现优秀。

(2)RS-232通常用于需要较长传输距离(最长可达50英尺)和较高抗干扰能力的应用,如计算机串口、调制解调器、终端设备等。它适合于工业环境和长距离通信需求,但其电平范围较广,需要较多的电气和电子元件支持。

(3)RS-232通常使用DB-9DB-25连接器,这些连接器包含多个引脚,用于传输数据及控制信号。

TTL电平

(1)TTL(Transistor-Transistor Logic)电平是指通常在逻辑电路中使用的电平标准,典型的TTL电平是0V到5V。逻辑1通常为高电平(约3.3V至5V),逻辑0为低电平(约0V至0.8V),这种电平适合数字电路和集成电路之间的直接通信。

(2)TTL电平通常用于短距离通信和数字电路之间的通信,如微控制器与传感器之间的串行通信、逻辑电路板之间的通信等。由于其电平范围较窄,适合于低功耗应用和简化的通信接口

(3)TTL电平通常使用简单的单针或双针连接器,如用于Arduino等开发板的数字输入输出引脚。

(四)CH340(串口)

CH340模块是一种USB转串口芯片,能够将USB接口转换为异步串口通信接口,支持RS-232和TTL电平标准,适用于单片机开发、嵌入式系统及消费电子产品中,提供稳定的数据传输和低功耗解决方案。

USB/TTL转232

CH340是一个USB总线的转接芯片,实现USB转串口、USB转IrDA红外或者USB转打印口。为了增加串口通讯的远距离传输及抗干扰能力,RS-232标准使用-15V表示逻辑1,+15V 表示逻辑0。常常会使用MH340芯片对USB/TTL与RS- 232电平的信号进行转换。

二、标准库开发

为了解决不同芯片厂商生产的基于Cortex内核的微处理器在软件上的兼容问题,ARM公司与众多芯片和软件厂商共同制定了CMSIS标准(Cortex Microcontroller Software Interface Standard,Cortex微控制器软件接口标准),意在将所有Cortex芯片厂商产品的软件接口标准化。

固件库:

安装步骤:

img

导入后列表:

三、标准库点灯

(一)配置GPIO函数:

//1.打开APB2时钟(不懂为什么第一步是这个的可以参考我上一篇博客)RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//2.GPIO引脚设置GPIO_InitTypeDef GPIO_InitStructure;//GPIO结构体定义GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//设置GPIO功能模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;//设置作用GPIO引脚GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//配置GPIO速度GPIO_Init(GPIOB, &GPIO_InitStructure);//如果你设置的引脚是PB9,()内分别为GPIOB,&你定义的结构体//3.GPIO输出电平设置GPIO_ResetBits(GPIOB, GPIO_Pin_9);//低电平,点亮LED灯(因为我们的LED灯正极接电源侧,负极接引脚PB9,要使LED亮需要使PB9输出低电平导通)GPIO_SetBits(GPIOB, GPIO_Pin_9);//高电平,LED灯不亮

接线图:

(二)完整点灯代码:

​
#include "stm32f10x.h"                  // Device header
#include "Delay.h"     
​
int main(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //开启GPIOA的时钟//使用各个外设前必须开启时钟,否则对外设的操作无效/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;                    //定义结构体变量GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;        //GPIO模式,赋值为推挽输出模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;               //GPIO引脚,赋值为第0号引脚GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;       //GPIO速度,赋值为50MHzGPIO_Init(GPIOA, &GPIO_InitStructure);                  //将赋值后的构体变量传递给GPIO_Init函数//函数内部会自动根据结构体的参数配置相应寄存器//实现GPIOA的初始化/*主循环,循环体内的代码会一直循环执行*/while (1){/*设置PA0引脚的高低电平,实现LED闪烁,下面展示3种方法*//*方法1:GPIO_ResetBits设置低电平,GPIO_SetBits设置高电平*/GPIO_ResetBits(GPIOA, GPIO_Pin_0);                  //将PA0引脚设置为低电平Delay_ms(500);                                      //延时500msGPIO_SetBits(GPIOA, GPIO_Pin_0);                    //将PA0引脚设置为高电平Delay_ms(500);                                      //延时500ms/*方法2:GPIO_WriteBit设置低/高电平,由Bit_RESET/Bit_SET指定*/GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);        //将PA0引脚设置为低电平Delay_ms(500);                                      //延时500msGPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);          //将PA0引脚设置为高电平Delay_ms(500);                                      //延时500ms/*方法3:GPIO_WriteBit设置低/高电平,由数据0/1指定,数据需要强转为BitAction类型*/GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0);     //将PA0引脚设置为低电平Delay_ms(500);                                      //延时500msGPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1);     //将PA0引脚设置为高电平Delay_ms(500);                                      //延时500ms}
}​

四、标准库串口通信实现

LED GPIO 初始化:

#include "stm32f10x.h"
#include "OLED_Font.h"
​
/*引脚配置*/
#define OLED_W_SCL(x)       GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x)       GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))
​
/*引脚初始化*/
void OLED_I2C_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_Init(GPIOB, &GPIO_InitStructure);OLED_W_SCL(1);OLED_W_SDA(1);
}
​
/*** @brief  I2C开始* @param  无* @retval 无*/
void OLED_I2C_Start(void)
{OLED_W_SDA(1);OLED_W_SCL(1);OLED_W_SDA(0);OLED_W_SCL(0);
}
​
/*** @brief  I2C停止* @param  无* @retval 无*/
void OLED_I2C_Stop(void)
{OLED_W_SDA(0);OLED_W_SCL(1);OLED_W_SDA(1);
}
​
/*** @brief  I2C发送一个字节* @param  Byte 要发送的一个字节* @retval 无*/
void OLED_I2C_SendByte(uint8_t Byte)
{uint8_t i;for (i = 0; i < 8; i++){OLED_W_SDA(Byte & (0x80 >> i));OLED_W_SCL(1);OLED_W_SCL(0);}OLED_W_SCL(1);  //额外的一个时钟,不处理应答信号OLED_W_SCL(0);
}
​
/*** @brief  OLED写命令* @param  Command 要写入的命令* @retval 无*/
void OLED_WriteCommand(uint8_t Command)
{OLED_I2C_Start();OLED_I2C_SendByte(0x78);        //从机地址OLED_I2C_SendByte(0x00);        //写命令OLED_I2C_SendByte(Command); OLED_I2C_Stop();
}
​
/*** @brief  OLED写数据* @param  Data 要写入的数据* @retval 无*/
void OLED_WriteData(uint8_t Data)
{OLED_I2C_Start();OLED_I2C_SendByte(0x78);        //从机地址OLED_I2C_SendByte(0x40);        //写数据OLED_I2C_SendByte(Data);OLED_I2C_Stop();
}
​
/*** @brief  OLED设置光标位置* @param  Y 以左上角为原点,向下方向的坐标,范围:0~7* @param  X 以左上角为原点,向右方向的坐标,范围:0~127* @retval 无*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{OLED_WriteCommand(0xB0 | Y);                    //设置Y位置OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));    //设置X位置高4位OLED_WriteCommand(0x00 | (X & 0x0F));           //设置X位置低4位
}
​
/*** @brief  OLED清屏* @param  无* @retval 无*/
void OLED_Clear(void)
{  uint8_t i, j;for (j = 0; j < 8; j++){OLED_SetCursor(j, 0);for(i = 0; i < 128; i++){OLED_WriteData(0x00);}}
}
​
/*** @brief  OLED显示一个字符* @param  Line 行位置,范围:1~4* @param  Column 列位置,范围:1~16* @param  Char 要显示的一个字符,范围:ASCII可见字符* @retval 无*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{       uint8_t i;OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);       //设置光标位置在上半部分for (i = 0; i < 8; i++){OLED_WriteData(OLED_F8x16[Char - ' '][i]);          //显示上半部分内容}OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);   //设置光标位置在下半部分for (i = 0; i < 8; i++){OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);      //显示下半部分内容}
}
​
/*** @brief  OLED显示字符串* @param  Line 起始行位置,范围:1~4* @param  Column 起始列位置,范围:1~16* @param  String 要显示的字符串,范围:ASCII可见字符* @retval 无*/
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{uint8_t i;for (i = 0; String[i] != '\0'; i++){OLED_ShowChar(Line, Column + i, String[i]);}
}
​
/*** @brief  OLED次方函数* @retval 返回值等于X的Y次方*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{uint32_t Result = 1;while (Y--){Result *= X;}return Result;
}
​
/*** @brief  OLED显示数字(十进制,正数)* @param  Line 起始行位置,范围:1~4* @param  Column 起始列位置,范围:1~16* @param  Number 要显示的数字,范围:0~4294967295* @param  Length 要显示数字的长度,范围:1~10* @retval 无*/
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{uint8_t i;for (i = 0; i < Length; i++)                            {OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');}
}
​
/*** @brief  OLED显示数字(十进制,带符号数)* @param  Line 起始行位置,范围:1~4* @param  Column 起始列位置,范围:1~16* @param  Number 要显示的数字,范围:-2147483648~2147483647* @param  Length 要显示数字的长度,范围:1~10* @retval 无*/
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{uint8_t i;uint32_t Number1;if (Number >= 0){OLED_ShowChar(Line, Column, '+');Number1 = Number;}else{OLED_ShowChar(Line, Column, '-');Number1 = -Number;}for (i = 0; i < Length; i++)                            {OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');}
}
​
/*** @brief  OLED显示数字(十六进制,正数)* @param  Line 起始行位置,范围:1~4* @param  Column 起始列位置,范围:1~16* @param  Number 要显示的数字,范围:0~0xFFFFFFFF* @param  Length 要显示数字的长度,范围:1~8* @retval 无*/
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{uint8_t i, SingleNumber;for (i = 0; i < Length; i++)                            {SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;if (SingleNumber < 10){OLED_ShowChar(Line, Column + i, SingleNumber + '0');}else{OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');}}
}
​
/*** @brief  OLED显示数字(二进制,正数)* @param  Line 起始行位置,范围:1~4* @param  Column 起始列位置,范围:1~16* @param  Number 要显示的数字,范围:0~1111 1111 1111 1111* @param  Length 要显示数字的长度,范围:1~16* @retval 无*/
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{uint8_t i;for (i = 0; i < Length; i++)                            {OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');}
}
​
/*** @brief  OLED初始化* @param  无* @retval 无*/
void OLED_Init(void)
{uint32_t i, j;for (i = 0; i < 1000; i++)          //上电延时{for (j = 0; j < 1000; j++);}OLED_I2C_Init();            //端口初始化OLED_WriteCommand(0xAE);    //关闭显示OLED_WriteCommand(0xD5);    //设置显示时钟分频比/振荡器频率OLED_WriteCommand(0x80);OLED_WriteCommand(0xA8);    //设置多路复用率OLED_WriteCommand(0x3F);OLED_WriteCommand(0xD3);    //设置显示偏移OLED_WriteCommand(0x00);OLED_WriteCommand(0x40);    //设置显示开始行OLED_WriteCommand(0xA1);    //设置左右方向,0xA1正常 0xA0左右反置OLED_WriteCommand(0xC8);    //设置上下方向,0xC8正常 0xC0上下反置
​OLED_WriteCommand(0xDA);    //设置COM引脚硬件配置OLED_WriteCommand(0x12);OLED_WriteCommand(0x81);    //设置对比度控制OLED_WriteCommand(0xCF);
​OLED_WriteCommand(0xD9);    //设置预充电周期OLED_WriteCommand(0xF1);
​OLED_WriteCommand(0xDB);    //设置VCOMH取消选择级别OLED_WriteCommand(0x30);
​OLED_WriteCommand(0xA4);    //设置整个显示打开/关闭
​OLED_WriteCommand(0xA6);    //设置正常/倒转显示
​OLED_WriteCommand(0x8D);    //设置充电泵OLED_WriteCommand(0x14);
​OLED_WriteCommand(0xAF);    //开启显示OLED_Clear();               //OLED清屏
}

波特率配置:

img

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>
​
/*** 函    数:串口初始化* 参    数:无* 返 回 值:无*/
void Serial_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);  //开启USART1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //开启GPIOA的时钟/*GPIO初始化*/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);                  //将PA9引脚初始化为复用推挽输出/*USART初始化*/USART_InitTypeDef USART_InitStructure;                  //定义结构体变量USART_InitStructure.USART_BaudRate = 9600;              //波特率USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要USART_InitStructure.USART_Mode = USART_Mode_Tx;         //模式,选择为发送模式USART_InitStructure.USART_Parity = USART_Parity_No;     //奇偶校验,不需要USART_InitStructure.USART_StopBits = USART_StopBits_1;  //停止位,选择1位USART_InitStructure.USART_WordLength = USART_WordLength_8b;     //字长,选择8位USART_Init(USART1, &USART_InitStructure);               //将结构体变量交给USART_Init,配置USART1/*USART使能*/USART_Cmd(USART1, ENABLE);                              //使能USART1,串口开始运行
}
​
/*** 函    数:串口发送一个字节* 参    数:Byte 要发送的一个字节* 返 回 值:无*/
void Serial_SendByte(uint8_t Byte)
{USART_SendData(USART1, Byte);       //将字节数据写入数据寄存器,写入后USART自动生成时序波形while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);   //等待发送完成/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
​
/*** 函    数:串口发送一个数组* 参    数:Array 要发送数组的首地址* 参    数:Length 要发送数组的长度* 返 回 值:无*/
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{uint16_t i;for (i = 0; i < Length; i ++)       //遍历数组{Serial_SendByte(Array[i]);      //依次调用Serial_SendByte发送每个字节数据}
}
​
/*** 函    数:串口发送一个字符串* 参    数:String 要发送字符串的首地址* 返 回 值:无*/
void Serial_SendString(char *String)
{uint8_t i;for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止{Serial_SendByte(String[i]);     //依次调用Serial_SendByte发送每个字节数据}
}
​
/*** 函    数:次方函数(内部使用)* 返 回 值:返回值等于X的Y次方*/
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{uint32_t Result = 1;    //设置结果初值为1while (Y --)            //执行Y次{Result *= X;        //将X累乘到结果}return Result;
}
​
/*** 函    数:串口发送数字* 参    数:Number 要发送的数字,范围:0~4294967295* 参    数:Length 要发送数字的长度,范围:0~10* 返 回 值:无*/
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{uint8_t i;for (i = 0; i < Length; i ++)       //根据数字长度遍历数字的每一位{Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');    //依次调用Serial_SendByte发送每位数字}
}
​
/*** 函    数:使用printf需要重定向的底层函数* 参    数:保持原始格式即可,无需变动* 返 回 值:保持原始格式即可,无需变动*/
int fputc(int ch, FILE *f)
{Serial_SendByte(ch);            //将printf的底层重定向到自己的发送字节函数return ch;
}
​
/*** 函    数:自己封装的prinf函数* 参    数:format 格式化字符串* 参    数:... 可变的参数列表* 返 回 值:无*/
void Serial_Printf(char *format, ...)
{char String[100];               //定义字符数组va_list arg;                    //定义可变参数列表数据类型的变量argva_start(arg, format);          //从format开始,接收参数列表到arg变量vsprintf(String, format, arg);  //使用vsprintf打印格式化字符串和参数列表到字符数组中va_end(arg);                    //结束变量argSerial_SendString(String);      //串口发送字符数组(字符串)
}

主函数代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
​
int main(void)
{/*模块初始化*/OLED_Init();                        //OLED初始化Serial_Init();                      //串口初始化/*串口基本函数*/Serial_SendByte(0x41);              //串口发送一个字节数据0x41uint8_t MyArray[] = {0x42, 0x43, 0x44, 0x45};   //定义数组Serial_SendArray(MyArray, 4);       //串口发送一个数组//Serial_SendString("hello windows!");      //串口发送字符串Serial_SendNumber(111, 3);          //串口发送数字/*下述3种方法可实现printf的效果*//*方法1:直接重定向printf,但printf函数只有一个,此方法不能在多处使用*/printf("\r\nNum2=%d", 222);         //串口发送printf打印的格式化字符串//需要重定向fputc函数,并在工程选项里勾选Use MicroLIB/*方法2:使用sprintf打印到字符数组,再用串口发送字符数组,此方法打印到字符数组,之后想怎么处理都可以,可在多处使用*/char String[100];                   //定义字符数组sprintf(String, "\r\nNum3=%d", 333);//使用sprintf,把格式化字符串打印到字符数组Serial_SendString(String);          //串口发送字符数组(字符串)/*方法3:将sprintf函数封装起来,实现专用的printf,此方法就是把方法2封装起来,更加简洁实用,可在多处使用*/Serial_Printf("\r\nNum4=%d", 444);  //串口打印字符串,使用自己封装的函数实现printf的效果Serial_Printf("\r\n");while (1){Serial_SendString("hello windows");Serial_Printf("\r\n");}
}

结果演示:

STM32以查询方式接收上位机(win10)串口发来的数据,如果接收到“Y”则点亮链接到stm32上的一个LED灯;接收到“N”则熄灭LED灯。

代码演示:

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
​
void USART_Config(void)
{
//1.开启GPIOA和USART1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);//2.结构体定义GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;//3.USART设置RX/TX//USART1_TX,默认情况下复用PA9引脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure); //USART1_RX,默认情况下复用PA10引脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure); //4.USART1参数配置USART_InitStructure.USART_BaudRate = 9600;//设置波特率为9600USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据位占8位USART_InitStructure.USART_StopBits = USART_StopBits_1; //1位停止位USART_InitStructure.USART_Parity = USART_Parity_No; //无校验USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;USART_Init(USART1, &USART_InitStructure);
​USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStructure);//5.初始化串口1USART_Cmd(USART1, ENABLE); //使能串口1
​
}
​
//重定向c 库函数printf 到串口,重定向后可使用printf 函数
int fputc(int ch, FILE *f)
{/* 发送一个字节数据到串口 */USART_SendData(USART1, (uint8_t) ch);/* 等待发送完毕 */while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);return (ch);
}
​
///重定向c 库函数scanf 到串口,重写向后可使用scanf、getchar 等函数
int fgetc(FILE *f)
{/* 等待串口输入数据 */while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);return (int)USART_ReceiveData(USART1);
}
​
int main(void)
{USART_Config();//1.开启GPIOB时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//2.结构体定义GPIO_InitTypeDef GPIO_InitStructure;//3.GPIO配置  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure); //4.初始化灯GPIO_SetBits(GPIOA,GPIO_Pin_4);char ch;while(1){printf("请输入指令:Y亮灯,N灭灯!");ch=getchar();printf("接收到字符:%c\n",ch);switch(ch){case 'N':GPIO_SetBits(GPIOA,GPIO_Pin_4);break;case 'Y':GPIO_ResetBits(GPIOA,GPIO_Pin_4);break;default:break;}// 等待一段时间,以便在串口调试工具中可以看到消息之间的间隔  for (uint32_t i = 0; i < 10000000; i++);    }                                       
}
#include "stm32f10x.h" // Device header
#include "Serial.h"
//操作IO口的三个步骤
//1、使用RCC开启GPIO时钟
//2、使用GPIO_Init函数初始化GPIO
//3、使用输出或输入函数控制GPIO口
uint8_t KeyNum;
uint8_t RxData;
int main()
{OLED_Init();Serial_Init();GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);//将A6口初始化为电平
//GPIO_SetBits(GPIOA,GPIO_Pin_6);另一种初始化函数while(1){if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)//若接收寄存器数据转到RDR中,则RXNE标志位置一,标志位自动清零{RxData=USART_ReceiveData(USART1);if(RxData=='Y'){GPIO_ResetBits(GPIOA,GPIO_Pin_6);}if(RxData=='N'){GPIO_SetBits(GPIOA,GPIO_Pin_6);}}}
}

五、Keil的仿真逻辑分析仪观察时序波形

img

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/369067.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零到一:eBay自养号测评全流程解析与实操建议

eBay自养号测评是一种通过模拟真实买家行为&#xff0c;为卖家提供市场反馈并提升店铺权重和排名的技术手段。以下是进行eBay自养号测评的具体步骤和注意事项&#xff1a; 一、准备阶段 1. 技术配置&#xff1a;搭建境外服务器&#xff1a;选择稳定的境外服务器&#xff0c;模…

【LeetCode:841. 钥匙和房间 + DFS】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【等保2.0是什么意思?等保2.0的基本要求有哪些? 】

一、等保2.0是什么意思&#xff1f; 等保2.0又称“网络安全等级保护2.0”体系&#xff0c;它是国家的一项基本国策和基本制度。在1.0版本的基础上&#xff0c;等级保护标准以主动防御为重点&#xff0c;由被动防守转向安全可信&#xff0c;动态感知&#xff0c;以及事前、事中…

Linux之文本三剑客

Linux之三剑客 Linux的三个命令,主要是用来处理文本,grep,sed,awk,处理日志的时候使用的非常多 1 grep 对文本的内容进行查找 1) 基础用法 语法 grep 选项 内容|正则表达式 文件选项: -i 不区分大小写 -v 排除,反选 -n 显示行号 -c 统计个数查看文件里包含有的内容 [roo…

【linux网络(七)】数据链路层详解

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; Linux网络 1. 前言2. 认识MAC…

从硬件角度看Linux的内存管理

1. 分页机制 分段机制的地址映射颗粒度太大&#xff0c;以整个进程地址空间为单位的分配方式导致内存利用率不高。 分页机制把这个分配机制的单位继续细化为固定大小的页(Page)&#xff0c;进程的虚拟地址空间也按照页来分割&#xff0c;这样常用的数据和代码就可以以页为单位…

uniapp中实现跳转链接到游览器(安卓-h5)

uniapp中实现跳转链接到游览器&#xff08;安卓-h5&#xff09; 项目中需要做到跳转到外部链接&#xff0c;网上找了很多都不是很符合自己的要求&#xff0c;需要编译成app后是跳转到游览器打开链接&#xff0c;编译成web是在新窗口打开链接。实现的代码如下&#xff1a; 效果&…

某安全公司DDoS攻击防御2024年6月报告

引言&#xff1a; 在2024年6月&#xff0c;网络空间的安全挑战汹涌澎湃。分布式拒绝服务&#xff08;DDoS&#xff09;攻击频发&#xff0c;针对云服务、金融科技及在线教育平台的精密打击凸显出当前网络威胁环境的严峻性。 某安全公司作为网络安全防护的中坚力量&#xff0c…

QT5.12环境搭建与源码编译

一、概述 QT版本&#xff1a;QT5.12.10 Qt网址&#xff1a;http://download.qt.io/archive/qt/ 编译平台 ubuntu18.04 二、安装交叉编译工具链 1、获取交叉编译工具链 一般如果是编译系统如果有对应的gcc 就是用这个就可以了 比如rk3128 lin…

Vue 详情实战涉及从项目初始化到功能实现、测试及部署的整个过程

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

机器人入门路线及参考资料(机器人操作方向)

机器人入门路线及参考资料&#xff08;机器人操作方向&#xff09; 前言1 数理基础和编程2 机器人学理论3 计算机视觉4 机器人实操5 专攻方向总结Reference: 前言 随着机器人和具身智能时代的到来&#xff0c;机器人越来越受到大家的重视&#xff0c;本文就介绍了机器人&#…

震惊!张宇25版高数18讲发布,656页惹争议!

这个张宇老师在微博已经解释过了&#xff01; 我觉得张宇老师本意是好的&#xff0c;在考研数学教学创新这方面&#xff0c;他真的有自己的思考。 他为什么要这么做&#xff1f; 其实作为一个考研高数老师&#xff0c;他完全可以像其他老师一样&#xff0c;什么都不做&#x…

武汉免费 【FPGA实战训练】 Vivado入门与设计师资课程

一&#xff0e;背景介绍 当今高度数字化和智能化的工业领域&#xff0c;对高效、灵活且可靠的技术解决方案的需求日益迫切。随着工业 4.0 时代的到来&#xff0c;工业生产过程正经历着前所未有的变革&#xff0c;从传统的机械化、自动化逐步迈向智能化和信息化。在这一背景下&…

11 - matlab m_map地学绘图工具基础函数 - 绘制航迹、椭圆、风向玫瑰图和特定的圆形区域的有关函数及其用法

11 - matlab m_map地学绘图工具基础函数 - 绘制航迹、椭圆、风向玫瑰图和特定的圆形区域的有关函数及其用法 0. 引言1. 关于m_track2. 关于m_range_ring3. 关于m_ellipse4. 关于m_windrose5. 结语 0. 引言 本篇介绍下m_map中绘制航迹图函数&#xff08;m_track&#xff09;、绘…

Redis深度解析:核心数据类型与键操作全攻略

文章目录 前言redis数据类型string1. 设置单个字符串数据2.设置多个字符串类型的数据3.字符串拼接值4.根据键获取字符串的值5.根据多个键获取多个值6.自增自减7.获取字符串的长度8.比特流操作key操作a.查找键b.设置键值的过期时间c.查看键的有效期d.设置key的有效期e.判断键是否…

AI绘画Stable Diffusion 新手入门教程:万字长文解析Lora模型的使用,快速上手Lora模型!

大家好&#xff0c;我是设计师阿威 今天给大家讲解一下AI绘画Stable Diffusion 中的一个重要模型—Lora模型&#xff0c;如果还有小伙伴没有SD安装包的&#xff0c;可以看我往期入门教程2024最新超强AI绘画Stable Diffusion整合包安装教程&#xff0c;零基础入门必备&#xff…

项目基础知识

1.JDBC编程和MySQL数据库 数据库的连接&#xff08;以前写qq项目时的代码&#xff09; package com.wu.Util; import java.sql.*; public class JDBCUtil {private static JDBCUtil jdbcUtil null;private JDBCUtil() {}public static JDBCUtil getJdbcUtil() {if (jdbcUtil…

超融合服务器挂载硬盘--linux系统

项目中需要增加服务器的硬盘容量&#xff0c;通过超融合挂载了硬盘后&#xff0c;还需要添加到指定的路径下&#xff0c;这里记录一下操作步骤。 一&#xff1a;通过管理界面挂载硬盘 这一步都是界面操作&#xff0c;登录超融合控制云台后&#xff0c;找到对应的服务器&#…

Qt之Pdb生成及Dump崩溃文件生成与调试(含注释和源码)

文章目录 一、Pdb生成及Dump文件使用示例图1.Pdb文件生成2.Dump文件调试3.参数不全Pdb生成的Dump文件调试 二、个人理解1.生成Pdb文件的方式2.Dump文件不生产的情况 三、源码Pro文件mian.cppMainWindowUi文件 总结 一、Pdb生成及Dump文件使用示例图 1.Pdb文件生成 下图先通过…

Websocket通信实战项目(图片互传应用)+PyQt界面+python异步编程(async) (上)服务器端python实现

Rqtz : 个人主页 ​​ 共享IT之美&#xff0c;共创机器未来 ​ Sharing the Beauty of IT and Creating the Future of Machines Together 目录 项目背景 ​编辑​专有名词介绍 服务器GUI展示 功能(位置见上图序号) 客户端GUI展示&#xff08;h5cssjs&#xf…