一、前言
最近使用一款国产32位MCU做产品开发,与之前用过的传统32位MCU相比,代码配置上遇到了一些自己未曾接触过的写法,这里做个随笔,方便日后回顾。
二、CMS32L051芯片介绍
1、芯片概述
这里直接摘抄手册中的功能描述,可自行与其它同类芯片比较。这里给出获取芯片相关资料的网址,包含基本的数据手册、应用手册、PACK包和例程。
芯片资料链接:点击此处。
三、芯片在KEIL中的初始化配置
这里默认你已经获取了上面提到的所有相关资料。
1、启动文件
打开启动文件,找到汇编代码中关于Reset_Handler这一部分。与其它通用32位MCU一样,先会运行SystemInit()这个函数,再跑到main()函数中。
2、SystemInit()函数
void SystemInit (void)
{
/* ToDo: add code to initialize the system
do not use global variables because this function is called before
reaching pre-main. RW section maybe overwritten afterwards. */
/* RAM Parity Error Reset Disable */
SAF->RPECTL = 0x80U;
CGC->WDTCFG0 = 0x1A;
CGC->WDTCFG1 = 0x2B;
CGC->WDTCFG2 = 0x3C;
CGC->WDTCFG3 = 0x4D;
DBG->DBGSTOPCR = 0;
SystemCoreClock = CLK_GetHocoFreq();
/* NVIC Clear Pending IRQ */
NVIC->ICPR[0U] = 0xFFFFFFFF;
/* NVIC Enable IRQs */
NVIC->ISER[0U] = 0xFFFFFFFF;
/* NVIC Lower Priority */
NVIC->IP[0U] = 0xC0C0C0C0;
NVIC->IP[1U] = 0xC0C0C0C0;
NVIC->IP[2U] = 0xC0C0C0C0;
NVIC->IP[3U] = 0xC0C0C0C0;
NVIC->IP[4U] = 0xC0C0C0C0;
NVIC->IP[5U] = 0xC0C0C0C0;
NVIC->IP[6U] = 0xC0C0C0C0;
NVIC->IP[7U] = 0xC0C0C0C0;
/* restart watchdog timer */
WDT->WDTE = 0xACU;
}
- 文件在system_CMS32L051.c中。大概操作了RAM的奇偶校验、看门狗配置、NVIC配置以及获取系统时钟。等等,没看到系统时钟配置啊,怎么就获取时钟频率了?进去看看。
3、CLK_GetHocoFreq()函数
/*----------------------------------------------------------------------------
Clock functions
*----------------------------------------------------------------------------*/
uint32_t CLK_GetHocoFreq(void)
{
uint32_t freq;
uint8_t frqsel = (*(uint8_t *)0x000000C2U);
frqsel &= 0xF8; /* Mask the lower 3 bits */
frqsel |= CGC->HOCODIV; /* Refer the value of HOCODIV */
freq = 1000000U; /* fIH = 1MHz except for the following cases */
switch(frqsel)
{
case HOCO_FREQ_64MHZ:
freq = 64000000U; /* fIH = 64MHz */
break;
case HOCO_FREQ_48MHZ:
freq = 48000000U; /* fIH = 48MHz */
break;
case HOCO_FREQ_32MHZ:
freq = 32000000U; /* fIH = 32MHz */
break;
case HOCO_FREQ_24MHZ:
freq = 24000000U; /* fIH = 24MHz */
break;
case HOCO_FREQ_16MHZ:
freq = 16000000U; /* fIH = 16MHz */
break;
case HOCO_FREQ_12MHZ:
freq = 12000000U; /* fIH = 12MHz */
break;
case HOCO_FREQ_8MHZ:
freq = 8000000U; /* fIH = 8MHz */
break;
case HOCO_FREQ_6MHZ:
freq = 6000000U; /* fIH = 6MHz */
break;
case HOCO_FREQ_4MHZ:
freq = 4000000U; /* fIH = 4MHz */
break;
case HOCO_FREQ_3MHZ:
freq = 3000000U; /* fIH = 3MHz */
break;
case HOCO_FREQ_2MHZ:
freq = 2000000U; /* fIH = 2MHz */
break;
}
return(freq);
}
- 大致看了下,先获取了0x000000C2U这个地址上的内容,再清除其中的低3位,与HOCODIV这个寄存器上的内容相或。读到的内容再处理下,返回对应的频率值。
- 翻看手册,这里是读的字节选项上的内容,看来低3位似乎就是切换频率的位了,可看了启动文件,这就是最前面的运行代码了,代码中又是如何配置的呢?
4、代码选项配置
/*----------------------------------------------------------------------------
User Option Byte
*----------------------------------------------------------------------------*/
/* ToDo: add here your necessary defines for device initialization
following is an example for different system frequencies */
//-------- <<< Use Configuration Wizard in Context Menu >>> ------------------
const uint8_t user_opt_data[4] __attribute__((used)) __attribute__((section(".ARM.__AT_0x000000C0"))) =
{
/**
* @brief WDT Control BYTE
* Please refer to the user manual for details.
* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
* --------|---------|---------|-------|-------|-------|-------|----------
* WDTINT | WINDOW1 | WINDOW0 | WDTON | WDCS2 | WDCS1 | WDCS0 | WDSTBYON
* --------|---------|---------|-------|-------|-------|-------|----------
*/
// <h> WDT Control Option Byte (C0H)
// <e.4> Enable WDT (WDTON)
// <o.5..6> Watchdog timer window open period setting <2=> 75% <3=> 100%
// <o.1..3> Watchdog timer overflow time setting <0=> 2^6/fIL <1=> 2^7/fIL
// <2=> 2^8/fIL <3=> 2^9/fIL
// <4=> 2^11/fIL <5=> 2^13/fIL
// <6=> 2^14/fIL <7=> 2^16/fIL
// <e.0> Operation in Standby mode setting (WDSTBYON)
// <i> WDT Operaton in SLEEP/DEEPSLEEP mode.
// </e>
// <e.7> interrupt enable
// <i> interval interrupt is generated when 75% + 1/2 fIL of the overflow time is reached.
// </e>
// </e>
// </h>
0xEF,
/**
* @brief LVD Control BYTE (C1H)
* Please refer to the user manual for details.
* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
* -------|-------|-------|-------|-------|-------|---------|----------
* VPOC2 | VPOC1 | VPOC0 | 1 | LVIS1 | LVIS0 | LVIMDS1 | LVIMDS0
* -------|-------|-------|-------|-------|-------|---------|----------
*/
// <h> LVD Control Option Byte (C1H)
// <o.0..7> Voltage detection setting (VLVD) <0xFF=> ( LVD OFF )
// <0x3D=> VLVD = 1.88V/1.84V ( interrupt mode )
// <0x39=> VLVD = 1.98V/1.94V ( interrupt mode )
// <0x35=> VLVD = 2.09V/2.04V ( interrupt mode )
// <0x5D=> VLVD = 2.50V/2.45V ( interrupt mode )
// <0x59=> VLVD = 2.61V/2.55V ( interrupt mode )
// <0x55=> VLVD = 2.71V/2.65V ( interrupt mode )
// <0x7D=> VLVD = 2.81V/2.75V ( interrupt mode )
// <0x79=> VLVD = 2.92V/2.86V ( interrupt mode )
// <0x75=> VLVD = 3.02V/2.96V ( interrupt mode )
// <0x3F=> VLVD = 1.88V/1.84V ( reset mode )
// <0x3B=> VLVD = 1.98V/1.94V ( reset mode )
// <0x37=> VLVD = 2.09V/2.04V ( reset mode )
// <0x5F=> VLVD = 2.50V/2.45V ( reset mode )
// <0x5B=> VLVD = 2.61V/2.55V ( reset mode )
// <0x57=> VLVD = 2.71V/2.65V ( reset mode )
// <0x7F=> VLVD = 2.81V/2.75V ( reset mode )
// <0x7B=> VLVD = 2.92V/2.86V ( reset mode )
// <0x77=> VLVD = 3.02V/2.96V ( reset mode )
// <0x3A=> VLVDH = 1.98V/1.94V, VLVDL = 1.84V ( interrupt & reset mode )
// <0x36=> VLVDH = 2.09V/2.04V, VLVDL = 1.84V ( interrupt & reset mode )
// <0x32=> VLVDH = 3.13V/3.06V, VLVDL = 1.84V ( interrupt & reset mode )
// <0x5A=> VLVDH = 2.61V/2.55V, VLVDL = 2.45V ( interrupt & reset mode )
// <0x56=> VLVDH = 2.71V/2.65V, VLVDL = 2.45V ( interrupt & reset mode )
// <0x52=> VLVDH = 3.75V/3.67V, VLVDL = 2.45V ( interrupt & reset mode )
// <0x7A=> VLVDH = 2.92V/2.86V, VLVDL = 2.75V ( interrupt & reset mode )
// <0x76=> VLVDH = 3.02V/2.96V, VLVDL = 2.75V ( interrupt & reset mode )
// <0x72=> VLVDH = 4.06V/3.98V, VLVDL = 2.75V ( interrupt & reset mode )
// <i> Please setting the item for interrupt & reset mode
// </h>
0xFF,
/**
* @brief HOCO Control BYTE (FRQSEL)
* Please refer to the user manual for details.
* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
* -------|-------|-------|---------|---------|---------|---------|---------
* 1 | 1 | 1 | FRQSEL4 | FRQSEL3 | FRQSEL2 | FRQSEL1 | FRQSEL0
* -------|-------|-------|---------|---------|---------|---------|---------
*/
// <h> HOCO Control Option Byte (C2H)
// <o.0..4> High-speed OCO clock setting <0xE8=> fHOCO = 64MHz, fIH = 64MHz
// <0xE0=> fHOCO = 48MHz, fIH = 48MHz
// <0xE9=> fHOCO = 64MHz, fIH = 32MHz
// <0xE1=> fHOCO = 48MHz, fIH = 24MHz
// <0xEA=> fHOCO = 64MHz, fIH = 16MHz
// <0xE2=> fHOCO = 48MHz, fIH = 12MHz
// <0xEB=> fHOCO = 64MHz, fIH = 8MHz
// <0xE3=> fHOCO = 48MHz, fIH = 6MHz
// <0xEC=> fHOCO = 64MHz, fIH = 4MHz
// <0xE4=> fHOCO = 48MHz, fIH = 3MHz
// <0xED=> fHOCO = 64MHz, fIH = 2MHz
// </h>
0xEA,
/**
* @brief Flash Protect Control BYTE
* Please refer to the user manual for details.
*/
// <h> OCD Control Option Byte (C3H)
// <o.0..7> On-chip debug setting (OCDEN) <0xFF=> Enable <0xC3=> Disable
// <i> OCDM(500004H) == 0x3C && OCDEN == 0xC3: Debugger can not erease/write/read Flash.
// <i> OCDM(500004H) != 0x3C && OCDEN == 0xC3: Debugger can only chip erease Flash but cannot write/read Flash.
// </h>
0xFF
};
- 中微的写法给到了上面,似乎没啥特别。使用const关键词将内容定义到FLASH存储区,4个字节大小对应字节选项,中微也给其中的每一位作了详细解释。可是这是如何精准的映射到000C0H~000C3H这四个地址之中的呢?
const uint8_t user_opt_data[4] __attribute__((used)) __attribute__((section(".ARM.__AT_0x000000C0"))) =
- 我们看到在定义时还加了额外的东西 使用了__attribute__((used)) 这个关键词,后面指定section区域,放置在名叫.ARM.__AT_0x000000C0这个地方。
- attribute()是GNU C的一种机制,编译时可对函数、变量进行一些设置。更多描述可见该文章:点击此处
- 打开我们的.map文件,搜索 .ARM.__AT_0x000000C0 我们发现在目标文件中确实存在着这么一个区域地址映射到了0x000000C0上,大小为4个字节。
- 到这里我们就大概明白了,只需修改该数组中变量的值,就能设置芯片的看门狗、LVD、以及系统时钟,没有太多的繁琐步骤。
5、main()函数
- 这个时候再看我们的主函数,通过前面的配置获取实际的系统运行频率,再配置了1ms的systick中断。
int main()
{
uint32_t msCnt; // count value of 1ms
SystemCoreClockUpdate();
msCnt = SystemCoreClock / 1000;
SysTick_Config(msCnt);
while(1)
{
}
}
四、其它
- 至此,我们清楚了CMS32L051这颗芯片在初始化时对内部到底作了哪些操作。接下来,我会逐步分享一些该芯片的其它相关demo。
!!!本文为欢喜6666在CSDN原创发布,复制或转载请注明出处:)!!!