爆肝9万字,我已从小白晋升ARM嵌入式工程师!带你从零熟悉常用的M4嵌入式功能,建议收藏(含码源)(3)

简介: 位 31:12 保留,必须保持复位值

4.5.6 控制寄存器3(USART_CR3)

2345_image_file_copy_58.jpg

位 31:12 保留,必须保持复位值

位 11 ONEBIT:一个采样位方法使能 (One sample bit method enable)

该位允许用户选择采样方法。选择一个采样位方法后,将禁止噪声检测标志 (NF)。

0:三个采样位方法

1:一个采样位方法

位 10 CTSIE:CTS 中断使能 (CTS interrupt enable)

0:禁止中断

1:当 USART_SR 寄存器中 CTS = 1 时,生成中断

注意:该位不适用于 UART4 UART5

位 9 CTSE: CTS 使能 (CTS enable)

0:

禁止 CTS 硬件流控制

1:

使能 CTS 模式,仅当 nCTS 输入有效 (连接到 0)时才发送数据。

如果在发送数据时使

nCTS 输入无效,会在停止之前完成发送。

如果使 nCTS 有效时数据已写入数据寄存器,则

将延迟发送,直到 nCTS 有效。

注意:该位不适用于 UART4 UART5

位 8 RTSE: RTS 使能 (RTS enable)

0:

禁止 RTS 硬件流控制

1:

使能 RTS 中断,仅当接收缓冲区中有空间时才会请求数据。

发送完当前字符后应停止发

送数据。

可以接收数据时使 nRTS 输出有效 (连接到 0)。

注意:该位不适用于 UART4 UART5

位 7 DMAT: DMA 使能发送器 (DMA enable transmitter)

该位由软件置 1/ 复位。

1:

针对发送使能 DMA 模式。

0:

针对发送禁止 DMA 模式。

位 6 DMAR: DMA 使能接收器 (DMA enable receiver)

该位由软件置 1/ 复位。

1:

针对接收使能 DMA 模式

0:针对接收禁止 DMA 模式

位 5 SCEN:智能卡模式使能 (Smartcard mode enable)

该位用于使能智能卡模式。

0:

禁止智能卡模式

1:使能智能卡模式

注意:该位不适用于 UART4 UART5

位 4 NACK:智能卡 NACK 使能 (Smartcard NACK enable)

0:

出现奇偶校验错误时禁止 NACK 发送

1:出现奇偶校验错误时使能 NACK 发送

注意:该位不适用于 UART4 UART5

位 3 HDSEL:半双工选择 (Half-duplex selection)

选择单线半双工模式

0:未选择半双工模式

1:选择半双工模式

位 2 IRLP:IrDA 低功耗 (IrDA low-power)

该位用于选择正常模式和低功耗 IrDA 模式

0:正常模式

1:低功耗模式

位 1 IREN:IrDA 模式使能 (IrDA mode enable)

此位由软件置 1 和清零。

0:禁止 IrDA

1:使能 IrDA

位 0 EIE:错误中断使能 (Error interrupt enable)

对于多缓冲区通信(USART_CR3 寄存器中 DMAR = 1),如果发生帧错误、上溢错误或出

现噪声标志(USART_SR 寄存器中 FE = 1 或 ORE = 1 或 NF = 1),则需要使用错误中断

使能位来使能中断生成。

0:禁止中断

1:当 USART_CR3 寄存器中的 DMAR = 1 并且 USART_SR 寄存器中的 FE = 1 或 ORE = 1

或 NF = 1 时,将生成中断

4.5.7 保护时间和预分频USART_GTPR)

2345_image_file_copy_59.jpg

位 31:16 保留,必须保持复位值

位 15:8 GT[7:0]

保护时间值 (Guard time value)

该位域提供保护时间值 (以波特时钟数为单位)。

该位用于智能卡模式。

经过此保护时间后,发送完成标志置 1。

注意:该位不适用于 UART4 UART5

位 7:0 PSC[7:0]:预分频器值

IrDA 低功耗模式下:

PSC[7:0] = IrDA 低功耗波特率

用于编程预分频器,进行系统时钟分频以获得低功耗频率:

使用寄存器中给出的值(8 个有效位)对源时钟进行分频:

00000000:保留 - 不编程此值

源时钟 1 分频

00000010:源时钟 2 分频

在正常 IrDA 模式下:PSC 必须设置为 00000001。

在智能卡模式下:

PSC[4:0]:预分频器值

用于编程预分频器,进行系统时钟分频以提供智能卡时钟。

将寄存器中给出的值(5 个有效位)乘以 2 得出源时钟频率的分频系数:

00000:保留 - 不编程此值

00001:源时钟 2 分频

00010:源时钟 4 分频

00011:源时钟 6 分频

注意:1:如果使用智能卡模式,则位 [7:5] 不起作用。

2:该位不适用于 UART4 UART5

2345_image_file_copy_60.jpg

串口硬件电路


4.5.8 程序配置步骤

  • RCC配置:要开相应的GPIO组时钟和相应的串口时钟;
  • GPIO配置:在GPIO配置中,将发送端的管脚配置为复用推挽输出,将接收端的管脚配置为浮空输入;
  • USART配置:USART寄存器配置;
  • NVIC配置:设置串口的中断抢占优先级和亚优先级;
  • 发送/接收数据:通过串口寄存器接收/发送数据。

4.5.9 实例代码

#include "usart1.h"                  // Device header
void USART1_Init(int bps)  
{
  GPIO_InitTypeDef U1_TXRX;
  USART_InitTypeDef U1;
  NVIC_InitTypeDef U1_NVIC;
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
  GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
  GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
  //PA9-发送
  U1_TXRX.GPIO_Mode =GPIO_Mode_AF;
  U1_TXRX.GPIO_OType = GPIO_OType_PP;
  U1_TXRX.GPIO_Pin = GPIO_Pin_9;
  U1_TXRX.GPIO_PuPd = GPIO_PuPd_UP;   //默认下拉
  U1_TXRX.GPIO_Speed = GPIO_High_Speed;
  GPIO_Init(GPIOA,&U1_TXRX);   //根据U1_TXRX配置的参数进行初始化
  //PA10-接收
  U1_TXRX.GPIO_Mode =GPIO_Mode_AF;
  U1_TXRX.GPIO_OType = GPIO_OType_PP;
  U1_TXRX.GPIO_Pin = GPIO_Pin_10;
  U1_TXRX.GPIO_PuPd = GPIO_PuPd_NOPULL;   //默认下拉
  U1_TXRX.GPIO_Speed = GPIO_High_Speed;
  GPIO_Init(GPIOA,&U1_TXRX);
  //USART
  U1.USART_BaudRate=bps;   //波特率
  U1.USART_HardwareFlowControl=USART_HardwareFlowControl_None;  //无硬件流
  U1.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;  //发送和接收
  U1.USART_Parity=USART_Parity_No;      //无校验
  U1.USART_StopBits=USART_StopBits_1;     //停止位
  U1.USART_WordLength=USART_WordLength_8b;  //数据位
  USART_Init(USART1, &U1);   //根据U1配置的参数进行初始化
  //记得分开写中断
  USART_ITConfig(USART1, USART_IT_RXNE,ENABLE);   //使能串口接收中断
  USART_ITConfig(USART1, USART_IT_IDLE,ENABLE);   //使能串口空闲中断 
  U1_NVIC.NVIC_IRQChannel = USART1_IRQn;
  U1_NVIC.NVIC_IRQChannelPreemptionPriority = 1;  //抢占为1
  U1_NVIC.NVIC_IRQChannelSubPriority = 1;//响应为0
  U1_NVIC.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&U1_NVIC);  
  USART_Cmd(USART1, ENABLE);  //使能串口进行工作
}
/********************************************************************
函数功能:串口1发送1个字节的字符数据
参数1:data:你要发送的数据
参数2:无
返回值:无
作者:xxx
********************************************************************/
void usart1_sendbyte(char data)
{
  while(!(USART_GetFlagStatus(USART1,USART_FLAG_TC))); //等待判断发送寄存器为空
  USART_SendData(USART1,data);
}
/********************************************************************
函数功能:串口1发送一串字符数据
参数1:data:你要发送的数据
参数2:无
返回值:无
作者:xxx
********************************************************************/
void usart1_sendstring(char *data)
{
  while(*data != '\0')usart1_sendbyte(*data++);  //循环发送,直到遇到\0,停止发送
}
/********************************************************************
函数功能:串口1接收读取1个字节的字符数据
参数1:data:你要发送的数据
参数2:无
返回值:无
作者:xxx
********************************************************************/
char usart1_readbyte(void)
{
  while(!(USART_GetFlagStatus(USART1,USART_FLAG_RXNE))); //等待判断接收寄存器非空
  return USART_ReceiveData(USART1);  //读到的数据
}
/*
 //接收中断
struct U1_DATA  u1={0,0,0};  //定义结构体变量,同时初始化为0
char data;
//串口中断服务函数:每收到一个字符数据,CPU都会先暂停所有程序,自动执行一次USART1_IRQHandler函数
void USART1_IRQHandler(void)
{
  data=USART_ReceiveData(USART1);  //读数据并清除中断标志位
  if(data=='\r' || data=='\n') 
  {
    u1.ok_flag=1;     //u1.ok_flag=1,说明接收完成
    u1.buf[u1.len]='\0';   //添加结束符\0
  }else {
    u1.buf[u1.len++]=data;  //没收到\r\n,就把数据存储到数组
  }
}
*/
//接收中断+空闲中断
struct U1_DATA  u1={0,0,0};  //定义结构体变量,同时初始化为0
char data;
//接收中断:每收到一个字符数据就会执行一次USART1_IRQHandler中断服务函数
//空闲中断:收完数据之后,串口产生空闲,会自动执行一次USART1_IRQHandler中断服务函数
void USART1_IRQHandler(void)
{
  if(USART_GetITStatus(USART1, USART_IT_RXNE)==SET)   //接收中断,存储数据
  {
    u1.buf[u1.len]=USART_ReceiveData(USART1);  //读数据并清除中断标志位
    u1.len++;  //自增,为下个数据存储做准备
  }else if(USART_GetITStatus(USART1, USART_IT_IDLE)==SET)  //空闲中断,接收数据结束
  {
    USART_ClearITPendingBit(USART1, USART_IT_IDLE);  //读SR
    USART_ReceiveData(USART1);   //读DR
    u1.ok_flag=1;         //接收完成
    u1.buf[u1.len]='\0';  //添加结束符
  }
}
/* 告知连接器不从C库链接使用半主机的函数 */
#pragma import(__use_no_semihosting)
/* 定义 _sys_exit() 以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}
/* 标准库需要的支持类型 */
struct __FILE
{
    int handle;
};
FILE __stdout;
/*  */
int fputc(int ch, FILE *stream)
{
    /* 堵塞判断串口是否发送完成 */
  usart1_sendbyte(ch);  //串口发送一个字符
    return ch;
}

5.系统滴答定时器

学习这个我们要知道定时器部分学习的三个目的:了解系统滴答定时器的作用是什么?掌握系统滴答定时器寄存器的配置;掌握系统滴答定时器的驱动软件设计。

5.1 系统滴答定时器概述

  在我们的Cortex-M4当中,systick定时器被捆绑到我们的NVIC当中,用于产生systick异常。在我们STM32的SysTick定时器有两个可选的时钟源,一个是外部时钟源(STCLK,等于HCLK/8-仅限于stm32f10x),另一个是内核时钟(FCLK, 等于HCLK)。假若你选择内核时钟,并将HCLK频率设置为72Mhz的话,系统时钟周期为1/(168M); systick 有一个24位的递减计数器,每个系统时钟周期计数器值减一.那么当计数器减到零时,时间经过了:系统时钟周期*计数器初值。当你将计数器初值设为72000时,当计数器值减到0时经过了1/(72M)*(72000)=0. 001s,即1ms.

5.2 系统滴答定时器的作用

  • 1.产生操作系统的时钟节拍;

   SysTick定时器模捆绑在NVIC中,用于产生SYSTICK异常(异常号: 15)。大多操作系统需要一个硬件定时器来产生操作系统需要的滴等中断,作为整个系统的时基。因此,需要一个定时器来产生周期性的中断。而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。

  • 2.方便于不同处理器之间的程序移植;

Cortex”M4处理器内部包含了一个简单的定时器。因为所有的CM4芯片都带有这个定时器,软件在不同CM4器件间的移植工作得以化简。该定时器的时钟源可以是内部时钟(FCLK, CM4上的自由运行时钟),或者是外部时钟(CM4处理器上的STCLK信号)。不过,STCLK 的具体来源则由芯片设计者决定,因此不同产品之间的时钟频率可能会大不相同,你需要检视芯片的器件手册来决定选择什么作为时钟源。SysTick定时器能产生中断,CM4为它专门开出一个异常类型,并且在向量表中有它的一席之地。它使操作系统和其它系统软件在CM4器件间的移植变得简单多了,因为在所

有CM4产品间对其处理都是相同的。

  • 3.作为一个闹钟用来测量时间;

  SysTick定时器除了能服务于操作系统之外,还能用于其它目的:如作为一个闹铃,用于测量时间等。要注意的是,当处理器在调试期间被喊停(halt) 时,则SysTick定时器亦将暂停运作。

5.3 系统滴答定时器寄存器

2345_image_file_copy_61.jpg

systick寄存器

image.png

更加详细的说明大家可以到相关固件库手册自行查阅哦!

5.5 代码实例

我们使用的是在裸机状态下完成的,因为在逻辑状态下的原因,所以可以使用系统滴答定时器产生精准延时;

#include "delay.h"                  // Device header
//系统滴答延时初始化
void SysTick_init(void)
{
  SysTick->CTRL &=~(1<<2);  //选择外部时钟源,systick时钟主频为168/8=21MHZ
}
//最大的us延时时间:24位最大值16777215/21=798915us,转换为ms,798.915ms
void delay_us(int us)
{
  SysTick->LOAD = us*21;  //1us*9个数,开始计数前,先填充好计数时间的值
  SysTick->CTRL |=1<<0; //使能定时器,开始向下计数
  SysTick->VAL=0;   //清除0
  while(!(SysTick->CTRL & 1<<16));  //等待之前填充好计数时间的值为0,延时时间到
  SysTick->CTRL &=~(1<<0); //关闭定时器
  SysTick->VAL=0;   //清除0
}
//最大的us延时时间:24位最大值16777215/21=798915us,转换为ms,798.915ms
void delay_nms(int nms)
{
  SysTick->LOAD = nms*21000;  //1us*21000个数,开始计数前,先填充好计数时间的值
  SysTick->CTRL |=1<<0; //使能定时器,开始向下计数
  while(!(SysTick->CTRL & 1<<16));  //等待之前填充好计数时间的值为0,延时时间到
  SysTick->CTRL &=~(1<<0); //关闭定时器
}
//函数功能:将ms进行扩大
void delay_ms(int ms)
{
  int i;
  int beishu=ms/500;  //循环调用倍数
  int yushu=ms%500;   //循环调用余数
  for(i=0;i<beishu;i++)
  {
    delay_nms(500);
  }
  if(yushu!=0)delay_nms(yushu);  //余数
}

6.定时器

6.1 通用定时器

什么是通用定时器?是一个可以通过可编程预分频器驱动的16位自动装载计数器。可以用于很多场合,比如测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出和PWM)。使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几毫秒间的调整。每个定时器都是完全独立的,没有互相共享的任何资源!

6.2 STM32F4系列通用定时器特点

2345_image_file_copy_62.jpg

定时器基本框架图

  • 16位向上、向下、向上/向下自动装载计数器
  • 16位可编程预分频器,计数器时钟频率的分频系数为1-65536之间的任意数值;
  • 4个独立通道:--输入捕获;一输出比较;一PWM生成(边缘或中间对齐模式);一单脉冲模式输出
  • 死区时间可编程的互补输出
  • 使用外部信 号控制定时 器和定时器互联的同步电路
  • 允许在指定数目的计数器周期之后更新定时器寄存器的重复计数器
  • 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态
  • 如下事件发生时产生中断/DMA :

         ---更新:计数器向上溢出向下滋出,计数器初始化(通过软件或者内部/外部触发)

         一触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)

         一输入捕获

         一输出比较

         一刹车信号输入

  • 支持计对定位的增量(正交)编码器和霍尔传感器电路
  • 触发输入作为外部时钟或者按周期的电流管理

6.3 与基本定时器相关的寄存器

6.3.1 TIM6和TIM7控制寄存器1(TIMx_CR1)

2345_image_file_copy_63.jpg

位 15:8 保留,必须保持复位值。

位 7 ARPE:自动重载预装载使能 (Auto-reload preload enable)

0TIMx_ARR 寄存器不进行缓冲。

1TIMx_ARR 寄存器进行缓冲。

位 6:4 保留,必须保持复位值。

位 3 OPM:单脉冲模式 (One-pulse mode)

0:计数器在发生更新事件时不会停止计数

1:计数器在发生下一更新事件时停止计数(将CEN 位清零)。

位 2 URS:更新请求源 (Update request source)

此位由软件置1 和清零,用以选择UEV 事件源。

0:使能时,所有以下事件都会生成更新中断或DMA 请求。此类事件包括:

计数器上溢/下溢

UG 位置1

通过从模式控制器生成的更新事件

1:使能时,只有计数器上溢/下溢会生成更新中断或DMA 请求。

位 1 UDIS:更新禁止 (Update disable)

此位由软件置 1 和清零,用以使能 / 禁止 UEV 事件生成。

0 :使能 UEV 。更新 (UEV) 事件可通过以下事件之一生成:

计数器上溢 / 下溢

将 UG 位置 1

通过从模式控制器生成的更新事件

然后更新影子寄存器的值。

1:禁止UEV。不会生成更新事件,各影子寄存器的值(ARR PSC)保持不变。但如果将UG 位置1,或者从从模式控制器接收到硬件复位,则会重新初始化计数器和预分频器。

位 0 CEN:计数器使能 (Counter enable)

0:禁止计数器

1:使能计数器

1 :使能计数器

注意:只有事先通过软件将 CEN 位置 1 ,才可以使用门控模式。而触发模式可通过硬件自动将

CEN 位置 1 。

在单脉冲模式下,当发生更新事件时会自动将 CEN 位清零。

6.3.2 TIM6和TIM7控制寄存器2(TIMx_CR2)

2345_image_file_copy_64.jpg

位 15:7 保留,必须保持复位值。

位 6:4 MMS:主模式选择 (Master mode selection)

这些位用于选择主模式下将要发送到从定时器以实现同步的信息(TRGO)。这些位的组合如下:

000 : 复位 —— TIMx_EGR 寄存器中的 UG 位用作触发输出 (TRGO) 。如果复位由触发输入生成(从模式控制器配置为复位模式),则 TRGO 上的信号相比实际复位会有延迟。

001 : 使能 ——计数器使能信号 (CNT_EN) 用作触发输出 (TRGO) 。该触发输出可用于同时启动多个定时器,或者控制在一段时间内使能从定时器。计数器使能信号由 CEN 控制位与门控模式下的触发输入的逻辑或运算组合而成。

当计数器使能信号由触发输入控制时, TRGO 上会存在延迟,选择主 / 从模式时除外(请参见TIMx_SMCR 寄存器中对 MSM 位的说明)。

010 : 更新 ——选择更新事件作为触发输出 (TRGO) 。例如,主定时器可用作从定时器的预分频器。

位 3:0 保留,必须保持复位值。


6.3.3 TIM6 TIM7 DMA/中断使能寄存器 (TIMx_DIER)

2345_image_file_copy_65.jpg

位 15:9 保留,必须保持复位值

位 8 UDE:更新 DMA 请求使能 (Update DMA request enable)

0:禁止更新DMA 请求。

1:使能更新DMA 请求。

位 7:1 保留,必须保持复位值

位 0 UIE:更新中断使能 (Update interrupt enable)

0:禁止更新中断。

1:使能更新中断。


6.3.4 TIM6和TIM7状态寄存器(TIMx_SR)

2345_image_file_copy_66.jpg

位 15:1 保留,必须保持复位值。

位 0 UIF:更新中断标志 (Update interrupt flag)

该位在发生更新事件时通过硬件置1。但需要通过软件清零。

0 :未发生更新。

1 :更新中断挂起。该位在以下情况下更新寄存器时由硬件置 1 :

— 上溢或下溢并且当 TIMx_CR1 寄存器中 UDIS = 0 时。

— 当由于 TIMx_CR1 寄存器中 URS = 0 且 UDIS = 0 而通过软件使用 TIMx_EGR 寄存器中的 UG 位重新初始化 CNT 时

6.3.5 TIM6和TIM7事件生产寄存器(TIMx_EGR)

2345_image_file_copy_67.jpg

位 15:1 保留,必须保持复位值。

位 0 UG:更新生成 (Update generation)

该位可通过软件置1,并由硬件自动清零。

0:不执行任何操作。

1:重新初始化定时器计数器并生成寄存器更新事件。请注意,预分频器计数器也将清零(但

预分频比不受影响)。

6.3.6 TIM6和TIM7计数器(TIMx_CNT)

2345_image_file_copy_68.jpg

6.3.7 TIM6和TIM7预分频器(TIMx_PSC)

2345_image_file_copy_69.jpg

位 15:0 PSC[15:0]:预分频器值 (Prescaler value)

计数器时钟频率 CK_CNT 等于 f CK_PSC / (PSC[15:0] + 1) 。

PSC 包含在每次发生更新事件时要装载到实际预分频器寄存器的值。

6.3.8 TIM6和TIM7自动重载寄存器(TIMx_ARR)

2345_image_file_copy_70.jpg

位 15:0 ARR[15:0]:自动重载值 (Auto-reload value)

ARR 为要装载到实际自动重载寄存器的值。

当自动重载值为空时,计数器不工作




相关文章
|
30天前
|
监控 网络协议 安全
验证嵌入式ARM32环境中4G模块的有效方法
验证嵌入式ARM32环境中4G模块的有效方法
95 0
|
30天前
|
安全 Unix Linux
【ARM】在NUC977上搭建基于boa的嵌入式web服务器
【ARM】在NUC977上搭建基于boa的嵌入式web服务器
|
30天前
|
物联网 编译器 测试技术
【嵌入式 交叉编译器】如何在 ARM 架构下选择和使用高版本交叉编译器
【嵌入式 交叉编译器】如何在 ARM 架构下选择和使用高版本交叉编译器
480 7
|
30天前
|
存储 机器学习/深度学习 人工智能
嵌入式中一文搞懂ARM处理器架构
嵌入式中一文搞懂ARM处理器架构
62 1
ARM6818开发板画任意矩形,圆形,三角形,五角星,6818开发板画太极,画五星红旗(含码源与思路)
ARM6818开发板画任意矩形,圆形,三角形,五角星,6818开发板画太极,画五星红旗(含码源与思路)
359 0
|
10月前
|
编译器 C语言
ARM与C语言的混合编程【嵌入式系统】
ARM与C语言的混合编程【嵌入式系统】
94 0
|
10月前
|
存储 芯片
ARM简单程序设计【嵌入式系统】
ARM简单程序设计【嵌入式系统】
119 0
|
缓存 Linux 编译器
ARM嵌入式开发——基础概念
ARM嵌入式开发——基础概念
184 0
|
30天前
|
数据处理 编译器 数据库
x64 和 arm64 处理器架构的区别
x64 和 arm64 处理器架构的区别
658 0
【各种问题处理】X86架构和ARM架构的区别
【1月更文挑战第13天】【各种问题处理】X86架构和ARM架构的区别