当前位置:   article > 正文

手势点灯任务二:利用python串口通信点灯

python串口通信

任务二:串口通信


一、stm32串⼝通信理论部分

1、通信协议大总结

91810806173caa4241db82071ec493dd.png

  • 串口通信即为其中一种,其可以实现两个设备间的互相通信
  • 串口通信实现了各硬件模块的互相通信

2、串口通信硬件电路连接

其中:

  • GND接地(严格意义上讲也是GND数据线,传输数据依靠的是电平差)
  • VCC用于供电,若是两个设备均有单独供电,则不需要接这根线; 若需要供电,还要注意子系统的供电需求
  • TX与RX要交叉接(很好理解)
  • 若只需单工通讯,只接一根数据线即可
  • 若电平标准不一致,需要加装电平转换芯片
附:串口常用的电平标准
  • TTL电平:+3.3V或+5V表示1,0V表示0(一般用于单片机等小型设备)
    TX对地为3.3V,则为逻辑1;对地为0V,则为逻辑0
  • RS232电平:-3 ~ -15V表示1,+3 ~ +15V表示0(用于大型设备,所需电压高)
  • RS485电平:两线压差+2 ~ +6V表示1,-2 ~ -6V表示0(差分信号)(抗干扰能力强)

3、串口软件部分(串口时序、参数)

(1)串口参数
  • 数据帧:每个字节的存放地,由起始位、数据位和停止位组成
    数据位有8个代表一个字节的8位(还可能在最后有一个奇偶校验位)

  • 波特率:串口通信的速率(两个设备协调的统一通信速率)

  • 起始位:标志一个数据帧的开始,固定为低电平(空闲状态为高电平,起始位产生下降沿,来告诉设备要开始发送数据了)

  • 停止位:用于数据帧间隔,固定为高电平(为下一个起始位做准备,切换到高电平空闲状态)

  • 数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行

  • 比如:发送一个字节为0x0F,首先转化为二进制0000 1111,再低位先行:依次传入1111 000,可将整个过程理解为蛇进洞

  • 校验位:根据数据位计算得来,用于判断数据传输是否出错,出错可选择丢弃或者重传

    • 奇校验:发送方发送数据后,包括校验位在内的9个数据位会出现奇数个1

        原为0000 1111,则校验位补1,使1的个数为奇数
        原为0000 1110,则校验位补0,使1的个数为偶数
      
      • 1
      • 2

      接收方接收数据后,验证数据位和校验位中1的个数

    • 偶校验类似(保证1个数为偶数)

    • CRC校验法(循环冗余校验码)(网上查过,看不懂一点。。。)

(2)串口收发时序

开端位->数据位->校验位->中止位

  • 开端位,为一位逻辑0;
  • 数据位,可设为5-8位,由低位开端逐位发送;
  • 奇偶校验位,为一位,能够省掉;
  • 中止位,能够挑选1,1.5或2位,为逻辑1;
  • 闲暇时刻为逻辑1

二、学习并驱动stm32串口外设,实现串口发送和接受

1、USART简介

  • 很少在该串口中使用同步功能,故基本上与UART差不多

    同步功能多了一个时钟输出而已,甚至不支持时钟输入。。(不支持两个USART之间的同步通信)

  • 可以看作为两部分:接受和发送

    • 将数据寄存器中的一个字节的数据自动生成数据帧时序,从TX引脚发送出去
    • 自动从RX引脚接收数据帧时序拼接成一个字节数据,存放于数据寄存器
  • 自带波特率发生器最高达4.5Mbits/s(最常用:9600,115200)

  • 可配置数据位(8,常用/9)和停止位长度(0.5/1,常用/1.5/2)

  • 可选校验位(无校验、常用/奇校验/偶校验)

  • 持同步模式、硬件流控制(控制数据流量)、DMA、智能卡、IrDA(红外通信)、LIN(后三者属于其他协议)

  • stm32F103C8T6上共有3根USART的挂载线

2、USART结构

(1)串口数据的收发过程

  • 发送(TDR)/接收(RDR)数据寄存器:只读或只写
  • 发送移位寄存器:把字节的数据一位一位地移出去
    在发送移位寄存器中,要先等未发送的字节全部发送完毕,才能继续接入数据寄存器中的字节
  • 接收移位寄存器:一位一位接收,全部接收完成后一起进入数据寄存器中(也就是发送的逆过程,比较好理解)

(2)串口的控制系统


  • 发送器控制、接收器控制(顾名思义,很好理解)

  • 硬件流控管脚:控制数据流流入速率(一般不用)

    • nRTS:请求发送,为输出脚,接到对面的nCTS(告诉别人我能不能收)
    • nCTS:清除发送,是输入脚,接到外部设备的RTS用于接收别人nRTS的信号(查看别人是否可以接收)
  • 时钟引脚SCLK:产生同步的时钟信号,配合发送移位寄存器(一般不用)
    发送寄存器每移位一次,同步时钟电平就跳变一个周期

  • 唤醒单元:实现串口挂载多设备(一般不用)
    在USART地址处给串口分配一个地址
    当发送指定地址时此设备唤醒开始工作;
    当发送别的设备地址时别的设备工作,没收到地址的设备不唤醒保持沉默

  • 中断(输出)控制:TXE(发送寄存器空)和RXNE(接收寄存器非空)较重要
    用于判断发送、接收状态的必要标志位

(3)波特率发生器

  • 波特率发生器其实就是分频器

  • USART1挂载在APB2上(72MHz),其他的USART都挂载在APB1上(36MHz)

3、USART对应引脚

引脚模式USART1USART2USART3
TXPA9PA2PB10
RXPA10PA3PB11

4、一些细节问题

(1)数据帧
  • 8位字长也可以设置有无校验位,一般为了发送完整字节都选择无校验。9位字长常选择有校验
  • 可配置停止位长度为0.5,1,1.5,2四种,本质是时长不同,一般选择1位
(2)输入数据策略
  • 起始位侦测:
    输入电路对采样时钟进行了细分,会以波特率的16倍频率进行采样,即在1位的时间里进行16次采样
  • 数据采样

    由于起始位侦测已经对齐了采样时钟,所以这里就直接在第8、9、10次采样数据位(为了保证数据可靠性连续采样3次)

5、串口的发送

(1)初始化流程
  • 开启时钟:将USART和GPIO的时钟打开
  • GPIO初始化:TX配置为复用输出,RX配置为输入
  • 配置USART:直接使用一个结构体,即可配置所有参数
  • 只发送:开启USART即可
    还要接收的话:可能还要配置中断,需要在开启USART之前,再加上ITConfig和NVIC的代码
(2)认识库函数
USART使用函数
USART_DeInit(USARTx);
//将USART寄存器重置为默认值
    
USART_Init(USARTx,&USART_InitStruct);
//根据结构体的参数配置来对USARTx外设进行初始化

USART_StructInit(USART_InitStruct);
//将USART_InitStructure结构体初始化

USART_ClockInit(USARTx, USART_ClockInitStruct)
void USART_ClockStructInit(USART_ClockInitStruct)
//用于时钟输出(不常用)
    
USART_Cmd(USARTx, NewState)
//使能或者失能USART外设
//eg:USART_Cmd(USART1 , ENABLE)

USART_ITConfig(USARTx,USART_IT,NewState)
//配置指定的USART中断
//eg:USART_ITConfig(USART1 , USART_IT_RXNE , ENABLE)

USART_DMACmd(USARTx,USART_DMAReq, NewState)
//使能或者失能USART的DMA请求
//eg:USART_DMACmd(USART1 , USART_DMAReq_Tx , ENABLE)

USART_SendData(USARTx, uint16_t Data)
//通过USARTx外设传输单个字节数据
uint16_t USART_ReceiveData(USARTx)
//返回由USARTx外设接收的最新数据
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
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;//校验位(ODD奇校验,EVEN偶校验)
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
USART_InitStructure.USART_WordLength=USART_WordLength_8b;//字长
	    
USART_Init(USART1,&USART_InitStructure);
USART_Cmd(USART1,ENABLE);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
发送函数的书写
void Serial_SendByte(uint8_t Byte){
	USART_SendData(USART1, Byte);
    //将Byte变量写入TDR
        
    //需要等待数据进入移位寄存器,所以需要等待一下标志位置1
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
	//即等待发送数据寄存器空标志位变为SET(置1后)
	//此时也不需要手动清零标志位,下一次SendData时会自动清零
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
(3)代码书写注意
  • USART1是APB2的外设,其他都是APB1的(APB2性能高于APB1)
  • TX引脚是USART外设控制的输出脚,应选用复用推挽输出模式
    RX引脚是USART外设数据输入脚,应选择输入模式(一般浮空或上拉)
(4)不同数据模式
  • HEX模式/十六进制模式/二进制模式:以原始数据形式显示(显示一个个十六进制数)
  • 文本模式/字符模式:显示数据编码后的形式
    eg:发送0x41,HEX模式显示41,文本模式显示为A(ASCII码)
先实现下发送吧。。。

(这里的发送案例是发送单个字节)

  • serial.c
    serial.png
  • main.c
    main.png
    接上(库函数认识)
(5)发送数组
void Serial_SendArray(uint8_t Array[], uint16_t Length){
	uint16_t i;
	for (i = 0; i < Length ; i ++){
		Serial_SendByte(Array[i]);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
(6)发送字符串
void Serial_SendString(char String[]){
	uint8_t i;
	for (i = 0; String[i] != 0;i++){
		Serial_SendByte(String[i]);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
(7)发送一个数字
void Serial_SendNumber(uint32_t Number,uint8_t Length){
	uint8_t i;
	for (i = 0 ; i < Length ; i++){
		Serial_SendByte(Number / SerialPow(10,Length - i - 1) % 10 + '0');
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
(8)对printf()函数重定向
int fputc(int ch, FILE *f){
	Serial_SendByte(ch);
	return ch;
}
  • 1
  • 2
  • 3
  • 4
(9)对sprintf()函数的封装
void Serial_Printf(char *format, ...){
	char String[100];
	va_list arg;//定义一个参数列表变量
	va_start(arg, format);//从format位置开始接受参数表,放在arg中
	vsprintf(String, format, arg);//打印位置String,格式化字符串format,参数列表arg
	va_end(arg);//释放参数表
	Serial_SendString(String);//把String发送出去
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

6、串口的接收

(1)利用查询接收数据
  • 流程:主函数中不断查询标志位(若置1,则说明收到数据)->调用recievedata读取DR即可
  • 这里我还是将函数进行了封装
void Serial_RecieveByte(uint8_t Byte){	
	if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET){
		Byte = USART_ReceiveData(USART1);
		OLED_ShowHexNum(1, 1, Byte, 2);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
(2)利用中断接收数据

(这里博主还没学,因为这个小项目暂时用不到)

三、学习python串口通信,⽤python实现串口发送和接受

1、为了使程序更简洁,我写了一个PC13.c和PC13.h方便函数调用

  • 这里参照的是LED.c和LED.h进行的书写,通过书写可进一步熟悉相关函数的调用

2、主函数的书写及遇到的问题

  • 关于不用中断读取DR中的值:使用输入寄存器标志位判断
  • 由USART_RecieveData()函数读取的值是ASK码,因此判断时应当使用0/1/2对应的ASK码作为依据
  • 注意OLED相关函数输出的进制数

3、利用python作为串口助手进行收发

芜湖!!任务二也完成辣!是不是很开心呢┗( ▔, ▔ )┛

接下来登场的是手势识别数字,期待与你的再次相遇!

特别声明:以上的图片部分来自于网络,感谢CSDN、知乎等平台上各位博主的分享,本文用作交流学习予以引用,在此一并表示感谢!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/211158
推荐阅读
相关标签
  

闽ICP备14008679号