蓝桥杯嵌入式开发指南 06 - 串口配置(USART2)

实例

通过配置串口,将电位器采集到的电压通过串口发送到上位机,将上位机发送的数据显示到下位机。

电路分析



电路上有两个串口,串口 1(PA9,PA10)通过 RS232 接口链接,串口 2(PA2,PA3)通过 USB 接口链接,我们在调试过程中使用的是 USB 连接方式,故我们通常配置串口 2m,直接使用下载线进行串口通讯,不配置串口 1。

工程建立

本次工程在上一次的工程上继续修改,复制上一次的工程作为本次的工程基础。

添加库函数

本例中使用到了串口通讯功能,故要添加以下库函数:

  • stm32f10x_usart.c

初始化串口 2

将 PA2 和 PA3 分别设置为 TX 和 RX 对应的模式,开启串口发送和接收模式,并配置接收中断:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//传入参数:baudarate:设定波特率(通常为9600或115200)
void USART2_Init(u32 baudrate)
{
//声明用到的结构体变量
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitSturcture;

//使能对应时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);

//配置中断优先级
NVIC_InitStructure.NVIC_IRQChannel=USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);

//配置发送引脚 TX-PA2 AF_PP
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//配置接收引脚 RX-PA3 IN_FLOATING
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//配置2串口
USART_InitSturcture.USART_BaudRate=baudrate; //设定波特率
USART_InitSturcture.USART_WordLength=USART_WordLength_8b; //8位数据位
USART_InitSturcture.USART_StopBits=USART_StopBits_1; //1位停止位
USART_InitSturcture.USART_Parity=USART_Parity_No; //无奇偶校验
USART_InitSturcture.USART_Mode=USART_Mode_Rx|USART_Mode_Tx; //设置位接收和发送模式
USART_InitSturcture.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使能硬件流控制
USART_Init(USART2,&USART_InitSturcture);

//使能串口2
USART_Cmd(USART2,ENABLE);

//使能串口2接收中断
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
}

发送程序

通过串口 2 将字符串发送到上位机:

1
2
3
4
5
6
7
8
9
10
11
void USART2_SendString(u8 *str)
{
u8 index=0; //定义索引量
do
{
USART_SendData(USART2,str[index]); //发送索引到的数据
while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==0); //等待发送完成
index++; //索引量++
}
while(str[index]!=0); //索引到的字符是否为0
}

发送字符串函数的传入参数为一个指针,在这里填一个数组,即为将数组内的数据逐个逐个发送出去,直到发送完毕,void USART_SendData (USART_TypeDef* USARTx, uint16_t Data); 可以发送一个字节的数据。

接收程序

通过串口 2 接收上位机下发的数据:
定义全局变量如下:

1
2
3
u8 RX_Buffer[20]; //接收缓存区
u8 RX_Over=0; //接收结束标志
u8 RX_Cnt=0; //接收索引

约定每次接收到回车符 (\r\n) 即为一次数据传输完成,则修改接收中断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void USART2_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)
{
USART_ClearITPendingBit(USART2,USART_IT_RXNE);
temp=USART_ReceiveData(USART2);
if(temp=='\n')
{
RX_Cnt=0;
RX_Over=1;
}
else
{
RX_Buffer[RX_Cnt]=temp;
RX_Cnt++;
}
}
}

回传和显示数据

实例,给程序添加如下功能:

  • 在 LCD 的第四行显示提示文本:“USART Received:”
  • 在 LCD 的第五行显示接收到的数据,上电时显示:“None.”,字体颜色设置为红色;
  • 设定串口波特率为 115200,并每隔 1 秒通过串口 2 向 PC 发送当前采集到的电压,格式为 “ADC: X.XXV”,每次发送结束均要换行。

定义全局变量:

1
2
u8 USART_Send_Flag=0; //发送时间到达标志位
u8 Send_String[20]; //发送用的字符串

修改定时器中断程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void TIM1_UP_IRQHandler(void)
{
static u16 cnt=0,adc_cnt=0,usart_cnt=0;
if(TIM_GetITStatus(TIM1,TIM_IT_Update)==SET) //1ms到
{
TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
if(++cnt>=50)
{
cnt=0;
Flag50ms=1;
if(++adc_cnt>=10)
{
adc_cnt=0;
ADC_Flag=1;
if(++usart_cnt>=2)
{
usart_cnt=0;//清空USART计时
USART_Send_Flag=1;//1s到
}
}
}
}
}

修改主程序如下:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
int main(void)
{
float ADC_Value=0;

SysTick_Config(SystemCoreClock/1000);
Delay_Ms(200);

STM3210B_LCD_Init();
LCD_Clear(Black);
LCD_SetBackColor(Black);
Timer1_Init();
LED_Init();
Key_Init();
ADC1_Init();

//初始化USART2波特率为115200
USART2_Init(115200);

LCD_SetTextColor(White);
LCD_DisplayStringLine(Line1,(u8*)" Welcome!");
//显示提示符
LCD_DisplayStringLine(Line4,(u8*)" USART Received:");
LCD_DisplayStringLine(Line5,(u8*)" None.");
while(1)
{
if(Flag50ms)
{
Flag50ms=0;
Key_Scan();
}
sprintf((char*)Display_String," LED1-I/O:%d",L1_Status);
LCD_SetTextColor(Blue);
LCD_DisplayStringLine(Line2,Display_String);

if(ADC_Flag)
{
ADC_Flag=0;
ADC_Value=ADC_Get(8)*3.3/4096;
sprintf((char*)Display_String," ADC:%.2fV",ADC_Value);
LCD_SetTextColor(Green);
LCD_DisplayStringLine(Line3,Display_String);
}

//串口发送
if(USART_Send_Flag)
{
USART_Send_Flag=0;
sprintf((char*)Send_String,"ADC:%.2fV\r\n",ADC_Value);
USART2_SendString(Send_String);
}

//串口接收后显示
if(RX_Over)
{
RX_Over=0;
LCD_DisplayStringLine(Line5,RX_Buffer);
}
}
}

下载测试

使用串口助手软件进行调试,设置好对应的参数,然后打开串口,可以看到接收区能够顺利接收到采集到的电压数据,通过串口下发数据,开发板能够正常显示接收到的数据。

优化改进


下载后发现在接收到的数据显示上,多了一个未能够正确识别的字符,其实这个字符就是之前约定的结束标志换行符,若要取消换行符的显示,可以对串口接收程序做如下修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void USART2_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)
{
USART_ClearITPendingBit(USART2,USART_IT_RXNE);
temp=USART_ReceiveData(USART2);
if(temp=='\n')
{
//检测到传输完成后,将回车换行符的'\r'替换为'\0'
RX_Buffer[RX_Cnt-1]='\0';
RX_Cnt=0;
RX_Over=1;
}
else
{
RX_Buffer[RX_Cnt]=temp;
RX_Cnt++;
}
}
}
改进后能够正常显示: ![](https://i.loli.net/2021/01/19/eNxB7VarmTtfHdi.png) 为什么做如上修改可以使后续的字符不予显示?
1
2
3
4
5
6
7
8
9
10
11
12
13
void LCD_DisplayStringLine(u8 Line, u8 \*ptr)
{
u32 i = 0;
u16 refcolumn = 319;//319;

while ((\*ptr != 0) && (i < 20)) // 20
{
LCD_DisplayChar(Line, refcolumn, *ptr);
refcolumn -= 16;
ptr++;
i++;
}
}
在调用程序 LCD_DisplayStringLine 对字符串进行显示时,其显示程序也是做的单字节发送显示,当指针为 0 或者字符串长度超出 20 时,停止显示,故将回车换行的 '\r' 改为 '\0' 即可隐藏后续内容。