一、串口通信/协议
(一)串口通信简介
串口通信是一种通过串行传输方式在电子设备之间进行数据交换的通信方式。它通常涉及两条线(一条用于发送数据,一条用于接收数据),适用于各种设备,从微控制器到计算机等。串口通信的关键特点包括:
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-9或DB-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芯片厂商产品的软件接口标准化。
固件库:
安装步骤:
导入后列表:
三、标准库点灯
(一)配置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清屏
}
波特率配置:
#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);}}}
}