文章目录
一、新建项目
注意项目名称和项目文件夹不能有中文
二、关于STM32CubeMX软件生成的代码
- 选好芯片型号后的必要步骤
- 关于代码保护区的概念
- 关于HAL库的查看
选好芯片后,开启DEBUG模式,引脚会自动设置。SW是占用引脚最少的DEBUG模式
main函数里面有这三条函数
HAL_Init();//HAL库初始化,不用管
SystemClock_Config();//时钟初始化,不用管
MX_GPIO_Init();//由于没加什么功能,功能初始化就只有这个
注意:自己构建代码保护区是不行的
如果此选项卡不小心关掉,可以从view里面调出来
HAL库函数返回一般时枚举类型,右键go to即可查看定义。在每个库的.c文件里前面的内容会说明该功能怎么用
三、GPIO操作
GPIO_Inputhe GPIO_Outout的使用
利用宏定义方便获取按键
LED1-PE5,LED0-PB5
配置完后生成代码,再去GPIO库里找些函数用
用宏定义定义后,就可以直接用LED0和KEY1等直接读取值
注意在下面定义了函数,一定要在开头声明。
注意:按键要做消抖处理
四、GPIO外部中断与DEBUG找问题
- 中断的设置
- 回调函数的使用
- 中断使用延时卡死问题
精英板GPIO口为PE4
将外部中断的优先级设置的更高
修改之后中断里就能用延时了
五、RCC设置于TIM
- 设置时钟
- 定时器TIM中断
RCC :reset clock control 复位和时钟控制器。 时钟是单片机运行的基础,时钟信号推动单片机内各个部分执行相应的指令。单片机有了时钟,才能够运行执行指令。
Stm32的时钟系统可参见博文:系统时钟RCC详解
首先应该注意的是:HSI RC,LSI RC为芯片自带晶振源,外部晶振源HSE没有激活,因为没给单片机设置外部晶振
引脚设置好了
然后生成代码
定时器设置好后,还需要函数进行开始
计时器中断后会进入一个回调函数
下面写标准点,加上判断,利用入参进行判断,入参是个指针,判断方法参考初始化函数
下面去解决之前的一个问题,我启用了两个中断,现在去判断哪个是定时器溢出中断
然后灯没反应了
六、定时器中断和PWM
- 定时器中断频率计算
- PWM占空比和周期频率计算
关于定时器中断频率设置,只需要关注:定时器输入时钟的频率、预分频值(Prescaler)、自动重装值(Counter Period)。
预分频和自动重装值注意要减1
APB2 timer clocks
Prescaler和Counter Period
下面开始PWM操作,先选定一个引脚
PWM和定时器中断一样,也需要函数启动
复制到main函数里,复制过来后,因为函数里的Channel不知道怎么填,可以到tim.c找找
HAL_TIM_PWM_Start((TIM_HandleTypeDef *) &htim3, (uint32_t)TIM_CHANNEL_2 );
下面开始设置PWM占空比,占空比跟自动重装值和Pulse有关,占空比=Pulse/自动重装值
Pulse由TIM3->CCR2设置
七、串口发送以及printf
- 复习以下新建工程的预先步骤
- 串口传输函数的使用
- printf重定义(非网上的传统方式)
新建项目,记得设置RCC
首先查手册看USART的引脚
然后设置引脚功能
然后生成代码,寻找HAL串口传输函数
串口有三种传输模式:
- 普通阻塞
- 中断-非阻塞
- DMA-非阻塞
先用普通模式:
用这个函数
第一个函参,串口结构体(可以到usart.c查看,记得在前面加取址符&)
第二个函参,发送的数组地址
第三个函参,发送数组内的多少个数据
第四个函参,设置超时时间(单位ms,因为该函数会阻塞,执行时间超过设置的时间就会跳出传输)
复制这个函数到main函数
HAL_UART_Transmit((UART_HandleTypeDef *)&huart1,
(uint8_t *)"Uart1 OK!\n\r", //这里输入字符串,实际意义为字符串的地址
(uint16_t) strlen("Uart1 OK!\n\r"), //可以用strlen()函数获取字符串的长度,不过要先声明string.h
(uint32_t) 999); //超时时间一般设置的大一点,足够把函数执行完就行了,这里设置成999ms
由于工程时新建的,要设置下载器:
- 选自己的下载器
- 设置成SW模式
- 下载后复位(看需求)
选中这个后Reset and Run,下载即自动复位
下面重定义printf
注意:宏定义函数,必须写在一行内。反斜杠起到了续行的作用,c语言编译器会忽略行尾的换行符,而把下一行的内容也算作是本行的内容。如果不加反斜杠,编译过不了。
#define printf1(...) HAL_UART_Transmit((UART_HandleTypeDef *)&huart1, \
(uint8_t *)u_buf,\
sprintf((char*)u_buf,__VA_ARGS__),\
0xffff);
复制完宏定义还不都,还需要声明一个数组(这个重定义printf跟网上的有根本性质的不同,所以需要声明一个数组)
给个256空间就绰绰有余了(这数组共占了RAM的256Byte)
然后printf1()函数和printf()函数就能实现同样的功能了。
原理解释:
使用sprintf()函数将发送的内容放进u_buf数组,而且sprintf会返回发送内容的长度,用u_len保存
VA_ARGS:用于在宏替换部分中,表示可变参数列表;该宏代表是可变参数;就是宏定义中参数列表的最后一个参数为省略号(也就是“···”)。这样预定义宏VA_ARGS就可以被用在替换部分中,替换省略号所代表的字符串。省略号只能代替最后面的宏参数。
##VA_ARGS :加##用来支持0个可变参数的情况
八、串口发送方式对运算速度影响
- 串口3种发送方式
- 阻塞状态对运算速度的影响
注:阻塞相当于延时,对单线程来说运算速度影响很大
DMA:直接内存访问。直接内存访问(Direct Memory Access,DMA)
直接内存访问是计算机科学中的一种内存访问技术。它允许某些计算机内部的硬件子系统(计算机外设),可以独立地直接读写系统内存,而不需中央处理器(CPU)介入处理 。在同等程度的处理器负担下,DMA是一种快速的数据传送方式。很多硬件的系统会使用DMA,包含硬盘控制器、绘图显卡、网卡和声卡。
首先,打开串口中断与DMA模式,并且把中断等级设置为1(避免与重要的中断产生不必要的抢占)
注意:如果使用串口DMA,一定要把串口中断也打开
然后把DMA,IT发送函数复制过来看看
这两个方式就是比普通方式少了个超时参数,因为是非阻塞才不需要超时参数
#define U1_printf(...) HAL_UART_Transmit((UART_HandleTypeDef *)&huart1, \
(uint8_t *)u_buf,\
sprintf((char*)u_buf,__VA_ARGS__),\
0xffff);
#define U1_printf_DMA(...) HAL_UART_Transmit_DMA((UART_HandleTypeDef *)&huart1, \
(uint8_t *)u_buf,\
sprintf((char*)u_buf,__VA_ARGS__));
#define U1_printf_IT(...) HAL_UART_Transmit_IT((UART_HandleTypeDef *)&huart1, \
(uint8_t *)u_buf,\
sprintf((char*)u_buf,__VA_ARGS__));
测试一下
U1_printf_DMA("Uart1_DMA ok!\n\r");
HAL_Delay(100); //这里要加一个小延时,等他发送完再继续发送,不然很大可能发送不出来
U1_printf_IT("Uart1_IT ok!\n\r");
HAL_Delay(100);
去掉中间延时会导致有一段消息没法送出来,所以非阻塞函数要看自己有没有能力保证数据完整采用
下面演示下这三种发送方式对运算速度的影响
下面创建一个定时器中断,如果不知道在APB1还是2,就都设置成一样的就行了
定时器中断使用:
- 开启计数器计数中断
- 定义好回调函数内容
回调函数为:__weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
定时器开始计数函数:HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
九、串口接收与数据错位
- 常用的串口DMA定长传输
- 定长传输常见的数据错位
- 数据CRC8校验
DMA定长接收函数:HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
因为是DMA循环模式,所以接受完一次还会接着接收
然后找接收回调函数,让他接受玩后输出出来
接收回调函数:_weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
具体实现步骤见视频:串口接收与数据错位