STM32F10X笔记

笔记采子正点原子STM32F1开发指南

系统框架:



时钟树:



一般时钟初始化:如103对应着上面时钟树看

/* RCC system reset(for debug purpose) */
RCC_DeInit();


/* Enable HSE */
RCC_HSEConfig(RCC_HSE_ON);//设置为外部高速时钟源


/* Wait till HSE is ready */
HSEStartUpStatus = RCC_WaitForHSEStartUp();


if(HSEStartUpStatus == SUCCESS)
{
/* HCLK = SYSCLK */
RCC_HCLKConfig(RCC_SYSCLK_Div1); 
  
/* PCLK2 = HCLK */
RCC_PCLK2Config(RCC_HCLK_Div1); //设置PCLK2的时钟跟HCLK一样,不分频HCLK即APB2


/* PCLK1 = HCLK/2 */  APB1
RCC_PCLK1Config(RCC_HCLK_Div2);//设置PCLK1的时钟为HCLK二分频,因为最大为HCLK二分一


/* Flash 2 wait state */
FLASH_SetLatency(FLASH_Latency_2);//设置FLASH的等待时间

STM32的CPU的最大速率已知为72MHz,但FLASH无法达到这么高的速度,因此要在CPU存取FLASH的过程中插入所谓的“等待周期”,显然CPU速度越快,所要插入的等待周期个数越多,原则是:

  • 当CPU速率为0 ~ 24MHz时,不需要插入等待周期,即等到周期个数为0;
  • 当CPU速率为24 ~ 48MHz时,插入1个等待周期; 
  • 当CPU速率为48MHz ~ 72MHz时,插入2个等待周期;

/* Enable Prefetch Buffer */使能预取内存
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);

    #ifdefine  STM32F10X_CL

RCC_PREDIV2Config(RCC_PREDIV2_Div5);//25/5=5M

RCC_PLL2Config(RCC_PLL2Mul_8);//5X8=40M

RCC_PLL2Cmd(ENABLE); 
while(RCC_GetFlagStatus(RCC_FLAG_PLL2RDY) == RESET);

RCC_PREDIV1Config(RCC_PREDIV1_Source_PLL2, RCC_PREDIV1_Div5);40/5=8M
#endif

/* PLLCLK = 8MHz * 9 = 72 MHz */倍频外部时钟信号源得PLLCLK
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);


/* Enable PLL */ 
RCC_PLLCmd(ENABLE);


/* Wait till PLL is ready */
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}


/* Select PLL as system clock source */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);//设置系统时钟为倍频后的时钟(看上图系统时钟有多种选择)


/* Wait till PLL is used as system clock source */
while(RCC_GetSYSCLKSource() != 0x08)
{
}


}

F107互联型 MCU区别与F103,因为他们具有USB OTG功能,因此需要特别的时钟


所以使用外部25M的晶震,分频5得8通过PLL2 X5得40,再分频5得8M;


端口复用和重映射:

为了使不同器件封装的外设 IO 功能数量达到最优,可以把一 些复用重新映射其他功能数量达到最优,可以把一 些复用重新映射其他功能数量达到最优,可以把一 些复用重新映射其他些引脚上。 STM32 中有很多内置外设的输入出引脚都具重映射 中有很多内置外设的输入出引脚都具重映射 (remap) 的功能 。

详细步骤为:

开GPIO时钟;使能对应复用功能时钟;使能复用时钟AFIO;开启重映射。

而部分重映射有自己对应的设置;

中断优先级:1.如果两个中断的抢占优先级和响应都一样,则看哪个中断更早先执行;2.高优先级的抢占优先级可以打断低的抢占优先级,而抢占优先级相同,高优先级的响应优先级不可以打断低响应优先级的中断。

结合实例说明一下:假定设置中断优先级组为 2,然后设置中断 ,然后设置中断 3(RTC 3(RTC 中断 )的抢占优先级 为 2,响应优先级为1中断 。中断 6(外部中断 (外部中断 0)的抢占优先级为 3,,响应优先级为 0。中断 。中断 7(外 部中断 1)的抢占优先级为 的抢占优先级为 2,响应优先级为 0。那么这 。那么这 3个中断 的优先级顺序为:个中断 的优先级顺序为:7> 中 断 3> 中断 6。

上面例子中的断 3和中断 和中断 7都可以打断 中断6的中断 。而7和中断 和中断 3却不可以相互打断。

中断分组表:

中断优先级分组函数:NVIC_PriorityGroupConfig其实就是对寄存器SCB->SIRCR设置,

NVIC_IRQChannelPreemptionPriority :定义该中断的抢占优先级;

NVIC_IRQChannelChnnel:定义是什么中断;

NVIC_IRQChannelSubPriority:定义中断子优先级表(中断优先级);

NVIC_IRQChannelCmd:使能中断。


当中得delay.h文件

CM3内核处理器有一个SYSTICK时钟

sys.h文件宏定义了IO口的操作,包括读入和输出。

usart文件对主要写串口得操作

printf函数得支持,


STM32IO口:一共又8种模式

输出:1.推挽输出:可以输出高、低电平,由IC决定 推挽电路是两个参数相同的三极管或MOSFET,以推挽方式存在于电路中,各负责正负半周的波形放大任务,电路工作时,两只对称的功率开关管每次只有一个导通,所以导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载抽取电流。推拉式输出级既提高电路的负载能力,又提高开关速度。

2.开漏输出:输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行。适合于做电流型的驱动,其吸收电流的能力相对强(一般20mA以内)。

3浮空输入:空输入状态下,IO的电平状态是不确定的,完全由外部输入决定,如果在该引脚悬空的情况下,读取该端口的电平是不确定的。有可能被干扰导致输入不准确。

4上拉输入/下拉输入/模拟输入

5:复用开漏输出、复用推挽输出:GPIO口被用作第二功能时的配置情况所需要设置。

具体软件配置:

通用IO端口(GPIO)初始化:
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | B | C, ENABLE):使能APB2总线外设时钟
 RCC_ APB2PeriphResetCmd (RCC_APB2Periph_GPIOA | B | C, DISABLE):释放GPIO复位
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;//设置端口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//输入输出类型
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//响应速度如翻转(只有在输出有用)有5,10,50
GPIO_Init(GPIOA, &GPIO_InitStructure); 

STM32串口:
以串口1为例子:
软件配置
//TX端
 GPIO_StructInit(&GPIO_InitStructure);
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//根据表设置为复用推挽
 GPIO_Init(GPIOA , &GPIO_InitStructure);
 
 //PA10作为US1的RX端,负责接收数据
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//可以为浮空或上拉
 GPIO_Init(GPIOA, &GPIO_InitStructure);
 //串口初始化

       USART_InitTypeDef  USART_InitStructure;
         //将结构体设置为缺省状态
       USART_StructInit(&USART_InitStructure);
       //波特率设置为115200
       USART_InitStructure.USART_BaudRate = 115200;
       //一帧数据的宽度设置为8bits
       USART_InitStructure.USART_WordLength = USART_WordLength_8b;
       //在帧结尾传输1个停止位
       USART_InitStructure.USART_StopBits = USART_StopBits_1;
       //奇偶失能模式,无奇偶校验
       USART_InitStructure.USART_Parity = USART_Parity_No;
       //发送/接收使能
       USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
       //硬件流控制失能
       USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
       //设置串口1
       USART_Init(USART1, &USART_InitStructure);
     
       //打开串口1的中断响应函数
       USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
 
       //打开串口1
       USART_Cmd(USART1, ENABLE);
既然设置了中断响应函数,那么就要有中断设置:
NVIC_InitTypeDef NVIC_InitStructure;
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                           //选择中断分组2
        NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQChannel;                     //选择串口1中断
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;                   //抢占式中断优先级设置
        NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;                          //响应式中断优先级设置为0
        NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;                             //使能中断
        NVIC_Init(&NVIC_InitStructure);
然后是中断响应函数:

void USART1_IRQHandler(void)                             //串口1中断

{

char RX_dat;                                                        //定义字符变量

    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)    //判断发生接收中断

    {USART_ClearITPendingBit(USART1,    USART_IT_RXNE);        //清除中断标志

     GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)0x01);           //开始传输

RX_dat=USART_ReceiveData(USART1) & 0x7F;                        //接收数据,整理除去前两位

USART_SendData(USART1, RX_dat);                                      //发送数据

while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){}//等待发送结束

    }

}

配置

EXTI线,使中断线和

IO

针脚线连接上

外部中断:

STM32的每一个IO口都可以作为外部中盾的中断输入口,




配置EXTI线,使中断线和IO针脚线连接上

1、 将EXTI线连接到IO端口上 
将EXTI线4连接到端口GPIOD的第4个针脚上 
      GPIO_EXTILineConfig(GPIO_PortSourceGPIOD,GPIO_PinSource4);

2、配置中断边沿 
       /*配置EXTI线4上出现下降沿,则产生中断*/      

       EXTI_InitStructure.EXTI_Line = EXTI_Line2; 配置的4号针脚,
       EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式分为中断和事件
       EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发   (可以是上,下,上下)    

       EXTI_InitStructure.EXTI_LineCmd = ENABLE;     //中断线使能       

       EXTI_Init(&EXTI_InitStructure);                 //初始化中断 
       EXTI_GenerateSWInterrupt(EXTI_Line4);         //EXTI_Line4中断使能

中断函数:

void EXTI2_RQHandler(void)

 { 
     if(EXTI_GetITStatus(EXTI_Line2)!= RESET)    

  {  中断实现逻辑
          EXTI_ClearITPendingBit(EXTI_Line2);   //清除标志

   } 

}

同一条中断线只能控制一个IO

定时器中断实验:

定时器分通用定时器(2,3,4,5)TIMx_CNT计数,TIMx_PSC预分频(1-65535),每个定时器有4个独立通道可以用来1.输入捕获,2.输出比较,3.PWM生成,4.单脉冲模式输出

可设置多种中断模式:1.更行:计数器的上或者下一处,计数器初始化(通过软件或内/外触发)2.触发时间(计数器启动,停止,初始化或者由内,外触发计数)3.输入捕获4.输出比较。5支持针对定位的增量 (正交 )编码器和霍尔传感电路

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQChannel;    

TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = arr;  //设置自动重装的值也就是计数的值         
TIM_TimeBaseStructure.TIM_Prescaler = psc;//时钟频率除数的预分频值          
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ;//时钟线的分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//模式为向上计数
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;  //溢出中断计算就是溢出多少次才给你一个溢出中断
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //初始化TM2
  
TIM_ARRPreloadConfig(TIM2, ENABLE); //使能TM2 
TIM_Cmd(TIM2, ENABLE);  


TIM_TimeBaseStructure.TIM_Prescaler = 2;//分频2      72M/(2+1)/2=24MHz
TIM_TimeBaseStructure.TIM_Period = 65535; //计数值65535 
((1+TIM_Prescaler )/72M)*(1+TIM_Period )=((1+2)/72M)*(1+65535)=0.00273秒=366.2Hz */


void TIM2_IRQHandler(void)
{
       u8 ReadValue;
       //检测是否发生溢出更新事件
       if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
       {
              //清除TIM2的中断待处理位

     实现逻辑
              TIM_ClearITPendingBit(TIM2 , TIM_FLAG_Update);
       }

}


PWM输出:除了6,7定时器都可以用来输出PWM,高级定时器1,8更可以同时产生7路PWM输出。

定时器的相关设置功能一样

PWM的设置


TIM_OCInitTypeDef  TIM_OCInitStructure;

TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;                           //配置为PWM模式1  
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;                //比较输出使能
TIM_OCInitStructure.TIM_Pulse = CCR1;  

TIM_OCInitStructure.TIM_OCPolarity =TIM_OCPolarity_High; //输出极性为高

TIM_OC1Init(TIM3, &TIM_OCInitStructure);//初始化

 TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);  

TIM_Cmd(TIM3,ENABLE);//使能定时器

PWM模式1- 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为
无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否
则为有效电平(OC1REF=1)。

PWM模式2- 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为
有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电
平。



SPI:串行外围设备接口

一共4条通讯线MISO,MOSI,SCLK,CS。SPI的特点是能同时收发串行数据提供频率可编程时钟,发送结束中断标志,写保护等



【 CPHA相位】
首先说明一点,capture strobe = latch = read = sample,都是表示数据采样,数据有效的时刻。
相位,对应着数据采样是在第几个边沿(edge),是第一个边沿还是第二个边沿,0对应着第一个边沿,1对应着第二个边沿。
对于:
CPHA=0,表示第一个边沿:
对于CPOL=0,idle时候的是低电平,第一个边沿就是从低变到高,所以是上升沿;
对于CPOL=1,idle时候的是高电平,第一个边沿就是从高变到低,所以是下降沿;
CPHA=1,表示第二个边沿:
对于CPOL=0,idle时候的是低电平,第二个边沿就是从高变到低,所以是下降沿;
对于CPOL=1,idle时候的是高电平,第一个边沿就是从低变到高,所以是上升沿;

【 CPOL极性】
先说什么是SCLK时钟的空闲时刻,其就是当SCLK在数发送8个bit比特数据之前和之后的状态,于此对应的,SCLK在发送数据的时候,就是正常的工作的时候,有效active的时刻了。


先说英文,其精简解释为:Clock Polarity = IDLE state of SCK。
再用中文详解:
SPI的CPOL,表示当SCLK空闲idle的时候,其电平的值是低电平0还是高电平1:
CPOL=0,时钟空闲idle时候的电平是低电平,所以当SCLK有效的时候,就是高电平,就是所谓的active-high;
CPOL=1,时钟空闲idle时候的电平是高电平,所以当SCLK有效的时候,就是低电平,就是所谓的active-l



void SPI1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
  
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_SPI1, ENABLE );
 
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);


  GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);


SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //通讯模式,半双工,全双工,串行发和收这里是全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//设置STM32为主机
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//数据帧格式为8位
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//时钟极性空闲为高
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//相位在第二个边沿被采集
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//设置NSS管脚由硬件还是软件控制(这里是软件)
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//SPI的波特率 频率=时钟/分频
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//数据传输高位在前
SPI_InitStructure.SPI_CRCPolynomial = 7;//设置CRC教研多项式

SPI_InitStructure.SPI_BaudRatePrescaler = SpeedSet ;//传送速率
SPI_Init(SPI1, &SPI_InitStructure);  //¸
 
SPI_Cmd(SPI1, ENABLE); //

SPI1_ReadWriteByte(0xff);// 
}   

对于原子的MISO设置为复用推挽

对于STM32的这一类管脚来说(如USART_RX)即可以设置成为输入模式,也可以设置成为复用的推挽输出。其工作都是正常的,不过建议大家还是设置成为输入端口的好,容易理解。
具体产生这一问题的原因是:从功能上来说,MISO应该配置为输入模式才对,但为什么也可以配置为GPIO_Mode_AF_PP?请看下面的GPIO复用功能配置框图。当一个GPIO端口配置为GPIO_Mode_AF_PP是,这个端口的内部结构框图如下:图中可以看到,片上外设的复用功能输出信号会连接到输出控制电路,然后在端口上产生输出信号。但是在芯片内部,MISO是SPI模块的输入引脚,而不是输出引脚,也就是说图中的"复用功能输出信号"根本不存在,因此"输出控制电路"不能对外产生输出信号。
 
u8 SPI1_ReadWriteByte(u8 TxData)
{		
	u8 retry=0;				 	
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //
		{
		retry++;
		if(retry>200)return 0;
		}			  
	SPI_I2S_SendData(SPI1, TxData); //
	retry=0;


	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)//
		{
		retry++;
		if(retry>200)return 0;
		}	  						    
	return SPI_I2S_ReceiveData(SPI1); //				    
}
一般的读写如下
u8 SPI_ReadSR(void)   
{  
	u8 byte=0;   
	SPI_FLASH_CS=0;                            //置0片选使能器件
	SPI1_ReadWriteByte(W25X_ReadStatusReg);    // 向FLASH的寄存器发送读命令
	byte=SPI1_ReadWriteByte(0Xff);             //发送一个无效的命令来读取数据(应为要发送时钟)
	SPI_FLASH_CS=1;                            //  
	return byte;   
} 
//
//
void SPI_Write_SR(u8 sr)   
{   
	SPI_FLASH_CS=0;                            //置0片选使能器件
	SPI1_ReadWriteByte(W25X_WriteStatusReg);   // 向FLASH的寄存器发送写命令

	SPI1_ReadWriteByte(sr);               //写入数据
	SPI_FLASH_CS=1;                            //  	  取消使能器件    
}   

MPU6050的初始化当中包含了MPU6050.h头文件
 mpu.setClockSource(MPU6050_CLOCK_PLL_ZGYRO);          // Set Clock to ZGyro
  mpu.setFullScaleGyroRange(MPU6050_GYRO_FS);           // Set Gyro Sensitivity to config.h
  mpu.setFullScaleAccelRange(MPU6050_ACCEL_FS_2);       // +- 2G
  mpu.setDLPFMode(MPU6050_DLPF_BW_256);                 // Set Gyro Low Pass Filter
  mpu.setRate(0);                                       // 0=1kHz, 1=500Hz, 2=333Hz, 3=250Hz, 4=200Hz
  mpu.setSleepEnabled(false);
  mpu.i2cErrors = 0;

USB初始化:
	Set_System();
	Set_USBClock();
	USB_Interrupts_Config();
	USB_Init();

I2C:(硬件I2C)
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE); 开启I2C时钟
       GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
       GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;	       // 
I2C工作模式配置:
  I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//设置为I2C模式(还有其模式)
  I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//设置I2C的占空比:注意该参数只有I2C工作在快速模式下(即SCK时钟频率高于100kHz)下才有意义。
  I2C_InitStructure.I2C_OwnAddress1 =I2C1_OWN_ADDRESS7; //设置自己的地址
  I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;//设置为有应答
  I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//设置相连IC的地址位,与外设有关
在I2C通信时,只有在时钟信号SCL为高电平时,数据线上对应的数据才是有效数据,所以若高电平持续时间短的话,读写时间短,数据可能没有读写完,导致数据传输不准确;若高电平持续时间长的话,读写时间相应变长,可能导致时间的浪费,通信效率变低。所以合理的占空比对I2C通信是很必要的!
void I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr)
{
  /* Send STRAT condition */
  I2C_GenerateSTART(I2C1, ENABLE);
  /* Test on EV5 and clear it */
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));  
  /* Send EEPROM address for write */
  I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter); 
  /* Test on EV6 and clear it */
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));    
  /* Send the EEPROM's internal address to write to */
  I2C_SendData(I2C1, WriteAddr); 
  /* Test on EV8 and clear it */
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
  /* Send the byte to be written */
  I2C_SendData(I2C1, *pBuffer);    
  /* Test on EV8 and clear it */
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); 
  /* Send STOP condition */
  I2C_GenerateSTOP(I2C1, ENABLE);
}

使用IO口软件模拟I2C

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;   //2个IO口设置为推挽输出


void IIC_Start(void)
{
SDA_OUT();     //配置SCL口的输出速率50
IIC_SDA=1;    
IIC_SCL=1;
delay_us(4);
  IIC_SDA=0;//START:when CLK is high,DATA change form high to low 
delay_us(4);
IIC_SCL=0;
}  


void IIC_Stop(void)
{
SDA_OUT();//
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
  delay_us(4);
IIC_SCL=1; 
IIC_SDA=1;//
delay_us(4);  
}

等待从机应答信号;
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN();      //配置SDA口为浮空输入
IIC_SDA=1;delay_us(1);  
IIC_SCL=1;delay_us(1); 
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;   
return 0;  

//产生应答;就是SDA输出0

void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不产生应答  就是SDA输出1   
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}     
 
void IIC_Send_Byte(u8 txd)
{                        
    u8 t;   
SDA_OUT();    
    IIC_SCL=0;//
    for(t=0;t<8;t++)
    {              
        IIC_SDA=(txd&0x80)>>7;
        txd<<=1;  
delay_us(2);   //
IIC_SCL=1;
delay_us(2); 
IIC_SCL=0;
delay_us(2);
    }  
}    
//
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();
    for(i=0;i<8;i++ )
{
        IIC_SCL=0; 
        delay_us(2);
IIC_SCL=1;
        receive<<=1;
        if(READ_SDA)receive++;   
delay_us(1); 
    }  
    if (!ack)
        IIC_NAck();//
    else
        IIC_Ack(); // 
    return receive;
}

R/W(0为写1为读)

IIC写的过程:


IIC读的过程:

主机发送数据流程

(1)主机在检测到总线为“空闲状态”(即 SDA、SCL 线均为高电平)时,发送一个启动信号“S”,开始一次通信的开始

(2)主机接着发送一个命令字节。该字节由 7 位的外围器件地址和 1 位读写控制位 R/W组成(此时 R/W=0)

(3)相对应的从机收到命令字节后向主机回馈应答信号 ACK(ACK=0)

(4)主机收到从机的应答信号后开始发送第一个字节的数据

(5)从机收到数据后返回一个应答信号 ACK

(6)主机收到应答信号后再发送下一个数据字节

(7)当主机发送最后一个数据字节并收到从机的 ACK 后,通过向从机发送一个停止信号P结束本次通信并释放总线。从机收到P信号后也退出与主机之间的通信


主机接收数据流程

(1)主机发送启动信号后,接着发送命令字节(其中 R/W=1)

(2)对应的从机收到地址字节后,返回一个应答信号并向主机发送数据

(3)主机收到数据后向从机反馈一个应答信号

(4)从机收到应答信号后再向主机发送下一个数据 

(5)当主机完成接收数据后,向从机发送一个“非应答信号(ACK=1)”,从机收到ASK=1 的非应答信号后便停止发送

(6)主机发送非应答信号后,再发送一个停止信号,释放总线结束通信



注意都是要写入命令和地址(都是写入)

应答就是SDA口读取,0为成功1为失败。


USB部分

上面的时钟树图可以看到USB的时钟是要设置成48M,在D+上上拉电阻可以设置成全速模式
USB初始化通过
void USB_Init(void)
{
  pInformation = &Device_Info;
  pInformation->ControlState = 2;
  pProperty = &Device_Property;
  pUser_Standard_Requests = &User_Standard_Requests;
  /* Initialize devices one by one */
  pProperty->Init(); //从这里进入开始进入到下面的程序
}
void Virtual_Com_Port_init(void)
{


  /* Update the serial number string descriptor with the data from the unique
  ID*/
  Get_SerialNum();//获取要发送给设备当设备ID


  pInformation->Current_Configuration = 0;


  /* Connect the device */
  PowerOn();  


  /* Perform basic device initialization operations */
  USB_SIL_Init();


  /* configure the USART to the default settings */
//  USART_Config_Default();


  bDeviceState = UNCONNECTED;
}
STM32 有两个USB的中断处理函数
void USB_LP_CAN1_RX0_IRQHandler(void)
{
    USB_Istr();
}
void USB_HP_CAN1_TX_IRQHandler(void)
{
  CTR_HP();  
}
USB_Istr()是处理USB_LP_CAN1_RX0_IRQHandler中断的,而这个中断管理的是控制传输、中断传输、批量传输(单缓冲区)。
wIstr = _GetISTR();这句获取ISTR寄存器的值
然后后面的ISTR_CTR是寄存器USB_ISTR中的CTR位,表明端点一次正确的传输
浙西ISTR_XXX就是ISTR寄存器的某个标志位 用wIstr &ISTR_XXX获取该位的值然后做处理
wInterrupt_Mask是中断掩模,wIstr & ISTR_CTR & wInterrupt_Mask就是表示传输正确并产生中断
CTR_LP();才是真正的中断服务函数
EPindex = (u8)(wIstr & ISTR_EP_ID);是获取端点号,然后分端点号是否是0号进行处理
后面跟着的是获取保存相对应端点的状态,然后设置到NAK。
wIstr & ISTR_DIR 是获取方向标志 0 表示有数据进
  In0_Process();就是处理数据进
如果是1则表示主机要OUT或者在SETUP
  /* DIR = 1 & CTR_RX       => SETUP or OUT int */
   /* DIR = 1 & (CTR_TX | CTR_RX) => 2 int pending */
 所以这个中断服务程序其实包括接受和发送的处理
调用Setup0_Process()或Out0_Process();
如果不是端点0怎调用相应的端点回调函数
(*pEpInt_IN[EPindex-1])();
(*pEpInt_OUT[EPindex-1])();
我们自己能在响应的回调函数添加自己的程序。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值