STM32:功能特性与技术详解
OLED驱动函数
NVIC
NVIC中断优先级由优先级寄存器的4位(0-15)决定,这四位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级
抢占优先级高:中断嵌套 响应优先级:优先排队
抢占优先级和响应优先级相同:按照中断号进行排队
EXTI:外部中断
EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
TIM定时中断
注意总线位置
预分频器:对输入的基准频率进行分频操作:假设寄存器写的是0,那么可以说是1分频(72/1=72),如果寄存器写的是1,那么就是(1+1=2)2分频,那么就是(72/2=36) 实际分频系数=预分频器的值+1
CNT计数器:++
自动重装载寄存器:到达一个特定值就会归零然后重新计时
ADC
GPIO_Mode_AIN 模拟输入模式,在AIN模式下GPIO口使无效的,防止GPIO口的输入输出对模拟电压造成的干扰
AIN模式就是ADC专属模式(AIN)
校准
while里面两个函数等待校准完 RESET的时候就校准完成,跳出循环
ADC_ResetCalibration(ADC1)//开启校准,将校准位置1
while循环就是如果ADC1是1的时候就一直循环,直到校准完成后,跳出循环
串口发送和接收:
1.开启时钟,将GPIO、USART的时钟打开
2.GPIO和USART初始化
3.配置USART、GPIO,使用结构体将参数配好
4.如果只需要发送,只需要开启USART时钟。如果还需要接收,则需要配置中断,在开启USART时钟之前,加上ITConfig和NVIC代码即可
USART串口数据包
HEX数据包格式:
将多个字节打包成一个整体出发(防止串口在接收数据的时候不知道如何对应。防止出现数据错位)
将数据分成一个个的数据包,并且在第一个数据上添加标志位进行分割(比如说在XYZ的数据流中,我们可以在X的数据包上的最高位置1作为标志位,当接收包的最高位是1的时候,就知道后面的数据位YZ)
串口数据包通常使用额外包头包尾的方式进行数据分割打包:规定每个数据包前面的是包头,后面的是包尾。这样就可以自定义数据包格式,这样可以连续不断的分割出想要的数据包
文本数据包格式:
与hex一样,包含固定包长和可变包长。
与HEX的数据包相比:HEX传输更直接且解析数据更加简单。但是包头包尾容易重复
文本数据包的优点就是更直观更利于理解,不易重复
在需要直接传输和简单解析原始数据的情况下,HEX数据包是更好的选择。而在需要输入指令进行人机交互的场合,文本数据包则更为适用。
、
固定包长:等待包头——接收数据———等待包尾
可变包长:等待包头(判断是否位包头形式,是则进入接收状态)———依次接收数据,在接受的同时还要判断是否位包尾的格式
串口发送代码分析
设置包头包尾
设置中断
分三种情况:012
下面代码非常重要
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
uint8_t Serial_TxPacket[4]; //定义发送数据包数组,数据包格式:FF 01 02 03 04 FE
uint8_t Serial_RxPacket[4]; //定义接收数据包数组
uint8_t Serial_RxFlag; //定义接收数据包标志位
/**
* 函 数:串口初始化
* 参 数:无
* 返 回 值:无
*/
void Serial_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA9引脚初始化为复用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA10引脚初始化为上拉输入
/*USART初始化*/
USART_InitTypeDef USART_InitStructure; //定义结构体变量
USART_InitStructure.USART_BaudRate = 9600; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验,不需要
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位
USART_Init(USART1, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART1
/*中断输出配置*/
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启串口接收数据的中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //选择配置NVIC的USART1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*USART使能*/
USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}
/**
* 函 数:串口发送一个字节
* 参 数:Byte 要发送的一个字节
* 返 回 值:无
*/
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
/**
* 函 数:串口发送一个数组
* 参 数:Array 要发送数组的首地址
* 参 数:Length 要发送数组的长度
* 返 回 值:无
*/
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for (i = 0; i < Length; i ++) //遍历数组
{
Serial_SendByte(Array[i]); //依次调用Serial_SendByte发送每个字节数据
}
}
/**
* 函 数:串口发送一个字符串
* 参 数:String 要发送字符串的首地址
* 返 回 值:无
*/
void Serial_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
{
Serial_SendByte(String[i]); //依次调用Serial_SendByte发送每个字节数据
}
}
/**
* 函 数:次方函数(内部使用)
* 返 回 值:返回值等于X的Y次方
*/
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1; //设置结果初值为1
while (Y --) //执行Y次
{
Result *= X; //将X累乘到结果
}
return Result;
}
/**
* 函 数:串口发送数字
* 参 数:Number 要发送的数字,范围:0~4294967295
* 参 数:Length 要发送数字的长度,范围:0~10
* 返 回 值:无
*/
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i ++) //根据数字长度遍历数字的每一位
{
Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0'); //依次调用Serial_SendByte发送每位数字
}
}
/**
* 函 数:使用printf需要重定向的底层函数
* 参 数:保持原始格式即可,无需变动
* 返 回 值:保持原始格式即可,无需变动
*/
int fputc(int ch, FILE *f)
{
Serial_SendByte(ch); //将printf的底层重定向到自己的发送字节函数
return ch;
}
/**
* 函 数:自己封装的prinf函数
* 参 数:format 格式化字符串
* 参 数:... 可变的参数列表
* 返 回 值:无
*/
void Serial_Printf(char *format, ...)
{
char String[100]; //定义字符数组
va_list arg; //定义可变参数列表数据类型的变量arg
va_start(arg, format); //从format开始,接收参数列表到arg变量
vsprintf(String, format, arg); //使用vsprintf打印格式化字符串和参数列表到字符数组中
va_end(arg); //结束变量arg
Serial_SendString(String); //串口发送字符数组(字符串)
}
/**
* 函 数:串口发送数据包
* 参 数:无
* 返 回 值:无
* 说 明:调用此函数后,Serial_TxPacket数组的内容将加上包头(FF)包尾(FE)后,作为数据包发送出去
*/
void Serial_SendPacket(void)
{
Serial_SendByte(0xFF);
Serial_SendArray(Serial_TxPacket, 4);
Serial_SendByte(0xFE);
}
/**
* 函 数:获取串口接收数据包标志位
* 参 数:无
* 返 回 值:串口接收数据包标志位,范围:0~1,接收到数据包后,标志位置1,读取后标志位自动清零
*/
uint8_t Serial_GetRxFlag(void)
{
if (Serial_RxFlag == 1) //如果标志位为1
{
Serial_RxFlag = 0;
return 1; //则返回1,并自动清零标志位
}
return 0; //如果标志位为0,则返回0
}
/**
* 函 数:USART1中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void USART1_IRQHandler(void)
{
static uint8_t RxState = 0; //定义表示当前状态机状态的静态变量
static uint8_t pRxPacket = 0; //定义表示当前接收数据位置的静态变量
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
uint8_t RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
/*使用状态机的思路,依次处理数据包的不同部分*/
/*当前状态为0,接收数据包包头*/
if (RxState == 0)
{
if (RxData == 0xFF) //如果数据确实是包头
{
RxState = 1; //置下一个状态
pRxPacket = 0; //数据包的位置归零
}
}
/*当前状态为1,接收数据包数据*/
else if (RxState == 1)
{
Serial_RxPacket[pRxPacket] = RxData; //将数据存入数据包数组的指定位置
pRxPacket ++; //数据包的位置自增
if (pRxPacket >= 4) //如果收够4个数据
{
RxState = 2; //置下一个状态
}
}
/*当前状态为2,接收数据包包尾*/
else if (RxState == 2)
{
if (RxData == 0xFE) //如果数据确实是包尾部
{
RxState = 0; //状态归0
Serial_RxFlag = 1; //接收数据包标志位置1,成功接收一个数据包
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除标志位
}
}
I2C通信协议
由于引脚少,硬件实施简单,可拓展性强,不需要USART、CAN等通讯协议的外部收发设备,被广泛运用到IC(集成电路)中
I2C总线:用于芯片之间进行通信的串行通信
由两条线组成:串行时钟线(SCL)和串行数据线(SDA)。这种总线允许多个设备在同一条总线上进行通信
I2C通信协议:
1.他是支持设备的总线,在一个I2C通信总线中,可以连接多个I2C通讯总线,支持多个通讯主机和多个通讯从机
2.一个I2C总线只有SCL:数据收发同步 SDA:表示数据
3.总线通过上拉电阻接到电源,空闲:高阻态
设备的SCL和SDA均要配置成开漏输出模式
SCL和SDA各添加一个上拉电阻,组织一般为4.7k左右
I2C支持总线挂载多设备(一主多从、多主多从)
I2C发送一个字节流程
发送一个字节:主机拉低SCL,将数据放在SDL上,主机松开SCL,从机读取SDL数据。在SCL的同步下,依次进行主句发送和从机接收,依次循环8次,发送8个数据(1个字节)
另外这里是高位先行:则第一位是一个字节的最高位B7
I2C时序设计
起始和终止条件
发送一个字节的流程
SCL低电平的时候,主机将数据依次放在SDA上,然后释放SCL
接收一个字节流程
在SCL低电平的时候,从机将数据放在SDL上(高位先行),然后释放SCL,主机将SCL高电平的时候读取数据,
发送应答和接收应答:
发送应答:主机在接受完一个字节之后,在下一个时钟发送一个数据,
数据0表示答应,数据1表示非答应
接收应答:主机在发送一个字节后,在下一个时钟接收一个数据,判断是否应答
数据0表示应答,数据1表示非应答
补充(uint8_t和void函数的区别)
void My_I2C(void) :无返回值,纯粹的初始化函数,仅执行硬件初始化(GPIO的配置、设置IC时钟等等),不返回任何的状态和结果
uint8_t My_I2C(void):返回值类型uint8_t,除了初始化硬件外,还会返回一个状态值,可以用于验证初始化是否成功的场景
SPI通信协议
SPI协议:串行外围设备接口、高速全双工的通信总线
SPI通讯总线有三条总线和片选线:3条总线分别为SCK、MOSI、MISO;片选线SS
SCK:时钟信号线,用于通讯数据同步,作为主从机设备输出,从设备的输入
MOSI:主设备输出引脚/从设备输入引脚。主机的数据从MOSI输出,从机由这条信号线读入从机发送的数据 主机——>从机
MISO: 从机——>主机
NSS:从设备选择。让主设备可以单独地与特定的从设备通讯,避免产生数据线上的冲突,功能为作为”片选引脚“
时钟信号的的相位和极性
CPOL:时钟极性:控制在没有数据传输时时钟的空闲状态电平
CPOL:0——SCK在空闲状态保持低电平
CPLO:1——SCL在空闲状态保持高电平
CPHA:时钟相位
CPHA:0 数据在 SCLK 的第一个边沿(即第一个跳变沿) 采样,数据输出在 SCLK 的第二个边沿 变化
CPHA:1 数据在 SCLK 的第二个边沿 采样,数据输出在 SCLK 的第一个边沿 变化
状态标志
发送缓存器空闲标志(TXE):”1“表示发送缓存其为空,当写入SPI_DR的时候,TXE标志被清除
接收缓冲器非空(RXNE):”1“表示在接收缓冲器中包含有效的接收数据
忙(Busy):BSY标志由硬件设置与清除,此标志表明SPI通信层的状态
写”1“:SPI忙于通信
以下情况被清0:1)当传输结束(在主模式下,如果是连续通信情况除外)
2)关闭SPI
3)主模式失效
SPI协议层
1.通讯的起始和终止
NSS信号线从高到低:SPI通讯的起始信号
NSS信号线从低到高:SPI通讯的停止信号
2.数据有效性
SPI使用MOSI和MISO信号线来传输数据,使用SCL信号线进行数据传输
MISO和MOSI数据线在SCL的每个时钟周期传输一位数据
要保证MSB和LSB两个通讯设备之间使用同样的协议,但是采用MSB先行模式
MOSI和MISO的数据在SCK的上升沿期间变化输出,在SCK的下降沿时被采样
3.主模式收发流程及时间说明
1)通讯开始的时候,SCK时钟开始运行,MOSI把发送缓冲区的数据一位一位的传输,MISO将数据一位一位的储存进缓冲区
2)当发送完一帧之后,TXE标志位:置1,表示传输完一帧;发送缓冲区已空
当接收完一帧之后,RXNE标志位:置1,表示传输完一帧,接收缓冲区非空
3)等待TXE标志位为1,如果还要继续发送数据,则再次往”数据寄存器DR“写如即可
输出引脚:推挽输出
输入引脚:浮空或上拉输入
SPI基本结构
作者:Jniip