stm32-USART
通信


串口通信

硬件电路方面


两边的地位标准一样才能使电压值标准一样,所以对地的线GND必须连接,保证地值一致;若两设备都有独立供电则VCC可以不连接,反之需要向没有独立供电的一方供电,则需要连接VCC。
TTL电平和RS232电平的电压都是一根线相对于GND(地线,0V),单片机使用的就是TTL电平;RS485电平的电压是两根线之间的电压差,抗干扰能力更强,准确传送距离是前两种的百倍,CAN 总线使用的就是这种电平。
软件时序方面

串口使用异步时钟,需要双方约定通信速率,也就是什么时候采集信号,波特率就是控制时间的,例如,波特率=9600时,意为一秒发送9600个信号,那么一个信号时间就是1/9600s,约104us,起始位的时间也是这么多。
空闲即无数据发送时,输出固定高电平;当有信号发送时,先置低电平表示起始位,提示开始发信号了,随后数据会转换为二进制然后先低位后高位发送,发送八位为一个字节;
如果选择无校验的话数据发送完毕后会置高电平表示停止位,意为发送完毕;如果选择奇校验或者偶校验则会在最后一位数据位之后加一个校验位,用于接收端检验数据是否出错,这样就是九位。最后接收端根据波特率规定的时间去
采集信号。停止位的时间可以是0.5倍信号时间、1倍信号时间、1.5倍信号时间和2倍信号时间。

USART
介绍

使用同步的时候就是多了一根时钟线SCLK,但是只能输出不能输入,所以两个USART不能同时通信,一般用于兼容其他协议或者特殊用途,比如同步模式下和SPI协议很相似,就能兼容SPI协议。
硬件流控制就是发送端发送数据太快,接收端来不及处理,接收数据会受影响,可以使用硬件流控制,接收端向发送端发送高低电平来表示自己有没有准备好,发送端就会在它准备好的时候才发送数据。
智能卡、IrDA和LIN是和USART很相似的协议,所以对USART改动一下就能兼容他们。
USART1挂在APB2总线,其余两个在APB1总线。
结构

发送数据时,数据先以二进制的形式存在发送数据寄存器TDR中,此时硬件会检测发送移位寄存器中是否有正在移位的数据,有的话就会等待移位完成,如果没有的话TDR的数据会马上移入移位寄存器中,移入后会置标志位TXE为1,所以检测标志位TXE就知道TDR中是否还有数据,没有数据了就可以继续给TDR写入下一个数据;
发送移位寄存器中的二进制数据会在发射器控制的控制下向右移位,根据高位在左低位在右,就是低位先移出去,移至TX引脚发送出去。
接收数据时,也是低位先从RX引脚进来,放在接收移位寄存器的高位位置,随着后面位进来就向右移位,移位也是由接收器控制来控制;最后全部移入接收数据寄存器RDR中,这时会置标志位RXNE为1,我们对RDR进行读操作就能读出数据。
唤醒单元可以实现多设备通信,往USART地址中写入一个设备的地址,唤醒单元检测到地址写入就会唤醒对应地址的设备来进行通信。
涉及到的四个寄存器在软件层面表现为一个寄存器DR,使用的是同一个地址
SCLK可以产生同步的时钟,只能输出,所以和发送移位寄存器搭配使用,移动一位出去时,时钟电平跳变一个周期。可以用于自适应波特率,接收端不清楚规定的波特率时可以检测这里的周期来计算出波特率。

中断控制是设置连接NVIC的,发送了数据或者接收了数据时置标志位时可以申请中断。
总线的时钟72MHZ除以USARTDIV和16就能得到波特率,控制移位时间,所以波特率本质就是对时钟分频。
流程

噪声处理

起始位低电平时间是波特率时钟,硬件会产生一个16倍波特率时钟的时钟去检测起始位,那么在这段低电平里就会采集16次电平,取第3、5、7次为第一组,8、9、10次为第二组,在一组的这三次中采集的电平应该至少有两个0,那么就认为这是起始位,如果少于两个0,就认为前面检测到的下降沿是噪声导致,会重新检测下降沿。第二组就是这段电平的中间,数值一般是准确的,所以后面采集信号就在第8、9、10次采集。

起始位之后采集信号,检测16次,取8、9、10次,如果三次全为高/低电平,就认为这一位是高/低电平;如果三次中有一次不一样,就认为这一位是次数更多的那个电平。
波特率

就是USART外设时钟,APB2总线就是2,APB1总线就是1。
比如,想要9600的波特率,代入公式,9600=72MHZ/16/DIV,算出DIV的值(DIV可以是小数,能更精确波特率),转换为二进制,写入BRR寄存器中,就能得到想要的波特率。72MHZ/DIV就得到上面说的16倍波特率时钟,再除以16就得到波特率时钟。
发送数据代码
配置USART:开启USART和GPIO的时钟,初始化GPIO,TX引脚配置为复用输出,使用结构体配置USART,这里只需要输出,所以最后直接使能USART
void serial_init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//USART1的TX是PA9
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使用USART1
//初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//初始化USART
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate=9600;//波特率
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//不使用硬件流控制
USART_InitStructure.USART_Mode=USART_Mode_Tx;//发送模式
USART_InitStructure.USART_Parity=USART_Parity_No;//无校验
USART_InitStructure.USART_StopBits=USART_StopBits_1;//一位停止位
USART_InitStructure.USART_WordLength=USART_WordLength_8b;//8位
USART_Init(USART1,&USART_InitStructure);
//开启USART
USART_Cmd(USART1,ENABLE);
}
//发送一个字节
void serial_send(uint8_t byte)
{
USART_SendData(USART1,byte);//写入数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);//等待TXE标志位置一
}
//发送数组
void serial_array(uint8_t *array,uint16_t length)//数组地址和大小
{
uint16_t i;
for(i=0;i<length;i++)
{
serial_send(array[i]);
}
}
//发送字符串
void serial_string(uint8_t *string)//字符串结尾自带空字符结束标志\0,不需要传大小了
{
uint8_t i;
for(i=0;string[i]!='\0';i++)
{
serial_send(string[i]);
}
}
//发送数字,需要将数字每一位取出来发送,第x位就/10的x-1次幂%10
uint32_t Pow(uint8_t x,uint8_t y)//返回值 x的y次幂
{
uint32_t i=1;
while(y--)
{
i*=x;
}
return i;
}
void serial_number(uint32_t num,uint16_t length)
{
uint16_t i;
for(i=0;i<length;i++)
{
serial_send(num/Pow(10,length-i-1)%10+0x30);//0x30表示0,0x31表示1。。。
}
}
接收数据代码
配置USART,开启USART和GPIO的时钟,初始化GPIO,TX引脚配置为复用输出,RX上拉输入或者浮空输入,使用结构体配置USART,
如果使用查询来接收数据,就直接使能USART,在主函数不断判断接收标志位是否置1,然后读取接收寄存器,但这样如果主程序程序多,就不能及时处理串口接收到的数据;
如果使用中断来接收数据,在使能前要打开RXNE标志位的中断并配置NVIC,在中断函数中读取接收数据并在主函数显示
使用中断
uint8_t RXDATA;
void serial_init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//USART1的TX是PA9,RXPA10
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使用USART1
//初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//上拉输入,或者浮空输入,因为空闲时默认高电平
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//初始化USART
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate=9600;//波特率
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//不使用硬件流控制
USART_InitStructure.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;//发送和接收模式
USART_InitStructure.USART_Parity=USART_Parity_No;//无校验
USART_InitStructure.USART_StopBits=USART_StopBits_1;//一位停止位
USART_InitStructure.USART_WordLength=USART_WordLength_8b;//8位
USART_Init(USART1,&USART_InitStructure);
//打开中断
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
//开启USART
USART_Cmd(USART1,ENABLE);
}
void USART1_IRQHandler(void)
{
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)
{
RXDATA=USART_ReceiveData(USART1);
}
}
uint8_t serial_receive(void)
{
return RXDATA;
}
uint8_t RXdata;
int main()
{
OLED_Init();
serial_init();
while(1)
{
RXdata=serial_receive();
OLED_ShowHexNum(1,1,RXdata,2);
}
}
更多推荐

所有评论(0)