MM32F013x

相信很多做变频设计或者电机控制领域的朋友们都熟悉一项重要技术——使用单电阻电流重构技术采样相电流,实现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移相功能可以配置相关寄存器的配置即可实现,操作简单。

转自:灵动微电子

围观 170

在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;
}

转自:灵动微电子

围观 346

在I2C中,通信是借助设备地址寻址实现的,大致可以分为两类:一对多、多对多通信。在多主机通信时,从机如果想接收多个主机的数据,就需要使用到从机多地址的功能。

本文是针对在MM32F013x上实现I2C多地址的功能应用。

配置方式

MM32F013x的多地址功能是通过配置I2C_SLAVMASK寄存器来实现的。通过I2C_SAR寄存器配置从机地址后,再配置I2C_SLAVMASK寄存器。MM32F013x是支持7位地址和10位地址格式的,所以需要按照自己的实际情况配置I2C_SLAVMASK寄存器的低九位,I2C_SLVRCVADDR寄存器会给出真实地址。

注意:在I2C中有些特殊地址是不会产生响应的。


寄存器描述


相关软件的实现

01、功能验证

制作一个主机设备发送16个字节的字符串,连接两块开发板的SDA/SCL,通过主机向从机发送数据,分别将目标地址设置为:0xA0、0xA2、0xA4、0xA6、0xA8、0xAA、0xAC、0xAE进行通信测试。

1.1 主机程序

void I2C_WRTest(void)
{
    Write(0x00, gTxData, 0x10);
    DELAY_Ms(100);
    Read(0x00, gRxData, 0x10);
    DELAY_Ms(100);
}

s32 main(void)
{
    DELAY_Init();
    I2C_WRInit();

    for(int i=0;i<8;i++)
    {
        I2C_Cmd(I2C1, DISABLE);
        I2C_SetDeviceAddr(I2C1, EEPROM_ADDR+2*i);
        I2C_Cmd(I2C1, ENABLE);
        I2C_WRTest();
    }
    While(1)
    {

    }
}

1.2 从机初始化部分

void I2C_NVIC_SlaveInit(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    GPIO_InitTypeDef  GPIO_InitStructure;

    NVIC_InitStructure.NVIC_IRQChannel = I2C1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);

    GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_1);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_1);
    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; //Need extra plus pull
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_6;  //I2C1 remap IO port
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;  // clock input
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    I2C_SlaveMode();
}

void I2C_NVIC_SlaveTest()
{
    u32 i;
    while( gTxFlag | gRxFlag);
    for(i = 0; i < 16; i++) 
    {
        printf("TX data%d is  : %x \r\n", i, gTxBuff[i]);
    }
    for(i = 0; i < 16; i++)
    {
        printf("RX data%d is  : %x \r\n", i, gRxBuff[i]);
    }
    gTxFlag = 1;
    gRxFlag = 1;
}

void I2C_SlaveMode()
{
    I2C_InitTypeDef I2C_InitStructure;
    I2C_StructInit(&I2C_InitStructure);

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

    I2C_DeInit(I2C1);
    I2C_InitStructure. Mode = (I2C_CR_MASTER >> 1); 
    I2C_InitStructure. OwnAddress = 0;
    I2C_InitStructure. Speed = I2C_CR_STD;
    I2C_InitStructure. ClockSpeed = 100000;
    I2C_Init(I2C1, &I2C_InitStructure);

    I2C_ITConfig( I2C1, I2C_IT_RD_REQ, ENABLE );//Read request
    I2C_ITConfig( I2C1, I2C_IT_RX_FULL, ENABLE );//Receive interrupt

    I2C_SendSlaveAddress(I2C1, 0xA8);

    I2C1->SLVMASK = 0x0F;
    I2C_Cmd(I2C1, ENABLE);
}

s32 main(void)
{
    CONSOLE_Init(115200);
    I2C_NVIC_SlaveInit();
    I2C_NVIC_SlaveTest();
    while(1) 
    {
    }
}

以上程序将设备配置为从机模式,使能读请求/接收缓冲非空中断,从机地址配置为0xA8,SLVMASK配置为0x0F,表示地址的低四位不进行比较,则从机设备可以从总线上接受地址为0xA0、0xA2、0xA4、0xA6、0xA8、0xAA、0xAC、0xAE的数据包。

1.3 从机多地址中断处理程序

void I2C1_IRQHandler(void)
{
    u16 stop_flag, start_flag;
    if(I2C_GetITStatus(I2C1, I2C_IT_RD_REQ)) 
    { 
        I2C_ClearITPendingBit(I2C1,I2C_IT_RD_REQ);   
        //The master has sent a read request from the slave
        I2C1->DR = (u8)gTxBuff[gTxCnt];
        I2C_TX_EmptyCheck(I2C1);
        while(!(I2C1->SR & 0x4));
        I2C_GenerateSTOP( I2C1, ENABLE );

        gTxCnt ++;
        if(gTxCnt == 16) 
        {
            gTxCnt = 0;
        }
        gTxFlag = 0;
    }
    // interrupt receive
    if(I2C_GetITStatus(I2C1, I2C_IT_RX_FULL)) 
    { 
        //Master sends slave receive
        gRxBuff[gRxCnt++] = I2C_ReceiveData(I2C1);
        while(!(I2C1->SR & 0x4));
        I2C_GenerateSTOP( I2C1, ENABLE );
        if(gRxCnt == 16)
        {
            gRxCnt = 0;
        }
        gRxFlag = 0;
    }
    stop_flag = I2C1->STOP;
    start_flag = I2C1->START;
    if((stop_flag  & start_flag) != ((u32)RESET)) //slave receive
    {
        I2C_ClearITPendingBit(I2C1,I2C_IT_STOP_DET);
        I2C_ClearITPendingBit(I2C1,I2C_IT_STOP_DET);   
    }
}

1.4 测试结果

通过UART1打印接收及发送的结果发现每次结果都如下图所示:


波形如下:









结合上述结果与调试过程,可知从机可以接收地址为:0xA0、0xA2、0xA4、0xA6、0xA8、0xAA、0xAC、0xAE的数据包。

转自:灵动微电子

围观 173

本文将介绍在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<10;index++)
    {
        printf(" %x ",txbuff[index]);
    }    
    printf("\r\n");        
    while(1) 
    {            
       if(gUartRxSta == 1) //收到一包数据
       {
           gUartRxSta = 0;        
           printf("RX Data: ");             
           for(index=0;index<10;index++)
           {
               printf(" %x ",Rx_buff[index]);
           }                    
               printf("\r\n");                               
               memset(Rx_buff,0x00,10);                    
        }
    }
    //return 0;
}

主机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数据是一样的。


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


转自:灵动微电子

围观 450

本文是针对在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取反波形

转自:灵动微电子

围观 240

在上一次的灵动微课堂中和大家分享过MM32F013x-UART 9bit通信实例,本次微课堂在此实例的基础上实现UART多处理器通信。MM32F013x系列MCU支持UART多处理器通信,其工作原理是主从机设备采用复用漏极开路,主从机外部接上拉电阻,在空闲时使从机处于静默模式,主机要控制从机执行任务时主机发送指令唤醒从机并发送数据控制从机执行相应任务。

1、UART静默模式

MM32F013x系列MCU UART静默模式的特点

  • 任何接收状态位都不会被设置
  • 所有的接收中断都被禁止
  • UART_CCR寄存器中的RWU位被置1。RWU可以被硬件自动控制或在某个条件下由软件写入。

根据UART_CCR寄存器中的WAKE位状态,UART多处理器通信有二种方法进入或退出静默模式分别是:

  • WAKE 位被设置0:进行空闲总线检测。
  • WAKE 位被设置1:进行地址标记检测。

空闲总线检测

空闲帧唤醒可以同时唤醒所有从机,在从机处于静默模式时主机发送空闲帧(即所有位均为1的数据)实现多个从机同步被唤醒。

地址标记检测

地址标记唤醒可以唤醒单个从机,从机进入静默模式时,主机向从机寻址发送地址帧,从机自动比对地址,地址配对正确则该从机被唤醒,否则继续进入静默模式。这样只有被主机寻址的从机才被唤醒激活并接收数据,从而减少未被寻址的从机参与带来的多余的UART服务开销。

2、配置流程

与UART多处理器通信相关的主要寄存器有UART通用控制寄存器、UART接收地址寄存器UART_RXADDR和UART接收掩码寄存器UART_RXMASK其描述如下寄存器表所示:

图1

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

Bit13

WAKE(rw,reset:0x00)唤醒方法,该位决定UART多机通信从机唤醒的方法。

1:地址标记唤醒。
库函数设置:
UART_WakeUpConfig(UART1,UART_WakeUp_AddressMark);

0:空闲总线唤醒。
库函数设置:
UART_WakeUpConfig(UART1, UART_WakeUp_IdleLine);

Bit12

RWU(rw, reset:0x00)接收唤醒,该位用来决定是否把UART置于静默模式。该位可以由软件设置或清除。当唤醒序列到来时,硬件也会自动将其清零。

1:接收器处于静默模式。
库函数设置:
UART_ReceiverWakeUpCmd(UART1, ENABLE);

0:接收器处于正常工作模式。
库函数设置:
UART_ReceiverWakeUpCmd(UART1, DISABLE);

注:在设置地址标记唤醒时,如果接收 buffer 非空则不能软件修改。

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)

图2

如上图2所示为UART接收地址寄存器UART_RXADDR,在MM32F013x UM手册的第491页有关于该寄存器位的详细描述。本实例UART多处理器通信要用到的相关于UART_RXADDR接收地址寄存器位的说明如下:

Bit31:8:

Reserved,始终读为0x00

Bit7:0:

RXADDR(rw,reset:0x00)UART 同步帧数据本机匹配地址。

库函数设置:
UART_SetRXAddress(UART1, SLAVEADDR);

其中地址参数SLAVEADDR可以设置为宏定义形式。

如果 RXMASK =0xFF时接收到的同步帧数据与本机匹配地址相同时,产生RXB8_INTF。地址 0是广播地址,收到后都会响应。

图3

如上图3所示为UART接收掩码寄存器UART_RXMASK,在MM32F013x UM手册的第492页有关于该寄存器位的详细描述。本实例UART多处理器通信要用到的相关于UART_RXMSK接收掩码寄存器位的说明如下:

Bit31:8:

Reserved,始终读为0x00

Bit7:0:

RXMASK(rw,reset:0xFF)UART数据位全为“0”时,接收到任何数据都产生同步帧中断请求。如果数据位为“1”,RDR和RXADDR的相应位匹配时,产生同步帧中断请求。

库函数设置:

UART_SetRXMASK(UART1,SLAVEADDR); 其中地址参数SLAVEADDR可以设置为宏定义形式。

根据上文与UART 9bit多处理器通信相关的寄存器位的描述,本实例从机唤醒模式使用标记从机地址方式,在MM32F013x-UART 9bit通信实例的基础上增加从机UART 9bit多处理器通信相关的寄存器位的初始化,这里以库函数方式给出,增加的4行代码如下所示:

//Wake up method
UART_WakeUpConfig(UART1, UART_WakeUp_AddressMark);
//Synchronous frame match address
UART_SetRXAddress(UART1, SLAVEADDR);
//Synchronous frame match address mask
UART_SetRXMASK(UART1,SLAVEADDR);
//Receive wake up method
UART_ReceiverWakeUpCmd(UART1, ENABLE);

3、程序配置

01、初始化主机MM32F013x UART1 9bit通信

本实例使用MM32F0133C7P核心板作为UART多处理器通信,主机初始化代码如下所示:

bsp_UART1_Master_irq_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_Enable9bit(UART1, ENABLE);
    UART_Set9bitLevel(UART1, DISABLE);
    UART_Set9bitAutomaticToggle(UART1, ENABLE);
    UART_Init(UART1, &UART_InitStructure);

    UART_ITConfig(UART1, UART_IT_RXIEN, 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);
}

02、初始化从机MM32F013x UART1 9bit通信

MM32F0133C7P UART1从机初始化代码如下所示:

注意:多从机通信,初始化从机串口时需要修改从机地址宏为0x01、0x02等。

#define SLAVEADDR  (0x01)
void bsp_UART1_Slave_irq_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_OD;
   GPIO_Init(GPIOA, &GPIO_InitStructure);

   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
   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_WakeUpConfig(UART1, UART_WakeUp_AddressMark);
   UART_WakeUpConfig(UART1, UART_WakeUp_IdleLine);
   UART_SetRXAddress(UART1, SLAVEADDR);
   UART_SetRXMASK(UART1,0x02);
   UART_ReceiverWakeUpCmd(UART1, ENABLE);
   UART_Enable9bit(UART1, ENABLE);
   UART_Set9bitLevel(UART1, DISABLE);
   UART_Set9bitAutomaticToggle(UART1, ENABLE);
   UART_Init(UART1, &UART_InitStructure);
   UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE);

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

   UART_Cmd(UART1, ENABLE);
}

03、编写MM32F013x UART1主机中断服务函数

MM32F0133C7P UART1主机中断服务函数,代码如下所示:

#define RX_MASTER_LEN   (3)
u8 g_Rx_Master_Buf[RX_MASTER_LEN] = {0x00};
u8 g_Rx_Master_Cnt = 0;
void UART1_IRQHandler(void)
{
     u8 res;
     if(UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET) 
     {
          UART_ClearITPendingBit(UART1, UART_IT_RXIEN);
          res = UART_ReceiveData(UART1);
          g_Rx_Master_Buf[g_Rx_Master_Cnt] = res;
          if(g_Rx_Master_Cnt < RX_MASTER_LEN-1)
          {
             g_Rx_Master_Cnt++;
          }
          else
          {
             g_Rx_Master_Cnt = 0;
          }
     }
}

04、编写MM32F013x UART1从机中断服务函数

MM32F0133C7P UART1从机中断服务函数,代码如下所示:

#define RX_SLAVE_LEN    (3)
u8 g_Rx_Slave_Buf[RX_SLAVE_LEN] = {0x00};
u8 g_Rx_Slave_Cnt = 0;
void UART1_IRQHandler(void)
{
     u8 res;
     if(UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET) 
     {
         UART_ClearITPendingBit(UART1, UART_IT_RXIEN);
         res = UART_ReceiveData(UART1);
         g_Rx_Slave_Buf[g_Rx_Slave_Cnt] = res;
         if(g_Rx_Slave_Cnt < RX_SLAVE_LEN-1)
         {
             g_Rx_Slave_Cnt++;
         }
         else
         {
             g_Rx_Slave_Cnt = 0;
         }  
     }
}

05、编写MM32F013x UART1主从机

通用发送数据函数

MM32F0133C7P UART1主从机通用发送函数,代码如下所示:

u8 g_Tx_Master_Buf[2] = {0xAA,0x55};
void bsp_UART_Send_Byte_Data(UART_TypeDef* uart,u8 data)
{
    UART_SendData(uart, data);
    while(!UART_GetFlagStatus(uart, UART_FLAG_TXEPT));
}
void bsp_UART_Send_Bytes_Data(UART_TypeDef* uart, u8* buf, u16 len)
{
    while(len--)
    {
        bsp_UART_Send_Byte_Data(uart,*buf++);
    }
}

06、编写MM32F013x UART1主机发送从机地址函数

MM32F0133C7P UART1主机发送从机地址函数,代码如下所示:

u8 g_Tx_Master_Buf[2] = {0xAA,0x55};
#define SLAVEADDR1       (0x01)
#define SLAVEADDR2       (0x02)
void bsp_UART_Send_SlaveAddr(UART_TypeDef* uart,u8 data)
{
    UART_SendData(uart, data);
    while(!UART_GetFlagStatus(uart, UART_FLAG_TXEPT));
}

07、编写MM32F013x UART1主机按键

发送从机地址和数据函数

宏定义按键GPIO端口和管脚,本实例只用到MM32F013x核心板的PA0作为KEY1对应核心板。

#define KEY1_GPIO_Port  GPIOA
#define KEY1_Pin         GPIO_Pin_0
#define KEY2_GPIO_Port  GPIOB
#define KEY2_Pin         GPIO_Pin_2
#define KEY3_GPIO_Port  GPIOB
#define KEY3_Pin        GPIO_Pin_10
#define KEY4_GPIO_Port  GPIOB
#define KEY4_Pin        GPIO_Pin_11
#define KEY1         GPIO_ReadInputDataBit(KEY1_GPIO_Port,KEY1_Pin)  //read key1
#define KEY2         GPIO_ReadInputDataBit(KEY2_GPIO_Port,KEY2_Pin)  //read key2
#define KEY3         GPIO_ReadInputDataBit(KEY3_GPIO_Port,KEY3_Pin)  //read key3
#define KEY4         GPIO_ReadInputDataBit(KEY4_GPIO_Port,KEY4_Pin)  //read key4
#define KEY1_DOWN_VALUE     1   //KEY1
#define KEY2_DOWN_VALUE     0   //KEY2
#define KEY3_DOWN_VALUE     0   //KEY3
#define KEY4_DOWN_VALUE     0   //KEY4
#define KEY1_PRES           1   //KEY1
#define KEY2_PRES           2   //KEY2
#define KEY3_PRES           3   //KEY3
#define KEY4_PRES           4   //KEY4
//Init Key GPIO
void bsp_Key_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB ,ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin  = KEY1_Pin;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
    GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin  = KEY2_Pin;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(KEY2_GPIO_Port, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin  = KEY3_Pin;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(KEY3_GPIO_Port, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin  = KEY4_Pin;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(KEY4_GPIO_Port, &GPIO_InitStructure);    
}

u8 bsp_Key_Scan(u8 mode)
{
    static u8 key_up = 1;
    if(mode)
    {
        key_up = 1;
    }
    if(key_up && ((KEY1 == KEY1_DOWN_VALUE) || (KEY2 == KEY2_DOWN_VALUE) || \
                  (KEY3 == KEY3_DOWN_VALUE) || (KEY4 == KEY4_DOWN_VALUE))) 
    {
        DELAY_Ms(10);

        key_up = 0;

        if(KEY1 == KEY1_DOWN_VALUE)
        {
            return KEY1_PRES;
        }
        else if(KEY2 == KEY2_DOWN_VALUE)
        {
            return KEY2_PRES;
        }
        else if(KEY3 == KEY3_DOWN_VALUE)
        {
            return KEY3_PRES;
        }
        else if(KEY4 == KEY4_DOWN_VALUE)
        {
            return KEY4_PRES;
        }
    }
    else if((KEY1 != KEY1_DOWN_VALUE) && (KEY3 != KEY3_DOWN_VALUE) && \
            (KEY4 != KEY4_DOWN_VALUE) && (KEY2 != KEY2_DOWN_VALUE))
    {
        key_up = 1;
    }

    return 0;
}

u8 Key_Nnum = 0;
void bsp_Process_Key_Task(void)
{
    static u8 Key_Value = 0;
    Key_Value = bsp_Key_Scan(0);
    switch(Key_Value)
    {
        case KEY1_PRES:           
           if(Key_Nnum == 0)
           {
                Key_Nnum = 1;
                bsp_UART_Send_SlaveAddr(UART1, SLAVEADDR1); //Send SlaveAddr1
                bsp_UART_Send_Bytes_Data(UART1, g_Tx_Master_Buf, sizeof(g_Tx_Master_Buf)); //Send data           
            }
            else if(Key_Nnum == 1)
            {
                Key_Nnum = 0;                
                bsp_UART_Send_SlaveAddr(UART1, SLAVEADDR2); //Send SlaveAddr2
                bsp_UART_Send_Bytes_Data(UART1, g_Tx_Master_Buf, sizeof(g_Tx_Master_Buf)); //Send data 
            }          
            break;
        case KEY2_PRES:
            break;
        case KEY3_PRES:
            break;
        case KEY4_PRES:
            break;
        default :
            break;        
    }
}

08、编写MM32F013x UART1主机

接收从机返回的数据函数

处理MM32F0133C7P UART1主机接收数据函数,代码如下所示:

 void bsp_UART_Master_Rec_Task(void)
{
  if(((g_Rx_Master_Buf[0] == SLAVEADDR1 ) || (g_Rx_Master_Buf[0] == SLAVEADDR2)) && (g_Rx_Master_Buf[1] == 0xAA) && (g_Rx_Master_Buf[2] == 0x55))
   {
      LED1_TOGGLE();   
      g_Rx_Master_Cnt = 0;
       memset(g_Rx_Master_Buf,0,sizeof(g_Rx_Master_Buf));
    }
}

09、编写MM32F013x UART1从机

接收主机发送的数据函数

处理MM32F0133C7P UART1主机接收数据函数,代码如下所示:

注意:g_Rx_Slave_Buf[1]为主机发送给从机的地址由从机接收判断,多从机通信需要修改从机地址宏:#define SLAVEADDR (0x02)等。

void bsp_UART_Slave_Rec_Task(void)
{
  if((g_Rx_Slave_Buf[0] == SLAVEADDR) && (g_Rx_Slave_Buf[1] == 0xAA) && (g_Rx_Slave_Buf[2] == 0x55))
  {
     LED1_TOGGLE();   
     bsp_UART_Send_Bytes_Data(UART1, g_Rx_Slave_Buf, sizeof(g_Rx_Slave_Buf));
     g_Rx_Slave_Cnt = 0;
     memset(g_Rx_Slave_Buf,0,sizeof(g_Rx_Slave_Buf));
  }
}

10、MM32F013x UART1 9bit多处理器通信功能演示

MM32F0133C7P多处理器通信功能演示:

主机main函数中调用DELAY_Init、LED_Init、bsp_Key_GPIO_Init和主机串口初始化函数bsp_UART1_Master_irq_9Bit_Init,在while(1)后台调用按键发送从机地址和发送数据函数bsp_Process_Key_Task以及处理接收从机返回数据函数bsp_UART_Master_Rec_Task代码如下所示:

s32 main(void)
{
    //SysTick init
    DELAY_Init();
    //LED Init
    LED_Init();
    //Key GPIO Init
    bsp_Key_GPIO_Init();
    //UART1 9Bit irt Init
    bsp_UART1_Master_irq_9Bit_Init(115200);
    while(1) 
    {
        //Key processing multi-processor communication
        bsp_Process_Key_Task();
        //Processing master receiving tasks
        bsp_UART_Master_Rec_Task();
    }
}

从机main函数中调用LED_Init和从机串口初始化函数bsp_UART1_Slave_irq_9Bit_Init,在while(1)后台调用处理从机接收任务函数bsp_UART_Slave_Rec_Task代码如下所示:

s32 main(void)
{
    //LED Init
    LED_Init();
    //UART1 9bit init
    bsp_UART1_Slave_irq_9Bit_Init(115200);
    while(1) 
    {
        //Processing slave receiving tasks
        bsp_UART_Slave_Rec_Task(); 
    }
}

分别编译以上主机和从机工程代码,然后烧录软件到MM32F0133C7P核心板上,烧录多从机时需修改从机工程代码的从机地址宏SLAVEADDR,编译后再烧录。本实例从机烧录0x01和x02为例作为演示,即1台主机和2台从机作为演示。

4、接线方式

MM32F0133C7P多处理器通信UART主机和从机接线方法:

各从机TX线与连接,RX线与连接,从机线与连接的TX接到主机RX端,从机线与连接的RX接到主机的TX端,主从机分别外接5.1~10K值范围之一的上拉电阻,本实例接5.1K上拉电阻。

MM32F0133C7P UART多处理器通信演示1台主机和2台从机分别发送和接收数据:

主机第1次按下MM32F0133C7P核心板上的S1按键,主机发送1字节从机地址0x01,接着发送2字节数据0xAA和0x55,从机1(地址0x01)在静默模式下自动比对主机寻址从机的地址0x01比对成功从机1被唤醒继续接收0xAA和0x55数据,收到完整的3字节数据时从机LED1状态翻转LED1亮并原样返回收到的数据给主机,此时从机2仍处于静默模式,主机完全收到从机1返回的3字节数据即0x01,0XAA,0x55时主机LED1状态翻转LED亮,实物现象如下图4所示。

图4(上从机1、中主机、下从机2)

主机第2次按下MM32F0133C7P核心板上的S1按键,主机发送1字节从机地址0x02,接着发送2字节数据0xAA和0x55,从机2(地址0x02)在静默模式下自动比对主机寻址从机的地址0x02比对成功从机2被唤醒继续接收0xAA和0x55数据,收到完整的3字节数据时从机LED1状态翻转LED1亮并原样返回收到的数据给主机,此时从机1仍处于静默模式其LED1维持上次点亮状态,主机完全收到从机2返回的3字节数据时即0x02,0XAA,0x55时主机LED1状态翻转LED灭,实物现象如下图5所示:

图5(上从机1、中主机、下从机2)

5、波形抓取

逻辑分析仪抓取到的MM32F0133C7多处理器通信波形图如下图6和图7所示:

图6上波形红色框注为主机发送:0x01、0xAA、0x55寻址从机1,从机(地址0x01)从静默模式唤醒并收到主机发送的数据0xAA和0x55时原样返回给主机如图6下波形蓝色框注:0x01、0xAA、0x55,此时从机2(地址0x02)并未作响应仍处于静默模式。

图6

图7上红色框注为主机发送:0x02、0xAA、0x55寻址从机2,从机(地址0x02)从静默模式唤醒并收到主机发送的数据0xAA和0x55时原样返回给主机如图7下波形蓝色框注:0x02、0xAA、0x55,此时从机1(地址0x01)并未作响应仍处于静默模式。

图7

转自:灵动MM32MCU

围观 110

在之前的微课堂中和大家分享过灵动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

围观 160

随着汽车电子技术的高速发展和广泛应用,实现智能化和网络化是汽车发展的必然趋势。为简化日益增加的汽车电控设备的线路连接,提升系统的可靠性和故障诊断水平,实现各电控设备之间的数据资源共享,并建成开发的标准化、模块化结构,汽车网络总线技术得到了很大发展。目前,已经开发出多种总线,如控制器局域网总线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

围观 394

嵌入式工程师在开发产品中经常会用到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

围观 93

页面

订阅 RSS - MM32F013x