最近公司事情太多了,将近一个月没有更新了。 我的想法是把我用到的所有单片机都更新一下(当然不局限于这些,后期也会更新一些其他和嵌入式相关的知识点,我就想着先把STM32这整个更新完,再去更新其他的,包括STM32下 RTOS、UI(emwin,LVGL)还有一些项目上的经验什么的。 等STM32相关的更新完之后会写其他的东西。 也会根据实际的情况进行其他内容的更新:我用过STM32 CH32、HC32F4、S32K148等,还有各种传感器啥的。
从本章节开始我们进行一个STM32实战操作,为此我还特意画了个简单的板子。 是物联网相关的,后面会设计一个BMS的板子来写一下。
1-编写第一个程序点亮LED灯
下面是原理图,连接的是PC0-PC7引脚:
1static void Led_Cofig(void)
2{
3 /*定义一个GPIO_InitTypeDef类型的结构体*/
4 GPIO_InitTypeDef GPIO_InitStructure;
5 /*开启LED相关的GPIO外设时钟*/
6 RCC_APB2PeriphClockCmd(LED_GPIO_CLCK, ENABLE);
7 /*选择要控制的GPIO引脚*/
8 GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;
9 /*设置引脚模式为通用推挽输出*/
10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
11 /*设置引脚速率为50MHz */
12 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //(指的是内部驱动电路的响应速度,速度越大越快,一般情况是有多个不同的速度,也可根据自己的需要安排)
13 GPIO_Init(LED_GPIO_PORT, &GPIO_InitStructure);
14 /* 关闭led灯 */
15 GPIO_SetBits(LED_GPIO_PORT, LED1_GPIO_PIN);
16}
1/*LED连接GPIO的定义,程序开发者如果要修改引脚只需要修改这里就好*/
2#define LED_GPIO_PORT GPIOC // GPIO端口定义,为了方便修改
3#define LED_GPIO_CLCK RCC_APB2Periph_GPIOC /* GPIO端口时钟 */
4/*GPIO引脚*/
5#define LED1_GPIO_PIN GPIO_Pin_0
6#define LED2_GPIO_PIN GPIO_Pin_1
7#define LED3_GPIO_PIN GPIO_Pin_2
8#define LED4_GPIO_PIN GPIO_Pin_3
9#define LED5_GPIO_PIN GPIO_Pin_4
10#define LED6_GPIO_PIN GPIO_Pin_5
11#define LED7_GPIO_PIN GPIO_Pin_6
12#define LED8_GPIO_PIN GPIO_Pin_7
2-代码说明
1:GPIO_InitTypeDef GPIO_InitStructure 语句是声明一个结构体GPIO_InitStructure结构体原型由GPIO_InitTypeDef确定(这点是C语言的基础知识)设置完之后就可以对GPIO_Init(LED_GPIO_PORT, &GPIO_InitStructure); 进行操作。
2:GPIO_SetBits()是库函数,可以对多个I/O口同时置1。
这里还有一个GPIO_ResetBits()库函数,可以对多个I/O口置0。
注释和工程添加就不说了,网上相关的也挺多的。
GPIO_InitStructure.XXXX后面的speed什么的也不说了,不知道的可以去STM32知识篇看看。 这里主要讲述函数配置什么的,毕竟是实战嘛,指定是不能都介绍的。
3-main函数
1int main()
2{
3 LED_Init();
4 while (1)
5 {
6
7 GPIO_ResetBits(LED_GPIO_PORT, LED1_GPIO_PIN);
8 }
9}
因为接的是正极,点亮LED灯两端需要电压差,所以在这里使用GPIO_ResetBits才能点亮,如果你的一端接的是地,则是使用GPIO_SetBits。 这点要知道。 就是不管是哪种器件,只有保持电压差才能进行一个数据的接收,灯的点亮,我认为是这样的,可以说是所有的元器件芯片都是这样的,但不是绝对的,只能说大部分。
上面点亮之后,我们这样感觉修改太麻烦。 可以在头文件之后对其设置一个宏定义函数,如下:
1/*标准库点亮LED灯*/
2/*当a=0时,LED灯灭,当LED=1时,LED灯亮*/
3#define LED1(a) \\
4 if (a) \\
5 { \\
6 GPIO_ResetBits(LED_GPIO_PORT, LED1_GPIO_PIN); \\
7 } \\
8 else \\
9 GPIO_SetBits(LED_GPIO_PORT, LED1_GPIO_PIN);
当a=1时,灯亮,0时灯灭。
1int main()
2{
3 LED_Init();
4 while (1)
5 {
6 //GPIO_ResetBits(LED_GPIO_PORT,LED1_GPIO_PIN);
7 LED1(1);
8 }
9}
下面说一说位操作,这是嵌入式开发中最常用的。
5-位操作
什么是位操作,我认为位操作就是吃羊肉串,从前面吃,从后面吃,从中间吃这样的。 但是官方说法比我这好听。 其实位操作操作的是二进制或者十六进制这样的,你看像不像你在吃羊肉串,比如二进制0b0000 0001,你看从后面读取就相当于你从后面开始吃羊肉串。 下面说正经的。
我们对上面这个表进行一些介绍和使用说明
针对这种情况,应该怎么做才能实现对某几个位赋值呢? 我们可以把“&”和“|” 两个位操作结合起来使用,步骤如下。
(1)先对需要设置的位用“&”操作符进行清零操作。
(2)再用“|” 操作符赋值。
例如,在初始化时,若配置PD8引脚为推挽输出、速度为50 MHz,需将GPIOD->CRH的0~3位设置为3(即二进制0011B),这时可先对寄存器的0~3位进行“&”清零操作。
1GPIOD->CRH&=0Xfffffff0; //清掉原来的设置,同时不影响其他位设置
然后再与需要设置的值进行“|” 运算:
1GPIOD->CRH|=0X00000003; //设置0~3 位的值为3,不改变其他位的值
移位操作在STM32程序开发中也非常重要。 比如在初始化时,若需要使能GPIOD口的时钟,就可使用移位操作来实现,使能PORTD时钟的语句是:
1RCC->APB2ENR|=1<<5;
使能GPIOD和GPIOE口时钟的语句是:
1RCC->APB2ENR|=3<<5;
这个左移位操作,就是将RCC->APB2ENR寄存器的第5位设置为1,使能PORTD时钟。 为什么要通过左移而不是直接设置一个固定的值来对寄存器进行操作呢? 其实,这样做是为了提高代码的可读性以及可重用性。 读者可以很直观明了地看到,这行代码是将第5位设置为1。 如果写成:
1RCC->APB2ENR =0x00000020;
但是这样代码写出来不太友好,首先是不可重复使用,其次真烦呀。 反正这种挺多的,大家可以自己多练练。
SR寄存器的每一位都代表一个状态。 在某个时刻,我们希望设置某一位为0,同时其他位都保留为1,简单的做法是直接给寄存器设置一个值。
1TIMx->SR=0xF7FF;
上述代码设置第11位为0,但代码可读性不太友好。 但是我们可以这样写:
1#define TIM_FLAG_Update ((uint16_t)0x0001)
2TIMx->SR &=~(TIM_FLAG_Update <<11);
从上面的代码中,我们可以从第一条语句看出,宏定义了TIM_FLAG_Update第0位是1,其他位是0; 第二条语句让TIM_FLAG_Update左移11位取反,第11位就为0,其他位都为1; 最后通过按位与操作,使第11位为0,其他位保持不变。 这样,读者就能很容易地看明白代码,所以代码的可读性也就非常强的。
全部0条评论
快来发表一下你的评论吧 !