MM32F013x

一. MM32F013x片内FLASH

MM32F013x芯片内嵌高达64KB的程序FLASH存储空间,由64页组成,每页大小为1KB;用户的可执行程序从FLASH的起始地址0x08000000开始存放,支持读、写操作,页擦除,整片擦除,可通过 16 位(半字)方式编程写入闪存,其擦写寿命可达 20000 次。闪存控制器在读取数据时,支持带预取缓冲器的数据接口,以支持 MCU 运行在更高的主频。FLASH的每页都可以独立的设置写保护功能,以防止芯片内部可执行程序被复制,增强产品的安全性。

如果用户的可执行程序没有占满FLASH的存储空间,那我们就可以利用剩余的FLASH空间来当作存储器使用,可以存储一些用户配置、运行日志等记录,同时对于存储数据量不大的情况,可以省去外置的存储芯片,节省BOM成本。

本篇通过移植开源的EasyFlash组件,使用MM32F013x内置的空闲的FLASH存储空间来实现用户数据存储记录的功能。

二. EasyFlash介绍

EasyFlash是一款开源的轻量级嵌入式FLASH存储器库,方便开发者更加轻松的实现基于FLASH存储器的常见应用开发,非常适合智能家居、可穿戴、工控、医疗、物联网等需要断电存储功能的产品,资源占用极低,支持各种MCU片上存储器。该库主要包括三大实用功能:

ENV

快速保存产品参数,支持写平衡(磨损平衡)及掉电保护功能

EasyFlash不仅能够实现对产品的设定参数或运行日志等信息的掉电保存功能,还封装了简洁的增加、删除、修改及查询方法,降低了开发者对产品参数的处理难度,也保证了产品在后期升级时拥有更好的扩展性。让Flash变为NoSQL(非关系型数据库)模型的小型键值(Key-Value)存储数据库。

IAP

在线升级再也不是难事儿

该程序库封装了IAP(In-Application Programming)功能常用的接口,支持CRC32校验,同时支持Bootloader及Application的升级。

Log

无需文件系统,日志可直接存储在FLASH上

非常适合应用在小型的不带文件系统的产品中,方便开发人员快速定位、查找系统发生崩溃或死机的原因。同时配合EasyLogger一起使用,轻松实现日志的FLASH存储功能。

本篇章我们需要通过MM32F013x来实现ENV环境变量的存取功能,也可以叫做KV数据库模式;目前ENV功能有两种主要模式,一种为V4.0版本带来的NG(Next Generation)模式,还有一种为延续V3.0版本的Legacy模式。

对于NG模式相比较于Legacy模式具有以下新特性:

  • 更小的资源占用,内存占用几乎为0;V4.0以前版本会使用额外的RAM空间进行缓存;
  • ENV的值类型支持任意类型、任意长度,相当于直接memcpy变量至Flash;V4.0 之前只支持存储字符串;
  • ENV操作效率比以前的模式高,充分利用剩余空闲区域,擦除次数及操作时间显著降低;
  • 原生支持磨损平衡、掉电保护功能;V4.0之前需要占用额外的Flash扇区;
  • ENV支持增量升级,固件升级后ENV也支持升级;

三. EasyFlash ENV模式对比


四. EasyFlash资源占用

最低要求:ROM:6KB,RAM:0.1KB。

五. EasyFlash移植说明

下载最新的EasyFlash源代码:
https://github.com/armink/EasyFlash

01、目录结构

先解压下载好的源码包,文件的目录结构大致如下:


02、拷贝port文件

将\easyflash\目录下的inc、src及port文件夹拷贝到项目中:


03、添加工程文件

添加\easyflash\src\及\easyflash\port\文件夹下的源文件到项目工程目录中:


04、添加路径

根据项目需求,选择性添加\easyflash\src\中的其他源码文件,\easyflash\inc\文件夹到编译的头文件目录列表中:


六. EasyFlash接口移植

01、移植初始化

EasyFlash移植初始化。可以传递默认环境变量,初始化EasyFlash移植所需的资源等等。

/**
 * Flash port for hardware initialize.
 *
 * @param default_env default ENV set for user
 * @param default_env_size default ENV size
 *
 * @return result
 */
EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size)
{
    EfErrCode result = EF_NO_ERR;

    *default_env = default_env_set;
    *default_env_size = sizeof(default_env_set) / sizeof(default_env_set[0]);
    return result;
}

02、读取FLASH

/**
 * Read data from flash.
 * @note This operation's units is word.
 *
 * @param addr flash address
 * @param buf buffer to store read data
 * @param size read bytes size
 *
 * @return result
 */
EfErrCode ef_port_read(uint32_t addr, uint32_t *buf, size_t size)
{
    EfErrCode result = EF_NO_ERR;
    /* You can add your code under here. */
    uint8_t *Data = (uint8_t *)buf;
    for(size_t i = 0; i < size; i++, addr++, Data++)
    {
        *Data = *(uint8_t *)addr;
    }
    return result;
}

03、擦除FLASH

/**
 * Erase data on flash.
 * @note This operation is irreversible.
 * @note This operation's units is different which on many chips.
 *
 * @param addr flash address
 * @param size erase bytes size
 *
 * @return result
 */
EfErrCode ef_port_erase(uint32_t addr, size_t size)
{
    EfErrCode result = EF_NO_ERR;

    /* make sure the start address is a multiple of EF_ERASE_MIN_SIZE */
    EF_ASSERT(addr % EF_ERASE_MIN_SIZE == 0);
    /* You can add your code under here. */
    FLASH_Status Status;
    size_t Number;
    Number = size / 1024;
    if((size % 1024) != 0) Number++;
    FLASH_Unlock();
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
    for(size_t i = 0; i < Number; i++)
    {
        Status = FLASH_ErasePage(addr + 1024 * i);
        FLASH_ClearFlag(FLASH_FLAG_EOP);
        if(Status != FLASH_COMPLETE)
        {
            printf("\r\nErase Error!!!");
            result = EF_ERASE_ERR; break;
        }
    }
    FLASH_Lock();
    return result;
}

04、写入FLASH

/**
 * Write data to flash.
 * @note This operation's units is word.
 * @note This operation must after erase. @see flash_erase.
 *
 * @param addr flash address
 * @param buf the write data buffer
 * @param size write bytes size
 *
 * @return result
 */
EfErrCode ef_port_write(uint32_t addr, const uint32_t *buf, size_t size)
{
    EfErrCode result = EF_NO_ERR;
    EF_ASSERT(size % 4 == 0);
    /* You can add your code under here. */
    FLASH_Unlock();
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
    for(size_t i = 0; i < size; i+=4, buf++, addr+=4)
    {
        FLASH_ProgramWord(addr,   *buf);
        FLASH_ClearFlag(FLASH_FLAG_EOP);
        uint32_t Data = *(uint32_t *)addr;
        if(Data != *buf)
        {
            printf("\r\nWrite Error!!!");
            result = EF_WRITE_ERR; break;
        }
    }
    FLASH_Lock();
    return result;
}

05、对环境变量缓冲区加锁

/**
 * lock the ENV ram cache
 */
void ef_port_env_lock(void)
{
    /* You can add your code under here. */
    __disable_irq();
}

06、对环境变量缓冲区解锁

/**
 * unlock the ENV ram cache
 */
void ef_port_env_unlock(void)
{
    /* You can add your code under here. */
    __enable_irq();
}

07、打印调试日志信息

static char log_buf[128];

/**
 * This function is print flash debug info.
 *
 * @param file the file which has call this function
 * @param line the line number which has call this function
 * @param format output format
 * @param ... args
 *
 */
void ef_log_debug(const char *file, const long line, const char *format, ...)
{
#ifdef PRINT_DEBUG
    va_list args;
    /* args point to the first variable parameter */
    va_start(args, format);
    /* You can add your code under here. */
    ef_print("\r\n[Debug](%s:%ld) ", file, line);
    vsprintf(log_buf, format, args);
    ef_print("%s", log_buf);
    printf("\r\n");
    va_end(args);
#endif
}

08、打印普通日志信息

/**
 * This function is print flash routine info.
 *
 * @param format output format
 * @param ... args
 */
void ef_log_info(const char *format, ...)
{
    va_list args;
    /* args point to the first variable parameter */
    va_start(args, format);
    /* You can add your code under here. */
    ef_print("\r\n[LogInfo]");
    /* must use vprintf to print */
    vsprintf(log_buf, format, args);
    ef_print("%s", log_buf);
    printf("\r\n");
    va_end(args);
}

09、无格式打印信息

/**
 * This function is print flash non-package info.
 *
 * @param format output format
 * @param ... args
 */
void ef_print(const char *format, ...)
{
    va_list args;
    /* args point to the first variable parameter */
    va_start(args, format);
    /* You can add your code under here. */
    vsprintf(log_buf, format, args);
    printf("%s", log_buf);
    va_end(args);
}

10、默认环境变量集合

/* default environment variables set for user */
static const ef_env default_env_set[] =
{
    {"startup_times", "0"},
    {"pressed_times", "0"},
};

七. 设置参数

配置时需要修改项目中的ef_cfg.h文件,开启、关闭、修改对应的宏即可。

01、FLASH最小擦除单元

操作方法:修改EF_ERASE_MIN_SIZE宏对应值即可,单位:byte

/* The minimum size of flash erasure. May be a flash sector size. */
#define EF_ERASE_MIN_SIZE      1024  /* @note you must define it for a value */

02、FLASH写入粒度

操作方法:修改EF_WRITE_GRAN宏对应值即可,单位:bit,仅支持:1/8/32

/* the flash write granularity, unit: bit. only support 1(nor flash)/ 8/ 32 */    
#define EF_WRITE_GRAN          32    /* @note you must define it for a value */

03、备份区起始地址

操作方法:修改EF_START_ADDR宏对应值即可

/* backup area start address */
#define EF_START_ADDR    (0x08000000 + 50 * 1024)  /* @note you must define it for a value */

04、环境变量区总容量

操作方法:修改ENV_AREA_SIZE宏对应值即可

/* ENV area size. It's at least one empty sector for GC. So it's definition must
more then or equal 2 flash sector size. */
#define ENV_AREA_SIZE    (2 * 1024)    /* @note you must define it for a value if you used ENV */

在配置时需要注意以下几点:

1、所有的区域必须按照EF_ERASE_MIN_SIZE对齐;

2、环境变量分区大少至少为两倍以上EF_ERASE_MIN_SIZE;

3、从V4.0开始ENV的模式命名为NG模式,V4.0之前的称之为LEGACY遗留模式;遗留模式已经被废弃,不再建议继续使用;如果需要继续使用遗留模式,请EasyFlash的V3.X版本。

八. 测试验证

每次使用前,务必先执行easyflash_init()方法对EasyFlash库及所使用的FLASH进行初始化,保证初始化没问题后,再使用各功能的API方法。如果出现错误或断言,需根据提示信息检查移植配置及接口。

01、编写系统启动次数记录

/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
void EasyFlash_Demo(void)
{
    uint32_t startup_times = 0;
    char *old_startup_times, new_startup_times[30] = {0};
    old_startup_times = ef_get_env("startup_times");
    startup_times = atol(old_startup_times);
    startup_times++;
    sprintf(new_startup_times, "%d", startup_times);
    printf("\r\nThe system now startup %d times\r\n\r\n", startup_times);
    ef_set_env("startup_times", new_startup_times);
    ef_save_env();
}

02、编写按键次数记录

/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
void EasyFlash_Demo(void)
{
    uint32_t startup_times = 0;
    char *old_startup_times, new_startup_times[30] = {0};
    old_startup_times = ef_get_env("startup_times");
    startup_times = atol(old_startup_times);
    startup_times++;
    sprintf(new_startup_times, "%d", startup_times);
    printf("\r\nThe system now startup %d times\r\n\r\n", startup_times);
    ef_set_env("startup_times", new_startup_times);
    ef_save_env();
}

九. 运行测试

01、下载程序后,第一次运行

检测到EasyFlash没有默认配置或者说存储的数据CRC校验错误时,EasyFlash都会将存储恢复到默认值;随后将当前系统启动的次数记录到片机FLASH中,按键2次按键后,对按键的次数进行存储记录,如下图所示:


02、开发板重新上电运行

此时片内FLASH已经存在记录数据,等EasyFlash初始化成功后,取出当前的启动次数记录,进行操作后,更新启动次数存储记录;接着按键用户按键,我们会发现按键的次数接在上次的记录数据后面继续累加了,如下图所示:


参考源码:
https://github.com/Samplecode-MM32/MM32MCU_Code

从 EasyFlash V4.1 后,基于 EasyFlash 全新设计开发的 FlashDB 开源项目也正式上线,新集成了时序数据库、多分区管理,多数据库实例等功能,也从一定程度上提升了整体性能。

来源:灵动微电子

围观 35

嵌入式应用中经常会遇到需要保存一些数据,比如配置信息等等,为了在设备关机或掉电情况下数据不丢失,我们通常做法是会将数据保存在存储区,可以在FLASH主存储区、备份寄存器和选项字节等存储用户数据。

MM32F013x系列芯片的嵌入式闪存高达 64K 字节 ,整个片内 FLASH由两部分组成:一部分是主存储块,另一部分是信息存储块。主存储块除了被用于存储用户代码,也可被模拟成EEPROM来存储用户数据;在信息存储块中,包括了保护字节、保密空间、系统存储器 ISP 和选项字节四部分,其中除了系统存储器 ISP区域用户不可使用外,用户可以通过对应的操作流程对其它区域进行读写操作,于用户而言,选项字节用户数据区也可以被用来存储2个字节长度的有效数据。

本文将重点介绍如何在MM32F013x上实现用FLASH 选项字节存储用户数据以及使能硬件独立看门狗模式的功能。

选项字节介绍

MM32F013x 系列MCU 的选项字节主要用于存储用户对芯片的配置信息及用户关键数据,主要有写保护使能、看门狗模式切换等等不同配置内容,FLASH 控制器可以通过对这些值的设置来选择不同的系统功能选项。选项字节区块的前16字节,每两个字节组成一个正反对,其中用户只需要设置低位的字节,高位由系统自动填充为其反码。

选项字节的组成如下表所示(位 15 ∼ 8 中的值为位 7 ∼ 0 中选项字节 0 的反码):


注意:在写保护值中,一个比特位对应四页,即 4096 字节,其它详情请参见UM手册。

USER:字节2,用户字节,配置看门狗模式、停机复位模式、待机复位模式以及BOOT1。

DATA0:字节4,数据字节0,由用户存储数据。

DATA1:字节6,数据字节1,由用户存储数据。

WRP0:字节8,写保护字节0,存储对主存储块的写保护设置。

WRP1:字节10,写保护字节1,存储对主存储块的写保护设置。

01、相关寄存器

除了之前熟悉的FLASH_ACR 、FLASH_KEYR、FLASH_CR 以及FLASH_AR 等FLASH 基础寄存器外,主要还需要关注以下相关的寄存器:



02、擦除流程

选项字节区块擦除操作流程的具体步骤如下:


1. 检查FLASH_CR寄存器的LOCK位,如未被解锁则执行解锁操作。

if((FLASH->CR & FLASH_CR_LOCK) == SET )
{
    FLASH->KEYR = FLASH_KEY1 ;
    FLASH->KEYR = FLASH_KEY2 ;
}

2. 检查FLASH_CR寄存器的OPTWRE位,如未置位则执行解锁OPTWRE操作。

if((FLASH->CR & FLASH_CR_OPTER) == RESET)
{
    FLASH->OPTKEYR = FLASH_KEY1 ;
    FLASH->OPTKEYR = FLASH_KEY2 ;
}

3. 将闪存选项字节块基地址写入FLASH_AR寄存器。

FLASH->AR = OB_BASE ;

4. 设置FLASH_CR寄存器的OPTER位为1,选择选项字节擦除操作。

FLASH->CR |= FLASH_CR_OPTER ;

5. 设置FLASH_CR寄存器的STRT位为1。

FLASH->CR |= FLASH_CR_STRT ;

6. 等待FLASH_SR寄存器的BSY位变为0,再读出选项字节寄存器中所有地址来验证擦除操作,直到流程结束。

do{               
       ret = (((FLASH->SR & FLASH_FLAG_BSY)) ? FLASH_BUSY:            \
              ((FLASH->SR & FLASH_FLAG_PGERR) ? FLASH_ERROR_PG:         \
              ((FLASH->SR & FLASH_FLAG_WRPRTERR) ? 
                FLASH_ERROR_WRP : FLASH_COMPLETE))) ;
       EraseTimeout-- ;
       for (i = 0xFF; i != 0; i--) ;
  }while((ret == FLASH_BUSY) && (EraseTimeout!= 0x00)) ;
  FLASH->CR = 0 ;
  FLASH->SR = FLASH_SR_EOP | FLASH_SR_WRPRTERR | FLASH_SR_PGERR ;        
  return (FLASH_Status)((EraseTimeout== 0x00) ? FLASH_TIMEOUT : ret) ;

由于选项字节只有16字节,因此,擦除时是整个选项字节都将被擦除,此时需要注意,将有效的用户数据及配置信息提前保存到内存中再进行擦除。

03、编程流程

选项字节区块编程操作流程的具体步骤如下:


1. 检查FLASH_CR寄存器的LOCK位,如未被解锁则执行解锁操作。

if((FLASH->CR & FLASH_CR_LOCK) == SET )
    {
        FLASH->KEYR = FLASH_KEY1 ;
        FLASH->KEYR = FLASH_KEY2 ;
    }

2. 检查FLASH_CR寄存器的OPTWRE位,如未置位则执行解锁OPTWRE操作。

if((FLASH->CR & FLASH_CR_OPTER) == RESET)
{
    FLASH->OPTKEYR = FLASH_KEY1 ;
    FLASH->OPTKEYR = FLASH_KEY2 ;
}

3. 设置FLASH_CR寄存器的OPTPG位为1,选择编程操作。

FLASH->CR |= FLASH_CR_OPTPG ;

4. 写入要编程的半字到指定的地址,启动编程操作。

__IO u16 temp ;
temp = (u16)(~data) ;
temp = (temp << 8) & 0xFF00 ;
temp = temp | (u16)data ;
*(__IO u16*)address = temp ;

5. 等待FLASH_SR寄存器的BSY位变为0,可选读目标地址数据,以确保半字编程成功,直到所有编程流程结束。

do{               
       ret = (((FLASH->SR & FLASH_FLAG_BSY)) ? FLASH_BUSY:            \
              ((FLASH->SR & FLASH_FLAG_PGERR) ? FLASH_ERROR_PG:         \
              ((FLASH->SR & FLASH_FLAG_WRPRTERR) ? 
                FLASH_ERROR_WRP : FLASH_COMPLETE))) ;
       ProgramTimeout -- ;
       for (i = 0xFF; i != 0; i--) ;
  }while((ret == FLASH_BUSY) && (ProgramTimeout != 0x00)) ;
   FLASH->CR = 0 ;
   FLASH->SR = FLASH_SR_EOP | FLASH_SR_WRPRTERR | FLASH_SR_PGERR ;        
   return (FLASH_Status)((ProgramTimeout == 0x00) ? FLASH_TIMEOUT : ret) ;

软件实现步骤

下面列出配置代码,实现开启硬件IWDG独立看门狗以及将用户数据存储在DATA0和DATA1。

01、主程序初始化

s32 main(void)
{
    /* Systick delay init */
    DELAY_Init();
    /* Uart1 init */
    CONSOLE_Init(115200) ;
    printf("\r\nMM32F013x OptionByte Demo %s %s\r\n", __DATE__, __TIME__);

    /* OptionByte test entry */
    FLASH_OptionByte_Entry() ;

    /* Set the HWIWDG parameters,no need to turn on LSI or IWDG_Enable */
    Set_IWDG(IWDG_Prescaler_64, 999);
    printf("\r\n IWDG reload value is set to 1.6s !\r\n ");

    while (1)
    {
        /* Feed the HW_IWDG if needed*/
        Feed_IWDG() ;
        DELAY_Ms(1000) ;
        printf("\r\n System is going on. Feed the IWDG...\r\n "); 
    }
}

以上为整个软件工程的入口主函数,平台相关的堆栈设置、时钟设置以及FLASH延迟设置等等都在STARTUP_MM32F013X_KEIL.S和SYSTEM_MM32f013x.C文件中已经完成,默认主频为内部时钟HSI倍频到72M,上电后程序会跳转到主函数。初始化好延时和串口后,进入到测试选项字节的功能函数中,并且接下来的初始化中还配置了IWDG的重装载值以及分频系数,在while(1)循环里选择是否喂狗看不同现象。

02、OptionByte 实验函数

void FLASH_OptionByte_Entry(void)
{
    volatile uint32_t TempOptionByteValue = 0;
    uint8_t WriteUserData[2] ={0x01,0x02} ;

    /* Unlock The FLASH Controller */
    FLASH_Unlock();

    if( FLASH_GetFlagStatus(FLASH_FLAG_OPTERR) == RESET )
    {      
        TempOptionByteValue = FLASH_GetUserOptionByte();
        printf("\r\nTempOptionByteValue=0x%2x\r\n ", TempOptionByteValue);

        if(((uint8_t)(TempOptionByteValue >> 10) != WriteUserData[0]) &&           \
            ((uint8_t)(TempOptionByteValue >> 18) != WriteUserData[1]) )
        {/* User Data0 != 0x01 , User Data1 != 0x02*/           
            /* Erase Option Bytes */
            FLASH_Status status = FLASH_EraseOptionBytes();

            /* Write User Data0 */
            status = FLASH_ProgramOptionByteData(0x1FFFF804, WriteUserData[0]) ;
            /* Write User Data1 */
            status = FLASH_ProgramOptionByteData(0x1FFFF806, WriteUserData[1]) ;
            /* Write User Byte to enable HW mode of IWDG */
            status = FLASH_ProgramOptionByteData(0x1FFFF802,0xFE) ;
            printf("\r\n UserDatas and UserByte are written!\r\n ");       

            printf("\r\n INPORTANT! Need to reset the system ……\r\n ");
            /* Lock The Flash Program Erase Controller */
            FLASH_Lock();
            /* System Reset */
            NVIC_SystemReset();
        }
        else
        {                    
            if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET)
            {
                printf("\r\n RCC_FLAG_IWDGRST is set!\r\n ");
                /* Clear reset flags */
                RCC_ClearFlag();
                printf("HW IWDG was set successfully,need to RL_IWDG!\r\n ") ;
            }
            else
            {
                printf("\r\n Reboot ok.\r\n "); 
                printf("\r\n UserDatas were updated successfully!\r\n ");  
            }
        }
    }
    /* Lock The Flash Controller */
    FLASH_Lock();
}

由于每次系统复位后,选项字节才会被重新加载,并保存在选项字节寄存器 (FLASH_OBR)中,每个选择位都在信息块中有它的反码位,在加载选择位时,反码位用于验证选择位是否正确,如果有任何的差别,将产生一个选项字节错误标志 (OPTERR),所以建议操作选项字节空间时先判断FLASH标志位。

按照流程对FLASH的选项字节空间进行数据读取和写入操作,且设置独立看门狗为硬件模式,做完所有的操作后,一定要将系统进行软复位或者上电复位,这样才能使之前的写入操作正式生效。

最后,通过RCC的复位标志判断此次复位是否由看门狗引发,搭配串口信息输出来进行所有功能验证。

测试验证

本次实验同样是基于eMiniboard MB-025硬件资源完成的,前述完整代码中,开启了在主循环的喂狗操作,每隔1s钟进行喂狗动作且通过串口打印运行状态,串口助手完整显示如下:


可以发现已经正常将用户数据0x01和0x02分别写入到了Data0和Data1中,且独立看门狗的硬件模式也有设置,为了验证看门狗已经生效,屏蔽喂狗语句后重新编译工程,擦除全片后重新烧录程序,此刻,串口助手完整显示如下:


通过以上打印信息,可以确认硬件看门狗的硬件模式已经生效,无需通过用户代码开启LSI时钟和开启独立看门狗,只要芯片正常上电即可完成对用户程序异常情况发生的防护,从而提高系统整体安全性能。

转自:灵动微电子

围观 10

RTC可用于周期性从低功耗模式下唤醒MCU,RTC可选三个时钟源:

  • 低功耗 32.768kHz 外部低速晶振 (LSE):该时钟源提供了一个低功耗且精确的时间基准。
  • 低功耗内部低速振荡器 (LSI):使用该时钟源,节省了一个 32.768kHz 晶振的成本,但是精度没有外部晶振的精度高。
  • 外部高速晶振(HSE)128分频:在某些低功耗模式中HSE被关闭会导致RTC无时钟源。

为了用 RTC 闹钟事件将系统从停机模式下唤醒,必须进行如下操作:

  • 配置外部中断线 17 为上升沿触发。
  • 配置 RTC 使其可产生 RTC 闹钟事件。

如果要从待机模式中唤醒, 不必配置外部中断线 17。

MM32F013x有三种低功耗模式:睡眠模式(Sleep Mode)、停机模式(Stop Mode)和待机模式(Standby Mode),三种低功耗模式的对比如下表所示:

MM32F013x的三种低功耗模式

从上表中可以看出,当MCU工作在停机模式时,可以通过任一外部中断事件来唤醒。MM32F013x低功耗模式下的功耗列表如下图所示:

MM32F013x低功耗模式下的功耗列表

MM32F013x有22个外部中断源,其中EXTI 17对应的是RTC闹钟事件,所以结合RTC闹钟的功能,本文将重点介绍如何在MM32F013x上通过内部RTC模块的闹钟事件来唤醒处于停机模式下的MCU。

01、实现功能

通过内部RTC模块的闹钟事件(对应的是外部中断EXTI 17)来唤醒处于停机模式下的MCU。系统在进入停机模式时拉高GPIO端口,在恢复到正常运行状态时拉低GPIO端口。

02、配置顺序

1)使能PWR和BKP时钟:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

2)使能后备寄存器访问:

PWR_BackupAccessCmd(ENABLE);

3)配置RTC时钟源,使能RTC时钟,如果使用LSE,要打开LSE:

RCC_LSEConfig(RCC_LSE_ON);
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RCC_RTCCLKCmd(ENABLE);

4)设置RTC预分频系数:

RTC_SetPrescaler();

5)开启相关中断:

RTC_ITConfig(RTC_IT_ALR, ENABLE);

6)配置外部中断线:

EXTI_StructInit(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line    = EXTI_Line17;
EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);

7)配置中断服务函数:

NVIC_InitStructure.NVIC_IRQChannel  = RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

8)部分操作要等待写操作完成和同步。

03、参考代码

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

void RTC_Configure(void)
{
    uint16_t BKP_Value = 0x5A5A;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    /* Enable PWR Clock */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, 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 Alarm Interrupt */
        RTC_ITConfig(RTC_IT_ALR, 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();
    }
    else
    {
        /* Wait For Synchronization */
        RTC_WaitForSynchro();
        /* Enable RTC Alarm Interrupt */
        RTC_ITConfig(RTC_IT_ALR, ENABLE);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();
    }
    /* Clear EXTI Line17 Flag */
    EXTI_ClearITPendingBit(EXTI_Line17);

   /* Configure EXTI Line17(RTC Alarm) To Generate An Interrupt On Rising Edge */
    EXTI_StructInit(&EXTI_InitStructure);
    EXTI_InitStructure.EXTI_Line    = EXTI_Line17;
    EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

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

3.2 RTC闹钟中断函数

void RTC_BKP_IRQHandler(void)
{
    if(RTC_GetITStatus(RTC_IT_ALR) != RESET)
    {
        GPIO_WriteBit(LED_GPIO, LED_PIN, Bit_RESET);

        /* Clear EXTI Line17 Flag */
        EXTI_ClearITPendingBit(EXTI_Line17);

        /* Check If The Wake-Up Flag Is Set */
        if(PWR_GetFlagStatus(PWR_FLAG_WU) != RESET)
        {
            /* Clear Wake Up Flag */
            PWR_ClearFlag(PWR_FLAG_WU);
        }
        /* Clear Alarm Flag */
        RTC_ClearITPendingBit(RTC_IT_ALR);

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

3.3 设置RTC闹钟时间,系统进入STOP模式

void RTC_EntryStopMode(void)
{
    /* Wait till RTC Second event occurs */
    RTC_ClearFlag(RTC_FLAG_SEC);
    while(RTC_GetFlagStatus(RTC_FLAG_SEC) == RESET);

    /* Set The RTC Alarm Value */
    RTC_SetAlarm(RTC_GetCounter() + 3);

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

    GPIO_WriteBit(LED_GPIO, LED_PIN, Bit_SET);

    /* Enter Stop Mode */
    PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI);
}

04、运行结果

编译软件工程无误后下载代码,调用RTC_EntryStopMode()函数使系统进入到停机模式,等待RTC闹钟事件3S后唤醒停机模式下的MCU,唤醒后继续执行程序,通过观察GPIO的电平状态来查看运行结果:


用户可以结合上一篇万年历功能设置在某年某月某时某刻定时唤醒MCU功能,万年历的具体实方式可以参考上一篇《MM32F013x——万年历》

转自:灵动微电子

围观 18

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产生一次中断,在中断中我们将当前的日期信息通过串口打印在显示终端软件上:

转自:灵动微电子

围观 29

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

转自:灵动微电子

围观 19

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

转自:灵动微电子

围观 48

在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的数据包。

转自:灵动微电子

围观 24

本文将介绍在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数据是一样的。


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


转自:灵动微电子

围观 37

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

转自:灵动微电子

围观 10

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

围观 10

页面

订阅 RSS - MM32F013x