20220402
Kshine
- 单片机中的各种外设功能,反应在引脚上,实际就是引脚的变化。
- 在使用单片机设计负责功能时,经常会遇到外设引脚对应不上,或者不够用的情况。
1. 模拟IIC
1.1 IIC简介
- I2C (inter-integrated circuit) bus 。IIC总线官网。
- 标准模式(Sm,最高 100 kHz)和 快速模式(Fm,最高 400 kHz)。
- 可用于多种用途,包括 CRC 生成和验证、SMBus(系统管理总线)和 PMBus(电源管理总线)。
1.1.1 通讯流
- Master 主模式。I2C 接口启动数据传输并生成时钟信号。串行数据传输总是以开始条件开始并以停止条件结束。启动和停止条件均由软件在主模式下生成。
- Slave 从模式。接口能够识别自己的地址(7 位或 10 位)和广播呼叫地址。通用呼叫地址检测可以通过软件启用或禁用。
- 数据和地址以 8 位字节传输,MSB 在前。
- 起始条件后的第一个字节包含地址(一个在 7 位模式下,两个在 10 位模式下)。地址总是在主模式下传输。
- 第 9 个时钟脉冲跟随字节传输的 8 个时钟周期,在此期间接收器必须向发送器发送一个确认位。
1.2 进行IO适配,模拟IIC
- 参考链接:https://zhuanlan.zhihu.com/p/161710767
- 参考链接:https://www.cnblogs.com/-wenli/p/10907967.html
- 参考链接:https://blog.csdn.net/Kshine2017/article/details/124683260?spm=1001.2014.3001.5501
- 在设计电路时,搞反了SCL和SDA,可以通过模拟IIC的方式,弥补这一问题。
- 下面为模拟IO的适配操作。
1.2.1 STM32
#define GPIO_PORT_I2C GPIOB
#define I2C_SCL_PIN GPIO_PIN_7
#define I2C_SDA_PIN GPIO_PIN_8
//---------------------------------------------------------------------------------------//
//输出控制
#define I2C_SCL_1() GPIO_PORT_I2C->BSRR = I2C_SCL_PIN // SCL = 1
#define I2C_SCL_0() GPIO_PORT_I2C->BSRR = (uint32_t)I2C_SCL_PIN << 16U // SCL = 0
#define I2C_SDA_1() GPIO_PORT_I2C->BSRR = I2C_SDA_PIN // SDA = 1
#define I2C_SDA_0() GPIO_PORT_I2C->BSRR = (uint32_t)I2C_SDA_PIN << 16U // SDA = 0
//输入检测
#define I2C_SDA_READ() (GPIO_PORT_I2C->IDR & I2C_SDA_PIN) // 读取SDA电平
#define I2C_SCL_READ() (GPIO_PORT_I2C->IDR & I2C_SCL_PIN) // 读取SCL电平 (一般用不到)
//切换SDA的输入输出方向
#define I2C_SDA_DIR_OUT GPIOB->CRH &= 0xFFFFFFF0;GPIOB->CRH|=3<<0; //输出
#define I2C_SDA_DIR_IN GPIOB->CRH &= 0xFFFFFFF0;GPIOB->CRH|=8<<0; //输入
1.2.2 HHD32
- 海威华芯。
#define GPIO_PORT_I2C GPIOB
#define I2C_SCL_PIN PIN7
#define I2C_SDA_PIN PIN8
//port->SET.all |= pin;
//port->CLR.all |= pin;
#define I2C_SCL_1() GPIO_PORT_I2C->SET.all |= I2C_SCL_PIN // SCL = 1
#define I2C_SCL_0() GPIO_PORT_I2C->CLR.all |= I2C_SCL_PIN // SCL = 0
#define I2C_SDA_1() GPIO_PORT_I2C->SET.all |= I2C_SDA_PIN // SDA = 1
#define I2C_SDA_0() GPIO_PORT_I2C->CLR.all |= I2C_SDA_PIN // SDA = 0
//port->PIN.all & pin
#define I2C_SDA_READ() (GPIO_PORT_I2C->PIN.all & I2C_SDA_PIN) // 读取SDA电平
#define I2C_SCL_READ() (GPIO_PORT_I2C->PIN.all & I2C_SCL_PIN) // 读取SCL电平
//port->DIR.all |= pins;
//port->DIR.all &= ~pins;
#define I2C_SDA_DIR_OUT GPIO_PORT_I2C->DIR.all |= I2C_SCL_PIN; //输出
#define I2C_SDA_DIR_IN GPIO_PORT_I2C->DIR.all &= ~I2C_SCL_PIN; //输入
1.3 其他准备工作
- 类型的定义,延时函数。
typedef int32_t s32;
typedef int16_t s16;
typedef int8_t s8;
typedef const int32_t sc32; /*!< Read Only */
typedef const int16_t sc16; /*!< Read Only */
typedef const int8_t sc8; /*!< Read Only */
typedef uint32_t u32;
typedef uint16_t u16;
typedef uint8_t u8;
typedef const uint32_t uc32; /*!< Read Only */
typedef const uint16_t uc16; /*!< Read Only */
typedef const uint8_t uc8; /*!< Read Only */
//短暂延时
static void delaysoft(int DelayData)
{
int i = 0;
while(i <= DelayData)
{
i++;
}
}
1.4 模拟IIC的实现
1.4.1 起始和结束条件
- SDA和SCL同时为高时,为IIC总线的空闲状态。
- 起始:时钟线SCL为高时,数据线SDA由高到低。
- 停止:时钟线SCL为高时,数据线SDA由低到高。
void IIC_Start()
{
//配置SDA引脚,为输出
//-----------------//
I2C_SDA_1(); //sda=1
I2C_SCL_1(); //scl=1
delaysoft(40);
I2C_SDA_0(); //SDA 由高变低 ,产生下降沿
delaysoft(40);
//--------------------------------------------------------//
I2C_SCL_0(); //时钟线拉低,钳住IIC总线,准备发送数据
}
void IIC_Stop()
{
I2C_SCL_0(); //确保时钟线为低时,数据线才能变化为0,否则这就可能成起始信号
//-----------------//
//配置SDA引脚,为输出
I2C_SDA_0(); //sda=0
I2C_SCL_1(); //scl=1
delaysoft(40);
I2C_SDA_1();//SDA 由低变成高,产生上升沿
delaysoft(40);
}
1.4.2 等待ACK
- 用于发送模式下,发送8位后,等待器件应答第9位。
- SDA引脚配置为输入,由从设备拉低,表示ACK。
uint8_t IIC_Wait_Ack()
{
u8 ucErrTime=0;
//配置SDA引脚,为输入
I2C_SDA_DIR_IN //
//-----------------//
I2C_SDA_1();//拉高,等待从机拉低。
//delaysoft(1);
I2C_SCL_1();//scl=1,产生脉冲,第9位
while(I2C_SDA_READ()) //等待SDA被拉低
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
I2C_SCL_0();//scl=0
//配置SDA引脚,为输出
I2C_SDA_DIR_OUT
return 0;
}
1.4.3 发出ACK
- 用于读取模式(SDA为in)读了8位器件数据后,在第9位给出一个应答,我还要继续读。
//产生ACK应答
void IIC_Ack()
{
I2C_SCL_0(); //确保时钟线为低时,数据线才能变化为0,否则这就可能成起始信号
//-----------------//
//配置SDA引脚,为输出
I2C_SDA_0();//sda=0
delaysoft(20);
I2C_SCL_1();//scl=1
delaysoft(20);
I2C_SCL_0();//scl=0
}
1.4.4 发送NACK
- 用于读取模式(SDA为in)读了8位器件数据后,在第9位给出一个应答,我不想读了。
- 拉高SDA,表示不应答NACK。
void IIC_NAck()
{
I2C_SCL_0(); //确保时钟线为低时,数据线才能变化为0,否则这就可能成起始信号
//-----------------//
//配置SDA引脚,为输出
I2C_SDA_1();//sda=1
delaysoft(20);
I2C_SCL_1();//scl=1
delaysoft(20);
I2C_SCL_0(); //scl=0
}
1.4.5 发送8位
void IIC_Send_Byte(uint8_t txd)
{
uint8_t i;
I2C_SCL_0();
for (i = 0; i < 8; i++)
{
if (txd & 0x80)
{
I2C_SDA_1();
}
else
{
I2C_SDA_0();
}
txd<<=1;
delaysoft(20);
I2C_SCL_1();//scl=1
delaysoft(20);
I2C_SCL_0();//scl=0
}
}
1.4.6 接收8位,并回复
- 读取一个字节时,SDA配置为输入;发出ACK或者NACK时,SDA配置为输出。
uint8_t IIC_Read_Byte(unsigned char ack)
{
uint8_t i;
uint8_t receive=0;
I2C_SDA_DIR_IN //配置SDA为输入模式
for (i = 0; i < 8; i++)
{
delaysoft(20);
I2C_SCL_0();
delaysoft(20);
I2C_SCL_1();
receive <<= 1;
if (I2C_SDA_READ())
{
receive++;
}
}
I2C_SDA_DIR_OUT
if (!ack)
IIC_NAck();
else
IIC_Ack();
return receive;
}
1.5 写入字节与读取字节
1.5.1 写入
- 需要查看从器件的IIC通信时序。严格按照数据手册组合时序。
- 向从设备的某一个寄存器,写入数值。
void IIC_WriteByteSoft(uint8_t DcvaddrWrite, uint8_t Regaddr, uint8_t data)
{
IIC_Start();
IIC_Send_Byte(DcvaddrWrite);
IIC_Wait_Ack();
IIC_Send_Byte(Regaddr);
IIC_Wait_Ack();
IIC_Send_Byte(data);
IIC_Wait_Ack();
IIC_Stop();
}
1.5.1 读取
- 需要查看从器件的IIC通信时序。严格按照数据手册组合时序。
u8 IIC_ReadByteSoft(uint8_t DcvaddrWrite, uint8_t Regaddr,uint8_t DcvaddrRead, unsigned char ack)
{
u8 temp;
IIC_Start();
IIC_Send_Byte(DcvaddrWrite);
IIC_Wait_Ack();
IIC_Send_Byte(Regaddr); //Read 命令
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(DcvaddrRead);
IIC_Wait_Ack();
temp=IIC_Read_Byte(ack);
IIC_Stop();
return temp;
}
2. 模拟SPI
- SPI外设引脚不够用的时候,或者引脚分配错误的时候,可以使用模拟SPI。
- 缺点是,占用CPU的处理时间。
- 参考链接:https://blog.csdn.net/zwj695535100/article/details/107303648/
- CPOL与CPHA的定义,有些芯片DATASHEET中描述与通用的规则是相反的,所以选型时候一定要以DATASHEET中的时序图为准。
- 以W25Q128JV为例。
2.1 引脚定义
- MISO,主设备输入,从设备输出。
- MOSI,主设备输出,从设备输入。
- SCLK,时钟信号(由主设备产生)。
- CS, 片选信息(由主设备控制)。
2.1.1 STM32
/**SPI GPIO Configuration
PC14 CS
PC15 CLK
PB15 DIN -- MOSI (5) MCU -> OUT
PB14 Dout -- MISO (2) MCU -> IN
*/
#define SPI_SCK_PIN MCU_CLK_Pin
#define SPI_SCK_GPIO_PORT MCU_CLK_GPIO_Port
#define SPI_MOSI_PIN MCU_DIN_Pin
#define SPI_MOSI_GPIO_PORT MCU_DIN_GPIO_Port
#define SPI_MISO_PIN MCU_DOUT_Pin
#define SPI_MISO_GPIO_PORT MCU_DOUT_GPIO_Port
#define SPI_NSS_PIN MCU_CS_Pin
#define SPI_NSS_GPIO_PORT MCU_CS_GPIO_Port
#define SPI_SCK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()
#define SPI_MISO_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPI_MOSI_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPI_NSS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()
#define MOSI_H HAL_GPIO_WritePin(SPI_MOSI_GPIO_PORT, SPI_MOSI_PIN, GPIO_PIN_SET)
#define MOSI_L HAL_GPIO_WritePin(SPI_MOSI_GPIO_PORT, SPI_MOSI_PIN, GPIO_PIN_RESET)
#define SCK_H HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_SET)
#define SCK_L HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_RESET)
#define MISO HAL_GPIO_ReadPin(SPI_MISO_GPIO_PORT, SPI_MISO_PIN)
#define NSS_H HAL_GPIO_WritePin(SPI_NSS_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET)
#define NSS_L HAL_GPIO_WritePin(SPI_NSS_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_RESET)
2.2 相关函数
2.2.1 引脚初始化
- 必须对引脚进行初始化,且使得输出引脚处于某种电平状态。
- 输出引脚 :SCLK = 0;MOSI = 0;CS = 1;
void SPI_Init(void)
{
/*##-1- Enable peripherals and GPIO Clocks #########################*/
/* Enable GPIO TX/RX clock */
SPI_SCK_GPIO_CLK_ENABLE();
SPI_MISO_GPIO_CLK_ENABLE();
SPI_MOSI_GPIO_CLK_ENABLE();
SPI_NSS_GPIO_CLK_ENABLE();
/*##-2- Configure peripheral GPIO #######################*/
/* SPI SCK GPIO pin configuration */
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = SPI_SCK_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
//GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_RESET);
/* SPI MISO GPIO pin configuration */
GPIO_InitStruct.Pin = SPI_MISO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
HAL_GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);
/* SPI MOSI GPIO pin configuration */
GPIO_InitStruct.Pin = SPI_MOSI_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(SPI_MOSI_GPIO_PORT, SPI_MOSI_PIN, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = SPI_NSS_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(SPI_NSS_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET);
}
2.2.2 延时函数
void delay_us(uint32_t nus)
{
uint16_t i;
for(i=0;i<nus;i++)
{
}
}
2.2.3 不同模式的时序函数
2.2.3.1 mode0
(1) 下降沿之后(低电平),MOSI输出。
(2) 上升沿之后(高电平),读取MISO输入。
/* CPOL = 0, CPHA = 0, MSB first */
uint8_t SOFT_SPI_RW_MODE0( uint8_t write_dat )
{
uint8_t i, read_dat;
for( i = 0; i < 8; i++ )
{
if( write_dat & 0x80 )
MOSI_H;
else
MOSI_L;
write_dat <<= 1;
delay_us(1);
SCK_H;
read_dat <<= 1;
if( MISO )
read_dat++;
delay_us(1);
SCK_L;
__nop();
}
return read_dat;
}
2.2.3.2 mode1
(1) 上升沿之后(高电平),MOSI输出。
(2) 下降沿之后(低电平),读取MISO输入。
/* CPOL=0,CPHA=1, MSB first */
uint8_t SOFT_SPI_RW_MODE1(uint8_t byte)
{
uint8_t i,Temp=0;
for(i=0;i<8;i++)
{
SCK_H;
if(byte&0x80)
{
MOSI_H;
}
else
{
MOSI_L;
}
byte <<= 1;
delay_us(1);
SCK_L;
Temp <<= 1;
if(MISO)
Temp++;
delay_us(1);
}
return (Temp);
}
2.2.3.3 mode2
(1) 上升沿之后(高电平),MOSI输出。
(2) 下降沿之后(低电平),读取MISO输入。
/* CPOL=1,CPHA=0, MSB first */
uint8_t SOFT_SPI_RW_MODE2(uint8_t byte)
{
uint8_t i,Temp=0;
for(i=0;i<8;i++)
{
if(byte&0x80)
{
MOSI_H;
}
else
{
MOSI_L;
}
byte <<= 1;
delay_us(1);
SCK_L;
Temp <<= 1;
if(MISO)
Temp++;
delay_us(1);
SCK_H;
}
return (Temp);
}
2.2.3.4 mode3
(1) 下降沿之后(低电平),MOSI输出。
(2) 上升沿之后(高电平),读取MISO输入。
/* CPOL = 1, CPHA = 1, MSB first */
uint8_t SOFT_SPI_RW_MODE3( uint8_t write_dat )
{
uint8_t i, read_dat;
for( i = 0; i < 8; i++ )
{
SCK_L;
if( write_dat & 0x80 )
MOSI_H;
else
MOSI_L;
write_dat <<= 1;
delay_us(1);
SCK_H;
read_dat <<= 1;
if( MISO )
read_dat++;
delay_us(1);
__nop();
}
return read_dat;
}
2.2.4 用户封装使用SPI
//HAL_SPI_Receive(&_W25QXX_SPI, pBuffer, sizeof(pBuffer), 100);
void Kshine_SPI_Receive(uint8_t* buff,uint16_t len)
{
uint16_t i = 0;
for(i=0;i<len;i++)
{
buff[i] = SOFT_SPI_RW_MODE0(0xA5);
}
}
//HAL_SPI_Transmit(&_W25QXX_SPI, pBuffer, NumByteToWrite_up_to_PageSize, 100);
void Kshine_SPI_Transmit(uint8_t* buff,uint16_t len)
{
uint16_t i = 0;
for(i=0;i<len;i++)
{
SOFT_SPI_RW_MODE0(buff[i]);
}
}
3. 模拟PWM
未完待续。。。