拿到芯片肯定是先搭建开发环境,搭建开发环境相关内容在这篇文章里面。
开始学习单片机的第一件事----先看一眼它的系统框图。
等等,怎么有点眼熟(bushi
时钟树
从系统框图上可以看出这哥们的时钟总线和另一个某32有点像啊
咳咳,正经点。
AHB总线连接着总线矩阵和外设总线APB1和APB2,经测试,APB1总线的默认配置时钟频率为60M,而APB2总线的默认频率是和AHB总线(即系统时钟)相同的120M。
时钟总线框架图如下:
相关函数
//使能各个总线时钟
void RCC_AHBPeriphClockCmd(u32 ahb_periph, FunctionalState state);
void RCC_AHB2PeriphClockCmd(u32 ahb_periph, FunctionalState state);
void RCC_AHB3PeriphClockCmd(u32 ahb_periph, FunctionalState state);
void RCC_AHBPeriphResetCmd(u32 ahb_periph, FunctionalState state);
void RCC_AHB2PeriphResetCmd(u32 ahb_periph, FunctionalState state);
void RCC_AHB3PeriphResetCmd(u32 ahb_periph, FunctionalState state);
void RCC_APB2PeriphClockCmd(u32 apb2_periph, FunctionalState state);
void RCC_APB1PeriphClockCmd(u32 apb1_periph, FunctionalState state);
GPIO
这就不用废话了,直接上初始化GPIO为输出的代码:
void MM_GPIO_Init()
{
//定义GPIO句柄
GPIO_InitTypeDef GPIO_InitStruct;
//开启GPIO外设时钟
//GPIO挂在AHB总线上
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);
//初始化GPIO结构体,防止使用时出现未被初始化的成员
GPIO_StructInit(&GPIO_InitStruct);
//设置GPIO复用功能
GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_15);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
// GPIO初始化
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
相关函数
初始化之后即可使用以下函数来控制输出以及读取端口值:
//GPIO引脚置位
void GPIO_SetBits(GPIO_TypeDef* gpio, u16 pin);
//GPIO引脚复位
void GPIO_ResetBits(GPIO_TypeDef* gpio, u16 pin);
//GPIO写引脚(上面两个复合版本
void GPIO_WriteBit(GPIO_TypeDef* gpio, u16 pin, BitAction value);
//GPIO端口写入(控制多个引脚
void GPIO_Write(GPIO_TypeDef* gpio, u16 value);
定时器
定时器时钟
调试了以下,看了看定时器里面的寄存器的值,得知APB1总线预分频为4,APB2总线预分频为1:
结合时钟框图:
APB1总线频率分到定时器时钟时由于预分频不为1,故做一次2倍频,也即定时器时钟为AHB时钟2分频。同理,APB2总线频率与AHB相同。
基础定时中断
void MM_TIM_Init(void)
{
//定义定时器句柄
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
//使能外设时钟
// TIM2挂载在APB1总线上
RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM2, ENABLE);
//初始化以及赋值定时器结句柄
TIM_TimeBaseStructInit(&TIM_TimeBaseStruct);
TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStruct.TIM_Prescaler = 6000 - 1;
TIM_TimeBaseStruct.TIM_Period = 10000 - 1;
TIM_TimeBaseStruct.TIM_RepetitionCounter = 0;
//定时器初始化
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
//使能ARR预装载
TIM_ARRPreloadConfig(TIM2, ENABLE);
//清除更新中断标志位
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
//使能中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
//使能定时器
TIM_Cmd(TIM2, ENABLE);
}
void NVIC_Config(void)
{
//定义中断句柄
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
//初始化中断
NVIC_Init(&NVIC_InitStruct);
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
DebugLog("tim\n");
}
}
其中中断服务函数的函数名可以在启动文件的中断向量表中找到。
PWM输出
废话不多说,使用定时器5输出一路PWM:
void MM_PWM_Init(void)
{
//定义GPIO、时基单元、输出比较句柄
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;
//使能GPIO以及定时器时钟
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM5, ENABLE);
//初始化GPIO引脚复用功能
GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_2);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
//初始化时基、输出比较句柄
TIM_TimeBaseStructInit(&TIM_TimeBaseStruct);
TIM_OCStructInit(&TIM_OCInitStruct);
//时基句柄赋值
TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStruct.TIM_Prescaler = 60 - 1;
TIM_TimeBaseStruct.TIM_Period = 100000 - 1;
TIM_TimeBaseStruct.TIM_RepetitionCounter = 0;
//输出比较句柄赋值
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse = 5000;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
//初始化时基、输出比较功能
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStruct);
TIM_OC2Init(TIM5, &TIM_OCInitStruct);
//使能预装载寄存器
TIM_OC2PreloadConfig(TIM5, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM5, ENABLE);
//使能PWM输出
TIM_CtrlPWMOutputs(TIM5, ENABLE);
//使能定时器
TIM_Cmd(TIM5, ENABLE);
}
IIC通信
IIC初始化函数:
void MM_I2C_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_4);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_4);
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_20MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
I2C_InitTypeDef I2C_Initstruct;
RCC_APB1PeriphClockCmd(RCC_APB1ENR_I2C1, ENABLE);
I2C_StructInit(&I2C_Initstruct);
I2C_Initstruct.Mode = I2C_Mode_MASTER;
I2C_Initstruct.OwnAddress = 0;
I2C_Initstruct.ClockSpeed = 100000;
I2C_Initstruct.Speed = I2C_Speed_STANDARD;
I2C_Init(I2C1, &I2C_Initstruct);
I2C_Cmd(I2C1, ENABLE);
}
为了方便后续使用,封装了IIC读写函数,封装的过程挺崎岖的,主要是库函数说明不清不楚,某些注释甚至有问题,导致走了许多弯路。
IIC写函数:
uint8_t MM_I2C_WriteMem(I2C_TypeDef* i2c, uint8_t devAddr, uint8_t regAddr, uint8_t* pData, uint32_t len, uint32_t i2cTimeout)
{
uint32_t timeout;
I2C_GenerateSTART(i2c, ENABLE);
//发送从设备地址
I2C_Cmd(i2c, DISABLE);
I2C_Send7bitAddress(i2c, devAddr, I2C_Direction_Transmitter);
I2C_Cmd(i2c, ENABLE);
//发送寄存器地址并超时等待
timeout = i2cTimeout;
I2C_SendData(i2c, regAddr);
while ((I2C_GetFlagStatus(i2c, I2C_STATUS_FLAG_TFE) == RESET)) {
if (timeout-- == 0) {
I2C_GenerateSTOP(i2c, ENABLE);
return 1;
}
}
//发送数据包并超时等待
for (int i = 0; i < len; i++) {
timeout = i2cTimeout;
I2C_SendData(i2c, *(pData + i));
while ((I2C_GetFlagStatus(i2c, I2C_STATUS_FLAG_TFE) == RESET)) {
if (timeout-- == 0) {
I2C_GenerateSTOP(i2c, ENABLE);
return 2;
}
}
}
//发送停止信号
I2C_GenerateSTOP(i2c, ENABLE);
return 0;
}
IIC读函数:
uint8_t MM_I2C_ReadMem(I2C_TypeDef* i2c, uint8_t devAddr, uint8_t regAddr, uint8_t* pData, uint32_t len, uint32_t i2cTimeout)
{
uint32_t timeout;
//发送起始信号
I2C_GenerateSTART(i2c, ENABLE);
//发送从机地址
I2C_Cmd(i2c, DISABLE);
i2c->IC_TAR = devAddr >> 1;
I2C_Cmd(i2c, ENABLE);
//发送寄存器地址
timeout = i2cTimeout;
I2C_SendData(i2c, regAddr);
while ((I2C_GetFlagStatus(i2c, I2C_STATUS_FLAG_TFE) == RESET)) {
if (timeout-- == 0) {
I2C_GenerateSTOP(i2c, ENABLE);
return 1;
}
}
//接收并读取数据
uint8_t flag = 0, _cnt = 0;
for (int i = 0; i < len; i++) {
while (1) {
//
if ((I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFNF)) && (flag == 0)) {
I2C_ReadCmd(I2C1);
if (_cnt++ == len)
flag = 1;
}
if (I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_RFNE)) {
pData[i] = I2C_ReceiveData(I2C1);
break;
}
}
}
I2C_GenerateSTOP(i2c, ENABLE);
return 0;
}