STM32F103C8T6-流水灯、呼吸灯
一、流水灯
1. 搭建电路
注意面包板的导通情况:
这里我们将开发板插在面包板上,3.3V 和 GND 分别通过电阻接面包板的正极、负极;LED的正极接面包板正极,负极接 GPIO 口。
2. 代码编写
因为这里使用的引脚属于不同的端口,无法使用二进制位移操作,因此我们使用两个数组标记引脚。
/* USER CODE BEGIN 2 */
uint16_t pinsA[4] = {LED_1_Pin, LED_2_Pin, LED_3_Pin, LED_4_Pin};
uint16_t pinsB[2] = {LED_5_Pin, LED_6_Pin};
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
for (int i = 0; i < sizeof(pinsA) / sizeof(pinsA[0]); i ++){
HAL_GPIO_WritePin(GPIOA, LED_1_Pin | LED_2_Pin | LED_3_Pin | LED_4_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, pinsA[i], GPIO_PIN_RESET);
HAL_Delay(1000);
}
HAL_GPIO_WritePin(GPIOA, LED_4_Pin, GPIO_PIN_SET);
for (int i = 0; i < sizeof(pinsB) / sizeof(pinsB[0]); i ++){
HAL_GPIO_WritePin(GPIOB, LED_5_Pin | LED_6_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, pinsB[i], GPIO_PIN_RESET);
HAL_Delay(1000);
}
HAL_GPIO_WritePin(GPIOB, LED_6_Pin, GPIO_PIN_SET);
/* USER CODE END WHILE */
此外还要注意的是HAL_GPIO_WritePin(GPIOA, LED_1_Pin | LED_2_Pin | LED_3_Pin | LED_4_Pin, GPIO_PIN_SET)
这种写法,用于同时操作多个 GPIO 引脚;因为每个引脚通常用一个二进制位表示,每个宏(如 LED_1_Pin)只在它对应的那一位上为1,其余位为0。这个整体数值称为位掩码(bitmask)。
3. 交替循环
上文实现的是单灯循环,进一步实现交替循环,即 右 -> 左 -> 右…
/* USER CODE BEGIN WHILE */
while (1)
{
for (int i = 0; i < sizeof(pinsA) / sizeof(pinsA[0]); i ++){
HAL_GPIO_WritePin(GPIOA, LED_1_Pin | LED_2_Pin | LED_3_Pin | LED_4_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, pinsA[i], GPIO_PIN_RESET);
HAL_Delay(1000);
}
HAL_GPIO_WritePin(GPIOA, LED_4_Pin, GPIO_PIN_SET);
for (int i = 0; i < sizeof(pinsB) / sizeof(pinsB[0]); i ++){
HAL_GPIO_WritePin(GPIOB, LED_5_Pin | LED_6_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, pinsB[i], GPIO_PIN_RESET);
HAL_Delay(1000);
}
HAL_GPIO_WritePin(GPIOB, LED_6_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, LED_5_Pin, GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOB, LED_5_Pin, GPIO_PIN_SET);
for (int i = sizeof(pinsA) / sizeof(pinsA[0]) - 1; i >= 1; i --){
HAL_GPIO_WritePin(GPIOA, LED_1_Pin | LED_2_Pin | LED_3_Pin | LED_4_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, pinsA[i], GPIO_PIN_RESET);
HAL_Delay(1000);
}
HAL_GPIO_WritePin(GPIOB, LED_1_Pin, GPIO_PIN_SET);
/* USER CODE END WHILE */
- 此处出现过的 bug 有 LED_6 亮的时间超出,简单修改。
4. 分组亮灯
通过结构体,统一管理 LED 的相关信息:
/* USER CODE BEGIN PD */
# define LED_NUM sizeof(LEDs) / sizeof(LEDTypedef)
/* USER CODE END PD */
/* USER CODE BEGIN PM */
typedef struct {
GPIO_TypeDef *port;
uint16_t pin;
} LEDTypedef;
/* USER CODE END PM */
/* USER CODE BEGIN PV */
LEDTypedef LEDs[] = {
{GPIOA, LED_1_Pin},
{GPIOA, LED_2_Pin},
{GPIOA, LED_3_Pin},
{GPIOA, LED_4_Pin},
{GPIOB, LED_5_Pin},
{GPIOB, LED_6_Pin}
};
/* USER CODE END PV */
编写分组亮灯的函数:
/* USER CODE BEGIN 0 */
void LED_GROUP_ON(const uint16_t *groupIndex, uint16_t groupSize){
for (int i = 0; i < groupSize; i ++){
uint8_t index = groupIndex[i];
HAL_GPIO_WritePin(LEDs[index].port, LEDs[index].pin, GPIO_PIN_RESET);
}
}
void LED_GROUP_OFF(const uint16_t *groupIndex, uint16_t groupSize){
for (int i = 0; i < groupSize; i ++){
uint8_t index = groupIndex[i];
HAL_GPIO_WritePin(LEDs[index].port, LEDs[index].pin, GPIO_PIN_SET);
}
}
/* USER CODE END 0 */
分组:
/* USER CODE BEGIN 2 */
uint16_t group1[3] = {0, 2, 4}, group2[3] = {1, 3, 5};
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
LED_GROUP_ON(group1, 3);
HAL_Delay(1000);
LED_GROUP_OFF(group1, 3);
LED_GROUP_ON(group2, 3);
HAL_Delay(1000);
LED_GROUP_OFF(group2, 3);
/* USER CODE END WHILE */
二、PWM 与呼吸灯
1. PWM 的定义
绝大多数的单片机上,处理模拟信号的资源很少。STM32F103C8T6 上只有模数转换功能(ADC),用于读取模拟信号的电压值;脉冲宽度调制(PWM, Pulse-Width Modulation)能够通过改变占空比,用数字信号尽可能地模拟模拟信号效果。只要动态地改变占空比,宏观上看就能产生 [0, 3.3V] 的电压。
2. 定时器:输出比较模式之 PWM 模式
通过调节比较寄存器值的大小,控制 PWM 的占空比
3. CubeMX 配置
- 设置串口 debug
- 设置外部时钟晶振
- 设置时钟树
- TIM 设置
-
选择 MCU 的内部时钟系统来驱动定时器。
-
启用TIM3的 Channel1 并配置为 PWM输出模式。Channel1 被分配到 PA6 引脚。
Prescaler(预分频器,PSC):该参数定义了定时器的预分频系数。定时器的时钟频率通过预分频器从 MCU 的系统时钟分频得到。PSC = N 表示时钟每经过(N + 1)个周期才让定时器计数器递增 1。
Counter Period(计数周期,ARR):该参数定义了定时器的周期,即计数器从 0 计数到的最大值。定时器每次从 0 计数到 ARR(自动重载寄存器的值)后会重置为 0,重新开始计数。此处计数器从 0 计数到 99。 -
-
Pulse: 设置 Channel1 的初始脉冲宽度,即占空比为 50。Pulse 值 是输出比较寄存器中的值。当计数器的值小于 Pulse 时,PWM 输出为高电平。
- 注:要查看函数定义,必须先编译。光标放在函数上,右键。
- 此处的输出为 GPIO 的复用推挽输出模式。因为外设需要进行推挽输出?
3. 代码
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
for (int i = 0; i < 100; i ++){
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, i);
HAL_Delay(10);
}
for (int i = 99; i >= 0; i --){
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, i);
HAL_Delay(10);
}
/* USER CODE END WHILE */