就不说什么同步通讯了,一般也都是用的异步串口通讯。
只需要两个线,RX,TX。
连接方式是 1设备的RX接2设备的TX,1设备的TX接2设备的RX
这里只说串口一,其他串口一样的
串口需要的参数:
起始位
数据位(8位或9位【没有校验位】)
奇偶校验位(第九位)
停止位(1,1.5,2位停止位)
波特率
初始化
时钟初始化,串口1的时钟在APB2上,其他串口的都在APB1上
时钟初始化
RCC->APB2ENR|=1<<2; //使能PORTA口时钟
RCC->APB2ENR|=1<<14; //使能串口时钟
初始化引脚
RX(PA10)输入,TX(PA9)输出
空数据的时候是高电平 所以RX用上拉输入 (CRH:1000)
(有搜到这样的话:RX不上拉,悬空的话,电路的某些信号影响(比如射频,大功率器件),就容易误触发,进入接收中断。)
TX就复用推挽输出 (CRH:1011)【不知道为什么是这个数,看这里,CRL,CRH端口配置寄存器】
GPIOA->CRH&=0XFFFFF00F;//PA9,PA10 CRH寄存器清0
GPIOA->CRH|=0X000008B0;//PA9,PA10 CRH寄存器置数
复位
外设有异常的时候通过复位来恢复,一般初始化的时候就复位一次。(复位写一后记得写回0)
RCC->APB2RSTR|=1<<14; //复位串口1
RCC->APB2RSTR&=~(1<<14);//停止复位
波特率设置
接收器和发送器的波特率在USARTDIV的整数和小数寄存器中的值应设置成相同。
524/754
Tx / Rx 波特率 =
这里的fCK是给外设的时钟(PCLK1用于USART2、3、4、5,PCLK2用于USART1)
USARTDIV是一个无符号的定点数。这12位的值设置在USART_BRR寄存器。
注: 在写入USART_BRR之后,波特率计数器会被波特率寄存器的新值替换。因此,不要在通信进行中改变波特率寄存器的数值。
举例子
假设串口1要115200的波特率
整数部分:DIV_Mantissa=39=0X27;
小数部分:DIV_Fraction=16*0. 0625=1=0X01;
合起来给BBR寄存器的:0X0271float temp; u16 mantissa; u16 fraction; temp=(float)(pclk2*1000000)/(bound*16);//得到USARTDIV mantissa=temp;//得到整数部分 fraction=(temp-mantissa)*16; //得到小数部分 mantissa<<=4; mantissa+=fraction;
这是常用的波特率和给出的计算结果。
设置控制寄存器
控制寄存器有3个,USART_CR1,CR2,CR3,这里只看CR1
关于CR2,可能用到设置停止位,如果你不想1位停止位,去CR2设置
这里的代码就是使能了发送接收,接收中断,和USART模块,对应上面的表看就行
USART1->CR1|=0X202C; //1位停止,无校验位,使能接收中断. 0010 0000 0010 1100
设置中断
上面我们使能了接收中断,那么下面要开启串口1的NVIC中断,
setpriority函数(中断源,抢占优先级,相应优先级)
HAL_NVIC_SetPriority(USART1_IRQn, 2, 2);
HAL_NVIC_EnableIRQ(USART1_IRQn);
中断函数(HAL库)
USART1_IRQHandler函数,在USART1产生中断后,就会跳到这里来执行。想写啥就写啥
HAL_UART_IRQHandler(&huart1);会清除中断标志,
后面要继续用中断接收的话,要重新用一次HAL_UART_Receive_IT
使用(HAL库)
关于中断,HAL库中, 中断可以使用USARTx_IRQHandler(),也可以用HAL_UART_RxCpltCallback()
他两个什么区别我也说不来,大概是IRQHandler后面会进行HAL_UART_RxCpltCallback
而HAL_UART_RxCpltCallback是多个UART共用的。
IRQHandler没有__wike修饰,不能重写,所以要去it.c里面修改
RxCpltCallback被__wike修饰,可以重写,但用多个uart时判断麻烦。
结论:
- 如果只用一个uart,用RxCpltCallback
- 如果用多个uart,用IRQHandler
接收任意长度字符并发送回去
初始化都自动生成的,记得在初始化里面加入或者找其他地方加入初始化中断接收
HAL_UART_Receive_IT(&huart1,(uint8_t *)&g_rx_buffer,1);
一次接收一个字节比较灵活。
定义变量
uint8_t g_rx_buffer[1]; //保存的每一次中断接收到的数据,做一个中转
uint8_t g_usart_rx_sta = 0; //用16位数据记录保存到的数据的状态
uint8_t g_usart_rx_buff[256]; //最终保存的数据,可以设置大点2^8=256
int len,times;
回调函数(接收):
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART1) //判断是否是usart1来的中断
{
if((g_usart_rx_sta &0x8000 )== 0) //判断是否接收到过/n (1000 0000 0000 0000)
{
if(g_usart_rx_sta &0x4000) //没接收到过/n,判断是否接收到过/r (0100 0000 0000 0000)
{
if(g_rx_buffer[0] != 0x0A) //接收到过/r,判断当前数据是否是/n
{
g_usart_rx_sta=0; //不是/n,接收了一半回车,有问题,数据不要了
}
else
{
g_usart_rx_sta |=0x8000; //是/n,让最高位置1
}
}
else //没接收到过/r
{
if(g_rx_buffer[0] == 0x0D) //判断当前数据是否为\r
{
g_usart_rx_sta |=0x4000; //是的话把次高位置1
}
else //不是\n,是正常的数据,记下来
{
g_usart_rx_buff[g_usart_rx_sta & 0x3FFF]=g_rx_buffer[0];
g_usart_rx_sta++;
if(g_usart_rx_sta>(256-1)) //数据量过大,清零
{
g_usart_rx_sta=0;
}
}
}
}
}
HAL_UART_Receive_IT(&huart1,g_rx_buffer,1);
}
发送数据
当g_usart_rx_sta最高位为1的时候,发送数据
void uart1_t()
{
if(g_usart_rx_sta & 0x8000)
{
len = g_usart_rx_sta & 0x3fff;
printf("长度:%d",len);
printf("\r\n这次发送的数据:\r\n");
HAL_UART_Transmit(&huart1,g_usart_rx_buff,len,200);
printf("\r\n---------换行-----------\r\n");
g_usart_rx_sta=0;
}
}
重写printf
其实没啥用,不过我觉得挺方便的
有两种写法,建议不用半主机
半主机是作用于ARM目标的一种机制,可以将来自STM32单片机应用程序的输入与输出请求传送至运行仿真器的PC主机上。使用此机制可以启用C库中的函数,如printf()和scanf()等输入与输出函数,使用PC主机的屏幕和键盘。
建议看这里的
半主机模式
#include <stdio.h>
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 50);
return ch;
}
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, &ch, 1, 50);
return ch;
}
在usart.c中引入stdio.h库,然后重写一下fputc,
选择上使用微库,连上SWD
就可以用printf了
这是半主机模式的,没有SWD连接到电脑的话,程序会卡死