MM32学习笔记

拿到芯片肯定是先搭建开发环境,搭建开发环境相关内容在这篇文章里面。

开始学习单片机的第一件事----先看一眼它的系统框图。

等等,怎么有点眼熟(bushi

image-20220226144321213.png

时钟树

从系统框图上可以看出这哥们的时钟总线和另一个某32有点像啊

咳咳,正经点。

AHB总线连接着总线矩阵和外设总线APB1和APB2,经测试,APB1总线的默认配置时钟频率为60M,而APB2总线的默认频率是和AHB总线(即系统时钟)相同的120M。

时钟总线框架图如下:

image-20220226170245579.png

相关函数

//使能各个总线时钟
void RCC_AHBPeriphClockCmd(u32 ahb_periph, FunctionalState state);
void RCC_AHB2PeriphClockCmd(u32 ahb_periph, FunctionalState state);
void RCC_AHB3PeriphClockCmd(u32 ahb_periph, FunctionalState state);
void RCC_AHBPeriphResetCmd(u32 ahb_periph, FunctionalState state);
void RCC_AHB2PeriphResetCmd(u32 ahb_periph, FunctionalState state);
void RCC_AHB3PeriphResetCmd(u32 ahb_periph, FunctionalState state);
void RCC_APB2PeriphClockCmd(u32 apb2_periph, FunctionalState state);
void RCC_APB1PeriphClockCmd(u32 apb1_periph, FunctionalState state);

GPIO

这就不用废话了,直接上初始化GPIO为输出的代码:

void MM_GPIO_Init()
{
    //定义GPIO句柄
    GPIO_InitTypeDef GPIO_InitStruct;

    //开启GPIO外设时钟
    //GPIO挂在AHB总线上
    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);

    //初始化GPIO结构体,防止使用时出现未被初始化的成员
    GPIO_StructInit(&GPIO_InitStruct);

    //设置GPIO复用功能
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_15);
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    // GPIO初始化
    GPIO_Init(GPIOB, &GPIO_InitStruct);
}

相关函数

初始化之后即可使用以下函数来控制输出以及读取端口值:

//GPIO引脚置位
void GPIO_SetBits(GPIO_TypeDef* gpio, u16 pin);
//GPIO引脚复位
void GPIO_ResetBits(GPIO_TypeDef* gpio, u16 pin);
//GPIO写引脚(上面两个复合版本
void GPIO_WriteBit(GPIO_TypeDef* gpio, u16 pin, BitAction value);
//GPIO端口写入(控制多个引脚
void GPIO_Write(GPIO_TypeDef* gpio, u16 value);

定时器

定时器时钟

调试了以下,看了看定时器里面的寄存器的值,得知APB1总线预分频为4,APB2总线预分频为1:

image-20220226211759119.png

结合时钟框图:

image-20220226212004190.png

APB1总线频率分到定时器时钟时由于预分频不为1,故做一次2倍频,也即定时器时钟为AHB时钟2分频。同理,APB2总线频率与AHB相同。

基础定时中断

void MM_TIM_Init(void)
{
    //定义定时器句柄
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;

    //使能外设时钟
    // TIM2挂载在APB1总线上
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM2, ENABLE);

    //初始化以及赋值定时器结句柄
    TIM_TimeBaseStructInit(&TIM_TimeBaseStruct);
    TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStruct.TIM_Prescaler = 6000 - 1;
    TIM_TimeBaseStruct.TIM_Period = 10000 - 1;
    TIM_TimeBaseStruct.TIM_RepetitionCounter = 0;
    //定时器初始化
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);

    //使能ARR预装载
    TIM_ARRPreloadConfig(TIM2, ENABLE);
    //清除更新中断标志位
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
    //使能中断
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

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

void NVIC_Config(void)
{
    //定义中断句柄
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;

    //初始化中断
    NVIC_Init(&NVIC_InitStruct);
}

void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
        DebugLog("tim\n");
    }
}

其中中断服务函数的函数名可以在启动文件的中断向量表中找到。

PWM输出

废话不多说,使用定时器5输出一路PWM:

void MM_PWM_Init(void)
{
    //定义GPIO、时基单元、输出比较句柄
    GPIO_InitTypeDef GPIO_InitStruct;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
    TIM_OCInitTypeDef TIM_OCInitStruct;

    //使能GPIO以及定时器时钟
    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM5, ENABLE);

    //初始化GPIO引脚复用功能
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_2);
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    //初始化时基、输出比较句柄
    TIM_TimeBaseStructInit(&TIM_TimeBaseStruct);
    TIM_OCStructInit(&TIM_OCInitStruct);

    //时基句柄赋值
    TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStruct.TIM_Prescaler = 60 - 1;
    TIM_TimeBaseStruct.TIM_Period = 100000 - 1;
    TIM_TimeBaseStruct.TIM_RepetitionCounter = 0;
    //输出比较句柄赋值
    TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStruct.TIM_Pulse = 5000;
    TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
    //初始化时基、输出比较功能
    TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStruct);
    TIM_OC2Init(TIM5, &TIM_OCInitStruct);

    //使能预装载寄存器
    TIM_OC2PreloadConfig(TIM5, TIM_OCPreload_Enable);
    TIM_ARRPreloadConfig(TIM5, ENABLE);
    //使能PWM输出
    TIM_CtrlPWMOutputs(TIM5, ENABLE);

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

IIC通信

IIC初始化函数:

void MM_I2C_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);

    GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_4);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_4);

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_20MHz;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    I2C_InitTypeDef I2C_Initstruct;

    RCC_APB1PeriphClockCmd(RCC_APB1ENR_I2C1, ENABLE);

    I2C_StructInit(&I2C_Initstruct);
    I2C_Initstruct.Mode = I2C_Mode_MASTER;
    I2C_Initstruct.OwnAddress = 0;
    I2C_Initstruct.ClockSpeed = 100000;
    I2C_Initstruct.Speed = I2C_Speed_STANDARD;
    I2C_Init(I2C1, &I2C_Initstruct);

    I2C_Cmd(I2C1, ENABLE);
}

为了方便后续使用,封装了IIC读写函数,封装的过程挺崎岖的,主要是库函数说明不清不楚,某些注释甚至有问题,导致走了许多弯路。

IIC写函数:

uint8_t MM_I2C_WriteMem(I2C_TypeDef* i2c, uint8_t devAddr, uint8_t regAddr, uint8_t* pData, uint32_t len, uint32_t i2cTimeout)
{
    uint32_t timeout;

    I2C_GenerateSTART(i2c, ENABLE);

    //发送从设备地址
    I2C_Cmd(i2c, DISABLE);
    I2C_Send7bitAddress(i2c, devAddr, I2C_Direction_Transmitter);
    I2C_Cmd(i2c, ENABLE);

    //发送寄存器地址并超时等待
    timeout = i2cTimeout;
    I2C_SendData(i2c, regAddr);
    while ((I2C_GetFlagStatus(i2c, I2C_STATUS_FLAG_TFE) == RESET)) {
        if (timeout-- == 0) {
            I2C_GenerateSTOP(i2c, ENABLE);
            return 1;
        }
    }

    //发送数据包并超时等待
    for (int i = 0; i < len; i++) {
        timeout = i2cTimeout;
        I2C_SendData(i2c, *(pData + i));
        while ((I2C_GetFlagStatus(i2c, I2C_STATUS_FLAG_TFE) == RESET)) {
            if (timeout-- == 0) {
                I2C_GenerateSTOP(i2c, ENABLE);
                return 2;
            }
        }
    }

    //发送停止信号
    I2C_GenerateSTOP(i2c, ENABLE);

    return 0;
}

IIC读函数:

uint8_t MM_I2C_ReadMem(I2C_TypeDef* i2c, uint8_t devAddr, uint8_t regAddr, uint8_t* pData, uint32_t len, uint32_t i2cTimeout)
{
    uint32_t timeout;

    //发送起始信号
    I2C_GenerateSTART(i2c, ENABLE);

    //发送从机地址
    I2C_Cmd(i2c, DISABLE);
    i2c->IC_TAR = devAddr >> 1;
    I2C_Cmd(i2c, ENABLE);

    //发送寄存器地址
    timeout = i2cTimeout;
    I2C_SendData(i2c, regAddr);
    while ((I2C_GetFlagStatus(i2c, I2C_STATUS_FLAG_TFE) == RESET)) {
        if (timeout-- == 0) {
            I2C_GenerateSTOP(i2c, ENABLE);
            return 1;
        }
    }

    //接收并读取数据
    uint8_t flag = 0, _cnt = 0;
    for (int i = 0; i < len; i++) {
        while (1) {
            //
            if ((I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFNF)) && (flag == 0)) {
                I2C_ReadCmd(I2C1);
                if (_cnt++ == len)
                    flag = 1;
            }
            if (I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_RFNE)) {
                pData[i] = I2C_ReceiveData(I2C1);
                break;
            }
        }
    }

    I2C_GenerateSTOP(i2c, ENABLE);

    return 0;
}
上一篇
下一篇