嵌入式软件开发学习过程记录,本部分结合本人的学习经验撰写,系统描述各类基础例程的程序撰写逻辑。构建裸机开发的思维,为RTOS做铺垫(本部分基于库函数版实现),如有不足之处,敬请批评指正。
(4)中主要讲解串口配置,看门狗配置(独立看门狗、窗口看门狗)
一 USART串口通讯
一些基本的概念在此不再赘述,读者可以自行查阅,即:
串行通信与并行通信
异步通信与同步通信
通信速率:比特率 bitrate
在此解释一下单工、半双工(485、IIC)与全双工(232、SPI)
单工是指数据传输仅能沿一个方向,不能实现反向传输半双工是指数据传输可以沿两个方向,但需要分时进行(同一时间只能单向传输)(长距离)全双工是指数据可以同时进行双向传输(短距离)
接口标准:串口通信的接口标准有很多,有 RS-232C、RS-232、RS-422A、RS-485 等。 常用的就是 RS-232 和 RS-485。以RS-232为例,在串口通信中,只使用3个管脚,即TXD、RXD、SGND。
RS232 的通信协议比较简单,通常遵循 96-N-8-1 格式。即波特率9600、无校验位、8位数据位、1位停止位。
RS-232C 对逻辑电平也做了规定,如下在 TXD 和 RXD 数据线上:1.逻辑 1 为-3~-15V 的电压2.逻辑 0 为 3~15V 的电压在 RTS、CTS、DSR、DTR 和 DCD 等控制线上:1.信号有效(ON 状态)为 3~15V 的电压2.信号无效(OFF 状态)为-3~-15V 的电压
标号1.功能引脚
TX:发送数据输出引脚。RX:接收数据输入引脚。SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚。nRTS:请求以发送(Request To Send),n 表示低电平有效。如果使能 RTS 流控制,当 USART 接收器准备好接收新数据时就会将 nRTS 变成低电平;当接收寄存器已满时,nRTS 将被设置为高电平。该引脚只适用于硬件流控制。nCTS:清除以发送(Clear To Send),n 表示低电平有效。如果使能 CTS 流控制,发送器在发送下一帧数据之前会检测 nCTS 引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。
标号2.数据寄存器
USART_DR 实际是包含了两个寄存器,一个专门用于发送的可写 TDR,一个专门用于接收的可读 RDR。当进行发送操作时,往 USART_DR 写入数据会自动存储在 TDR 内;当进行读取操作时,向USART_DR 读取数据会自动提取 RDR 数据。
标号3.控制器
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能 GPIOA 时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);// 使能 USART1 时钟
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//TX //串口输出 PA9GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出GPIO_Init(GPIOA,&GPIO_InitStructure); /* 初始化串口输入 IO */GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//RX // 串口输入 PA10GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; // 模拟输入GPIO_Init(GPIOA,&GPIO_InitStructure); /* 初始化 GPIO */
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
typedef struct{uint32_t USART_BaudRate; //波特率uint16_t USART_WordLength; //字长uint16_t USART_StopBits; //停止位uint16_t USART_Parity; //校验位uint16_t USART_Mode; //USART 模式uint16_t USART_HardwareFlowControl; //硬件流控制} USART_InitTypeDef;成员变量介绍:USART_BaudRate:波特率设置。常用的波特率为 4800、9600、115200 等。标准库函数会根据设定值计算得到 USARTDIV 值 , 并设置USART_BRR 寄存器值。USART_WordLength:数据帧字长。可以选择为 8 位或者 9 位,通过 USART_CR1寄存器的 M 位的值决定。如果没有使能奇偶校验控制,一般使用 8 数据位;如果使能了奇偶校验则一般设置为 9 数据位。USART_StopBits:停止位设置。可选 0.5 个、 1 个、 1.5 个和 2 个停止位,它设定 USART_CR2 寄存器的 STOP[1:0]位的值,一般我们选择 1 个停止位。USART_Parity:奇偶校验控制选择。可选 USART_Parity_No( 无 校 验 ) 、 USART_Parity_Even( 偶 校 验 ) 以 及 USART_Parity_Odd( 奇 校 验 ) ,它设定 USART_CR1 寄存器的 PCE 位和 PS 位的值。USART_Mode:USART 模式选择。可以为 USART_Mode_Rx 和 USART_Mode_Tx,允许使用逻辑或运算选择两个,它设定 USART_CR1 寄存器的 RE 位和 TE 位。USART_HardwareFlowControl:硬件流控制选择。只有在硬件流控制模式才有效,可以选 择无硬件流 USART_HardwareFlowControl_None 、RTS控制USART_HardwareFlowControl_RTS、CTS 控制 USART_HardwareFlowControl_CTS、RTS 和 CTS 控制 USART_HardwareFlowControl_RTS_CTS。
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);USART_Cmd(USART1, ENABLE); //使能串口 1
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启接收中断
USART_ITConfig(USART1,USART_IT_TC, ENABLE);
#define USART_IT_PE ((uint16_t)0x0028)#define USART_IT_TXE ((uint16_t)0x0727)#define USART_IT_TC ((uint16_t)0x0626)#define USART_IT_RXNE ((uint16_t)0x0525)#define USART_IT_IDLE ((uint16_t)0x0424)#define USART_IT_LBD ((uint16_t)0x0846)#define USART_IT_CTS ((uint16_t)0x096A)#define USART_IT_ERR ((uint16_t)0x0060)#define USART_IT_ORE ((uint16_t)0x0360)#define USART_IT_NE ((uint16_t)0x0260)#define USART_IT_FE ((uint16_t)0x0160)
编写一个串口中断服务函数,通过中断函数处理串口产生的相关中断。串口中断服务函数名在 STM32F1 启动文件内就有,USART1 中断函数名如下:
USART1_IRQHandler
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
#include "usart.h"
void USART1_Init(u32 bound)//USART_Init是一个已经被定义的库函数,所以初始化函数写为USART1_Init
{
//(1)定义各种结构体变量
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
//初始化串口参数,包含波特率、字长、奇偶校验(解决干扰)等参数————USART串口
NVIC_InitTypeDef NVIC_InitStructure;
//设置串口中断优先级,使能串口中断通道————中断
//(2)使能串口时钟及GPIO端口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能 GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能 USART1时钟————USART串口
//(3)GPI0端口模式设置,设置串口对应的GPIO引脚为复用功能
//配置输出引脚
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;; //原理图上GPIOA的输出引脚TX
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //设置复用推挽输出模式——不是主要功能
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率
GPIO_Init(GPIOA,&GPIO_InitStructure); /* 初始化GPIO */
//配置接收引脚
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10; //原理图上GPIOA的接收引脚RX
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //设置浮空输入模式
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率
GPIO_Init(GPIOA,&GPIO_InitStructure); /* 初始化GPIO */
//(4)USART1-串口部分的初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置4800、9600、15200
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); //初始化串口1
//(5)使能串口
USART_Cmd(USART1, ENABLE); //使能串口1
USART_ClearFlag(USART1, USART_FLAG_TC);//清除状态标志
//(6)设置串口中断类型并使能
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启接收非空中断
//(7)由于涉及到了中断,配置NVIC中断优先级
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
}
//(8)编写串口中断服务函数
void USART1_IRQHandler(void) //串口1中断服务程序,函数名定好的不能随便写
//因为串口的中断类型有很多,所以进入中断后,我们需要在中断服务函数开头处通过状态寄存器的值判断此次中断是哪种类型,然后做出相应的控制。
{
u8 r;
//把r设置为一个数组,就可以连续存储数据
// USART_GetITStatus(USART1, USART_IT_RXNE 监测串口,有数据就触发中断
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
//读取串口中断状态标志位的函数——>!= RESET不等于0——>产生一个接收中断
{
//USART_ReceiveData() 用这个函数,捕获数据
// 用 r (自定义) 接 这个数据
r =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
//接到以后 可以进行你想干的事了
//USART_SendData 串口发送函数 向串口1 发送数据
USART_SendData(USART1,r);//发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) != SET);
//发送完成的标志\USART_FLAG_TC置1时,则发送完成,所以设不等于1时则跳出循环
}
USART_ClearFlag(USART1,USART_FLAG_TC);//清除状态标志
}
二 printf重定向
借助串口,实现原本打印在PC端的printf函数转为将信息打印在串口调试助手上
%d 按照十进制整型数打印%6d 按照十进制整型数打印,至少 6 个字符宽%f 按照浮点数打印%6f 按照浮点数打印,至少 6 个字符宽%.2f 按照浮点数打印,小数点后有 2 位小数%6.2f 按照浮点数打印,至少 6 个字符宽,小数点后有 2 位小数%x 按照十六进制打印%c 打印字符%s 打印字符串
int fputc (int ch,FILE *p)
{
USART_SendData(USART1,(u8)ch); //将输出指向串口
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
return ch;
}
二 看门狗
独立看门狗(Independent Watchdog,简称IWDG)和窗口看门狗(Window Watchdog,简称WWDG)都是 MCU 中常用的看门狗,它们的作用都是在系统死机或者程序运行异常的情况下强制复位系统。
IWDG 是一种基于定时器的看门狗,其特点是独立于 CPU 运行,只要 MCU 上电接通,IWDG 就开始工作,无需软件干预。IWDG 会定期产生一个重装载信号,须在此期限内喂狗,否则 IWDG 将会产生一个复位信号强制复位整个系统。IWDG 适用于那些对可靠性要求较高的实时应用场景中。
WWDG 是一种基于窗口的看门狗,其特点是需要依赖 CPU 进行初始化并且需要每次在特定时间内刷新或者重置计数器,否则就会产生复位信号强制复位整个系统。与 IWDG 不同的是,WWDG 有一个可调窗口范围,如果未在这个时间范围内进行重置,WWDG 也会产生复位信号。WWDG 更多地应用于那些需要在不同周期中刷新看门狗的应用场景中。
1)独立看门狗(IWDG)
首先, IWDG_PR 和 IWDG_RLR 寄存器具有写访问保护。若要修改寄存器,必须首先对IWDG_KR 寄存器写入代码 0x5555,如果写入其他的值将重新开启写保护。在库函数中实现函数如下:这个函数非常简单,里面的参数就是用来使能或失能写访问,即开启或关闭写访问。
设置 IWDG 预分频系数函数为:
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler); //设置 IWDG 预分频值
设置 IWDG 重装载值函数为:
void IWDG_SetReload(uint16_t Reload); //设置 IWDG 重装载值
设置好 IWDG 的分频系数 pre 和重装载值就可以知道独立看门狗的喂狗时间,也就是看门狗溢出时间,该时间的计算公式前面已经介绍,公式如下:
Tout = (4*2^pre) / 40 * rlr
其中 Tout 为独立看门狗溢出时间,单位是 ms。pre 是预分频器系数(0-6), rlr 是重装载寄存器的值,公式内的 40 是独立看门狗的时钟。这里需要提醒大家的是,看门狗的时钟不是准确的 40Khz,所以在喂狗的时候,最好不要太晚了,否则,有可能发生看门狗复位。
重载计数器值(喂狗)库函数是:,此函数功能是将 IWDG_RLR 寄存器内值重新加载到独立看门狗计数器内,实现喂狗操作。
IWDG_ReloadCounter(); //重装载初值
开启 IWDG 的库函数是:
注意:IWDG 在一旦启用,就不能再被关闭,想要关闭,只能重启,并且重启之后不能打开 IWDG,否则问题依旧存在。所以如果不用 IWDG 的话,就不要去打开它,免得麻烦。
IWDG_Enable(); //打开独立看门狗
#include "iwdg.h"
void IWDG_Init(u8 pre,u16 rlr)//IWDG配置
{
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //开启寄存器,取消寄存器写保护
IWDG_SetPrescaler(pre);//设置预分频系数pre 0-6
IWDG_SetReload(rlr);//设置重装载值rlr
IWDG_ReloadCounter(); //重装载初值(喂狗)
IWDG_Enable(); //打开独立看门狗(使能)
}
void IWDG_FeedDog(void) //喂狗的动作
{
IWDG_ReloadCounter(); //重装载初值
}
2)窗口看门狗(WWDG)
从图中看到,T[6:0]是窗口控制寄存器(WWDG_CR)的低7位,W[6:0]是窗口配置寄存器(WWDG_CFR)低 7 位。T[6:0]就是窗口看门狗的计数器值,而W[6:0]是窗口看门狗的上窗口,下窗口是固定值 0X40。当窗口看门狗的计数器在上窗口值之外或者低于下窗口值被刷新都会产生复位。
上窗口值(W[6:0])是由用户自己设定的,根据实际要求来设计窗口值,但是一定要确保窗口值大于 0X40,否则窗口就不存在了。窗口看门狗 WWDG 通常被用来监测,由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG,ENABLE);
void WWDG_SetWindowValue(uint8_t WindowValue);
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler);
WWDG_EnableIT();
void WWDG_Enable(uint8_t Counter);
void WWDG_SetCounter(uint8_t Counter);
WWDG_IRQHandler
WWDG_ClearFlag(); //清除窗口看门狗状态标志
#include "wwdg.h"
#include "led.h"
#include "SysTick.h"
void WWDG_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG,ENABLE); //开启窗口看门狗的时钟
WWDG_SetWindowValue(0x5f);//设置窗口值
WWDG_SetPrescaler(WWDG_Prescaler_8);//设置分频值
//中断初始化
NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn;//窗口中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化NVIC寄存器
WWDG_Enable(0x7f); //使能窗口看门狗并初始化计数器值
WWDG_ClearFlag(); //清除窗口看门狗状态标志(这一句必须加上,否则进入不了中断)
WWDG_EnableIT(); //开启中断
}
void WWDG_IRQHandler(void)
{
WWDG_SetCounter(0x7f); //重新赋值
WWDG_ClearFlag(); //清除窗口看门狗状态标志
led2=!led2;
}