目录
前言
上一期我发布了USART串口之间通讯的理论知识(链接:stm32入门-----USART串口通讯(上——理论篇)-CSDN博客),那么本期我们主要去学习USART串口通讯的项目实践。本次项目有两个部分,第一个就是USART串口实现stm32向电脑发生数据,第二个就是USART串口实现stm32从电脑接收数据,总体来说就是stm32与电脑互相接收和发送数据。(视频:[9-3] 串口发送&串口发送+接收_哔哩哔哩_bilibili)
本期项目的全部代码以及串口助手软件都在百度网盘,可自行下载。
链接:https://pan.baidu.com/s/1qvBbwlQiRIMS2CumzX8jLw?pwd=0721
提取码:0721
一、数据编码
在学习代码之前,我们需要了解一个很重要的东西也就是数据编码,其实这部分大家应该都不陌生的说。比如阿斯克码,每一个我们常见的字符都有对应的编码样式,所以数据的发送是需要去进行编码和解码的,而不是直接发送过去的,所以这部分我们还是需要学习一下的。
本期我们主要是用串口助手来去查看发送和接收到的数据,主要了解HEX模式的数据和文本模式的数据。
- HEX模式/十六进制模式/二进制模式:以原始数据的形式显示
- 文本模式/字符模式:以原始数据编码后的形式显示
入下图所示,我们的文本数据在USART之间是以HEX编码模式来传输的,到达对方我们可以进行译码查看到文本数据。
二、发送——C语言编程步骤
1.初始化步骤
首先我们先看到stm32的引脚图,这里我们以USART1为示例使用,如果想用USART2等其他的可以去查看引脚表。在stm32中USART1对应的发送和接收引脚是PA9和PA10。
同样的我们根据下面的结构图去把通道打通配置就行了。
1.1开启时钟
//1.开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
1.2配置GPIO口引脚
//2.配置引脚,这里只需要配置输出
GPIO_InitTypeDef GPIO_initstruct;
GPIO_initstruct.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_initstruct.GPIO_Pin=GPIO_Pin_9;
GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_initstruct);
1.3初始化USART
//3.初始化USART
USART_InitTypeDef USART_initstruct;
USART_initstruct.USART_BaudRate=9600; //波特率,直接写就行了,初始化会自动计算和配置好的
USART_initstruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//硬件流控,这里选择不使用
USART_initstruct.USART_Mode=USART_Mode_Tx;//选择串口模式,这里我们只需要发送所以选择发送模式即可
USART_initstruct.USART_Parity=USART_Parity_No;//选择校验位,这里不需要选择no
USART_initstruct.USART_StopBits=USART_StopBits_1;//选择停止位,这里选1位
USART_initstruct.USART_WordLength=USART_WordLength_8b; //字长,可选8位或者9位,这里没有校验所以选择8位
USART_Init(USART1,&USART_initstruct);
1.4USART使能
//4.开启USART接口
USART_Cmd(USART1,ENABLE);
以上四步就搞定USART发送功能的初始化,整体代码如下:
void Serial_init(){
//1.开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//2.配置引脚,这里只需要配置输出
GPIO_InitTypeDef GPIO_initstruct;
GPIO_initstruct.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_initstruct.GPIO_Pin=GPIO_Pin_9;
GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_initstruct);
//3.初始化USART
USART_InitTypeDef USART_initstruct;
USART_initstruct.USART_BaudRate=9600; //波特率,直接写就行了,初始化会自动计算和配置好的
USART_initstruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//硬件流控,这里选择不使用
USART_initstruct.USART_Mode=USART_Mode_Tx;//选择串口模式,这里我们只需要发送所以选择发送模式即可
USART_initstruct.USART_Parity=USART_Parity_No;//选择校验位,这里不需要选择no
USART_initstruct.USART_StopBits=USART_StopBits_1;//选择停止位,这里选1位
USART_initstruct.USART_WordLength=USART_WordLength_8b; //字长,可选8位或者9位,这里没有校验所以选择8位
USART_Init(USART1,&USART_initstruct);
//4.开启USART接口
USART_Cmd(USART1,ENABLE);
}
2.发送数据步骤
2.1发送单个无符号类型数据
下面是对这个函数的定义图,这里我们可以看到,最后是对DR寄存器进行写入的操作,然后把数据发送出去。
这里我们可以去把这个功能的函数封装起来,代码如下:
//发送一个字符数据
void Serial_sendbyte(uint8_t byte){
USART_SendData(USART1,byte);//把要写入的数据写入到DR寄存器(TDR)
//当DR寄存器完成了移位到移位寄存器后,下面就去判断是否完成标志位1,然后再去对DR寄存器清零(清零是硬件自动处理的),重新放入数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
}
2.1发送数组
发送数组,实际上我们只需要对这个数组进行遍历,然后一位一位发送出去即可,所以实际上就是对上面发送单个无符号类型数据的功能进行封装。
// 发送一个数组
void Serial_sendarray(uint8_t * array,uint16_t length){
uint16_t i;
for(i=0;i<length;i++){
Serial_sendbyte(array[i]);
}
}
2.3发生字符串
同样的,发送字符串也是一个一个发送出去,不同与数组的是,字符串不需要去确定长度,字符串本身是有一个终止符 '\0' 的,所以只需要识别到就结束即可。
//发送字符串
void Serial_sendstring(char* string){
uint8_t i;
for(i=0;string[i]!='\0';i++){
Serial_sendbyte(string[i]);
}
}
2.4发送有符号整形数字
发送数字的话,我们是需要把数字去进行类型转换为字符串然后再去发送,具体怎么转换的,方法很多,学过C语言的都应该知道怎么去转换了,下面我就给出一个样例。
//发送数字,转字符串
uint32_t Serial_pow(uint32_t x,uint32_t y){
uint32_t result=1;
while (y--)
{
result*=x;
}
return result;
}
void Serial_sendnumber(uint32_t num,uint8_t length){
uint8_t i;
for(i=0;i<length;i++){
Serial_sendbyte(num/Serial_pow(10,length-i-1)%10+'0');
}
}
2.5重定向printf和sprintf函数
这两个函数我们都不陌生了吧,是标准输入输出里面的函数,对于printf函数,这个是打印函数,打印的结果只能在我们电脑显示出来,但是如果我想去把这个作为一个数据写入的函数,我们就需要去对其进行底层的重映射。
//fputc重定向
//重定向printf ,这个函数的底层实际上就是fputc,多次调用
int fputc(int ch,FILE*f){
Serial_sendbyte(ch);
return ch;
}
sprintf函数是常见的格式化函数,这里我们也行把其映射为数据写入函数,同样的我们也要去进行修改,这里要用到的是#include <stdarg.h>里面的功能。
//对sprintf进行封装,可变参数格式 头文件 #include <stdarg.h>
void Serial_sprintf(char* format,...){
char str[100];
va_list arg;
va_start(arg,format);
vsprintf(str,format,arg);
va_end(arg);
Serial_sendstring(str);
}
3.项目示例
我们把上面提到的函数封装到Serial文件里面,工程文件:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
int main(void)
{
OLED_Init();
Serial_init();
Serial_sendbyte('A');
uint8_t arr[] = { "1234" };
Serial_sendarray(arr, 4);
Serial_sendnumber(12345,5);
Serial_sendstring("hello word\r\n");//如果想换行就要加上\r\n,表示回车换行
printf("num=%d\r\n", 66666);
Serial_sprintf("我爱中国\r\n");
while(1){
}
}
显示结果如下,这里我们要把波特率设置到跟我们上面配置的一样9600,接受模式选择文本模式即可,这里要注意一下文本编码是否跟代码的文本编码一致,不然中文会显示乱码,我这里是utf8编码的。
三、接收——C语言编程步骤
1.初始化
实际上接收和发送的初始化是一样的,不过只需要进行添加和修改即可,其中要修改的有三个部分:
(1)配置接收部分的引脚
(2)初始化USART添加RX模式
(3)如果要通过中断来接收数据,那就要去配置中断的NVIC(如果用直接查询的话就不需要这一条)
初始化代码如下:
void Serial_init(){
//1.开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//2.配置引脚,这里只需要配置输出
//发送部分
GPIO_InitTypeDef GPIO_initstruct;
GPIO_initstruct.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_initstruct.GPIO_Pin=GPIO_Pin_9;
GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_initstruct);
//接收部分
GPIO_initstruct.GPIO_Mode=GPIO_Mode_IPU; //上拉输入浮空输入下拉输入都行
GPIO_initstruct.GPIO_Pin=GPIO_Pin_10;
GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_initstruct);
//3.初始化USART
USART_InitTypeDef USART_initstruct;
USART_initstruct.USART_BaudRate=9600; //波特率,直接写就行了,初始化会自动计算和配置好的
USART_initstruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//硬件流控,这里选择不使用
USART_initstruct.USART_Mode=USART_Mode_Tx | USART_Mode_Rx;//选择串口模式,选择RX或上TX,发送和接收都有
USART_initstruct.USART_Parity=USART_Parity_No;//选择校验位,这里不需要选择no
USART_initstruct.USART_StopBits=USART_StopBits_1;//选择停止位,这里选1位
USART_initstruct.USART_WordLength=USART_WordLength_8b; //字长,可选8位或者9位,这里没有校验所以选择8位
USART_Init(USART1, &USART_initstruct);
//开启RX读取的中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_initstruct;
NVIC_initstruct.NVIC_IRQChannel = USART1_IRQn; //选择中断的通道
NVIC_initstruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_initstruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_initstruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_initstruct);
//4.开启USART接口
USART_Cmd(USART1, ENABLE);
}
2.项目示例
这里我会展示查询法和中断法接收数据这两种方法的示例,虽然两种方法的效果是一致的,但是中断法是更好的,可以去节省软件的资源。
先看实验现象:
注意发送区和接收区文本编码一致。
电路连线图:
工程文件:
2.1查询法
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
int main(void)
{
OLED_Init();
Serial_init();
OLED_ShowString(1, 1, "Rxdata:");
while (1) {
//下面是查询方法,也就是一直去判断DR读取标志位是否为1,如果为1那就有数据进来,获取这些数据.
if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) {
data = USART_ReceiveData(USART1); //这里DR读完后,标志位硬件会自动清除的
OLED_ShowHexNum(1, 1, data, 2);
}
}
}
2.2中断法(推荐)
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
uint8_t data;
uint8_t flag;
uint8_t Serial_getflag();
uint8_t Serial_getdata();
int main(void)
{
OLED_Init();
Serial_init();
OLED_ShowString(1, 1, "Rxdata:");
while (1) {
if (Serial_getflag()) {
data = Serial_getdata();//获取数据
Serial_sendbyte(data); //回传到电脑上
OLED_Clear();
OLED_ShowString(1, 1, "Rxdata:");
OLED_ShowChar(1, 8, data);
}
}
}
//中断标志位
uint8_t Serial_getflag() {
if (flag) {
flag = 0;
return 1;
}
return 0;
}
//获取数据
uint8_t Serial_getdata() {
return data;
}
//中断读取
void USART1_IRQHandler() {
//先判断标志位
if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) {
//对于标志位,如果读取了DR的值,那么硬件会自动清除,如果什么都不做,那得手动去清除
data = USART_ReceiveData(USART1);
flag = 1;
USART_ClearITPendingBit(USART1, USART_IT_RXNE);//清除中断标志位
}
}
对比上面两部分代码我们就可以看出,查询法是每次循环都要去判断是否有数据被读取,而中断法代码相对比较多,但是不用每次循环都去判断是否有数据要读取,当有数据的读取时候只需要去中断操作即可,极大的提高了程序的运行效率。
以上就是本期的全部内容了,我们下次见!
今日壁纸: