STM32-串口
总是靠点灯来判断执行的位置,是很不方便的,能表达的信息也太少了。所以,还是需要把串口调通,能打印出各种日志,调试起来多么方便啊!
本文关于串口的内容,分为下面几部分:
1,串口的初始化;
2,串口的中断函数;
3,串口输出函数;
4,怎么使用printf函数;
5,控制是否输出调试信息;
6,使用串口时的注意点。
先简单介绍下开发环境,芯片类型是stm32F030C8,集成开发环境用的是Keil5 MDK-ARM,仿真器使用JLINK。
一,串口的初始化
串口设置的一般步骤如下:
1、时钟配置,包括串口时钟与GPIO时钟;
2、GPIO设置;
3、串口参数配置;
4、中断的配置(若有必要)。
这里是以串口2为例,完成的设置过程,代码如下:
void USART2_Init(uint32_t baud)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStruct;
/* 时钟配置 */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); //使能GPIOA的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);//使能USART的时钟
/* USART2的端口配置 */
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_1);//配置PA2成第二功能引脚 TX
GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_1);//配置PA3成第二功能引脚 RX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* USART2的基本配置 */
USART_InitStructure.USART_BaudRate = baud; //波特率
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(USART2, &USART_InitStructure);
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE); //使能接收中断
USART_Cmd(USART2, ENABLE); //使能USART2
/* USART2的NVIC中断配置 */
NVIC_InitStruct.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPriority = 0x02;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
二,串口的中断函数
串口中断里面,通常关注的是两个标志:
USART_IT_TXE:发送中断
USART_IT_RXNE:接收中断
我这里主要关注接收中断,所做的操作,就是简单的将接受的字符再原样送回去,目的就是在串口中能看到输入的内容。
另外,添加了一个溢出中断:USART_FLAG_ORE
串口在接收数据过多时,会出现串口溢出错误,并进入溢出中断(ORE中断)。
void USART2_IRQHandler(void)
{
/* 接收数据临时变量 */
u8 com_data; /* 判断过载错误中断 */
if(USART_GetFlagStatus(USART2,USART_FLAG_ORE) == SET) // 检查 ORE 标志
{
USART_ClearFlag(USART2,USART_FLAG_ORE);
USART_ReceiveData(USART2);
}
/* 判断是否接收中断 */
if( USART_GetITStatus(USART2,USART_IT_RXNE) )
{
/* 清除中断标志 */
USART_ClearITPendingBit(USART2,USART_IT_RXNE); /* 接收数据及后续的任务 */
com_data = USART2->RDR; /* 数据处理解析 */
DT_Data_Receive_Process(com_data);
}
}
三,串口输出函数;
完成上面两步后,就可以使用串口功能了。
例如,使用下面这样的函数,进行一个字符串的输出:
void send_str_usart2(unsigned char *p,unsigned short len)
{
unsigned short i;
for (i=0; i<len; i++)
{
USART_SendData(USART2,*p);
while(USART_GetFlagStatus(USART2,USART_FLAG_TXE) == RESET);//等待发送buffer为空
p++;
}
}
具体调用例子:
在main()中,添加如下两句代码即可实现:
USART2_Init(115200);
send_str_usart2("usart2 start",12);
当然,也要求串口硬件连接正常。例如连接到电脑的串口上,开启了串口终端软件(或者就是一个串口调试助手),设置好波特率,通讯两端保持一致,然后,在电脑的串口终端软件上,就可以看见信息了。
这样虽然能输出信息了,但是还是不太方便啊,只能输出一个字符串,而我希望能打印一些变量的值,怎么办?
一个办法是使用sprintf来做一次转换,类似于这样:
char buf_print[256];
sprintf(buf_print,"num=%d,desc=%s",num,desc);
send_str_usart2(buf_print,strlen(buf_print));
不过,还有一个更好的方法,就是使用printf()。
四,怎么使用printf函数?
参考网上的例子,是这样实现的:
//加入以下代码,支持log函数,而不需要选择use MicroLIB
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using log() for debugging, no file handling */
/* is required. */
};
/* FILE is typedef’ d in stdio.h. */
FILE __stdout; //定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
} //重定义fputc函数
int fputc(int ch, FILE *f)
{
while(!((USART2->ISR)&(1<<7))){}
USART2->TDR=ch;
return (ch);
}
然后,就可以惬意的使用printf了,例如这样:
printf("num=%d,desc=%s\r\n",num,desc);
相比于之前使用sprintf的方法,是不是简洁多了。
五,控制是否输出调试信息
作为调试信息,最好有一个方便的开关,可以控制是否输出调试信息。
我的方法也很简单,就是使用一个宏,来控制是否输出调试信息,如下:
#define PRINT_UART
#ifdef PRINT_UART
#define log(fmt...) printf(fmt)
#else
#define log(fmt...)
#endif
在要输出调试信息的地方,都调用log()函数,就可以方便的进行是否输出的控制了。
只要注释掉宏定义的这行,如下:
//#define PRINT_UART
就不再输出调试信息了。
六,使用串口时的注意点
作为stm32的常用调试方式之一,使用串口输出信息是方便的,但是也有一些注意事项:
1,要知道,串口输出,也是有耗时的,对于某些对时序要求比较高的操作,要小心调试信息的干扰。
如何计算耗时:可以按波特率大致估算:
以波特率等于115200来计算,若输出20字节的调试信息的耗时:
波特率:115200bps=115200位/秒=(115200/8)字节/秒=14400字节/秒=14.4字节/毫秒
20字节耗时:20字节/(14.4字节/毫秒)=1.389ms
调试信息的长度,我就有时不经意的会写出几十字节来。而对于时序要求高的地方,调试信息的这几毫秒,有时会带来致命的影响。这点,一定要牢记在心里。我就是在这点上很是吃过几次亏的。虽然有些弯路总是需要自己去走,也许多经过几次提醒,掉坑的次数会少一点点。
2,中断里面,尽量少用串口调试;
这一条注意事项,其实是上一条的一个具体化的展现。
这算是一个常识了:中断里面,尽量少做耗时的操作。
当然,为了必要的调试,偶尔用用也是可以的,但是,调试完成了,就一定要去掉中断里面的调试信息。
3,如果这个串口同时也是一个通讯口,在调试与通讯功能之间切换,最好也设置好一个宏来进行控制。当然,我希望你实际中不会遇到这种情况。