所谓协议,就是一种交换信息的规定。想要传输信息,必须按照一定的规则才能识别。USART是串行,全双工,异步通信。
TTL: 0~3.3/5V 一般芯片输出的都是TTL;
RS-232:-15~15V,但-15V对应逻辑1,+15V对应逻辑0。主要用于工业设备的通信,高的电平差有较高的容错能力。
USB转串口 :电平转换芯片有CH340,PL2303,CP2102;需要安装驱动。指南者上的是CH340,通过跳帽连接USART1;
起始位:一个逻辑0表示
结束位:一般由一个1个逻辑1表示,也可选择其他方式。
有效数据位:数据位后面就是数据位,stm32中为9个位。
校验位:可以使用校验来提高稳定性。
TX:数据发送。
RX:数据接收。
SCLK:时钟,仅同步通信使用(一般使用异步)
nRTS: 请求发送
nCTS: 允许发送
这些引脚外设可以在数据手册 3 Pinouts and pin descriptions 查到,下面是VET6芯片的引脚总结。
这里列出的是串口复用功能的默认端口,在重映射下,这些功能可以对应其他端口,这样同样可以在数据手册这一章查到。在参考手册 8.3 有更集中的总结。
注意这里只有USART1是挂在在APB2总线下的,其于串口在APB1总线下,打开时钟的时候需要注意。
其反应了发送过程中,发送状态的改变
9位有效,其包含一个发送数据寄存器TDR和接收数据寄存器RDR。
一共有3个控制寄存器,这三个寄存器完成对USART通信模式的控制,如使能,字长,校验等。并且其中有对发送和接收中断的配置。
发送时,起始的配置位有 CR1 的 UE,TE,RE 位。
配置波特率,这个配置需要配置整数和小数部分。这样是因为配置波特率的公式中需要用到小数。
stm32波特率计算公式是:
b a u d = f C K 16 ∗ U S A R T D I V baud = \frac{f_{CK}}{16*USARTDIV} baud=16∗USARTDIVfCK
b a u d baud baud是波特率, f C K f_{CK} fCK是时钟频率, U S A R T D I V USARTDIV USARTDIV就是要配置寄存器写入值
如:想要波特率为115200,配置时钟为72M,可以算出,USARTDIV=39.0625。
如何配置呢?对于整数部分,直接写入39,16进制为0x17。
对于小数,寄存器中留有四个二进制位,可以表示16个数,也就是1被16等分,0001就表示 1 16 \frac{1}{16} 161,因此想要得到0.625,就应该写入0.625/ 1 16 \frac{1}{16} 161=0x01。如果为0.1875,0.1875/ 1 16 \frac{1}{16} 161=0x03。
最终,向寄存器写入0x171表示39.0625.若写入0x173则表示39.1875。(注意这里表示为16进制数)
其实真正的寻找应该从函数开始,到结构体。但那样太乱,这先介绍结构体。
typedef struct
{
uint32_t USART_BaudRate; //配置波特率
uint16_t USART_WordLength; //字长
uint16_t USART_StopBits; //终止位个数
uint16_t USART_Parity; //校验位
uint16_t USART_Mode; //模式:发送,接收
uint16_t USART_HardwareFlowControl; //硬件控制流
} USART_InitTypeDef;
这里列出一些重要的函数。
两个编程任务,第一实现串口发送;第二,实现接收返回,和串口控制led灯。第一部分完成串口的发送。
使用USART接收电脑发送的数据,并且返回这个数据。使用中断完成
因为USART是复用功能,从GPIO原理框图可以看出,必须初始化GPIO。PA9为发送,初始化为推挽输出;PA10为输入,初始化为浮空输入;打开时钟;
static void USART_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&GPIO_InitStruct);
}
初始化USART,配置波特率115200,字长8,停止位1,无校验位,无硬件流。
这里注意
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
这个骚操作,直接把输入和输出模式一起配置了。
注意,最后要使能 USART!
static void USART_Init_Config(void)
{
USART_InitTypeDef USART_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
USART_InitStruct.USART_BaudRate = 115200;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1,&USART_InitStruct);
USART_Cmd(USART1,ENABLE);
}
中端的配置为了第二个实验的接收
USART的中断配置十分简单,一个函数就可以解决。
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
对串口1配置,接收中断。
配置分组为1
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
这里要注意 NVIC_IRQChannel 这个结构体成员变量可赋值的查找。详细请见STM32 EXTI外部中断小结 配置 NVIC 寄存器 部分。
static void USART_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);//配置优先级分组
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
}
下面应该写中断配置函数,在中断配置函数中读取输入数据,然后发送回电脑。这部分内容放在实验二,现在先把上面的代码整合一下。
这样所有的初始化工作完成,编写一个函数集成上面的函数。
void USART_Config(void)
{
USART_GPIO_Config(); //配置GPIO端口
USART_Init_Config(); //配置USART模式
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); //使能中断,接收中断
USART_NVIC_Config(); //配置中断管理
}
配置到这里,程序已经可以使得芯片接收数据,并且跳转到中断中了。
为了完成在中断中读取数据和发送数据我们必须编写函数。
在USART库中有函数 USART_SendData,这个函数实际上已经可以发送一些数据了,这里对其进行包装。使用函数检测是否发送完成。其实这里不用检测。直接使用USART_SendData也可完成这项工作。
void USART_SentByte(USART_TypeDef* pUSARTx, uint8_t data)
{
USART_SendData(pUSARTx, data);
while( USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE) == RESET);
}
函数需要传入USART外设的指针和要发送的数据。
另外注意。虽然USART_SendData 的第二个参数可以传入16位数据,但实际上只有低8位有效,也就是说,使用一次USART_SendData 只能发送一个字节的数据。
注意:单片机发出的信号其实是一串二进制码,串口调试助手默认是以ASCII码的方式显示。在keil中,发送的数据也以ASCII码的形式编码。
这是一张ASCII码对应表,具体可见ASCII码对照表
如:USART_SentByte(USART1, 0x41),这里单片机把 0x41 编码为2进制 0100 0001 ,通过串口发送出去,串口调试助手默认把这串二进制码用ASCII解释,显示A。
如: USART_SentByte(USART1,‘A’) ,这里keil把 A 按ASCII码编码为0100 0001,通过串口发送出去,串口调试助手认把这串二进制码用ASCII解释,显示A。但如果选中16进制显示,则会显示FF41。
因为只能发送一个字节,我们在编写一个可以发送两个字节。
void USART_Sent2Byte(USART_TypeDef* pUSARTx, uint16_t data)
{
uint8_t temp_h,temp_l;
temp_h = (data >> 8) & 0x00ff;
temp_l = data&0x00ff;
USART_SendData(pUSARTx, temp_h);
while( USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE) == RESET);
USART_SendData(pUSARTx, temp_h);
while( USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE) == RESET);
}
其实实际使用中用的最多的是使用重新定向后的printf。记得要在头文件中引用C库 #include
//重新定向C库函数,以便于使用printf
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (uint8_t) ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return (ch);
}
//重新定向C库函数,以便于使用scanf
int fgetc(FILE *f)
{
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART1);
}
更具体的这些函数做到了什么,我也没学,没法解释。但实验的结果可以看出,在printf() 的括号中填入任何长度的字符,都可打印出来。
为了使得代码有较好的移植性,我们使用宏定义对代码进行封装。
// GPIO
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA)
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10
//USART
#define DEBUG_USARTx USART1
#define DEBUG_USART_CLK RCC_APB2Periph_USART1
#define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_BAUDRATE 115200
3.中断部分
初始化NVIC中,channel的选择和中断服务函数的编写用到了USARTx
#define DEBUG_USART_IRQ USART1_IRQn
#define DEBUG_USART_IRQHandler USART1_IRQHandler
这样,整个程序就有了对于USARTx之间的移植性,只要修改宏定义中的参数,就可以开启不同的USART。
第二部分完成数据的接收返回和控制LED。
上面已经完成中断配置的前三个步骤了,这里完成最后一个上面已经完成中断配置的前三个步骤了,这里完成最后一个。
中断服务函数最好写在stm32f10x_it.c中,其名称必须为startup_stm32f10x_hd.s 中定义的名称。
void DEBUG_USART_IRQHandler(void)
{
uint8_t Sentback;
//进入中断后再次检测是否真的置位
if( USART_GetFlagStatus(DEBUG_USARTx,USART_IT_RXNE) != RESET)
{
Sentback = USART_ReceiveData(DEBUG_USARTx);
USART_SendData(DEBUG_USARTx,Sentback);//使用这个函数把得到的数据返回
//也可用printf("%d",Sentback);代替
}
}
直接修改中断服务函数,使用 getchar() 获得输入;使用 switch构成判断。
void DEBUG_USART_IRQHandler(void)
{
uint8_t LED_Common;
LED_Common = getchar();
switch(LED_Common)
{
case '1':LED_R(ON);printf("LED_R ON!\n");Infor_LED[0]=1;break;
case '2':LED_G(ON);printf("LED_R ON!\n");Infor_LED[1]=1;break;
case '3':LED_B(ON);printf("LED_R ON!\n");Infor_LED[2]=1;break;
case '4':LED_R(OFF);printf("LED_R OFF!\n");Infor_LED[0]=0;break;
case '5':LED_G(OFF);printf("LED_R OFF!\n");Infor_LED[1]=0;break;
case '6':LED_B(OFF);printf("LED_R OFF!\n");Infor_LED[2]=0;break;
}
上面提到,如果使用USART_SentByte(USART1, A),函数会把发送的数据以ASIIC码编码,通过串口发送,A 的 ASIIC码 为0100 0001。串口调试助手默认以 ASIIC码 解码 显示 A。若选择 以16进制显示 则显示 41。
同样对于电脑端发送的数据,串口调试助手也以 ASIIC码 编码发送。如发送 A,会编码 0100 0001 发送。若选择以16进制发送,则会发送 1010。