GD32E103C8T6《调试篇》之USART + 超时检测 (附代码)
前言
PCB板是现成的,之前用的USART0,发现不对;在老工程师的指导下,发现必须要根据板的IO配置来编程。
芯片:GD32E103C8T6
编译环境:keil V5.35.0.0 / MDK-ARM V5.35.0.2
串口知识点
USART——通用同步异步收发器,串行通信设备,全双工数据交换
USART 和 UART 区别本篇不做探讨
串口通讯的数据包由发送设备通过自身的 TXD 接口传输到接收设备的 RXD 接口。在串口通讯的协议层中,规定了数据包的内容,它由启始位、主体数据、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据;
串行通信一般是以帧格式传输数据,即是一帧一帧的传输,每帧包含有起始信号、数据信息、停止信息,可能还有校验信息
1、参看GD32E103的Datasheet,找到对应芯片的封装(重要)
因为我就找错过一次,明明芯片是48脚,却看的100脚的
2、查看管脚详细信息,找到对应芯片的封装(重要)
可以看见GD32103C8的USART有3个可以用(0-2)
PCB已经决定了GD32E103的21脚连接485芯片的4脚;22脚连接485芯片的1脚,参看103的数据手册
21脚是PB10, 22脚是PB11 ;
GPIO对应485芯片:PB10–Tx ,PB11–Rx;
这样一来用串口几通讯也限制了,看图,我只能用USART2
3、利用RS485芯片(75176B)和外部通讯
由于我的目标板通过一个485芯片和外部通讯,所以会用到一根USB TO RS485的工具
1)485芯片封装
2)RS485芯片使用说明
RS485——单工、半双工
一般普通的485电路,用单片机的RXD连接485芯片的RO引脚、用单片机的TXD连接485芯片的DI引脚,485芯片的2、3脚接在一起,连接至单片机某一个引脚(本文例子连的单片机19脚——PB1);
当单片机要发送数据的时候,把PB1置1,数据通过TXD发送出去。
当单片机要接收数据的时候,把PB1置0,数据通过RXD接收回来。
总结:RE/DE为低电平时发送禁止,接收有效,RE/DE为高电平时,则发送有效,接收截止;
4、单片机发送数据,通过串口打印
单片机发送数据,所以485芯片的配置应该是把PB1置1
gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);
gpio_bit_set(GPIOB,GPIO_PIN_1); //TX -- 1
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdarg.h>
#include "gd32e10x.h"
#include <stdio.h>
//PB10--Tx PB11--Rx
void usart2_init() // 初始化串口2
{
rcu_periph_clock_enable(RCU_GPIOB); // 使能GPIOA时钟
rcu_periph_clock_enable(RCU_USART2); // 使能串口2时钟
/* connect port to USARTx_Tx *///配置TX 推挽复用模式
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
/* connect port to USARTx_Rx *///配置RX 浮空输入模式
gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_11);
/***** 485 TX enable ****/ //pin19--PB1
gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1); //GPIO_OSPEED_2MHZ
gpio_bit_set(GPIOB,GPIO_PIN_1); //TX -- 1
// 步骤1-7:
usart_deinit(USART2);
usart_word_length_set(USART2,USART_WL_8BIT); ///2、配置USART字长
usart_stop_bit_set(USART2,USART_STB_1BIT); ///3、配置USART停止位
usart_baudrate_set(USART2,115200); ///5、配置USART波特率
usart_transmit_config(USART2,USART_TRANSMIT_ENABLE); // 6、USART发送配置
usart_receive_config(USART2,USART_RECEIVE_ENABLE);
usart_enable(USART2);//使能串口
}
int main(void)
{
systick_config(); //初始化延时函数
delay_ms(100);
usart2_init();
while(1)
{
printf("****yang****");
printf("\r\n");
delay_ms(1000);
printf("****fa****");
printf("\r\n");
delay_ms(1000);
}
}
// 中断处理函数
void USART2_IRQHandler(void)
{
// 串口2外部给串口2发送了数据,就会进入下面这个中断,然后把数据读取到data里面
if( RESET != usart_interrupt_flag_get(USART2,USART_INT_FLAG_RBNE) )// 发生中断,则返回RESET
{
usart_data_receive(USART2); // 读取串口2接收到的数据
}
usart_interrupt_flag_clear(USART2, USART_INT_FLAG_EB);
}
/* retarget the C library printf function to the USART */
int fputc(int ch, FILE *f)
{
usart_data_transmit(USART2, (uint8_t)ch);
while(RESET == usart_flag_get(USART2, USART_FLAG_TBE));
return ch;
}
效果
5、串口外部向单片机发送数据,单片机接收数据后,回显数据(有超时检测)
1)查看固件库使用手册
找到设置USART接收超时阈值的函数
使能接收超时函数
配置时间,使能超时
usart_receiver_timeout_threshold_config(USART2,500);
usart_receiver_timeout_enable(USART2);
使能相应的子中断
usart_interrupt_enable(USART2,USART_INT_RBNE);
usart_interrupt_enable(USART2,USART_INT_RT);
———————————————————————————————————
中断处理函数里
由于使能相应的子中断,所以选择对应的中断标志
if( RESET != usart_interrupt_flag_get(USART2,USART_INT_FLAG_RBNE) )
{
//不需要手动清除
}
if( RESET != usart_interrupt_flag_get(USART2,USART_INT_FLAG_RT) )
{//需要手动清除
usart_interrupt_flag_clear(USART2, USART_INT_FLAG_RT);//超时事件中断标志
}
usart_interrupt_flag_clear(USART2, USART_INT_FLAG_EB);//块结束事件中断标志 USART2总中断清除
Q:为什么USART_INT_FLAG_RBNE不需要手动清除,USART_INT_FLAG_RT需要手动清除
A:查看用户数据手册,找到17章(USART),再找17.4(USART状态寄存器)
重点来了!!!敲黑板!!!
————————————————————
软件可以通过对该位写0 或 读USART_DATA寄存器来将该位清0
意思就是可以手动清除,或者寄存器自动清0
————————————————————
软件可以通过对该位写0,没有或者
意思就是只能手动清除
可以试试把usart_interrupt_flag_clear(USART2, USART_INT_FLAG_RT);注释了,你会发现点击发送数据就不会回送给串口了;
这里菜鸟我请教的老工程师,自己是琢磨不出来的;
2)代码
#include "common.h"
//PB10--Tx PB11--Rx
#define BUFSIZE 64
typedef struct
{
u8 recved;
u8 len;
u8 rxbuf[BUFSIZE];
u8 rxidx;
}ts_uart;
ts_uart logo;
void usart2_init() // 初始化串口2
{
rcu_periph_clock_enable(RCU_GPIOB); // 使能GPIOB时钟
rcu_periph_clock_enable(RCU_USART2); // 使能串口2时钟
/* connect port to USARTx_Tx *///配置TX 推挽复用模式
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
/* connect port to USARTx_Rx *///配置RX 浮空输入模式
gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_11); //
/***** 485 TX enable ****/ //pin19--PB1
gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1); //GPIO_OSPEED_2MHZ
gpio_bit_reset(GPIOB,GPIO_PIN_1); //0-Rx
// 步骤1-7:
usart_deinit(USART2); //1、
usart_word_length_set(USART2,USART_WL_8BIT); ///2、配置USART字长
usart_stop_bit_set(USART2,USART_STB_1BIT); ///3、配置USART停止位
usart_baudrate_set(USART2,115200); ///5、配置USART波特率
usart_transmit_config(USART2,USART_TRANSMIT_ENABLE); // 6、USART发送配置
usart_receive_config(USART2,USART_RECEIVE_ENABLE);
usart_receiver_timeout_threshold_config(USART2,500);//超时检测 500———4340us RT该位域用于指定接收超时值,单位是波特时钟的时长。 标准模式下,如果在最后一个字节接收后,在RT规定的时长内,没有检测到新的起始位,USART_STAT1寄存器中RTF标志被置位。
usart_receiver_timeout_enable(USART2);
usart_enable(USART2);//使能串口
// // 在nvic中配置中断向量和中断优先级
nvic_irq_enable(USART2_IRQn,0,0); //使能NVIC的中断
// // 使能USART子中断
usart_interrupt_enable(USART2,USART_INT_RBNE);
usart_interrupt_enable(USART2,USART_INT_RT);
}
int main(void)
{
u8 i;
systick_config(); //初始化延时函数
delay_ms(100);
usart2_init();
while(1)
{
if(logo.recved)
{
gpio_bit_set(GPIOB,GPIO_PIN_1); //0-Rx 1-Tx
delay_ms(1);//延时是有必要的 485的RX、TX切换需要时间
for(i=0;i<logo.len;i++)
{
usart_data_transmit(USART2, logo.rxbuf[i]);
while(RESET == usart_flag_get(USART2, USART_FLAG_TBE));//手动清除
}
logo.rxidx=0;
logo.recved=0;
gpio_bit_reset(GPIOB,GPIO_PIN_1); //0-Rx 不发数据时,默认Rx open;
}
}
}
// 中断处理函数
void USART2_IRQHandler(void)
{
unsigned char data;
// 串口2外部给串口2发送了数据,就会进入下面这个中断,然后把数据读取到data里面
//usart_interrupt_flag_get获取USART中断标志位状态;USART_INT_FLAG_RBNE读数据缓冲区非空中断标志
//get a byte
if( RESET != usart_interrupt_flag_get(USART2,USART_INT_FLAG_RBNE) )// 发生中断,则返回RESET
{
data = usart_data_receive(USART2); // 读取串口2接收到的数据
if(logo.rxidx < BUFSIZE)
{
logo.rxbuf[logo.rxidx++] = data;
}
}
//rx timeout check
if( RESET != usart_interrupt_flag_get(USART2,USART_INT_FLAG_RT) )// 接收超时标志
{
logo.len = logo.rxidx;
logo.recved = 1; //接收完成标志
usart_interrupt_flag_clear(USART2, USART_INT_FLAG_RT);//清除接收超时中断
}
usart_interrupt_flag_clear(USART2, USART_INT_FLAG_EB);//USART2总中断清除
}
/* retarget the C library printf function to the USART */
int fputc(int ch, FILE *f)
{
usart_data_transmit(USART2, (uint8_t)ch);
while(RESET == usart_flag_get(USART2, USART_FLAG_TBE));
return ch;
}
效果:
jsrecord_2024-01-04-17-26-43
补充:
115200bps bit/s 1bit ------ 1000000 us
1+8+1(起始位+数据位+停止位) 10bit ------ 约86us
一帧数据传送需要86us , 超时检测的时间设置太长,会延迟回显数据;我做了实验,超时检测时间设为10也就是86us,很快能收到,设成1都行;设为115200会延时1s;
一帧数据要保证传输完成,再发送,所以接收完成标志是有必要的;
本文如有错误之处,请予以指正!