强化学习 Reinforcement Learning(五)—— 使用 K20 单片机玩一个 Q 学习小游戏
写在前面
因为这个学期需要学习飞思卡尔的K20单片机,突发奇想看看能不能写个Q学习的小游戏让单片机自己玩一下。
本文用到的单片机:KinetisMKDN512
小游戏内容
上图中有 5 个房间,分别被标记成房间 0~4,房间外被标记成 5,现在智能体 Agent 被随机丢在 0-4 号 5 个房间中的任意 1 个,它的目标是寻找到离开房间的路。
若智能体可以成功走出房门,则奖励+100。
整理后用有向图的方式将含有奖励信息的环境画出,如上图所示。
该环境的奖励矩阵如下:
其中,值为“-1”表示不可移动到的位置,在代码中可以作为判断的依据。如当智能体位于房间 3 时,智能体无法直接到达房间 5,因为房间 3 里没有门可以直接出去。
程序功能
- 进行训练并将训练过程的信息显示到数码管上
- 训练完成后对模型进行测试,将测试时模型到达目标所用的步数显示到数码管上。
代码部分
主函数代码,包括初始化函数以及Q学习部分。对于训练的过程做了可视化处理,主要是将关键信息显示在数码管上。
/*
* @Author: ZiSeoi
* @Date: 2020-12-03 21:51:14
* @LastEditTime: 2020-12-03 22:09:25
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
*/
#include "stdlib.h"
#include "derivative.h" /* include peripheral declarations */
unsigned char num[10]={0xA0,0xBE,0x62,0x2A,0x3C,0x29,0x21,0xBA,0x20,0x28};//1~10
uint32_t Select_LED[6]={0xFFFFEFFF,0xFFFFDFFF,0xFFFFBFFF,0xFFFF7FFF,0xFFFEFFFF,0xFFFDFFFF};//片选
void init(void)//初始化
{
SIM_SCGC5|=0x200;
SIM_SCGC5|=0x1000;
SIM_SCGC5|=SIM_SCGC5_PORTB_MASK;
SIM_SCGC5|=SIM_SCGC5_PORTE_MASK;
//使能PORTA时钟,PORTB时钟,PORTD时钟和PORTE时钟
PORTA_PCR12=0x0100;
PORTA_PCR13=0x0100;
PORTA_PCR14=0x0100;
PORTA_PCR15=0x0100;
PORTA_PCR16=0x0100;
PORTA_PCR17=0x0100;
PORTD_PCR0=0x0100;
PORTD_PCR1=0x0100;
PORTD_PCR2=0x0100;
PORTD_PCR3=0x0100;
PORTD_PCR4=0x0100;
PORTD_PCR5=0x0100;
PORTD_PCR6=0x0100;
PORTD_PCR7=0x0100;
GPIOD_PDDR|=0x00FF; //PD全设置为输出
GPIOA_PDDR|=0x03F000; //PA全设置为输出
GPIOA_PDOR|=0x03F000; //PA全设置为1
GPIOD_PDOR=0xFF;
PORTB_PCR17=0x0A0103;
PORTB_PCR16=0x0A0103;
PORTB_PCR11=0x0A0103;
PORTB_PCR10=0x0A0103;
NVICICPR2=1<<(88%32);
NVICISER2=1<<(88%32); //开启PORTB中断
GPIOB_PDDR&=~0x30C00; //设置4个输入端口
PORTE_PCR2=0x0A0103;
PORTE_PCR3=0x0A0103;
PORTE_PCR4=0x0A0103;
PORTE_PCR5=0x0A0103;
NVICICPR2=1<<(91%32);
NVICISER2=1<<(91%32); //开启PORTE中断
GPIOE_PDDR&=~0x03C; //设置4个输入端口
}
//动作数
#define ACTIONS 6
//探索次数
#define episode 100
//目标状态,即:移动到 5 号房间。
#define target_state 5
//γ,折损率,取值是 0 到 1 之间。
#define gamma 0.8
int max(int* p, int m)
{
int i, max = *p;
for (i = 1; i < m; i++)
{
if (*(p + i) > max)
max = *(p + i);
}
return max;
}
int main()
{
while(1)
{
init();
// 经验矩阵。
int q[6][6] = { 0 };
int r[6][6] = {
{ -1, -1, -1, -1, 0, -1},
{-1, -1, -1, 0, -1, 100.0},
{ -1, -1, -1, 0, -1, -1},
{-1, 0, 0, -1, 0, -1},
{0, -1, -1, 0, -1, 100.0},
{ -1, 0, -1, -1, 0, 100.0 }
};
int start_room;
int current_state;
int current_action;
int current_action_point;
int next_state;
int step;
int next_state_max_q;
// 搜索次数
int i=0;
int j=0;
for (i = 0; i < episode; i++)
{
// Agent 的初始位置的状态。
start_room = rand() % 5;
// 当前状态。
current_state = start_room;
while (current_state != target_state)
{
//当前状态中的随机选取下一个可执行的动作。
current_action = rand() % 6;
// 执行该动作后的得分。
current_action_point = r[current_state][current_action];
if (current_action_point < 0)
{
q[current_state][current_action] = current_action_point;
}
else
{
// 得到下一个状态。
next_state = current_action;
// 获得下一个状态中,在自我经验中,也就是 Q 矩阵的最有价值的动作的经验得分。
next_state_max_q = max(&q[next_state][0],6);
// 当前动作的经验总得分 = 当前动作得分 + γ X 执行该动作后的下一个状态的最大的经验得分
// 即:积累经验 = 动作执行后的即时奖励 + 下一状态根据现有学习经验中最有价值的选择 X 折扣率
q[current_state][current_action] = current_action_point + gamma * next_state_max_q;
current_state = next_state;
}
for(j=1;j>0;j--)//多次显示防止低位数字显示过暗
{
GPIOA_PDOR|=0xFF000;//清除寄存器
GPIOD_PDOR=num[current_state];//显示字符
GPIOA_PDOR&=Select_LED[2];//片选
_delay();
GPIOA_PDOR|=0xFF000;//清除寄存器
GPIOD_PDOR=num[next_state];//显示字符
GPIOA_PDOR&=Select_LED[0];//片选
delay();
}
}
}
start_room = rand()%5;
current_state = start_room;
step = 0;
// 这里是进行测试,依据训练好的Q值,选择动作。
while (current_state != target_state)
{
// 用两个for循环实现。
int m = -100;
int i=0;
for (i = 0; i < 6; ++i)
{
if (m < q[current_state][i])
{//找最大值
m = q[current_state][i];
}
}
for (j = 0; j < 6; j++)
{
if (m == q[current_state][j])
next_state = j;
}
//Agent 由current_state号房间移动到了next_state号房间
current_state = next_state;
step += 1;
}
for(j=200000;j>0;j--)
{
/*step显示*/
GPIOA_PDOR|=0xFF000;//清除寄存器
GPIOD_PDOR=num[step/100];//显示字符
GPIOA_PDOR&=Select_LED[5];//片选
GPIOA_PDOR|=0xFF000;//清除寄存器
GPIOD_PDOR=num[(step-(step%100))/10];//显示字符
GPIOA_PDOR&=Select_LED[4];//片
GPIOA_PDOR|=0xFF000;//清除寄存器
GPIOD_PDOR=num[step%10];//显示字符
GPIOA_PDOR&=Select_LED[3];//片选
GPIOA_PDOR|=0xFF000;//清除寄存器
GPIOD_PDOR=num[start_room];//显示字符
GPIOA_PDOR&=Select_LED[1];//片选
}
}
return 0;
}
中断函数部分,包括中断向量表
/*
* kinetis_sysinit.c - Default init routines for P2
* Kinetis ARM systems
*/
#include "kinetis_sysinit.h"
#include "derivative.h"
/**
**===========================================================================
** External declarations
**===========================================================================
*/
#if __cplusplus
extern "C" {
#endif
extern uint32_t __vector_table[];
extern unsigned long _estack;
extern void __thumb_startup(void);
#if __cplusplus
}
#endif
/**
**===========================================================================
** Default interrupt handler
**===========================================================================
*/
void Default_Handler()
{
__asm("bkpt");
}
/**
**===========================================================================
** Reset handler
**===========================================================================
*/
void __init_hardware()
{
SCB_VTOR = (uint32_t)__vector_table; /* Set the interrupt vector table position */
/*
Disable the Watchdog because it may reset the core before entering main().
There are 2 unlock words which shall be provided in sequence before
accessing the control register.
*/
WDOG_UNLOCK = KINETIS_WDOG_UNLOCK_SEQ_1;
WDOG_UNLOCK = KINETIS_WDOG_UNLOCK_SEQ_2;
WDOG_STCTRLH = KINETIS_WDOG_DISABLED_CTRL;
}
/* Weak definitions of handlers point to Default_Handler if not implemented */
void NMI_Handler() __attribute__ ((weak, alias("Default_Handler")));
void HardFault_Handler() __attribute__ ((weak, alias("Default_Handler")));
void MemManage_Handler() __attribute__ ((weak, alias("Default_Handler")));
void BusFault_Handler() __attribute__ ((weak, alias("Default_Handler")));
void UsageFault_Handler() __attribute__ ((weak, alias("Default_Handler")));
void SVC_Handler() __attribute__ ((weak, alias("Default_Handler")));
void DebugMonitor_Handler() __attribute__ ((weak, alias("Default_Handler")));
void PendSV_Handler() __attribute__ ((weak, alias("Default_Handler")));
void SysTick_Handler() __attribute__ ((weak, alias("Default_Handler")));
int count=0;
int color=0;
void PORTB_Handler(void)
{
int temp_color;
temp_color=LED_change(color);
if(PORTB_ISFR&0x020000) //SW1
{
GPIOC_PDOR|=0x0FFF;
if(temp_color==3) GPIOC_PDOR&=~0x07;
else GPIOC_PDOR&=~(0x01<<temp_color);
GPIOC_PDOR|=0x248;
count=1;
}
else if(PORTB_ISFR&0x010000)//SW2
{
GPIOC_PDOR|=0x0FFF;
if(temp_color==3) GPIOC_PDOR&=~0x03F;
else GPIOC_PDOR&=~(0x09<<temp_color);
GPIOC_PDOR|=0x240;
count=2;
}
else if(PORTB_ISFR&0x0800)//SW3
{
GPIOC_PDOR|=0x0FFF;
if(temp_color==3) GPIOC_PDOR&=~0x1FF;
else GPIOC_PDOR&=~(0x049<<temp_color);
GPIOC_PDOR|=0x200;
count=3;
}
else if(PORTB_ISFR&0x0400)//SW4
{
GPIOC_PDOR|=0x0FFF;
if(temp_color==3) GPIOC_PDOR&=~0x0FFF;
else GPIOC_PDOR&=~(0x249<<temp_color);
count=4;
}
PORTB_PCR17|=0x01000000;
PORTB_PCR16|=0x01000000;
PORTB_PCR11|=0x01000000;
PORTB_PCR10|=0x01000000;
void PORTE_Handler(void)
{
int temp_GPIOC_PDOR;
temp_GPIOC_PDOR=LED_change(count);
if(PORTE_ISFR&0x4)
{
GPIOC_PDOR|=0x0FFF;
GPIOC_PDOR&=~temp_GPIOC_PDOR;
color=5;
}
else if(PORTE_ISFR&0x8)
{
GPIOC_PDOR|=0x0FFF;
GPIOC_PDOR&=~(temp_GPIOC_PDOR<<1);
color=6;
}
else if(PORTE_ISFR&0x10)
{
GPIOC_PDOR|=0x0FFF;
GPIOC_PDOR&=~(temp_GPIOC_PDOR<<2);
color=7;
}
else if(PORTE_ISFR&0x20)
{
GPIOC_PDOR|=0x0FFF;
GPIOC_PDOR&=~((temp_GPIOC_PDOR<<2)|(temp_GPIOC_PDOR<<1)|(temp_GPIOC_PDOR));
color=8;
}
PORTE_PCR2|=0x01000000;
PORTE_PCR3|=0x01000000;
PORTE_PCR4|=0x01000000;
PORTE_PCR5|=0x01000000;
}
/* The Interrupt Vector Table */
void (* const InterruptVector[])() __attribute__ ((section(".vectortable"))) = {
/* Processor exceptions */
(void(*)(void)) &_estack,
__thumb_startup,
NMI_Handler,
HardFault_Handler,
MemManage_Handler,
BusFault_Handler,
UsageFault_Handler,
0,
0,
0,
0,
SVC_Handler,
DebugMonitor_Handler,
0,
PendSV_Handler,
SysTick_Handler,
/* Interrupts */
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
PORTB_Handler,
Default_Handler,
Default_Handler,//90
PORTE_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
Default_Handler,
};
效果展示
训练时,数码管第四位是当前所处的房间号,第六位是下一次要到达的房间号。
训练完成后,测试结果打出,数码管前三位为测试所用的步数,第五位为初始房间号。
K20单片机Q学习小游戏演示