搜索

[MM32软件] 【MM32 eMiniBoard测评报告】学习笔记

[复制链接]
254|6
 楼主 | 2020-5-23 08:02 | 显示全部楼层 |阅读模式
一. 环境搭建
硬件平台:MM32 eMiniBoardMM32L073PF
1. 官网www.mm32mcu.com下载资料
(1) 下载KeilPack文件,并安装
可以只安装MindMotion.MM32L0xx_DFP.1.0.9
(2) 下载对应MCU的库函数和例程
MM32L0xx(n) 库函数和例程
(3) 下载MMLinkMM32-Link 编程开发与调试安装包)驱动
如果已经打开Keil,关闭Keil再重新打开,可以看到已经有MMLink

2. 打开
MM32L0xx_n_Lib_Samples_V1.20_SC\MM32L073PF_MiniBoard_libReg\MBL073PF_n_lib\BLINK\IOToggle\KEIL_PRJ
下的工程,直接编译OK

3. 这个例程是控制4GPIO翻转
例程中对应的4GPIO分别为A15B3B4B5
eMiniBoard MB-023的原理图也是这几个IO(文档的电路图够模糊)。
1.png 1.png

4. 修改led.c中的LED_Init函数
LED1_OFF();
LED2_OFF();
LED3_OFF();
LED4_OFF();
改为
LED1_OFF();
LED2_ON();
LED3_OFF();
LED4_ON();
将延时时间改成1
delay_ms(300); ---> delay_ms(1000);
编译代码,烧录程序。
VID20200521201231 00_00_00-00_00_30.gif

二. 库说明
MM32的库分2种使用方式,一种是HAL的方式,另一种是寄存器的方式。HALSTM32的标准库类似。
在库和例程中,文件结构如下:
1.png
Device文件夹是库文件夹,文件夹结构如下:
1.png
CMSIS头文件里面分IAR_COREKEIL_CORE两个文件夹(奇怪,STM32F030并没有区别IDE)。因为MM32F073属于Cortex-M0内核,只用到core_cm0.hcore_cmFunc.hcore_cmInstr.h
MM32L0xx文件夹是库文件,文件夹结构如下:
1.png
MDK工程中如果使用HAL_lib,需要添加USE_STDPERIPH_DRIVER的宏定义
1.png
注意,并没有和STM32一样会有一个芯片的宏定义。

三. Systick
Systick的基本使用与STM32一样,systick的配置API函数也是SysTick_Config,不过例程没有system_MM32L0xx.h,即这里要添加一个SystemCoreClock 的外部申明。另外,systick的中断函数也是SysTick_Handler。

extern uint32_t SystemCoreClock;
void systickInit(uint8_t ms)
{
    SysTick_Config(((SystemCoreClock / 1000) * ms));
}

void SysTick_Handler(void)
{

}

四.  UART
1. GPIO的初始化与STM32F0xx一样。
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
    //UART
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);

    //UART1_TX   GPIOA.9
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.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;//PA10
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
2. 其他API函数基本与STM32F0xx相同,区别是把USART改为UART,例如:
USART_TypeDef *USARTx;
UART_TypeDef *UARTx
另外,有一些宏定义不同,分别为:
A) USART_IT_RXNE -> UART_IT_RXIEN
B) USART_FLAG_ORE -> UART_FLAG_OVER(目前的库没有这个定义)
这段代码是STM32中断函数中处理UART溢出的处理。
if (UART_GetFlagStatus(UARTx, UART_FLAG_OVER) != RESET)
    {
        UART_ClearFlag(UARTx,  UART_FLAG_OVER);
        UART_ReceiveData(UARTx);
}
UART_GetFlagStatus函数查到的是UARTx->CSR
1.png
没有OVER的错误。改成检查中断是否是溢出中断。
if (UART_GetITStatus(UARTx, UART_OVER_ERR) != RESET)
{
        UART_ClearITPendingBit(UARTx,  UART_OVER_ERR);
        UART_ReceiveData(UARTx);
}
C) 无空闲中断UART_IT_IDLE
D) USART_FLAG_TC -> UART_FLAG_TXEPT

eMiniBoardMM32-LINK-OB有个错误导致UART0接收不了数据
1.png
这个二极管反了。

(待续)







使用特权

评论回复
 楼主 | 2020-5-23 17:31 | 显示全部楼层
本帖最后由 pq113_6 于 2020-5-23 20:48 编辑

五,SPI
1.SPI的IO口基本与STM32F0xx一样。以SPI2为例:
//SPI
    //SFLASH CS: PB12
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_12;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    //SPI SCK: PB13
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    //SPI MOSI: PB15
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    //SPI MISO: PB14
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_14;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
   
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource12,GPIO_AF_0);
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource13,GPIO_AF_0);
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource14,GPIO_AF_0);
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource15,GPIO_AF_0);
其中CS脚的控制方式与STM32F0xx不同,STM32F0xx完全是作为GPIO的方式控制CS脚,这里MM设置为了SPI IP控制。应该也可以用GPIO的方式控制。
2.SPI初始化与STM32F0xx略微有不同,如下面红色部分。
SPI_InitTypeDef  SPI_InitStructure;
    SPI_Cmd(SPIx, DISABLE);
    //SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_DataWidth = SPI_DataWidth_8b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;  
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    //SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPIx, &SPI_InitStructure);
    /*Enable SPIx.NSS as a GPIO*/
    //SPI_SSOutputCmd(SPIx, ENABLE);
    SPI_BiDirectionalLineConfig(SPIx, SPI_Direction_Tx);
    SPI_BiDirectionalLineConfig(SPIx, SPI_Direction_Rx);
    //SPI_RxFIFOThresholdConfig(SPIx,SPI_RxFIFOThreshold_QF);
    SPI_Cmd(SPIx, ENABLE);
这里多了一个SPI_DataWidth,SPI_DataSize设置的是SPI的全局控制寄存器(SPI_GCTL)的bit 11(DW8_32,设置发送和接收寄存器的有效数据位),SPI_DataWidth设置的是数据控制寄存器(EXTCTL),
1.png

3.读写API函数不同
SPI_SendData8 -> SPI_SendData
SPI_ReceiveData8 -> SPI_ReceiveData
4.判断SPI状态的API函数不同
SPI_I2S_GetFlagStatus -> SPI_GetFlagStatus
SPI_I2S_FLAG_TXE -> SPI_FLAG_TXEPT
SPI_I2S_FLAG_RXNE -> SPI_FLAG_RXAVL
5.使能SPI的RCC与STM32相同,不过SPI的例程应该有个bug,SPI_DeInit中SPI2是APB1,但是调用的是APB2.
void SPI_DeInit(SPI_TypeDef* SPIx)
{
    /* Check the parameters */
    assert_param(IS_SPI_ALL_PERIPH(SPIx));
   
    switch (*(uint32_t*)&SPIx)
    {
    case SPI1_BASE:
        /* Enable SPI1 reset state */
        RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1, ENABLE);
        /* Release SPI1 from reset state */
        RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1, DISABLE);
        break;
    case SPI2_BASE:
        RCC_APB2PeriphResetCmd(RCC_APB1Periph_SPI2, ENABLE);
        RCC_APB2PeriphResetCmd(RCC_APB1Periph_SPI2, DISABLE);

        break;
    default:
        break;
    }
}

板子上的SPI Nor Flash用的是第二个SPI,即SPI2, SW2要拨向板内方向SS脚才连接到Nor Flash的CS脚。CS控制改为软件控制,
#define sflashCSHigh(n)                 {GPIO_SetBits(GPIOB, GPIO_Pin_12);}
#define sflashCSLow(n)                  {GPIO_ResetBits(GPIOB, GPIO_Pin_12);}
GPIOB12的初始化改为
//SFLASH CS: PB12
GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//GPIO_PinAFConfig(GPIOB,GPIO_PinSource12,GPIO_AF_0);

读Nor Flash的ID,打印出来的结果:
ID is 0x5e4015

使用特权

评论回复
 楼主 | 2020-5-24 09:01 | 显示全部楼层
6. I2C
1. I2CIO定义
原理图用的是GPIO B6B7,例程用的是GPIO B8B9
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_1);
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_1);
    //I2C SDA: PB7; SCL: PB6
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_6 | GPIO_Pin_7;  
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_Init(GPIOB, &GPIO_InitStructure);
例程有个特别的地方,为什么在初始化I2C时要先设置IOGPIO_Mode_IPU,初始化完后又设置为GPIO_Mode_AF_OD
2. I2C的初始化
MM32L073只有1I2C接口。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);  
    I2C_InitStructure.I2C_ClockSpeed = 100000;
    I2C_InitStructure.I2C_Mode = I2C_Mode_MASTER;
    I2C_InitStructure.I2C_OwnAddress = 0xa8;
    I2C_InitStructure.I2C_Speed = I2C_Speed_STANDARD;
    I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
I2C_OwnAddress库并没有处理,可能是作为从设备时才使用?
3. I2C读函数
官方例程中的I2CMasterReadI2CMasterRead的正常流程应该是:
Start
SendByte(i2cAddr)
SendByte(memAddr)
Start
SendByte(i2cAddr | 0x01)
while(--len)
{
*buf = ReadByte() + ACK;
buf++
}
*buf = ReadByte() + NACK;
最后一个字节需要回NACK,但是官方的例程是都回ACK的,下图是逻辑分析仪的结果:
1.png
从上图看I2C IP会自己发送2次地址(0xA8 + 0xA9)?这块也是疑问,怎么实现的?这里要吐槽一下MM32Link,经常莫名其妙的跑飞。
实在不懂就自己来,参考例程+研究一下文档,自己实现一个API函数,如下:
bool_t i2cRead(uint8_t port, uint8_t slaveAddr, uint8_t addrbit, uint16_t addr, uint8_t *pdata, uint16_t len)
{
    uint32_t i;
   
    I2C_TypeDef* I2Cx;
    if(port > 0)
        return FALSE;
    I2Cx = i2cGroup[port];
   
    I2Cx->IC_TAR = slaveAddr >> 1;
    i2cEnable(I2Cx);
    if(addrbit == 16)
        i2cTXByte(I2Cx, I2C_CMD_WRITE, (addr >> 8));
    i2cTXByte(I2Cx, I2C_CMD_WRITE | I2C_CMD_RESTART, (addr & 0xff));
    for(i = 0; i < len ;i++)
    {
        if (i == (len - 1))
            pdata = i2cRXByte(I2Cx, I2C_CMD_READ | I2C_CMD_STOP); //set stop flag
        else
            pdata = i2cRXByte(I2Cx, I2C_CMD_READ);
    }
   
    i2cDisable(I2Cx);
    return TRUE;
}
参数说明:port表示第几个I2C,这里只有1个;slaveAddrI2C从设备的地址;addrbit是从设备内部地址的位数,816是有效值;addr是从设备内部地址;pdata是读入数据的buffer指针;len是需要读入的数据长度。
4. I2C写函数
类似的写函数:
bool_t i2cWrite(uint8_t port, uint8_t slaveAddr, uint8_t addrbit, uint16_t addr, uint8_t *pdata, uint16_t len)
{
    uint32_t i = 0;
    I2C_TypeDef* I2Cx;
    if(port > 0)
        return FALSE;
    I2Cx = i2cGroup[port];
   
    I2Cx->IC_TAR = slaveAddr >> 1;
    i2cEnable(I2Cx);
   
    if(addrbit == 16)
        i2cTXByte(I2Cx, I2C_CMD_WRITE, (addr >> 8));
    i2cTXByte(I2Cx, I2C_CMD_WRITE, (addr & 0xff));
   
    for(i = 0; i < len; i++)
    {
        i2cTXByte(I2Cx, I2C_CMD_WRITE, pdata); //tx data
    }
    i2cTXByte(I2Cx, I2C_CMD_STOP, 0);
    i2cDisable(I2Cx);
    return TRUE;
}
参数含义与i2cRead基本相同。
5. i2cTXByte和i2cRXByte的改造
上面这2个函数增加了一个cmd的参数。这个参数都是写入到寄存器IC_DATA_CMD。支持的命令宏定义:
#define I2C_CMD_STOP        ((uint16_t)0x0200)
#define I2C_CMD_RESTART     ((uint16_t)0x0400)
#define I2C_CMD_READ        ((uint16_t)0x0100)
#define I2C_CMD_WRITE       ((uint16_t)0x0000)
void i2cTXByte(I2C_TypeDef *I2Cx, uint16_t cmd, uint8_t dat)
{
    I2Cx->IC_DATA_CMD = cmd | dat;
    while(1)
    {
        if((I2Cx->IC_RAW_INTR_STAT & I2C_FLAG_TX_EMPTY) != (uint32_t)RESET)
        {
            break;
        }
    }
}
uint8_t i2cRXByte(I2C_TypeDef *I2Cx, uint16_t cmd)
{
    I2Cx->IC_DATA_CMD = cmd;
    while(1)
    {
        if((I2Cx->IC_RAW_INTR_STAT & I2C_FLAG_RX_FULL) != (uint32_t)RESET)
        {
            break;
        }
    }
    return (uint8_t)I2Cx->IC_DATA_CMD;
}
6. 验证结果:
16个字节数据到AT24C02的地址0x10
1.png
从相同的地址读16个字节
1.png

使用特权

评论回复
 楼主 | 2020-5-24 09:15 | 显示全部楼层
Update一下,I2C的Clock只能设置到100KHz,超过要设置为I2C_Speed_FAST,但是验证通信会出错

使用特权

评论回复
 楼主 | 2020-5-24 15:52 | 显示全部楼层
本帖最后由 pq113_6 于 2020-5-30 17:55 编辑

七. 时钟
支持4种时钟源
• HSI振荡器时钟
内部高速时钟振荡器,目前例程上用的是这种模式。system_MM32L0xx.c中的有3个不同频率(实际是4种,如果没有定义HSEHSI,默认就是HSI 8M)的宏定义:
//#define SYSCLK_HSI_24MHz  24000000
//#define SYSCLK_HSI_36MHz  36000000
#define SYSCLK_HSI_48MHz  48000000

• HSE振荡器时钟
外部高速时钟振荡器
2种类型:
1. HSE 外部晶体/陶瓷谐振器
常见的类型,eMiniBoard上硬件有预留这种类型,经典的外部晶体为8MHz
1.png
同样,system_MM32L0xx.c中的有4个不同频率的宏定义,这7种(HSI+HSE的频率配置)设置只能有一个打开。
//#define SYSCLK_FREQ_HSE    HSE_VALUE
//#define SYSCLK_FREQ_24MHz  24000000  
//#define SYSCLK_FREQ_36MHz  36000000
//#define SYSCLK_FREQ_48MHz  48000000
其中HSE_VALUE是外部晶体的频率,这里是8M
1. HSE 用户外部时钟
又叫HSE旁路,外部时钟最大24MHz
1.png

• PLL时钟
内部 PLL 可以用来倍频 HSI 振荡器的输出时钟或 HSE 晶体输出时钟。

• LSI时钟
内部速时钟振荡器
LSI 振荡器担当一个低功耗时钟源的角色,它可以在停机和待机模式下保持运行,为独立
看门狗和自动唤醒单元提供时钟。LSI 时钟频率大约 40KHz
MCU允许将时钟输出到GPIO A8GPIO A9脚(即MCO 管脚), 5个时钟信号可被选作 MCO 时钟:
#define RCC_MCO_SYSCLK                   ((uint8_t)0x04)
#define RCC_MCO_HSI                      ((uint8_t)0x05)
#define RCC_MCO_HSE                      ((uint8_t)0x06)
#define RCC_MCO_PLLCLK_Div2              ((uint8_t)0x07)
#define RCC_MCO_LSI                      ((uint8_t)0x02)
//初始化
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_8;   //mco  pa8
GPIO_InitStructure.GPIO_Speed =  GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;     
GPIO_Init(GPIOA, &GPIO_InitStructure);
   
GPIO_PinAFConfig( GPIOA,GPIO_PinSource8,GPIO_AF_0);
//配置MCO输出类型   
RCC_MCOConfig(RCC_MCO_PLLCLK_Div2);
用逻辑分析仪测到的频率是大约是25MHz。但是如果设置其他输出类型,并没有输出信号,原因未明,这个功能用的少,暂时不研究了。

获取当前系统各个时钟的值:
RCC_ClocksTypeDef  rcc_clocks;
RCC_GetClocksFreq(&rcc_clocks);
Printf("System Clk:%dMHz\r\n", rcc_clocks.SYSCLK_Frequency / 1000 / 1000);
Printf("HCLK:%dMHz\r\n", rcc_clocks.HCLK_Frequency / 1000 / 1000);
Printf("APB1:%dMHz\r\n", rcc_clocks.PCLK1_Frequency / 1000 / 1000);
Printf("APB2:%dMHz\r\n", rcc_clocks.PCLK2_Frequency / 1000 / 1000);

使用特权

评论回复
 楼主 | 2020-5-24 15:54 | 显示全部楼层
本帖最后由 pq113_6 于 2020-5-30 17:55 编辑

八. PWM输出
PWM的功能利用的是定时器的PWM功能实现。
MM32L073PF的定时器有TIM132位,高级控制定时器)、TIM2(32位,通用定时器)、TIM316位,通用定时器)、TIM1416位,基本定时器)、TIM1616位,基本定时器)和TIM1716位,基本定时器),每个定时器能实现4PWM输出(注意芯片管脚的复用)。


1. 初始化定时器
首先使能定时器时钟,以TIM3为例:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
然后调用库函数TIM_TimeBaseInit
TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure);
其中参数TIM_TimeBaseStructure的配置如下:
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
A) TIM_TimeBaseStructure.TIM_Prescaler = TIM_PRESCALER;
#define TIM_DEFAULT_FREQ    (8 * 1000 * 1000)
#define TIM_PRESCALER       ((SystemCoreClock / TIM_DEFAULT_FREQ) - 1)
这个参数是设置计数器的计数时钟。计数器将对这个时钟进行计数。对于PWM来说,理论上TIM_DEFAULT_FREQ的值越大,PWM的精度越高?对比设置48M8M1M10K并没有太大差别,如下图:
1.png
1.png
1.png
1.png





B) TIM_TimeBaseStructure.TIM_Period = (TIM_DEFAULT_FREQ / freq) - 1;
这个参数是设置计数器的计数周期,可以理解为计数器计数到这个值后恢复为0,即一个计数周期是这么多个时钟。
例如PWM的频率是freq = 1KHzTIM_DEFAULT_FREQ = 8MHz,那这个数就等于7999
C) TIM_TimeBaseStructure.TIM_ClockDivision = 0;
库函数里面并没有使用到这个参数。
D) TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
设置计数器计数方向。TIM_CounterMode_Up表示计数器从 0 计数到自动加载值 (TIMx_ARR 计数器的内容),然后重新从 0 开始计数并且产生一个计数器溢出事件。而TIM_CounterMode_Down表示计数器从自动装入的值(TIMx_ARR 计数器的值) 开始向下计数到 0,然后从自动装入的值重新开始并且产生一个计数器向下溢出事件。TIM_CounterMode_CenterAligned1/TIM_CounterMode_CenterAligned2/TIM_CounterMode_CenterAligned3三种中央对齐模式,在中央对齐模式,计数器从0 开始计数到自动加载的值 (TIMx_ARR 寄存器) - 1,产生一个计数器溢出事件,然后向下计数到 1 并且产生一个计数器下溢事件;然后再从0开始重新计数。
UP
1.png

Down
1.png

TIM_CounterMode_CenterAligned1
1.png

TIM_CounterMode_CenterAligned2
1.png

TIM_CounterMode_CenterAligned3
1.png
注意中央对齐的模式中PWM的频率只有一半了。


2. 初始化通道
CH14个通道对应的库函数不同)为例:
TIM_OC1PreloadConfig(TIMx, &TIM_OCInitStructure);
其中参数TIM_OCInitStructure的配置如下:
A) TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCMode_PWM1: 在向上计数时,当TIMx_CNT< TIMx_CCR1时通道1为有效电平,        否则为无效电平;在向下计数时,当TIMx_CNT>TIMx_CCR1时通道1为无效电平                (OC1REF=0),否则为有效电平(OC1REF=1)。
TIM_OCMode_PWM2: 在向上计数时,当TIMx_CNT< TIMx_CCR1时通道1为无效电平,        否则为有效电平;在向下计数时,当TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则        为无效电平。
下图中上面的设定是TIM_OCMode_PWM1,下面的是TIM_OCMode_PWM22者的占        空比正好反过来了,PWM130%PWM270%

1.png

A) TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
表示该通道使能输出的方式,即OC1信号输出到对应的输出引脚,
B) TIM_OCInitStructure.TIM_Pulse = ((TIMx->ARR + 1) * duty) / 100;
设置PWM的占空比。实际初始化程序中这个数值是写入到CCR1(对应哪个通道就是        哪个CCRn,这里是以通道1为例)。duty的范围是0-100,表示0% - 100%
C) TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
设置是高电平有效还是低电平有效。
高电平(占空比70%):
1.png
低电平(占空比30%)
1.png
3. 调整占空比
    switch(ch)
    {
        case HW_TIM_CH0:
            TIMx->CCR1 = ((TIMx->ARR + 1) * duty) / 100;
            break;
        case HW_TIM_CH1:
            TIMx->CCR2 = ((TIMx->ARR + 1) * duty) / 100;
            break;
        case HW_TIM_CH2:
            TIMx->CCR3 = ((TIMx->ARR + 1) * duty) / 100;
            break;
        case HW_TIM_CH3:
            TIMx->CCR4 = ((TIMx->ARR + 1) * duty) / 100;
            break;
    }

使用特权

评论回复
 楼主 | 2020-6-4 20:46 | 显示全部楼层
九、ADC
MM32L073ADC主要特征:
• 最高 12 位可编程分辨率的逐次逼近寄存器型(SAR)模数转换器(ADC),多达 10 路外部输入通道
高达 1Msps 转换速率 sps是采样率,是每秒采样点的数量, 1MSPS = 1MHz
支持多种工作模式:
– 单次转换模式:A/D 转换在指定通道完成一次转换
– 单周期扫描模式:A/D 转换在所有指定通道完成一个周期 (从低序号通道到高
序号通道) 转换
– 连续扫描模式:A/D 转换连续执行单周期扫描模式直到软件停止 A/D 转换
通道采样时间,分辨率可软件配置
支持 DMA 传输
• A/D 转换开始条件:
软件启动
外部触发启动
– Timer 匹配
模拟看门狗,转换结果可和指定的值相比较,当转换值和设定值相匹配时,用户可设定是否产生中断请求
1. 什么是SAR ADC
逐次逼近寄存器型(SAR)模拟数字转换器(ADC)是采样速率低于5Msps (每秒百万次采样)的中等至高分辨率应用的常见结构。SAR ADC的分辨率一般为8位至16位,具有低功耗、小尺寸等特点是一种低成本的adc实现方案,主要缺点是速度慢,在高精度ADC采用较少,但是成本低,电路低,在一些要求不高的场合依然广泛采用。
2. ADC的主要参数:
l 采样精度分辨率是决定A/D采样精度的一个重要参数,通常人们习惯用ADC输出二数的位数来说明ADC对输入信号的分辨能力。理论上讲,n位输出的ADC能区分2n次方(4096个不同等输入模拟电压,能区分输入电压的最小值为满量程的
l 采样速率
A/D转换器的采样速率是选择ADC的另一个重要的因素。为了降低频率混叠成分,提        高能正确分析的谐波次数,需尽可能提高采样率。但是提高采样率又必需满足下列条件:
           ①AD具有较短的转换时间;
           ②AD具有较高的采样转换位数,否则两点之者的数据区分度不够;
           ③最重要的也是最关键的一点,软件最长程序流程的执行时间要求更短,须小于两次
样间隔时间,否则会产生数据阻塞和重叠。
3. 初始化ADCIO
MM32L073PF只有一组ADCADC110个通道。eMinBoard上有3ADC通道的硬件电路作为实验,A1A2A3分别对应PA1PA4PA5,对应的通道ADC1_VIN[1]ADC1_VIN[4]ADC1_VIN[5]
1.png
         GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_0);
         GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_0);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_0);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_0);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_0);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_0);
   
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
4. 初始化ADC
A) 使能ADC时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
B) 设置ADC时钟的分频系数
ADC_InitStructure.ADC_PRESCARE = ADC_PCLK2_PRESCARE_16;
APB2的时钟频率一般为48MHz16分频即频率为3MHzADC 的输入时钟不得超过15MHz,所以这个参数最多只能是ADC_PCLK2_PRESCARE_4。因此它的有效参数有:
#define ADC_PCLK2_PRESCARE_4                                                 ((uint32_t)0x00000010)
#define ADC_PCLK2_PRESCARE_6                                                 ((uint32_t)0x00000020)
#define ADC_PCLK2_PRESCARE_8                                                 ((uint32_t)0x00000030)
#define ADC_PCLK2_PRESCARE_10                                                 ((uint32_t)0x00000040)
#define ADC_PCLK2_PRESCARE_12                                                 ((uint32_t)0x00000050)
#define ADC_PCLK2_PRESCARE_14                                                 ((uint32_t)0x00000060)
#define ADC_PCLK2_PRESCARE_16                                                 ((uint32_t)0x00000070)
C) 设置ADC的工作模式
ADC_InitStructure.ADC_Mode = ADC_Mode_Single;
支持3种方式:
#define ADC_Mode_Single                     ((uint32_t)0x00000000)
#define ADC_Mode_Single_Period              ((uint32_t)0x00000200)
#define ADC_Mode_Continuous_Scan            ((uint32_t)0x00000400)
D) ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;  //无意义,底层没使用
E) 设置数据对齐方式
数据可以左对齐或右对齐,如下图所示。
1.png
一般是设置为右对齐:ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
F) 设置外部触发源
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
支持的外部触发源有:
#define ADC_ExternalTrigConv_T1_CC1                 ((uint32_t)0x00000000)
#define ADC_ExternalTrigConv_T1_CC2                             ((uint32_t)0x00000010)
#define ADC_ExternalTrigConv_T1_CC3                       ((uint32_t)0x00000020)
#define ADC_ExternalTrigConv_T2_CC2                              ((uint32_t)0x00000030)
#define ADC_ExternalTrigConv_T3_TRGO                      ((uint32_t)0x00000040)
#define ADC_ExternalTrigConv_T3_CC1                        ((uint32_t)0x00000060)
#define ADC_ExternalTrigConv_EXTI_11                 ((uint32_t)0x00000070)
分别是定时器1CC1事件,定时器1CC2事件,定时器1CC3事件,定时器2CC2事件,定时器3TRGO事件,定时器3CC1事件和EXTI线11中断。外部触发源使得ADC可以利用内部时钟进行周期性的转换,也可以利用外部IO在需要时转换.
这里的例程并没有使用外部触发源,只是直接配置寄存器触发,通过配置控制寄存器ADCRADST位,写1时开始转换,写0时停止转换
G) 设置ADC的位数
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
有效参数:
#define ADC_Resolution_12b                         ((uint32_t)0x00000000)
#define ADC_Resolution_11b                         ((uint32_t)0x00000080)
#define ADC_Resolution_10b                         ((uint32_t)0x00000100)
#define ADC_Resolution_9b                          ((uint32_t)0x00000180)
#define ADC_Resolution_8b                          ((uint32_t)0x00000200)
位数越高,精度越高,转换速度越慢;反之,位数越低,精度越低,转换速度越快。
H) 最后写入配置值
ADC_Init(ADC1, &ADC_InitStructure);
5. 初始化通道
A) 关闭所有通道:
ADC_RegularChannelConfig(ADC1, DISABLE_ALL_CHANNEL , 0, 0);
#define DISABLE_ALL_CHANNEL     9
但是问题是库里面9对应的是通道9?
case ADC_Channel_9: ADCx->ADCHS |= CHEN9_ENABLE;  
    break;
还有一句话如下图,并不能关闭所有通道。
1.png
B) 使能通道:
ADC_RegularChannelConfig(ADC1, ADC_Channel_x, 0, ADC_SampleTime_28_5Cycles);
参数ADC_SampleTime_28_5Cycles表示采样周期是28.5
采样时间是通过寄存器告诉MCU采样模拟量的时间,设置越长越精确
TCONV(转换时间)= 采样时间 + 12.5 个周期,这里设置为28.5就意味转换时间为28.5 + 12.5 = 41个周期。
6. 参数设定规则
ADC的时钟频率设置为3MHz,转换时间是41个周期,即13.67us/73KHz。采样频率为73KHz,一般采样最少是频率的2倍,所以这样的设定只能最大测试73/2 = 36.5KHz的信号。                                                               
7. 读取ADC的值
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)==0);
    ADC_ClearFlag(ADC1,  ADC_FLAG_EOC);
    puiADData = ADC_GetConversionValue(ADC1);
8. 计算电压值
fValue = ((float)ADCVAL/4095)*3.3;
注意这里的4095并不随ADC的位数而改变,例如改为8bit的方式
ADC_InitStructure.ADC_Resolution = ADC_Resolution_8b;
得到的ADC值取得是12bit数据中的高8bit

使用特权

评论回复
扫描二维码,随时随地手机跟帖
您需要登录后才可以回帖 登录 | 注册

本版积分规则

我要发帖 投诉建议 创建版块 申请版主

快速回复

您需要登录后才可以回帖
登录 | 注册
高级模式

论坛热帖

在线客服 快速回复 返回顶部 返回列表