使用STM32CubeMX生成初始化代码

发布时间:2023-06-26  

我使用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时间,效率大大提高。


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

相关文章

    雅特力AT32 Workbench图形化代码生成工具,简化嵌入式开发利器; 嵌入式系统应用市场广泛,早已遍及日常生活,随着产品需求复杂度的提升,32位MCU开发难度也随之增加,如何降低开发成本,缩短......
    -IHM07M1;受控电机:BR2804-1700KV-1无刷电机; 软件环境:电机参数设置及配置工具:ST Motor Control SDK 5.4.3工程代码的生成工具:STM32CubeMX......
    话就能做出应用。 “秒哒是跟任何辅助代码生成工具是完全不一样的,因为它根本不需要你能看懂代码,所以看直播的朋友们,如果你是程序员,那你现在可以不看了,这个软件是给非程序员来用的。” 简单......
    效果好。 3. 用三极管恒流驱动方式,成本低。 4. 提供完整的参考设计,包括硬件原理图、PCB图、软件源代码(需要授权)。 5. 提供图形化的初始化代码生成工具,开发简单。 ►方案规格 1. 工作......
    硬件原理图、PCB图、软件源代码(需要授权)。 5. 提供图形化的初始化代码生成工具,开发简单。 ►方案规格 1. 工作电压:9~30V 2. 工作电流:0.55A(最大值) 3. LED数量:2......
    制器图形化配置外围设备和中间件的功能模式和初始化参数 C代码生成项目覆盖STM32微控制器的初始化符合IAR,Keil的和GCC编译器。 对于新的产品设计,我们强烈推荐使用STM32Cube来加速你的开发过程,并为......
    载芯片示意图上会看到下图PA6就是TIM3的通道1PWM波输出口。 生成工程设置 注意在生产工程设置中不能出现中文,不然会报错。 代码生成设置 最后设置生成独立的初始化文件: 生成代码 配置keil......
    化 C 代码生成项目, 符合 IAR™, Keil®和 GCC 编译, 运行 ARM ®的Cortex ® -M 核心8. Arm®Cortex®a core (STM32 微处理器)部分 Linux......
    I2C1引脚 配置时钟树 STM32L0的最高主频到32M,所以配置PLL,最后使HCLK = 32Mhz即可: 时钟树配置 生成工程设置 生成工程设置 代码生成设置 最后设置生成......
    ENGINEERING China 2022(第十八届)年度最佳产品奖颁奖典礼中,MathWorks 凭借 Embedded Coder®代码生成工具箱,获嵌入式控制类最佳产品奖。CEC 评选......

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>