STM32串口通信-简单版
STM32串口通信-简单版
参考原子串口通信的程序,编写一个STM32串口简单版的程序,巩固其原理以及要注意的点
串口初始化
初始化的部分都一样,但先不用中断,所以不用初始化NVIC
/**
* @name USART1_Init
* @brief 延时初始化
* @param None
* @retval None
*/
void USART1_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStruc;
USART_InitTypeDef USART_InitStruc;
//初始化GPIO和串口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1,ENABLE);
//初始化PA9,接到TX
GPIO_InitStruc.GPIO_Mode = GPIO_Mode_AF_PP; //设置为推挽输出
GPIO_InitStruc.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruc.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruc);
//初始化PA10,接到RX
GPIO_InitStruc.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置为浮空输入
GPIO_InitStruc.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA,&GPIO_InitStruc);
//初始化串口
USART_InitStruc.USART_BaudRate = bound; //波特率
USART_InitStruc.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件控制流
USART_InitStruc.USART_Mode = USART_Mode_Rx|USART_Mode_Tx; //发送和接收模式
USART_InitStruc.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStruc.USART_StopBits = USART_StopBits_1; //一位停止位
USART_InitStruc.USART_WordLength = USART_WordLength_8b; //8位数据位
USART_Init(USART1,&USART_InitStruc);
//串口1使能
USART_Cmd(USART1,ENABLE);
}
发送一个字符
初始化后,就可以在主函数中进行发送和接收操作了,这里是个最简单的发送操作,调用USART_SendData函数往串口1发送一个字符
开发板上电后,就往串口发送字符H
int main()
{
u8 temp;
//延时初始化
delay_Init();
//串口初始化
USART1_Init(9600);
USART_SendData(USART1,'H');
while(1)
{
}
}
如果连续调用USART_SendData函数发送数据,则会出现问题,如下面程序
int main()
{
u8 temp;
//延时初始化
delay_Init();
//串口初始化
USART1_Init(9600);
USART_SendData(USART1,'H');
USART_SendData(USART1,'E');
USART_SendData(USART1,'L');
USART_SendData(USART1,'K');
while(1)
{
}
}
上电执行,串口助手只收到字符K,多个K是按了多次复位键产生的,刚上电时才只有一个K
这是因为程序执行太快,USART_SendData( )只是把数据放入到TDR寄存器中,数据还没来得及放入发送移位寄存器中,下一个数据就将DR寄存器的值给覆盖了,所以最后只有一个K字符没被覆盖,被转移到了移位寄存器中,再发送了出去
修改:
因为串口的状态寄存器(USART_SR) 的TXE位可以判断数据是否被转移到移位寄存器,所以加上while循环判断,等待TXE位被置1,等数据已经全部转移到移位寄存器中,才将下一个数据装入TDR寄存器中
int main()
{
u8 temp;
//延时初始化
delay_Init();
//串口初始化
USART1_Init(9600);
USART_SendData(USART1,'H');
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
USART_SendData(USART1,'E');
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
USART_SendData(USART1,'L');
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
USART_SendData(USART1,'K');
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
while(1)
{
}
}
串口正常发送了HELK四个字符
接收数据
可以在主函数的while循环中接收上位机发来的数据,再将数据发送回去
接收数据是调用USART_ReceiveData函数,接收标志位是USART_FLAG_RXNE
int main()
{
u8 temp;
//延时初始化
delay_Init();
//串口初始化
USART1_Init(9600);
USART_SendData(USART1,'H');
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
USART_SendData(USART1,'E');
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
USART_SendData(USART1,'L');
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
USART_SendData(USART1,'K');
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
while(1)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET); //等待RXNE标志位为1
temp = USART_ReceiveData(USART1); //接收串口1的数据
USART_SendData(USART1,temp); //再发送出去
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); //等待发送完毕
}
}
程序结果:
上电时单片机会发送‘HELK’四个字符,然后在串口助手上首先发送了abcd四个字符,再发送了”串口接收数据“四个汉字,串口1都接收到了,并原封不动地发送回串口助手上显示
发送字符串
32发送字符串的操作与51的类似
首先可以参考USART_SendData函数,写一个自己的发送字符函数,再写一个发送字符串的函数
USART_SendByte函数体内容可以直接拷贝标准库函数USART_SendData的内容,然后在最后加上等待TXE标志位的while循环即可,因为发送字符串函数需要调用到这个USART_SendByte函数,所以避免在发送字符串时前一个字符还没有转移到移位寄存器,就被后一个字符给覆盖了,造成数据丢失的情况
发送字符串原理:依次发送字符串中字符,每发送一个检查下TXE标志位,发送完全部字符以后,最后检查TC标志位。TC标志位是指数据完全从TX发送出去
/**
* @name USART_SendByte
* @brief 串口发送一个字节
* @param Data:要发送的数据
* @retval None
*/
void USART_SendByte(USART_TypeDef* USARTx, uint8_t Data)
{
/* Check the parameters */
assert_param(IS_USART_ALL_PERIPH(USARTx));
assert_param(IS_USART_DATA(Data));
/* Transmit Data */
USARTx->DR = (Data & (uint16_t)0x01FF);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
}
/**
* @name USART_SendString
* @brief 串口1发送一个字符串
* @param str:要发送的数据
* @retval None
*/
void USART_SendString(USART_TypeDef* USARTx,char* str)
{
while(*str != '\0')
{
USART_SendByte(USARTx,*str++);
}
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);
}
写好函数后,就可以在主函数中调用进行字符串的发送
int main()
{
//延时初始化
delay_Init();
//串口初始化
USART1_Init(9600);
USART_SendByte(USART1,'H');
USART_SendByte(USART1,'E');
USART_SendString(USART1,"串口发送字符串\r\n");
while(1)
{
}
}
程序结果:
每按一次复位键就发送一次’HE’字符和后面的字符串
中断接收数据
正因为原子的串口中断函数写有点复杂,不太好懂,但懂了之后发现其写法挺不错的,不过有时候只需要实现简单的功能,不想写这么复杂,所以这里编写一个简单的中断处理函数,将串口接收到的数据再发送回去
首先要初始化NVIC,再使能串口中断,这些都在串口初始化函数中编写
/**
* @name USART1_Init
* @brief 延时初始化
* @param None
* @retval None
*/
void USART1_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStruc;
USART_InitTypeDef USART_InitStruc;
NVIC_InitTypeDef NVIC_InitStruc;
//初始化GPIO和串口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1,ENABLE);
//初始化PA9,接到TX
GPIO_InitStruc.GPIO_Mode = GPIO_Mode_AF_PP; //设置为推挽输出
GPIO_InitStruc.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruc.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruc);
//初始化PA10,接到RX
GPIO_InitStruc.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置为浮空输入
GPIO_InitStruc.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA,&GPIO_InitStruc);
//NVCI初始化
NVIC_InitStruc.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruc.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruc.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruc.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruc);
//初始化串口
USART_InitStruc.USART_BaudRate = bound; //波特率
USART_InitStruc.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件控制流
USART_InitStruc.USART_Mode = USART_Mode_Rx|USART_Mode_Tx; //发送和接收模式
USART_InitStruc.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStruc.USART_StopBits = USART_StopBits_1; //一位停止位
USART_InitStruc.USART_WordLength = USART_WordLength_8b; //8位数据位
USART_Init(USART1,&USART_InitStruc);
//串口1使能
USART_Cmd(USART1,ENABLE);
//使能串口1接收中断
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
}
编写中断处理函数
/**
* @name USART1_IRQHandler
* @brief 中断处理函数
* @param None
* @retval None
*/
void USART1_IRQHandler(void)
{
u8 temp;
//判断RXNE标志位是否为1,为1表示产生了接收中断
if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET)
{
temp = USART_ReceiveData(USART1); //对USART_DR的读操作可以将RXNE位清零
USART_SendByte(USART1,temp); //将接收到的数据再发送回去
}
}
通过查看状态寄存器(USART_SR) 的RXNE标志位说明可知,只要对USART_DR的读操作就可以将RXNE位清零,所以temp = USART_ReceiveData(USART1); 这一句刚好是将DR寄存器里的值读取到 temp 变量中,所以这一句执行过后RXNE就被清零了,不用手动清零,可以进行下一次数据接收
最后记得在主函数中初始化NVIC分组,while循环里就不用再接收数据了
int main()
{
//NVCI分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置为分组2
//延时初始化
delay_Init();
//串口初始化
USART1_Init(9600);
USART_SendByte(USART1,'H');
USART_SendByte(USART1,'E');
USART_SendString(USART1,"串口发送字符串\r\n");
while(1)
{
}
}
程序结果:
printf 重定向可以看这篇文章:串口通信printf函数重定向