目录
USART协议
串口初始化代码示例:
void USART2_Init()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_2; //TX
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz ; //配置引脚的速率
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ; //上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 ; //RX
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ; //配置引脚的速率
GPIO_Init(GPIOA,&GPIO_InitStructure);
//定义完引脚的模式,功能之后开始设置USART2的相关配置
USART_InitTypeDef USART2_InitStructure;
USART2_InitStructure.USART_BaudRate = 115200; //配置波特率
USART2_InitStructure.USART_HardwareFlowControl =USART_HardwareFlowControl_None;//是否开启硬件流控模式
USART2_InitStructure.USART_Mode = USART_Mode_Tx|USART_Mode_Rx; //设置工作模式
USART2_InitStructure.USART_Parity = USART_Parity_No; //设置奇偶校验
USART2_InitStructure.USART_StopBits = USART_StopBits_1; //设置停止位
USART2_InitStructure.USART_WordLength = USART_WordLength_8b; //设置数据长度
USART_Init(USART2,&USART2_InitStructure);
//串口中断配置 NVIC(Nested Vectored Interrupt Controller),用于处理中断优先级和中断使能设置
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE); //启用了 USART2 的接收中断(USART_IT_RXNE),当 USART2 接收到数据后将触发中断。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置 NVIC 的优先级分组为 2,这个函数用于设置中断优先级分组的方式,决定了优先级位数的分配,以及子优先级位数的分配
/*NVIC成员的属性配置*/
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART2_IRQn; //设置中断通道为 USART2 的中断通道
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //使能 USART2 的中断通道
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; //设置 USART2 中断的抢占优先级为 1。抢占优先级数值越低,优先级越高。
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; //设置 USART2 中断的子优先级为 1。子优先级是在同一抢占优先级下用于区分不同中断的优
NVIC_Init(&NVIC_InitStruct);
USART_Cmd(USART2,ENABLE);
}
STM中断知识补充:
在ARM Cortex-M处理器中,中断优先级的分组和子优先级是通过NVIC(Nested Vectored Interrupt Controller)来配置的。这个配置是为了更灵活地管理中断优先级,以适应不同外设和应用的需求。
-
中断优先级分组: NVIC 支持将中断优先级分为两个部分,一个是抢占优先级(Preemption Priority),另一个是子优先级(Subpriority)。抢占优先级用来决定在同一抢占组中哪个中断优先处理,而子优先级则用来在同一抢占优先级中决定中断的执行顺序。
-
如果中断源的抢占优先级高于正在执行的中断,那么会立即切换到新的中断。
-
如果中断源的抢占优先级相同,那么通过子优先级来决定执行顺序。
中断优先级分组的设置通过
NVIC_PriorityGroupConfig
函数进行配置,你的代码中的NVIC_PriorityGroup_2
指定了分组方式为2。 -
-
子优先级: 子优先级是在同一抢占优先级组内,用于区分不同中断的执行顺序。子优先级数值越低,优先级越高。
在你的代码片段中,抢占优先级为1,子优先级也为1。这意味着USART2的中断具有相对较高的优先级。当有多个中断同时发生时,系统会根据这些优先级来确定中断的执行顺序。
一般来说,对于外设的中断配置,可以根据应用需求来设置不同的优先级。例如,一些关键任务可能需要更高的中断优先级,而一些普通任务可以使用较低的优先级。在实际配置中,需要根据具体应用场景和系统要求来调整中断优先级的设置。
USART协议读数据
这段代码是ST官方库中提供的一条函数,实现串口的数据接收功能。
uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
{
/* Check the parameters */
assert_param(IS_USART_ALL_PERIPH(USARTx));
/* Receive Data */
return (uint16_t)(USARTx->DR & (uint16_t)0x01FF);
从 USART 的数据寄存器(DR,Data Register)中读取接收到的数据,并以 16 位无符号整数的形式返回。
}
下面这段代码是一个USART2串口的中断服务函数。当USART2接收到数据(USART_IT_RXNE标志被置位)时,会触发中断,执行这个中断服务函数。
void USART2_IRQHandler(void)
{
if (USART_GetITStatus(USART2, USART_IT_RXNE) == SET) //检查USART2接收寄存器是否准备好接收新的数据
{
Serial_RxData = USART_ReceiveData(USART2);//读取USART2接收到的数据,将其存储在Serial_RxData变量中
Serial_RxFlag = 1; //设置一个标志位(通常是一个全局变量),表示数据已经被接收到了
USART_ClearITPendingBit(USART2, USART_IT_RXNE); //清除接收寄存器非空的中断标志位,表示已经处理了这个中断
}
}
USART协议写数据
下面这段代码是官方源文件中给的一个函数,可以实现通过USARTx外设传输单个数据。
/**
* @brief Transmits single data through the USARTx peripheral.
* @param USARTx: Select the USART or the UART peripheral.
* This parameter can be one of the following values:
* USART1, USART2, USART3, UART4 or UART5.
* @param Data: the data to transmit.
* @retval None
*/
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
{
/* Check the parameters 检查函数的参数是否合法*/
assert_param(IS_USART_ALL_PERIPH(USARTx)); //这里的两个函数是一个宏定义,具体参看工程源文件
assert_param(IS_USART_DATA(Data));
/* Transmit Data */
USARTx->DR = (Data & (uint16_t)0x01FF); //这行代码实际上是将要发送的数据写入 USART 数据寄存器 (DR - Data Register)。在这里,使用了位掩码 0x01FF 来确保只有低9位有效,因为 USART 的数据寄存器通常是 9 位。这可以确保超过 9 位的数据部分不会影响传输
}
我们可以在此基础上来封装函数,实现长数据的发送。
void Serial_SendByte(uint8_t Byte) //发送一个字节
{
USART_SendData(USART2, Byte);
while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}
void Serial_SendArray(uint8_t *Array, uint16_t Length) //发送一个数组
{
uint16_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Array[i]);
}
}
void Serial_SendString(char *String) //发送一个字符串
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)
{
Serial_SendByte(String[i]);
}
}
——————————————————————————————————————————————————————
IIC协议
IIC数据的传输过程:
-
空闲状态
-
起始信号
-
传输过程
-
结束信号
IIC协议读数据:
读取数据的简要流程:
-
总线初始化: 首先,需要初始化I2C总线。这包括设置I2C的时钟频率、选择模式(主模式或从模式)以及配置I2C控制寄存器。
-
开始信号(Start Signal): 在进行读取操作之前,主设备(通常是微控制器或处理器)发送一个开始信号,表示要开始I2C通信。
-
发送从设备地址和读取位: 主设备发送要读取的从设备的7位地址,并设置读取位(R/W位)为1(表示读取操作)。这将引发从设备的应答信号。
-
从设备应答: 从设备接收到地址和读取位后,如果找到匹配的地址,它将发送一个应答信号。
-
读取数据: 主设备读取从设备发送的数据。在每个字节接收之后,主设备发送一个应答信号,表示继续读取下一个字节。如果主设备不需要继续读取,则发送无应答信号。
-
停止信号(Stop Signal): 在读取完成后,主设备发送一个停止信号,表示I2C通信结束。
// 从指定地址读数据 --->硬件模式
uint8_t I2C_ReadData(uint8_t deviceAddress, uint8_t registerAddress) {
uint8_t receivedData;
// 等待直到I2C总线空闲
while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
// 生成起始条件
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
// 发送设备地址和写模式(0)
I2C_Send7bitAddress(I2C1, deviceAddress, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
// 发送寄存器地址
I2C_SendData(I2C1, registerAddress);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// 生成重复起始条件
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
// 发送设备地址和读模式(1)
I2C_Send7bitAddress(I2C1, deviceAddress, I2C_Direction_Receiver);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
// 禁用应答
I2C_AcknowledgeConfig(I2C1, DISABLE);
// 产生停止条件
I2C_GenerateSTOP(I2C1, ENABLE);
// 等待接收到数据
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
receivedData = I2C_ReceiveData(I2C1);
// 启用应答
I2C_AcknowledgeConfig(I2C1, ENABLE);
return receivedData;
}
IIC协议写数据:
(软件模拟IIC)
#include "stm32f10x.h"
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))
/*引脚初始化*/
void OLED_I2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOB, &GPIO_InitStructure);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void OLED_I2C_Start(void)
{
OLED_W_SDA(1);
OLED_W_SCL(1);
OLED_W_SDA(0);
OLED_W_SCL(0);
}
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void OLED_I2C_Stop(void)
{
OLED_W_SDA(0);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C发送一个字节
* @param Byte 要发送的一个字节
* @retval 无
*/
void OLED_I2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
OLED_W_SDA(Byte & (0x80 >> i));
OLED_W_SCL(1);
OLED_W_SCL(0);
}
OLED_W_SCL(1); //额外的一个时钟,不处理应答信号
OLED_W_SCL(0);
}
IIC协议传输代码示例
(硬件IIC)
#include "stm32f10x.h"
void I2C_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
// Step 1: Enable the Clocks
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// Step 2: Configure the GPIO pins
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// Step 3: Configure the I2C peripheral
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; //设置I2C的工作模式为I2C模式
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; //设置I2C的占空比为50%
I2C_InitStructure.I2C_OwnAddress1 = 0x00; //not used in master mode : 在主模式下,该值不被使用
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //启用I2C的应答功能。当从设备收到数据后,需要向主设备发送一个应答信号,表示数据已被接收。
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //采用7位地址模式对从设备的地址进行确认
I2C_InitStructure.I2C_ClockSpeed = 100000; //100kHz 设置I2C的时钟速度为100kHz。这个参数决定了I2C通信的速度,即SCL线的频率
I2C_Init(I2C1, &I2C_InitStructure);
// Step 4: Enable the I2C peripheral
I2C_Cmd(I2C1, ENABLE);
}
// 向指定地址写数据
void I2C_WriteData(uint8_t deviceAddress, uint8_t registerAddress, uint8_t data) {
// 等待直到I2C总线空闲
while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
// 生成起始条件
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
// 发送设备地址和写模式(0)
I2C_Send7bitAddress(I2C1, deviceAddress, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
// 发送寄存器地址
I2C_SendData(I2C1, registerAddress);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// 发送数据
I2C_SendData(I2C1, data);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// 产生停止条件
I2C_GenerateSTOP(I2C1, ENABLE);
}
——————————————————————————————————————————————————————
SPI协议
SPI协议初始化代码示例
//SPI——————VS1053芯片的读写操作
//初始化VS1053的IO口
void VS_Init(void)
{
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(VS_SPIGPIO_CLK|VS_GPIO_DREQ_CLK|VS_GPIO_RST_CLK|VS_GPIO_XDCS_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = VS_DREQ; //DREQ
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(VS_GPIO_DREQ_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = VS_RST; //PB9
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_Init(VS_GPIO_RST_PORT, &GPIO_InitStructure);
/*初始化STM32 SPI2接口*/
RCC_APB1PeriphClockCmd(VS_SPI_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = VS_SCLK | VS_MISO | VS_MOSI;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用输出
GPIO_Init(VS_SPIGPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = VS_XCS;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_Init(VS_SPIGPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = VS_XDCS;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推免输出
GPIO_Init(VS_GPIO_XDCS_PORT, &GPIO_InitStructure);
/* SPI2 配置 */
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI的通讯方式--全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //SPI工作在主模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //每次数据传输8位
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //表示时钟空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //表示数据采样发生在第二个时钟沿(前面的时钟极性为SPI_CPOL_High,时钟默认为高电平。第二个时钟沿也就是下降沿,对应下降沿采样)
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //表示软件管理NSS信号
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //表示波特率预分频为256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //表示数据传输从最高位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //指定SPI的CRC多项式。在这里设置为7
SPI_Init(VS_SPI, &SPI_InitStructure);
/* 使能 SPI2 */
SPI_Cmd(VS_SPI, ENABLE);
SPI2_ReadWriteByte(0xff);//启动传输
}
SPI协议读数据:
SPI协议接收数据的流程通常如下:
-
发送数据: 在SPI通信开始之前,通常需要先发送一些数据到从设备,这通常会激活从设备并准备好它来对主设备发送数据。
-
发送和接收同步: 在SPI的全双工模式下,发送和接收是同时进行的。主设备发送数据的同时也接收从设备返回的数据。
-
发送接收数据: 主设备一边发送数据,一边接收从设备返回的数据。SPI使用一组寄存器来进行数据的发送和接收。
-
等待数据准备: 主设备发送完一个字节后,需要等待从设备准备好返回的数据。
-
读取数据: 当从设备准备好返回数据时,主设备从SPI数据寄存器中读取接收到的数据。
/*******************************************************************************
* Function Name : SPI_FLASH_SendByte
* Description : Sends a byte through the SPI interface and return the byte
* received from the SPI bus.
* Input : byte : byte to send.
* Output : None
* Return : The value of the received byte.
*******************************************************************************/
unsigned char SPI2_ReadWriteByte(unsigned char writedat)
{
/* Loop while DR register in not emplty */
while(SPI_I2S_GetFlagStatus(VS_SPI,SPI_I2S_FLAG_TXE) == RESET);
/* Send byte through the SPI1 peripheral */
SPI_I2S_SendData(VS_SPI, writedat);
while(SPI_I2S_GetFlagStatus(VS_SPI, SPI_I2S_FLAG_RXNE) == RESET);
/* Return the byte read from the SPI bus */
return SPI_I2S_ReceiveData(VS_SPI);
}
这个函数可以理解发送一个字节的数据到SPI总线上,并等待接收到一个字节的数据作为返回值
-
while(SPI_I2S_GetFlagStatus(VS_SPI,SPI_I2S_FLAG_TXE) == RESET)`:通过检查SPI_I2S_FLAG_TXE标志位的状态,等待直到发送缓冲区为空(即可以发送新数据)。
-
SPI_I2S_SendData(VS_SPI, writedat)
:将参数writedat
的值发送到SPI总线上。 -
while(SPI_I2S_GetFlagStatus(VS_SPI, SPI_I2S_FLAG_RXNE) == RESET)
:通过检查SPI_I2S_FLAG_RXNE标志位的状态,等待直到接收缓冲区中有数据。 -
return SPI_I2S_ReceiveData(VS_SPI)
:从SPI总线接收数据,并将接收到的数据返回。
#include "stm32f4xx.h" // 包含适用于你的STM32型号的头文件
// 定义片选引脚
#define CS_PIN GPIO_PIN_4
#define CS_PORT GPIOA
// 初始化SPI和片选引脚
void SPI_Init() {
// 初始化SPI外设
// 例如:设置SPI的工作模式、数据帧格式、速度等
// 这里假设SPI已经被正确地初始化
// 初始化片选引脚
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_Pin = CS_PIN;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(CS_PORT, &GPIO_InitStruct);
}
// 选择从设备
void SPI_SelectDevice() {
GPIO_ResetBits(CS_PORT, CS_PIN); // 低电平选中从设备
}
// 取消选择从设备
void SPI_DeselectDevice() {
GPIO_SetBits(CS_PORT, CS_PIN); // 高电平取消选择从设备
}
// 通过SPI发送和接收数据
unsigned char SPI_ReadWriteByte(unsigned char writedat) {
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, writedat);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
int main() {
SPI_Init();
// 选择从设备
SPI_SelectDevice();
// 向从设备发送数据并接收返回数据
unsigned char dataToSend = 0x55; // 你要发送的数据
unsigned char receivedData = SPI_ReadWriteByte(dataToSend);
// 取消选择从设备
SPI_DeselectDevice();
// 处理接收到的数据
// ...
while (1) {
// 主程序循环
}
}
/* 在这个示例中,我们首先定义了片选引脚的引脚号和端口,然后初始化SPI外设和片选引脚。SPI_SelectDevice函数用于选择从设备,即通过将片选引脚置为低电平来激活从设备。SPI_DeselectDevice函数用于取消选择从设备,即通过将片选引脚置为高电平来取消从设备的激活。 在main函数中,首先调用SPI_SelectDevice来选择从设备,然后使用SPI_ReadWriteByte函数向从设备发送数据并接收返回的数据。最后,通过调用SPI_DeselectDevice来取消选择从设备。 */
SPI协议写数据:
首先发送要写入数据的地址,然后再发送需要写入的数据。
中间 涉及到数据的采样在什么时候发生,例如(上升沿采样还是下降沿采样-->由SPI的时钟相性决定)
SPI的时钟极性决定了 时钟的空闲状态为高电平还是低电平。
void VS_WR_Cmd(u8 address,u16 data)
{
while(VS_DREQ_IN==0);//等待空闲
VS_SPI_SpeedLow();//低速
VS_XDCS_SET;
VS_XCS_CLR;
VS_SPI_ReadWriteByte(VS_WRITE_COMMAND);//发送VS10XX的写命令
VS_SPI_ReadWriteByte(address); //地址
VS_SPI_ReadWriteByte(data>>8); //发送高八位
VS_SPI_ReadWriteByte(data); //第八位
VS_XCS_SET;
VS_SPI_SpeedHigh(); //高速
}
//向VS10XX写数据
//data:要写入的数据
void VS_WR_Data(u8 data)
{
VS_SPI_SpeedHigh();//高速,对VS1003B,最大值不能超过36.864/4Mhz,这里设置为9M
VS_XDCS_CLR;
VS_SPI_ReadWriteByte(data);
VS_XDCS_SET;
}
——————————————————————————————————————————————————————
CAN协议
在CAN(Controller Area Network)协议中,存在几种不同的工作模式,包括正常模式、静默模式、静默回环模式和回环模式。以下是对这些模式的简要介绍:
-
正常模式(Normal Mode):
-
正常模式是CAN总线的默认工作模式。在这种模式下,节点可以发送和接收数据帧,与其他节点进行通信。节点在这个模式下正常地发送数据,并接收其他节点发送的数据。这是CAN总线的标准工作状态。
-
-
静默模式(Silent Mode):
-
静默模式是一种特殊的模式,节点在这个模式下不发送自己的数据帧到总线上,但仍然能够接收其他节点发送的数据帧。这意味着节点在静默模式下处于被动状态,仅接收数据而不发送任何数据。静默模式通常用于故障诊断或特定测试场景下。
-
-
静默回环模式(Silent Loopback Mode):
-
静默回环模式是一种结合了静默模式和回环模式的特殊模式。节点在这个模式下不发送数据到总线上,并且发送的数据会通过内部回环机制回收,以便节点可以接收自己发送的数据。这种模式通常用于测试CAN控制器和软件的功能性,能够在不影响真实网络的情况下测试发送和接收功能。
-
-
回环模式(Loopback Mode):
-
回环模式是一种用于自测和诊断的模式,节点发送的数据会被传送到接收端,同时也会被发送端自己接收到。这使得节点能够测试其CAN控制器和通信软件,而无需实际的物理总线连接。在这种模式下,发送的数据会经过内部环回,并由接收端接收,从而可以验证通信的正确性。
-
CAN外设初始化代码示例
#include "stm32f10x.h"
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
CanTxMsg TxMessage;
CanRxMsg RxMessage;
//初始化配置can外设引脚,及can模块成员属性
void CAN_Configuration(void) {
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIO和CAN时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
// 配置CAN引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// CAN模块初始化
CAN_InitStructure.CAN_TTCM = DISABLE; //时间触发通信方式:禁止
CAN_InitStructure.CAN_ABOM = DISABLE; //禁用自动总线关闭管理
CAN_InitStructure.CAN_AWUM = DISABLE; //禁用自动唤醒模式
CAN_InitStructure.CAN_NART = DISABLE; //禁用自动重传
CAN_InitStructure.CAN_RFLM = DISABLE; //禁用接收FIFO锁定模式
CAN_InitStructure.CAN_TXFP = DISABLE; //禁用传输FIFO优先级
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; //CAN_Mode_Normal表示使用正常模式
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq; //这里设置为CAN_SJW_1tq,表示1个时间单元
CAN_InitStructure.CAN_BS1 = CAN_BS1_4tq; //时间段1的时间单元数量,表示4个时间单元
CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq; //时间段2的时间单元数量,表示3个时间单元
CAN_InitStructure.CAN_Prescaler = 10; // 根据波特率需求进行调整
CAN_Init(CAN1, &CAN_InitStructure);
// CAN滤波器配置
CAN_FilterInitStructure.CAN_FilterNumber = 0; //配置的过滤器编号,这里设置为0。
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; //表示标识符掩码模式
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; //过滤器的比例表示32位比例
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000; //配置标识符的高位和低位,表示不过滤任何标识符
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000; //标识符的掩码,表示不使用掩码
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = 0; //指定过滤器与FIFO关联,设置为0表示关联到FIFO0
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; //启用过滤器
CAN_FilterInit(&CAN_FilterInitStructure);
}
CAN协议读数据:
//CanRxMsg RxMessage;
void My_CAN_Receive(void) {
// 检查是否有消息待接收
if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0) {
// 接收消息
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage); //st官方提供的库函数
// 处理接收到的数据,根据实际情况进行处理
// RxMessage.StdId 是接收到的消息的标准标识符
// RxMessage.DLC 是接收到的数据长度
// RxMessage.Data 包含了实际的数据
}
}
CAN协议写数据:
void My_CAN_Transmit(uint32_t id, uint8_t *data, uint8_t length) {
// 设置CAN发送消息 CanTxMsg TxMessage; CanTxMsg结构体定义了can发送帧的成员属性
TxMessage.StdId = id;
TxMessage.ExtId = 0x01;
TxMessage.RTR = CAN_RTR_DATA;
TxMessage.IDE = CAN_ID_STD;
TxMessage.DLC = length;
for (int i = 0; i < length; i++) {
TxMessage.Data[i] = data[i];
}
// 发送消息
CAN_Transmit(CAN1, &TxMessage); //st官方提供的库函数
}