NRF24L01 无线通信模块使用
最后更新于:2022-04-01 14:51:30
NRF24L01驱动代码下载:[http://download.csdn.net/detail/ieczw/7029597](http://download.csdn.net/detail/ieczw/7029597)
NRF24L01调试了近一个星期!多多少少有点浪费感情,因为由于板子的问题害的我一直无法调通,后来又找了两块板子,立马搞好!当然我的程序很大成分上是参考老一辈革命家的;但是在这之间,由于板子的问题,迫使我对NRF24L01的datasheet进行了一番研究!
NRF24L01与控制器的通信采用的SPI通信协议,这个协议具体细节大家可能都懂,不懂的可以之间百度;他们的通信模型是这样的:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bd8e6fb.jpg)
NRF*之间基本上可以互相通信,只要你地址设置的没有问题!他有六个通道,每一个通道都有自己的缓冲区,这六个通道可以同时跟不同的NRF进行通信,但是通信的两个之间的TX_ADDR和RX_ADDR一定要相同;
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bde9cb1.jpg)
这幅图能够充分的说明这个问题;Point1 to Point2,Point1和Point2的通道地址一定要一样;具体的设置在下面这个寄存器里面配置:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915be1a5c9.jpg)
NRF24L01有RX模式,TX模式,Standby-I模式,Standby-II模式,掉电模式;他们之间的模式转换如下图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915be88eb1.jpg)
这张图看着可能没有一点感觉,比较乱,下面这张可能会比较清晰一点:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bef106b.jpg)
只要我们想切换到什么模式就可以根据这个对寄存器进行配置;不过在配置的过程中,一点要注意适当的延时,我这次也被这个延时纠结了半天;
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bf51f44.jpg)
从Standy Mode转化到Tx/Rx mode 我这次就是可能延时的比较长,导致初始化一直没有成功,我刚开始一直的想法是,只要我等待的时间越长,他就越容易实现我的配置;看来这种观念需要改正下;
为了让整个传输稳定和快速,我可以可以设置自动应答模式;但是他只适合单通道模式:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915c03f427.jpg)
当发送方把数据发送给接收方,接收方会有一个短暂的延时大致130us,然后会高速发送方我接收了;然后接收方会产生一个中断信号,当发送方没有接收到这个ACk,他就会一直卡在这里;
说到他的中断,他有三种中断模式,一种是接收中断,二是发送完成中断,三发送超时中断;具体我们可以看STATU寄存器:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915c077511.jpg)
我们一般判断的主要是[6:4],RX_DR是接收到中断标志位,TX_DS发送完成中断标志位,MAX_RT超过最大发送次数标志位;这三位是可读可写的;写1的时候是清除中断标志位;这里还有一个预留位,我们一般不用,但是有时候我可以用他来测试看看是否写寄存器成功;当然,其他寄存器的预留位也可以来测试是否控制器与NRF模块通信是否正常。
另外还有FIFO_STATUS寄存器,他主要是检测FIFO寄存器的状态:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915c0d4628.jpg)
说这么久了,忘记说这个FIFO寄存器,也就是数据寄存器,他有32个Byte,所以我们在设置装载数据的大小时,只能最大设置为32byte;当然可以小于32byte;在接收模式下,如果接收完毕,我们最好把FIFO寄存器清空了,可以看FLUSH_RX寄存器,不过理论上,只要把数据读走,他都会自动清空;但是为了以防万一,还是清空了好;
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915c166810.jpg)
这几个是比较重要的寄存器,要写寄存器的话,必须以W_REGISTER为基地址+命令地址进行写操作;读的话同样的方法;但是一般不写R_REGISTER,因为AAAAA=5bit Register Map Address,正好R_Register的高三位为0,所有Register Map Address的高三位都为0,所以我们可以不用写了;但是对于写操作,高三为为001,故我们一般定义W_REGISTER为0x20,然后与Command与操作;即可;
R_RX_PAYLOAD与W_TX_PAYLOAD作为发送和接收的缓冲区;
有什么不懂得,我们可以留言继续进行讨论,我觉得datasheet说的真的是太清晰了,有点地方你没有看懂,我估计是你没有找到。
请各位多多指教!
STM32 加入调试信息来调试代码
最后更新于:2022-04-01 14:51:28
这个想法是从K60上得出来的;今天再帮一哥们看程序的时候,他可以用串口看出来那个文件那一行文件出现问题了,于是很好奇,就问他,他也不知道,然后我就细心的研究了下他的库;发现一个不错的调试方法,其实这个在stm32里面本身也是设置好了的,但是大家一致都没有去用;
先看stm32f10x_conf.h里面的一些内容:
~~~
/* Exported macro ------------------------------------------------------------*/
#ifdef USE_FULL_ASSERT
/**
* @brief The assert_param macro is used for function's parameters check.
* @param expr: If expr is false, it calls assert_failed function which reports
* the name of the source file and the source line number of the call
* that failed. If expr is true, it returns no value.
* @retval None
*/
#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
void assert_failed(uint8_t* file, uint32_t line);
#else
#define assert_param(expr) ((void)0)
#endif /* USE_FULL_ASSERT */
~~~
默认情况下是:
#define assert_param(expr) ((void)0)
明显,没有什么用,如果我们想调试的时候,可以加一个宏定义:
~~~
#define USE_FULL_ASSERT
~~~
那么我们就可以用这个判断了;
~~~
#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
~~~
void assert_failed(uint8_t* file, uint32_t line);
不过还没有完,我们需要在再写一个assert_failed的函数,来实现他的功能:
~~~
#ifdef USE_FULL_ASSERT
#include "stdio.h"
void assert_failed(uint8_t* file, uint32_t line)
{
char buff[64];
sprintf(buff,"%s %d",file,line);
RS232SendStr(buff);
while(1);
}
#endif
~~~
你还可以在里面加入其他调试信息;
this all!
【stm32+uC/OS-II】ucosii移植简单详细步骤
最后更新于:2022-04-01 14:51:25
μC/OS-II由Micrium公司提供,是一个可移植、可固化的、可裁剪的、占先式多任务实时内核,它适用于多种微处理器,微控制器和数字处理芯片(已经移植到超过100种以上的微处理器应用中)。同时,该系统源代码开放、整洁、一致,注释详尽,适合系统开发。 μC/OS-II已经通过联邦航空局(FAA)商用航行器认证,符合航空无线电技术委员会(RTCA)DO-178B标准。
**——摘自百度百科**
经过三天对uC/OS-II的研究和琢磨,成功移植了自己的uC/OS-II;回首看下,简单的移植是非常简单的;可能这句话比较啰嗦,等我下面解析完之后就认同了;
首先,来附图,我相信只要刚刚认识ucosii的人,都见过这种类型的图了;而且版本不一;
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bd44008.jpg)
这个是摘自一个教程上面的,但是我觉得还是有点复杂,然后,我又仔细的精简了框架和代码;当然只适合初学者;
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bd692c1.jpg)
可能有人还不知道什么是BSP,Board Support Package,板级支持包;这样的话,一看我们的代码就可以分为四部分了;
为了有点逻辑,我从uC/OS-II Source开始说起;这部分代码大家都知道是不用修改的,但是我们至少要知道他依赖的外部头文件都有哪些;
### Step one:
+------------------------------------------
|core:os_core.c
| os:os_flag.c os_mbox.c
| os_mem.c os_mutex.c
| os_q.c os_sem.c
| os_task.c os_time.c
| os_tmr.c
|head:ucos_ii.h
+------------------------------------------
理论上这几个文件我们完全不用修改!但是,他依赖外部的一些头文件;
app_cfg.h//应用程序的一些功能,目测这里是不需要的;但是他已经写了,那我们就保留吧;所以我们就要先建立一个空的app_cfg.h文件
os_cfg.h//做一些os功能的开关,我们可以由此来对系统进行一些裁剪;保留我们需要的功能;
os_cpu.h//为了适应os,我们必须把os与cpu之间建立一个桥梁;就是通过os_port来进行建立的;
### step two:
**os_ports**在uCOS-II\Ports\ARM-Cortex-M3\Generic\IAR
os_cpu_c.c//有两个地方要说下:1:在stm32的启动代码里面已经有一些功能函数了,所以我们要把他进行删除,以及他所附带的函数;并在os_cpu.h中注释点他们的外部声明;2:部分宏定义也删除了;
~~~
#if 0
#define OS_CPU_CM3_NVIC_ST_CTRL (*((volatile INT32U *)0xE000E010uL)) /* SysTick Ctrl & Status Reg. */
#define OS_CPU_CM3_NVIC_ST_RELOAD (*((volatile INT32U *)0xE000E014uL)) /* SysTick Reload Value Reg. */
#define OS_CPU_CM3_NVIC_ST_CURRENT (*((volatile INT32U *)0xE000E018uL)) /* SysTick Current Value Reg. */
#define OS_CPU_CM3_NVIC_ST_CAL (*((volatile INT32U *)0xE000E01CuL)) /* SysTick Cal Value Reg. */
#define OS_CPU_CM3_NVIC_PRIO_ST (*((volatile INT8U *)0xE000ED23uL)) /* SysTick Handler Prio Reg. */
#define OS_CPU_CM3_NVIC_ST_CTRL_COUNT 0x00010000uL /* Count flag. */
#define OS_CPU_CM3_NVIC_ST_CTRL_CLK_SRC 0x00000004uL /* Clock Source. */
#define OS_CPU_CM3_NVIC_ST_CTRL_INTEN 0x00000002uL /* Interrupt enable. */
#define OS_CPU_CM3_NVIC_ST_CTRL_ENABLE 0x00000001uL /* Counter mode. */
#define OS_CPU_CM3_NVIC_PRIO_MIN 0xFFu /* Min handler prio. */
#endif
#if 0
void OS_CPU_SysTickHandler (void)
void OS_CPU_SysTickInit (INT32U cnts)
#endif
~~~
os_cpu.h 同样把下面几个外部声明的函数给去掉;
~~~
#if 0
/* See OS_CPU_C.C */
void OS_CPU_SysTickHandler(void);
void OS_CPU_SysTickInit(void);
/* See BSP.C */
INT32U OS_CPU_SysTickClkFreq(void);
#endif
~~~
os_cpu_a.asm
这部分是汇编代码;由于他里面有部分指令集不适合stm32,所以我们要稍微改下:
1、将所有的PUBLIC 改为 EXPORT
2、把自己对齐部分也改下,也是因为指令集不匹配;
; RSEG CODE:CODE:NOROOT(2)
AREA |.text|, CODE, READONLY, ALIGN=2
THUMB
REQUIRE8
PRESERVE8
注:AREA 一点不能顶头写,这是规定,不然回编译出错;
os_dbg.c
`#define OS_COMPILER_OPT __root `
这个不兼容,需要把它改下;
~~~
#define OS_COMPILER_OPT //__root
~~~
### step three:
将ST的官方库导进去即可;
### step four:
startup_stm32f10x_hd.s
将 PendSV_Handler 替换成 OS_CPU_PendSVHandler
stm32f10x_it.c
加头文件: ucos_ii.h,并添加如下代码:
~~~
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
void SysTick_Handler(void)
{
OSIntEnter();
OSTimeTick();
OSIntExit();
}
~~~
os_cfg.h对部分功能进行剪裁;
~~~
#define OS_FLAG_EN0
#define OS_MBOX_EN 0
#define OS_MEM_EN 0
#define OS_MUTEX_EN 0
#define OS_Q_EN 0
#define OS_SEM_EN 0
#define OS_TMR_EN 0
#define OS_DEBUG_EN 0
#define OS_APP_HOOKS_EN 0
#define OS_EVENT_MULTI_EN 0
~~~
这样算是简单系统以及移植完成了,下面就是写自己的app了;我直接附上自己的main.c代码;
main.c
~~~
#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "ucos_ii.h"
#define ON 1
#define OFF 0
#define LED1(opt) ((opt) ? (GPIOD->BRR |= 1<<3):(GPIOD->BSRR |= 1<<3))
#define LED2(opt) ((opt) ? (GPIOD->BRR |= 1<<6):(GPIOD->BSRR |= 1<<6))
#define LED3(opt) ((opt) ? (GPIOB->BRR |= 1<<5):(GPIOB->BSRR |= 1<<5))
#define SystemFrequency 72000000
#define STARTUP_TASK_PRIO 4
#define STARTUP_TASK_STK_SIZE 80
void SysTick_init(void)
{
SysTick_Config(SystemFrequency/OS_TICKS_PER_SEC);
}
void LED_Init()
{
RCC->APB2ENR |= (1<<3)|(1<<5);
GPIOB->CRL &= ~(0xff<<20);
GPIOB->CRL |= 0x33<<(4*5);
GPIOD->CRL &= ~(0xff<<(4*3));
GPIOD->CRL &= ~(u32)((u32)0xff<<(4*6));
GPIOD->CRL |= 0x33<<(4*3);
GPIOD->CRL |= 0x33<<(4*6);
LED1(OFF);
LED2(OFF);
LED3(OFF);
}
void TestLed1(void *p_arg)
{
SysTick_init();
while(1)
{
LED1(ON);
OSTimeDlyHMSM(0,0,1,0);
LED1(OFF);
OSTimeDlyHMSM(0,0,1,0);
}
}
void TestLed2(void *p_arg)
{
SysTick_init();
while(1)
{
LED2(ON);
OSTimeDlyHMSM(0,0,0,500);
LED2(OFF);
OSTimeDlyHMSM(0,0,0,500);
}
}
void TestLed3(void *p_arg)
{
SysTick_init();
while(1)
{
LED3(ON);
OSTimeDlyHMSM(0,0,0,100);
LED3(OFF);
OSTimeDlyHMSM(0,0,0,100);
}
}
static OS_STK task_testled1[STARTUP_TASK_STK_SIZE];
static OS_STK task_testled2[STARTUP_TASK_STK_SIZE];
static OS_STK task_testled3[STARTUP_TASK_STK_SIZE];
int main()
{
LED_Init();
OSInit();
OSTaskCreate(TestLed1,(void *)0,&task_testled1[STARTUP_TASK_STK_SIZE-1],STARTUP_TASK_PRIO);
OSTaskCreate(TestLed2,(void *)0,&task_testled2[STARTUP_TASK_STK_SIZE-1],STARTUP_TASK_PRIO-1);
OSTaskCreate(TestLed3,(void *)0,&task_testled3[STARTUP_TASK_STK_SIZE-1],STARTUP_TASK_PRIO-2);
OSStart();
return 0;
}
~~~
OK,到此为止,已经移植完成;我们就可以测试下;希望大家多多指教;后期继续深入学习ucosii的其他功能;
stm32 灵活静态存储控制器(FSMC)(NORFLASH\PSRAM)
最后更新于:2022-04-01 14:51:23
Flexible static memory controller(FSMC)
今天在处理TFT彩屏的时候突然发现有人用FSMC控制器来处理,然后就认真的研究了下FSMC;
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bc9f3db.jpg)
可见他分为4个块,三个类型,我们可以根据自己的需要来选择;这次我就直说FSMC 的Block 1;
首先,基地址BASE_ADDR = 6000 0000;至于片选,datasheet上也说了,我们可以通过控制HADDR(27,26)来选择操作;
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bcb5cf3.jpg)
然后还有今天一直困扰我的问题,我要选择A16,我用的是16位数据,他的数据地址为6000 0000 + 2^16*2 = 6002 0000;我一直都在疑惑:明明是A16,为什么是第17位被置1,后来终于在datasheet上发现这个问题的根源!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bccb4b7.jpg)
上面说的很清楚,数据宽度为16位时,HADDR[25:1]与FSMC_A[24:0]相连,那么这时候的FSMC_A16,就与HADDR[17]相连,所以地址就是6000 0000 + 2^17;
下面来看看FSMC如何与TFT联系起来!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bce111e.jpg)
A16 --> RS -- 使能
D0~D15 数据线
FSMC_WE --> WE
FSMC_OE --> OE
这个连接方式让我想起了微机原理上的8059;呵呵,如果你学过了微机原理,看到这个图应该懂了80%;
代码分析:
~~~
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
FSMC_NORSRAMTimingInitTypeDef p;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC |
RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE , ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_SetBits(GPIOD, GPIO_Pin_13);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5 |
GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_14 |
GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 |
GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 |
GPIO_Pin_15;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 ;
GPIO_Init(GPIOD, &GPIO_InitStructure);
p.FSMC_AddressSetupTime = 0x02;
p.FSMC_AddressHoldTime = 0x02;
p.FSMC_DataSetupTime = 0x05;
p.FSMC_AccessMode = FSMC_AccessMode_B;
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM1;
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_NOR;
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &p;
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &p;
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE);
~~~
时钟初始化是必须的!希望大家不要forget了!
另外gpio的初始化,要看两个地方:1,数据手册上,每种管脚初始化成什么模式;2,哪些管脚需要初始化;
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bd02c6b.jpg)
管脚对应自己对着原理图一个一个找!
FSMC_NORSRAMTimingInitTypeDef 设置
p.FSMC_AddressSetupTime = 0x02;
p.FSMC_AddressHoldTime = 0x02;
p.FSMC_DataSetupTime = 0x05;
p.FSMC_AccessMode = FSMC_AccessMode_B;
这里我只设置了4项,因为其他几项都是与norflash有关的,所以我们不用设置,因为TFT内部是RAM;
具体时间参考,在datasheet上也给了说明:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bd2138f.jpg)
见网上有人设置FSMC_ADDressHoldTime = 0x0;发现也没啥影响,但是我们还是最好按照这个标准来!因为毕竟是官方给的标准!
~~~
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM1;
~~~
选择Bank1_NORSRAM1;因为我们本身就是选的他毋庸置疑;
~~~
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
~~~
我再TFT上也没有用到多路复用,因为我就只是跟TFT通信,说到这,我们还可以用它来控制4个彩屏一起显示!
~~~
FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_PSRAM;
~~~
这个参数是用来选择到底是哪个类型的存储器,他有三个option:
~~~
#define FSMC_MemoryType_SRAM ((uint32_t)0x00000000)
#define FSMC_MemoryType_PSRAM ((uint32_t)0x00000004)
#define FSMC_MemoryType_NOR ((uint32_t)0x00000008)
~~~
因为我不知道彩屏的内存到底是哪种类型的,我测试了下,三个选项对彩屏都是一样的效果,因为我对SRAM,NOR比较熟悉,所以就选了一个不熟悉的选项;
~~~
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
~~~
16位的数据宽度
~~~
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &p;
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &p;
~~~
然后我的彩屏控制基地址为
~~~
#define Bank1_LCD_DATA ((uint32_t)0x60020000)
#define Bank1_LCD_CTL ((uint32_t)0x60000000)
~~~
使用方法:
~~~
*(__IO uint16_t *) (Bank1_LCD_C)= reg;
*(__IO uint16_t *) (Bank1_LCD_D)= cmd;
~~~
可能理解的还不是太好,请大家多多指点;
stm32 DMA初始化选项研究
最后更新于:2022-04-01 14:51:21
DMA比较好用,也比较简单,今天在做多通道ADC“连续”“扫描”采样时,对DMA有了更深一点的认识,今天给大家分享下:
~~~
#define ADC1_DR_Address ((uint32_t)0x4001244C)
unsigned short Buff[3];
......
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_Address;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Buff;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 3;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
*** DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;***
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
**DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;**
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
~~~
初始化时,要先用DMA_DeInit将DMA的通道初始化成缺省值。
外设地址设置,内存基地址设置,设置外设的传输属性(输入/输出),这些都容易理解;
~~~
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
#define DMA_DIR_PeripheralDST ((uint32_t)0x00000010)
#define DMA_DIR_PeripheralSRC ((uint32_t)0x00000000)
~~~
这里是设置谁为数据传输源;
DMA_InitStructure.DMA_BufferSize = 3;
如果是获取一组数值,像ADC,USART,我们可以用一个缓冲区,如果不用,像ADC数据更新快的,马上就被刷新了。我这里的DMA_Buffersize 是 Buff的大小;
~~~
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
#define DMA_PeripheralInc_Enable ((uint32_t)0x00000040)
#define DMA_PeripheralInc_Disable ((uint32_t)0x00000000)
#define DMA_MemoryInc_Enable ((uint32_t)0x00000080)
#define DMA_MemoryInc_Disable ((uint32_t)0x00000000)
~~~
这个根据需要进行设置;
~~~
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
#define DMA_PeripheralDataSize_Byte ((uint32_t)0x00000000)
#define DMA_PeripheralDataSize_HalfWord ((uint32_t)0x00000100)
#define DMA_PeripheralDataSize_Word ((uint32_t)0x00000200)
#define DMA_MemoryDataSize_Byte ((uint32_t)0x00000000)
#define DMA_MemoryDataSize_HalfWord ((uint32_t)0x00000400)
#define DMA_MemoryDataSize_Word ((uint32_t)0x00000800)
~~~
这个设置获取数据的大小;
~~~
*DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;*
#define DMA_Mode_Circular ((uint32_t)0x00000020)
#define DMA_Mode_Normal ((uint32_t)0x00000000)
~~~
这个要强调下,像我这里,要不断更新Buff[0],Buff[1],Buff[2],我上面设置的缓冲区正好也是3,我们可以用Circular属性来设置!他就不停的刷新这段地址!
他还有一种属性是Normal,这种属性一般用在Point 2 Point时使用;
~~~
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
#define DMA_Priority_VeryHigh ((uint32_t)0x00003000)
#define DMA_Priority_High ((uint32_t)0x00002000)
#define DMA_Priority_Medium ((uint32_t)0x00001000)
#define DMA_Priority_Low ((uint32_t)0x00000000)
~~~
因为DMA有多路,所以我们要规定先传哪路数据,所以我们就要规定一个优先级;
~~~
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
#define DMA_M2M_Enable ((uint32_t)0x00004000)
#define DMA_M2M_Disable ((uint32_t)0x00000000)
~~~
这个是设置是不是Memary to Memary的!
SD卡fat文件系统移植
最后更新于:2022-04-01 14:51:19
经过充分的研究,发现fatfs文件系统移植的比较简单!因为代码都已经被别人做好了!我们只需把io层稍稍做个处理就ok了;
至于sd卡的驱动请看我这篇博客:[http://blog.csdn.net/ieczw/article/details/17378475](http://blog.csdn.net/ieczw/article/details/17378475)
移植是以这个驱动为前提的!!
[http://elm-chan.org/fsw/ff/00index_e.html](http://elm-chan.org/fsw/ff/00index_e.html)
这个网站发布了所有版本的文件fatfs文件系统,我这次下载最新版的http://elm-chan.org/fsw/ff/ff9a.zip
直接解压,里面有两个文件,一个是src,另一个是doc;
把src放到自己的工程目录下面,并添加。首先看看00Readme.txt,我们主要是关注这些东西
~~~
FILES
ffconf.h Configuration file for FatFs module.
ff.h Common include file for FatFs and application module.
ff.c FatFs module.
diskio.h Common include file for FatFs and disk I/O module.
diskio.c An example of glue function to attach existing disk I/O module to FatFs.
integer.h Integer type definitions for FatFs.
option Optional external functions.
Low level disk I/O module is not included in this archive because the FatFs
module is only a generic file system layer and not depend on any specific
storage device. You have to provide a low level disk I/O module that written
to control your storage device.
~~~
所以我们主要修改的是diskio.c
~~~
DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE*buff, DWORD sector, BYTE count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, BYTE count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
~~~
我们需要改的是这几个底层io函数,按照下面的修改,我想大家应该能懂!
~~~
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2012 */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various exsisting */
/* storage control module to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*/
#include "diskio.h" /* FatFs lower layer API */
#include "stm32_eval_sdio_sd.h"
/* Definitions of physical drive number for each media */
#define ATA 0
#define MMC 1
#define USB 2
#define SECTOR_SIZE 512U
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive */
DSTATUS disk_initialize (
BYTE drv /* Physical drive nmuber (0..) */
)
{
return SD_Init();
}
/*-----------------------------------------------------------------------*/
/* Return Disk Status */
DSTATUS disk_status (
BYTE drv /* Physical drive nmuber (0..) */
)
{
return SD_GetStatus();
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s) */
DRESULT disk_read (
BYTE drv, /* Physical drive nmuber (0..) */
BYTE *buff, /* Data buffer to store read data */
DWORD sector, /* Sector address (LBA) */
BYTE count /* Number of sectors to read (1..255) */
)
{
return (DRESULT)SD_ReadBlock(buff,sector << 9 ,count);
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s) */
#if _READONLY == 0
DRESULT disk_write (
BYTE drv, /* Physical drive nmuber (0..) */
const BYTE *buff, /* Data to be written */
DWORD sector, /* Sector address (LBA) */
BYTE count /* Number of sectors to write (1..255) */
)
{
return (DRESULT)SD_WriteBlock((uint8_t *)buff,sector << 9 ,count);
}
#endif /* _READONLY */
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions */
DRESULT disk_ioctl (
BYTE drv, /* Physical drive nmuber (0..) */
BYTE ctrl, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
return RES_OK;
}
unsigned short get_fattime(void){
return 0;
}
~~~
这些修改完后,如果你直接编译会出现如下错误:
..\fatfs\cc936.c(11): error: #35: #error directive: This file is not needed in current configuration. Remove from the project.
找到cc936.c,他的头文件是这样定义的
~~~
/*------------------------------------------------------------------------*/
/* Unicode - OEM code bidirectional converter (C)ChaN, 2009 */
/* */
/* CP936 (Simplified Chinese GBK) */
/*------------------------------------------------------------------------*/
#include "../ff.h"
#if !_USE_LFN || _CODE_PAGE != 936
#error This file is not needed in current configuration. Remove from the project.
#endif
~~~
他是找不到USE_LFN或者_CODE_PAGE !=936
那么可能头文件处理问题,大家应该发现问题了,把头文件修改下:#include "ff.h"
为什么要用936的呢?大家看ffconf.h,有这么一段注释
~~~
/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/----------------------------------------------------------------------------*/
#define _CODE_PAGE 936
/* The _CODE_PAGE specifies the OEM code page to be used on the target system.
/ Incorrect setting of the code page can cause a file open failure.
/
/ 932 - Japanese Shift-JIS (DBCS, OEM, Windows)
/ 936 - Simplified Chinese GBK (DBCS, OEM, Windows)
/ 949 - Korean (DBCS, OEM, Windows)
/ 950 - Traditional Chinese Big5 (DBCS, OEM, Windows)
/ 1250 - Central Europe (Windows)
/ 1251 - Cyrillic (Windows)
/ 1252 - Latin 1 (Windows)
/ 1253 - Greek (Windows)
/ 1254 - Turkish (Windows)
/ 1255 - Hebrew (Windows)
/ 1256 - Arabic (Windows)
/ 1257 - Baltic (Windows)
/ 1258 - Vietnam (OEM, Windows)
/ 437 - U.S. (OEM)
/ 720 - Arabic (OEM)
/ 737 - Greek (OEM)
/ 775 - Baltic (OEM)
/ 850 - Multilingual Latin 1 (OEM)
/ 858 - Multilingual Latin 1 + Euro (OEM)
/ 852 - Latin 2 (OEM)
/ 855 - Cyrillic (OEM)
/ 866 - Russian (OEM)
/ 857 - Turkish (OEM)
/ 862 - Hebrew (OEM)
/ 874 - Thai (OEM, Windows)
/ 1 - ASCII only (Valid for non LFN cfg.)
*/
~~~
所以,因为我们是中国人,所以我们要用936!哈哈。。。
然后编译又出现另一个问题:
..\output\stm32.axf: Error: L6218E: Undefined symbol get_fattime (referred from ff.o).
这个我们需要自己添加,我在上面修改diskio.c的时候已经添加了。
这些做完之后,也算移植玩了,简单吧,至于如何使用,我们还是看下载网页:[http://elm-chan.org/fsw/ff/00index_e.html](http://elm-chan.org/fsw/ff/00index_e.html)
或者双击doc/目录下的00index_e.html
~~~
Application Interface
FatFs module provides following functions to the applications. In other words, this list describes what FatFs can do to access the FAT volumes.
f_open - Open/Create a file
f_close - Close an open file
f_read - Read file
f_write - Write file
f_lseek - Move read/write pointer, Expand file size
f_truncate - Truncate file size
f_sync - Flush cached data
f_forward - Forward file data to the stream
f_stat - Check existance of a file or sub-directory
f_opendir - Open a directory
f_closedir - Close an open directory
f_readdir - Read a directory item
f_mkdir - Create a sub-directory
f_unlink - Remove a file or sub-directory
f_chmod - Change attribute
f_utime - Change timestamp
f_rename - Rename/Move a file or sub-directory
f_chdir - Change current directory
f_chdrive - Change current drive
f_getcwd - Retrieve the current directory
f_getfree - Get free clusters
f_getlabel - Get volume label
f_setlabel - Set volume label
f_mount - Register/Unregister a work area
f_mkfs - Create a file system on the drive
f_fdisk - Divide a physical drive
f_gets - Read a string
f_putc - Write a character
f_puts - Write a string
f_printf - Write a formatted string
f_tell - Get current read/write pointer
f_eof - Test for end-of-file on a file
f_size - Get size of a file
f_error - Test for an error on a file
~~~
里面对每个函数都做了说明,这样就有点像linux操作系统里面的文件io操作了。
我们可以任意点击去一个函数,里面都有例程,我们可以用这些例程来测试下
~~~
/* Read a text file and display it */
FATFS FatFs; /* Work area (file system object) for logical drive */
int main (void)
{
FIL fil; /* File object */
char line[82]; /* Line buffer */
FRESULT fr; /* FatFs return code */
/* Register work area to the default drive */
f_mount(&FatFs, "", 0);
/* Open a text file */
fr = f_open(&fil, "message.txt", FA_READ);
if (fr) return (int)fr;
/* Read all lines and display it */
while (f_gets(line, sizeof line, &fil))
printf(line);
/* Close the file */
f_close(&fil);
return 0;
}
~~~
好,基于STM32的fatfs文件系统移植就到这里了!!!!
群名称是蓝桥杯-嵌入式交流群
147520657
【stm32库应用】SD驱动移植(基于SDIO外设)
最后更新于:2022-04-01 14:51:16
上星期六刚刚买的新板子,因为之前的板子是蓝桥杯竞赛专用板(STM32F103RB)64PIN的,外设很少,以后比赛结束还要把这个烂板子交个学校创新创业中心!
由于之前答应@蓝桥杯-嵌入式交流群里面的哥们们把SD卡搞下,所以就接着新板子,来处理下SD卡这个东西(后期还将做fatfs文件系统移植)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bc3e90a.jpg)
图1 SD接口图
图1 的接口图不是完全正确的,每个PIN上都必须接一个50K的上拉电阻;
在ST官方提供的库里面有很多意见做好的外设,LCD,EEPROM,等等,当然我们比较幸运,也包括SD卡,这次移植基于3.5的库
我们要移植的文件在这个STM32F10x_StdPeriph_Lib_V3.5.0\Utilities\STM32_EVAL下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bc55243.jpg)
图2 移植需要的文件
因为我的板子是stm32f103vet6跟STM3210E_EVAL比较相近,所以选这个;
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bc70920.jpg)
图3 common目录
把stm32_eval_spi_sd.c / stm32_eval_spi_sd.h 复制到你的工程里面,并添加;如果就这样编译的话有很多error和warning;
因为他里面确实一些函数,这些函数属于API层、或者说是驱动层的,跟处理器有关,所以我们就要到STM3210E_EVAL目录下看看了。
如果你把上面那个直接拿过去编译的话,会提示少下面一些函数:
~~~
void SD_LowLevel_DeInit(void);
void SD_LowLevel_Init(void);
void SD_LowLevel_DMA_TxConfig(uint32_t *BufferSRC, uint32_t BufferSize);
void SD_LowLevel_DMA_RxConfig(uint32_t *BufferDST, uint32_t BufferSize);
uint32_t SD_DMAEndOfTransferStatus(void);
~~~
和一些宏定义:
~~~
/**
* @brief SD FLASH SDIO Interface
*/
#define SD_DETECT_PIN GPIO_Pin_11 /* PF.11 */
#define SD_DETECT_GPIO_PORT GPIOF /* GPIOF */
#define SD_DETECT_GPIO_CLK RCC_APB2Periph_GPIOF
#define SDIO_FIFO_ADDRESS ((uint32_t)0x40018080)
/**
* @brief SDIO Intialization Frequency (400KHz max)
*/
#define SDIO_INIT_CLK_DIV ((uint8_t)0xB2)
/**
* @brief SDIO Data Transfer Frequency (25MHz max)
*/
#define SDIO_TRANSFER_CLK_DIV ((uint8_t)0x00)
~~~
这些声明和定义都在STM3210E_EVAL目录下的 "stm3210e_eval.h"里面,找到他们并复制到stm32_eval_spi_sd.h里面,如果你不想找的话,直接把我的这些复制走就好了!
对应的函数也都在"stm3210e_eval.c"里面;
~~~
/**
* @brief DeInitializes the SDIO interface.
* @param None
* @retval None
*/
void SD_LowLevel_DeInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*!< Disable SDIO Clock */
SDIO_ClockCmd(DISABLE);
/*!< Set Power State to OFF */
SDIO_SetPowerState(SDIO_PowerState_OFF);
/*!< DeInitializes the SDIO peripheral */
SDIO_DeInit();
/*!< Disable the SDIO AHB Clock */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_SDIO, DISABLE);
/*!< Configure PC.08, PC.09, PC.10, PC.11, PC.12 pin: D0, D1, D2, D3, CLK pin */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/*!< Configure PD.02 CMD line */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOD, &GPIO_InitStructure);
}
/**
* @brief Initializes the SD Card and put it into StandBy State (Ready for
* data transfer).
* @param None
* @retval None
*/
void SD_LowLevel_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*!< GPIOC and GPIOD Periph clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | SD_DETECT_GPIO_CLK, ENABLE);
/*!< Configure PC.08, PC.09, PC.10, PC.11, PC.12 pin: D0, D1, D2, D3, CLK pin */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/*!< Configure PD.02 CMD line */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOD, &GPIO_InitStructure);
/*!< Configure SD_SPI_DETECT_PIN pin: SD Card detect pin */
GPIO_InitStructure.GPIO_Pin = SD_DETECT_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(SD_DETECT_GPIO_PORT, &GPIO_InitStructure);
/*!< Enable the SDIO AHB Clock */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_SDIO, ENABLE);
/*!< Enable the DMA2 Clock */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
}
/**
* @brief Configures the DMA2 Channel4 for SDIO Tx request.
* @param BufferSRC: pointer to the source buffer
* @param BufferSize: buffer size
* @retval None
*/
void SD_LowLevel_DMA_TxConfig(uint32_t *BufferSRC, uint32_t BufferSize)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_ClearFlag(DMA2_FLAG_TC4 | DMA2_FLAG_TE4 | DMA2_FLAG_HT4 | DMA2_FLAG_GL4);
/*!< DMA2 Channel4 disable */
DMA_Cmd(DMA2_Channel4, DISABLE);
/*!< DMA2 Channel4 Config */
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)BufferSRC;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = BufferSize / 4;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA2_Channel4, &DMA_InitStructure);
/*!< DMA2 Channel4 enable */
DMA_Cmd(DMA2_Channel4, ENABLE);
}
/**
* @brief Configures the DMA2 Channel4 for SDIO Rx request.
* @param BufferDST: pointer to the destination buffer
* @param BufferSize: buffer size
* @retval None
*/
void SD_LowLevel_DMA_RxConfig(uint32_t *BufferDST, uint32_t BufferSize)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_ClearFlag(DMA2_FLAG_TC4 | DMA2_FLAG_TE4 | DMA2_FLAG_HT4 | DMA2_FLAG_GL4);
/*!< DMA2 Channel4 disable */
DMA_Cmd(DMA2_Channel4, DISABLE);
/*!< DMA2 Channel4 Config */
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)BufferDST;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = BufferSize / 4;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA2_Channel4, &DMA_InitStructure);
/*!< DMA2 Channel4 enable */
DMA_Cmd(DMA2_Channel4, ENABLE);
}
/**
* @brief Returns the DMA End Of Transfer Status.
* @param None
* @retval DMA SDIO Channel Status.
*/
uint32_t SD_DMAEndOfTransferStatus(void)
{
return (uint32_t)DMA_GetFlagStatus(DMA2_FLAG_TC4);
}
~~~
同样,把这些复制到stm32_eval_spi_sd.c里面;你也可以从这里直接复制!
就这么多,如果你不是我这个平台的处理器,那么一定要注意找到适合你自己的再移植!
**如何判别是否适合呢?**
在STM321xxxE_EVAL目录下的stm32xxxe_eval.c里面看函数void SD_LowLevel_DeInit(void);看他初始化的管脚是否是你的外设管脚;
~~~
/*!< Configure PC.08, PC.09, PC.10, PC.11, PC.12 pin: D0, D1, D2, D3, CLK pin */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOC, &GPIO_InitStructure);
~~~
OK!这些移植好后,我们可以到STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\SDIO\uSDCard里面把例程移植一个看看效果怎么样;
我这里用了这个代码,看看他有SD卡有多大:
~~~
Status = SD_Init();
Status = SD_GetCardInfo(&SDCardInfo);
printf("%d,%d\n",SDCardInfo.CardBlockSize,SDCardInfo.CardCapacity);
Status = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16));
Status = SD_EnableWideBusOperation(SDIO_BusWide_4b);
Status = SD_ReadBlock(buff, 0x00, 512);
if (Status == SD_OK)
printf("\nRead%s",buff);
printf("Test SD OK!");
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bc87dc2.jpg)
图4: 效果图
【1:block size 2:memory size 3:read message】
今天就先到这里了!周日之前把fatfs文件系统移植好!
再次感谢 @STM32-粤-十万 的启发!!!
蓝桥杯-嵌入式交流群
147520657
【STM库应用】stm32 之 TIM (详解二 脉冲宽度、周期测量)
最后更新于:2022-04-01 14:51:14
昨天已经把这个研究出来了,但是由于修改专利申请书,一直没有时间上传,今天补上!
今天主要是用TIM3进行PWM的输入模式,进行对矩形波的脉冲信号宽度以及其周期进行测量,先来看一幅图。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bb8cee0.jpg)
**图1 TIM内部逻辑图**
我们先来看看datasheet上是怎么说的:
该模式是输入捕获模式的一个特例,除下列区别外,操作与输入捕获模式相同:
● 两个ICx信号被映射至同一个TIx输入。
● 这2个ICx信号为边沿有效,但是极性相反。
● 其中一个TIxFP信号被作为触发输入信号,而从模式控制器被配置成复位模式。
例如,你需要测量输入到TI1上的PWM信号的长度(TIMx_CCR1寄存器)和占空比(TIMx_CCR2
寄存器),具体步骤如下(取决于CK_INT的频率和预分频器的值)
● 选择TIMx_CCR1的有效输入:置TIMx_CCMR1寄存器的CC1S=01(选择TI1)。
● 选择TI1FP1的有效极性(用来捕获数据到TIMx_CCR1中和清除计数器):置CC1P=0(上升沿
有效)。
● 选择TIMx_CCR2的有效输入:置TIMx_CCMR1寄存器的CC2S=10(选择TI1)。
● 选择TI1FP2的有效极性(捕获数据到TIMx_CCR2):置CC2P=1(下降沿有效)。
● 选择有效的触发输入信号:置TIMx_SMCR寄存器中的TS=101(选择TI1FP1)。
● 配置从模式控制器为复位模式:置TIMx_SMCR中的SMS=100。
● 使能捕获:置TIMx_CCER寄存器中CC1E=1且CC2E=1。
我们从图1上可以看出只有TI1FP1和TI2FP2连到了从模式控制器,所以PWM输入模式只能使用TIMx_CH1 /TIMx_CH2信号。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bbb1393.jpg)
**图2 IC1,IC2捕获信号图**
TI1为输入PWM波信号波形,TIMx_CNT为计数器计数值,当第一个下降沿信号到来,IC2会先进行捕获,然后到下一个上升沿到来,IC1进行捕获!在捕获的同时,IC1和IC2会吧CNT的值映射到对应的CCR1、CCR2的寄存器里面,这个值就是我们需要的计数值!
那么我们所需测量的周期就是IC1捕获的值,脉冲宽度即为IC2测量的值了!
可是,这里有个bug,不注意的人,很容易犯错!我们的CCRx寄存器是一个16bit的寄存器计数范围为0~0xffff,即0~65535,可是当测量的矩形波周期太长,脉冲宽度太宽,二我们的计数又太快,很容易出现CCRx溢出现象,所以,我们在测量低频信号时,一定要对TIM的时钟进行分频,这样我们的计数就不会那么快了,也不会产生溢出现象,当我们计数计完了,再把分频所导致的结果进行倍频就OK了。
还有个地方,可能你只按照我上面说的进行做,你会发现他总是跟实际的频率,或者周期差那么一点点!其实这是有来由的!下面看看图3:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bbc812a.jpg)
**图3 注意点**
根据图3,我们可以看出他在开始计数的过程中会差那么三个时钟,所以我们要在数值上进行补齐!也就这么多!下面附上代码:
~~~
void inpwm_init(void)
{
TIM_ICInitTypeDef TIM_ICInitStructure;
/* TIM3 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
/* GPIOA clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* NVIC configuration */
NVIC_Configuration();
/* Configure the GPIO ports */
GPIO_Configuration();
/* TIM3 configuration: PWM Input mode ------------------------
The external signal is connected to TIM3 CH2 pin (PA.07),
The Rising edge is used as active edge,
The TIM3 CCR2 is used to compute the frequency value
The TIM3 CCR1 is used to compute the duty cycle value
------------------------------------------------------------ */
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x0;
TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);
/* Select the TIM3 Input Trigger: TI2FP2 */
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
/* Select the slave Mode: Reset Mode */
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
/* Enable the Master/Slave Mode */
TIM_SelectMasterSlaveMode(TIM3, TIM_MasterSlaveMode_Enable);
/* TIM enable counter */
TIM_Cmd(TIM3, ENABLE);
/* Enable the CC2 Interrupt Request */
TIM_ITConfig(TIM3, TIM_IT_CC2|TIM_IT_CC1, ENABLE);
}
~~~
~~~
void TIM3_IRQHandler(void)
{
if(TIM_GetFlagStatus(TIM3,TIM_FLAG_CC1) == SET)
{
DutyCycle = TIM_GetCapture1(TIM3)+3;
Frequency = 72000000 / DutyCycle;
TIM_ClearFlag(TIM3,TIM_FLAG_CC1);
}
if(TIM_GetFlagStatus(TIM3,TIM_FLAG_CC2) == SET)
{
IC2Value = TIM_GetCapture2(TIM3)+4;
TIM_ClearFlag(TIM3,TIM_FLAG_CC2);
}
}
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bbe6a3e.jpg)
**图4 实际波形**
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bc177f7.jpg)
**图5 测量结果**
蓝桥杯-嵌入式交流群
147520657
【STM库应用】stm32 之 TIM (详解一 通用定时器)
最后更新于:2022-04-01 14:51:12
比赛终于结束了,好几天没有更新了,今天重新开始!进入深入学习TIM!
定时器,中断这两样东西是学习一个MCU必须掌握的,也是非常有用的!
STM32的TIM一般有高级定时器TIM1,(TIM8只有在互联性产品有),普通定时器TIM2,TIM3,TIM4,(TIM5,TIM6,TIM7有点设备中没有);今天就只介绍普通定时器,因为高级定时器我还不会!每一个普通定时器都有4路通道!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bb129e9.jpg)
我们先看看这个逻辑图吧!我们今天先讨论讨论定时器的问题!我用红色笔标过的路线就是定时器的工作路线,时钟有内部时钟产生,到PSC哪里进行分频处理,然后CNT进行计数,上面还有一个自动重装载寄存器APP。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bb30d8f.jpg)
这个是分频器的工作原理,我们可以看,分频器设定之前分频系数为1[1],后面的[2][3][4]分频系数为2,分频系数改变后,计数周期也跟着改变了;同时预分频设置生效时,他还会产生一个中断信号,这个中断信号不要管他,一个系统时钟周期后会自动消失,跟I2C的差不多!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bb4672c.jpg)
这个是计数过程,上面说过了,计数跟分频后的周期有关;当计数达到装载的数值之后,系统会产生一个三个信号,其中溢出信号和更新事件一个时钟周期后会自动消失,而这时候触发了更新中断标志位UIF,我们可以用这个UPDATE来做定时器的中断标志信号!
TIM_ITConfig(TIM2, TIM_IT_UPDATE, ENABLE);
另外还有4个中断,我们知道PWM的产生把,他就跟PWM的产生有着血缘关系!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915bb5d086.jpg)
如果我们在OC模式选择的时候,TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;选择了Timing那么我们就可以做定时器了。
有时候我们会发现当UPdate中断信号产生时,其他四个中断信号也产生,什么原因呢?因为我们在设置tim.TIM_Period = period;时,period时间太短,就错觉的以为他们是一群产生中断信号的!来我们分析下:
假设分频因子为71,即72分频,PCLK为72M,我们设置的周期为1000;那么我们产生定时器的Upddate信号频率为1000Hz,周期为1ms,假设我们这里设置的CCR1 = 100;CCR2 = 500;CCR3 = 600;CCR4 = 900;
那么update信号产生后0.1ms产生CCR1的中断信号,0.5ms后产生CCR2的中断信号,0.6ms后产生CCR3的中断信号,这些中断标志位是:
~~~
#define TIM_FLAG_Update ((uint16_t)0x0001)
#define TIM_FLAG_CC1 ((uint16_t)0x0002)
#define TIM_FLAG_CC2 ((uint16_t)0x0004)
#define TIM_FLAG_CC3 ((uint16_t)0x0008)
#define TIM_FLAG_CC4 ((uint16_t)0x0010)
#define TIM_FLAG_COM ((uint16_t)0x0020)
#define TIM_FLAG_Trigger ((uint16_t)0x0040)
#define TIM_FLAG_Break ((uint16_t)0x0080)
#define TIM_FLAG_CC1OF ((uint16_t)0x0200)
#define TIM_FLAG_CC2OF ((uint16_t)0x0400)
#define TIM_FLAG_CC3OF ((uint16_t)0x0800)
#define TIM_FLAG_CC4OF ((uint16_t)0x1000)
~~~
[OF为溢出]后面做研究!
对于这些CCR1在定时方面的应用我还没有想出什么好的点子,但是他确实存在,如果想验证的话,我们可以把周期设置长一点,比如设置为1s,然后通过设置CCR的值,来看看效果,呵呵,不过这是不容易实现的,因为CCRx 最大为0xffff = 65535跟72M比比,还能说什么?好吧,谁有什么好办法,可以跟我说下!
~~~
void tim_init(u32 period,u32 psc)
{
TIM_TimeBaseInitTypeDef tim;
TIM_OCInitTypeDef TIM_OCInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
tim.TIM_Period = period;
tim.TIM_Prescaler = psc;
tim.TIM_ClockDivision = 0;
tim.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &tim);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse = 1*period/4;
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Disable);
TIM_OCInitStructure.TIM_Pulse = 2*period/4;
TIM_OC3Init(TIM2, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Disable);
TIM_OCInitStructure.TIM_Pulse = 3*period/4;
TIM_OC4Init(TIM2, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Disable);
TIM_ITConfig(TIM2, TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4, ENABLE);
TIM_Cmd(TIM2,ENABLE);
/* Enable the TIM2 global Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
~~~
~~~
void TIM2_IRQHandler(void)
{
static u32 cc2 = 0,cc3 = 0,cc4 = 0,cc5 = 0;
static u8 flag2 = 0,flag3 = 0,flag4 = 0,flag5 = 0;
u32 capture;
if(TIM_GetFlagStatus(TIM2,TIM_FLAG_CC2) == SET)
{
cc2 ++;
if(cc2 > 1000)
{
flag2 = ~flag2;
if(flag2) led_on(4);
else led_off(4);
cc2 = 0;
}
TIM_ClearFlag(TIM2,TIM_FLAG_CC2);
capture = TIM_GetCapture2(TIM2);
TIM_SetCompare2(TIM2, capture + 255);
}
if(TIM_GetFlagStatus(TIM2,TIM_FLAG_CC3) == SET)
{
cc3 ++;
if(cc3 > 1000)
{
flag3 = ~flag3;
if(flag3) led_on(5);
else led_off(5);
cc3 = 0;
}
TIM_ClearFlag(TIM2,TIM_FLAG_CC3);
capture = TIM_GetCapture3(TIM2);
TIM_SetCompare3(TIM2, capture + 500);
}
if(TIM_GetFlagStatus(TIM2,TIM_FLAG_CC4) == SET)
{
cc4 ++;
if(cc4 > 1000)
{
flag4 = ~flag4;
if(flag4) led_on(6);
else led_off(6);
cc4 = 0;
}
TIM_ClearFlag(TIM2,TIM_FLAG_CC4);
capture = TIM_GetCapture4(TIM2);
TIM_SetCompare4(TIM2, capture + 725);
}
if(TIM_GetFlagStatus(TIM2,TIM_FLAG_Update) == SET)
{
cc5 ++;
if(cc5 > 1000)
{
flag5 = ~flag5;
if(flag5) led_on(7);
else led_off(7);
cc5 = 0;
}
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
}
}
~~~
蓝桥杯-嵌入式交流群 147520657
关于结构体初始化
最后更新于:2022-04-01 14:51:09
我们一般见的结构体初始化有两种:
~~~
typedef struct
{
uint32_t I2C_ClockSpeed; /*!< Specifies the clock frequency.
This parameter must be set to a value lower than 400kHz */
uint16_t I2C_Mode; /*!< Specifies the I2C mode.
This parameter can be a value of @ref I2C_mode */
uint16_t I2C_DutyCycle; /*!< Specifies the I2C fast mode duty cycle.
This parameter can be a value of @ref I2C_duty_cycle_in_fast_mode */
uint16_t I2C_OwnAddress1; /*!< Specifies the first device own address.
This parameter can be a 7-bit or 10-bit address. */
uint16_t I2C_Ack; /*!< Enables or disables the acknowledgement.
This parameter can be a value of @ref I2C_acknowledgement */
uint16_t I2C_AcknowledgedAddress; /*!< Specifies if 7-bit or 10-bit address is acknowledged.
This parameter can be a value of @ref I2C_acknowledged_address */
}I2C_InitTypeDef;
~~~
比如这个结构体,我们一般这样初始化:
~~~
I2C_InitTypeDef i2c;
i2c.I2C_Ack = I2C_Ack_Enable;
i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
i2c.I2C_ClockSpeed = clock*1000;
i2c.I2C_DutyCycle = I2C_DutyCycle_2;
i2c.I2C_Mode = I2C_Mode_I2C;
i2c.I2C_OwnAddress1 = addr;
~~~
~~~
I2C_InitTypeDef i2c = {
I2C_Ack_Enable,
I2C_AcknowledgedAddress_7bit,
clock*1000,
I2C_DutyCycle_2,
I2C_Mode_I2C,
addr};
~~~
希望大家不要纠结我这个结构体变量的顺序,肯定错了,我只是说下有这种方法!
但是在linux内核里面有这样一种初始化方法:
~~~
I2C_InitTypeDef i2c = {
.I2C_Ack = I2C_Ack_Enable ,
.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit,
.I2C_ClockSpeed = 40000,
.I2C_DutyCycle = I2C_DutyCycle_2,
.I2C_Mode = I2C_Mode_I2C ,
.I2C_OwnAddress1 = 0x0a
};
~~~
但是在keil里面默认是不可以用这种方法初始化的,他属于C99的标准,但是keil默认是C90的标准!
如果需要这样用的话,或者你拿到的工程代码里面有这样定义的,我们可以这样设置就可以了!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915baa90a7.jpg)
蓝桥杯-嵌入式交流群
147520657
【STM库应用】stm32 之 中断按键初始化(注意事项)
最后更新于:2022-04-01 14:51:07
之前做终端按键的时候都是只做了一个,没有做多个,昨天在把所有按键都设置成中断模式的时候遇到问题,于是乎还跟一个网上的哥们进行了热议,后来还是我发现了问题!最终把问题给解决了!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915ba8b878.jpg)
我的按键的GPIO连接有点奇葩,他不是连续的,这可能就是竞赛板故意设置的难度吧!
首先管脚初始化:
~~~
GPIO_InitTypeDef key;
RCC->APB2ENR |= ((1<<0)|(1<<2)|(1<<3));
key.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_8;
key.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOA, &key);
key.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2;
key.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOB, &key);
~~~
全部设置成输入模式,AFIO再时钟使能的时候不要忘记了!这里我就不多说了!
然后就是中断组设置:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC初始化:
~~~
key_nvic.NVIC_IRQChannel = EXTI0_IRQn;
key_nvic.NVIC_IRQChannelCmd = ENABLE;
key_nvic.NVIC_IRQChannelPreemptionPriority = 0;
key_nvic.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&key_nvic);
~~~
重点都不在这,值得注意的是下面:
我第一次在配置EXTI Line的时候这样配置!
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA|GPIO_PortSourceGPIOB,\
GPIO_PinSource0|GPIO_PinSource1|GPIO_PinSource2|GPIO_PinSource8);
大致一看,貌似很正常啊!但是问题就出在这!
我们跳转到GPIO_PinSourcex和GPIO_PortSourceGPIOx哪里看看:
~~~
#define GPIO_PortSourceGPIOA ((uint8_t)0x00)
#define GPIO_PortSourceGPIOB ((uint8_t)0x01)
#define GPIO_PortSourceGPIOC ((uint8_t)0x02)
#define GPIO_PortSourceGPIOD ((uint8_t)0x03)
#define GPIO_PortSourceGPIOE ((uint8_t)0x04)
#define GPIO_PortSourceGPIOF ((uint8_t)0x05)
#define GPIO_PortSourceGPIOG ((uint8_t)0x06)
~~~
~~~
#define GPIO_PinSource0 ((uint8_t)0x00)
#define GPIO_PinSource1 ((uint8_t)0x01)
#define GPIO_PinSource2 ((uint8_t)0x02)
#define GPIO_PinSource3 ((uint8_t)0x03)
#define GPIO_PinSource4 ((uint8_t)0x04)
#define GPIO_PinSource5 ((uint8_t)0x05)
#define GPIO_PinSource6 ((uint8_t)0x06)
#define GPIO_PinSource7 ((uint8_t)0x07)
#define GPIO_PinSource8 ((uint8_t)0x08)
#define GPIO_PinSource9 ((uint8_t)0x09)
#define GPIO_PinSource10 ((uint8_t)0x0A)
#define GPIO_PinSource11 ((uint8_t)0x0B)
#define GPIO_PinSource12 ((uint8_t)0x0C)
#define GPIO_PinSource13 ((uint8_t)0x0D)
#define GPIO_PinSource14 ((uint8_t)0x0E)
#define GPIO_PinSource15 ((uint8_t)0x0F)
~~~
我们来计算下:
GPIO_PortSourceGPIOA | GPIO_PortSourceGPIOB = 0x00 | 0x01 = 0x01 = GPIO_PortSourceGPIOB
GPIO_PinSource0 | GPIO_PinSource1 | GPIO_PinSource2 | GPIO_PinSource8 = 0x00 | 0x01 | 0x02 | 0x08 = 0x0b = GPIO_PinSource11
所以我最后初始化后的中断就成为: GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource11);
最终让我事与愿违了。发现这个问题后,我仔细研究了一下GPIO_EXTILineConfig函数
~~~
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
{
uint32_t tmp = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_EXTI_PORT_SOURCE(GPIO_PortSource));
assert_param(IS_GPIO_PIN_SOURCE(GPIO_PinSource));
tmp = ((uint32_t)0x0F) << (0x04 * (GPIO_PinSource & (uint8_t)0x03));
AFIO->EXTICR[GPIO_PinSource >> 0x02] &= ~tmp;
AFIO->EXTICR[GPIO_PinSource >> 0x02] |= (((uint32_t)GPIO_PortSource) << (0x04 * (GPIO_PinSource & (uint8_t)0x03)));
}
~~~
明白了,没有什么好纠结的了!
最后,我就感叹,他这个中断函数写的一点都不灵活!我还是喜欢我写的这个,详情看前面中断按键!
~~~
void init_interrupt(u8 group,u8 inter_id,u8 preempting,u8 subpriority)
{
u32 aircr;
u8 ip;
/* Set Group :2 */
aircr = SCB->AIRCR; //Get AIRCR register
aircr &= 0x0000f8ff; //Clear Password & PriGroup
aircr |= 0x05fa0000; //Set Password
aircr |= ((~group&0x7)<<8); //Set PriGroup Group:2 0000 0010 => 1111 1101 [5 = 0101b]<<8
SCB->AIRCR = aircr; //Set AIRCR
/*
* Group 2 2:2
* 0~3 : 0~3
* Set Preempting = 0 Subpriority = 0
* 1001 0000b = 0x00;
*/
if(inter_id<32)
NVIC->ISER[0] = 1<< inter_id;
else
NVIC->ISER[1] = 1<<(inter_id-32); //EXIT15_10 vector:37
switch(group)
{
case 0: ip = 0x0f&subpriority;break;
case 1: ip = (0x08&preempting) | (0x07&subpriority);break;
case 2: ip = (0x0C&preempting) | (0x03&subpriority);break;
case 3: ip = (0x0e&preempting) | (0x01&subpriority);break;
case 4: ip = 0x0f&preempting;break;
default: ip = 0x00;break;
}
NVIC->IP[inter_id] = 0xf0&(ip<<4);
}
~~~
不要看我的一些注释,那些注释是给我自己看的!没有什么参考价值!
这个问题搞清楚了,就没有什么容易出错的了,下面是代码:
~~~
void ITkey_init(void)
{
EXTI_InitTypeDef key_exti;
NVIC_InitTypeDef key_nvic;
GPIO_InitTypeDef key;
RCC->APB2ENR |= ((1<<0)|(1<<2)|(1<<3));
key.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_8;
key.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOA, &key);
key.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2;
key.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOB, &key);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
key_nvic.NVIC_IRQChannel = EXTI0_IRQn;
key_nvic.NVIC_IRQChannelCmd = ENABLE;
key_nvic.NVIC_IRQChannelPreemptionPriority = 0;
key_nvic.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&key_nvic);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
key_exti.EXTI_Line = EXTI_Line0;
key_exti.EXTI_LineCmd = ENABLE;
key_exti.EXTI_Mode = EXTI_Mode_Interrupt;
key_exti.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&key_exti);
key_nvic.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_Init(&key_nvic);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource8);
key_exti.EXTI_Line = EXTI_Line8;
EXTI_Init(&key_exti);
key_nvic.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_Init(&key_nvic);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
key_exti.EXTI_Line = EXTI_Line1;
EXTI_Init(&key_exti);
key_nvic.NVIC_IRQChannel = EXTI2_IRQn;
NVIC_Init(&key_nvic);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource2);
key_exti.EXTI_Line = EXTI_Line2;
EXTI_Init(&key_exti);
}
~~~
【STM库应用】stm32 之 IIC应用
最后更新于:2022-04-01 14:51:05
iic协议是比较简单的双线协议,时钟线CLK和数据线SDA。
一般我们常见的还有spi总线,这种总线可以可以根据需要扩展,还有单总线等等
这次还以at240c2为例进行操作!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915ba02866.jpg)
PS:这就是传说中的iic时序图
硬件构造我们不过多的分析,今天用到库了!我们先从库函数硬件iic初始化说起!
PB6 -- CLK
PB7 -- SDA
~~~
void i2c_init(u8 addr,u32 clock)
{
I2C_InitTypeDef i2c;
RCC->APB2ENR |= 1<<3;
GPIOB->CRL |= (u32)0xff<<(6*4);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
i2c.I2C_Ack = I2C_Ack_Enable;
i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
i2c.I2C_ClockSpeed = clock*1000;
i2c.I2C_DutyCycle = I2C_DutyCycle_2;
i2c.I2C_Mode = I2C_Mode_I2C;
i2c.I2C_OwnAddress1 = addr;
I2C_Cmd(I2C1,ENABLE);
I2C_Init(I2C1,&i2c);
}
~~~
在配置管脚方面,我还是喜欢用寄存器配置,因为我的两行代码可以解决库函数的N多行代码的问题!
还有在结构体变量命名方面也是属于我自己的独创吧,这样反正我觉得是既容易识别,也少打几个字!
~~~
typedef struct
{
uint32_t I2C_ClockSpeed; //I2C时钟频率设置
uint16_t I2C_Mode; //I2C模式设置
uint16_t I2C_DutyCycle; //高低电平时间之比
uint16_t I2C_OwnAddress1; //主设备地址设置,也就是自己的地址
uint16_t I2C_Ack; //Check
uint16_t I2C_AcknowledgedAddress; //地址长度,可以为7bit的也可以为10bit的
}I2C_InitTypeDef;
~~~
IIC初始化完之后,我们开始来研究eeprom
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915ba166b1.jpg)
看完这个写一个字节的协议之后,我们应该对这个写已经没有什么问题了,很简单的。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915ba2ce96.jpg)
这个是写一个page
注:在eeprom里面写数据时,一次最多只能写一个page,一个page为8byte,同时这个也有字节对齐的要求!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915ba40592.jpg)
比如我们从Address = 4开始写,那么我们最多一次性可写4个byte,如果我们从8开始写的话,我们就可以8个byte,最后偏移到15。
~~~
void eeprom_write_byte(u8 wt_addr,u8 data)
{
I2C_GenerateSTART(I2C1,ENABLE);delay(5);
I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1,wt_addr);delay(5);
I2C_SendData(I2C1,data);delay(5);
I2C_GenerateSTOP(I2C1, ENABLE);
delay(20);
}
~~~
由于stm32的i2c确实做的不怎么样,标着寄存器太多,也不容易识别,我们就不要检测这些标志寄存器,用延时了把他们隔离了。不过在把地址发送出去之后,要检测设备是否被选中,这个在我们的模拟的i2c里面也是必须检测的!可以认为是必不可少的!
~~~
void eeprom_write_page(u8 wt_addr,u8 *buff,u32 length)
{
int i = 0;
I2C_GenerateSTART(I2C1,ENABLE);delay(5);
I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1,wt_addr);delay(5);
for(i=0; i<length; i++)
{
I2C_SendData(I2C1,buff[i]);delay(5);
}
I2C_GenerateSTOP(I2C1, ENABLE);
}
~~~
这个是写一个page的函数,如果大家想看比较灵活的写函数,去我看我前面发表的博客里面找找,那个无论你写几个都无所谓,只要不超过eeprom的大小!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915ba57e04.jpg)
读数据是稍稍复杂一点点的,我们首先要选中设备,然后选择我们要操作的地址,这时候不要stop,如果stop信号一发出,总线就被释放掉了,设备也就跟处理器断开,所以这里需要一个RSTART,跟START不一样,多了个R,这个可以理解为重新开始,这个信号不会选中其他设备,也不会丢失当前设备。
然后还有个注意点是,在读完第N个字节后,不要返回回应,直接stop,不然设备会以为你没有结束,会一直占据总线,等待下一个数据的发送,这样等你下一次来访问他的时候,他就不让你访问了,因为他还停留在给你传数据的状态,所以这里一定不要返回acK直接stop信号发出哦!
~~~
unsigned char eeprom_read_byte(u8 rd_addr)
{
u8 temp = 0;
I2C_GenerateSTART(I2C1,ENABLE);delay(5);
I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_Cmd(I2C1, ENABLE);
I2C_SendData(I2C1,rd_addr);delay(10);
I2C_GenerateSTART(I2C1,ENABLE);delay(5);
I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Receiver);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
I2C_AcknowledgeConfig(I2C1, DISABLE);
while(!(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)));
temp = I2C_ReceiveData(I2C1);delay(5);
I2C_GenerateSTOP(I2C1, ENABLE);
delay(20);
I2C_AcknowledgeConfig(I2C1, ENABLE);
return temp;
}
~~~
还是有几个信号是必须确认的,设备地址发送出去,看设备是否有回应!
这里最后一个NOACK必须在发送最后一个字节前使能,在stop信号发出后,记得吧ACK信号重新使能,因为我们刚刚开始是需要ack的,只是最后有时候不需要!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915ba70026.jpg)
对于数据的读,在所读数据长度上,是没有要求的,也没有page限制,想读多少,读多少!
~~~
void eeprom_read(u8 rd_addr,u8 *buff,u32 length)
{
int i = 0;
I2C_GenerateSTART(I2C1,ENABLE);delay(5);
I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_Cmd(I2C1, ENABLE);
I2C_SendData(I2C1,rd_addr);delay(20);
I2C_GenerateSTART(I2C1,ENABLE);delay(5);
I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Receiver);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
for(i=0; i<length; i++)
{
if(i == length-1)
I2C_AcknowledgeConfig(I2C1, DISABLE);
while(!(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)));
buff[i] = I2C_ReceiveData(I2C1);delay(5);
}
I2C_GenerateSTOP(I2C1, ENABLE);
I2C_AcknowledgeConfig(I2C1, ENABLE);
}
~~~
PS:在提一下,在接收最后一个数据之前,必须关闭ACK,不然,。。。后果很严重!
stm32 启动代码应用技巧
最后更新于:2022-04-01 14:51:03
前段时间对stm32的启动代码进行了一个小小的研究,发现了一些比较好用的技巧,在这分享下!
总体上说,整个启动代码就是中断初始化为主,以及中断之后如何进行调配函数!
首先看这么一段代码:Reset_Handler
~~~
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SysInit
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =SysInit
BLX R0
LDR R0, =__main
BX R0
ENDP
~~~
这其实上是Reset中断后生的事情,不难发现,这也就是进入主函数的通道!
我在这里加了一个
~~~
IMPORT SysInit
~~~
~~~
LDR R0, =SysInit
BLX R0
~~~
这是我自己写的一个函数,因为我每次程序一开始跑,我就像让他运行这段代码,所以我就把他也加到这里了!
可见,我们如果想在进入主函数之前运行什么,那么我们就可以在这里加我们需要的代码;
一定要注意我加的位置,他其实上就是入栈和出栈的做法!所以我们一定要注意入栈和出栈的顺序!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b9e24be.jpg)
我们在来看看其他的中断处理,也都是同样的道理!
~~~
PendSV_Handler PROC
EXPORT PendSV_Handler [WEAK]
B .
ENDP
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP
~~~
ok,这个其实很简单,不过很实用!
PS:有的人用的是库里面的bootloader,为只读文件,我们需要把他删了,重新复制一个添加进去!
中断源去抖办法
最后更新于:2022-04-01 14:51:00
今天在做处理一个中断按键的时候,总是避免不了抖动的影响!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b9caf52.jpg)
像这个按键信号,如果我们用一般的扫描方式的话,用一个延时就可以解决了,但是我们用中断的话,怎么办?
这样明显会中断5次,在51里面还好,在进入中断的时候,可以把中断屏蔽了,但是51屏蔽中断之后,其他中断就不能触发了,在stm32,甚至任意一个平台都一样,有人说关掉这个中断源,我大致试了一下,最后程序都卡死!跑飞了。。所以这种也方法不可以去!
既然是这样,我们用一个中断程序执行标志符怎么?
~~~
void EXTI0_IRQHandler(void)
{
static u8 button = 0;
if(RESET == EXTI_GetFlagStatus(EXTI_Line0))return;
if(!button){
button = 1;
delay(KEY_DELAY);
if(!(GPIOA->IDR & (1<<0)))goto end;
/******************/
/******************/
end: button = 0;
}
EXTI_ClearFlag(EXTI_Line0);
}
~~~
button2起到标志位的作用,他必须是静态变量,delay适当的延时,这样就可以解决这个问题了!
【STM库应用】stm32 之 USART
最后更新于:2022-04-01 14:50:58
STM库是官方提供的,其已经做好了底层驱动的配置,用起来是相当简单的;我们只需要了解其每个函数的功能,已经每个函数所使用流程即可!
整个框架就是下面这幅图,我们只需在顶层做调用即可,跟现在做app的差不多!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b913574.jpg)
注:摘自野火,希望大哥不要说我盗版
刚刚开始拿道库,一看这么多函数,感觉有点棘手,无从下手;多亏stm官方也针对该库提供了一个应用手册,画了两天时间进行了探索和实践,发现了一点比较方便的诀窍。
在此给大家分享分享,我们就拿今天的USART来说吧!
至于建工程,网上以及教程里面是非常多的,我也就不多说了!
任意一个外设都对应一个ppp.c和ppp.h文件,所以我们其实也不是无章可循的!同时,stm库他有一套自己的缩写定义,我们必须记住,不过这个很好记!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b935cc0.jpg)
同时他的函数命名也有一定的规则:
名为PPP_Init的函数,其功能是根据PPP_InitTypeDef中指定的参数,初始化外设PPP,例如 TIM_Init.
名为PPP_DeInit的函数,其功能为复位外设PPP的所有寄存器至缺省值,例如 TIM_DeInit.
名为PPP_StructInit的函数,其功能为通过设置PPP_InitTypeDef 结构中的各种参数来定义外设的功能,例如:USART_StructInit
名为PPP_Cmd的函数,其功能为使能或者失能外设PPP,例如: SPI_Cmd.
名为PPP_ITConfig的函数,其功能为使能或者失能来自外设PPP某中断源,例如: RCC_ITConfig.
名为PPP_DMAConfig的函数,其功能为使能或者失能外设PPP的DMA接口,例如:TIM1_DMAConfig.
用以配置外设功能的函数,总是以字符串“Config”结尾,例如GPIO_PinRemapConfig.
名为PPP_GetFlagStatus的函数,其功能为检查外设PPP某标志位被设置与否,例如:I2C_GetFlagStatus.
名为PPP_ClearFlag的函数,其功能为清除外设PPP标志位,例如:I2C_ClearFlag.
名为PPP_GetITStatus的函数,其功能为判断来自外设PPP的中断发生与否,例如:I2C_GetITStatus.
名为PPP_ClearITPendingBit 的函数 , 其功能 为 清除外 设PPP 中断待处理标志位,例如:I2C_ClearITPendingBit.
这些在官方给的库说明里面都有,我只是特别说明下,有助于我么了解!
我们经常用的不多,如:PPP_Init,PPP_Cmd,PPP_ITConfig,PPP_DMAConfig,PPP_GetFlagStatus,PPP_ClearFlag
同时我们需要熟悉的结构体:PPP_InitTypeDef等
上面指出的都是常用的,不多6个函数,1个结构体,相信大家应该能很快记住,我比较笨,记了两天才记的还不是很清!
下面我们先把USART1d的代码附上去!
~~~
void usart_config()
{
USART_InitTypeDef usart;
NVIC_InitTypeDef nvic;
RCC->APB2ENR |= 1<<2;
RCC->APB2ENR |= 1<<14;
GPIOA->CRH &= ~(0xff<<4);
GPIOA->CRH |= 0x8b<<4;
usart.USART_BaudRate = 9600;
usart.USART_Parity = USART_Parity_No;
usart.USART_StopBits = USART_StopBits_1;
usart.USART_WordLength = USART_WordLength_8b;
usart.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
usart.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1,&usart);
USART_Cmd(USART1,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
nvic.NVIC_IRQChannel = USART1_IRQn;
nvic.NVIC_IRQChannelCmd = ENABLE;
nvic.NVIC_IRQChannelPreemptionPriority = 0x0f;
nvic.NVIC_IRQChannelSubPriority = 0x0f;
NVIC_Init(&nvic);
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
}
~~~
怎么说呢,我是比较懒的!所以我比喜欢别人那种命名变量的方式,我以前是做linux的,我喜欢linux那套命名模式!
另外,我建议大家在做开发的时候用Keil4.0以上的版本,为啥呢?下面正式我想说的
我们在声明一个结构体变量的时候,我们不知道结构题里面每个变量的名字,怎么办?找吗?很麻烦是吧?
如果我们用了mdk4.0以上的版本,他会自动匹配
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b9542a2.jpg)
可能有的人用过了,会说我多余,呵呵但是对那些没有用过的人很有用,比如李想老师!呵呵,开个玩笑
这样我们就不用一个一个输入了!但是后面的变量赋值怎么办?其实这些变量赋值都在ppp.h里面定义好了,我大致看了下ppp.h内容不是很多,所以很容易就找到,我有一个方法就是查找!
比如我们要找USART_Mode所对应的赋值:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b96c156.jpg)
切换到stm32f10x_usart.h,直接在上面输入USART_Mode,按下Enter键,就找到了,那么我就直接把这个定义赋值过去,好了!
有人说我函数名记不住,那么我们还是在stm32f10x_usart.h直接一下子拉到最下面!是不是看到了所有的函数?
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b987764.jpg)
如果你细心看我代码的话,你会发现,我对GPIO的初始化没有用结构图,我发现直接配置管脚的话很方便,代码不多,我4行能解决他十几行的代码!
RCC->APB2ENR |= 1<<2;
RCC->APB2ENR |= 1<<14;
GPIOA->CRH &= ~(0xff<<4);
GPIOA->CRH |= 0x8b<<4;
这样就把整个配置完了!有的人说我能记住吗?这么多位!我只想呵呵!不信你自己对着寄存器看下
比如输入模式:后两位一定是00b,输出模式一定是其他三种!在输出中00,01为通用,10,11为复用,偶数为推挽,奇数为开漏,好记吧?
在输入中,有三种模式,浮空就是复位的时候,还有模拟输入,还有上拉下拉输入!
我今天想写中断函数,怎么办?记不住啊,我想既然是中断函数,他一定在启动代码里面有定义,直接切换到startup_stm32f10x.md.s里面,直接搜索Handler
找到所有中断函数的定义:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b9ab41e.jpg)
有了这些基本思想,我们在写程序的时候方便多了,这次完成串口的配置就用了半个小时!我只能说库用着好方便!
~~~
/*****************************************
* @file obj/main.c
* @author ieczw
* @version V1.0.1
* @date 24-11-2013
*****************************************/
/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#include "stdio.h"
#include "stm32f10x_conf.h"
void usart_config(void);
void led_init(void);
void msleep(u16 timeout);
void rs232_send_byte(u8 byte);
u16 systick = 0;
int main(void)
{
u8 ascii = 0;
SysTick_Config(72000); //Config DiDa = 1ms
/* Add your application code here */
usart_config();
led_init();
/* Infinite loop */
GPIOC->BRR |= 0xff<<8;
msleep(1000);
GPIOC->BSRR |= 0xff<<8;
while (1)
{
rs232_send_byte(ascii++);
msleep(1000);
}
}
void led_init()
{
RCC->APB2ENR |= 3<<4;
GPIOC->CRH = 0x33333333;
GPIOD->CRL &= ~(0xf<<2);
GPIOD->CRL |= 3<<2;
GPIOD->ODR &= ~(1<<2);
GPIOC->ODR |= 0xff<<8;
}
void usart_config()
{
USART_InitTypeDef usart;
NVIC_InitTypeDef nvic;
RCC->APB2ENR |= 1<<2;
RCC->APB2ENR |= 1<<14;
GPIOA->CRH &= ~(0xff<<4);
GPIOA->CRH |= 0x8b<<4;
usart.USART_BaudRate = 9600;
usart.USART_Parity = USART_Parity_No;
usart.USART_StopBits = USART_StopBits_1;
usart.USART_WordLength = USART_WordLength_8b;
usart.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
usart.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1,&usart);
USART_Cmd(USART1,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
nvic.NVIC_IRQChannel = USART1_IRQn;
nvic.NVIC_IRQChannelCmd = ENABLE;
nvic.NVIC_IRQChannelPreemptionPriority = 0x0f;
nvic.NVIC_IRQChannelSubPriority = 0x0f;
NVIC_Init(&nvic);
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
}
void rs232_send_byte(u8 byte)
{
USART_SendData(USART1,byte);
while(RESET == USART_GetFlagStatus(USART1,USART_FLAG_TXE));
}
void USART1_IRQHandler(void)
{
static u8 flag = 0;
flag = ~flag;
if(flag)
GPIOC->BRR |= 0xff<<8;
else
GPIOC->BSRR |= 0xff<<8;
if(USART1->SR&(1<<5))
{
USART1->DR = USART1->DR;
while(0 == (USART1->SR&(1<<6)));
}
}
void msleep(u16 timeout)
{
systick = timeout;
while(systick);
}
~~~
今天就是说库如何使用,原理请看[http://blog.csdn.net/ieczw/article/details/16118387](http://blog.csdn.net/ieczw/article/details/16118387)
希望各位指点!
【菜鸟入门】stm32 之 DAC
最后更新于:2022-04-01 14:50:56
DAC可谓是stm32继按键最简单的一个寄存器配置吧,花了半个小时搞定!
DAC主要特征
● 2个DAC转换器:每个转换器对应1个输出通道
● 8位或者12位单调输出
● 12位模式下数据左对齐或者右对齐
● 同步更新功能
● 噪声波形生成
● 三角波形生成
● 双DAC通道同时或者分别转换
● 每个通道都有DMA功能
● 外部触发转换
看了这些东西,貌似很激动的样子,我们下面就开始配置DAC外设了
先直接看看寄存器:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b890486.jpg)
位12 DMAEN1:DAC通道1 DMA使能(DAC channel1 DMA enable
该位由软件设置和清除。
0:关闭DAC通道1 DMA模式;
1:使能DAC通道1 DMA模式。
MAMP1[3:0]:DAC通道1屏蔽/幅值选择器(DAC channel1 mask/amplitude selector) 位11:8
由软件设置这些位,用来在噪声生成模式下选择屏蔽位,在三角波生成模式下选择波形的幅
值。
0000:不屏蔽LSFR位0 / 三角波幅值等于1;
0001:不屏蔽LSFR位[1:0] / 三角波幅值等于3;
0010:不屏蔽LSFR位[2:0] / 三角波幅值等于7;
0011:不屏蔽LSFR位[3:0] / 三角波幅值等于15;
0100:不屏蔽LSFR位[4:0] / 三角波幅值等于31;
0101:不屏蔽LSFR位[5:0] / 三角波幅值等于63;
0110:不屏蔽LSFR位[6:0] / 三角波幅值等于127;
0111:不屏蔽LSFR位[7:0] / 三角波幅值等于255;
1000:不屏蔽LSFR位[8:0] / 三角波幅值等于511;
1001:不屏蔽LSFR位[9:0] / 三角波幅值等于1023;
1010:不屏蔽LSFR位[10:0] / 三角波幅值等于2047;
≥1011:不屏蔽LSFR位[11:0] / 三角波幅值等于4095。
位7:6 WAVE1[1:0]:DAC通道1噪声/三角波生成使能(DAC channel1 noise/triangle wave generation
enable)
该2位由软件设置和清除。
00:关闭波形生成;
10:使能噪声波形发生器;
1x:使能三角波发生器。
位5:3 TSEL1[2:0]:DAC通道1触发选择(DAC channel1 trigger selection)
该位用于选择DAC通道1的外部触发事件。
000:TIM6 TRGO事件;
001:对于互联型产品是TIM3 TRGO事件,对于大容量产品是TIM8 TRGO事件;
010:TIM7 TRGO事件;
011:TIM5 TRGO事件;
100:TIM2 TRGO事件;
101:TIM4 TRGO事件;
110:外部中断线9;
111:软件触发。
注意:该位只能在TEN1= 1(DAC通道1触发使能)时设置。
位2 TEN1:DAC通道1触发使能(DAC channel1 trigger enable)
该位由软件设置和清除,用来使能/关闭DAC通道1的触发。
0:关闭DAC通道1触发,写入寄存器DAC_DHRx的数据在1个APB1时钟周期后传入寄存器
DAC_DOR1;
1:使能DAC通道1触发,写入寄存器DAC_DHRx的数据在3个APB1时钟周期后传入寄存器
DAC_DOR1。
注意:如果选择软件触发,写入寄存器DAC_DHRx的数据只需要1个APB1时钟周期就可以传入
寄存器DAC_DOR1。
位1 BOFF1:关闭DAC通道1输出缓存(DAC channel1 output buffer disable)
该位由软件设置和清除,用来使能/关闭DAC通道1的输出缓存。
0:使能DAC通道1输出缓存;
1:关闭DAC通道1输出缓存。
位0 EN1:DAC通道1使能(DAC channel1 enable)
该位由软件设置和清除,用来使能/失能DAC通道1。
0:关闭DAC通道1;
1:使能DAC通道1。
因为DAC1对应的是PA4,所以我们在初始化DAC1的时钟时别忘了 PA4
RCC->APB1ENR |= 1<<29;
RCC->APB2ENR |= 1<<2;
因为我们这里不用DMA,所以就关掉
DAC->CR &= ~(1<<12);//close DMA
如果想输出三级波,那就把6:7位都置1,想输出噪声我们就弄成10b,什么都不想要我们就用00b;这里我想玩玩三角波,我就配置称11;
DAC->CR &= ~(3<<6);//好习惯是先把几位全部清空
DAC->CR |= 3<<6;
因为我们上面设置了三级波模式,这里我们来设置三级波的VPP,可以根据上面的datasheet设置!
DAC->CR &= ~(0xf<<8);
DAC->CR |= 0xf<<8;
DA的触发模式,为了方便起见我们之间选用软件触发,但是
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b8a6301.jpg)
所以我们要现设置TEN1=1;然后再设置成软件触发
DAC->CR |= 1<<2;
DAC->CR |= 7<<3;//sw
我们也用不上DAC的缓存,所以二话不说关掉!
DAC->CR |= 1<<1;
下面设置要输出的偏移电源,0当然就是0V了,
DAC->DHR12R1 = 0;
因为我们用到了软件触发,所以我们要时刻关注软件触发寄存器,寄存器就两位:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b8c21d3.jpg)
DAC->SWTRIGR |= 1<<0;
然后就打开DA,开始工作!
DAC->CR |= 1<<0;
下面附上我的代码!
~~~
/* dac.c */
#include <stm32f10x.h>
void dac_init()
{
RCC->APB1ENR |= 1<<29;
RCC->APB2ENR |= 1<<2;
DAC->CR &= ~(1<<12); //close DMA
DAC->CR &= ~(0xf<<8);
DAC->CR |= 0xf<<8; //Vp = 512
DAC->CR &= ~(3<<6); //
DAC->CR |= 3<<6;
DAC->CR |= 1<<2;
DAC->CR |= 7<<3; //sw
DAC->CR |= 1<<1;
DAC->DHR12R1 = 0;
DAC->SWTRIGR |= 1<<0;
DAC->CR |= 1<<0;
}
~~~
主函数
~~~
#include <stm32f10x.h>
#include "init.h"
#include "usart.h"
#include "stdio.h"
#include "dac.h"
int main()
{
rs232_init(CPU_72M,9600);
dac_init();
while(1)
{
if(!(DAC->SWTRIGR&(1<<0)))
{
DAC->SWTRIGR |= 1<<0;
}
}
}
~~~
效果:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b8d99ba.jpg)
还有什么说的?慢慢瞎搞吧!把瞎搞进行到底!
【菜鸟入门】stm32 之 DMA
最后更新于:2022-04-01 14:50:54
DMA是个好东西,他可以帮助CPU分担好多工作,减轻CPU的工作压力,我们可以一边传输数据,一边干别的事情,很不错!
今天可谓是配置的好艰辛啊,看电视把我看蒙了,好多注意事项都木有看见,浪费了我好多时间。
好吧,开始进入主题!
时钟配置我就不用罗嗦了!大家自己去RCC里面的AHBENR找:
RCC->AHBENR |= 1<<0;//DAM1 Clock Enable
主要来看看DMAx的控制寄存器CCR
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b7a89c6.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b7bf841.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b7df933.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b8072a0.jpg)
这个寄存器看着很容易理解,今天对控制寄存器有了新的认识,一般的寄存器可能要在控制寄存器的某一位关闭或者打开才能修改,而控制寄存器基本上可以在任意时刻进行修改,所以,在配置控制寄存器的时候我们没必要考虑那么多!
我今天准备把一个字符串通过DMA与USART1建立一个通道,把数据发送出去!
所以我们不从外设读数据,我们需要Read From Register,如果我们从ADC里面读数据我们就可以把这位置0
dma->CCR |= 1<<4;//Read from register
我们可以采用循环发送,也可以不循环发送,如果是ADC的话,那就用循环发送
dma->CCR &= ~(1<<5);//normal mode
因为USART1的地址不进行偏移或者改变
dma->CCR &= ~(1<<6);//Device Address non-Rise
在一个字符串中我们要不断寻址去发送该为地址上的数据
dma->CCR |= 1<<7;//Memary Address Rise
所有设备和存储都按8bit进行存储和读取
dma->CCR &= ~(3<<8);//Device 8 Bit Data
dma->CCR &= ~(3<<10);//Memary 8 Bit
在设置两位或两位以上的数据是一定要把其全部清空在进行设置,这样方便一点
dma->CCR &= ~(3<<12);//clear 12:13
dma->CCR |= 1<<12;//Set Priority level
14bit的配置我不用说了,大家看一下就肯定知道了
dma->CCR &= ~(1<<14);//non M2M mode
DMA还有这么几个寄存器
DMA_CNDTR :要发送的数据个数
DM_CPAR:外设地址寄存器
DM_CMAR:存储器地址寄存器
这3个寄存器必须在DMA_CCRx的EN=0时才能进行修改!我今天就在这个地方沦陷了,后来看了李想老师的视频,他的感觉他的配置有问题,但是还是。。。
可能我们的板子不一样吧,反正我他那几个配置违背了EN=0时才能修改的原则
dma->CCR &= ~(1<<0);
dma->CPAR = cpar;
dma->CMAR = cmar;
dma->CNDTR = cndtr;
dma->CCR |= 1<<0;
还有其他规则,大家可以看看!DMA还是相对比较简单的,不过是建立在你细心的基础上!
另外我们要用到USART1的发送,所以我们也要去打开USART1里面的dma功能
在USART1的控制寄存器里面有这么两个配置:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b81d47d.jpg)
我们要什么功能就选什么功能了!
这里我选USART1->CR3 |= 1<<7;
好了,那几天就这样,下面附上我的代码
~~~
/* dma.c */
#include <stm32f10x.h>
#include "stdio.h"
#include "usart.h"
#include "init.h"
void dma1_init(DMA_Channel_TypeDef * dma)
{
RCC->AHBENR |= 1<<0;//DAM1 Clock Enable
dma->CCR |= 1<<4; //Read from register
dma->CCR &= ~(1<<5); //normal mode
dma->CCR &= ~(1<<6); //Device Address non-Rise
dma->CCR |= 1<<7; //Memary Address Rise
dma->CCR &= ~(3<<8); //Device 8 Bit Data
dma->CCR &= ~(3<<10); //Memary 8 Bit
dma->CCR &= ~(3<<12); //clear 12:13
dma->CCR |= 1<<12; //Set Priority level
dma->CCR &= ~(1<<14); //non M2M mode
USART1->CR3 |= 1<<7;
}
void dma1_enable(DMA_Channel_TypeDef * dma,u32 cpar,u32 cmar,u32 cndtr)
{
#ifdef __DEBUG
char buff[255];
#endif
dma->CCR &= ~(1<<0);
dma->CPAR = cpar;
dma->CMAR = cmar;
dma->CNDTR = cndtr;
dma->CCR |= 1<<0;
#ifdef __DEBUG
sprintf(buff,"Initialize<start>.....\ndma:0x%08x\nCPAR:0x%08x\nCMAR:0x%08x\nCNDTR:0x%08x\n",\
(u32)dma,(u32)cpar,(u32)cmar,cndtr);
rs232_send_str(buff,strlen(buff));
sprintf(buff,"Initialize.....\ndma:0x%08x\nCPAR:0x%08x\nCMAR:0x%08x\nCNDTR:0x%08x\n",\
(u32)dma,dma->CPAR,dma->CMAR,dma->CNDTR);
rs232_send_str(buff,strlen(buff));
#endif
}
~~~
主函数:
~~~
#include <stm32f10x.h>
#include "init.h"
#include "usart.h"
#include "dma.h"
#include "stdio.h"
int main()
{
char str[512];
rs232_init(CPU_72M,9600);
RCC->APB2ENR |= 1<<4;
GPIOC->CRH = 0x33333333;
GPIOC->ODR |= 0xff00;
sprintf(str,"This is a DMA1 Channel4 Test!\n");
dma1_init(DMA1_Channel4);
while(1)
{
delay_ms(1000);
dma1_enable(DMA1_Channel4,(u32)&USART1->DR,(u32)str,strlen(str));
while(1)
{
if(DMA1->ISR&(1<<13))
break;
delay_ms(1000);
}
DMA1->IFCR |= 1<<13;
}
}
~~~
效果图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b839b22.jpg)
,
PS:最近在研究库,感觉库挺好用的,所以关于寄存器配置的进度比较慢!
【菜鸟入门】stm32 之 实时时钟
最后更新于:2022-04-01 14:50:51
经过这么10天的瞎搞,我的库已经初具规模了,于是,不用每次都把所有的文件copy过去,直接在Option里面把path给加上就ok了。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b5db5af.jpg)
RTC的时钟配置,RTC的时间寄存器是2个32位的寄存器,无非就是一个计数器,大概可以这样理解吧,我们先看看时钟吧
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b6a84b0.jpg)
RTC的时钟可以从这3路来,我们需要PTCSEL寄存器来进行设置,
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b6be9d4.jpg)
上面这个图是摘自李想老师的课件里面的,我觉得这个是做的相对好的!
位了保证RTC正常工作,我们需要在系统断电时,RTC不受影响,当然我们一般都需要接一个Battery,作为rtc的后备电源,这里设计到电源管理,我们先来看看电源管理里面关于rtc的
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b6e19d0.jpg)
只要我们把第八位置1我们就可以对其进行正常供电,我们还发现,他也可以给后备寄存器供电,这个后备寄存器是是个什么东东呢?
有兴趣的可以研究研究备份寄存器(BKP),他的主要功能是侵入检查和RTC校准,他既然跟RTC有关系,我们就要好好看看他了;
复位和时钟控制里面有个备份域控制寄存器RCC_BDCR
注意: 备份域控制寄存器中(RCC_BDCR)的LSEON、LSEBYP、RTCSEL和RTCEN位处于备份域。因
此,这些位在复位后处于写保护状态,只有在电源控制寄存器(PWR_CR)中的DBP位置’1’后才
能对这些位进行改动。进一步信息请参考5.1节。这些位只能由备份域复位清除(见6.1.3节)。任
何内部或外部复位都不会影响这些位。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b71068b.jpg)
位31:17 保留,始终读为0。
BDRST:备份域软件复位(Backup domain software reset) 位16
由软件置’1’或清’0’
0:复位未激活;
1:复位整个备份域。
RTCEN:RTC时钟使能(RTC clock enable) 位15
由软件置’1’或清’0’
0:RTC时钟关闭;
1:RTC时钟开启。
位14:10 保留,始终读为0。
RTCSEL[1:0]:RTC时钟源选择(RTC clock source selection) 位9:8
由软件设置来选择RTC时钟源。一旦RTC时钟源被选定,直到下次后备域被复位,它不能在被
改变。可通过设置BDRST位来清除。
00:无时钟;
01:LSE振荡器作为RTC时钟;
10:LSI振荡器作为RTC时钟;
11:HSE振荡器在128分频后作为RTC时钟。
位7:3 保留,始终读为0。
LSEBYP:外部低速时钟振荡器旁路(External low-speed oscillator bypass) 位2
在调试模式下由软件置’1’或清’0’来旁路LSE。只有在外部32kHz振荡器关闭时,才能写入该位
0:LSE时钟未被旁路;
1:LSE时钟被旁路。
LSERDY:外部低速LSE就绪(External low-speed oscillator ready) 位1
由硬件置’1’或清’0’来指示是否外部32kHz振荡器就绪。在LSEON被清零后,该位需要6个外部
低速振荡器的周期才被清零。
0:外部32kHz振荡器未就绪;
1:外部32kHz振荡器就绪。
LSEON:外部低速振荡器使能(External low-speed oscillator enable) 位0
由软件置’1’或清’0’
0:外部32kHz振荡器关闭;
1:外部32kHz振荡器开启。
看来这一寄存器果真与RTC有很大的联系,我们需要启用外部32K的振荡器,所以RCC->BDCR |= 1<<0;
设置完了,我们还需要等待32K的时钟就绪,判断bit1的状态!
由于我们选用的32K的LSE作为RTC的时钟,所以上面我们提到的RTCSEL寄存器必须设置为1,设置完后我们就开启32K时钟
RCC->BDCR |= 1<<8;
RCC->BDCR |= 1<<15;
下面正式看RTC的寄存器,先从低位控制寄存器开始CRL
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b7261af.jpg)
位15:6 保留,被硬件强制为0。
位5 RTOFF:RTC操作关闭(RTC operation OFF) 位5
RTC模块利用这位来指示对其寄存器进行的最后一次操作的状态,指示操作是否完成。若此位
为’0’,则表示无法对任何的RTC寄存器进行写操作。此位为只读位。
0:上一次对RTC寄存器的写操作仍在进行;
1:上一次对RTC寄存器的写操作已经完成。
位4 CNF:配置标志(Configuration flag) 位4
此位必须由软件置’1’以进入配置模式,从而允许向RTC_CNT、RTC_ALR或RTC_PRL寄存器
写入数据。只有当此位在被置’1’并重新由软件清’0’后,才会执行写操作。
0:退出配置模式(开始更新RTC寄存器);
1:进入配置模式。
位3 RSF:寄存器同步标志(Registers synchronized flag)
每当RTC_CNT寄存器和RTC_DIV寄存器由软件更新或清’0’时,此位由硬件置’1’。在APB1复位
后,或APB1时钟停止后,此位必须由软件清’0’。要进行任何的读操作之前,用户程序必须等待
这位被硬件置’1’,以确保RTC_CNT、RTC_ALR或RTC_PRL已经被同步。
0:寄存器尚未被同步;
1:寄存器已经被同步。
位2 OWF:溢出标志(Overflow flag) 位2
当32位可编程计数器溢出时,此位由硬件置’1’。如果RTC_CRH寄存器中OWIE=1,则产生中
断。此位只能由软件清’0’。对此位写’1’是无效的。
0:无溢出;
1:32位可编程计数器溢出。
位1 ALRF:闹钟标志(Alarm flag) 位1
当32位可编程计数器达到RTC_ALR寄存器所设置的预定值,此位由硬件置’1’。如果RTC_CRH
寄存器中ALRIE=1,则产生中断。此位只能由软件清’0’。对此位写’1’是无效的。
0:无闹钟;
1:有闹钟。
位0 SECF:秒标志(Second flag) 位0
当32位可编程预分频器溢出时,此位由硬件置’1’同时RTC计数器加1。因此,此标志为分辨率可
编程的RTC计数器提供一个周期性的信号(通常为1秒)。如果RTC_CRH寄存器中SECIE=1,则
产生中断。此位只能由软件清除。对此位写’1’是无效的。
0:秒标志条件不成立;
1:秒标志条件成立。
感觉CRL更像SR,我在想为什么他有点功能不放到SR的里面呢?
好吧,CRL里的功能说的很清楚,看着不会有什么异议,我这里就不解释了,直接掠过,包括CRH。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b73a0f2.jpg)
在RTC计数器寄存器里面和RTC闹钟寄存器里面有这么一段话
RTC核有一个32位可编程的计数器,可通过两个16位的寄存器访问。计数器以预分频器产生的
TR_CLK时间基准为参考进行计数。RTC_CNT寄存器用来存放计数器的计数值。他们受
RTC_CR的位RTOFF写保护,仅当RTOFF值为’1’时,允许写操作。在高或低寄存器
(RTC_CNTH或RTC_CNTL)上的写操作,能够直接装载到相应的可编程计数器,并且重新装载
RTC预分频器。当进行读操作时,直接返回计数器内的计数值(系统时间)。
当可编程计数器的值与RTC_ALR中的32位值相等时,即触发一个闹钟事件,并且产生RTC闹钟
中断。此寄存器受RTC_CR寄存器里的RTOFF位写保护,仅当RTOFF值为’1’时,允许写操作。
所以我们在配置RTC_CNTx RTC_ALRx 寄存器时,不行把RTOFF寄存器置为1,当写完之后将CNT设为1,即进入配置模式,等待RTOFF配置完成,即RTOFF自动置为0,才完成对CNTx和ALRx两个寄存器进行修改!
为了实现计数的时间间隔,我们要对RTC预分频装载寄存器进行配置
我们需要1s钟计时一次,而我们用的是LSE 32KHz的振荡器,所以我们需要配置的分频器是?
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b78b6c5.jpg)
可以看出我们只需让RTC_PRLL = 0x7fff 即 32767即可得到1s的周期
当然我们需要两秒的话那就是0xffff了。
这样,整个就配置完成了,下面附上我的代码,大家可以研究下!
~~~
#include <stm32f10x.h>
#include "init.h"
#include "usart.h"
#define RTC_CF 0x01CD //Define RTC Config Flag
int rtc_init()
{
u8 temp = 0;
if(BKP->DR1 != RTC_CF)
{
RCC->APB1ENR |= 1<<28; //Power Interface Clock Enable
RCC->APB1ENR |= 1<<27; //Backup Interface Clock Enable
PWR->CR |= 1<<8; //Disable backup domain write protection
RCC->BDCR |= 1<<16;
RCC->BDCR &= ~(1<<16);
RCC->BDCR |= 1<<0;
while((!(RCC->BDCR&1<<1))&&(temp++)<250)
delay_ms(10);
if(temp>=250)return -1;
RCC->BDCR |= 1<<8;
RCC->BDCR |= 1<<15;
while(!(RTC->CRL & (1<<5)));
while(!(RTC->CRL & (1<<3)));
RTC->CRH |= 1<<0;
while(!(RTC->CRL & (1<<5)));
/* Config Time */
RTC->CRL |= 1<<4;
RTC->PRLH = 0;
RTC->PRLL = 32767;
RTC->CNTH = 0;//Config time
RTC->CNTL = 0;
RTC->ALRH = 0;
RTC->ALRL = 20;
RTC->CRL &= ~(1<<4);
while(!(RTC->CRL & (1<<5)));
BKP->DR1 = RTC_CF;
}
else{
while(!(RTC->CRL & (1<<3)));
RTC->CRH |= 1<<0;
while(!(RTC->CRL & (1<<5)));
}
init_interrupt(2,3,3,2);
rs232_send_int(RTC->CNTL);
return 0;
}
void RTC_IRQHandler(void)
{
rs232_send_str("INTER\n",6);
if(RTC->CRL & (1<<0))
{
rs232_send_int(RTC->CNTL);
rs232_send_byte('\n');
}
if(RTC->CRL & (1<<1))
{
RTC->CRL |= 1<<4;
RTC->CNTL = 1;
RTC->CRL &= ~(1<<4);
rs232_send_str("Time==>\n",8);
}
RTC->CRL &= ~(7<<0);//Clear all interrupt flag
while(!(RTC->CRL & (1<<5)));
}
~~~
多谢各位指导!
【菜鸟入门】stm32 之 ADC 模数转换
最后更新于:2022-04-01 14:50:49
今天对ADC进行了研究,个人感觉,ADC的配置相对也对比较复杂一点,因为需要配置的寄存器是比较多的!
在datasheet 关于ADC的简介中,明确说明ADC的输入时钟不得超过14M,他是有PCLK2经过分频得来的
这次我们选用ADC1_IN0作为讲解的对象,ADC1_IN0 -->PA0
所以在配置时钟的时候要配置PA0和ADC1,关于怎么配置,已经说的很清楚了。
在配置PA0的输入模式方面我要说明一下,有好多人在这个地方还是很郁闷的
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b46f5c8.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b4902db.jpg)
在8.1.11 外设GPIO的配置中每种配置都说的很清楚,如果我们对那中模式配置有疑问,我们都可以直接在这里查找
这里的ADC我们采用模拟输入模式:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b4a6d14.jpg)
所以我们这里需要把PA1配置为0000b
输入配置完成,在开头我已经强调过了,ADC的时钟不能超过14MHz,所以我们要对ADC的时钟进行些操作
RCC_CFGR这个寄存器找了我半天,我就感觉在RCC里面配置,但是一直找不到ADC的配置项,后来在网上找了半天,才知道ADC的频率配置在这里选择
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b4bb8d1.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b4d55e9.jpg)
我一般在配置系统时钟的时候喜欢配置为72MHz,即PCLK2 = 72MHz
所以为了满足我们的ADC,我们至少需要6分频,当然8分频也可以,好吧废话了一句。。
我们这里就6分频吧:RCC->CFGR &= ~(3<<14);RCC->CRGR |= 2<<14;
这里得到的ADC时钟为12MHz,时钟配置完成后,那我们就来专心配置ADC register了
当然,有经验的人,不用想,直接先找到控制寄存器(CR ,Control Register)
ADC的控制寄存器比较多,我刚开始看的比较郁闷,然后再李想老师的代码里面找了一段(因为最近比较忙,没有时间,所以喜欢搞定现成的代码研究下);当然大家也可以只要,只要你把别人的配置方法,配置原理学会了,也是很不错的,有时候我道觉得这是一种比较学习的快捷办法!这里给大家交流下学习经验
先看看ADCx_CR1
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b4ee5d1.jpg)
CR1寄存器大部分位是管理WATCH DOG的,我们一般不怎么用WATCH DOG,在430上我基本上没有用过看门狗,感觉这个狗不是很听话,我也不是很了解他,所以以后用到了再说吧。
首先是双模式选择
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b51b69a.jpg)
我们需要注意下:在ADC2和ADC3中这些位为保留的,所以以后我们再配置的时候注意下,还有下面的几行
这里我们用独立模式0000b,在此模式中,双ADC同步不工作,每个ADC接口独立工作;
关于模式大家可以看看11.9章
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b5334f9.jpg)
至今我还没有明白这个扫描模式时干嘛用的!谁会了,帮忙指点下,谢谢了。
关于CR2的配置相对比较多的!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b54964a.jpg)
我们这里不使用外部事件来启动转换,直接用软件来转换,所以20位我们要置0,从而在选择启动规则通道组转换的外部事件我们就只能用SWSTART(Software Start)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b5681a1.jpg)
我们用的是ADC1,而且还关闭了外部启动转换,所以我们这里就选择111
为了保证数据数据的实时性,我们需要进行连续转换,我不知道李想老师为什么选用单次转换,不过也无所谓了。
然后为了保证读书的方便,我们可以把数据存储的时候进行右对齐;这样我们就不需要进行移位的操作了,直接读就ok 了。
关于SQR寄存器,规则序列寄存器,听着都纠结,我们只用一个通道,所以我们就二话不说的配置为0000b
通道的采样时间,我的观念是采样时间越长越精确,经过测试确实是这样
由于我们用的是CH1,所以我们呢就要配置SMPR的SMP1设置为111
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b57eeb5.jpg)
这些配置完了,那我们就来启动ADC吧。
还是CR2,打开ADC,进行矫正复位,矫正。
完成后,就没有了,只剩下读数据了。
读数据我要说下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b5a0f3a.jpg)
我们要先配置我们要采样的通道,然后打开控制寄存器CR2上的开始转换 Start Conversion
注意检测状态寄存器里面的转换状态,转换结束,他会把EOC位置1
然后我们就可以读数据了;
现在附上代码,大家可以参考代码看看:
~~~
/* adc.c */
#include <stm32f10x.h>
void adc1_init()
{
RCC->APB2ENR |= 1<<2;
GPIOA->CRL &= ~(0xf<<0);
GPIOA->CRL |= 0x0<<0;
RCC->APB2ENR |= 1<<9;
RCC->APB2RSTR |= 1<<9;
RCC->APB2RSTR &= ~(1<<9);
RCC->CFGR &= ~(3<<14);
RCC->CFGR |= 2<<14; // 6 div PCLK2 / 6 = 12MHz
ADC1->CR1 &= ~(0xf<<16);
ADC1->CR1 |= 0<<16; //Set Indenpendence Mode
ADC1->CR1 &= ~(1<<8); //Scan Mode Disable
/* Config Control Register 2*/
ADC2->CR2 |= 1<<1; //Continuous conversion mode
ADC1->CR2 &= ~(7<<17); //Clear
ADC1->CR2 |= 7<<17; //software start
ADC1->CR2 |= 1<<20; //Conversion on external event enable
ADC1->CR2 &= ~(1<<11); //Right Alignment
ADC1->SQR1 &= ~(0xf<<20);
ADC1->SQR1 |= 0<<20;
ADC1->SMPR2 &= ~(0x7<<3);
ADC1->SMPR2 |= 7<<3;
ADC1->CR2 |= 1<<0; //Start ADC to Calibration
ADC1->CR2 |= 1<<3;
while(ADC1->CR2 & 1<<3);
ADC1->CR2 |= 1<<2;
while(ADC1->CR2 & 1<<2);
}
unsigned short get_adc(unsigned char ch)
{
ADC1->SQR3 &= ~(0xf<<0);
ADC1->SQR3 |= ch;
ADC1->CR2 |= 1<<22;
while(!(ADC1->SR & 1<<1));
return ADC1->DR;
}
~~~
主函数::::
~~~
/* main.c */
#include <stm32f10x.h>
#include "stdio.h"
#include "init.h"
#include "usart.h"
#include "adc.h"
int main()
{
char buff[256];
sys_init(9);
rs232_init(CPU_72M,9600);
rs232_send_byte('\n');
adc1_init();
while(1)
{
sprintf(buff,"V:%.3f V\n",3.3*get_adc(1)/4096);
rs232_send_str(buff,strlen(buff));
delay_ms(1000);
}
}
~~~
ADC有的地方我还没有搞的完全懂,愿意听各位大神指点!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b5bef99.jpg)
【菜鸟入门】stm32 之 pwm
最后更新于:2022-04-01 14:50:47
这个pwm几天前就搞出来了,但是觉得不是很难,就没有写
今天jlink固件坏了,修了半天没有修好,现在就先扔着吧,先借大黄的用用,下面闲着木事,就把pwm写下吧
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b397e1c.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b3bc73f.jpg)
好吧,开始今天的pwm
还是老话,先附上一段代码:(由于stm的pwm有多个,每个又有多个通道,我们今天选TIM2_CH1来产生)
~~~
/*
* TIM2_CH1
*/
int pwm_init(u16 arr,u16 psc)
{
RCC->APB1ENR = 1<<0; //enable timer2
RCC->APB2ENR = 1<<2; //enable GPIOA
GPIOA->CRL &= ~(0xF<<0); //Clear bit0 Control Register
GPIOA->CRL |= 0xb<<0; //Set Bit0 Control Register
// GPIOA->ODR |= 1<<0; //Enable Bit0
TIM2->ARR = arr;
TIM2->PSC = psc;
TIM2->CCMR1 |= 7<<4;
TIM2->CCMR1 |= 1<<3;
TIM2->CCER |= 1<<0;
TIM2->CR1 |= 1<<0;
return 0;
}
~~~
前5句,现在大家都应该懂了吧,TIMER时钟配置,管脚时钟配置,管脚模式配置,这里肯定要配置称输出模式
由于咱们选用的是TIMER2,那我们就用TIM2
先设置自动装载寄存器ARR和预分频寄存器PSC,这两个寄存器一起来设置分频的频率
举个例子:
PCLK = 72M = 72000KHz,设置ARR = 900,PSC = 1;
则输出的PWM周期为:72 000 /(900*(1+1) ) = 40KHz
至于CCMR比较/捕获模式寄存器
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b3dd83f.jpg)
他有 两个功能,当选择比较寄存器的时候,看上面一栏,捕获模式时看下面一栏
输出比较模式,他有两种输出模式,OC1和OC2,其实总共有4组,他有4路输出,CCMR1,CCMR2分别控制两组;
我们这里用的是CH1通道,所以我们需要配置0~7bit的数据。
首先我们来了解下pwm输出模式:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b3f33f4.jpg)
根据这段描述,这里的无效电平,会导致OCxREF为低电平,从而管脚输出电压即为低电平。
所以,011:在把低电平换位高电平,高电平换成低电平
100:输出全部为低电平
101:输出全为高电平
110:先输出高电平,再输出低电平,之类的CCR1比较/捕获寄存器是用来区分什么时候发生电平跳变的,CCRx寄存器也有4组,我们用的是CH1通道,估大家都懂,我们应该用CCR1
111:跟上一组相反
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b4287f6.jpg)
~~~
int main()
{
sys_init(9);
pwm_init(900,3); //Set Period
RCC->APB2ENR |= 1<<6;
GPIOE->CRL &= ~(0xf<<0);
GPIOE->CRL |= 0xb<<0;
GPIOE->ODR |= 1<<0;
TIM2->CCR1 = 0;
sw5_config();
while(1)
{
if(!(GPIOE->IDR & GPIO_Pin_14))
{
delay_ms(20);
if(!(GPIOE->IDR & GPIO_Pin_14))
{
TIM2->CCR1 += 10;
}
while(!(GPIOE->IDR & GPIO_Pin_14));
}
}
}
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576915b44966a.jpg)
这里ARR = 900,PSC = 3
所以: F = 72000KHz/(900*(3+1)) = 20KHz
基本上跟示波器上的一样
关于pwm的配置是相对比较简单的,大家不要被他寄存器的数量吓到了,那么多寄存器,是因为他有多路,所以,他会有那么多,学会了一路,其他都是非常easy的!