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])();
我們自己能在響應的回調函數添加自己的程序。


發佈了36 篇原創文章 · 獲贊 4 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章