嵌入式软件开发学习过程记录,本部分结合本人的学习经验撰写,系统描述各类基础例程的程序撰写逻辑。构建裸机开发的思维,为RTOS做铺垫(本部分基于库函数版实现),如有不足之处,敬请批评指正。
(5)中主要讲解外部数据的获取,首先介绍模数、数模转换、随后介绍除串口外的常用四种通信协议:IIC、SPI、485、CAN
一 ADC模数转换实验
ADC按照其转换原理主要分为逐次逼近型、双积分型、电压频率转换型三种。STM32F1 的 ADC 就是逐次逼近型的模拟数字转换器。STM32F103 系列一般都有 3 个 ADC,这些 ADC 可以独立使用,也可以使用双重/三重模式(提高采样率)。
选择输入通道-设置转换顺序-选择触发源(外部触发事件:禁止触发检测、上升沿检测、下降沿检测以及上升沿和下降沿均检测)-设置采样周期-数据寄存器存储数据
1.STM32F1 的 ADC 是 12 位逐次逼近型的模拟数字转换器。
12位表示ADC输出的数字信号由12位数字值组成
2.具有多达 18 个复用通道,可测量来自 16 个外部源、2 个内部信号源。
3.通道的 A/D 转换可以单次、连续、扫描或间断模式执行。
4.ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中。
5.ADC 具有模拟看门狗特性,允许应用程序检测输入电压是否超出用户定义的阀值上限或者下限。
18个通道对应的引脚
ADC中的中断
DMA(Direct Memory Access)是单片机中的一种数据传输方式,它可以在不占用处理器资源的情况下,通过直接将数据从外设传输到存储器或者从存储器传输到外设。
在传统的 I/O 操作中,CPU 需要向外设发送读写指令,并不断地查询外设是否已经完成读写操作。这种方式需要占用 CPU 大量的时间和资源,容易造成 CPU 的负载过高,影响系统的稳定性和可靠性。而采用 DMA 传输方式,可以使得 CPU 参与的仅仅是开始和结束的操作,即启动 DMA 传输和等待 DMA 传输完成,从而节省了大量的 CPU 资源。
单片机 DMA 的实现方式有多种,其中最常见的是将该功能集成在芯片内部,由硬件直接控制。在 STM32 系列单片机中,DMA 控制器包括多个通道,每个通道可以连接不同的外设和存储器地址,通过配置相应的寄存器,可以实现数据在外设和存储器之间的直接传输。这样,就能够提高系统的并行性和整体性能,同时降低 CPU 的使用率,提高系统的可靠性。
需要注意的是,使用单片机 DMA 传输时需要对通道进行合理的分配和调度,避免出现数据竞争和冲突等问题,同时需要根据具体的硬件和软件要求进行相应的配置,并且确保 DMA 传输的正确性和可靠性。
配置步骤
外部通道,每个通道都会对应芯片的一个引脚,比如 ADC1_IN1 对应 STM32F103ZET6 的 PA1 引脚,所以首先要使能 GPIOA 端口时钟和 ADC1 时钟,如下:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AN; //模拟输入模式
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
(3)初始化 ADC 参数,包括 ADC 工作模式、规则序列等
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
typedef struct{uint32_t ADC_Mode; // ADC 工作模式选择FunctionalState ADC_ScanConvMode; /* ADC 扫描(多通道)或者单次 (单通道)模式选择 */FunctionalState ADC_ContinuousConvMode; // ADC 单次转换或者连续转换选择uint32_t ADC_ExternalTrigConv; // ADC 转换触发信号选择uint32_t ADC_DataAlign; // ADC 数据寄存器对齐格式uint8_t ADC_NbrOfChannel; // ADC 采集通道数} ADC_InitTypeDef;参数如下:ADC_Mode:ADC 模式选择,有独立模式、双重模式,在双重模式下还有很多细分模式可选,具体由 ADC_CR1:DUALMOD 位配置。ADC_ScanConvMode:ADC 扫描模式选择。可选参数为 ENABLE 或 DISABLE,用来设置是否打开 ADC 扫描模式。如果是单通道 AD 转换,选择 DISABLE;如果是多通道 AD 转换,选择 ENABLE。ADC_ContinuousConvMode:ADC 连续转换模式选择。可选参数为 ENABLE 或DISABLE,用来设置是连续转换还是单次转换模式。如果为 ENABLE,则选择连续转换模式;如果为 DISABLE,则选择单次转换模式,转换一次后停止,需要手动控制才能重新启动转换。ADC_ExternalTrigConv:ADC 外部触发选择。ADC 外部触发条件有很多,在前面介绍框图时已列出,根据需要选择对应的触发条件,通常我们使用软件自动触发,所以此成员可以不用配置。ADC_DataAlign: ADC 数据对齐方式 。可选参数为右对齐ADC_DataAlign_Right 和左对齐 ADC_DataAlign_Left。ADC_NbrOfChannel:AD 转换通道数目,根据实际设置。具体的通道数和通道的转换顺序是配置规则序列或注入序列寄存器。
开启 ADC 的库函数void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);开启 ADC1ADC_Cmd(ADC1, ENABLE);//开启 AD 转换器执行复位校准的方法是:ADC_ResetCalibration(ADC1);执行 ADC 校准的方法是:ADC_StartCalibration(ADC1); //开始指定 ADC1 的校准状态每次进行校准之后要等待校准结束。 这里是通过获取校准状态来判断是否校准是否结束。比如while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
(在实际使用时,如果要使用32的原装AD转换而不用外置芯片的话,AD转换为温度是有计算公式的,这里只获取AD值,注意哦)
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel,uint8_t Rank, uint8_t ADC_SampleTime);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5 );
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的 ADC1 的软件转换启动功能
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
ADC_GetConversionValue(ADC1);
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
#include "adc.h"
#include "SysTick.h"
//ADC1初始化函数
void ADCx_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);//使能时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
//配置GPIO引脚
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;//ADC
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; //模拟输入
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//初始化ADC参数,包括ADC工作模式、规则序列等(结构体)
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//关闭连续转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//禁止触发检测,使用软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1;//1个转换在规则序列中 也就是只转换规则序列1
ADC_Init(ADC1, &ADC_InitStructure);//ADC初始化
ADC_Cmd(ADC1, ENABLE);//使能ADC或开启AD转换器
ADC_ResetCalibration(ADC1);//重置指定的ADC的校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1));//获取ADC重置校准寄存器的状态
ADC_StartCalibration(ADC1);//开始指定ADC的校准状态
while(ADC_GetCalibrationStatus(ADC1));//获取指定ADC的校准程序
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能或者失能指定的ADC的软件转换启动功能
}
//编写读取ADC转换值函数
u16 Get_ADC_Value(u8 ch,u8 times)
{
u32 temp_val=0;
u8 t;
//设置指定ADC的规则组通道,采样顺序,采样周期
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5); //ADC1,ADC通道,239.5个周期,提高采样时间可以提高精确度
for(t=0;t
二 DAC 数模转换实验
STM32F1 DAC 模块是 12 位电压输出数模转换器,它可以配置为 8 位或 12 位模式,
也可以与 DMA 控制器配合使用。
DAC 工作在 12 位模式下,数据可以采 用左对齐或右对齐。
DAC 工作在 8 位模式下,数据只有右对齐方式。
DAC 有两个输出通道,每个通道各有一个转换器。
在 DAC 双通道模式下,每个通道可以单独进行转换;
当两个通道组合在一起同步执行更新操作时,也可以同时进行转换。
DAC 可通过一个输入参考电压引脚 VREF+(与 ADC 共享)来提高转换后的数据精度。
配置步骤
例如要让 DAC1_OUT 输出,其对应的是 PA4 引脚,所以使能时钟和配置 PA4 引脚为模拟输入模式,代码如下:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);// 使能 GPIOA 时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);//使能 DAC 时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化
void DAC_Init(uint32_t DAC_Channel, DAC_InitTypeDef* DAC_InitStruct);
函 数 中 第 一 个 参 数 是 用 来 确 定 哪 个 DAC 通 道 , 例 如 DAC 通 道 1 (DAC_Channel_1);第二个参数是一个结构体指针变量,结构体类型是 DAC_InitTypeDef,其内包含了 DAC 初始化的成员变量。下面我们简单介绍下它的成员:
typedef struct{uint32_t DAC_Trigger; //DAC 触发选择uint32_t DAC_WaveGeneration; //DAC 波形发生uint32_t DAC_LFSRUnmask_TriangleAmplitude; //屏蔽/幅值选择器uint32_t DAC_OutputBuffer; //DAC 输出缓存}DAC_InitTypeDef;参数如下:DAC_Trigger:设置是否使用触发功能。前面介绍框图时已经说了 DAC 具有多个触发源,有定时器触发,外部中断线 9 触发,软件触发和不使用触发。其配置参数可在stm32f10x_dac.h 找到DAC_WaveGeneration:设置是否使用波形发生。在前面框图介绍也讲过,其配置参数可在 stm32f10x_dac.h 找到DAC_LFSRUnmask_TriangleAmplitude:设置屏蔽/幅值选择器。这个变量只在使用波形发 生器的时候才有用, 通常我们设置为 0 即可,值 为 DAC_LFSRUnmask_Bit0。其他配置参数同样可在 stm32f10x_dac.h 找到。DAC_OutputBuffer:设置输出缓存控制位。通常我们不使用输出缓存功能, 所以配置参数为 DAC_OutputBuffer_Disable。如果使用的话可以配置为使能 DAC_OutputBuffer_Enable。
初始化 DAC 后,我们就需要开启它,使能 DAC 输出通道的库函数为:
void DAC_Cmd(uint32_t DAC_Channel, FunctionalState NewState);
DAC_Cmd(DAC_Channel_1, ENABLE); //使能 DAC 通道 1
DAC_SetChannel1Data(DAC_Align_12b_R, 0); //12 位右对齐数据格式设置 DAC 值
uint16_t DAC_GetDataOutputValue(uint32_t DAC_Channel);
#include "dac.h"
#include "usart.h"
//初始化DCA函数
void DAC1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //初始化结构体变量
DAC_InitTypeDef DAC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);//使能DAC时钟
//设置GPIO引脚为模拟输入模式
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;//DAC_1
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;//模拟量输入
GPIO_Init(GPIOA,&GPIO_InitStructure);
//初始化DAC,设置DAC的工作模式
DAC_InitStructure.DAC_Trigger=DAC_Trigger_None; //不使用触发功能 TEN1=0
DAC_InitStructure.DAC_WaveGeneration=DAC_WaveGeneration_None;//不使用波形发生
DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;//屏蔽、幅值设置
DAC_InitStructure.DAC_OutputBuffer=DAC_OutputBuffer_Disable ; //DAC1输出缓存关闭 BOFF1=1
DAC_Init(DAC_Channel_1,&DAC_InitStructure); //初始化DAC通道1
DAC_SetChannel1Data(DAC_Align_12b_R, 0); //12位右对齐数据格式设置DAC值
DAC_Cmd(DAC_Channel_1, ENABLE); //使能DAC通道1
}
三 IIC实验(软件模拟IIC,因为STM32自带的稳定性不好)
I2C 总线只有两根双向信号线。一根是数据线 SDA,另一根是时钟线 SCL。由于其管脚少,硬件实现简单,可扩展性强等特点,因此被广泛的使用在各大集成芯片内。
包括:主机、从机、主模式、从模式、仲裁、同步、发送器、接收器。
IIC的物理层有如下特点
(1)它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在 一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。(2)一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。(3)每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。(4)总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。(5)多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。(6)具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。(7)连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制。
IIC的协议层有如下特点
(4)总线的寻址方式
比较,如果一样,器件会判定它被主机寻址,其他地址不同的器件将被忽略后面的数据信号。至于是从机接收器还是从机发送器,都由 R/W 位决定的
(5)数据传输
在总线的一次数据传送过程中,可以有以下几种组合方式:a、主机向从机发送数据,数据传送方向在整个传送过程中不变注意:有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送。A 表示应答,A 非表示非应答(高电平)。S 表示起始信号,P 表示终止信号。
b、主机在第一个字节后,立即从从机读数据
c、在传送过程中,当需要改变传送方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好相反
注意:此处使用IO口模拟IIC输出,而不是使用STM32的硬件IIC
#include "iic.h"
#include "SysTick.h"
/*******************************************************************************
* 函 数 名 : IIC_Init
* 函数功能 : IIC初始化
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;//初始化结构体变量
RCC_APB2PeriphClockCmd(IIC_SCL_PORT_RCC|IIC_SDA_PORT_RCC,ENABLE);//使能时钟
//对GPIO引脚的配置
GPIO_InitStructure.GPIO_Pin=IIC_SCL_PIN;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//配置推挽输出
GPIO_Init(IIC_SCL_PORT,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;
GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
IIC_SCL=1;
IIC_SDA=1;
}
/*******************************************************************************
* 函 数 名 : SDA_OUT
* 函数功能 : SDA输出配置
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void SDA_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
}
/*******************************************************************************
* 函 数 名 : SDA_IN
* 函数功能 : SDA输入配置
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void SDA_IN(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
}
/*******************************************************************************
* 函 数 名 : IIC_Start
* 函数功能 : 产生IIC起始信号
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=1;
IIC_SCL=1;
delay_us(5);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(6);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
/*******************************************************************************
* 函 数 名 : IIC_Stop
* 函数功能 : 产生IIC停止信号
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
IIC_SCL=1;
delay_us(6);
IIC_SDA=1;//发送I2C总线结束信号
delay_us(6);
}
/*******************************************************************************
* 函 数 名 : IIC_Wait_Ack
* 函数功能 : 等待应答信号到来
* 输 入 : 无
* 输 出 : 1,接收应答失败
0,接收应答成功
*******************************************************************************/
u8 IIC_Wait_Ack(void)
{
u8 tempTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;
delay_us(1);
IIC_SCL=1;
delay_us(1);
while(READ_SDA)
{
tempTime++;
if(tempTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
/*******************************************************************************
* 函 数 名 : IIC_Ack
* 函数功能 : 产生ACK应答
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(5);
IIC_SCL=0;
}
/*******************************************************************************
* 函 数 名 : IIC_NAck
* 函数功能 : 产生NACK非应答
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(5);
IIC_SCL=0;
}
/*******************************************************************************
* 函 数 名 : IIC_Send_Byte
* 函数功能 : IIC发送一个字节
* 输 入 : txd:发送一个字节
* 输 出 : 无
*******************************************************************************/
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t0) //0x80 1000 0000
IIC_SDA=1;
else
IIC_SDA=0;
txd
具体使用:
编写功能函数——写入一个数据——写入长度为Len的数据——写入指定个数的数据
#include "24cxx.h"
#include "SysTick.h"
/*******************************************************************************
* 函 数 名 : AT24CXX_Init
* 函数功能 : AT24CXX初始化
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void AT24CXX_Init(void)
{
IIC_Init();//IIC初始化
}
/*******************************************************************************
* 函 数 名 : AT24CXX_ReadOneByte
* 函数功能 : 在AT24CXX指定地址读出一个数据
* 输 入 : ReadAddr:开始读数的地址
* 输 出 : 读到的数据
*******************************************************************************/
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{
u8 temp=0;
IIC_Start();
if(EE_TYPE>AT24C16)
{
IIC_Send_Byte(0XA0); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr>>8);//发送高地址
}
else
{
IIC_Send_Byte(0XA0+((ReadAddr/256)AT24C16)
{
IIC_Send_Byte(0XA0); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(WriteAddr>>8);//发送高地址
}
else
{
IIC_Send_Byte(0XA0+((WriteAddr/256)>(8*t))&0xff);
}
}
/*******************************************************************************
* 函 数 名 : AT24CXX_ReadLenByte
* 函数功能 : 在AT24CXX里面的指定地址开始读出长度为Len的数据
用于读出16bit或者32bit的数据
* 输 入 : ReadAddr :开始读出的地址
Len :要读出数据的长度2,4
* 输 出 : 读取的数据
*******************************************************************************/
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len)
{
u8 t;
u32 temp=0;
for(t=0;t
四 SPI实验
SPI 接口一般使用 4 条线通信,事实上只需 3 条线也可以进行 SPI 通信(单向传输时),其中 3 条为 SPI 总线(MISO、MOSI、SCLK),一条为 SPI 片选信号线(CS)。它们的作用如下:
MISO:主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。MOSI:主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。SCLK:时钟信号线,用于通信数据同步。它由主机产生,决定了通信的速率,不同的设备支持的最高时钟频率不一样,如 STM32 的 SPI 时钟频率最大为 fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。CS:从设备选择信号线,常称为片选信号线,也称为 NSS 或 CS,以下用 CS 表示。 当有多个 SPI 从设备与 SPI 主机相连时,设备的其它信号线 SCK、MOSI 及 MISO 同时并联到相同的 SPI 总线上,即无论有多少个从设备,都共同只使用这 3 条总线;而每个从设备都有独立的这一条 CS 信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条选信号线。I2C 协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而 SPI 协议中没有设备地址,它使用 CS 信号线来寻址,当主机要选择从设备时,把该从设备的 CS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI 通信。所以 SPI 通信以 NSS 线置低电平为开始信号,以 CS 线被拉高作为结束信号。
配置步骤(与IIC不同,这里是直接调用SPI)
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
/* SPI 的 IO 口设置 */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15 复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
typedef struct{uint16_t SPI_Direction; //设置 SPI 的单双向模式uint16_t SPI_Mode; //设置 SPI 的主/从机端模式uint16_t SPI_DataSize; //设置 SPI 的数据帧长度,可选 8/16 位uint16_t SPI_CPOL; //设置时钟极性 CPOL,可选高/低电平uint16_t SPI_CPHA; //设置时钟相位,可选奇/偶数边沿采样uint16_t SPI_NSS; //设置 NSS 引脚由 SPI 硬件控制还是软件控制uint16_t SPI_BaudRatePrescaler;//设置时钟分频因子uint16_t SPI_FirstBit; //设置 MSB/LSB 顺序uint16_t SPI_CRCPolynomial; //设置 CRC 校验的表达式}SPI_InitTypeDef;参数如下:SPI_Direction:用于设置 SPI 的通信方向,可设置为双线全双工 (SPI_Direction_2Lines_FullDuplex) , 双线只接收(SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模式(SPI_Direction_1Line_Tx)。本实验设置的是双线全双工,即 SPI_Direction_2Lines_FullDuplex。SPI_Mode:用于设置 SPI 工作在主机模式(SPI_Mode_Master)或从机模式 (SPI_Mode_Slave ),这两个模式的最大区别为 SPI 的 SCK 信号线的时序, SCK的时序是由通讯中的主机产生的。若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。本实验设置的是主机模式,即 SPI_Mode_Master。SPI_DataSize : 用于设置 SPI 通信的数据帧长度,可以选择 8 位 (SPI_DataSize_8b)或者 16 位(SPI_DataSize_16b)。本实验设置的是 8 位,即SPI_DataSize_8b。SPI_CPOL:用于设置时钟极性,可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。本实验我们设置串行同步时钟的空闲状态为高电平所以选择SPI_CPOL_High。SPI_CPHA:用于设置时钟相位,也就是选择在串行同步时钟的第几个跳变沿(上升或下降)数据被采样,可以为 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 或 SPI_CPHA_2Edge (在 SCK 的偶数边沿采集数据) 。SPI_NSS : 用于设置 NSS 引脚的使用模式,可以选择为硬件模式 (SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的 NSS 信号由 SPI 硬件自动产生,而软件模式则需要我们使用相应的 GPIO 端口来控制。本实验我们使用软件模式,即 SPI_NSS_Soft。SPI_BaudRatePrescaler:用于设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。可设置为 fpclk 的 2、 4、 6、 8、 16、 32、 64、 128 、 256 分 频 。SPI_FirstBit:用于设置数据传输顺序是 MSB 位在前还是 LSB 位在前SPI_CRCPolynomial:用于设置 CRC 校验多项式,提高通信可靠性,若我们使用 CRC 校验时,就使用这个成员的参数(多项式),来计算 CRC 的值,我们这里大于 1 即可。
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
SPI_Cmd(SPI2, ENABLE); //使能 SPI 外设
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE);
#include "spi.h"
//以下是SPI模块的初始化代码,配置成主机模式
//SPI口初始化
//这里针是对SPI1的初始化
void SPI1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //初始化结构体
SPI_InitTypeDef SPI_InitStructure;
/* SPI的IO口和SPI外设打开时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
/* SPI的IO口设置,配置复用功能 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:波特率预分频值为256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI1, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI1, ENABLE); //使能SPI外设
SPI1_ReadWriteByte(0xff);//启动传输
}
//SPI1速度设置函数
//SPI速度=fAPB2/分频系数
//@ref SPI_BaudRate_Prescaler:SPI_BaudRatePrescaler_2~SPI_BaudRatePrescaler_256
//fAPB2时钟一般为84Mhz:
void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler)
{
SPI1->CR1&=0XFFC7;//位3-5清零,用来设置波特率
SPI1->CR1|=SPI_BaudRatePrescaler; //设置SPI1速度
SPI_Cmd(SPI1,ENABLE); //使能SPI1
}
//SPI1 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI1_ReadWriteByte(u8 TxData)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);//等待发送区空
SPI_I2S_SendData(SPI1, TxData); //通过外设SPIx发送一个byte 数据
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); //等待接收完一个byte
return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据
}
//SPI口初始化
//这里针是对SPI2的初始化
void SPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
/* SPI的IO口和SPI外设打开时钟 */
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
/* SPI的IO口设置 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:波特率预分频值为256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI2, ENABLE); //使能SPI外设
SPI2_ReadWriteByte(0xff);//启动传输
}
//SPI2速度设置函数
//SPI速度=fAPB1/分频系数
//@ref SPI_BaudRate_Prescaler:SPI_BaudRatePrescaler_2~SPI_BaudRatePrescaler_256
//fAPB1时钟一般为36Mhz:
void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
{
SPI2->CR1&=0XFFC7;//位3-5清零,用来设置波特率
SPI2->CR1|=SPI_BaudRatePrescaler; //设置SPI速度
SPI_Cmd(SPI2,ENABLE); //使能SPI2
}
//SPI2 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);//等待发送区空
SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个byte 数据
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET); //等待接收完一个byte
return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据
}
五 RS485实验(半双工)
#include "rs485.h"
#include "SysTick.h"
/*******************************************************************************
* 函 数 名 : RS485_Init
* 函数功能 : USART2初始化函数
* 输 入 : bound:波特率
* 输 出 : 无
*******************************************************************************/
void RS485_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG|RCC_APB2Periph_GPIOA,ENABLE); //使能GPIOAG时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟
/* 配置GPIO的模式和IO口 */
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; //TX-485 //串口输出PA2
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure); /* 初始化串口输入IO */
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3; //RX-485 //串口输入PA3
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //模拟输入
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3; //CS-485
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOG,&GPIO_InitStructure);
//USART2 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置
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(USART2, &USART_InitStructure); //初始化串口2
USART_Cmd(USART2, ENABLE); //使能串口 2
USART_ClearFlag(USART2, USART_FLAG_TC);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启接受中断
//Usart2 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =2; //子优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
RS485_TX_EN=0; //默认为接收模式
}
/*******************************************************************************
* 函 数 名 : USART2_IRQHandler
* 函数功能 : USART2中断函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void USART2_IRQHandler(void)
{
u8 res;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)//接收到数据
{
res =USART_ReceiveData(USART2);//;读取接收到的数据USART2->DR
RS485_TX_EN=1;
delay_ms(1);
USART_SendData(USART2,res);
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)!=1);
delay_ms(2);
RS485_TX_EN=0;
}
USART_ClearFlag(USART2,USART_FLAG_TC);
}
六 CAN通信实验
这部分由于笔者会从事于汽车电控的相关知识,因此会进行学习,一般来说CAN会用在汽车内的通信总线,具备多主的特点。而在其他领域 MODBUS RTU/TCP协议则会更加通用
系统的柔软性。与总线相连的单元没有类似于“地址”的信息。因此在总线上增加单元时,连接在总线上的其它单元的软硬件及应用层都不需要改变。
通信速度较快,通信距离远。 最高 1Mbps(距离小于 40M),最远可达 10KM(速率低于 5Kbps)。
CAN的帧结构
以数据帧为例
(1)帧起始。表示数据帧开始的段。(2)仲裁段。表示该帧优先级的段。(3)控制段。表示数据的字节数及保留位的段。(4)数据段。数据的内容,一帧可发送 0~8 个字节的数据。(5)CRC 段。检查帧的传输错误的段。(6)ACK 段。表示确认正常接收的段。(7)帧结束。表示数据帧结束的段。
图中 D 表示显性电平, R 表示隐形电平(下同)。
①.帧起始是由 1 个位的显性电平表示,标准帧和扩展帧相同。
②.仲裁段,表示数据优先级的段,标准帧和扩展帧格式在本段有所区别, 如图所示
从图中可以看出,标准格式的 ID 有 11 个位。从 ID28 到 ID18 被依次发送。禁止高 7 位都为隐性(禁止设定: ID=1111111XXXX)。扩展格式的 ID 有 29 个位。基本 ID 从 ID28 到 ID18,扩展 ID 由 ID17 到 ID0 表示。基本 ID 和 标 准 格 式 的 ID 相 同 。 禁 止 高 7 位 都 为 隐 性 ( 禁 止 设 定 : 基 本 ID=1111111XXXX)
上图中, r0 和 r1 为保留位。保留位必须全部以显性电平发送,但接收方可以接收显性、隐性及其任意组合的电平。DLC 为数据长度码,数据长度码与数据的字节数的对应关系如图所示:
数据的字节数必须为 0~8 字节。但接收方对 DLC = 9~15 的情况并不视为错误。
CRC 顺序是根据多项式生成的 CRC 值, CRC 的计算范围包括帧起始、仲裁段、控制段、数据段。接收方以同样的算法计算 CRC 值并进行比较,不一致时会通报错误。
发送单元在 ACK 段发送 2 个位的隐性位。接收到正确消息的单元在 ACK 槽(ACK Slot)发送显性位, 通知发送单元正常接收结束。这称作“发送 ACK” 或者“返回 ACK”。发送 ACK 的是在既不处于总线关闭态也不处于休眠态的所有接收单元中,接收到正常消息的单元(发送单元不发送 ACK)。所谓正常消息是指不含填充错误、格式错误、CRC 错误的消息。
CAN配置步骤
(CAN 相关库函数在 stm32f10x_can.c 和 stm32f10x_can.h 文件中)
要使用 CAN,首先就是使能它的时钟,我们知道 CAN1 和 CAN2 是挂接在 APB1 总线上的,其发送和接收引脚对应不同的 STM32F1 IO(具体 IO 可以通过数据手册查找,也可以在我们原理图上查找),因此使能 CAN 时钟后,还需要使能对应端口的时钟,并且将其引脚配置为复用功能。因为我们使用的 STM32F103ZET6 芯片只有一个 CAN,即 CAN1,其对应的 IO 是 PA11(CAN1_RX)和 PA12(CAN1_TX)。 所以配置代码如下:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); //打开CAN1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //PA端口时钟打开
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PA11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入模式
GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PA12
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure);
uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct);
typedef struct{uint16_t CAN_Prescaler;uint8_t CAN_Mode;uint8_t CAN_SJW;uint8_t CAN_BS1;uint8_t CAN_BS2;FunctionalState CAN_TTCM;FunctionalState CAN_ABOM;FunctionalState CAN_AWUM;FunctionalState CAN_NART;FunctionalState CAN_RFLM;FunctionalState CAN_TXFP;} CAN_InitTypeDef;参数如下:CAN_Prescaler:用于设置 CAN 外设的时钟分频,它可控制时间片 tq 的时
间长度,这里设置的值最终会加 1 后再写入 BRP 寄存器位。CAN_Mode : 用于设置 CAN 的工作模式, 可设置为正常模式 (CAN_Mode_Normal) 、 回 环模式 (CAN_Mode_LoopBack) 、 静默模式 (CAN_Mode_Silent)以及回环静默模式(CAN_Mode_Silent_LoopBack)。本实验使用到的只有正常模式和回环模式。CAN_SJW:用于置 SJW 的极限长度,即 CAN 重新同步时单次可增加或缩短的最大长度,它可以被配置为 1-4tq(CAN_SJW_1/2/3/4tq)。CAN_BS1:用于设置 CAN 位时序中的 BS1 段的长度,它可以被配置为 1-16 个 tq 长度(CAN_BS1_1/2/3…16tq)。CAN_BS2:用于设置 CAN 位时序中的 BS2 段的长度,它可以被配置为 1-8 个 tq 长度(CAN_BS2_1/2/3…8tq)。CAN_TTCM:用于设置是否使用时间触发功能,ENABLE 为使能,DISABLE 为失能。时间触发功能在某些 CAN 标准中会使用到。CAN_ABOM:用于设置是否使用自动离线管理(ENABLE/DISABLE),使用自动离线管理可以在节点出错离线后适时自动恢复,不需要软件干预。CAN_AWUM:用于设置是否使用自动唤醒功能(ENABLE/DISABLE),使能自动唤醒功能后它会在监测到总线活动后自动唤醒。CAN_NART:用于设置是否使用自动重传功能(ENABLE/DISABLE),使用自动重传功能时,会一直发送报文直到成功为止。CAN_RFLM:用于设置是否使用锁定接收 FIFO(ENABLE/DISABLE),锁定接收
FIFO 后,若 FIFO 溢出时会丢弃新数据,否则在 FIFO 溢出时以新数据覆盖旧数据。CAN_TXFP:用于设置发送报文的优先级判定方法(ENABLE/DISABLE),使能时,以报文存入发送邮箱的先后顺序来发送,否则按照报文 ID 的优先级来发送。
void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);
typedef struct{uint16_t CAN_FilterIdHigh;uint16_t CAN_FilterIdLow;uint16_t CAN_FilterMaskIdHigh;uint16_t CAN_FilterMaskIdLow;uint16_t CAN_FilterFIFOAssignment; //uint8_t CAN_FilterNumber;uint8_t CAN_FilterMode;uint8_t CAN_FilterScale;FunctionalState CAN_FilterActivation;} CAN_FilterInitTypeDef;参数如下:CAN_FilterIdHigh:用于存储要筛选的 ID,若筛选器工作在 32 位模式, 它存储的是所筛选 ID 的高 16 位;若筛选器工作在 16 位模式,它存储的就是一个完整的要筛选的 ID。CAN_FilterIdLow:同上一个成员一样,它也是用于存储要筛选的 ID,若筛选器工作在 32 位模式,它存储的是所筛选 ID 的低 16 位;若筛选器工作在 16 位模式,它存储的就是一个完整的要筛选的 ID。CAN_FilterMaskIdHigh : 用 于 存 储 要 筛 选 的 ID 或 掩 码 。CAN_FilterMaskIdHigh 存储的内容分两种情况,当筛选器工作在标识符列表模式时,它的功能与 CAN_FilterIdHigh 相同,都是存储要筛选的 ID;而当筛选器工作在掩码模式时,它存储的是 CAN_FilterIdHigh 成员对应的掩码,与 CAN_FilterIdLow 组成一组筛选器。CAN_FilterMaskIdLow:同上一个成员一样,它也是用于存储要筛选的 ID或掩码,只不过这里对应存储 CAN_FilterIdLow 的成员。
CAN_FilterFIFOAssignment:用于设置当报文通过筛选器的匹配后,该报文会被存储到哪一 个接收 FIFO,它的可选值为 FIFO0 或 FIFO1(CAN_Filter_FIFO0/1)。CAN_FilterNumber:用于设置筛选器的编号,即使用的是哪个筛选器。CAN 一共有 28 个筛选器,所以它的可输入参数范围为 0-27。CAN_FilterMode:用于设置筛选器的工作模式,可以设置为列表模式 (CAN_FilterMode_IdList)及掩码模式(CAN_FilterMode_IdMask)。CAN_FilterScale : 用于设置筛选器的位宽,可以设置为 32 位长
(CAN_FilterScale_32bit)及 16 位长(CAN_FilterScale_16bit)。CAN_FilterActivation:用于设置是否激活这个筛选器(ENABLE/DISABLE)。
第一个参数用来选择 CAN,第二个参数用来选择 CAN 中断类型,最后一个参数用来使能或者失能 CAN 中断。
CAN 的中断类型很多,可以在 stm32f10x_can.h 文件查找,void CAN_ITConfig(CAN_TypeDef* CANx, uint32_t CAN_IT, FunctionalState NewState);
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage);
typedef struct{uint32_t StdId;uint32_t ExtId;uint8_t IDE;uint8_t RTR;uint8_t DLC;uint8_t Data[8];} CanTxMsg;参数如下:StdId:用于存储报文的 11 位标准标识符,范围是 0-0x7FF。ExtId:用于存储报文的 29 位扩展标识符,范围是 0-0x1FFFFFFF。ExtId 与 StdId 这两个成员哪一个有效要根据下面的 IDE 位配置。IDE:用于存储扩展标志 IDE 位的值,其值可配置为 CAN_ID_STD 和CAN_ID_EXT。如果为 CAN_ID_STD 时表示本报文是标准帧,使用 StdId 成员存储报文 ID。如果为 CAN_ID_EXT 时表示本报文是扩展帧,使用 ExtId 成员存储报文 ID。RTR:用于存储报文类型标志 RTR 位的值,当它的值为宏 CAN_RTR_Data 时表示本报文是数据帧;当它的值为宏 CAN_RTR_Remote 时表示本报文是遥控帧,由于遥控帧没有数据段,所以当报文是遥控帧时,下面的 Data[8]成员的内容是无效的。DLC:用于存储数据帧数据段的长度,其值范围是 0-8,当报文是遥控帧时DLC 值为 0。Data[8]:用于存储数据帧中数据段的数据。例如:CanTxMsg TxMessage;TxMessage.StdId=0x12; // 标准标识符为 0TxMessage.ExtId=0x12; // 设置扩展标示符(29 位)TxMessage.IDE=0; // 使用扩展标识符TxMessage.RTR=0; // 消息类型为数据帧,一帧 8 位TxMessage.DLC=8; // 发送两帧信息for(i=0;i
TxMessage.Data[8]=msg[i]; // 第一帧信息CAN_Transmit(CAN1, &TxMessage);
void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage);
typedef struct{uint32_t StdId;uint32_t ExtId;uint8_t IDE;uint8_t RTR;uint8_t DLC;uint8_t Data[8];uint8_t FMI;} CanRxMsg;前面几个成员和 CanTxMsg 结构体内是一样的,在 CanRxMsg 中多了一个成员 FMI,它用于存储筛选器的编号,表示本报文是经过哪个筛选器存储进接收 FIFO 的,可以用它简化软件处理。
#include "can.h"
#include "usart.h"
//CAN初始化
//tsjw:重新同步跳跃时间单元.范围:CAN_SJW_1tq~ CAN_SJW_4tq
//tbs2:时间段2的时间单元. 范围:CAN_BS2_1tq~CAN_BS2_8tq;
//tbs1:时间段1的时间单元. 范围:CAN_BS1_1tq ~CAN_BS1_16tq
//brp :波特率分频器.范围:1~1024; tq=(brp)*tpclk1
//波特率=Fpclk1/((tbs1+tbs2+1)*brp);
//mode:CAN_Mode_Normal,普通模式;CAN_Mode_LoopBack,回环模式;
//Fpclk1的时钟在初始化的时候设置为36M,如果设置CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);
//则波特率为:36M/((8+9+1)*4)=500Kbps
//返回值:0,初始化OK;
// 其他,初始化失败;
void CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
{
GPIO_InitTypeDef GPIO_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
#if CAN_RX0_INT_ENABLE
NVIC_InitTypeDef NVIC_InitStructure;
#endif
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); //打开CAN1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //PA端口时钟打开
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PA11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PA12
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure);
//CAN单元设置
CAN_InitStructure.CAN_TTCM=DISABLE; //非时间触发通信模式
CAN_InitStructure.CAN_ABOM=DISABLE; //软件自动离线管理
CAN_InitStructure.CAN_AWUM=DISABLE;//睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位)
CAN_InitStructure.CAN_NART=ENABLE; //使用报文自动传送
CAN_InitStructure.CAN_RFLM=DISABLE; //报文不锁定,新的覆盖旧的
CAN_InitStructure.CAN_TXFP=DISABLE; //优先级由报文标识符决定
CAN_InitStructure.CAN_Mode= mode; //模式设置
CAN_InitStructure.CAN_SJW=tsjw; //重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1tq~CAN_SJW_4tq
CAN_InitStructure.CAN_BS1=tbs1; //Tbs1范围CAN_BS1_1tq ~CAN_BS1_16tq
CAN_InitStructure.CAN_BS2=tbs2;//Tbs2范围CAN_BS2_1tq ~ CAN_BS2_8tq
CAN_InitStructure.CAN_Prescaler=brp; //分频系数(Fdiv)为brp+1
CAN_Init(CAN1, &CAN_InitStructure); // 初始化CAN1
//配置过滤器
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;32位ID
CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;//32位MASK
CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;//过滤器0关联到FIFO0
CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //激活过滤器0
CAN_FilterInit(&CAN_FilterInitStructure);//滤波器初始化
#if CAN_RX0_INT_ENABLE
CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE); //FIFO0消息挂号中断允许.
NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 主优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 次优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
#endif
}
#if CAN_RX0_INT_ENABLE //使能RX0中断
//中断服务函数
void USB_LP_CAN1_RX0_IRQHandler(void)
{
CanRxMsg RxMessage;
int i=0;
CAN_Receive(CAN1, 0, &RxMessage);
for(i=0;i=0XFFF)return 1;
return 0;
}
//can口接收数据查询
//buf:数据缓存区;
//返回值:0,无数据被收到;
// 其他,接收的数据长度;
u8 CAN_Receive_Msg(u8 *buf)
{
u32 i;
CanRxMsg RxMessage;
if( CAN_MessagePending(CAN1,CAN_FIFO0)==0)return 0; //没有接收到数据,直接退出
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);//读取数据
for(i=0;i
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net