usb转串口驱动使能会影响GPIOA其他口线吗

X-004-UBOOT-串口驱动移植(Bubblegum-96平台)
话说现在的u-boot长得和linux kernel越来越像,设备模型(driver model)、device tree、各种framework(gpio、pinctrl、clock、i2c、regulator、等等),各种概念,均和linux kernel保持一致。这对工程师(特别是linux驱动工程师)来说,是一个利好,因为熟悉了linux kernel相关子系统之后,去搞u-boot基本上就毫无压力了。
不过,对“蜗窝”来说,压力(或者说矛盾)就来了:要不要为u-boot中相关的子系统写分析文章?写吧,实在提不起兴趣,毕竟和kernel类似,我们的重点又在kernel分析上。不写吧,不符合我们的风格啊!
最后,鉴于时间的压力,只能选一个折衷方案:
对于“” u-boot移植过程所涉及的driver模块,只写一篇移植说明,至于其它的,只能战略性放弃(当然,如果有同学有兴趣帮忙补上,我们还是很欢迎的)。
本文是这类文章的第一篇,介绍串口驱动(serial driver)的移植过程。因为u-boot跑起来之后,第一件事就是要把串口输出(console)准备好,以便后续模块的debug。
2. 硬件信息汇整
老规矩,编写设备驱动之前,我们需要罗列该设备和软件有关的硬件信息,这次以问答(当然是自问自答)的形式吧。问题如下:
板子上有哪些串口资源,对外的连接方式如何?
每个串口都有哪些信号,这些信号连接到CPU的哪些管脚上了,管脚的复用情况如何,使用哪些寄存器控制?
串口控制器的power、clock、reset等资源是否可以单独控制,相应的子系统是否已经提供标准的控制接口了,如果没有,由哪些寄存器控制?
串口控制器的寄存器说明,主要包括数据位、校验位、停止位、流控等信息的控制,数据的收发等。
下面我们将分节解答以上问题。
注1:为了节省篇幅,我尽可能的直接给出结论,具体的推导过程,大家可以根据自己板子实际情况,自行揣摩。
注2:u-boot中的串口,主要用作控制台,因此本文只会介绍和控制台相关的串口信息,其它的,大家触类旁通即可。
2.1 板子上有哪些串口资源,对外的连接方式如何?
由“Bubblegum 96boards硬件手册[2]”和“Bubblegum 96boards原理图[3]”的描述可知,Bubblegum-96开发板上有两个串口可用于控制台输出:
位于J2(一个40pin的双排插座)上,pin11和pin13分别是TX和RX(相对于CPU来说);
直接和S900 CPU连接,电平是1.8v;
由于UART接口的工作电平是3.3v,因此要使用特殊的串口子板进行电平转换后,才能通过串口线和PC连接。因此,通常的连接方式是:
&&&&&&& S900&--------&串口子板(1.8/3.3转换)&--------&USB转串口&--------&PC(USB接口)
&&&&&&& S900&--------&串口子板(1.8/3.3转换)&--------&电平转换接口(3.3/5转换)&--------&串口线&--------&PC
具体的连接图片,以及串口子板的使用方法,我就不在这里列出了。
额外的UART测试点,包括3V、TX、RX、GND 4个信号,位于板子的USB接口旁边;
由于只有测试点,需要动下手,焊接一些测试用的PIN脚;
已经包含了1.8/3.3转换电路,不再需要串口子板。因此通常的连接方式是:
&&&&&&& S900&--------&USB转串口&--------&PC(USB接口)
&&&&&&& S900&--------&电平转换接口(3.3/5转换)&--------&串口线&--------&PC
由于开发板都配置了串口子板,因此UART2接口可以暂时忽略。
2.2 每个串口都有哪些信号,这些信号连接到CPU的哪些管脚上了,管脚的复用情况如何,使用哪些寄存器控制?
由“Bubblegum 96boards原理图[3]“可知:
UART5的两个信号名称为“GPIOA25/UART5_RX”和“GPIOA27/UART5_TX”,分别连接到S900的A7和D8两个管脚上。这两个管脚在原理图中的名称分别为“GPIOA25/UART5_RX/SENS0_VSYNC/PWM2”和“GPIOA27/UART5_TX/SENS0_HSYNC/PWM2”,说明它们和GPIOA25、GPIOA27、SENS0_VSYNC、SENS0_HSYNC、PWM2等功能复用。
UART2的两个信号名称为“GPIO_UART2_RX”和“GPIO_UART2_TX”,经过一个电平转换芯片,分别连接到S900的B22和A22两个管脚上。这两个管脚在原理图中的名称分别为“GPIOD0/UART2_RX/OEP”和“GPIOD1/UART2_TX/OEN”,说明它们和GPIOD0、GPIOD1、OEP、OEN等功能复用。
然后参考“S900 IC Spec[1]”,可以知道,上面UART5和UART2的管脚复用都可以通过MFP_CTL1(0xE01B0044)寄存器控制:
MFP_CTL1的bit25:23为100b时,使能UART5_TX,MFP_CTL1的bit28:26为001b时,使能UART5_RX。
MFP_CTL1的bit22为1时,使能UART2_TX和UART2_RX。
2.3 串口控制器的power、clock、reset等资源是否可以单独控制,相应的子系统是否已经提供标准的控制接口了,如果没有,由哪些寄存器控制?
从bubblegum-96公开发布的资料上,找不到任何关于clock、reset等信息。不过还好,我们可以从其kernel的source code中倒推,如下:
serial2: uart@e0124000 {
clocks = &&clock CLK_UART2&;
clock-names = "uart";
resets = &&reset RESET_UART2&;
serial5: uart@e012a000 {
clocks = &&clock CLK_UART5&;
clock-names = "uart";
resets = &&reset RESET_UART5&;
可知:UART2的控制器,由RESET_UART2控制reset,CLK_UART2控制clock;UART5的控制器,由RESET_UART5控制reset,CLK_UART5控制clock。
结合如下三个文件中有关reset的定义:
reset: reset-controller@e01600a8 {
compatible = "actions,s900-reset";
reg = &0 0="" 0xe01600a8="" 0x8=""&;
#reset-cells = &1&;
#define RESET_UART2
#define RESET_UART5
static int owl_clk_reset_assert(struct reset_controller_dev *rcdev,
unsigned long id)
reg = reset-&base + (id / 32) * 4;
spin_lock_irqsave(&reset-&lock, flags);
val = readl(reg);
val &= ~BIT(id % 32);
writel(val, reg);
可知UART2和UART5的reset分别由0xe01600ac(0xe01600a8 + 0x4)的bit7和bit17控制,该寄存器的名称也称作CMU_DEVRST1(可参考中有关的定义)。
同时,结合如下三个文件有关clock的定义:
clock: clock-controller@e0160000 {
compatible = "actions,s900-clock";
reg = &0 0="" 0xe0160000="" 0x1000=""&;
#clock-cells = &1&;
static const char *uart_clk_mux_p[] __initdata = { "hosc", "dev_pll"};
COMP_DIV_CLK(CLK_UART2, "uart2", 0,
C_MUX(uart_clk_mux_p, CMU_UART2CLK, 16, 1, 0),
C_GATE(CMU_DEVCLKEN1, 8, 0),
C_DIVIDER(CMU_UART2CLK, 0, 8, NULL, CLK_DIVIDER_ROUND_CLOSEST)),
COMP_DIV_CLK(CLK_UART5, "uart5", 0,
C_MUX(uart_clk_mux_p, CMU_UART5CLK, 16, 1, 0),
C_GATE(CMU_DEVCLKEN1, 21, 0),
C_DIVIDER(CMU_UART5CLK, 0, 8, NULL, CLK_DIVIDER_ROUND_CLOSEST)),
UART2和UART5的时钟源有两个:hosc和dev_pll(uart_clk_mux_p),由CMU_UART2CLK/CMU_UART5CLK的bit16控制,0为hosc,1为dev_pll;
由于u-boot中clock driver还没有做,为了简单,可以先使用hosc为时钟源,由“Bubblegum 96boards原理图[3]”的描述可知,HOSC的频率为24MHz;
UART2和UART5的时钟大小,由CMU_UART2CLK/CMU_UART5CLK中的bit8:0控制,即:uart clock = clock source / ([bit8:0] + 1);
最后,UART2和UART5控制器的clock,还可以通过CMU_DEVCLKEN1寄存器的bit8和bit21开关;
以上涉及的寄存器地址分别为:
CMU_DEVCLKEN1&&&&&&& 0xE01600A4
CMU_UART2CLK&&&&&&&&&& 0xE0160064
CMU_UART5CLK&&&&&&&&&& 0xE01600B8
2.4 串口控制器的寄存器说明,主要包括数据位、校验位、停止位、流控等信息的控制,数据的收发等
参考“S900 IC Spec[1]”,再结合“”,可以得知:
UART2和UART5的寄存器基址分别为UART2_BASE(0xE0124000)和UART5_BASE(0xE012a000);
UART_CTL(0x0)可以控制串口的数据位(bit1:0)、校验位(bit6:4)、停止位(bit2)、流控(bit12)等信息;
UART_CTL(0x0)的bit15可以控制串口的使能;
UART_RXDAT(0x4)和UART_TXDAT(0x8)可用于数据的收发;
UART_STAT(0xc)指示fifo的状态。
3. 串口驱动的软件框架
移植开始之前,有必要先简单了解一下u-boot中串口驱动有关的软件框架,如下:
图片1 u-boot_serial_driver_architecture
由上图可知:
对下,串口驱动依赖driver model、device tree、clock driver、pinctrl driver等基础模块。
对上,串口驱动向u-boot的console模块提供接口,进而为lib中的printf等提供接口。
另外,为了简化串口驱动的编写,u-boot将串口有关的共性实现,抽象出来并封装在serial uclass中,我们编写驱动的时候,只需要按照serial uclass的规则,填充执行的serial ops即可。具体可以参考后续的说明。
注3:写篇文章的时候,“”的u-boot仅仅完成了启动部分的移植,所有的基础模块都没有实现。因此,串口驱动的移植的过程中,所涉及到的基础操作,如clock、pinmux等,暂时只能通过寄存器操作代替。与此同时,device tree暂未支持,我们只能使用传统的driver、device注册方式。
4. 串口驱动移植过程
4.1 修改版型的配置文件,使u-boot可以正确启动
我们在“”中,以U-boot SPL为例,介绍并移植了基于SPL的启动过程。由于串口驱动没有必要在SPL中使用,所以我们需要比照SPL的移植过程,将u-boot run起来。
移植的过程比较简单,只需要修改“include/configs/bubblegum.h”中相应的配置项即可,具体过程和SPL类似,这里不再详细描述,感兴趣的同学可以参考这个patch:
4.2 在drivers/serial目录中,添加串口驱动
步骤如下:
1)在drivers/serial中创建serial_owl.c文件(owl的命名是从bubblegum-96board的官方github[4]上抄来的,不太清楚原因,可以是SOC的系列名称,后续的driver开发,也会遵守该命名规范)。
2)修改drivers/serial/Kconfig和drivers/serial/Makefile两个文件,将serial_owl.c添加进去。
具体可参考如下patch:
4.3 配置u-boot,使能DM、使能DM_SERIAL、使能OWL_SERIAL
cd ./x_project/build
make uboot-config
具体可参考如下patch:
4.4 利用serial uclass提供的接口,编写串口驱动
1)在drivers/serial/serial_owl.c中,调用U_BOOT_DRIVER,定义serial driver,如下:
U_BOOT_DRIVER(serial_owl) = {
&&& .name&&& = "serial_owl",
&&& .id&&& = UCLASS_SERIAL,
&&& .probe = owl_serial_probe,
&&& .ops&&& = &owl_serial_ops,
&&& .flags = DM_FLAG_PRE_RELOC,
该driver的name为“serial_owl”(用于device的bind),id为UCLASS_SERIAL,DM_FLAG_PRE_RELOC表示在relocation[5]之前可能会使用,其它字段后面会详细介绍。
2)调用U_BOOT_DEVICE,静态定义serial device
由于此时device tree没有ready,暂时使用静态定义的方式,如下:
/* TODO */
U_BOOT_DEVICE(stm32_serials) = {
&&& .name = "serial_owl",
3)实现driver的probe接口
serial driver和serial device绑定的时候,会调用probe函数(owl_serial_probe),该函数需要完成串口有关的初始化操作,包括:
管脚复用的配置;
串口控制器的总线时钟、reset等配置;
串口参数的配置(波特率、停止位、校验位等);
串口的使能。
具体可参考“”
4)实现owl_serial_ops
u-boot的driver model,比较依赖class的概念,对serial而言,serial uclass通过struct dm_serial_ops 数据结构,将串口有关的通用操作封装在一起,因此串口驱动的开发,从论述题转变为了填空题(填充struct dm_serial_ops)。struct dm_serial_ops的定义可参考下面文件(u-boot的注释写的很好,点赞!!):
对“x project”的使用场景来说,实现putc、pending、getc、setbrg四个回调函数即可,如下:
static const struct dm_serial_ops owl_serial_ops = {
&&& .putc =&&& owl_serial_putc,
&&& .pending = owl_serial_pending,
&&& .getc =&&& owl_serial_getc,
&&& .setbrg&&& = owl_serial_setbrg,
具体可参考“”。
5)串口波特率的计算
对串口驱动来说,需要注意波特率的计算,公式如下:
divider = HOSC_FREQ / (baudrate * 8);
if (divider & 0)
&&& divider--;
clrsetbits_le32(CMU_UART5CLK, 0x1f, divider);
4.5 编译后,验证是否okay
编译成功后,将bubblegum-96 board通过串口子板,以串口线(或USB转串口),和PC连接后,使用下面命令将u-boot上传到SRAM中执行,在控制台上应该就可以看到u-boot的输出信息了:
sudo ../tools/dfu/dfu bubblegum 0xe406b200 out/u-boot/u-boot.bin 1
本文简单介绍了在u-boot中实现串口驱动的基本方法,由于很多基础子系统的代码没有ready,串口驱动的实现比较简陋,仅仅通过操作寄存器的方式,实现了UART5(或UART2)的代码,尚且不能动态切换。后续随着“”的进行,我们将继续完善之。
另外,由于资料的欠缺,本文的大部分篇幅,都花在了串口有关的硬件信息的获取上,这在正常的开发过程中是不存在的,因此u-boot下的串口驱动开发,还是比较简单的。
6. 参考文档
[1] S900 IC Spec,
[2] Bubblegum 96boards硬件手册,
[3] Bubblegum 96boards原理图,
原创文章,转发请注明出处。蜗窝科技,。STM32(32)

串口作为&MCU&的重要外部接口,同时也是软件开发重要的调试手段,&其重要性不言而喻。STM32&的串口资源相当丰富的,功能也相当强劲。ALIENTEK&战舰&STM32&开发板所使用的&STM32F103ZET6&最多可提供&5&路串口,有分数波特率发生器、支持同步单线通信和半双工单线通讯、支持&LIN、&支持调制解调器操作、&智能卡协议和&IrDA&SIR&ENDEC&规范、具有&DMA等。
串口设置的一般步骤可以总结为如下几个步骤:
1)&&串口时钟使能,GPIO&时钟使能
2)&&串口复位
3)&&GPIO&端口模式设置
4)&&串口参数初始化
5)&&开启中断并且初始化&NVIC(如果需要开启中断才需要这个步骤)
6)&&使能串口
7)&&编写中断处理函数
与串口基本配置直接相关的几个固件库函数。这些函数和定义主要分布在&stm32f10x_usart.h&和&stm32f10x_usart.c&文件中。
1.串口时钟使能。串口是挂载在&APB2&下面的外设,所以使能函数为:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1);
2.串口复位。当外设出现异常的时候可以通过复位设置,
实现该外设的复位,然后重新配置这个外设达到让其重新工作的目的。一般在系统刚开始配置外设的时候,都会先执行复位该外设的操作。复位的是在函数&USART_DeInit()中完成:
void&USART_DeInit(USART_TypeDef*&USARTx);//串口复位&
比如我们要复位串口&1,方法为:
USART_DeInit(USART1);&&&//复位串口&1
3.串口参数初始化。串口初始化是通过&USART_Init()函数实现的,
void&USART_Init(USART_TypeDef*&USARTx,&USART_InitTypeDef*&USART_InitStruct);
这个函数的第一个入口参数是指定初始化的串口标号,这里选择&USART1。
第二个入口参数是一个&USART_InitTypeDef&类型的结构体指针,这个结构体指针的成员变量用来设置串口的一些参数。一般的实现格式为:
  USART_InitStructure.USART_BaudRate
//一般设置为 9600;
  USART_InitStructure.USART_WordLength
= USART_WordLength_8b;//字长为 8 位数据格式
  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(USART1,
&USART_InitStructure);
4.数据发送与接收。STM32&的发送与接收是通过数据寄存器&USART_DR&来实现的,这是一个双寄存器,包含了&TDR&和&RDR。当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也是存在该寄存器内。&
STM32&库函数操作&USART_DR&寄存器发送数据的函数是:
void&USART_SendData(USART_TypeDef*&USARTx,&uint16_t&Data);
通过该函数向串口寄存器&USART_DR&写入一个数据。
STM32&库函数操作&USART_DR&寄存器读取串口接收到的数据的函数是:
uint16_t&USART_ReceiveData(USART_TypeDef*&USARTx);
通过该函数可以读取串口接受到的数据。
在固件库函数里面,读取串口状态的函数是:
FlagStatus&USART_GetFlagStatus(USART_TypeDef*&USARTx,&uint16_t&USART_FLAG);
这个函数的第二个入口参数非常关键,它是标示我们要查看串口的哪种状态,比如RXNE(读数据寄存器非空)以及&TC(发送完成)。例如我们要判断读寄存器是否非空(RXNE),操作库函数的方法是:
USART_GetFlagStatus(USART1,&USART_FLAG_RXNE);
我们要判断发送是否完成(TC),操作库函数的方法是:
USART_GetFlagStatus(USART1,&USART_FLAG_TC);&
6.串口使能。串口使能是通过函数&USART_Cmd()来实现的,这个很容易理解,使用方法是:USART_Cmd(USART1,&ENABLE);&&&&&&&&&&&&&&&&&&&&&//使能串口&&&
7.开启串口响应中断。有些时候当我们还需要开启串口中断,那么我们还需要使能串口中断,使能串口中断的函数是:
void&USART_ITConfig(USART_TypeDef*&USARTx,&uint16_t&USART_IT,&FunctionalState&NewState)
这个函数的第二个入口参数是标示使能串口的类型,也就是使能哪种中断,因为串口的中断类型有很多种。&比如在接收到数据的时候(RXNE&读数据寄存器非空),我们要产生中断,那么我们开启中断的方法是:
USART_ITConfig(USART1,&USART_IT_RXNE,&ENABLE);//开启中断,接收到数据中断
我们在发送数据结束的时候(TC,发送完成)要产生中断,那么方法是:
USART_ITConfig(USART1,USART_IT_TC,ENABLE);
8.获取相应中断状态。当我们使能了某个中断的时候,当该中断发生了,就会设置状态寄存器中的某个标志位。&经常我们在中断处理函数中,要判断该中断是哪种中断,使用的函数是:&&
ITStatus&USART_GetITStatus(USART_TypeDef*&USARTx,&uint16_t&USART_IT)
比如我们使能了串口发送完成中断,那么当中断发生了,&&&我们便可以在中断处理函数中调用这个函数来判断到底是否是串口发送完成中断,方法是:
USART_GetITStatus(USART1,&USART_IT_TC)
返回值是&SET,说明是串口发送完成中断发生。
//初始化 IO 串口 1
//bound:波特率
void uart_init(u32 bound)
  GPIO_InitTypeDef GPIO_InitStructure;
  USART_InitTypeDef USART_InitStructure;
  NVIC_InitTypeDef NVIC_InitStructure;
  //①串口时钟使能,GPIO 时钟使能,复用时钟使能
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|
  RCC_APB2Periph_GPIOA, ENABLE);
//使能 USART1,GPIOA 时钟
  //②串口复位
  USART_DeInit(USART1);
//复位串口 1
  //③GPIO 端口模式设置
  GPIO_InitStructure.GPIO_Pin
= GPIO_Pin_9;
//ISART1_TX PA.9
  GPIO_InitStructure.GPIO_Speed
= GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode
= GPIO_Mode_AF_PP;
//复用推挽输出
  GPIO_Init(GPIOA,
&GPIO_InitStructure);
//初始化 GPIOA.9
  GPIO_InitStructure.GPIO_Pin
= GPIO_Pin_10;
//USART1_RX PA.10
  GPIO_InitStructure.GPIO_Mode
= GPIO_Mode_IN_FLOATING;
//浮空输入
  GPIO_Init(GPIOA,
&GPIO_InitStructure);
//初始化 GPIOA.10
  //④串口参数初始化
  USART_InitStructure.USART_BaudRate
//波特率设置
  USART_InitStructure.USART_WordLength
= USART_WordLength_8b;
//字长为 8 位
  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(USART1,
&USART_InitStructure);
//初始化串口
  #if EN_USART1_RX
//如果使能了接收
  //⑤初始化 NVIC
  NVIC_InitStructure.NVIC_IRQChannel
= USART1_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3
; //抢占优先级 3
  NVIC_InitStructure.NVIC_IRQChannelSubPriority
//子优先级 3
  NVIC_InitStructure.NVIC_IRQChannelCmd
//IRQ 通道使能
  NVIC_Init(&NVIC_InitStructure);
//中断优先级初始化
  //⑤开启中断
  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//开启中断
  #endif
  //⑥使能串口
  USART_Cmd(USART1, ENABLE);
//使能串口
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:101714次
积分:1508
积分:1508
排名:千里之外
原创:40篇
转载:65篇
评论:14条
(1)(3)(1)(3)(1)(2)(2)(1)(1)(5)(1)(2)(1)(1)(3)(6)(5)(5)(3)(12)(9)(7)(30)6.4.&串口通信的收与发-STM32(初学宝典)神舟IV号开发板
什么是串口通信
串口通信是指外设和计算机间,通过数据信号线
、地线、控制线等,按位进行传输数据的一种通讯方式。这种通信方式使用的数据线少,在远距离通信中可以节约通信成本,但其传输速度比并行传输低。
串口是计算机上一种非常通用的设备通信协议。大多数计算机(不包括笔记本电脑)包含两个基于RS-232的串口。串口同时也是仪器仪表设备通用的通信协议(串口通信协议也可以用于获取远程采集设备的数据)。
当年51单片机内置串口的时候,被认为是微控制器发展史上的重大事件,因为当时的串口是唯一一个微控制器与PC交互的接口。MCU微控制器经过这么多年的发展,串口仍然是其必不可少的接口之一。
6.4.2 串口通信的属性
通信存在的问题
评价一个通信是否优质,主要体现在传输的速度,数据的正确性,功耗是否低,布线成本是否低(例如1根线收发都能满足就比8根线的并行收发要节约成本);使用是否普及(就好像大家都学英语,世界很大部分的人都可以独立使用英语吗,会英语的人多,就非常普及,可通信面就非常广;如果你学的鸟语,那就只能跟鸟通信,没有人能听懂)。
串口到底有几个标准?(经常听说有3线、5线串口)
传统的串行接口标准有22根线,采用标准25芯D型插头座(DB25),后来使用简化为9芯D型插座(DB9),现在应用中25芯插头座已很少采用。
像现在所说的几线串口,一般都是指使用了几根线,最初的RS-232串口是25针的,所有的针脚定义都有用到,后来变成了9针的,所谓全功能串口就是所有的针脚定义都使用上了,例如流量控制,握手信号等都有用到,一般来说国外的产品做产品比较规矩,把所有的串口信号都做上去了。但是国内的技术人员发现,其实RS-232串口最主要使用的就是2,3线,另外的接口如果不使用的话,也不会出现很大的问题,所以,就在9针的基础上做精简,所以就有所谓的2,3,4,5,6,8线的串口出来了。.
2线串口只有RXD,TXD两根基本的收发信号线;3线串口除了RXD和TXD,还有GND;所谓4~9线只是在TXD和RXD基础上增加了相应的控制信号线,依据实际需要进行设计。
一般来说,使用5线的232通信,是加了硬件流控的,即RTS,CTS信号,主要是为了保证高速通信时的可靠性,如果你的通信速度不是很高,完全可以不用理会。
串口的速度与距离
RS-232(串口的英文代名词)采取不平衡传输方式,即所谓单端通讯。由于其发送电平与接收电平的差仅为2V至3V左右,所以其共模抑制能力差,再加上双绞线上的分布电容,其传送距离最长为约15米,最高速率为20kb/s。RS-232是为点对点(即只用一对收、发设备)通讯而设计的,其驱动器负载为3~7kΩ。所以RS-232适合本地设备之间的通信。
从串口通信衍生出422与485的通信方式
RS-232、RS-422与RS-485都是串行数据接口标准,最初都是由电子工业协会(EIA)制订并发布的,RS-232在1962年发布,命名为EIA-232-E,作为工业标准,以保证不同厂家产品之间的兼容。
RS-422由RS-232发展而来,它是为弥补RS-232之不足而提出的。为改进RS-232通信距离短、速率低的缺点,RS-422定义了一种平衡通信接口,将传输速率提高到10Mb/s,传输距离延长到4000英尺(速率低于100kb/s时),并允许在一条平衡总线上连接最多10个接收器。RS-422是一种单机发送、多机接收的单向、平衡传输规范,被命名为TIA/EIA-422-A标准。
为扩展应用范围,EIA又于1983年在RS-422基础上制定了RS-485标准,增加了多点、双向通信能力,即允许多个发送器连接到同一条总线上,同时增加了发送器的驱动能力和冲突保护特性,扩展了总线共模范围,后命名为TIA/EIA-485-A标准。
由于EIA提出的建议标准都是以“RS”作为前缀,所以在通讯工业领域,仍然习惯将上述标准以RS作前缀称谓。&&&&&&&&&&&
RS-232、RS-422与RS-485标准只对接口的电气特性做出规定,而不涉及接插件、电缆或协议,在此基础上用户可以建立自己的高层通信协议。因此在视频界的应用,许多厂家都建立了一套高层通信协议,或公开或厂家独家使用。如录像机厂家中的Sony与松下对录像机的RS-422控制协议是有差异的,视频服务器上的控制协议则更多了,如Louth、Odetis协议是公开的,而ProLINK则是基于Profile上的。
串口的通信方式(串口属于串行通信)
(1)并行通信和串行通信
51单片机与外界通信的基本方式有两种:并行通信和串行通信,并行通信是指利用多条数据传输线将一个数据的各位同时发送或接收。串行通信是指利用一条传输线将数据一位位地顺序发送或接收。
并行通信和串行通信的示意图如下图:
在每一条传输线传输速率相同时,并行通信的传输速度比和串行通信快。然而当传输距离变长时,并行通信的缺点就会凸显,首先是相比于串行通信而言信号易受外部干扰,信号线之间的相互干扰也增加,其次是速率提升之后不能保证每根数据线的数据同时到达接收方而产生接收错误,而且距离越长布线成本越高。
所以并行通信目前主要用在短距离通信,比如处理器与外部的flash以及外部RAM以及芯片内部各个功能模块之间的通信。串行通信以其通信速率快和成本低等优点成为了远距离通信的首选。RS232C串口,以及差分串行总线像RS485串口、USB接口、CAN接口、IEEE-1394接口、以太网接口、SATA接口和PCIE接口等都属于串行通信的范畴。
下图左侧为每根数据线的数据同时到达接收方,被正确采样的最理想情况;右侧的图为每根数据线的数据不能同时到达接收方而产生接收错误情形。
(2)异步通信与同步通信
串行通信又分为两种方式:异步通信与同步通信。
A、 异步通信及其协议
异步通信以一个字符为传输单位,通信中两个字符间的时间间隔不固定可以是任意长的,然而在同一个字符中的两个相邻位代码间的时间间隔是固定的,接收时钟和发送时钟只要相近就可以。
通信双方必须使用约定的相同的一些规则(也叫通信协议)。
常见的传送一个字符的信息格式规定有起始位、数据位、奇偶校验位、停止位等,其中各位的意义如下:
&① 起始位&
先发出一个逻辑”0”信号,表示传输字符的开始。
&② 数据位&
紧接着起始位之后。数据位的个数可以是5、6、7、8等,构成一个字符。一般采用扩展的ASCII码,范围是0~255,使用8位表示。首先传送最低位。
&③ 奇偶校验位(不是必须)&
奇偶校验是串口通信中一种简单的检错方式,当然没有校验位也是可以的。数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。例如,如果数据是,那么对于偶校验,校验位为0。
&④ 停止位&
它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
&⑤ 空闲位&
处于逻辑“1”状态,表示当前线路上没有数据传送。
B、同步通信是指数据传送是以一个帧(数据块或一组字符)为传输单位,每个帧中包含有多个字符。在通信过程中,字符与字符之间、字符内部的位与位之间都同步,每个字符间的时间间隔是相等的,而且每个字符中各相邻位代码间的时间间隔也是固定的。同步通信的数据格式如图所示
同步通信的特点可以概括为:
① 以数据块为单位传送信息。
② 在一个数据块(信息帧)内,字符与字符间无间隔。
③ 接收时钟与发送进钟严格同步
同步串行通信方式中一次连续传输一块数据,开始前使用同步信号作为同步的依据。同步字符的插入可以是单同步字符方式或双同步字符方式,均由同步字符、数据字符和校验字符CRC等三部分组成:
同步字符位于帧结构开头,用于确认数据字符的开始。
数据字符在同步字符之后,字符个数不受限制,由所需传输的数据块长度决定;
校验字符有1~2个,位于帧结构末尾,用于接收端对接收到的数据字符的正确性的校验。
由于连续传输一个数据块,故收发双方时钟必须相当一致,否则时钟漂移会造成接收方数据辨认错误。这种方式下往往是发送方在发送数据的同时也通过一根专门的时钟信号线同时发送时钟信息,接收方使用发送方的时钟来接由数据。同步串行通信方式传输效率高,但对硬件要求高,电路结构复杂。
所有的串行接口电路都是以并行数据形式与CPU接口、而以串行数据形式与外部逻辑接口。所以串口对外应该是串行发送的,速度慢,但是比并行传输要稳定很多。
串口是如何解决干扰以及校验的问题
什么是数据校验?通俗的说,就是为保证数据的完整性,用一种指定的算法对原始数据计算出的一个校验值。接收方用同样的算法计算一次校验值,如果和随数据提供的校验值一样,就说明数据是完整的。
为了理解数据校验,什么是最简单的校验呢?最简单的校验就是把原始数据和待比较数据直接进行比较,看是否完全一样这种方法是最安全最准确的。同时这样的比对方式也是效率最低的。只适用于简单的数据量极小的通信。
串口通信使用的是奇偶校验方法,具体实现方法是在数据存储和传输中,字节中额外增加一个比特位,用来检验错误。校验位可以通过数据位异或计算出来;也就是说单片机串口通讯有一模式就是一次发送8位的数据通讯,增加一位第9位用于放校验值。
奇偶校验是一种校验代码传输正确性的方法。根据被传输的一组二进制代码的数位中“1”的个数是奇数或偶数来进行校验。采用奇数的称为奇校验,反之,称为偶校验。采用何种校验是事先规定好的。通常专门设置一个奇偶校验位,用它使这组代码中“1”的个数为奇数或偶数。若用奇校验,则当接收端收到这组代码时,校验“1”的个数是否为奇数,从而确定传输代码的正确性。
奇偶校验能够检测出信息传输过程中的部分误码(1位误码能检出,2位及2位以上误码不能检出),同时,它不能纠错。在发现错误后,只能要求重发。但由于其实现简单,仍得到了广泛使用。
什么是单片机的TTL电平?
单片机是一种数字集成芯片,数字电路中只有两种电平:高电平和低电平;高电平和低电平是通过单片机的管脚进行输入和输出的,我们只要记住一句话,单片机管脚不是输入就是输出,不是高电平就是低电平。
为了让大家在初学的时候对电平特性有一个清晰的认识,我们暂且定义单片机输出与输入为TTL电平,其中高电平为+5V,低电平为OV。计算机的串口出来的为RS-232C电平,其中高电平为-5V—-12V,低电平为+5V—+12V。这里要强调的是,RS-232C电平为负逻辑电平,所以高电平为负的,低电平为正的,大家千万不要认为上面是我写错了,因此当计算机与单片机之间要通信时,需要加电平转换芯片,我们在神舟51单片机实验板上所加的电平转换芯片是MAX3232(在串口DB9座附近)。初学者在学习时先掌握上面这点就够了,若有兴趣请大家再看下面的知识点——常用逻辑电平。
& & 知识点:常用逻辑电平
常用的逻辑电平有TTL、CMOS、LVTTL、ECL、PECL、GTL、RS-232、RS-422、RS-485、LVDS等。其中TTL和CMOS的逻辑电平按典型电压可分为四类:5V系列(5V的TTL和5V的CMOS)、3.3V系列,2.5V系列和1.8V系列。
5V的TTL和5V的CMOS是通用的逻辑电平。3.3V及以下的逻辑电平被称为低电压逻辑电平,常用的为LVTTL电平。低电压逻辑电平还有2.5V和1.8V两种。
那为什么TTL电平信号用的最多呢?
原因1:这是因为大部分数字电路器件都用这个电平标准。就好像我们学英语,国际通用英语这门语言,那大家都用这个语言进行交流和沟通,所以后来的人都要学习英语才能彼此相互能交流。所以使得越来越多的电路器件使用这个电平标准。TTL电平数据表示通常采用二进制,+5V等同于逻辑1,OV等同于逻辑O,这被称为TTL(晶体管一晶体管逻辑电平)信号系统,这是计算机处理器控制的设备内部各部分之问通信的标准技术。TTL电平信号对于计算机处理器控制的设备内部的数据传输是很理想的,首先计算机处理器控制的设备内部的数据传输对于电源的要求不高,热损耗也较低,另外TTL电平信号直接与集成电路连接而不需要价格昂贵的线路驱动器以及接收器电路。
原因2:TTL电平的特点适合设备内数据高速的传输。TTL的通信大多数情况是采用并行数据传输方式,但电平最高为+5V,电压相对比较低,所以传输过程中会有电压损耗和压降,导致TTL的传输距离是有限的,一般只适合近距离传输;而且并行数据传输对于超过10英尺的距离就可能会有同步偏差,传输距离太远,有可能造成数据不同步;所以TTL电平符合近距离(在芯片内部或者计算机内部进行高速数据交互)高速的并行传输,在数字电路要求数据处理速度高的时代来说,选择TTL这个标准是正确的,可靠的。
CMOS电平最高可达12V,CMOS电路输出高电平在3V~12V之间,而输出低电平接近0伏。CMOS电路中不使用的输入端不能悬空,否则会造成逻辑混乱。另外,CMOS集成电路因为电源电压可以在较大范围内变化,因而对电源的要求不像TTL集成电路那样严格。
TTL电路和CMOS电路的逻辑电平关系如下:
CMOS是场效应管构成,TTL为双极晶体管构成;因为TTL和COMS的高低电平的值不一样,所以互相连接时需
要电平的转换。
TTL电路是电流控制器件,而coms电路是电压控制器件。
TTL电路的速度快,传输延迟时间短(5-10ns),但是功耗大;COMS电路的速度慢,传输延迟时间长(25-50ns),但功耗低,COMS电路本身的功耗与输入信号的脉冲频率有关,频率越高,芯片集越热,这是正常现象。
CMOS集成电路电源电压可以在较大范围内变化,因而对电源的要求不像TTL集成电路那样严格。所以,用TTL电平在条件允许下他们就可以兼容。要注意到他们的驱动能力是不一样的,CMOS的驱动能力会大一些,有时候TTL的低电平触发不了CMOS电路,有时CMOS的高电平会损坏TTL电路,在兼容性上需注意。
CMOS的高低电平之间相差比较大、抗干扰性强,TTL则相差小,抗干扰能力差。
CMOS的工作频率较TTL略低。
TTL电平临界值:
1)TTL输出电压:逻辑电平1 = 2.4V,逻辑电平0
2)TTL输入电压:逻辑电平1 = 2.0V,逻辑电平0
CMOS电平临界值(设电源电压为+5V)
1) CMOS输出电压:逻辑电平1 =
4.99V,逻辑电平0 = 0.01V
2) CMOS输入电压:逻辑电平1 =
3.5V,逻辑电平0 = 1.5V
常用逻辑芯片的特点如下:
74LS系列:&&&
输入:TTL&&&
74HC系列:&&&
CMOS& 输入:&
CMOS& 输出:&
74HCT系列:&&
输入:TTL&&&
输出:& CMOS
CD4000系列:&
CMOS& 输入:&
CMOS& 输出:&
通常情况下,单片机、ARM、DSP、FPGA等各个器件之间引脚能否直接相连要参考以下方法进行判断:一般来说,同电压的是可以相连的,不过最好还是好好查看芯片技术手册上的VIL(逻辑电平0的输入电压)、VIH(逻辑电平1的输入电压)、VOL(逻辑电平0的输出电压)、VOH(逻辑电平1的输出电压)的值,看是否能够匹配。有些情况在一般应用中没有问题,虽然参数上有点不够匹配,但还是在管脚的最大和最小容忍值范围之内,不过有可能在某些情况下可能就不够稳定,所以我们在设计电路的时候要尽量保持匹配,这样是最佳的设计。
6.4.4 关于NPN和PNP的三极管基础知识?
对三极管放大作用的理解,切记一点:能量不会无缘无故的产生,所以,三极管一定不会产生能量,但三极管厉害的地方在于:它可以通过小电流控制大电流。放大的原理就在于:通过小的交流输入,控制大的静态直流。假设三极管是个大坝,这个大坝奇怪的地方是,有两个阀门,一个大阀门,一个小阀门。小阀门可以用人力打开,大阀门很重,人力是打不开的,只能通过小阀门的水力打开。所以,平常的工作流程便是,每当放水的时候,人们就打开小阀门,很小的水流涓涓流出,这涓涓细流冲击大阀门的开关,大阀门随之打开,汹涌的江水滔滔流下。如果不停地改变小阀门开启的大小,那么大阀门也相应地不停改变,假若能严格地按比例改变,那么,完美的控制就完成了。
在这里,基极B-&发射极E就是小水流,集电极C-&发射极E就是大水流。当然,如果把水流比为电流的话,会更确切,因为三极管毕竟是一个电流控制元件。
如果某一天,天气很旱,江水没有了,也就是大的水流那边是空的。管理员这时候打开了小阀门,尽管小阀门还是一如既往地冲击大阀门,并使之开启,但因为没有水流的存在,
所以,并没有水流出来。这就是三极管中的截止区。
饱和区是一样的,因为此时江水达到了很大很大的程度,管理员开的阀门大小已经没用了。如果不开阀门江水就自己冲开了,这就是二极管的击穿。
在模拟电路中,一般阀门是半开的,通过控制其开启大小来决定输出水流的大小。没有信号的时候,水流也会流,所以,不工作的时候,也会有功耗。
而在数字电路中,阀门则处于开或是关两个状态。当不工作的时候,阀门是完全关闭的,没有功耗。
那么NPN与PNP的三极管到底有些什么区别呢?
NPN和PNP主要就是电流方向和电压正负不同,说得“专业”一点,就是“极性”问题。
B→E 的电流(小水流)控制
的电流(大水流),E极电位最低,且正常放大时通常C极电位最高,即
VC&VB&VE。
E→B 的电流(小水流)控制
的电流(大水流),E极电位最高,且正常放大时通常C极电位最低,即VC。
半导体三极管也称为晶体三极管,可以说它是电子电路中最重要的器件。它最主要的功能是电流放大和开关作用。接下来的一些使用中会用到。
6.4.5 RS-232电平与TTL电平的转换
关于RS-232电平与TTL电平的特性在前面已经讲过,本节主要讲解使用较多的计算机RS-232电平与单片机TTL电平之间的转换方式。MAX232等芯片可实现RS-232电平到TTL电平的转换,但是现在用的较多还有MAX202,HIN232等芯片,它们同时集成了RS-232电平和TTL电平之间的互转。为丰富大家的知识,下面首先讲解在没有MAX3232这种现成电平转换芯片时,如何用二极管、三极管、电阻、电容等分立元件搭建一个简单的RS-232电平与TL电平之间的转换电路。
1.用单独的电容电阻三极管实现RS-232电平与TTL电平转换电路
集成芯片内部都是由最基本电子元件组成,如电阻、电容、二极管、三极管等元件,为了方便用户使用,制造商把这些具有一定功能的分立元件封装到一个芯片内,这样就制成了我们使用的各种芯片。学会本电路后,我们也就基本搞清了MAX232芯片内部的大致结构。
MAX232是把TTL电平从OV~5V转换到3V~15V或-3V~-15V之间。如下图所示:
若发送低电平O,首先TXD(TTL低电平)发送数据时,TXD上是低电平,这时Q3导通(具体请看上节三极管的描述),PCRXD由空闲时的低电平变高电平,满足条件。
发送高电平l时,TXD为高电平,Q3截止,由于PCRXD内部高阻,而PCTXD平时是-3~-15V(RS-232的高电平就是负的电压,这点是要注意的,高电平并不是正电压),通过D1和R7将其拉低PCRXD至-3~-15V,此时计算机接收到的就是1。
下面再反过来,PC发送信号,由单片机来接收信号。当PCTXD为低电平-3~-15V时,Q4截止,单片机端的RXD被R9拉到5V高电平;当PCTXD变高时,Q4导通,RXD被Q4拉到低电平,这样便实现的双向转换,这是一个很好的电路,值得大家学习。
2.MAX232芯片实现RS-232电平与TTL电平转换
MAX232芯片是MAXIM公司生产的、包含两路接收器和驱动器的IC芯片,它的内部有一个电源电压变换器,可以把输入的+5V电源电压变换成为RS-232输出电平所需的+10V电压。所以,采用此芯片接口的串行通信系统只需单一的+5V电源就可以了。对于没有+12V电源的场合,其适应性更强,加之其价格适中,硬件接口简单,所以被广泛采用。
MAX232芯片实物和其引脚结构和外围连接如下图所示:
在上图中上半部分电容Cl,C2,C3,C4及V+,V-是电源变换电路部分。在实际应用中,器件对电源噪声很敏感,因此VCC必须要对地加去耦电容C5,其值为O.luF。按芯片手册中介绍,电容Cl,C2,C3,C4应取1.OuF/16V的电解电容,经大量实验及实际应用,这4个电容都可以选用O.luF的非极性瓷片电容代替l.OuF/16V的电解电容,在具体设计电路时,这4个电容要尽量靠近MAX232芯片,以提高抗干扰能力。
图下半部分为发送和接收部分。实际应用中,T1IN,T2IN可直接连接TTL/CMOS电平的stm32主芯片的串口发送端TXD;R1out,R2out可直接连接TTL/CMOS电平的stm32主芯片的串行接收端RXD;
Tlout,T2out可直接连接PC机的RS-232串口的接收端RXD;
R1IN,R2IN可直接连接PC机的RS-232串口的发送端TXD。
现从MAX232芯片中两路发送、接收中任选一路作为接口。要注意其发送、接收的引脚要对应。如使T1IN连接单片机的发送端TXD,则PC机的RS-232接收端RXD一定要对应接T1out引脚。同时,R1out连接单片机的RXD引脚,PC机的RS-232发送端TXD对应接R1IN小引脚。&
6.4.6 串口波特率的理解
在信息传输通道中,携带数据信息的信号单元叫码元,每秒钟通过信道传输的码元数称为码元传输速率,简称波特率。波特率是指数据信号对载波的调制速率,它用单位时间内载波调制状态改变的次数来表示(也就是每秒调制了符号数),其单位是波特(Baud,symbol/s)。波特率是传输通道频宽的指标。
它是对信号传输速率的一种度量。但是波特率有时候会同比特率混淆,实际上后者是对信息传输速率(传信率)的度量。当1波特等于1比特的时候,波特率与比特率才相等;但是如果1波特等于8比特的时候,那么每秒钟发送的比特率是波特率的9倍,波特率可以被理解为单位时间内传输码元符号的个数(传符号率),通过不同的调制方法可以在一个码元上负载多个比特信息。
所以,如果用公式表示,比特率在数值上和波特率有这样的关系:
波特率与比特率的关系为:比特率=波特率X单个调制状态对应的二进制位数。
单片机或计算机在串口通信时的速率用波特率表示,它定义为每秒传输二进制代码的位数,即1波特=1位/秒,单位是bps(位/秒)。如每秒钟传送240个字符,而每个字符格式包含10位(1个起始位、1个停止位、8个数据位),这时的波特率为10位&240个/秒=
2400 bps。
串行接口或终端直接传送串行信息位流的最大距离与传输速率及传输线的电气特性也有关。当传输线使用每0.3m(约1英尺)有50pF电容的非平衡屏蔽双绞线时,传输距寓随传输速率的增加而减小。当比特率超过1000
bps时,最大传输距离迅速下降,如9600 bps时最大距离下降到只有76m(约250英尺)。因此我们在做串口通信实验选择较高速率传输数据时,尽量缩短数据线的长度,为了能使数据安全传输,即使是在较低传输速率下也不要使用太长的数据线。
6.4.7 例程01 最简单串口打印$字符
例程简介:
STM32的GPIOA端口的PA9和PA10位,即串口1;设置PA9为TX输出模式,复用功能推挽输出模式;设置PA10为RX输入模式,模拟输入模式;对超级终端打印输出字符”$”符号。
插入串口1,具体硬件电路这里不细说。&
调试说明:
1) 打开开始菜单-&程序-&附件-&通讯-&超级终端
2) 输入“STM32神舟系列开发板”
3) 将STM32神舟IV号串口1与电脑的串口接口相连,因为电脑的串口是电脑的COM1口,所以选择COM1
4) 波特率例程代码中设置的是115200,数据流控制是无,选择完毕,点确定按钮
5) 最后把例程程序下载到开发板里,然后按一下开发板复位或者重新上电,就会打印出“$”的字符,一会打印一个,恭喜发财哦!
关键代码:
int main(void)&
//main是程序入口
RCC_init();&&&&
//时钟频率的配置
LED_init();&&&&
//LED初始化配置
uart_init();&&&
//串口接口初始化,这个部分是按STM32芯片手册的要求来做的,比较枯燥,细节感兴趣的朋友可以去研究下
USART1-&DR = 0x24;&& //
打印符号$,0x24是ASCII码
//点亮LED灯
Delay(0xFFFFFF);&&&
& // 延时&&
LEDOFF;&&&&&&&&&&&&
& // 熄灭LED灯
Delay(0xFFFFFF);&&&
void uart_init()
u16 USARTDIV_& //这里相当于u16,无符号16位
u16 USARTDIV_&&
//这里相当于u16,无符号16位
RCC-&APB2ENR|=1&&2;&&
//使能PORTA口时钟&
RCC-&APB2ENR|=1&&14;&
//使能串口时钟
GPIOA-&CRH&=0XFFFFF00F;
GPIOA-&CRH|=0X; //IO状态设置
USARTDIV = (float)(72*1000000)/();
& & USARTDIV_zhengshu =
& & USARTDIV_xiaoshu =
(USARTDIV - USARTDIV_zhengshu)* 16;
& & USARTDIV_zhengshu
& & USARTDIV_zhengshu +=
RCC-&APB2RSTR|=1&&14;&&
//复位串口1
RCC-&APB2RSTR&=~(1&&14);//停止复位
& & USART1-&BRR
=& USARTDIV_
USART1-&CR1|=0X200C;& //1位停止,无校验位.
代码详细分析:
(1)RCC_init();这个函数是负责时钟频率的配置,这里默认配置为72MHZ,前面章节有详细分析,这里简化,有疑问的可以翻看前面的章节细节。
(2)LED_init()这个函数是初始化LED的配置,这个是附带的,如果串口无法正常打印,只要程序能运行,那LED灯就会进行闪烁。
(3)uart_init()这个函数负责串口的初始化,这个部分是按STM32芯片手册的要求来做的,比较枯燥,细节感兴趣的朋友可以继续往下看,不感兴趣的可以跳过这一节,这个函数要把波特率初始化为115200,下面我们仔细分析一下代码:
(3-1)初始化一下波特率的值,一个是波特率的整数部分,一个是波特率的小数部分
USARTDIV_& //这里相当于u16,无符号16位,波特率的整数部分
u16 USARTDIV_&&
//这里相当于u16,无符号16位,波特率的小数部分
(3-2)初始化串口的时钟,再初始化PA9和PA10两个管脚的GPIO端口A的时钟。从原理图可以看到,USART1的TX和RX就是PA9和PA10,要使用串口不仅仅要初始化GPIO端口A的时钟,还要初始化串口的时钟,这里是需要注意的,如果点灯程序或者只做为普通的GPIO管脚使用,就不需要初始化串口的时钟。
串口时钟使能。串口作为STM32的一个外设,其时钟由外设始终使能寄存器控制,这里我们使用的串口1是在APB2ENR寄存器的第14位。这里需要注意的一点是,除了串口1的时钟使能在APB2ENR寄存器,其他串口的时钟使能位都在APB1ENR。
RCC-&APB2ENR|=1&&2;&&
//使能PORTA口时钟&
RCC-&APB2ENR|=1&&14;&
//使能串口时钟
(3-3) 设置串口通信RXD和TXD的配置,一个管脚是接收就要设置为输入模式,一个管脚是输出,就设置成输出模式;寄存器CRL是设置GPIO的0~7位,CRH是设置GPIO的8~15位,可以看到这里是设置GPIOA端口的9和10位,即PA9和PA10设置PA9为TX输出模式,复用功能推挽输出模式设置PA10为RX输入模式,模拟输入模式
GPIOA-&CRH&=0XFFFFF00F;
GPIOA-&CRH|=0X; //IO状态设置
(3-4)设置波特率,在CPU是72MHZ的频率下,设置波特率为115200;STM32中波特率是如何计算的,首先看下文档:
(x=1、2)是给外设的时钟(PCLK1用于串口2、3、4、5,PCLK2用于串口1),USARTDIV是一个无符号的定点数,它的值可以有串口的USART_BRR寄存器值得到。而我们更关心的是如何从USARTDIV的值得到USART_BRR的值,因为一般我们知道的是波特率,和PCLKx的时钟,要求的就是USART_BRR的值。
可以看到上图波特比率寄存器USART_BRR是低16位有效,高16位是闲置的,最低4位用来存放小数部分DIV_Fraction,[15:4]这12位用来存放整数部分DIV_Mantissa。高16位未使用。这里波特率的计算通过如下公式计算:
假设我们的串口1要设置为115200的波特率,而PCLK2的时钟为72M。这样,我们根据上面的公式有:
= &&&/ 波特率*16 =&
(72*1000000)/() = 39.0625
我们查看《STM32F10XX参考手册》中的第481页的一个表:
USARTDIV的值被设置为39.0625,也就是USART_BRR寄存器
那么得到:
DIV_ Mantissa =
DIV_ Fraction
这样,我们就得到了USART1-&BRR的值为0x271。只要设置串口1的BRR寄存器值为0x271就可以得到115200的波特率。
& & &USARTDIV =
(float)(72*1000000)/();//算出USARTDIV的值
USARTDIV_zhengshu = USARTDIV;
USARTDIV_xiaoshu = (USARTDIV - USARTDIV_zhengshu)* 16;
USARTDIV_zhengshu &&=4;
USARTDIV_zhengshu += USARTDIV_
(3-5) 当CPU刚启动的时候一般都需要重新复位一下外设,确保该外设在正常供电稳定后,能够稳定的工作可以看到复位一下之后就可以了,然后停止复位,让其开始正常工作。
串口复位。当外设出现异常的时候可以通过复位寄存器里面的对应位设置,实现该外设的复位,然后重新配置这个外设达到让其重新工作的目的。一般在系统刚开始配置外设的时候,都会先执行复位该外设的操作。串口1的复位是通过配置APB2RSTR寄存器的第14位来实现的。APB2RSTR寄存器的各位描述如下:
从上图可知串口1的复位设置位在APB2RSTR的第14位。通过向该位写1复位串口1,写0结束复位。其他串口的复位位在APB1RSTR里面。
RCC-&APB2RSTR|=1&&14;&&
//复位串口1
RCC-&APB2RSTR&=~(1&&14);//停止复位
(3-6)把115200的波特率设置到USART1-&BRR寄存器中,并且设置一下串口控制的寄存器USART_CR1。STM32的每个串口都有3个控制寄存器USART_CR1~3,串口的很多配置都是通过这3个寄存器来设置的。这里我们只要用到USART_CR1就可以实现我们的功能了,这里主要设置该串口使能,正式启动这个串口功能该寄存器的描述在《STM32参考手册》第496有更多的详细介绍:
USART1-&BRR
=& USARTDIV_
USART1-&CR1|=0X200C;& //1位停止,无校验位.
(3-7)数据发送与接收。STM32的发送与接收是通过数据寄存器USART_DR来实现的,这是一个双寄存器,包含了发送或接收的数据。由于它是由两个寄存器组成的,一个给发送用(TDR),一个给接收用(RDR),该寄存器兼具读和写的功能,寄存器描述如下:
DR[8:0]为串口数据,可以看出,虽然是一个32位寄存器,但是只用了低9位(DR[8:0]),其他都是保留。
代码USART1-&DR =
0x24是被用来打印符号$,0x24是ASCII码请看下图,通过把这个0x24输送给USART_DR寄存器后,就可以打印出$字符。
(4)进入while(1)死循环,不停的让LED灯亮和灭,然后每次LED亮灭一次,就打印一个$字符,中间有一些延时,具体代码很简单。
6.4.8 例程02
单串口打印字符-初级
例程简介:
例程同上个一样,只是打印出这么多字符来,增加了一些ASCII码。
调试说明:
其他都与上个例程一样,下载进去后,打印出来的是如下图所示
关键代码:
int main(void)&
//main是程序入口
RCC_init();&&&&
//时钟频率的配置
LED_init();&&&&
//LED初始化配置
uart_init();&&&
//串口接口初始化,这个部分是按STM32芯片手册的要求来做的,比较枯燥,细节感兴趣的朋友可以去研究下
USART1-&DR = 0x77;& //w
&Delay(0xFFF);&&
&USART1-&DR = 0x77; //w
&Delay(0xFFF);&&
&USART1-&DR = 0x77; //w
&Delay(0xFFF);&&
&USART1-&DR =
0x2e;&& //.
&Delay(0xFFF);&&
&USART1-&DR = 0x61;&
&Delay(0xFFF);&&
&USART1-&DR = 0x72; //r
&Delay(0xFFF);&&
&& // 延时
&USART1-&DR = 0x6d;&
&Delay(0xFFF);&&
&USART1-&DR = 0x6a; //j
&Delay(0xFFF);&&
&& // 延时
&USART1-&DR = 0x69; //i
&Delay(0xFFF);&&
&& // 延时
&USART1-&DR = 0x73;&
&Delay(0xFFF);&&
&USART1-&DR = 0x68; //h
&Delay(0xFFF);&&
&& // 延时
&USART1-&DR = 0x75; //u
&Delay(0xFFF);&&
&& // 延时
&USART1-&DR = 0x2e;&
&Delay(0xFFF);&&
&USART1-&DR = 0x63;&
&Delay(0xFFF);&&
&& // 延时
&USART1-&DR = 0x6f; //o
&Delay(0xFFF);&&
&& // 延时
&USART1-&DR = 0x6d; // m
&Delay(0xFFF);&&
&& // 延时&&&&&&&&&&&&&&&&&
& &USART1-&DR = 0x20;
//点亮LED灯
&Delay(0xFFFFFF);&&&
& // 延时&&
&LEDOFF;&&&&&&&&&&&&
& // 熄灭LED灯
&Delay(0xFFFFFF);&&&
代码分析:
可以看到,其实就是逐个打印出码而已。
6.4.9 例程03
单串口打印字符-中级
1. 例程简介:
例程同上个一样,只是打印出这么多字符来,增加了一些ASCII码,主要体现代码撰写表达有区别。
调试说明:
其他都与上个例程一样,下载进去后,打印出来的是如下图所示
关键代码:
int main(void)&
//main是程序入口
RCC_init();&&&&
//时钟频率的配置
LED_init();&&&&
//LED初始化配置
uart_init();&&&
&&&&&&&&&&&&&
USART1_Printf("
//点亮LED灯
Delay(0xFFFFFF);&&&
& // 延时&&
LEDOFF;&&&&&&&&&&&&
& // 熄灭LED灯
Delay(0xFFFFFF);&&&
void USART1_Printf(char *pch)
while(*pch !=
USART_SendData(USART1,pch);&&&&&&&
pch++;&&&&&&&&&
void USART_SendData(USART_TypeDef* USARTx, char
& USARTx-&DR =
Delay(0xFFF);&&&&&&&&&&
代码分析:
可以看到,用USART1_Printf("
")这个函数逐个打印出
(2)USART1_Printf()函数内部具体实现,就是把*pch指向这个其中一个单个字符,然后调用USART_SendData(USART1,pch)将这个字符往串口1发送输出,再用while(*pch
!= '\0')判断下一个字符是不是结束符,不是的话继续打印下一个字符。
(3)最后调用USART_SendData(USART_TypeDef*
USARTx, char *Data)这个函数,将字符数据的USARTx-&DR =
*D给到USART1_DR寄存器,这里的USART_SendData()函数中的*Data等同于上个函数的USART1_Printf
()函数中的*pch,而*pch等同于USART1_Printf()函数中的一个字符。
&关于USART1_Printf("
"),这个在USART1_Printf()函数中创造了一个内存空间,这个内存空间是在main函数开始调用USART1_Printf()函数时分配的,而传入到USART1_Printf()中的pch是为所分配的这个内存区域的一个指针的地址,然后继续把这个指针的地址传给USART_SendData()中的Data变量;换句话说,Data等于pch等于字符串内存区域的首地址。
6.4.10 例程04
单串口打印字符-高级
1. 例程简介:
例程同上个一样,只是打印出这么多字符来,主要体现代增加了一些串口传送校验。
调试说明:
其他都与上个例程一样,下载进去后,打印出来的是如下图所示
关键代码:
void USART1_Printf(char *pch)
while(*pch !=
USART_SendData(USART1,pch);
&&&&&&&&&&&&&
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) ==
&&&&&&&&&&&&&
USART_ClearFlag(USART1, USART_FLAG_TXE);
pch++;&&&&&
void USART_SendData(USART_TypeDef* USARTx, char
& USARTx-&DR = (*Data &
(uint16_t)0x01FF);& // 这里的*Data就是一个字符
while((USARTx-&SR&0x40) ==
代码分析:
USARTx-&DR = (*Data & (uint16_t)0x01FF)
这里的*Data就是一个字符,ASCII码是0~127,并且DR是0~8个bit有效,可以看手册的USART_DR寄存器的描述,所以这里实际上我们只需要取二进制的前9位即可。
while((USARTx-&SR&0x40) == 0),USART_SR是状态寄存器
&判断TC是不是已经发送完成,当包含有数据的一帧发送完成后,由硬件将该位置位,之前的代码是通过一定的延时,现在是判断寄存器,判断寄存器可以使得数据在第一时间发出之后,就能够使得开始进行下面的代码,比人工去延时的效率显著提高,所以算是比上个例程性能上的一种优化。
(3)当发送完一个数据后,while(USART_GetFlagStatus(USART1,
USART_FLAG_TXE) == RESET)查看一下寄存器说明:
判断USART _SR寄存器当中的TXE是不是为1,如果是为1才能结束这个while循环,当等于1的时候,数据已经被发送出去了,进入了移位寄存器。
(4)USART_ClearFlag(USART1,
USART_FLAG_TXE)这句代码是我们查看《STM32F103中文手册》获得494页24.6.1节
状态寄存器(USART_SR)章节,这里说的就是我们程序代码里的USART_FLAG_TXE这个标志位,等数据发送完毕后,再进行标志清空,方便进行下一次传输的时候有效使用。
6.4.11 例程05
USART-COM1串口接收与发送实验-初级版
1.例程简介:
例程主要实现在键盘上敲一个字符,输入这个字符到串口中,然后再通过超级终端打印出来。
2.调试说明:
1)下载完程序后,打开超级终端,敲入字符d,可以看到整个超级终端不停的输出d这个字符,表示输入成功。
2)重新按下复位按键,再次输入字符a,可以看到界面满屏幕都输出整屏a
4. 关键代码:
main(void)&
uint8_t inputstr[CMD_STRING_SIZE];
RCC_init();&&&&&&&&
uart_init();&&&
GetInputString(inputstr);&&
void GetInputString (uint8_t *
uint8_t c = 0;
c = (uint8_t)USART1-&DR;
USART_SendData(USART1,c);
while (1);
void USART_SendData(USART_TypeDef*
USARTx, uint8_t Data)
& USARTx-&DR =
(Data & (uint16_t)0x01FF);
代码分析:
(1)uint8_t&&
inputstr[CMD_STRING_SIZE]代码声明一个数组,可以看到程序中变量CMD_STRING_SIZE
= 128,就是表示这个数组有128个成员,每个成员都是uint8_t类型的。
接下来就是时钟初始化函数RCC_init()和串口初始化函数uart_init(),之前都有详细介绍,这里就不做具体分析了。
(3)GetInputString(inputstr)
这句代码表示取inputstr[]数组的初地址传入到GetInputString()这个函数中,知道了inputstr[]数组的首地址后,就可以在函数里操作和修改这个数组的内容。这里主要是熟悉一下指针和数组的一些基础概念,前面章节已经有详细分析。
可以看到数据寄存器USART_DR包含了发送或接收的数据。由于它是由两个寄存器组成的,一个给发送用(TDR),一个给接收用(RDR),该寄存器兼具读和写的功能。
在GetInputString()函数中,用c =
(uint8_t)USART1-&DR这句代码来读取输入到超级终端的键盘字符,如果有输入,那么c就会得到输入的初值
最后USART_SendData(USART1,c)将c中的字符输出到超级终端上,细节请见代码。
6.4.12 例程06
USART-COM1串口接收与发送实验-中级版
1.例程简介:
例程主要实现在键盘上敲一个字符,输入这个字符到串口中,然后再通过超级终端打印出来,例程与上个不同的是“键盘输入2013年:”的字符串,而这个字符串输入出来有一个人机交互界面的感觉,其他都与上个例程相同
2.调试说明:
1)下载完程序后,打开超级终端,按下复位按键,可以看到“键盘输入2013年:”的字符串
,然后敲入字符d,可以看到整个超级终端不停的输出d这个字符,表示输入成功。
2)重新按下复位按键,可以再次看到“键盘输入2013年:”的字符串,输入字符a,可以看到界面满屏幕都输出整屏a
3. 关键代码:
这里代码不做具体分析,之前有详细说
6.4.13 例程05
USART-COM1串口接收与发送实验-高级版
1. 例程简介:
例程主要实现在键盘上敲一个字符,输入这个字符到串口中,而之前的例程输出都是整屏不停的输出,这个例程加入了字符串部分代码的健壮,使得输入一个字符就是一个字符,绝对不会溢出或者数据丢失,或者重复输入。&
2. 调试说明:
下载完程序后,打开超级终端,按下复位按键,可以看到“键盘输入2013年:”的字符串
,每敲一个字符,就会输出一个字符,不会有多余的字符出现。
3. 关键代码:
uint32_t SerialKeyPressed(uint8_t *key)
USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET)
*key = (uint8_t)USART1-&DR;
uint8_t GetKey(void)
uint8_t key = 0;
if (SerialKeyPressed((uint8_t*)&key))
& & return
void GetInputString (uint8_t * buffP)
& & uint32_t
bytes_read = 0;
uint8_t c = 0;
c = GetKey();
if (c == '\r')
if (c == '\b')
if (bytes_read & 0)
USART1_Printf("\b \b");
bytes_read --;
if (bytes_read &= (CMD_STRING_SIZE))
USART1_Printf("Command string size overflow\r\n");
bytes_read = 0;
if (c &= 0x20 && c &= 0x7E)
buffP[bytes_read++] =
SerialPutChar(c);
while (1);
USART1_Printf(("\n\r"));
& & buffP[bytes_read]
代码分析:
SerialKeyPressed(uint8_t *key)函数分析
1)用语句USART_GetFlagStatus(USART1,
USART_FLAG_RXNE) != RESET判断USART_DR寄存器中是否收到数据,如果收到了数据就可以读出。
(uint8_t)USART1-&DR; 第二步就是把数据取出来放到*key中,*key的此时的值就是键盘输入的字符的ASCII码的值。
GetKey(void)函数在函数里分配了key的内存空间,然后调用了SerialKeyPressed()函数;其实这GetKey和SerialKeyPressed这两个函数在这个例程里可以合并,但是这样的写法有个好处就是,获取键盘的输入不一定是在串口中,也有可能是can的数据,或者485,或者是网口等其他渠道的输入,用GetKey()这个标准函数,再在里面调用具体的子函数,这样的好处就是增加了代码的可移植性。
GetInputString()分析获得一个输入之后,就开始一系列判断,这个输入是不是空格,是不是换行符,如果是,就进行相对应的处理,这里值得注意的是,越详细,越复杂就表示你的输入这部分的代码越健壮,各种异常输入情况都考虑进去了;最后经过一连串的判断之后,合格的输入就调用SerialPutChar()函数,把用户输入的内容输出到超级终端上。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

我要回帖

更多关于 usb转串口驱动 的文章

 

随机推荐