1. 串口的基本概念
在STM32的参考手册中,串口被描述成通用同步异步收发器(USART),它提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的外部设备之间进行全双工数据交换。USART利用分数波特率发生器提供宽范围的波特率选择。它支持同步单向通信和半双工单线通信,也支持LIN(局部互联网),智能卡协议和IrDA(红外数据组织)SIR ENDEC规范,以及调制解调器(CTS/RTS)操作。它还允许多处理器通信。还可以使用DMA方式,实现高速数据通信。
各种支持模块通信详细介绍和相关寄存器说明见地址https://ld.sogou.com/article?aid=3000435108
USART通过3个引脚与其他设备连接在一起,任何USART双向通信至少需要2个引脚:接受数据输入(RX)和发送数据输出(TX)。
RX: 接受数据串行输入。通过过采样技术来区别数据和噪音,从而恢复数据。
TX: 发送数据输出。当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活,并且不发送数据时,TX引脚处处于高电平。在单线和智能卡模式里,此I/O口被同时用于数据的发送和接收。
2. 串口是如何工作的
一般有两种方式:查询和中断。
(1)查询:串口程序不断地循环查询,看看当前有没有数据要它传送。如果有,就帮助传送(可以从PC到STM32板子,也可以从STM32板子到PC)。
(2)中断:平时串口只要打开中断即可。如果发现有一个中断来,则意味着要它帮助传输数据——它就马上进行数据的传送。同样,可以从 PC到STM3板子,也可以从STM32板子到PC。
3. 串口的硬件连接
我用的奋斗STM32 V3开发板拥有二路RS-232 接口,CPU 的PA9-US1-TX(P68)、PA10-US1-RX(P69)、PA9-US2-TX(P25)、PA10-US2-RX(P26)通过MAX3232 实现两路RS-232 接口,分别连接在XS5 和XS17 接口上。 USART1 在系统存储区启动模式下,将通过该口通过PC对板上的CPU进行ISP,该口也可作为普通串口功能使用,JP3,JP4 的短路冒拔去,将断开第二路的RS232通信,仅作为TTL 通信通道。
4. 编程实例
我们要对串口进行操作,首先要将STM32的串口和CPU进行连接。在Windows操作系统中,有一个自带的系统软件叫“超级终端”。VISTA以上的操作系统去掉了这个软件,不过可以从XP的系统中,复制“hypertrm.dll”和“hypertrm.exe”到“windows/system32”文件夹下,然后双击运行hypertrm.exe,就可以看见超级终端的运行界面了。
运行超级终端以后,会弹出“连接描述”,输入名称和选择图标,这个地方随便写个什么名称都可以。然后弹出“连接到”设置,在“连接时使用”选择你自己PC和STM32连接的COMx,如果不知道是哪个COM口的话,可以在PC的设备管理器中找到。在选择好COM口之后,会弹出一个“属性”对话框,在“位/秒”选择和你STM32中设置的波特率一致就好,数据位也是按照STM32的设置来选择,奇偶校验选择无,停止位选择1,数据流控制选择无。注意,以上的选项都必须和STM32中的串口设置相匹配,要不然可能会出现一些未知错误。
配置好超级终端之后,我们便可以开始对STM32进行编程了。编程一般按照如下步骤进行:
(1)RCC配置:
由于UART的TX和RX和AFIO都挂在APB2桥上,因此采用固件库函数RCC_APB2PeriphClockCmd()进行初始化。UARTx需要分情况讨论,如果是UART1,则挂在APB2桥上,因此采用RCC_APB2PeriphClockCmd()进行初始化,其余的UART2~5均挂在APB1上。
(2)GPIO配置:
GPIO的属性包含在结构体GPIO_InitTypeDef,其中对于TX引脚,GPIO_Mode字段设置为GPIO_Mode_AF_PP(复用推挽输出),GPIO_Speed切换速率设置为GPIO_Speed_50MHz;对于RX引脚,GPIO_Mode字段设置为GPIO_Mode_IN_FLOATING(浮空输入),不需要设置切换速率。最后通过GPIO_Init()使能IO口。
(3)USART配置:
STM32在只有一个中断的情况下,仍然需要配置优先级,其作用是使能某条中断的触发通道。STM32的中断有至多两个层次,分别是抢占优先级(主优先级)和子优先级(从优先级),而整个优先级设置参数的长度为4位,因此需要首先划分抢占优先级位数和子优先级位数,通过NVIC_PriorityGroupConfig()实现;
特定设备的中断优先级NVIC的属性包含在结构体NVIC_InitTypeDef中,其中字段NVIC_IRQChannel包含了设备的中断向量,保存在启动代码中;字段NVIC_IRQChannelPreemptionPriority为主优先级NVIC_IRQChannelSubPriority为从优先级,取值的范围应根据位数划分的情况而定;最后NVIC_IRQChannelCmd字段是是否使能,一般置为ENABLE。最后通过NVIC_Init()来使能这一中断向量。
(4)NVIC配置:
通过结构体USART_InitTypeDef来确定。UART模式下的字段如下:
USART_BaudRate:波特率(每秒能传输的数据位),缺省值为9600。
USART_WordLength:字长
USART_StopBits:停止位
USART_Parity:校验方式(奇偶校验)
USART_HardwareFlowControl:硬件流控制
USART_Mode:单/双工,即收发状态。
最后通过USART_Init()来设置。
(5) 发送/接收数据。
在RCC配置中,我们除了常规的时钟设置以外,要记得打开USART相对应的IO口时钟,USART时钟,还有管脚功能复用时钟。
在GPIO配置中,将发送端的管脚配置为复用推挽输出,将接收端的管脚配置为浮空输入。
在USART的配置中,通过USART_InitTypeDef结构体对USART进行初始化操作,按照自己所需的功能配置好就可以了。注意,在超级终端的设置中,需要和这个里面的配置相对应。由于我是采用中断接收数据的方式,所以记得在USART的配置中要打开串口的中断,同时最后还要打开串口。
在NVIC的配置中,主要是USART1_IRQChannel的配置。
全部配置好之后就可以开始发送/接收数据了。发送数据用USART_SendData()函数,接收数据用USART_ReceiveData()函数。具体的函数功能可以参考固件库的参考文件。根据USART的配置,在发送和接收时,都是采用的8bits一帧来进行的,因此,在发送的时候,先开辟一个缓存区,将需要发送的数据送入缓存区,然后再将缓存区中的数据发送出去,在接收的时候,同样也是先接收到缓存区中,然后再进行相应的操作。
注意在对数据进行发送和接收的时候,要检查USART的状态,只有等到数据发送或接收完毕之后才能进行下一帧数据的发送或接收。采用USART_GetFlagStatus()函数。
同时还要注意的是,在发送数据的最开始,需要清除一下USART的标志位,否则,第1位数据会丢失。因为在硬件复位之后,USART的状态位TC是置位的。当包含有数据的一帧发送完成之后,由硬件将该位置位。只要当USART的状态位TC是置位的时候,就可以进行数据的发送。然后TC位的置零则是通过软件序列来清除的,具体的步骤是“先读USART_SR,然后写入USART_DR”,只有这样才能够清除标志位TC,但是在发送第一帧数据的时候,并没有进行读USART_SR的操作,而是直接进行写操作,因此TC标志位并没有清空,那么,当发送第一帧数据,然后用USART_GetFlagStatus()检测状态时返回的是已经发送完毕(因为TC位是置1的),所以程序会马上发送下一帧数据,那么这样,第一帧数据就被第二帧数据给覆盖了,所以看不到第一帧数据的发送。
按照上面的方法编程后,我们便可以在超级终端上查看串口通信的具体状态了。我的这个例程,在硬件复位以后,可以马上在超级终端上看见“Welcome to my STM32! Please press any key!”字样,然后如果在超级终端中通过PC机键盘按下相应的键,则这个键会发送到STM32中,并且马上返回到PC机的超级终端上,因此可以马上从超级终端的页面中看到按下的相应的键。
奋斗STM32 V3开发板源代码附最后。
STM32USART一般配置源码:
void uart_init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1和GPIOA时钟
USART_DeInit(USART1); //复位串口1(各参数置为缺省值)
//USART1_TX(发送数据) PA.9引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA.9
//USART1_RX(接收数据) PA.10引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA.10
//NVIC中断向量配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级置为3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级置为3,优先级依据不同的中断重要性不同来确定。
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据上面设置的参数初始化NVIC寄存器
//USART初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率为9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据
USART_InitStructure.USART_StopBits = USART_StopBits_1;//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); //串口初始化
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//中断开启
USART_Cmd(USART1, ENABLE); //串口使能
}
5、串口DMA发送:
5.1发送数据的流程:
前台程序中有数据要发送,则需要做如下几件事5.2串口DMA接收:
接收数据的流程:判断数据数据接收完成:
这里判断接收完成是通过串口空闲中断的方式实现,即当串口数据流停止后,就会产生IDLE中断。这个中断里面做如下几件事:
1. 关闭串口接收DMA通道,2点原因:1.防止后面又有数据接收到,产生干扰。2.便于DMA的重新配置赋值,下面第4点。
2. 清除DMA 所有标志位
3. 从DMA寄存器中获取接收到的数据字节数
4. 重新设置DMA下次要接收的数据字节数,注意,这里是给DMA寄存器重新设置接收的计数值,这个数量只能大于或者等于可能接收的字节数,否则当DMA接收计数器递减到0的时候,又会重载这个计数值,重新循环递减计数,所以接收缓冲区的数据则会被覆盖丢失。
5. 开启DMA通道,等待下一次的数据接收,注意,对DMA的相关寄存器配置写入,如第4条的写入计数值,必须要在关闭DMA的条件进行,否则操作无效。
说明一下,STM32的IDLE的中断在串口无数据接收的情况下,是不会一直产生的,产生的条件是这样的,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一断接收的数据断流,没有接收到数据,即产生IDLE中断。
USART 和 DMA 硬件初始化配置
/— LumModule Usart Config —————————————/
void Uart_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
/* System Clocks Configuration */
//= System Clocks Configuration ====================================================================//
/* Enable GPIO clock */
RCC_APB2PeriphClockCmd(LUMMOD_UART_GPIO_CLK , ENABLE ); // 开启串口所在IO端口的时钟
/* Enable USART Clock */
RCC_APB1PeriphClockCmd(LUMMOD_UART_CLK, ENABLE); // 开始串口时钟
//=NVIC_Configuration==============================================================================//
/* Configure the NVIC Preemption Priority Bits */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
/* Enable the DMA Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = LUMMOD_UART_Tx_DMA_IRQ; // 发送DMA通道的中断配置
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 优先级设置
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* Enable the USART Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = LUMMOD_UART_IRQn; // 串口中断配置
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//=GPIO_Configuration==============================================================================//
GPIO_PinRemapConfig(GPIO_PartialRemap_USART3, ENABLE); // 我这里没有用默认IO口,所以进行了重新映射,这个可以根据自己的硬件情况配置选择
/* Configure USART3 Rx as input floating */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 串口接收IO口的设置
GPIO_InitStructure.GPIO_Pin = LUMMOD_UART_RxPin;
GPIO_Init(LUMMOD_UART_GPIO, &GPIO_InitStructure);
/* Configure USART3 Tx as alternate function push-pull */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 串口发送IO口的设置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 这里设置成复用形式的推挽输出
GPIO_InitStructure.GPIO_Pin = LUMMOD_UART_TxPin;
GPIO_Init(LUMMOD_UART_GPIO, &GPIO_InitStructure);
DMA_Uart_Init(); // 串口 DMA 配置
/* USART Format configuration ------------------------------------------------------*/
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 串口格式配置
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;
/* Configure USART3 */
USART_InitStructure.USART_BaudRate = 115200; // 波特率设置
USART_Init(LUMMOD_UART, &USART_InitStructure);
/* Enable USART3 Receive and Transmit interrupts */
USART_ITConfig(LUMMOD_UART, USART_IT_IDLE, ENABLE); // 开启 串口空闲IDEL 中断
/* Enable the USART3 */
USART_Cmd(LUMMOD_UART, ENABLE); // 开启串口
/* Enable USARTy DMA TX request */
USART_DMACmd(LUMMOD_UART, USART_DMAReq_Tx, ENABLE); // 开启串口DMA发送
USART_DMACmd(LUMMOD_UART, USART_DMAReq_Rx, ENABLE); // 开启串口DMA接收
}
void DMA_Uart_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
/* DMA clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 开启DMA1时钟
//=DMA_Configuration==============================================================================//
/— LUMMOD_UART_Tx_DMA_Channel DMA Config —/
DMA_Cmd(LUMMOD_UART_Tx_DMA_Channel, DISABLE); // 关DMA通道
DMA_DeInit(LUMMOD_UART_Tx_DMA_Channel); // 恢复缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&LUMMOD_UART->DR);// 设置串口发送数据寄存器
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)LumMod_Tx_Buf; // 设置发送缓冲区首地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 设置外设位目标,内存缓冲区 -> 外设寄存器
DMA_InitStructure.DMA_BufferSize = LUMMOD_TX_BSIZE; // 需要发送的字节数,这里其实可以设置为0,因为在实际要发送的时候,会重新设置次值
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不做增加调整,调整不调整是DMA自动实现的
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存缓冲区地址增加调整
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据宽度8位,1个字节
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存数据宽度8位,1个字节
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 单次传输模式
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // 优先级设置
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 关闭内存到内存的DMA模式
DMA_Init(LUMMOD_UART_Tx_DMA_Channel, &DMA_InitStructure); // 写入配置
DMA_ClearFlag(LUMMOD_UART_Tx_DMA_FLAG); // 清除DMA所有标志
DMA_Cmd(LUMMOD_UART_Tx_DMA_Channel, DISABLE); // 关闭DMA
DMA_ITConfig(LUMMOD_UART_Tx_DMA_Channel, DMA_IT_TC, ENABLE); // 开启发送DMA通道中断
/— LUMMOD_UART_Rx_DMA_Channel DMA Config —/
DMA_Cmd(LUMMOD_UART_Rx_DMA_Channel, DISABLE); // 关DMA通道
DMA_DeInit(LUMMOD_UART_Rx_DMA_Channel); // 恢复缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&LUMMOD_UART->DR);// 设置串口接收数据寄存器
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)LumMod_Rx_Buf; // 设置接收缓冲区首地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 设置外设为数据源,外设寄存器 -> 内存缓冲区
DMA_InitStructure.DMA_BufferSize = LUMMOD_RX_BSIZE; // 需要最大可能接收到的字节数
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不做增加调整,调整不调整是DMA自动实现的
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存缓冲区地址增加调整
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据宽度8位,1个字节
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存数据宽度8位,1个字节
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 单次传输模式
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // 优先级设置
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 关闭内存到内存的DMA模式
DMA_Init(LUMMOD_UART_Rx_DMA_Channel, &DMA_InitStructure); // 写入配置
DMA_ClearFlag(LUMMOD_UART_Rx_DMA_FLAG); // 清除DMA所有标志
DMA_Cmd(LUMMOD_UART_Rx_DMA_Channel, ENABLE); // 开启接收DMA通道,等待接收数据
}
void BSP_Init(void)
{
Uart_Init();
}
//============================================================//
DMA 发送应用源码
void DMA1_Channel2_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_FLAG_TC2))
{
LumMod_Uart_DAM_Tx_Over();
}
}
void LumMod_Uart_DAM_Tx_Over(void)
{
DMA_ClearFlag(LUMMOD_UART_Tx_DMA_FLAG); // 清除标志
DMA_Cmd(LUMMOD_UART_Tx_DMA_Channel, DISABLE); // 关闭DMA通道
OSMboxPost(mbLumModule_Tx, (void*)1); // 设置标志位,这里我用的是UCOSII ,可以根据自己的需求进行修改
}
void LumMod_Cmd_WriteParam( uint8 sample_num, uint8 *psz_param )
{
uint8 err;
uint8 LumMod_Tx_Index ;
LumMod_Tx_Index = 0;
LumMod_Tx_Buf[LumMod_Tx_Index++] = 1;
LumMod_Tx_Buf[LumMod_Tx_Index++] = 2;
LumMod_Tx_Buf[LumMod_Tx_Index++] = 3;
LumMod_Tx_Buf[LumMod_Tx_Index++] = 4;
LumMod_Tx_Buf[LumMod_Tx_Index++] = 5;
LumMod_Tx_Buf[LumMod_Tx_Index++] = 6;
LumMod_Tx_Buf[LumMod_Tx_Index++] = 7;
LumMod_Tx_Buf[LumMod_Tx_Index++] = 8;
LumMod_Uart_Start_DMA_Tx( LumMod_Tx_Index );
OSMboxPend(mbLumModule_Tx, 0, &err);
}
void LumMod_Uart_Start_DMA_Tx(uint16_t size)
{
LUMMOD_UART_Tx_DMA_Channel->CNDTR = (uint16_t)size; // 设置要发送的字节数目
DMA_Cmd(LUMMOD_UART_Tx_DMA_Channel, ENABLE); //开始DMA发送
}
//============================================================//
DMA 接收应用源码
void USART3_IRQHandler(void)
{
if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET) // 空闲中断
{
LumMod_Uart_DMA_Rx_Data();
USART_ReceiveData( USART3 ); // Clear IDLE interrupt flag bit
}
}
void LumMod_Uart_DMA_Rx_Data(void)
{
DMA_Cmd(LUMMOD_UART_Rx_DMA_Channel, DISABLE); // 关闭DMA ,防止干扰
DMA_ClearFlag( LUMMOD_UART_Rx_DMA_FLAG ); // 清DMA标志位
LumMod_Rx_Data.index = LUMMOD_RX_BSIZE - DMA_GetCurrDataCounter(LUMMOD_UART_Rx_DMA_Channel); //获得接收到的字节数
LUMMOD_UART_Rx_DMA_Channel->CNDTR = LUMMOD_RX_BSIZE; // 重新赋值计数值,必须大于等于最大可能接收到的数据帧数目
DMA_Cmd(LUMMOD_UART_Rx_DMA_Channel, ENABLE); /* DMA 开启,等待数据。注意,如果中断发送数据帧的速率很快,MCU来不及处理此次接收到的数据,中断又发来数据的话,这里不能开启,否则数据会被覆盖。有2种方式解决。
1. 在重新开启接收DMA通道之前,将LumMod_Rx_Buf缓冲区里面的数据复制到另外一个数组中,然后再开启DMA,然后马上处理复制出来的数据。
2. 建立双缓冲,在LumMod_Uart_DMA_Rx_Data函数中,重新配置DMA_MemoryBaseAddr 的缓冲区地址,那么下次接收到的数据就会保存到新的缓冲区中,不至于被覆盖。*/
OSMboxPost(mbLumModule_Rx, LumMod_Rx_Buf); // 发送接收到新数据标志,供前台程序查询
}
附奋斗STM32 V3开发板程序源代码:http://ishare.iask.sina.com.cn/f/35109141.html
#include "stm32f10x_lib.h"
FlagStatus RX_status;
void RCC_cfg();
void GPIO_cfg();
void USART_cfg();
void NVIC_cfg();
int main()
{
int i;
unsigned char TxBuf1[] = "Welcome to my STM32! Please press any key!";
RCC_cfg();
GPIO_cfg();
NVIC_cfg();
USART_cfg();
//清除标志位,否则第1位数据会丢失
USART_ClearFlag(USART1,USART_FLAG_TC);
//发送数据
//PB5的作用是显示正在发送数据
//当有数据在发送的时候,PB5会亮
for( i=0;TxBuf1[i]!='\0';i++)
{
USART_SendData(USART1,TxBuf1[i]);
GPIO_SetBits(GPIOB,GPIO_Pin_5);
//等待数据发送完毕
while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);
GPIO_ResetBits(GPIOB,GPIO_Pin_5);
}
while(1);
}
//RCC时钟配置
void RCC_cfg()
{
//定义错误状态变量
ErrorStatus HSEStartUpStatus;
//将RCC寄存器重新设置为默认值
RCC_DeInit();
//打开外部高速时钟晶振
RCC_HSEConfig(RCC_HSE_ON);
//等待外部高速时钟晶振工作
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if(HSEStartUpStatus == SUCCESS)
{
//设置AHB时钟(HCLK)为系统时钟
RCC_HCLKConfig(RCC_SYSCLK_Div1);
//设置高速AHB时钟(APB2)为HCLK时钟
RCC_PCLK2Config(RCC_HCLK_Div1);
//设置低速AHB时钟(APB1)为HCLK的2分频
RCC_PCLK1Config(RCC_HCLK_Div2);
//设置FLASH代码延时
FLASH_SetLatency(FLASH_Latency_2);
//使能预取指缓存
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
//设置PLL时钟,为HSE的9倍频 8MHz * 9 = 72MHz
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
//使能PLL
RCC_PLLCmd(ENABLE);
//等待PLL准备就绪
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
//设置PLL为系统时钟源
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
//判断PLL是否是系统时钟
while(RCC_GetSYSCLKSource() != 0x08);
}
//打开GPIO时钟,复用功能,串口1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO | RCC_APB2Periph_USART1, ENABLE);
}
//IO口配置
void GPIO_cfg()
{
GPIO_InitTypeDef GPIO_InitStructure;
//PA9作为US1的TX端,打开复用,负责发送数据
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA , &GPIO_InitStructure);
//PA10作为US1的RX端,负责接收数据
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//LED显示串口正在发送/接收数据
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//串口初始化
void USART_cfg()
{
USART_InitTypeDef USART_InitStructure;
//将结构体设置为缺省状态
USART_StructInit(&USART_InitStructure);
//波特率设置为115200
USART_InitStructure.USART_BaudRate = 115200;
//一帧数据的宽度设置为8bits
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
//在帧结尾传输1个停止位
USART_InitStructure.USART_StopBits = USART_StopBits_1;
//奇偶失能模式,无奇偶校验
USART_InitStructure.USART_Parity = USART_Parity_No;
//发送/接收使能
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
//硬件流控制失能
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
//设置串口1
USART_Init(USART1, &USART_InitStructure);
//打开串口1的中断响应函数
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//打开串口1
USART_Cmd(USART1, ENABLE);
}
//配置中断
void NVIC_cfg()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //选择中断分组2
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQChannel; //选择串口1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占式中断优先级设置为0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应式中断优先级设置为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断
NVIC_Init(&NVIC_InitStructure);
}
然后在stm32f10x_it.c文件中找到相应的中断处理函数,并填入一下内容。注意在stm32f10x_it.c中,要声明一下外部变量RX_status
extern FlagStatus RX_status;
void USART1_IRQHandler(void)
{
GPIO_SetBits(GPIOB, GPIO_Pin_5);
//确认是否接收到数据
RX_status = USART_GetFlagStatus(USART1, USART_FLAG_RXNE);
//接收到数据
if(RX_status == SET)
{
//将数据回送至超级终端
USART_SendData(USART1, USART_ReceiveData(USART1));
//等待数据发送完毕
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
GPIO_ResetBits(GPIOB, GPIO_Pin_5);
}
}
=====================================================STM32F103
STM32----1----USART
一,串口USART全功能配置函数
随着程序的代码的增加,调试程序变的越来越困难,通过STM32的串口调试程序不失为一种解决问题的方法。其实串口和PC的通信中,串口主动的向PC发送数据使用的数据较多,PC主动向STM32发送的数据较少。本串口的USART函数借鉴ZN51的串口的配置思路,利用MDK2.0库在STM32中进行了实现。
1、Usart.h
#ifndef __UART_H__
#define __UART_H__
#define uchar unsigned char
#define uint unsigned short
#define ulong unsigned long
//UART 外部相关函数------------------------
void USART_Configuration(void);
void USART_Send_Byte(uchar mydata);
void USART_Send_Str(char *s);
void USART_Send_Enter(void);
void USART_Output_Num(ulong dat);
void USART_Output_Information(char *inf,ulong dat);
//UART-------------------------------------
#endif
这里是USART的头文件,包含了USART操作的所有函数,这些函数包括USART的配置初始化、发送字节、发送字符串、发送回车、发送位长数据、发送调试信息等
2、usart.c文件
#include "usart.h"
#include "stm32f10x_lib.h"
#include "string.h "
#include "myfun.h"
/***************************************************************************
- 功能描述:STM32f103串口的初始化
- 隶属模块:STM32串口操作
- 函数属性:外部,使用户使用
- 参数说明:无
- 返回说明:无
- 串口配置步骤:
(1)首先建立结构体变量,再配置的参数(波特率、字长、停止位、奇偶校验位、接收和发送)
用参数初始化串口1,最后使能该串口。
(2)配置串口操作所需的IO口,其中GPIO_TX为发送端配置为复用推挽输出
其中GPIO_RX配置为输入浮空
(3)对于USART1,PA9为GPIO_TX发送端,PA10为GPIO_RX接收端
*************************************************************************/
void USART_Configuration()
{
//定义串口和GPIO的结构体变量
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitStructure.USART_BaudRate =38400;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
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_Tx | USART_Mode_Rx;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1 , ENABLE);
GPIO_StructInit(&GPIO_InitStructure);
//USART1在没有重映像的条件下,PA9为USART1_TX发送端
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA , &GPIO_InitStructure);
//USART1在没有重映像的条件下,PA10为USART1_RX接收端
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
/***************************************************************************
- 功能描述:STM32f103串口的发送字节函数
- 隶属模块:STM32串口操作
- 函数属性:外部,使用户使用
- 参数说明:mydata待发送的数据
- 返回说明:无
- 函数实现步骤:
(1)清除串口的发送完成标志位
(2)利用库函数 USART_SendData发送数据mydata
(3)等待发送完成标志位置位,置位以后再清除标志
***************************************************************************/
void USART_Send_Byte(uchar mydata)
{
USART_ClearFlag(USART1,USART_FLAG_TC);
USART_SendData(USART1, mydata);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
USART_ClearFlag(USART1,USART_FLAG_TC);
}
/***************************************************************************
- 功能描述:STM32f103串口的发送字符串函数
- 隶属模块:STM32串口操作
- 函数属性:外部,使用户使用
- 参数说明:*s待发送的字符串
- 返回说明:无
***************************************************************************/
void USART_Send_Str(char *s)
{
int len=strlen(s)-1;
int i;
for(i=0;i
USART_Send_Byte(s[i]);
if(s[i]=='\n')
{
USART_Send_Enter();
}
else
{
USART_Send_Byte(s[i]);
}
}
/***************************************************************************
- 功能描述:STM32f103串口的发送会成回车换行函数
- 隶属模块:STM32串口操作
- 函数属性:外部,使用户使用
- 参数说明:*s待发送的字符串
- 返回说明:无
- 注:此函数就是发送0d 0a这两个字节,在“超级终端”上会有回车换行的效果
***************************************************************************/
void USART_Send_Enter(void)
{
USART_Send_Byte(0x0d);
USART_Send_Byte(0x0a);
}
/**************************************************************************
- 功能描述:STM32f103的串口发送数值
- 隶属模块:STM32串口操作
- 函数属性:外部,使用户使用
- 参数说明:dat:要发送的数值
- 返回说明:无
- 注:函数中会将数值转为相应的字符串,发送出去。比如 4567 转为 "4567"
(1)ulong为4字节32位表示的最大数字为4294967296,所以用长度20的字符串数组可以装下
**************************************************************************/
void USART_Output_Num(ulong dat)
{
char temp[20];
u32tostr(dat,temp);
USART_Send_Str(temp);
}
/**************************************************************************
- 功能描述:STM32f103的串口发送调试信息
- 隶属模块:STM32串口操作
- 函数属性:外部,使用户使用
- 参数说明:inf:指向提示信息字符串的指针
dat:一个数值,前面的提示信息就是在说明这个数值的意义
- 返回说明:无
**************************************************************************/
void USART_Output_Information(char *inf,unsigned long dat)
{
USART_Send_Str(inf);
USART_Output_Num(dat);
USART_Send_Str("\n");
}
以上函数中void USART_Configuration()最为重要,只有这个函数的配置正确,其他函数才能书写工作。至于如何配置USART_Configuration,步骤如下:
串口配置步骤:
(1)首先建立结构体变量,再配置的参数(波特率、字长、停止位、奇偶校验位、接收和发送)
用参数初始化串口1,最后使能该串口。
(2)配置串口操作所需的IO口,其中GPIO_TX为发送端配置为复用推挽输出,其中GPIO_RX配置为输入浮空
(3)对于USART1,PA9为GPIO_TX发送端,PA10为GPIO_RX接收端。
3、有了以上的配置就在main函数中书写主函数
(1)配置好UASRT1的时钟它在APB2总线上。
(2)具体如下:
int main(void)
{
RCC_Configuration();
GPIO_Configuration();
USART_Configuration();
USART_Send_Str("串口初始化成功:\r\n");
LED_Init() ;
USART_Send_Str("LED初始化成功:\r\n");
delay_init(72);
USART_Send_Str("系统精确延时函数初始化成功:\r\n");
USART_Output_Information("欢迎进入STM32F",103);
USART_Send_Enter();
while (1)
{
LED0_On();
delay_ms(500);
USART_Send_Str("LED0亮,PA8为低电平:\r\n");
LED0_Off();
delay_ms(500);
USART_Send_Str("LED0灭,PA8为高电平:\r\n");
}
}
其中LED0由PA8控制,PD2由PD2控制。
编译运行完成后,在串口助手中或超级终端中就可以看到:如下的信息:
这样我们就可以利用串口助手,来调控程序,知道程序的流程。从而调试程序。STM32串口的学习就到此为止。当然当PC向STM32发送数据时,直接用USART_ReceiveData函数即可,若要接受大量的数据,做成一个数组buff即可。当然这个过程在中断中实现比较好。由于STM32接受信息在调试程序中用到的不多,所以这方面的程序就没有给出。