前言

资源准备

龙芯集成开发环境.zip

我的阿里云盘-原理图

龙芯官网

龙芯1B手册

龙芯软件最新版

龙芯1B — NOR Flash烧录PMON

新建项目

工程代码在线调试

通过 Mini USB 电缆,将龙芯 1B 开发板的 USB 接口PC/ 笔记本电脑的USB 接口 进行连接,并且通过外部 +5V 电源和电源开关给龙芯 1B 开发板供电。该步骤使得龙芯 1B 开发板处于正常的工作状态和调试准备状态。

双击运行LoongIDE安装路径下的 ftditest.exe ,然后点击 Load Driver 再点击 Do Test 如果出现了 driver is working fine. 说明驱动工作正常

开发板程序固化

点击 齿轮 图标进行编译,编译无误后,点击 三角 图标,将程序下载到内存之中。【注意:此时代码没有下载到 nand flash 之中,按下复位键后,程序会消失】

使用 EJTAG 下载程序会在动态内存中运行,断电后丢失,使用 LoongIDE Nand Flash编程工具可以将程序下载到 Nand Flash 将程序固化

PMON 是一个兼有 BIOS 和 bootloader 部分功能的开放源码软件,多用于嵌入式系统。

NAND Flash 用于存储用户应用程序,通过 bootloader 装载运行

  • 使用 Nand Flash 编程之前,首先需要 设置电脑与开发板至同一网段内,保证开发板与电脑可以使用 ping 命令进行测试。
  1. 打开Windows 网络配置,设置电脑有线网卡为固定 IP , 注意:系统可能有多个网卡请选择与开发板连接的网卡进行修改。
设置本机IP 地址 设置子网掩码 设置默认网关
192.168.1.X(X是任意数) 255.255.255.0 192.168.1.1
  1. 设置完IP 地址之后,使用网线连接开发板 以太网接口 与电脑以太网接口 打开Windows CMD 窗口,输入 ping 192.168.1.2 ,看看是否通了

需要注意的是在电脑与开发板进行ping 命令测试时, 一定要让开发板处于 PMON 命令模式 ,如果开发板目前在运行程序,是无法 ping 通的, 如果开发板内目前有程序运行,可以先将开发板复位或者擦除开发板 Nand Flash 内的程序

  1. 打开Nand Flash 工具后,会自动加载当前打开工程的可执行文件,点击确定即可

  1. 开发板启动以后,串口输出以下数据,代表固化成功,开机自动运行。通常来说,在产品发布阶段或者作品提交时才会进行代码固化,Flash 的擦写次数是有限的,且固化程序之后,想要再次固化或者调试,需要先擦除掉已有的固化程序。

开发板固化程序擦除

方法1

擦除固化时候,首先打开Nand Flash 编程工具 ,按住键盘 Ctrl 按键,窗口上方会提示 “清除 PMON 的自动运行设置”,点击确定,再次点击是,即可将固化程序擦除。【过程中不用松开Ctrl】

开发板启动以后,串口输出 “PMON>” 即代表固化程序被擦除,自动进入到PMON命令行模式。

方法2

打开串口助手,连接开发板串口调试接口。

串口助手连接成功以后,将开发板复位,在开发板启动阶段,通过串口助手 连续点击发送“ unset al ”字符注意需要勾选发送新行

一端连接开发板的 UART5,另一端连接上位机

串口控制台参数: 1152008N1(8位数据、无校验、1位停止位)

芯片介绍

主要
集成 2个 SPI控制器,支持系统启动
集成 AC97控制器
集成 1个全功能串口 、 1个四线串口 和 10个两线串口
集成 3路 I2C控制器,兼容 SMBUS
集成 2个 CAN总线控制器
集成 61个 GPIO端口
集成 1个 RTC接口
集成 4个 PWM控制器【数据宽度32位】
集成看门狗电路
核心板参数
SoC:龙芯1B200
内存:64M DDR2(其存储技术为DDR2)
NOR Flash:512K
NAND Flash:128M
尺寸:64mm×46mm
连接器:molex

开发板板载 EJTAG 调试器,通过USB连接开发主机。

LoongIDE工程介绍

  • 这个是龙芯1B外设库函数,开发板设备的通用驱动,包含 LS1B 所有控制器

  • core:启动文件和 LS1B 的管脚定义

    libc:库文件

    include:头文件

  • 一个设备在使用前必需执行 initialize,完成硬件初始化、创建mutex、安装中断等的初始化工作;有些设备还需要执行open操作,才可以进行读写操作【如果设备是通过复用功能配置的,必需在initialize中执行初始化操作】

  • 新建 .c .h 需要在编译设置那添加头文件路径然后重启项目

  • 在函数上使用 Ctrl + 鼠标左键 可跳转

项目文件后缀
.lxp 项目文件
.layout 项目配置文件
.S 大写,MIPS汇编语言源文件
.c/.cpp 项目源文件
.h/.hpp 项目头文件

LoongIDE对c/c++_源文件进行预处理,约定 "" < >的使用:

#include "xxx": 查找顺序为当前目录、本地搜索路径、系统搜索路径

#include <xxx>: 查找顺序为系统搜索路径、当前目录、本地搜索路径

GPIO结构表

GPIO

  • 芯片的61个GPIO都可以复用为外部功能,GPIO对应的所有PAD都是 推拉方式,内部上拉;输出高电平为 3.3V,输出低电平为 0V;作为输入时,外部可为 3.3~5V,输入低电平为 0V
  • PAD和PIN 的区别在于PAD是一种物理连接,它可以用来连接外部设备,而PIN是一种电气连接,它可以用来连接处理器内部的电路
  • GPIO 引脚编号: GPIO00~GPIO61,但是没有 GPIO31,共计 61 个引脚,相应函数在 #include "ls1b_gpio.h"
//使用时需要包含头文件
#include "ls1b_gpio.h"

//初始化GPIO,设置方向
//参数1:端口序号(0~61)
//参数2:方向 DIR_IN: 输入, DIR_OUT: 输出
static inline void gpio_enable(int ioNum, int dir)
    
//读取管脚电平
//参数:管脚序号(0~61)    
//返回值:1-高电平 0-低电平    
static inline int gpio_read(int ioNum)    
    
//输出电平
//参数1:管脚序号(0~61)
//参数2:1-高电平 0-低电平    
static inline void gpio_write(int ioNum, int val)    
    
//恢复GPIO管脚状态
//参数:管脚序号(0~61)       
static inline void gpio_disable(int ioNum)    
//GPIO中断使能
//参数:管脚序号
//返回值:成功返回0 失败返回-1
int ls1x_enable_gpio_interrupt(int gpio)
    
//GPIO禁止中断
//参数:管脚序号
//返回值:成功返回0 失败返回-1    
int ls1x_disable_gpio_interrupt(int gpio)    
    
//GPIO中断配置触发模式与中断服务函数
//参数1:管脚序号
//参数2:触发方式(上升沿 下降沿 高电平 低电平)
//参数3:中断向量(中断服务函数)
//参数4:附带参数(传进中断处理函数的参数,不需要时填NULL)
//返回值:成功返回0 失败返回-1    
int ls1x_install_gpio_isr(int gpio, int trigger_mode, void (*isr)(int, void *), void *arg)    
    
//GPIO删除中断
//参数:管脚序号    
int ls1x_remove_gpio_isr(int gpio)    

static inline 修饰的函数是一种内联函数,它可以提高程序的执行效率,减少函数调用的开销,从而提高程序的性能,一般来说,那些经常被调用的函数,如简单的数学运算函数、字符串处理函数等,都可以使用static inline void修饰; 但是只能提高程序的执行效率,而不能改变程序的结构和算法;一般来说,不建议使用static inline修饰复杂的函数,因为这样会增加程序的代码量,从而降低程序的执行效率。此外,也不建议使用static inline修饰那些经常被修改的函数,因为这样会导致程序的可维护性变差。

一般 static inline 修饰的函数放在 .h 文件里,方便其他文件调用

printk 函数主要做两件事情:第一件就是将信息记录到log中第二件事就是调用控制台驱动来将信息输出;功能类似 printf

printk相比printf来说还多了个:日志级别的设置, 用来控制printk打印的这条信息是否在终端上显示的,当日志级别的数值小于控制台级别时,printk要打印的信息才会在控制台打印出来,否则不会显示在控制台

LED

硬件连接

高电平亮

GPIO 用途
34 红LED
35 蓝LED
37 绿LED

程序编写

led.h
#ifndef _LED_H
#define _LED_H
#include "all.h"

//管脚定义
#define LED1 34 //红
#define LED2 37 //绿
#define LED3 35 //蓝


typedef struct
{
    uint8_t Red_Led;
    uint8_t Green_Led;
    uint8_t Blue_Led;
    void (*vLED_init)(void);
    void (*vLED_set)(uint8_t,uint8_t);
}LED_TypeDef;

extern LED_TypeDef Led_Data;


void vLED_init(void);
void vLED_set(uint8_t led_color,uint8_t swch);
#endif
led.c
/*
 * led.c
 *
 * created: 2023-03-23
 *  author:
 *  module: LED区
 */
#include "led.h"

//初始化结构体
LED_TypeDef Led_Data =
{
    .Red_Led = LED1,
    .Green_Led =LED2,
    .Blue_Led = LED3,
    .vLED_init = &vLED_init,
    .vLED_set = &vLED_set
};

//LED初始化
void vLED_init(void)
{
    //设置GPIO为输出状态
    gpio_enable(Led_Data.Blue_Led,DIR_OUT);
    gpio_enable(Led_Data.Green_Led,DIR_OUT);
    gpio_enable(Led_Data.Red_Led,DIR_OUT);
    //关闭LED
    gpio_write(Led_Data.Blue_Led,RESET);
    gpio_write(Led_Data.Green_Led,RESET);
    gpio_write(Led_Data.Red_Led,RESET);
}

//点亮/熄灭灯
void vLED_set(uint8_t led_color,uint8_t swch)
{
    if(SET == swch)
    {
        gpio_write(led_color,SET);
    }
    else
    {
        gpio_write(led_color,RESET);
    }
}

按键

硬件连接

对应管脚序号 用途
56 S1
57 S2
40 S3
41 S4

程序编写

key.h
#ifndef _KEY_H
#define _KEY_H
#include "all.h"

//按键
#define S1 56
#define S2 57
#define S3 40
#define S4 41
//读取电平
#define KEY1 gpio_read(S1)
#define KEY2 gpio_read(S2)
#define KEY3 gpio_read(S3)
#define KEY4 gpio_read(S4)

typedef struct
{
    bool Key1_Down_Flag;
    bool Key2_Down_Flag;
    bool Key3_Down_Flag;
    bool Key4_Down_Flag;
    void (*vKEY_init)(void);
    uint8_t (*ucKEY_sub)(void);
    void (*vKEY_detection)(void);
    void (*vKEY_function)(void);
}KEY_TypeDef;

extern KEY_TypeDef Key_Data;

void vKEY_init(void);
uint8_t ucKEY_sub(void);
void vKEY_detection(void);
void vKEY_function(void);
#endif
key.c
/*
 * key.c
 *
 * created: 2023-03-23
 *  author: 
 *  module: 按键
 */
 
#include "key.h"

static uint8_t Key_Up,Key_Down,Key_Value;


//初始化结构体
KEY_TypeDef Key_Data =
{
    .Key1_Down_Flag = 0,
    .Key2_Down_Flag = 0,
    .Key3_Down_Flag = 0,
    .Key4_Down_Flag = 0,
    .vKEY_init = &vKEY_init,
    .ucKEY_sub = &ucKEY_sub,
    .vKEY_detection = &vKEY_detection,
    .vKEY_function = &vKEY_function
};

//按键初始化
void vKEY_init(void)
{
    gpio_enable(S1,DIR_IN);
    gpio_enable(S2,DIR_IN);
    gpio_enable(S3,DIR_IN);
    gpio_enable(S4,DIR_IN);
}

//按键检测子程序
uint8_t ucKEY_sub(void)
{
    if((!KEY1) || (!KEY2) || (!KEY3) || (!KEY4))
    {
        if(!KEY1)
        {
            return 1;
        }
        if(!KEY2)
        {
            return 2;
        }
        if(!KEY3)
        {
            return 3;
        }
        if(!KEY4)
        {
            return 4;
        }
    }
    return 0;
}

//按键检测
void vKEY_detection(void)
{
    static uint8_t Key_Old;
    
    Key_Value = Key_Data.ucKEY_sub();
    Key_Up = ~Key_Value & (Key_Old^Key_Value);
    Key_Down = Key_Value & (Key_Old^Key_Value);
    Key_Old = Key_Value;
    
    switch(Key_Up)
    {
        case 1:
            {
                Key_Data.Key1_Down_Flag = 1;
                break;
            }
        case 2:
            {
                Key_Data.Key2_Down_Flag = 1;
                break;
            }
        case 3:
            {
                Key_Data.Key3_Down_Flag = 1;
                break;
            }
        case 4:
            {
                Key_Data.Key4_Down_Flag = 1;
                break;
            }
            default:
                break;
    }
}
//按键功能执行
void vKEY_function(void)
{
    if(Key_Data.Key1_Down_Flag)
    {
        Key_Data.Key1_Down_Flag = 0;
        Led_Data.vLED_set(Led_Data.Blue_Led,SET);
    }
    if(Key_Data.Key2_Down_Flag)
    {
        Key_Data.Key2_Down_Flag = 0;
        Led_Data.vLED_set(Led_Data.Green_Led,SET);
    }
    if(Key_Data.Key3_Down_Flag)
    {
        Key_Data.Key3_Down_Flag = 0;
        Led_Data.vLED_set(Led_Data.Red_Led,SET);
    }
    if(Key_Data.Key4_Down_Flag)
    {
        Key_Data.Key4_Down_Flag = 0;
    }
}


外部中断按键

LS1B0200 处理器的中断可以分为 软中断(软中断 0 和软中断 1)、 外设中断 (INT0,INT1, INT2 和 INT3 四个中断控制总线)、 Mips 性能中断Mips 计数中断 四类。INT0,INT1,INT2 和 INT3 四个中断控制总线连接到 CPU,其中 INT0 和 INT1 负责内部 64 个中断源,INT2 和 INT3 负责外部 61 个 GPIO 中断源。 没有中断优先级

  • 中断处理函数名称可以自定义,但是格式必须是 void (*isr)(int, void *)【可在irq.c里参考默认中断服务函数格式】
key.c
#include "bsp.h"    //包含打印函数所在的头文件

unsigned char KEY_EXTI_Flag = 0;    //外部中断标志位

//外部中断服务函数
void KEY_IRQhandler(int IRQn, void *arg)
{
    KEY_EXTI_Flag = 1;
}

void KEY_EXTI_function(void)
{
    static unsigned char change_flag = 0;
    static unsigned int num;

    if(KEY_EXTI_Flag)
    {
        KEY_EXTI_Flag = 0;
        gpio_write(LED1,change_flag);
        num++;
        change_flag ^=1;
        printk("num=%d\n",num);
    }
}

//按键外部中断初始化
//BUTTOM1 上升沿触发,其他的下降沿触发
void KEY_EXTI_init(unsigned char ionum)
{
    ls1x_disable_gpio_interrupt(ionum); //失能引脚外部中断
    ls1x_install_gpio_isr(ionum,INT_TRIG_EDGE_UP,KEY_IRQhandler,NULL);  //中断配置上升沿触发
    ls1x_enable_gpio_interrupt(ionum);  //使能引脚外部中断
}

蜂鸣器

硬件连接

对应管脚序号 用途
46 BEEP

程序编写

buzzer.h
#ifndef _BUZZER_H
#define _BUZZER_H
#include "all.h"

//管脚定义
#define BUZZER 46

typedef struct
{
    void (*vBUZZER_init)(void);
    void (*vBUZZER_control)(uint8_t);
}BUZZER_TypeDef;

extern BUZZER_TypeDef Buzzer_Data;

void vBUZZER_init(void);
void vBUZZER_control(uint8_t swch);
#endif
buzzer.c
/*
 * buzzer.c
 *
 * created: 2023-03-24
 *  author:
 *  module: 蜂鸣器
 */
 
#include "buzzer.h"

//结构体初始化
BUZZER_TypeDef Buzzer_Data =
{
    .vBUZZER_init = &vBUZZER_init,
    .vBUZZER_control = &vBUZZER_control
};


//蜂鸣器初始化
void vBUZZER_init(void)
{
    gpio_enable(BUZZER,DIR_OUT);    //输出
    gpio_write(BUZZER,RESET);   //关闭
}

//开启\关闭蜂鸣器
void vBUZZER_control(uint8_t swch)
{
    if(SET == swch)
    {
        gpio_write(BUZZER,SET);
    }
    else
    {
        gpio_write(BUZZER,RESET);
    }
}

数码管

硬件连接

对应管脚序号 用途
39 SI
48 RCK
49 SCK
45 COM1
44 COM2
43 COM3
42 COM4

程序编写

smg.h
#ifndef _SMG_H
#define _SMG_H
#include "all.h"

//管脚定义
#define SMG_SI 39
#define SMG_RCK 48
#define SMG_SCK 49
#define SMG_COM1 45
#define SMG_COM2 44
#define SMG_COM3 43
#define SMG_COM4 42

#define  HC595_SI(val)  gpio_write(SMG_SI,val) //控制595芯片串行输入(Serial Input)管脚的电平
#define  HC595_RCK(val) gpio_write(SMG_RCK,val) //控制595芯片锁存(Latch)管脚的电平
#define  HC595_SCK(val) gpio_write(SMG_SCK,val) //控制595芯片串行时钟(Serial Clock)管脚的电平

typedef struct
{
    void (*vSMG_init)(void);
    void (*vHC595_send_data)(uint8_t);
    void (*vSMG_choose)(uint8_t);
    void (*vSMG_countdown)(uint16_t);
    
}SMG_TypeDef;

extern SMG_TypeDef Smg_Data;
extern unsigned char Display[16];
extern unsigned char Display_1[];


void vSMG_init(void);
void vHC595_send_data(uint8_t dat);
void vSMG_choose(uint8_t index);
void vSMG_countdown(uint16_t init_value);
#endif
smg.c
/*
 * smg.c
 *
 * created: 2023-03-24
 *  author:
 *  module: 共阴数码管
 */
 
#include "smg.h"

//结构体初始化
SMG_TypeDef Smg_Data =
{
    .vSMG_init = &vSMG_init,
    .vHC595_send_data = &vHC595_send_data,
    .vSMG_choose = &vSMG_choose,
    .vSMG_countdown = &vSMG_countdown
};

//段码--0~F
unsigned char Display[16] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71}; // 不带小数点
unsigned char Display_1[] = {0xbf,0x86,0xdb,0xcf,0xef,0xed,0xfd,0x87,0xff,0xef,0xff,0x00};	// 带小数点

//数码管初始化
void vSMG_init(void)
{
    //595
    gpio_enable(SMG_SI,DIR_OUT);
    gpio_enable(SMG_RCK,DIR_OUT);
    gpio_enable(SMG_SCK,DIR_OUT);
    //COM端
    gpio_enable(SMG_COM1,DIR_OUT);
    gpio_enable(SMG_COM2,DIR_OUT);
    gpio_enable(SMG_COM3,DIR_OUT);
    gpio_enable(SMG_COM4,DIR_OUT);

    Smg_Data.vHC595_send_data(0x00);    //全灭
}

//HC595发送数据
//参数:段码数据
void vHC595_send_data(uint8_t dat)
{
    uint8_t dat_buf = 0, i;
    for(i=0; i<8; i++)
    {
        dat_buf = dat & 0x80;
        if (dat_buf)      //输出1bit数据
        {
            HC595_SI(SET);    //串行输入拉高
        }
        else
        {
            HC595_SI(RESET);    //串行输入拉低
        }
        HC595_SCK(RESET);   //时钟拉低
        delay_us(1);
        HC595_SCK(SET); //时钟拉高
        delay_us(1);
        dat <<= 1;
    }
    HC595_RCK(RESET);   //锁存拉低
    delay_us(3);
    HC595_RCK(SET);   //锁存拉高
}

//数码管选择(从左到右)
//参数:对应的数码管(0~3)
void vSMG_choose(uint8_t index)
{
    switch(index)
    {
        case 0: //1
            gpio_write(SMG_COM1,SET);
            gpio_write(SMG_COM2,RESET);
            gpio_write(SMG_COM3,RESET);
            gpio_write(SMG_COM4,RESET);
            break;
        case 1: //2
            gpio_write(SMG_COM1,RESET);
            gpio_write(SMG_COM2,SET);
            gpio_write(SMG_COM3,RESET);
            gpio_write(SMG_COM4,RESET);
            break;
        case 2: //3
            gpio_write(SMG_COM1,RESET);
            gpio_write(SMG_COM2,RESET);
            gpio_write(SMG_COM3,SET);
            gpio_write(SMG_COM4,RESET);
            break;
        case 3: //4
            gpio_write(SMG_COM1,RESET);
            gpio_write(SMG_COM2,RESET);
            gpio_write(SMG_COM3,RESET);
            gpio_write(SMG_COM4,SET);
            break;
        default:    //全部不选
            gpio_write(SMG_COM1,RESET);
            gpio_write(SMG_COM2,RESET);
            gpio_write(SMG_COM3,RESET);
            gpio_write(SMG_COM4,RESET);
            break;
    }
}

//倒计时
void vSMG_countdown(uint16_t init_value)
{
    //判断参数是否超出最大范围
    if(init_value > 9999)
    {
        init_value = 0;
    }
        
    uint16_t count = init_value;
    uint8_t temp = 0;   //段码数据
    uint32_t ms_count = get_clock_ticks()+1000;;  //获取当前时间+1000
    
    while(count > 0)
    {
        // 每隔1000个倒计时周期刷新显示
        if(get_clock_ticks() > ms_count)
        {
            ms_count=get_clock_ticks()+1000;
            count--;
        }
        //数码管1显示
        temp = Display[count / 1000];
#if 0
        if(0x3F == temp)
        {
            Smg_Data.vHC595_send_data(0x00);
        }
#endif
        Smg_Data.vSMG_choose(0);
        delay_ms(1);
        //数码管2显示
        temp = Display[count / 100 % 10];
        Smg_Data.vHC595_send_data(temp);
        Smg_Data.vSMG_choose(1);
        delay_ms(1);
        //数码管3显示
        temp = Display[count / 10 % 10];
        Smg_Data.vHC595_send_data(temp);
        Smg_Data.vSMG_choose(2);
        delay_ms(1);
        //数码管4显示
        temp = Display[count % 10];
        Smg_Data.vHC595_send_data(temp);
        Smg_Data.vSMG_choose(3);
        delay_ms(1);
    }
//倒计时完成显示0000
        //数码管1显示
        temp = Display[0];
        Smg_Data.vHC595_send_data(temp);
        Smg_Data.vSMG_choose(0);
        delay_ms(1);
        //数码管2显示
        temp = Display[0];
        Smg_Data.vHC595_send_data(temp);
        Smg_Data.vSMG_choose(1);
        delay_ms(1);
        //数码管3显示
        temp = Display[0];
        Smg_Data.vHC595_send_data(temp);
        Smg_Data.vSMG_choose(2);
        delay_ms(1);
        //数码管4显示
        temp = Display[0];
        Smg_Data.vHC595_send_data(temp);
        Smg_Data.vSMG_choose(3);
        delay_ms(1);
}


LCD屏幕

需要在 bsp.h 里打开下面外设:

#define BSP_USE_FB

然后定义一个全局数组(必须跟下面一模一样)

char LCD_display_mode[] = LCD_800x480;  //定义

包含头文件 #include "ls1x_fb.h"

引入 lkdGui_source ,添加在src那(在resource里),然后初始化,之前的LCD初始化不要屏蔽或者删留着否则看不到效果

defaultFontInit();/* 字库初始化 */
GuiClearScreen(CBLACK); /* 更新屏幕-清屏---黑色 */
fb_cons_clear();    //清屏然后再自带的清屏顺序不要乱
  • Gui库函数

颜色只有黑白

函数 参数 描述
void GuiRowText(lkdCoord x, lkdCoord y,uint16_t wide, FontFlag flag,uint8_t *str) x,y 起始坐标
wide 单行文本宽度
flag 字体对齐标志
str 文本字符串
写单行文本
注意如果显示后面有乱码可以在字符串后面空几个格

坐标是:

  • 自带

显示图片

把生成的数组复制,最好新建一个 .h 文件保存,然后需要去 ls1x_fb_utils.h 里找到 LS1x_draw_rgb565_pixel把宏定义打开

需要保证 图片的像素 和函数的像素一致

//显示图片
//参数1:x
//参数2:y
//参数3:像素
//参数4:像素
//参数5:显示的数组
void display_pic(unsigned int xpos,unsigned int ypos,unsigned int x1,unsigned int y1,unsigned char *ptrs)
{
    {
        int x, y;
        unsigned char *ptr = ptrs;

        for (y=0; y<y1; y++)
        {
            for (x=0; x<x1; x++)
            {
                unsigned int color;

                color = (*ptr << 8) | *(ptr+1);

                LS1x_draw_rgb565_pixel(x+xpos, y+ypos, color);

                ptr += 2;
            }
        }

        flush_dcache();
    }

}

如果使用自带库屏幕闪动,可以去 ls1x_fb.c 里大概 288行找到:

divider_int  = LS1B_PLL_FREQUENCY(CPU_XTAL_FREQUENCY) / 1000 / 4;

4分频 改成 6分频 即可减轻这种现象

坐标是横屏的

因为LCD显示占时间所以小创接收可能不能接收到或者偶尔可以接收到回传,解决方案是用到显示再打开,不用就关闭

小创

硬件连接

需要头文件 #include "ns16550.h"

更新词条需要把下面的开关拨到 更新词条 那档(2个为一组),更新完要识别则要拨到 语音识别 那档

小创使用的是 串口4 ,需要在 bsp.h 打开宏定义 #define BSP_USE_UART4

注意写的格式,有可能读不出来就是格式有问题:

以下是内部函数对串口的初始化设置(参数可自行修改,在ns16550.c大概1170行):

/* UART 4 */
#ifdef BSP_USE_UART4
static NS16550_t ls1c_UART4 =
{
    .BusClock  = 0,   //总线频率,初始化时填充              
    .BaudRate  = 115200,	// 默认速率
    .CtrlPort  = LS1C_UART4_BASE,	// 串口寄存器基址
    .DataPort  = LS1C_UART4_BASE,	
    .bFlowCtrl = false,             // 启用硬件支持
    .ModemCtrl = 0,
    .bIntrrupt = true,              // 使用中断方式
    .IntrNum   = LS1C_UART4_IRQ,	// 系统中断号
    .IntrCtrl  = LS1C_INTC1_BASE,	// 中断寄存器
    .IntrMask  = INTC1_UART4_BIT,	// 中断屏蔽位
    .dev_name  = "uart4",	// 设备名称
};
void *devUART4 = &ls1c_UART4;
#endif

注意

~~中断里判断接收的数组时需要延时或者打印控制台(不推荐在中断延时)然后才能判断(反正就是不要这么快执行到下面代码就行),不然判断不了(很sb的这龙芯,debug半天才找到是这个原因)~~2023.3.25(此方案虽然可以接收回传并且执行对应功能但是会造成其他功能全部卡死原因是中断使用延时导致定时器中断卡死)

  • 方案2–不使用中断
xiaochuang.h
#ifndef _XIAOCHUANG_H
#define _XIAOCHUANG_H
#include "all.h"

//定义管脚
#define USART4_GPIO 58
#define UART4_RX_MAX_LEN 100 //接收最大长度

typedef struct
{
    bool UART4_Rx_Over_Flag;    //接收有效数据完成标志位
    uint8_t Rx_Data[8]; //有效数据缓存
    char UART4_RX_BUFF[UART4_RX_MAX_LEN];   //接收数据存储数组
    uint8_t XiaoChuang_return_state;    //小创回传状态(用于分辨不同回传0~255)
    void (*vUART4_init)(void);
    void (*vXIAOCHUANG_order_parse)(void);
    void (*vXIAOCHUANG_state_function)(void);
    void (*vXIAOCHUANG_play_specify_content)(int);
    void (*vXIAOCHUANG_send_rouse)(void);
    void (*vUART4_rx_data_function)(void);
} XIAOCHUANG_TypeDef;

extern XIAOCHUANG_TypeDef XiaoChuangData;
extern uint8_t ucXiaoChuang_rouse_arr[5];

void vUART4_init(void);
void vUART4_rx_data_function(void);
void vXIAOCHUANG_order_parse(void);
void vXIAOCHUANG_state_function(void);
void vXIAOCHUANG_play_specify_content(int id);
void vXIAOCHUANG_send_rouse(void);
#endif

xiaochuang.c
/*
 * xiaochuang.c
 *
 * created: 2023-03-24
 *  author: Yang
 *  module: 小创语音
 */
#include "xiaochuang.h"

uint8_t ucXiaoChuang_rouse_arr[5] = {0xFA, 0xFA, 0xFA, 0xFA, 0xA1};	//小创唤醒词

//初始化结构体数据
XIAOCHUANG_TypeDef XiaoChuangData =
{
    .UART4_Rx_Over_Flag = 0,
    .Rx_Data = {0},
    .UART4_RX_BUFF = {0},
    .XiaoChuang_return_state = 0,
    .vUART4_init = &vUART4_init,
    .vXIAOCHUANG_order_parse = &vXIAOCHUANG_order_parse,
    .vXIAOCHUANG_state_function = &vXIAOCHUANG_state_function,
    .vXIAOCHUANG_play_specify_content = &vXIAOCHUANG_play_specify_content,
    .vXIAOCHUANG_send_rouse = &vXIAOCHUANG_send_rouse,
    .vUART4_rx_data_function = &vUART4_rx_data_function
};

//串口4初始化
void vUART4_init(void)
{
    unsigned int BaudRate = 115200;

    ls1x_uart_init(devUART4,(void *)BaudRate); //初始化串口
    ls1x_uart_open(devUART4,NULL); //打开串口
}

/*
功能:串口4接收函数
小创播报后回传命令格式:55 02 01 00(帧头 数据类型 状态标志 数据位 ----帧头固定的后面3位可自定义)
uart6_flag执行顺序:0x00-->0x01-->0x02-->0x03-->0x00...以此循环
*/
void vUART4_rx_data_function(void)
{
    static uint8_t timing_flag = 0;  //时序
    static uint8_t rx_count = 0;

    rx_count = ls1x_uart_read(devUART4,XiaoChuangData.UART4_RX_BUFF,UART4_RX_MAX_LEN,NULL);  //接收数据,返回值是读取的字节数

    if(rx_count > 0)
    {
        if (timing_flag == 0x00)
        {
            if (XiaoChuangData.UART4_RX_BUFF[0] == 0x55)				// 自定义数据帧头
            {
                timing_flag = 0x01;
                XiaoChuangData.Rx_Data[0] = XiaoChuangData.UART4_RX_BUFF[0];	// 帧头
                XiaoChuangData.Rx_Data[1] = 0x00;
                XiaoChuangData.Rx_Data[2] = 0x00;
                XiaoChuangData.Rx_Data[3] = 0x00;
            }
        }
        else if (timing_flag == 0x01)
        {
            timing_flag = 0x02;
            XiaoChuangData.Rx_Data[1] = XiaoChuangData.UART4_RX_BUFF[0];		// 数据类型
        }
        else if(timing_flag == 0x02)
        {
            timing_flag = 0x03;
            XiaoChuangData.Rx_Data[2] = XiaoChuangData.UART4_RX_BUFF[0];		// 状态标志
        }
        else if(timing_flag == 0x03)
        {
            timing_flag = 0x00;
            XiaoChuangData.Rx_Data[3] = XiaoChuangData.UART4_RX_BUFF[0];		// 数据位
            XiaoChuangData.UART4_Rx_Over_Flag = 1; //接收完成
            memset(XiaoChuangData.UART4_RX_BUFF,0,sizeof(XiaoChuangData.UART4_RX_BUFF));  //清0
        }
        else    //不会执行到这
        {
            timing_flag = 0x00;
            XiaoChuangData.UART4_Rx_Over_Flag = 0; 
            XiaoChuangData.Rx_Data[0] = 0x00;
            memset(XiaoChuangData.UART4_RX_BUFF,0,sizeof(XiaoChuangData.UART4_RX_BUFF));  //清0
        }
    }
}

/*
当前数据:
任务一启动::好的,任务一启动:55020100
任务二启动::好的,任务二启动:55020200
任务三启动::好的,任务三启动:55020300
任务四启动::好的,任务四启动:55020400
任务五启动::好的,任务五启动:55020500
任务六启动::好的,任务六启动:55020600
任务七启动::好的,任务七启动:55020700
任务八启动::好的,任务八启动:55020800
任务九启动::好的,任务九启动:55020900
任务十启动::好的,任务十启动:55020A00
砺练能力::识别成功,砺练能力:55020B00
交流技艺::识别成功,交流技艺:55020C00
超越自我::识别成功,超越自我:55020D00
勇攀高峰::识别成功,勇攀高峰:55020E00
*/

/*
功能:小创回传解析
主要是判断asrWordlist.txt里的命令回传
通过赋不同值标志位XiaoChuang_return_state来进行分辨不同回传命令
回传格式:5502XX00
*/
void vXIAOCHUANG_order_parse(void)
{
    if (XiaoChuangData.UART4_Rx_Over_Flag)
    {
        XiaoChuangData.UART4_Rx_Over_Flag = 0;

        if(0x02 == XiaoChuangData.Rx_Data[1]) //数据类型
        {
            switch(XiaoChuangData.Rx_Data[2])    //状态标志
            {
                case 0x01:
                    {
                        XiaoChuangData.XiaoChuang_return_state = 1;
                        break;
                    }
                case 0x02:
                    {
                        XiaoChuangData.XiaoChuang_return_state = 2;
                        break;
                    }
                case 0x03:
                    {
                        XiaoChuangData.XiaoChuang_return_state = 3;
                        break;
                    }
                case 0x04:
                    {
                        XiaoChuangData.XiaoChuang_return_state = 4;
                        break;
                    }
                case 0x05:
                    {
                        XiaoChuangData.XiaoChuang_return_state = 5;
                        break;
                    }
                case 0x06:
                    {
                        XiaoChuangData.XiaoChuang_return_state = 6;
                        break;
                    }
                case 0x07:
                    {
                        XiaoChuangData.XiaoChuang_return_state = 7;
                        break;
                    }
                case 0x08:
                    {
                        XiaoChuangData.XiaoChuang_return_state = 8;
                        break;
                    }
                case 0x09:
                    {
                        XiaoChuangData.XiaoChuang_return_state = 9;
                        break;
                    }
                case 0x0A:
                    {
                        XiaoChuangData.XiaoChuang_return_state = 10;
                        break;
                    }
                case 0x0B:
                    {
                        XiaoChuangData.XiaoChuang_return_state = 11;
                        break;
                    }
                case 0x0C:
                    {
                        XiaoChuangData.XiaoChuang_return_state = 12;
                        break;
                    }
                case 0x0D:
                    {
                        XiaoChuangData.XiaoChuang_return_state = 13;
                        break;
                    }
                case 0x0E:
                    {
                        XiaoChuangData.XiaoChuang_return_state = 14;
                        break;
                    }
                default:
                    break;
            }
        }
        else if(0x03 == XiaoChuangData.Rx_Data[2]) //数据类型【主要判断serialTTS.txt里的】
        {
            ;
        }
    }
    memset(XiaoChuangData.Rx_Data,0,sizeof(XiaoChuangData.Rx_Data));  //清0
}

/*
功能:根据小创回传命令状态XiaoChuang_return_state执行对应功能
*/
void vXIAOCHUANG_state_function(void)
{
    switch(XiaoChuangData.XiaoChuang_return_state)
    {
        case 1:
            {
                auto_task1();   //任务1
                break;
            }
        case 2:
            {
                break;
            }
        case 3:
            {
                auto_task3();   //任务3
                break;
            }
        case 4:
            {
                auto_task4();
                break;
            }
        case 5:
            {
                break;
            }
        case 6:
            {
                break;
            }
        case 7:
            {
                break;
            }
        case 8:
            {
                break;
            }
        case 9:
            {
                break;
            }
        case 10:
            {
                break;
            }
        case 11:
            {
                memset(Lcd_xiaochuang_identify_display_str,0,sizeof(Lcd_xiaochuang_identify_display_str));
                snprintf(Lcd_xiaochuang_identify_display_str,sizeof(Lcd_xiaochuang_identify_display_str),"砺练能力");
                //Lcd_Data.Lcd_Display4_Flag = 1; //LCD刷新
                GuiRowText(0,0+300,400, FONT_LEFT,(uint8_t*)Lcd_xiaochuang_identify_display_str);
                break;
            }
        case 12:
            {
                memset(Lcd_xiaochuang_identify_display_str,0,sizeof(Lcd_xiaochuang_identify_display_str));
                snprintf(Lcd_xiaochuang_identify_display_str,sizeof(Lcd_xiaochuang_identify_display_str),"交流技艺");
                //Lcd_Data.Lcd_Display4_Flag = 1; //LCD刷新
                GuiRowText(0,0+300,400, FONT_LEFT,(uint8_t*)Lcd_xiaochuang_identify_display_str);
                break;
            }
        case 13:
            {
                Led_Data.vLED_control(Led_Data.Red_Led,SET);
                memset(Lcd_xiaochuang_identify_display_str,0,sizeof(Lcd_xiaochuang_identify_display_str));
                snprintf(Lcd_xiaochuang_identify_display_str,sizeof(Lcd_xiaochuang_identify_display_str),"超越自我");
                //Lcd_Data.Lcd_Display4_Flag = 1; //LCD刷新
                GuiRowText(0,0+300,400, FONT_LEFT,(uint8_t*)Lcd_xiaochuang_identify_display_str);
                break;
            }
        case 14:
            {
                memset(Lcd_xiaochuang_identify_display_str,0,sizeof(Lcd_xiaochuang_identify_display_str));
                snprintf(Lcd_xiaochuang_identify_display_str,sizeof(Lcd_xiaochuang_identify_display_str),"勇攀高峰");
                //Lcd_Data.Lcd_Display4_Flag = 1; //LCD刷新
                GuiRowText(0,0+300,400, FONT_LEFT,(uint8_t*)Lcd_xiaochuang_identify_display_str);
                break;
            }
        default:
            break;
    }
    XiaoChuangData.XiaoChuang_return_state = 0;    //清除状态
}

/*
当前数据
01::庆祝中国共产党成立100周年:55030100
02::喜迎二十大、永远跟党走、奋进新征程:55030200
*/
/*
播报指定词条
参数:词条编号
*/
void vXIAOCHUANG_play_specify_content(int id)
{
    int ID_number = id;
    ls1x_uart_write(devUART4,&ID_number,1,NULL);    //发送一个字节
}

/*
功能:发送小创唤醒
*/
void vXIAOCHUANG_send_rouse(void)
{
    ls1x_uart_write(devUART4,&ucXiaoChuang_rouse_arr,sizeof(ucXiaoChuang_rouse_arr),NULL);    //发送唤醒
}


然后在定时器里进行判断

if(50 == Time_50ms) //50ms小创回传检测
{
    Time_50ms = 0;
    XiaoChuang_Data.vXIAOCHUANG_order_parse();
}

在主循环进行接收

for(;;)
{
    XiaoChuangData.vUART4_rx_data_function();
}

定时器

需要包含头文件 #include "ls1x_rtc.h",然后在 bsp.h 打开宏定义 #define BSP_USE_RTC

程序编写

tim.h
#ifndef _TIM_H
#define _TIM_H
#include "all.h"

typedef struct
{
    void (*vTIM_rtc0_init)(void);
}TIM_TypeDef;

extern TIM_TypeDef Tim_Data;

void vTIM_rtc0_init(void);
void rtctimer_callback(int device, unsigned match, int *stop);
#endif
tim.c
/*
 * tim.c
 *
 * created: 2023-03-24
 *  author:
 *  module: 定时器
 */

#include "tim.h"

extern uint16_t number;

//结构体初始化
TIM_TypeDef Tim_Data =
{
    .vTIM_rtc0_init = &vTIM_rtc0_init
};

//RTC开启定时器模式(定时1ms)
void vTIM_rtc0_init(void)
{
    ls1x_rtc_init(NULL,NULL);   //初始化

    //开启RTC定时中断功能
    rtc_cfg_t rtc;
    rtc.cb = rtctimer_callback;	//定时器中断回调函数
    rtc.isr = NULL;	//中断服务函数
    rtc.interval_ms = 1;	//定时器时间间隔(ms)
    rtc.trig_datetime = NULL;	//用于toymatch的日期
    //开启定时器
    ls1x_rtc_timer_start(DEVICE_RTCMATCH0,&rtc);
}

//定时器回调函数
void rtctimer_callback(int device, unsigned match, int *stop)
{
    static uint16_t Time_50ms = 0;
    static uint8_t Time_20ms = 0;

    if(DEVICE_RTCMATCH0 == device)
    {
        Time_20ms++;
        Time_50ms++;
        if(20 == Time_20ms) //20ms按键检测
        {
            Time_20ms = 0;
            Key_Data.vKEY_detection();
        }
        if(50 == Time_50ms)	//50msLCD刷新
        {
            Time_50ms = 0;
            Lcd_Data.Lcd_Display_Flag = 1;
            number++;
        }
    }
}

温度传感器

硬件连接

分析:此电路用到 LM35DZ 传感器,还有 LMV611 差分放大器

LM35DZ是一种温度传感器,其输出信号为温度值直接输出,但输出电压较小,一般在 0.01V/K(每增加1摄氏度的温度变化)左右。为了能够将其输出信号放大并转换为标准的电压信号,需要联合使用运放等电路进行处理。

LMV611是一款高精度、低功耗、铁电电容耦合的差分放大器。其输入端可以直接连接LM35DZ的输出端,作为放大器的非反馈端,另外一个反馈端接地。这样,当LM35DZ输出的温度变化时,LMV611内部就会产生相应的放大电压信号,并通过其输出端进行输出。

在连接LMV611的过程中,需要将 LM35DZ的输出端连接到LMV611的非反馈端(+端),而不是反馈端(-端),这是因为 LMV611是差分放大器,它将输入信号在反相输入端和非反相输入端之间进行差分放大,同时去除掉共模电压干扰。因此,为了更好地抵消环境噪声对输出的影响,并保证系统的精度,需要将LM35DZ的输出信号接到差分放大器的非反馈端。

如果输出端接到反馈端 无论LM35DZ的温度变化如何,输出的电压信号始终为0

//需要在 bsp.h 打开宏定义 #define ADS1015_DRV 
//ADS1015 挂接在 I2C0 上,I2C 地址和通信速率定义如下
#define ADS1015_ADDRESS         0x48	    /* Ground 1001 000 */

#define ADS1015_BAUDRATE        1000000

//包含头文件
#include "ls1x_i2c_bus.h"
#include "i2c/ads1015.h"

读取转换电压思想

adc的值是从0到4095的,adc接到GND,读出来必然是0,接到VCC必然是4095;需要事先定义好量程和分辨率。 量程其实就是基准电压,以5V电压为基准,那么测量的范围就是0V~5V; 分辨率就是测量的精度,假如12位, 12位二进制最大值为4095 ;这时候就可以知道 0V=0,5V=4095 了,把5V分为4095份就可以了;再配合相应的滤波算法,自然可以很好的写出转化的电压

电压值=参考电压*量化精度*ADC采集到的数值/量化等级

程序编写

lm35.h
#ifndef _LM35_H
#define _LM35_H
#include "all.h"

typedef struct
{
    float Lm35_temp;    //读取的温度值
    void (*vLM35_init)(void);
    float (*fLM35_get_temperature)(void);
    
}LM35_TypeDef;


extern LM35_TypeDef Lm35_Data;


void vLM35_init(void);
float fLM35_get_temperature(void);
#endif
lm35.c
/*
 * lm35.c
 *
 * created: 2023-03-25
 *  author:
 *  module: 温度传感器
 */

#include "lm35.h"

LM35_TypeDef Lm35_Data =
{
    .Lm35_temp = 0.0,
    .vLM35_init = &vLM35_init,
    .fLM35_get_temperature = &fLM35_get_temperature
};

//初始化
void vLM35_init(void)
{
    ls1x_i2c_initialize(busI2C0);   //初始化I2C0
    ls1x_ads1015_ioctl(busI2C0,IOCTL_ADS1015_DISP_CONFIG_REG,NULL); //初始化ADC
}

/*
功能:获取温度
返回值:温度
*/
float fLM35_get_temperature(void)
{
    uint16_t adc2 = 0;
    float temp = 0;   //温度临时存储

    adc2 = get_ads1015_adc(busI2C0, ADS1015_REG_CONFIG_MUX_SINGLE_2); //获取ADC
    temp = 4.096*2*adc2/4096;//采集电压的转换公式
    temp = temp*100;
    Lm35_Data.Lm35_temp = temp;
    
    return temp;
}

然后把这个获取温度的函数放在定时器500ms获取一次即可

注意

如果直接把获取函数放定时器肯定会卡死的,因为 get_ads1015_adc 函数里面的实现有使用 delay_ms导致定时器卡死,所以方法是去掉延时即可

加热电阻

硬件连接

分析:这个Q1是 NMOS,S极接地,N极接负载,G极是开关控制,高电平导通(因为要G极电压高于S极才导通)

程序编写

resistor.h
#ifndef _RESISTOR_H
#define _RESISTOR_H
#include "all.h"

//定义管脚
#define RESISTOR_PIN 38

typedef struct
{
    void (*vRESISTOR_init)(void);
    void (*vRESISTOR_control)(uint8_t);
}RESISTOR_TypeDef;

extern RESISTOR_TypeDef Resistor_Data;

void vRESISTOR_init(void);
void vRESISTOR_control(uint8_t swch);
#endif
resistor.c
/*
 * resistor.c
 *
 * created: 2023-03-26
 *  author:
 *  module: 加热电阻
 */
 
#include "resistor.h"

//结构体初始化
RESISTOR_TypeDef Resistor_Data =
{
    .vRESISTOR_init = &vRESISTOR_init,
    .vRESISTOR_control = &vRESISTOR_control
};

/*
功能:初始化
*/
void vRESISTOR_init(void)
{
    gpio_enable(RESISTOR_PIN,DIR_OUT);  //输出模式
    Resistor_Data.vRESISTOR_control(RESET); //默认关闭状态
}

/*
功能:控制加热电阻开关
参数:SET--开 RESET--关
*/
void vRESISTOR_control(uint8_t swch)
{
    if(SET == swch)
    {
        gpio_write(RESISTOR_PIN,SET); //开
    }
    else
    {
        gpio_write(RESISTOR_PIN,RESET); //关
    }
}

风扇

硬件连接

需要包含头文件 #include "i2c/gp7101.h"

程序编写

fan.h
#ifndef _FAN_H
#define _FAN_H
#include "all.h"

//定义管脚
#define FAN_PIN 36

typedef struct
{
    void (*vFAN_init)(void);
    void (*vFAN_control)(uint8_t);
    void (*vFAN_set_speed)(uint8_t);
}FAN_TypeDef;

extern FAN_TypeDef Fan_Data;

void vFAN_init(void);
void vFAN_control(uint8_t swch);
void vFAN_set_speed(uint8_t speed);
#endif


fan.c
/*
 * fan.c
 *
 * created: 2023-03-26
 *  author:
 *  module: 风扇 硬件连接:插板子左侧FAN
 */
#include "fan.h"

//初始化结构体
FAN_TypeDef Fan_Data =
{
    .vFAN_init = &vFAN_init,
    .vFAN_control = &vFAN_control,
    .vFAN_set_speed = &vFAN_set_speed
};


/*
功能:初始化
*/
void vFAN_init(void)
{
    gpio_enable(FAN_PIN,DIR_OUT);//输出
    Fan_Data.vFAN_control(RESET);//默认关闭
    Fan_Data.vFAN_set_speed(0); //速度0
}

/*
功能:控制风扇
参数:SET--开 RESET--关
*/
void vFAN_control(uint8_t swch)
{
    if(SET == swch)
    {
        gpio_write(FAN_PIN,SET);
    }
    else
    {
        gpio_write(FAN_PIN,RESET);
    }
}

/*
功能:风扇转速控制
参数:速度(0~100)
*/
void vFAN_set_speed(uint8_t speed)
{
    set_lcd_brightness(speed);
}

光度传感器

硬件连接

IIC地址线,接GND时器件地址为 0100011 ,接VCC时器件地址为 1011100,上面原理图是接GND的

ADDR='L' 时,该芯片的I2C地址为 0x23,当 ADDR='H' 时,该芯片的I2C地址为 0x5C 。根据不同的地址选择,可以在同一个电路上添加多个BH1750,以便监测多个位置的光强度。

常用命令(手册):

  • 时序

但是使用模拟的方法初始化I2C的IO后温度传感器就失效了冲突,所以还是不要用模拟的

bh1750.h
#ifndef _BH1750_H
#define _BH1750_H
#include "all.h"


#define BH1750_ADDR 0x23    //默认地址 0100 011
#define BH1750_ON   0x01    //等待测量命令
#define BH1750_CON  0x10    //以1 lx 分辨率开始测量测量时间通常为 120 毫秒
#define BH1750_ONE  0x20    //以 1 lx 分辨率开始测量测量时间通常为 120 毫秒测量后自动设置为掉电模式
#define BH1750_RSET 0x07    //复位数据寄存器值复位命令

typedef struct
{
    float Bh1750_value; //最终光度值(单位lx)
    uint8_t BH1750_Read_Buff[2];    //存储读取的待合成的光照值2字节
    void (*BH1750_init)(void);
    void (*vBH1750_Cmd_Write)(uint8_t);
    void (*vBH1750_start)(void);
    void (*vBH1750_read)(void);
    float (*vBH1750_convert_data)(void);
    void (*vBH1750_whole_get_data)(void);
}BH1750_TypeDef;

extern BH1750_TypeDef Bh1750_Data;

void BH1750_init(void);
void vBH1750_Cmd_Write(uint8_t cmd);
void vBH1750_start(void);
void vBH1750_read(void);
float vBH1750_convert_data(void);
void vBH1750_whole_get_data(void);
#endif

bh1750.c
/*
 * bh1750.c
 *
 * created: 2023-03-26
 *  author:
 *  module: 光度传感器 ---I2C通信
 */

#include "bh1750.h"

//初始化结构体
BH1750_TypeDef Bh1750_Data =
{
    .Bh1750_value = 0.0,
    .BH1750_Read_Buff = {0},
    .BH1750_init = &BH1750_init,
    .vBH1750_Cmd_Write = &vBH1750_Cmd_Write,
    .vBH1750_start = &vBH1750_start,
    .vBH1750_read = &vBH1750_read,
    .vBH1750_convert_data = &vBH1750_convert_data,
    .vBH1750_whole_get_data = &vBH1750_whole_get_data
};

/*
功能:初始化
*/
void BH1750_init(void)
{
    Bh1750_Data.vBH1750_start();
}

/*
功能:发送设备地址
*/
void vBH1750_Cmd_Write(uint8_t cmd)
{
    uint8_t data[2]= {0};
    data[0]=cmd;

    ls1x_i2c_send_start(busI2C0,BH1750_ADDR);//起始信号
    ls1x_i2c_send_addr(busI2C0,BH1750_ADDR,0);	//发送设备地址+写信号
    ls1x_i2c_write_bytes(busI2C0, data, 1);	//内部寄存器地址
    ls1x_i2c_send_stop(busI2C0,BH1750_ADDR);	//发送停止信号
    delay_ms(5);
}

/*
功能:开启一次高分辨率模式
*/
void vBH1750_start(void)
{
    Bh1750_Data.vBH1750_Cmd_Write(BH1750_ON);   //打开
    Bh1750_Data.vBH1750_Cmd_Write(BH1750_RSET); //清空
    Bh1750_Data.vBH1750_Cmd_Write(BH1750_CON); //1lx分辨率,至少120ms(如果是ONE则会进入掉电模式每次读取都要先执行vBH1750_start)
}

/*
功能:读光照数据
*/
void vBH1750_read(void)
{
    ls1x_i2c_send_start(busI2C0, BH1750_ADDR);//开始信号
    ls1x_i2c_send_addr(busI2C0,BH1750_ADDR,1);//发送设备地址+读信号
    ls1x_i2c_read_bytes(busI2C0,Bh1750_Data.BH1750_Read_Buff, 2);//读取两个字节数据
    ls1x_i2c_send_stop(busI2C0,BH1750_ADDR);//发送停止信号
}

/*
功能:合成光照数据
返回值:最终的光照数据
*/
float vBH1750_convert_data(void)
{
    uint16_t temp = 0;  //16位

    temp = Bh1750_Data.BH1750_Read_Buff[0];
    temp = (temp<<8)+Bh1750_Data.BH1750_Read_Buff[1];		//合成数据,即光照数据
    Bh1750_Data.Bh1750_value = (float)temp/1.2;

    return Bh1750_Data.Bh1750_value;
}

/*
功能:完成一次完整的采集
*/
void vBH1750_whole_get_data(void)
{
    Bh1750_Data.vBH1750_read(); //连续读取
    Bh1750_Data.vBH1750_convert_data(); //合成
}

电子罗盘

硬件连接

DRDY 是HMC5883L模块上的一个引脚,它表示"Data Ready",即数据是否已经准备好。在每次数据转换完成后,HMC5883L会将DRDY引脚拉低,表示数据已经准备好并可以进行读取。因此,通过监视DRDY引脚即可得知何时可以读取数据,以提高读取数据的效率。另外, 如果您正常使用HMC5883L读取数据,您可以忽略DRDY引脚的存在

  • 查看芯片手册

用到的主要是 配置寄存器A配置寄存器B模式寄存器

CRA7 :为将来的功能保留,在配置时设置为 0

CRA6 - CRA5 : 采样平均数,默认1次;

CRA4 - CRA2 : 数据输出速率位 这些位设置数据写入所有三个数据输出寄存器的速率

CRA1 - CRA0 : 设置XYZ轴正偏压或负偏压,默认00即可

一般情况下只需改 采样平均数速率 ,其他位固定–0XXX XX00

CRB7~CRB5:增益配置位 这些位配置设备的增益 增益配置对所有通道都是通用的,即多大的值为1高斯,默认1090/高斯

CRB4~CRB0:置0才能使用

MR7~MR2:设置此引脚以启用高速 i 2 c 3400 khz,置0

MR1~MR0:模式选择位

模式 描述
00 连续测量模式。在连续测量模式下,装置不断进行测量,并将数据更新至数据寄存器。RDY升高,此时新数据放置在所有三个寄存器。在上电或写入模式或配置寄存器后,第一次测量可以在三个数据输出寄存器经过一个2/fpo后设置,随后的测量可用一个频率foo进行,fpo为数据输出的频率。
01 单一测量模式(默认)。当选择单测量模式时,装置进行单一测量,RDY设为高位并回到闲置模式。模式寄存器返回闲置模式位值。测量的数据留在输出寄存器中并且RDY仍然在高位,直到数据输出寄存器读取或完成另一次测量。
10 闲置模式。装置被放置在闲置模式。
11 闲置模式。装置被放置在闲置模式。

数据寄存器:每个轴的数据共16位,存储在两个寄存器(高八位MSB,第八位LSB)。只有把所有寄存器都读取完后,才会清除RDY位和DRDY信号

SR7~SR2:这些位是保留的,置0

SR1:数据输出寄存器锁存。当六个数据输出寄存器上的一些但不是全部数据被读取时,该位置位。当此位置位时,六个数据输出寄存器被锁定且任何新的数据将不会被更新至这些寄存器中,除非符合以下三个条件之一:

  1. 所有6个寄存器已被读取或模式改变
  2. 模式发生变化
  3. 测量配置发生变化
  4. 电源复位

SR0:准备就绪位。当数据都写入了6个数据寄存器,该位置位。在一个或几个数据写入输出寄存器以后且在装置开始向数据输出寄存器写入数据时该位被清除。当RDY位已清除,RDY应保持清除状态至少250us。DRDY 引脚可被用来作为一种替代的状态寄存器的监测装置为测量数据。

识别寄存器:用来识别装置。

因为HMC5883L是一款 二维磁力仪,只能测量被垂直于芯片表面的平面磁场,因此只有 X轴和Y轴 的数据具有实际意义,而 Z轴 的数据并没有什么用处

I2C器件地址出厂固定—(手册规格表有写)

根据角度找出

hmc5883l.h
#ifndef _HMC5883L_H
#define _HMC5883L_H
#include "all.h"
#include "my_iic.h"
//定义引脚
#define HMC5883L_SDA 29 //数据线管脚
#define HMC5883L_SCL 28 //时钟线管脚
//写操作地址
#define HMC5883L_WRITE_ADDR 0x3C
//读操作地址
#define HMC5883L_READ_ADDR 0x3D
//寄存器地址
#define HMC5883L_REGISTER_A 0x00    //A
#define HMC5883L_REGISTER_B 0x01    //B
#define HMC5883L_REGISTER_MODE 0x02    //模式

typedef struct
{
    bool Hmc5883l_Over_Flag;    //运行标志位
    float Angle_value;  //角度值
    bool Hmc588l_WN_Flag;    //西北
    bool Hmc588l_WS_Flag;    //西南
    bool Hmc588l_ES_Flag;    //东南
    bool Hmc588l_EN_Flag;    //东北
    bool Hmc588l_W_Flag;    //西
    bool Hmc588l_E_Flag;    //东
    bool Hmc588l_S_Flag;    //南
    bool Hmc588l_N_Flag;    //北
    void (*vHMC5883L_init)(void);
    uint8_t (*ucHMC5883L_read)(MY_IIC_DATA_TypeDef *,uint8_t);
    uint8_t (*ucHMC5883L_write)(MY_IIC_DATA_TypeDef *,uint8_t,uint8_t);
    float (*vHMC5883L_get_angle)(void);
    void (*vHMC5883L_calculate_direction)(float);
    void (*vHMC5883L_direction_function)(void);
}HMC5883L_TypeDef;

extern HMC5883L_TypeDef Hmc5883l_Data;
extern MY_IIC_DATA_TypeDef HMC_IIC_DATA;

void vHMC5883L_init(void);
float vHMC5883L_get_angle(void);
uint8_t ucHMC5883L_read(MY_IIC_DATA_TypeDef *hmc,uint8_t register_addr);
uint8_t ucHMC5883L_write(MY_IIC_DATA_TypeDef *hmc,uint8_t register_addr,uint8_t data);
void vHMC5883L_calculate_direction(float angle_num);
void vHMC5883L_direction_function(void);
#endif
hmc5883l.c
/*
 * hmc5883l.c
 *
 * created: 2023-03-26
 *  author:
 *  module: 电子罗盘(磁力计) 硬件连接:P1插板子左侧P3
 */
#include "hmc5883l.h"

int16_t HMC_X,HMC_Y,HMC_Z;

//初始化管脚
MY_IIC_DATA_TypeDef HMC_IIC_DATA =
{
    .SCL_Pin = HMC5883L_SCL,
    .SDA_Pin = HMC5883L_SDA
};

//初始化结构体
HMC5883L_TypeDef Hmc5883l_Data =
{
    .Hmc5883l_Over_Flag = 0,
    .Angle_value = 0.0,
    .Hmc588l_WN_Flag = 0,
    .Hmc588l_WS_Flag = 0,
    .Hmc588l_ES_Flag = 0,
    .Hmc588l_EN_Flag = 0,
    .Hmc588l_W_Flag = 0,
    .Hmc588l_E_Flag = 0,
    .Hmc588l_S_Flag = 0,
    .Hmc588l_N_Flag = 0,
    .vHMC5883L_init = &vHMC5883L_init,
    .ucHMC5883L_read = &ucHMC5883L_read,
    .ucHMC5883L_write = &ucHMC5883L_write,
    .vHMC5883L_get_angle = &vHMC5883L_get_angle,
    .vHMC5883L_calculate_direction = &vHMC5883L_calculate_direction,
    .vHMC5883L_direction_function = &vHMC5883L_direction_function
};

/*
功能:初始化
*/
void vHMC5883L_init(void)
{
    vMY_IIC_init(&HMC_IIC_DATA);    //i2c引脚初始化
    Hmc5883l_Data.ucHMC5883L_write(&HMC_IIC_DATA,HMC5883L_REGISTER_A,0x58); //输出速率75hz 平均采样4次
    Hmc5883l_Data.ucHMC5883L_write(&HMC_IIC_DATA,HMC5883L_REGISTER_B,0x20); //默认1090
    Hmc5883l_Data.ucHMC5883L_write(&HMC_IIC_DATA,HMC5883L_REGISTER_MODE,0x00); //连续测量模式
}
/*
功能:读取数据计算角度
返回值:角度
*/
float vHMC5883L_get_angle(void)
{
    uint8_t i;
    int16_t read_buff[6];

    for(i=0; i<6; i++)
    {
        //x--03 04 z--05 06 y--07 08
        read_buff[i] = ucHMC5883L_read(&HMC_IIC_DATA,i+3);
    }
    HMC_X = read_buff[0]<<8 | read_buff[1];//组合数据
    HMC_Z = read_buff[2]<<8 | read_buff[3];//组合数据
    HMC_Y = read_buff[4]<<8 | read_buff[5];//组合数据
    Hmc5883l_Data.Angle_value = atan2((double)HMC_Y,(double)HMC_X) * (180 / 3.14159265) + 180; // 计算角度

    return Hmc5883l_Data.Angle_value;
}

/*
功能:通过角度计算方向
参数:角度
*/
void vHMC5883L_calculate_direction(float angle_num)
{
    if(angle_num >= 22 && angle_num <= 67)  //东北
    {
        Hmc5883l_Data.Hmc588l_EN_Flag = 1;
    }
    else if(angle_num >= 68 && angle_num <= 111)    //东
    {
        Hmc5883l_Data.Hmc588l_E_Flag = 1;
    }
    else if(angle_num >= 112 && angle_num <= 157)   //东南
    {
        Hmc5883l_Data.Hmc588l_ES_Flag = 1;
    }
    else if(angle_num >= 158 && angle_num <= 201)   //南
    {
        Hmc5883l_Data.Hmc588l_S_Flag = 1;
    }
    else if(angle_num >= 202 && angle_num <= 247)   //西南
    {
        Hmc5883l_Data.Hmc588l_WS_Flag = 1;
    }
    else if(angle_num >= 248 && angle_num <= 291)   //西
    {
        Hmc5883l_Data.Hmc588l_W_Flag = 1;
    }
    else if(angle_num >= 292 && angle_num <= 337)   //西北
    {
        Hmc5883l_Data.Hmc588l_WN_Flag = 1;
    }
    else    //北
    {
        Hmc5883l_Data.Hmc588l_N_Flag = 1;
    }
}

/*
功能:根据方向执行功能
*/
void vHMC5883L_direction_function(void)
{
    char arr1[10] = {0};
    char arr2[30] = {0};
    if(Hmc5883l_Data.Hmc5883l_Over_Flag)
    {
        Hmc5883l_Data.vHMC5883L_get_angle();
        Hmc5883l_Data.vHMC5883L_calculate_direction(Hmc5883l_Data.Angle_value);

        if(Hmc5883l_Data.Hmc588l_EN_Flag)  //东北
        {
            sprintf(arr1,"东北   ");
            Hmc5883l_Data.Hmc588l_EN_Flag = 0;
        }
        if(Hmc5883l_Data.Hmc588l_E_Flag)    //东
        {
            sprintf(arr1,"东    ");
            Hmc5883l_Data.Hmc588l_E_Flag = 0;
        }
        if(Hmc5883l_Data.Hmc588l_ES_Flag)   //东南
        {
            sprintf(arr1,"东南    ");
            Hmc5883l_Data.Hmc588l_ES_Flag = 0;
        }
        if(Hmc5883l_Data.Hmc588l_S_Flag)    //南
        {
            sprintf(arr1,"南    ");
            Hmc5883l_Data.Hmc588l_S_Flag = 0;
        }
        if(Hmc5883l_Data.Hmc588l_WS_Flag)   //西南
        {
            sprintf(arr1,"西南    ");
            Hmc5883l_Data.Hmc588l_WS_Flag = 0;
        }
        if(Hmc5883l_Data.Hmc588l_W_Flag)    //西
        {
            sprintf(arr1,"西    ");
            Hmc5883l_Data.Hmc588l_W_Flag = 0;
        }
        if(Hmc5883l_Data.Hmc588l_WN_Flag)   //西北
        {
            sprintf(arr1,"西北    ");
            Hmc5883l_Data.Hmc588l_WN_Flag = 0;
        }
        if(Hmc5883l_Data.Hmc588l_N_Flag)    //北
        {
            sprintf(arr1,"北    ");
            Hmc5883l_Data.Hmc588l_N_Flag = 0;
        }
        sprintf(arr2,"角度:%.0f°方向:%s",Hmc5883l_Data.Angle_value,arr1);
        GuiRowText(0,0+300,400, FONT_LEFT,(uint8_t*)arr2);
        Hmc5883l_Data.Hmc5883l_Over_Flag = 0;
    }
}


/*
功能:HMC5883L读取寄存器的数据
参数1:结构体指针
参数2:寄存器地址
*/
uint8_t ucHMC5883L_read(MY_IIC_DATA_TypeDef *hmc,uint8_t register_addr)
{
    static uint8_t read_temp = 0;

    vMY_IIC_start(hmc); //开始信号
    vMY_IIC_send_byte(hmc,HMC5883L_WRITE_ADDR); //写操作
    ucMY_IIC_wait_ack(hmc); //等待应答
    vMY_IIC_send_byte(hmc,register_addr); //发送寄存器地址
    ucMY_IIC_wait_ack(hmc); //等待应答

    vMY_IIC_start(hmc); //开始信号
    vMY_IIC_send_byte(hmc,HMC5883L_READ_ADDR); //读操作
    ucMY_IIC_wait_ack(hmc); //等待应答
    read_temp = ucMY_IIC_read_byte(hmc);    //读取一个字节
    vMY_IIC_nack(hmc);  //发送NACK
    vMY_IIC_stop(hmc);  //停止信号

    return read_temp;
}
/*
功能:HMC5883L把数据写入寄存器
参数1:结构体指针
参数2:寄存器地址
参数3:要写入的数据
返回值:1---写入失败 0---写入成功
*/
uint8_t ucHMC5883L_write(MY_IIC_DATA_TypeDef *hmc,uint8_t register_addr,uint8_t data)
{
    vMY_IIC_start(hmc); //开始信号
    vMY_IIC_send_byte(hmc,HMC5883L_WRITE_ADDR); //写操作
    if(ucMY_IIC_wait_ack(hmc))  //等待ACK
    {
        vMY_IIC_stop(hmc);  //停止信号
        return 1;
    }
    vMY_IIC_send_byte(hmc,register_addr); //发送寄存器地址
    ucMY_IIC_wait_ack(hmc);
    vMY_IIC_send_byte(hmc,data); //发送数据
    if(ucMY_IIC_wait_ack(hmc))  //等待ACK
    {
        vMY_IIC_stop(hmc);  //停止信号
        return 1;
    }
    vMY_IIC_stop(hmc);  //停止信号

    return 0;
}

I2C

直接写成通用,注意不要把这个头文件丢 all.h ,不然报错因为重复包含了,直接哪个 .c用到这个 my_iic.h就哪个.h单独包含即可,而且这个 my_iic.h也不能包含 all.h,用到什么就包含什么

my_iic.h
#ifndef _MY_IIC_H
#define _MY_IIC_H
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "ls1b.h"
#include "mips.h"
#include "tick.h"
#include "ls1b_gpio.h"

//引脚定义
#define IIC_SCL(pin,state) gpio_write(pin,state) //时钟线
#define IIC_SDA(pin,state) gpio_write(pin,state) //数据线
#define SDA_READ(pin) gpio_read(pin)  //读取数据线电平
#define SDA_INPUT(pin) gpio_enable(pin,DIR_IN)  //设置数据线为输入模式
#define SDA_OUTPUT(pin) gpio_enable(pin,DIR_OUT)  //设置数据线为输出模式

//结构体
typedef struct
{
    uint8_t SCL_Pin;    //时钟线引脚编号
    uint8_t SDA_Pin;    //数据线引脚编号
}MY_IIC_DATA_TypeDef;


void vMY_IIC_init(MY_IIC_DATA_TypeDef *dev);
void vMY_IIC_delay(void);
void vMY_IIC_start(MY_IIC_DATA_TypeDef *dev);
void vMY_IIC_stop(MY_IIC_DATA_TypeDef *dev);
uint8_t ucMY_IIC_wait_ack(MY_IIC_DATA_TypeDef *dev);
void vMY_IIC_ack(MY_IIC_DATA_TypeDef *dev);
void vMY_IIC_nack(MY_IIC_DATA_TypeDef *dev);
void vMY_IIC_send_byte(MY_IIC_DATA_TypeDef *dev,uint8_t data);
uint8_t ucMY_IIC_read_byte(MY_IIC_DATA_TypeDef *dev);
#endif
my_iic.c
/*
 * my_iic.c
 *
 * created: 2023-03-27
 *  author:
 *  module: I2C通用驱动
 */
#include "my_iic.h"

/*
功能:IIC初始化
参数:指向 MY_IIC_DATA_TypeDef 类型的指针
*/
void vMY_IIC_init(MY_IIC_DATA_TypeDef *dev)
{
    gpio_enable(dev->SCL_Pin,DIR_OUT);    //时钟线输出模式
    gpio_enable(dev->SDA_Pin,DIR_OUT);    //数据线输出模式
    IIC_SCL(dev->SCL_Pin,1);    //拉高
    IIC_SDA(dev->SDA_Pin,1);    //拉高
}

/*
功能:延时
一般延时5us
*/
void vMY_IIC_delay(void)
{
    delay_us(5);
}

/*
功能:产生开始信号
参数:指向 MY_IIC_DATA_TypeDef 类型的指针
*/
void vMY_IIC_start(MY_IIC_DATA_TypeDef *dev)
{
    SDA_OUTPUT(dev->SDA_Pin);   //数据线输出模式
    IIC_SDA(dev->SDA_Pin,1);    //拉高
    vMY_IIC_delay();    
    IIC_SCL(dev->SCL_Pin,1);    //拉高
    vMY_IIC_delay();    
    IIC_SDA(dev->SDA_Pin,0);    //拉低
    vMY_IIC_delay();    
    IIC_SCL(dev->SDA_Pin,0);    //拉低
    vMY_IIC_delay();    
}

/*
功能:产生停止信号
参数:指向 MY_IIC_DATA_TypeDef 类型的指针
*/
void vMY_IIC_stop(MY_IIC_DATA_TypeDef *dev)
{
    SDA_OUTPUT(dev->SDA_Pin);   //数据线输出模式
    IIC_SCL(dev->SCL_Pin,0);    //拉低
    vMY_IIC_delay();
    IIC_SDA(dev->SDA_Pin,0);    //拉低
    vMY_IIC_delay();
    IIC_SCL(dev->SCL_Pin,1);    //拉高
    vMY_IIC_delay();
    IIC_SDA(dev->SDA_Pin,1);    //拉高
    vMY_IIC_delay();
}

/*
功能:等待应答
参数:指向 MY_IIC_DATA_TypeDef 类型的指针
返回值:1--接收应答失败 0--接收应答成功
*/
uint8_t ucMY_IIC_wait_ack(MY_IIC_DATA_TypeDef *dev)
{
    uint8_t ucErrorTime = 0;    //超时计数
    
    SDA_INPUT(dev->SDA_Pin);    //数据线输入模式
    IIC_SDA(dev->SDA_Pin,1);    //拉高
    vMY_IIC_delay();
    IIC_SCL(dev->SCL_Pin,1);    //拉高
    vMY_IIC_delay();
    
    while(SDA_READ(dev->SDA_Pin))
    {
        ucErrorTime++;
        if(ucErrorTime > 250)
        {
            vMY_IIC_stop(dev);
            return 1;
        }
    }
    IIC_SCL(dev->SCL_Pin,0);    //拉低
    return 0;
}

/*
功能:产生应答
参数:指向 MY_IIC_DATA_TypeDef 类型的指针
*/
void vMY_IIC_ack(MY_IIC_DATA_TypeDef *dev)
{
    IIC_SCL(dev->SCL_Pin,0);    //拉低
    vMY_IIC_delay();
    SDA_OUTPUT(dev->SDA_Pin);   //数据线输出模式
    IIC_SDA(dev->SDA_Pin,0);    //拉低
    vMY_IIC_delay();
    IIC_SCL(dev->SCL_Pin,1);    //拉高
    vMY_IIC_delay();
    IIC_SCL(dev->SCL_Pin,0);    //拉低
    vMY_IIC_delay();
}

/*
功能:不产生应答
参数:指向 MY_IIC_DATA_TypeDef 类型的指针
*/
void vMY_IIC_nack(MY_IIC_DATA_TypeDef *dev)
{
    IIC_SCL(dev->SCL_Pin,0);    //拉低
    vMY_IIC_delay();
    SDA_OUTPUT(dev->SDA_Pin);   //数据线输出模式
    IIC_SDA(dev->SDA_Pin,1);    //拉高
    vMY_IIC_delay();
    IIC_SCL(dev->SCL_Pin,1);    //拉高
    vMY_IIC_delay();
    IIC_SCL(dev->SCL_Pin,0);    //拉低
    vMY_IIC_delay();
}

/*
功能:发送一个字节
参数1:指向 MY_IIC_DATA_TypeDef 类型的指针
参数2:待发送的数据
*/
void vMY_IIC_send_byte(MY_IIC_DATA_TypeDef *dev,uint8_t data)
{
    uint8_t i = 8;
    
    SDA_OUTPUT(dev->SDA_Pin);   //数据线输出模式
    while(i--)
    {
        IIC_SCL(dev->SCL_Pin,0);    //拉低
        vMY_IIC_delay();
        IIC_SDA(dev->SDA_Pin,data&0x80);    //SDA输出data的最高位
        vMY_IIC_delay();
        data <<= 1;   //左移1位
        vMY_IIC_delay();
        IIC_SCL(dev->SCL_Pin,1);    //拉高
        vMY_IIC_delay();
    }
    IIC_SCL(dev->SCL_Pin,0);    //拉低
    vMY_IIC_delay();
}
/*
功能:读取一个字节
参数1:指向 MY_IIC_DATA_TypeDef 类型的指针
返回值:读取的数据(一个字节)
*/
uint8_t ucMY_IIC_read_byte(MY_IIC_DATA_TypeDef *dev)
{
    uint8_t i = 8;
    uint8_t read_temp = 0;
    
    SDA_INPUT(dev->SDA_Pin);    //数据线输入模式
    while(i--)
    {
        read_temp <<= 1;    //读入的数据左移1位(第一次的话 0<<1还是0)
        IIC_SCL(dev->SCL_Pin,0);    //拉低
        vMY_IIC_delay();
        IIC_SCL(dev->SCL_Pin,1);    //拉高
        vMY_IIC_delay();
        read_temp |= SDA_READ(dev->SDA_Pin);    //将读取的数据的值(0或1)返回给read_temp
    }
    IIC_SCL(dev->SCL_Pin,0);    //拉低
    vMY_IIC_delay();
    SDA_OUTPUT(dev->SDA_Pin);   //数据线输出模式
    return read_temp;
}

直流电机

硬件连接

GND:接地

VCC:接+5V,芯片内部供电

VM:驱动电机

IN1:通过输入IN1或相位信号,可以控制电机的旋转方向和速度

IN2:输入

nSLEEP:睡眠模式输入,当 nSLEEP 输入为逻辑低电平时,芯片将进入低功耗睡眠模式,从而减少功耗。当 nSLEEP 输入为逻辑高电平时,芯片将在正常工作模式下运行。芯片内部已经配置了一个下拉电阻,因此如果该输入未连接,则 nSLEEP 会默认为逻辑低电平,将芯片置于睡眠模式

OUT1/OUT2:这两个端口通常用于控制电机的正反转。通过施加不同的电压信号(输出相反的高低电平)到 OUT1 和 OUT2 端口,可以使电机旋转到不同的方向

因为电机是由GP7101 芯片驱动的,这个PWMB是取反信号,所以值越大电机速度越慢

程序编写

motor.h
#ifndef _MOTOR_H
#define _MOTOR_H
#include "all.h"

//引脚定义
#define MOTOR_PIN 53    //编码器的

typedef struct
{
    float Now_RPM;    //当前转速
    volatile uint32_t Motor_IRQ_count;   //(编码器)中断次数
    void (*vMOTOR_init)(void);
    void (*vMOTOR_control)(uint8_t);
    void (*vMOTOR_set_speed)(uint8_t);
    void (*vMOTOR_calculate_minute_speed)(void);
    
}MOTOR_TypeDef;

extern MOTOR_TypeDef Motor_Data;

void vMOTOR_init(void);
void vMOTOR_IRQhandler(int IRQn,void *param);
void vMOTOR_control(uint8_t swch);
void vMOTOR_set_speed(uint8_t speed);
void vMOTOR_calculate_minute_speed(void);
#endif


//PWM--每分钟转速
//0 ---370
//20	差
//5---350
//40
//10---310
//50
//15---260
//50
//20---210
//40
//25---170
//50
//30---120
//40
//35---80
//50
//40---30
//20
//42---10
//10
//43---0
motor.c
/*
 * motor.c
 *
 * created: 2023-03-27
 *  author: Yang
 *  module: 直流电机  硬件连接:P2插板子右上角P5  P5插板子右上角P2(由于驱动电机的PWM为GP7101的反向输出,所以PWM脉宽越高,电机速度越慢)
 */

#include "motor.h"


//结构体初始化
MOTOR_TypeDef Motor_Data =
{
    .Now_RPM = 0.0,
    .Motor_IRQ_count = 0,
    .vMOTOR_init = &vMOTOR_init,
    .vMOTOR_control = &vMOTOR_control,
    .vMOTOR_set_speed = &vMOTOR_set_speed,
    .vMOTOR_calculate_minute_speed = &vMOTOR_calculate_minute_speed
};

/*
功能:初始化
*/
void vMOTOR_init(void)
{
    ls1x_install_gpio_isr(MOTOR_PIN, INT_TRIG_EDGE_DOWN, vMOTOR_IRQhandler,NULL);  // 下降沿触发
    ls1x_enable_gpio_interrupt(MOTOR_PIN);  //使能中断
    set_lcd_brightness(100);  //默认关闭
}

/*
功能:控制直流电机开关
参数:SET--开 RESET--关
*/
void vMOTOR_control(uint8_t swch)
{
    if(SET == swch)
    {
        set_lcd_brightness(0);
    }
    else
    {
        set_lcd_brightness(100);
    }
}

/*
功能:直流电机速度
参数:(0~50)值越大越慢
*/
void vMOTOR_set_speed(uint8_t speed)
{
    set_lcd_brightness(speed);
}

/*
功能:计算每分钟转速
*/
void vMOTOR_calculate_minute_speed(void)
{
    Motor_Data.Now_RPM = (float)(Motor_Data.Motor_IRQ_count*60) /(7*30); //减速比30  编码器一圈7个脉冲
}


/*
功能:中断服务函数
*/
void vMOTOR_IRQhandler(int IRQn,void *param)
{
    Motor_Data.Motor_IRQ_count++;   //编码器计数
    return;
}

超声波

硬件连接

测距芯片CX20106

cx20106 是一款红外线检波接受的专用芯片,常用于电视机红外遥控接收器,考虑到红外遥控常用的载波频率 38kHz 与测距超声波频率 40kHz 较为接近,可以利用它作为超声波检测电路

使用 CX20106A 作为超声波接收处理的典型电路。(当 CX20106A 接收到 40KHz 的信号时,会在第 7 脚产生一个低电平下降脉冲,这个信号可以接到单片机的外部中断引脚作为中断信号输入)

TRIG:TRIG引脚是触发引脚的缩写,它是用于发出超声波信号的引脚

ECHO:ECHO引脚是回波引脚的缩写,它是用于接收超声波信号的引脚

注意程序里宏定义不要跟系统重复命名,ECHO 已经有宏定义了所以需要换成别的名字

程序编写

ultrasound.h
#ifndef _ULTRASOUND_H
#define _ULTRASOUND_H
#include "all.h"

//管脚定义
#define CX_TRIG 51  //发射端
#define CX_ECHO 50  //接收端(注意ECHO重名不能用)

typedef struct
{
    float Ultrasound_last_distance; //上一次距离
    bool Ultrasound_Over_Flag;  //超声波完成标志位
    float Ultrasound_distance;  //距离
    void (*vULTRASOUND_init)(void);
    float (*vULTRASOUND_get_distance_function)(void);
}ULTRASOUND_TypeDef;

extern ULTRASOUND_TypeDef Ultrasound_Data;

void vULTRASOUND_init(void);
void vULTRASOUND_IRQhandler(int IRQn,void *param);
float vULTRASOUND_get_distance_function(void);
#endif
ultrasound.c
/*
 * ultrasound.c
 *
 * created: 2023-03-28
 *  author: Yang
 *  module: 超声波(使用CX20106芯片进行测距)  硬件连接---P1插UART1
 */
#include "ultrasound.h"


extern pwm_cfg_t pwm2_cfg;

//初始化结构体
ULTRASOUND_TypeDef Ultrasound_Data =
{
    .Ultrasound_last_distance = 0,
    .Ultrasound_Over_Flag = 0,
    .Ultrasound_distance = 0.0,
    .vULTRASOUND_init = &vULTRASOUND_init,
    .vULTRASOUND_get_distance_function = &vULTRASOUND_get_distance_function
};



/*
功能:初始化
*/
void vULTRASOUND_init(void)
{
    gpio_enable(CX_TRIG,DIR_OUT);   //输出模式
    gpio_enable(CX_ECHO,DIR_IN);   //输入模式
    ls1x_install_gpio_isr(CX_ECHO,INT_TRIG_EDGE_DOWN,vULTRASOUND_IRQhandler,NULL);    //下降沿触发 gpio 中断
    ls1x_enable_gpio_interrupt(CX_ECHO);   //使能中断
    gpio_write(CX_TRIG,RESET);  //发送引脚默认设置为低电平
}

/*
功能:中断服务函数
*/
void vULTRASOUND_IRQhandler(int IRQn,void *param)
{
    My_pwm_Data.PWM2_Start_Flag = 0;    //关闭定时器计数
    Ultrasound_Data.Ultrasound_distance = My_pwm_Data.PWM2_Time_count*1.7/10/1.2;//(1.2为定时器补偿)/1.2
    My_pwm_Data.PWM2_Time_count = 0;    //定时器计数清0
}

/*
功能:超声波测距函数
不能在这直接LCD显示否则导致屏幕卡死
*/
float vULTRASOUND_get_distance_function(void)
{
    static float last_distance;
    uint8_t i=0;
    char arr[30] = {0};
    if(Ultrasound_Data.Ultrasound_Over_Flag)
    {
        for(i=0; i<4; i++)
        {
            gpio_write(CX_TRIG,SET);
            delay_us(12);
            gpio_write(CX_TRIG,RESET);
            delay_us(12);
        }
      //  ls1x_pwm_timer_start(devPWM2,(void *)&pwm2_cfg);    //打开定时器(会卡死)
        My_pwm_Data.PWM2_Start_Flag = 1;    //开启PWM2定时器计数
        delay_ms(100);

        if(Ultrasound_Data.Ultrasound_distance >= 2000)
        {
            Ultrasound_Data.Ultrasound_distance = 2000;
        }
        if(Ultrasound_Data.Ultrasound_distance <= 0)
        {
            Ultrasound_Data.Ultrasound_distance = Ultrasound_Data.Ultrasound_last_distance;
        }
        sprintf(arr, "距离:%.1fcm   ", Ultrasound_Data.Ultrasound_distance);
        GuiRowText(0,0+400,400, FONT_LEFT,arr);
        Ultrasound_Data.Ultrasound_last_distance = Ultrasound_Data.Ultrasound_distance;
        Ultrasound_Data.Ultrasound_Over_Flag = 0;
        return Ultrasound_Data.Ultrasound_distance;
    }
    return 0;
}


PWM

  • LS1B芯片集成了四路脉冲宽度调节/计数控制器(PWM),每路PWM工作和控制方式完全相同。每路控制器有四个寄存器,分别是: 主计数器(CNTR)高脉冲定时参考寄存器(HRC)低脉冲定时参考寄存器(LRC)控制寄存器(CTRL)
  • 使用时需要在 bsp.h 打开PWM宏定义才能用

程序编写

my_pwm.h
#ifndef _MY_PWM_H
#define _MY_PWM_H
#include "all.h"

typedef struct
{
    bool PWM2_Start_Flag;   //PWM中断开启标志位
    uint32_t PWM2_Time_count;  //中断里计数
    void (*vMY_PWM2_time_init)(void);
}MY_PWM_TypeDef;

extern MY_PWM_TypeDef My_pwm_Data;

void vMY_PWM2_time_init(void);
void pwmtimer_callback(void *pwm, int *stopit);
#endif
my_pwm.c
/*
 * my_pwm.c
 *
 * created: 2023-03-28
 *  author: Yang
 *  module: PWM
 */

#include "my_pwm.h"

//初始化结构体
MY_PWM_TypeDef My_pwm_Data =
{
    .PWM2_Start_Flag = 0,
    .PWM2_Time_count = 0,
    .vMY_PWM2_time_init = &vMY_PWM2_time_init
};

pwm_cfg_t pwm2_cfg;
/*
功能:PWM2当做定时器初始化(超声波需要用到)
*/
void vMY_PWM2_time_init(void)
{
    pwm2_cfg.hi_ns = 5000;  //高电平脉冲宽度(ns)定时器模式仅用hi_ns
    pwm2_cfg.lo_ns = NULL;  //低电平脉冲宽度(ns)定时器模式没用
    pwm2_cfg.mode = PWM_CONTINUE_TIMER; //脉冲持续产生
    pwm2_cfg.cb = pwmtimer_callback;    //定时器中断回调函数
    pwm2_cfg.isr = NULL;    //工作在定时器模式
    
    ls1x_pwm_init(devPWM2,NULL);    //PWM初始化
    ls1x_pwm_open(devPWM2,(void *)&pwm2_cfg);   //PWM打开
}

/*
功能:中断回调函数
*/
void pwmtimer_callback(void *pwm, int *stopit)
{
    if(My_pwm_Data.PWM2_Start_Flag)
    {
        My_pwm_Data.PWM2_Time_count++;
    }
}



串口通信

硬件连接

需要在bsp.h 打开宏定义 #define BSP_USE_UART5

程序编写

my_uart.h
#ifndef _MY_UART_H
#define _MY_UART_H
#include "all.h"

//接收的最大长度
#define UART5_RX_MAX_LEN 50

typedef struct
{
    bool UART5_Over_Flag;   //时间片运行标志位
    char Uart5_rx_buff[UART5_RX_MAX_LEN];   //接收数组
    uint8_t Uart5_rx_len;   //接收长度
    void (*vMY_UART5_init)(void);
    void (*vMY_UART5_receive_data)(void);
    void (*vMY_UART5_run_function)(void);
}MY_UART_TypeDef;

extern MY_UART_TypeDef My_uart_Data;


void vMY_UART5_init(void);
void vMY_UART5_receive_data(void);
void vMY_UART5_run_function(void);
#endif

my_uart.c
/*
 * my_uart.c
 *
 * created: 2023-03-28
 *  author: Yang
 *  module: 串口通信
 */
#include "my_uart.h"


//初始化结构体
MY_UART_TypeDef My_uart_Data =
{
    .UART5_Over_Flag = 0,
    .Uart5_rx_buff = {0},
    .Uart5_rx_len = 0,
    .vMY_UART5_init = &vMY_UART5_init,
    .vMY_UART5_receive_data = &vMY_UART5_receive_data,
    .vMY_UART5_run_function = &vMY_UART5_run_function
};

/*
功能:串口5初始化
*/
void vMY_UART5_init(void)
{
    uint32_t BaudRate = 115200; //波特率
    ls1x_uart_init(devUART5,(void *)BaudRate); //初始化串口
    ls1x_uart_open(devUART5,NULL); //打开串口
}

/*
功能:接收数据
*/
void vMY_UART5_receive_data(void)
{
    if(My_uart_Data.UART5_Over_Flag)
    {
        My_uart_Data.UART5_Over_Flag = 0;
        My_uart_Data.Uart5_rx_len = ls1x_uart_read(devUART5,My_uart_Data.Uart5_rx_buff,UART5_RX_MAX_LEN,NULL);
        if(My_uart_Data.Uart5_rx_len > 0)
        {
            ls1x_uart_write(devUART5,My_uart_Data.Uart5_rx_buff,My_uart_Data.Uart5_rx_len,NULL);  //发送数据
            //My_uart_Data.Uart5_rx_len = 0;
            My_uart_Data.vMY_UART5_run_function();
            memset(My_uart_Data.Uart5_rx_buff,0,sizeof(My_uart_Data.Uart5_rx_buff));    //清空
        }
    }
}

/*
功能:根据串口数据执行
*/
void vMY_UART5_run_function(void)
{
    if(strncmp(My_uart_Data.Uart5_rx_buff,"led_on",6) == 0) //比较前n个字节
    {
        Led_Data.vLED_control(Led_Data.Red_Led,SET);// 开启LED
    }
    if(strncmp(My_uart_Data.Uart5_rx_buff,"led_off",7) == 0) //比较前n个字节
    {
        Led_Data.vLED_control(Led_Data.Red_Led,RESET);// 开启LED
    }

}

RTC实时时钟

  • 需要在 bsp.h 打开宏定义 #define BSP_USE_RTC,包含头文件 #include "ls1x_rtc.h"

程序编写

my_rtc.h
#ifndef _MY_RTC_H
#define _MY_RTC_H
#include "all.h"

typedef struct
{
    bool MY_RTC_over_Flag; //RTC时间片标志位
    uint16_t RTC_now_year;    //年
    uint8_t RTC_now_mon;    //月
    uint8_t RTC_now_day;    //日
    uint8_t RTC_now_hour;    //时
    uint8_t RTC_now_min;    //分
    uint8_t RTC_now_sec;    //秒
    void (*vMY_RTC_init)(void);
    void (*vMY_RTC_get_time_function)(void);
}MY_RTC_TypeDef;

extern MY_RTC_TypeDef myRtcData;

void vMY_RTC_init(void);
void vMY_RTC_get_time_function(void);
#endif

my_rtc.c
/*
 * my_rtc.c
 *
 * created: 2023-03-28
 *  author: Yang
 *  module: RTC时间
 */
#include "my_rtc.h"

//初始化写入初始时间
struct tm tmp,now =
{
    .tm_sec = 20,
    .tm_min = 22,
    .tm_hour = 17,
    .tm_mday = 28,
    .tm_mon = 3,
    .tm_year = 2023
};

//初始化结构体
MY_RTC_TypeDef myRtcData =
{
    .MY_RTC_over_Flag = 0,
    .RTC_now_year = 0,
    .RTC_now_mon = 0,
    .RTC_now_day = 0,
    .RTC_now_hour = 0,
    .RTC_now_min = 0,
    .RTC_now_sec = 0,
    .vMY_RTC_init = &vMY_RTC_init,
    .vMY_RTC_get_time_function = &vMY_RTC_get_time_function
};

/*
功能:初始化
*/
void vMY_RTC_init(void)
{
    ls1x_rtc_init(NULL,NULL);
    ls1x_rtc_set_datetime(&now);    //设置RTC时间
}

/*
功能:获取当前时间
*/
void vMY_RTC_get_time_function(void)
{
    char arr1[50] = {0};
    char arr2[50] = {0};
    
    if(myRtcData.MY_RTC_over_Flag)
    {
        myRtcData.MY_RTC_over_Flag = 0;
        ls1x_rtc_get_datetime(&tmp);    //获取时间
        normalize_tm(&tmp,0);   //转换为实际时间---年份+1900,月份+1
        myRtcData.RTC_now_year = tmp.tm_year;
        myRtcData.RTC_now_mon = tmp.tm_mon;
        myRtcData.RTC_now_day = tmp.tm_mday;
        myRtcData.RTC_now_hour = tmp.tm_hour;
        myRtcData.RTC_now_min = tmp.tm_min;
        myRtcData.RTC_now_sec = tmp.tm_sec;

        snprintf(arr1,sizeof(arr1),"当前日期:%04d-%02d-%02d    ",myRtcData.RTC_now_year,myRtcData.RTC_now_mon,myRtcData.RTC_now_day);
        GuiRowText(0,0+500,400, FONT_LEFT,(uint8_t*)arr1);
        snprintf(arr2,sizeof(arr2),"当前时间:%02d-%02d-%02d    ",myRtcData.RTC_now_hour,myRtcData.RTC_now_min,myRtcData.RTC_now_sec);
        GuiRowText(0,0+600,400, FONT_LEFT,(uint8_t*)arr2);
    }
}

ADC

硬件连接

ADS1015挂接在 I2C0

注意:可能和温度传感器一起用会卡死,因为温度传感器是读取ADC2

程序编写

my_adc.h
#ifndef _MY_ADC_H
#define _MY_ADC_H
#include "all.h"

typedef struct
{
    bool Adc_Over_Flag; //ADC采集完成标志位
    float Adc1_value;   //ADC1
    float Adc2_value;   //ADC2
    float Adc3_value;   //ADC3
    float Adc4_value;   //ADC4
    void (*vMY_ADC_get_value)(void);
}MY_ADC_TypeDef;

extern MY_ADC_TypeDef myAdcData;

void vMY_ADC_get_value(void);
#endif


my_adc.c
/*
 * my_adc.c
 *
 * created: 2023-03-28
 *  author: Yang
 *  module: ADC (初始化已在LM35初始化了这里不需要再初始化) 会卡死如果同时用
 */
#include "my_adc.h"

//初始化结构体
MY_ADC_TypeDef myAdcData =
{
    .Adc_Over_Flag = 0,
    .Adc1_value = 0.0,
    .Adc2_value = 0.0,
    .Adc3_value = 0.0,
    .Adc4_value = 0.0,
    .vMY_ADC_get_value = &vMY_ADC_get_value
};


/*
功能:获取ADC值
*/
void vMY_ADC_get_value(void)
{
    uint16_t adc1,adc2,adc3,adc4;

    myAdcData.Adc_Over_Flag = 0;
    adc1 = get_ads1015_adc(busI2C0, ADS1015_REG_CONFIG_MUX_SINGLE_0);   //AIN0
    adc2 = get_ads1015_adc(busI2C0, ADS1015_REG_CONFIG_MUX_SINGLE_1);   //AIN1
    adc3 = get_ads1015_adc(busI2C0, ADS1015_REG_CONFIG_MUX_SINGLE_2);   //AIN2
    adc4 = get_ads1015_adc(busI2C0, ADS1015_REG_CONFIG_MUX_SINGLE_3);   //AIN3
    myAdcData.Adc1_value = 4.096*2*adc1/4096;//采集电压的转换公式
    myAdcData.Adc2_value = 4.096*2*adc2/4096;//采集电压的转换公式
    myAdcData.Adc3_value = 4.096*2*adc3/4096;//采集电压的转换公式
    myAdcData.Adc4_value = 4.096*2*adc4/4096;//采集电压的转换公式
}

LCD


//初始化并打开framebuffer驱动
int fb_open(void);

//设置屏幕输出使用的背景色
void fb_set_bgcolor(unsigned coloridx,unsigned value);

//设置屏幕输出使用的前景色
void fb_set_fgcolor(unsigned coloridx,unsigned value);

//在指定位置删除文本
void fb_textout(int x,int y,char *str);

//清屏函数
void fb_cons_clear(void);

//在指定坐标处用指定颜色显示字符串
void fb_put_string(int x,int y,char *str,unsigned coloridx);

//输出显示一个汉字
void fb_draw_gb2312_char(int x,int y,unsigned char *str);

//在指定坐标处画一个像素点
void fb_drawpixel(int x,int y,unsigned coloridx);

//在指定坐标处画一个点,支持两种大小
void fb_drawpoint(int x,int y,int thickness,unsigned coloridx);

//根据两个指定的坐标画直线
void fb_drawline(int x1,int y1,int x2,int y2,unsigned coloridx);

//根据指定的坐标画矩形框
void fb_drawrect(int x1,int y1,int x2,int y2,unsigned coloridx);

//根据指定的坐标填充矩形
void fb_fillrect(int x1,int y1,int x2,int y2,unsigned coloridx);

//在指定坐标处显示图片(图片需取模)
void display_pic(unsigned int cpos,unsigned int ypos,unsigned int x1,unsigned int y1,unsigned char *ptrs);
  • 取模软件:Image2LCD

  • 使用时需要在 bsp.h 打开FB宏定义才能用
lcd.c
#include "lcd.h"

#if 0

//LCD初始化
void LCD_init(void)
{
    //设置LCD显示模式
    char LCD_display_mode[] = LCD_480x800;
    //初始化并打开framebuffer驱动
    fb_open();
    //设置字符输出使用的背景色
    fb_set_bgcolor(cidxWHITE,clWHITE);
    //显示背景色
    fb_cons_clear();
    //设置字符输出使用的前景色
    fb_set_fgcolor(cidxBRTRED,clbRED);
    //在坐标(200,400)显示字符串
    fb_textout(200,400,buf);
}

//显示字符串
void LCD_Dis(void)
{
    unsigned char buf[] = "Hello World";
    //在坐标(150,400)显示字符串
    fb_textout(150,400,buf);
    //在坐标(150,200)处指定颜色打印字符串
    fb_put_string(150,200,buf,cidxGREEN);
    //在坐标(150,500)输出一个汉字
    fb_draw_gb2312_char(150,500,"龙");
}

//画图
void LCD_Draw(void)
{
    //在LCD[x,y]处指定颜色画像素
    fb_drawpixel(280,32,cidxBLUE);
    //在LCD[x,y]处指定颜色,宽度画点--支持两种宽度
    fb_drawpoint(300,32,1,cidxBLUE);
    fb_drawpoint(300,32,2,cidxBLUE);
    //在LCD[x1,y1]~[x2,y2]处指定颜色画线
    fb_drawline(36,64,300,64,cidxYELLOW);   //水平线
    fb_drawline(0,0,478,798,cidxYELLOW);    //对角线
    //在LCD[x1,y1]~[x2,y2]处指定颜色画矩形框
    fb_drawrect(0,0,478,798,cidxBRTCYAN);
    //在LCD[x1,y1]~[x2,y2]处指定颜色填充矩形框
    fb_fillrect(100,100,250,250,cidxBRTCYAN);
}

//显示图片
void LCD_Image(void)
{
    display_pic(45,50,400,400,gImage_2);
}

#endif
pwm.c
#include "pwm.h"

unsigned int period = 5000; //周期为5000ns
pwm_cfg_t cfg;

//PWM初始化
void PWM_init(void)
{
    //中断服务函数无
    cfg.isr = NULL;
    //回调函数无
    cfg.cb = NULL;
    //连续脉冲模式
    cfg.mode = PWM_CONTINUE_PULSE;
}

//PWM测试
void pwm_test(void)
{
    static unsigned char dir = 1;
    static unsigned char count = 1;
    
    if(dir)
    {
        count++;
    }
    else
    {
        count--;
    }
    printk("count=%d\n",count);
    cfg.hi_ns = period - count*100; //PWM高电平时间
    cfg.lo_ns = count*100;  //PWM低电平时间
    ls1x_pwm_pulse_start(devPWM2,&cfg); //启PWM
    delay_ms(20);
    ls1x_pwm_pulse_stop(devPWM2);   //停止PWM
    if(49 == dir)
    {
        dir = 1;
    }
    if(1 == dir)
    {
        dir = 49;
    }
}

DAC

//需要在 bsp.h 打开宏定义 #define MCP4725_DRV
//MCP4725 挂接在 I2C0 上,I2C 地址和通信速率定义如下
#define MCP4725_ADDRESS			0x62	    /* mcp4725 address 1100 010(7bits) */

#define MCP4725_BAUDRATE        1000000

//包含头文件
#include "ls1x_i2c_bus.h"
#include "i2c/mcp4725.h"	

//初始化MCP4725
ls1x_mcp4725_ioctl(busI2C0,IOCTL_MCP4725_DISP_CONFIG_REG,NULL);

set_mcp4725_dac(busI2C0,dac);
out_v = 3.3*dac/4096;	//输出电压公式

int set_mcp4725_dac(void *bus,unsigned short dacVal)
{
    //向MCP4725写 2字节数值进行DAC转换
    return MCP4725_write(bus,(void*)&dacVal,2,NULL);
}

adc = get_ads1015_adc(busI2C0,ADS1015_REG_CONFIG_MUX_SINGLE_3);
in_v = 4.096*2*adc/4096;

菜单实现

用途:可以管理每一个任务,可以是死循环的,而且可以随时退出死循环

key.c
uint8_t key1_short_menu_task_state = 0;	//当前任务号
uint8_t key1_long_enter_flag = 0;  //按键1长按确认键(进入当前选择的任务)
uint8_t key2_long_quit_flag = 0;  //按键2长按退出键(退出当前正在执行的任务回到菜单页)
uint8_t Menu_display_flag = 1;  //菜单显示(上电默认显示)


/*
功能:主菜单
*/

void vKEY_menu_display(void)
{
    char display_menu_arr[50] = {0};
    if(Menu_display_flag)
    {
        GuiRowText(0,0+10,480,FONT_LEFT,"短按:K1任务+   K2任务-");
        GuiRowText(0,0+50,480,FONT_LEFT,"长按:K1进入任务 K2退出任务");
        GuiRowText(0,0+200,480,FONT_LEFT,"==============================");
        GuiRowText(0+170,0+240,400,FONT_LEFT,"任务★菜单");
        snprintf(display_menu_arr,sizeof(display_menu_arr),"∴选择任务:%d ",key1_short_menu_task_state);
        GuiRowText(0+100,0+280,480,FONT_LEFT,display_menu_arr);
    }
}


//按键检测
void vKEY_detection(void)
{
    static uint8_t Key_Old;

    Key_Value = Key_Data.ucKEY_sub();
    Key_Up = ~Key_Value & (Key_Old^Key_Value);
    Key_Down = Key_Value & (Key_Old^Key_Value);
    Key_Old = Key_Value;

    if(Key_Down)
    {
        Key_longtime = 0;
    }

    if(Key_longtime < 10)
    {
        switch(Key_Up)
        {
            case 1: //按键1短按当做菜单加(任务1,2...递增,上电默认0)
                {
                    if(Menu_display_flag)   //按键只在菜单页面生效
                    {
                        Key_Data.Key1_Down_Flag = 1;
                        key1_short_menu_task_state+=1;    //任务++
                        if(key1_short_menu_task_state >= 20)	//任务最大为20
                        {
                            key1_short_menu_task_state = 20;
                        }
                    }
                    break;
                }
            case 2:  //按键2短按当做菜单减(任务1,2...递减,上电默认0)
                {
                    if(Menu_display_flag)   //按键只在菜单页面生效
                    {
                        Key_Data.Key2_Down_Flag = 1;
                        key1_short_menu_task_state-=1;    //任务--
                        if(key1_short_menu_task_state <= 1)	//任务最小为1
                        {
                            key1_short_menu_task_state = 1;
                        }
                    }
                    break;
                }
            case 3:
                {
                    Key_Data.Key3_Down_Flag = 1;
                    break;
                }
            case 4:
                {
                    Key_Data.Key4_Down_Flag = 1;
                    break;
                }
            default:
                break;
        }
    }
    else    //长按
    {
        switch(Key_Value)
        {
            case 1: //进入任务
                {
                    if(Menu_display_flag && (key1_short_menu_task_state!= 0))   //按键只在菜单页面生效且任务不为0
                    {
                        Key_Data.Key1_Down_Long_Flag = 1;
                        Menu_display_flag = 0;  //关闭菜单
                        key1_long_enter_flag = 1;   //长按标志位
                    }
                    break;
                }
            case 2: //退出任务
                {
                    if(!Menu_display_flag)   //按键只在非菜单页面生效
                    {
                        Key_Data.Key2_Down_Long_Flag = 1;
                        TaskData.Now_task_state = 0;    //任务结束
                        key2_long_quit_flag = 1;
                    }
                    break;
                }
            case 3:
                {
                    Key_Data.Key3_Down_Long_Flag = 1;
                    break;
                }
            case 4:
                {
                    Key_Data.Key4_Down_Long_Flag = 1;
                    break;
                }
            default:
                break;
        }
    }

}

//按键功能执行
void vKEY_function(void)
{
    static uint8_t key_num = 0;

    /*短按*/
    if(Key_Data.Key1_Down_Flag)
    {
        Key_Data.Key1_Down_Flag = 0;
    }
    if(Key_Data.Key2_Down_Flag)
    {
        Key_Data.Key2_Down_Flag = 0;
    }
    if(Key_Data.Key3_Down_Flag)
    {
        Key_Data.Key3_Down_Flag = 0;
    }
    if(Key_Data.Key4_Down_Flag)
    {
        Key_Data.Key4_Down_Flag = 0;
    }
    /*长按*/
    if(Key_Data.Key1_Down_Long_Flag)
    {
        Key_Data.Key1_Down_Long_Flag = 0;
        if(key1_long_enter_flag)
        {
            key1_long_enter_flag = 0;
            GuiClearScreen(CBLACK);  //清屏
            fb_cons_clear();    //清屏
            TaskData.Now_task_state = key1_short_menu_task_state;   //当前任务为选择的任务
        }
    }
    if(Key_Data.Key2_Down_Long_Flag)
    {
        Key_Data.Key2_Down_Long_Flag = 0;
        if(key2_long_quit_flag)
        {
            key2_long_quit_flag = 0;
            GuiClearScreen(CBLACK);  //清屏
            fb_cons_clear();    //清屏
            Menu_display_flag = 1;  //显示菜单
        }
    }
    if(Key_Data.Key3_Down_Long_Flag)
    {
        Key_Data.Key3_Down_Long_Flag = 0;
    }
    if(Key_Data.Key4_Down_Long_Flag)
    {
        Key_Data.Key4_Down_Long_Flag = 0;
    }
}
main.c
Key_Data.vKEY_menu_display();   //菜单
Key_Data.vKEY_function();
TaskData.vTASK_all();   //所有任务

使用示例:

if(3 == TaskData.Now_task_state)
{
    while(TaskData.Now_task_state != 0) //循环显示2023 0402
    {
        //这里死循环当Now_task_state等于0就退出
    }
}

待解决