技术标签: stm32 c语言 51单片机 嵌入式硬件 单片机
目录
1、并行通信/串行通信
2、异步通信/同步通信
3、半双工通信/全双工通信
并行通信:传输速度快,一次传输8bit,但是通信成本高,需要8个独立的通道,另外不支持长距离传输。用于打印机和扫描仪等设备,例如DB-25接口。
串行通信:传输速度慢,成本低,支持长距离传输,是计算机通信的主要方式,例如DB-接口。
异步通信:用于低速设备,会有更高的误码率。
同步通信:用于高速设备传输,同步传输有同步时钟为节拍进行传输数据。
单工通信:发送机只能给接收机发送数据,不允许从接收机发送给发送机。
半双工通信:发送机和接收机可以相互读写通信,但不能同时读写。
全双工工信:发送机和接收机可以相互读写通信,且能同时读写。
UART(Universal Asynchronous Receiver Transmitter),具有串行通信、异步通信、全双工通信的特点,两线制(TX, RX),传输速度慢,点对点的异步通信,一般用于RJ45 Console、打印机等。
UART工作原理:
发送器UART1从发送端数据总线接收到并行数据,将起始位、校验位和停止位添加到数据帧中,打包成数据包;然后将数据包以串行方式发送给接收器UART2;UART2以预配置的波特率对数据进行采样,将数据包还原成数据帧;最后UART2将数据帧串行转并传输给接收端的数据总线。
I2C(Inter-Integrated Circuit),具有串行通信、同步通信、半双工通信的特点,两线制(SDA, SCLK),用于监控、存储和数字信号处理器等。
I2C是两线制(SDA, SCLK),通过上拉电阻接到电源线,总线空闲时,SDA,SCLK都保持高电平。
I2C的数据传输过程:
Step1: I2C总线空闲时,上拉电阻使SDA, SCLK处于高电平。
Step2:Master发送start后,将SDA由高电平切换成低电平,然后SCLK线也由高电平切换成低电平。
Step3: Master在发送start后,再发送 slave的地址和读/写的命令,其中write是0,read是1,slave收到地址和读写命令后,向master回复ASK。
Step4:Master收到ASK后,再发送特定寄存器的地址,slave收到后回复master ASK。
Step5:Master再次收到ASK后,再像特定的寄存器发送8bit数据,slave收到数据后回复ASK,重复这一动作直至数据发完。
Step6:Master收到stop,SCLK由低电平切换成高电平,随后SDA也从低电平切换成高电平。
SPI(Serial Peripheral Interface),具有串行通信、同步通信、全双工通信的特点,四线制(CS, SCLK, MOSI, MISO),传输速度快,时许同步准确,一般用于存储器、数字信号处理、传感器和语音识别等。
/*******************************************************************************
* 函 数 名 : iic_start
* 函数功能 : 产生IIC起始信号
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void iic_start(void)
{
IIC_SDA=1;//如果把该条语句放在SCL后面,第二次读写会出现问题
delay_10us(1);
IIC_SCL=1;
delay_10us(1);
IIC_SDA=0; //当SCL为高电平时,SDA由高变为低
delay_10us(1);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
delay_10us(1);
}
void iic_start(void)
{
IIC_SDA=1;//如果把该条语句放在SCL后面,第二次读写会出现问题
delay_10us(1);
IIC_SCL=1;
delay_10us(1);
IIC_SDA=0; //当SCL为高电平时,SDA由高变为低
delay_10us(1);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
delay_10us(1);
}
/*******************************************************************************
* 函 数 名 : iic_stop
* 函数功能 : 产生IIC停止信号
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void iic_stop(void)
{
IIC_SDA=0;//如果把该条语句放在SCL后面,第二次读写会出现问题
delay_10us(1);
IIC_SCL=1;
delay_10us(1);
IIC_SDA=1; //当SCL为高电平时,SDA由低变为高
delay_10us(1);
}
/*******************************************************************************
* 函 数 名 : iic_ack
* 函数功能 : 产生ACK应答
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void iic_ack(void)
{
IIC_SCL=0;
IIC_SDA=0; //SDA为低电平
delay_10us(1);
IIC_SCL=1;
delay_10us(1);
IIC_SCL=0;
}
/*******************************************************************************
* 函 数 名 : iic_nack
* 函数功能 : 产生NACK非应答
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void iic_nack(void)
{
IIC_SCL=0;
IIC_SDA=1; //SDA为高电平
delay_10us(1);
IIC_SCL=1;
delay_10us(1);
IIC_SCL=0;
}
/*******************************************************************************
* 函 数 名 : iic_wait_ack
* 函数功能 : 等待应答信号到来
* 输 入 : 无
* 输 出 : 1,接收应答失败
0,接收应答成功
*******************************************************************************/
u8 iic_wait_ack(void)
{
u8 time_temp=0;
IIC_SCL=1;
delay_10us(1);
while(IIC_SDA) //等待SDA为低电平
{
time_temp++;
if(time_temp>100)//超时则强制结束IIC通信
{
iic_stop();
return 1;
}
}
IIC_SCL=0;
return 0;
}
/*******************************************************************************
* 函 数 名 : iic_write_byte
* 函数功能 : IIC发送一个字节
* 输 入 : dat:发送一个字节
* 输 出 : 无
*******************************************************************************/
void iic_write_byte(u8 dat)
{
u8 i=0;
IIC_SCL=0;
for(i=0;i<8;i++) //循环8次将一个字节传出,先传高再传低位
{
if((dat&0x80)>0)
IIC_SDA=1;
else
IIC_SDA=0;
dat<<=1;
delay_10us(1);
IIC_SCL=1;
delay_10us(1);
IIC_SCL=0;
delay_10us(1);
}
}
/*******************************************************************************
* 函 数 名 : iic_read_byte
* 函数功能 : IIC读一个字节
* 输 入 : ack=1时,发送ACK,ack=0,发送nACK
* 输 出 : 应答或非应答
*******************************************************************************/
u8 iic_read_byte(u8 ack)
{
u8 i=0,receive=0;
for(i=0;i<8;i++ ) //循环8次将一个字节读出,先读高再传低位
{
IIC_SCL=0;
delay_10us(1);
IIC_SCL=1;
receive<<=1;
if(IIC_SDA)receive++;
delay_10us(1);
}
if (!ack)
iic_nack();
else
iic_ack();
return receive;
}
PA4和PA2分别作为SCL和SDA。
void MyI2C_Init(void)
{
//开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PB10和PB11引脚初始化为开漏输出
/*设置默认电平*/
GPIO_SetBits(GPIOA, GPIO_Pin_4 | GPIO_Pin_2); //设置PB10和PB11引脚初始化后默
}
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); //根据BitValue,设置SCL引脚的电平
Delay_us(10); //延时10us,防止时序频率超过要求
}
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_2, (BitAction)BitValue);
Delay_us(10);
}
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2); //读取SDA电平
Delay_us(10); //延时10us,防止时序频率超过要求
return BitValue; //返回SDA电平
}
void MyI2C_Start(void)
{
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for(i=0;i<8;i++)
{
MyI2C_W_SDA(Byte&0x80>>i);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i,Byte=0x00;
MyI2C_W_SDA(1);
for(i=0;i<8;i++)
{
MyI2C_W_SCL(1);
if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}
MyI2C_W_SCL(0);
}
return Byte;
}
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
AckBit= MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit;
}
51单片机中没有硬件I2C的功能,只有STM32中有,这里只讲解STM32中的I2C功能。
通信引脚:
数据逻辑控制:
主接收器:
主接收器接收流程及事件说明如下:
I2C初始化结构体:
typedef struct
{
uint32_t I2C_ClockSpeed; /*!< 设置 SCL 时钟频率,此值要低于 400000*/
uint16_t I2C_Mode; /*!< 指定工作模式,可选 I2C 模式及 SMBUS 模式 */
uint16_t I2C_DutyCycle; /* 指定时钟占空比,可选 low/high = 2:1 及 16:9 模式*/
uint16_t I2C_OwnAddress1; /*!< 指定自身的 I2C 设备地址 */
uint16_t I2C_Ack; /*!< 使能或关闭响应 (一般都要使能) */
uint16_t I2C_AcknowledgedAddress; /*!< 指定地址的长度,可为 7 位及 10 位 */
} I2C_InitTypeDef;
配置完这些结构体成员值,调用库函数 I2C_Init 即可把结构体的配置写入到寄存器中。
说白了,硬件I2C就是帮我们完成了I2C协议的基本时序,但是如何实现通信,需要我们把他进行组合,不断检测标志位,判断通信到了哪一步,然后我们在调用硬件I2C的函数,一般是清楚标志位、发送开始时序,发送结束时序,发送非应答,接收数据(读取SR)、发送数据(写入SR)生成对应的时序,继续通信。
这里可以和DMA模块一起复用,通过DMA,可以不断的发送数据,这里可以粗略的讲解一下,我们知道,当I2C硬件完成发送一个数据之后,相应的标志位会被置位,我们在设置相应DMA触发事件,就可以不断的往SR中搬运数据,实现连续发送数据,读取也是一样。
1、前者时序的搭建,需要CPU的参与,不断的改变引脚电平,来满足时序的要求。
2、后者不需要CPU的参与,大大节省了CPU的资源。
3、他们的波形也有些许差别。
软件:
硬件:
仔细观察,可以发现,当SCL拉低的那一瞬间之后,软件的SDA没有立即改变,而硬件控制的SDA则迅速反应,这是因为软件完成时序是通过函数改变引脚电平,这段时间会有点延时。
在单片机使用的串口通讯中,一般只使用 RXD、TXD 以及 GND 三条信号线,直接传输数据信号。
这里需要用到CH340USB转TTL模块。
CH340是一种USB转串口芯片,常用于单片机与计算机之间的串口通信。它的主要作用是将计算机的USB接口转换为串行通信接口,从而实现计算机与单片机之间的数据传输。
在单片机开发中,通常需要通过串口与计算机进行通信,以实现数据的传输、调试和监控等功能。而现代计算机通常只提供USB接口而不再配备传统的串口接口,因此需要通过USB转串口芯片来连接单片机和计算机。
CH340芯片具有成本低廉、稳定可靠、兼容性好等优点,因此被广泛应用于单片机开发中。它提供了简单易用的串口通信功能,使得开发者可以轻松实现单片机与计算机之间的数据交换。
在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定为 5、6、7 或 8 位长。
串口通信初始化代码可以从STC-ISP中获取:
发送数据:
//串口发送一个字节数据
void UART_SendByte(unsigned char Byte){
SBUF=Byte;
//检测是否完成
while(TI==0);
TI=0;//TI复位
}
接收数据:
这里用的是中断4
void uart() interrupt 4 //串口通信中断函数
{
u8 rec_data;
RI = 0; //清除接收中断标志位
rec_data=SBUF; //存储接收到的数据
}
TX:发送数据输出引脚。
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 嵌套向量中断控制器组选择 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* 配置 USART 为中断源 */
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
/* 抢断优先级为 1 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 子优先级为 1 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中断 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
/* 初始化配置 NVIC */
NVIC_Init(&NVIC_InitStructure);
}
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 打开串口 GPIO 的时钟
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
// 打开串口外设的时钟
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
将 USART Tx 的 GPIO 配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
// 将 USART Rx 的 GPIO 配置为浮空输入模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
// 配置串口的工作参数
// 配置波特率
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
// 配置 针数据字长
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
// 配置停止位
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(DEBUG_USARTx, &USART_InitStructure);
// 串口中断优先级配置
NVIC_Configuration();
// 使能串口接收中断
USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
// 使能串口
USART_Cmd(DEBUG_USARTx, ENABLE);
}
/**
* 函 数:串口发送一个字节
* 参 数:Byte 要发送的一个字节
* 返 回 值:无
*/
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
/**
* 函 数:USART1中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
Serial_RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
Serial_RxFlag = 1; //置接收标志位变量为1
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除USART1的RXNE标志位
//读取数据寄存器会自动清除此标志位
//如果已经读取了数据寄存器,也可以不执行此代码
}
}
这里由于还是通过CPU直接控制GPIO引脚来模拟时序,对于51和32,思路都是一样,我这里就只列举STM32中软件模拟。
/**
* 函 数:SPI写SS引脚电平
* 参 数:BitValue 协议层传入的当前需要写入SS的电平,范围0~1
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平
*/
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); //根据BitValue,设置SS引脚的电平
}
/**
* 函 数:SPI写SCK引脚电平
* 参 数:BitValue 协议层传入的当前需要写入SCK的电平,范围0~1
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCK为低电平,当BitValue为1时,需要置SCK为高电平
*/
void MySPI_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue); //根据BitValue,设置SCK引脚的电平
}
/**
* 函 数:SPI写MOSI引脚电平
* 参 数:BitValue 协议层传入的当前需要写入MOSI的电平,范围0~0xFF
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置MOSI为低电平,当BitValue非0时,需要置MOSI为高电平
*/
void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue); //根据BitValue,设置MOSI引脚的电平,BitValue要实现非0即1的特性
}
/**
* 函 数:I2C读MISO引脚电平
* 参 数:无
* 返 回 值:协议层需要得到的当前MISO的电平,范围0~1
* 注意事项:此函数需要用户实现内容,当前MISO为低电平时,返回0,当前MISO为高电平时,返回1
*/
uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); //读取MISO电平并返回
}
/**
* 函 数:SPI初始化
* 参 数:无
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,实现SS、SCK、MOSI和MISO引脚的初始化
*/
void MySPI_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA4、PA5和PA7引脚初始化为推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6引脚初始化为上拉输入
/*设置默认电平*/
MySPI_W_SS(1); //SS默认高电平
MySPI_W_SCK(0); //SCK默认低电平
}
/*协议层*/
/**
* 函 数:SPI起始
* 参 数:无
* 返 回 值:无
*/
void MySPI_Start(void)
{
MySPI_W_SS(0); //拉低SS,开始时序
}
/**
* 函 数:SPI终止
* 参 数:无
* 返 回 值:无
*/
void MySPI_Stop(void)
{
MySPI_W_SS(1); //拉高SS,终止时序
}
/**
* 函 数:SPI交换传输一个字节,使用SPI模式0
* 参 数:ByteSend 要发送的一个字节
* 返 回 值:接收的一个字节
*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
for (i = 0; i < 8; i ++) //循环8次,依次交换每一位数据
{
MySPI_W_MOSI(ByteSend & (0x80 >> i)); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
MySPI_W_SCK(1); //拉高SCK,上升沿移出数据
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} //读取MISO数据,并存储到Byte变量
//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
MySPI_W_SCK(0); //拉低SCK,下降沿移入数据
}
return ByteReceive; //返回接收到的一个字节数据
}
数据控制逻辑
SPI初始化:
void MySPI_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //开启SPI1的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA4引脚初始化为推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA5和PA7引脚初始化为复用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6引脚初始化为上拉输入
/*SPI初始化*/
SPI_InitTypeDef SPI_InitStructure; //定义结构体变量
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //模式,选择为SPI主模式
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //方向,选择2线全双工
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //数据宽度,选择为8位
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //先行位,选择高位先行
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; //波特率分频,选择128分频
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //SPI极性,选择低极性
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS,选择由软件控制
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC多项式,暂时用不到,给默认值7
SPI_Init(SPI1, &SPI_InitStructure); //将结构体变量交给SPI_Init,配置SPI1
/*SPI使能*/
SPI_Cmd(SPI1, ENABLE); //使能SPI1,开始运行
/*设置默认电平*/
MySPI_W_SS(1); //SS默认高电平
}
SPI配置:
/**
* 函 数:SPI写SS引脚电平,SS仍由软件模拟
* 参 数:BitValue 协议层传入的当前需要写入SS的电平,范围0~1
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平
*/
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); //根据BitValue,设置SS引脚的电平
}
/**
* 函 数:SPI起始
* 参 数:无
* 返 回 值:无
*/
void MySPI_Start(void)
{
MySPI_W_SS(0); //拉低SS,开始时序
}
/**
* 函 数:SPI终止
* 参 数:无
* 返 回 值:无
*/
void MySPI_Stop(void)
{
MySPI_W_SS(1); //拉高SS,终止时序
}
/**
* 函 数:SPI交换传输一个字节,使用SPI模式0
* 参 数:ByteSend 要发送的一个字节
* 返 回 值:接收的一个字节
*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET); //等待发送数据寄存器空
SPI_I2S_SendData(SPI1, ByteSend); //写入数据到发送数据寄存器,开始产生时序
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET); //等待接收数据寄存器非空
return SPI_I2S_ReceiveData(SPI1); //读取接收到的数据并返回
}
总结:
硬件SPI看似很复杂,实际上,我们只需要通过配置好结构体,然后把结构体放入库封装好的初始化函数,之后我们调用它给我们写好的时序函数,在不断判断标志位,并调用对应的库函数,实现SPI外设与其他设备的通信。
文章浏览阅读83次。串行通讯协议有很多种,像RS232,RS485,RS422,甚至现今流行的USB等都是串行通讯协议。而串行通讯技术的应用无处不在。可能大家见的最多就是电脑的串口与Modem的通讯。记得在PC机刚开始在中国流行起来时(大约是在90年代前五年),那时甚至有人用一条串行线进行两台电脑之间的数据共享。除了这些,手机,PDA,USB鼠标、键盘等等都是以串行通讯的方式与电脑连接。而笔者工作性质的关系,..._peak 串口
文章浏览阅读1.2k次。MySql索引失效及解决方案_mysql or索引失效如何解决
文章浏览阅读1.1k次。Android安卓实战项目(12)---关于身体分析,BMI计算,喝水提醒,食物卡路里计算APP【支持中英文切换】生活助手类APP(源码在文末)
文章浏览阅读2.7k次。分享一下这几天对人工智能的想法人工智能,缩写AI。谈到人工智能,我们首先想到的,它是一门学科,要我们去学习,但人工智能的终极目标是对人的意识、思维过程的模拟,它能像人那样思考,甚至超过人的智能。人工智能是一个交叉学科,涉及多领域,多专业,所以其复杂度可想而知。虽然,现在的人工智能还处于初级阶段,但是,不缺我们对之想象探索。它将是现在以及未来社会建设和发展的主流之一,将会影响我们未来生活各个方面发生重大改变。当然,我们现在的生活中在很多运用人工智能,如很多网站的AI客服,网上购平台为了提前预见客户的需求_你对人工智能目的的理解
文章浏览阅读2.5k次。HBase实践1.下载HBase查看版本号对应的java,Hadoophttps://hbase.apache.org/book.html#configuration下载地址:https://archive.apache.org/dist/hbase/stable/我下载的版本是stable版本:2.HBase2.4.10安装1.1 解压安装包hbase-2.2.2-bin.tar.gz至路径 /usr/local,命令如下:cd ~/Downloads/解压到/usr/loca
文章浏览阅读1.5k次。安装Python 3.9有多种方法,包括在不同操作系统上使用不同的包管理器进行安装。下面是在几种常见操作系统上安装Python 3.9的方法。_python 3.9
文章浏览阅读814次。摘 要 信息科学技术的进步使得互联网技术行业快速发展起来。其中网页设计作为互联网技术行业中重要的一部分,应用先进技术提高其工作效率与工作质量具有重要意义。DIV+CSS技术在网页布局中的优势作用使得其在网页设计与开发中应用越来越广泛。基于此,本文首先对DIV+CSS技术进行概述,并对其在网页布局中的优势与应用原理进行分析,最后举出网页设计的实例对该项技术的具体应用进行分布阐述。【关键词】DIV C..._布局与样式分离
文章浏览阅读240次。Spring版本:5.1.14.RELEASEBean实例创建过程如下图,Bean的创建过程大部分是在docreateBean()里面完成的。_在spring中bean的创建过程
文章浏览阅读892次。1. RTT studio创建工程创建工程## 创建完成以后的目录结构2. 配置CubuMx双击cubumx的图标打开CubeMx配置时钟生成代码构建后的代码结构编译代码满屏错误:不要慌3. 新增脚本新建scons脚本文件 SConscript脚本内容如下import osfrom building import *cwd = GetCurrentDir()src = Glob('*.c')# add cubemx driverssrc = Split('''_rtt cube
文章浏览阅读3.2w次,点赞4次,收藏10次。展开全部i、replace方法该方法的作用是替换字符串中所有指定的字e69da5e6ba9062616964757a686964616f31333337616637符,然后生成一个新的字符串。经过该方法调用以后,原来的字符串不发生改变。例如:Strings=“abcat”;Strings1=s.replace(‘a’,‘1’);该代码的作用是将字符串s中所有的字符a替换成字符1,生成的..._string去掉指定字符
文章浏览阅读749次,点赞3次,收藏11次。面试官特别爱问SpringIOC底层实现,Spring源码晦涩难懂 怎么办呢? 跟着老师手动实现一个mini ioc容器吧,实现后再回头看Spring源码事半功倍哦~,就算直接和面试官讲也完全可以哦,类名完全按照源码设计,话不多说 开干~!手动实现IOC容器的设计需要实现的IOC功能:可以通过xml配置bean信息 可以通过容器getBean获取对象 能够根据Bean的依赖属性实现依赖注入 可以配置Bean的单例多例实现简易IOC设计的类类之间关系模型..._java手写ioc
文章浏览阅读558次。1.协议1.1 应用层自定制协议HTTP协议1.2 传输层UDP协议TCP协议1.3 自定制协议自定制协议是应用层协议,被程序员定义出来的协议(应用层对要传输的数据,进行数据格式的约定,消息发送方和接收方都必须遵守约定)TCP特性:面向字节流 2.TCP粘包问题 我们需要在应用层自定制协议,自定制协议增加报头和分隔符【定长报头(数据长度)】+数据【定长报头】+ 数据+分隔符【不定长报头】+数据+分隔符 对于定长报头,双方的收发都是遵守约定的 不定长的_应用层解析