- 积分
- 486
- 在线时间
- 210 小时
- 最后登录
- 2024-5-20
- 阅读权限
- 50
- 精华
- 0
- UID
- 787724
- 帖子
- 424
- 精华
- 0
- 经验
- 486 点
- 金钱
- 396 ¥
- 注册时间
- 2015-3-20
|
楼主 |
发表于 2022-9-24 01:43
|
显示全部楼层
本帖最后由 丰年好大雪 于 2022-9-24 03:22 编辑
代码我使用了对新手最为友好的官方驱动库编程模式,虽然相比IAR等第三方工具,ST意法的官方开发环境STVP还是显得有点不好用,但制作这么简单的程序,仍然是绰绰有余。我尽量加了尽可能详细的注释以便于理解。
/* MAIN.C file
*
* Copyright (c) 2002-2005 STMicroelectronics
*/
#include <stm8s.h>
#include <stm8s_gpio.h>
#include <stm8s_i2c.h>
#include <stm8s_conf.h>
#include <stm8s_clk.h>
#include <stm8s_exti.h>
#include <lib_rotary.h>
//以上引用的库文件,从stm8s.h到exti.h均为意法的官方驱动库"STM8S_StdPeriph_Lib",最后一个lib_rotary.h为驱动旋转编码器自制库
//未来如果需要用SPI端口驱动屏幕等,还需要加入spi官方库,以及为点阵屏、液晶屏专门用的其他驱动,到时候再说
u8 TCA9554_ADDR = 0x40; //注:我最早使用的是TCA9554,和TCA9534能混用,此片为驱动LED用
u8 TCA9554_ADDR1 = 0x42; //注:此片驱动继电器用
u8 VALUE_1;
u8 Switch_3 = 0; //注:旋转编码器按压静音功能未做出来,我单独使用一个引脚控制额外的继电器断开输出,电路图中并未体现,因此未加入,有兴趣的朋友可以找例程研究
u8 Temp; //Temp为温度计式字符串式LED的真值
bool LED_Status = TRUE;
/*下方为旋转编码器声明部分*/
#define PIN_MASK (4 | 5) //做一下位或,选择GPIO_D的4、5脚
#define IDR GPIO_ReadInputData(GPIOD) //为方便,我将所有输入引脚均连接至GPIO的D组
static char val_cur; //当前旋转编码器位置
static char val_max; //最高步长
static char Direction; //方向向量
static char pins_last; //最后位置暂存
static char roll_up, roll_down; //向左/向右判断变量
//结束
void delay_1ms(void) //1ms延时函数,以16M高速时钟为参考
{
u8 t=108;
while(--t);
}
void delay_ms(u16 ms) // ms延时函数,以16M高速时钟为参考
{
while(--ms)
{
delay_1ms();
}
}
void SYS_Init(void) //系统初始化函数,无返回
{
CLK_HSICmd(ENABLE); //使能STM8内置16M高速时钟
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV2); //高速时钟配置,除2
//CLK_LSICmd(ENABLE); //使能STM8内置低速400Khz时钟,害怕窜入干扰可以开启这个,同时注释掉上边两行,但注意总时钟变慢后,延时函数将边长,必须重新计算delay函数!
CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, ENABLE); //使能I2C端口分频后的时钟
GPIO_Init(GPIOA, GPIO_PIN_3, GPIO_MODE_IN_PU_IT); //PD3上拉输入带中断,给EC11按下使能静音用,本程序无体现
GPIO_Init(GPIOD, GPIO_PIN_4, GPIO_MODE_IN_PU_NO_IT);
GPIO_Init(GPIOD, GPIO_PIN_5, GPIO_MODE_IN_PU_NO_IT); //PD4、5脚上拉输入,无中断
EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOD, EXTI_SENSITIVITY_FALL_ONLY); //中断设置,仅下降沿触发
I2C_DeInit(); //I2C端口标准初始化
I2C_Init(200000, 0x1D, I2C_DUTYCYCLE_2, I2C_ACK_CURR, I2C_ADDMODE_7BIT, 16); //I2C端口设置,32位速度200k可以上400k酌情修改,占空比1:1,应答当前,7位地址模式,16M输入时钟
I2C_Cmd(ENABLE); //I2C使能
}
void I2C_Write(u8 CS,u8 Addr,u8 Data) //I2C写函数,带三个输入参数,分别为目标芯片地址、寄存器地址、数据
{
while(I2C_GetFlagStatus(I2C_FLAG_BUSBUSY)); //判断总线忙
/* 1.开始 */
I2C_GenerateSTART(ENABLE);
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //主模式
/* 2.设备地址/写 */
I2C_Send7bitAddress(CS,I2C_DIRECTION_TX); //写目标芯片TCA9554_ADDR的7位地址
while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断主模式
/* 3.数据地址 */
I2C_SendData(Addr);
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断写动作完毕
/* 4.写一字节数据 */
I2C_SendData(Data);
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断写动作完毕
/* 5.停止 */
I2C_GenerateSTOP(ENABLE);
}
/******************************************************************************
* 以下为旋转编码器设置
*
* 输入参数: 步长和总步长
******************************************************************************/
void alps_init(char steps, char option) //旋转编码器初始化
{
val_cur = 0;
val_max = steps;
Direction = 0;
pins_last = IDR & PIN_MASK; //将输入脚初始化并记录;
// EC11编码器总步长设定
roll_up = steps - 1; //当达到最大值不再增加
roll_down = 1; //当达到最小值保持0
if (option == ALPS_ROLLOVER)
{
roll_up = 0;
roll_down = steps;
}
}
/******************************************************************************
* 获得编码器当前位置 (需要前面的设置)
*****************************************************************************/
char alps_value(void)
{
return val_cur;
}
/******************************************************************************
* 轮询引脚
******************************************************************************/
void alps_poll(void)
{
char pins_cur;
char diff;
pins_cur = IDR & PIN_MASK;
if ((pins_cur == 0) || (pins_cur == PIN_MASK)) // 判断是否全低
{
if (pins_cur == pins_last)
return;
pins_last = pins_cur; //如果等同上一次的值就返回(无动作)
if (Direction) //判断旋转事件
{
val_cur++; //当前值变量加一
if (val_cur == val_max) //如已达到最大值
val_cur = roll_up;
return;
}
if (val_cur == 0) //判断第二种旋转事件
val_cur = roll_down; //如为负
val_cur--; //当前值减一
return;
}
diff = pins_cur ^ pins_last;
Direction = 0; //则向左旋转
if (diff == 4) //如果同时另外一脚为高
Direction = 1; //向右旋转
}
/********************************************************************************
*迅速滚动开机自检LED
********************************************************************************/
void LED_Rolls(void)
{
I2C_Write(TCA9554_ADDR,0x03,0x00);
I2C_Write(TCA9554_ADDR,0x01,0x00);
delay_ms(1000);
I2C_Write(TCA9554_ADDR,0x01,0xFF);
delay_ms(1000);
I2C_Write(TCA9554_ADDR,0x01,0x00);
delay_ms(1000);
return;
}
main() //主函数
{
SYS_Init();
LED_Rolls();
I2C_Write(TCA9554_ADDR,0x03,0x00);
I2C_Write(TCA9554_ADDR1,0x03,0x00); //此两行为先设定TCA9554/9534的方向寄存器,开漏输出
alps_init(63,63); //设定编码器步长,这里需要根据继电器数量自行决定,这里假设是6bit
LED_Status = TRUE;
//二进制音量设置,进入死循环轮询模式
while(1)
{
delay_ms(100); //轮询时间间断
alps_poll();
alps_value();
VALUE_1 = ~val_cur;
VALUE_1--;
I2C_Write(TCA9554_ADDR1,0x01,VALUE_1); //先把继电器都给跳喽
delay_ms(10); //没啥用,就是延时一下
switch(VALUE_1) //将真值变量送入switch语句转换成LED字符显示,我这里用了笨办法预设值查表,没有用更高级的枚举类型加指针,就是为了让初学者能看明白
{
//假设使用8个led指示,每三个步长一跳变,自己酌情增减
case 0xFE:
I2C_Write(TCA9554_ADDR,0x01,0b11111110);break;
case 0xFC:
I2C_Write(TCA9554_ADDR,0x01,0b11111110);break;
case 0xFA:
I2C_Write(TCA9554_ADDR,0x01,0b11111110);break;
case 0xF8:
I2C_Write(TCA9554_ADDR,0x01,0b11111100);break;
case 0xF6:
I2C_Write(TCA9554_ADDR,0x01,0b11111100);break;
case 0xF4:
I2C_Write(TCA9554_ADDR,0x01,0b11111100);break;
case 0xF2:
I2C_Write(TCA9554_ADDR,0x01,0b11111000);break;
case 0xF0:
I2C_Write(TCA9554_ADDR,0x01,0b11111000);break;
case 0xEE:
I2C_Write(TCA9554_ADDR,0x01,0b11111000);break;
case 0xEC:
I2C_Write(TCA9554_ADDR,0x01,0b11110000);break;
case 0xEA:
I2C_Write(TCA9554_ADDR,0x01,0b11110000);break;
case 0xE8:
I2C_Write(TCA9554_ADDR,0x01,0b11110000);break;
case 0xE6:
I2C_Write(TCA9554_ADDR,0x01,0b11100000);break;
case 0xE4:
I2C_Write(TCA9554_ADDR,0x01,0b11100000);break;
case 0xE2:
I2C_Write(TCA9554_ADDR,0x01,0b11100000);break;
case 0xE0:
I2C_Write(TCA9554_ADDR,0x01,0b11000000);break;
case 0xDE:
I2C_Write(TCA9554_ADDR,0x01,0b11000000);break;
case 0xDC:
I2C_Write(TCA9554_ADDR,0x01,0b11000000);break;
case 0xDA:
I2C_Write(TCA9554_ADDR,0x01,0b10000000);break;
case 0xD8:
I2C_Write(TCA9554_ADDR,0x01,0b10000000);break;
case 0xD6:
I2C_Write(TCA9554_ADDR,0x01,0b10000000);break;
case 0xD4:
I2C_Write(TCA9554_ADDR,0x01,0b00000000);break;
//真开漏模式必须输入反码,必须注意
}
}
}
#ifdef USE_FULL_ASSERT //STM8主函数后边要加上这玩意避免无法编译
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval : None
*/
void assert_failed(u8* file, u32 line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf(“Wrong parameters value: file %s on line %drn”, file, line) */
/* Infinite loop */
while (1)
{
}
}
#endif
/******************************************************************
*全部结束
******************************************************************/
//彩蛋,我懒得删除的中断部分,使用一个引脚外接按钮,设定为上拉作为中断输入,可以用来控制第三片9534来做输入信号选择,按一下变换一次,有兴趣可以研究研究怎么用
//以下部分可以放在主函数后边
// 3选1开关
@far @interrupt void EXTI1_IRQHandler(void)
{
disableInterrupts(); //讨厌的STM8,响应中断必须先关中断,再观察输入寄存器
switch(Switch_3)
{
case 0x00:
Switch_3 ++;
I2C_Write(TCA9554_2,0x01,0b11111110);
break;
case 0x01:
Switch_3 ++;
I2C_Write(TCA9554_2,0x01,0b11111101);
break;
case 0x02:
I2C_Write(TCA9554_2,0x01,0b11111011);
Switch_3 = 0x00;
break;
}
delay_ms(1000);
enableInterrupts();
}
//第二彩蛋,没有放进去的EC11按下静音功能
@far @interrupt void EXTI1_IRQHandler(void)
{
disableInterrupts();
if(LED_Status == TRUE)
{
I2C_Write(TCA9554_2,0x01,0b01111111);
}
else
{
I2C_Write(TCA9554_2,0x01,0b11111111);
}
LED_Status = ~LED_Status;
delay_ms(500);
enableInterrupts();
}
/*****
*lib_rotary.h头文件
*****/
void alps_init(char steps, char option);
#define ALPS_LIMITS 1
#define ALPS_ROLLOVER 2
/*
* 当前值
*/
char alps_value(void);
/*
* 轮询时间
*/
void alps_poll(void);
//彩蛋3,如果要用中断,可以改写主程序和下列中断向量表:
/* BASIC INTERRUPT VECTOR TABLE FOR STM8 devices
* Copyright (c) 2007 STMicroelectronics
*/
typedef void @far (*interrupt_handler_t)(void);
struct interrupt_vector {
unsigned char interrupt_instruction;
interrupt_handler_t interrupt_handler;
};
@far @interrupt void NonHandledInterrupt (void)
{
/* in order to detect unexpected events during development,
it is recommended to set a breakpoint on the following instruction
*/
return;
}
extern void _stext(); /* startup routine */
//extern @far @interrupt void EXTI1_IRQHandler (void); //**Interrupt of PD
struct interrupt_vector const _vectab[] = {
{0x82, (interrupt_handler_t)_stext}, /* reset */
{0x82, NonHandledInterrupt}, /* trap */
{0x82, NonHandledInterrupt}, /* irq0 */
{0x82, NonHandledInterrupt}, /* irq1 */
{0x82, NonHandledInterrupt}, /* irq2 */
{0x82, NonHandledInterrupt}, /* irq3 */
{0x82, NonHandledInterrupt}, /* irq4 */
{0x82, NonHandledInterrupt}, /* irq5 */
//{0x82, NonHandledInterrupt}, /* irq6 */
{0x82, (interrupt_handler_t)EXTI1_IRQHandler}, /* 中断irq6 */
{0x82, NonHandledInterrupt}, /* irq7 */
{0x82, NonHandledInterrupt}, /* irq8 */
{0x82, NonHandledInterrupt}, /* irq9 */
{0x82, NonHandledInterrupt}, /* irq10 */
{0x82, NonHandledInterrupt}, /* irq11 */
{0x82, NonHandledInterrupt}, /* irq12 */
{0x82, NonHandledInterrupt}, /* irq13 */
{0x82, NonHandledInterrupt}, /* irq14 */
{0x82, NonHandledInterrupt}, /* irq15 */
{0x82, NonHandledInterrupt}, /* irq16 */
{0x82, NonHandledInterrupt}, /* irq17 */
{0x82, NonHandledInterrupt}, /* irq18 */
{0x82, NonHandledInterrupt}, /* irq19 */
{0x82, NonHandledInterrupt}, /* irq20 */
{0x82, NonHandledInterrupt}, /* irq21 */
{0x82, NonHandledInterrupt}, /* irq22 */
{0x82, NonHandledInterrupt}, /* irq23 */
{0x82, NonHandledInterrupt}, /* irq24 */
{0x82, NonHandledInterrupt}, /* irq25 */
{0x82, NonHandledInterrupt}, /* irq26 */
{0x82, NonHandledInterrupt}, /* irq27 */
{0x82, NonHandledInterrupt}, /* irq28 */
{0x82, NonHandledInterrupt}, /* irq29 */
};
//结束,累死我了 |
评分
-
查看全部评分
|