我使用STM32CubeMX生成初始化代碼,使用LL庫,這里只介紹跟i2c相關的部分,其他必要的初始化需要自己完成。芯片使用stm32f042。本文的代碼不能到手即用,只提供思路。
1、初始化
初始化部分包括GPIO、DMA、I2C等。
1、GPIO
這部分自動生成就OK,一般不需要作修改;
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA);
/**I2C1 GPIO Configuration
PA9 ------> I2C1_SCL
PA10 ------> I2C1_SDA
*/
GPIO_InitStruct.Pin = LL_GPIO_PIN_9;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_OPENDRAIN;
GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
GPIO_InitStruct.Alternate = LL_GPIO_AF_4;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LL_GPIO_PIN_10;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_OPENDRAIN;
GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
GPIO_InitStruct.Alternate = LL_GPIO_AF_4;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
2、DMA
DMA的初始化自動生成的程序會分為兩個部分:
第一個部分如下,會打開時鐘、初始化中斷:
void MX_DMA_Init(void)
{
/* Init with LL driver */
/* DMA controller clock enable */
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
/* DMA interrupt init */
/* DMA1_Channel2_3_IRQn interrupt configuration */
NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0);
NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
}
第二部分在I2C的初始化程序中
LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_3, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_3, LL_DMA_PRIORITY_HIGH);
LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_3, LL_DMA_MODE_NORMAL);
LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_3, LL_DMA_PERIPH_NOINCREMENT);
LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_3, LL_DMA_MEMORY_INCREMENT);
LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_3, LL_DMA_PDATAALIGN_BYTE);
LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_3, LL_DMA_MDATAALIGN_BYTE);
//上面是自動生成的,下面的部分需要自己添加
LL_DMA_SetDataLength(DMA1,LL_DMA_CHANNEL_3,5);
LL_DMA_SetMemoryAddress(DMA1,LL_DMA_CHANNEL_3,(uint32_t)i2cDataRx);
LL_DMA_SetPeriphAddress(DMA1,LL_DMA_CHANNEL_3,LL_I2C_DMA_GetRegAddr(I2C1,LL_I2C_DMA_REG_DATA_RECEIVE));
LL_DMA_EnableIT_TC(DMA1,LL_DMA_CHANNEL_3);
自動生成程序會完成DMA的如下設置:
- 數據傳輸方向
- 通道極性
- 模式
- 外設地址模式
- 內存地址模式
- 外設數據大小
- 內存數據大小
我們需要自己添加:
- 傳輸數據個數
- 設置內存地址
- 設置外設地址
- 打開中斷,根據需要選擇傳輸完成、傳輸一半和傳輸錯誤
DMA的模式有兩種:NORMAL和CIRCULAR。
CIRCULAR模式一旦開始傳輸,DMA控制器就會自動不停的從源地址拿數據發送到目的地址,不需要我們干預。由于是異步的,如果內存的數據多于1個,有可能出現內存數據一部分新一部分舊的情況,導致數據不同步,如果各個數據之間獨立還好,如果是一個整體就會出問題,所以要根據實際需求決定是否使用這種方式。
NORMAL模式發送一次后就停止了,如果還要發送就需要我們先關閉DMA通道,設置傳輸的數據個數,再打開通道,循環往復。本例使用這種方式,在中斷中處理三個過程,稍后介紹。
3、I2C
I2C包括時鐘、中斷、地址、時鐘、模式等等
/* Peripheral clock enable */
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C1);
/* I2C1 interrupt Init */
NVIC_SetPriority(I2C1_IRQn, 0);
NVIC_EnableIRQ(I2C1_IRQn);
/* USER CODE BEGIN I2C1_Init 1 */
/* USER CODE END I2C1_Init 1 */
/** I2C Initialization
*/
LL_I2C_DisableGeneralCall(I2C1);
LL_I2C_EnableClockStretching(I2C1);
I2C_InitStruct.PeripheralMode = LL_I2C_MODE_I2C;
I2C_InitStruct.Timing = 0x2000090E;
I2C_InitStruct.AnalogFilter = LL_I2C_ANALOGFILTER_ENABLE;
I2C_InitStruct.DigitalFilter = 0;
I2C_InitStruct.OwnAddress1 = 0x5A;
I2C_InitStruct.TypeAcknowledge = LL_I2C_ACK;
I2C_InitStruct.OwnAddrSize = LL_I2C_OWNADDRESS1_7BIT;
LL_I2C_Init(I2C1, &I2C_InitStruct);
LL_I2C_EnableAutoEndMode(I2C1);
LL_I2C_SetOwnAddress2(I2C1, 0, LL_I2C_OWNADDRESS2_NOMASK);
LL_I2C_EnableOwnAddress2(I2C1);
//上面的部分是自動生成的,下面是自己添加的
/* USER CODE BEGIN I2C1_Init 2 */
LL_I2C_Enable(I2C1);
LL_I2C_EnableIT_ADDR(I2C1);
// LL_I2C_EnableIT_ERR(I2C1);
LL_I2C_EnableDMAReq_RX(I2C1);
LL_I2C_EnableDMAReq_TX(I2C1);
地址必須是偶數,這里使用了雙地址,地址2是0,這樣0和5A都可以通信。
I2C在通信過程中會產生很多中斷,比如地址匹配、NACK、STOP、錯誤、溢出等等,這里根據需要只開啟地址匹配中斷(ADDR),一旦檢測到地址匹配,我們就開啟DMA傳輸數據,其他的事情交給DMA處理。
最后兩個分別是啟用 DMA 接收請求和啟用 DMA 發送請求,只有開啟它們DMA和I2C才能關聯上。
到此初始化基本完成。
2、中斷處理程序
1、I2C中斷處理程序
這里就判斷是否地址匹配,如果匹配,判斷是讀還是寫,這里讀寫以主機視角確定,如果是WRITE,說明從機此時要接收數據。(這里我發現不同的版本和系列定義的還不一樣,使用的時候要注意。)
void I2C1_IRQHandler(void)
{
/* USER CODE BEGIN I2C1_IRQn 0 */
if(LL_I2C_IsActiveFlag_ADDR(I2C1))
{
if(LL_I2C_GetTransferDirection(I2C1) == LL_I2C_DIRECTION_WRITE)
{
LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_3);
}
else
{
LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_2);
}
/* Clear ADDR flag value in ISR register */
LL_I2C_ClearFlag_ADDR(I2C1);
}
/* USER CODE END I2C1_IRQn 0 */
/* USER CODE BEGIN I2C1_IRQn 1 */
/* USER CODE END I2C1_IRQn 1 */
}
這里根據方向開啟對應的DMA通道,清除ADDR標志,之后數據就自動通過DMA傳輸了。
2、DMA中斷處理程序
這里由于通道2和3公用一個中斷,所以要先判斷是誰觸發的中斷,然后清除對應的中斷標志。前面我們設置的是DMA傳輸完成中斷,所以進入這里就表面數據傳完了。由于我們使用的是NORMAL模式,所以我在這個回調里關閉通道并重設傳輸的數據個數。我之所以放到這里是考慮到傳輸玩數據后一般會有個間隔,這段時間沒事干就處理一下這些必要的事情,等下次想要傳輸的時候直接打開就行。(前面在i2c中斷程序里我們可以看到打開通道的代碼。)你要是無所謂都放到I2C的中斷里也可以的。
void DMA1_Channel2_3_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Channel2_3_IRQn 0 */
if(LL_DMA_IsActiveFlag_TC3(DMA1))
{
//LL_DMA_ClearFlag_GI3(DMA1);
LL_DMA_ClearFlag_TC3(DMA1);
I2C_SlaveDMARxCpltCallback();
LL_DMA_DisableChannel(DMA1,LL_DMA_CHANNEL_3);
LL_DMA_SetDataLength(DMA1,LL_DMA_CHANNEL_3,5);
}
else if (LL_DMA_IsActiveFlag_TC2(DMA1))
{
LL_DMA_ClearFlag_TC2(DMA1);
LL_DMA_DisableChannel(DMA1,LL_DMA_CHANNEL_2);
LL_DMA_SetDataLength(DMA1,LL_DMA_CHANNEL_2,5);
}
/* USER CODE END DMA1_Channel2_3_IRQn 0 */
/* USER CODE BEGIN DMA1_Channel2_3_IRQn 1 */
/* USER CODE END DMA1_Channel2_3_IRQn 1 */
}
在接收中斷中有一個回調函數
I2C_SlaveDMARxCpltCallback(),里邊是用戶自定義程序,你想收到數據干啥就可以在這里邊處理。
剩下所要做的事情就是準備好要發送的數據和使用收到的數據就行了。
最近發現使用DMA真的很方便,尤其在發送或接收多個數據的時候,就不用for循環了,這樣既能收發大量數據,還不會占用CPU時間,效率大大提高。
-
I2C
+關注
關注
28文章
1510瀏覽量
126224 -
dma
+關注
關注
3文章
569瀏覽量
101907 -
代碼
+關注
關注
30文章
4872瀏覽量
69913 -
GPIO
+關注
關注
16文章
1236瀏覽量
53177 -
stm32cubemx
+關注
關注
5文章
286瀏覽量
15824
發布評論請先 登錄
相關推薦
STM32接入PC后能枚舉出來麥克風設備,但是開始錄音后無數據,為什么?
STM32接入PC后能枚舉出來麥克風設備,開始錄音后無數據是怎么回事?
【NUCLEO-F412ZG試用體驗】一、USART+LED+KEY
【NUCLEO-F412ZG試用體驗】FreeRTOS創建任務
STM32CubeMX外設在哪里初始化?
STM32F4 USB麥克風錄音后無數據,BUSHOUN數據長度一直是0無法進入函數USBD_AUDIO_DataIn怎么解決
STM32CubeMX與HAL庫學習--ADC與USART的簡單練習

評論