灵动微电子

MM32F013x内部的RTC是一个独立的定时器单元,它拥有一组连续计数的计数器,配置相应的寄存器参数,可以实现闹钟、秒中断、毫秒中断、MCU定时唤醒、万年历等功能。

主要特征

① 可编程的预分频系数:分频系数最高为 220

② 32 位的可编程计数器,用于较长时间段的测量

③ 2 个分离的时钟:用于 APB1 接口的 PCLK1 和 RTC 时钟 (RTC 时钟的频率必须小于PCLK1 时钟频率的四分之一以上)

④ 可以选择以下三种 RTC 的时钟源
– HSE 时钟除以 128
– LSE 振荡器时钟
– LSI 振荡器时钟

⑤ 2 个独立的复位类型
– APB1 接口由系统复位
– RTC 核心 (预分频器、闹钟、计数器和分频器) 只能由后备域复位

⑥ 3 个专门的屏蔽中断
– 闹钟中断,用来产生一个软件可编程的闹钟中断
– 秒 / 毫秒中断,用来产生一个可编程的周期性中断信号 (最长可达 1 秒)
– 溢出中断,指示内部可编程计数器溢出并返回为 0 的状态

本文将重点介绍如何在MM32F013x上通过内部RTC模块实现万年历的功能。

实现功能

通过修改RTC计数器的初始值来设置系统当前的时间和日期,使能RTC秒中断功能;在RTC产生秒中断后,通过获取当前RTC的计数值,将其转换为对应的年月日信息,再通过蔡勒公式计算出星期,将最终的结果通过串口的形式输出显示。

RTC模块的电源域处在VDD数字电源域,只要MCU供电就可以使用RTC,没有独立的VBAT供电引脚,所以无法使用纽扣电池类的应用。

参考代码

01、结构体定义及全局变量

typedef struct
{
    uint16_t year;
    uint8_t  month;
    uint8_t  day;
    uint8_t  week;
    uint8_t  hour;
    uint8_t  minute;
    uint8_t  second;
} CALENDAR_t;
const uint8_t RTC_DayOfMonth[12] =
{
    31,28,31,30,31,30,31,31,30,31,30,31
};
CALENDAR_t    RTC_Calendar;

02、RTC初始化配置:使用外部32.768kHz的晶振源

void RTC_Configure(void)
{
    uint16_t BKP_Value = 0x5A5A;
    NVIC_InitTypeDef NVIC_InitStructure;
    /* Enable PWR Clock */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    /* Enable WKUP pin */
    PWR_WakeUpPinCmd(ENABLE);
    /* Enable Access To The RTC & Backup Registers */
    PWR_BackupAccessCmd(ENABLE);
    if(BKP_ReadBackupRegister(BKP_DR1) != BKP_Value)
    {
        BKP_DeInit();
        /* Enable LSE Clock Source */
        RCC_LSEConfig(RCC_LSE_ON);

        /* Wait LSI Clock Source Ready */
        while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);

        /* Config RTC Clock Source : LSE */
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);

        /* Enable RTC Clock */
        RCC_RTCCLKCmd(ENABLE);

        /* Wait For Synchronization */
        RTC_WaitForSynchro();
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        /* Set The RTC Prescaler Value */
        RTC_SetPrescaler(32767);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        /* Enable RTC Second Interrupt */
        RTC_ITConfig(RTC_IT_SEC, ENABLE);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        /* Exit From The RTC Configuration Mode */
        RTC_ExitConfigMode();

        BKP_WriteBackupRegister(BKP_DR1, BKP_Value);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        /* Set initial time */
        RTC_SetDateTime(2021, 1, 12, 14, 48, 0);
    }
    else
    {
        /* Wait For Synchronization */
        RTC_WaitForSynchro();

        /* Enable RTC Second Interrupt */
        RTC_ITConfig(RTC_IT_SEC, ENABLE);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();
    }

    /* Config RTC NVIC */
    NVIC_InitStructure.NVIC_IRQChannel  = RTC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

03、RTC秒中断函数

void RTC_BKP_IRQHandler(void)
{
    if(RTC_GetITStatus(RTC_IT_SEC) != RESET)
    {
        /* Update Date and Time */
        RTC_UpdateCalendar();

        /* Print current Date and Time */
        RTC_PrintDateTime();

        /* Clear Alarm Flag */
        RTC_ClearITPendingBit(RTC_IT_SEC);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();
    }
}

04、将RTC计数值转换为日期信息

void RTC_UpdateCalendar(void)
{
    static uint32_t PreTotalDay = 0;

    uint32_t TotalSecond = 0;
    uint32_t TotalDay    = 0;

    uint16_t Year  = 1970;
    uint8_t  Month = 0;

    /* Get The RTC Counter Value */
    TotalSecond = RTC_GetCounter();
    TotalDay    = TotalSecond / 86400;

    if(PreTotalDay != TotalDay)
    {
        PreTotalDay = TotalDay;

        while(TotalDay >= 365)
        {
            if(RTC_LeapYear(Year) == 1)
            {
                if(TotalDay >= 366)
                {
                    TotalDay -= 366;
                }
                else
                {
                    break;
                }
            }
            else
            {
                TotalDay -= 365;
            }

            Year++;
        }
        RTC_Calendar.year = Year;
        while(TotalDay >= 28)
        {
            if((Month == 1) && (RTC_LeapYear(RTC_Calendar.year) == 1))
            {
                if(TotalDay >= 29)
                {
                    TotalDay -= 29;
                }
                else
                {
                    break;
                }
            }
            else
            {
                if(TotalDay >= RTC_DayOfMonth[Month])
                {
                    TotalDay -= RTC_DayOfMonth[Month];
                }
                else
                {
                    break;
                }
            }

            Month++;
        }

        RTC_Calendar.month = Month    + 1;
        RTC_Calendar.day   = TotalDay + 1;

        RTC_Calendar.week  = RTC_GetWeek(RTC_Calendar.year, RTC_Calendar.month, RTC_Calendar.day);
    }

    RTC_Calendar.hour   =  (TotalSecond % 86400) / 3600;
    RTC_Calendar.minute = ((TotalSecond % 86400) % 3600) / 60;
    RTC_Calendar.second = ((TotalSecond % 86400) % 3600) % 60;
}

05、将日期信息转换为RTC计数值

void RTC_SetDateTime(uint16_t Year, uint8_t Month, uint8_t Day,
                     uint8_t  Hour, uint8_t Min,   uint8_t Sec)
{
    uint32_t TotalSecond = 0;
    uint16_t y = 0;
    uint8_t  m = 0;

    if((Year >= 1970) && (Year <= 2099))
    {
        for(y = 1970;  y < Year; y++)
        {
            if(RTC_LeapYear(y) == 1)
            {
                TotalSecond += 31622400;    /* Total Seconds Of Leap   Year */
            }
            else
            {
                TotalSecond += 31536000;    /* Total Seconds Of Normal Year */
            }
        }

        for(m = 0; m < (Month - 1); m++)
        {
            TotalSecond += RTC_DayOfMonth[m] * 86400; /*Total Seconds Of Month */
            if((RTC_LeapYear(Year) == 1) && (m == 1))
            {
                TotalSecond += 86400;
            }
        }
        TotalSecond += (uint32_t)(Day - 1) * 86400; /* Total Seconds Of Day    */
        TotalSecond += (uint32_t)Hour      * 3600;  /* Total Seconds Of Hour   */
        TotalSecond += (uint32_t)Min       * 60;    /* Total Seconds Of Minute */
        TotalSecond += Sec;

        /* Enable PWR Clock */
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

        /* Enable Access To The RTC & Backup Registers */
        PWR_BackupAccessCmd(ENABLE);

        /* Set The RTC Counter Value */
        RTC_SetCounter(TotalSecond);

        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        RTC_UpdateCalendar();
    }
    else
    {
        printf("\r\nError Date & Time!!!\r\n");
    }
}

06、RTC信息打印

void RTC_PrintDateTime(void)
{
    printf("\r\n%04d-%02d-%02d", RTC_Calendar.year, RTC_Calendar.month,  RTC_Calendar.day);

    switch(RTC_Calendar.week)
    {
     case 0 :
        printf(" SUN ");
        break;

     case 1 :
        printf(" MON ");
        break;

     case 2 :
        printf(" TUE ");
        break;

     case 3 :
        printf(" WED ");
        break;

     case 4 :
        printf(" THU ");
        break;

     case 5 :
        printf(" FRI ");
        break;

     case 6 :
        printf(" SAT ");
        break;

     default:
        break;
    }

    printf("%02d:%02d:%02d\r\n", RTC_Calendar.hour, RTC_Calendar.minute, RTC_Calendar.second);
}

07、RTC功能函数:判断闰年、蔡勒公式计算星期

uint8_t RTC_LeapYear(uint16_t Year)
{
    if(
        (((Year % 400) == 0)                     ) ||   /* Century Leap Year */
        (((Year % 100) != 0) && ((Year % 4) == 0))      /* Normal  Leay Year */
    )
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

uint8_t RTC_GetWeek(uint16_t Year, uint8_t Month, uint8_t Day)
{
    int w, c, y;
    /* Month 1 Or 2 of This Year Must Be As Last Month 13 Or 14 */
    if((Month == 1) || (Month == 2))
    {
        Month += 12;
        Year  -= 1;
    }

    w = 0;          /* Weekday */
    c = Year / 100; /* Century */
    y = Year % 100; /* Year    */

    w = y + (y / 4) + (c / 4) - (2 * c) + (26 * (Month + 1) / 10) + Day - 1;

    while(w < 0) w += 7;

    w %= 7;

    return w;
}

运行结果

编译软件工程无误后下载代码,在串口终端工具中我们可以看到每间隔1秒钟,RTC产生一次中断,在中断中我们将当前的日期信息通过串口打印在显示终端软件上:

转自:灵动微电子

围观 9

相信很多做变频设计或者电机控制领域的朋友们都熟悉一项重要技术——使用单电阻电流重构技术采样相电流,实现FOC控制。那采样相电流时就涉及到低调制区域,采样时间大于PWM分段矢量作用时间,此时就需要使用移相技术,就是在中央对齐互补模式下实现非对称PWM输出。

电机控制单电阻采样机制是在一个 PWM 波形内采集两相电流 ADC 数据,但某些扇区边界条件下只能获得一路电流 ADC 数据, 需要对 PWM 波形进行变形用于构造电流采样区域。


本文将重点介绍如何在MM32F013x上实现TIM1的硬件移相功能。

实现方式

目前MM32 MCU实现PWM 移相功能有多种实现方式,其中一种实现方式:设置TIM1和TIMx主从模式,开启TIMx的CCx中断,且开启对应的DMA请求通道去完成TIM1 CCRx值的改变,此外只需要在TIMx_CCx中断服务函数中改变TIM1 对应通道的CCR值即可在软件中实现动态修改移相角度功能,同样也就实现了PWM占空比在每半个周期内交替变化。

MM32F013x系列MCU中新增 TIM1的硬件移相功能,新增了PDER(通道x输出 PWM 移相使能位) 和 CCRxFALL(通道x在 PWM 中央对齐模式向下计数时的捕获/比较值)寄存器,允许 TIM1的5 个通道在硬件上完成输出 PWM 移相操作。开启 PDER 寄存器的 PWM移相使能,根据需要移动相位,配置 CCRxFALL 以及 CCRx,即可实现 PWM 输出可编程的移相波形,可左移或是右移。

相关寄存器

除了之前熟悉的TIM1 PWM输出相关的寄存器外,主要还需要关注以下新增寄存器。


定时器1的中央对齐模式

脉冲宽度调制模式可以产生一个由 TIMx_ARR 寄存器确定频率、由 TIMx_CCRx 寄存器确定占空比的信号。

当 TIMx_CR1 寄存器中的 CMS 位不为‘00’时为中央对齐模式 (所有其它的配置对 OCxREF/OCx 信号都有相同的作用)。根据不同的 CMS 位的设置,比较标志可以在计数器向上计数时被置 1、在计数器向下计数时被置 1、或在计数器向上和向下计数时被置‘1’。TIMx_CR1寄存器中的计数方向位 (DIR) 由硬件更新,不要用软件修改它。

中央对齐模式的图例可用下图来表示:


中央对齐有3种不同模式,可以通过软件设置TIM1_CR寄存器的CMS位来选择对应模式。3种模式中计数器的计数方式都是一样的,计数器从 0 开始计数到自动加载的值 (TIM1_ARR - 1),产生一个计数器溢出事件,然后向下计数到 1 并且产生一个计数器下溢事件,之后再循环从 0 开始重新计数。3种模式的不同之处在于,输出比较中断标志位被设置的时刻点均不一致,根据实际需求来进行选择。

使用中央对齐模式还有以下几点需要注意:

进入中央对齐模式时,使用当前的上/下计数配置;这就意味着计数器向上还是向下计数取决于 TIMx_CR1 寄存器中 DIR 位的当前值。此外,软件不能同时修改 DIR 和 CMS位。

不推荐当运行在中央对齐模式时改写计数器,因为会产生不可预知的结果。特别地:
– 如果写入计数器的值大于自动重加载的值 (TIMx_CNT > TIMx_ARR),则方向不会被更新
– 例如,如果计数器正在向上计数,它就会继续向上计数
– 如果将 0 或者 TIMx_ARR 的值写入计数器,方向被更新,但不产生更新事件UEV

使用中央对齐模式最保险的方法,就是在启动计数器之前产生一个软件更新 (设置 TIMx_EGR位中的 UG 位)。

软件实现步骤

了解完应用场景、实现原理以及涉及到的寄存器,下面介绍配置代码。

01、主程序初始化

以上为整个软件工程的入口主函数,默认主频为内部时钟HSI倍频到72M。初始化好了TIM1 前3个PWM输出通道所用引脚以及TIM1,并且开启TIM1一直输出PWM波形。

extern u32 SystemCoreClock;

s32 main(void)
{
    /* TIM1 PWM GPIO AF initial */
    TIM1_GPIO_Init();

    TIM1_PWM_Init(500 - 1, SystemCoreClock / 1000000 - 1);
    DELAY_Ms(100);
    TIM1_PWM_Shift_Test() ;
}

02、TIM1初始化

void TIM1_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_2);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_2);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_2);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 ;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_SetBits(GPIOA,GPIO_Pin_7) ;
}

以上代码完成了TIM1前3个PWM输出通道所用引脚的复用功能配置,对应的复用功能号需要根据芯片数据手册中的引脚功能定义表来获取到。

void TIM1_PWM_Shift_Init(u16 arr, u16 psc)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef  TIM_OCInitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2ENR_TIM1, ENABLE);  

    TIM_DeInit(TIM1) ;   
    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
    TIM_TimeBaseStructure.TIM_Period = arr;
    TIM_TimeBaseStructure.TIM_Prescaler = psc;   
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

    TIM_OCStructInit(&TIM_OCInitStructure);
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_Pulse = 250 ;
    TIM_OC1Init(TIM1, &TIM_OCInitStructure);

    TIM_OCInitStructure.TIM_Pulse = 250 ;
    TIM_OC2Init(TIM1, &TIM_OCInitStructure);

    TIM_OCInitStructure.TIM_Pulse = 250 ;
    TIM_OC3Init(TIM1, &TIM_OCInitStructure);

    TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
    TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
    TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable);
    TIM_ARRPreloadConfig(TIM1, ENABLE);
    TIM_SelectOutputTrigger(TIM1,TIM_TRGOSource_Update);

    TIM_Cmd(TIM1, ENABLE);
    TIM_CtrlPWMOutputs(TIM1, ENABLE);    
}

以上配置接口程序为时基相关参数,调用时候只需要根据实际应用情况传入不同的参数即可改变PWM输出的频率。

计数模式设置为中央对齐模式1,输出比较模式设置为PWM1输出模式,输出有效极性设置为高电平。当计时器值小于比较器设定值时则对应通道输出有效高电平,当计时器值大于或等于比较器设定值时则对应通道输出无效低电平。

void TIM1_PWM_Shift_Test(void)
{    
    GPIO_ResetBits(GPIOA,GPIO_Pin_7) ;

    TIM_SetCCR2FALL(TIM1, 200);
    TIM_SetCCR3FALL(TIM1, 300);

    TIM_PWMShiftConfig(TIM1, TIM_PDER_CCR2SHIFTEN|TIM_PDER_CCR3SHIFTEN, ENABLE);
}

TIM_PWMShiftConfig库函数实现开启或关闭对应通道输出PWM移相功能。TIM_SetCCRxFALL库函数实现设置对应通道在PWM中央对齐模式向下计数时的捕获/比较值。开启 PDER 寄存器的PWM移相使能后,根据需要移动相位,配置 CCRxFALL 以及 CCRx,即可实现PWM 输出可编程的移相波形,即可以左移亦或是右移。

测试验证

本次微课堂实验是基于eMiniboard MB-025硬件资源完成的,前述代码中,通过设置TIM1的时基为1KHz,且不开启OC1通道的移相功能,只使能OC2和OC3通道的允许输出PWM移相使能位,通过逻辑分析仪抓取TIM1 前3个PWM输出通道的波形,稳定的波形结果如下图所示:


粉红色表示通道1波形,深蓝色表示通道2波形,浅绿色表示通道3波形,黄色表示开始移相时刻点。图中看出,通道2较通道1往右移了18度的相位(最大只能移动180度,这里占用时长为500us,右移了50us为18度),通道2较通道1往左移了18度的相位(左移了50us为18度)。根据上述波形结果展示,TIM1 的硬件PWM移相功能可以配置相关寄存器的配置即可实现,操作简单。

转自:灵动微电子

围观 13

在MCU的应用场景中,处处都有用到ADC,比如电池电量的采集、温度采集、电机应用中电流检测等等。MM32F013x的ADC模块新增了任意通道工作模式,支持在多种应用场景中更灵活的应用;本文针对任意通道工作模式,分享在MM32F013x上实现任意通道工作模式的使用与具体配置。

任意顺序多通道功能

在MM32F013x系列的MCU中新增了ADC对任意通道的支持,在任意通道配置(ADC_ANY_CR. CHANY_MDEN)使能后,其优先级高于常规通道配置,后续的转换按任意通道配置的方式转换。

任意通道模式支持单次转换模式、单周期转换模式和连续扫描模式。

A/D 转换开始条件:
① 软件启动
② 外部触发启动,且软件可配置外部触发延时
③ Timer1/2/3 匹配或 TRGO 信号,外部 EXTI 信号源

相关的寄存器


具体功能与详细描述,请参考MM32F013x系列的用户手册。

任意通道工作模式

01、单次转换模式

在单次转换模式下,A/D 转换相应通道上只执行一次,具体流程如下:

软件设置寄存器ADC_ANY_CFG,ADC_CHANY0,ADC_CHANY1,设置转换通道,置位CHANY_MDEN。(单次转换模式,只需设置CHANY_SEL0)

通过软件、外部触发输入及定时器溢出置位ADCR 寄存器的ADST,开始A/D转换。

A/D 转换完成时,A/D 转换的数据值将存储于数据寄存器ADDATA 和ADDRn 中。

A/D 转换完成时,状态寄存器ADSTA 的ADIF 位置1。若此时控制寄存器ADCR 的ADIE位置1,将产生AD 转换结束中断请求。

A/D 转换期间,ADST 位保持为1。A/D 通道采样结束后,ADST 位自动清0,A/D 转换器进入空闲模式。

若在A/D 转换过程中,软件更新ADC_ANY_CFG,ADC_CHANY0,ADC_CHANY1,硬件不会立即更新这些配置,只会在当前设置的通道都转换结束时更新,然后等待下一次软件置位ADST。


该模式仍然支持通过过配置当外部事件(比如TIM Trig或EXTI)触发转换时序。

02、单周期扫描模式

在单周期扫描模式下,A/D 转换相应通道上执行一遍按配定顺序的转换,具体流程如下:

软件设置寄存器ADC_ANY_CFG,ADC_CHANY0,ADC_CHANY1,将需要转换的通道、数量设置好,然后置位CHANY_MDEN。

通过软件、外部触发置位ADCR 寄存器的ADST,外部触发可软件配置触发延时,A/D转换方向从CHANY_SEL0 到CHANY_SEL15,转换通道数量由CHANY_NUM 配置,且CHANY_SEL0 到CHANY_SEL15 是任意配置的,可以完全相同,或完全不相同。

每路A/D 转换完成时,A/D 转换的数据值将有序装载到相应通道的数据寄存器中,ADIF转换结束标志被设置,若此时控制寄存器ADCR 的ADIE 位置1,将产生AD 转换结束中断请求。

A/D 最后一个通道采样结束后,ADST 位自动清0,A/D 转换器进入空闲模式。

若在A/D 转换过程中,软件更新ADC_ANY_CFG,ADC_CHANY0,ADC_CHANY1,硬件不会立即更新这些配置,只会在当前设置的通道都转换结束时更新,然后等待下一次软件软件置位ADST。


在一些场景中,需要在执行一遍上述采样后,对采样顺序做调整;或减少采样通道数,以减少采样总体时间,可以通过简单的配置一两个寄存器实现灵活的配置;

03、连续扫描模式

在连续扫描模式下,A/D 转换通道依软件配置一直执行,直到软件禁止。具体流程如下:

软件设置寄存器ADC_ANY_CFG,ADC_CHANY0,ADC_CHANY1,将需要转换的通道、数量设置好,然后置位CHANY_MDEN。

通过软件、外部触发置位ADCR 寄存器的ADST,外部触发可软件配置触发延时,A/D转换方向从CHANY_SEL0 到CHANY_SEL15,转换通道数量由CHANY_NUM 配置,且CHANY_SEL0 到CHANY_SEL15 是任意配置的,可以完全相同,或完全不相同。

每路A/D 转换完成时,A/D 转换的数据值将有序装载到相应通道的数据寄存器中,ADIF转换结束标志被设置,若此时控制寄存器ADCR 的ADIE 位置1,将产生AD 转换结束中断请求。

通过软件、外部触发置位ADCR 寄存器的ADST,外部触发可软件配置触发延时,A/D转换方向从CHANY_SEL0 到CHANY_SEL15,转换通道数量由CHANY_NUM 配置,且CHANY_SEL0 到CHANY_SEL15 是任意配置的,可以完全相同,或完全不相同。

只要ADST 位保持为1,持续进行A/D 转换。当ADST 位被清0,当前A/D 转换完成后停止,A/D 转换器进入空闲状态。

若在A/D 转换过程中,软件更新ADC_ANY_CFG,ADC_CHANY0,ADC_CHANY1,硬件不会立即更新这些配置,只会在当前设置的通道都转换结束时更新,即下一个扫描周期开始新的通道转换。


应用还可以结合外部触发功能与DMA传输功能,实现TIM触发多通道 ADC 转换,DMA装载数据的功能。

具体可以参考官方的Lib样例程序:
http://www.mindmotion.com.cn/getfile.aspx?id=1219

下面通过寄存器配置多个通道,实现多路转换,多次切换任意通道,附上全部Reg版本Demo代码:

// Define to prevent recursive inclusion
#define _ADC_C_

// Files includes
#include "delay.h"
#include "sys.h"
#include "uart.h"
#include "adc.h"

#define ADCSCANNUM              4
#define RESULTLEN               4

vu8 ADCflag = 0;

u16 ADC_flag;

u16 ADCValue[ADCSCANNUM];
u16 varADC_ResultList[RESULTLEN+10][ADCSCANNUM];

void ADC_AnyChanChangeDefault(void)
{
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL0, 0<<ADC1_CHANY0_SEL0_Pos);
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL1, 2<<ADC1_CHANY0_SEL1_Pos);
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL2, 5<<ADC1_CHANY0_SEL2_Pos);
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL3, 7<<ADC1_CHANY0_SEL3_Pos);    
}

void ADC1_AnyChanMultiChannelInit(void)
{
    SET_BIT(RCC->AHBENR,RCC_AHBENR_GPIOA); //enable GPIOA clock
    SET_BIT(RCC->APB2ENR, RCC_APB2ENR_ADC1EN); //enable ADC1clock

    //set PA0,2,5,7 as Analog Input
    MODIFY_REG(GPIOA->CRL, (GPIO_CNF_MODE_MASK << GPIO_CRL_CNF_MODE_0_Pos), GPIO_CNF_MODE_AIN << GPIO_CRL_CNF_MODE_0_Pos);
    MODIFY_REG(GPIOA->CRL, (GPIO_CNF_MODE_MASK << GPIO_CRL_CNF_MODE_2_Pos), GPIO_CNF_MODE_AIN << GPIO_CRL_CNF_MODE_2_Pos);
    MODIFY_REG(GPIOA->CRL, (GPIO_CNF_MODE_MASK << GPIO_CRL_CNF_MODE_5_Pos), GPIO_CNF_MODE_AIN << GPIO_CRL_CNF_MODE_5_Pos);
    MODIFY_REG(GPIOA->CRL, (GPIO_CNF_MODE_MASK << GPIO_CRL_CNF_MODE_7_Pos), GPIO_CNF_MODE_AIN << GPIO_CRL_CNF_MODE_7_Pos);
    SET_BIT(RCC->APB2RSTR,RCC_APB2RSTR_ADC1RST); //ADC1reset
    CLEAR_BIT(RCC->APB2RSTR,(RCC_APB2RSTR_ADC1RST)); //reset   end

    //ADC configure soft trigger, single period mode
    //8 fractional frequency
    MODIFY_REG(ADC1->ADCFG, ADC_CFGR_PRE, ADCFG_ADCPRE_8);                      
    MODIFY_REG(ADC1->ADCR, \
               ADCR_ADMD_PERIOD | ADCR_ADMD_CONTINUE | ADCR_ALIGN_LEFT, \
               ADCR_ADMD_PERIOD);
    SET_BIT(ADC1->ADCHS, ADCHS_CHEN0|ADCHS_CHEN2|ADCHS_CHEN5|ADCHS_CHEN7);
    //single PERIOD mode , Data right-ALIGNED, discontinue 
    //enable 4 channels
    WRITE_REG(ADC1->ANYCFG, 4);
    //Enable chan 0,2,5, 7

    ADC_AnyChanChangeDefault();
    SET_BIT(ADC1->ADCR, ADC_CR_DMAEN);
    SET_BIT(ADC1->ADCFG,ADCFG_ADEN);//ADC1 enable
}

void ADC_AnyChanChangeFirst(void)
{
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL0, 2<<ADC1_CHANY0_SEL0_Pos);
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL1, 0<<ADC1_CHANY0_SEL1_Pos);
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL2, 5<<ADC1_CHANY0_SEL2_Pos);
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL3, 7<<ADC1_CHANY0_SEL3_Pos);    

}

void ADC_AnyChanChangeSecond(void)
{
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL0, 5<<ADC1_CHANY0_SEL0_Pos);
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL1, 0<<ADC1_CHANY0_SEL1_Pos);
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL2, 2<<ADC1_CHANY0_SEL2_Pos);
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL3, 7<<ADC1_CHANY0_SEL3_Pos);    
}

void ADC_AnyChanChangeThird(void)
{
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL0, 0<<ADC1_CHANY0_SEL0_Pos);
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL1, 7<<ADC1_CHANY0_SEL1_Pos);
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL2, 5<<ADC1_CHANY0_SEL2_Pos);
    MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL3, 2<<ADC1_CHANY0_SEL3_Pos);    
}

void M0_NVIC_Init(u32 NVIC_IRQChannelPriority, IRQn_Type NVIC_IRQChannel, FunctionalState NVIC_IRQChannelCmd)
{
    if (NVIC_IRQChannelCmd != DISABLE)
    {
        NVIC->IP[NVIC_IRQChannel >> 0x02] =
            (NVIC->IP[NVIC_IRQChannel >> 0x02] &
             (~(((u32)0xFF) << ((NVIC_IRQChannel & 0x03) * 8)))) |
            ((((u32)NVIC_IRQChannelPriority << 6) & 0xFF) << ((NVIC_IRQChannel & 0x03) * 8));

        NVIC->ISER[0] = 0x01 << (NVIC_IRQChannel & 0x1F);
    }
    else 
    {
        NVIC->ICER[0] = 0x01 << (NVIC_IRQChannel & 0x1F);
    }
}

void DMA1_Channel1_IRQHandler()
{
    if(DMA1->ISR & DMA_ISR_TCIF1)
    {
        DMA1->IFCR = DMA_IFCR_CTCIF1;
        ADCflag = 1;
    }
}

void DMAcheckStatus(u32 DMA_FLAG)
{
    while(1)
    {
        if(DMA1->ISR & DMA_FLAG)
        {
            DMA1->IFCR = DMA_FLAG;
            break;
        }
    }
}

void DMAdisable(DMA_Channel_TypeDef* DMAy_Channelx)
{
    //disable DMA_EN
    DMAy_Channelx->CCR &= 0xFFFFFFFE;
}

void DMA_AdctoM16_Init(void)
{
    DMA_Channel_TypeDef* dma_channel;
    dma_channel = DMA1_Channel1;
    RCC->AHBENR |= RCC_AHBENR_DMA1EN ;
    DELAY_Ms(5);                                  //wait DMAclock stabilization
    DMAdisable(dma_channel);
    dma_channel->CPAR = (u32) & (ADC1->DR);;   //DMA1 external  address
    dma_channel->CMAR = (u32)ADCValue;         //DMA1,memory  device address
    dma_channel->CCR &= ~DMA_CCR1_DIR;
    dma_channel->CNDTR = ADCSCANNUM;
    dma_channel->CCR &= ~DMA_CCR1_PINC;
    dma_channel->CCR |= DMA_CCR1_MINC;
    dma_channel->CCR |= DMA_CCR1_PSIZE_0;      //external  data 16bit
    dma_channel->CCR |= DMA_CCR1_MSIZE_0;      //memory  device data 16bit
    dma_channel->CCR |= DMA_CCR1_PL_0;         //Medium priority
    dma_channel->CCR |= DMA_CCR_CIRC;
    dma_channel->CCR &= ~DMA_CCR1_MEM2MEM;  //register memory device to memory  device mode

    M0_NVIC_Init(0, DMA1_Channel1_IRQn, ENABLE);                                
    dma_channel->CCR |= DMA_CCR1_TCIE;                              
    ADCflag = 0x0;                              
    dma_channel->CCR |= DMA_CCR1_EN;         //start DMA transmission
}

void Get_ResultListFun(u16  list_number)
{
    u16 chan  = 0;
    for(chan = 0; chan < ADCSCANNUM; chan++)
    {
        varADC_ResultList[list_number][chan] = ADCValue[chan];
    }
}

void ADC_ConvertSoftwareStart(ADC_TypeDef* adc, FunctionalState state)
{
    (state) ? (adc->ADCR |= ADC_CR_ADST) : (adc->ADCR &= ~ADC_CR_ADST);
}

u16 ADC1_MultiChanAnyDemo(void)
{
    ADC1_AnyChanMultiChannelInit();
    DMA_AdctoM16_Init();
    ADC_ConvertSoftwareStart(ADC1, ENABLE);
    ADCflag = 0;
    while(1)
    {
        if(ADCflag == 1)
        {
            ADCflag = 0;
            Get_ResultListFun(0);
            break;
        }
    }
    ADC_AnyChanChangeFirst();
    ADC_ConvertSoftwareStart(ADC1, ENABLE);
    while(1)
    {
        if(ADCflag == 1)
        {
            ADCflag = 0;
            Get_ResultListFun(1);
            break;
        }
    }
    ADC_AnyChanChangeSecond();
    ADC_ConvertSoftwareStart(ADC1, ENABLE);
    while(1)
    {
        if(ADCflag == 1)
        {
            ADCflag = 0;
            Get_ResultListFun(2);
            break;
        }
    }    
    ADC_AnyChanChangeThird();
    ADC_ConvertSoftwareStart(ADC1, ENABLE);
    while(1)
    {
        if(ADCflag == 1)
        {
            ADCflag = 0;
            Get_ResultListFun(3);
            break;
        }
    }        
    return 0;
}

转自:灵动微电子

围观 10

本文将介绍在MM32F013x上实现UART单线半双工的功能应用。

UART单线半双工简介

在使用数字舵机通讯时所用到的通信方式为UART通信,但舵机只有三根接线,除去VCC和GND,只有一条通信线,也就是说要实现双向通信,只能使用单线半双工模式。在单线半双工模式下,TX 和 RX 引脚在芯片内部互连。

01 配置流程

单线半双工模式是通过设置UART_SCR寄存器的HDSEL位,在这个模式里UART_SCR 寄存器的SCEN位必须保持清零状态。

在单线半双工模式下,TX和RX引脚在芯片内部互联,使用控制位”HALF DUPLEX SEL”(UART_SCR 中的 HDSEL 位) 选择半双工和全双工通信。

注意
当选择单线半双工模式时RX 不再被使用,当有数据需要发送的时候IO才会被UART驱动,没有数据传输时TX总是被释放,所以使用单线半双工需要外部加上拉。

除此之外通讯上和正常的UART模式类似。由于是单线半双工同一时刻总线上只能有一个节点发送,所以需要软件协议层去管理线上冲突防止多个设备同时发送,当 TXEN 位被设置时,只要数据一写到数据寄存器上,发送就继续。

02 UART_SCR寄存器描述

配置UART_SCR 的HDSEL为1

UART_SCR 寄存器的SCEN位清零


初始化UART1

从官网上下载MM32F013x例程,里面有UART普通模式的配置,在这个基础上我们直接调用UART_HalfDuplexCmd(UART1,ENABLE);函数接口将串口配置成单线半双工模式,然后IO口初始化只需要配置PA9 TX即可,如下:

void UART1_NVIC_Init(u32 baudrate)
{
    UART_InitTypeDef UART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

    //UART1 NVIC
    NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    //Baud rate
    UART_StructInit(&UART_InitStructure);
    UART_InitStructure.BaudRate = baudrate;
    //The word length is in 8-bit data format.
    UART_InitStructure.WordLength = UART_WordLength_8b;
    UART_InitStructure.StopBits = UART_StopBits_1;
    //No even check bit.
    UART_InitStructure.Parity = UART_Parity_No;
    //No hardware data flow control.
    UART_InitStructure.HWFlowControl = UART_HWFlowControl_None;
    UART_InitStructure.Mode = UART_Mode_Rx | UART_Mode_Tx;
    UART_Init(UART1, &UART_InitStructure);        

    UART_HalfDuplexCmd(UART1,ENABLE);  //Half Duplex Enable 
    UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE);    

    UART_Cmd(UART1, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);

    //UART1_TX   GPIOA.9
    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

功能验证测试

UART单线半双工功能测试我们现在拿两个MM32F0133的板子一个做主机一个做从机进行单线收发测试,主机先发送一包数据给从节点,当从节点收到这包数据后再把这包数据发回给主机,然后主机和从机两个板子PA9短接到一起,外部在加一个4.7K上拉电阻。

主机函数处理:

uint8_t txbuff[10]= {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xAA};
s32 main(void)
{
    CONSOLE_Init(115200);//UART2 printf打印
    UART1_NVIC_Init(115200);
    printf("UART Half Duplex TX Test \r\n");
    UART1_Send_Group(txbuff,sizeof(txbuff));
    printf("TX Data: "); 
    for(index=0;index

主机UART的中断服务函数里面,将接从机发送的数据存放在Rx_buff里面,当收到一包数据后通过printf打印到串口,和原始发送的数据进行对比。

void UART1_IRQHandler(void)
{
   u8 recvbyte;
   // Send packet
   if (UART_GetITStatus(UART1, UART_IT_TXIEN) != RESET) 
   {
      UART_ClearITPendingBit(UART1, UART_IT_TXIEN);
   }
   // Recv packet
   if (UART_GetITStatus(UART1, UART_ISR_RX) != RESET)
   {
      UART_ClearITPendingBit(UART1, UART_ISR_RX);
      recvbyte = UART_ReceiveData(UART1);
      Rx_buff[rx_cnt] = recvbyte;
      rx_cnt++;
      if(rx_cnt == 10)
      {
         gUartRxSta = 1;                 
         rx_cnt = 0;
      }
   }
}

从机函数处理:

s32 main(void)
{
   CONSOLE_Init(115200);//UART2 printf打印
   UART1_NVIC_Init(115200);
   printf("UART Half Duplex RX Test\r\n");    
   while(1)
   {            
      if(gUartRxSta == 1)//收到一包数据
      {
         gUartRxSta = 0;
         UART1_Send_Group(Rx_buff,10);
         memset(Rx_buff,0x00,10);
      }  
   }
   //return 0;
}

从机UART的中断服务函数里面,将接主机发送的数据存放在Rx_buff里面,当收到一包数据后通过单线半双工这个串口发送回去。

void UART1_IRQHandler(void)
{
    u8 recvbyte;
    // Send packet
    if (UART_GetITStatus(UART1, UART_IT_TXIEN) != RESET) 
    {
        UART_ClearITPendingBit(UART1, UART_IT_TXIEN);
    }
    // Recv packet
    if (UART_GetITStatus(UART1, UART_ISR_RX) != RESET)
    {
        UART_ClearITPendingBit(UART1, UART_ISR_RX);
        recvbyte = UART_ReceiveData(UART1);
        Rx_buff[rx_cnt] = recvbyte;
        rx_cnt++;
        if(rx_cnt == 10)
        {
            gUartRxSta = 1;
            rx_cnt = 0;
        }
    }
}

观察测试结果:

然后我们通过主机UART2 的printf打印可以看到主机TX Data 和从机返回的RX Data数据是一样的。


再看看下图逻辑分析仪抓取的逻辑波形,可以也可以看到主机发送的波形和从机返回的波形数据是一样的。


转自:灵动微电子

围观 13

本文是针对在MM32F013x上实现UART极性取反的功能应用。

在嵌入式领域,通常默认串口的电平是高电平为逻辑1,低电平为逻辑0,而在MM32的UART特性中是可以将高电平设置为逻辑0,低电平设置为逻辑1的,UART极性取反虽然不常用,但还是在特殊情况下是需要这个功能,比如硬件设计已经导致必须使用极性取反,否则电路就要改板或者增加反相电路。例如下图的UART隔离电路就需要UART发送极性取反功能。

图1 UART隔离电路

01、UART极性取反简介

UART极性取反下的电平与正常模式下的电平是相反的,正常情况下,UART空闲时电平是高,起始位是空闲状态下变成低电平,bit为1时电平也高。在UART极性取反状态下,空闲电平是低电平,起始位是高,bit为1时电平是低。

在数据接收发送寄存器中,数据也是可以反转的,原来的0变为1,原来的1变为0,这和电平极性反转是类似。需要特别注意的是,在极性反转的时候,起始位和结束位也都反转了,所有的信号电平都反转;而在数据寄存器中只反转了数据位,其中也包含了校验位,没有反转信号的起始位和结束位的极性。

图2 UART极性取反波形

上图是用逻辑分析仪抓取的UART极性取反的逻辑波形。红色字体:“IDLE”部分是空闲状态,“START”是起始位,后面“0~7”是数据的bit0~bit7,“STOP” 是停止位,“IDLE”是空闲(注意,逻辑分析仪设置反向,不然只能抓到波形,无法解析出数据)。

图3 UART极性取反控制位

UART->GCR寄存器描述

设置寄存器 TX_TOG位来使能UART发送极性取反功能。

如果UART接收极性也需要取反,则设置RX_TOG位来使能UART接收极性取反功能。

除了设置上述2个位外,其他部分的设置跟普通模式一模一样。

02、初始化UART1

从官网上下载MM32F013x例程,里面有UART普通模式的配置,主要是增加了UART->GCR的TX_TOG和RX_TOG位设置,如下:

void uart_nvic_init(u32 bound)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    UART_InitTypeDef UART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);

    //UART1 NVIC
    NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 3;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    //Baud rate
    UART_StructInit(&UART_InitStructure);
    UART_InitStructure.BaudRate = bound;
    //The word length is in 8-bit data format.
    UART_InitStructure.WordLength = UART_WordLength_8b;
    UART_InitStructure.StopBits = UART_StopBits_1;
    //No even check bit.
    UART_InitStructure.Parity = UART_Parity_No;
    //No hardware data flow control.
    UART_InitStructure.HWFlowControl = UART_HWFlowControl_None;
    UART_InitStructure.Mode = UART_Mode_Rx | UART_Mode_Tx;

    UART_Init(UART1, &UART_InitStructure);

    UART_ITConfig(UART1,UART_IT_RXIEN,ENABLE);

    UART1->GCR |= UART_GCR_TXTOG; //发送取反位
    UART1->GCR |= UART_GCR_RXTOG; //接收取反位

    UART_Cmd(UART1, ENABLE);

    //UART1_TX   GPIOA.9
    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    //UART1_RX    GPIOA.10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

03、功能测试

UART极性取反测试可以自发自收,需要将PA9和PA10短接,需要注意的是收发都需要配置成极性取反功能。在main函数所在.c文件里面,定义一个u8型全局变量UART_SendValue,UART_SendValue每隔500ms自加1,然后通过UART发送出去,依次循环。

u8 UART_SendValue = 0;
s32 main(void)
{
    DELAY_Init();
    LED_Init();
    uart_nvic_init(9600);
    while(1) {        
                 UartSendByte(++UART_SendValue);
                 DELAY_Ms(500);
             }
} 

在UART的中断服务函数里面,将接收到的数据存放在printBuf,这样可以在仿真界面下的watch窗口观看printBuf的值是否每隔500ms增加一次并且和UART_SendValue的值一致。

void UART1_IRQHandler(void)
{
    if (UART_GetITStatus(UART1, UART_ISR_RX) != RESET) 
    {
         UART_ClearITPendingBit(UART1, UART_ISR_RX);
        printBuf = UART_ReceiveData(UART1);
    }
}

下图仿真界面下可以看到printBuf和UART_SendValue的值是一致的。

图4 UART仿真watch窗口数据对比

下图逻辑分析仪抓取的逻辑波形,可以看到电平和分析到的数据都是一致的。

图5 UART极性0x09取反波形

转自:灵动微电子

围观 7

在之前的微课堂中和大家分享过灵动MM32系列MCU的UART通信实例,在此实例的基础上我们增加UART 9bit通信功能。UART 9bit通信的作用是第9bit用于标识是地址或数据,第9bit 为1标识是从机地址,为0标识是数据,此外UART通信的第9bit也可作为数据的同步帧位使用。

在双机通讯中,UART的8bit通信的第九位一般是奇偶校验位,而多机通讯中,第九位用于标识地址或数据,常用1表示后面的是从机地址,0表示后面的是数据。我们通常希望只有被寻址的接收者才被激活,来接收随后的数据,这样就可以减少由未被寻址的接收器的参与带来的多余的UART服务开销。未被寻址的设备可启用其静默功能置于静默模式。在静默模式里,任何接收状态位都不会被设置,所有接收中断被禁止。

以MM32F013x系列MCU的UART通信为例,通过一个示例Demo介绍UART 9bit通信的同步帧方式。

一、与UART 9bit通信相关的寄存器

图1

如上图1所示为UART通用控制寄存器UART_CCR,在MM32F013x UM手册的第489和第490页有关于该寄存器位的详细描述。本实例用到的UART通用控制寄存器UART_CCR位说明如下:

Bit11

B8EN(rw, reset:0x00)UART同步帧发送第9bit使能控制位。该位使能后校验使能PEN不起作用。

1:使能同步帧第9bit发送。

库函数设置:

UART_Enable9bit(UART1, ENABLE)

0:禁止同步帧第9bit发送。

库函数设置:

UART_Enable9bit(UART1, DISABLE)

Bit10

B8TOG(rw,reset:0x00)UART同步帧发送第9bit自动翻转控制位。

1:使能第9bit自动翻转。

库函数设置:

UART_Set9bitAutomaticToggle(UART1, ENABLE)

0:禁止第9bit自动翻转。

库函数设置:

UART_Set9bitAutomaticToggle(UART1, DISABLE)

注:在 B8TXD 和 B8POL 的值相同时,在配置完寄存器后传输的第二个数据开始翻转,第一个数据默认为地址位。

Bit8

B8TXD(rw,reset:0x00)UART同步帧发送数据第9bit。

1:发送同步帧第9bit为高电平。

库函数设置:

UART_Set9bitLevel(UART1, ENABLE)

0:发送同步帧第9bit为低电平。

库函数设置:

UART_Set9bitLevel(UART1, DISABLE)

二、程序配置

初始化MM32F013x UART1 9bit通信

从官网下载MM32F013x例程,以MM32F0133C7P的UART1通信为例,增加与UART 9bit通信相关的寄存器位的初始化,这里以库函数方式给出,增加的3行代码如下所示:

//Synchronous frame enable bit UART_CCR Bit11:B8EN
UART_Enable9bit(UART1, ENABLE);
//Synchronous frame transmit UART_CCR Bit8: B8TXD
UART_Set9bitLevel(UART1, DISABLE);
//Synchronous frame auto toggle UART_CCR Bit10:B8TOG
UART_Set9bitAutomaticToggle(UART1, ENABLE);

MM32F0133C7P UART1 9bit通信,初始化代码如下所示:

void bsp_UART1_9Bit_Init(u32 baudrate)
{
GPIO_InitTypeDef GPIO_InitStructure;
UART_InitTypeDef UART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);

GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
UART_StructInit(&UART_InitStructure);

UART_InitStructure.BaudRate = baudrate;
UART_InitStructure.WordLength = UART_WordLength_8b;
UART_InitStructure.StopBits = UART_StopBits_1;

UART_InitStructure.Parity = UART_Parity_No;
UART_InitStructure.HWFlowControl = UART_HWFlowControl_None;
UART_InitStructure.Mode = UART_Mode_Rx | UART_Mode_Tx;
UART_Init(UART1, &UART_InitStructure);

UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE);
UART_Enable9bit(UART1, ENABLE);
UART_Set9bitLevel(UART1, DISABLE);
UART_Set9bitAutomaticToggle(UART1, ENABLE);

NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

UART_Cmd(UART1, ENABLE);
}

1 ● 编写MM32F013x UART1中断函数

MM32F013x UART1中断服务函数,同时将收到的数据发送出去,代码如下所示:

void UART1_IRQHandler(void)
{
u8 res;

if(UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET)
{
//Receiving interrupts (data received must end at 0x0D 0x0a)
UART_ClearITPendingBit(UART1, UART_IT_RXIEN);

//read receive data.
res = UART_ReceiveData(UART1);

bsp_UART1_Send_Byte(res);
}
}

2 ● 编写MM32F013x UART1发送函数

使用之前工程的MM32F0133C7P UART1发送函数,代码如下所示:

void bsp_UART1_Send_Byte(u8 dat)
{
UART_SendData(UART1, dat);

while(!UART_GetFlagStatus(UART1, UART_FLAG_TXEPT));
}

MM32F013x UART1 9bit通信功能演示

在main函数中调用SysTick和UART1 9bit通信初始化函数,代码如下所示:

s32 main(void)
{
//SysTick init
DELAY_Init();
//UART1 9bit init
bsp_UART1_9Bit_Init(115200);

while(1)
{
bsp_UART1_Send_Byte(0x55);

DELAY_Ms(500);
}
}

编译工程代码,然后烧录软件到MM32F0133C7P核心板上,用逻辑分析仪抓取UART1 9bit通信发送数据和接收数据的波形:

演示发送数据:
以MM32F0133C7P发送0x55为例,使用逻辑分析仪抓取UART1 9bit通信发送数据的波形如下图所示。

图2

演示接收数据:
以上位机串口助手发送0xAA为例,使用逻辑分析仪抓取UART1 9bit通信收到的数据的波形,观察箭头所指第bit9位,如下图3所示:

图3

转自:灵动MM32MCU

围观 6

随着汽车电子技术的高速发展和广泛应用,实现智能化和网络化是汽车发展的必然趋势。为简化日益增加的汽车电控设备的线路连接,提升系统的可靠性和故障诊断水平,实现各电控设备之间的数据资源共享,并建成开发的标准化、模块化结构,汽车网络总线技术得到了很大发展。目前,已经开发出多种总线,如控制器局域网总线CAN、车内网络总线LIN、高速容错网络总线FlexRay、面向媒体的系统传输总线MOST、更高带宽和传输速率的车载以太网Ethernet等,这里给大家介绍在MM32F013x上实现LIN通信的功能应用。

Part.1 什么是LIN

LIN 是 Local Interconnect Network 的缩写,是基于 UART/SCI(Universal Asynchronous Receiver-Transmitter /Serial Communication Interface,通用异步收发器/串行通信接口)的低成本串行通信协议,可用于汽车、家电、办公设备等多种领域。本文主要针对在MM32F013x上实现LIN在分布式的汽车电子网络系统中的应用。

LIN总线特点

  • 低成本:几乎车规级微控制器都具备LIN 通信必需的硬件
  • 极少的信号线即可实现国际标准ISO9141 规定
  • 传输速率最高可达20Kbit/s
  • 单主控器/多从设备模式无需仲裁机制
  • 从节点不需晶振或陶瓷震荡器就能实现自同步,节省了从设备的硬件成本
  • 保证信号传输的延迟时间
  • 不需要改变LIN 从节点的硬件和软件就可以在网络上增加节点
  • 通常一个LIN 网络上节点数目小于12 个共有64 个标志符


Part.2 LIN总线 帧结构

帧(Frame)包含帧头(Header)和应答(Response)两部分。

帧头包括同步间隔段、同步段以及PID(Protected Identifier,受保护ID)段,应答包括数据段和校验和段。

如图所示,其中值“0”为显性电平(Dominant),值“1”为隐性电平(Recessive),总线上实行“线-与”:当总线上有大于等于一个节点发送显性电平时,总线呈显性电平;所有的节点都发送隐性电平或不发送信息(不发送任何信息时总线默认呈隐性电平)时,总线才呈现隐性电平,即显性电平起主导作用。


Part.3 程序配置

01、初始化MM32F013x UART1串口

从官网下载MM32F013x例程,参考MM32F0133C7P的UART例程的初始化以及中断服务函数,这里不在赘述。

02、LIN的发送

2.1 同步间隔段的发送

UART内部有Break信号的发送,通过使能LIN,即可实现LIN的间隔段,代码如下:

bool LINSendbreak(void)
{
uint16_t Tempcnt = 0;
UART2->CCR |= UART_CCR_LIN; //LIN Enable
UART2->CCR |= UART_CCR_BRK; //Send Break
while ((UART2->ISR & 0x00000080) == 0) //TXBRK_INTF
{
Tempcnt++;
if (Tempcnt > 5000)
return (false);
}
return (true);
}

2.2 数据的发送

数据的发送沿用原有的UART接口数据发送即可:

bool LINSendChar(uint8_t ch)
{
uint16_t Tempcnt = 0;

while ((UART2->CSR & UART_IT_TXIEN) == 0)//The loop is sent until it is finished
{
Tempcnt++;
if (Tempcnt > 5000)
return (false);
}
UART2->TDR = (ch & (uint16_t)0x00FF);
return (true);
}

2.3 数据包的发送

有了上面2个基础函数进行发送,现在来看下数据包的具体发送,从下图中可以看出数据包是根据保护段的定义来进行区分是发送信号帧还是诊断帧。


信号帧的数据就可以做成以下的方式:

bool LINSendMsg(void)
{
uint8_t check_sum, i;
frame_send.error = 0;
if (!LINSendbreak()) //Send Break
return (false);
if (!LINSendChar(0x55)) //Send Sync Byte
return (false);
msg_send.Data[0] = LINCalcParity(msg_send.Data[0]);
for (i = 0; i < 9; i++)
{
if (!LINSendChar(msg_send.Data[i])) //Send Data
return (false);
}
check_sum = LINCalcChecksum(msg_send.Data, 1);
if (!LINSendChar(check_sum)) //Send Checksum
return (false);
frame_send.state = IDLE;
return (true);
}

而诊断帧的就是:

bool LINSendID(void)
{
if (!LINSendbreak())
return (false);
if (!LINSendChar(0x55))
return (false);
msg_send.Data[0] = LINCalcParity(msg_send.Data[0]);
if (!LINSendChar(msg_send.Data[i]))
return (false);
return (true);
}

这样就可以在等待UART中断函数里收到从机返回的数据。

2.4 从机的接受与发送

从机收数据与正常的串口一样,只需要做BRK信号的处理即可,从机收到数据以后按正常的返回数据,返回数据就不需要发送BRK信号。

if (LIN_RxBuff[1] == LIN_PID_60_0x3C)
{
msg_send1.Data[0] = 0x3C;
msg_send1.Data[0] = LINCalcParity(msg_send1.Data[0]);
UART2->ICR |= UART_ICR_RXIDLE; //clear idle int bit
UART2->IER |= UART_IER_RXIDLE; //enable uart rx idle int
for (i = 1; i < 9; i++)
{
if (!LINSendChar(msg_send1.Data[i]))
return (false);
}
check_sum = LINCalcChecksum(msg_send1.Data, 1);
if (!LINSendChar(check_sum))
return (false);
}

Part.4 MM32F013x LIN通信功能演示

通过逻辑分析仪可以看到信号帧的数据:


而诊断帧数据就有从机回应的数据:


有这个演示作为基础就可以开发LIN的通讯产品了。

转自:灵动MM32MCU

围观 8

嵌入式工程师在开发产品中经常会用到MCU的片上UART和其它模块进行通信,为了在某些非正常的恶劣环境下能正常使用串口通信,可能需要对UART通信波特率进行自适应校准,在我们之前的微课堂中讲解过关于MM32通用MCU的UART串口通信方面的基础知识,这里我们增加基于灵动微电子MM32F013x 系列UART硬件自适应波特率的使用。

1、原理

UART硬件波特率自适应检测首个通信字节的位宽(1bit、2bit、4bit、8bit),检测前一个边沿和后一个边沿之间的位长,即检测前一个边沿为下降沿,后一个边沿为上升沿或前一个边沿为下降沿,后一个边沿为下降沿,可通过软件灵活配置。

本实例以串口工具作为上位机,MM32F013x的UART1作为下位机,MCU端初始化为非标准波特率9200,使能空闲中断及其他状态标志位,上位机切换不同的波特率,由于上位机和MCU端的波特率不同,可能出现通信失败的情况,启动UART硬件波特率自适应功能,即检测上位机发的首个字节位宽来识别上位机的波特率,MCU端通过硬件波特率自适应切换到对应的波特率,与上位机维持后续正常的通信。

如下图所示,以首字节0XF8为例,首字节位宽为4bit的原理说明:


2、程序配置

2.1 初始化MM32F013x UART1串口

从官网下载MM32F013x例程,这里我们在MM32F0133C7P的样例程序中添加注释并对代码修改。

#include "bsp_UART.h"
#include "led.h"
/*******************************************************************************
* 函数名称:void bsp_UART1_Init(u32 baudrate)
* 函数功能:初始化UART1 PA9/PA10分别作为UART1的TX/RX
* 输入参数:无
* 返回数值:无
******************************************************************************/
void bsp_UART1_Init(u32 baudrate)
{
    //GPIO初始化结构体
    GPIO_InitTypeDef GPIO_InitStructure;
    //UART初始化结构体
    UART_InitTypeDef UART_InitStructure;    
    //NVIC初始化结构体
    NVIC_InitTypeDef NVIC_InitStructure;

    //使能UART1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);        
    //使能GPIOA时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);  

    //开启GPIOA PA9复用于UART1_TX功能 
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_1);    
    //开启GPIOA PA10复用于UART1_RX功能 
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_1);   

    //UART1_TX   GPIOA.9
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;     
    //配置GPIOA.9 速度为高速50MHz
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    
    //配置GPIOA.9为复用推挽输出
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        
    //根据GPIO结构体初始化UART1_TX GPIOA.9
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    //UART1_RX GPIOA.10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;    
    //配置UART1_RX GPIOA.10为上拉输入
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;    
    //根据GPIO结构体初始化UART1_RX GPIOA.10
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    //串口波特率
    UART_InitStructure.UART_BaudRate = baudrate;    
    //字长为8位数据格式
    UART_InitStructure.UART_WordLength = UART_WordLength_8b;    
    //一位停止位
    UART_InitStructure.UART_StopBits = UART_StopBits_1;    
    //无奇偶校验位
    UART_InitStructure.UART_Parity = UART_Parity_No;    
    //无硬件数据流控
    UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;    
    //允许收发模式
    UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx;
    //根据UART结构体初始化串口UART1
    UART_Init(UART1, &UART_InitStructure);

    //硬件自动波特率检测第1个字节位的位宽前一个边沿为下降沿,后一个边沿为上升沿
    //___              _______
    //   |_ _ _ _|1 x x x x|        = Binary:xxxx 1000  Fall to Rise -> 1 start bit 
    //AutoBaudRate Mode Fall to Rise 4bit width,the first byte is 0xF8 use test

    UART_AutoBaudRateSet(UART1, ABRMODE_FALLING_TO_RISINGEDGE4BIT, ENABLE);

    //接收数据中断、接收帧错误中断、自动波特率结束中断、自动波特率错误中断、空闲中断
    UART_ITConfig(UART1, UART_IT_RXIEN | UART_ICR_RXFERRCLR | UART_ICR_ABRENDCLR |\ UART_ICR_ABRERRCLR | UART_ICR_RXIDLE | UART_IT_ERR, ENABLE);
    //使能UART1
    UART_Cmd(UART1, ENABLE);

    //UART1 NVIC中断优先级设置
    NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;
    //UART通道优先级0
    NVIC_InitStructure.NVIC_IRQChannelPriority = 0;          
    //IRQ通道使能
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        
    //根据指定的参数初始化NVIC寄存器
    NVIC_Init(&NVIC_InitStructure);    
}

2.2 编写MM32F013x UART1串口中断服务函数

继续在bsp_UART.c文件中编写MM32F013x UART1串口中断服务函数如下所示。

/*******************************************************************************
* 函数名称:void UART1_IRQHandler(void)
* 函数功能:串口1 UART1中断服务程序
* 输入参数:无
* 返回数值:无
*******************************************************************************/
//自动波特率帧错误标志
u8 Auto_BaudRate_FraErr_Flag = 0;
void UART1_IRQHandler(void)                    
{
    u8 Res;

    //UART1接收中断
    if(UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET)  
    {
        //清UART1接收中断标志
        UART_ClearITPendingBit(UART1,UART_IT_RXIEN);            
        //读取UART1接收到的数据
        Res = UART_ReceiveData(UART1);        
        //UART1接收数据缓存起来,最大接收UART1_REC_LEN个字节 
        UART1_Rx_Buf[UART1_Rx_Cnt] = Res;        
        //UART1作接收缓存溢出判断,最大接收UART1_REC_LEN个字节
        if(UART1_Rx_Cnt < UART1_REC_LEN-1)
        {
            //还有数据要接收,接收计数变量自加
            UART1_Rx_Cnt++;
        }
        else
        {
            UART1_Rx_Cnt = 0;
        }
    }

    //帧错误中断标志位
    if(UART_GetITStatus(UART1, UART_IER_RXFERR) != RESET)
    {
        //自动波特率帧错误标志置1
        Auto_BaudRate_FraErr_Flag = 1;
        //清帧错误中断标志位
        UART_ClearITPendingBit(UART1,UART_IER_RXFERR);
    }

    //接收数据帧错误中断
    if(UART_GetITStatus(UART1, UART_ICR_RXFERRCLR) != RESET)
    {     
        UART_ClearITPendingBit(UART1,UART_ICR_RXFERRCLR);
    }

    //空闲中断硬件波特率自校准
    if(UART_GetITStatus(UART1, UART_ICR_RXIDLE) != RESET)
    {
        UART_ClearITPendingBit(UART1,UART_ICR_RXIDLE);

        //自动波特率帧错误标志
        if(Auto_BaudRate_FraErr_Flag == 1)
        {
            Auto_BaudRate_FraErr_Flag = 0;

    //----------------Check MM32F013x UART_AutoBaudRateHard----------
    //___              _______
    //   |_ _ _ _|1 x x x x|  = Binary:xxxx 1000  Fall to Rise -> 1 start bit 
    //AutoBaudRate Mode Fall to Rise 4bit width,the first byte is 0xF8 use test

        UART_AutoBaudRateSet(UART1, ABRMODE_FALLING_TO_RISINGEDGE4BIT, ENABLE);    
        }
    }

    //自动波特率错误中断清除位
    if(UART_GetITStatus(UART1, UART_ICR_ABRERRCLR) != RESET)
    {      
        UART_ClearITPendingBit(UART1,UART_ICR_ABRERRCLR);  
    }

    //自动波特率结束中断清除位
    if(UART_GetITStatus(UART1, UART_ICR_ABRENDCLR) != RESET)
    {
        UART_ClearITPendingBit(UART1,UART_ICR_ABRENDCLR);
    }
}

2.3 MM32F013x UART1串口接收函数

在bsp_UART.h文件中宏定义UART1波特率、接收字节长度,变量声明以及UART1接收数据函数和发送数据函数声明。

 void UART1_Recv_Task(void)
{   
   //收到的数据原样返回到串口上位机
   UART_SendBytes(UART1,UART1_Rx_Buf, UART1_Rx_Cnt);
}

2.4 MM32F013x UART串口发送函数

在bsp_UART.c文件中编写MM32F013x UART1发送数据函数,发送单字节数据和发送多字节数据函数分别如下所示:

/*******************************************************************************
* 函数名称:void UART_SendByte(UART_TypeDef* UARTx,u8 dat)
* 函数功能:UART发送单字节数据
* 输入参数:UARTx:UART1/UART2;dat:待发送的数据
* 返回数值:无
*******************************************************************************/
void UART_SendByte(UART_TypeDef* UARTx,u8 dat)
{
    UART_SendData(UARTx, dat);

    while(!UART_GetFlagStatus(UARTx, UART_FLAG_TXEPT));
}

/*******************************************************************************
* 函数名称:void UART_SendBytes(UART_TypeDef* UARTx,u8* buf, u16 len)
* 函数功能:UART发送多字节数据
* 输入参数:UARTx:UART1/UART2;buf:待发送的数据;len:待发送数据的长度
* 返回数值:无
*******************************************************************************/
void UART_SendBytes(UART_TypeDef* UARTx,u8* buf, u16 len)
{
    while(len--)
    {
        UART_SendByte(UARTx,*buf++);       
    }
}

3、MM32F013x UART硬件 自适应波特率的功能演示

在main.c文件的main函数里初始化bsp_UART1_Init(9200)串口初始化函数,在while(1)大循环里调用测试UART1硬件自动波特率收发数据函数:UART1_Recv_Task();这里以检测UART1通信首字节为4bit宽为例,模式为前一个边沿为下降沿,后一个边沿为上升沿。

MCU端设置非标准波特率9200,我们通过上位机以不同的波特率发送F8进行硬件波特率自适应,自适应完成后UART1就切换到对应的波特率,MCU端收到数据后直接返回给上位机。


本文转自:灵动MM32MCU

围观 13

前面我们介绍了新出USB设备类型WebUSB,其中使用MM32 MCU实现WebUSB功能。既然可以通过网页与USB设备通信,那是否可以做别的功能,比如USB-DFU,当然是可以的,我们通过网页进行DFU功能,即WebDFU功能。因此我们本节我们讲解如何在MM32 MCU实现WebDFU功能。

DFU是使用USB作为微控制器和编程工具之间的通信信道,通常是PC。在DFU类规格书说明中指出所有的DFU命令、状态和数据交换都需要通过端点0进行。命令集和基本协议都定义好的,但是上层协议(数据格式,错误信息等)是客户相关的。也就是说DFU类并没有定义数据传输格式(s19,16进制,纯2进制等等)

由于一个设备同时进行DFU操作和正常运行功能活动是不现实的,因此在DFU操作期间必须停止正常运行活动,这就意味着设备必须改变运行模式——也就是说我们在进行固件更新时比如打印机不再是打印机了,它是一个flash存储器编程器。但是支持DFU的设备不能自主改变模式,这需要接受外部(人或者主机操作系统)的干预。

对于DFU功能,其完成实现固件升级可以分为4个不同阶段。

01、枚举

设备把自身的一些特性告知主机,嵌入在设备正常运行描述符中的一个DFU类接口描述符和相关的函数符能够完成这个目的,并且能够为通过控制管道的类专用的请求提供目标。

02、DFU枚举

主机和设备同意开始固件升级,主机向设备发出USB复位,设备发出第二个描述符集合,并且为传输阶段做准备,这会是相应设备的运行时驱动无效,并使得DFU驱动不受其他目标为该设备通信妨碍,重编程设备的固件。

03、传输

主机将固件映像传输给设备,功能描述符中的参数用于确保非易失性存储器编程的块大小和时序的正确性。状态请求用于保持主机和设备之间的同步。

04、显示

一旦设备向主机报告重新编程完成,主机箱设备则发送usb复位,设备重枚举并执行升级后的固件。为了保证只有DFU驱动加载,有必要的在枚举DFU描述符集合改变id-product字段。

本节我们来讲解如何在MM32 MCU实现WebDFU设备功能,对于MM32 MCU来说,实现WebDFU只需要在之前程序基础上修改添加部分代码即可,按照开源的WebDFU协议加入功能。

本次我们采用MM32L373 miniboard作为测试开发板。为了方便大家使用MM32 MCU的WebDFU设备功能,我们重新封装好全部代码,用户不需要自己配置那些麻烦的描述符等参数,只需要知道用之前的单一设备函数即可。

软件资源如下:

对于MM32 MCU的WebDFU,我们可以配置WebDFU的参数。

#define USBD_DFU_DNLOAD_ENABLE      1

#define USBD_DFU_UPLOAD_ENABLE      0

#define USBD_DFU_STRDESC              L"USB_DFU"

#define USBD_DFU_XFERBUF_SIZE        1024

#define USBD_WEBUSB_VENDOR_CODE   0x21

#define USBD_WEBUSB_BASE_LANDING_URL "devanlai.github.io/webdfu/dfu-util/?vid="

#define USBD_WEBUSB_LANDING_URL     CONCAT_MACRO_TO_STRING(USBD_WEBUSB_BASE_LANDING_URL, USBD_DEVDESC_IDVENDOR)

#define USBD_WEBUSB_ORIGIN_URL      "devanlai.github.io/"

#define USBD_WEBUSB_IF_NUM          USBD_DFU_IF_NUM

参数设置如上。当进行DFU升级时候,可以看到电脑上显示的设备名称为USB_DFU,就是配置的USBD_DFU_STRDESC参数。

在使用MM32 WebDFU功能之前先调用USB初始化函数来初始化USB协议栈。

int main(void)

{

// USB Device Initialization and connect

usbd_init();

usbd_connect(__TRUE);

while (!usbd_configured())     // Wait for USB Device to configure

{

}

while (1)

{     

……

}

}

然后依然和之前一样只是在WebUSB基础上修改添加WebDFU相关参数函数接口即可,代码如下:

//DFU初始化

void usbd_dfu_init(void)

{

DFU_Reset();

current_write_addr = 0;

}

//USB DFU开始升级

BOOL USBD_DFU_StartUpgrade(void) {

error_t err = flash_manager_init(target_device);

current_write_addr = target_device.flash_start;

switch (err) {

case ERROR_SUCCESS:

initialized = true;

break;

case ERROR_RESET:

case ERROR_ALGO_DL:

case ERROR_ALGO_DATA_SEQ:

case ERROR_INIT:

case ERROR_SECURITY_BITS:

case ERROR_UNLOCK:

DFU_SetStatus(DFU_STATUS_ERR_PROG);

break;

case ERROR_ERASE_SECTOR:

case ERROR_ERASE_ALL:

DFU_SetStatus(DFU_STATUS_ERR_ERASE);

break;

case ERROR_WRITE:

DFU_SetStatus(DFU_STATUS_ERR_WRITE);

break;

case ERROR_FAILURE:

case ERROR_INTERNAL:

default:

DFU_SetStatus(DFU_STATUS_ERR_UNKNOWN);

break;

}



return (err == ERROR_SUCCESS) ? (__TRUE) : (__FALSE);

}

//复位目标

static bool reset_target(bool error_condition) {

current_write_addr = 0;

if (initialized) {

error_t err = flash_manager_uninit();

switch (err) {

case ERROR_SUCCESS:

if (config_get_auto_rst()) {

// Target is reset and run by the uninit

} else if (!error_condition) {

// Reset and run the target at the end of a successful upgrade

target_set_state(RESET_RUN);

}

break;

case ERROR_RESET:

case ERROR_ALGO_DL:

case ERROR_ALGO_DATA_SEQ:

case ERROR_INIT:

case ERROR_SECURITY_BITS:

case ERROR_UNLOCK:

DFU_SetStatus(DFU_STATUS_ERR_PROG);

break;

case ERROR_ERASE_SECTOR:

case ERROR_ERASE_ALL:

DFU_SetStatus(DFU_STATUS_ERR_ERASE);

break;

case ERROR_WRITE:

DFU_SetStatus(DFU_STATUS_ERR_WRITE);

break;

case ERROR_FAILURE:

case ERROR_INTERNAL:

default:

DFU_SetStatus(DFU_STATUS_ERR_UNKNOWN);

break;

}

initialized = false;

return (err == ERROR_SUCCESS);

}



return true;

}

//USB DFU结束升级

BOOL USBD_DFU_FinishUpgrade(void) {

return reset_target(false) ? (__TRUE) : (__FALSE);

}

//USB DFU写数据

BOOL USBD_DFU_WriteBlock(const U8 *buffer, U16 blockSize) {

error_t err = flash_manager_data(current_write_addr, (U8*)buffer, blockSize);

switch (err) {

case ERROR_SUCCESS:

current_write_addr += blockSize;

break;

case ERROR_RESET:

case ERROR_ALGO_DL:

case ERROR_ALGO_DATA_SEQ:

case ERROR_INIT:

case ERROR_SECURITY_BITS:

case ERROR_UNLOCK:

DFU_SetStatus(DFU_STATUS_ERR_PROG);

break;

case ERROR_ERASE_SECTOR:

case ERROR_ERASE_ALL:

DFU_SetStatus(DFU_STATUS_ERR_ERASE);

break;

case ERROR_WRITE:

DFU_SetStatus(DFU_STATUS_ERR_WRITE);

break;

case ERROR_FAILURE:

case ERROR_INTERNAL:

default:

DFU_SetStatus(DFU_STATUS_ERR_UNKNOWN);

break;

}

return (err == ERROR_SUCCESS);

}

这样我们就完成MM32 MCU的WebDFU功能,将程序下载到板子中,USB插上电脑,电脑上会枚举出USB DFU。在USB DFU枚举成功后,我们需要检查是否真的可以被WebDFU网页识别。

打开https://devanlai.github.io/webdfu/dfu-util/通过该网页检测WebDFU工作状态,网页如下图所示:


通过点击Connect就可以像之前WebUSB设备一样弹出窗口选择我们的USB DFU设备,然后就可以在Vendor ID(hex)窗口看到序列号。可以通过网页上的Detach DFU和Download以及Upload进行DFU升级动作。

以上就是MM32 MCU USB的WebDFU功能,具体的WebDFU协议过程想详细了解看dapboot,大家可以自由发挥修改底层和上层的代码实现自己的WebDFU网页端和设备端。

本文转自:灵动MM32MCU

围观 18

页面

订阅 RSS - 灵动微电子