一个串口只能连接一个串口设备,,,不能连接多个设备,,,
I2C : 一个接口可以连接多个设备,,,


不连接默认就是高电平




占空比: 在一个周期里面,,高电压占整个周期的比例
duty cycle


stm32内部的每个外设,,,USART还是I2C 本质都是一堆寄存器+状态机
外设复位就是: 把这些寄存器恢复默认值
c
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
// 开启 复位信号
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);
// 关闭复位信号 ,,, 硬件的复位,,本质是一个脉冲,,,是一个短暂的高电平
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);

SR: status register : 状态寄存器

SR : status register : 状态寄存器
busy : 总线忙标志位 1:总线忙 0:总线空闲
- SB : start bit : 起始位标志,, 当成功发送出起始条件 start condition 后,,硬件会将此置为1
- BUSY : bus busy ,, 总线忙碌标志,,当I2C外设正在进行数据传输时(总线上有通信活动时),,会置为1,,用于判断总线是否被其他设备占用
- AF : acknowledge failure : 应答失败标志,,, 本地发送完一个字节后,没有收到从机返回的ack,,置为1
- ADDR :address sent / matched : 地址标志,,,1:寻址成功 ,,,

- TxE : transmit data register empty : 发送数据寄存器空标志
- BTF: byte transfer finished : 字节传输完成标志

0x00
电荷泵 : 小型升压器 ,,将3.3V 变成7v,8v,10v
oled像素发光时,,需要比较高的驱动电压,,但是stm32只给3.3不够,必须自己升压
电荷泵 : 一点一点把电荷 搬高,,所以叫 电荷泵,, charge pump
oled里面空间很小,,放不下变压器,,所以他用的是 电容+开关 来升压
SSD1306 规定:
- 0x00 : 后面是命令
- 0x40 : 后面是显示数据
- 0x8D : 要干什么
- 0x14 : 开启电荷泵
- 0x10 : 关闭电荷泵
- 0xAF : 打开显示器
- 0xAE : 黑屏
- 0xA5 : 全屏点亮 : 无视显存,,所有像素全亮
点亮屏幕:
c
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "buzzer.h"
#include "lightSensor.h"
#include "stdio.h"
int my_i2c_sendBytes(I2C_TypeDef* i2cx,uint8_t addr,uint8_t *pData,uint16_t size){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
// 等待总线空闲
while(I2C_GetFlagStatus(i2cx,I2C_FLAG_BUSY) == SET);
// 发送起始位
I2C_GenerateSTART(i2cx,ENABLE);
// start bit 等待起始标志位发出去
while(I2C_GetFlagStatus(i2cx,I2C_FLAG_SB) == RESET);
// AF是错误标志,必须软件手动清零
// 清除 acknowledge failure
I2C_ClearFlag(i2cx,I2C_FLAG_AF);
// 发送地址 ,, 向从机写数据
I2C_SendData(i2cx,addr & 0xfe);
// 等待从机ack
while(1){
if(I2C_GetFlagStatus(i2cx,I2C_FLAG_AF) == SET){
// ack失败
I2C_GenerateSTOP(i2cx,ENABLE);
return -1; // 寻址失败
}
// 寻址成功,,跳到下面发送数据
if(I2C_GetFlagStatus(i2cx,I2C_FLAG_ADDR) == SET){
break;
}
}
// 清除ADDR标志位,,,把 status register 读一遍,,就自动清零了ADDR
I2C_ReadRegister(i2cx,I2C_Register_SR1);
I2C_ReadRegister(i2cx,I2C_Register_SR2);
// 发送数据
for(uint16_t i=0;i<size;i++){
// 查看上一个数据是否被拒收 AF : acknowledge failure
// 查看上一个数据是否发送完了 txe : transmit data register empty
while(1){
if(I2C_GetFlagStatus(i2cx,I2C_FLAG_AF) == SET){
// ack失败
I2C_GenerateSTOP(I2C1,ENABLE);
return -2; // 数据被拒收
}
if(I2C_GetFlagStatus(i2cx,I2C_FLAG_TXE) == SET){
break;
}
}
// 写数据
I2C_SendData(i2cx,pData[i]);
}
// 发完之后判断数据是否发送完成
while(1){
if(I2C_GetFlagStatus(i2cx,I2C_FLAG_AF) == SET){
// ack失败
I2C_GenerateSTOP(i2cx,ENABLE);
return -2; // 数据被拒收
}
if(I2C_GetFlagStatus(i2cx,I2C_FLAG_BTF) == SET){
break;
}
}
// 发送停止位
I2C_GenerateSTOP(i2cx,ENABLE);
return 0; // 成功
}
/**
板载led初始化
*/
void my_onBoardLED_init(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitTypeDef GPIO_initStruct = {0};
// 操控引脚
GPIO_initStruct.GPIO_Pin = GPIO_Pin_13;
// 板载led ,,是开漏输出
GPIO_initStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_initStruct.GPIO_Speed = GPIO_Speed_50MHz;
// 初始化IO引脚,,
GPIO_Init(GPIOC,&GPIO_initStruct);
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
}
void my_oled_init(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_initStruct = {0};
// 操控引脚
GPIO_initStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
// 必须是开漏,,多设备共用一根线,,如果多个人输出高,,就短路了,,I2C规定只能拉低
GPIO_initStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_initStruct.GPIO_Speed = GPIO_Speed_50MHz;
// 初始化IO引脚,,
GPIO_Init(GPIOB,&GPIO_initStruct);
}
uint8_t keyNum;
uint8_t lightSensorFlag;
int main(void)
{
my_oled_init();
//
// 开时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
// 开启复位信号
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);
// 关闭复位信号 ,,, 硬件的复位本质上是一个脉冲
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);
I2C_InitTypeDef i2c_initStruct;
i2c_initStruct.I2C_ClockSpeed = 400000;
i2c_initStruct.I2C_Mode = I2C_Mode_I2C;
i2c_initStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_Init(I2C1,&i2c_initStruct);
// 闭合I2C1的总开关
I2C_Cmd(I2C1,ENABLE);
uint8_t commands[] = {0x00,0x8d,0x14,0xaf,0xa5};
my_i2c_sendBytes(I2C1,0x78,commands,5);
while(1)
{
}
}
stm32的 i2c 外设被 卡在了地址阶段,,只有清除了 ADDR (address sent and acknowledged),,,不清除不允许进入发送数据阶段,,I2C硬件会认为,,软件还没处理地址阶段
TXE : transmit data register empty

stm32的I2C外设,,, 内部会自动ack,,默认情况下,,每收到一个字节,,硬件都会自动ack,,
将ack禁用之后,默认是开漏输出,,高阻态,,不回复ack,就相当于NACK
stop之后不是立即生效的,,所以也需要设置nack
RXNE : receive data register not empty
i2c读取数据:
c
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "buzzer.h"
#include "lightSensor.h"
#include "stdio.h"
int my_i2c_sendBytes(I2C_TypeDef* i2cx,uint8_t addr,uint8_t *pData,uint16_t size){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
// 等待总线空闲
while(I2C_GetFlagStatus(i2cx,I2C_FLAG_BUSY) == SET);
// 发送起始位
I2C_GenerateSTART(i2cx,ENABLE);
// start bit 等待起始标志位发出去
while(I2C_GetFlagStatus(i2cx,I2C_FLAG_SB) == RESET);
// AF是错误标志,必须软件手动清零
// 清除 acknowledge failure
I2C_ClearFlag(i2cx,I2C_FLAG_AF);
// 发送地址 ,, 向从机写数据
I2C_SendData(i2cx,addr & 0xfe);
// 等待从机ack
while(1){
if(I2C_GetFlagStatus(i2cx,I2C_FLAG_AF) == SET){
// ack失败
I2C_GenerateSTOP(i2cx,ENABLE);
return -1; // 寻址失败
}
// 寻址成功,,跳到下面发送数据
if(I2C_GetFlagStatus(i2cx,I2C_FLAG_ADDR) == SET){
break;
}
}
// 清除ADDR标志位,,,把 status register 读一遍,,就自动清零了ADDR
I2C_ReadRegister(i2cx,I2C_Register_SR1);
I2C_ReadRegister(i2cx,I2C_Register_SR2);
// 发送数据
for(uint16_t i=0;i<size;i++){
// 查看上一个数据是否被拒收 AF : acknowledge failure
// 查看上一个数据是否发送完了 txe : transmit data register empty
while(1){
if(I2C_GetFlagStatus(i2cx,I2C_FLAG_AF) == SET){
// ack失败
I2C_GenerateSTOP(I2C1,ENABLE);
return -2; // 数据被拒收
}
if(I2C_GetFlagStatus(i2cx,I2C_FLAG_TXE) == SET){
break;
}
}
// 写数据
I2C_SendData(i2cx,pData[i]);
}
// 发完之后判断数据是否发送完成
while(1){
if(I2C_GetFlagStatus(i2cx,I2C_FLAG_AF) == SET){
// ack失败
I2C_GenerateSTOP(i2cx,ENABLE);
return -2; // 数据被拒收
}
if(I2C_GetFlagStatus(i2cx,I2C_FLAG_BTF) == SET){
break;
}
}
// 发送停止位
I2C_GenerateSTOP(i2cx,ENABLE);
return 0; // 成功
}
/**
板载led初始化
*/
void my_onBoardLED_init(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitTypeDef GPIO_initStruct = {0};
// 操控引脚
GPIO_initStruct.GPIO_Pin = GPIO_Pin_13;
// 板载led ,,是开漏输出
GPIO_initStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_initStruct.GPIO_Speed = GPIO_Speed_50MHz;
// 初始化IO引脚,,
GPIO_Init(GPIOC,&GPIO_initStruct);
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
}
void my_oled_init(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_initStruct = {0};
// 操控引脚
GPIO_initStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
// 必须是开漏,,多设备共用一根线,,如果多个人输出高,,就短路了,,I2C规定只能拉低
GPIO_initStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_initStruct.GPIO_Speed = GPIO_Speed_50MHz;
// 初始化IO引脚,,
GPIO_Init(GPIOB,&GPIO_initStruct);
// 开时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
// 开启复位信号
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);
// 关闭复位信号 ,,, 硬件的复位本质上是一个脉冲
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);
I2C_InitTypeDef i2c_initStruct;
i2c_initStruct.I2C_ClockSpeed = 400000;
i2c_initStruct.I2C_Mode = I2C_Mode_I2C;
i2c_initStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_Init(I2C1,&i2c_initStruct);
// 闭合I2C1的总开关
I2C_Cmd(I2C1,ENABLE);
}
/**
接收从机数据
*/
int my_i2c_receiveBytes(I2C_TypeDef* i2cx,uint8_t addr,uint8_t *pBuffer,uint16_t size){
// 从机发送,, 主机接收
// 发送起始位
I2C_GenerateSTART(i2cx,ENABLE);
// start bit 等待起始标志位发出去
while(I2C_GetFlagStatus(i2cx,I2C_FLAG_SB) == RESET);
// 清除 acknowledge failure
I2C_ClearFlag(i2cx,I2C_FLAG_AF);
// 发送地址 ,, 向从机读数据
I2C_SendData(i2cx,addr | 0x01);
while(1){
if(I2C_GetFlagStatus(i2cx,I2C_FLAG_AF) == SET){
// ack失败
I2C_GenerateSTOP(i2cx,ENABLE);
return -1; // 寻址失败
}
// 寻址成功,,跳到下面发送数据
if(I2C_GetFlagStatus(i2cx,I2C_FLAG_ADDR) == SET){
break;
}
}
// 从从机读数据
if(size ==1){
// 只接收1个字节
// 清除ADDR
I2C_ReadRegister(i2cx,I2C_Register_SR1);
I2C_ReadRegister(i2cx,I2C_Register_SR2);
// 最后一个字节必须设置 NACK 和 stop
I2C_AcknowledgeConfig(i2cx,DISABLE);
I2C_GenerateSTOP(i2cx,ENABLE);
while(I2C_GetFlagStatus(i2cx,I2C_FLAG_RXNE) == RESET);
// 接收到数据
pBuffer[0] = I2C_ReceiveData(i2cx);
}
// 接收两个字节
if(size == 2){
// 清除ADDR
I2C_ReadRegister(i2cx,I2C_Register_SR1);
I2C_ReadRegister(i2cx,I2C_Register_SR2);
// 第一个字节 ack为1
I2C_AcknowledgeConfig(i2cx,ENABLE);
// 等待第一个字节接收完成
while(I2C_GetFlagStatus(i2cx,I2C_FLAG_RXNE) == RESET);
pBuffer[0] = I2C_ReceiveData(i2cx);
// 设置ack为0,和stop
// 最后一个字节必须设置 NACK 和 stop
I2C_AcknowledgeConfig(i2cx,DISABLE);
I2C_GenerateSTOP(i2cx,ENABLE);
while(I2C_GetFlagStatus(i2cx,I2C_FLAG_RXNE) == RESET);
pBuffer[1] = I2C_ReceiveData(i2cx);
}
if(size > 2){
// 清除ADDR
I2C_ReadRegister(i2cx,I2C_Register_SR1);
I2C_ReadRegister(i2cx,I2C_Register_SR2);
I2C_AcknowledgeConfig(i2cx,ENABLE);
for(uint8_t i=0;i<size;i++){
if(i==size-1){
// 最后一个字节必须设置 NACK 和 stop
I2C_AcknowledgeConfig(i2cx,DISABLE);
I2C_GenerateSTOP(i2cx,ENABLE);
}
while(I2C_GetFlagStatus(i2cx,I2C_FLAG_RXNE) == RESET);
pBuffer[i] = I2C_ReceiveData(i2cx);
}
}
return 0;
}
uint8_t keyNum;
uint8_t lightSensorFlag;
int main(void)
{
my_onBoardLED_init();
my_oled_init();
//
//
uint8_t commands[] = {0x00,0x8d,0x14,0xaf,0xa5};
my_i2c_sendBytes(I2C1,0x78,commands,5);
//
uint8_t rcvd;
my_i2c_receiveBytes(I2C1,0x78,&rcvd,1);
if((rcvd & (0x01 << 6)) == 0){
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
}else{
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
}
while(1)
{
}
}