整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

STM32 学习8 USART串口通讯与printf

STM32 学习8 USART串口通讯与printf重定向


、串口通信介绍

STM32 F103ZET6包含多个UART、USART串口。

1. USART介绍

USART,全称:Universal Synchronous/Asynchronous Receiver/Transmitter,是通用同步/异步串行接收/发送器,主要特点有:

  • 同步和异步通信
  • 全双工通信
  • 支持硬件和软件流控制机制

2. UART介绍

UART,全称:Universal Asynchronous Receiver/Transmitter,是通用异步收发器,在USART功能的基础上,裁剪掉了同步通信功能,其主要特点:

  • 异步通信
  • 全双工通信
  • 无需外部时钟信号

3. STM32 F103ZET6串口资源

STM32 F103ZET6芯片,有5个USART接口,数据手册可在官网查询:
https://www.st.com/zh/microcontrollers-microprocessors/stm32f103.html

根据手册的描述:

STM32F103xC、STM32F103xD和STM32F103xE性能型系列集成了:

  • 三个通用同步/异步串行收发器(USART1、USART2和USART3)
  • 两个通用异步串行收发器(UART4和UART5)。

这五个接口提供了异步通信、IrDA SIR ENDEC支持、多处理器通信模式、单线半双工通信模式,并具有LIN主/从能力。USART1接口能够以高达4.5 Mbit/s的速度进行通信。其他可用的接口的通信速度为最高2.25 Mbit/s。

USART1、USART2和USART3还提供CTS和RTS信号的硬件管理、智能卡模式(符合ISO 7816标准)以及类SPI通信功能。
除了UART5外,所有接口都可以由DMA控制器服务。

开发板原理图:



4. STM32 USART作用

USART 一个常见应用是将printf 函数通过串口输出,方便程序调试。
另外, USART还支持 LIN(域互连网络)、智能卡协议与红外IrDA协议 SIR ENDEC规范、调制解调器操作(CTS/RTS)、和DMA功能。

5. STM32 USART框图

在《stm3210x参考手册.pdf》P309可以看到STM32的USART框图:



引脚说明

  • TX:发送端口;
  • RX:接收端口;
  • nRTS、nCTS:硬件流控,不常使用,只针对异步串口通讯端口;
  • SCLK:时钟,只针对异步串口通讯端口;
  • IRDA_OUT、IRDA_IN:内部引脚。

6. 寄存器

这里简单列出常用的USART寄存器,详细使用方法可以参考《stm32中文参考手册.pdf》。

USART_SR(Status Register,状态寄存器):

用于存储USART的状态信息,包括发送完成、接收缓冲区非空、校验错误等。

USART_DR(Data Register,数据寄存器):

用于存储发送和接收的数据。写入此寄存器可以启动数据发送,读取此寄存器可以获取接收到的数据。

USART_BRR(Baud Rate Register,波特率寄存器):

用于设置USART的波特率,通常需要根据系统时钟和所需的波特率进行配置。

USART_CR1(Control Register 1,控制寄存器1):

用于配置USART的工作模式、数据格式、中断使能等。

USART_CR2(Control Register 2,控制寄存器2):

用于配置USART的硬件流控、时钟极性等特性。

USART_CR3(Control Register 3,控制寄存器3):

用于配置USART的其他特性,如DMA使能、多主机模式等。

USART_GTPR(Guard Time and Prescaler Register,守护时间和预分频器寄存器):

用于配置USART的守护时间和时钟预分频器,通常与同步通信相关。

USART_IT(Interrupt Register,中断寄存器):

用于配置USART的中断使能和中断标志位。

7. 中断请求

在《STM32中文参考手册》中,中断请求表:



二、开发板RS-232硬件连接

在普中-F1开发板上,提供了 RS-232 母头,其线序:



可以使用一根 RS-232转TTL转USB的连接线,连接USB接电脑,电脑上使用串口调试工具进行开发实验。

RS-232公头线序:.



三、串口通信的配置步骤

配置USART的步骤如下:

1.时钟使能:

首先需要使能USART所使用的时钟,确保其正常工作。USART挂接的系统总线:

  • USART1: APB2时钟总线
  • USART2~5:APB1时钟总线

代码示例:

// 使能 GPIOA 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
// 使能 USART1 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

2.GPIO配置:

配置相关的GPIO引脚,将其设置为USART的TX(发送)和RX(接收)功能。此外,还需要配置引脚的模式(输入/输出)、速率、上拉/下拉等参数。
代码示例:

GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
// 接收设置输入浮空模式
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;

3.USART参数配置:

配置USART的工作模式、波特率、数据位、停止位、校验位等参数。这些参数需要根据具体的通信需求进行设置。

#include "stm32f10x.h"

void USART1_UART_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;

    // 使能串口1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

    // 串口1 GPIO初始化
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟
    GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9; // TX引脚
    GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP; // 复用推挽输出模式
    GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz; // GPIO速度为50MHz
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10; // RX引脚
    GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING; // 浮空输入模式
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 串口1参数配置
    USART_InitStruct.USART_BaudRate=115200; // 波特率为115200
    USART_InitStruct.USART_WordLength=USART_WordLength_8b; // 数据位长度为8位
    USART_InitStruct.USART_StopBits=USART_StopBits_1; // 停止位为1位
    USART_InitStruct.USART_Parity=USART_Parity_No; // 无校验位
    USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; // 无硬件流控制
    USART_InitStruct.USART_Mode=USART_Mode_Rx | USART_Mode_Tx; // 支持接收和发送
    USART_Init(USART1, &USART_InitStruct);

    // 使能USART1模块
    USART_Cmd(USART1, ENABLE);
}

4.使能USART:

最后需要使能USART模块,开始其正常工作。这涉及到设置相应的控制寄存器来启动USART的发送和接收功能。

void USART_Cmd(USAR_TypeDef* USARTx, FunctionalState NewState);
USART_Cmd(USART1, ENABLE);

5. 设置串口中断类型并使能

本章使用使用到串口中断,关于中断具体概念和使用会在后续章节介绍。

// 设置中断类型
void USART_ITConfig(USART_TypeDef * USARTx,uint16_t USART_IT,FunctionalState NewState);
// 串口1接收使能
USART_ITConfig(USART1, USART_IT_RXNE,ENABLE);
// 发送使能
USART_ITConfig(USART1, USART_IT_TC,ENABLE);

6. 设置串口中断优先级、使能串口中断通道

NVIC_Init()

7. 串口中断函数

ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
if(USART_GetITStatus(USART1, USART_IT_RXNE) !=RESET){
  // 接收USART1 中断的处理
}
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);

四、代码实现

下面代码实现的功能是:
通过电脑串口给开发板的USART3发送数据0-9的数据,开发板的数码管显示对应的数值,并且回复同样的内容给开发板。

1. 用 uart 库



2. usart_utils.c

#include "usart_utils.h"

#include "stm32f10x.h"
#include "led_utils.h"

// 初始化USART3
void USART3_Init(u32 bound) {
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    // 使能USART3和GPIOB时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    // 配置USART3的GPIO引脚
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10; // TX引脚
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11; // RX引脚
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // 配置USART3参数
    USART_InitStructure.USART_BaudRate=9600;
    USART_InitStructure.USART_WordLength=USART_WordLength_8b;
    USART_InitStructure.USART_StopBits=USART_StopBits_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(USART3, &USART_InitStructure);

    // 使能USART3
    USART_Cmd(USART3, ENABLE);

    USART_ClearFlag(USART3, USART_FLAG_TC);
    USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel=USART3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
}


// USART3中断服务函数
void USART3_IRQHandler(void) {
    if (USART_GetITStatus(USART3, USART_IT_RXNE) !=RESET) {
        // 读取接收到的数据
        u8 data=USART_ReceiveData(USART3);
        if(data<=9){
            // 收到什么,就发送什么
            USART3_SendData(data);
            // led也显示对应的值
            led_lightn(data);
        }

        // 清除接收中断标志位
        USART_ClearITPendingBit(USART3, USART_IT_RXNE);
    }
}

// 发送函数
void USART3_SendData(u8 data) {
    USART_SendData(USART3, data);
    while (USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET);
}

3. usart_utils.h

#ifndef __USART_UTILS_H__
#define __USART_UTILS_H__
#include "stm32f10x.h"

void USART3_Init(u32 bound);
void USART3_IRQHandler(void);
// 发送函数
void USART3_SendData(u8 data);
#endif

4. main函数

#include "gpio_utils.h"
#include "rcc_utils.h"
#include "stm32f10x.h"
#include "sys_tick_utils.h"
#include "led_utils.h"
#include "key_utils.h"
#include "usart_utils.h"

// 主函数
int main(void)
{
	GPIO_Configuration(); // 调用GPIO配置函数
	sys_tick_init(72);
	led_all_off();
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	USART3_Init(9600);

	while (1) // 无限循环
	{
		delay_ms(20);
	}
}

五、printf重定向

1. 实现方式

通过printf的重定向 ,可以实现在打印printf内容时,通过串口将内容输出来,以方便调试。

要在 STM32 上实现 printf 的重定向,通常需要重写 fputc 函数,以便将输出重定向到你所选择的串口。下面是一个基本的示例:

#include <stdio.h>
#include "usart_utils.h"

// 重定向 fputc 函数,将输出重定向到 USART3
int fputc(int ch, FILE *f) {
    USART_SendData(USART3, (uint8_t)ch);
    while (USART_GetFlagStatus(USART3, USART_FLAG_TXE)==RESET);
    return ch;
}

在这个示例中, fputc 函数会重定向到 USART3,并且通过调用 USART_SendData 函数发送一个字节到 USART3,然后等待发送完成。

需要注意的是,使用这个功能时,要钩选microLIB功能。 钩选microLIB会增加程序的体积,需要权衡利弊。

2. 调用

示例代码里,使用printf("HelloWorld");,就可以在串口看到输出的字符串。

本文代码开源在:
https://gitee.com/xundh/stm32_arm_learn

一小节分享了在串口中断中进行实时协议解析的一种处理方法,仅适用于一些需要对串口指令实时处理的特殊场合。大部分情况下,串口中断服务函数中是不建议太多的处理代码的,一般可以在串口中断中完成数据的接收,通过串口中断将数据接收到数据缓冲区中【最好的方式是采用环形队列】,本节代码即是采用超时接收的处理方式,在串口中断中进行数据的接收,在定时中断中进行断帧,实现数据的接收【前面小节介绍过此方法】,然后在主程序中对数据协议进行解析处理。本小节代码和前面不同的地方在于舍弃了帧头帧尾的判断,增加了CRC校验的功能,自定义的协议格式,参考的是MODBUS通信协议进行设计的。因此本小节的代码,也可以用于RS485通信中,只是不是标准的MODBUS协议罢了,是自定义的一种协议。关于自定义协议的分析,大家可以查找网络上的相关文档进行了解,也可以参看本人的视频课程:串口中断即时解析用户自定义通讯协议(接收数据字节固定)的编程,视频地址链接:https://www.ixigua.com/6846389376847610376中的具体介绍。另外,如果协议指令只是用于控制如一个单片机控制板中的不同的器件时,如多路继电器等,也可以省略地址信息,如果用于多从机的控制,可以加上地址,大家可以根据具体的应用场合举一反三,适当的调整协议指令格式,灵活处理。本小节代码对应的单片机视频课程为:串口超时接收用户自定义通讯协议(基于CRC16校验)的编程实现,视频课程地址链接为:https://www.ixigua.com/6847871377668571652。话不多说,下面是具体的代码,大家可以适当的参考下,有不当之处的欢迎评论区留言讨论。

/*
******************************************
*
* 文件名:main.c
* 描  述:主程序
* 版本号:
* 备  注:
******************************************
*/
/************************************************************************************
功能:带CRC校验的 自定义通讯协议
      01      01       02     03     CRC16/MODBUS
		 地址   功能字         数据         校验字
     
		例如:      
			01  01  80  80  30  78  LED点亮      0x8080毫秒		
		  01  02  80  80  C0  78  蜂鸣器鸣响   0x8080毫秒	
    CRC16.c中的CRC校验算法计算得到的CRC校验字和在线工具中
		计算的CRC校验字高低字节正好相反。
    CRC在线计算工具网址:http://www.ip33.com/crc.html
		可以根据自己的应用需求进行协议的扩展,如控制多路继电器,
		同时还可以扩展到RS485多从机系统中使用
**************************************************************************************/
//头文件
#include <reg51.h>
#include "delay.h"
#include "uart.h"
#include "CRC16.h"
#include "time.h"

//本机地址
#define LOCAL_ADRRESS 0x01

void main(void)
{
	unsigned char i;
	unsigned int crc;
	unsigned char crch,crcl;
	Timer0Init();//定时器初始化
	UartInit();//串口初始化
	EA=1;//开总中断
	DelayXms(10);
	while(1)
	{
		if(recv_flag)//接收到数据
		{
			recv_flag=0;//清零接收标志位
			start_timer=0;//关软件定时器
			//校验本机地址,是本机的,则接收处理,否则退出
			if(recv_buf[0] !=LOCAL_ADRRESS)			
			{
				return;
			}
			//CRC16校验,校验正确,我们才处理,否则直接返回,并给出错误码
			crc=GetCRC16(recv_buf,recv_cnt - 2);
			crch=crc >> 8;
			crcl=crc & 0xFF;
			if((recv_buf[recv_cnt - 2] !=crch) || (recv_buf[recv_cnt - 1] !=crcl))
			{
				recv_buf[1]=recv_buf[1] | 0x80;
				crc=GetCRC16(recv_buf,recv_cnt - 2);
				recv_buf[4]=crc & 0xFF;
				recv_buf[5]=crc >> 8;
				for(i=0;i<recv_cnt;i++)
				{
					sendByte(recv_buf[i]);
				}
				return;
			}
			switch(recv_buf[1])//命令字/功能字判断
			{
				case 1:led_data=recv_buf[2];
							 led_data=led_data << 8;
							 led_data=led_data + recv_buf[3];//取数据
							 led_cnt=0;//目的是使LED点亮上述接收的数据的时间
					break;
				case 2:beep_data=recv_buf[2];
							 beep_data=beep_data << 8;
							 beep_data=beep_data + recv_buf[3];//取数据
							 beep_cnt=beep_data;//蜂鸣器鸣响时间数据
					break;
				default:break;
			}
			//返回接收数据 测试用
			for(i=0;i<recv_cnt;i++)
			{
				sendByte(recv_buf[i]);
			}
			clr_recvbuffer(recv_buf);//清除缓冲buffer
			recv_cnt=0;
		}
	}
}
/*
******************************************
*
* 文件名:time.c
* 描  述:定时/计数器0初始化及中断处理
* 版本号:
* 备  注:
******************************************
*/
#include "time.h"
#include "uart.h"

//全局变量定义
unsigned int led_data;
unsigned int beep_data;
unsigned int led_cnt;
unsigned int beep_cnt;

void Timer0Init(void)		//1毫秒@11.0592MHz
{
	TMOD &=0xF0;		//设置定时器模式
	TMOD |=0x01;		//设置定时器模式
	TL0=0x66;		//设置定时初值
	TH0=0xFC;		//设置定时初值
	TF0=0;		//清除TF0标志
	ET0=1;
	TR0=1;		//定时器0开始计时
}

void timer0_ISR() interrupt 1
{
	TR0=0;
	if(start_timer==1)
	{
		recv_timer_cnt++;//1、累加定时时间计数器
		if(recv_timer_cnt > MAX_REV_TIME)//2、判断定时时间是否超过了设定的最大的阈值,
			                               //超过则说明等待一段时间后没有新的数据到,我们
		                                   //判断一包数据接收完毕
		{
			recv_timer_cnt=0;//3、清除定时计数器 处理数据 清除buffer(放到数据处理之后)
			recv_flag=1;		
		}
	}	
	//LED点亮时间控制
	if(led_cnt < led_data)
	{
		led_cnt++;
		LED=0;
	}
	else
	{
		LED=1;
	}
	//蜂鸣器鸣响时间控制
	if(beep_cnt !=0)
	{
		beep_cnt--;
		BEEP=~BEEP;
	}
	TL0=0x66;		//设置定时初值
	TH0=0xFC;		//设置定时初值
	TR0=1;
}
//time.h
#ifndef __TIME_H__
#define __TIME_H__

#include <reg51.h>

sbit LED=P1^0;
sbit BEEP=P3^7;

extern unsigned int led_data;
extern unsigned int beep_data;
extern unsigned int led_cnt;
extern unsigned int beep_cnt;

void Timer0Init(void);

#endif
/*
**************************************
*
* 文件名:uart.c
* 描  述:串口处理
* 版本号:
* 备  注:
**************************************
*/

#include "uart.h"

unsigned char recv_flag=0;
unsigned char start_timer=0;
unsigned char recv_buf[MAX_REV_NUM];
unsigned char recv_cnt;
unsigned char recv_timer_cnt;

void UartInit(void)		//9600bps@11.0592MHz
{
	PCON &=0x7F;		//波特率不倍速
	SCON=0x50;		//8位数据,可变波特率
	TMOD &=0x0F;		//清除定时器1模式位
	TMOD |=0x20;		//设定定时器1为8位自动重装方式
	TL1=0xFD;		//设定定时初值
	TH1=0xFD;		//设定定时器重装值
	ET1=0;		//禁止定时器1中断
	ES=1;
	TR1=1;		//启动定时器1
}

void sendByte(unsigned char dat)
{
	SBUF=dat;
	while(!TI);
		TI=0;
}

void sendString(unsigned char *dat)//Hello World!
{
	while(*dat !='\0')
	{
		sendByte(*dat++);
	}
}

char putchar(char c)
{
	sendByte(c);
	return c;
}
void clr_recvbuffer(unsigned char *buf)
{
	unsigned char i;
	for(i=0;i<MAX_REV_NUM;i++)
	{
		buf[i]=0;
	}
}
/*************************************
1、中断服务函数一定是一个没有返回值的函数
2、中断服务函数一定是没有参数的函数
3、中断服务函数函数名后跟关键字 interrupt 
4、interrupt n 0 - 4   5个中断源     8*n + 0003H  
    0003H INT0 000BH T0  0013H INT1 001BH T1 0023H ES
5、中断服务函数不能北主程序或其他程序所调用
6、 n 后面 跟 using m (0 - 3 )工作寄存器组
***************************************/
void uart_ISR() interrupt 4
{
	if(RI)
	{
		RI=0;
		start_timer=1;//1、每接收一帧数据的时候,打开软件定时器,去计数
		if(recv_cnt < MAX_REV_NUM)
		{	
			recv_buf[recv_cnt]=SBUF;//2、接收数据到数据缓冲区,注意缓冲区的大小范围问题
			recv_cnt++;
		}
		else
		{
			recv_cnt=MAX_REV_NUM;
		}
		recv_timer_cnt=0;//3、每接收一帧数据,记得把定时计数清零 相当于是喂狗信号
		                   //但是在定时中断里面会不断累加
	}
	if(TI)
	{
		TI=0;
	}
}
//uart.h
#ifndef __UART_H__
#define __UART_H__

#include <reg51.h>
#include <stdio.h>

#define MAX_REV_NUM   20
#define MAX_REV_TIME  5

extern unsigned char start_timer;
extern unsigned char recv_buf[MAX_REV_NUM];
extern unsigned char recv_cnt;
extern unsigned char recv_timer_cnt;
extern unsigned char recv_flag;

void UartInit(void);
void sendByte(unsigned char dat);
void sendString(unsigned char *dat);
void clr_recvbuffer(unsigned char *buf);

#endif
/*
*********************
*
* 文件名:CRC16.c
* 描  述:通用的CRC16校验算法文件
* 版本号:v1.0
* 备  注:此文件代码源自网络
*******************************************************************************
*/
#include "CRC16.h"
/* CRC16计算函数,ptr-数据指针,len-数据长度,返回值-计算出的CRC16数值 */
unsigned int GetCRC16(unsigned char *ptr,  unsigned char len)
{ 
    unsigned int index;
    unsigned char crch=0xFF;  //高CRC字节
    unsigned char crcl=0xFF;  //低CRC字节
    unsigned char code TabH[]={  //CRC高位字节值表
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,  
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,  
        0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,  
        0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,  
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,  
        0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,  
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,  
        0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,  
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,  
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,  
        0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,  
        0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,  
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,  
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,  
        0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,  
        0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,  
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,  
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,  
        0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,  
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,  
        0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,  
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,  
        0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,  
        0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,  
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,  
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40  
    } ;  
    unsigned char code TabL[]={  //CRC低位字节值表
        0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,  
        0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,  
        0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,  
        0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,  
        0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,  
        0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,  
        0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,  
        0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,  
        0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,  
        0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,  
        0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,  
        0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,  
        0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,  
        0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,  
        0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,  
        0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,  
        0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,  
        0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,  
        0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,  
        0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,  
        0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,  
        0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,  
        0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,  
        0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,  
        0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,  
        0x43, 0x83, 0x41, 0x81, 0x80, 0x40  
    } ;
 
    while (len--)  //计算指定长度的CRC
    {
        index=crch ^ *ptr++;
        crch=crcl ^ TabH[index];
        crcl=TabL[index];
    }
    
    return ((crch<<8) | crcl);  
}
//CRC16.h
#ifndef __CRC16_H__
#define __CRC16_H__

unsigned int GetCRC16(unsigned char *ptr,  unsigned char len);

#endif

delay.c 和delay.h前期文章中已经分享过,本节不再单独贴出。通过Proteus仿真和虚拟串口,可以对上述的代码功能进行简单的测试,当然也可以通过开发板或自制通信板进行相关的测试,具体的测试结果如下所示:

LED灯点亮命令测试

蜂鸣器鸣响命令测试

CRC校验错误测试

串口通信的相关代码分享告一段落,其他串口相关的介绍,可以关注后,查看主页单片机视频集合中的单片机应用实践篇中的相关视频内容,其他的几个视频的代码就不再单独贴出了,因为根据最近几篇文章分享的模块化代码,很容易的就可以根据视频的功能要求,调试出来相应的程序来,没有必要每个视频都贴出源码的。另外视频代码仅供大家参考,Bug在所难免,主要是示例程序代码,很多的功能并不完善,希望大家可以根据这种编程思路,进行相应的拓展。主要是感觉串口通信自定义协议是一个重点,也是一个难点,很多初学的同学,对此会感觉比较难,实际上,如果理解透彻了,也就不觉得难了的。同时,串口如果掌握好了,后面很多的器件或模块接口都是采用串口通信,学习起来也就轻松了许多,比如蓝牙模块,WIFI模块,4G模组,RFID等。所以,有必要把串口好好的学一下,欢迎大家关注,留言讨论。

片机与Labview串口通信

单片机与LabVIEW之间的串口通信是一种常见的数据传输方式,它允许单片机与计算机之间进行实时数据交换和控制。LabVIEW作为一种功能强大的图形化编程软件,具有友好的用户界面和强大的数据处理能力,被广泛应用于工程实践和科学研究中。通过串口通信,单片机可以将采集到的数据发送给计算机进行进一步的处理和分析,也可以接收计算机发送的控制指令,实现远程控制和自动化测试。

作为互联网行业工作者,深切建议大家认真学习并尝试涉及单片机的学习,我整理了一些资料大家可以了解下:

https://m.hqyjai.net/emb_study_blue_short.html?xt=cpx

在进行单片机与LabVIEW串口通信时,需要注意以下几个方面:

1. 串口设置:首先需要正确配置串口的参数,包括波特率、数据位、停止位、校验位等。这些参数的设置必须与单片机端的串口设置保持一致,以确保数据的正确传输。

2. 数据格式:单片机发送的数据需要按照一定的格式进行编码,以便计算机能够正确解析。常见的数据格式包括文本格式和二进制格式。在发送数据时,单片机需要按照约定的格式将数据打包成数据包,并在数据包中添加必要的头信息和校验码,以确保数据的完整性和正确性。

3. 通信协议:单片机与计算机之间的通信需要遵循一定的通信协议,以确保数据的正确传输和解析。常见的通信协议包括ASCII协议和Modbus协议等。选择合适的通信协议可以提高数据传输的可靠性和稳定性。

4. 错误处理:在串口通信过程中,可能会出现各种错误,如数据丢失、传输错误等。为了保证数据的正确性和可靠性,需要采取一定的错误处理措施,如数据校验、重传机制等。

总之,单片机与LabVIEW之间的串口通信是一种重要的数据传输方式,它可以实现单片机与计算机之间的实时数据交换和控制。在实际应用中,需要根据具体的应用场景和需求,选择合适的通信协议和数据处理方式,确保数据的正确性和可靠性。同时,也需要不断学习和实践,提高自己的技能水平和应用能力。

https://m.hqyjai.net/emb_study_blue_short.html?xt=cpx#如何学习单片机# #什么叫单片机#